from __future__ import annotations import os import time import urllib.request import gradio as gr from pyharp import * from gradio_client import Client, handle_file _BACKEND_SPACE = "k2-fsa/OmniVoice" _BACKEND_API_NAME = "/_clone_fn" _BACKEND_TOKEN_ENV = "HF_TOKEN" _ACCEPT_USER_TOKEN = True # How many times to wake+retry a sleeping backend, and how long to wait for # it to boot (a free Space cold start can take a few minutes). _CALL_RETRIES = int(os.environ.get("BACKEND_CALL_RETRIES", "4")) _WAKE_TIMEOUT = float(os.environ.get("BACKEND_WAKE_TIMEOUT", "420")) _client = None def _backend_client(): # Lazily create and cache one warm connection using this Space's own # token (from the HF_TOKEN secret) or anonymous if none is set. User # tokens are NOT cached here -- they get a fresh per-call connection. global _client if _client is None: _token = os.environ.get(_BACKEND_TOKEN_ENV) or None _client = Client(_BACKEND_SPACE, hf_token=_token) return _client def _reset_client(): # Drop the cached connection so the next attempt reconnects to a Space # that has since finished waking. global _client _client = None def _make_conn(tok): tok = (tok or '').strip() if tok: return Client(_BACKEND_SPACE, hf_token=tok) return _backend_client() def _space_url(space): slug = space.strip().lower().replace('/', '-').replace('_', '-') return f'https://{slug}.hf.space/' def _is_cold_start(message): # Errors that mean 'the backend was asleep/booting', worth waking+retrying # (vs. a real application error, which we surface immediately). _low = (message or '').lower() return any(s in _low for s in ( 'read operation timed out', 'timed out', 'timeout', 'starting', 'building', 'not ready', 'no application', 'connection', '503', '502', )) def _wake_backend(): # A sleeping Space boots when its URL is hit; poll until it answers (or # the budget expires) so the retried call lands on a running backend. _url = _space_url(_BACKEND_SPACE) _deadline = time.time() + _WAKE_TIMEOUT _delay = 5.0 while time.time() < _deadline: try: _req = urllib.request.Request(_url, headers={'User-Agent': 'harp-frontend'}) with urllib.request.urlopen(_req, timeout=30) as _resp: if getattr(_resp, 'status', 200) < 500: return True except Exception: pass time.sleep(_delay) _delay = min(_delay * 1.5, 30.0) return False def _quota_hint(message): # Turn a backend error into an actionable message. # NOTE: 'message' is the backend's error text; it never contains our token. _low = (message or "").lower() if "quota" in _low or "zerogpu" in _low: if _ACCEPT_USER_TOKEN: return ( "The backend's ZeroGPU quota is exhausted for the identity making " "this call. Paste your own Hugging Face token in the token field " "(read scope) so usage is attributed to your account." ) return ( "The backend's ZeroGPU quota is exhausted. This Space's calls are " "anonymous unless an HF_TOKEN secret is set (Settings -> Variables " "and secrets); use a token from a PRO account or a ZeroGPU-enabled org." ) # Opaque backend failure: the Space raised an exception it refuses to # expose (it runs with show_error=False), so all we get is a generic # 'Internal Gradio error'. The frontend can't fix a server-side crash -- # point the user at where the real cause lives. if ( "internal gradio error" in _low or "internal server error" in _low or "apperror" in _low or _low.strip() in ("", "none") ): _hint = ( "The backend Space raised an error it did not expose (it runs with " "show_error disabled), so the real cause is only in the backend " "Space's Logs tab (" + _BACKEND_SPACE + ")." ) if _ACCEPT_USER_TOKEN: _hint += ( " If it is a ZeroGPU Space, an anonymous call can fail this way -- " "paste a Hugging Face token in the token field and retry." ) return _hint return message or "Backend call failed." model_card = ModelCard( name="Omnivoice", description="TODO: describe this model.", author="k2-fsa", tags=[], ) def process_fn(text, lang, ref_aud, ref_text, instruct, ns, gs, dn, sp, du, pp, po, _hf_user_token=''): _tok = (_hf_user_token or '').strip() # Call the backend, waking it and retrying if it was asleep (a cold # start otherwise fails the first hit with 'read operation timed out'). _raw = None for _attempt in range(_CALL_RETRIES + 1): try: _conn = _make_conn(_tok) _raw = _conn.predict( text, lang, (handle_file(ref_aud) if ref_aud else None), ref_text, instruct, ns, gs, dn, sp, du, pp, po, api_name="/_clone_fn", ) break except Exception as _exc: # never surfaces the token if _attempt < _CALL_RETRIES and _is_cold_start(str(_exc)): _reset_client() _wake_backend() continue raise gr.Error(_quota_hint(str(_exc))) _values = list(_raw) if isinstance(_raw, (list, tuple)) else [_raw] _detail = " | ".join(str(_v) for _v in _values if isinstance(_v, str) and _v.strip()) _out_output_audio = _values[0] if len(_values) > 0 else None if not _out_output_audio: raise gr.Error(_detail or "The backend Space returned no 'output_audio' output. Check the backend Space's logs; if it uses ZeroGPU it may need a moment to warm up.") return _out_output_audio with gr.Blocks() as demo: input_components = [ gr.Textbox(label="Text to Synthesize / \u5f85\u5408\u6210\u6587\u672c"), gr.Dropdown(choices=["Auto", "Abadi", "Abkhazian", "Abron", "Abua", "Adamawa Fulfulde", "Adyghe", "Afade", "Afrikaans", "Agwagwune", "Aja (Benin)", "Akebu", "Alago", "Albanian", "Algerian Arabic", "Algerian Saharan Arabic", "Ambo-Pasco Quechua", "Ambonese Malay", "Amdo Tibetan", "Amharic", "Anaang", "Angika", "Antankarana Malagasy", "Aragonese", "Arb\u00ebresh\u00eb Albanian", "Arequipa-La Uni\u00f3n Quechua", "Armenian", "Ashe", "Ash\u00e9ninka Peren\u00e9", "Askopan", "Assamese", "Asturian", "Atayal", "Awak", "Ayacucho Quechua", "Azerbaijani", "Baatonum", "Bacama", "Bade", "Bafia", "Bafut", "Bagirmi Fulfulde", "Bago-Kusuntu", "Baharna Arabic", "Bakoko", "Balanta-Ganja", "Balti", "Bamenyam", "Bamun", "Bangwinji", "Banjar", "Bankon", "Baoul\u00e9", "Bara Malagasy", "Barok", "Basa (Cameroon)", "Basa (Nigeria)", "Bashkir", "Basque", "Batak Mandailing", "Batanga", "Bateri", "Bats", "Bayot", "Bebele", "Belarusian", "Bengali", "Betawi", "Bhili", "Bhojpuri", "Bilur", "Bima", "Bodo", "Boghom", "Bokyi", "Bomu", "Bondei", "Borgu Fulfulde", "Bosnian", "Brahui", "Braj", "Breton", "Buduma", "Buginese", "Bukharic", "Bulgarian", "Bulu (Cameroon)", "Bundeli", "Bunun", "Bura-Pabir", "Burak", "Burmese", "Burushaski", "Cacaloxtepec Mixtec", "Cajatambo North Lima Quechua", "Cakfem-Mushere", "Cameroon Pidgin", "Campidanese Sardinian", "Cantonese", "Catalan", "Cebuano", "Cen", "Central Kurdish", "Central Nahuatl", "Central Pame", "Central Pashto", "Central Puebla Nahuatl", "Central Tarahumara", "Central Yupik", "Central-Eastern Niger Fulfulde", "Chadian Arabic", "Chichewa", "Chichicapan Zapotec", "Chiga", "Chimalapa Zoque", "Chimborazo Highland Quichua", "Chinese", "Chiqui\u00e1n Ancash Quechua", "Chitwania Tharu", "Chokwe", "Chuvash", "Cibak", "Coastal Konjo", "Copainal\u00e1 Zoque", "Cornish", "Corongo Ancash Quechua", "Croatian", "Cross River Mbembe", "Cuyamecalco Mixtec", "Czech", "Dadiya", "Dagbani", "Dameli", "Danish", "Dargwa", "Dazaga", "Deccan", "Degema", "Dera (Nigeria)", "Dghwede", "Dhatki", "Dhivehi", "Dhofari Arabic", "Dijim-Bwilim", "Dogri", "Domaaki", "Dotyali", "Duala", "Dutch", "Du\u0303Ya", "Dyula", "Eastern Balochi", "Eastern Bolivian Guaran\u00ed", "Eastern Egyptian Bedawi Arabic", "Eastern Krahn", "Eastern Mari", "Eastern Yiddish", "Ebri\u00e9", "Eggon", "Egyptian Arabic", "Ejagham", "Eleme", "Eloyi", "Embu", "English", "Erzya", "Esan", "Esperanto", "Estonian", "Eton (Cameroon)", "Ewondo", "Extremaduran", "Fang (Equatorial Guinea)", "Fanti", "Farefare", "Fe'fe'", "Filipino", "Filomena Mata-Coahuitl\u00e1n Totonac", "Finnish", "Fipa", "French", "Fulah", "Galician", "Gambian Wolof", "Ganda", "Garhwali", "Gawar-Bati", "Gawri", "Gbagyi", "Gbari", "Geji", "Gen", "Georgian", "German", "Geser-Gorom", "Gheg Albanian", "Ghom\u00e1l\u00e1'", "Gidar", "Glavda", "Goan Konkani", "Goaria", "Goemai", "Gola", "Greek", "Guarani", "Guduf-Gava", "Guerrero Amuzgo", "Gujarati", "Gujari", "Gulf Arabic", "Gurgula", "Gusii", "Gusilay", "Gweno", "G\u00fcil\u00e1 Zapotec", "Hadothi", "Hahon", "Haitian", "Hakha Chin", "Hak\u00f6", "Halia", "Hausa", "Hawaiian", "Hazaragi", "Hebrew", "Hemba", "Herero", "Highland Konjo", "Hijazi Arabic", "Hindi", "Huarijio", "Huautla Mazatec", "Huaxcaleca Nahuatl", "Huba", "Huitepec Mixtec", "Hula", "Hungarian", "Hunjara-Kaina Ke", "Hwana", "Ibibio", "Icelandic", "Idakho-Isukha-Tiriki", "Idoma", "Igbo", "Igo", "Ikposo", "Ikwere", "Imbabura Highland Quichua", "Indonesian", "Indus Kohistani", "Interlingua (International Auxiliary Language Association)", "Inupiaq", "Irish", "Iron Ossetic", "Isekiri", "Isoko", "Italian", "Ito", "Itz\u00e1", "Ixtayutla Mixtec", "Izon", "Jambi Malay", "Japanese", "Jaqaru", "Jauja Wanca Quechua", "Jaunsari", "Javanese", "Jiba", "Jju", "Judeo-Moroccan Arabic", "Juxtlahuaca Mixtec", "Kabardian", "Kabras", "Kabuverdianu", "Kabyle", "Kachi Koli", "Kairak", "Kalabari", "Kalasha", "Kalenjin", "Kalkoti", "Kamba", "Kamo", "Kanauji", "Kanembu", "Kannada", "Karekare", "Kashmiri", "Kathoriya Tharu", "Kati", "Kazakh", "Keiyo", "Khams Tibetan", "Khana", "Khetrani", "Khmer", "Khowar", "Kinga", "Kinnauri", "Kinyarwanda", "Kirghiz", "Kirya-Konz\u0259l", "Kochila Tharu", "Kohistani Shina", "Kohumono", "Kok Borok", "Kol (Papua New Guinea)", "Kom (Cameroon)", "Koma", "Konkani", "Konzo", "Korean", "Korwa", "Kota (India)", "Koti", "Kuanua", "Kuanyama", "Kui (India)", "Kulung (Nigeria)", "Kuot", "Kushi", "Kwambi", "Kwasio", "Lala-Roba", "Lamang", "Lao", "Larike-Wakasihu", "Lasi", "Latgalian", "Latvian", "Levantine Arabic", "Liana-Seti", "Liberia Kpelle", "Liberian English", "Libyan Arabic", "Ligurian", "Lijili", "Lingala", "Lithuanian", "Loarki", "Logooli", "Logudorese Sardinian", "Loja Highland Quichua", "Loloda", "Longuda", "Loxicha Zapotec", "Luba-Lulua", "Luo", "Lushai", "Luxembourgish", "Maasina Fulfulde", "Maba (Chad)", "Macedo-Romanian", "Macedonian", "Mada (Cameroon)", "Mafa", "Maithili", "Malay", "Malayalam", "Mali", "Malinaltepec Me'phaa", "Maltese", "Mandara", "Mandjak", "Manggarai", "Manipuri", "Mansoanka", "Manx", "Maori", "Marathi", "Marghi Central", "Marghi South", "Maria (India)", "Marwari (Pakistan)", "Masana", "Masikoro Malagasy", "Mats\u00e9s", "Mazaltepec Zapotec", "Mazatl\u00e1n Mazatec", "Mazatl\u00e1n Mixe", "Mbe", "Mbo (Cameroon)", "Mbum", "Medumba", "Mekeo", "Meru", "Mesopotamian Arabic", "Mewari", "Min Nan Chinese", "Mingrelian", "Mitlatongo Mixtec", "Miya", "Mokpwe", "Moksha", "Mom Jango", "Mongolian", "Moroccan Arabic", "Motu", "Mpiemo", "Mpumpong", "Mundang", "Mungaka", "Musey", "Musgu", "Musi", "Naba", "Najdi Arabic", "Nalik", "Nawdm", "Ndonga", "Neapolitan", "Nepali", "Ngamo", "Ngas", "Ngiemboon", "Ngizim", "Ngomba", "Ngombale", "Nigerian Fulfulde", "Nigerian Pidgin", "Nimadi", "Nobiin", "North Mesopotamian Arabic", "North Moluccan Malay", "Northern Betsimisaraka Malagasy", "Northern Hindko", "Northern Kurdish", "Northern Pame", "Northern Pashto", "Northern Uzbek", "Northwest Gbaya", "Norwegian", "Norwegian Bokm\u00e5l", "Norwegian Nynorsk", "Notsi", "Nyankpa", "Nyungwe", "Nzanyi", "N\u00fcpode Huitoto", "Occitan", "Od", "Odia", "Odual", "Omani Arabic", "Orizaba Nahuatl", "Orma", "Ormuri", "Oromo", "Pahari-Potwari", "Paiwan", "Panjabi", "Papuan Malay", "Parkari Koli", "Pedi", "Pero", "Persian", "Petats", "Phalura", "Piemontese", "Piya-Kwonci", "Plateau Malagasy", "Polish", "Poqomam", "Portuguese", "Pulaar", "Pular", "Puno Quechua", "Pushto", "P\u00f6koot", "Qaqet", "Quiotepec Chinantec", "Rana Tharu", "Rangi", "Rapoisi", "Ratahan", "Ray\u00f3n Zoque", "Romanian", "Romansh", "Rombo", "Rotokas", "Rukai", "Russian", "Sacapulteco", "Saidi Arabic", "Sakalava Malagasy", "Sakizaya", "Saleman", "Samba Daka", "Samba Leko", "San Felipe Otlaltepec Popoloca", "San Francisco Del Mar Huave", "San Juan Atzingo Popoloca", "San Mart\u00edn Itunyoso Triqui", "San Miguel El Grande Mixtec", "Sansi", "Sanskrit", "Santa Ana de Tusi Pasco Quechua", "Santa Catarina Albarradas Zapotec", "Santali", "Santiago del Estero Quichua", "Saposa", "Saraiki", "Sardinian", "Saya", "Sediq", "Serbian", "Seri", "Shina", "Shona", "Siar-Lak", "Sibe", "Sicilian", "Sihuas Ancash Quechua", "Sikkimese", "Sinaugoro", "Sindhi", "Sindhi Bhil", "Sinhala", "Sinicahua Mixtec", "Sipacapense", "Siwai", "Slovak", "Slovenian", "Solos", "Somali", "Soninke", "South Giziga", "South Ucayali Ash\u00e9ninka", "Southeastern Nochixtl\u00e1n Mixtec", "Southern Betsimisaraka Malagasy", "Southern Pashto", "Southern Pastaza Quechua", "Soyaltepec Mazatec", "Spanish", "Standard Arabic", "Standard Moroccan Tamazight", "Sudanese Arabic", "Sulka", "Svan", "Swahili", "Swedish", "Tae'", "Tahaggart Tamahaq", "Taita", "Tajik", "Tamil", "Tandroy-Mahafaly Malagasy", "Tangale", "Tanosy Malagasy", "Tarok", "Tatar", "Tedaga", "Telugu", "Tem", "Teop", "Tepeuxila Cuicatec", "Tepinapa Chinantec", "Tera", "Terei", "Termanu", "Tesaka Malagasy", "Tetelcingo Nahuatl", "Teutila Cuicatec", "Thai", "Tibetan", "Tida\u00e1 Mixtec", "Tidore", "Tigak", "Tigre", "Tigrinya", "Tilquiapan Zapotec", "Tinputz", "Tlacoapa Me'phaa", "Tlacoatzintepec Chinantec", "Tlingit", "Toki Pona", "Tomoip", "Tondano", "Tonsea", "Tooro", "Torau", "Torwali", "Tsimihety Malagasy", "Tsotso", "Tswana", "Tugen", "Tuki", "Tula", "Tulu", "Tunen", "Tungag", "Tunisian Arabic", "Tupuri", "Turkana", "Turkish", "Turkmen", "Tututepec Mixtec", "Twi", "Ubaghara", "Uighur", "Ukrainian", "Umbundu", "Upper Sorbian", "Urdu", "Ushojo", "Uzbek", "Vai", "Vietnamese", "Votic", "V\u00f5ro", "Waci Gbe", "Wadiyara Koli", "Waja", "Wakhi", "Wanga", "Wapan", "Warji", "Welsh", "Wemale", "Western Frisian", "Western Highland Purepecha", "Western Juxtlahuaca Mixtec", "Western Maninkakan", "Western Mari", "Western Niger Fulfulde", "Western Panjabi", "Wolof", "Wuzlam", "Xanagu\u00eda Zapotec", "Xhosa", "Yace", "Yakut", "Yalahatan", "Yanahuanca Pasco Quechua", "Yangben", "Yaqui", "Yauyos Quechua", "Yekhee", "Yiddish", "Yidgha", "Yoruba", "Yutanduchi Mixtec", "Zacatl\u00e1n-Ahuacatl\u00e1n-Tepetzintla Nahuatl", "Zarma", "Zaza", "Zulu", "\u00d6mie"], value="Auto", label="Language (optional) / \u8bed\u79cd (\u53ef\u9009)"), gr.Audio(type="filepath", label="Reference Audio / \u53c2\u8003\u97f3\u9891"), gr.Textbox(label="Reference Text (optional) / \u53c2\u8003\u97f3\u9891\u6587\u672c\uff08\u53ef\u9009\uff09"), gr.Textbox(label="Instruct", value="american accent, child").set_info( "Comma + space separated English items (e.g. 'male, whisper') or " "full-width-comma separated Chinese items (e.g. '男,河南话'). Do not mix languages. " "Valid English items: american accent, australian accent, british accent, canadian accent, " "child, chinese accent, elderly, female, high pitch, indian accent, japanese accent, " "korean accent, low pitch, male, middle-aged, moderate pitch, portuguese accent, " "russian accent, teenager, very high pitch, very low pitch, whisper, young adult. " "Valid Chinese items: 东北话,中年,中音调,云南话,低音调,儿童,四川话,女,宁夏话,少年," "极低音调,极高音调,桂林话,河南话,济南话,甘肃话,男,石家庄话,老年,耳语,贵州话," "陕西话,青岛话,青年,高音调" ), gr.Slider(minimum=4, maximum=64, step=1, value=32, label="Inference Steps"), gr.Slider(minimum=0.0, maximum=4.0, step=0.1, value=2.0, label="Guidance Scale (CFG)"), gr.Checkbox(value=True, label="Denoise"), gr.Slider(minimum=0.5, maximum=1.5, step=0.05, value=1.0, label="Speed"), gr.Number(label="Duration (seconds)"), gr.Checkbox(value=True, label="Preprocess Prompt"), gr.Checkbox(value=True, label="Postprocess Output"), gr.Textbox(label="Hugging Face token (optional)", type="password", info="Optional. Paste a Hugging Face token (Settings -> Access Tokens, read scope) so ZeroGPU usage on the backend is charged to YOUR account. Used only for this call; not stored. Leave blank to use this Space's own token."), ] output_components = [ gr.Audio(type="filepath", label="Output Audio / \u5408\u6210\u7ed3\u679c"), ] build_endpoint( model_card=model_card, input_components=input_components, output_components=output_components, process_fn=process_fn, ) demo.queue().launch(share=True, show_error=False, pwa=True)