Skip to content

Refactor: move video segment encoding to dedicated module and detach SceneFileWriter from renderer state - #4976

Open
behackl wants to merge 37 commits into
ManimCommunity:mainfrom
behackl:refactor/encoder-writer-detachment
Open

Refactor: move video segment encoding to dedicated module and detach SceneFileWriter from renderer state#4976
behackl wants to merge 37 commits into
ManimCommunity:mainfrom
behackl:refactor/encoder-writer-detachment

Conversation

@behackl

@behackl behackl commented Aug 30, 2026

Copy link
Copy Markdown
Member

Summary

Stacked on #4969, actual diff: behackl/manim@refactor/media-layout-output-plan...refactor/encoder-writer-detachment

This resolves video-segment encoder settings once per render session, extracts PyAV segment encoding into a dedicated component, and removes SceneFileWriter's dependencies on renderer internals and mutable global configuration.

Each scene now has a frozen VideoEncoderSpec covering the segment container, codec, pixel format, geometry, exact frame rate, and codec options. VideoSegmentEncoder owns PyAV stream setup, segment-local timestamps, repeated frames, flushing, cleanup, and contextual errors. SceneFileWriter coordinates these encoders and assembles artifacts from its resolved output plan and writer settings.

Renderers now pass concrete top-left-origin, C-contiguous uint8 RGBA arrays across the writer boundary. In particular, OpenGL performs GPU readback in the renderer, and only when file output needs a frame.

Encoder configuration

With codec and pixel format set to auto, Manim retains its current libx264, qtrle, and libvpx-vp9 choices for MP4, MOV, WebM, and GIF output. They can now be overridden with:

  • --video-codec CODEC;
  • --pixel-format FORMAT; and
  • repeatable --encoder-option KEY=VALUE arguments.

The equivalent configuration is:

[video_encoder]
codec = libx264
pixel_format = yuv420p

[video_encoder.options]
crf = 18
preset = slow

If any --encoder-option is supplied, the CLI option map replaces the complete [video_encoder.options] map. Invalid containers, codecs, pixel formats, alpha combinations, geometry, frame rates, and conflicting stream options fail during session resolution rather than partway through rendering.

The old [ffmpeg] loglevel / config.ffmpeg_loglevel setting is replaced by [media] loglevel / config.media_loglevel, reflecting that media operations are handled through PyAV rather than an ffmpeg subprocess.

Cache and assembly changes

  • Visual cache keys are now one SHA-256 digest covering the renderer backend, resolved encoder profile, camera, animations, mobjects, and renderer-specific raster state. Changing any byte-affecting encoder setting invalidates the corresponding segment.
  • Existing segment filenames do not match the new cache identity and will incur a one-time cache miss.
  • Render-time --flush_cache and config.flush_cache are removed. Use manim cache clear FILE SCENE [SCENE ...] to clear concrete scene caches without starting a render.
  • Cache maintenance only removes recognized segment files, preserves diagnostic and unrelated files, and treats max_files_cached = -1 as unlimited.
  • Final and section assembly consume private in-memory concat manifests. partial_movie_file_list.txt remains as an atomically written diagnostic snapshot of the complete main-scene segment order and is never used as live assembly input.
  • Section metadata retains the original display name, while section filenames include a safe Unicode-preserving slug and scene-local ordinal.

Renderer and file-writer extension APIs

Custom renderers and writer implementations need updates:

  • renderer.init_scene(scene, session_spec) now also receives the resolved writer settings.
  • Injected SceneFileWriter implementations are constructed from that settings object instead of receiving a renderer, scene name, and separate output arguments.
  • SceneFileWriter.write_frame(...) now receives a concrete RGBA array and repeat= count; ownership of the array transfers to the writer because encoding may consume it asynchronously.
  • Segment opening now receives the scene-local animation index explicitly.

The writer-settings aggregate is intentionally private. This PR creates a reviewable dependency seam; it does not establish renderer-owned writer construction as a permanent public API.

Out of scope / follow-up work

This does not yet move writer ownership, play ordinals, clocks, cache decisions, or segment lifecycle into Manager. Renderers still schedule writer calls temporarily. It also does not decompose audio and final artifact assembly, redesign recursive scene-state serialization, or add incremental mobject mutation tracking. Those remain follow-up work for the Manager-owned execution and renderer rework.

behackl and others added 30 commits August 26, 2026 11:31
Co-authored-by: nikolajmunk <28557236+nikolajmunk@users.noreply.github.com>
…out-output-plan

# Conflicts:
#	docs/source/guides/deep_dive.rst
#	tests/test_scene_rendering/opengl/test_cli_flags_opengl.py
#	tests/test_scene_rendering/test_file_writer.py
@behackl behackl added refactor Refactor or redesign of existing code breaking changes This PR introduces breaking changes labels Aug 30, 2026
def _abort_encoder(self) -> None:
try:
self.encoder.abort()
except BaseException as exception:
try:
for packet in self._stream.encode():
self._container.mux(packet)
except BaseException as error:
first_error = error
try:
self._container.close()
except BaseException as error:
self._closed = True
try:
self._container.close()
except BaseException as error:
first_error = error
try:
self.target.unlink(missing_ok=True)
except BaseException as error:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking changes This PR introduces breaking changes refactor Refactor or redesign of existing code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants