Skip to content

feat: render audiograms for audio-only episodes - #153

Open
nmbrthirteen wants to merge 3 commits into
mainfrom
audiogram
Open

feat: render audiograms for audio-only episodes#153
nmbrthirteen wants to merge 3 commits into
mainfrom
audiogram

Conversation

@nmbrthirteen

Copy link
Copy Markdown
Owner

What

An episode recorded as audio now produces clips: captions and a moving waveform over the show's artwork, instead of a crash.

Why

There was no path through the tool for an audio podcast. Transcription ran to completion, then get_dimensions raised "No video stream found". An hour of audio paid for its full Whisper run to be told there was never going to be a clip, in a message that reads like a bug rather than a fact about the file.

How

There is no frame to crop and no face to follow, so the picture is made instead of found: artwork behind, the voice as bars, and the same captions every other clip gets. HormoziCaptions and friends are reused untouched, which is why an audiogram looks like the rest of the show rather than like a different product.

The bar levels are computed in Python and handed over already reduced, one row per frame:

  • These samples are already read on that side for moment detection. Shipping an hour of PCM into a browser to average it there is the same arithmetic somewhere slower and harder to test, and it avoids adding @remotion/media-utils.
  • RMS rather than peak, because peaks put every bar at full height on any voice that clips.
  • Normalised against the window's own loudest bar, so a quietly recorded episode still moves.

The branch sits at the top of generate_clip rather than at its five call sites, so process, the MCP tools and batch rendering all get it without knowing anything changed. What comes back is the same dict describing the same window, with crop_strategy: "audiogram".

Detection is deliberately strict. Cover art is a video stream to ffprobe and does not count as a picture to crop. A file ffprobe cannot read at all is not evidence of anything, so it goes down the video road exactly as before, which is what keeps the existing stub-file tests passing.

Verified by rendering

Not just by exit code. A real mp3 through generate_clip produced a 1080x1920 clip with audio muxed, bars tracking the waveform across the window, and captions in step with the words.

Tests

615 pass, 12 new. tests/test_ai_fallback.py::test_find_cli_falls_back_to_shell_lookup fails on my machine and fails identically on untouched main: it finds a real claude at ~/.local/bin that the mock does not isolate.

🤖 Generated with Claude Code

nmbrthirteen and others added 3 commits August 10, 2026 23:31
An episode recorded as audio has no frame to crop, no face to follow and
nothing to burn captions onto, and the pipeline found that out the expensive
way: transcription ran to completion, then get_dimensions raised "No video
stream found". An hour of audio paid for its full Whisper run to be told it was
never going to produce a clip, and the message read like a bug in the tool
rather than a fact about the file.

The header knew from the start. Checked there instead, right after the file is
confirmed to exist.

The check is deliberately stricter than "no video stream found", because this
one is allowed to stop a run. Cover art counts as a video stream to ffprobe and
is not something to crop, so it does not save a file from the message. A file
ffprobe cannot read at all is the opposite case: a stub, a truncated download or
a container this build has no demuxer for is not evidence of anything, and
refusing it with a message about audio would swap one unhelpful failure for
another. Anything short of "read it, has sound, has no moving picture" goes down
the path it always took, which is what keeps the existing tests passing on their
stub files.

Rendering an audiogram for these episodes is the obvious next step and is not
here. This only makes the refusal immediate and true.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An audio podcast had no path through this tool. Transcription ran to
completion, then get_dimensions raised "No video stream found", so an hour of
audio paid for its full Whisper run to be told there was never going to be a
clip, in a message that read like a bug rather than a fact about the file.

There is no frame to crop and no face to follow, so the picture is made instead
of found: the show's own artwork behind, the voice drawn as bars, and the same
captions every other clip gets. The caption components are reused untouched,
which is why an audiogram looks like the rest of the show rather than like a
different product.

The bars are computed here and handed over already reduced, one row of levels
per frame. These samples are read on this side for moment detection anyway, and
shipping an hour of PCM into a browser to average it there would be the same
arithmetic somewhere slower and harder to test. RMS rather than peak, because
peaks put every bar at full height on any voice that clips, and normalised
against the window's own loudest bar so a quietly recorded episode still moves.

The branch lives at the top of generate_clip rather than at its five call
sites, so process, the MCP tools and batch rendering all get it without knowing
anything changed: what comes back is the same dict describing the same window.

Verified by rendering: 1080x1920, audio muxed, bars tracking the waveform,
captions in step.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The .gitignore rule is node_modules/, which matches a directory. My build
worktree linked the real one in as a symlink, and a symlink is a file, so the
rule did not catch it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@nmbrthirteen, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0403b1b6-1f2a-4169-a3c4-cce39fa5284f

📥 Commits

Reviewing files that changed from the base of the PR and between 43e31c0 and 214186c.

📒 Files selected for processing (7)
  • backend/cli.py
  • backend/services/audiogram.py
  • backend/services/clip_generator.py
  • remotion/render-audiogram.mjs
  • remotion/src/Audiogram.tsx
  • remotion/src/Root.tsx
  • tests/test_audiogram.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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