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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to capcut-cli are documented here. The format follows [Keep

## [Unreleased]

## [0.27.0] — 2026-10-03

### Added

- `relink --dir <folder> --recursive` searches nested media folders. Duplicate basenames are reported in `ambiguous` and left unchanged; prefix remapping matches whole path components, and directory symlinks are not followed.

### Fixed

- `replace-media` stages the requested bytes even when a different file with the same basename already exists. Occupied hash filenames are checked by content; dry runs create no asset directories or files, and directories/non-media segments are rejected before mutation.
- Replacement and relink refresh changed media's `local_material_id` links and imported-media entries when a readable `draft_meta_info.json` exists. Sidecar and timeline writes share conflict checks, backups, and rollback; existing and unrelated import entries are preserved. Bare timelines still use `register --materials --apply` to create registration metadata.
- `compile` derives target durations from rounded start and end boundaries, keeping adjacent fractional-second clips contiguous across text, video, audio, photos, and timed operations.
- Compile operation payloads are checked with the same builders used by real writes before creating output. Failed builds remove only the output directory created by that invocation; successful builds register in the project index after the draft is saved.
- `serve` binds a job ID to its effective command payload and execution settings. Identical submissions still deduplicate; reusing an ID for a different job returns a failure without executing the conflicting job.
- Queue project locks resolve project roots, relative paths, timeline files, and symlink aliases so concurrent writers to the same project serialize.
- Queue input and limits reject malformed or non-finite values. Combined child output is checked again at exit before reading it, so a fast process cannot bypass the configured capture threshold. Overflow results omit captured output; the threshold is polled during execution and is not a hard disk quota.
- `import-timeline` resolves relative media references against the OTIO document's directory and decodes local `file:` URLs, including escaped spaces. Remote URLs and inaccessible references remain placeholders.

## [0.26.1] — 2026-10-03

### Fixed
Expand Down
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,9 @@ The host reads a draft and passes its JSON as tool input. The component itself h

## Release notes

> **New in v0.26.0:** exact frame-grid lint/fix; character-level Chinese/Japanese script alignment with an optional match gate; explicit caption audio-stream selection; safe ripple delete and boundary shifts; scalable FFmpeg filter scripts; nested OTIO import; CRF/bitrate proxy controls; progressive word-reveal captions; and atomic whole-track `restyle`. Full details in the [changelog](./CHANGELOG.md).
> **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).

> **New in v0.25.0:** `caption` follows the transcript's script. Whisper's "words" for Chinese and Japanese are single characters or short tokens, so the Latin defaults (four words per cue, joined with spaces) produced fragments with spaces between the characters; cues are now joined without spaces and bounded by characters alone, at the width `lint` holds captions to (zh 16, ja 13, ko 16), and the result reports `caption_script`. An explicit `--max-words` / `--max-chars` still wins. Full details in the [changelog](./CHANGELOG.md).

> **New in v0.24.0:** captions in Chinese, Japanese and Korean are held to their own limits — `lint` flags a 32-character Chinese line and a 15 chars/s cue that the Latin defaults (42, 20) let through, and `--fix` re-wraps between characters (zh 16/9, ja 13/4, ko 16/12; an explicit `--max-chars` / `--max-cps` still applies everywhere). On a JianYing 6.0+ drafts folder, where every app-written project is encrypted, `init` / `quickstart` / `compile` now say that none could seed the new draft (`template.store`, a WARNING) and `lint` reports `template-unverified-store` instead of nothing. Plus a one-command agent install: `npx skills add renezander030/capcut-cli`. Full details in the [changelog](./CHANGELOG.md).
> **Fixed in v0.26.1:** ratio-only compile canvases and full source-media durations; JSON-escaped Windows fixture redaction with automatic leak verification; and active-timeline edits on the fixture-backed CapCut 8.7.0 Windows layout. A patched desktop round-trip remains pending. Full details in the [changelog](./CHANGELOG.md).

## Built with capcut-cli

Expand Down
6 changes: 2 additions & 4 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,9 @@ Claude Code 也可以把它作为插件加载:

## 发布说明

> **v0.26.0 新增:** 精确帧网格检查/修复;中日文按字对齐脚本并可设置匹配率门槛;字幕音轨选择;安全波纹删除和边界平移;大型 FFmpeg 滤镜脚本;嵌套 OTIO 导入;CRF/码率预览控制;逐词显现字幕;以及整轨原子化 `restyle`。完整说明见[更新日志](./CHANGELOG.md)。
> **v0.27.0 新增:** 按文件内容安全替换媒体;替换和重链接后自动更新媒体导入登记;递归搜索并报告同名歧义;精确处理小数秒时间边界;编译前校验操作并清理失败输出;将队列 ID 绑定到任务参数;统一项目锁;限制队列输出;以及解析 OTIO 的本地文件 URL 和相对路径。完整说明见[更新日志](./CHANGELOG.md)。

> **v0.25.0 新增:** `caption` 按转写文本的文字来分句。Whisper 对中文、日文给出的"词"是单个字或很短的片段,按拉丁默认(每句 4 词、用空格连接)会生成字与字之间带空格的碎片;现在中日文按字直接连接、只按字数上限分句,上限就是 `lint` 对字幕的行宽(zh 16、ja 13、ko 16),结果里会报告 `caption_script`。显式传入的 `--max-words` / `--max-chars` 仍然优先。完整说明见[更新日志](./CHANGELOG.md)。

> **v0.24.0 新增:** 中文、日文、韩文字幕按各自的规范检查 —— `lint` 会指出 32 字的中文单行和每秒 15 字的字幕(拉丁默认的 42 字 / 每秒 20 字会放过它们),`--fix` 按字重新折行(zh 16/9、ja 13/4、ko 16/12;显式传入 `--max-chars` / `--max-cps` 仍对所有文字生效)。在剪映 6.0+ 的草稿目录里(应用写出的项目全部加密),`init` / `quickstart` / `compile` 现在会明确说明没有任何项目可作为种子(`template.store` 与 WARNING),`lint` 会报告 `template-unverified-store` 而不是沉默。另外,一条命令即可把它装进 Agent:`npx skills add renezander030/capcut-cli`。完整说明见[更新日志](./CHANGELOG.md)。
> **v0.26.1 修复:** 仅指定比例的编译画布与完整源媒体时长;对 JSON 转义的 Windows 路径进行 fixture 脱敏并自动检查泄漏;以及基于真实 fixture 的 CapCut 8.7.0 Windows 活动时间线编辑。修复后的桌面应用往返验证仍待完成。完整说明见[更新日志](./CHANGELOG.md)。

## 使用 capcut-cli 构建

Expand Down
15 changes: 12 additions & 3 deletions docs/command-reference.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "capcut-cli",
"version": "0.26.1",
"version": "0.27.0",
"schema_version": 2,
"description": "Edit CapCut/JianYing draft_content.json directly. JSON in, JSON out.",
"global_flags": [
Expand Down Expand Up @@ -4531,7 +4531,7 @@
{
"name": "relink",
"summary": "Repair broken media paths (--dir or --from/--to).",
"usage": "capcut relink <project> (--dir <path> | --from <prefix> --to <prefix>) [--stage]",
"usage": "capcut relink <project> (--dir <path> [--recursive] | --from <prefix> --to <prefix>) [--stage]",
"positionals": [
{
"name": "project",
Expand All @@ -4552,7 +4552,16 @@
],
"type": "path",
"required": false,
"description": "Directory containing replacement files."
"description": "Directory containing replacement files; ambiguous basenames are reported and left unchanged."
},
{
"name": "recursive",
"flags": [
"--recursive"
],
"type": "boolean",
"required": false,
"description": "Search nested directories under --dir; directory symlinks are not followed."
},
{
"name": "from",
Expand Down
2 changes: 1 addition & 1 deletion docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
| `prune` | `capcut prune <project>` | yes | Remove materials no segment references. |
| `register` | `capcut register <project-dir> [--apply] [--materials] [--drafts <dir>]` | yes | Repair an existing draft's registration metadata (draft_meta_info.json + root_meta_info.json entry) from a read-only draft_content.json so the CapCut app lists it (plan by default; --apply writes with .bak); --materials also registers the timeline's media in draft_materials (the CapCut 9.1 relink-prompt fix). |
| `rename` | `capcut rename <project> <new-name> [--drafts <dir>]` | yes | Rename a draft after creation: the folder on disk plus draft_name and every self-referential path in draft_meta_info.json and the store's root_meta_info.json entry, transactionally (refuses when the target folder exists). |
| `relink` | `capcut relink <project> (--dir <path> \| --from <prefix> --to <prefix>) [--stage]` | yes | Repair broken media paths (--dir or --from/--to). |
| `relink` | `capcut relink <project> (--dir <path> [--recursive] \| --from <prefix> --to <prefix>) [--stage]` | yes | Repair broken media paths (--dir or --from/--to). |
| `replace-media` | `capcut replace-media <project> <segment-id> <new-file> [--retime]` | yes | Swap a segment's source file (placeholder > final) keeping its timing, effects, and keyframes. |
| `timeline` | `capcut timeline <project> [--cols <number>]` | no | Show the track/segment layout (JSON, or -H ASCII bars). |
| `projects` | `capcut projects [query] [--drafts <path>] [--names]` | no | List CapCut/JianYing draft folders on disk. |
Expand Down
2 changes: 1 addition & 1 deletion docs/command-reference.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
| `prune` | `capcut prune <project>` | 是 | 删除没有任何片段引用的素材。 |
| `register` | `capcut register <project-dir> [--apply] [--drafts <dir>]` | 是 | 从只读的 draft_content.json 修复已有草稿的注册元数据(draft_meta_info.json + root_meta_info.json 条目),让 CapCut 应用能列出它(默认只出计划;--apply 写入并留 .bak)。 |
| `rename` | `capcut rename <project> <new-name> [--drafts <dir>]` | 是 | 在创建后重命名草稿:磁盘上的文件夹,加上 draft_meta_info.json 和存储 root_meta_info.json 条目里的 draft_name 及所有自引用路径,事务性完成(目标文件夹已存在时拒绝)。 |
| `relink` | `capcut relink <project> (--dir <path> \| --from <prefix> --to <prefix>)` | 是 | 修复失效的媒体路径(--dir 或 --from/--to)。 |
| `relink` | `capcut relink <project> (--dir <path> [--recursive] \| --from <prefix> --to <prefix>) [--stage]` | 是 | 修复失效的媒体路径(--dir 可递归搜索;同名歧义会报告并保持原路径;--from/--to 按路径边界映射)。 |
| `replace-media` | `capcut replace-media <project> <segment-id> <new-file> [--retime]` | 是 | 替换片段的源文件(占位素材 → 成片素材),保留其时间、特效与关键帧。 |
| `timeline` | `capcut timeline <project> [--cols <number>]` | 否 | 显示轨道/片段布局(JSON;-H 显示 ASCII 条形图)。 |
| `projects` | `capcut projects [query] [--drafts <path>] [--names]` | 否 | 列出磁盘上的 CapCut/剪映草稿文件夹。 |
Expand Down
59 changes: 59 additions & 0 deletions docs/reviews/v0.27.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# v0.27.0 review guide

This release makes local media edits, draft compilation, queued automation, and
OTIO imports reliable at their file and timing boundaries.

| Behavior | Regression coverage |
|---|---|
| Same-name replacement uses the requested bytes; dry-run creates no assets | `test/replace.test.mjs` |
| Replacement/relink preserve imported entries and update local IDs in a guarded transaction | `test/media-registration-edit.test.mjs`, `test/changed-media-registration.test.mjs` |
| Nested media search reports duplicate basenames and respects prefix boundaries | `test/relink-recursive.test.mjs` |
| Fractional clip boundaries remain contiguous at 136 BPM | `test/compile-fractional-timing.test.mjs` |
| Invalid operations fail before creation; failures remove only their owned output | `test/compile-safety.test.mjs` |
| Identical queue IDs deduplicate; conflicting payloads never execute | `test/serve-boundaries.test.mjs` |
| Relative, file, directory, symlink, and active-timeline project aliases serialize | `test/serve-boundaries.test.mjs` |
| Fast output and stale size observations cannot bypass bounded result reads | `test/serve-boundaries.test.mjs` |
| Relative OTIO paths and local file URLs find real media from another working directory | `test/otio-media-paths.test.mjs` |

## Verify from source and from the package

```bash
npm ci
npm test
npm run lint
npm run docs:commands
npm --prefix wasm/capcut-core ci
npm run wasm:verify
npm pack --pack-destination /tmp
npm run smoke:package -- /tmp/capcut-cli-0.27.0.tgz .
```

The package smoke requires `ffprobe` on PATH. It installs the tarball into a fresh
temporary directory, exercises CLI and library entry points, and checks compile
metadata/timing/preflight, replacement bytes/import IDs, queue ID conflicts,
fixture redaction, and active-timeline edits/restoration. It removes its temporary
projects afterward. On Windows, substitute a writable temporary tarball directory
for `/tmp`.

## Scope and limits

Queue IDs and locks last for one invocation. They cover declared project paths and
explicit destinations; implicit compile destinations, `compile --data` outputs,
separate runners, and shared library-index writes need external serialization.
Output limits are checked during execution and at exit; they are capture thresholds,
not hard disk quotas. Overflow jobs are not retried.

Media registration refresh requires a readable sidecar. Bare timeline files retain
their existing behavior and can use `register --materials --apply` to create one.
Existing imports remain intact; a reused import is conflict-checked without rewriting
or backing up an unchanged sidecar.

The existing version guards and fixture evidence limits remain in place. These
checks validate file behavior; a patched CapCut 8.7.0 Windows desktop round-trip
remains pending as recorded in [version support](../version-support.md).

The release package is `capcut-cli` on npm. The Python client continues to forward
commands to that CLI and has no source change in this release. The optional Wasm
component remains hosted through Wassette; this release adds no standalone MCP
server package. GitHub release artifacts can include the verified component and
checksum after maintainer review.
22 changes: 22 additions & 0 deletions examples/serve-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,25 @@ Build the image from this repo with `docker build -t capcut-cli .`.
> process to babysit. A queue runner that starts, drains, and exits composes with the
> retry/idempotency model your automation tool already has. If you genuinely need HTTP,
> put `serve` behind a one-line handler that pipes the request body to it.

## Queue identity and capture limits

IDs last for one queue drain. An ID binds to the effective argv and execution settings
(timeout, retries, backoff, and output threshold). Repeating that payload returns the
original result with `deduplicated: true`; reusing the ID for different arguments or
settings returns a failure without executing the conflicting job. Use a new ID for
an intentional new edit.

Project positionals and explicit project destinations are locked by their resolved
project root, including relative paths, timeline files, and symlink aliases. Locks
are local to this queue invocation; separate runners and the desktop editor do not
share them. Commands with implicit destinations, such as `compile --data`, should be
run separately or with one worker.

`cmd`, `id`, and `project` must be non-empty strings when present; `args` must be an
array of strings. Workers must be an integer from 1 to 32, timeout a positive integer
in milliseconds, and retries/backoff non-negative integers. `--max-buffer-mb` sets a
positive combined stdout/stderr capture threshold. It is checked every 25 ms and
again at exit before reading the files, so it is not a hard disk quota. An overflow
result has `ok: false` and `overflow: true`, omits captured output, and is not retried.
Other failed jobs keep the configured retry policy.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "capcut-cli",
"version": "0.26.1",
"version": "0.27.0",
"description": "Independent, unofficial CLI to create and edit CapCut projects — build drafts from scratch, add video/audio/text, subtitles, timing, speed, volume, templates, cut long-form to shorts. No API needed. Not affiliated with ByteDance.",
"type": "module",
"bin": {
Expand Down Expand Up @@ -43,6 +43,7 @@
"dev": "node --import tsx src/index.ts",
"extract-enums": "python3 scripts/extract-enums.py",
"test": "npm run build && node --test --test-reporter=spec",
"smoke:package": "node scripts/release-smoke.mjs",
"test:fast": "node --test --test-reporter=spec",
"lint": "biome check --error-on-warnings src/ test/",
"lint:fix": "biome check --write src/ test/",
Expand Down
Loading
Loading