You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 685472b
Browse filesBrowse the repository at this point in the historyBrowse files
fix(text): store styles[].range in UTF-16 code units, not bytes (#85) (#87)
A text material's content.styles[].range holds UTF-16 code units — plain JS
string indices. This CLI wrote UTF-16LE bytes, so every stored offset was
doubled. A single full-span range survived that, because [0, 2n] clamps back
to the end of the text when CapCut opens the draft, which is why add-text
looked correct and the premise went unchallenged. A multi-range highlight did
not: on the 17 code-unit string "อย่าไปซื้อ Claude", text-ranges wrote the
emphasised span at [22, 34] and CapCut painted nothing. text-ranges,
caption --karaoke, --highlight-words and presets carrying text_ranges were
all affected.
The premise was this repo's own, asserted in docs/draft-schema and never
measured. @hillimited measured it: across 38 app-authored drafts, 211 text
materials from CapCut International 7.9.0 and 8.9.1 store code units and none
store bytes, and CLI-written drafts read back as code units once the app has
re-saved them.
Eight sites shared the assumption, so flipping the writer alone would have
broken the readers. All of them now go through src/text-offsets.ts, which is
also the one place to branch if a JianYing draft ever proves different:
- decorators setTextRanges (writer)
- caption addCaptionSegment (full-span block)
- preset capture and rangeless-preset collapse
- lint --fix re-wrap (newline shift is 1 code unit, not 2 bytes)
- export-srt karaoke word matching
- draft updateTextContent, factory buildTextContent / text override
- docs/draft-schema/02-materials.md
In the code-unit domain range is the JS string index, so the BMP assumption
that came with the byte conversion is gone and an emoji highlight is correct
by construction.
Adds lint text-range-doubled (fixable): the doubled form is identifiable with
certainty — every offset even, inside [0, 2n], last ending at exactly 2n,
which is out of bounds as code units and precisely where the old writer's
trailing block landed — so lint --fix halves them and converges. export-srt
repairs the same form on read, so old karaoke drafts still collapse per word.
The canonical test fixture carried the doubled form too; corrected.
700 tests pass (8 new).
-**Queue runner** — `capcut serve` reads JSONL jobs from stdin, for [n8n / Make / Coze](./examples/serve-automation.md)
30
30
31
-
> **New in v0.19.0:**a long `render` no longer fails with `ERR_CHILD_PROCESS_STDIO_MAXBUFFER` on a draft that was fine, and a render that does fail now names the missing decoder, encoder or filter instead of dumping ffmpeg's output; `render --progress` streams progress so a ten-minute job stops looking hung. `lint` gained three checks — caption reading speed (`--max-cps`), vertical safe-area (`--safe-area`) and speed consistency — and `lint --fix` can finally re-wrap CJK captions without disturbing per-character styling. Plus `export-timeline` no longer rounds a sub-half-frame clip to a zero-length OTIO clip ([#82](https://github.com/renezander030/capcut-cli/issues/82)), and the keyframe docs now name the `property_type` the code actually writes ([#80](https://github.com/renezander030/capcut-cli/issues/80)). No command was removed and no existing flag changed meaning. Full details in the [changelog](./CHANGELOG.md).
31
+
> **New in v0.19.1:**every multi-range text highlight was being written past the end of the text it styled. `styles[].range` holds UTF-16 code units, not UTF-16LE bytes, so `text-ranges`, `caption --karaoke`, `--highlight-words` and any preset carrying `text_ranges` stored offsets twice as large as they should be — a plain `add-text` looked fine only because a full-span range clamps back to the end of the text ([#85](https://github.com/renezander030/capcut-cli/issues/85), measured by [@hillimited](https://github.com/hillimited) across 38 app-authored drafts). Fixed everywhere those offsets are read or written, and `lint --fix` repairs drafts written by earlier versions (`text-range-doubled`). Full details in the [changelog](./CHANGELOG.md).
32
32
33
-
> **New in v0.18.0:**`capcut fixture` now redacts the device identifiers CapCut stamps into every draft, so a bundle is finally safe to attach to a public issue (#59); `init` warns when the bundled template predates the CapCut version that owns the target drafts folder, instead of letting the app refuse the draft with a misleading "unusual path" error (#67); the nested `Timelines/` guidance is gated on detected app version, so 8.5.0 users stop seeing a 7.x warning their own evidence contradicts (#68); and the schema docs record CapCut's frame quantisation of durations on first open (#69). Full details in the [changelog](./CHANGELOG.md).
33
+
> **New in v0.19.0:**a long `render` no longer fails with `ERR_CHILD_PROCESS_STDIO_MAXBUFFER` on a draft that was fine, and a render that does fail now names the missing decoder, encoder or filter instead of dumping ffmpeg's output; `render --progress` streams progress so a ten-minute job stops looking hung. `lint` gained three checks — caption reading speed (`--max-cps`), vertical safe-area (`--safe-area`) and speed consistency — and `lint --fix` can finally re-wrap CJK captions without disturbing per-character styling. Plus `export-timeline` no longer rounds a sub-half-frame clip to a zero-length OTIO clip ([#82](https://github.com/renezander030/capcut-cli/issues/82)), and the keyframe docs now name the `property_type` the code actually writes ([#80](https://github.com/renezander030/capcut-cli/issues/80)). No command was removed and no existing flag changed meaning. Full details in the [changelog](./CHANGELOG.md).
@@ -131,7 +131,9 @@ The top-level fields (`text_color`, `border_width`, `has_shadow`, …) are the "
131
131
}
132
132
```
133
133
134
-
**The `range` array is in UTF-16 little-endian byte offsets.** This trips up everyone the first time. For ASCII text, byte index = character index. For Chinese / emoji, each char is 2 bytes (BMP) or 4 bytes (non-BMP). `capcut-cli text-ranges` handles the conversion.
134
+
**The `range` array is in UTF-16 code units** — the same indices `"abc".slice(start, end)` takes in JavaScript, or `text[start:end]` in Python 3. One BMP character (Latin, Chinese, Thai) is one unit; one emoji or other astral character is two, because UTF-16 encodes it as a surrogate pair.
135
+
136
+
This is worth stating plainly because this repo got it wrong. Until v0.19.1 `capcut-cli` wrote these offsets as UTF-16LE **bytes** — every value doubled ([#85](https://github.com/renezander030/capcut-cli/issues/85)). A single full-span range survived the mistake, since `[0, 2n]` clamps back to the end of the text when CapCut opens the draft, which is exactly why it went unnoticed; a multi-range highlight did not. A scan of 38 app-authored drafts from CapCut International 7.9.0 and 8.9.1 settled it: 211 text materials in code units, none in bytes. `capcut lint --fix` repairs drafts written by the older versions (`text-range-doubled`).
| 7.9 / 8.9 (International, macOS) | reported | one field measured | A scan of 38 app-authored drafts on one machine established that `materials.texts[].content` → `styles[].range` holds UTF-16 code units, not UTF-16LE bytes: 211 text materials in code units, none in bytes ([#85](https://github.com/renezander030/capcut-cli/issues/85)). That settled a real writer bug, fixed in 0.19.1. It is a measurement of one field, not a suite run — no sanitized app-created folder from either version is committed yet, so these versions stay off "fixture-tested". |
21
22
| 8.7 Windows | reported + synthetic-tested | adapter shipped, real validation pending | Issue #35 reports that `draft_content.json` edits may be ignored in favour of `template-2.tmp` / `draft_meta_info.json`. v0.11 discovers nested/string JSON timeline envelopes, selects modern storage, synchronizes every readable target, and provides `diagnose --bundle` and `fixture --out` (one-command sanitized bundle). v0.13 adds `sync-timelines` to reconcile an already-drifted mirror (plan by default, `--apply` to write); `diagnose` names it as the remedy. A reporter-provided real folder is still required before marking this fixture-tested. |
22
23
| 9.x | expected-compatible | unverified |`common_masks` may coexist with legacy mask fields. Use `version`, `diagnose`, and `migrate`; do not treat this row as desktop-app verification. |
23
24
| 10.x (Mac and Windows) | reported | write-guarded | New builds are reported to reject tool-written drafts as corrupted ("内容已损坏"; pyJianYingDraft#177, #194 for the JianYing 10.8 counterpart; Mac primary file reported as `draft_info.json`, Jianying-CapCut2XML#4). No fixture exists; mutating commands refuse without `--force-write`. Fixture wanted — see the write guard section below. |
Copy file name to clipboardExpand all lines: package.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
{
2
2
"name": "capcut-cli",
3
-
"version": "0.19.0",
3
+
"version": "0.19.1",
4
4
"description": "Independent, unofficial CLI to create and edit CapCut projects — build drafts from scratch, add video/audio/text, subtitles, timing, speed, volume, templates, cut long-form to shorts. No API needed. Not affiliated with ByteDance.",
0 commit comments