Skip to content

Release v0.31.0: loudness to a delivery target, a dry-run-safe and resilient caption --cloud, three lint checks - #158

Merged
renezander030 merged 9 commits into
masterfrom
release/v0.31.0
Oct 23, 2026
Merged

renezander030 merged 9 commits into
masterfrom
release/v0.31.0

Conversation

@renezander030

Copy link
Copy Markdown
Owner

What's in it

Added

  • loudness <project> measures every audible clip with ffmpeg's EBU R128 meter over the part of its media the timeline plays, and reports each clip's integrated loudness, true peak and loudness at its segment volume, plus an estimate for the whole draft. --target <LUFS> (for example -14 for long-form, -11 for shorts) computes the gain that reaches it: one gain for every clip, so the balance between voice and music holds, or with --per-clip each clip on its own. --max-gain-db caps a boost (default 20 dB). --apply writes the gain as segment volume, journaled and undoable with restore. Skipped clips (photos, muted, missing media, no audio stream, silent, sound separated to an audio track) carry the reason.
  • lint gains three checks: sound-separated-orphan (warning, fixable), for a video material marked has_sound_separated with no separated audio on the timeline, which plays the clip silent in the app (--fix clears the mark); keyframe-trimmed-clock (info), for keyframes on a clip trimmed at its head or played at another speed; and uniform-scale-keyframe (info), for UNIFORM_SCALE zooms on JianYing 11.6 and later.

Changed

  • caption --cloud --dry-run uploads nothing. It makes the same option checks as a real run, reports what it would send (would_upload: file, size, duration, destination) and exits without contacting CapCut's servers.
  • caption --cloud gives each request a time limit and retries the upload signature, upload registration and result polling up to three times on a network error, HTTP 429 or a 5xx answer. The wait for the result grows with the audio's length (2 minutes plus half the duration, at most 30 minutes) instead of a fixed 2 minutes.
  • caption --cloud --language <code> warns that the language has no effect, since the service detects the spoken language itself.
  • CI installs the packed tarball and runs smoke:package on every push, and the release workflow does the same before it publishes.

Fixed

  • npm test passes on hosts with many cores: the running-editor test no longer starts a process every concurrently running test file can see, and the compile tests keep recorded app versions in scratch files instead of the developer's ~/.config/capcut-cli/app-versions.json.

Also released here: the experimental caption --cloud (#157), the asset-hash cache (#155/#156), portable skill scripts and the read-path fixes already listed under Unreleased.

Verification

  • npm test (unsplit, as CI runs it): 1350 tests, 1348 pass, 2 skipped, 0 fail; npm run lint, npm run wasm:verify, the Python client's unittest suite, npm pack plus smoke:package on the tarball: all pass.
  • docs/command-reference.json regenerated with npm run docs:commands; describe matches it. Help, per-command help, the library exports and the shipped type declarations are unchanged apart from the new loudness command and the --language option text.
  • loudness checked against ffmpeg's real meter on a generated tone; the cloud flow is tested offline with a stand-in for CapCut's endpoints (no live upload).

Review notes

  • Base: 9e56073 (master).
  • The first commit, "Tidy-up: 5 behavior-preserving refactors", is a separate cleanup with no change in output (cloud ASR request helpers, one caption source-conflict rule, whisper dialect detection in whisper.ts, restore source picking in history-integrity.ts, an animation slot table). It reviews on its own.
  • One commit per feature after that; the last two are the release commit (version, CHANGELOG, READMEs, command docs) and a small loudness follow-up that moves its target rules into the module without changing output.
  • Version bumped to 0.31.0 in package.json and the lock file. Nothing is tagged or published.
  • Untouched: the draft write path, the write guard, render, compile and the store.

- caption --cloud: the editor-API calls share one request-and-refusal path;
  an offline test now drives the whole recognition exchange.
- caption: one rule set decides which options contradict --words and --cloud,
  used by the CLI and the caption module alike.
- whisper: the binary-name-to-dialect rule lives beside the dialect names.
- restore: choosing the .bak or a history step, and refusing a changed one,
  lives beside the integrity check it applies.
- animations: one per-slot table replaces the repeated in/out/combo ternaries.

No change to CLI output, help, the describe registry, library exports or the
shipped type declarations.
capcut loudness <project> measures every audible clip with ffmpeg's ebur128
meter over the part of its media the timeline plays, and estimates the
export's integrated loudness. --target <LUFS> adds the gain that reaches it,
one gain for the whole mix (keeps the balance) or per clip with --per-clip,
capped by --max-gain-db (default 20). --apply writes it as segment volume
through the journaled save, so restore can undo it.
… effect

- caption --cloud --dry-run used to upload the audio and only skip the save.
  It now sends nothing and reports what it would upload (file, size, length,
  destination), the way translate sends nothing in a dry run. The caption
  module also refuses to upload under --dry-run for any other caller.
- --language with --cloud warns that CapCut's recognition detects the spoken
  language itself (the request carries no language), instead of being
  silently ignored.
…audio

- Every request has a time limit (60 s; the audio upload gets one more second
  per 128 KiB), so a stalled connection no longer hangs the run.
- Calls that are safe to repeat (signing, the upload-node lookup, status
  polls) are tried up to three times on a dropped connection, a time-out,
  429 or 5xx, with a growing pause. The upload, its commit and the task
  creation are never repeated.
- The wait for the recognition is 2 minutes plus half the audio's length
  (at most 30 minutes) instead of a fixed 120 seconds, so long recordings
  can finish.
… on JianYing 11.6+

- sound-separated-orphan (warning, --fix): a video clip marked
  has_sound_separated with no separated audio anywhere on the timeline plays
  silent; the mark comes along when a clip is copied from another draft.
  --fix clears it. A draft whose sound really was split in the app
  (an extract_music or video_original_sound clip on the timeline) is left alone.
- keyframe-trimmed-clock (info): keyframes on a clip trimmed at its head or
  played at another speed. Keyframe times are written from the segment's
  start; Jianying Pro 11.6 has been reported to read them on the media's own
  clock, so the note says where to check the motion.
- uniform-scale-keyframe (info, JianYing 11.6+ only): UNIFORM_SCALE zooms
  have been reported ignored there in favour of KFTypeScaleX with uniform
  scaling on.
…an see

The sync-timelines guard test faked a running editor with a real process
named CapCut. On a machine with many cores the other test files run at the
same time, see that process and have their writes refused (editor-open), so
npm test failed there. The test now puts a stand-in ps on the child's PATH
that lists a CapCut process, which only that test's commands see.
…est the packed tarball

compile-safety, compile-fractional-timing and asset-hash-cache now point
CAPCUT_CLI_APP_VERSIONS at a per-file temp store like the other compile
tests, so npm test no longer writes the developer's own
~/.config/capcut-cli/app-versions.json.

CI gains a package job, and the release workflow a step before publish,
that packs the tarball, installs it and runs smoke:package.
…path test

measureLoudness refuses --per-clip without --target and a target when no
clip could be measured, so the rules sit with the computation and are
testable without the CLI; the media path check reuses isLocalPath from
materials-register. Output is unchanged.
@renezander030
renezander030 merged commit 1c6edf6 into master Oct 23, 2026
14 of 15 checks passed
@renezander030
renezander030 deleted the release/v0.31.0 branch October 23, 2026 13:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant