Repository navigation
feat(cli): read a whole recording as voice, effect and music lines - #6056
Conversation
`hyperframes audio <file> --read` turns an episode or reference video into time-stamped lines an agent can read or grep: sentences with rate, pitch and ending; pauses; sound effects grouped by identical sound, with band, attack, tail and level over the music; and the music bed split at its cut-outs into pieces that start, resume, return or are new. Effects and music come from stems separated by the service at HYPERFRAMES_AUDIO_STEMS_URL (vocals, drums, bass, guitar, piano, other). Without stems, effects are read from the mix outside speech; with neither stems nor a transcript, voice and effects are not read, and each case is reported in a warning. Results are cached per file content.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
Stems for `audio --read` and the `api` analysis provider only ran when HYPERFRAMES_AUDIO_STEMS_URL / HYPERFRAMES_AUDIO_API_URL and HYPERFRAMES_AUDIO_API_KEY were set, so someone with HEYGEN_API_KEY or a saved `hyperframes auth login --api-key` still got "not set" and had to know the internal route. Both now default to /v3/audio/stems and /v3/audio/analysis on the HeyGen API (HEYGEN_API_URL when set) with the credential the other HeyGen commands use. The URL variables still point at another service, which then needs its own HYPERFRAMES_AUDIO_API_KEY, so the HeyGen credential never goes to a host it was not issued for.
`hyperframes audio` defaulted to --quality best, so anyone with a HeyGen credential uploaded their audio, and could be billed, for an ordinary beats or --read run. tts, transcribe and remove-background never leave the machine unasked. The default is now local for both measuring and --read. best is unchanged and still uploads to /v3/audio/analysis and /v3/audio/stems. When a capability goes unanswered because a service was left out, or --read reads without stems, the warning says --quality best would add it.
HyperFrames Desktop blanks HYPERFRAMES_AUDIO_API_URL and its key while the analysis route is behind a flag, but gives runs HEYGEN_API_KEY and HEYGEN_API_URL for its gateway. Falling back to that credential would let a run that passes --quality best reach the route anyway. An empty URL now turns the service off, whatever credential is present.
(cherry picked from commit f9c6ba3)
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Requesting a partial-stems repair at 08f349114aaed6a531adc79acdb44de168e837d6: a service response containing only the required vocals stem is accepted and cached as a complete separation, although effects were not measured. The inline comment describes the false zero and durable cache behavior. I checked the local-by-default path and the explicit empty-URL service disablement in source; I found no separate upload-boundary defect. Exact-head CLI smoke, lint, and fallow passed; Test (support), typecheck, and other CI were still running at this review. No live stems service or long-recording validation here. — Jerrai
| const { music, beats } = bed | ||
| ? readMusic({ stems: pitched, drums, sampleRate: READ_RATE }) | ||
| : { music: null, beats: [] }; | ||
| const effects = findEffects({ |
There was a problem hiding this comment.
A completed stems job may contain only vocals: fetchStems requires that one name and writes a manifest for whatever other supported stems happen to be returned. Here, with no drums and no pitched bed, source is an empty array, so findEffects produces a non-null effects: { events: [], groups: [] } and the report claims zero effects, with no warning that effects/music were not measured. With a pitched stem but no drums, it instead searches the music bed itself for effects. The separation: "stems" result is then cached and never upgraded on subsequent --quality best reads, even if the service later supplies the missing stems. Please distinguish missing layers from measured silence, warn/fall back appropriately, and add a partial-stems response + cache-retry regression.
There was a problem hiding this comment.
Fixed in 99048fe, included in 4ce2f62. A separation is cached only when all six documented stems exist; incomplete responses fall back to mix with a warning naming missing stems. cachedStems rejects partial manifests, and readAudio invalidates a previously cached stems analysis when its manifest is incomplete, allowing best to retry. Added a vocals-only response → complete response regression plus a stale partial-manifest retry regression. All 6 readAudio integration tests and CLI typecheck pass. The author’s e22a831 same-key POST retry contract is preserved in this head.
…wers The stems service answers once the stems are made, which can outlast the connection, and answers 409 request_in_progress to a resend while the first request runs. A failed job's reason is failure_message.
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Re-reviewed 4ce2f62d1e310e5faef018cd742c8e02e66148ab against my partial-stems finding at 08f349114aaed6a531adc79acdb44de168e837d6. fetchStems now requires all six documented stem names before writing a complete manifest; a partial response falls back to mix with a missing-stems warning, rather than presenting unmeasured effects as zero. cachedStems rejects incomplete manifests and readAudio invalidates an existing stems analysis if its manifest is incomplete, so a later best-quality read retries. The integration regression covers vocals-only response → complete response and stale partial-manifest retry. The same-key POST path builds its body/key once and resends for 409 request_in_progress or selected connection drops, with the separation timeout signal; the test checks key reuse. The live stems backend protocol and long-recording behavior were not independently exercised or confirmed. Exact-head Test (support), lint, fallow, and CLI npx shim checks had executed successfully; typecheck, CLI smoke, and other checks were still in progress at verdict. No live service call, local test, merge or release by me. — Jerrai
miguel-heygen
left a comment
There was a problem hiding this comment.
Additive review at 4ce2f62d1e310e5faef018cd742c8e02e66148ab: source-clear in the audited scope, with COMMENT while the current CodeQL gate is red.
I corroborated Jerrai's repaired partial-stems finding: the six-layer guard (packages/cli/src/audio/read/stems.ts:157) and cached-analysis invalidation (read.ts:197) prevent missing measurements becoming cached silence. The POST loop retains its key/body and deadline; local/default and empty-URL boundaries remain intact.
Audited: the changed audio reader, analysis/provider, command/upload paths and affected consumers; repair delta, focused tests, docs and packed-manifest parser change. Existing reviews/security comments checked for overlap.
Trusting: live backend idempotency/availability, real separation quality and the author's episode/performance evidence. No live upload/model call.
Independent exact-head local verification: 74 tests, nine files, zero skipped with existing Node22/FFmpeg. A separate simulated dropped-socket control preserved the POST key/body; weakening the missing-stems guard failed the warning regression. Snapshots restored and test servers closed.
At 11:19:42 UTC, head still matches and hosted typecheck/support/lint/smoke checks passed, but CodeQL119789418071 is failure: “Unvalidated dynamic method call” at packages/cli/src/audio/read/read.test.ts:104. I read its annotation and the guarded Map.get handler dispatch; this is a test-fixture alert, not a separately demonstrated production exploit. The current PR body does not explain that failing result. It needs repair or an accepted disposition through normal gates. Three Windows test jobs were still running. No merge or rerun by me.
— Magi Home
Verdict: COMMENT
Reasoning: No additional source blocker found; independent controls support the repair. The observed CodeQL failure prevents an approval at this snapshot.
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Reviewed the successor commit against the previously approved 4ce2f62d head. The only delta is the loopback test fixture's explicit typeof route === "function" check before dispatching a handler from the route Map. It does not change production audio behavior or the partial-stems/cache-retry repair. The prior test-fixture CodeQL alert motivated this guard; fresh JavaScript/TypeScript CodeQL analysis is still running, so I have not represented it as cleared. Other CI remains independently gated. No live stems-service or paid-media validation in this review.
— Jerrai
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Reviewed the merge-forward exact head. Its parents are the previously approved 2f7c4702 audio head and new main 2faf660a. I compared nontruncated exact-ref Git trees for all 23 net PR-owned files: every blob is unchanged from the approved audio head, including stems, cache, service retry, upload and tests. The two consumer-install helper blobs at the merge head are byte-identical to the new main parent, rather than an unreviewed mix of the older branch helper and main's newer parser. The first-parent delta consists of inherited main changes outside the audio feature. Fresh-head lint executed successfully; support tests, Windows lane and JavaScript CodeQL were still running at review. I did not independently rerun the reported scoped Windows diagnostic or verify a live stems backend/media job. This is a code review of the integrated head, not a merge/release action.
— Jerrai
There was a problem hiding this comment.
Reviewed the merge-forward head a9b6db396eeb044f94e2c51e736620efebf365aa. All 23 net PR-owned files have the same Git blobs as reviewed 2f7c4702; the consumer-install helper and its tests are byte-identical to main parent 2faf660a. Independently ran the two focused ageBlockedPackages parser tests: both pass, zero skipped. No new source finding in the integration.
Initial CI snapshot: CLI smoke (required) job119808974463 fails during npm install -g because npm returns HTTP404 for puppeteer-core-25.16.0.tgz. Correction: the earlier build, render and telemetry smoke steps completed successfully; the failure occurs in the later global-install phase. The full required job did not pass at that snapshot. Current-head Windows, runtime/build and JavaScript CodeQL were still running in the snapshot. I did not rerun CI or claim the registry failure is transient.
The previous-head scoped Windows diagnostic job119801477742 succeeded: 2316 engine/5315 CLI tests, plus runtime contracts. Its full log is retained, alongside the original unexplained exit1; that original failure is not claimed fixed and the prior-head success does not replace new-head gates.
Audited: the exact merge parents, complete first-parent delta, blob parity for all net PR-owned files, newer consumer-install parser and its two tests. Audio/cache/service-boundary source remains as in my prior bounded review, with no live service call. Trusting: inherited main changes outside the integration conflict surface and hosted broader suites at their actual head. This is a bounded review of the larger audio feature, not real-recording/backend verification. No merge or deploy.
— Magi Home
Verdict: COMMENT
Reasoning: The integration is source-clear. The installation failure needed diagnosis before an approval refresh; fresh required checks remain independent.
Update 2026-10-22 12:14 UTC: scoped required CLI-smoke job119813422506 now SUCCESS (full log inspected). Current Windows engine-cli job119809364796 again ends exit1 without failed assertion or Vitest summary; required Windows aggregate119814492732 fails. This remains undiagnosed; no rerun by this reviewer.
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Exact final tree matches the reviewed integration, with temporary diagnostics absent. Earlier Windows exits remain unexplained; fresh required checks independently gate merge. — Jerrai
miguel-heygen
left a comment
There was a problem hiding this comment.
Reviewed cleanup head e45dd4e6ad42905335be37de80d9139f9adb45e5. Its complete Git tree is identical to reviewed a9b6db396 (9989d3f0ae0b160854cf44149b61df1caafe0756); the net delta is empty. The cleanup removes all ten temporary Windows diagnostic lines in packages/cli/vitest.config.ts, preserving the existing Fontconfig cleanup. The audio/cache/service-boundary repairs and main integration are unchanged.
I inspected the complete diagnostic Windows log for job119817507264, checked out at merge c2022f3 containing diagnostic head 596a664: engine 2316 passed, CLI 5315 passed / 223 skipped, and runtime contract 2311 passed. The instrumented CLI runner explicitly reports exit 0. This confirms that run completed; it does not diagnose or fix the earlier premature exits, and it does not substitute for final-head CI.
Audited: complete cleanup commit, exact Git-tree parity, authoritative final PR stat (23 files), prior review/inline discussions, and the diagnostic log. This continues my earlier bounded source review and 74-test local verification; I did not rerun those unchanged tests. Trusting: live backend protocol, separation quality, real-recording/performance claims, and final-head hosted suites as they complete. No live service call, source change, rerun, merge or deployment.
At the current e45dd4e6 snapshot, required smoke and other CI are still running; no failed check is present. JavaScript CodeQL is also running, so the aggregate neutral signal is not a claim that analysis has cleared. The earlier unexplained Windows failures remain recorded. Normal required checks remain merge gates.
Verdict: APPROVE
Reasoning: The final source exactly restores the reviewed tree and introduces no new source blocker. The diagnostic run now has complete passing summaries; final-head CI must finish through the normal gates.
— Magi Home
What
hyperframes audio <file> --readreads one whole recording — an episode, a reference video — as time-stamped lines an agent can read or grep, instead of measurements of one sound:--transcript, or<file>.transcript.jsonbeside it) with speaking rate, pitch when it is 3+ semitones off the median, and a rising or falling end; pauses of 0.4 s or more.S1most used), quick runs folded into one line. Hits on the music's beat and sounds more than 20 dB under the recording are left out.resumesafter a short break,returnslater, ornew.--range a,bpicks what is shown; the whole recording's text is cached under.hyperframes/read-audioper file content, with its stems.Stems
The default is
--quality local, which never uploads; at--quality bestthe recording is uploaded through the asset direct-upload routes and separated by HeyGen's/v3/audio/stemswith the CLI's HeyGen credential (HEYGEN_API_KEY,HYPERFRAMES_API_KEY,HEYGEN_ACCESS_TOKENor a savedhyperframes auth logincredential;HYPERFRAMES_AUDIO_STEMS_URL+HYPERFRAMES_AUDIO_API_KEYpoint at another service instead, and the HeyGen credential is never sent there) (POSTwaits for the stems; in-progress or dropped requests retry the same body and idempotency key; contract inpackages/cli/src/audio/read/stems.ts). The backend half of that route is a separate change. Theapianalysis provider now finds its credential the same way, on/v3/audio/analysis. Without stems, effects are read from the mix outside speech and the music is not read; with neither stems nor a transcript, voice and effects are not read at all, because the mix alone does not tell speech from music. Every case says so in a warning.analysis/providers/shared.tsgainsserviceAccessandserviceFailure, now shared by the analysis API provider and the stems client.The configured separation deadline aborts encoding, upload, job requests, retry waits and downloads. Stem files use fixed names for the six documented stems and the existing atomic streaming downloader. An explicitly empty service URL disables that service. Fractional sample hops stay aligned over long recordings; cached read analyses are versioned so older timestamps are recomputed.
Incomplete responses are not cached as complete: all six documented stems must be present. Missing stems produce a fallback warning, and an incomplete cached manifest causes a later
--quality bestread to retry.Verification
htdemucs_6sstems and a Whisper transcript took 9.4 s and about 1 GB RSS. The author checked reported breaks against per-stem levels. That episode is not in the repository.