diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c1bd39..896ebeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `), 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 ` 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 diff --git a/docs/quickstart.zh-CN.md b/docs/quickstart.zh-CN.md index 93f1f53..241f6bc 100644 --- a/docs/quickstart.zh-CN.md +++ b/docs/quickstart.zh-CN.md @@ -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 自动化、批量字幕修正) diff --git a/examples/README.md b/examples/README.md index 8c9c771..f2dff91 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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 `` 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. diff --git a/examples/scripts/edge-tts-wav.sh b/examples/scripts/edge-tts-wav.sh new file mode 100755 index 0000000..17a132c --- /dev/null +++ b/examples/scripts/edge-tts-wav.sh @@ -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 0s --text-file script.txt \ +# --tts-cmd "bash examples/scripts/edge-tts-wav.sh {text} {out} zh-CN-XiaoxiaoNeural" +# Args: [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" diff --git a/examples/scripts/narrate-short.sh b/examples/scripts/narrate-short.sh new file mode 100755 index 0000000..a02e847 --- /dev/null +++ b/examples/scripts/narrate-short.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# Silent clip -> narrated 9:16 draft, editable in CapCut / JianYing. +# +# bash examples/scripts/narrate-short.sh ["Draft name"] [--drafts ] [--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 --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 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 [name] [--drafts ] [--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\"." diff --git a/examples/short-video-narration.md b/examples/short-video-narration.md new file mode 100644 index 0000000..2cafa6c --- /dev/null +++ b/examples/short-video-narration.md @@ -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 + "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 ` 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 "" 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 "" --from-segment --script script.txt + +# 4. Check +capcut lint "" -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 ` (extract one with `make-preset` from a caption you styled in the app) or `--style-ref `; `--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 ` reports the state, and [jianying-encryption.md](../docs/jianying-encryption.md) has the background. diff --git a/examples/short-video-narration.zh-CN.md b/examples/short-video-narration.zh-CN.md new file mode 100644 index 0000000..c3de7c6 --- /dev/null +++ b/examples/short-video-narration.zh-CN.md @@ -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 "" 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 "" --from-segment --script script.txt + +# 4. 检查 +capcut lint "" -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 `(先用 `make-preset` 从你调好的字幕段提取)或 `--style-ref `;卡拉 OK 高亮加 `--karaoke` + +## 剪映 6.0+ 用户 + +这样新建的草稿是明文,据报告剪映 11.4(macOS)打开后会就地升级,其他版本未验证。已有的加密草稿本 CLI 不读取,`capcut decrypt <草稿>` 会报告加密状态;来龙去脉见 [jianying-encryption.zh-CN.md](../docs/jianying-encryption.zh-CN.md)。