neu_glance.movie.rendering

Render a neu_glance.movie.animate.Timeline to a sequence of PNGs, through a real viewer.

Each frame is a round trip: Python pushes a state, the browser loads the visible chunks, draws, and posts the image back. neu_glance.movie.animate decides what each frame is.

By default a headless chrome or chromium does the drawing, started with subprocess.Popen (no selenium: the viewer’s own channel carries every state change and screenshot). With GPU_FLAGS it is also fast: measured on one 1920x1080 mesh scene, 0.21 s/frame, against 2.17 s/frame without them and 0.57 s/frame through a browser on another machine. record(..., browser="none") prints a URL and waits instead.

neuroglancer is imported only inside functions (a test checks), so import neu_glance does not need it. Beyond neuroglancer.tool.screenshot, this:

  • requeues a wrong-sized reply instead of writing it;

  • writes frames atomically, and on resume checks each file’s PNG signature;

  • gives up after a bounded number of browser refreshes instead of hanging.

Attributes

Exceptions

RenderProblem

A render could not start, or could not finish the frame it was on.

Functions

ffmpeg_command(out_dir, fps, *[, output, crf, pattern]) → str

The command that turns the frames into an mp4, quoted for a shell. Printed, never run.

find_browser([binary]) → str | None

A chrome/chromium executable, or None. binary overrides the search.

frame_path(out_dir, index[, pattern]) → str

<out_dir>/frame_00042.png. pattern must be usable verbatim by ffmpeg -i.

headless_browser(url, *[, size, binary, gpu, extra_args])

Run a browser on url for the duration of the block, with no window and no tab.

is_written(path) → bool

True only for a file that exists and begins with the PNG signature, so a frame

open_viewer(*[, bind, port])

A viewer for a browser to attach to. Returns it; its .get_viewer_url() is the link.

pending_frames(timeline, out_dir, *[, resume, ...]) → list[tuple[int, float, str]]

(index, seconds, path) for the frames still to render. States are built later.

print_encode_hint(out_dir, fps, *[, pattern, file]) → None

Print the encode command, stating plainly that nothing has been encoded yet.

record(timeline, out_dir, *[, size, viewer, bind, ...]) → list[str]

Render timeline into out_dir. Returns the frame paths written this run.

render_state(state, *[, show_axis_lines, ...]) → dict

A state with the viewer's furniture switched off, ready to be a frame.

stale_frames(out_dir, frame_count[, pattern]) → list[str]

Frame files numbered at or past frame_count — leftovers from a longer earlier run.

wait_for_browser(viewer, state, *[, timeout, ...]) → None

Push state, then block until a browser has it open and loaded.

Module Contents

exception neu_glance.movie.rendering.RenderProblem

Bases: RuntimeError

A render could not start, or could not finish the frame it was on.

Initialize self. See help(type(self)) for accurate signature.

neu_glance.movie.rendering.ffmpeg_command(out_dir: str, fps: float, *, output: str | None = None, crf: int = DEFAULT_CRF, pattern: str = 'frame_%05d.png') → str

The command that turns the frames into an mp4, quoted for a shell. Printed, never run.

Not run, because ffmpeg is often not on PATH (on a cluster it is usually a module), and because re-encoding should never mean re-rendering. Keep -pix_fmt yuv420p: without it the video encodes but does not play in QuickTime, PowerPoint or Slack.

neu_glance.movie.rendering.find_browser(binary: str | None = None) → str | None

A chrome/chromium executable, or None. binary overrides the search.

neu_glance.movie.rendering.frame_path(out_dir: str, index: int, pattern: str = 'frame_%05d.png') → str

<out_dir>/frame_00042.png. pattern must be usable verbatim by ffmpeg -i.

neu_glance.movie.rendering.headless_browser(url: str, *, size: tuple[int, int] = (1920, 1080), binary: str | None = None, gpu: bool = True, extra_args: Sequence[str] = ())

Run a browser on url for the duration of the block, with no window and no tab.

No selenium is needed: the browser only has to load the URL, and the viewer’s channel does the rest. Yields a function returning None while the browser runs, or why it stopped.

The window is sized to the render, or every screenshot comes back the wrong size. The profile is a temporary directory, removed afterwards; sharing one would make a second render attach to the first browser.

neu_glance.movie.rendering.is_written(path: str) → bool

True only for a file that exists and begins with the PNG signature, so a frame killed mid-write is rendered again.

neu_glance.movie.rendering.open_viewer(*, bind: str | None = None, port: int = 0)

A viewer for a browser to attach to. Returns it; its .get_viewer_url() is the link.

bind and port take effect only before the first viewer in the process, since neuroglancer reads them at server start. bind="0.0.0.0" puts the host’s name in the URL, for a browser on another machine.

neu_glance.movie.rendering.pending_frames(timeline: neu_glance.movie.animate.Timeline, out_dir: str, *, resume: bool = True, start_frame: int = 0, end_frame: int | None = None, pattern: str = 'frame_%05d.png') → list[tuple[int, float, str]]

(index, seconds, path) for the frames still to render. States are built later.

States are built one per frame later, rather than held for the whole timeline.

neu_glance.movie.rendering.print_encode_hint(out_dir: str, fps: float, *, pattern: str = 'frame_%05d.png', file=None) → None

Print the encode command, stating plainly that nothing has been encoded yet.

neu_glance.movie.rendering.record(timeline: neu_glance.movie.animate.Timeline, out_dir: str, *, size: tuple[int, int] = (1920, 1080), viewer=None, bind: str | None = None, port: int = 0, prefetch: int = 1, refresh_timeout: float = 120.0, give_up_after: int = 5, connect_timeout: float = 600.0, size_attempts: int = 5, resume: bool = True, start_frame: int = 0, end_frame: int | None = None, pattern: str = 'frame_%05d.png', shape: bool = True, verbose: bool = False, progress_interval: float = 10.0, browser: str = 'auto', browser_binary: str | None = None, gpu: bool = True) → list[str]

Render timeline into out_dir. Returns the frame paths written this run.

browser="auto" runs a headless chrome or chromium if one is on the PATH, else prints a URL to open; "headless" requires one; "none" always waits for you to open the URL. verbose=True prints the capture loop’s own lines; otherwise a progress bar is shown, and the last lines are printed only if the render fails.

Writes timeline.json beside the frames. Interrupting is safe and re-running resumes, unless the timeline changed, in which case every frame is rendered again. Frames past the end of the timeline, left by a longer earlier run, are deleted. start_frame / end_frame render a slice with the same numbering.

neu_glance.movie.rendering.render_state(state: Mapping[str, Any], *, show_axis_lines: bool = False, show_default_annotations: bool = False, show_scale_bar: bool | None = None, gpu_memory_limit: int | None = None, system_memory_limit: int | None = None, concurrent_downloads: int | None = None) → dict

A state with the viewer’s furniture switched off, ready to be a frame.

Apply it once to the base state, before interpolation. Axis lines and default annotations (bounding boxes) default to off, unlike in the viewer. The memory limits and concurrent downloads are left at the viewer’s defaults unless given; they matter for scenes with many mesh layers.

neu_glance.movie.rendering.stale_frames(out_dir: str, frame_count: int, pattern: str = 'frame_%05d.png') → list[str]

Frame files numbered at or past frame_count — leftovers from a longer earlier run.

ffmpeg -i frame_%05d.png reads the whole numbered sequence, so after shortening an animation these would be appended to the video.

neu_glance.movie.rendering.wait_for_browser(viewer, state: Mapping[str, Any], *, timeout: float = 600.0, notify_every: float = 15.0, stall_timeout: float = 180.0, launched: bool = False, watch=None) → None

Push state, then block until a browser has it open and loaded.

launched=True when the browser was started here, which changes the messages (no “open this link”) and the diagnosis if it never attaches.

Two waits: timeout for a browser to attach, then for the scene to load, failing if no progress is made for stall_timeout. A viewer with no layers and no dimensions never reports ready, so it cannot answer a screenshot; attachment is therefore tested with state’s dimensions and no layers, which is ready almost at once. The viewer config (such as its size) is not touched, since a small viewerSize is shown magnified in the user’s window.

neu_glance.movie.rendering.BROWSERS = ('google-chrome', 'google-chrome-stable', 'chromium-browser', 'chromium')
neu_glance.movie.rendering.CHROME_OFF
neu_glance.movie.rendering.GPU_FLAGS = ('--use-angle=vulkan', '--enable-features=Vulkan', '--enable-gpu', '--ignore-gpu-blocklist')
neu_glance.movie.rendering.HEADLESS_FLAGS = ('--headless=new', '--no-sandbox', '--disable-dev-shm-usage', '--disable-extensions', '--mute-audio')
neu_glance.movie.rendering.PNG_MAGIC = b'\x89PNG\r\n\x1a\n'