Files
hyperframes/.github
dd97478175 fix(media-use): remove clicks and ~25ms gaps at every transcript-cut splice (#4179)
* fix(media-use): remove clicks and ~25ms gaps at every transcript-cut splice

transcript-cut extracts each kept range to its own final-codec file, then
concat-copies them. That produces two audible artifacts at every cut:

1. No boundary fades. Concat splices raw segment edges together, so the
   waveform steps discontinuously and each cut clicks.

2. Encoder priming silence. Each segment is encoded to AAC independently,
   so the encoder pads every one with priming samples. Stream-copy concat
   bakes that padding in as a silent gap at each splice. Measured 24.4 ms
   and 24.1 ms at the two splices of a three-range cut. It accumulates:
   the nth splice lands roughly 25n ms late, so splice positions drift
   away from the sum of the preceding segment durations.

The second survives the first -- adding fades alone leaves the gap.

Fix: intermediates carry PCM (.mkv for video, .wav for audio-only) instead
of the final codec, so audio is encoded exactly once, at concat. Boundary
fades of 30 ms are applied per segment, scaled down on segments shorter
than 4x the ramp and skipped on degenerate ones.

Measured on the same three-range cut, widest run of digital silence at
each splice:

  before  24.4 ms / 24.1 ms
  after    0.1 ms /  0.1 ms

--copy is unchanged: stream copy cannot filter, so it keeps producing
final-codec segments and a copy concat. Audio-only output is byte-identical
in duration before and after. scripts/lib test suite: 34/35, the one
failure (lut-preset-provider) pre-exists on main and is unrelated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(skills): regenerate manifest for media-use

* fix(media-use): honor the fadeIn/fadeOut flags fadeFilterFor was passed

The caller already computed {fadeIn, fadeOut} per segment, but
fadeFilterFor still took one parameter and always emitted both
afade ramps -- so a single kept segment (no splice at all) and every
segment's true start/end got faded regardless. Extracted the pure
function into lib/transcriptCutFade.mjs, made it honor both flags,
and added its first unit test: first/middle/last/single-segment
filter strings, no ffmpeg needed, fails against the previous version.

Also installs ffmpeg in the Test: skills CI job (same action the
Producer job uses), so transcript-cut.test.mjs's real-tone RED/GREEN
proof runs in CI instead of skipping silently for lack of the binary.

* fix(media-use): fix the packed-CLI import and lint on transcript-cut.mjs

build-copy.mjs replaces dist/skills/media-use/scripts/lib with a copy
of packages/cli/src/media-use/lib, so transcriptCutFade.mjs needed a
byte-identical twin there (and in MEDIA_USE_COPY_NAMES) or the packed
CLI's transcript-cut.mjs imported a file that no longer existed.
Moved its test alongside, matching every other lib helper's tests.

Also drops three redundant Boolean(args.copy) wrappers (a ternary
condition already coerces) that oxlint's no-extra-boolean-cast
flagged.

* style(media-use): reflow the shortened ext ternary to oxfmt's shape

Dropping the Boolean() wrapper shortened the line enough that oxfmt
0.41 now collapses the ternary onto one line; format:check was failing
on the stale multi-line version.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com>
2026-09-22 20:28:50 -04:00
..