Spaces:
Running on Zero
Running on Zero
Download app.py from tonyassi/voice-clone: direct link, hf CLI and curl.
- Browser
- Download file 6.37 kB
-
https://huggingface.co/spaces/tonyassi/voice-clone/resolve/main/app.py
- Command line
-
hf download hf://spaces/tonyassi/voice-clone/app.py
-
curl -L -o app.py https://huggingface.co/spaces/tonyassi/voice-clone/resolve/main/app.py
6.37 kB
| import spaces | |
| import gradio as gr | |
| import torch | |
| import os, json, time, uuid, datetime, pathlib | |
| # torch >= 2.6 defaults torch.load(weights_only=True), which breaks TTS's | |
| # pickle-based config loading. Restore the older behavior before TTS imports. | |
| _orig_torch_load = torch.load | |
| def _patched_torch_load(*args, **kwargs): | |
| kwargs.setdefault("weights_only", False) | |
| return _orig_torch_load(*args, **kwargs) | |
| torch.load = _patched_torch_load | |
| os.environ["COQUI_TOS_AGREED"] = "1" | |
| from TTS.api import TTS | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device) | |
| # ββ survey storage: one JSON line per event, batched into a PRIVATE dataset repo ββ | |
| # Events: "generate" (Generate clicked; the survey panel opens at the same moment) | |
| # "submit" (survey answered). Nothing else is stored β no text, no audio. | |
| SURVEY_REPO = os.environ.get("SURVEY_REPO", "tonyassi/voice-clone-survey") | |
| DATA_DIR = pathlib.Path("survey_data"); DATA_DIR.mkdir(exist_ok=True) | |
| DATA_FILE = DATA_DIR / f"events-{uuid.uuid4().hex[:8]}.jsonl" | |
| scheduler = None | |
| if os.environ.get("TOKEN"): | |
| from huggingface_hub import CommitScheduler | |
| scheduler = CommitScheduler(repo_id=SURVEY_REPO, repo_type="dataset", | |
| folder_path=DATA_DIR, path_in_repo="data", | |
| every=5, token=os.environ["TOKEN"], private=True) | |
| def log_event(event, **fields): | |
| row = {"ts": datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds"), | |
| "event": event, **fields} | |
| lock = scheduler.lock if scheduler else __import__("threading").Lock() | |
| with lock: | |
| with DATA_FILE.open("a") as f: | |
| f.write(json.dumps(row) + "\n") | |
| # ββ the app itself, unchanged ββ | |
| def clone(text, audio): | |
| tts.tts_to_file(text=text, speaker_wav=audio, language="en", file_path="./output.wav") | |
| gr.Info("Done. If you have 20 seconds, tell me what you'd want from a better version β the panel on the left.", duration=8) | |
| return "./output.wav" | |
| def open_survey(): | |
| log_event("generate") | |
| return gr.Sidebar(open=True) | |
| def submit_survey(use, worth, other, email): | |
| if not (use or worth or (other or "").strip()): | |
| return gr.update(value="Pick one thing or write a line first."), gr.update() | |
| log_event("submit", use=use, worth=worth, other=(other or "").strip()[:500], | |
| email=(email or "").strip()[:200]) | |
| return gr.update(value="Thank you. That helps more than you'd think."), gr.update(interactive=False) | |
| CSS = """ | |
| .top-line { display:block; text-align:center; padding:10px 14px; background:#0f766e; color:#fff; | |
| text-decoration:none; font-weight:600; cursor:pointer; } | |
| .top-line:hover { filter:brightness(1.05); } | |
| #survey-btn { width:100%; } | |
| /* one choice per line in the survey */ | |
| #q-use .wrap, #q-worth .wrap { flex-direction: column; align-items: stretch; } | |
| #q-use label, #q-worth label { width: 100%; } | |
| """ | |
| with gr.Blocks(title="Voice Clone", css=CSS, | |
| theme=gr.themes.Base(primary_hue="teal", secondary_hue="teal", neutral_hue="slate")) as demo: | |
| top = gr.Button("I'm building a better version of this. 20 seconds β tell me what you'd want β", | |
| elem_classes=["top-line"], variant="primary") | |
| gr.Markdown(""" | |
| # Voice Clone | |
| by [Tony Assi](https://www.tonyassi.com/) | |
| --- | |
| <h3>If you like voice clone then try <a href="https://huggingface.co/spaces/tonyassi/video-face-swap" target="_blank" rel="noopener noreferrer">Video Face Swap</a></h3> | |
| --- | |
| This space uses xtts_v2 model. Non-commercial use only. [Coqui Public Model License](https://huggingface.co/coqui/XTTS-v2/blob/main/LICENSE.txt) | |
| Please β€οΈ this Space. [Email me](mailto:tony.assi.media@gmail.com). | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| text = gr.Textbox(label="Text") | |
| audio = gr.Audio(type="filepath", label="Voice reference audio file") | |
| go = gr.Button("Submit", variant="primary") | |
| with gr.Column(): | |
| out = gr.Audio(type="filepath", label="output") | |
| ask = gr.Button("Tell me what you'd want from a better version β", elem_id="survey-btn") | |
| gr.Examples( | |
| examples=[["Hey! It's me Dorthy, from the Wizard of Oz. Type in whatever you'd like me to say.", "./audio/Wizard-of-Oz-Dorthy.wav"], | |
| ["It's me Vito Corleone, from the Godfather. Type in whatever you'd like me to say.", "./audio/Godfather.wav"], | |
| ["Hey, it's me Paris Hilton. Type in whatever you'd like me to say.", "./audio/Paris-Hilton.mp3"], | |
| ["Hey, it's me Megan Fox from Transformers. Type in whatever you'd like me to say.", "./audio/Megan-Fox.mp3"], | |
| ["Hey there, it's me Jeff Goldblum. Type in whatever you'd like me to say.", "./audio/Jeff-Goldblum.mp3"], | |
| ["Hey there, it's me Heath Ledger as the Joker. Type in whatever you'd like me to say.", "./audio/Heath-Ledger.mp3"]], | |
| inputs=[text, audio], | |
| ) | |
| with gr.Sidebar(open=False) as side: | |
| gr.Markdown("### A better version is coming.") | |
| q_use = gr.Radio(["Fun", "Content for social media", "Narration or audiobook", "Work", "Personal"], | |
| label="What are you making this for?", elem_id="q-use") | |
| q_worth = gr.CheckboxGroup(["Better quality", "Longer text", "Other languages", "Save my voice", "Faster", "Nothing, free is enough"], | |
| label="What would make it worth paying for?", elem_id="q-worth") | |
| q_other = gr.Textbox(label="Anything else?", placeholder="What would you want it to do?", lines=3) | |
| q_email = gr.Textbox(label="Email (optional) β I'll tell you when it's ready") | |
| send = gr.Button("Send", variant="primary") | |
| thanks = gr.Markdown("") | |
| # Generate opens the panel first (instant), then runs the job. | |
| go.click(fn=open_survey, inputs=None, outputs=side, queue=False) | |
| go.click(fn=clone, inputs=[text, audio], outputs=out) | |
| top.click(fn=open_survey, inputs=None, outputs=side, queue=False) | |
| ask.click(fn=open_survey, inputs=None, outputs=side, queue=False) | |
| send.click(fn=submit_survey, inputs=[q_use, q_worth, q_other, q_email], outputs=[thanks, send]) | |
| demo.queue() | |
| if __name__ == "__main__": | |
| demo.launch() | |