Merge branch 'main' of https://github.com/KevinZhang19870314/MoneyPrinterTurbo
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,3 +10,5 @@
|
|||||||
/*/__pycache__/*
|
/*/__pycache__/*
|
||||||
.vscode
|
.vscode
|
||||||
/**/.streamlit
|
/**/.streamlit
|
||||||
|
__pycache__
|
||||||
|
logs/
|
||||||
21
Dockerfile
21
Dockerfile
@@ -4,7 +4,7 @@ FROM python:3.10-slim
|
|||||||
# Set the working directory in the container
|
# Set the working directory in the container
|
||||||
WORKDIR /MoneyPrinterTurbo
|
WORKDIR /MoneyPrinterTurbo
|
||||||
|
|
||||||
ENV PYTHONPATH="/MoneyPrinterTurbo:$PYTHONPATH"
|
ENV PYTHONPATH="/MoneyPrinterTurbo"
|
||||||
|
|
||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
@@ -17,11 +17,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
RUN sed -i '/<policy domain="path" rights="none" pattern="@\*"/d' /etc/ImageMagick-6/policy.xml
|
RUN sed -i '/<policy domain="path" rights="none" pattern="@\*"/d' /etc/ImageMagick-6/policy.xml
|
||||||
|
|
||||||
# Copy the current directory contents into the container at /MoneyPrinterTurbo
|
# Copy the current directory contents into the container at /MoneyPrinterTurbo
|
||||||
COPY ./app ./app
|
COPY . .
|
||||||
COPY ./webui ./webui
|
|
||||||
COPY ./resource ./resource
|
|
||||||
COPY ./requirements.txt ./requirements.txt
|
|
||||||
COPY ./main.py ./main.py
|
|
||||||
|
|
||||||
# Install Python dependencies
|
# Install Python dependencies
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
@@ -30,8 +26,13 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
EXPOSE 8501
|
EXPOSE 8501
|
||||||
|
|
||||||
# Command to run the application
|
# Command to run the application
|
||||||
CMD ["streamlit", "run", "./webui/Main.py","--browser.serverAddress=0.0.0.0","--server.enableCORS=True","--browser.gatherUsageStats=False"]
|
CMD ["streamlit", "run", "./webui/Main.py","--browser.serverAddress=127.0.0.1","--server.enableCORS=True","--browser.gatherUsageStats=False"]
|
||||||
|
|
||||||
# At runtime, mount the config.toml file from the host into the container
|
# 1. Build the Docker image using the following command
|
||||||
# using Docker volumes. Example usage:
|
# docker build -t moneyprinterturbo .
|
||||||
# docker run -v ./config.toml:/MoneyPrinterTurbo/config.toml -v ./storage:/MoneyPrinterTurbo/storage -p 8501:8501 moneyprinterturbo
|
|
||||||
|
# 2. Run the Docker container using the following command
|
||||||
|
## For Linux or MacOS:
|
||||||
|
# docker run -v $(pwd)/config.toml:/MoneyPrinterTurbo/config.toml -v $(pwd)/storage:/MoneyPrinterTurbo/storage -p 8501:8501 moneyprinterturbo
|
||||||
|
## For Windows:
|
||||||
|
# docker run -v %cd%/config.toml:/MoneyPrinterTurbo/config.toml -v %cd%/storage:/MoneyPrinterTurbo/storage -p 8501:8501 moneyprinterturbo
|
||||||
43
README.md
43
README.md
@@ -66,6 +66,9 @@
|
|||||||
- [ ] 支持更多的语音合成服务商,比如 OpenAI TTS, Azure TTS
|
- [ ] 支持更多的语音合成服务商,比如 OpenAI TTS, Azure TTS
|
||||||
- [ ] 自动上传到YouTube平台
|
- [ ] 自动上传到YouTube平台
|
||||||
|
|
||||||
|
## 交流讨论 💬
|
||||||
|
<img src="docs/wechat-01.jpg" width="300">
|
||||||
|
|
||||||
## 视频演示 📺
|
## 视频演示 📺
|
||||||
|
|
||||||
### 竖屏 9:16
|
### 竖屏 9:16
|
||||||
@@ -102,8 +105,17 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
## 配置要求 📦
|
||||||
|
- 建议最低 CPU 4核或以上,内存 8G 或以上,显卡非必须
|
||||||
|
- Windows 10 或 MacOS 11.0 以上系统
|
||||||
|
|
||||||
## 安装部署 📥
|
## 安装部署 📥
|
||||||
|
|
||||||
|
> 不想部署的可以直接下载安装包,解压直接使用
|
||||||
|
- **Windows** 版本下载地址
|
||||||
|
- 百度网盘: https://pan.baidu.com/s/1BB3SGtAFTytzFLS5t2d8Gg?pwd=5bry
|
||||||
|
|
||||||
|
### 前提条件
|
||||||
- 尽量不要使用 **中文路径**,避免出现一些无法预料的问题
|
- 尽量不要使用 **中文路径**,避免出现一些无法预料的问题
|
||||||
- 请确保你的 **网络** 是正常的,VPN需要打开`全局流量`模式
|
- 请确保你的 **网络** 是正常的,VPN需要打开`全局流量`模式
|
||||||
|
|
||||||
@@ -230,8 +242,8 @@ python main.py
|
|||||||
|
|
||||||
当前支持2种字幕生成方式:
|
当前支持2种字幕生成方式:
|
||||||
|
|
||||||
- edge: 生成速度更快,性能更好,对电脑配置没有要求,但是质量可能不稳定
|
- **edge**: 生成`速度快`,性能更好,对电脑配置没有要求,但是质量可能不稳定
|
||||||
- whisper: 生成速度较慢,性能较差,对电脑配置有一定要求,但是质量更可靠。
|
- **whisper**: 生成`速度慢`,性能较差,对电脑配置有一定要求,但是`质量更可靠`。
|
||||||
|
|
||||||
可以修改 `config.toml` 配置文件中的 `subtitle_provider` 进行切换
|
可以修改 `config.toml` 配置文件中的 `subtitle_provider` 进行切换
|
||||||
|
|
||||||
@@ -241,6 +253,25 @@ python main.py
|
|||||||
1. whisper 模式下需要到 HuggingFace 下载一个模型文件,大约 3GB 左右,请确保网络通畅
|
1. whisper 模式下需要到 HuggingFace 下载一个模型文件,大约 3GB 左右,请确保网络通畅
|
||||||
2. 如果留空,表示不生成字幕。
|
2. 如果留空,表示不生成字幕。
|
||||||
|
|
||||||
|
> 由于国内无法访问 HuggingFace,可以使用以下方法下载 `whisper-large-v3` 的模型文件
|
||||||
|
|
||||||
|
下载地址:
|
||||||
|
- 百度网盘: https://pan.baidu.com/s/11h3Q6tsDtjQKTjUu3sc5cA?pwd=xjs9
|
||||||
|
- 夸克网盘:https://pan.quark.cn/s/3ee3d991d64b
|
||||||
|
|
||||||
|
模型下载后解压,整个目录放到 `.\MoneyPrinterTurbo\models` 里面,
|
||||||
|
最终的文件路径应该是这样: `.\MoneyPrinterTurbo\models\whisper-large-v3`
|
||||||
|
```
|
||||||
|
MoneyPrinterTurbo
|
||||||
|
├─models
|
||||||
|
│ └─whisper-large-v3
|
||||||
|
│ config.json
|
||||||
|
│ model.bin
|
||||||
|
│ preprocessor_config.json
|
||||||
|
│ tokenizer.json
|
||||||
|
│ vocabulary.json
|
||||||
|
```
|
||||||
|
|
||||||
## 背景音乐 🎵
|
## 背景音乐 🎵
|
||||||
|
|
||||||
用于视频的背景音乐,位于项目的 `resource/songs` 目录下。
|
用于视频的背景音乐,位于项目的 `resource/songs` 目录下。
|
||||||
@@ -375,14 +406,6 @@ pip install Pillow==8.4.0
|
|||||||
|
|
||||||
- 可以提交 [issue](https://github.com/harry0703/MoneyPrinterTurbo/issues)
|
- 可以提交 [issue](https://github.com/harry0703/MoneyPrinterTurbo/issues)
|
||||||
或者 [pull request](https://github.com/harry0703/MoneyPrinterTurbo/pulls)。
|
或者 [pull request](https://github.com/harry0703/MoneyPrinterTurbo/pulls)。
|
||||||
- 也可以关注我的 **抖音** 或 **视频号**:`网旭哈瑞.AI`
|
|
||||||
- 我会在上面发布一些 **使用教程** 和 **纯技术** 分享。
|
|
||||||
- 如果有更新和优化,我也会在上面 **及时通知**。
|
|
||||||
- 有问题也可以在上面 **留言**,我会 **尽快回复**。
|
|
||||||
|
|
||||||
| 抖音 | | 视频号 |
|
|
||||||
|:---------------------------------------:|:------------:|:-------------------------------------------:|
|
|
||||||
| <img src="docs/douyin.jpg" width="180"> | | <img src="docs/shipinghao.jpg" width="200"> |
|
|
||||||
|
|
||||||
## 参考项目 📚
|
## 参考项目 📚
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,45 @@
|
|||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import toml
|
import toml
|
||||||
|
import shutil
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
|
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
|
||||||
config_file = f"{root_dir}/config.toml"
|
config_file = f"{root_dir}/config.toml"
|
||||||
if not os.path.isfile(config_file):
|
|
||||||
example_file = f"{root_dir}/config.example.toml"
|
|
||||||
if os.path.isfile(example_file):
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
shutil.copyfile(example_file, config_file)
|
|
||||||
logger.info(f"copy config.example.toml to config.toml")
|
|
||||||
|
|
||||||
logger.info(f"load config from file: {config_file}")
|
def load_config():
|
||||||
|
# fix: IsADirectoryError: [Errno 21] Is a directory: '/MoneyPrinterTurbo/config.toml'
|
||||||
|
if os.path.isdir(config_file):
|
||||||
|
shutil.rmtree(config_file)
|
||||||
|
|
||||||
try:
|
if not os.path.isfile(config_file):
|
||||||
_cfg = toml.load(config_file)
|
example_file = f"{root_dir}/config.example.toml"
|
||||||
except Exception as e:
|
if os.path.isfile(example_file):
|
||||||
logger.warning(f"load config failed: {str(e)}, try to load as utf-8-sig")
|
shutil.copyfile(example_file, config_file)
|
||||||
with open(config_file, mode="r", encoding='utf-8-sig') as fp:
|
logger.info(f"copy config.example.toml to config.toml")
|
||||||
_cfg_content = fp.read()
|
|
||||||
_cfg = toml.loads(_cfg_content)
|
|
||||||
|
|
||||||
|
logger.info(f"load config from file: {config_file}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
_config_ = 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()
|
||||||
|
_config_ = toml.loads(_cfg_content)
|
||||||
|
return _config_
|
||||||
|
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
_cfg = load_config()
|
||||||
app = _cfg.get("app", {})
|
app = _cfg.get("app", {})
|
||||||
whisper = _cfg.get("whisper", {})
|
whisper = _cfg.get("whisper", {})
|
||||||
pexels = _cfg.get("pexels", {})
|
pexels = _cfg.get("pexels", {})
|
||||||
@@ -36,7 +53,7 @@ listen_port = _cfg.get("listen_port", 8080)
|
|||||||
project_name = _cfg.get("project_name", "MoneyPrinterTurbo")
|
project_name = _cfg.get("project_name", "MoneyPrinterTurbo")
|
||||||
project_description = _cfg.get("project_description",
|
project_description = _cfg.get("project_description",
|
||||||
"<a href='https://github.com/harry0703/MoneyPrinterTurbo'>https://github.com/harry0703/MoneyPrinterTurbo</a>")
|
"<a href='https://github.com/harry0703/MoneyPrinterTurbo'>https://github.com/harry0703/MoneyPrinterTurbo</a>")
|
||||||
project_version = _cfg.get("project_version", "1.0.1")
|
project_version = _cfg.get("project_version", "1.1.0")
|
||||||
reload_debug = False
|
reload_debug = False
|
||||||
|
|
||||||
imagemagick_path = app.get("imagemagick_path", "")
|
imagemagick_path = app.get("imagemagick_path", "")
|
||||||
@@ -46,19 +63,3 @@ if imagemagick_path and os.path.isfile(imagemagick_path):
|
|||||||
ffmpeg_path = app.get("ffmpeg_path", "")
|
ffmpeg_path = app.get("ffmpeg_path", "")
|
||||||
if ffmpeg_path and os.path.isfile(ffmpeg_path):
|
if ffmpeg_path and os.path.isfile(ffmpeg_path):
|
||||||
os.environ["IMAGEIO_FFMPEG_EXE"] = 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))
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from typing import List
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
from openai import AzureOpenAI
|
from openai import AzureOpenAI
|
||||||
|
from openai.types.chat import ChatCompletion
|
||||||
|
|
||||||
from app.config import config
|
from app.config import config
|
||||||
|
|
||||||
|
|
||||||
@@ -57,6 +59,11 @@ def _generate_response(prompt: str) -> str:
|
|||||||
api_key = config.app.get("qwen_api_key")
|
api_key = config.app.get("qwen_api_key")
|
||||||
model_name = config.app.get("qwen_model_name")
|
model_name = config.app.get("qwen_model_name")
|
||||||
base_url = "***"
|
base_url = "***"
|
||||||
|
elif llm_provider == "cloudflare":
|
||||||
|
api_key = config.app.get("cloudflare_api_key")
|
||||||
|
model_name = config.app.get("cloudflare_model_name")
|
||||||
|
account_id = config.app.get("cloudflare_account_id")
|
||||||
|
base_url = "***"
|
||||||
else:
|
else:
|
||||||
raise ValueError("llm_provider is not set, please set it in the config.toml file.")
|
raise ValueError("llm_provider is not set, please set it in the config.toml file.")
|
||||||
|
|
||||||
@@ -69,17 +76,31 @@ def _generate_response(prompt: str) -> str:
|
|||||||
|
|
||||||
if llm_provider == "qwen":
|
if llm_provider == "qwen":
|
||||||
import dashscope
|
import dashscope
|
||||||
|
from dashscope.api_entities.dashscope_response import GenerationResponse
|
||||||
dashscope.api_key = api_key
|
dashscope.api_key = api_key
|
||||||
response = dashscope.Generation.call(
|
response = dashscope.Generation.call(
|
||||||
model=model_name,
|
model=model_name,
|
||||||
messages=[{"role": "user", "content": prompt}]
|
messages=[{"role": "user", "content": prompt}]
|
||||||
)
|
)
|
||||||
content = response["output"]["text"]
|
if response:
|
||||||
return content.replace("\n", "")
|
if isinstance(response, GenerationResponse):
|
||||||
|
status_code = response.status_code
|
||||||
|
if status_code != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"[{llm_provider}] returned an error response: \"{response}\"")
|
||||||
|
|
||||||
|
content = response["output"]["text"]
|
||||||
|
return content.replace("\n", "")
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
f"[{llm_provider}] returned an invalid response: \"{response}\"")
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
f"[{llm_provider}] returned an empty response")
|
||||||
|
|
||||||
if llm_provider == "gemini":
|
if llm_provider == "gemini":
|
||||||
import google.generativeai as genai
|
import google.generativeai as genai
|
||||||
genai.configure(api_key=api_key)
|
genai.configure(api_key=api_key, transport='rest')
|
||||||
|
|
||||||
generation_config = {
|
generation_config = {
|
||||||
"temperature": 0.5,
|
"temperature": 0.5,
|
||||||
@@ -111,10 +132,30 @@ def _generate_response(prompt: str) -> str:
|
|||||||
generation_config=generation_config,
|
generation_config=generation_config,
|
||||||
safety_settings=safety_settings)
|
safety_settings=safety_settings)
|
||||||
|
|
||||||
convo = model.start_chat(history=[])
|
try:
|
||||||
|
response = model.generate_content(prompt)
|
||||||
|
candidates = response.candidates
|
||||||
|
generated_text = candidates[0].content.parts[0].text
|
||||||
|
except (AttributeError, IndexError) as e:
|
||||||
|
print("Gemini Error:", e)
|
||||||
|
|
||||||
convo.send_message(prompt)
|
return generated_text
|
||||||
return convo.last.text
|
|
||||||
|
if llm_provider == "cloudflare":
|
||||||
|
import requests
|
||||||
|
response = requests.post(
|
||||||
|
f"https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run/{model_name}",
|
||||||
|
headers={"Authorization": f"Bearer {api_key}"},
|
||||||
|
json={
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": "You are a friendly assistant"},
|
||||||
|
{"role": "user", "content": prompt}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result = response.json()
|
||||||
|
logger.info(result)
|
||||||
|
return result["result"]["response"]
|
||||||
|
|
||||||
if llm_provider == "azure":
|
if llm_provider == "azure":
|
||||||
client = AzureOpenAI(
|
client = AzureOpenAI(
|
||||||
@@ -133,7 +174,15 @@ def _generate_response(prompt: str) -> str:
|
|||||||
messages=[{"role": "user", "content": prompt}]
|
messages=[{"role": "user", "content": prompt}]
|
||||||
)
|
)
|
||||||
if response:
|
if response:
|
||||||
content = response.choices[0].message.content
|
if isinstance(response, ChatCompletion):
|
||||||
|
content = response.choices[0].message.content
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
f"[{llm_provider}] returned an invalid response: \"{response}\", please check your network "
|
||||||
|
f"connection and try again.")
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
f"[{llm_provider}] returned an empty response, please check your network connection and try again.")
|
||||||
|
|
||||||
return content.replace("\n", "")
|
return content.replace("\n", "")
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from faster_whisper import WhisperModel
|
from faster_whisper import WhisperModel
|
||||||
@@ -17,8 +18,13 @@ model = None
|
|||||||
def create(audio_file, subtitle_file: str = ""):
|
def create(audio_file, subtitle_file: str = ""):
|
||||||
global model
|
global model
|
||||||
if not model:
|
if not model:
|
||||||
logger.info(f"loading model: {model_size}, device: {device}, compute_type: {compute_type}")
|
model_path = f"{utils.root_dir()}/models/whisper-{model_size}"
|
||||||
model = WhisperModel(model_size_or_path=model_size,
|
model_bin_file = f"{model_path}/model.bin"
|
||||||
|
if not os.path.isdir(model_path) or not os.path.isfile(model_bin_file):
|
||||||
|
model_path = model_size
|
||||||
|
|
||||||
|
logger.info(f"loading model: {model_path}, device: {device}, compute_type: {compute_type}")
|
||||||
|
model = WhisperModel(model_size_or_path=model_path,
|
||||||
device=device,
|
device=device,
|
||||||
compute_type=compute_type)
|
compute_type=compute_type)
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ def wrap_text(text, max_width, font='Arial', fontsize=60):
|
|||||||
|
|
||||||
width, height = get_text_size(text)
|
width, height = get_text_size(text)
|
||||||
if width <= max_width:
|
if width <= max_width:
|
||||||
return text
|
return text, height
|
||||||
|
|
||||||
logger.warning(f"wrapping text, max_width: {max_width}, text_width: {width}, text: {text}")
|
logger.warning(f"wrapping text, max_width: {max_width}, text_width: {width}, text: {text}")
|
||||||
|
|
||||||
@@ -149,8 +149,9 @@ def wrap_text(text, max_width, font='Arial', fontsize=60):
|
|||||||
if processed:
|
if processed:
|
||||||
_wrapped_lines_ = [line.strip() for line in _wrapped_lines_]
|
_wrapped_lines_ = [line.strip() for line in _wrapped_lines_]
|
||||||
result = '\n'.join(_wrapped_lines_).strip()
|
result = '\n'.join(_wrapped_lines_).strip()
|
||||||
|
height = len(_wrapped_lines_) * height
|
||||||
logger.warning(f"wrapped text: {result}")
|
logger.warning(f"wrapped text: {result}")
|
||||||
return result
|
return result, height
|
||||||
|
|
||||||
_wrapped_lines_ = []
|
_wrapped_lines_ = []
|
||||||
chars = list(text)
|
chars = list(text)
|
||||||
@@ -165,8 +166,9 @@ def wrap_text(text, max_width, font='Arial', fontsize=60):
|
|||||||
_txt_ = ''
|
_txt_ = ''
|
||||||
_wrapped_lines_.append(_txt_)
|
_wrapped_lines_.append(_txt_)
|
||||||
result = '\n'.join(_wrapped_lines_).strip()
|
result = '\n'.join(_wrapped_lines_).strip()
|
||||||
|
height = len(_wrapped_lines_) * height
|
||||||
logger.warning(f"wrapped text: {result}")
|
logger.warning(f"wrapped text: {result}")
|
||||||
return result
|
return result, height
|
||||||
|
|
||||||
|
|
||||||
def generate_video(video_path: str,
|
def generate_video(video_path: str,
|
||||||
@@ -199,23 +201,15 @@ def generate_video(video_path: str,
|
|||||||
|
|
||||||
logger.info(f"using font: {font_path}")
|
logger.info(f"using font: {font_path}")
|
||||||
|
|
||||||
if params.subtitle_position == "top":
|
def create_text_clip(subtitle_item):
|
||||||
position_height = video_height * 0.1
|
phrase = subtitle_item[1]
|
||||||
elif params.subtitle_position == "bottom":
|
|
||||||
position_height = video_height * 0.9
|
|
||||||
else:
|
|
||||||
position_height = "center"
|
|
||||||
|
|
||||||
def generator(txt, **kwargs):
|
|
||||||
max_width = video_width * 0.9
|
max_width = video_width * 0.9
|
||||||
# logger.debug(f"rendering text: {txt}")
|
wrapped_txt, txt_height = wrap_text(phrase,
|
||||||
wrapped_txt = wrap_text(txt,
|
max_width=max_width,
|
||||||
max_width=max_width,
|
font=font_path,
|
||||||
font=font_path,
|
fontsize=params.font_size
|
||||||
fontsize=params.font_size
|
)
|
||||||
) # 调整max_width以适应你的视频
|
_clip = TextClip(
|
||||||
|
|
||||||
clip = TextClip(
|
|
||||||
wrapped_txt,
|
wrapped_txt,
|
||||||
font=font_path,
|
font=font_path,
|
||||||
fontsize=params.font_size,
|
fontsize=params.font_size,
|
||||||
@@ -225,15 +219,28 @@ def generate_video(video_path: str,
|
|||||||
stroke_width=params.stroke_width,
|
stroke_width=params.stroke_width,
|
||||||
print_cmd=False,
|
print_cmd=False,
|
||||||
)
|
)
|
||||||
return clip
|
duration = subtitle_item[0][1] - subtitle_item[0][0]
|
||||||
|
_clip = _clip.set_start(subtitle_item[0][0])
|
||||||
|
_clip = _clip.set_end(subtitle_item[0][1])
|
||||||
|
_clip = _clip.set_duration(duration)
|
||||||
|
if params.subtitle_position == "bottom":
|
||||||
|
_clip = _clip.set_position(('center', video_height * 0.95 - _clip.h))
|
||||||
|
elif params.subtitle_position == "top":
|
||||||
|
_clip = _clip.set_position(('center', video_height * 0.1))
|
||||||
|
else:
|
||||||
|
_clip = _clip.set_position(('center', 'center'))
|
||||||
|
return _clip
|
||||||
|
|
||||||
video_clip = VideoFileClip(video_path)
|
video_clip = VideoFileClip(video_path)
|
||||||
audio_clip = AudioFileClip(audio_path).volumex(params.voice_volume)
|
audio_clip = AudioFileClip(audio_path).volumex(params.voice_volume)
|
||||||
|
|
||||||
if subtitle_path and os.path.exists(subtitle_path):
|
if subtitle_path and os.path.exists(subtitle_path):
|
||||||
sub = SubtitlesClip(subtitles=subtitle_path, make_textclip=generator, encoding='utf-8')
|
sub = SubtitlesClip(subtitles=subtitle_path, encoding='utf-8')
|
||||||
sub_clip = sub.set_position(lambda _t: ('center', position_height))
|
text_clips = []
|
||||||
video_clip = CompositeVideoClip([video_clip, sub_clip])
|
for item in sub.subtitles:
|
||||||
|
clip = create_text_clip(subtitle_item=item)
|
||||||
|
text_clips.append(clip)
|
||||||
|
video_clip = CompositeVideoClip([video_clip, *text_clips])
|
||||||
|
|
||||||
bgm_file = get_bgm_file(bgm_type=params.bgm_type, bgm_file=params.bgm_file)
|
bgm_file = get_bgm_file(bgm_type=params.bgm_type, bgm_file=params.bgm_file)
|
||||||
if bgm_file:
|
if bgm_file:
|
||||||
@@ -258,7 +265,7 @@ if __name__ == "__main__":
|
|||||||
txt_zh = "测试长字段这是您的旅行技巧指南帮助您进行预算友好的冒险"
|
txt_zh = "测试长字段这是您的旅行技巧指南帮助您进行预算友好的冒险"
|
||||||
font = utils.resource_dir() + "/fonts/STHeitiMedium.ttc"
|
font = utils.resource_dir() + "/fonts/STHeitiMedium.ttc"
|
||||||
for txt in [txt_en, txt_zh]:
|
for txt in [txt_en, txt_zh]:
|
||||||
t = wrap_text(text=txt, max_width=1000, font=font, fontsize=60)
|
t, h = wrap_text(text=txt, max_width=1000, font=font, fontsize=60)
|
||||||
print(t)
|
print(t)
|
||||||
|
|
||||||
task_id = "aa563149-a7ea-49c2-b39f-8c32cc225baf"
|
task_id = "aa563149-a7ea-49c2-b39f-8c32cc225baf"
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
version: "3"
|
|
||||||
|
|
||||||
x-common-volumes: &common-volumes
|
x-common-volumes: &common-volumes
|
||||||
- ./config.toml:/MoneyPrinterTurbo/config.toml
|
- ./:/MoneyPrinterTurbo
|
||||||
- ./storage:/MoneyPrinterTurbo/storage
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
webui:
|
webui:
|
||||||
@@ -12,7 +9,7 @@ services:
|
|||||||
container_name: "webui"
|
container_name: "webui"
|
||||||
ports:
|
ports:
|
||||||
- "8501:8501"
|
- "8501:8501"
|
||||||
command: ["streamlit", "run", "./webui/Main.py","--browser.serverAddress=0.0.0.0","--server.enableCORS=True","--browser.gatherUsageStats=False"]
|
command: [ "streamlit", "run", "./webui/Main.py","--browser.serverAddress=127.0.0.1","--server.enableCORS=True","--browser.gatherUsageStats=False" ]
|
||||||
volumes: *common-volumes
|
volumes: *common-volumes
|
||||||
restart: always
|
restart: always
|
||||||
api:
|
api:
|
||||||
|
|||||||
BIN
docs/wechat-01.jpg
Normal file
BIN
docs/wechat-01.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 160 KiB |
@@ -1,2 +1,7 @@
|
|||||||
|
@echo off
|
||||||
|
set CURRENT_DIR=%CD%
|
||||||
|
echo ***** Current directory: %CURRENT_DIR% *****
|
||||||
|
set PYTHONPATH=%CURRENT_DIR%
|
||||||
|
|
||||||
rem set HF_ENDPOINT=https://hf-mirror.com
|
rem set HF_ENDPOINT=https://hf-mirror.com
|
||||||
streamlit run .\webui\Main.py --browser.gatherUsageStats=False --server.enableCORS=True
|
streamlit run .\webui\Main.py --browser.gatherUsageStats=False --server.enableCORS=True
|
||||||
@@ -38,7 +38,7 @@ hide_streamlit_style = """
|
|||||||
<style>#root > div:nth-child(1) > div > div > div > div > section > div {padding-top: 0rem;}</style>
|
<style>#root > div:nth-child(1) > div > div > div > div > section > div {padding-top: 0rem;}</style>
|
||||||
"""
|
"""
|
||||||
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|
||||||
st.title("MoneyPrinterTurbo")
|
st.title(f"MoneyPrinterTurbo v{config.project_version}")
|
||||||
|
|
||||||
font_dir = os.path.join(root_dir, "resource", "fonts")
|
font_dir = os.path.join(root_dir, "resource", "fonts")
|
||||||
song_dir = os.path.join(root_dir, "resource", "songs")
|
song_dir = os.path.join(root_dir, "resource", "songs")
|
||||||
@@ -175,7 +175,7 @@ with st.expander(tr("Basic Settings"), expanded=False):
|
|||||||
# qwen (通义千问)
|
# qwen (通义千问)
|
||||||
# gemini
|
# gemini
|
||||||
# ollama
|
# ollama
|
||||||
llm_providers = ['OpenAI', 'Moonshot', 'Azure', 'Qwen', 'Gemini', 'Ollama', 'G4f', 'OneAPI']
|
llm_providers = ['OpenAI', 'Moonshot', 'Azure', 'Qwen', 'Gemini', 'Ollama', 'G4f', 'OneAPI', "Cloudflare"]
|
||||||
saved_llm_provider = config.app.get("llm_provider", "OpenAI").lower()
|
saved_llm_provider = config.app.get("llm_provider", "OpenAI").lower()
|
||||||
saved_llm_provider_index = 0
|
saved_llm_provider_index = 0
|
||||||
for i, provider in enumerate(llm_providers):
|
for i, provider in enumerate(llm_providers):
|
||||||
@@ -190,6 +190,7 @@ with st.expander(tr("Basic Settings"), expanded=False):
|
|||||||
llm_api_key = config.app.get(f"{llm_provider}_api_key", "")
|
llm_api_key = config.app.get(f"{llm_provider}_api_key", "")
|
||||||
llm_base_url = config.app.get(f"{llm_provider}_base_url", "")
|
llm_base_url = config.app.get(f"{llm_provider}_base_url", "")
|
||||||
llm_model_name = config.app.get(f"{llm_provider}_model_name", "")
|
llm_model_name = config.app.get(f"{llm_provider}_model_name", "")
|
||||||
|
llm_account_id = config.app.get(f"{llm_provider}_account_id", "")
|
||||||
st_llm_api_key = st.text_input(tr("API Key"), value=llm_api_key, type="password")
|
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_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)
|
st_llm_model_name = st.text_input(tr("Model Name"), value=llm_model_name)
|
||||||
@@ -200,6 +201,11 @@ with st.expander(tr("Basic Settings"), expanded=False):
|
|||||||
if st_llm_model_name:
|
if st_llm_model_name:
|
||||||
config.app[f"{llm_provider}_model_name"] = st_llm_model_name
|
config.app[f"{llm_provider}_model_name"] = st_llm_model_name
|
||||||
|
|
||||||
|
if llm_provider == 'cloudflare':
|
||||||
|
st_llm_account_id = st.text_input(tr("Account ID"), value=llm_account_id)
|
||||||
|
if st_llm_account_id:
|
||||||
|
config.app[f"{llm_provider}_account_id"] = st_llm_account_id
|
||||||
|
|
||||||
config.save_config()
|
config.save_config()
|
||||||
|
|
||||||
with right_config_panel:
|
with right_config_panel:
|
||||||
|
|||||||
@@ -58,6 +58,6 @@
|
|||||||
"Model Name": "Model Name",
|
"Model Name": "Model Name",
|
||||||
"Please Enter the LLM API Key": "Please Enter the **LLM API Key**",
|
"Please Enter the LLM API Key": "Please Enter the **LLM API Key**",
|
||||||
"Please Enter the Pexels API Key": "Please Enter the **Pexels API Key**",
|
"Please Enter the Pexels API Key": "Please Enter the **Pexels API Key**",
|
||||||
"Get Help": "If you need help, or have any questions, you can join discord for help: https://harryai.cc/moneyprinterturbo"
|
"Get Help": "If you need help, or have any questions, you can join discord for help: https://harryai.cc"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,9 +55,10 @@
|
|||||||
"LLM Provider": "LLM Provider",
|
"LLM Provider": "LLM Provider",
|
||||||
"API Key": "API Key (:red[Required])",
|
"API Key": "API Key (:red[Required])",
|
||||||
"Base Url": "Base Url",
|
"Base Url": "Base Url",
|
||||||
|
"Account ID": "Account ID (Get from Cloudflare dashboard)",
|
||||||
"Model Name": "Model Name",
|
"Model Name": "Model Name",
|
||||||
"Please Enter the LLM API Key": "Please Enter the **LLM API Key**",
|
"Please Enter the LLM API Key": "Please Enter the **LLM API Key**",
|
||||||
"Please Enter the Pexels API Key": "Please Enter the **Pexels API Key**",
|
"Please Enter the Pexels API Key": "Please Enter the **Pexels API Key**",
|
||||||
"Get Help": "If you need help, or have any questions, you can join discord for help: https://harryai.cc/moneyprinterturbo"
|
"Get Help": "If you need help, or have any questions, you can join discord for help: https://harryai.cc"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,9 +55,10 @@
|
|||||||
"LLM Provider": "大模型提供商",
|
"LLM Provider": "大模型提供商",
|
||||||
"API Key": "API Key (:red[必填,需要到大模型提供商的后台申请])",
|
"API Key": "API Key (:red[必填,需要到大模型提供商的后台申请])",
|
||||||
"Base Url": "Base Url (可选)",
|
"Base Url": "Base Url (可选)",
|
||||||
|
"Account ID": "账户ID (Cloudflare的dash面板url中获取)",
|
||||||
"Model Name": "模型名称 (:blue[需要到大模型提供商的后台确认被授权的模型名称])",
|
"Model Name": "模型名称 (:blue[需要到大模型提供商的后台确认被授权的模型名称])",
|
||||||
"Please Enter the LLM API Key": "请先填写大模型 **API Key**",
|
"Please Enter the LLM API Key": "请先填写大模型 **API Key**",
|
||||||
"Please Enter the Pexels API Key": "请先填写 **Pexels API Key**",
|
"Please Enter the Pexels API Key": "请先填写 **Pexels API Key**",
|
||||||
"Get Help": "有任何问题或建议,可以加入 **微信群** 求助或讨论:https://harryai.cc/moneyprinterturbo"
|
"Get Help": "有任何问题或建议,可以加入 **微信群** 求助或讨论:https://harryai.cc"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user