"""Encodes a PNG frame sequence (Godot movie writer output) into an H.264 MP4 with Blender's bundled FFmpeg, so no separate FFmpeg installation is needed. Run: blender -b --python-exit-code 1 --python tools/luneraria/encode_video.py -- [fps] [scale] scale (default 1.0) shrinks the output, e.g. 0.5 for 960x540. """ import sys from pathlib import Path import bpy args = sys.argv[sys.argv.index('--') + 1:] frames_dir, out = Path(args[0]), Path(args[1]) fps = int(args[2]) if len(args) > 2 else 30 scale = float(args[3]) if len(args) > 3 else 1.0 files = sorted(p for p in frames_dir.iterdir() if p.suffix.lower() == '.png') if not files: raise SystemExit('ENCODE_FAILED: no frames in %s' % frames_dir) bpy.ops.wm.read_factory_settings(use_empty=True) scene = bpy.context.scene scene.sequence_editor_create() strips = scene.sequence_editor.strips if hasattr(scene.sequence_editor, 'strips') else scene.sequence_editor.sequences strip = strips.new_image(name='frames', filepath=str(files[0]), channel=1, frame_start=1) for f in files[1:]: strip.elements.append(f.name) image = bpy.data.images.load(str(files[0])) width, height = image.size scene.render.resolution_x = int(width * scale) // 2 * 2 scene.render.resolution_y = int(height * scale) // 2 * 2 scene.render.resolution_percentage = 100 scene.render.fps = fps scene.frame_start = 1 scene.frame_end = len(files) scene.render.image_settings.media_type = 'VIDEO' if hasattr(scene.render.image_settings, 'media_type') else scene.render.image_settings.file_format scene.render.image_settings.file_format = 'FFMPEG' scene.render.ffmpeg.format = 'MPEG4' scene.render.ffmpeg.codec = 'H264' scene.render.ffmpeg.constant_rate_factor = 'MEDIUM' scene.render.ffmpeg.ffmpeg_preset = 'GOOD' scene.render.ffmpeg.audio_codec = 'NONE' scene.render.use_sequencer = True scene.render.filepath = str(out) out.parent.mkdir(parents=True, exist_ok=True) bpy.ops.render.render(animation=True) print('ENCODE_DONE', out, len(files), 'frames', '%dx%d' % (scene.render.resolution_x, scene.render.resolution_y), '%d fps' % fps, flush=True)