Instructions to use Splintir/mms-tts-ceb-pld with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Splintir/mms-tts-ceb-pld with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-to-audio", model="Splintir/mms-tts-ceb-pld")# Load model directly from transformers import AutoTokenizer, AutoModelForTextToWaveform tokenizer = AutoTokenizer.from_pretrained("Splintir/mms-tts-ceb-pld") model = AutoModelForTextToWaveform.from_pretrained("Splintir/mms-tts-ceb-pld", device_map="auto") - Notebooks
- Google Colab
- Kaggle
| """Turn one PLD language into SpeechT5 training inputs, cached on the Hub. | |
| Why this exists: `halohalo/finetune_tts.py` reads PLD from a raw corpus on a | |
| local disk (`PLD_RAW`), which a Colab VM does not have. The Hub copy | |
| (`sapinsapin/pld`) is 201 train shards of every language interleaved, so | |
| pulling one language means touching all ~24 GB. | |
| That is a one-time cost, and this script pays it once: stream the shards one at | |
| a time (never more than one on disk), keep the rows for one language, run the | |
| same text/mel/x-vector preprocessing the original training used, and write the | |
| result to parquet. Push that to a Hub dataset repo and every later run -- | |
| including every resume after a free-tier VM disappears -- pulls ~1 GB instead | |
| of 24. | |
| Preprocessing is deliberately identical to `finetune_tts.py`: | |
| - text: curly apostrophes normalized, digit-bearing lines dropped | |
| - audio: SpeechT5Processor log-mel targets at 16 kHz | |
| - speaker: one speechbrain x-vector per *clip*, F.normalize'd, never averaged | |
| - caps: <=220 input ids, <=960 mel frames | |
| Usage (on the VM): | |
| python scripts/tts_data.py --language ceb --push-to Splintir/pld-ceb-tts-proc | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import io | |
| import os | |
| import re | |
| import shutil | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| SR = 16000 | |
| PLD_REPO = "sapinsapin/pld" | |
| N_TRAIN_SHARDS = 201 | |
| DIGIT_RE = re.compile(r"\d") | |
| # Columns read from each shard. `audio` dominates the transfer; the rest are | |
| # cheap and decide whether a row is kept at all. | |
| COLUMNS = [ | |
| "audio", | |
| "sentence", | |
| "language", | |
| "speech_type", | |
| "num_words", | |
| "text_is_prompt", | |
| "speaker_id", | |
| "duration", | |
| ] | |
| MAX_INPUT_IDS = 220 | |
| MAX_MEL_FRAMES = 960 | |
| def clean_text(text: str) -> str | None: | |
| """SpeechT5's tokenizer is character-level Latin: normalize apostrophes and | |
| reject digits rather than teach the model to skip them.""" | |
| text = text.replace("’", "'").replace("‘", "'").strip() | |
| if not text or DIGIT_RE.search(text): | |
| return None | |
| return text | |
| def keep_row(row: dict, language: str) -> bool: | |
| """The TTS filter from halolib.finetune._PLD_FILTERS, plus the language.""" | |
| return ( | |
| row["language"] == language | |
| and row["speech_type"] == "read" | |
| and not row["text_is_prompt"] | |
| and row["num_words"] >= 3 | |
| ) | |
| def build_embedder(): | |
| import torch | |
| # speechbrain 1.1 registers optional integrations (k2, wordemb, ...) as lazy | |
| # modules that import on any attribute access. Loading the Xvector lobe goes | |
| # through pydoc, which probes dunders on every module in sys.modules and so | |
| # force-imports integrations whose dependencies are absent. Dunders never | |
| # come from a lazy import, so refusing them is safe. Same patch as | |
| # sapin-hil/tts.py. | |
| from speechbrain.utils import importutils as _importutils | |
| _lazy_getattr = _importutils.LazyModule.__getattr__ | |
| _importutils.LazyModule.__getattr__ = ( | |
| lambda self, attr: (_ for _ in ()).throw(AttributeError(attr)) | |
| if attr.startswith("__") else _lazy_getattr(self, attr)) | |
| from speechbrain.inference.speaker import EncoderClassifier | |
| from speechbrain.utils.fetching import LocalStrategy | |
| savedir = Path(os.environ.get("HF_HOME", "~/.cache")).expanduser() / "speechbrain-xvect" | |
| return EncoderClassifier.from_hparams( | |
| source="speechbrain/spkrec-xvect-voxceleb", | |
| savedir=str(savedir), | |
| # COPY, not the default SYMLINK: symlinking needs Developer Mode on | |
| # Windows and fails with WinError 1314 otherwise. Harmless on Linux. | |
| local_strategy=LocalStrategy.COPY, | |
| run_opts={"device": "cuda" if torch.cuda.is_available() else "cpu"}, | |
| ) | |
| def iter_shard_rows(language: str, token: str | None, shards: range): | |
| """Yield matching rows shard by shard, holding one shard on disk at a time.""" | |
| import pyarrow.parquet as pq | |
| from huggingface_hub import hf_hub_download | |
| scratch = Path("/content/_pld_shard") if Path("/content").exists() else Path("./_pld_shard") | |
| for i in shards: | |
| name = f"data/train-{i:05d}-of-{N_TRAIN_SHARDS:05d}.parquet" | |
| shutil.rmtree(scratch, ignore_errors=True) | |
| scratch.mkdir(parents=True, exist_ok=True) | |
| path = hf_hub_download( | |
| PLD_REPO, name, repo_type="dataset", token=token, local_dir=str(scratch) | |
| ) | |
| table = pq.read_table(path, columns=COLUMNS) | |
| # to_pylist on the whole shard materializes 1500 audio blobs (~120 MB); | |
| # row-group at a time keeps the peak an order of magnitude lower. | |
| for batch in table.to_batches(max_chunksize=100): | |
| for row in batch.to_pylist(): | |
| if keep_row(row, language): | |
| yield i, row | |
| del table | |
| shutil.rmtree(scratch, ignore_errors=True) | |
| def process(rows, processor, embedder, log_every: int = 250): | |
| """(audio, text) -> (input_ids, mel labels, x-vector), dropping what cannot train.""" | |
| import soundfile as sf | |
| import torch | |
| kept = seen = 0 | |
| t0 = time.time() | |
| for shard_i, row in rows: | |
| seen += 1 | |
| text = clean_text(row["sentence"]) | |
| if text is None: | |
| continue | |
| wav, sr = sf.read(io.BytesIO(row["audio"]["bytes"]), dtype="float32") | |
| if sr != SR: | |
| raise SystemExit(f"expected {SR} Hz, shard {shard_i} gave {sr}") | |
| if wav.ndim > 1: | |
| wav = wav.mean(axis=1) | |
| example = processor( | |
| text=text, audio_target=wav, sampling_rate=SR, return_attention_mask=False | |
| ) | |
| input_ids = example["input_ids"] | |
| labels = np.asarray(example["labels"][0], dtype=np.float32) | |
| if len(input_ids) > MAX_INPUT_IDS or len(labels) > MAX_MEL_FRAMES: | |
| continue | |
| with torch.no_grad(): | |
| emb = embedder.encode_batch(torch.tensor(wav).unsqueeze(0)) | |
| emb = torch.nn.functional.normalize(emb, dim=2).squeeze().cpu().numpy() | |
| kept += 1 | |
| if kept % log_every == 0: | |
| rate = seen / max(time.time() - t0, 1e-9) | |
| print( | |
| f" shard {shard_i:3d} scanned {seen:6d} kept {kept:6d}" | |
| f" ({rate:.1f} rows/s)", | |
| flush=True, | |
| ) | |
| yield { | |
| "input_ids": list(map(int, input_ids)), | |
| "labels": labels.reshape(-1).tolist(), | |
| "n_mel_frames": int(labels.shape[0]), | |
| "speaker_embeddings": emb.astype(np.float32).tolist(), | |
| "text": text, | |
| "speaker_id": row["speaker_id"], | |
| "duration": float(row["duration"]), | |
| } | |
| def write_parquet(records, out_dir: Path, rows_per_file: int = 1000) -> int: | |
| """Stream records to parquet shards so peak memory stays near one shard.""" | |
| import pyarrow as pa | |
| import pyarrow.parquet as pq | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| buf, n, part = [], 0, 0 | |
| def flush() -> None: | |
| nonlocal buf, part | |
| if not buf: | |
| return | |
| pq.write_table(pa.Table.from_pylist(buf), out_dir / f"part-{part:04d}.parquet") | |
| part += 1 | |
| buf = [] | |
| for rec in records: | |
| buf.append(rec) | |
| n += 1 | |
| if len(buf) >= rows_per_file: | |
| flush() | |
| flush() | |
| return n | |
| def main() -> None: | |
| sys.stdout.reconfigure(encoding="utf-8") | |
| ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) | |
| ap.add_argument("--language", required=True, help="PLD ISO 639-3 code, e.g. ceb") | |
| ap.add_argument("--out", default="/content/pld_proc", help="local parquet dir") | |
| ap.add_argument("--push-to", default="", help="Hub dataset repo to upload to") | |
| ap.add_argument("--private", action="store_true") | |
| ap.add_argument("--shards", type=int, default=N_TRAIN_SHARDS, | |
| help="how many train shards to scan (fewer = smaller sample)") | |
| ap.add_argument("--max-samples", type=int, default=0, help="0 = every usable clip") | |
| args = ap.parse_args() | |
| os.environ.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1") | |
| token = os.environ.get("HF_TOKEN") or None | |
| from transformers import SpeechT5Processor | |
| processor = SpeechT5Processor.from_pretrained("microsoft/speecht5_tts") | |
| embedder = build_embedder() | |
| out_dir = Path(args.out) / args.language | |
| shutil.rmtree(out_dir, ignore_errors=True) | |
| print(f"scanning {args.shards} PLD train shards for {args.language} ...", flush=True) | |
| rows = iter_shard_rows(args.language, token, range(args.shards)) | |
| records = process(rows, processor, embedder) | |
| if args.max_samples: | |
| import itertools | |
| records = itertools.islice(records, args.max_samples) | |
| t0 = time.time() | |
| n = write_parquet(records, out_dir) | |
| print(f"wrote {n} rows to {out_dir} in {(time.time() - t0) / 60:.1f} min") | |
| if n == 0: | |
| raise SystemExit(f"no usable {args.language} rows -- check the filters") | |
| size = sum(f.stat().st_size for f in out_dir.glob("*.parquet")) / 1024**3 | |
| print(f"parquet size: {size:.2f} GB") | |
| if args.push_to: | |
| from huggingface_hub import HfApi | |
| api = HfApi(token=token) | |
| api.create_repo(args.push_to, repo_type="dataset", exist_ok=True, | |
| private=args.private) | |
| api.upload_folder( | |
| folder_path=str(out_dir), | |
| repo_id=args.push_to, | |
| repo_type="dataset", | |
| path_in_repo=f"data/{args.language}", | |
| commit_message=f"Preprocessed {args.language} TTS inputs ({n} clips)", | |
| ) | |
| print(f"pushed: https://huggingface.co/datasets/{args.push_to}") | |
| if __name__ == "__main__": | |
| main() | |