Build Camtasia

YESback 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 local screen recorder and trimmer whose hard problems are keeping a recording usable when the process dies, keeping audio aligned with a capture whose frame rate is genuinely variable, and cutting where the user asked rather than at the nearest keyframe.

estimated effort weekendthe files for this build are in the project pack

CaptureThe platform screen capture source through FFmpeg, written as fragments or segmentsProcessingFFmpeg for everything after capture, driven from PythonEditingA non-destructive cut list over the original file, never edits in placePreviewFast approximate preview, exact render only on exportStorageRecordings and renders in separate directories with a disk check before capture

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 It is the capture, the processing and the export, and the fragmented container behaviour this build depends on is documented there.

    Get it Your package manager, or ffmpeg.org, then check that your platform's screen capture input is listed. open ↗

    Verify ffmpeg -devices lists your screen capture input and a ten second test recording plays

  2. installfree

    Why Drives capture, the cut list and the render queue.

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

    Verify python3 --version prints 3.12 or higher

  3. have readyfree

    Why Screen recording fills a disk faster than people expect and a recording that stops because the volume was full is the failure this build exists to avoid.

    Get it Know your free space and measure the bitrate of a one minute test at your settings.

    Verify A one minute test gives a megabytes per minute figure used by the estimate

  4. decidefree

    Why On current desktop systems the capture will silently produce a black screen or no audio until the permission is granted to the exact binary doing the capture.

    Get it Grant screen recording and microphone access, then confirm with a test capture from the same process the app uses.

    Verify A test recording contains both picture and sound

  5. decidefree

    Why Resolution, frame rate and what cleanup may delete all become tests, and deciding them afterwards means accepting whatever the code happened to do.

    Get it Write down the capture resolution and frame rate, the scratch retention period, and the rule that recordings are never deleted automatically.

    Verify All three are in configuration and named in tests

Data model

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

- recordings(id, started_at, stopped_at, path, container, fragmented, width, height, nominal_fps, audio_streams, bytes, verified_at, verify_result)
- sources(recording_id, kind, device, sample_rate, channels) · kind is screen, microphone or system audio, each kept as its own stream
- projects(id, recording_id, name, created_at)
- cuts(id, project_id, ordinal, start_ms, end_ms, action) · action is keep or drop, applied to the original, never to a previous export
- markers(id, project_id, at_ms, label) · set while recording, because that is when you know
- renders(id, project_id, preset, output_path, started_at, finished_at, state, log_excerpt, bytes)
- The cuts table is a description of the edit. The recording file is never modified, so every export starts from the original.

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
RECORDINGS_DIRrequired./data/recordingsOriginal captures. Never written to by the editor and never touched by cleanup.
SCRATCH_DIRrequired./data/scratchPreviews and render segments. This is the directory cleanup is allowed to empty.
RENDERS_DIRrequired./data/rendersFinished exports, with the index at the front for playback.
CAPTURE_FPSrequired30The rate the variable capture is normalised to. Higher costs disk linearly.
SEGMENT_SECONDSrequired60Recording segment length, and the resume granularity for long exports.
SCRATCH_RETENTION_HOURSrequired48How long previews and render segments survive. Recordings are not included.
MIN_FREE_GBrequired20Recording refuses to start below this, because a full disk truncates a capture.

The build, in order

  1. A recording that survives the process dying

    Killing the recorder mid-capture leaves a file that opens and plays up to the moment it stopped.

    1. This is the one decision the whole build is arranged around.

      Files src/capture.pytests/test_kill_recovery.py

    2. An estimate at the start beats a truncated file at the end.

      Files src/disk.py

    3. You want to find out now, not when you go to edit it.

      Files src/verify.py

    done when · tick each as it passes
    watch out
    • Recording straight to a plain MP4 and relying on a clean stop means a crash, a full disk or a forced reboot leaves a file with no index, and the documentation is explicit that such a file is undecodable, so the recording you cannot retake is gone. Record fragmented or in segments and verify the file immediately after stopping.
  2. Audio that stays with the picture

    Twenty minutes in, the voice is still aligned with what is on screen.

    1. Screen capture is genuinely variable, sometimes zero frames per second.

      Files src/capture.py

    2. Fixing a loud notification later is only possible if it is separable.

      Files src/audio.py

    3. Twenty minutes, because five minutes hides it.

      Files tests/test_drift.pydocs/drift-test.md

    done when · tick each as it passes
    watch out
    • Assuming the capture produced the frame rate you asked for gives a file whose timestamps wander, and the audio appears to drift further out of sync the longer the recording runs, which is discovered at the end of a long tutorial. Normalise the frame rate explicitly, and note that combining a fixed output rate with a passthrough frame rate mode is rejected outright, so choose one deliberately.
  3. Cuts that land where you asked

    Trimming removes exactly the section selected, and the original file is never touched.

    1. The original is the only irreplaceable artefact here.

      Files src/cuts.py

    2. And label the preview as approximate in the interface.

      Files src/export.pytests/test_cut_accuracy.py

    3. Otherwise the join produces artefacts that look like corruption.

      Files src/join.py

    done when · tick each as it passes
    watch out
    • Trimming by seeking and stream copying looks instant and lands the cut at the nearest keyframe before the point you chose, which with a common encoder default can be several seconds early, so the export keeps the throat clearing you were removing. Re-encode on export for accuracy, keep copying for the preview, and say which is which in the interface.
  4. The things a tutorial actually needs

    Markers, a zoom on the area that matters, and captions that are a file rather than burnt in.

    1. Afterwards you will not remember when it happened.

      Files src/markers.py

    2. Not as a preview effect that never reaches the file.

      Files src/zoom.py

    3. A typo should cost a text edit, not a re-render.

      Files src/captions.py

    done when · tick each as it passes
    watch out
    • Burning captions into the only exported file means a typo in a name costs a full re-render of an hour of video, and the version with the typo is already uploaded. Emit a subtitle file beside the video by default, and make the burnt-in copy a separate deliberate export.
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 screen recording.