Skip to content

Releases: renezander030/capcut-cli

v0.29.0 — aligner word timings, reviewed batch plans, render fidelity and lint checks

Choose a tag to compare

@renezander030 renezander030 released this 09 Oct 13:21
648f469

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.cpp segments[].words[], WhisperX word_segments[], or a plain {word|text|char, start, end} array with times in seconds, start_ms/end_ms or start_time/end_time (per-character forced-aligner output). The result reports words_format, words_skipped and source_words. Entries without timing are skipped; entries that end before they start or go back in time are refused with refused [words-invalid] naming the entry. Grouping, CJK joining without spaces, --script, --karaoke and --word-reveal behave exactly as with Whisper words. --words cannot be combined with --audio, --from-segment, --audio-stream, --ffmpeg-cmd or --whisper-*.
  • batch <project> --plan <plan.json> < ops.jsonl validates the operations under dry-run without touching the draft and writes a reviewable plan: project, draft_sha256, the normalized operations, operations_sha256 (over key-sorted JSON) and a per-operation preview. batch <project> --apply-plan <plan.json> reads no stdin and applies the plan through the normal transactional batch. It refuses with refused [plan-draft-changed] when the draft changed since the plan was written, refused [plan-tampered] when the operations no longer match their hash, and refused [plan-project-mismatch] when the plan names another project. Without --continue-on-error a failing operation writes no plan.
  • render (including --dry-run) reports a fidelity census of what the proxy leaves out compared with the app: faithful, expected_duration_us, the checked categories, 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 --strict refuses with refused [render-unfaithful] before anything is written.
  • After a real render, render probes the written file with ffprobe (--ffprobe-cmd selects the binary) and reports verification: { 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 reports verified: false with a reason. render --verify exits 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 (SQL never fires inside MySQLite; CJK rules match anywhere). Malformed lexicons refuse with refused [lexicon-invalid], and equal-length rules that say different things at the same position refuse with refused [lexicon-ambiguous], both before any engine runs. The result reports lexicon: { rules, applied, spoken_text } with offsets in UTF-16 code units into the trimmed text. Works with --text and --text-file.
  • lint reports segment-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 --fix repairs 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 keep caption-overlap.
  • lint reports segment-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 into canvas_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.jsonl gets { index, time, command, argv, before_sha256, after_sha256 } per write (library callers record command: null), trimmed together with the snapshots. restore --list shows command, argv and time per 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 的字幕路线) and examples/short-video-narration.md: the app's own auto captions followed by restyle / export-srt, import-srt, and caption --words.

Changed

  • doctor's Whisper hint names caption --words and import-srt as routes that work without Whisper. The check's status is unchanged.
  • lint --frame-grid --fix derives 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

Choose a tag to compare

@renezander030 renezander030 released this 04 Oct 06:19
cb348ed

v0.28.0

Added

  • --active-timeline explicitly follows a validated Timelines/project.json pointer 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, --plan and --dry-run validate both spec and destination without writing. Nested layouts require active selection. Desktop acceptance on these opt-in paths remains unverified.

  • describe --compact emits 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 through capcut.describe(compact=True, command="compile").

Fixed

  • Python client v0.1.3 parses quoted Windows CAPCUT_CLI paths 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

Choose a tag to compare

@renezander030 renezander030 released this 03 Oct 13:58
f85efa3

v0.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.

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

Choose a tag to compare

@renezander030 renezander030 released this 03 Oct 12:12
44b35c4

Fixes

  • fixture redacts 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 in SANITIZE_REPORT.json (#134).
  • compile handles 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-timelines repairs in the active → root direction (#50).

Install

npm install -g [email protected]
capcut --version

Verification 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

Choose a tag to compare

@renezander030 renezander030 released this 25 Sep 07:39
e98e3d8

Highlights

  • Frame-grid linting and repair with lint --frame-grid --fix.
  • Character-level Chinese/Japanese script alignment and a --min-script-match write 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.1

See CHANGELOG.md for full behavior and compatibility details.

v0.25.0

Choose a tag to compare

@renezander030 renezander030 released this 18 Sep 08:46
7eb07f6

Added

  • 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 in Chinese and Japanese are now joined without spaces and bounded by characters alone, at the width lint holds captions to (16 zh, 13 ja; Korean keeps spaces and four words at 16); karaoke ranges follow the new offsets. The result reports caption_script (latin | zh | ja | ko). An explicit --max-words / --max-chars applies as given. Library: groupWords takes a separator; wordSeparator, groupingDefaults and GroupingDefaults are exported; the script detection moved to script.ts (still exported from the lint entry points).
  • caption --max-chars is documented in the command reference alongside --max-words.
  • The Chinese README notes the feature in 科技爱好者周刊 issue 413.

Unchanged

  • --script alignment still tokenizes the script file on whitespace; a Chinese script line is one token to it.

v0.24.0

Choose a tag to compare

@renezander030 renezander030 released this 18 Sep 08:00
72cd7d3

Added

  • lint holds 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), --fix re-wraps between characters, and an explicit --max-chars / --max-cps applies to every script. Library: captionScript, captionLimits, scriptLimitsExcept, CJK_SCRIPT_LIMITS, and LintOptions.scriptLimits (null keeps the Latin limits everywhere).
  • init, quickstart and compile report 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.warning in the JSON, the quickstart create step and the compile warnings list — and name what is known about the bundled template on that app.
  • lint reports template-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. The capcut-edit skill 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

Choose a tag to compare

@renezander030 renezander030 released this 11 Sep 09:59
e4a5339

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 / compile seed 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,
    config and the render flags stay; tracks, every materials.* 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
    its Timelines/ 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 bundled forces it and keeps the #67 warning, now naming
    --template auto as the fix), and --template <dir> is unchanged apart
    from the hygiene below. The result carries template: { 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
    with register/lint/sync-timelines unchanged for the shell flow.
  • --template <dir> never carries per-project state — the donor's
    Timelines/ directory, .bak files, .capcut-cli-history/ and its
    draft_meta_info.json sidecar are skipped (listed in template.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
    config when the draft has none. Timeline content is never touched; the
    result lists restamped, added, unchanged and unavailable.
    --from-store picks 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.
  • lint template-stale (warning) — a draft carrying the bundled
    template's signature (an app_version but none of version,
    new_version, last_modified_platform) inside a drafts folder whose
    projects come from a newer app major, with migrate --from-store as the
    suggested command. The store is scanned only for drafts that carry the
    signature and live in a store (a root_meta_info.json next to the project
    or the managed path), so every other lint run costs nothing extra.
  • local_material_id links, 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 its draft_materials entry; 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, compile and import-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, .bak then atomic
    replace) and write the entry id into the material; add-video /
    add-audio report registration: "draft_materials" | "none" (none = no
    sidecar to register in, register --materials remains the step). For
    drafts built earlier, lint reports media-unlinked (info, no exit-code
    flip on the installed base) and lint --fix writes the link from the
    sidecar; where the file has no entry yet, the issue names
    register --materials --apply first. register --materials reports
    unlinked_materials in its plan and points at lint --fix — register
    itself still never writes the timeline.
  • lint source-range-exceeds-material (info) — a segment whose
    source_timerange reaches 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 the trim that fits.
  • compile --check validates text-style and text-ranges payloads —
    styling keys written flat on the operation instead of under style
    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.
  • lint media-outside-draft names the macOS permission case — media
    under ~/Desktop, ~/Documents or ~/Downloads makes JianYing 11.4 show
    "no access permission" and ask to relink although the path is valid
    (pyJianYingDraft#198);
    the message says so and points at lint --fix, which stages the file into
    the draft.

Fixed

  • init stamps every timeline mirror — the bundled template ships
    draft_info.json and draft_content.json, and init wrote the new
    draft's id and name into the first one only, so draft_content.json kept
    id: "" / name: "" and register refused the CLI's own quickstart
    draft with "has no id" (#111's isolation control). Both root mirrors (and
    a plain-JSON template-2.tmp) now carry the identity, draft_content.json
    is the registered identity file (what register reads first), and
    register falls back to the sidecar's draft_id for 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.md records 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.md now says what the encryption does and does
    not block: reading app-authored drafts, not generating new ones.
    docs/draft-schema/00-overview.md documents the local_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

Choose a tag to compare

@renezander030 renezander030 released this 04 Sep 16:25
88ea562

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's draft_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);
    --materials writes the registration: one type-0 entry per distinct local file, matched by file_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 as music). 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's media-unregistered note and diagnose
    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.md records what is measured and what is inferred).
  • export-timeline --captions markers + caption import in import-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 markers each cue becomes a Marker.1 on the Stack — name = cue text, marked_range =
    cue timing, metadata.capcut.kind = "caption" plus the text and track name, and Resolve's own Resolve_OTIO.Note so
    the text shows in its marker panel — which Resolve/Premiere import as timeline markers. import-timeline turns
    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 stays skip, 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 --karaoke the timed script words group as usual. The result's script
    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 --similarity floor 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, so cut/compile consume it unchanged. Reads the draft's
    text tracks (or one --track-name) or --srt <file> with no draft; never writes.
  • render --soft-captions — a toggleable mov_text subtitle 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>.srt next to the output (players auto-load it by name anyway) and muxed as stream 0:s:0; a build
    without the mov_text encoder skips with a note like the drawtext fallback. The plan drops -shortest only 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, opacity are accepted by keyframe,
    keyframe --batch and compile's keyframe op and stored under the canonical property_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/--help list 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 --batch even 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" / 智能抠像 — writes flag: 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; --off writes 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 in docs/draft-schema/02-materials.md. Per material: segments
    sharing the material are reported as shared_segments.
  • init / quickstart --ratio 9:16 (or --width/--height) — the bundled template is 1920×1080, so a portrait
    short needed a compile spec or a second step; the most-diverged community fork shipped init --width/--height for
    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's media-unregistered issue suggests capcut register <project> --materials --apply (it used to ask for a
    fixture bundle); still info-severity and fixable: false — a sidecar write is register's job, not lint --fix's.
  • diagnose's media-registration note names the same repair.
  • export-timeline reports text tracks as skipped with a pointer to --captions markers as well as export-srt.

v0.21.1 — the editor-open guard no longer detects its own npm process

Choose a tag to compare

@renezander030 renezander030 released this 30 Aug 07:45
a4f51f3

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.