2016-07-26 05:13:39 +10:00
|
|
|
#!/usr/bin/env python3
|
2023-08-16 00:20:26 +10:00
|
|
|
# SPDX-FileCopyrightText: 2010-2023 Blender Authors
|
2023-06-15 13:09:04 +10:00
|
|
|
#
|
2022-02-11 09:07:11 +11:00
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
2010-06-15 12:06:30 +00:00
|
|
|
|
|
|
|
|
# This module can get render info without running from inside blender.
|
2025-06-23 12:53:55 +02:00
|
|
|
|
2010-06-15 12:06:30 +00:00
|
|
|
|
2022-06-07 11:51:53 +10:00
|
|
|
__all__ = (
|
|
|
|
|
"read_blend_rend_chunk",
|
|
|
|
|
)
|
|
|
|
|
|
2025-06-27 23:41:07 +10:00
|
|
|
import _blendfile_header
|
2025-06-23 12:53:55 +02:00
|
|
|
|
2022-06-07 11:51:53 +10:00
|
|
|
|
|
|
|
|
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")
|
2010-07-05 22:22:22 +00:00
|
|
|
|
2022-06-07 11:51:53 +10:00
|
|
|
self._blendfile_base = blendfile_base
|
|
|
|
|
self._blendfile = blendfile
|
2010-06-15 12:06:30 +00:00
|
|
|
|
2022-06-07 11:51:53 +10:00
|
|
|
return self._blendfile
|
2010-06-15 12:06:30 +00:00
|
|
|
|
2024-12-02 11:29:05 +11:00
|
|
|
def __exit__(self, _exc_type, _exc_value, _exc_traceback):
|
2022-06-07 11:51:53 +10:00
|
|
|
self._blendfile.close()
|
|
|
|
|
if self._blendfile_base is not None:
|
|
|
|
|
self._blendfile_base.close()
|
2010-07-05 22:22:22 +00:00
|
|
|
|
2022-06-07 11:51:53 +10:00
|
|
|
return False
|
2010-06-15 12:06:30 +00:00
|
|
|
|
|
|
|
|
|
2025-06-23 12:53:55 +02:00
|
|
|
def get_render_info_structure(endian_str, size):
|
|
|
|
|
import struct
|
|
|
|
|
# The maximum size of the scene name changed over time, so create a different
|
|
|
|
|
# structure depending on the size of the entire block.
|
|
|
|
|
if size == 2 * 4 + 24:
|
|
|
|
|
return struct.Struct(endian_str + b'ii24s')
|
|
|
|
|
if size == 2 * 4 + 64:
|
|
|
|
|
return struct.Struct(endian_str + b'ii64s')
|
|
|
|
|
if size == 2 * 4 + 256:
|
|
|
|
|
return struct.Struct(endian_str + b'ii256s')
|
|
|
|
|
raise ValueError("Unknown REND chunk size: {:d}".format(size))
|
|
|
|
|
|
|
|
|
|
|
2022-06-07 11:51:53 +10:00
|
|
|
def _read_blend_rend_chunk_from_file(blendfile, filepath):
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
from os import SEEK_CUR
|
|
|
|
|
|
2025-06-23 12:53:55 +02:00
|
|
|
try:
|
2025-06-27 23:41:07 +10:00
|
|
|
blender_header = _blendfile_header.BlendFileHeader(blendfile)
|
|
|
|
|
except _blendfile_header.BlendHeaderError:
|
2024-04-27 16:06:51 +10:00
|
|
|
sys.stderr.write("Not a blend file: {:s}\n".format(filepath))
|
2010-06-15 12:06:30 +00:00
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
scenes = []
|
2010-07-05 22:22:22 +00:00
|
|
|
|
2025-06-23 12:53:55 +02:00
|
|
|
endian_str = b'<' if blender_header.is_little_endian else b'>'
|
2010-06-15 12:06:30 +00:00
|
|
|
|
2025-06-23 12:53:55 +02:00
|
|
|
block_header_struct = blender_header.create_block_header_struct()
|
2025-06-27 23:41:07 +10:00
|
|
|
while bhead := _blendfile_header.BlockHeader(blendfile, block_header_struct):
|
2025-06-23 12:53:55 +02:00
|
|
|
if bhead.code == b'ENDB':
|
2022-07-15 14:52:32 +10:00
|
|
|
break
|
2025-06-23 12:53:55 +02:00
|
|
|
remaining_bytes = bhead.size
|
|
|
|
|
if bhead.code == b'REND':
|
|
|
|
|
rend_block_struct = get_render_info_structure(endian_str, bhead.size)
|
|
|
|
|
start_frame, end_frame, scene_name = rend_block_struct.unpack(blendfile.read(rend_block_struct.size))
|
|
|
|
|
remaining_bytes -= rend_block_struct.size
|
2010-06-15 12:06:30 +00:00
|
|
|
|
2022-06-07 11:51:53 +10:00
|
|
|
scene_name = scene_name[:scene_name.index(b'\0')]
|
|
|
|
|
# It's possible old blend files are not UTF8 compliant, use `surrogateescape`.
|
2024-10-16 14:45:08 +11:00
|
|
|
scene_name = scene_name.decode("utf8", errors="surrogateescape")
|
2022-06-07 11:51:53 +10:00
|
|
|
scenes.append((start_frame, end_frame, scene_name))
|
2010-06-15 12:06:30 +00:00
|
|
|
|
2025-06-23 12:53:55 +02:00
|
|
|
blendfile.seek(remaining_bytes, SEEK_CUR)
|
2010-06-15 12:06:30 +00:00
|
|
|
|
2022-06-07 11:51:53 +10:00
|
|
|
return scenes
|
2010-06-15 12:06:30 +00:00
|
|
|
|
2012-08-22 10:29:30 +00:00
|
|
|
|
2022-06-07 11:51:53 +10:00
|
|
|
def read_blend_rend_chunk(filepath):
|
|
|
|
|
with RawBlendFileReader(filepath) as blendfile:
|
|
|
|
|
return _read_blend_rend_chunk_from_file(blendfile, filepath)
|
2010-06-15 12:06:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
import sys
|
2022-06-07 11:51:53 +10:00
|
|
|
|
|
|
|
|
for filepath in sys.argv[1:]:
|
|
|
|
|
for value in read_blend_rend_chunk(filepath):
|
2024-04-27 16:06:51 +10:00
|
|
|
print("{:d} {:d} {:s}".format(*value))
|
2010-06-15 12:06:30 +00:00
|
|
|
|
2018-07-03 06:27:53 +02:00
|
|
|
|
2010-06-15 12:06:30 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|