"""通用 HuggingFace 数据集适配器(流式)。 config 条目示例: - name: ascend type: hf hf_id: CAiRE/ASCEND split: test text_field: transcription audio_field: audio # 默认 audio lang: zh-en domain: general trust_remote_code: false 流式(streaming=True)避免为跑小样而下载整个数据集;每条把音频物料化为 16k wav 缓存。 """ from __future__ import annotations import io import os from collections.abc import Iterator import soundfile as sf from ..manifest import Sample from .base import Dataset, write_wav_16k class HFDataset(Dataset): def __init__(self, cfg: dict, audio_cache: str): self.name = cfg["name"] self.hf_id = cfg["hf_id"] self.hf_config = cfg.get("hf_config") self.split = cfg.get("split", "test") self.text_field = cfg["text_field"] self.audio_field = cfg.get("audio_field", "audio") self.lang = cfg.get("lang", "zh") self.domain = cfg.get("domain", "general") self.trust_remote_code = cfg.get("trust_remote_code", False) self.cache_dir = os.path.join(audio_cache, self.name) def samples(self, limit: int | None = None) -> Iterator[Sample]: from datasets import Audio, load_dataset ds = load_dataset( self.hf_id, self.hf_config, split=self.split, streaming=True, trust_remote_code=self.trust_remote_code, ) # 关闭自动解码(新版 datasets 需 torchcodec):拿原始 bytes/path 自己用 soundfile 读,避开重依赖。 ds = ds.cast_column(self.audio_field, Audio(decode=False)) n = 0 for i, row in enumerate(ds): if limit is not None and n >= limit: break text = (row.get(self.text_field) or "").strip() if not text: continue audio = row.get(self.audio_field) if not audio: continue wav_path = os.path.join(self.cache_dir, f"{i:06d}.wav") if not os.path.exists(wav_path): array, sr = self._decode(audio) if array is None: continue write_wav_16k(array, sr, wav_path) n += 1 yield Sample( id=f"{self.name}-{i:06d}", audio_path=wav_path, ref_text=text, lang=self.lang, domain=self.domain, dataset=self.name, ) @staticmethod def _decode(audio: dict): """从未解码的 HF 音频列读出 (array, sr):优先 bytes,其次本地 path。""" if audio.get("bytes"): data, sr = sf.read(io.BytesIO(audio["bytes"]), dtype="float32", always_2d=False) return data, sr if audio.get("path") and os.path.exists(audio["path"]): data, sr = sf.read(audio["path"], dtype="float32", always_2d=False) return data, sr return None, None