diff --git a/app/config/config.py b/app/config/config.py index afec11b..cd3ba6a 100644 --- a/app/config/config.py +++ b/app/config/config.py @@ -1,6 +1,6 @@ import os import socket -import tomli +import toml from loguru import logger root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) @@ -16,17 +16,17 @@ if not os.path.isfile(config_file): logger.info(f"load config from file: {config_file}") try: - with open(config_file, mode="rb") as fp: - _cfg = tomli.load(fp) + _cfg = toml.load(config_file) except Exception as e: logger.warning(f"load config failed: {str(e)}, try to load as utf-8-sig") with open(config_file, mode="r", encoding='utf-8-sig') as fp: _cfg_content = fp.read() - _cfg = tomli.loads(_cfg_content) + _cfg = toml.loads(_cfg_content) app = _cfg.get("app", {}) whisper = _cfg.get("whisper", {}) pexels = _cfg.get("pexels", {}) +ui = _cfg.get("ui", {}) hostname = socket.gethostname() @@ -47,9 +47,18 @@ ffmpeg_path = app.get("ffmpeg_path", "") if ffmpeg_path and os.path.isfile(ffmpeg_path): os.environ["IMAGEIO_FFMPEG_EXE"] = ffmpeg_path + # __cfg = { # "hostname": hostname, # "listen_host": listen_host, # "listen_port": listen_port, # } # logger.info(__cfg) + + +def save_config(): + with open(config_file, "w", encoding="utf-8") as f: + _cfg["app"] = app + _cfg["whisper"] = whisper + _cfg["pexels"] = pexels + f.write(toml.dumps(_cfg)) diff --git a/app/services/material.py b/app/services/material.py index 84e1d8c..0f16a2e 100644 --- a/app/services/material.py +++ b/app/services/material.py @@ -11,13 +11,14 @@ from app.models.schema import VideoAspect, VideoConcatMode, MaterialInfo from app.utils import utils requested_count = 0 -pexels_api_keys = config.app.get("pexels_api_keys") -if not pexels_api_keys: - raise ValueError( - f"\n\n##### pexels_api_keys is not set #####\n\nPlease set it in the config.toml file: {config.config_file}\n\n{utils.to_json(config.app)}") def round_robin_api_key(): + pexels_api_keys = config.app.get("pexels_api_keys") + if not pexels_api_keys: + raise ValueError( + f"\n\n##### pexels_api_keys is not set #####\n\nPlease set it in the config.toml file: {config.config_file}\n\n{utils.to_json(config.app)}") + # if only one key is provided, return it if isinstance(pexels_api_keys, str): return pexels_api_keys diff --git a/app/utils/utils.py b/app/utils/utils.py index d784282..9d3e675 100644 --- a/app/utils/utils.py +++ b/app/utils/utils.py @@ -1,3 +1,4 @@ +import locale import os import platform import threading @@ -174,3 +175,25 @@ def split_string_by_punctuations(s): def md5(text): import hashlib return hashlib.md5(text.encode('utf-8')).hexdigest() + + +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" + + +def load_locales(i18n_dir): + _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 diff --git a/webui.bat b/webui.bat index 3d51aa1..03e54ed 100644 --- a/webui.bat +++ b/webui.bat @@ -1,4 +1,2 @@ -set CURRENT_DIR=%CD% -set PYTHONPATH=%CURRENT_DIR% rem set HF_ENDPOINT=https://hf-mirror.com -streamlit run .\webui\Main.py \ No newline at end of file +streamlit run .\webui\Main.py --browser.gatherUsageStats=False --server.enableCORS=True \ No newline at end of file diff --git a/webui.sh b/webui.sh index fcde471..89b3089 100644 --- a/webui.sh +++ b/webui.sh @@ -1,7 +1,3 @@ -CURRENT_DIR=$(pwd) -echo "***** Current directory: $CURRENT_DIR *****" -export PYTHONPATH="${CURRENT_DIR}:$PYTHONPATH" - # If you could not download the model from the official site, you can use the mirror site. # Just remove the comment of the following line . # 如果你无法从官方网站下载模型,你可以使用镜像网站。 diff --git a/webui/Main.py b/webui/Main.py index 43afe0a..5bf07ef 100644 --- a/webui/Main.py +++ b/webui/Main.py @@ -9,15 +9,12 @@ if root_dir not in sys.path: print(sys.path) print("") -import json -import locale import streamlit as st import os from uuid import uuid4 import platform import streamlit.components.v1 as components -import toml from loguru import logger st.set_page_config(page_title="MoneyPrinterTurbo", @@ -35,6 +32,7 @@ st.set_page_config(page_title="MoneyPrinterTurbo", from app.models.schema import VideoParams, VideoAspect, VideoConcatMode from app.services import task as tm, llm, voice from app.utils import utils +from app.config import config hide_streamlit_style = """ @@ -46,33 +44,7 @@ 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" - +system_locale = utils.get_system_locale() if 'video_subject' not in st.session_state: st.session_state['video_subject'] = '' @@ -81,7 +53,7 @@ if 'video_script' not in st.session_state: 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()) + st.session_state['ui_language'] = config.ui.get("language", system_locale) def get_all_fonts(): @@ -163,19 +135,7 @@ 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() +locales = utils.load_locales(i18n_dir) def tr(key): @@ -183,20 +143,70 @@ def tr(key): 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 +with st.expander(tr("Basic Settings"), expanded=True): + config_panels = st.columns(3) + left_config_panel = config_panels[0] + middle_config_panel = config_panels[1] + right_config_panel = config_panels[2] + with left_config_panel: + 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() + selected_language = st.selectbox(tr("Language"), options=display_languages, + index=selected_index) + if selected_language: + code = selected_language.split(" - ")[0].strip() + st.session_state['ui_language'] = code + config.ui['language'] = code + config.save_config() + + with middle_config_panel: + # openai + # moonshot (月之暗面) + # oneapi + # g4f + # azure + # qwen (通义千问) + # gemini + # ollama + llm_providers = ['OpenAI', 'Moonshot', 'Azure', 'Qwen', 'Gemini', 'Ollama', 'G4f', 'OneAPI'] + saved_llm_provider = config.app.get("llm_provider", "OpenAI").lower() + saved_llm_provider_index = 0 + for i, provider in enumerate(llm_providers): + if provider.lower() == saved_llm_provider: + saved_llm_provider_index = i + break + + llm_provider = st.selectbox(tr("LLM Provider"), options=llm_providers, index=saved_llm_provider_index) + llm_provider = llm_provider.lower() + config.app["llm_provider"] = llm_provider + st.write(f"**{tr('LLM Provider')}:** {llm_provider}") + + llm_api_key = config.app.get(f"{llm_provider}_api_key", "") + llm_base_url = config.app.get(f"{llm_provider}_base_url", "") + llm_model_name = config.app.get(f"{llm_provider}_model_name", "") + st_llm_api_key = st.text_input(tr("API Key"), value=llm_api_key, type="password") + st_llm_base_url = st.text_input(tr("Base Url"), value=llm_base_url) + st_llm_model_name = st.text_input(tr("Model Name"), value=llm_model_name) + if st_llm_api_key: + config.app[f"{llm_provider}_api_key"] = st_llm_api_key + if st_llm_base_url: + config.app[f"{llm_provider}_base_url"] = st_llm_base_url + if st_llm_model_name: + config.app[f"{llm_provider}_model_name"] = st_llm_model_name + + config.save_config() + + with right_config_panel: + pexels_api_key = config.app.get("pexels_api_keys", "") + pexels_api_key = st.text_input(tr("Pexels API Key"), value=pexels_api_key, type="password") + if pexels_api_key: + config.app["pexels_api_keys"] = pexels_api_key + config.save_config() panel = st.columns(3) left_panel = panel[0] @@ -286,7 +296,7 @@ with middle_panel: replace("Male", tr("Male")). replace("Neural", "") for voice in voices} - saved_voice_name = cfg.get("voice_name", "") + saved_voice_name = config.ui.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) @@ -302,8 +312,8 @@ with middle_panel: voice_name = list(friendly_names.keys())[list(friendly_names.values()).index(selected_friendly_name)] params.voice_name = voice_name - cfg['voice_name'] = voice_name - save_config() + config.ui['voice_name'] = voice_name + config.save_config() params.voice_volume = st.selectbox(tr("Speech Volume"), options=[0.6, 0.8, 1.0, 1.2, 1.5, 2.0, 3.0, 4.0, 5.0], index=2) @@ -334,7 +344,13 @@ with right_panel: st.write(tr("Subtitle Settings")) params.subtitle_enabled = st.checkbox(tr("Enable Subtitles"), value=True) font_names = get_all_fonts() - params.font_name = st.selectbox(tr("Font"), font_names) + saved_font_name = config.ui.get("font_name", "") + saved_font_name_index = 0 + if saved_font_name in font_names: + saved_font_name_index = font_names.index(saved_font_name) + params.font_name = st.selectbox(tr("Font"), font_names, index=saved_font_name_index) + config.ui['font_name'] = params.font_name + config.save_config() subtitle_positions = [ (tr("Top"), "top"), @@ -350,9 +366,14 @@ with right_panel: font_cols = st.columns([0.3, 0.7]) with font_cols[0]: - params.text_fore_color = st.color_picker(tr("Font Color"), "#FFFFFF") + saved_text_fore_color = config.ui.get("text_fore_color", "#FFFFFF") + params.text_fore_color = st.color_picker(tr("Font Color"), saved_text_fore_color) + config.ui['text_fore_color'] = params.text_fore_color + with font_cols[1]: - params.font_size = st.slider(tr("Font Size"), 30, 100, 60) + saved_font_size = config.ui.get("font_size", 60) + params.font_size = st.slider(tr("Font Size"), 30, 100, saved_font_size) + config.ui['font_size'] = params.font_size stroke_cols = st.columns([0.3, 0.7]) with stroke_cols[0]: @@ -362,12 +383,23 @@ with right_panel: start_button = st.button(tr("Generate Video"), use_container_width=True, type="primary") if start_button: + config.save_config() task_id = str(uuid4()) 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() + if not config.app.get(f"{llm_provider}_api_key", ""): + st.error(tr("Please Enter the LLM API Key")) + scroll_to_bottom() + st.stop() + + if not config.app.get("pexels_api_keys", ""): + st.error(tr("Please Enter the Pexels API Key")) + scroll_to_bottom() + st.stop() + log_container = st.empty() log_records = [] diff --git a/webui/i18n/de.json b/webui/i18n/de.json index a2023da..3369e24 100644 --- a/webui/i18n/de.json +++ b/webui/i18n/de.json @@ -48,6 +48,15 @@ "Generating Video": "Video wird erstellt, bitte warten...", "Start Generating Video": "Beginne mit der Generierung", "Video Generation Completed": "Video erfolgreich generiert", - "You can download the generated video from the following links": "Sie können das generierte Video über die folgenden Links herunterladen" + "You can download the generated video from the following links": "Sie können das generierte Video über die folgenden Links herunterladen", + "Basic Settings": "**Grunde Instellungen**", + "Pexels API Key": "Pexels API Key (:red[Required] [Get API Key](https://www.pexels.com/api/))", + "Language": "Language", + "LLM Provider": "LLM Provider", + "API Key": "API Key (:red[Required])", + "Base Url": "Base Url", + "Model Name": "Model Name", + "Please Enter the LLM API Key": "Please Enter the **LLM API Key**", + "Please Enter the Pexels API Key": "Please Enter the **Pexels API Key**" } } \ No newline at end of file diff --git a/webui/i18n/en.json b/webui/i18n/en.json index f4de6d1..79af2ac 100644 --- a/webui/i18n/en.json +++ b/webui/i18n/en.json @@ -48,6 +48,15 @@ "Generating Video": "Generating video, please wait...", "Start Generating Video": "Start Generating Video", "Video Generation Completed": "Video Generation Completed", - "You can download the generated video from the following links": "You can download the generated video from the following links" + "You can download the generated video from the following links": "You can download the generated video from the following links", + "Pexels API Key": "Pexels API Key (:red[Required] [Get API Key](https://www.pexels.com/api/))", + "Basic Settings": "**Basic Settings** (:blue[Click to expand])", + "Language": "Language", + "LLM Provider": "LLM Provider", + "API Key": "API Key (:red[Required])", + "Base Url": "Base Url", + "Model Name": "Model Name", + "Please Enter the LLM API Key": "Please Enter the **LLM API Key**", + "Please Enter the Pexels API Key": "Please Enter the **Pexels API Key**" } } \ No newline at end of file diff --git a/webui/i18n/zh.json b/webui/i18n/zh.json index ed7bc67..b949a27 100644 --- a/webui/i18n/zh.json +++ b/webui/i18n/zh.json @@ -48,6 +48,15 @@ "Generating Video": "正在生成视频,请稍候...", "Start Generating Video": "开始生成视频", "Video Generation Completed": "视频生成完成", - "You can download the generated video from the following links": "你可以从以下链接下载生成的视频" + "You can download the generated video from the following links": "你可以从以下链接下载生成的视频", + "Basic Settings": "**基础设置** (:blue[点击展开])", + "Language": "界面语言", + "Pexels API Key": "Pexels API Key (:red[必填] [点击获取](https://www.pexels.com/api/))", + "LLM Provider": "大模型提供商", + "API Key": "API Key (:red[必填,需要到大模型提供商的后台申请])", + "Base Url": "Base Url (可选)", + "Model Name": "模型名称 (:blue[需要到大模型提供商的后台确认被授权的模型名称])", + "Please Enter the LLM API Key": "请先填写大模型 **API Key**", + "Please Enter the Pexels API Key": "请先填写 **Pexels API Key**" } } \ No newline at end of file