Build diclip

KINDAreplaces $6/mosaves $72/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A personal video clipper whose hard problems are producing word-level timings accurate enough to cut on, making the cut land exactly where the transcript said it would, and rendering captions and framing without pretending to do the face tracking that the paid product actually does.

estimated effort multi-daythe files for this build are in the project pack

RuntimePython with a small local web interface, bound to the loopback addressTranscriptionA local speech model producing word-level timingsRenderingFFmpeg, invoked with explicit arguments you understandProjectsJSON on disk beside the media, with renders in their own directoryRankingA rented model if a key is present, a local heuristic if not

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the local application and the render queue.

    Get it python.org, or your version manager. open ↗

    Verify python3 --version prints 3.12 or higher

  2. installfree

    Why Every cut, caption burn and framing operation is an FFmpeg invocation, and the seeking behaviour you depend on is documented there.

    Get it Your package manager, or a static build from the project. open ↗

    Verify ffmpeg -version prints a version and the probe tool is on the path

  3. installfree, but the model download is large

    Why Word timings are what the cuts are computed from, so a model that only returns sentences is not usable here.

    Get it Install the library and download a model size your machine can run.

    Verify A one minute fixture returns words with start and end times

  4. have readyfree if you have the space

    Why A long recording plus its renders and intermediates is the constraint that ends most sessions, and it fails in a confusing way.

    Get it Point the project directory at a volume with room to spare and set the threshold below it.

    Verify The application reports free space and refuses below the threshold

  5. API keypay per token; a long transcript is a few pence

    Why Only the ranking step uses it, and the build is designed to be useful without it.

    Get it Any provider, with a spend limit set in their console.

    Verify A ranked candidate list arrives with the source recorded as model

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- projects(id, source_path, source_sha256, duration_ms, imported_at, transcript_state, render_state)
- words(project_id, ordinal, text, start_ms, end_ms, speaker, confidence) · the unit everything else is built from
- segments(project_id, ordinal, start_ms, end_ms, speaker, text) · sentences assembled from words, never from the model's own punctuation guesses alone
- candidates(id, project_id, start_ms, end_ms, hook_text, score, reason, source, state) · source is model or heuristic, state is pending, accepted or rejected
- renders(id, candidate_id, aspect, out_path, started_at, finished_at, ffmpeg_args, actual_start_ms, actual_duration_ms, failure)
- captions(render_id, ordinal, start_ms, end_ms, text, speaker) · grouped for reading, not one word per frame
- actual_start_ms is measured from the produced file, not copied from the request. The gap between requested and actual is the number this build exists to keep at zero.

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.

VariableNeededExampleWhere the value comes from
PROJECT_DIRrequired~/clips/projectsProject JSON and transcripts. The part worth backing up.
RENDER_DIRrequired~/clips/exportsFinished clips. Regenerable, so back it up last.
MIN_FREE_GBrequired10Renders refuse to start below this. It is the cheapest error message in the build.
CLIP_MIN_SECONDSrequired30Lower bound for a candidate.
CLIP_MAX_SECONDSrequired90Upper bound for a candidate.
RANKER_API_KEYsecretoptionalsk-...redactedOptional. Absent means the heuristic ranker runs.
CUT_TOLERANCE_FRAMESrequired1A render whose measured start drifts beyond this fails rather than shipping.

The build, in order

  1. Import and transcribe, without touching the source

    A long file becomes words with timings, and the original is never modified or moved.

    1. A sentence boundary is the wrong resolution for a cut point.

      Files src/transcribe/words.pytests/test_segments.py

    2. The recording is the one file you cannot regenerate.

      Files src/project/import.py

    3. An unreadable file should fail in a second, not in an hour.

      Files src/media/probe.py

    done when · tick each as it passes
    watch out
    • Storing only sentence timings because they are what the interface shows means every later cut is quantised to a sentence boundary, and the clip that needed to start three words earlier cannot. Store words, derive sentences, and keep the word table as the thing cuts are computed from.
  2. Candidates with a reason you can argue with

    Every proposed clip carries a hook line, a score and a stated reason, and the tool works with no model key.

    1. The tool has to work before anyone pays for a token.

      Files src/rank/heuristic.pytests/test_heuristic.py

    2. The model reads text; the word table knows the time.

      Files src/rank/locate.py

    3. A cut inside a word is audible and unfixable later.

      Files src/rank/snap.py

    done when · tick each as it passes
    watch out
    • Letting the model return timestamps directly gives you cut points that are plausible rather than real, because the model is reading text and inventing a time, and the clip then starts mid-word. Have the model return quoted text spans, locate those spans in the word table, and take the timings from the words.
  3. Cuts that land where you asked

    A rendered clip starts and ends within a frame of the requested times, and the build proves it.

    1. Argument order is the difference between correct and nearly.

      Files docs/ffmpeg-seeking.mdsrc/render/cut.py

    2. The only trustworthy timing is the one measured afterwards.

      Files src/render/verify.pytests/test_cut_accuracy.py

    3. Every strange clip is explained by its own command line.

      Files src/render/record.py

    done when · tick each as it passes
    watch out
    • Cutting with a stream copy because it is fast produces clips that begin at the previous keyframe, so a thirty second clip arrives with two seconds of the wrong sentence in front and the start you carefully snapped to a word is gone. Re-encode the cut, or accept keyframe-aligned boundaries explicitly, and in either case probe the output and compare its actual start and duration against what you asked for.
  4. Captions and framing, without pretending to track faces

    Burned captions are readable and correctly timed, and the vertical framing is honest about what it is doing.

    1. A fixed word count breaks lines in the wrong places.

      Files src/captions/group.pytests/test_caption_grouping.py

    2. Discovering the crop after a five minute render is the annoyance.

      Files src/render/preview.py

    3. The paid product does the other thing and this one does not.

      Files src/web/templates/render.html

    done when · tick each as it passes
    watch out
    • Grouping captions by a fixed word count produces lines that break mid-phrase and change at the wrong moment, which reads worse than no captions at all, and it is the first thing a viewer notices. Group by pause and reading speed with a maximum line length, and check the result against a clip where somebody speaks quickly.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more audio & video.