Build Camtasia
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.
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 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 - 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 - 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 - 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 - 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.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
RECORDINGS_DIR | required | ./data/recordings | Original captures. Never written to by the editor and never touched by cleanup. |
SCRATCH_DIR | required | ./data/scratch | Previews and render segments. This is the directory cleanup is allowed to empty. |
RENDERS_DIR | required | ./data/renders | Finished exports, with the index at the front for playback. |
CAPTURE_FPS | required | 30 | The rate the variable capture is normalised to. Higher costs disk linearly. |
SEGMENT_SECONDS | required | 60 | Recording segment length, and the resume granularity for long exports. |
SCRATCH_RETENTION_HOURS | required | 48 | How long previews and render segments survive. Recordings are not included. |
MIN_FREE_GB | required | 20 | Recording refuses to start below this, because a full disk truncates a capture. |
The build, in order
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.
This is the one decision the whole build is arranged around.
Files
src/capture.pytests/test_kill_recovery.pyAn estimate at the start beats a truncated file at the end.
Files
src/disk.pyYou want to find out now, not when you go to edit it.
Files
src/verify.py
done when · tick each as it passeswatch 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.
Audio that stays with the picture
Twenty minutes in, the voice is still aligned with what is on screen.
Screen capture is genuinely variable, sometimes zero frames per second.
Files
src/capture.pyFixing a loud notification later is only possible if it is separable.
Files
src/audio.pyTwenty minutes, because five minutes hides it.
Files
tests/test_drift.pydocs/drift-test.md
done when · tick each as it passeswatch 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.
Cuts that land where you asked
Trimming removes exactly the section selected, and the original file is never touched.
The original is the only irreplaceable artefact here.
Files
src/cuts.pyAnd label the preview as approximate in the interface.
Files
src/export.pytests/test_cut_accuracy.pyOtherwise the join produces artefacts that look like corruption.
Files
src/join.py
done when · tick each as it passeswatch 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.
The things a tutorial actually needs
Markers, a zoom on the area that matters, and captions that are a file rather than burnt in.
Afterwards you will not remember when it happened.
Files
src/markers.pyNot as a preview effect that never reaches the file.
Files
src/zoom.pyA typo should cost a text edit, not a re-render.
Files
src/captions.py
done when · tick each as it passeswatch 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.
Exports, long renders and the diskproduct builder
A long export can be resumed, the deliverable plays without downloading fully, and the scratch files do not fill the disk.
An hour of video is long enough for something to interrupt it.
Files
src/render.pytests/test_resume.pyIt is one flag and it is the difference between playing and waiting.
Files
src/export.pyThat directory holds the only copies of unrepeatable events.
Files
src/cleanup.pytests/test_cleanup.py
done when · tick each as it passeswatch out- An automatic cleanup that treats recordings like temporary files eventually deletes the only copy of something that cannot be recorded again, usually while you are on holiday. Delete scratch and preview files automatically, never recordings, and make recording deletion a deliberate action that names what it is about to remove.
That is the whole plan for Camtasia. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Multi-track compositing, transitions and titles.
- Webcam overlay with background removal.
- Hosting and sharing.
- Per-machine hardware encoder tuning.
- production codecs, rendering speed, and media templates
- hosted recording reliability
- large-scale rendering
- distribution and podcast hosting
- professional codecs, effects, and collaboration
- Automatic zoom from cursor movement, computed after recording from a cursor position log
- A silence remover that proposes cuts you accept, driven from the same cut list
- A second export preset for a different platform, which is configuration rather than code
Need the files? The project pack on the verdict page hands your agent the whole brief · more screen recording.