SohomToom's picture
Update app.py
b9d2659 verified
raw
history blame
8.86 kB
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()