You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 648f469
Browse filesBrowse the repository at this point in the historyBrowse files
Release v0.29.0: aligner word timings, reviewed batch plans, render fidelity and lint checks (#142)
caption --words, batch --plan/--apply-plan, render fidelity census with --strict and --verify, tts --lexicon, segment-overlap and segment-offscreen lint checks, and a write journal shown by restore --list.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+19Lines changed: 19 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,25 @@ All notable changes to capcut-cli are documented here. The format follows [Keep
4
4
5
5
## [Unreleased]
6
6
7
+
## [0.29.0] — 2026-10-09
8
+
9
+
### Added
10
+
11
+
-`caption --words <file.json|->` builds captions from word timings produced by an external aligner, so Whisper does not have to be installed. The format is detected automatically: Whisper / whisper.cpp `segments[].words[]`, WhisperX `word_segments[]`, or a plain `{word|text|char, start, end}` array with times in seconds, `start_ms`/`end_ms` or `start_time`/`end_time` (per-character forced-aligner output). The result reports `words_format`, `words_skipped` and `source_words`. Entries without timing are skipped; entries that end before they start or go back in time are refused with `refused [words-invalid]` naming the entry. Grouping, CJK joining without spaces, `--script`, `--karaoke` and `--word-reveal` behave exactly as with Whisper words. `--words` cannot be combined with `--audio`, `--from-segment`, `--audio-stream`, `--ffmpeg-cmd` or `--whisper-*`.
12
+
-`batch <project> --plan <plan.json> < ops.jsonl` validates the operations under dry-run without touching the draft and writes a reviewable plan: `project`, `draft_sha256`, the normalized `operations`, `operations_sha256` (over key-sorted JSON) and a per-operation `preview`. `batch <project> --apply-plan <plan.json>` reads no stdin and applies the plan through the normal transactional batch. It refuses with `refused [plan-draft-changed]` when the draft changed since the plan was written, `refused [plan-tampered]` when the operations no longer match their hash, and `refused [plan-project-mismatch]` when the plan names another project. Without `--continue-on-error` a failing operation writes no plan.
13
+
-`render` (including `--dry-run`) reports a `fidelity` census of what the proxy leaves out compared with the app: `faithful`, `expected_duration_us`, the `checked` categories, and per dropped category a count, up to 20 segment ids and a hint (missing media, closed main-track gaps, uncomposited overlay tracks, unburned text, stickers, transitions, effects, filters, masks, keyframes, text/video animations, blend modes, chroma key, matting). `render --strict` refuses with `refused [render-unfaithful]` before anything is written.
14
+
- After a real render, `render` probes the written file with ffprobe (`--ffprobe-cmd` selects the binary) and reports `verification`: `{ verified, duration_us, expected_duration_us, drift_us, tolerance_us, within_tolerance }`, with a tolerance of one frame at the render fps. When ffprobe is missing or cannot read the file it reports `verified: false` with a reason. `render --verify` exits non-zero on drift beyond tolerance or an unverifiable file; the file stays on disk and the JSON result is still printed.
15
+
-`tts --lexicon <file.json>` applies pronunciation rules (`{"rules":[{"text","say","case_sensitive"?}]}` or a bare array) to the text handed to the TTS engine only: longest match first, at word boundaries for rule edges that are word characters (`SQL` never fires inside `MySQLite`; CJK rules match anywhere). Malformed lexicons refuse with `refused [lexicon-invalid]`, and equal-length rules that say different things at the same position refuse with `refused [lexicon-ambiguous]`, both before any engine runs. The result reports `lexicon: { rules, applied, spoken_text }` with offsets in UTF-16 code units into the trimmed text. Works with `--text` and `--text-file`.
16
+
-`lint` reports `segment-overlap` (error) when two segments on the same non-text track (video, audio, sticker, effect, filter …) overlap, naming the track, both segment ids and the overlap in µs. `lint --fix` repairs overlaps of at most one frame at the draft's fps (the independent-rounding overlap) by ending the earlier segment where the next begins, keeping its source duration in proportion to speed. Wider overlaps are reported, not fixed. Text tracks keep `caption-overlap`.
17
+
-`lint` reports `segment-offscreen` (warning) for video, photo, sticker and text segments that cannot be seen: the clip's box lies entirely outside the canvas (material size fitted into `canvas_config`, rotation allowed for), a scale axis is 0, or opacity is 0. Segments whose position, scale or alpha is keyframed are not judged on that property. No `--fix`.
18
+
- Every undo-history snapshot now records what made it: `.capcut-cli-history/<draft>.journal.jsonl` gets `{ index, time, command, argv, before_sha256, after_sha256 }` per write (library callers record `command: null`), trimmed together with the snapshots. `restore --list` shows `command`, `argv` and `time` per step; older snapshots without a journal line list with nulls. A torn or unreadable journal never breaks a write or a restore.
19
+
- Whisper-free caption routes are documented in `docs/quickstart.zh-CN.md` (不装 Whisper 的字幕路线) and `examples/short-video-narration.md`: the app's own auto captions followed by `restyle` / `export-srt`, `import-srt`, and `caption --words`.
20
+
21
+
### Changed
22
+
23
+
-`doctor`'s Whisper hint names `caption --words` and `import-srt` as routes that work without Whisper. The check's status is unchanged.
24
+
-`lint --frame-grid --fix` derives repaired source durations through the same helper as the new overlap repair: at speed 1 nothing changes; on clips at other speeds whose source duration matched target × speed (within 1%), the source duration now follows the snapped target duration instead of being left as it was.
Copy file name to clipboardExpand all lines: README.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -105,9 +105,10 @@ The host reads a draft and passes its JSON as tool input. The component itself h
105
105
106
106
## Release notes
107
107
108
+
> **New in v0.29.0:** captions from any aligner's word timings without Whisper (`caption --words`); reviewed batch plans bound to the draft (`batch --plan` / `--apply-plan`); a render fidelity census and output-duration check (`render --strict` / `--verify`); TTS pronunciation lexicons (`tts --lexicon`); `segment-overlap` and `segment-offscreen` lint checks; and `restore --list` naming the command behind each step. Full details in the [changelog](./CHANGELOG.md).
109
+
108
110
> **New in v0.28.0:** opt-in active-timeline selection; compilation into empty app-created projects; compact command discovery and selection by name for agents; Windows command-path quoting fixed in Python client v0.1.3; and Python client CI on Linux, macOS, and Windows. Full details in the [changelog](./CHANGELOG.md).
109
111
110
-
> **New in v0.27.0:** content-safe media replacement; automatic import registration after replacement and relink; recursive, ambiguity-safe relinking; exact fractional-second compile boundaries; operation preflight and failed-build cleanup; payload-bound queue IDs; canonical project locks; bounded queue results; and local OTIO file/relative references. Full details in the [changelog](./CHANGELOG.md).
111
112
112
113
For an existing nested project, use `capcut diagnose <project> --active-timeline` to inspect the selected document before editing. The opt-in follows the pointer on unverified builds and refuses invalid or conflicting selected documents; it does not bypass write guards.
"description": "TTS command template, run without a shell: {out} (required) is replaced with the .wav path the tool must write, {text} with the text as one argument; without {text} the text is piped to stdin."
1458
1463
},
1464
+
{
1465
+
"name": "lexicon",
1466
+
"flags": [
1467
+
"--lexicon"
1468
+
],
1469
+
"type": "path",
1470
+
"required": false,
1471
+
"description": "Pronunciation lexicon JSON ({\"rules\": [{\"text\", \"say\", \"case_sensitive\"?}]} or a bare array) applied to the spoken text only: longest match first, at word boundaries (CJK rules match anywhere). Equal-length rules that say different things refuse [lexicon-ambiguous]; lexicon.applied offsets are UTF-16 code unit indices into the trimmed text."
1472
+
},
1459
1473
{
1460
1474
"name": "volume",
1461
1475
"flags": [
@@ -3588,12 +3602,17 @@
3588
3602
{
3589
3603
"name": "batch",
3590
3604
"summary": "Run multiple edits from stdin (JSONL), one file write.",
"description": "Commit only successful operations and exit 1 if any fail."
3608
3627
},
3628
+
{
3629
+
"name": "plan",
3630
+
"flags": [
3631
+
"--plan"
3632
+
],
3633
+
"type": "path",
3634
+
"required": false,
3635
+
"description": "Validate the stdin operations as a real run would and write a reviewable plan file (draft and operations sha256, per-operation preview) instead of changing the draft."
3636
+
},
3637
+
{
3638
+
"name": "apply_plan",
3639
+
"flags": [
3640
+
"--apply-plan"
3641
+
],
3642
+
"type": "path",
3643
+
"required": false,
3644
+
"description": "Apply a plan written by --plan (no stdin). Refused if the draft changed since the plan or its operations were edited."
3645
+
},
3609
3646
{
3610
3647
"name": "active_timeline",
3611
3648
"flags": [
@@ -4343,7 +4380,7 @@
4343
4380
{
4344
4381
"name": "caption",
4345
4382
"summary": "Transcribe audio via whisper into real caption-track segments.",
"description": "Word timings from an external aligner instead of running Whisper (path or - for stdin): Whisper/whisper.cpp segments[].words[], WhisperX word_segments[], or an array of {word|text|char, start, end} (seconds; start_ms/end_ms and start_time/end_time also read). The result reports words_format and words_skipped. Not combinable with --audio, --from-segment, --audio-stream or --whisper-*."
4423
+
},
4378
4424
{
4379
4425
"name": "audio_stream",
4380
4426
"flags": [
@@ -5956,7 +6002,7 @@
5956
6002
],
5957
6003
"type": "boolean",
5958
6004
"required": false,
5959
-
"description": "List snapshots."
6005
+
"description": "List snapshots, newest first, with the command that made each write."
5960
6006
},
5961
6007
{
5962
6008
"name": "active_timeline",
@@ -6584,6 +6630,33 @@
6584
6630
"required": false,
6585
6631
"description": "Stream ffmpeg's progress to stderr instead of buffering it."
6586
6632
},
6633
+
{
6634
+
"name": "strict",
6635
+
"flags": [
6636
+
"--strict"
6637
+
],
6638
+
"type": "boolean",
6639
+
"required": false,
6640
+
"description": "Refuse (refused [render-unfaithful], nothing rendered) when the fidelity census finds anything the proxy drops: transitions, effects, filters, masks, keyframes, uncomposited tracks, stickers, text, animations, blend modes, chroma, matting, gaps or missing media."
6641
+
},
6642
+
{
6643
+
"name": "verify",
6644
+
"flags": [
6645
+
"--verify"
6646
+
],
6647
+
"type": "boolean",
6648
+
"required": false,
6649
+
"description": "Probe the written file with ffprobe and exit non-zero when its duration drifts from the draft's by more than one frame, or when it cannot be probed. The file is kept."
6650
+
},
6651
+
{
6652
+
"name": "ffprobe_cmd",
6653
+
"flags": [
6654
+
"--ffprobe-cmd"
6655
+
],
6656
+
"type": "path",
6657
+
"required": false,
6658
+
"description": "ffprobe binary for output verification."
Copy file name to clipboardExpand all lines: docs/command-reference.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,7 +27,7 @@
27
27
|`add-audio`|`capcut add-audio <project> <file-or-url> <start> [duration] [options]`| yes | Add a local or Wikimedia audio file on an audio track. |
28
28
|`add-video`|`capcut add-video <project> <file-or-url> <start> [duration] [options]`| yes | Add a local or Wikimedia video/image on a video track. |
29
29
|`add-text`|`capcut add-text <project> <start> <duration> <text> [options]`| yes | Add a text segment with font/color/position options. |
30
-
|`tts`|`capcut tts <project> [start] [duration] (--text <string> \| --text-file <path>) --tts-cmd <template> [options]`| yes | Synthesize a voiceover from text via a local TTS command (--tts-cmd) and add it as an audio segment. |
30
+
|`tts`|`capcut tts <project> [start] [duration] (--text <string> \| --text-file <path>) --tts-cmd <template> [--lexicon <file>] [options]`| yes | Synthesize a voiceover from text via a local TTS command (--tts-cmd) and add it as an audio segment. |
31
31
|`crop`|`capcut crop <project> <segment-id> [--ratio <r> \| --rect <x,y,w,h> \| --reset]`| yes | Read or set a video/photo segment's source-material crop (--ratio preset, --rect x,y,w,h, or --reset). |
32
32
|`cut`|`capcut cut <project> <start> <end> --out <path>`| yes | Extract a time range into a new standalone draft. |
33
33
|`duplicate`|`capcut duplicate <project> <segment-id> [--track <track-name>] [--new-track]`| yes | Duplicate a segment at its same timeline position onto a track above the source. |
@@ -51,11 +51,11 @@
51
51
|`apply-template`|`capcut apply-template <project> <template> <start> <duration> [text] [options]`| yes | Stamp a template into a project with new timing/text. |
52
52
|`make-preset`|`capcut make-preset <project> <text-segment-id> --out <preset.json>`| no | Extract a text segment's styling as a reusable preset JSON (apply via --preset). |
53
53
|`templates`|`capcut templates <project>`| no | List bundled reusable templates. |
54
-
|`batch`|`capcut batch <project> [--continue-on-error] < operations.jsonl`| yes | Run multiple edits from stdin (JSONL), one file write. |
54
+
|`batch`|`capcut batch <project> [--continue-on-error] [--plan <plan.json>] < operations.jsonl \| capcut batch <project> --apply-plan <plan.json>`| yes | Run multiple edits from stdin (JSONL), one file write. |
55
55
|`import-srt`|`capcut import-srt <project> <srt-or-> [options]`| yes | Import an SRT file/stdin as one text segment per cue. |
56
56
|`import-ass`|`capcut import-ass <project> <ass-or-> [options]`| yes | Import an ASS/SSA subtitle file as text segments, keeping inline overrides as per-range styles. |
57
57
|`text-ranges`|`capcut text-ranges <project> <id> --styles <json-or-@file>`| yes | Apply byte-accurate multi-style ranges to a text segment. |
58
-
|`caption`|`capcut caption <project> (--audio <path> \| --from-segment <id>) [options]`| yes | Transcribe audio via whisper into real caption-track segments. |
58
+
|`caption`|`capcut caption <project> (--audio <path> \| --from-segment <id> \| --words <file.json\|->) [options]`| yes | Transcribe audio via whisper into real caption-track segments. |
59
59
|`translate`|`capcut translate <project> --to <language> --out <path> [options]`| yes | Clone a draft into another language via the Anthropic API. |
60
60
|`migrate`|`capcut migrate <project> (--from <version> --to <version> \| --like <project> \| --from-store)`| yes | Apply known schema migrations across version boundaries. |
61
61
|`add-sfx`|`capcut add-sfx <project> <slug> <start> <duration> [options]`| yes | Add a sound effect on a dedicated track. |
0 commit comments