Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
47a2e7c
feat(analysis): enforce one canonical audio resource policy (#781)
seonghobae Aug 22, 2026
80c43f3
test: reject non-numeric decoded audio dtypes
seonghobae Aug 22, 2026
706a80d
fix: fail closed on non-numeric decoded audio
seonghobae Aug 22, 2026
d104a40
docs: align audio policy rejection catalog
seonghobae Aug 22, 2026
bb62c80
test: preserve YouTube resource-policy reason codes
seonghobae Aug 22, 2026
c0b7422
fix: preserve YouTube policy rejection reasons
seonghobae Aug 22, 2026
4c3d178
test: reject non-finite separator decoder output
seonghobae Aug 22, 2026
713b1f2
test: keep separator policy regression type-safe
seonghobae Aug 22, 2026
b1b5bb2
fix: validate separator output before finite normalization
seonghobae Aug 22, 2026
ab2562f
test: keep empty chord buffers shape-invariant
seonghobae Aug 22, 2026
37d0da0
test: define bounded audio metadata preflight contract
seonghobae Aug 22, 2026
2f2fe18
feat: preflight untrusted audio container metadata
seonghobae Aug 22, 2026
a040f09
fix(analysis): fail closed cleanly on metadata probe errors
seonghobae Aug 22, 2026
d2cf204
test(analysis): keep metadata preflight lint-clean
seonghobae Aug 22, 2026
98b70bc
Merge remote-tracking branch 'origin/develop' into HEAD
seonghobae Aug 28, 2026
2ed5383
fix(analysis): close audio resource edge contracts
seonghobae Aug 28, 2026
5d91c05
test(audio): reproduce pre-decode metadata admission gap
seonghobae Aug 28, 2026
90681ef
fix(audio): preflight temporal source metadata before decode
seonghobae Aug 28, 2026
ad67cba
fix(audio): preflight transcription metadata before decode
seonghobae Aug 28, 2026
18f8a9c
fix(audio): preflight separation source metadata before decode
seonghobae Aug 28, 2026
5ae317e
test(audio): isolate transcription decode-duration contract
seonghobae Aug 28, 2026
e68d9a9
test(audio): isolate temporal decoder contracts from metadata admission
seonghobae Aug 28, 2026
4e0d514
test(audio): isolate decoded separation policy contract
seonghobae Aug 28, 2026
86a9719
test(audio): isolate separation decoder failure contracts
seonghobae Aug 28, 2026
33d14e4
fix(audio): enforce desktop resource byte ceiling
seonghobae Aug 28, 2026
0e2486a
fix(policy): fail closed on oversized metadata
seonghobae Aug 29, 2026
b8a6db4
refactor(policy): reuse rejection helper
seonghobae Aug 29, 2026
669d1d0
fix(audio): preserve compressed path decoder support
seonghobae Aug 29, 2026
f3b040e
fix(audio): pin audioread runtime dependency
seonghobae Aug 30, 2026
6bfe3c0
docs(audio): align metadata boundary notes
seonghobae Aug 30, 2026
209cc2f
fix(audio): preserve compressed path decoder support
seonghobae Aug 30, 2026
ae58626
test(audio): require one bounded decode authority
seonghobae Sep 1, 2026
0bacf79
fix(audio): centralize bounded PCM decode authority
seonghobae Sep 1, 2026
f71d46c
refactor(audio): route temporal decode through owned port
seonghobae Sep 1, 2026
f1a362e
test(audio): bind temporal regressions to decode port
seonghobae Sep 1, 2026
9d0fbb2
refactor(audio): route bass decode through owned port
seonghobae Sep 1, 2026
a62329f
test(audio): bind bass transcription to decode port
seonghobae Sep 1, 2026
2149b90
test(audio): require semantic decode port identifiers
seonghobae Sep 2, 2026
05637b0
fix(audio): use semantic decode port identifiers
seonghobae Sep 2, 2026
76ca943
test(audio): require semantic metadata identifiers
seonghobae Sep 2, 2026
a104929
fix(audio): use semantic metadata port identifiers
seonghobae Sep 2, 2026
bf98029
test(audio): lock semantic resource policy vocabulary
seonghobae Sep 2, 2026
cf42b0c
fix(audio): use semantic resource policy vocabulary
seonghobae Sep 2, 2026
ad3b32a
test(audio): follow semantic policy error contract
seonghobae Sep 2, 2026
ff309c6
fix(audio): follow semantic policy rejection contract
seonghobae Sep 2, 2026
b24a86b
fix(audio): consume semantic policy rejection fields
seonghobae Sep 2, 2026
e09720e
test(audio): follow semantic metadata rejection fields
seonghobae Sep 2, 2026
f616320
test(audio): adopt semantic policy vocabulary
seonghobae Sep 2, 2026
539cb38
test(audio): follow semantic policy rejection field
seonghobae Sep 2, 2026
37978b6
test(audio): align separator rejection contract
seonghobae Sep 2, 2026
071c1c8
fix(audio): import decoder warnings from owning boundary
seonghobae Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ This section applies to any agent (Claude, Codex, Cursor, opencode, ...) working
- `docs/operations/deploy-runbook.md`
- `docs/brand-story.md`
- `docs/security/app-security.md`
- `docs/doctoring/audio-resource-policy.md`
- `docs/security/dependency-policy.md`
- `docs/security/cross-platform-build-policy.md`
- `docs/workflow/github-bootstrap-execution-policy.md`
Expand All @@ -90,6 +91,7 @@ This section applies to any agent (Claude, Codex, Cursor, opencode, ...) working
- Do not add network-dependent runtime paths for local analysis.
- Treat YouTube import as policy-constrained and fallback-friendly.
- Treat files, URLs, metadata, model artifacts, and project files as untrusted input.
- Apply the canonical audio resource policy in `bandscope_analysis.audio_resource_policy` (15 minutes / 100 MiB / mono-stereo / 44.1 kHz target) before decode or feature DSP. Do not invent a five-minute cap or a silent 120-second transcription window. Rejection copy must name the next rehearsal action and stay payload-free.
- Do not add generic exec/read/write APIs.
- Use `shell=False`-style subprocess invocation with argument arrays only.
- Keep local backend access on allowlisted IPC or `127.0.0.1` only, with strict schema validation.
Expand Down
11 changes: 10 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ARCHITECTURE.md

Last updated: 2026-03-11
Last updated: 2026-08-23

## Brand source

Expand Down Expand Up @@ -55,6 +55,15 @@ Last updated: 2026-03-11
- Split privilege where feasible across UI, analysis workers, subprocesses, model delivery, and updater behavior.
- Fail safely when a link, file, artifact, or boundary cannot be validated.

## Canonical audio resource policy

- Local audio admission is versioned in `services/analysis-engine/src/bandscope_analysis/audio_resource_policy.py` and documented in `docs/doctoring/audio-resource-policy.md`.
- Policy version 1 admits one rehearsal recording up to 15 minutes, 100 MiB encoded, mono or stereo, with source rates from 8 kHz through 192 kHz and a 44.1 kHz analysis target.
- Feature analyzers must not invent a shorter silent cap (including the former 120-second transcription window or a five-minute chord guard). Feature DSP resampling happens after canonical validation.
- Encoded size is checked before decode; decoded layout, sample count, duration, and memory are revalidated because container metadata is untrusted.
- Size conversions use checked arithmetic and fail closed on overflow or non-finite metadata. User-facing copy is payload-free and names the next file-selection action.
- YouTube import uses the same 15-minute / 100 MiB ceiling as local files.

## Repository map

- `apps/desktop` - desktop shell and user-facing React UI
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Added

- Canonical local-audio resource policy (15 minutes / 100 MiB / mono-stereo / 44.1 kHz target) shared by temporal analysis, stem separation, YouTube import, bass transcription, and chord recognition. Oversized or malformed songs now name the next file to choose instead of echoing sizes.
- Name tonight's first playable range on the ready rehearsal map and tell the player to check that span on their instrument before the section.
- Display the analyzed song tempo (BPM) as a badge in the rehearsal workspace.
- 각 합주 역할(Role)별 개인 연습 진행도를 0~100% 범위로 기록 및 시각화할 수 있는 연습 진척도(`practiceProgress`) 트래커 기능 추가. UI 컨트롤(슬라이더 및 +/- 버튼)과 한/영 다국어 지원 포함.
Expand All @@ -14,6 +15,9 @@

### Fixed

- Kept path-backed temporal analysis and stem separation compatible with the
documented MP3/M4A intake formats by using the existing local audioread
fallback when libsndfile cannot inspect a compressed container.
- Upgraded the local score PDF parser to `pdfjs-dist` 6.2.108, pinned Undici 7.29.0 across the workspace, and constrained PDF loading to copied in-memory bytes with a same-origin bundled worker and npm-generated lock provenance.

## [0.1.3] - 2026-04-29
Expand Down Expand Up @@ -74,4 +78,4 @@

- `ChordsFeature` (코드 분석) 화면에서 각 파트(Role)의 `transpositionPlan`(이조/조옮김 계획)을 표시하는 기능을 추가했습니다.
- `RangesFeature` (음역대 분석) 화면에서 겹침 경고(Overlap warning) 외에 해당 파트의 채보(Transcription) 가능 노드 수를 요약하여 보여주는 기능을 추가했습니다.
- 신규 UI 요소에 대한 단위 테스트를 추가했습니다 (`apps/desktop/src/features/chords/index.test.tsx`, `apps/desktop/src/features/ranges/index.test.tsx`).
- 신규 UI 요소에 대한 단위 테스트를 추가했습니다 (`apps/desktop/src/features/chords/index.test.tsx`, `apps/desktop/src/features/ranges/index.test.tsx`).
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Three layers, decoupled through shared contracts:

- `apps/desktop` — Tauri 2 + Vite + React 19 shell (Tailwind 4, Base UI, Storybook). Feature screens live in `src/features/` (home, workspace, chords, ranges, player, settings). The ready workspace names tonight's first playable range and the next instrument check. `src/lib/analysis.ts` and `src/lib/job_runner.ts` call typed Tauri IPC commands, with a browser fallback that serves demo data when not running inside Tauri.
- `apps/desktop/src-tauri/src/main.rs` — the Rust orchestration boundary. Tauri commands (`start_analysis_job`, `get_analysis_job_status`, `select_local_audio_source`, `import_youtube_url`) validate untrusted input (project IDs, file paths, URLs) and spawn the Python engine as a subprocess. There is no loopback HTTP listener and no network path for local analysis.
- `services/analysis-engine` — Python package `bandscope_analysis` (librosa/numpy). Entry point `cli.py` reads a JSON job request on stdin and prints a structured job-status JSON envelope on stdout (`--progress-jsonl` streams progress lines). `api.py` orchestrates the pipeline across the `separation`, `sections`, `roles`, `chords`, `ranges`, `temporal`, `transcription`, and `youtube` modules.
- `services/analysis-engine` — Python package `bandscope_analysis` (librosa/numpy). Entry point `cli.py` reads a JSON job request on stdin and prints a structured job-status JSON envelope on stdout (`--progress-jsonl` streams progress lines). `api.py` orchestrates the pipeline across the `separation`, `sections`, `roles`, `chords`, `ranges`, `temporal`, `transcription`, and `youtube` modules. `audio_resource_policy.py` is the versioned 15-minute / 100 MiB local-audio budget shared by those modules; feature DSP rates (for example bass pYIN at 22050 Hz) run only after that admission check.

Data flow: React UI → Tauri IPC command → Rust validation + Python subprocess over stdin/stdout → job status and progress events emitted back to the UI.

Expand Down
24 changes: 24 additions & 0 deletions apps/desktop/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ pub const ANALYSIS_WAIT_POLL: Duration = Duration::from_millis(50);

pub const AUDIO_EXTENSIONS: [&str; 4] = ["wav", "mp3", "flac", "m4a"];

/// Canonical encoded-audio ceiling shared by desktop intake and the engine.
pub const MAX_AUDIO_FILE_BYTES: u64 = 100 * 1024 * 1024;

pub const AUDIO_FILE_TOO_LARGE_MESSAGE: &str =
"Choose a shorter or smaller song file to start analysis.";

pub const MISSING_ANALYSIS_PYTHON: &str = "__bandscope_missing_analysis_python__";

pub const YOUTUBE_IMPORT_TIMEOUT: Duration = Duration::from_secs(120);
Expand Down Expand Up @@ -296,6 +302,14 @@ pub struct ProjectBootstrapSummaryPayload {
pub source: LocalAudioSourcePayload,
}

/// Reject an encoded audio file that exceeds the canonical resource ceiling.
pub fn validate_audio_file_size(file_size_bytes: u64) -> Result<(), String> {
if file_size_bytes > MAX_AUDIO_FILE_BYTES {
return Err(AUDIO_FILE_TOO_LARGE_MESSAGE.to_string());
}
Ok(())
}

pub fn next_project_id(state: &AppState) -> String {
format!(
"project-{}-{}",
Expand Down Expand Up @@ -344,6 +358,7 @@ pub fn youtube_source_from_metadata(
if !file_metadata.is_file() || file_metadata.len() == 0 {
return Err("YouTube import returned an invalid audio file.".to_string());
}
validate_audio_file_size(file_metadata.len())?;

let extension = canonical
.extension()
Expand Down Expand Up @@ -843,6 +858,15 @@ mod tests {
assert!(serde_json::from_value::<RehearsalSongPayload>(payload).is_err());
}

#[test]
fn audio_file_size_uses_the_canonical_resource_ceiling() {
assert!(validate_audio_file_size(MAX_AUDIO_FILE_BYTES).is_ok());
assert_eq!(
validate_audio_file_size(MAX_AUDIO_FILE_BYTES + 1),
Err(AUDIO_FILE_TOO_LARGE_MESSAGE.to_string())
);
}

#[test]
fn project_payload_from_content_rejects_legacy_missing_time_range() {
let mut payload = shared_contract_payload(json!({ "start": 10, "end": 30 }));
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ fn normalize_local_audio_source(path: &Path) -> Result<LocalAudioSourcePayload,
if !metadata.is_file() || metadata.len() == 0 {
return Err("Could not read the selected audio file.".into());
}
validate_audio_file_size(metadata.len())?;
let file_name = canonical
.file_name()
.and_then(|value| value.to_str())
Expand Down
1 change: 1 addition & 0 deletions docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ GitHub is the source of truth for repository governance, PR review, CI/CD, Code
- prefer local processing for audio and analysis
- keep risky capabilities narrow, allowlisted, and explicit
- treat files, URLs, models, caches, and release artifacts as untrusted inputs
- admit local and YouTube audio through one canonical resource policy (15 minutes / 100 MiB / mono-stereo) before decode or feature DSP
- route orchestration through typed Tauri IPC and a narrow Python subprocess bridge before considering any loopback HTTP surface
- bootstrap local audio projects by validating the selected file in Rust, then passing only typed source metadata through the orchestration boundary
- keep project and temp/cache bootstrap roots under Tauri-resolved app-owned directories rather than the shared OS temp namespace
Expand Down
98 changes: 98 additions & 0 deletions docs/doctoring/audio-resource-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Canonical audio resource policy

BandScope admits one rehearsal recording at a time. Every intake path, decoder,
and feature analyzer must apply the same versioned resource budget before
expensive work starts.

## Published policy (version 1)

| Bound | Value | Why this number |
| --- | --- | --- |
| Encoded file bytes | 100 MiB inclusive | Existing temporal intake ceiling; long enough for a 15-minute stereo rehearsal capture without inviting decompression bombs. |
| Duration | 0.05 s through 15 minutes inclusive | Existing temporal and YouTube intake evidence. Not an invented five-minute cap. |
| Source sampling rate | 8 kHz through 192 kHz | Covers phone voice notes through high-rate interface captures. Feature DSP may resample after admission (bass pYIN at 22 050 Hz is allowed). |
| Target sampling rate | 44 100 Hz | Compact-disc PCM rate used by temporal analysis and stem separation. |
| Channel count | Mono or stereo | Rehearsal recordings are not multichannel session stems. |
| Decoded sample count | `15 × 60 × 44100` | Checked product of duration and target rate. |
| Decoded memory | sample count × 2 channels × 4 bytes | Float32 stereo estimate; overflow fails closed. |

## Next action copy

Rejection copy is payload-free. It names the next rehearsal action and never
echoes paths, sizes, durations, or header bytes:

- Choose a shorter or smaller song file to start analysis.
- Choose a song shorter than 15 minutes to start analysis.
- Choose a shorter song file to start analysis.
- Choose a longer song file to start analysis.
- Choose a WAV, MP3, FLAC, or M4A file recorded at a standard sample rate.
- Choose a mono or stereo song file to start analysis.
- Choose another song file. This one could not be measured safely.
- Choose another song file. This one could not be read as audio.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Audit metadata records `policy_version` and `reason` on
`AudioResourcePolicyError`. Those fields stay off the user-facing string.

## Validation order

1. Encoded byte size, before open/decode, where the filesystem size is
trustworthy as an upper bound.
2. YouTube metadata duration ceiling, before download.
3. Decode with the canonical duration bound as a loader safety cap, not as a
silent shorter feature policy.
4. Revalidate decoded arrays because container metadata is untrusted: layout,
sampling rate, sample count, wall-clock duration, and memory estimate.
5. Feature DSP (chromagram hop, pYIN 22 050 Hz, Demucs split) runs only on an
admitted buffer.

Path-backed temporal and separation intake keeps the path form for librosa's
existing audioread fallback, so an M4A container that libsndfile cannot inspect
can still be admitted from local decoder metadata. File-like stem bytes retain
the libsndfile metadata contract.

## Consumers

- `bandscope_analysis.audio_resource_policy` — versioned policy and validators
- `temporal.analyzer` — local file preflight and decoded revalidation
- `separation.audio_separator` — stem decode preflight and decoded revalidation
- `youtube.download_youtube_audio` — duration ceiling and 100 MiB encoded budget
- `transcription.api` — stem byte budget and 15-minute loader cap (no 120 s silent cap)
- `chords.chord_recognizer` — decoded revalidation at `recognize()`

Desktop Rust enforces this encoded-byte ceiling at local-file selection and
again when accepting the downloaded YouTube file into the app-owned cache. The
Python engine repeats the check before analysis as a defense-in-depth boundary.

## Rollback

Revert this slice to restore feature-local limits (YouTube 50 MiB, bass
transcription 120 s, payload-bearing size errors). Do not leave a mix of
canonical validators and the old silent caps on the same branch.

## Security Notes

- Attack surface: untrusted local files, YouTube containers, local decoder
fallback processes, decoder output,
and caller-supplied NumPy arrays.
- Trust boundary: this policy classifies resources only. It does not open
files, follow paths, or talk to the network.
- Mitigations: checked integer products, fail-closed non-finite metadata,
payload-free copy, decoded revalidation after untrusted headers, and the
existing fixed audioread decoder path only for already-resolved local files.
- Test points: inclusive ceilings, next-byte/next-millisecond rejections,
empty and malformed metadata, decoded expansion, overflow, provenance.
- Realistic threats: decompression bombs, huge channel counts, extreme
sampling rates, integer overflow in size conversions, inconsistent
feature-local caps that fail only after expensive work.
- Remaining risk: compressed-container preflight depends on an available local
decoder and duration still requires decoder metadata; GPU/VRAM budgets are
not part of policy version 1.

## References

International Electrotechnical Commission. (1999). *Compact disc digital audio
system* (IEC 60908). Geneva, Switzerland: IEC.

National Institute of Standards and Technology. (2020). *Security and privacy
controls for information systems and organizations* (NIST Special Publication
800-53 Rev. 5). https://doi.org/10.6028/NIST.SP.800-53r5
1 change: 1 addition & 0 deletions docs/security/app-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Every boundary crossing requires validation, scope restriction, minimal logging,
- Cross-check extension, MIME, and actual decode behavior.
- Prefer isolated worker processing for decode and analysis.
- Guard against very large files, abnormal duration, and hostile metadata.
- Apply the canonical audio resource policy (`docs/doctoring/audio-resource-policy.md`): 15 minutes, 100 MiB encoded, mono or stereo, 8–192 kHz source, 44.1 kHz analysis target. Check encoded size before decode and revalidate decoded output. Return payload-free copy that names the next song to choose.
- Do not add arbitrary filesystem scanning just to find media files.
- When bootstrapping a project around local audio, prefer referencing the validated original file plus app-owned temp/cache/project roots over copying the file until persistence requirements justify the extra storage boundary.

Expand Down
1 change: 1 addition & 0 deletions services/analysis-engine/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ version = "0.1.0"
description = "BandScope local-first analysis engine"
requires-python = ">=3.12"
dependencies = [
"audioread==3.1.0",
"demucs>=4.0.1 ; sys_platform != 'darwin' or platform_machine == 'arm64'",
"librosa>=0.11.0",
"numba<0.67.0",
Expand Down
103 changes: 103 additions & 0 deletions services/analysis-engine/src/bandscope_analysis/audio_decode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Bounded PCM decode port for the Resource Admission & Decode context.

This module is the single analysis-engine boundary that may turn an admitted
container into PCM. The current adapter still delegates to ``librosa`` and is
therefore a transitional owner while #1129 removes the libsndfile-backed
runtime graph. Consumers must call this port rather than selecting decoder
fallbacks themselves.

Security Notes:
- Untrusted input: caller-authorized local audio handles/paths, container
metadata, decoder output, and third-party decoder exceptions.
- Trust boundary: metadata admission happens before PCM decode and decoded PCM
is revalidated before it can enter MIR/DSP consumers.
- Privacy: third-party decoder details are retained only as exception causes;
the surfaced resource-policy error is stable and payload-free.
- Dependency boundary: this adapter deliberately centralizes the legacy
librosa decoder so the commercial #1129 replacement has one owned seam. It
does not claim that libsndfile has already been removed.
"""

from __future__ import annotations

import warnings
from typing import cast

import librosa
import numpy as np
from numpy.typing import NDArray

from bandscope_analysis.audio_metadata import AudioSource, preflight_audio_metadata
from bandscope_analysis.audio_resource_policy import (
DEFAULT_AUDIO_RESOURCE_POLICY,
AudioResourcePolicy,
AudioResourcePolicyError,
policy_rejection_message,
validate_decoded_audio,
)

KNOWN_LIBROSA_NUMBA_WARNING_FILTERS = (
(DeprecationWarning, r".*pkg_resources is deprecated.*", r".*librosa.*"),
(FutureWarning, r".*Numba.*", r".*numba.*"),
)

AudioMonoArray = NDArray[np.float32]


def _malformed_decode_error() -> AudioResourcePolicyError:
"""Build the stable payload-free decoder failure."""
return AudioResourcePolicyError(
"malformed_header", policy_rejection_message("malformed_header")
)


def decode_mono_audio(
audio_source: AudioSource,
*,
target_sample_rate_hz: int,
max_duration_seconds: float,
audio_resource_policy: AudioResourcePolicy = DEFAULT_AUDIO_RESOURCE_POLICY,
) -> tuple[AudioMonoArray, int]:
"""Admit and decode one source to bounded mono float32 PCM.

The audio source is first measured against the same resource policy that
later revalidates the decoded buffer. Decoder/provider detail never becomes
the surfaced error message.
"""
preflight_audio_metadata(audio_source, audio_resource_policy)

try:
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", category=DeprecationWarning, module=r"^audioread"
)
warnings.filterwarnings(
"ignore", category=FutureWarning, module=r"^audioread"
)
for (
warning_category,
warning_message,
warning_module,
) in KNOWN_LIBROSA_NUMBA_WARNING_FILTERS:
warnings.filterwarnings(
"ignore",
category=warning_category,
message=warning_message,
module=warning_module,
)
decoded_audio, sample_rate_hz = librosa.load( # type: ignore[arg-type]
audio_source,
sr=target_sample_rate_hz,
mono=True,
duration=max_duration_seconds,
)
except Exception as decode_error:
raise _malformed_decode_error() from decode_error

try:
pcm_audio = np.ravel(np.asarray(decoded_audio, dtype=np.float32))
except (TypeError, ValueError) as decode_error:
raise _malformed_decode_error() from decode_error

validate_decoded_audio(pcm_audio, sample_rate_hz, audio_resource_policy)
return cast(AudioMonoArray, pcm_audio), int(sample_rate_hz)
Loading
Loading