From 53e099e63b036f0b396d17db2515b78ba0464f00 Mon Sep 17 00:00:00 2001 From: Josiah Date: Mon, 19 Jul 2021 10:50:43 -0500 Subject: [PATCH 1/2] subclass FileIO Blendfile is essentially a wrapper on top of io.FileIO, and besides the high level commands it adds, it also needs to open and close the underlying stream properly. It is logical to subclass io.FileIO, and makes it very convenient to use the wrapper. I am not sure whether allowing users to read from the underlying object is a good idea, but it shouldn't effect the behavior of the high level API. --- blendparse.py | 63 ++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/blendparse.py b/blendparse.py index 95fdfcf..e9ad9af 100644 --- a/blendparse.py +++ b/blendparse.py @@ -3,44 +3,45 @@ import io # A class for opening and reading data from .blend files -class Blendfile(): +class Blendfile(io.FileIO): def __init__(self, filename): - self.filename = filename - with open(filename, "rb") as f: - self.stream = io.BufferedReader(f) - self.stream.seek(0) + super().__init__(filename, "rb") + self.read_header() - # File identifier, 8-byte string; should always be "BLENDER" - self.identifier = self.stream.read(7).decode("utf-8") + def read_header(self): + self.seek(0) - # Pointer size, 1-byte char; '-' indicates 8 bytes, '_' indicates 4 - self.pointer_size = 8 if self.stream.read(1).decode("utf-8") == "-" else 4 + # File identifier, 8-byte string; should always be "BLENDER" + self.identifier = self.read(7).decode("utf-8") - # Endianness, 1-byte char; 'v' indicates little endian, 'V' indicates big - self.endianness = "little" if self.stream.read(1).decode("utf-8") == "v" else "big" + # Pointer size, 1-byte char; '-' indicates 8 bytes, '_' indicates 4 + self.pointer_size = 8 if self.read(1).decode("utf-8") == "-" else 4 - # Blender version, 3-byte int; v2.93 is represented as 293, and so on - self.version = int(self.stream.read(3)) + # Endianness, 1-byte char; 'v' indicates little endian, 'V' indicates big + self.endianness = "little" if self.read(1).decode("utf-8") == "v" else "big" - # Looping through file block headers to find scene header - # TODO: Implement proper error handling if the target file block does not exist. - while True: + # Blender version, 3-byte int; v2.93 is represented as 293, and so on + self.version = int(self.read(3)) - # File block code; identifies type of data - code = self.stream.read(4).decode("utf-8") + # Looping through file block headers to find scene header + # TODO: Implement proper error handling if the target file block does not exist. + while True: - # Size of file block, after this header - size = int.from_bytes(self.stream.read(4), self.endianness) + # File block code; identifies type of data + code = self.read(4).decode("utf-8") - # Seeking to end of file block header - self.stream.seek(8+self.pointer_size+size, 1) + # Size of file block, after this header + size = int.from_bytes(self.read(4), self.endianness) - # Scene file block codes will always begin with "SC" - if code.startswith("SC"): - # TODO: Acquire SDNA index of scene data, to be found in DNA1 file block - # SDNA index occurs directly following file block size in header - # and is pointer_size bytes long - break - - # Seeking to the start of the next file block - self.stream.seek(8+self.pointer_size+size, 1) \ No newline at end of file + # Seeking to end of file block header + self.seek(8+self.pointer_size+size, 1) + + # Scene file block codes will always begin with "SC" + if code.startswith("SC"): + # TODO: Acquire SDNA index of scene data, to be found in DNA1 file block + # SDNA index occurs directly following file block size in header + # and is pointer_size bytes long + break + + # Seeking to the start of the next file block + self.seek(8+self.pointer_size+size, 1) \ No newline at end of file From 07fb196f306a23a522844476733c6901003231ce Mon Sep 17 00:00:00 2001 From: Josiah Date: Mon, 19 Jul 2021 11:30:08 -0500 Subject: [PATCH 2/2] implement block offset cache --- blendparse.py | 58 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/blendparse.py b/blendparse.py index e9ad9af..604bb73 100644 --- a/blendparse.py +++ b/blendparse.py @@ -6,7 +6,40 @@ import io class Blendfile(io.FileIO): def __init__(self, filename): super().__init__(filename, "rb") + # Offset of first file block header. + self._block_start = 12 self.read_header() + # Offsets of each file block by code. + self._block_offsets = self._read_block_offsets() + + # Looping through file block headers to find scene header + for block_code, offset in self._block_offsets.items(): + if block_code.startswith("SC"): + print(f"Found scene block at offset {offset}!") + + def _read_block_offsets(self): + """ + Cache the offset to each file block. + """ + + block_offsets = {} + self.seek(self._block_start) + while True: + # File block code; identifies type of data + block_code = self.read(4).decode("utf-8") + # Empty string indicates EOF. + if block_code == "": + break + block_offsets[block_code] = self.tell() + # Size of file block, after this header + block_size = int.from_bytes(self.read(4), self.endianness) + + # Skip rest of file header. + self.seek(self.pointer_size + 8, io.SEEK_CUR) + # Skip to next file block. + self.seek(block_size, io.SEEK_CUR) + + return block_offsets def read_header(self): self.seek(0) @@ -21,27 +54,4 @@ class Blendfile(io.FileIO): self.endianness = "little" if self.read(1).decode("utf-8") == "v" else "big" # Blender version, 3-byte int; v2.93 is represented as 293, and so on - self.version = int(self.read(3)) - - # Looping through file block headers to find scene header - # TODO: Implement proper error handling if the target file block does not exist. - while True: - - # File block code; identifies type of data - code = self.read(4).decode("utf-8") - - # Size of file block, after this header - size = int.from_bytes(self.read(4), self.endianness) - - # Seeking to end of file block header - self.seek(8+self.pointer_size+size, 1) - - # Scene file block codes will always begin with "SC" - if code.startswith("SC"): - # TODO: Acquire SDNA index of scene data, to be found in DNA1 file block - # SDNA index occurs directly following file block size in header - # and is pointer_size bytes long - break - - # Seeking to the start of the next file block - self.seek(8+self.pointer_size+size, 1) \ No newline at end of file + self.version = int(self.read(3)) \ No newline at end of file