Repository navigation
Releases: renezander030/capcut-cli
Release list
v0.29.0 — aligner word timings, reviewed batch plans, render fidelity and lint checks
v0.29.0
Added
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.cppsegments[].words[], WhisperXword_segments[], or a plain{word|text|char, start, end}array with times in seconds,start_ms/end_msorstart_time/end_time(per-character forced-aligner output). The result reportswords_format,words_skippedandsource_words. Entries without timing are skipped; entries that end before they start or go back in time are refused withrefused [words-invalid]naming the entry. Grouping, CJK joining without spaces,--script,--karaokeand--word-revealbehave exactly as with Whisper words.--wordscannot be combined with--audio,--from-segment,--audio-stream,--ffmpeg-cmdor--whisper-*.batch <project> --plan <plan.json> < ops.jsonlvalidates the operations under dry-run without touching the draft and writes a reviewable plan:project,draft_sha256, the normalizedoperations,operations_sha256(over key-sorted JSON) and a per-operationpreview.batch <project> --apply-plan <plan.json>reads no stdin and applies the plan through the normal transactional batch. It refuses withrefused [plan-draft-changed]when the draft changed since the plan was written,refused [plan-tampered]when the operations no longer match their hash, andrefused [plan-project-mismatch]when the plan names another project. Without--continue-on-errora failing operation writes no plan.render(including--dry-run) reports afidelitycensus of what the proxy leaves out compared with the app:faithful,expected_duration_us, thecheckedcategories, 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 --strictrefuses withrefused [render-unfaithful]before anything is written.- After a real render,
renderprobes the written file with ffprobe (--ffprobe-cmdselects the binary) and reportsverification:{ 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 reportsverified: falsewith a reason.render --verifyexits non-zero on drift beyond tolerance or an unverifiable file; the file stays on disk and the JSON result is still printed. 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 (SQLnever fires insideMySQLite; CJK rules match anywhere). Malformed lexicons refuse withrefused [lexicon-invalid], and equal-length rules that say different things at the same position refuse withrefused [lexicon-ambiguous], both before any engine runs. The result reportslexicon: { rules, applied, spoken_text }with offsets in UTF-16 code units into the trimmed text. Works with--textand--text-file.lintreportssegment-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 --fixrepairs 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 keepcaption-overlap.lintreportssegment-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 intocanvas_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.- Every undo-history snapshot now records what made it:
.capcut-cli-history/<draft>.journal.jsonlgets{ index, time, command, argv, before_sha256, after_sha256 }per write (library callers recordcommand: null), trimmed together with the snapshots.restore --listshowscommand,argvandtimeper step; older snapshots without a journal line list with nulls. A torn or unreadable journal never breaks a write or a restore. - Whisper-free caption routes are documented in
docs/quickstart.zh-CN.md(不装 Whisper 的字幕路线) andexamples/short-video-narration.md: the app's own auto captions followed byrestyle/export-srt,import-srt, andcaption --words.
Changed
doctor's Whisper hint namescaption --wordsandimport-srtas routes that work without Whisper. The check's status is unchanged.lint --frame-grid --fixderives 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.
This coordinated release comprises npm [email protected] and the Wasm component attached here with its checksum, license, and third-party notices. The Python client is unchanged (PyPI capcut==0.1.3).
Validation: 1,210 CLI tests pass with one platform-specific skip, and all 13 CI jobs pass. The packed tarball passes the release smoke test.
Merged in #142. All 13 checks pass on the tagged source: CI.
v0.28.0 — active timelines, app-created projects and client fixes
v0.28.0
Added
-
--active-timelineexplicitly follows a validatedTimelines/project.jsonpointer on app builds without a verified storage fixture (#50). Invalid, deleted, symlinked or conflicting selected timelines are refused; normal defaults and write guards remain unchanged. Writes keep root and active IDs and preserve other timelines. -
compile --into <project>populates an existing empty project created in CapCut (#52). It preserves project identity, per-document app markers and unknown settings, stages content-correct media, and commits imported-media registration with timeline writes. Failed copies and commits clean up only newly owned assets; the shared store index is never rewritten.--check,--planand--dry-runvalidate both spec and destination without writing. Nested layouts require active selection. Desktop acceptance on these opt-in paths remains unverified. -
describe --compactemits a small command discovery index with names, summaries, usage, and write status.describe --command <name>returns complete contracts for selected commands; repeat the flag for several names. The full v2 command contract remains the default. Python client v0.1.3 forwards these options throughcapcut.describe(compact=True, command="compile").
Fixed
- Python client v0.1.3 parses quoted Windows
CAPCUT_CLIpaths without retaining surrounding quotes or losing backslashes. Escaped quotes, trailing backslashes, and empty arguments follow Windows argv quoting; malformed quotes fail before spawning a process. POSIX command quoting and shell-free execution remain unchanged. Python client regression tests now run on Linux, macOS, and Windows, including Python 3.9 and 3.14.
This coordinated release comprises npm [email protected], PyPI capcut==0.1.3, and the Wasm component attached here with its checksum, license, and third-party notices.
Validation: 1,134 CLI tests pass with one platform-specific skip, 19 Python tests pass, and all 13 CI jobs pass. Packed npm installation, real FFmpeg compilation into an app-created shell, Python wheel integration, and twine check pass.
The new opt-in CapCut paths still require a desktop open/save/close/reopen acceptance check; issues #50 and #52 remain open.
Merged in #139. All 13 checks pass on the tagged source: CI.
v0.27.0
v0.27.0 — 2026-10-03
Added
relink --dir <folder> --recursivesearches nested media folders. Duplicate basenames are reported inambiguousand left unchanged; prefix remapping matches whole path components, and directory symlinks are not followed.
Fixed
replace-mediastages 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_idlinks and imported-media entries when a readabledraft_meta_info.jsonexists. Sidecar and timeline writes share conflict checks, backups, and rollback; existing and unrelated import entries are preserved. Bare timelines still useregister --materials --applyto create registration metadata. compilederives 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.
servebinds 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-timelineresolves relative media references against the OTIO document's directory and decodes localfile:URLs, including escaped spaces. Remote URLs and inaccessible references remain placeholders.
Install: npm install -g [email protected]
The optional capability-free WebAssembly component and its checksums and license notices are attached below. It provides read-only inspection, linting, and diff operations through a compatible host.
v0.26.1 — active timelines, compile fixes, and fixture redaction
Fixes
fixtureredacts JSON-escaped Windows home paths, including embedded JSON, and automatically checks every generated bundle for recognizable residual private values. Failed checks return a nonzero exit status and are recorded inSANITIZE_REPORT.json(#134).compilehandles ratio-only and explicit-dimension canvases consistently. Material and sidecar durations use the full probed source duration; segments keep their requested lengths and source in-points. Invalid speed-adjusted source ranges are refused before creating a draft (#133).- On the fixture-backed CapCut 8.7.0 Windows layout, the CLI follows the validated active timeline pointer and synchronizes its readable mirrors and root copies while preserving document IDs and other timelines. Assets and metadata stay at the project root;
sync-timelinesrepairs in the active → root direction (#50).
Install
npm install -g [email protected]
capcut --versionVerification and compatibility
All 1,017 tests passed locally. CI passed on Node 18/20/22 and Windows/macOS/Linux, including the capability-free Wasm component. The packed npm release was installed separately and checked against the three fixes.
The active-timeline change is scoped to the evidenced Windows 8.7.0 layout. Other builds retain their existing behavior. A patched CapCut desktop open/close confirmation remains pending in #50; file-level tests do not establish desktop compatibility.
See the end-to-end verification guide and changelog.
v0.26.0
Highlights
- Frame-grid linting and repair with
lint --frame-grid --fix. - Character-level Chinese/Japanese script alignment and a
--min-script-matchwrite gate. - Multi-stream transcription through
caption --audio-stream. - Boundary-safe ripple removal and partial-timeline shifting.
- Automatic FFmpeg filter scripts for large proxy renders.
- Nested OTIO timeline import.
- CRF or target-bitrate render controls.
- Editable progressive captions with
caption --word-reveal. - Atomic project-wide text styling with
restyle --preset.
Also included: richer draft-store diagnostics in doctor, a complete narrated-short workflow, and the new dependency-free Python client.
Install
npm install -g [email protected]
pip install capcut==0.1.1See CHANGELOG.md for full behavior and compatibility details.
v0.25.0
Added
captionfollows 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 in Chinese and Japanese are now joined without spaces and bounded by characters alone, at the widthlintholds captions to (16 zh, 13 ja; Korean keeps spaces and four words at 16); karaoke ranges follow the new offsets. The result reportscaption_script(latin|zh|ja|ko). An explicit--max-words/--max-charsapplies as given. Library:groupWordstakes aseparator;wordSeparator,groupingDefaultsandGroupingDefaultsare exported; the script detection moved toscript.ts(still exported from the lint entry points).caption --max-charsis documented in the command reference alongside--max-words.- The Chinese README notes the feature in 科技爱好者周刊 issue 413.
Unchanged
--scriptalignment still tokenizes the script file on whitespace; a Chinese script line is one token to it.
v0.24.0
Added
lintholds captions written in Chinese, Japanese or Korean to their own limits: 16 characters per line and 9 per second for Chinese, 13 and 4 for Japanese, 16 and 12 for Korean, in place of the Latin 42 and 20 that let a 32-character Chinese line pass. Messages name the applied default (>16, zh default),--fixre-wraps between characters, and an explicit--max-chars/--max-cpsapplies to every script. Library:captionScript,captionLimits,scriptLimitsExcept,CJK_SCRIPT_LIMITS, andLintOptions.scriptLimits(nullkeeps the Latin limits everywhere).init,quickstartandcompilereport what the drafts folder held when the skeleton was chosen (template.store:projects,readable,markerless,encrypted,unreadable). When every project is encrypted (JianYing 6.0+) and nothing could seed the draft, they say so — a WARNING on stderr,template.warningin the JSON, the quickstartcreatestep and the compilewarningslist — and name what is known about the bundled template on that app.lintreportstemplate-unverified-store(info) for a bundled-template draft in a drafts folder whose projects are all encrypted: nothing could have seeded it and no version can be compared, so it is unverified for that app rather than stale.- README (English and Chinese) documents the one-command agent install,
npx skills add renezander030/capcut-cli, and the Claude Code plugin route. Thecapcut-editskill also triggers on Chinese requests (剪映, 字幕, 草稿).
Changed
- English and Chinese quickstarts now link to the maintainer's GitHub profile for more practical AI agent tools.
- Claude Code plugin manifest 0.2.0: description and keywords match the current command surface.
v0.23.0 — drafts that open on modern builds, local_material_id links, template-stale repair
Nine items, mined the same way as 0.22: this repo's threads, the forks
ahead of it, the downstream projects that build on it and the neighbouring
CapCut/JianYing tools, each checked against what the CLI already did before
it was built. This cycle one pain dominated everything else, and it is the
oldest one in the tracker: a draft built from the bundled template does not
open on a modern app build. The template stamps CapCut 6.5.0 and none of the
schema markers the app writes, and 8.4, 8.5, 8.7 Windows and 9.3 list such a
draft at 00:00 and refuse it as "from an unusual path"
(#67,
#111, the 8.4.0 and
9.2.8 reports in #50,
and the shell-first recipe at
zxypro1/capcut-shell-inject
— four independent reporters, the last two in the past week). #111 is the
real 8.7 Windows round-trip this repo has been waiting on since 0.11, and it
came back negative for the bundled template and positive for a template
captured from the installed app. The first four items below turn that
finding into the default. The next three close the other 9.x gap, media
that the app cannot resolve because the timeline material never named its
sidecar entry. The last two are validation gaps with a single reporter each.
No command was removed, no existing flag changed meaning, and every existing
JSON output keeps its shape — new fields appear only where a draft was
created or a registration was planned.
Added
init/quickstart/compileseed new drafts from the store's newest
app-authored project (--template auto, now the default when--template
is omitted) — when the drafts folder holds a project from a newer CapCut
major than the bundled template declares, the new draft's skeleton is that
project's timeline document with its content emptied:version,
new_version,platform,last_modified_platform,color_space,
configand the render flags stay;tracks, everymaterials.*list,
keyframes,relationships, the cover and the timestamps are reset; the
id and name are the new draft's; the canvas and fps stay the template's
defaults (or--ratio/--width), so a portrait donor cannot turn every
new draft portrait. Nothing else of the donor travels — in particular not
itsTimelines/mirrors, which is how--template <app project>produced
a draft that opened with the donor's empty timeline on 9.2.8 (#50). The
bundled template remains the seed when the store is empty or on the same
major (--template bundledforces it and keeps the #67 warning, now naming
--template autoas the fix), and--template <dir>is unchanged apart
from the hygiene below. The result carriestemplate: { source: "store" | "path", path, app_version, skipped, reset }and one stderr line naming the
seed. This is the same finding as the shell-first recipe (create an empty
project in the app, quit, inject with this CLI): the app opens a draft
whose markers it wrote itself — done here without the manual step, and
withregister/lint/sync-timelinesunchanged for the shell flow.--template <dir>never carries per-project state — the donor's
Timelines/directory,.bakfiles,.capcut-cli-history/and its
draft_meta_info.jsonsidecar are skipped (listed intemplate.skipped);
the new draft gets a fresh sidecar and, on 8.4+, the app materialises
Timelines/from the root documents on first open (the observation in
#50's 8.4.0 report).migrate <project> --from-store/--like <project>— restamps a
draft an older release built (several per reporter in #111) with the
schema markers of a project the installed app wrote, in place:version,
new_version,platform,last_modified_platform,color_space,
render_index_track_mode_on,free_render_index_mode_on,source, plus
configwhen the draft has none. Timeline content is never touched; the
result listsrestamped,added,unchangedandunavailable.
--from-storepicks the newest app-authored project in the draft's own
drafts folder (never the draft itself) and refuses with a clear message
when the store holds only markerless drafts.linttemplate-stale(warning) — a draft carrying the bundled
template's signature (anapp_versionbut none ofversion,
new_version,last_modified_platform) inside a drafts folder whose
projects come from a newer app major, withmigrate --from-storeas the
suggested command. The store is scanned only for drafts that carry the
signature and live in a store (aroot_meta_info.jsonnext to the project
or the managed path), so every other lint run costs nothing extra.local_material_idlinks, at add time and on repair — JianYing 5.9+
and CapCut 9.3 resolve a local clip through the material's
local_material_id, the id of itsdraft_materialsentry; blank, the
clip shows as missing / inaccessible and the app's own Link-media dialog
cannot repair it
(luoluoluo22/jianying-editor-skill#23,
JmsLdrn/capcut-mcp#1,
the 9.3 follow-up on pyCapCut#13).
Every video, photo and audio material this CLI wrote carried""there.
add-video,add-audio,quickstart,compileandimport-timeline
now register the file in the draft's sidecar as they add it (one entry
per distinct file, the app's own entries preserved,.bakthen atomic
replace) and write the entry id into the material;add-video/
add-audioreportregistration: "draft_materials" | "none"(none = no
sidecar to register in,register --materialsremains the step). For
drafts built earlier,lintreportsmedia-unlinked(info, no exit-code
flip on the installed base) andlint --fixwrites the link from the
sidecar; where the file has no entry yet, the issue names
register --materials --applyfirst.register --materialsreports
unlinked_materialsin its plan and points atlint --fix— register
itself still never writes the timeline.lintsource-range-exceeds-material(info) — a segment whose
source_timerangereaches past its material's duration: CapCut clamps the
in-point to zero, so the clip plays from the start of its file whatever was
written — a two-camera cut came out as both cameras replaying their opening
seconds (JmsLdrn/capcut-mcp#1, on 9.3.0). Photos and materials without a
duration are skipped, one frame of tolerance covers rounding, and the
suggested command is thetrimthat fits.compile --checkvalidatestext-styleandtext-rangespayloads —
styling keys written flat on the operation instead of understyle
crashed the build with "Cannot read properties of undefined (reading
'alpha')" (#110);
the pre-flight now names the keys it found and where they belong, before
any draft directory exists.lintmedia-outside-draftnames the macOS permission case — media
under~/Desktop,~/Documentsor~/Downloadsmakes JianYing 11.4 show
"no access permission" and ask to relink although the path is valid
(pyJianYingDraft#198);
the message says so and points atlint --fix, which stages the file into
the draft.
Fixed
initstamps every timeline mirror — the bundled template ships
draft_info.jsonanddraft_content.json, andinitwrote the new
draft's id and name into the first one only, sodraft_content.jsonkept
id: ""/name: ""andregisterrefused the CLI's ownquickstart
draft with "has no id" (#111's isolation control). Both root mirrors (and
a plain-JSONtemplate-2.tmp) now carry the identity,draft_content.json
is the registered identity file (whatregisterreads first), and
registerfalls back to the sidecar'sdraft_idfor drafts stamped by
earlier releases instead of refusing — it still never invents an id.- The canonical test fixture's first video segment read 7.5 s of a 5 s
material; the material now declares the length the segment needs.
Docs
docs/version-support.mdrecords the real 8.7 Windows validation (#111,
negative with the bundled template, positive with a captured one on
9.3.0), the 8.4.0 root-to-nested and 9.2.8 nested-canonical observations
from #50, the 9.3.0 shell-first recipe, and JianYing 11.4 opening plaintext
drafts and upgrading them in place (pyJianYingDraft#198) — so
docs/jianying-encryption.mdnow says what the encryption does and does
not block: reading app-authored drafts, not generating new ones.
docs/draft-schema/00-overview.mddocuments thelocal_material_id
foreign key and the markers modern builds insist on.
v0.22.0 — the draft_materials registration, captions across the NLE handoff, retake detection
Nine items, each mined from a pain users are hitting now — this repo's own
threads, the forks ahead of it, the downstream projects that build on it
(vertir, qcut, OpenChatCut) and the neighbouring CapCut/JianYing libraries —
and each checked against what the CLI already did before it was built. The
cross-repo intersection was thin this cycle (the repo is mature; most hot
ecosystem pains are already shipped here), so the list leans on Tier-1
evidence: the sidecar registration is the one item with a concrete, reproduced
app failure behind it; the OTIO, script-alignment and retake items ride
long-standing asks in neighbouring tools; the last four are ergonomics the
downstream integrators and the most-diverged fork had to build for themselves.
No command was removed, no existing flag changed meaning, and every existing
JSON output keeps its shape — new fields appear only when the new flag is used.
Added
register --materials— the CapCut 9.1 "file inaccessible" fix (pyCapCut#13) —
newer builds decide which media is imported from draft_meta_info.json'sdraft_materials, not from the timeline's
paths, so every tool-built draft (this CLI's included: the sidecar's groups were always empty) opened with each clip
shown as inaccessible and a relink prompt. v0.21 could only observe the empty state (media-unregistered);
--materialswrites the registration: one type-0 entry per distinct local file, matched byfile_Path, shape per
pyCapCut PR #14 (verified by its author against the 9.1.0 prompt;
photos get the 5 s nominal duration, audio registers asmusic). It folds into register's own sidecar write — a
missing sidecar is recreated and registered in one write, one.bak, one changed-on-disk check — merges rather than
replaces (app-written entries untouched), and re-runs are no-ops.lint'smedia-unregisterednote anddiagnose
now name the command instead of asking for a fixture first; an app-authored 9.1 sidecar is still welcome as ground
truth (docs/draft-schema/00-overview.mdrecords what is measured and what is inferred).export-timeline --captions markers+ caption import inimport-timeline— OTIO has had no title/subtitle
schema since the request was opened in 2017 (OpenTimelineIO#62,
#805 still WIP), so the export dropped every
text track. With--captions markerseach cue becomes aMarker.1on the Stack — name = cue text,marked_range=
cue timing,metadata.capcut.kind = "caption"plus the text and track name, and Resolve's ownResolve_OTIO.Noteso
the text shows in its marker panel — which Resolve/Premiere import as timeline markers.import-timelineturns
flagged caption markers back into text segments on the recorded track name (de-collided like the clip tracks) and
reports any foreign timeline marker instead of dropping it. Default staysskip, so today's documents are
byte-identical; the skip note now points at the flag.caption --script <file>— whisper's timing, your wording — names, product terms and punctuation are what speech
recognition gets wrong most, and the pipelines that most want burned-in captions already have the script
(neo9su/autoclipvideo#174, baizhiheizi/enjoy_player#540 both ask for word timing on a KNOWN text). The script and the
recognised words are aligned globally (Needleman–Wunsch on normalised tokens, with a near-miss term so
"chanel"/"channel" pair with each other rather than with a neighbour); every script word inherits its recognised
word's start/end, unheard words are spread across the gap between timed neighbours. Each non-empty script line is a
cue (split only past--max-chars); with--karaokethe timed script words group as usual. The result'sscript
block reports matched / substituted / inserted / dropped words and the match ratio; below 50 % it warns that the
script probably belongs to other audio.detect-retakes— the repeat the silence pass cannot see — talking-head recordings are full of second attempts;
the one ecosystem tool that tried (mrbuslov/capcut-ai-editor, PR #3)
shows the failure to design against: its sentence-similarity pass matched unrelated sentences 27 minutes apart and
collapsed a 29-minute recording to 1:47. Three explicit guards here: a--window(later take must start within 60 s
of the earlier one ending),--min-words(cues under 4 normalised words never count — "okay so" repeats constantly),
and a--similarityfloor on the word sequences (2·LCS/(a+b), default 0.8, so a rephrased sentence is not a repeat).
The later take is the keeper; earlier spans become cuts, merged where they touch, with the complementary keep spans
in seconds and microseconds — the detect-silence shape, socut/compileconsume it unchanged. Reads the draft's
text tracks (or one--track-name) or--srt <file>with no draft; never writes.render --soft-captions— a toggleablemov_textsubtitle stream in the proxy instead of (or as well as) pixels
burned in (munimtechnologies/munim-ffmpeg#3, home-lang/home-os#86 — "soft subtitle muxing"). The cues are written
as<preview>.srtnext to the output (players auto-load it by name anyway) and muxed as stream0:s:0; a build
without themov_textencoder skips with a note like the drawtext fallback. The plan drops-shortestonly when a
subtitle stream is muxed, because ffmpeg would otherwise end the file at the last cue.- Keyframe property aliases —
scale(=uniform_scale),x,y,opacityare accepted bykeyframe,
keyframe --batchand compile's keyframe op and stored under the canonicalproperty_type; the two downstream IR
compilers built on this CLI (FullFran/vertir's PROP_MAP, Quriosity-agent/qcut's spec.py) had each re-implemented the
same translation table.describe/--helplist both forms; the error for an unknown name lists the aliases. keyframe --easing hold— the step easing the same integrators carried in their IR and had to degrade to linear
because CapCut always interpolates. The step is emulated the way an editor does it by hand: a helper keyframe with
the held value one frame before the NEXT keyframe on that property, so the whole ramp happens inside one frame.
Works in--batcheven when the held keyframe is named before its pair; a hold with no later keyframe, or one whose
neighbour already carries the value, is reported (hold_keyframes, warnings) rather than invented. The schema docs
list a"Hold"curveType, but no app-authored draft carries it, so the CLI does not write an unverified encoding.matting <project> <segment-id> [--off]— smart matting / "Remove background" / 智能抠像 — writesflag: 3
(smart portrait matting) on the segment's VIDEO MATERIAL and leaves the cache fields at their empty defaults for the
app to fill on next open;--offwrites the documented flag-0 object back, keeping any app-authored cache. Encoding
per the pyJianYingDraft contributor PRs #183/#184
(built twice, never merged); provenance recorded indocs/draft-schema/02-materials.md. Per material: segments
sharing the material are reported asshared_segments.init/quickstart--ratio 9:16(or--width/--height) — the bundled template is 1920×1080, so a portrait
short needed acompilespec or a second step; the most-diverged community fork shippedinit --width/--heightfor
exactly this. Presets at CapCut's native sizes (16:9,9:16,1:1,4:3,3:4); explicit width+height win over
the preset's size and keep its label (or get"original"). The canvas is stamped into every timeline file the
template ships, so the mirrors never disagree; without the flags the template canvas is kept and the output carries
canvas: null.
Changed
lint'smedia-unregisteredissue suggestscapcut register <project> --materials --apply(it used to ask for a
fixturebundle); still info-severity andfixable: false— a sidecar write is register's job, notlint --fix's.diagnose's media-registration note names the same repair.export-timelinereports text tracks as skipped with a pointer to--captions markersas well asexport-srt.
v0.21.1 — the editor-open guard no longer detects its own npm process
One fix, and if you run the CLI via npx it is the whole release.
refused [editor-open] on every write under npx / npm exec, with CapCut closed (#99) — editorProcesses() substring-matched the joined process listing, and npm rewrites its own process title to the full command line — which contains capcut-cli — so the CLI detected itself as a running editor and the README's zero-install path could never complete a write. Process names are now compared per process, by exact basename, case-insensitively (macOS ps -o comm= prints the executable's full bundle path, so whole-line equality would have broken real detection). The Windows branch had the same substring shape and now compares the exact tasklist image name, which also stops an image merely containing capcut.exe from tripping the guard.
Pinned by the reporter's fixture: a listing whose only capcut is an npm exec [email protected] … title returns nothing, while the full CapCut bundle path still detects. A really-running CapCut/JianYing still refuses; npm never does.
Diagnosis, scope table and fix direction all came with the report — thanks @hansuk94.
Upgrade: npm install -g capcut-cli@latest — or just npx capcut-cli@latest <command>, which now works for writes.