Build ScreenFlow

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 whose hard problems are capturing through the supported framework with permission handled properly, writing the take to disk continuously so a crash never destroys it, and keeping variable rate screen frames in step with a microphone over a long recording.

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

CaptureScreenCaptureKit through a small native helper, with the system sharing pickerProcessingFFmpeg as a subprocess for assembly, trimming and exportRuntimePython 3.12 with FastAPI serving a local interfaceStorageSQLite for recordings and edits, media inside a project folderEditingAn edit list applied at export, sources never modified

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. have readyfree if you have one

    Why The permission flow, the picker and the capture framework are the first phase, and none of it can be tested anywhere else.

    Get it Any supported Mac, with the ability to change privacy settings.

    Verify A trivial capture helper records five seconds of the screen

  2. installfree

    Why The capture helper is native, and the toolchain download is large enough to be worth starting before you need it.

    Get it Install Xcode or the command line tools from Apple. open ↗

    Verify swiftc --version prints a version

  3. installfree

    Why Assembly, trimming and export all shell out to it, and ffprobe is how you verify timestamps and drift.

    Get it Your package manager, or a static build from the official downloads page. open ↗

    Verify ffmpeg -version and ffprobe -version both print a version

  4. have readyfree

    Why Drift, disk growth and crash recovery all behave perfectly on a two minute clip and reveal themselves only on a long one.

    Get it Record yourself working for half an hour with a clap at the start and the end.

    Verify The clap appears at the same moment in both tracks at both ends

  5. decidefree

    Why This tool writes gigabytes without being asked, and deciding the limits after filling a disk means deciding them under pressure.

    Get it Write down the minimum free space to start recording and how long segments survive a successful export.

    Verify Both are in the environment and enforced in code

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, ended_at, source_kind, state, segments_dir, bytes, recovered_from_crash)
- segments(recording_id, ordinal, path, start_pts_ms, end_pts_ms, bytes, sealed_at) · written as they arrive and sealed as they close
- tracks(recording_id, kind, device_name, sample_rate, channels, first_pts_ms) · screen, microphone and system audio each carry their own clock
- projects(id, name, folder, created_at) · media referenced by relative path, so the folder can be moved or copied
- edits(project_id, ordinal, recording_id, in_ms, out_ms, track_kind) · an edit list, never a rewritten source
- exports(id, project_id, preset, path, settings_json, bytes, started_at, finished_at, exit_code) · the settings that produced the file, kept with it
- No operation in this model modifies a source recording, which is the only reason a mistaken trim at midnight is survivable.

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
DATABASE_PATHrequired./data/recorder.dbRecordings, segments, edits and exports.
PROJECTS_DIRrequired~/Movies/RecordingsProject folders holding media. Referenced relatively so they can move.
CAPTURE_HELPER_PATHrequired./helper/build/CaptureHelperThe native helper. Without it there is no capture at all.
FFMPEG_PATHrequired/opt/homebrew/bin/ffmpegAssembly, trimming and export.
SEGMENT_SECONDSrequired5How much of a take a crash can cost. Smaller is safer and slightly busier.
MIN_FREE_BYTESrequired10000000000Recording refuses to start below this and stops cleanly if it is reached.
SEGMENT_RETENTION_DAYSrequired14After a successful export only. The cleanup reports what it removed.

The build, in order

  1. Capture through the supported path

    Recording starts only after permission is granted, through the system's own picker.

    1. One small process with one job, talking to the rest over a local socket.

      Files helper/Capture.swiftsrc/capture/client.py

    2. A missing description string is a silent denial at the worst moment.

      Files helper/Info.plist

    3. Less code, fewer prompts, and the sources the person actually expects.

      Files helper/Picker.swift

    done when · tick each as it passes
    watch out
    • Building your own window list and capturing through the older CoreGraphics calls still works today, prompts the person with warnings about software that can collect detailed information about them, and is the path Apple is closing; it also breaks quietly after an update nobody chose. Use ScreenCaptureKit with the system picker, and handle a denied permission as a first class state.
  2. A take that survives the process dying

    Whatever was recorded before a crash is still on disk and playable.

    1. A few seconds each; the cost is negligible and the insurance total.

      Files src/capture/segments.py

    2. The recovery path is the feature, so it runs automatically.

      Files src/recover.pytests/test_recover.py

    3. Refusing to start beats stopping at minute fifty.

      Files src/disk.py

    done when · tick each as it passes
    watch out
    • Buffering the take and muxing one file when the person presses stop means a crash, a forced restart or a full disk at minute fifty destroys the entire recording, which is the single thing this kind of tool must never do. Seal a segment every few seconds and make a crashed session recoverable from what is already on disk.
  3. Picture and sound that stay together

    A long recording is still in sync at the end.

    1. Never recompute a time from a frame index.

      Files src/capture/segments.pysrc/assemble.py

    2. Separate clocks, aligned at assembly, not merged at capture.

      Files src/audio.py

    3. A number in the test output, not a feeling while watching.

      Files tests/test_drift.py

    done when · tick each as it passes
    watch out
    • Assuming a constant frame rate is safe because screen capture reports one is how a forty minute tutorial ends up with the mouse click arriving a second before the sound: the capture delivers frames when the screen changes, so the frame count and the wall clock disagree more the longer you record. Keep the timestamps from capture and assemble against them.
  4. Trimming without re-encoding everything

    Cutting the dead air off the front takes seconds and costs no quality.

    1. The timeline is data and the file is evidence.

      Files src/edits.py

    2. Re-encode the boundary pieces only, and say when you had to.

      Files src/export/trim.py

    3. One test that makes the whole model true.

      Files tests/test_source_untouched.py

    done when · tick each as it passes
    watch out
    • Re-encoding a twenty minute recording to remove two seconds at the front costs a long wait and a generation of quality every time somebody changes their mind, which they will, four times. Store an edit list, copy the stream where keyframes allow, and re-encode only the pieces at the boundaries.
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.