Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,29 @@

All notable changes to capcut-cli are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.33.0] — 2026-11-06

### Added

- `diff <project> --since-write` shows what changed in a draft since capcut-cli last wrote it, such as what CapCut rewrote when it opened or saved a tool-written draft. It prints the usual `diff` result with `a` as written and `b` as the draft is now, plus `since_write` with `written_at`, `written_by` (the command that made the write), `written_sha256`, `current_sha256` and `changed` (false when the file is exactly what the CLI wrote, which stderr also says), `top_level_changed` (the document fields that differ, such as `update_time`) and `track_order_changed`. Every write now keeps a copy of what it wrote as `<file>.last-write` in the draft's `.capcut-cli-history/` directory, one file per draft file, replaced atomically on each write and on `restore`; snapshots, `.bak`, `restore` and `restore --list` are unchanged. A draft this version has not written yet has no record, and `--since-write` exits 1 saying so. `diff <a> <b>` is unchanged.
- `fixture --anonymize` builds a bundle you can share without showing what the draft is about. Media file names become `media-001.mp4`, `media-002.mp3`, … (each keeps its extension, and the same file gets the same name in every mirror the bundle carries: `draft_content.json`, `draft_info.json`, `template-2.tmp`, `draft_meta_info.json` and the nested `Timelines/` documents). Folder names on media and project paths become `folder-001`, … (standard folders such as `Desktop` and the app's own stay), and the project name becomes `project`. Every text is replaced character by character with a placeholder of the same length and script (CJK, kana and Hangul with 字, Latin letters with x/X by case, digits with 0; spaces, punctuation and style ranges kept), so line wrapping and style range offsets behave as in the original. Only the replaced strings change; every other byte of each file stays as it was. `fixture` never copies media, so no media file is included either way. The report records `anonymized: true` and counts what was replaced, and the redaction check runs on the anonymized bundle.
- `translate --cloud` and `caption --translate <lang>` (experimental, unofficial) translate with the subtitle translation behind CapCut's auto-captions, on ByteDance's servers and without an API key. `translate --cloud` writes the translated draft as the Anthropic path does; `caption --translate` translates the cues before they are written and keeps CapCut's timings for the translated words, so `--karaoke` and `--word-reveal` work in the target language. Language names such as `Vietnamese`, `deutsch` or `simplified chinese` map to CapCut's codes. Like `caption --cloud`, it calls CapCut's private endpoints as the desktop app does, so ByteDance can change or refuse them at any time. Every run says on stderr how many lines go to ByteDance, and `--dry-run` sends nothing (`caption --translate --dry-run` reports the lines under `translation`). A reply with more or fewer lines than were sent is refused instead of pairing translations with the wrong timings, and a line that comes back without a translation keeps its original text and is counted in `kept_original` ([#169](https://github.com/renezander030/capcut-cli/pull/169), contributed by @datnt-97).
- `loudness` can gate a delivery, so a script stops before exporting a mix that misses its target. `--tolerance <LU>` (with `--target`) fails the run when the estimated loudness sits further than that from the target, and `--max-true-peak <dBTP>` fails it when the loudest clip's true peak, at its volume, is above the ceiling. With `--apply` the mix after this run's gain is judged, `--max-gain-db` cap included; without it, the mix as it stands. A miss exits 1 with `ok: false` and the reason on one stderr line, and the result's `gate` carries the target, tolerance, estimate and each verdict. With `--apply` the gain is written either way (journaled, `restore` undoes it). Without either flag the result and exit code are unchanged.
- `lint --max-still-secs <n>` and `--max-shot-secs <n>` check pacing. A still image (a photo material) that plays longer than n seconds on the timeline is reported as `still-too-long`, and a video clip longer than n seconds, overlay tracks included, as `shot-too-long`. Both are warnings (exit 1) that name the segment, its track, where it starts and how long it plays; neither is fixable, since where to cut is an editing decision. Neither check runs unless its flag is given, so lint's default result is unchanged.
- `lint --min-silent-secs <n>` reports `silent-stretch` (warning) for every range of n seconds or longer, up to the end of the timeline, where nothing audible plays: no audio clip above volume 0 and no video clip with its own sound. A clip counts as silent only when the draft says so (volume 0 without volume keyframes, a muted track, a photo, or a video whose sound was separated to an audio track), so a video clip whose file may have no sound still counts as sound and the check never reports silence that might not be there. The message gives the range's start, end and length. Not fixable, and off unless the flag is given.
- `caption` checks that its cues cover the speech it transcribed. After transcribing (local Whisper or `--cloud`), ffmpeg's silencedetect measures the non-silent spans of the audio that was transcribed (the extracted stream with `--audio-stream`), the cue spans are subtracted in the audio's own time, and the result carries `coverage: {speech_s, captioned_s, ratio, uncaptioned: [{start_s, end_s}], tail_uncaptioned_s}`. `uncaptioned` lists the speech no cue covers that lasts at least `--min-uncaptioned` seconds (default 1.5), and `tail_uncaptioned_s` is the part of it after the last cue. When any span is listed, one `[uncaptioned-speech]` warning on stderr names them, so a transcription that stops before the end of the audio, or drops quiet speech, no longer passes unnoticed. The exit code is unchanged. Without ffmpeg the block is `{skipped: "ffmpeg not found"}` and the run goes on; `--no-coverage` skips the check, and `--words` runs, which read no audio, have none.
- `caption` caches what it transcribed, so a run that only changes the styling, the grouping, `--script` or `--translate` does not transcribe again, and with `--cloud` does not upload again. The raw transcription (cues, words, engine) is stored under the sha256 of the exact audio bytes that were transcribed (after `--audio-stream` extraction) together with the engine, Whisper command, model, language, word-timing mode and `--cloud`, so changing any of those transcribes afresh. The result carries `transcript_cache: "hit" | "miss" | "off"`. Entries live in `CAPCUT_CLI_CACHE_DIR` if set, else `$XDG_CACHE_HOME/capcut-cli/transcripts`, else `~/.cache/capcut-cli/transcripts` (`%LOCALAPPDATA%\capcut-cli\cache\transcripts` on Windows), one JSON file per key written atomically; an entry that cannot be read or does not match its key is ignored and rewritten. `--no-cache` neither reads nor writes it, and `--dry-run` reads it but never writes it.

### Changed

- `render --burn-captions` draws each caption in its text material's own font file instead of ffmpeg's default face. The font is the first style block's `font.path` in the material's content (what CapCut renders from), else the material's `font_path`, and it is passed to drawtext as `fontfile=` when the file exists and is a `.ttf`, `.otf` or `.ttc`; the path is escaped so commas, brackets, colons and backslashes in it stay inside the option. A caption whose font is missing, of another type or not named keeps the default face as before. The render result, `--dry-run` plans included, reports `caption_fonts: {from_draft, default}`.
- The Python client's 3.9 job runs on `ubuntu-24.04`. `ubuntu-latest` moved to 26.04, for which `setup-python` has no 3.9 build, so the job failed before it ran a test; the package still supports Python 3.9.

### Fixed

- `text-style` applies `--font-size`, `--color`, `--align`, `--x` and `--y` without `--preset`. The flags were declared (and shared with `add-text`) but only honoured through a preset, so a plain `text-style <project> <id> --font-size 22 --color #FFD60A` exited 0 and left the text unchanged. Font size and colour are written to the material and to every style block CapCut renders from, `--x`/`--y` move the segment as `restyle` does, and `--color` must be `#RRGGBB`. `compile`'s `text-style` operation accepts and validates `fontSize`, `alignment` and `color` too.
- Writes under Volta no longer stop with `[editor-open]`. The editor-open guard compared process names only, so the shim named `capcut` that starts node was taken for the CapCut editor and every `--apply` was refused. The guard now leaves out the CLI's own process and each of its parents (on Windows, where `tasklist` lists no parent ids, the direct parent) and still finds an editor running beside it ([#168](https://github.com/renezander030/capcut-cli/issues/168)).
- `fixture --out` requires a new or empty directory outside the source project. It refuses occupied destinations and output symlinks instead of modifying original drafts, overwriting unrelated files, or leaving private media in a bundle reported as media-free. Bundles are built in a temporary sibling directory and published only after generation and redaction inspection finish; a generation failure leaves a new destination absent or an existing empty destination empty. Existing redaction findings remain available for manual review.

## [0.32.0] — 2026-10-29

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ The host reads a draft and passes its JSON as tool input. The component itself h

## Release notes

> **New in v0.32.0:** time arguments reject malformed or unsafe values before writing; translation responses must contain exactly one string per input; and subtitle imports reject malformed UTF-8 instead of silently corrupting caption text. Full details in the [changelog](./CHANGELOG.md).
> **New in v0.33.0:** `caption` reports speech its cues leave uncaptioned and reuses a cached transcription when only the styling changes; `loudness --tolerance` and `--max-true-peak` gate a delivery with an exit code; `lint` gains opt-in pacing and silent-stretch checks; `diff --since-write` shows what CapCut changed after the CLI's last write; `fixture --anonymize` makes a shareable bundle; and `render --burn-captions` draws captions in the draft's own font. Full details in the [changelog](./CHANGELOG.md).

> **New in v0.31.0:** experimental, unofficial `caption --cloud` that transcribes without a local Whisper install by uploading the audio to ByteDance's CapCut servers ([#157](https://github.com/renezander030/capcut-cli/pull/157)), uploads nothing in a dry run, retries flaky requests and waits as long as the audio needs; loudness to a delivery target (`loudness --target -14`, applied as segment volume and undoable); faster compiles that reuse media hashes across repeated cuts; and three `lint` checks for clips that play silent after a copy, keyframes on trimmed or re-timed clips, and `UNIFORM_SCALE` zooms on JianYing 11.6+. Full details in the [changelog](./CHANGELOG.md).

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Claude Code 也可以把它作为插件加载:

## 发布说明

> **v0.32.0 更新:** 时间参数会在写入前拒绝格式错误或超出安全范围的值;翻译结果必须为每条输入提供一个字符串;字幕导入拒绝无效的 UTF-8,避免静默破坏字幕文本。完整说明见[更新日志](./CHANGELOG.md)。
> **v0.33.0 更新:** `caption` 会报告字幕未覆盖的语音,并在只改样式时复用缓存的转写结果;`loudness --tolerance` 和 `--max-true-peak` 以退出码把关交付响度;`lint` 新增可选的节奏与静音段检查;`diff --since-write` 显示 CapCut 在 CLI 上次写入后改动了什么;`fixture --anonymize` 生成可分享的匿名包;`render --burn-captions` 使用草稿自身的字体绘制字幕。完整说明见[更新日志](./CHANGELOG.md)。

> **v0.31.0 新增:** 实验性、非官方的 `caption --cloud`:无需本地 Whisper,将音频上传到字节跳动的 CapCut 服务器转写([#157](https://github.com/renezander030/capcut-cli/pull/157)),dry run 中不上传任何内容,请求失败会自动重试,等待时间随音频长度增加;按发布平台的响度目标调整音量(`loudness --target -14`,写入片段音量,可撤销);同一素材的重复剪辑复用内容哈希,编译更快;新增三项 `lint` 检查:复制后无声的片段、裁剪或变速片段上的关键帧,以及剪映 11.6 及以上版本中的 `UNIFORM_SCALE` 缩放。完整说明见[更新日志](./CHANGELOG.md)。

Expand Down
Loading
Loading