Repository navigation
Conversation
Update the vendored tinyagents submodule to the latest commit. Auto-committed-on: dragonfly
Add a codec module that decodes WAV, Ogg Opus, and M4A AAC audio and exposes probe, transcode, and waveform operations over the bus. The contract version is bumped to 1.11 to cover the three new methods. Auto-committed-on: dragonfly
The mp4 muxer wrote an empty SLConfig descriptor, which violates ISO/IEC 14496-1, so the esds descriptor is patched with the required predefined value byte and all enclosing atom lengths are adjusted. A gapless edit list and corrected track/movie durations are added, and decoded M4A samples are truncated to the container duration so AAC frame padding is not played back. Auto-committed-on: dragonfly
The codec module now validates emitted Opus packets with a separately implemented decoder in tests, and the M4A repair path uses checked arithmetic throughout so malformed muxer output cannot panic. The voice bus rejects codecs it cannot represent instead of silently mapping them. Auto-committed-on: dragonfly
Adds in-memory probe, transcode, and waveform-peak extraction for WAV/PCM16, Ogg/Opus, and M4A/AAC-LC voice notes using pure-Rust codecs, so hosts can handle codec work without FFmpeg or system libraries. WAV decoding now rejects non-PCM16 variants instead of misreading them, and the module contract is bumped to 1.11 with three new methods. Auto-committed-on: dragonfly
Tiny Sweeper reviewAdds an in-memory voice-note codec module (`tinyvoice::codec`) with probe/transcode/waveform operations, a three-method bus contract, and module-side offloading through a bounded worker pool. The change spans the library implementation, the typed `tinyvoice-bus` contract, the `tinyvoice-module` adapter, documentation, and tests. Contract version advances to 1.11 and the declared method count increases from 53 to 56. State: Changes requested Review snapshot
Completeness: Complete What changedIntroduces pure-Rust WAV/PCM16, Ogg/Opus, M4A/AAC-LC, and MP3 decode support with mono 48 kHz encoded output and WAV-to-WAV passthrough; adds typed codec request/response messages and three bus methods (`CodecProbe`, `CodecTranscode`, `CodecWaveform`) bound at contract 1.11; routes module codec calls through a semaphore-bounded `spawn_blocking` pool with an 8 MiB output cap; adds a new `AudioCodec` error variant; and adds internal, integration, and module-level tests plus fixtures and specs. Features
Tests
Findings
Before merge
How this fits togetherflowchart LR
n0["...ration_preserves_all_explicit_host_fields<br/>changed"]:::changed
n1["codec_error"]:::impacted
n2["fixture"]:::impacted
n3["map"]:::impacted
n4["decode"]:::impacted
n5["decode_symphonia"]:::impacted
n6["...tion_uses_the_selected_symphonia_track_id"]:::impacted
n0 -->|calls| n2
n0 -->|tests| n2
n4 -->|calls| n1
n4 -->|calls| n3
n4 -->|calls| n5
n5 -->|calls| n1
n6 -->|calls| n1
n6 -->|tests| n1
n6 -->|calls| n5
n6 -->|tests| n5
classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Agent review detailscritique
security
tests
commits
description
Evidence and run details
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Add MP3 as a supported source codec so TTS output can be probed and transcoded into voice-note formats. CodecTranscode now takes a declared `from` codec that is checked against the payload, and the codec module was split into a directory with its tests. Auto-committed-on: dragonfly
Replace the short pop fixture with a generated voice-envelope clip and a single-packet opus interop tone so the codec tests exercise multiframe encoding, gapless duration, and envelope retention. Add round-trip and truncation coverage for the AAC path and assert that decoded audio keeps the original signal envelope. Auto-committed-on: dragonfly
Replace the manual sample conversion and window averaging with f64::from and a fold, and let the malformed-audio test return Result so fixture encoding failures surface as errors instead of panics. Auto-committed-on: dragonfly
…r crates Regenerate the lockfile after adding tinystoragedrivers-core and the tinyinference-providers dependency, which pulls in the secrets storage stack and its crypto crates. Also bumps the tinyinference crates to 0.4.1. Auto-committed-on: dragonfly
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 329008085e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Add tests asserting that empty, silent, oversized, and unsupported-codec inputs are rejected across probe, waveform, and transcode, and that the ADTS splitter rejects bad or truncated frames. Also verify that an empty Ogg Opus stream yields no playable audio. Auto-committed-on: dragonfly
Adjust the test fixture to match the current OpusHead layout, dropping the removed version field and renaming output_gain to output_gain_q8. Auto-committed-on: dragonfly
Replace the short-input transcode assertions with direct checks that encoding an empty buffer fails for both Opus and AAC/M4A, so the rejection paths for those codecs are exercised without relying on a one-sample WAV. Auto-committed-on: dragonfly
Add an example that exercises Opus encoding over a longer input to verify behaviour beyond the short clips covered by existing tests. Auto-committed-on: dragonfly
There was a problem hiding this comment.
tinysweeper found nothing blocking, but could not review everything, so this is not an approval: crates/tinyvoice-bus/src/codec.rs, crates/tinyvoice-bus/src/version/mod.rs, crates/tinyvoice/src/error/mod.rs, crates/tinyvoice/src/lib.rs.
$0.0059 · 1,376,199 in / 93,234 out · 196,608 cached (14%) · ladder/vectors-oai3, glm-5.3-flash, deepseek-v4.1-flash · 805 embedded
critique: $0.0014 · 582,526 in / 12,803 out · 71,296 cached (12%) · glm-5.3-flash, deepseek-v4.1-flash
security: $0.0041 · 608,889 in / 75,706 out · 113,664 cached (19%) · deepseek-v4.1-flash
tests: $0.0001 · 94,808 in / 1,226 out · 6,144 cached (6%) · deepseek-v4.1-flash
description: $0.0001 · 71,677 in / 1,307 out · 5,504 cached (8%) · deepseek-v4.1-flash
The example that transcoded the voice-envelope fixture to Ogg Opus and WAV baselines has been deleted, as it is no longer needed. Auto-committed-on: dragonfly
Add a test asserting that waveform bucket range computation stays correct for sample counts near u32::MAX, where the naive 32-bit multiply would overflow. The expected bounds are derived with u128 arithmetic so the test pins the wide-math behaviour. Auto-committed-on: dragonfly
The waveform bucket start and end offsets were computed in usize, which could overflow on 32-bit targets for large sample counts. The range calculation now goes through a shared helper that widens to u128 before converting back, and the existing tests were reformatted to match. Auto-committed-on: dragonfly
Codec probe, transcode, and waveform calls now run on blocking workers behind a shared semaphore so attacker-controlled payloads cannot build an unbounded queue. Excess calls fail fast with a capacity error instead of piling up. Auto-committed-on: dragonfly
Add a test that holds the only codec worker slot to confirm run_codec_worker rejects when capacity is full, then releases it and checks the work runs on a different thread than the executor. Auto-committed-on: dragonfly
Extend the waveform bucket test to check the start and end indices for the last bucket of a realistic clip length, ensuring the range covers the final sample. The existing 32-bit limit check is kept alongside the new assertions. Auto-committed-on: dragonfly
Auto-committed-on: dragonfly
Auto-committed-on: dragonfly
The rejection test now builds wav bytes from the shared f32_mono_to_wav helper and patches the sample rate and channel fields directly, so the invalid headers are exercised without depending on hound's writer. Auto-committed-on: dragonfly
The test no longer checks that encoding a single sample to AAC fails, leaving it focused on the dedicated Ogg codec rejection. Auto-committed-on: dragonfly
…s/tinyvoice/tests/codecs.rs Auto-committed-on: dragonfly
Auto-committed-on: dragonfly
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 94e04b1d85
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Add a test asserting the repaired M4A carries an SLConfig descriptor with its required predefined byte and that the result still parses independently, guarding against regressions in the muxer output. Auto-committed-on: dragonfly
The M4A repair no longer inserts a byte into the SLConfig descriptor and bumps every enclosing atom and descriptor length. The muxer already reserves the byte, so the predefined value is now written directly into the existing slot, which keeps all sizes valid without resizing. Auto-committed-on: dragonfly
Add a test that transcodes exactly five minutes of audio to M4A/AAC and asserts the probed duration is 300000 ms, confirming frame padding is trimmed. It also checks that one sample beyond the limit is rejected. Auto-committed-on: dragonfly
AAC decoding can emit up to one frame of samples beyond the nominal duration, so the five minute limit now tolerates a codec-specific padding of 1024 samples while still rejecting anything larger. The M4A path also re-checks the truncated playable length against the limit, and a test covers the padding boundary. Auto-committed-on: dragonfly
Extend the codec tests to check the raw SLConfig descriptor before repair and to verify that a five minute silent AAC fixture trims real frame padding while a longer duration is rejected. The stereo fixture now exposes its muxed bytes so the descriptor can be inspected prior to repair. Auto-committed-on: dragonfly
Reformat the assertion in the m4a SLConfig descriptor test to satisfy rustfmt line length limits. No behaviour change. Auto-committed-on: dragonfly
Auto-committed-on: dragonfly
Auto-committed-on: dragonfly
…okup Renamed MAX_DURATION_SECS to MAX_DURATION_SECONDS for clarity and reformatted the audio track lookup closure to satisfy rustfmt. No behaviour change. Auto-committed-on: dragonfly
Auto-committed-on: dragonfly
Adds a debug dump of the movie timescale, duration, and per-track timing entries to the five-minute AAC padding test so the fixture's container metadata can be inspected when the duration assertion fails. Auto-committed-on: dragonfly
The duration limit comparison now scales the seconds bound by the track timescale instead of converting the duration to nanoseconds first, avoiding a redundant intermediate value. A debug print of parsed mp4 header details was also dropped from the five minute m4a test. Auto-committed-on: dragonfly
Move the M4A duration and playable sample computation out of decode_symphonia into a dedicated m4a_playable_samples helper. The logic is unchanged, but isolating it keeps the decode path shorter and makes the duration limit check easier to follow. Auto-committed-on: dragonfly
The playable sample count for M4A files previously used only the first edit list entry, which undercounted duration when a track had multiple entries. The duration is now the checked sum of every entry, with an error returned on overflow. Auto-committed-on: dragonfly
Trim the extra newline at the end of the CC0-1.0 license fixture so the file matches the canonical text. Auto-committed-on: dragonfly
The M4A duration check previously picked the first audio track from the MP4 container, which could disagree with the track Symphonia actually decoded when a file has multiple audio tracks. The playable-sample calculation now takes the decoded track id and looks up that exact track, and a multi-track fixture test covers the mismatch. Auto-committed-on: dragonfly
The m4a duration test now derives the expected sample count from the track's media header duration and timescale instead of the track duration in microseconds, so the assertion matches the units the decoder actually uses. Auto-committed-on: dragonfly
The m4a duration test now wraps Symphonia probe, MP4 parse, and decode errors with descriptive messages so a failure points at the step that broke instead of surfacing a bare error. Auto-committed-on: dragonfly
The multi-track AAC fixture now rewrites the SLConfig descriptor of every esds atom to the predefined value before returning the encoded bytes, so the generated stream matches what the decoder expects. Auto-committed-on: dragonfly
There was a problem hiding this comment.
tinysweeper found nothing blocking. Approving.
$0.0021 · 1,926,227 in / 23,421 out · 383,104 cached (20%) · ladder/vectors-oai3, deepseek-v4.1-flash · 850 embedded
critique: $0.0012 · 1,109,999 in / 14,480 out · 170,880 cached (15%) · deepseek-v4.1-flash
security: $0.0005 · 576,910 in / 5,334 out · 164,352 cached (28%) · deepseek-v4.1-flash
tests: $0.0001 · 132,851 in / 922 out · 44,544 cached (34%) · deepseek-v4.1-flash
description: $0.0001 · 72,790 in / 822 out · 3,328 cached (5%) · deepseek-v4.1-flash
Add an assertion that the playable sample count for the selected symphonia track differs from the other track's count, so the existing duration check cannot pass by coincidence when both tracks yield the same length. Auto-committed-on: dragonfly
Replace the unchecked cast of AAC_FRAME_SAMPLES with a fallible conversion so the test fixture surfaces an error instead of silently truncating if the constant ever exceeds u32. Auto-committed-on: dragonfly
There was a problem hiding this comment.
Requesting changes: 1 lane(s) blocking, worst finding is high.
Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.
$0.0009 · 678,766 in / 8,829 out · 84,736 cached (12%) · ladder/vectors-oai3, deepseek-v4.1-flash · 849 embedded
critique: $0.0003 · 244,979 in / 3,693 out · 22,400 cached (9%) · deepseek-v4.1-flash
security: $0.0001 · 181,615 in / 1,395 out · 39,808 cached (22%) · deepseek-v4.1-flash
tests: $0.0001 · 143,653 in / 1,094 out · 19,200 cached (13%) · deepseek-v4.1-flash
description: $0.0000 · 72,602 in / 468 out · 3,328 cached (5%) · deepseek-v4.1-flash
| fn looks_like_mp3(bytes: &[u8]) -> bool { | ||
| bytes.starts_with(b"ID3") | ||
| || bytes | ||
| .get(..2) | ||
| .is_some_and(|header| header[0] == 0xff && header[1] & 0xe0 == 0xe0) | ||
| } |
There was a problem hiding this comment.
Bound the MP3 sync test before indexing the header
The is_some_and closure indexes header[1], but get(..2) returns Some only for a slice of length exactly 2 — it is the starts_with(b"ID3") branch that accepts a 3-byte prefix with no length check. A three-byte input beginning ID3 (e.g. b"ID3", or any truncated MP3 tag) therefore reaches bytes.starts_with(b"ID3") == false, then get(..2) == Some(b"ID"), and header[1] == b'D', so header[1] & 0xe0 == 0xe0 is false and the closure does not panic here — but the same shape with the bytes ID followed by a byte whose top three bits are set, e.g. b"ID\xff", takes the same path and panics with "index out of bounds: the len is 2 but the index is 1". That input is reachable from every public entry point that sniffs the container: probe(b"ID\xff"), waveform(b"ID\xff", 1), transcode(b"ID\xff", AudioCodec::Mp3, ...) and decode_as with AudioCodec::Mp3 all call looks_like_mp3 on the raw buffer. A panic in a library path also violates the repository rule "Do not unwrap(), expect(), or panic! in library code paths." Test the length explicitly instead of relying on get(..2) plus indexing.
| fn looks_like_mp3(bytes: &[u8]) -> bool { | |
| bytes.starts_with(b"ID3") | |
| || bytes | |
| .get(..2) | |
| .is_some_and(|header| header[0] == 0xff && header[1] & 0xe0 == 0xe0) | |
| } | |
| fn looks_like_mp3(bytes: &[u8]) -> bool { | |
| bytes.starts_with(b"ID3") | |
| || matches!(bytes.get(..2), Some([0xff, second]) if second & 0xe0 == 0xe0) | |
| } |
[RULE] unchecked-index ·
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 843686b540
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let samples = if rate == VOICE_RATE { | ||
| samples | ||
| } else { | ||
| audio::resample(&samples, rate, VOICE_RATE)? |
There was a problem hiding this comment.
Keep resampled clips within the duration limit
When a WAV is exactly five minutes at certain valid non-48 kHz rates (for example, 2,400,300 frames at 8,001 Hz), this call reaches audio::resample, whose f64 calculation rounds the target length to 14,400,001. The returned Opus/M4A is therefore one sample over the strict decoder limit and is rejected by probe, waveform, and a subsequent transcode even though transcode accepted the source; calculate the target length with integer arithmetic or cap this rounding spill before encoding.
Useful? React with 👍 / 👎.
| let duration_units = elst | ||
| .entries | ||
| .iter() | ||
| .try_fold(0u64, |total, entry| { | ||
| total.checked_add(entry.segment_duration) |
There was a problem hiding this comment.
Apply M4A edit-list media offsets
When an input M4A has an edit list with a leading empty edit (media_time = -1) or a nonzero media offset, m4a_playable_samples sums only segment_duration and the caller later merely truncates the decoded tail. That neither inserts the empty presentation interval nor skips the requested media prefix, so probe and waveform/transcode expose different duration/content than conforming players (for example, one second of empty edit plus one second of audio becomes only the one-second raw decode); apply each entry's media offset and rate to the PCM timeline instead of collapsing the list to a count.
Useful? React with 👍 / 👎.
7a8e3ac
into
tinyhumansai:epic/7964-channel-v2
TinyVoice currently has WAV framing primitives but no shared way to turn common speech-service MP3 output into the Ogg/Opus or AAC/M4A voice-note formats used by channel clients. This adds a bounded, in-memory codec API for WAV/PCM16, Ogg/Opus, M4A/AAC-LC, and MP3 input, with explicit source-format validation, metadata probing, and normalized waveform extraction.
tinyvoice::codec::transcode(bytes, from, to)normalizes encoded voice output to mono 48 kHz, including same-format Opus/AAC conversions; WAV-to-WAV preserves input bytes. MP3 is decode-only. Pure-Rust Opus, AAC-LC, MP3, WAV, and M4A facilities avoid FFmpeg subprocesses and system codec dependencies. The compiled module exposes typedCodecProbe,CodecTranscode, andCodecWaveformoperations throughtinyvoice-buscontract 1.11.Validation includes all-feature tinyvoice and module suites, TinyVoice bus tests, strict clippy, cargo-deny, rustdoc warnings, and local Windows GNU/macOS ARM cross-target checks. Codec integration fixtures prove MP3 decoding, independent Opus decoding, Symphonia AAC decoding, duration preservation, 256-bucket waveform output, and malformed/truncated-input rejection. The measured Linux x86_64 release module is 20.8 MiB total / 7.8 MiB
.text; codec backend detail is indocs/specs/voice-codecs.md. Hosted CI still needs to complete the repository's Windows, macOS, and Linux jobs.