#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later # This module can get render info without running from inside blender. # # This struct won't change according to Ton. # Note that the size differs on 32/64bit # # typedef struct BHead { # int code, len; # void *old; # int SDNAnr, nr; # } BHead; __all__ = ( "read_blend_rend_chunk", ) class RawBlendFileReader: """ Return a file handle to the raw blend file data (abstracting compressed formats). """ __slots__ = ( # The path to load. "_filepath", # The file base file handler or None (only set for compressed formats). "_blendfile_base", # The file handler to return to the caller (always uncompressed data). "_blendfile", ) def __init__(self, filepath): self._filepath = filepath self._blendfile_base = None self._blendfile = None def __enter__(self): blendfile = open(self._filepath, "rb") blendfile_base = None head = blendfile.read(4) blendfile.seek(0) if head[0:2] == b'\x1f\x8b': # GZIP magic. import gzip blendfile_base = blendfile blendfile = gzip.open(blendfile, "rb") elif head[0:4] == b'\x28\xb5\x2f\xfd': # Z-standard magic. import zstandard blendfile_base = blendfile blendfile = zstandard.open(blendfile, "rb") self._blendfile_base = blendfile_base self._blendfile = blendfile return self._blendfile def __exit__(self, exc_type, exc_value, exc_traceback): self._blendfile.close() if self._blendfile_base is not None: self._blendfile_base.close() return False def _read_blend_rend_chunk_from_file(blendfile, filepath): import struct import sys from os import SEEK_CUR head = blendfile.read(7) if head != b'BLENDER': sys.stderr.write("Not a blend file: %s\n" % filepath) return [] is_64_bit = (blendfile.read(1) == b'-') # true for PPC, false for X86 is_big_endian = (blendfile.read(1) == b'V') # Now read the bhead chunk! blendfile.seek(3, SEEK_CUR) # Skip the version. scenes = [] sizeof_bhead = 24 if is_64_bit else 20 # Should always be 4, but a malformed/corrupt file may be less. while (bhead_id := blendfile.read(4)) != b'ENDB': if len(bhead_id) != 4: sys.stderr.write("Unable to read until ENDB block (corrupt file): %s\n" % filepath) break sizeof_data_left = struct.unpack('>i' if is_big_endian else '2i' if is_big_endian else '<2i', blendfile.read(8)) sizeof_data_left -= 8 scene_name = blendfile.read(64) sizeof_data_left -= 64 scene_name = scene_name[:scene_name.index(b'\0')] # It's possible old blend files are not UTF8 compliant, use `surrogateescape`. scene_name = scene_name.decode("utf8", errors='surrogateescape') scenes.append((start_frame, end_frame, scene_name)) if sizeof_data_left > 0: blendfile.seek(sizeof_data_left, SEEK_CUR) elif sizeof_data_left < 0: # Very unlikely, but prevent attempting to further parse corrupt data. sys.stderr.write("Error calculating next block (corrupt file): %s\n" % filepath) break return scenes def read_blend_rend_chunk(filepath): with RawBlendFileReader(filepath) as blendfile: return _read_blend_rend_chunk_from_file(blendfile, filepath) def main(): import sys for filepath in sys.argv[1:]: for value in read_blend_rend_chunk(filepath): print("%d %d %s" % value) if __name__ == '__main__': main()