Add production bindings for Python, Android, and iOS - #5
Conversation
Add the shared SDK, Python package crate, UniFFI crate, and core runtime support needed by the bindings plan.
Add the Gradle Android library, lean and bundled ORT flavors, sample app, wrapper files, and generated-output ignore rules.
Add the Swift package, generated UniFFI Swift source, XCFramework packaging scripts, and iOS sample app.
Add package workflow jobs, release docs, binding overview docs, and GPU Python distribution metadata.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughThis PR extracts shared SDK crates, adds Android/Swift/Python binding packages and samples, expands packaging automation, and updates core inference for iOS CoreML support and a pure nalgebra backend. ChangesCore SDK substrate
Sequence Diagram(s)sequenceDiagram
participant PythonAPI
participant SdkPipeline
participant ModelStore
participant AudioDecode
participant Queue
PythonAPI->>SdkPipeline: prepare / from_prepared / diarize_file
SdkPipeline->>ModelStore: load or verify prepared models
SdkPipeline->>AudioDecode: decode and resample audio
SdkPipeline->>SdkPipeline: run diarization
SdkPipeline->>Queue: push or receive queued jobs
Public bindings, packaging, and release workflow
Estimated code review effort: 5 (Critical) | ~150 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
| Filename | Overview |
|---|---|
| crates/speakrs-ffi/src/lib.rs | UniFFI bindings for Swift/Android; SpeakrsQueue::recv holds Mutex while blocking, causing deadlock with concurrent pushes |
| crates/speakrs-python/src/lib.rs | PyO3 Python bindings; same queue deadlock pattern as FFI — recv holds Mutex while blocking |
| crates/speakrs-sdk/src/facade.rs | SDK facade over the diarization pipeline and queue; queue results always report duration=0.0 |
| crates/speakrs-sdk/src/audio.rs | Audio decode/resample using Symphonia and Rubato; well-tested, handles stereo downmix and sample-rate conversion to 16 kHz |
| crates/speakrs-sdk/src/models.rs | Model store: manifest generation, SHA-256 verification, optional HuggingFace download, cache list/cleanup; comprehensive tests |
| src/linalg.rs | Adds pure-linalg backend via nalgebra (inverse + generalized eigendecomposition), refactors existing BLAS backends into sub-modules |
| src/inference/embedding/load/sessions.rs | Makes primary ORT sessions optional for CoreML-native mode; skips ONNX session loading when use_native_coreml is true |
| src/inference/segmentation.rs | Primary segmentation session is now Option, skipped when CoreML native is active; has_batched_session helper added |
| .github/workflows/package.yml | Adds CI jobs for Python wheels (cibuildwheel), Android AARs, and Swift XCFramework; GPU jobs conditional on workflow dispatch input |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
PY[Python caller] -->|PyO3| PYB[speakrs-python]
SW[Swift / Kotlin caller] -->|UniFFI| FFI[speakrs-ffi]
PYB --> SDK[speakrs-sdk facade]
FFI --> SDK
SDK --> PIPELINE[SdkPipeline\nrun_with_config]
SDK --> QUEUE[SdkQueue\npush / recv]
PIPELINE --> SPEAKRS[speakrs core]
QUEUE --> SPEAKRS
SDK --> AUDIO[audio.rs\ndecode + resample]
SDK --> MODELS[models.rs\nModelStore + manifest verify]
MODELS -->|optional| HF[HuggingFace\nhf-hub]
SPEAKRS --> LINALG{linalg backend}
LINALG -->|intel-mkl| MKL[Intel MKL]
LINALG -->|openblas-*| OB[OpenBLAS]
LINALG -->|pure-linalg| NA[nalgebra]
SPEAKRS -->|coreml feature| COREML[CoreML native\nno ORT sessions]
SPEAKRS -->|default| ORT[ONNX Runtime\nSessions]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
PY[Python caller] -->|PyO3| PYB[speakrs-python]
SW[Swift / Kotlin caller] -->|UniFFI| FFI[speakrs-ffi]
PYB --> SDK[speakrs-sdk facade]
FFI --> SDK
SDK --> PIPELINE[SdkPipeline\nrun_with_config]
SDK --> QUEUE[SdkQueue\npush / recv]
PIPELINE --> SPEAKRS[speakrs core]
QUEUE --> SPEAKRS
SDK --> AUDIO[audio.rs\ndecode + resample]
SDK --> MODELS[models.rs\nModelStore + manifest verify]
MODELS -->|optional| HF[HuggingFace\nhf-hub]
SPEAKRS --> LINALG{linalg backend}
LINALG -->|intel-mkl| MKL[Intel MKL]
LINALG -->|openblas-*| OB[OpenBLAS]
LINALG -->|pure-linalg| NA[nalgebra]
SPEAKRS -->|coreml feature| COREML[CoreML native\nno ORT sessions]
SPEAKRS -->|default| ORT[ONNX Runtime\nSessions]
Reviews (1): Last reviewed commit: "Add binding release packaging" | Re-trigger Greptile
Protect queue receiver state with a mutex so queued jobs can be received across threads while producers continue to enqueue. Propagate segment duration through the queue result so bindings can return timing metadata. Add Python and SDK tests to verify concurrent receive behavior and non-zero duration in queue results.
Extend package metadata and CI to target CPython 3.14, including wheel matrix selectors and package classifiers. Update the release checklist and Rust ORT crate feature set to keep build behavior consistent with the new packaging configuration.,
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (8)
.github/workflows/package.yml (3)
104-128: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winSet
persist-credentials: falseon checkout steps.The
actions/checkout@v6action defaults topersist-credentials: true, which leaves the GitHub token in the local git config. For jobs that produce release artifacts, this is an unnecessary credential exposure surface. Consider addingpersist-credentials: falseto all checkout steps in this workflow.🔒 Apply across all checkout steps
- uses: actions/checkout@v6 + with: + persist-credentials: false🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/package.yml around lines 104 - 128, Update the workflow’s `actions/checkout@v6` step in the `rust-api-docs` job to set `persist-credentials: false`, since checkout currently leaves the GitHub token in local git config. Apply the same setting to any other `actions/checkout` steps in this workflow so release-artifact jobs do not retain credentials unnecessarily.Source: Linters/SAST tools
342-393: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider extracting CUDA/MIGraphX jobs into a reusable workflow.
The
python-cuda-wheel(lines 289-341) andpython-migraphx-wheel(lines 342-393) jobs are near-identical, differing only in execution mode name, runner label, cache directory, and output path. A matrix-based approach or reusable workflow would eliminate this duplication and reduce the risk of the two jobs drifting apart over time.♻️ Example matrix approach
python-gpu-wheel: name: Python ${{ matrix.name }} wheel smoke if: ${{ github.event_name == 'workflow_dispatch' && (inputs.gpu_python_package == matrix.name || inputs.gpu_python_package == 'both') }} runs-on: [self-hosted, linux, x64, ${{ matrix.runner_label }}] strategy: matrix: include: - name: cuda runner_label: cuda mode: CUDA cache_dir: gpu-cuda-cache - name: migraphx runner_label: migraphx mode: MIGraphX cache_dir: gpu-migraphx-cache steps: - uses: actions/checkout@v6 with: persist-credentials: false - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Install Python uses: actions/setup-python@v6 with: python-version: "3.12" - name: Install Python build tools run: python -m pip install --upgrade pip uv - name: Build ${{ matrix.name }} wheel working-directory: bindings/python/speakrs-${{ matrix.name }} run: uvx maturin build --release -i python3.12 --compatibility linux --out ../../../wheelhouse/speakrs-${{ matrix.name }} - name: Install ${{ matrix.name }} wheel shell: bash run: | set -euo pipefail python -m pip install --upgrade pip python -m pip install wheelhouse/speakrs-${{ matrix.name }}/*.whl - name: ${{ matrix.name }} fixture smoke shell: bash run: | set -euo pipefail python - <<'PY' from pathlib import Path import speakrs cache_dir = Path("_scratch/${{ matrix.cache_dir }}") cache_dir.mkdir(parents=True, exist_ok=True) prepared = speakrs.prepare(speakrs.ExecutionMode.${{ matrix.mode }}, cache_dir=cache_dir) pipeline = speakrs.Pipeline.from_prepared(prepared, speakrs.ExecutionMode.${{ matrix.mode }}) result = pipeline.diarize_file("fixtures/test_short.wav", file_id="test_short") assert result.duration > 0 assert result.mode == "${{ matrix.mode }}" PY - name: Upload ${{ matrix.name }} wheel uses: actions/upload-artifact@v6 with: name: speakrs-${{ matrix.name }}-wheel path: wheelhouse/speakrs-${{ matrix.name }}/*.whl🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/package.yml around lines 342 - 393, The CUDA and MIGraphX wheel jobs are duplicated and should be consolidated so they don’t drift apart. Refactor the `python-cuda-wheel` and `python-migraphx-wheel` workflow sections into a single matrix-based job or a reusable workflow, using the varying symbols such as `ExecutionMode`, the runner labels, the cache directory names, and the wheel output paths as matrix inputs. Keep the shared build/install/smoke/artifact-upload steps in one place and parameterize only the per-backend differences.
129-167: 🔒 Security & Privacy | 🔵 Trivial | 🏗️ Heavy liftAdd cache integrity safeguards for release artifact jobs.
The
python-wheelsjob usesSwatinem/rust-cache@v2(line 146) and produces wheel artifacts that are intended for public distribution. If the Rust cache is poisoned (e.g., via a compromised PR or cache collision), the resulting wheels could contain malicious code. Consider addingCIBW_BEFORE_BUILDor post-build hash verification, and evaluate whether cache should be disabled or scoped more tightly for release builds.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/package.yml around lines 129 - 167, The python-wheels release job currently relies on Swatinem/rust-cache@v2, so tighten this path to reduce cache-poisoning risk. In the python-wheels workflow job, update the Build Python wheels step (pypa/cibuildwheel@v4.1.0) to add integrity checks such as CIBW_BEFORE_BUILD or post-build wheel hash verification, and consider scoping or disabling the Rust cache for this release artifact path. Keep the change localized to the python-wheels job and its existing cache/build steps.Source: Linters/SAST tools
crates/speakrs-sdk/src/models.rs (1)
452-460: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider using the
hexcrate instead of manual hex encoding.The
hex_lowerfunction manually encodes bytes to hex. Thehexcrate is a well-known, audited dependency that provideshex::encodewith identical behavior. This would reduce custom code and potential for subtle bugs.♻️ Proposed refactor
-fn hex_lower(bytes: &[u8]) -> String { - const HEX: &[u8; 16] = b"0123456789abcdef"; - let mut out = String::with_capacity(bytes.len() * 2); - for byte in bytes { - out.push(HEX[(byte >> 4) as usize] as char); - out.push(HEX[(byte & 0x0f) as usize] as char); - } - out -} +// In Cargo.toml: hex = "0.4" +// Then replace call site: +// hex::encode(hasher.finalize())🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/speakrs-sdk/src/models.rs` around lines 452 - 460, Replace the custom byte-to-hex implementation in hex_lower with the standard hex crate encoder by delegating to hex::encode, and remove the manual loop/lookup table. Update any callers in models.rs to rely on the same hex_lower entry point so the behavior stays identical while reducing custom code and maintenance risk.crates/speakrs-sdk/src/facade.rs (1)
97-128: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
diarize_samplescomputes duration assuming 16kHz without validation.Line 119 computes
samples.len() as f64 / 16_000.0as the duration, which is only correct for 16kHz mono input. Whilediarize_fileensures this via resampling, directdiarize_samplescallers could pass samples at a different rate, producing incorrect duration values in the result. Consider documenting this requirement or accepting sample rate as a parameter.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/speakrs-sdk/src/facade.rs` around lines 97 - 128, The duration calculation in diarize_samples assumes 16 kHz input by using samples.len() / 16_000.0, but this method can be called directly with arbitrary sample rates. Update diarize_samples in facade.rs to either accept an explicit sample rate parameter and use it for the duration passed into map_result, or clearly enforce/document the 16 kHz requirement at the API boundary; reference the diarize_samples method, map_result call, and the current duration computation so the fix stays aligned with the existing pipeline flow.crates/speakrs-sdk/src/audio.rs (1)
269-292: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueCorrupted audio packets are silently skipped during decoding.
SymphoniaError::DecodeError(_)is matched withcontinue, which silently discards corrupted packets. While this is a common pattern for resilient audio playback, it can hide partial data corruption — the caller receives a decoded stream with gaps but no indication that packets were dropped. Consider emitting a warning log or tracking skipped packet count for observability.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/speakrs-sdk/src/audio.rs` around lines 269 - 292, The decode loop in audio.rs currently swallows SymphoniaError::DecodeError(_) by continuing, which hides dropped/corrupted packets from callers. Update the packet handling in the decode match to record skipped decode errors or emit a warning through the existing logging path so the number of discarded packets is observable, while still allowing resilient decoding to continue. Use the decode match in the audio decoding function to locate the change and keep the existing Ok(decoded) and non-decode-error branches intact.src/inference/embedding/load.rs (1)
27-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the
use_native_coremlpattern into a shared helper.The
#[cfg(feature = "coreml")] / #[cfg(not(...))]guard foruse_native_coremlis duplicated insessions.rs(lines 72-75). Extracting it to a method onExecutionModeor a shared function would provide a single source of truth and prevent divergence — if one site is updated without the other, ORT sessions could be incorrectly built or skipped.♻️ Suggested helper extraction
impl ExecutionMode { + pub(crate) fn use_native_coreml(self) -> bool { + #[cfg(feature = "coreml")] + { self.is_coreml() } + #[cfg(not(feature = "coreml"))] + { false } + } +} +``` + +Then in `load.rs`: + +```diff mode.validate()?; - #[cfg(feature = "coreml")] - let use_native_coreml = mode.is_coreml(); - #[cfg(not(feature = "coreml"))] - let use_native_coreml = false; + let use_native_coreml = mode.use_native_coreml();
+And in
sessions.rs:
+
+```diff
- #[cfg(feature = "coreml")]
- let use_native_coreml = mode.is_coreml();
- #[cfg(not(feature = "coreml"))]
- let use_native_coreml = false;
- let use_native_coreml = mode.use_native_coreml();
</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.In
@src/inference/embedding/load.rsaround lines 27 - 34, The
use_native_coremlfeature-guard logic is duplicated betweenload.rsand
sessions.rs, so extract it into a shared helper or anExecutionModemethod
and use that single source everywhere. Updateload_embedding-related flow and
thesessionssetup code to call the shared symbol instead of repeating the
#[cfg(feature = "coreml")]/#[cfg(not(feature = "coreml"))]pattern,
keepingmode.is_coreml()behavior centralized so ORT initialization decisions
stay consistent.</details> <!-- cr-comment:v1:7ccda2325b33c18bfd3b3bfb --> </blockquote></details> <details> <summary>crates/speakrs-python/src/lib.rs (1)</summary><blockquote> `722-728`: _🚀 Performance & Scalability_ | _🔵 Trivial_ | _🏗️ Heavy lift_ **Blocking `recv` holds the GIL for the entire wait duration.** `recv` calls `SdkQueue::recv()` (a blocking channel receive) without releasing the GIL. This blocks all Python threads, including the event loop when `recv_async` delegates to `asyncio.to_thread`. Consider accepting a `py: Python<'_>` parameter and wrapping the blocking call in `py.allow_threads` so other Python threads can run during the wait. <details> <summary>♻️ Suggested refactor</summary> ```diff fn recv(&mut self, py: Python<'_>) -> PyResult<PyQueueResult> { - self.inner - .lock() - .map_err(|_| PyRuntimeError::new_err("queue lock was poisoned"))? - .recv() - .map(py_queue_result) - .map_err(sdk_py_err) + let guard = self + .inner + .lock() + .map_err(|_| PyRuntimeError::new_err("queue lock was poisoned"))?; + let result = py.allow_threads(|| guard.recv())?; + Ok(py_queue_result(result)) }This requires
SdkQueue: Sendso theMutexGuardcan cross theallow_threadsboundary. Verify that bound before implementing.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/speakrs-python/src/lib.rs` around lines 722 - 728, The blocking recv path in the PyO3 wrapper is holding the GIL while waiting on SdkQueue::recv(), which blocks other Python threads. Update the recv method in PyQueue to accept a Python<'_> token and move the blocking queue receive inside py.allow_threads so the GIL is released during the wait; keep the existing error mapping around the result. Before doing this, verify that SdkQueue: Send (and any types held through the Mutex) can safely cross the allow_threads boundary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@bindings/android/speakrs/build.gradle.kts`:
- Around line 205-215: The sourceSets configuration in speakrs is using
Kotlin-specific directories without the Kotlin Android plugin applied, so the
build will fail during configuration. Update the module’s Gradle setup to apply
org.jetbrains.kotlin.android alongside com.android.library, or move the
sourceSets.main.kotlin configuration into a module that already applies the
Kotlin Android plugin; use sourceSets and the main/bundledOrt/lean blocks as the
location to verify the fix.
In `@bindings/python/README.md`:
- Around line 27-33: The wheel build docs are out of sync with CI: the README’s
`maturin build` examples in the CUDA and MIGraphX sections use a different
compatibility flag than the workflow. Update the README examples to match the CI
choice used for the corresponding wheel builds, referencing the `maturin build`
commands under the CUDA and MIGraphX instructions so local builds produce the
same wheel tags as CI.
In `@bindings/swift/Samples/DiarizeFile/DiarizeFile.xcodeproj/project.pbxproj`:
- Around line 67-87: The DiarizeFile target is missing its resource bundle
entry, so `test_short.wav` is not being packaged and `Bundle.main` can fail with
`missingAudioFixture`. Update the Xcode project for the `DiarizeFile` target to
include the resource build phase that contains `test_short.wav` (alongside the
existing `Sources` and `Frameworks` phases), and regenerate/commit the project
so the target’s resources are properly bundled.
In `@crates/speakrs-python/python/speakrs/cli.py`:
- Around line 10-27: The CLI in main currently lets SpeakrsError escape from the
prepare/Pipeline.from_prepared/diarize_file flow, which results in a raw
traceback instead of a clean failure. Update main to wrap those pipeline calls
in a try/except that catches speakrs.SpeakrsError, prints a concise error
message to stderr, and returns a non-zero exit code; also add the requested sys
and speakrs imports so the exception path and exit handling can be referenced
from main.
In `@crates/speakrs-sdk/src/audio.rs`:
- Around line 177-209: The resampling helper uses an unsupported Rubato
constructor, so update resample_mono_to_16khz to stop calling
InterleavedOwned::new_from and switch to the buffer constructor that exists in
rubato 3.0.0. Keep the rest of the flow in resample_mono_to_16khz unchanged,
including Async::new_poly, process_all_needed_output_len, and
process_all_into_buffer, and adjust the input buffer setup so it matches the
supported API.
In `@crates/speakrs-sdk/src/facade.rs`:
- Around line 248-257: The categorization in from_pipeline_error is relying on
brittle message substring matching, so update it to inspect the
speakrs::pipeline::PipelineError value directly instead of calling to_string()
and checking for "requires the `". Use the existing from_pipeline_error helper
to map concrete PipelineError variants or any exposed structured fields to
SdkErrorCategory::UnsupportedRuntime versus SdkErrorCategory::Pipeline,
preserving the stable error category contract.
- Around line 259-265: The `from_audio_error` helper currently maps every
`AudioDecodeError` variant to `SdkErrorCategory::AudioDecode`, so update it to
special-case `AudioDecodeError::Resample` and map that variant to
`SdkErrorCategory::AudioResample` while leaving the other decode errors
unchanged. Use the existing `from_audio_error` function and `SdkErrorCategory`
variants to keep the category consistent for downstream consumers.
In `@crates/speakrs-sdk/src/models.rs`:
- Around line 229-242: The cleanup_revision method is building a filesystem path
directly from the untrusted revision string, which allows path traversal. In
cleanup_revision, validate revision before joining it to
cache_dir/cache_repo_dir, rejecting any value containing path separators or
parent-directory segments like .., and only then call remove_dir_all on the
sanitized path. Use the existing cleanup_revision and
ModelPrepareError::CacheCleanup flow so invalid input fails safely before
deletion.
---
Nitpick comments:
In @.github/workflows/package.yml:
- Around line 104-128: Update the workflow’s `actions/checkout@v6` step in the
`rust-api-docs` job to set `persist-credentials: false`, since checkout
currently leaves the GitHub token in local git config. Apply the same setting to
any other `actions/checkout` steps in this workflow so release-artifact jobs do
not retain credentials unnecessarily.
- Around line 342-393: The CUDA and MIGraphX wheel jobs are duplicated and
should be consolidated so they don’t drift apart. Refactor the
`python-cuda-wheel` and `python-migraphx-wheel` workflow sections into a single
matrix-based job or a reusable workflow, using the varying symbols such as
`ExecutionMode`, the runner labels, the cache directory names, and the wheel
output paths as matrix inputs. Keep the shared
build/install/smoke/artifact-upload steps in one place and parameterize only the
per-backend differences.
- Around line 129-167: The python-wheels release job currently relies on
Swatinem/rust-cache@v2, so tighten this path to reduce cache-poisoning risk. In
the python-wheels workflow job, update the Build Python wheels step
(pypa/cibuildwheel@v4.1.0) to add integrity checks such as CIBW_BEFORE_BUILD or
post-build wheel hash verification, and consider scoping or disabling the Rust
cache for this release artifact path. Keep the change localized to the
python-wheels job and its existing cache/build steps.
In `@crates/speakrs-python/src/lib.rs`:
- Around line 722-728: The blocking recv path in the PyO3 wrapper is holding the
GIL while waiting on SdkQueue::recv(), which blocks other Python threads. Update
the recv method in PyQueue to accept a Python<'_> token and move the blocking
queue receive inside py.allow_threads so the GIL is released during the wait;
keep the existing error mapping around the result. Before doing this, verify
that SdkQueue: Send (and any types held through the Mutex) can safely cross the
allow_threads boundary.
In `@crates/speakrs-sdk/src/audio.rs`:
- Around line 269-292: The decode loop in audio.rs currently swallows
SymphoniaError::DecodeError(_) by continuing, which hides dropped/corrupted
packets from callers. Update the packet handling in the decode match to record
skipped decode errors or emit a warning through the existing logging path so the
number of discarded packets is observable, while still allowing resilient
decoding to continue. Use the decode match in the audio decoding function to
locate the change and keep the existing Ok(decoded) and non-decode-error
branches intact.
In `@crates/speakrs-sdk/src/facade.rs`:
- Around line 97-128: The duration calculation in diarize_samples assumes 16 kHz
input by using samples.len() / 16_000.0, but this method can be called directly
with arbitrary sample rates. Update diarize_samples in facade.rs to either
accept an explicit sample rate parameter and use it for the duration passed into
map_result, or clearly enforce/document the 16 kHz requirement at the API
boundary; reference the diarize_samples method, map_result call, and the current
duration computation so the fix stays aligned with the existing pipeline flow.
In `@crates/speakrs-sdk/src/models.rs`:
- Around line 452-460: Replace the custom byte-to-hex implementation in
hex_lower with the standard hex crate encoder by delegating to hex::encode, and
remove the manual loop/lookup table. Update any callers in models.rs to rely on
the same hex_lower entry point so the behavior stays identical while reducing
custom code and maintenance risk.
In `@src/inference/embedding/load.rs`:
- Around line 27-34: The `use_native_coreml` feature-guard logic is duplicated
between `load.rs` and `sessions.rs`, so extract it into a shared helper or an
`ExecutionMode` method and use that single source everywhere. Update
`load_embedding`-related flow and the `sessions` setup code to call the shared
symbol instead of repeating the `#[cfg(feature = "coreml")]` /
`#[cfg(not(feature = "coreml"))]` pattern, keeping `mode.is_coreml()` behavior
centralized so ORT initialization decisions stay consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d7772e8d-8a55-4e1c-a969-3c297ac3b988
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.lockbindings/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jar
📒 Files selected for processing (68)
.gitattributes.github/workflows/package.yml.gitignoreCargo.tomlbindings/README.mdbindings/RELEASE.mdbindings/android/README.mdbindings/android/build.gradle.ktsbindings/android/gradle.propertiesbindings/android/gradle/wrapper/gradle-wrapper.propertiesbindings/android/gradlewbindings/android/gradlew.batbindings/android/sample/build.gradle.ktsbindings/android/sample/src/main/AndroidManifest.xmlbindings/android/sample/src/main/java/com/avencera/speakrs/sample/MainActivity.ktbindings/android/sample/src/main/res/values/styles.xmlbindings/android/settings.gradle.ktsbindings/android/speakrs/build.gradle.ktsbindings/android/speakrs/src/main/AndroidManifest.xmlbindings/python/README.mdbindings/python/speakrs-cuda/README.mdbindings/python/speakrs-cuda/pyproject.tomlbindings/python/speakrs-migraphx/README.mdbindings/python/speakrs-migraphx/pyproject.tomlbindings/swift/Package.swiftbindings/swift/README.mdbindings/swift/Samples/DiarizeFile/DiarizeFile.xcodeproj/project.pbxprojbindings/swift/Samples/DiarizeFile/DiarizeFile.xcodeproj/project.xcworkspace/contents.xcworkspacedatabindings/swift/Samples/DiarizeFile/README.mdbindings/swift/Samples/DiarizeFile/Sources/DiarizeFileApp.swiftbindings/swift/Samples/DiarizeFile/project.ymlbindings/swift/Sources/Speakrs/speakrs_ffi.swiftbindings/swift/scripts/build-xcframework.shbindings/swift/scripts/package-xcframework.shcrates/speakrs-ffi/Cargo.tomlcrates/speakrs-ffi/src/bin/uniffi-bindgen.rscrates/speakrs-ffi/src/lib.rscrates/speakrs-ffi/uniffi.tomlcrates/speakrs-python/API.mdcrates/speakrs-python/Cargo.tomlcrates/speakrs-python/README.mdcrates/speakrs-python/examples/diarize_file.pycrates/speakrs-python/pyproject.tomlcrates/speakrs-python/python/speakrs/__init__.pycrates/speakrs-python/python/speakrs/cli.pycrates/speakrs-python/src/lib.rscrates/speakrs-python/tests/test_python_api.pycrates/speakrs-sdk/Cargo.tomlcrates/speakrs-sdk/src/audio.rscrates/speakrs-sdk/src/cancel.rscrates/speakrs-sdk/src/dto.rscrates/speakrs-sdk/src/error.rscrates/speakrs-sdk/src/facade.rscrates/speakrs-sdk/src/lib.rscrates/speakrs-sdk/src/models.rssrc/clustering/plda.rssrc/inference.rssrc/inference/embedding.rssrc/inference/embedding/load.rssrc/inference/embedding/load/sessions.rssrc/inference/embedding/native/loaders.rssrc/inference/embedding/run.rssrc/inference/segmentation.rssrc/inference/segmentation/native.rssrc/inference/segmentation/run.rssrc/lib.rssrc/linalg.rssrc/pipeline/queued.rs
| sourceSets { | ||
| getByName("main") { | ||
| kotlin.directories.add(generatedKotlinDir.get().asFile.absolutePath) | ||
| } | ||
| getByName("bundledOrt") { | ||
| jniLibs.directories.add(generatedJniRoot.get().dir("bundledOrt").asFile.absolutePath) | ||
| } | ||
| getByName("lean") { | ||
| jniLibs.directories.add(generatedJniRoot.get().dir("lean").asFile.absolutePath) | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n --glob 'bindings/android/**/build.gradle*' --glob 'bindings/android/**/settings.gradle*' \
'org\.jetbrains\.kotlin\.android|kotlin\("android"\)|id\("org\.jetbrains\.kotlin\.android"\)'Repository: avencera/speakrs
Length of output: 154
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- files ---\n'
git ls-files 'bindings/android/**' | sed -n '1,200p'
printf '\n--- kotlin android plugin references in repo ---\n'
rg -n 'org\.jetbrains\.kotlin\.android|kotlin\("android"\)|id\("org\.jetbrains\.kotlin\.android"\)|kotlin-android|android kotlin' .
printf '\n--- build files mentioning sourceSets/main/kotlin ---\n'
rg -n 'sourceSets|kotlin\.directories|generatedKotlinDir|generatedJniRoot' bindings/androidRepository: avencera/speakrs
Length of output: 245
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the target build file and nearby build scripts.
wc -l bindings/android/speakrs/build.gradle.kts bindings/android/**/build.gradle.kts 2>/dev/null || true
printf '\n--- speakrs build.gradle.kts around sourceSets ---\n'
sed -n '1,260p' bindings/android/speakrs/build.gradle.kts
printf '\n--- other android gradle files ---\n'
find bindings/android -name 'build.gradle.kts' -o -name 'build.gradle' | sort | sed -n '1,200p'Repository: avencera/speakrs
Length of output: 8629
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- root build files ---\n'
git ls-files '*build.gradle*' '*settings.gradle*' 'gradle/libs.versions.toml' | sed -n '1,200p'
printf '\n--- plugin declarations mentioning android/kotlin ---\n'
rg -n 'plugins \{|id\("com\.android\.library"\)|id\("org\.jetbrains\.kotlin\.android"\)|kotlin\("android"\)|alias\(libs\.plugins' . --glob '*build.gradle*' --glob 'gradle/libs.versions.toml' --glob '*settings.gradle*'
printf '\n--- speakrs build.gradle.kts full file stats and top section ---\n'
wc -l bindings/android/speakrs/build.gradle.kts
sed -n '1,120p' bindings/android/speakrs/build.gradle.ktsRepository: avencera/speakrs
Length of output: 245
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- target file excerpt ---\n'
sed -n '1,260p' bindings/android/speakrs/build.gradle.kts
printf '\n--- android-related build scripts and convention plugins ---\n'
find . \( -path './.git' -o -path './node_modules' -o -path './build' \) -prune -o \
\( -name 'build.gradle.kts' -o -name 'build.gradle' -o -name 'settings.gradle.kts' -o -name 'settings.gradle' -o -name 'libs.versions.toml' -o -path './buildSrc/*' \) -print | sort | sed -n '1,250p'
printf '\n--- occurrences of com.android.library / kotlin android plugin / sourceSets.kotlin ---\n'
rg -n 'com\.android\.library|org\.jetbrains\.kotlin\.android|kotlin\("android"\)|sourceSets\.|generatedKotlinDir|generatedJniRoot' \
. --glob '!**/.git/**' --glob '!**/node_modules/**' --glob '!**/build/**'Repository: avencera/speakrs
Length of output: 245
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the exact lines around the reported block and surrounding plugin declarations.
nl -ba bindings/android/speakrs/build.gradle.kts | sed -n '1,260p'
printf '\n--- search for any implicit Kotlin Android application in the Android tree ---\n'
rg -n 'apply\(plugin = "org\.jetbrains\.kotlin\.android"\)|apply\(plugin = "kotlin-android"\)|id\("org\.jetbrains\.kotlin\.android"\)|kotlin\("android"\)' bindings/android .github gradle buildSrc \
--glob '*.gradle*' --glob '*.kts' --glob '!*build/**' --glob '!*node_modules/**'Repository: avencera/speakrs
Length of output: 194
🏁 Script executed:
#!/bin/bash
set -euo pipefail
set -o pipefail
printf '--- top-level gradle files ---\n'
find . -maxdepth 3 \( -name 'build.gradle.kts' -o -name 'build.gradle' -o -name 'settings.gradle.kts' -o -name 'settings.gradle' -o -name 'libs.versions.toml' -o -path './buildSrc/*' \) | sort
printf '\n--- plugin applications/references ---\n'
rg -n --hidden --glob '!**/.git/**' --glob '!**/node_modules/**' --glob '!**/build/**' \
'org\.jetbrains\.kotlin\.android|kotlin\("android"\)|id\("org\.jetbrains\.kotlin\.android"\)|apply\(plugin\s*=\s*"org\.jetbrains\.kotlin\.android"\)|apply\(plugin\s*=\s*"kotlin-android"\)|com\.android\.library|com\.android\.application' \
.
printf '\n--- files under bindings/android ---\n'
find bindings/android -maxdepth 3 -type f | sort | sed -n '1,200p'Repository: avencera/speakrs
Length of output: 245
🏁 Script executed:
#!/bin/bash
set -euo pipefail
set -o pipefail
printf '--- target file excerpt ---\n'
nl -ba bindings/android/speakrs/build.gradle.kts | sed -n '1,260p'
printf '\n--- gradle files and plugin declarations ---\n'
find . -maxdepth 4 \( -name 'build.gradle.kts' -o -name 'build.gradle' -o -name 'settings.gradle.kts' -o -name 'settings.gradle' -o -name 'libs.versions.toml' \) | sort | sed -n '1,200p'
printf '\n--- kotlin/android plugin references ---\n'
rg -n --hidden --glob '!**/.git/**' --glob '!**/node_modules/**' --glob '!**/build/**' \
'org\.jetbrains\.kotlin\.android|kotlin\("android"\)|id\("org\.jetbrains\.kotlin\.android"\)|apply\(plugin\s*=\s*"org\.jetbrains\.kotlin\.android"\)|apply\(plugin\s*=\s*"kotlin-android"\)' \
.
printf '\n--- sourceSets.kotlin / generated dirs references under bindings/android ---\n'
rg -n 'sourceSets|kotlin\.directories|generatedKotlinDir|generatedJniRoot' bindings/androidRepository: avencera/speakrs
Length of output: 245
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- bindings/android/build.gradle.kts ---'
cat -n bindings/android/build.gradle.kts | sed -n '1,220p'
echo
echo '--- plugin references under bindings/android ---'
grep -RInE 'org\.jetbrains\.kotlin\.android|kotlin\("android"\)|id\("org\.jetbrains\.kotlin\.android"\)|apply\(plugin *= *"org\.jetbrains\.kotlin\.android"\)|apply\(plugin *= *"kotlin-android"\)' bindings/android || true
echo
echo '--- sourceSets/kotlin references under bindings/android ---'
grep -RInE 'sourceSets|kotlin\.directories|generatedKotlinDir|generatedJniRoot' bindings/android || trueRepository: avencera/speakrs
Length of output: 1648
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- bindings/android/build.gradle.kts ---'
cat -n bindings/android/build.gradle.kts | sed -n '1,220p'
echo
echo '--- bindings/android/sample/build.gradle.kts ---'
cat -n bindings/android/sample/build.gradle.kts | sed -n '1,220p'
echo
echo '--- kotlin android plugin references anywhere in repository ---'
grep -RInE 'org\.jetbrains\.kotlin\.android|kotlin\("android"\)|id\("org\.jetbrains\.kotlin\.android"\)|apply\(plugin *= *"org\.jetbrains\.kotlin\.android"\)|apply\(plugin *= *"kotlin-android"\)' . --exclude-dir=.git --exclude-dir=build --exclude-dir=node_modules || trueRepository: avencera/speakrs
Length of output: 1952
Apply the Kotlin Android plugin here
bindings/android/speakrs/build.gradle.kts:205 uses sourceSets.main.kotlin, but this module only applies com.android.library. Add org.jetbrains.kotlin.android (or move this config into a module that already applies it), otherwise configuration will fail.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bindings/android/speakrs/build.gradle.kts` around lines 205 - 215, The
sourceSets configuration in speakrs is using Kotlin-specific directories without
the Kotlin Android plugin applied, so the build will fail during configuration.
Update the module’s Gradle setup to apply org.jetbrains.kotlin.android alongside
com.android.library, or move the sourceSets.main.kotlin configuration into a
module that already applies the Kotlin Android plugin; use sourceSets and the
main/bundledOrt/lean blocks as the location to verify the fix.
| pub fn resample_mono_to_16khz( | ||
| samples: &[f32], | ||
| sample_rate_hz: u32, | ||
| ) -> Result<Vec<f32>, AudioDecodeError> { | ||
| let Some(plan) = ResamplePlan::to_sdk_rate(sample_rate_hz)? else { | ||
| return Ok(samples.to_vec()); | ||
| }; | ||
|
|
||
| if samples.is_empty() { | ||
| return Ok(Vec::new()); | ||
| } | ||
|
|
||
| let input = InterleavedOwned::new_from(samples.to_vec(), 1, samples.len()) | ||
| .map_err(|err| AudioDecodeError::Resample(err.to_string()))?; | ||
| let mut resampler = Async::<f32>::new_poly( | ||
| plan.ratio(), | ||
| 1.1, | ||
| PolynomialDegree::Septic, | ||
| 1024, | ||
| 1, | ||
| FixedAsync::Input, | ||
| ) | ||
| .map_err(|err| AudioDecodeError::Resample(err.to_string()))?; | ||
| let output_capacity = resampler.process_all_needed_output_len(samples.len()); | ||
| let mut output = InterleavedOwned::new(0.0, 1, output_capacity); | ||
| let (_, output_len) = resampler | ||
| .process_all_into_buffer(&input, &mut output, samples.len(), None) | ||
| .map_err(|err| AudioDecodeError::Resample(err.to_string()))?; | ||
|
|
||
| let mut samples = output.take_data(); | ||
| samples.truncate(output_len); | ||
| Ok(samples) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify rubato 3.0.0 API by checking the crate documentation
# Check if InterleavedOwned::new_from, process_all_needed_output_len, and process_all_into_buffer exist
rg -n "pub fn new_from" --type rust $(find / -path "*/rubato*/src/audioadapter*" 2>/dev/null | head -5) 2>/dev/null || echo "Could not find rubato source locally"
rg -n "process_all_needed_output_len\|process_all_into_buffer" --type rust $(find / -path "*/rubato*/src/*" 2>/dev/null | head -5) 2>/dev/null || echo "Could not find rubato source locally"Repository: avencera/speakrs
Length of output: 191
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Dependency pin =="
rg -n 'rubato' Cargo.toml Cargo.lock crates -g '!target' || true
echo
echo "== Relevant code context =="
sed -n '1,260p' crates/speakrs-sdk/src/audio.rsRepository: avencera/speakrs
Length of output: 8707
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Cargo.lock rubato entries =="
rg -n '^name = "rubato"$|^version = ' Cargo.lock -n -A2 -B2
echo
echo "== Locate rubato source in cargo registry =="
python3 - <<'PY'
import glob, os
cands = glob.glob(os.path.expanduser('~/.cargo/registry/src/*/rubato-*'))
print("\n".join(cands[:20]) if cands else "NO_RUBATO_SOURCE_FOUND")
PY
echo
echo "== Search rubato source for the relevant APIs =="
RUBATO_SRC="$(python3 - <<'PY'
import glob, os
cands = glob.glob(os.path.expanduser('~/.cargo/registry/src/*/rubato-*'))
print(cands[0] if cands else "")
PY
)"
if [ -n "$RUBATO_SRC" ] && [ -d "$RUBATO_SRC" ]; then
rg -n "struct InterleavedOwned|impl InterleavedOwned|fn new_from|fn new\(|fn new_poly|fn process_all_needed_output_len|fn process_all_into_buffer|trait Resampler" "$RUBATO_SRC/src" || true
else
echo "rubato source not found in cargo registry cache"
fiRepository: avencera/speakrs
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import glob, os, pathlib, re
cands = glob.glob(os.path.expanduser('~/.cargo/registry/src/*/rubato-3.0.0'))
if not cands:
print("NO_RUBATO_3_0_0_SOURCE_FOUND")
raise SystemExit(0)
root = cands[0]
print(root)
targets = [
"src/lib.rs",
"src/async_resampler.rs",
"src/audioadapter_buffers/owned.rs",
"src/audioadapter_buffers/mod.rs",
]
for rel in targets:
p = pathlib.Path(root) / rel
if p.exists():
print(f"\n== {rel} ==")
text = p.read_text(errors="ignore").splitlines()
for needle in [
"pub struct InterleavedOwned",
"impl InterleavedOwned",
"fn new_from",
"fn new(",
"fn new_poly",
"fn process_all_needed_output_len",
"fn process_all_into_buffer",
"trait Resampler",
]:
for i, line in enumerate(text, 1):
if needle in line:
start = max(1, i - 3)
end = min(len(text), i + 8)
print(f"-- match {needle!r} at line {i} --")
for j in range(start, end + 1):
print(f"{j}:{text[j-1]}")
break
else:
print(f"\n== MISSING {rel} ==")
PYRepository: avencera/speakrs
Length of output: 183
🌐 Web query:
rubato 3.0.0 docs.rs InterleavedOwned new_from Async new_poly process_all_needed_output_len process_all_into_buffer
💡 Result:
In rubato 3.0.0, the components you mentioned are part of the core API for audio resampling. Below is a summary of their roles and usage based on the official documentation [1][2]. InterleavedOwned InterleavedOwned is a struct that wraps a Vec to store interleaved audio samples [1]. It is used as the return type for the convenience method Resampler::process, which allocates a new output buffer for each call [1][3]. Because it involves heap allocation, it is generally not recommended for real-time applications [1][2]. Async Async is one of the available resampler types (implementing the Resampler trait) that uses sinc interpolation [4]. It is designed for scenarios where sample rate conversion is required [4]. process_all_needed_output_len This method calculates the minimal length of the output buffer required to process an input buffer of a given length (input_len) using the process_all_into_buffer method [2][3]. It accounts for the expected output size (based on the resample ratio) plus additional buffer space required by the resampler, including its internal delay [3]. process_all_into_buffer This method is a convenience function that processes entire audio clips of arbitrary length [1][2]. It repeatedly calls process_into_buffer internally until all input frames have been processed [2]. It writes the result into a provided, pre-allocated output buffer (which must implement the AdapterMut trait) and automatically trims the initial silence caused by the resampler's delay [2]. Technical Context In rubato 3.0.0, the Resampler trait provides these methods to handle different use cases [1]: 1. Real-time processing: Use process_into_buffer with a pre-allocated buffer (e.g., using an Adapter) to avoid unpredictable heap allocations [2]. 2. Non-real-time/Batch processing: Use process_all_into_buffer for convenience when the entire audio clip is available in memory [1][2]. 3. Simple/Convenience usage: Use process if the overhead of allocation per call is acceptable [1][2]. For real-time usage, you should pre-calculate the required buffer size using process_all_needed_output_len and pass a pre-allocated output buffer to process_all_into_buffer or process_into_buffer to ensure predictable performance [2][3].
Citations:
- 1: https://docs.rs/rubato/latest/rubato/trait.Resampler.html
- 2: https://docs.rs/rubato/latest/rubato/struct.Fft.html
- 3: https://docs.rs/rubato/latest/src/rubato/lib.rs.html
- 4: https://docs.rs/rubato/latest/rubato/struct.Async.html
🌐 Web query:
rubato 3.0.0 Async::new_poly signature InterleavedOwned::new_from process_all_needed_output_len process_all_into_buffer docs.rs
💡 Result:
In rubato 3.0.0, the Async::new_poly constructor and the Resampler trait methods for buffer processing have specific signatures and usage patterns as follows: Async::new_poly signature The Async::new_poly constructor is used to create an asynchronous polynomial resampler [1]. Its signature is as follows: pub fn new_poly( resample_ratio: f64, max_relative_ratio: f64, degree: PolynomialDegree, chunk_size: usize, channels: usize, fixed: FixedAsync) -> ResampleResult This constructor accepts the resampling ratio, maximum relative ratio, polynomial interpolation degree (e.g., PolynomialDegree::Septic), chunk size, number of channels, and a FixedAsync variant (FixedAsync::Input or FixedAsync::Output) to determine if input or output size is fixed [1][2]. Resampler trait methods The methods process_all_needed_output_len and process_all_into_buffer are provided by the Resampler trait [3][4]. 1. process_all_needed_output_len(input_len: usize) -> usize: This method calculates the minimal length of the output buffer required to process an input clip of a specific length using process_all_into_buffer [3][4]. 2. process_all_into_buffer<'a, 'b>(&mut self, buffer_in: &dyn Adapter<'a, T>, buffer_out: &mut dyn AdapterMut<'b, T>, input_len: usize, active_channels_mask: Option<&[bool]>) -> ResampleResult<(usize, usize)>: This is a convenience method that repeatedly calls process_into_buffer to process an entire input buffer [3][4]. It returns the number of input frames processed and the number of output frames produced [3]. Regarding InterleavedOwned::new_from, this method is part of the audioadapter ecosystem (often used with or re-exported by rubato) for creating buffers, but the primary API for processing in rubato typically uses the Adapter/AdapterMut traits for memory-efficient handling rather than direct interaction with InterleavedOwned unless specifically initializing your own buffers [1][3][5]. top_results: [1][3][4][6]
Citations:
- 1: https://context7.com/henquist/rubato/llms.txt
- 2: https://docs.rs/rubato/latest/rubato/struct.Async.html
- 3: https://docs.rs/rubato/latest/rubato/trait.Resampler.html
- 4: https://docs.rs/rubato/latest/rubato/struct.Fft.html
- 5: https://docs.rs/rubato/latest/rubato/all.html
- 6: https://github.com/HEnquist/rubato/
🌐 Web query:
rubato 3.0.0 audioadapter-buffers InterleavedOwned::new_from docs
💡 Result:
In rubato 3.0.0, InterleavedOwned::new_from is not a standard part of the public API for the main resampler structs. Instead, the library uses InterleavedOwned::new to allocate buffers, as seen in the library's internal source code [1]. The InterleavedOwned struct is provided by the audioadapter-buffers crate (which rubato depends on) [2]. Based on the rubato 3.0.0 source code, you typically initialize an InterleavedOwned buffer using the new constructor [1]: InterleavedOwned::::new(fill_value, channels, frames) In this call: - fill_value: The initial value to populate the buffer (e.g., T::coerce_from(0.0)) [1]. - channels: The number of audio channels [1]. - frames: The number of frames to allocate [1]. If you are looking for functions to wrap existing data, the rubato documentation and examples typically recommend using InterleavedSlice::new for immutable slices or InterleavedSlice::new_mut for mutable slices [3][4]. These adapters allow you to work with existing memory without forcing an allocation, which is generally preferred for real-time audio processing [5][6]. The 3.0.0 release of rubato primarily introduced changes to lifetimes in process_into_buffer and performance improvements, but the fundamental buffer handling patterns using the audioadapter ecosystem remain consistent with these established methods [3][7].
Citations:
- 1: https://docs.rs/rubato/latest/src/rubato/lib.rs.html
- 2: https://docs.rs/rubato/latest/rubato/all.html
- 3: https://docs.rs/crate/rubato/latest
- 4: https://github.com/HEnquist/rubato/
- 5: https://docs.rs/rubato/latest/rubato/trait.Resampler.html
- 6: https://docs.rs/rubato/latest/rubato/struct.Async.html
- 7: https://github.com/HEnquist/rubato/releases/tag/v3.0.0
🌐 Web query:
site:docs.rs/rubato 3.0.0 "InterleavedOwned::new_from"
💡 Result:
There is no public API or documented method named InterleavedOwned::new_from in the rubato or audioadapter-buffers crates [1][2]. In the rubato crate, the InterleavedOwned struct is used as an output buffer for resamplers [1][2]. The standard method for creating an instance of this struct is InterleavedOwned::new, which takes a default value, the number of channels, and the number of frames as arguments [1]. Example usage found in the source code: let mut buffer_out = InterleavedOwned::::new(T::coerce_from(0.0), channels, frames); The rubato crate uses the audioadapter crate to handle input and output buffers [2][3]. For realtime applications, it is recommended to avoid the convenience methods that allocate memory (such as those returning InterleavedOwned) and instead use pre-allocated buffers with process_into_buffer [2][4].
Citations:
- 1: https://docs.rs/rubato/latest/src/rubato/lib.rs.html
- 2: https://docs.rs/rubato/latest/rubato/trait.Resampler.html
- 3: https://docs.rs/rubato/^1.0.1/
- 4: https://docs.rs/rubato/latest/rubato/struct.FftFixedInOut.html
Replace InterleavedOwned::new_from with a supported buffer constructor rubato 3.0.0 does not expose InterleavedOwned::new_from; the Async::new_poly, process_all_needed_output_len, and process_all_into_buffer calls match the 3.0.0 API.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/speakrs-sdk/src/audio.rs` around lines 177 - 209, The resampling
helper uses an unsupported Rubato constructor, so update resample_mono_to_16khz
to stop calling InterleavedOwned::new_from and switch to the buffer constructor
that exists in rubato 3.0.0. Keep the rest of the flow in resample_mono_to_16khz
unchanged, including Async::new_poly, process_all_needed_output_len, and
process_all_into_buffer, and adjust the input buffer setup so it matches the
supported API.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/package.yml (1)
143-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRust toolchain version inconsistency between Linux and macOS builds.
Linux builds pin Rust
1.88.0viaCIBW_BEFORE_ALL_LINUX(line 162), while macOS builds use unpinnedstablefromdtolnay/rust-toolchain@stable(line 143). Since cibuildwheel runs macOS builds natively on the runner (no container), macOS wheels compile with whatever "stable" resolves to at CI time, while Linux wheels always use 1.88.0. This divergence could produce different codegen or behavior across platforms if a new Rust release changes defaults.Consider pinning the macOS toolchain to the same version, e.g.
dtolnay/rust-toolchain@1.88.0, or addingCIBW_BEFORE_ALL_MACOSfor consistency.Also applies to: 162-162
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/package.yml at line 143, The Rust toolchain is inconsistent between the macOS and Linux build paths in the package workflow, so update the macOS setup to match the pinned Linux version used by CIBW_BEFORE_ALL_LINUX. Change the dtolnay/rust-toolchain action in the workflow to a fixed 1.88.0 reference, or otherwise make CIBW_BEFORE_ALL_MACOS install the same Rust version, so the cibuildwheel jobs build with a consistent toolchain across platforms.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In @.github/workflows/package.yml:
- Line 143: The Rust toolchain is inconsistent between the macOS and Linux build
paths in the package workflow, so update the macOS setup to match the pinned
Linux version used by CIBW_BEFORE_ALL_LINUX. Change the dtolnay/rust-toolchain
action in the workflow to a fixed 1.88.0 reference, or otherwise make
CIBW_BEFORE_ALL_MACOS install the same Rust version, so the cibuildwheel jobs
build with a consistent toolchain across platforms.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d2a5a393-7293-4244-97f1-ff90c1390ec0
📒 Files selected for processing (16)
.github/workflows/package.ymlCargo.tomlbindings/RELEASE.mdbindings/python/speakrs-cuda/pyproject.tomlbindings/python/speakrs-migraphx/pyproject.tomlcrates/speakrs-ffi/src/lib.rscrates/speakrs-python/Cargo.tomlcrates/speakrs-python/pyproject.tomlcrates/speakrs-python/python/speakrs/__init__.pycrates/speakrs-python/src/lib.rscrates/speakrs-python/tests/test_python_api.pycrates/speakrs-sdk/Cargo.tomlcrates/speakrs-sdk/src/dto.rscrates/speakrs-sdk/src/facade.rscrates/speakrs-sdk/src/lib.rssrc/pipeline/queued.rs
🚧 Files skipped from review as they are similar to previous changes (12)
- crates/speakrs-python/Cargo.toml
- bindings/python/speakrs-cuda/pyproject.toml
- crates/speakrs-sdk/src/lib.rs
- crates/speakrs-sdk/Cargo.toml
- Cargo.toml
- bindings/python/speakrs-migraphx/pyproject.toml
- crates/speakrs-python/tests/test_python_api.py
- crates/speakrs-sdk/src/facade.rs
- crates/speakrs-ffi/src/lib.rs
- crates/speakrs-python/python/speakrs/init.py
- crates/speakrs-sdk/src/dto.rs
- crates/speakrs-python/src/lib.rs
Ensure Android debug variants build the Rust artifacts and generated UniFFI Kotlin before packaging by adding dependencies for `preBundledOrtDebugBuild` and `preLeanDebugBuild`. Also make all Kotlin compile tasks depend on `generateUniFfiKotlin` so generated bindings are always available during compilation.
Return PipelineConfig as public Python dataclass values so tests can inspect mode defaults, and run queue ingestion with detached GIL sections to avoid blocking Python threads. Also extend CI to exercise pure-linalg and validate downloadable model fixtures with a tiny end-to-end diarization check during wheel testing.,
Implements SDK bindings across Python, Android, and iOS, with shared Rust SDK/FFI layers, explicit model preparation, typed configs/results/errors, Rust-owned audio decode/resample, queue support, demos, docs, and packaging workflows.
Includes local verification for Python CPU wheels, Android lean and bundled AARs with device smoke, Swift XCFramework/sample builds, and workspace Rust gates. GPU packages, hosted CI artifacts, registry signing/publish steps, hosted Swift binary URL, and iOS CoreML runtime smoke remain documented release blockers requiring external infrastructure or assets.
Summary by CodeRabbit