TTS Dataset Preparation Guide¶
This guide covers the critical first phase of any TTS project: preparing high-quality, correctly formatted audio and text data for TTS training or fine-tuning. The quality of your dataset directly impacts the quality of your final TTS model.
If an audio term feels unfamiliar while you read, use the glossary. This page only pauses to explain terms when they matter for the step you are doing.
Dataset Preparation Steps¶
Follow these steps systematically to transform raw audio into a training-ready dataset.
1. Audio Acquisition and Initial Processing¶
- Gather Audio: Collect your raw audio files (common formats include WAV, MP3, FLAC, OGG, M4A). Ensure you have the rights to use this audio.
- Convert to WAV: Most TTS frameworks expect WAV format. Use tools like
ffmpegor audio libraries (pydub,soundfile) to convert your audio. Aim for a standard WAV encoding like PCM 16-bit. - Standardize Channels (Mono): TTS models typically train on single-channel (mono) audio. Convert stereo tracks to mono.
# Example using ffmpeg to convert stereo WAV to mono WAV ffmpeg -i stereo_input.wav -ac 1 mono_output.wav-ac 1: Sets the number of audio channels to 1.
- Resample Audio: Ensure all audio files have the exact same sampling rate. Sampling rate means how many audio samples are stored per second. Choose your target rate based on your project goals and framework compatibility (e.g., 16000 Hz, 22050 Hz, 48000 Hz). 22050 Hz is common for many models.
-ar 22050: Sets the audio sampling rate (samples per second).
Small but important warning: do not mix sample rates, channel layouts, or clipping-heavy files in the same dataset and hope the model will figure it out. These inconsistencies usually show up later as noisy, unstable, or unnatural speech.
2. Advanced Audio Cleaning (Noise/Music Removal)¶
- Goal: To remove unwanted background sounds like noise (hum, hiss, fans), music, reverb, or other interfering voices from your source audio, isolating the target speaker's voice as much as possible. This step is crucial if your source audio is not studio quality.
-
Why? TTS models learn from the audio they are given. If the audio contains background noise or music, the resulting TTS voice will likely inherit these characteristics, sounding noisy or "muddy". Cleaner audio leads to a cleaner TTS voice.
-
Tools & Techniques:
- AI Source Separation Tools (Recommended for Music/Voice): These tools use AI models to separate audio into different stems (vocals, music, drums, bass, other).
- Ultimate Vocal Remover (UVR): A popular, free, open-source GUI application that provides access to various state-of-the-art AI separation models. It's excellent for removing background music or isolating dialogue.
- Models (like those mentioned): UVR allows you to use different AI models.
MDX-Inst-HQ3is one such model often good at separating vocals from instruments (hence "Inst"). Other MDX models, Demucs models (likehtdemucs), and potentially models like Mel-Roformer (if integrated or available standalone) are designed for similar tasks, each with slightly different strengths and weaknesses. Experimentation is key. Choose models focused on vocal isolation.
- Models (like those mentioned): UVR allows you to use different AI models.
- Other Tools: Online services (e.g., Lalal.ai) or other standalone software might use similar underlying models (often Demucs or Spleeter variants).
- Ultimate Vocal Remover (UVR): A popular, free, open-source GUI application that provides access to various state-of-the-art AI separation models. It's excellent for removing background music or isolating dialogue.
- Traditional Noise Reduction Tools: Often found in Digital Audio Workstations (DAWs) or audio editors.
- Audacity: Contains built-in noise reduction effects (requires sampling a noise profile). Can be effective for constant background noise (like hiss or hum).
- Commercial Plugins (e.g., Izotope RX, Waves Clarity): Offer more sophisticated AI-powered noise, reverb, and voice isolation tools, but come at a cost.
- Spectral Editing: Manually removing unwanted sounds in a spectral editor (like Adobe Audition, Izotope RX, Acon Digital Acoustica). Powerful but very time-consuming.
- AI Source Separation Tools (Recommended for Music/Voice): These tools use AI models to separate audio into different stems (vocals, music, drums, bass, other).
-
Workflow Considerations:
- When to Apply: It's generally recommended to apply cleaning to your longer audio files before chunking (Step 1.3 below). This allows the AI models to work with more context and can be more efficient than processing thousands of small chunks. However, if cleaning introduces too many artifacts on long files, you might try cleaning individual problematic chunks later.
- Process:
- Load your standardized WAV file (from Step 1.1) into the chosen tool (e.g., UVR).
- Select an appropriate vocal isolation model (e.g., an MDX or Demucs vocal model).
- Process the audio to generate a "vocals only" track.
- Listen Carefully: Critically evaluate the separated vocal track. Check for:
- Artifacts: AI separation can sometimes introduce "watery" sounds, glitches, or parts of the voice being mistakenly removed.
- Remaining Noise/Music: How effectively was the unwanted sound removed?
- Iterate: You might need to try different models, adjust settings within the tool, or even apply a secondary noise reduction pass (e.g., using Audacity's noise reduction on the AI-separated vocals) for best results.
- Save Output: Save the cleaned vocal track as a new WAV file (e.g.,
original_file_cleaned.wav). Use these cleaned files as the input for the next step (Chunking).
-
Caveats:
- Artifacts are Possible: Aggressive cleaning can degrade the naturalness of the target voice. Aim for a balance between removing noise and preserving voice quality.
- Computational Cost: AI separation models can be computationally intensive and may take significant time, especially on long audio files and without a powerful GPU.
3. Audio Chunking (Splitting into Segments)¶
- Goal: Break long audio files (like chapters of an audiobook or podcast episodes) into shorter, manageable segments. Ideal segment length is typically between 2 to 15 seconds.
- Why Chunk?
- Aligns audio duration with typical sentence lengths.
- Makes transcription feasible (transcribing hours-long files is difficult).
- Helps manage memory during training.
- Allows filtering out unsuitable segments (e.g., pure silence, noise, music).
- Method: Use tools that detect silence to split the audio.
pydubis a popular Python library for this. -
Important: Chunk boundaries should make later transcription straightforward. If a split cuts off the beginning or end of a word, fix the split first instead of hoping the model will learn around a bad audio-text pair.
- Review: Listen to a sample of the generated chunks. Are the splits logical? Is speech cut off? Adjust# Example using pydub for silence-based splitting from pydub import AudioSegment from pydub.silence import split_on_silence import os input_file = "resampled_mono_audio.wav" # Use the output from step 1.1 output_dir = "audio_chunks" # Create this directory os.makedirs(output_dir, exist_ok=True) print(f"Loading audio file: {input_file}") sound = AudioSegment.from_wav(input_file) print("Audio loaded. Splitting based on silence...") chunks = split_on_silence( sound, min_silence_len=500, # Minimum duration of silence in milliseconds to trigger a split. Adjust as needed. silence_thresh=-40, # Silence threshold in dBFS (decibels relative to full scale). Lower values (e.g., -50) detect quieter silences. Adjust based on your audio's noise floor. keep_silence=200 # Optional: Amount of silence (in ms) to leave at the beginning/end of each chunk. Helps avoid abrupt cuts. ) print(f"Found {len(chunks)} potential chunks before duration filtering.") # --- Filtering and Exporting --- min_duration_sec = 2.0 # Minimum chunk length in seconds max_duration_sec = 15.0 # Maximum chunk length in seconds target_sr = 22050 # Ensure chunks retain the correct sample rate (pydub usually handles this) exported_count = 0 for i, chunk in enumerate(chunks): duration_sec = len(chunk) / 1000.0 if min_duration_sec <= duration_sec <= max_duration_sec: # Ensure the chunk uses the target sample rate if necessary (pydub tries to preserve it) # chunk = chunk.set_frame_rate(target_sr) # Usually not needed if source was correctly sampled chunk_filename = f"segment_{exported_count:05d}.wav" # Use padding for easier sorting chunk_path = os.path.join(output_dir, chunk_filename) print(f"Exporting chunk {i} ({duration_sec:.2f}s) to {chunk_path}") chunk.export(chunk_path, format="wav") exported_count += 1 else: print(f"Skipping chunk {i} due to duration: {duration_sec:.2f}s") print(f"\nExported {exported_count} chunks meeting duration criteria ({min_duration_sec}-{max_duration_sec}s) to '{output_dir}'.")min_silence_lenandsilence_threshand re-run if necessary. Manually splitting or refining splits in an audio editor (like Audacity) might be needed for tricky audio.
4. Volume Normalization¶
- Goal: Ensure all audio chunks have a consistent volume level. This prevents quiet or loud segments from disproportionately affecting training.
- Methods:
- Peak Normalization: Adjusts the audio so the loudest point reaches a specific level (e.g., -3.0 dBFS). Simple, but doesn't guarantee consistent perceived loudness.
- Loudness Normalization (LUFS): LUFS measures how loud audio sounds to people, not just how high the waveform peaks. This adjusts the audio to meet a target perceived loudness level (e.g., -23 LUFS is common for broadcast). It is often more consistent than peak normalization, but it requires libraries like
pyloudnorm. - Beginner Tip: If LUFS feels too advanced for a first pass, consistent peak normalization is still much better than leaving clips at mixed volume levels.
-
Apply Consistently: Apply the chosen normalization method to all chunks created in the previous step. Save the normalized files to a new directory (e.g.,
normalized_chunks) to keep originals intact.* Note: For LUFS normalization, you'd use a library like# Example using pydub for PEAK normalization from pydub import AudioSegment import os import glob input_chunk_dir = "audio_chunks" output_norm_dir = "normalized_chunks" os.makedirs(output_norm_dir, exist_ok=True) target_dBFS = -3.0 # Target peak amplitude def match_target_amplitude(sound, target_dBFS): change_in_dBFS = target_dBFS - sound.dBFS return sound.apply_gain(change_in_dBFS) print(f"Normalizing chunks from '{input_chunk_dir}' to '{output_norm_dir}' with target peak {target_dBFS} dBFS.") wav_files = glob.glob(os.path.join(input_chunk_dir, "*.wav")) for i, wav_file in enumerate(wav_files): filename = os.path.basename(wav_file) output_path = os.path.join(output_norm_dir, filename) try: sound = AudioSegment.from_wav(wav_file) # Only apply gain if the sound is not silent (dBFS is not -inf) if sound.dBFS > -float('inf'): normalized_sound = match_target_amplitude(sound, target_dBFS) normalized_sound.export(output_path, format="wav") else: print(f"Skipping silent file: {filename}") # Optionally copy silent files or handle them as needed # shutil.copy(wav_file, output_path) if (i + 1) % 50 == 0: # Print progress print(f"Processed {i+1}/{len(wav_files)} files...") except Exception as e: print(f"Error processing {filename}: {e}") print(f"\nNormalization complete. Normalized files saved in '{output_norm_dir}'.")pyloudnorm, iterating through files similarly.
5. Transcription: Creating Text Pairs¶
- Goal: Obtain an accurate text transcript for every single normalized audio chunk. A transcript is simply the text version of what is spoken in the audio. It should represent exactly what is spoken in the chunk.
- Methods:
- Automatic Speech Recognition (ASR): Best for large datasets. Use high-quality ASR models.
- OpenAI Whisper: Excellent multilingual, open-source option. Runs locally (GPU recommended) or via API. Note: While powerful for word accuracy, Whisper's punctuation and capitalization may require careful review and correction during the cleaning step. Various community fine-tuned Whisper models (often found on Hugging Face) may offer improvements.
- Google audio transcription tools and APIs: Google may offer audio-capable models or services that can help with transcription. Product names, limits, and free tiers change over time, so verify the current documentation before depending on a specific workflow.
- Cloud Services: Google Cloud Speech-to-Text, AWS Transcribe, Azure Speech Service offer robust APIs, often with pay-as-you-go pricing and potentially free tiers initially.
- Other Models: Explore Hugging Face Models for other open-source or fine-tuned ASR models specific to your language.
- Manual Transcription: Most accurate but very time-consuming. Suitable for small, high-value datasets or for correcting ASR outputs.
- Existing Transcripts: If your source audio comes with aligned transcripts (e.g., some audiobooks, broadcast archives), you may need scripts to parse and align them with your chunks.
- Warning: Vendor model names, API limits, and pricing can change quickly. Always verify current documentation before building a workflow around a specific hosted ASR service.
- Output Format: Create one
.txtfile for each corresponding.wavfile in yournormalized_chunksdirectory. The filenames must match exactly (e.g.,normalized_chunks/segment_00001.wavneedstranscripts/segment_00001.txt). -
Text Cleaning and Normalization: This is crucial!
- Remove Non-Speech: Delete timestamps (like
[00:01:05]), speaker labels ("SPEAKER A:", "John Doe:"), sound event tags ([laughter],[music]), transcription comments. - Handle Filler Words: Decide whether to keep or remove common fillers ("uh," "um," "ah"). Keeping them might make the TTS sound more natural but can also introduce unwanted hesitations. Removing them leads to cleaner, more direct speech. Consistency is key.
- Punctuation: Ensure consistent and appropriate punctuation. Commas, periods, question marks help the model learn prosody. Avoid excessive or non-standard punctuation.
- Numbers, Acronyms, Symbols: Expand them into words (e.g., "101" -> "one hundred one", "USA" -> "U S A" or "United States of America", "%" -> "percent"). How you expand depends on how you want the TTS to pronounce them. Create a normalization dictionary/ruleset if needed.
- Case: Usually convert text to a consistent case (e.g., lowercase) unless your TTS framework/tokenizer handles casing appropriately. Check framework docs.
- Special Characters: Remove or replace characters that might confuse the tokenizer (e.g., emojis, control characters).
flowchart TD dataset["my_tts_dataset/"] audio["normalized_chunks/"] wav1["segment_00001.wav"] wav2["segment_00002.wav"] wavMore["..."] transcripts["transcripts/"] txt1["segment_00001.txt<br/>Contains: Hello world."] txt2["segment_00002.txt<br/>Contains: This is a test sentence."] txtMore["..."] dataset --> audio audio --> wav1 audio --> wav2 audio --> wavMore dataset --> transcripts transcripts --> txt1 transcripts --> txt2 transcripts --> txtMore - Remove Non-Speech: Delete timestamps (like
Example: Raw Transcript to Clean Training Text¶
Use consistent cleanup rules. For example:
Raw transcript:
[00:01:05] SPEAKER A: Um, I paid $12.50 for 3 apples...
Cleaned training text:
I paid twelve dollars and fifty cents for three apples.
The goal is not to make the text look literary. The goal is to make the text match what you want the model to say, in a form the tokenizer and model can learn consistently.
6. Data Structuring and Manifest File Creation¶
- Goal: Create index files called manifest files that tell the TTS training script where to find the audio files and their corresponding transcriptions.
- Manifest Format: The most common format is a plain text file where each line represents one audio-text pair, separated by a delimiter (usually a pipe
|).path/to/audio_chunk.wav: Relative path to the normalized audio file from the directory where the training script will be run.The corresponding transcription text: The cleaned, normalized text from the.txtfile.speaker_id: An identifier for the speaker (e.g.,speaker0,mary_smith). For single-speaker datasets, use the same ID for all lines. For multi-speaker datasets, use unique IDs for each distinct speaker.
- Splitting Data (Train/Validation): Divide your data into a training set (used to update model weights) and a validation set (used to monitor performance on unseen data and prevent overfitting). A common split is 90-98% for training and 2-10% for validation. Crucially, ensure that segments from the same original long recording do not end up in both train and validation sets if possible, to avoid data leakage. If splitting randomly, shuffle first.
- Beginner-safe default: If you do not yet have a better strategy, keep validation small but representative. A simple 95/5 split is usually fine for a first pass, as long as you avoid leaking chunks from the same original recording into both sets.
- Path warning: Many frameworks expect manifest audio paths to be relative to a specific working directory, not relative to the manifest file itself. Before training, open one manifest line and verify that the training script can actually resolve that path from the directory where you will run it.
-
Generate Manifests Script:
import os import random # --- Configuration --- dataset_name = "my_tts_dataset" normalized_audio_dir = os.path.join(dataset_name, "normalized_chunks") transcripts_dir = os.path.join(dataset_name, "transcripts") output_dir = dataset_name # Where manifest files will be saved train_manifest_path = os.path.join(output_dir, "train_list.txt") val_manifest_path = os.path.join(output_dir, "val_list.txt") speaker_id = "main_speaker" # Use a consistent ID for single speaker datasets # For multi-speaker, determine ID based on filename or source val_split_ratio = 0.05 # 5% for validation set random_seed = 42 # For reproducible splits # --------------------- manifest_entries = [] print("Reading audio and transcript files...") # Iterate through normalized audio files wav_files = sorted([f for f in os.listdir(normalized_audio_dir) if f.endswith(".wav")]) for wav_filename in wav_files: base_filename = os.path.splitext(wav_filename)[0] txt_filename = base_filename + ".txt" audio_path = os.path.join(normalized_audio_dir, wav_filename) # IMPORTANT: many frameworks want paths relative to the directory where # train.py will be launched, not absolute paths and not paths relative # to the manifest file itself. # Example: # relative_audio_path = os.path.relpath(audio_path, start=training_script_dir) relative_audio_path = audio_path # This only works if your framework accepts this exact path style. transcript_path = os.path.join(transcripts_dir, txt_filename) if os.path.exists(transcript_path): try: with open(transcript_path, "r", encoding="utf-8") as f: transcript = f.read().strip() # Basic cleaning: remove pipe chars, trim extra whitespace transcript = transcript.replace('|', ' ').strip() transcript = ' '.join(transcript.split()) # Normalize whitespace if transcript: # Ensure transcript is not empty after cleaning manifest_entries.append(f"{relative_audio_path}|{transcript}|{speaker_id}") else: print(f"Warning: Empty transcript for {wav_filename}. Skipping.") except Exception as e: print(f"Error reading or processing transcript {txt_filename}: {e}. Skipping.") else: print(f"Warning: Missing transcript file {txt_filename} for {wav_filename}. Skipping.") print(f"Found {len(manifest_entries)} valid audio-transcript pairs.") # Shuffle and split random.seed(random_seed) random.shuffle(manifest_entries) split_idx = int(len(manifest_entries) * (1 - val_split_ratio)) train_entries = manifest_entries[:split_idx] val_entries = manifest_entries[split_idx:] # Write manifest files try: with open(train_manifest_path, "w", encoding="utf-8") as f: f.write("\n".join(train_entries)) print(f"Successfully wrote {len(train_entries)} entries to {train_manifest_path}") with open(val_manifest_path, "w", encoding="utf-8") as f: f.write("\n".join(val_entries)) print(f"Successfully wrote {len(val_entries)} entries to {val_manifest_path}") except Exception as e: print(f"Error writing manifest files: {e}")
Practical Verification Scripts¶
Here are some practical scripts to help verify your dataset quality:
Platform note: the shell example below is for Linux or macOS. On Windows, either run it from Git Bash/WSL or translate the same checks into PowerShell or Python.
Check Audio Properties (Sampling Rate, Channels, Duration)¶
#!/bin/bash
# verify_audio.sh - Check audio properties across all WAV files
# Usage: ./verify_audio.sh /path/to/audio/directory
AUDIO_DIR="$1"
echo "Checking audio files in $AUDIO_DIR..."
# Check if SoX is installed
if ! command -v soxi &> /dev/null; then
echo "SoX not found. Please install it first (e.g., 'apt-get install sox' or 'brew install sox')."
exit 1
fi
# Initialize counters and arrays
total_files=0
non_mono=0
wrong_rate=0
too_short=0
too_long=0
target_rate=22050 # Change this to your target sampling rate
min_duration=1.0 # Minimum duration in seconds
max_duration=15.0 # Maximum duration in seconds
# Process all WAV files
find "$AUDIO_DIR" -name "*.wav" | while read -r file; do
total_files=$((total_files + 1))
# Get audio properties
channels=$(soxi -c "$file")
rate=$(soxi -r "$file")
duration=$(soxi -d "$file" | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }')
# Check properties
if [ "$channels" -ne 1 ]; then
echo "WARNING: Non-mono file: $file (channels: $channels)"
non_mono=$((non_mono + 1))
fi
if [ "$rate" -ne "$target_rate" ]; then
echo "WARNING: Wrong sampling rate: $file (rate: $rate Hz, expected: $target_rate Hz)"
wrong_rate=$((wrong_rate + 1))
fi
if (( $(echo "$duration < $min_duration" | bc -l) )); then
echo "WARNING: File too short: $file (duration: ${duration}s, minimum: ${min_duration}s)"
too_short=$((too_short + 1))
fi
if (( $(echo "$duration > $max_duration" | bc -l) )); then
echo "WARNING: File too long: $file (duration: ${duration}s, maximum: ${max_duration}s)"
too_long=$((too_long + 1))
fi
# Print progress every 100 files
if [ $((total_files % 100)) -eq 0 ]; then
echo "Processed $total_files files..."
fi
done
# Print summary
echo "===== SUMMARY ====="
echo "Total files checked: $total_files"
echo "Non-mono files: $non_mono"
echo "Files with wrong sampling rate: $wrong_rate"
echo "Files too short (<${min_duration}s): $too_short"
echo "Files too long (>${max_duration}s): $too_long"
if [ $((non_mono + wrong_rate + too_short + too_long)) -eq 0 ]; then
echo "All files passed basic checks!"
else
echo "Some issues were found. Please review the warnings above."
fi
Verify Manifest File Integrity¶
#!/usr/bin/env python3
# verify_manifest.py - Check that all files in manifest exist and have matching transcripts
# Usage: python verify_manifest.py path/to/manifest.txt
import os
import sys
from pathlib import Path
def verify_manifest(manifest_path):
"""Verify that all audio files and transcripts in the manifest exist and are valid."""
if not os.path.exists(manifest_path):
print(f"Error: Manifest file '{manifest_path}' not found.")
return False
print(f"Verifying manifest: {manifest_path}")
base_dir = os.path.dirname(os.path.abspath(manifest_path))
# Statistics
total_entries = 0
missing_audio = 0
empty_transcripts = 0
with open(manifest_path, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
total_entries += 1
# Parse the line (assuming pipe-separated format: audio_path|transcript|speaker_id)
parts = line.split('|')
if len(parts) < 2:
print(f"Line {line_num}: Invalid format. Expected at least 'audio_path|transcript'")
continue
audio_path = parts[0]
transcript = parts[1]
# Check if audio path is relative and resolve it
if not os.path.isabs(audio_path):
audio_path = os.path.join(base_dir, audio_path)
# Check if audio file exists
if not os.path.exists(audio_path):
print(f"Line {line_num}: Audio file not found: {audio_path}")
missing_audio += 1
# Check if transcript is empty
if not transcript or transcript.isspace():
print(f"Line {line_num}: Empty transcript for {audio_path}")
empty_transcripts += 1
# Print summary
print("\n===== SUMMARY =====")
print(f"Total entries: {total_entries}")
print(f"Missing audio files: {missing_audio}")
print(f"Empty transcripts: {empty_transcripts}")
if missing_audio == 0 and empty_transcripts == 0:
print("All manifest entries are valid!")
return True
else:
print("Issues found in manifest. Please fix them before proceeding.")
return False
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python verify_manifest.py path/to/manifest.txt")
sys.exit(1)
success = verify_manifest(sys.argv[1])
sys.exit(0 if success else 1)
Visualize Audio Spectrograms for Quality Assessment¶
This script helps you visually inspect the quality of your audio files by generating mel spectrograms, which are image-like views of audio energy over time:
#!/usr/bin/env python3
# generate_spectrograms.py - Create spectrograms for audio quality assessment
# Usage: python generate_spectrograms.py /path/to/audio/directory /path/to/output/directory [num_samples]
import os
import sys
import random
import numpy as np
import matplotlib.pyplot as plt
import librosa
import librosa.display
from pathlib import Path
def generate_spectrograms(audio_dir, output_dir, num_samples=10):
"""Generate spectrograms for a random sample of audio files."""
# Create output directory if it doesn't exist
os.makedirs(output_dir, exist_ok=True)
# Get all WAV files
wav_files = list(Path(audio_dir).glob('**/*.wav'))
if not wav_files:
print(f"No WAV files found in {audio_dir}")
return False
# Sample files if there are more than requested
if len(wav_files) > num_samples:
wav_files = random.sample(wav_files, num_samples)
print(f"Generating spectrograms for {len(wav_files)} files...")
for i, wav_path in enumerate(wav_files):
try:
# Load audio file
y, sr = librosa.load(wav_path, sr=None)
# Create figure with two subplots
plt.figure(figsize=(12, 8))
# Plot waveform
plt.subplot(2, 1, 1)
librosa.display.waveshow(y, sr=sr)
plt.title(f'Waveform: {wav_path.name}')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
# Plot spectrogram
plt.subplot(2, 1, 2)
D = librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max)
librosa.display.specshow(D, sr=sr, x_axis='time', y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('Log-frequency power spectrogram')
# Save figure
output_path = os.path.join(output_dir, f'spectrogram_{i+1}_{wav_path.stem}.png')
plt.tight_layout()
plt.savefig(output_path)
plt.close()
print(f"Generated: {output_path}")
except Exception as e:
print(f"Error processing {wav_path}: {e}")
print(f"Spectrograms saved to {output_dir}")
return True
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python generate_spectrograms.py /path/to/audio/directory /path/to/output/directory [num_samples]")
sys.exit(1)
audio_dir = sys.argv[1]
output_dir = sys.argv[2]
num_samples = int(sys.argv[3]) if len(sys.argv) > 3 else 10
success = generate_spectrograms(audio_dir, output_dir, num_samples)
sys.exit(0 if success else 1)
These scripts provide practical tools to verify your dataset's quality before training, helping you identify and fix issues early in the process.
Data Quality Checklist¶
Before moving to training setup, do one final quick review of the prepared dataset:
- Every
.wavlisted in the manifests actually exists. - Every audio segment has the correct matching transcript.
- Most segments stay within your target duration range, with few outliers.
- Random spot checks sound clean and do not contain strong noise, music, echo, or the wrong speaker.
- All files use the same sample rate, channel layout, and expected format.
- Volume levels are reasonably consistent across the dataset.
- Transcripts are clean: no timestamps, no speaker labels, and consistent punctuation.
- Manifest files use the correct format and resolve properly from the directory where you will run
train.py. - Your validation set does not overlap with the training set.
Tip: Use tools like soxi (from SoX) or ffprobe to batch-check audio properties (sampling rate, channels, duration). Write small scripts to verify file existence and basic manifest formatting.
Once your dataset passes this quality check, you are ready to proceed to setting up the training environment.