Add train_narrator_tts.py
Browse files- train_narrator_tts.py +13 -17
train_narrator_tts.py
CHANGED
|
@@ -20,9 +20,10 @@ Output: pushed to HF_TTS_MODEL_REPO
|
|
| 20 |
Key F5-TTS internals learned from source inspection:
|
| 21 |
- Dataset path: {f5_tts_pkg}/../../data/{dataset_name}/ (load_from_disk)
|
| 22 |
- Checkpoint out: {f5_tts_pkg}/../../ckpts/{dataset_name}/
|
| 23 |
-
-
|
| 24 |
-
-
|
| 25 |
- --tokenizer_path only respected when --tokenizer custom (not char/pinyin)
|
|
|
|
| 26 |
|
| 27 |
Env vars:
|
| 28 |
HF_TOKEN — HF write token (injected as --secrets HF_TOKEN)
|
|
@@ -55,7 +56,6 @@ MAX_SECS = 12.0
|
|
| 55 |
|
| 56 |
DATASET_NAME = "narrator"
|
| 57 |
EXP_ARCH = "F5TTS_v1_Base"
|
| 58 |
-
WAVS_DIR = Path("/tmp/f5_work/wavs")
|
| 59 |
|
| 60 |
|
| 61 |
def _lib_root() -> Path:
|
|
@@ -75,7 +75,7 @@ def _pkg_ckpt_root() -> Path:
|
|
| 75 |
|
| 76 |
|
| 77 |
def prepare_data() -> None:
|
| 78 |
-
"""Download LJSpeech,
|
| 79 |
tar_path = Path("/tmp/ljspeech.tar.bz2")
|
| 80 |
lj_root = Path("/tmp/LJSpeech-1.1")
|
| 81 |
|
|
@@ -87,9 +87,12 @@ def prepare_data() -> None:
|
|
| 87 |
tar.extractall("/tmp/", filter="data")
|
| 88 |
print(f"Extracted -> {lj_root}")
|
| 89 |
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
| 93 |
texts: list[str] = []
|
| 94 |
durations: list[float] = []
|
| 95 |
count = 0
|
|
@@ -119,9 +122,7 @@ def prepare_data() -> None:
|
|
| 119 |
import librosa
|
| 120 |
arr = librosa.resample(arr, orig_sr=orig_sr, target_sr=TARGET_SR)
|
| 121 |
|
| 122 |
-
|
| 123 |
-
sf.write(str(dest), arr, TARGET_SR)
|
| 124 |
-
wav_paths.append(str(dest))
|
| 125 |
texts.append(text)
|
| 126 |
durations.append(len(arr) / TARGET_SR)
|
| 127 |
count += 1
|
|
@@ -129,17 +130,12 @@ def prepare_data() -> None:
|
|
| 129 |
print(f"Prepared {count} samples")
|
| 130 |
|
| 131 |
# F5-TTS dataset.py builds the path as data/{dataset_name}_{tokenizer}/raw/
|
| 132 |
-
#
|
| 133 |
-
# We MUST use pinyin to keep text_embed.weight shape [2546, 512] matching the checkpoint.
|
| 134 |
-
# Pinyin tokenizer handles English ASCII fine (maps each char through its vocab).
|
| 135 |
token_dir = _pkg_data_root() / f"{DATASET_NAME}_pinyin"
|
| 136 |
raw_dir = token_dir / "raw"
|
| 137 |
raw_dir.mkdir(parents=True, exist_ok=True)
|
| 138 |
|
| 139 |
-
|
| 140 |
-
# {"path": str, "bytes": None} without torchcodec. Decode at training time uses soundfile.
|
| 141 |
-
ds = hf_datasets.Dataset.from_dict({"audio": wav_paths, "text": texts})
|
| 142 |
-
ds = ds.cast_column("audio", hf_datasets.Audio(sampling_rate=TARGET_SR))
|
| 143 |
ds.save_to_disk(str(raw_dir))
|
| 144 |
print(f"Saved dataset -> {raw_dir}")
|
| 145 |
|
|
|
|
| 20 |
Key F5-TTS internals learned from source inspection:
|
| 21 |
- Dataset path: {f5_tts_pkg}/../../data/{dataset_name}/ (load_from_disk)
|
| 22 |
- Checkpoint out: {f5_tts_pkg}/../../ckpts/{dataset_name}/
|
| 23 |
+
- CustomDataset accesses row["audio"]["array"] + row["audio"]["sampling_rate"]
|
| 24 |
+
- Store audio as plain dict (no hf_datasets.Audio feature) to avoid torchcodec/FFmpeg dep
|
| 25 |
- --tokenizer_path only respected when --tokenizer custom (not char/pinyin)
|
| 26 |
+
- Must use --tokenizer pinyin to match base model text_embed shape [2546, 512]
|
| 27 |
|
| 28 |
Env vars:
|
| 29 |
HF_TOKEN — HF write token (injected as --secrets HF_TOKEN)
|
|
|
|
| 56 |
|
| 57 |
DATASET_NAME = "narrator"
|
| 58 |
EXP_ARCH = "F5TTS_v1_Base"
|
|
|
|
| 59 |
|
| 60 |
|
| 61 |
def _lib_root() -> Path:
|
|
|
|
| 75 |
|
| 76 |
|
| 77 |
def prepare_data() -> None:
|
| 78 |
+
"""Download LJSpeech, build HF Arrow dataset + vocab at paths F5-TTS expects."""
|
| 79 |
tar_path = Path("/tmp/ljspeech.tar.bz2")
|
| 80 |
lj_root = Path("/tmp/LJSpeech-1.1")
|
| 81 |
|
|
|
|
| 87 |
tar.extractall("/tmp/", filter="data")
|
| 88 |
print(f"Extracted -> {lj_root}")
|
| 89 |
|
| 90 |
+
# Store audio as pre-decoded float arrays — NO hf_datasets.Audio feature.
|
| 91 |
+
# Using Audio feature triggers torchcodec at read time, which requires FFmpeg
|
| 92 |
+
# system libs that aren't available in HF Jobs. Storing plain dicts sidesteps this:
|
| 93 |
+
# row["audio"]["array"] is a Python list[float], row["audio"]["sampling_rate"] is int.
|
| 94 |
+
# F5-TTS CustomDataset calls torch.FloatTensor(row["audio"]["array"]) which accepts lists.
|
| 95 |
+
audio_data: list[dict] = []
|
| 96 |
texts: list[str] = []
|
| 97 |
durations: list[float] = []
|
| 98 |
count = 0
|
|
|
|
| 122 |
import librosa
|
| 123 |
arr = librosa.resample(arr, orig_sr=orig_sr, target_sr=TARGET_SR)
|
| 124 |
|
| 125 |
+
audio_data.append({"array": arr.tolist(), "sampling_rate": TARGET_SR})
|
|
|
|
|
|
|
| 126 |
texts.append(text)
|
| 127 |
durations.append(len(arr) / TARGET_SR)
|
| 128 |
count += 1
|
|
|
|
| 130 |
print(f"Prepared {count} samples")
|
| 131 |
|
| 132 |
# F5-TTS dataset.py builds the path as data/{dataset_name}_{tokenizer}/raw/
|
| 133 |
+
# Must use pinyin tokenizer to match base model's 2546-token text_embed shape.
|
|
|
|
|
|
|
| 134 |
token_dir = _pkg_data_root() / f"{DATASET_NAME}_pinyin"
|
| 135 |
raw_dir = token_dir / "raw"
|
| 136 |
raw_dir.mkdir(parents=True, exist_ok=True)
|
| 137 |
|
| 138 |
+
ds = hf_datasets.Dataset.from_dict({"audio": audio_data, "text": texts})
|
|
|
|
|
|
|
|
|
|
| 139 |
ds.save_to_disk(str(raw_dir))
|
| 140 |
print(f"Saved dataset -> {raw_dir}")
|
| 141 |
|