muvid.montage.render¶
Render a Plan to a YouTube-spec mp4 with ffmpeg.
Bounded stages, the same shape as muvid.footage.assemble: **one ffmpeg per
part** (a slot’s solo span, or the blend window between two slots), a
stream-copy concat of the parts, and one mux of the song. Memory is
O(1) in the number of cuts, and a 300-slot montage never builds a 300-input
filtergraph — a chained xfade of that depth is the classic way to make
ffmpeg crawl.
Every part is frame-exact. Slot boundaries are rounded to frames ONCE
(frame_layout()), a transition of n frames straddles its boundary
(n//2 before, the rest after — centred on the beat, the NLE convention),
and the solo parts are shortened by exactly what the blends take, so the parts
sum to round(duration * fps) by construction rather than by luck.
A still is turned into motion by zoompan=d=N over ONE decoded frame — the
image is decoded once per part, not once per output frame — with the window
path compiled to expressions in on (the output frame counter) plus a frame
offset. That offset is what lets a blend part sample the SAME Ken Burns path
the solo part was on: the motion never restarts on a crossfade. Stills are
pre-fitted to OVERSAMPLE times the tile so zoompan always
downsamples, which is what avoids its well-known sub-pixel jitter.
muvid.montage does not use looks.compile_motion for this, although
looks is the house motion compiler: its fragment keys on in_time and
is built for video sources at d=1; over one still frame expanded by
d=N the input time never advances. The expression builder here is the
on-keyed twin of it, and it is a pure function with a doctest.
All ffmpeg goes through muvid.visualize.ffmpeg; the encode arguments
are the visualizer’s own so a montage is the same H.264/AAC/no-edit-list file
the rest of muvid ships.
Functions
|
Slot boundaries in frames, and the transition length into each slot. |
|
The ffmpeg fragment for a grade, or |
|
Render |
|
The |
Classes
|
Output geometry. |
|
One ffmpeg invocation's worth of frames. |
- class muvid.montage.render.Canvas(*, width=1920, height=1080, fps=30)[source]¶
Bases:
objectOutput geometry.
- class muvid.montage.render.Part(*, kind, slot, offset, n_frames, prev=None, prev_offset=0, curve='fade')[source]¶
Bases:
objectOne ffmpeg invocation’s worth of frames.
offsetis the first frame of this part relative toslot’s start (negative on the incoming side of a blend). Ablendalso names the outgoingprevslot and its offset.
- muvid.montage.render.frame_layout(starts, duration, transitions_s, fps)[source]¶
Slot boundaries in frames, and the transition length into each slot.
starts[i]is slot i’s start;transitions_s[i]the crossfade INTO slot i (0 for a cut; slot 0’s is always 0). A transition is clamped to half the shorter neighbour and dropped belowMIN_TRANSITION_FRAMES.>>> frame_layout([0.0, 1.0, 2.0], 3.0, [0.0, 0.5, 0.05], 24) ([0, 24, 48, 72], [0, 12, 0]) >>> frame_layout([0.0, 0.1], 0.2, [0.0, 0.1], 24) # 2-frame slots: no room ([0, 2, 5], [0, 0])
- muvid.montage.render.grade_filter(grade, accent='#e0533d', *, strength=0.35)[source]¶
The ffmpeg fragment for a grade, or
""for none.tintmixesstrengthofluma * accentinto the frame — a closed form over the accent colour, never a caller-supplied filter string.- Return type:
>>> grade_filter('none') '' >>> grade_filter('mono') 'hue=s=0' >>> grade_filter('tint', '#ff0000', strength=0.5) 'colorchannelmixer=rr=0.6495:rg=0.2935:rb=0.057:gr=0:gg=0.5:gb=0:br=0:bg=0:bb=0.5'
- muvid.montage.render.render_plan(plan, *, canvas, audio, output, workdir, grade='none', palette=None, crf=18, preset='fast', keep_parts=False)[source]¶
Render
planoveraudiotooutput. Writes exactlyoutput.- Parameters:
plan (
Plan) – The edit list. Its media paths are opened as-is.canvas (
Canvas) – Output size and frame rate. Even dimensions only.audio (
Path|str) – The song; the video is exactly as long as it.workdir (
Path|str) – Where the parts go (workdir/parts, removed after a successful mux unlesskeep_parts).grade (
str) – One ofmuvid.montage.spec.GRADES.palette (
Palette|None) – Supplies the tint accent and the grid pad colour.crf (
int) – libx264 knobs, per part.preset (
str) – libx264 knobs, per part.keep_parts (
bool) – Leave the intermediate parts on disk.
- Return type:
- Returns:
A
RenderResultwhosemetacarries the frame accounting, the parts count, and the verifier’s findings.
- muvid.montage.render.zoompan_exprs(path, *, frame_offset, fps)[source]¶
The
z/x/yexpressions forzoompanover a still.onis the output frame counter;frame_offsetshifts it so a part that starts mid-slot (or, negative, before the slot’s first frame on the incoming side of a blend) samples the slot’s own path.>>> from muvid.montage.plan import Keyframe, Window >>> path = (Keyframe(0.0, Window(0.0, 0.0, 1.0)), Keyframe(2.0, Window(0.1, 0.1, 0.8))) >>> e = zoompan_exprs(path, frame_offset=12, fps=24) >>> e['z'] '1/(1+(-0.2)*min(max((((on+12)/24)-0)/2,0),1))' >>> e['x'] 'iw*(0+(0.1)*min(max((((on+12)/24)-0)/2,0),1))' >>> zoompan_exprs(path[:1], frame_offset=0, fps=24) {'z': '1/(1)', 'x': 'iw*(0)', 'y': 'ih*(0)'}