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

- `doctor` reports what each draft store holds — for every default CapCut/JianYing project directory it finds (or the one folder named with the new `--drafts <dir>`), a `draft-store` check counts the projects as readable, markerless, encrypted or unreadable. A JianYing 6.0+ store, where every project the app wrote is an encrypted payload, is now named once and up front (warn) with what still works — `init`, `quickstart` and `compile` build plaintext drafts from the bundled template — instead of being discovered one failed command at a time. Same classification as the `template.store` report of `init`/`quickstart`/`compile`. `capcut doctor --drafts <dir>` also makes the check usable on a machine without the app, and in CI.
- `examples/short-video-narration.md` (+ zh-CN) — silent clip → 9:16 draft with a TTS voiceover and script-accurate captions, as four commands (`quickstart --ratio 9:16` → `tts --text-file` → `caption --from-segment --script` → `lint`) and as one script, `examples/scripts/narrate-short.sh`. `examples/scripts/edge-tts-wav.sh` bridges edge-tts (MP3 only) to the WAV `tts` expects at `{out}`; any other engine plugs in through `--tts-cmd`. The vision-model step that writes the script is optional and stays outside the CLI: the script is a text file.

## [0.25.0] — 2026-09-18

Expand Down
1 change: 1 addition & 0 deletions docs/quickstart.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ capcut transition <草稿> <片段ID> "_3D空间" --jianying

## 接下来

- 想直接出一条带旁白和字幕的 9:16 短视频草稿:[短视频旁白示例](../examples/short-video-narration.zh-CN.md)(`quickstart` → `tts` → `caption` → `lint`,一个脚本串起来)。
- [命令参考(简体中文)](./command-reference.zh-CN.md) —— 全部命令一览
- [中文 README](../README.zh-CN.md) —— 项目总览、安装与赞助
- [examples/](../examples/) —— 端到端示例(英文:配音对齐、serve 自动化、批量字幕修正)
Expand Down
5 changes: 3 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ Copy-paste recipes for common CapCut / JianYing workflows. Every recipe is one s
| [keyframe-pan.md](./keyframe-pan.md) | Unfinished-pan keyframe pattern for epilogue / payoff stills |
| [verify-vo-alignment.md](./verify-vo-alignment.md) | Pre-flight check on ElevenLabs voiceover + word-level timestamps |
| [serve-automation.md](./serve-automation.md) | Wire the stateless JSONL queue runner into n8n / Make / Coze / Docker |
| [short-video-narration.md](./short-video-narration.md) · [中文](./short-video-narration.zh-CN.md) | Silent clip → 9:16 draft with a TTS voiceover and script-accurate captions |

All shell-only recipes assume `capcut` is on your `$PATH` (`npm install -g capcut-cli`).
The three keyframe / VO recipes ship with companion Python scripts under [`./scripts/`](./scripts/) — Python 3.9+, no external deps.
The three keyframe / VO recipes ship with companion Python scripts under [`./scripts/`](./scripts/) — Python 3.9+, no external deps. The narration recipe ships two shell scripts there (`narrate-short.sh`, `edge-tts-wav.sh`).

> **JianYing (剪映) users:** every recipe works on JianYing projects too — point `<project>` at the JianYing draft directory.

> **中文 / Chinese:** the project README has a Chinese translation at [`README.zh-CN.md`](../README.zh-CN.md). Translation of these recipes is pending.
> **中文 / Chinese:** the project README has a Chinese translation at [`README.zh-CN.md`](../README.zh-CN.md). The short-video narration recipe is written in both languages ([中文](./short-video-narration.zh-CN.md)); translation of the others is pending.
14 changes: 14 additions & 0 deletions examples/scripts/edge-tts-wav.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# edge-tts writes MP3 whatever the file is called; `capcut tts` expects the
# engine to write a WAV at {out}. This wrapper bridges the two:
# capcut tts <project> 0s --text-file script.txt \
# --tts-cmd "bash examples/scripts/edge-tts-wav.sh {text} {out} zh-CN-XiaoxiaoNeural"
# Args: <text> <out.wav> [voice]. Needs edge-tts (pip install edge-tts) and ffmpeg.
set -euo pipefail
text="${1:?text}"
out="${2:?out.wav}"
voice="${3:-${EDGE_TTS_VOICE:-zh-CN-XiaoxiaoNeural}}"
tmp="$(mktemp -t edge-tts-XXXXXX).mp3"
trap 'rm -f "$tmp"' EXIT
edge-tts --voice "$voice" --text "$text" --write-media "$tmp" >/dev/null
ffmpeg -v error -y -i "$tmp" -ar 24000 -ac 1 "$out"
83 changes: 83 additions & 0 deletions examples/scripts/narrate-short.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Silent clip -> narrated 9:16 draft, editable in CapCut / JianYing.
#
# bash examples/scripts/narrate-short.sh <clip.mp4> <script.txt> ["Draft name"] [--drafts <dir>] [--voice <edge-tts voice>]
#
# script.txt is the narration, plain text, one sentence per line. Write it by
# hand, or let any vision model describe the clip first (see the recipe). The
# script never needs a model: text in, draft out.
#
# Steps (each is one capcut command; run them by hand if you prefer):
# 1. quickstart --video <clip> --ratio 9:16 portrait draft with the clip on the main track
# 2. tts --text-file script.txt voiceover from the script via edge-tts (any --tts-cmd works)
# 3. caption --from-segment <voiceover> captions with whisper's timing and the script's wording
# 4. lint the draft is clean before it is opened in the app
# Needs: capcut (npm i -g capcut-cli), ffmpeg, edge-tts (pip install edge-tts).
# Optional: whisper for step 3 — without it the draft still has the clip and the voiceover.
# CAPCUT_TTS_CMD overrides the engine template ({out} required, {text} optional).
set -euo pipefail

clip="${1:?usage: narrate-short.sh <clip.mp4> <script.txt> [name] [--drafts <dir>] [--voice <voice>]}"
script="${2:?script.txt (one sentence per line)}"
name="旁白短视频"
shift 2
# A third positional that does not start with "--" is the draft name.
if [ $# -gt 0 ] && [ "${1#--}" = "$1" ]; then name="$1"; shift; fi
drafts=()
voice="${EDGE_TTS_VOICE:-zh-CN-XiaoxiaoNeural}"
while [ $# -gt 0 ]; do
case "$1" in
--drafts) drafts=(--drafts "$2"); shift 2 ;;
--voice) voice="$2"; shift 2 ;;
*) echo "unknown flag: $1" >&2; exit 2 ;;
esac
done
[ -s "$script" ] || { echo "script is empty: $script" >&2; exit 2; }
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Built separately: a "{out}" inside "${VAR:-...}" would close the expansion early.
default_tts="bash $here/edge-tts-wav.sh {text} {out} $voice"
tts_cmd="${CAPCUT_TTS_CMD:-$default_tts}"

# Run a capcut command; on failure print its message and stop. On success print its JSON.
run() {
local out
if ! out="$("$@" 2>/dev/null)"; then
echo "$1 ${2:-} failed:" >&2
"$@" 2>&1 >/dev/null | head -c 400 >&2 || true
echo "$out" | head -c 400 >&2
echo >&2
exit 1
fi
printf '%s' "$out"
}
# Read one top-level key from a JSON document on stdin (empty when absent or not JSON).
key() {
python3 -c "import json,sys
try: d = json.load(sys.stdin)
except Exception: sys.exit(0)
v = d.get('$1', '')
print(len(v) if isinstance(v, list) else v)"
}

# 1. Portrait draft with the clip on the main track.
project="$(run capcut quickstart "$name" --video "$clip" --ratio 9:16 "${drafts[@]}" | key draft_path)"
[ -n "$project" ] || { echo "quickstart returned no draft_path" >&2; exit 1; }
echo "draft: $project"

# 2. Voiceover from the script, placed at 0s.
segment="$(run capcut tts "$project" 0s --text-file "$script" --tts-cmd "$tts_cmd" | key segment_id)"
[ -n "$segment" ] || { echo "tts returned no segment_id" >&2; exit 1; }
echo "voiceover: segment $segment"

# 3. Captions: whisper supplies the timing, the script supplies the wording.
# Whisper missing is not fatal — the draft already has clip + voiceover.
if captions="$(capcut caption "$project" --from-segment "$segment" --script "$script" 2>&1)"; then
echo "captions: $(printf '%s' "$captions" | key segments) cue(s)"
else
echo "captions: skipped — $(printf '%s' "$captions" | key error | head -n 1 | cut -c1-160)"
fi

# 4. Lint, then open in the app (restart CapCut/JianYing so it lists the new draft).
capcut lint "$project" -H
echo
echo "Open CapCut / JianYing and pick \"$name\"."
68 changes: 68 additions & 0 deletions examples/short-video-narration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Short-video narration: silent clip → 9:16 voiced draft

English | [中文](./short-video-narration.zh-CN.md)

Three commands: `quickstart` creates a 9:16 draft with the clip on the main track, `tts` voices your script, `caption` writes captions with whisper's timing and your script's wording, then `lint` checks the result. No GUI in the loop; when you open the app, the video, voiceover and caption tracks are all still editable.

## You need

- capcut-cli (`npm install -g capcut-cli`, Node ≥ 18)
- ffmpeg
- a local TTS. The example uses [edge-tts](https://github.com/rany2/edge-tts) (`pip install edge-tts`, free neural voices); any command that can write a WAV plugs in through `--tts-cmd`
- optional: whisper (`pip install openai-whisper`) for the captions. Without it the draft still has the clip and the voiceover

## Inputs

```
clip.mp4 # silent footage, landscape or portrait (--ratio 9:16 sets the canvas)
script.txt # narration, one sentence per line
```

Write the script yourself, or let any vision model look at the clip first. For example, sample frames and hand them to whatever model CLI you use:

```bash
mkdir -p frames && ffmpeg -v error -i clip.mp4 -vf fps=1/2 frames/%02d.jpg
<your vision-model CLI> "Write a 5-sentence spoken narration for these frames, one sentence per line, no title" frames/*.jpg > script.txt
```

The model is optional: the script is a plain text file and a hand-written one works the same.

## One command

```bash
bash examples/scripts/narrate-short.sh clip.mp4 script.txt "Narrated short" --voice en-US-AriaNeural
```

`--drafts <dir>` names the draft library; without it the machine's default directory is used. The script only chains the four steps below.

## Step by step

```bash
# 1. Portrait draft, clip on the main track
capcut quickstart "Narrated short" --video clip.mp4 --ratio 9:16
# draft_path in the result is the draft folder

# 2. Voiceover at 0s. edge-tts only writes MP3 and `capcut tts` expects the engine
# to write a WAV at {out}, so examples/scripts/edge-tts-wav.sh converts in between
capcut tts "<draft_path>" 0s --text-file script.txt \
--tts-cmd "bash examples/scripts/edge-tts-wav.sh {text} {out} en-US-AriaNeural"
# segment_id in the result is the voiceover segment

# 3. Captions: timing from whisper, wording from your script
capcut caption "<draft_path>" --from-segment <segment_id> --script script.txt

# 4. Check
capcut lint "<draft_path>" -H
```

Restart CapCut / JianYing once so it lists the new draft, then open it.

## Other voices, other engines, caption styling

- edge-tts voices: `edge-tts --list-voices` (`--voice` or the `EDGE_TTS_VOICE` environment variable)
- other engines: any command that writes a WAV to `{out}`. `{text}` is optional; without it the script is piped to stdin (how piper works). macOS `say`: `--tts-cmd "say -o {out} --data-format=LEI16@24000 {text}"`; in the script, override with the `CAPCUT_TTS_CMD` environment variable
- caption styling: `caption --preset <preset.json>` (extract one with `make-preset` from a caption you styled in the app) or `--style-ref <segment-id>`; `--karaoke` for word highlighting

## JianYing 6.0+

A draft created this way is plaintext; JianYing 11.4 (macOS) is reported to open and upgrade it in place, other builds are unverified. Existing encrypted drafts are not read by this CLI; `capcut decrypt <project>` reports the state, and [jianying-encryption.md](../docs/jianying-encryption.md) has the background.
68 changes: 68 additions & 0 deletions examples/short-video-narration.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# 短视频旁白:无声素材 → 9:16 口播草稿

[English](./short-video-narration.md) | 中文

三条命令:`quickstart` 建一个 9:16 草稿并放入素材,`tts` 用你的文案生成旁白,`caption` 用 whisper 的时间轴配上你的文案生成字幕,最后 `lint` 检查。全程不开剪映界面;打开剪映时,视频轨、旁白轨、字幕轨都还是可编辑的。

## 你需要

- capcut-cli(`npm install -g capcut-cli`,Node ≥ 18)
- ffmpeg
- 一个本地 TTS。示例用 [edge-tts](https://github.com/rany2/edge-tts)(`pip install edge-tts`,微软中文神经网络音色,免费);任何能写出 WAV 的命令都可以通过 `--tts-cmd` 接入
- 可选:whisper(`pip install openai-whisper`),用于字幕。没有它,草稿仍然包含素材和旁白

## 输入

```
clip.mp4 # 无声素材(横竖都行,--ratio 9:16 决定画布)
script.txt # 旁白文案,一行一句
```

文案自己写,或者先让任意视觉模型看一遍素材再写。例如先抽帧,再把帧交给你手头的模型 CLI:

```bash
mkdir -p frames && ffmpeg -v error -i clip.mp4 -vf fps=1/2 frames/%02d.jpg
<你的视觉模型 CLI> "根据这些画面写一段 5 句的中文口播文案,一行一句,不要标题" frames/*.jpg > script.txt
```

模型不是必需的:文案就是一个普通文本文件,手写同样可以。

## 一键

```bash
bash examples/scripts/narrate-short.sh clip.mp4 script.txt "旁白短视频" --voice zh-CN-XiaoxiaoNeural
```

`--drafts <目录>` 指定草稿库;不指定就用本机的默认目录。脚本只是把下面四步串起来。

## 分步

```bash
# 1. 9:16 草稿,素材上主轨
capcut quickstart "旁白短视频" --video clip.mp4 --ratio 9:16
# 结果里的 draft_path 就是草稿目录

# 2. 旁白,放在 0s。edge-tts 只会写 MP3,capcut tts 要求引擎把 WAV 写到 {out},
# 所以经过 examples/scripts/edge-tts-wav.sh 转一次
capcut tts "<draft_path>" 0s --text-file script.txt \
--tts-cmd "bash examples/scripts/edge-tts-wav.sh {text} {out} zh-CN-XiaoxiaoNeural"
# 结果里的 segment_id 是旁白片段

# 3. 字幕:时间来自 whisper,文字来自你的文案。中文默认每条最多 16 个字,按字断句,不加空格
capcut caption "<draft_path>" --from-segment <segment_id> --script script.txt

# 4. 检查
capcut lint "<draft_path>" -H
```

重启一次剪映,让它读到新草稿,然后在草稿列表里打开它。

## 换音色、换引擎、改字幕样式

- edge-tts 中文音色:`edge-tts --list-voices | grep zh-CN`(Xiaoxiao、Yunxi、Yunyang……),用 `--voice` 或环境变量 `EDGE_TTS_VOICE` 指定
- 其他引擎:任何能把 WAV 写到 `{out}` 的命令都行。`{text}` 可选;模板里没有 `{text}` 时,文案从 stdin 传入(piper 就是这样用)。macOS 自带的 `say`:`--tts-cmd "say -o {out} --data-format=LEI16@24000 {text}"`,脚本里用 `CAPCUT_TTS_CMD` 环境变量覆盖
- 字幕样式:`caption --preset <preset.json>`(先用 `make-preset` 从你调好的字幕段提取)或 `--style-ref <segment-id>`;卡拉 OK 高亮加 `--karaoke`

## 剪映 6.0+ 用户

这样新建的草稿是明文,据报告剪映 11.4(macOS)打开后会就地升级,其他版本未验证。已有的加密草稿本 CLI 不读取,`capcut decrypt <草稿>` 会报告加密状态;来龙去脉见 [jianying-encryption.zh-CN.md](../docs/jianying-encryption.zh-CN.md)。
Loading