Spaces:
Sleeping
Sleeping
import os | |
os.environ["NUMBA_DISABLE_CACHE"] = "1" | |
import gradio as gr | |
from docx import Document | |
from TTS.api import TTS | |
import tempfile | |
import zipfile | |
# Voice model | |
VOICE_MODEL = "tts_models/en/vctk/vits" | |
# Embedded metadata (from your file) | |
SPEAKER_METADATA = { | |
300: { "age": 23, "gender": "F", "accent": "American"}, | |
271: { "age": 19, "gender": "M", "accent": "Scottish"}, | |
287: { "age": 23, "gender": "M", "accent": "English"}, | |
262: { "age": 23, "gender": "F", "accent": "Scottish"}, | |
284: { "age": 20, "gender": "M", "accent": "Scottish"}, | |
297: { "age": 20, "gender": "F", "accent": "American"}, | |
227: { "age": 38, "gender": "M", "accent": "English"}, | |
246: { "age": 22, "gender": "M", "accent": "Scottish"}, | |
225: { "age": 23, "gender": "F", "accent": "English"}, | |
259: { "age": 23, "gender": "M", "accent": "English"}, | |
252: { "age": 22, "gender": "M", "accent": "Scottish"}, | |
231: { "age": 23, "gender": "F", "accent": "English"}, | |
266: { "age": 22, "gender": "F", "accent": "Irish"}, | |
241: { "age": 21, "gender": "M", "accent": "Scottish"}, | |
312: { "age": 19, "gender": "F", "accent": "Canadian"}, | |
329: { "age": 23, "gender": "F", "accent": "American"}, | |
232: { "age": 23, "gender": "M", "accent": "English"}, | |
305: { "age": 19, "gender": "F", "accent": "American"}, | |
311: { "age": 21, "gender": "M", "accent": "American"}, | |
301: { "age": 23, "gender": "F", "accent": "American"}, | |
304: { "age": 22, "gender": "M", "accent": "NorthernIrish"}, | |
310: { "age": 21, "gender": "F", "accent": "American"}, | |
260: { "age": 21, "gender": "M", "accent": "Scottish"}, | |
315: { "age": 18, "gender": "M", "accent": "American"}, | |
374: { "age": 28, "gender": "M", "accent": "Australian"}, | |
364: { "age": 23, "gender": "M", "accent": "Irish"}, | |
269: { "age": 20, "gender": "F", "accent": "English"}, | |
345: { "age": 22, "gender": "M", "accent": "American"}, | |
326: { "age": 26, "gender": "M", "accent": "Australian"}, | |
343: { "age": 27, "gender": "F", "accent": "Canadian"}, | |
230: { "age": 22, "gender": "F", "accent": "English"}, | |
376: { "age": 22, "gender": "M", "accent": "Indian"}, | |
240: { "age": 21, "gender": "F", "accent": "English"}, | |
298: { "age": 19, "gender": "M", "accent": "Irish"}, | |
272: { "age": 23, "gender": "M", "accent": "Scottish"}, | |
248: { "age": 23, "gender": "F", "accent": "Indian"}, | |
264: { "age": 23, "gender": "F", "accent": "Scottish"}, | |
250: { "age": 22, "gender": "F", "accent": "English"}, | |
292: { "age": 23, "gender": "M", "accent": "NorthernIrish"}, | |
237: { "age": 22, "gender": "M", "accent": "Scottish"}, | |
363: { "age": 22, "gender": "M", "accent": "Canadian"}, | |
313: { "age": 24, "gender": "F", "accent": "Irish"}, | |
285: { "age": 21, "gender": "M", "accent": "Scottish"}, | |
268: { "age": 23, "gender": "F", "accent": "English"}, | |
302: { "age": 20, "gender": "M", "accent": "Canadian"}, | |
261: { "age": 26, "gender": "F", "accent": "NorthernIrish"}, | |
336: { "age": 18, "gender": "F", "accent": "SouthAfrican"}, | |
288: { "age": 22, "gender": "F", "accent": "Irish"}, | |
226: { "age": 22, "gender": "M", "accent": "English"}, | |
277: { "age": 23, "gender": "F", "accent": "English"}, | |
360: { "age": 19, "gender": "M", "accent": "American"}, | |
257: { "age": 24, "gender": "F", "accent": "English"}, | |
254: { "age": 21, "gender": "M", "accent": "English"}, | |
339: { "age": 21, "gender": "F", "accent": "American"}, | |
323: { "age": 19, "gender": "F", "accent": "SouthAfrican"}, | |
255: { "age": 19, "gender": "M", "accent": "Scottish"}, | |
249: { "age": 22, "gender": "F", "accent": "Scottish"}, | |
293: { "age": 22, "gender": "F", "accent": "NorthernIrish"}, | |
244: { "age": 22, "gender": "F", "accent": "English"}, | |
245: { "age": 25, "gender": "M", "accent": "Irish"}, | |
361: { "age": 19, "gender": "F", "accent": "American"}, | |
314: { "age": 26, "gender": "F", "accent": "SouthAfrican"}, | |
308: { "age": 18, "gender": "F", "accent": "American"}, | |
229: { "age": 23, "gender": "F", "accent": "English"}, | |
341: { "age": 26, "gender": "F", "accent": "American"}, | |
275: { "age": 23, "gender": "M", "accent": "Scottish"}, | |
263: { "age": 22, "gender": "M", "accent": "Scottish"}, | |
253: { "age": 22, "gender": "F", "accent": "Welsh"}, | |
299: { "age": 25, "gender": "F", "accent": "American"}, | |
316: { "age": 20, "gender": "M", "accent": "Canadian"}, | |
282: { "age": 23, "gender": "F", "accent": "English"}, | |
362: { "age": 29, "gender": "F", "accent": "American"}, | |
294: { "age": 33, "gender": "F", "accent": "American"}, | |
274: { "age": 22, "gender": "M", "accent": "English"}, | |
279: { "age": 23, "gender": "M", "accent": "English"}, | |
281: { "age": 29, "gender": "M", "accent": "Scottish"}, | |
286: { "age": 23, "gender": "M", "accent": "English"}, | |
258: { "age": 22, "gender": "M", "accent": "English"}, | |
247: { "age": 22, "gender": "M", "accent": "Scottish"}, | |
351: { "age": 21, "gender": "F", "accent": "NorthernIrish"}, | |
283: { "age": 24, "gender": "F", "accent": "Irish"}, | |
334: { "age": 18, "gender": "M", "accent": "American"}, | |
333: { "age": 19, "gender": "F", "accent": "American"}, | |
295: { "age": 23, "gender": "F", "accent": "Irish"}, | |
330: { "age": 26, "gender": "F", "accent": "American"}, | |
335: { "age": 25, "gender": "F", "accent": "NewZealand"}, | |
228: { "age": 22, "gender": "F", "accent": "English"}, | |
267: { "age": 23, "gender": "F", "accent": "English"}, | |
273: { "age": 18, "gender": "F", "accent": "English"} | |
} | |
# Return dropdown list like: "p225 - F, English" | |
def get_speaker_dropdown_choices(): | |
choices = [] | |
for speaker_id, meta in SPEAKER_METADATA.items(): | |
desc = f"p{speaker_id} - {meta['gender']}, {meta['accent']}" | |
choices.append((desc, f"p{speaker_id}")) | |
return choices | |
# Cache TTS model | |
MODEL_CACHE = {} | |
def load_tts_model(): | |
if VOICE_MODEL not in MODEL_CACHE: | |
MODEL_CACHE[VOICE_MODEL] = TTS(model_name=VOICE_MODEL, progress_bar=False, gpu=False) | |
return MODEL_CACHE[VOICE_MODEL] | |
def docx_to_wav(doc_file, selected_desc): | |
speaker_id = next((sid for desc, sid in get_speaker_dropdown_choices() if desc == selected_desc), None) | |
if not speaker_id: | |
raise ValueError("Invalid speaker selection") | |
tts = load_tts_model() | |
document = Document(doc_file.name) | |
full_text = "\n".join([para.text for para in document.paragraphs if para.text.strip()]) | |
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_wav: | |
wav_path = tmp_wav.name | |
tts.tts_to_file(text=full_text, file_path=wav_path, speaker=speaker_id) | |
return wav_path | |
def docx_to_zipped_wavs(doc_file, selected_desc): | |
speaker_id = next((sid for desc, sid in get_speaker_dropdown_choices() if desc == selected_desc), None) | |
if not speaker_id: | |
raise ValueError("Invalid speaker selection") | |
tts = load_tts_model() | |
document = Document(doc_file.name) | |
paragraphs = [p.text.strip() for p in document.paragraphs if p.text.strip()] | |
if not paragraphs: | |
raise ValueError("No non-empty paragraphs found in the document.") | |
with tempfile.TemporaryDirectory() as temp_dir: | |
wav_paths = [] | |
for i, para in enumerate(paragraphs, start=1): | |
wav_path = os.path.join(temp_dir, f"chunk_{i:02d}.wav") | |
tts.tts_to_file(text=para, file_path=wav_path, speaker=speaker_id) | |
wav_paths.append(wav_path) | |
# Create a zip file | |
zip_path = os.path.join(temp_dir, "voice_chunks.zip") | |
with zipfile.ZipFile(zip_path, "w") as zipf: | |
for wav in wav_paths: | |
zipf.write(wav, os.path.basename(wav)) | |
# Copy zip to a final temp file for Gradio to return | |
final_zip = tempfile.NamedTemporaryFile(suffix=".zip", delete=False) | |
with open(zip_path, "rb") as src, open(final_zip.name, "wb") as dst: | |
dst.write(src.read()) | |
return final_zip.name | |
# Gradio UI | |
with gr.Blocks() as interface: | |
gr.Markdown("# 🎤 English Voice Generator from DOCX") | |
gr.Markdown("Upload a `.docx` file and select a speaker to generate a WAV voiceover.") | |
doc_input = gr.File(label="Upload .docx File", type="filepath") | |
speaker_dropdown = gr.Dropdown( | |
choices=[desc for desc, _ in get_speaker_dropdown_choices()], | |
label="Select Speaker", | |
value=None | |
) | |
generate_btn = gr.Button("Generate WAV") | |
output_audio = gr.Audio(label="Generated Audio", type="filepath") | |
generate_btn.click( | |
fn=docx_to_zipped_wavs, | |
inputs=[doc_input, speaker_dropdown], | |
outputs=gr.File(label="Download ZIP of Audio Files") | |
) | |
if __name__ == "__main__": | |
interface.launch() | |