Spaces:
Sleeping
Sleeping
| import os | |
| os.environ["NUMBA_DISABLE_CACHE"] = "1" | |
| import os | |
| import gradio as gr | |
| from docx import Document | |
| from TTS.api import TTS | |
| import tempfile | |
| import zipfile | |
| from io import BytesIO | |
| import re | |
| # 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"} | |
| } | |
| # Static list of speakers for dropdown | |
| SPEAKER_CHOICES = [ | |
| f"{sid} - {data['gender']} - {data['accent']} (Age {data['age']})" | |
| for sid, data in SPEAKER_METADATA.items() | |
| ] | |
| # VCTK model (multi-speaker) | |
| MODEL_NAME = "tts_models/en/vctk/vits" | |
| tts = TTS(model_name=MODEL_NAME, progress_bar=False, gpu=False) | |
| # Extract plain text from docx, ignoring hyperlinks | |
| def extract_text_ignoring_hyperlinks(docx_file): | |
| doc = Document(docx_file.name) | |
| text_blocks = [] | |
| for para in doc.paragraphs: | |
| # Remove hyperlinks using regex or by inspecting runs | |
| if para.text.strip(): | |
| clean_text = re.sub(r'https?://\S+', '', para.text) | |
| text_blocks.append(clean_text.strip()) | |
| return text_blocks | |
| # Generate sample audio for preview | |
| def generate_sample_audio(sample_text, selected_speaker): | |
| if not sample_text.strip(): | |
| raise gr.Error("Sample text cannot be empty.") | |
| sid = selected_speaker.split(" ")[0] # Extract speaker ID | |
| with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_wav: | |
| tts.tts_to_file(text=sample_text, speaker="p"+sid, file_path=tmp_wav.name) | |
| return tmp_wav.name | |
| # Main conversion function | |
| def docx_to_zipped_wavs(doc_file, selected_speaker): | |
| sid = selected_speaker.split(" ")[0] | |
| paragraphs = extract_text_ignoring_hyperlinks(doc_file) | |
| audio_files = [] | |
| try: | |
| for i, para in enumerate(paragraphs): | |
| if not para: | |
| continue | |
| with tempfile.NamedTemporaryFile(suffix=f"_{i}.wav", delete=False) as tmp_wav: | |
| tts.tts_to_file(text=para, speaker="p"+sid, file_path=tmp_wav.name) | |
| audio_files.append(tmp_wav.name) | |
| except Exception as e: | |
| print("Connection interrupted, returning partial result.", str(e)) | |
| # Zip the results | |
| zip_buffer = BytesIO() | |
| with zipfile.ZipFile(zip_buffer, "w") as zipf: | |
| for wav_path in audio_files: | |
| zipf.write(wav_path, arcname=os.path.basename(wav_path)) | |
| zip_buffer.seek(0) | |
| # Save the zip temporarily for download | |
| final_zip = tempfile.NamedTemporaryFile(delete=False, suffix=".zip") | |
| final_zip.write(zip_buffer.read()) | |
| final_zip.close() | |
| return final_zip.name | |
| # Gradio UI | |
| with gr.Blocks() as interface: | |
| gr.Markdown("""# Multi-Paragraph Voiceover Generator | |
| Upload a `.docx` file and convert each paragraph to audio. You can also try a short sample first. | |
| """) | |
| with gr.Row(): | |
| #sample_text = gr.Textbox(label="Sample Text (Max 500 chars)", max_lines=4, lines=3, max_length=500) | |
| speaker_dropdown = gr.Dropdown(label="Select Speaker", choices=SPEAKER_CHOICES, value=SPEAKER_CHOICES[0]) | |
| # sample_button = gr.Button("Generate Sample Audio") | |
| # sample_audio = gr.Audio(label="Sample Audio", type="filepath") | |
| with gr.Row(): | |
| docx_input = gr.File(label="Upload .docx File", type="filepath") | |
| convert_button = gr.Button("Generate WAV Zip") | |
| final_output = gr.File(label="Download ZIP of WAVs") | |
| # sample_button.click(fn=generate_sample_audio, inputs=[sample_text, speaker_dropdown], outputs=sample_audio) | |
| convert_button.click(fn=docx_to_zipped_wavs, inputs=[docx_input, speaker_dropdown], outputs=final_output) | |
| if __name__ == "__main__": | |
| interface.launch() | |