| |
| """ |
| Create dataset from Hindi TTS data with phonemes. |
| Format: filename.wav|phoneme|language_id|speaker_id|text |
| """ |
|
|
| import json |
| import shutil |
| import multiprocessing as mp |
| from pathlib import Path |
| from tqdm import tqdm |
| import logging |
| try: |
| from phonemizer import phonemize |
| except ImportError: |
| print("Error: 'phonemizer' package is not installed. Please install it using 'pip install phonemizer' and ensure 'espeak-ng' is installed on your system.") |
| exit(1) |
|
|
| |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s') |
| log = logging.getLogger(__name__) |
| logging.getLogger("phonemizer").setLevel(logging.ERROR) |
|
|
| BASE_DIR = Path("IISc_SYSPIN_Data") |
| MALE_DIR = BASE_DIR / "IISc_SYSPINProject_Hindi_Male_Spk001_HC" |
| FEMALE_DIR = BASE_DIR / "IISc_SYSPINProject_Hindi_Female_Spk001_HC" |
| OUTPUT_WAV_DIR = Path("hindi_wavs") |
| WAV_REL_PATH = "hindi/hindi_wavs" |
| OUTPUT_FILE = Path("dataset.txt") |
| EXISTING_TRAIN_LIST = Path("..") / "list.txt" |
| MERGED_TRAIN_LIST = Path("..") / "list_merged.txt" |
| |
|
|
| |
| LANGUAGE_ID = 6 |
| SPEAKER_ID_MAP = { |
| "male": 9, |
| "female": 10, |
| } |
|
|
|
|
| def get_phonemes(text): |
| """Convert text to phonemes using phonemizer with espeak-ng.""" |
| try: |
| phonemes_list = phonemize( |
| [text], |
| language="hi", |
| backend="espeak", |
| strip=True, |
| preserve_punctuation=True, |
| with_stress=True, |
| ) |
| if phonemes_list: |
| return " ".join(phonemes_list[0].split()) |
| return "" |
| except Exception: |
| return "" |
|
|
|
|
| def process_entry(entry): |
| """Process single entry: (key, text, speaker_id) -> formatted line or None.""" |
| key, text, sid = entry |
| clean_text = text.replace("\n", " ").strip() |
| phn = get_phonemes(clean_text) |
| if phn: |
| return f"{WAV_REL_PATH}/{key}.wav|{phn}|{LANGUAGE_ID}|{sid}|{clean_text}" |
| return None |
|
|
|
|
| def load_transcripts(): |
| """Load all transcripts from JSON files.""" |
| entries = [] |
| |
| male_json = MALE_DIR / "IISc_SYSPINProject_Hindi_Male_Spk001_HC_Transcripts.json" |
| female_json = FEMALE_DIR / "IISc_SYSPINProject_Hindi_Female_Spk001_HC_Transcripts.json" |
| |
| log.info(f"Loading {male_json}") |
| with open(male_json, "r", encoding="utf-8") as f: |
| data = json.load(f) |
| for key, val in data.get("Transcripts", {}).items(): |
| entries.append((key, val["Transcript"], SPEAKER_ID_MAP["male"])) |
| log.info(f"Loaded {len(entries)} male entries") |
| |
| male_count = len(entries) |
| log.info(f"Loading {female_json}") |
| with open(female_json, "r", encoding="utf-8") as f: |
| data = json.load(f) |
| for key, val in data.get("Transcripts", {}).items(): |
| entries.append((key, val["Transcript"], SPEAKER_ID_MAP["female"])) |
| log.info(f"Loaded {len(entries) - male_count} female entries") |
| |
| return entries |
|
|
|
|
| def test_espeak(): |
| """Test that espeak-ng works.""" |
| log.info("Testing espeak-ng...") |
| result = get_phonemes("नमस्ते") |
| if result: |
| log.info(f"espeak-ng test passed: नमस्ते -> {result}") |
| return True |
| else: |
| log.error("espeak-ng test FAILED!") |
| return False |
|
|
|
|
| def phonemize_worker(texts, language, q): |
| """Worker for phonemization in a separate process.""" |
| try: |
| phns = phonemize( |
| texts, |
| language=language, |
| backend="espeak", |
| strip=True, |
| preserve_punctuation=True, |
| with_stress=True, |
| ) |
| q.put(phns) |
| except Exception: |
| q.put(None) |
|
|
|
|
| def phonemize_batch_safe(batch_texts, language="hi"): |
| """Run phonemizer in a separate process to avoid crashing the main process. |
| |
| Returns a list of phoneme strings or None if the worker crashed. |
| """ |
| ctx = mp.get_context("spawn") |
| queue = ctx.Queue() |
|
|
| proc = ctx.Process(target=phonemize_worker, args=(batch_texts, language, queue)) |
| proc.start() |
| proc.join() |
|
|
| if proc.exitcode != 0: |
| return None |
|
|
| try: |
| return queue.get_nowait() |
| except Exception: |
| return None |
|
|
|
|
| def main(): |
| log.info("Starting dataset creation...") |
| |
| if not test_espeak(): |
| return |
| |
| log.info("Loading transcripts...") |
| entries = load_transcripts() |
| total = len(entries) |
| log.info(f"Total entries to process: {total}") |
| |
| OUTPUT_WAV_DIR.mkdir(exist_ok=True) |
| log.info(f"Output wav dir: {OUTPUT_WAV_DIR}") |
| |
|
|
| log.info(f"Starting IPA conversion in batches...") |
| |
| results = [] |
| failed = 0 |
| BATCH_SIZE = 200 |
| |
| for i in tqdm(range(0, total, BATCH_SIZE), desc="IPA conversion", mininterval=0.5): |
| batch = entries[i:i+BATCH_SIZE] |
| batch_texts = [e[1].replace("\n", " ").strip() for e in batch] |
| |
| phonemes_list = phonemize_batch_safe(batch_texts, language="hi") |
| if phonemes_list is None: |
| log.error(f"Batch crashed at index {i}. Skipping this batch...") |
| failed += len(batch) |
| continue |
|
|
| for j, phn in enumerate(phonemes_list): |
| if phn: |
| key, _, sid = batch[j] |
| clean_text = batch_texts[j] |
| phn = " ".join(phn.split()) |
| results.append(f"{WAV_REL_PATH}/{key}.wav|{phn}|{LANGUAGE_ID}|{sid}|{clean_text}") |
| else: |
| failed += 1 |
|
|
| log.info(f"Conversion complete. Success: {len(results)}, Failed: {failed}") |
| log.info(f"Writing {OUTPUT_FILE}...") |
| |
| with open(OUTPUT_FILE, "w", encoding="utf-8") as f: |
| f.write("\n".join(results) + "\n") |
| |
| log.info(f"Wrote {len(results)} lines to {OUTPUT_FILE}") |
|
|
| log.info(f"Merging with {EXISTING_TRAIN_LIST}...") |
| all_lines = [] |
| if EXISTING_TRAIN_LIST.exists(): |
| with open(EXISTING_TRAIN_LIST, "r", encoding="utf-8") as f: |
| content = f.read().strip() |
| if content: |
| all_lines = content.split("\n") |
| |
| log.info(f"Loaded {len(all_lines)} lines from existing list.") |
| all_lines.extend(results) |
| |
| log.info(f"Writing {len(all_lines)} lines to {MERGED_TRAIN_LIST}...") |
| with open(MERGED_TRAIN_LIST, "w", encoding="utf-8") as f: |
| f.write("\n".join(all_lines) + "\n") |
| log.info(f"Merged file created at {MERGED_TRAIN_LIST}") |
| |
| |
| log.info("Collecting wav files...") |
| wav_files = list((MALE_DIR / "wav").glob("*.wav")) + list((FEMALE_DIR / "wav").glob("*.wav")) |
| log.info(f"Found {len(wav_files)} wav files to move") |
| |
| log.info("Moving wav files...") |
| for wav in tqdm(wav_files, desc="Moving wavs", mininterval=0.5): |
| shutil.move(str(wav), str(OUTPUT_WAV_DIR / wav.name)) |
| |
| log.info(f"Done! Dataset: {len(results)} entries, Wavs: {len(wav_files)} files") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|