Make video projects, with a moving channel split as the first video effect
A project can now be a clip: Setup has a Video switch with a length, a frame rate and presets that set the canvas too (Instagram story, reel and feed video, TikTok / YouTube Short, Square loop, Full HD clip). The preview renders the whole clip, shrunk to a memory budget, and loops it with play/pause and a scale; Save As... renders it again at full size and pipes the frames into ffmpeg as .mp4, .mov, .mkv, .webm or .gif, with a progress window that can cancel. --duration, --fps and --video-preset do the same from the command line. Effects learn about time through FrameTime and Effect::renderFrame, which falls back to render, so nothing that holds still changes. Channel split now sways, shakes and rolls a tracking bar down the picture in a video; its motion params are marked forVideo() and hidden in a still project, and a still renders byte for byte as before. renderFrames starts every frame from the same seed and renders the still stages ahead of the first moving one only once. The version moves to 0.6.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,9 @@ Everything is warning-clean under `-Wall -Wextra -Wpedantic`; keep it that way.
|
||||
The core test suite is a single hand-rolled binary (no framework), so there is no
|
||||
per-test filter: run `./build/tests/effector_tests` directly to see the section
|
||||
headings (`rng`, `registry`, `scanlines`, `tiles`, `blur`, `chain`, `project`,
|
||||
`image io`, `batch loading`) and the `ok`/`FAIL` lines. To iterate on one area, comment
|
||||
`image io`, `batch loading`, `video`, `version`) and the `ok`/`FAIL` lines. The
|
||||
`video` section writes real files through `ffmpeg` when it is on the PATH and
|
||||
prints a `skip` line otherwise. To iterate on one area, comment
|
||||
out the other blocks in `tests/core_tests.cpp` or add a new
|
||||
`std::cout << "section\n"` block — checks are plain
|
||||
`check(condition, "description")` calls and `main()` returns the failure count.
|
||||
@@ -170,11 +172,56 @@ the `effector` application name on each entry; the GUI tests point
|
||||
`XDG_DATA_HOME` at a temporary folder so they never touch the user's list. Opening one replaces the sources, so a decode still in flight
|
||||
is discarded rather than mixed into the new project (`dropLoadedImages_`).
|
||||
|
||||
### Video projects
|
||||
|
||||
A project is a still or a clip: `VideoSettings` (`src/core/video.hpp`: enabled,
|
||||
duration, fps, preset) rides in `Project::video` and `Options::video`, and is
|
||||
written to the project file only when enabled. `videoPresets()` is one table
|
||||
like `canvasFormats()` — a preset names a canvas format plus a length and rate,
|
||||
and like a format it is the presentation, never the truth: the render goes by
|
||||
duration and fps. Adding a preset is a line there.
|
||||
|
||||
Time reaches an effect as a `FrameTime` (`src/core/effect.hpp`; the default,
|
||||
`fps == 0`, is a still) through `Effect::renderFrame`, which falls back to
|
||||
`render` — an effect that does not move overrides nothing. One that moves
|
||||
overrides `renderFrame` and `animated(values)`, and marks its motion params
|
||||
`.forVideo()`: the interface hides those in a still project (declaratively,
|
||||
like `.styled()`), the CLI still has their flags. A still must render byte for
|
||||
byte what it did before the effect learned to move.
|
||||
|
||||
`renderFrames` (`src/core/chain.cpp`) renders a clip. Every frame starts from
|
||||
`Rng(seed)`, so a stage that does not move draws the same values every frame
|
||||
and holds still. A moving effect must therefore leave the shared rng in the
|
||||
same state on every frame — the channel split draws its per-frame shake from a
|
||||
generator of its own, seeded by one draw — or everything after it would flicker.
|
||||
The stages before the first `animated()` one are rendered once and their image
|
||||
and rng state reused; the `video` tests assert that frame k is exactly
|
||||
`renderChain(..., FrameTime{k, ...})`, so that shortcut can never change output.
|
||||
The determinism contract covers the frames; the encoded file is whatever the
|
||||
machine's ffmpeg makes of them.
|
||||
|
||||
`VideoWriter` pipes raw `rgb24` frames into the `ffmpeg` program
|
||||
(`posix_spawnp` with an argv — no shell; `SIGPIPE` ignored; stderr captured for
|
||||
the error message; codec by extension). `cancel()` SIGKILLs it — SIGTERM makes
|
||||
ffmpeg flush its encoder first — and removes the file; a writer destroyed
|
||||
unfinished cancels itself.
|
||||
|
||||
In the interface, a video render (`startRender`) renders every frame at full
|
||||
size, shrinks each (`Image::scaledToFit`, sized by `clipSide` to a memory
|
||||
budget) and hands them over through `pendingFrames_`/`clipProgress_`; the
|
||||
clip on screen keeps looping until the new one is complete, and
|
||||
`renderCancel_` stops a clip whose settings went stale. Save As… renders again
|
||||
at full size on `exporter_` into a `VideoWriter`, with an `exportWindow_`
|
||||
progress window (its `exportBar_` nulled in `closeExportWindow()`). Under Xvfb
|
||||
the GTK file dialog goes through the desktop portal and may open on the real
|
||||
session instead; set `GDK_DEBUG=no-portals` to keep it in the Xvfb display.
|
||||
|
||||
### Two link targets, deliberately
|
||||
|
||||
`effector_core` (static) holds image IO, params, effects, rng and links only
|
||||
`gdk-pixbuf-2.0`, `pangocairo` (the text effect's glyphs; Pango and Cairo need
|
||||
no display) + Threads — **no gtkmm, no display**. `effector_gui` (static) adds
|
||||
`effector_core` (static) holds image IO, params, effects, rng, video writing
|
||||
and links only `gdk-pixbuf-2.0`, `pangocairo` (the text effect's glyphs; Pango
|
||||
and Cairo need no display) + Threads — **no gtkmm, no display**; `ffmpeg` is a
|
||||
program it runs, not a library it links. `effector_gui` (static) adds
|
||||
`src/cli/` and `src/gui/` and links gtkmm, and the `effector` executable is
|
||||
`src/main.cpp` on top of it; the GUI tests link `effector_gui` too. Keep GTK out of
|
||||
`src/core/` and `src/effects/`; the tests link the core alone and rely on this.
|
||||
|
||||
Reference in New Issue
Block a user