Download app/services/voice.py from gitdeem/mp4: direct link, hf CLI and curl.
- Browser
- Download file 24.1 kB
-
https://huggingface.co/spaces/gitdeem/mp4/resolve/main/app/services/voice.py
- Command line
-
hf download hf://spaces/gitdeem/mp4/app/services/voice.py
-
curl -L -o voice.py https://huggingface.co/spaces/gitdeem/mp4/resolve/main/app/services/voice.py
24.1 kB
| import os | |
| import asyncio | |
| import time | |
| import uuid | |
| import json | |
| import edge_tts | |
| import re | |
| import xml.sax.saxutils | |
| from edge_tts import SubMaker, submaker | |
| from edge_tts.submaker import mktimestamp | |
| from moviepy.video.tools import subtitles | |
| from loguru import logger | |
| from typing import Tuple | |
| from xml.sax.saxutils import unescape | |
| PUNCTUATIONS = [ | |
| "?", | |
| ",", | |
| ".", | |
| "、", | |
| ";", | |
| ":", | |
| "!", | |
| "…", | |
| "?", | |
| ",", | |
| "。", | |
| "、", | |
| ";", | |
| ":", | |
| "!", | |
| "...", | |
| ] | |
| def split_string_by_punctuations(s): | |
| result = [] | |
| txt = "" | |
| previous_char = "" | |
| next_char = "" | |
| for i in range(len(s)): | |
| char = s[i] | |
| if char == "\n": | |
| if txt.strip(): # 只有在非空的情况下才添加 | |
| result.append(txt.strip()) | |
| txt = "" | |
| continue | |
| if i > 0: | |
| previous_char = s[i - 1] | |
| if i < len(s) - 1: | |
| next_char = s[i + 1] | |
| if char == "." and previous_char.isdigit() and next_char.isdigit(): | |
| txt += char | |
| continue | |
| if char not in PUNCTUATIONS: | |
| txt += char | |
| else: | |
| if txt.strip(): # 只有在非空的情况下才添加 | |
| result.append(txt.strip()) | |
| txt = "" | |
| if txt.strip(): # 最后一段如果非空也要添加 | |
| result.append(txt.strip()) | |
| # 过滤掉空字符串和只包含标点符号的片段 | |
| def is_valid_segment(segment): | |
| # 去掉所有标点符号后还有内容的才是有效片段 | |
| return bool(re.sub(r'[^\w\s]', '', segment).strip()) | |
| result = list(filter(is_valid_segment, result)) | |
| return result | |
| def get_all_azure_voices(filter_locals=None) -> list[str]: | |
| if filter_locals is None: | |
| filter_locals = ["zh-CN", "en-US", "zh-TW", "ja-JP", "ko-KR"] | |
| voices_str = """ | |
| Name: af-ZA-AdriNeural | |
| Gender: Female | |
| Name: af-ZA-WillemNeural | |
| Gender: Male | |
| Name: am-ET-AmehaNeural | |
| Gender: Male | |
| Name: am-ET-MekdesNeural | |
| Gender: Female | |
| Name: ar-AE-FatimaNeural | |
| Gender: Female | |
| Name: ar-AE-HamdanNeural | |
| Gender: Male | |
| Name: ar-BH-AliNeural | |
| Gender: Male | |
| Name: ar-BH-LailaNeural | |
| Gender: Female | |
| Name: ar-DZ-AminaNeural | |
| Gender: Female | |
| Name: ar-DZ-IsmaelNeural | |
| Gender: Male | |
| Name: ar-EG-SalmaNeural | |
| Gender: Female | |
| Name: ar-EG-ShakirNeural | |
| Gender: Male | |
| Name: ar-IQ-BasselNeural | |
| Gender: Male | |
| Name: ar-IQ-RanaNeural | |
| Gender: Female | |
| Name: ar-JO-SanaNeural | |
| Gender: Female | |
| Name: ar-JO-TaimNeural | |
| Gender: Male | |
| Name: ar-KW-FahedNeural | |
| Gender: Male | |
| Name: ar-KW-NouraNeural | |
| Gender: Female | |
| Name: ar-LB-LaylaNeural | |
| Gender: Female | |
| Name: ar-LB-RamiNeural | |
| Gender: Male | |
| Name: ar-LY-ImanNeural | |
| Gender: Female | |
| Name: ar-LY-OmarNeural | |
| Gender: Male | |
| Name: ar-MA-JamalNeural | |
| Gender: Male | |
| Name: ar-MA-MounaNeural | |
| Gender: Female | |
| Name: ar-OM-AbdullahNeural | |
| Gender: Male | |
| Name: ar-OM-AyshaNeural | |
| Gender: Female | |
| Name: ar-QA-AmalNeural | |
| Gender: Female | |
| Name: ar-QA-MoazNeural | |
| Gender: Male | |
| Name: ar-SA-HamedNeural | |
| Gender: Male | |
| Name: ar-SA-ZariyahNeural | |
| Gender: Female | |
| Name: ar-SY-AmanyNeural | |
| Gender: Female | |
| Name: ar-SY-LaithNeural | |
| Gender: Male | |
| Name: ar-TN-HediNeural | |
| Gender: Male | |
| Name: ar-TN-ReemNeural | |
| Gender: Female | |
| Name: ar-YE-MaryamNeural | |
| Gender: Female | |
| Name: ar-YE-SalehNeural | |
| Gender: Male | |
| Name: az-AZ-BabekNeural | |
| Gender: Male | |
| Name: az-AZ-BanuNeural | |
| Gender: Female | |
| Name: bg-BG-BorislavNeural | |
| Gender: Male | |
| Name: bg-BG-KalinaNeural | |
| Gender: Female | |
| Name: bn-BD-NabanitaNeural | |
| Gender: Female | |
| Name: bn-BD-PradeepNeural | |
| Gender: Male | |
| Name: bn-IN-BashkarNeural | |
| Gender: Male | |
| Name: bn-IN-TanishaaNeural | |
| Gender: Female | |
| Name: bs-BA-GoranNeural | |
| Gender: Male | |
| Name: bs-BA-VesnaNeural | |
| Gender: Female | |
| Name: ca-ES-EnricNeural | |
| Gender: Male | |
| Name: ca-ES-JoanaNeural | |
| Gender: Female | |
| Name: cs-CZ-AntoninNeural | |
| Gender: Male | |
| Name: cs-CZ-VlastaNeural | |
| Gender: Female | |
| Name: cy-GB-AledNeural | |
| Gender: Male | |
| Name: cy-GB-NiaNeural | |
| Gender: Female | |
| Name: da-DK-ChristelNeural | |
| Gender: Female | |
| Name: da-DK-JeppeNeural | |
| Gender: Male | |
| Name: de-AT-IngridNeural | |
| Gender: Female | |
| Name: de-AT-JonasNeural | |
| Gender: Male | |
| Name: de-CH-JanNeural | |
| Gender: Male | |
| Name: de-CH-LeniNeural | |
| Gender: Female | |
| Name: de-DE-AmalaNeural | |
| Gender: Female | |
| Name: de-DE-ConradNeural | |
| Gender: Male | |
| Name: de-DE-FlorianMultilingualNeural | |
| Gender: Male | |
| Name: de-DE-KatjaNeural | |
| Gender: Female | |
| Name: de-DE-KillianNeural | |
| Gender: Male | |
| Name: de-DE-SeraphinaMultilingualNeural | |
| Gender: Female | |
| Name: el-GR-AthinaNeural | |
| Gender: Female | |
| Name: el-GR-NestorasNeural | |
| Gender: Male | |
| Name: en-AU-NatashaNeural | |
| Gender: Female | |
| Name: en-AU-WilliamNeural | |
| Gender: Male | |
| Name: en-CA-ClaraNeural | |
| Gender: Female | |
| Name: en-CA-LiamNeural | |
| Gender: Male | |
| Name: en-GB-LibbyNeural | |
| Gender: Female | |
| Name: en-GB-MaisieNeural | |
| Gender: Female | |
| Name: en-GB-RyanNeural | |
| Gender: Male | |
| Name: en-GB-SoniaNeural | |
| Gender: Female | |
| Name: en-GB-ThomasNeural | |
| Gender: Male | |
| Name: en-HK-SamNeural | |
| Gender: Male | |
| Name: en-HK-YanNeural | |
| Gender: Female | |
| Name: en-IE-ConnorNeural | |
| Gender: Male | |
| Name: en-IE-EmilyNeural | |
| Gender: Female | |
| Name: en-IN-NeerjaExpressiveNeural | |
| Gender: Female | |
| Name: en-IN-NeerjaNeural | |
| Gender: Female | |
| Name: en-IN-PrabhatNeural | |
| Gender: Male | |
| Name: en-KE-AsiliaNeural | |
| Gender: Female | |
| Name: en-KE-ChilembaNeural | |
| Gender: Male | |
| Name: en-NG-AbeoNeural | |
| Gender: Male | |
| Name: en-NG-EzinneNeural | |
| Gender: Female | |
| Name: en-NZ-MitchellNeural | |
| Gender: Male | |
| Name: en-NZ-MollyNeural | |
| Gender: Female | |
| Name: en-PH-JamesNeural | |
| Gender: Male | |
| Name: en-PH-RosaNeural | |
| Gender: Female | |
| Name: en-SG-LunaNeural | |
| Gender: Female | |
| Name: en-SG-WayneNeural | |
| Gender: Male | |
| Name: en-TZ-ElimuNeural | |
| Gender: Male | |
| Name: en-TZ-ImaniNeural | |
| Gender: Female | |
| Name: en-US-AnaNeural | |
| Gender: Female | |
| Name: en-US-AndrewMultilingualNeural | |
| Gender: Male | |
| Name: en-US-AndrewNeural | |
| Gender: Male | |
| Name: en-US-AriaNeural | |
| Gender: Female | |
| Name: en-US-AvaMultilingualNeural | |
| Gender: Female | |
| Name: en-US-AvaNeural | |
| Gender: Female | |
| Name: en-US-BrianMultilingualNeural | |
| Gender: Male | |
| Name: en-US-BrianNeural | |
| Gender: Male | |
| Name: en-US-ChristopherNeural | |
| Gender: Male | |
| Name: en-US-EmmaMultilingualNeural | |
| Gender: Female | |
| Name: en-US-EmmaNeural | |
| Gender: Female | |
| Name: en-US-EricNeural | |
| Gender: Male | |
| Name: en-US-GuyNeural | |
| Gender: Male | |
| Name: en-US-JennyNeural | |
| Gender: Female | |
| Name: en-US-MichelleNeural | |
| Gender: Female | |
| Name: en-US-RogerNeural | |
| Gender: Male | |
| Name: en-US-SteffanNeural | |
| Gender: Male | |
| Name: en-ZA-LeahNeural | |
| Gender: Female | |
| Name: en-ZA-LukeNeural | |
| Gender: Male | |
| Name: es-AR-ElenaNeural | |
| Gender: Female | |
| Name: es-AR-TomasNeural | |
| Gender: Male | |
| Name: es-BO-MarceloNeural | |
| Gender: Male | |
| Name: es-BO-SofiaNeural | |
| Gender: Female | |
| Name: es-CL-CatalinaNeural | |
| Gender: Female | |
| Name: es-CL-LorenzoNeural | |
| Gender: Male | |
| Name: es-CO-GonzaloNeural | |
| Gender: Male | |
| Name: es-CO-SalomeNeural | |
| Gender: Female | |
| Name: es-CR-JuanNeural | |
| Gender: Male | |
| Name: es-CR-MariaNeural | |
| Gender: Female | |
| Name: es-CU-BelkysNeural | |
| Gender: Female | |
| Name: es-CU-ManuelNeural | |
| Gender: Male | |
| Name: es-DO-EmilioNeural | |
| Gender: Male | |
| Name: es-DO-RamonaNeural | |
| Gender: Female | |
| Name: es-EC-AndreaNeural | |
| Gender: Female | |
| Name: es-EC-LuisNeural | |
| Gender: Male | |
| Name: es-ES-AlvaroNeural | |
| Gender: Male | |
| Name: es-ES-ElviraNeural | |
| Gender: Female | |
| Name: es-ES-XimenaNeural | |
| Gender: Female | |
| Name: es-GQ-JavierNeural | |
| Gender: Male | |
| Name: es-GQ-TeresaNeural | |
| Gender: Female | |
| Name: es-GT-AndresNeural | |
| Gender: Male | |
| Name: es-GT-MartaNeural | |
| Gender: Female | |
| Name: es-HN-CarlosNeural | |
| Gender: Male | |
| Name: es-HN-KarlaNeural | |
| Gender: Female | |
| Name: es-MX-DaliaNeural | |
| Gender: Female | |
| Name: es-MX-JorgeNeural | |
| Gender: Male | |
| Name: es-NI-FedericoNeural | |
| Gender: Male | |
| Name: es-NI-YolandaNeural | |
| Gender: Female | |
| Name: es-PA-MargaritaNeural | |
| Gender: Female | |
| Name: es-PA-RobertoNeural | |
| Gender: Male | |
| Name: es-PE-AlexNeural | |
| Gender: Male | |
| Name: es-PE-CamilaNeural | |
| Gender: Female | |
| Name: es-PR-KarinaNeural | |
| Gender: Female | |
| Name: es-PR-VictorNeural | |
| Gender: Male | |
| Name: es-PY-MarioNeural | |
| Gender: Male | |
| Name: es-PY-TaniaNeural | |
| Gender: Female | |
| Name: es-SV-LorenaNeural | |
| Gender: Female | |
| Name: es-SV-RodrigoNeural | |
| Gender: Male | |
| Name: es-US-AlonsoNeural | |
| Gender: Male | |
| Name: es-US-PalomaNeural | |
| Gender: Female | |
| Name: es-UY-MateoNeural | |
| Gender: Male | |
| Name: es-UY-ValentinaNeural | |
| Gender: Female | |
| Name: es-VE-PaolaNeural | |
| Gender: Female | |
| Name: es-VE-SebastianNeural | |
| Gender: Male | |
| Name: et-EE-AnuNeural | |
| Gender: Female | |
| Name: et-EE-KertNeural | |
| Gender: Male | |
| Name: fa-IR-DilaraNeural | |
| Gender: Female | |
| Name: fa-IR-FaridNeural | |
| Gender: Male | |
| Name: fi-FI-HarriNeural | |
| Gender: Male | |
| Name: fi-FI-NooraNeural | |
| Gender: Female | |
| Name: fil-PH-AngeloNeural | |
| Gender: Male | |
| Name: fil-PH-BlessicaNeural | |
| Gender: Female | |
| Name: fr-BE-CharlineNeural | |
| Gender: Female | |
| Name: fr-BE-GerardNeural | |
| Gender: Male | |
| Name: fr-CA-AntoineNeural | |
| Gender: Male | |
| Name: fr-CA-JeanNeural | |
| Gender: Male | |
| Name: fr-CA-SylvieNeural | |
| Gender: Female | |
| Name: fr-CA-ThierryNeural | |
| Gender: Male | |
| Name: fr-CH-ArianeNeural | |
| Gender: Female | |
| Name: fr-CH-FabriceNeural | |
| Gender: Male | |
| Name: fr-FR-DeniseNeural | |
| Gender: Female | |
| Name: fr-FR-EloiseNeural | |
| Gender: Female | |
| Name: fr-FR-HenriNeural | |
| Gender: Male | |
| Name: fr-FR-RemyMultilingualNeural | |
| Gender: Male | |
| Name: fr-FR-VivienneMultilingualNeural | |
| Gender: Female | |
| Name: ga-IE-ColmNeural | |
| Gender: Male | |
| Name: ga-IE-OrlaNeural | |
| Gender: Female | |
| Name: gl-ES-RoiNeural | |
| Gender: Male | |
| Name: gl-ES-SabelaNeural | |
| Gender: Female | |
| Name: gu-IN-DhwaniNeural | |
| Gender: Female | |
| Name: gu-IN-NiranjanNeural | |
| Gender: Male | |
| Name: he-IL-AvriNeural | |
| Gender: Male | |
| Name: he-IL-HilaNeural | |
| Gender: Female | |
| Name: hi-IN-MadhurNeural | |
| Gender: Male | |
| Name: hi-IN-SwaraNeural | |
| Gender: Female | |
| Name: hr-HR-GabrijelaNeural | |
| Gender: Female | |
| Name: hr-HR-SreckoNeural | |
| Gender: Male | |
| Name: hu-HU-NoemiNeural | |
| Gender: Female | |
| Name: hu-HU-TamasNeural | |
| Gender: Male | |
| Name: id-ID-ArdiNeural | |
| Gender: Male | |
| Name: id-ID-GadisNeural | |
| Gender: Female | |
| Name: is-IS-GudrunNeural | |
| Gender: Female | |
| Name: is-IS-GunnarNeural | |
| Gender: Male | |
| Name: it-IT-DiegoNeural | |
| Gender: Male | |
| Name: it-IT-ElsaNeural | |
| Gender: Female | |
| Name: it-IT-GiuseppeMultilingualNeural | |
| Gender: Male | |
| Name: it-IT-IsabellaNeural | |
| Gender: Female | |
| Name: iu-Cans-CA-SiqiniqNeural | |
| Gender: Female | |
| Name: iu-Cans-CA-TaqqiqNeural | |
| Gender: Male | |
| Name: iu-Latn-CA-SiqiniqNeural | |
| Gender: Female | |
| Name: iu-Latn-CA-TaqqiqNeural | |
| Gender: Male | |
| Name: ja-JP-KeitaNeural | |
| Gender: Male | |
| Name: ja-JP-NanamiNeural | |
| Gender: Female | |
| Name: jv-ID-DimasNeural | |
| Gender: Male | |
| Name: jv-ID-SitiNeural | |
| Gender: Female | |
| Name: ka-GE-EkaNeural | |
| Gender: Female | |
| Name: ka-GE-GiorgiNeural | |
| Gender: Male | |
| Name: kk-KZ-AigulNeural | |
| Gender: Female | |
| Name: kk-KZ-DauletNeural | |
| Gender: Male | |
| Name: km-KH-PisethNeural | |
| Gender: Male | |
| Name: km-KH-SreymomNeural | |
| Gender: Female | |
| Name: kn-IN-GaganNeural | |
| Gender: Male | |
| Name: kn-IN-SapnaNeural | |
| Gender: Female | |
| Name: ko-KR-HyunsuMultilingualNeural | |
| Gender: Male | |
| Name: ko-KR-InJoonNeural | |
| Gender: Male | |
| Name: ko-KR-SunHiNeural | |
| Gender: Female | |
| Name: lo-LA-ChanthavongNeural | |
| Gender: Male | |
| Name: lo-LA-KeomanyNeural | |
| Gender: Female | |
| Name: lt-LT-LeonasNeural | |
| Gender: Male | |
| Name: lt-LT-OnaNeural | |
| Gender: Female | |
| Name: lv-LV-EveritaNeural | |
| Gender: Female | |
| Name: lv-LV-NilsNeural | |
| Gender: Male | |
| Name: mk-MK-AleksandarNeural | |
| Gender: Male | |
| Name: mk-MK-MarijaNeural | |
| Gender: Female | |
| Name: ml-IN-MidhunNeural | |
| Gender: Male | |
| Name: ml-IN-SobhanaNeural | |
| Gender: Female | |
| Name: mn-MN-BataaNeural | |
| Gender: Male | |
| Name: mn-MN-YesuiNeural | |
| Gender: Female | |
| Name: mr-IN-AarohiNeural | |
| Gender: Female | |
| Name: mr-IN-ManoharNeural | |
| Gender: Male | |
| Name: ms-MY-OsmanNeural | |
| Gender: Male | |
| Name: ms-MY-YasminNeural | |
| Gender: Female | |
| Name: mt-MT-GraceNeural | |
| Gender: Female | |
| Name: mt-MT-JosephNeural | |
| Gender: Male | |
| Name: my-MM-NilarNeural | |
| Gender: Female | |
| Name: my-MM-ThihaNeural | |
| Gender: Male | |
| Name: nb-NO-FinnNeural | |
| Gender: Male | |
| Name: nb-NO-PernilleNeural | |
| Gender: Female | |
| Name: ne-NP-HemkalaNeural | |
| Gender: Female | |
| Name: ne-NP-SagarNeural | |
| Gender: Male | |
| Name: nl-BE-ArnaudNeural | |
| Gender: Male | |
| Name: nl-BE-DenaNeural | |
| Gender: Female | |
| Name: nl-NL-ColetteNeural | |
| Gender: Female | |
| Name: nl-NL-FennaNeural | |
| Gender: Female | |
| Name: nl-NL-MaartenNeural | |
| Gender: Male | |
| Name: pl-PL-MarekNeural | |
| Gender: Male | |
| Name: pl-PL-ZofiaNeural | |
| Gender: Female | |
| Name: ps-AF-GulNawazNeural | |
| Gender: Male | |
| Name: ps-AF-LatifaNeural | |
| Gender: Female | |
| Name: pt-BR-AntonioNeural | |
| Gender: Male | |
| Name: pt-BR-FranciscaNeural | |
| Gender: Female | |
| Name: pt-BR-ThalitaMultilingualNeural | |
| Gender: Female | |
| Name: pt-PT-DuarteNeural | |
| Gender: Male | |
| Name: pt-PT-RaquelNeural | |
| Gender: Female | |
| Name: ro-RO-AlinaNeural | |
| Gender: Female | |
| Name: ro-RO-EmilNeural | |
| Gender: Male | |
| Name: ru-RU-DmitryNeural | |
| Gender: Male | |
| Name: ru-RU-SvetlanaNeural | |
| Gender: Female | |
| Name: si-LK-SameeraNeural | |
| Gender: Male | |
| Name: si-LK-ThiliniNeural | |
| Gender: Female | |
| Name: sk-SK-LukasNeural | |
| Gender: Male | |
| Name: sk-SK-ViktoriaNeural | |
| Gender: Female | |
| Name: sl-SI-PetraNeural | |
| Gender: Female | |
| Name: sl-SI-RokNeural | |
| Gender: Male | |
| Name: so-SO-MuuseNeural | |
| Gender: Male | |
| Name: so-SO-UbaxNeural | |
| Gender: Female | |
| Name: sq-AL-AnilaNeural | |
| Gender: Female | |
| Name: sq-AL-IlirNeural | |
| Gender: Male | |
| Name: sr-RS-NicholasNeural | |
| Gender: Male | |
| Name: sr-RS-SophieNeural | |
| Gender: Female | |
| Name: su-ID-JajangNeural | |
| Gender: Male | |
| Name: su-ID-TutiNeural | |
| Gender: Female | |
| Name: sv-SE-MattiasNeural | |
| Gender: Male | |
| Name: sv-SE-SofieNeural | |
| Gender: Female | |
| Name: sw-KE-RafikiNeural | |
| Gender: Male | |
| Name: sw-KE-ZuriNeural | |
| Gender: Female | |
| Name: sw-TZ-DaudiNeural | |
| Gender: Male | |
| Name: sw-TZ-RehemaNeural | |
| Gender: Female | |
| Name: ta-IN-PallaviNeural | |
| Gender: Female | |
| Name: ta-IN-ValluvarNeural | |
| Gender: Male | |
| Name: ta-LK-KumarNeural | |
| Gender: Male | |
| Name: ta-LK-SaranyaNeural | |
| Gender: Female | |
| Name: ta-MY-KaniNeural | |
| Gender: Female | |
| Name: ta-MY-SuryaNeural | |
| Gender: Male | |
| Name: ta-SG-AnbuNeural | |
| Gender: Male | |
| Name: ta-SG-VenbaNeural | |
| Gender: Female | |
| Name: te-IN-MohanNeural | |
| Gender: Male | |
| Name: te-IN-ShrutiNeural | |
| Gender: Female | |
| Name: th-TH-NiwatNeural | |
| Gender: Male | |
| Name: th-TH-PremwadeeNeural | |
| Gender: Female | |
| Name: tr-TR-AhmetNeural | |
| Gender: Male | |
| Name: tr-TR-EmelNeural | |
| Gender: Female | |
| Name: uk-UA-OstapNeural | |
| Gender: Male | |
| Name: uk-UA-PolinaNeural | |
| Gender: Female | |
| Name: ur-IN-GulNeural | |
| Gender: Female | |
| Name: ur-IN-SalmanNeural | |
| Gender: Male | |
| Name: ur-PK-AsadNeural | |
| Gender: Male | |
| Name: ur-PK-UzmaNeural | |
| Gender: Female | |
| Name: uz-UZ-MadinaNeural | |
| Gender: Female | |
| Name: uz-UZ-SardorNeural | |
| Gender: Male | |
| Name: vi-VN-HoaiMyNeural | |
| Gender: Female | |
| Name: vi-VN-NamMinhNeural | |
| Gender: Male | |
| Name: zh-CN-XiaoxiaoNeural | |
| Gender: Female | |
| Name: zh-CN-XiaoyiNeural | |
| Gender: Female | |
| Name: zh-CN-YunjianNeural | |
| Gender: Male | |
| Name: zh-CN-YunxiNeural | |
| Gender: Male | |
| Name: zh-CN-YunxiaNeural | |
| Gender: Male | |
| Name: zh-CN-YunyangNeural | |
| Gender: Male | |
| Name: zh-CN-liaoning-XiaobeiNeural | |
| Gender: Female | |
| Name: zh-CN-shaanxi-XiaoniNeural | |
| Gender: Female | |
| Name: zh-HK-HiuGaaiNeural | |
| Gender: Female | |
| Name: zh-HK-HiuMaanNeural | |
| Gender: Female | |
| Name: zh-HK-WanLungNeural | |
| Gender: Male | |
| Name: zh-TW-HsiaoChenNeural | |
| Gender: Female | |
| Name: zh-TW-HsiaoYuNeural | |
| Gender: Female | |
| Name: zh-TW-YunJheNeural | |
| Gender: Male | |
| Name: zu-ZA-ThandoNeural | |
| Gender: Female | |
| Name: zu-ZA-ThembaNeural | |
| Gender: Male | |
| """.strip() | |
| voices = [] | |
| name = "" | |
| for line in voices_str.split("\n"): | |
| line = line.strip() | |
| if not line: | |
| continue | |
| if line.startswith("Name: "): | |
| name = line[6:].strip() | |
| if line.startswith("Gender: "): | |
| gender = line[8:].strip() | |
| if name and gender: | |
| # voices.append({ | |
| # "name": name, | |
| # "gender": gender, | |
| # }) | |
| if filter_locals: | |
| for filter_local in filter_locals: | |
| if name.lower().startswith(filter_local.lower()): | |
| voices.append(f"{name}-{gender}") | |
| else: | |
| voices.append(f"{name}-{gender}") | |
| name = "" | |
| voices.sort() | |
| return voices | |
| def parse_voice_name(name: str): | |
| # zh-CN-XiaoyiNeural-Female | |
| # zh-CN-YunxiNeural-Male | |
| # zh-CN-XiaoxiaoMultilingualNeural-V2-Female | |
| name = name.replace("-Female", "").replace("-Male", "").strip() | |
| return name | |
| def convert_rate_to_percent(rate: float) -> str: | |
| if rate == 1.0: | |
| return "+0%" | |
| percent = round((rate - 1.0) * 100) | |
| if percent > 0: | |
| return f"+{percent}%" | |
| else: | |
| return f"{percent}%" | |
| async def generate_voice(text: str, voice_name: str, voice_rate: float = 0, audio_file: str = None, subtitle_file: str = None) -> Tuple[str, str]: | |
| """生成语音和字幕 | |
| Args: | |
| text (str): 文本内容 | |
| voice_name (str): 语音名称 | |
| voice_rate (float, optional): 语音速率. Defaults to 0. | |
| audio_file (str, optional): 语音文件路径. Defaults to None. | |
| subtitle_file (str, optional): 字幕文件路径. Defaults to None. | |
| Returns: | |
| Tuple[str, str]: 语音文件路径, 字幕文件路径 | |
| """ | |
| if audio_file is None: | |
| audio_file = f"temp_{uuid.uuid4()}.mp3" | |
| if subtitle_file is None: | |
| subtitle_file = f"temp_{uuid.uuid4()}.srt" | |
| # 生成语音 | |
| sub_maker = await edge_tts_voice(text, voice_name, audio_file, voice_rate) | |
| # 生成字幕 | |
| if sub_maker: | |
| await generate_subtitle(sub_maker, text, subtitle_file) | |
| else: | |
| logger.error("Failed to generate sub_maker") | |
| return audio_file, subtitle_file | |
| async def edge_tts_voice(text: str, voice_name: str, voice_file: str, voice_rate: float = 0) -> SubMaker: | |
| """使用 Edge TTS 生成语音""" | |
| rate_str = convert_rate_to_percent(voice_rate) | |
| for i in range(3): | |
| try: | |
| logger.info(f"start, voice name: {voice_name}, try: {i + 1}") | |
| communicate = edge_tts.Communicate(text, voice_name, rate=rate_str) | |
| sub_maker = edge_tts.SubMaker() | |
| with open(voice_file, "wb") as file: | |
| async for chunk in communicate.stream(): | |
| if chunk["type"] == "audio": | |
| file.write(chunk["data"]) | |
| elif chunk["type"] == "WordBoundary": | |
| logger.debug(f"Got word boundary: {chunk}") | |
| # 使用 SubMaker 的 create_sub 方法创建字幕 | |
| sub_maker.create_sub((chunk["offset"], chunk["duration"]), chunk["text"]) | |
| if not sub_maker or not sub_maker.subs: | |
| logger.warning("failed, sub_maker is None or sub_maker.subs is None") | |
| continue | |
| logger.info(f"completed, output file: {voice_file}") | |
| return sub_maker | |
| except Exception as e: | |
| logger.error(f"failed, error: {str(e)}") | |
| continue | |
| return None | |
| async def generate_subtitle(sub_maker: edge_tts.SubMaker, text: str, subtitle_file: str): | |
| """生成字幕文件""" | |
| try: | |
| if not sub_maker or not hasattr(sub_maker, "subs") or not sub_maker.subs: | |
| print("No subtitles to generate: sub_maker is None or sub_maker.subs is empty") | |
| return | |
| print(f"Generating subtitles with {len(sub_maker.subs)} items") | |
| # 直接使用创建字幕的函数 | |
| await create_subtitle(sub_maker=sub_maker, text=text, subtitle_file=subtitle_file) | |
| except Exception as e: | |
| print(f"failed to generate subtitle: {str(e)}") | |
| import traceback | |
| print(traceback.format_exc()) | |
| def get_audio_duration(sub_maker: edge_tts.SubMaker) -> float: | |
| """获取音频时长(秒)""" | |
| if not sub_maker or not hasattr(sub_maker, "subs") or not sub_maker.subs: | |
| return 0 | |
| last_sub = sub_maker.subs[-1] | |
| start, duration = last_sub[0] | |
| return (start + duration) / 10000000 # 转换为秒 | |
| def _format_text(text: str) -> str: | |
| text = text.replace("[", " ") | |
| text = text.replace("]", " ") | |
| text = text.replace("(", " ") | |
| text = text.replace(")", " ") | |
| text = text.replace("{", " ") | |
| text = text.replace("}", " ") | |
| text = text.strip() | |
| return text | |
| async def create_subtitle(sub_maker: edge_tts.SubMaker, text: str, subtitle_file: str): | |
| """ | |
| 优化字幕文件 | |
| 1. 将字幕文件按照标点符号分割成多行 | |
| 2. 逐行匹配字幕文件中的文本 | |
| 3. 生成新的字幕文件 | |
| """ | |
| text = _format_text(text) | |
| def formatter(idx: int, start_time: float, end_time: float, sub_text: str) -> str: | |
| """ | |
| 1 | |
| 00:00:00,000 --> 00:00:02,360 | |
| 跑步是一项简单易行的运动 | |
| """ | |
| start_t = mktimestamp(start_time).replace(".", ",") | |
| end_t = mktimestamp(end_time).replace(".", ",") | |
| return f"{idx}\n{start_t} --> {end_t}\n{sub_text}\n" | |
| start_time = -1.0 | |
| sub_items = [] | |
| sub_index = 0 | |
| script_lines = split_string_by_punctuations(text) | |
| logger.debug(f"Split text into {len(script_lines)} lines: {script_lines}") | |
| def match_line(_sub_line: str, _sub_index: int): | |
| if len(script_lines) <= _sub_index: | |
| return "" | |
| _line = script_lines[_sub_index] | |
| if _sub_line == _line: | |
| return script_lines[_sub_index].strip() | |
| _sub_line_ = re.sub(r"[^\w\s]", "", _sub_line) | |
| _line_ = re.sub(r"[^\w\s]", "", _line) | |
| if _sub_line_ == _line_: | |
| return _line_.strip() | |
| _sub_line_ = re.sub(r"\W+", "", _sub_line) | |
| _line_ = re.sub(r"\W+", "", _line) | |
| if _sub_line_ == _line_: | |
| return _line.strip() | |
| return "" | |
| sub_line = "" | |
| try: | |
| for _, (offset, sub) in enumerate(zip(sub_maker.offset, sub_maker.subs)): | |
| _start_time, end_time = offset | |
| if start_time < 0: | |
| start_time = _start_time | |
| sub = unescape(sub) | |
| sub_line += sub | |
| sub_text = match_line(sub_line, sub_index) | |
| if sub_text: | |
| sub_index += 1 | |
| line = formatter( | |
| idx=sub_index, | |
| start_time=start_time, | |
| end_time=end_time, | |
| sub_text=sub_text, | |
| ) | |
| sub_items.append(line) | |
| start_time = -1.0 | |
| sub_line = "" | |
| if len(sub_items) == len(script_lines): | |
| with open(subtitle_file, "w", encoding="utf-8") as file: | |
| file.write("\n".join(sub_items) + "\n") | |
| try: | |
| sbs = subtitles.file_to_subtitles(subtitle_file, encoding="utf-8") | |
| duration = max([tb for ((ta, tb), txt) in sbs]) | |
| logger.info( | |
| f"completed, subtitle file created: {subtitle_file}, duration: {duration}" | |
| ) | |
| except Exception as e: | |
| logger.error(f"failed, error: {str(e)}") | |
| os.remove(subtitle_file) | |
| else: | |
| logger.error( | |
| f"failed, sub_items len: {len(sub_items)}, script_lines len: {len(script_lines)}" | |
| ) | |
| except Exception as e: | |
| logger.error(f"failed, error: {str(e)}") | |