1. Added multi-language support to the UI

2. Optimized the voice name
3. Other UI optimizations
This commit is contained in:
harry committed 2024-03-29 17:13:25 +08:00
1 parent a7ba661053
commit bc8e005f59
6 files changed
+1293 -137

No files matched your search

+38 -38
View File
@@ -34,43 +34,43 @@ class MaterialInfo:
duration: int = 0
VoiceNames = [
# zh-CN
"female-zh-CN-XiaoxiaoNeural",
"female-zh-CN-XiaoyiNeural",
"female-zh-CN-liaoning-XiaobeiNeural",
"female-zh-CN-shaanxi-XiaoniNeural",
"male-zh-CN-YunjianNeural",
"male-zh-CN-YunxiNeural",
"male-zh-CN-YunxiaNeural",
"male-zh-CN-YunyangNeural",
# "female-zh-HK-HiuGaaiNeural",
# "female-zh-HK-HiuMaanNeural",
# "male-zh-HK-WanLungNeural",
#
# "female-zh-TW-HsiaoChenNeural",
# "female-zh-TW-HsiaoYuNeural",
# "male-zh-TW-YunJheNeural",
# en-US
"female-en-US-AnaNeural",
"female-en-US-AriaNeural",
"female-en-US-AvaNeural",
"female-en-US-EmmaNeural",
"female-en-US-JennyNeural",
"female-en-US-MichelleNeural",
"male-en-US-AndrewNeural",
"male-en-US-BrianNeural",
"male-en-US-ChristopherNeural",
"male-en-US-EricNeural",
"male-en-US-GuyNeural",
"male-en-US-RogerNeural",
"male-en-US-SteffanNeural",
]
# VoiceNames = [
# # zh-CN
# "female-zh-CN-XiaoxiaoNeural",
# "female-zh-CN-XiaoyiNeural",
# "female-zh-CN-liaoning-XiaobeiNeural",
# "female-zh-CN-shaanxi-XiaoniNeural",
#
# "male-zh-CN-YunjianNeural",
# "male-zh-CN-YunxiNeural",
# "male-zh-CN-YunxiaNeural",
# "male-zh-CN-YunyangNeural",
#
# # "female-zh-HK-HiuGaaiNeural",
# # "female-zh-HK-HiuMaanNeural",
# # "male-zh-HK-WanLungNeural",
# #
# # "female-zh-TW-HsiaoChenNeural",
# # "female-zh-TW-HsiaoYuNeural",
# # "male-zh-TW-YunJheNeural",
#
# # en-US
#
# "female-en-US-AnaNeural",
# "female-en-US-AriaNeural",
# "female-en-US-AvaNeural",
# "female-en-US-EmmaNeural",
# "female-en-US-JennyNeural",
# "female-en-US-MichelleNeural",
#
# "male-en-US-AndrewNeural",
# "male-en-US-BrianNeural",
# "male-en-US-ChristopherNeural",
# "male-en-US-EricNeural",
# "male-en-US-GuyNeural",
# "male-en-US-RogerNeural",
# "male-en-US-SteffanNeural",
# ]
class VideoParams:
@@ -97,7 +97,7 @@ class VideoParams:
video_language: Optional[str] = "" # auto detect
voice_name: Optional[str] = VoiceNames[0]
voice_name: Optional[str] = ""
bgm_type: Optional[str] = "random"
bgm_file: Optional[str] = ""
bgm_volume: Optional[float] = 0.2
+2 -15
View File
@@ -6,24 +6,11 @@ from os import path
from loguru import logger
from app.config import config
from app.models.schema import VideoParams, VoiceNames, VideoConcatMode
from app.models.schema import VideoParams, VideoConcatMode
from app.services import llm, material, voice, video, subtitle
from app.utils import utils
def _parse_voice(name: str):
# "female-zh-CN-XiaoxiaoNeural",
# remove first part split by "-"
if name not in VoiceNames:
name = VoiceNames[0]
parts = name.split("-")
_lang = f"{parts[1]}-{parts[2]}"
_voice = f"{_lang}-{parts[3]}"
return _voice, _lang
def start(task_id, params: VideoParams):
"""
{
@@ -40,7 +27,7 @@ def start(task_id, params: VideoParams):
"""
logger.info(f"start task: {task_id}")
video_subject = params.video_subject
voice_name, language = _parse_voice(params.voice_name)
voice_name = voice.parse_voice_name(params.voice_name)
paragraph_number = params.paragraph_number
n_threads = params.n_threads
max_clip_duration = params.video_clip_duration
+983 -1
View File
@@ -1,5 +1,4 @@
import asyncio
from concurrent.futures import ThreadPoolExecutor
from xml.sax.saxutils import unescape
from edge_tts.submaker import mktimestamp
from loguru import logger
@@ -8,6 +7,985 @@ import edge_tts
from app.utils import utils
def get_all_voices(filter_locals=None) -> list[str]:
if filter_locals is None:
filter_locals = ["zh-CN", "en-US", "zh-HK", "zh-TW"]
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-AndrewNeural
Gender: Male
Name: en-US-AriaNeural
Gender: Female
Name: en-US-AvaNeural
Gender: Female
Name: en-US-BrianNeural
Gender: Male
Name: en-US-ChristopherNeural
Gender: Male
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-GiuseppeNeural
Gender: Male
Name: it-IT-IsabellaNeural
Gender: Female
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-HyunsuNeural
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-ThalitaNeural
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
name = name.replace("-Female", "").replace("-Male", "").strip()
return name
def tts(text: str, voice_name: str, voice_file: str) -> [SubMaker, None]:
logger.info(f"start, voice name: {voice_name}")
try:
@@ -101,6 +1079,10 @@ def get_audio_duration(sub_maker: submaker.SubMaker):
if __name__ == "__main__":
voices = get_all_voices()
print(voices)
print(len(voices))
async def _do():
temp_dir = utils.storage_dir("temp")
+168 -83
View File
@@ -1,15 +1,28 @@
import json
import locale
import streamlit as st
st.set_page_config(page_title="MoneyPrinterTurbo", page_icon="🤖", layout="wide",
initial_sidebar_state="auto")
import sys
import os
from uuid import uuid4
import platform
import streamlit.components.v1 as components
import toml
from loguru import logger
from app.models.schema import VideoParams, VideoAspect, VoiceNames, VideoConcatMode
from app.services import task as tm, llm
st.set_page_config(page_title="MoneyPrinterTurbo",
page_icon="🤖",
layout="wide",
initial_sidebar_state="auto",
menu_items={
'Report a bug': "https://github.com/harry0703/MoneyPrinterTurbo/issues",
'About': "# MoneyPrinterTurbo\nSimply provide a topic or keyword for a video, and it will "
"automatically generate the video copy, video materials, video subtitles, "
"and video background music before synthesizing a high-definition short "
"video.\n\nhttps://github.com/harry0703/MoneyPrinterTurbo"
})
from app.models.schema import VideoParams, VideoAspect, VideoConcatMode
from app.services import task as tm, llm, voice
from app.utils import utils
hide_streamlit_style = """
@@ -21,6 +34,35 @@ st.title("MoneyPrinterTurbo")
root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
font_dir = os.path.join(root_dir, "resource", "fonts")
song_dir = os.path.join(root_dir, "resource", "songs")
i18n_dir = os.path.join(root_dir, "webui", "i18n")
config_file = os.path.join(root_dir, "webui", ".streamlit", "webui.toml")
def load_config() -> dict:
try:
return toml.load(config_file)
except Exception as e:
return {}
cfg = load_config()
def save_config():
with open(config_file, "w", encoding="utf-8") as f:
f.write(toml.dumps(cfg))
def get_system_locale():
try:
loc = locale.getdefaultlocale()
# zh_CN, zh_TW return zh
# en_US, en_GB return en
language_code = loc[0].split("_")[0]
return language_code
except Exception as e:
return "en"
if 'video_subject' not in st.session_state:
st.session_state['video_subject'] = ''
@@ -28,6 +70,8 @@ if 'video_script' not in st.session_state:
st.session_state['video_script'] = ''
if 'video_terms' not in st.session_state:
st.session_state['video_terms'] = ''
if 'ui_language' not in st.session_state:
st.session_state['ui_language'] = cfg.get("ui_language", get_system_locale())
def get_all_fonts():
@@ -109,113 +153,154 @@ def init_log():
init_log()
def load_locales():
locales = {}
for root, dirs, files in os.walk(i18n_dir):
for file in files:
if file.endswith(".json"):
lang = file.split(".")[0]
with open(os.path.join(root, file), "r", encoding="utf-8") as f:
locales[lang] = json.loads(f.read())
return locales
locales = load_locales()
def tr(key):
loc = locales.get(st.session_state['ui_language'], {})
return loc.get("Translation", {}).get(key, key)
display_languages = []
selected_index = 0
for i, code in enumerate(locales.keys()):
display_languages.append(f"{code} - {locales[code].get('Language')}")
if code == st.session_state['ui_language']:
selected_index = i
selected_language = st.selectbox("Language", options=display_languages, label_visibility='collapsed',
index=selected_index)
if selected_language:
code = selected_language.split(" - ")[0].strip()
st.session_state['ui_language'] = code
cfg['ui_language'] = code
save_config()
panel = st.columns(3)
left_panel = panel[0]
middle_panel = panel[1]
right_panel = panel[2]
cfg = VideoParams()
params = VideoParams()
with left_panel:
with st.container(border=True):
st.write("**文案设置**")
cfg.video_subject = st.text_input("视频主题(给定一个关键词,:red[AI自动生成]视频文案)",
value=st.session_state['video_subject']).strip()
st.write(tr("Video Script Settings"))
params.video_subject = st.text_input(tr("Video Subject"),
value=st.session_state['video_subject']).strip()
video_languages = [
("自动判断(Auto detect)", ""),
(tr("Auto Detect"), ""),
]
for lang in ["zh-CN", "zh-TW", "en-US"]:
video_languages.append((lang, lang))
for code in ["zh-CN", "zh-TW", "en-US"]:
video_languages.append((code, code))
selected_index = st.selectbox("生成视频脚本的语言(:blue[一般情况AI会自动根据你输入的主题语言输出])",
selected_index = st.selectbox(tr("Script Language"),
index=0,
options=range(len(video_languages)), # 使用索引作为内部选项值
format_func=lambda x: video_languages[x][0] # 显示给用户的是标签
)
cfg.video_language = video_languages[selected_index][1]
params.video_language = video_languages[selected_index][1]
if cfg.video_language:
st.write(f"设置AI输出文案语言为: **:red[{cfg.video_language}]**")
if st.button("点击使用AI根据**主题**生成 【视频文案】 和 【视频关键词】", key="auto_generate_script"):
with st.spinner("AI正在生成视频文案和关键词..."):
script = llm.generate_script(video_subject=cfg.video_subject, language=cfg.video_language)
terms = llm.generate_terms(cfg.video_subject, script)
st.toast('AI生成成功')
if st.button(tr("Generate Video Script and Keywords"), key="auto_generate_script"):
with st.spinner(tr("Generating Video Script and Keywords")):
script = llm.generate_script(video_subject=params.video_subject, language=params.video_language)
terms = llm.generate_terms(params.video_subject, script)
st.session_state['video_script'] = script
st.session_state['video_terms'] = ", ".join(terms)
cfg.video_script = st.text_area(
"视频文案(:blue[①可不填,使用AI生成 ②合理使用标点断句,有助于生成字幕])",
params.video_script = st.text_area(
tr("Video Script"),
value=st.session_state['video_script'],
height=180
)
if st.button("点击使用AI根据**文案**生成【视频关键词】", key="auto_generate_terms"):
if not cfg.video_script:
st.error("请先填写视频文案")
if st.button(tr("Generate Video Keywords"), key="auto_generate_terms"):
if not params.video_script:
st.error(tr("Please Enter the Video Subject"))
st.stop()
with st.spinner("AI正在生成视频关键词..."):
terms = llm.generate_terms(cfg.video_subject, cfg.video_script)
st.toast('AI生成成功')
with st.spinner(tr("Generating Video Keywords")):
terms = llm.generate_terms(params.video_subject, params.video_script)
st.session_state['video_terms'] = ", ".join(terms)
cfg.video_terms = st.text_area(
"视频关键词(:blue[①可不填,使用AI生成 ②用**英文逗号**分隔,只支持英文])",
params.video_terms = st.text_area(
tr("Video Keywords"),
value=st.session_state['video_terms'],
height=50)
with middle_panel:
with st.container(border=True):
st.write("**视频设置**")
st.write(tr("Video Settings"))
video_concat_modes = [
("顺序拼接", "sequential"),
("随机拼接(推荐)", "random"),
(tr("Sequential"), "sequential"),
(tr("Random"), "random"),
]
selected_index = st.selectbox("视频拼接模式",
selected_index = st.selectbox(tr("Video Concat Mode"),
index=1,
options=range(len(video_concat_modes)), # 使用索引作为内部选项值
format_func=lambda x: video_concat_modes[x][0] # 显示给用户的是标签
)
cfg.video_concat_mode = VideoConcatMode(video_concat_modes[selected_index][1])
params.video_concat_mode = VideoConcatMode(video_concat_modes[selected_index][1])
video_aspect_ratios = [
("竖屏 9:16(抖音视频)", VideoAspect.portrait.value),
("横屏 16:9(西瓜视频)", VideoAspect.landscape.value),
# ("方形 1:1", VideoAspect.square.value)
(tr("Portrait"), VideoAspect.portrait.value),
(tr("Landscape"), VideoAspect.landscape.value),
]
selected_index = st.selectbox("视频比例",
selected_index = st.selectbox(tr("Video Ratio"),
options=range(len(video_aspect_ratios)), # 使用索引作为内部选项值
format_func=lambda x: video_aspect_ratios[x][0] # 显示给用户的是标签
)
cfg.video_aspect = VideoAspect(video_aspect_ratios[selected_index][1])
params.video_aspect = VideoAspect(video_aspect_ratios[selected_index][1])
cfg.video_clip_duration = st.selectbox("视频片段最大时长(秒)", options=[2, 3, 4, 5, 6], index=1)
cfg.video_count = st.selectbox("同时生成视频数量", options=[1, 2, 3, 4, 5], index=0)
params.video_clip_duration = st.selectbox(tr("Clip Duration"), options=[2, 3, 4, 5, 6], index=1)
params.video_count = st.selectbox(tr("Number of Videos Generated Simultaneously"), options=[1, 2, 3, 4, 5],
index=0)
with st.container(border=True):
st.write("**音频设置**")
# 创建一个映射字典,将原始值映射到友好名称
st.write(tr("Audio Settings"))
voices = voice.get_all_voices(filter_locals=["zh-CN", "zh-HK", "zh-TW", "en-US"])
friendly_names = {
voice: voice.
replace("female", "女性").
replace("male", "男性").
replace("zh-CN", "中文").
replace("zh-HK", "香港").
replace("zh-TW", "台湾").
replace("en-US", "英文").
replace("Female", tr("Female")).
replace("Male", tr("Male")).
replace("Neural", "") for
voice in VoiceNames}
selected_friendly_name = st.selectbox("朗读声音", options=list(friendly_names.values()))
voice in voices}
saved_voice_name = cfg.get("voice_name", "")
saved_voice_name_index = 0
if saved_voice_name in friendly_names:
saved_voice_name_index = list(friendly_names.keys()).index(saved_voice_name)
else:
for i, voice in enumerate(voices):
if voice.lower().startswith(st.session_state['ui_language'].lower()):
saved_voice_name_index = i
break
selected_friendly_name = st.selectbox(tr("Speech Synthesis"),
options=list(friendly_names.values()),
index=saved_voice_name_index)
voice_name = list(friendly_names.keys())[list(friendly_names.values()).index(selected_friendly_name)]
cfg.voice_name = voice_name
params.voice_name = voice_name
cfg['voice_name'] = voice_name
save_config()
bgm_options = [
("无背景音乐 No BGM", ""),
("随机背景音乐 Random BGM", "random"),
("自定义背景音乐 Custom BGM", "custom"),
(tr("No Background Music"), ""),
(tr("Random Background Music"), "random"),
(tr("Custom Background Music"), "custom"),
]
selected_index = st.selectbox("背景音乐",
selected_index = st.selectbox(tr("Background Music"),
index=1,
options=range(len(bgm_options)), # 使用索引作为内部选项值
format_func=lambda x: bgm_options[x][0] # 显示给用户的是标签
@@ -225,49 +310,49 @@ with middle_panel:
# 根据选择显示或隐藏组件
if bgm_type == "custom":
custom_bgm_file = st.text_input("请输入自定义背景音乐的文件路径:")
custom_bgm_file = st.text_input(tr("Custom Background Music File"))
if custom_bgm_file and os.path.exists(custom_bgm_file):
cfg.bgm_file = custom_bgm_file
params.bgm_file = custom_bgm_file
# st.write(f":red[已选择自定义背景音乐]:**{custom_bgm_file}**")
cfg.bgm_volume = st.selectbox("背景音乐音量(0.2表示20%,背景声音不宜过高)",
options=[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], index=2)
params.bgm_volume = st.selectbox(tr("Background Music Volume"),
options=[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], index=2)
with right_panel:
with st.container(border=True):
st.write("**字幕设置**")
cfg.subtitle_enabled = st.checkbox("生成字幕(若取消勾选,下面的设置都将不生效)", value=True)
st.write(tr("Subtitle Settings"))
params.subtitle_enabled = st.checkbox(tr("Enable Subtitles"), value=True)
font_names = get_all_fonts()
cfg.font_name = st.selectbox("字体", font_names)
params.font_name = st.selectbox(tr("Font"), font_names)
subtitle_positions = [
("顶部(top)", "top"),
("居中(center)", "center"),
("底部(bottom,推荐)", "bottom"),
(tr("Top"), "top"),
(tr("Middle"), "center"),
(tr("Bottom"), "bottom"),
]
selected_index = st.selectbox("字幕位置",
selected_index = st.selectbox(tr("Position"),
index=2,
options=range(len(subtitle_positions)), # 使用索引作为内部选项值
format_func=lambda x: subtitle_positions[x][0] # 显示给用户的是标签
)
cfg.subtitle_position = subtitle_positions[selected_index][1]
params.subtitle_position = subtitle_positions[selected_index][1]
font_cols = st.columns([0.3, 0.7])
with font_cols[0]:
cfg.text_fore_color = st.color_picker("字幕颜色", "#FFFFFF")
params.text_fore_color = st.color_picker(tr("Font Color"), "#FFFFFF")
with font_cols[1]:
cfg.font_size = st.slider("字幕大小", 30, 100, 60)
params.font_size = st.slider(tr("Font Size"), 30, 100, 60)
stroke_cols = st.columns([0.3, 0.7])
with stroke_cols[0]:
cfg.stroke_color = st.color_picker("描边颜色", "#000000")
params.stroke_color = st.color_picker(tr("Stroke Color"), "#000000")
with stroke_cols[1]:
cfg.stroke_width = st.slider("描边粗细", 0.0, 10.0, 1.5)
params.stroke_width = st.slider(tr("Stroke Width"), 0.0, 10.0, 1.5)
start_button = st.button("开始生成视频", use_container_width=True, type="primary")
start_button = st.button(tr("Generate Video"), use_container_width=True, type="primary")
if start_button:
task_id = str(uuid4())
if not cfg.video_subject and not cfg.video_script:
st.error("视频主题 或 视频文案,不能同时为空")
if not params.video_subject and not params.video_script:
st.error(tr("Video Script and Subject Cannot Both Be Empty"))
scroll_to_bottom()
st.stop()
@@ -283,11 +368,11 @@ if start_button:
logger.add(log_received)
st.toast("正在生成视频,请稍候...")
logger.info("开始生成视频")
logger.info(utils.to_json(cfg))
st.toast(tr("Generating Video"))
logger.info(tr("Start Generating Video"))
logger.info(utils.to_json(params))
scroll_to_bottom()
tm.start(task_id=task_id, params=cfg)
tm.start(task_id=task_id, params=params)
open_task_folder(task_id)
logger.info(f"完成")
logger.info(tr("Video Generation Completed"))
+51
View File
@@ -0,0 +1,51 @@
{
"Language": "English",
"Translation": {
"Video Script Settings": "**Video Script Settings**",
"Video Subject": "Video Subject (Provide a keyword, :red[AI will automatically generate] video script)",
"Script Language": "Language for Generating Video Script (AI will automatically output based on the language of your subject)",
"Generate Video Script and Keywords": "Click to use AI to generate 【Video Script】 and 【Video Keywords】 based on **subject**",
"Auto Detect": "Auto Detect",
"Video Script": "Video Script (:blue[① Optional, AI generated ② Proper punctuation helps with subtitle generation])",
"Generate Video Keywords": "Click to use AI to generate 【Video Keywords】 based on **script**",
"Please Enter the Video Subject": "Please Enter the Video Script First",
"Generating Video Script and Keywords": "AI is generating video script and keywords...",
"Generating Video Keywords": "AI is generating video keywords...",
"Video Keywords": "Video Keywords (:blue[① Optional, AI generated ② Use **English commas** for separation, English only])",
"Video Settings": "**Video Settings**",
"Video Concat Mode": "Video Concatenation Mode",
"Random": "Random Concatenation (Recommended)",
"Sequential": "Sequential Concatenation",
"Video Ratio": "Video Aspect Ratio",
"Portrait": "Portrait 9:16",
"Landscape": "Landscape 16:9",
"Clip Duration": "Maximum Duration of Video Clips (seconds)",
"Number of Videos Generated Simultaneously": "Number of Videos Generated Simultaneously",
"Audio Settings": "**Audio Settings**",
"Speech Synthesis": "Speech Synthesis Voice",
"Male": "Male",
"Female": "Female",
"Background Music": "Background Music",
"No Background Music": "No Background Music",
"Random Background Music": "Random Background Music",
"Custom Background Music": "Custom Background Music",
"Custom Background Music File": "Please enter the file path for custom background music:",
"Background Music Volume": "Background Music Volume (0.2 represents 20%, background music should not be too loud)",
"Subtitle Settings": "**Subtitle Settings**",
"Enable Subtitles": "Enable Subtitles (If unchecked, the settings below will not take effect)",
"Font": "Subtitle Font",
"Position": "Subtitle Position",
"Top": "Top",
"Center": "Middle",
"Bottom": "Bottom (Recommended)",
"Font Size": "Subtitle Font Size",
"Font Color": "Subtitle Font Color",
"Stroke Color": "Subtitle Outline Color",
"Stroke Width": "Subtitle Outline Width",
"Generate Video": "Generate Video",
"Video Script and Subject Cannot Both Be Empty": "Video Subject and Video Script cannot both be empty",
"Generating Video": "Generating video, please wait...",
"Start Generating Video": "Start Generating Video",
"Video Generation Completed": "Video Generation Completed"
}
}
+51
View File
@@ -0,0 +1,51 @@
{
"Language": "简体中文",
"Translation": {
"Video Script Settings": "**文案设置**",
"Video Subject": "视频主题(给定一个关键词,:red[AI自动生成]视频文案)",
"Script Language": "生成视频脚本的语言(一般情况AI会自动根据你输入的主题语言输出)",
"Generate Video Script and Keywords": "点击使用AI根据**主题**生成 【视频文案】 和 【视频关键词】",
"Auto Detect": "自动检测",
"Video Script": "视频文案(:blue[①可不填,使用AI生成 ②合理使用标点断句,有助于生成字幕])",
"Generate Video Keywords": "点击使用AI根据**文案**生成【视频关键词】",
"Please Enter the Video Subject": "请先填写视频文案",
"Generating Video Script and Keywords": "AI正在生成视频文案和关键词...",
"Generating Video Keywords": "AI正在生成视频关键词...",
"Video Keywords": "视频关键词(:blue[①可不填,使用AI生成 ②用**英文逗号**分隔,只支持英文])",
"Video Settings": "**视频设置**",
"Video Concat Mode": "视频拼接模式",
"Random": "随机拼接(推荐)",
"Sequential": "顺序拼接",
"Video Ratio": "视频比例",
"Portrait": "竖屏 9:16(抖音视频)",
"Landscape": "横屏 16:9(西瓜视频)",
"Clip Duration": "视频片段最大时长(秒)",
"Number of Videos Generated Simultaneously": "同时生成视频数量",
"Audio Settings": "**音频设置**",
"Speech Synthesis": "朗读声音",
"Male": "男性",
"Female": "女性",
"Background Music": "背景音乐",
"No Background Music": "无背景音乐",
"Random Background Music": "随机背景音乐",
"Custom Background Music": "自定义背景音乐",
"Custom Background Music File": "请输入自定义背景音乐的文件路径",
"Background Music Volume": "背景音乐音量(0.2表示20%,背景声音不宜过高)",
"Subtitle Settings": "**字幕设置**",
"Enable Subtitles": "启用字幕(若取消勾选,下面的设置都将不生效)",
"Font": "字幕字体",
"Position": "字幕位置",
"Top": "顶部",
"Center": "中间",
"Bottom": "底部(推荐)",
"Font Size": "字幕大小",
"Font Color": "字幕颜色",
"Stroke Color": "描边颜色",
"Stroke Width": "描边粗细",
"Generate Video": "生成视频",
"Video Script and Subject Cannot Both Be Empty": "视频主题 和 视频文案,不能同时为空",
"Generating Video": "正在生成视频,请稍候...",
"Start Generating Video": "开始生成视频",
"Video Generation Completed": "视频生成完成"
}
}