| """Streaming speech examples with decoder-independent raw audio handling.""" |
| from __future__ import annotations |
|
|
| from dataclasses import dataclass |
| import io |
| from typing import Iterator, Mapping, Optional |
|
|
| import numpy as np |
| import torch |
|
|
|
|
| @dataclass(frozen=True) |
| class SpeechExample: |
| identifier: str |
| audio: np.ndarray |
| sampling_rate: int |
| text: str |
| document_id: Optional[str] = None |
| language: Optional[str] = None |
|
|
|
|
| def decode_audio_payload( |
| payload: Mapping[str, object], *, target_sampling_rate: int = 16_000 |
| ) -> tuple[np.ndarray, int]: |
| """Decode a datasets ``Audio(decode=False)`` payload via soundfile. |
| |
| This bypasses librosa/numba and keeps the dataset layer independent from |
| the system NumPy version. |
| """ |
| import soundfile as sf |
|
|
| raw_bytes = payload.get("bytes") |
| path = payload.get("path") |
| if raw_bytes is not None: |
| source = io.BytesIO(raw_bytes) |
| elif path: |
| source = str(path) |
| else: |
| raise ValueError("audio payload has neither bytes nor path") |
| audio, sampling_rate = sf.read(source, dtype="float32", always_2d=False) |
| value = np.asarray(audio, dtype=np.float32) |
| if value.ndim == 2: |
| value = value.mean(axis=1) |
| if value.ndim != 1 or value.size == 0: |
| raise ValueError("decoded audio must be a non-empty mono waveform") |
| sampling_rate = int(sampling_rate) |
| if sampling_rate != target_sampling_rate: |
| import torchaudio.functional as audio_functional |
|
|
| tensor = torch.from_numpy(value) |
| value = ( |
| audio_functional.resample(tensor, sampling_rate, target_sampling_rate) |
| .contiguous() |
| .numpy() |
| ) |
| sampling_rate = int(target_sampling_rate) |
| return value, sampling_rate |
|
|
|
|
| def iter_hf_speech_examples( |
| dataset_name: str, |
| *, |
| dataset_config: Optional[str], |
| split: str, |
| max_samples: Optional[int] = None, |
| start_offset: int = 0, |
| text_column: str = "text", |
| id_column: str = "id", |
| audio_column: str = "audio", |
| document_column: Optional[str] = None, |
| language: Optional[str] = None, |
| streaming: bool = True, |
| target_sampling_rate: int = 16_000, |
| ) -> Iterator[SpeechExample]: |
| """Yield deterministic dataset-order examples with raw-byte audio decode.""" |
| from datasets import Audio, load_dataset |
|
|
| if max_samples is not None and max_samples < 1: |
| raise ValueError("max_samples must be positive") |
| if start_offset < 0: |
| raise ValueError("start_offset must be non-negative") |
| dataset = load_dataset( |
| dataset_name, |
| dataset_config, |
| split=split, |
| streaming=streaming, |
| trust_remote_code=True, |
| ) |
| dataset = dataset.cast_column(audio_column, Audio(decode=False)) |
| if start_offset: |
| |
| |
| |
| dataset = dataset.skip(start_offset) |
| for index, row in enumerate(dataset): |
| if max_samples is not None and index >= max_samples: |
| break |
| waveform, sampling_rate = decode_audio_payload( |
| row[audio_column], target_sampling_rate=target_sampling_rate |
| ) |
| identifier = str(row.get(id_column, index + start_offset)) |
| document_id = ( |
| None if document_column is None else str(row.get(document_column, identifier)) |
| ) |
| yield SpeechExample( |
| identifier=identifier, |
| audio=waveform, |
| sampling_rate=sampling_rate, |
| text=str(row[text_column]), |
| document_id=document_id, |
| language=language, |
| ) |
|
|