Summary
Add a camkit export-video command that renders the current timeline to a
.mov using Apple ProRes 422. Today camkit can mix the timeline's audio
(export-audio) but has no way to produce the finished video — that step is
still a manual Camtasia GUI export (Share ▸ Export ▸ Local File ▸ ProRes 422).
This command closes the last manual gap in the rough-cut → cleanup → render
pipeline.
Why this must go through Camtasia (read before designing)
camkit's design principle is "no ffmpeg re-encoding — Camtasia stays the
final renderer" (see README). A pure-ffmpeg render is not viable here and
should not be attempted:
- The timeline composites effects, transitions, and injected Dynamic Caption
callouts that only Camtasia's engine knows how to render.
- Screen recordings (
.trec) carry a tscc2 video stream that ffmpeg cannot
decode (this is already documented in packages/cli/src/media.ts — audio is
readable, the video stream is not).
So export-video must drive Camtasia to render, not rebuild the frame
itself.
Feasibility risk — PROBE FIRST, then report
@camkit/darwin (packages/darwin/src/index.ts) already documents that
Camtasia's AppleScript suite is partially broken on 2026.1.3: document
list / open / close-with-save are verified working, but media-element access
fails with AppleEvent -10000 and some project properties are inaccessible.
Whether an export/render verb works is unverified.
The first task is a spike, not code. In order:
- Inspect the scripting definition for an export/batch-export command:
sdef /Applications/Camtasia.app | less # or open CamtasiaGo.sdef in the bundle
Look for any export, render, share, or produce verb and its
parameters (codec/preset, destination). If one exists, try it via
osascript against a throwaway .cmproj and confirm it can be told to use
ProRes 422.
- If no scripting verb works, fall back to UI scripting via System
Events (drive the Share/Export menu + the export dialog). This needs
Accessibility permission and is brittle across Camtasia versions — gate it
behind the same assertDarwin() and document the version it was verified on,
matching the existing convention in index.ts.
- If neither is feasible, do not ship a fake. Instead update this issue
/ the README with findings and implement a camkit export-video that prints
clear manual instructions (the exact Share ▸ Export ▸ ProRes 422 steps) and
exits non-zero, so the command exists as a documented stub. Report back
before doing more.
Proposed interface (if scripting works)
camkit export-video [--project PATH] [--out FILE] [--codec prores422] [--preset NAME]
--out FILE — output path; default ./<project>.mov (mirror how
export-audio derives its default name via bundleName(...) in
cmdExportAudio).
--codec — default prores422; structure it so prores4444/h264 can be
added later, but only prores422 is required for v1.
- Render the timeline as-is (run after
rebuild, same contract as
export-audio). macOS-only; throw the standard "macOS-only" error elsewhere.
- Respect that Camtasia must have the project open (or open it) to render —
reuse camtasiaDocPaths() / openProject() from @camkit/darwin.
Where the code goes (follow CONTRIBUTING boundaries)
- Anything that shells out to Camtasia/osascript →
@camkit/darwin
(packages/darwin/src/index.ts), throwing on non-darwin.
- Arg parsing, default-path logic, console output →
@camkit/cli
(packages/cli/src/camkit.ts): add cmdExportVideo, register it in
COMMANDS, add a HELP["export-video"] entry and a one-line summary in
printHelp's summaries map.
- Keep
@camkit/core out of this — there's no pure timeline math here.
Acceptance criteria
Relationship to the other issues
This is the render half of the pipeline. Putting the rendered footage back
into a project as media is a separate mechanism (pure JSON + ffprobe, no
Camtasia render) — tracked in the companion "import media" issue. They were
considered for merging but use different machinery; keep them separate. Both are
features and should land under the new release flow (the release issue),
bumping the minor version.
Summary
Add a
camkit export-videocommand that renders the current timeline to a.movusing Apple ProRes 422. Today camkit can mix the timeline's audio(
export-audio) but has no way to produce the finished video — that step isstill a manual Camtasia GUI export (Share ▸ Export ▸ Local File ▸ ProRes 422).
This command closes the last manual gap in the rough-cut → cleanup → render
pipeline.
Why this must go through Camtasia (read before designing)
camkit's design principle is "no ffmpeg re-encoding — Camtasia stays the
final renderer" (see README). A pure-ffmpeg render is not viable here and
should not be attempted:
callouts that only Camtasia's engine knows how to render.
.trec) carry atscc2video stream that ffmpeg cannotdecode (this is already documented in
packages/cli/src/media.ts— audio isreadable, the video stream is not).
So
export-videomust drive Camtasia to render, not rebuild the frameitself.
Feasibility risk — PROBE FIRST, then report
@camkit/darwin(packages/darwin/src/index.ts) already documents thatCamtasia's AppleScript suite is partially broken on 2026.1.3: document
list / open / close-with-save are verified working, but media-element access
fails with AppleEvent -10000 and some project properties are inaccessible.
Whether an export/render verb works is unverified.
The first task is a spike, not code. In order:
export,render,share, orproduceverb and itsparameters (codec/preset, destination). If one exists, try it via
osascriptagainst a throwaway.cmprojand confirm it can be told to useProRes 422.
Events (drive the Share/Export menu + the export dialog). This needs
Accessibility permission and is brittle across Camtasia versions — gate it
behind the same
assertDarwin()and document the version it was verified on,matching the existing convention in
index.ts./ the README with findings and implement a
camkit export-videothat printsclear manual instructions (the exact Share ▸ Export ▸ ProRes 422 steps) and
exits non-zero, so the command exists as a documented stub. Report back
before doing more.
Proposed interface (if scripting works)
--out FILE— output path; default./<project>.mov(mirror howexport-audioderives its default name viabundleName(...)incmdExportAudio).--codec— defaultprores422; structure it soprores4444/h264can beadded later, but only
prores422is required for v1.rebuild, same contract asexport-audio). macOS-only; throw the standard "macOS-only" error elsewhere.reuse
camtasiaDocPaths()/openProject()from@camkit/darwin.Where the code goes (follow CONTRIBUTING boundaries)
@camkit/darwin(
packages/darwin/src/index.ts), throwing on non-darwin.@camkit/cli(
packages/cli/src/camkit.ts): addcmdExportVideo, register it inCOMMANDS, add aHELP["export-video"]entry and a one-line summary inprintHelp'ssummariesmap.@camkit/coreout of this — there's no pure timeline math here.Acceptance criteria
Camtasia version) in the PR description and/or
index.tsdoc comment.camkit export-videoproduces a playable ProRes 422.movof the currenttimeline, or — if Camtasia can't be scripted — a documented stub that
prints exact manual steps and exits non-zero.
camkit export-video --helpand the top-level help list the command.permission is needed).
bun testandbun run typecheckpass..cmproj; note the verification in the PR(per CONTRIBUTING).
Relationship to the other issues
This is the render half of the pipeline. Putting the rendered footage back
into a project as media is a separate mechanism (pure JSON + ffprobe, no
Camtasia render) — tracked in the companion "import media" issue. They were
considered for merging but use different machinery; keep them separate. Both are
features and should land under the new release flow (the release issue),
bumping the minor version.