Build InVideo
KINDAback to the verdict
A local video assembler whose hard problems are keeping a render reproducible from a file rather than from a session, refusing likeness and voice work without a consent record, and exporting a file that actually starts playing when someone uploads it.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the service that drives FFmpeg and holds the project.
Get it python.org or your package manager. open ↗
Verify
python3 --version prints 3.12 or higher - installfree
Why Every render is an FFmpeg invocation, and a build missing an encoder fails at export time rather than at setup time.
Get it Your package manager, or a static build. open ↗
Verify
ffmpeg -encoders lists the video and audio encoders your presets name - have readyfree
Why Proxies, intermediates and failed renders fill a disk quietly, and the ceiling in this build is only useful if you decided what it should be.
Get it Choose a working directory on a volume with room and write the ceiling into the environment.
Verify
The application refuses a render that would exceed the ceiling - have readyfree
Why Phone video with a variable frame rate is where timeline drift comes from, and a project that only ever sees clean twenty-five frame files will not show you the bug.
Get it Record something on a phone and import it unmodified.
Verify
The probe reports an average rather than a constant frame rate and the timeline still holds - decidefree
Why Deciding after you have built the generation path means deciding it under pressure, and the schema needs the consent table before anything references it.
Get it Write down what evidence counts as consent, how long it lasts and what happens when it expires.
Verify
The rule is in the README and the consent table enforces the expiry
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- projects(id, name, created_at, width, height, fps, format_version) - assets(id, project_id, path, sha256, kind, duration_ms, source, licence, licence_url, generated_by, consent_id) · source and licence are NOT NULL, because an asset with unknown provenance is a liability rather than a file - consents(id, person_name, scope, evidence_path, obtained_at, expires_at) · required before any asset carrying a real face or voice is accepted at all - timeline(id, project_id, track, ordinal, asset_id, in_ms, out_ms, transform_json) - captions(id, project_id, start_ms, end_ms, text, origin) · origin is transcribed, written or generated, and it is never blank - renders(id, project_id, at, preset, filtergraph_path, output_path, duration_ms, exit_code, log_tail, asset_hashes_json) - Every render writes its filter graph to a file beside the output and records the hash of every asset it used. A video you cannot regenerate from a text file and a folder is not reproducible, and six months later that is the only question you will have about it.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
PROJECT_DIR | required | ./data/projects | Project JSON and per-project asset folders. This is what you back up. |
WORK_DIR | required | ./data/work | Proxies, intermediates and render logs. Disposable by design. |
DISK_CEILING_GB | required | 40 | A render that would exceed this is refused before it starts. |
FFMPEG_BIN | required | /usr/bin/ffmpeg | Pinned, so you know which build produced a given output. |
TTS_API_KEYsecret | optional | sk-...redacted | Generated speech only. Without it, narration is recorded rather than generated. |
ALLOW_LIKENESS_ASSETS | required | false | Even when true, a consent row is still required. This only gates the import path. |
RENDER_CONCURRENCY | required | 1 | One at a time. Two renders on one machine is two slow renders. |
The build, in order
Assets that know where they came from
Nothing enters a project without a hash, a source, a licence and, where a person appears, a consent record.
The hash is the only thing that notices a file changed under you.
Files
app/assets.pytests/test_hash_guard.pyA nullable licence column becomes a null licence column.
Files
app/models.pymigrations/0001_assets.sqlA check in code, not a sentence in the README.
Files
app/consent.pytests/test_consent_gate.py
done when · tick each as it passeswatch out- Referencing assets by path alone means the day someone re-exports a clip or moves a folder your project renders different footage without telling you, and you find out from the output rather than from an error; hash on import, store the hash, verify it before every render, and report a mismatch as a failed render rather than rendering whatever is at that path now.
A timeline and a preview that matches the export
What you approve in the preview is what comes out of the final render.
Drift you fix at the end is drift you have already approved.
Files
app/normalise.pyThe preview must not be a second implementation.
Files
app/graph.pytests/test_graph_parity.pyThis is the assertion that keeps them honest.
Files
tests/test_cut_positions.py
done when · tick each as it passeswatch out- Previewing by seeking in the original files gives you positions that disagree with the render when the sources have different frame rates, and phone footage with a variable frame rate disagrees with itself, so the cut you carefully placed lands a few frames elsewhere in the export; build the proxy from the same graph as the final at the project frame rate, and normalise every source to that rate on import rather than at render time.
Narration, captions and what they are
Every spoken word and every caption carries a typed origin, and a cloned voice is impossible without consent.
The check belongs at the point of creation, not in a warning.
Files
app/voice.pytests/test_voice_consent.pyTranscribed, written or generated. No default.
Files
app/captions.pyBurned-in text is the hardest thing in the file to correct.
Files
app/captions.pytests/test_caption_review.py
done when · tick each as it passeswatch out- Presenting a model-generated caption track as a transcript is a small lie that becomes a large one when the video is about anything factual, because a transcription model will confidently produce words nobody said and they end up burned into the picture; type the origin of every caption track, show the origin in the editor, and require a pass over generated captions before they can be burned in.
An export that actually starts playing
The exported file plays immediately in a browser and on the platforms you named, with a manifest beside it.
The flag is documented as a second pass that can be skipped in some cases.
Files
app/export.pytests/test_faststart.pyterminalffmpeg -i input.mp4 -movflags +faststart -c copy output.mp4
Better than an encoder error twenty minutes into a render.
Files
app/export.pyIt is the only record of what is in the file.
Files
app/manifest.py
done when · tick each as it passeswatch out- The MP4 muxer writes its index at the end of the file by default, and the documentation for the faststart flag says plainly that it is a second pass that moves the index to the beginning and is not enabled by default, so without it a viewer must download the whole file before anything appears and your upload looks broken rather than slow; add the flag, confirm it ran, and note that it does not work with fragmented output so check the output rather than trusting the flag.
Provenance, disk and the render queueproduct builder
The project survives the application, renders cannot fill the disk, and provenance is kept as a record rather than promised as a guarantee.
The test is another machine, not another folder.
Files
app/project_io.pytests/test_roundtrip.pyCheck before, not after twenty gigabytes of temporary files.
Files
app/queue.pyIt is the copy nobody downstream can strip.
Files
app/manifest.pyREADME.md
done when · tick each as it passeswatch out- Treating an attached provenance manifest as proof the viewer will see is wrong twice over, because any tool downstream can strip it during a re-encode and because the specification itself notes that a manifest without a time stamp stops being valid once its signing credential expires or is revoked; attach it if you can, keep your own sidecar manifest in the project whatever happens to the file, and describe it in the README as a record you hold rather than a guarantee you offer.
That is the whole plan for InVideo. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Faces or voices of real people without a consent record.
- A stock media library.
- Direct publishing to video platforms.
- Collaborative editing.
- production codecs, rendering speed, and media templates
- hosted recording reliability
- large-scale rendering
- distribution and podcast hosting
- professional codecs, effects, and collaboration
- A second export preset for a platform you actually use, verified by uploading rather than by reading a specification
- Scene detection on long source clips, which turns the tedious part of assembling into a review task
- A licence report for a finished project, generated from the manifest, which is the document you would want if anyone ever asked
Need the files? The project pack on the verdict page hands your agent the whole brief · more audio & video.