Skip to content

Commit 0550e43

Browse files
author
kevin.zhang
committed
2 parents 1fb3399 + 9ab13a7 commit 0550e43

14 files changed

+198
-99
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@
99
/app/utils/__pycache__/
1010
/*/__pycache__/*
1111
.vscode
12-
/**/.streamlit
12+
/**/.streamlit
13+
__pycache__
14+
logs/

Dockerfile

+11-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ FROM python:3.10-slim
44
# Set the working directory in the container
55
WORKDIR /MoneyPrinterTurbo
66

7-
ENV PYTHONPATH="/MoneyPrinterTurbo:$PYTHONPATH"
7+
ENV PYTHONPATH="/MoneyPrinterTurbo"
88

99
# Install system dependencies
1010
RUN apt-get update && apt-get install -y \
@@ -17,11 +17,7 @@ RUN apt-get update && apt-get install -y \
1717
RUN sed -i '/<policy domain="path" rights="none" pattern="@\*"/d' /etc/ImageMagick-6/policy.xml
1818

1919
# Copy the current directory contents into the container at /MoneyPrinterTurbo
20-
COPY ./app ./app
21-
COPY ./webui ./webui
22-
COPY ./resource ./resource
23-
COPY ./requirements.txt ./requirements.txt
24-
COPY ./main.py ./main.py
20+
COPY . .
2521

2622
# Install Python dependencies
2723
RUN pip install --no-cache-dir -r requirements.txt
@@ -30,8 +26,13 @@ RUN pip install --no-cache-dir -r requirements.txt
3026
EXPOSE 8501
3127

3228
# Command to run the application
33-
CMD ["streamlit", "run", "./webui/Main.py","--browser.serverAddress=0.0.0.0","--server.enableCORS=True","--browser.gatherUsageStats=False"]
29+
CMD ["streamlit", "run", "./webui/Main.py","--browser.serverAddress=127.0.0.1","--server.enableCORS=True","--browser.gatherUsageStats=False"]
3430

35-
# At runtime, mount the config.toml file from the host into the container
36-
# using Docker volumes. Example usage:
37-
# docker run -v ./config.toml:/MoneyPrinterTurbo/config.toml -v ./storage:/MoneyPrinterTurbo/storage -p 8501:8501 moneyprinterturbo
31+
# 1. Build the Docker image using the following command
32+
# docker build -t moneyprinterturbo .
33+
34+
# 2. Run the Docker container using the following command
35+
## For Linux or MacOS:
36+
# docker run -v $(pwd)/config.toml:/MoneyPrinterTurbo/config.toml -v $(pwd)/storage:/MoneyPrinterTurbo/storage -p 8501:8501 moneyprinterturbo
37+
## For Windows:
38+
# docker run -v %cd%/config.toml:/MoneyPrinterTurbo/config.toml -v %cd%/storage:/MoneyPrinterTurbo/storage -p 8501:8501 moneyprinterturbo

README.md

+33-10
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@
6666
- [ ] 支持更多的语音合成服务商,比如 OpenAI TTS, Azure TTS
6767
- [ ] 自动上传到YouTube平台
6868

69+
## 交流讨论 💬
70+
<img src="docs/wechat-01.jpg" width="300">
71+
6972
## 视频演示 📺
7073

7174
### 竖屏 9:16
@@ -102,8 +105,17 @@
102105
</tbody>
103106
</table>
104107

108+
## 配置要求 📦
109+
- 建议最低 CPU 4核或以上,内存 8G 或以上,显卡非必须
110+
- Windows 10 或 MacOS 11.0 以上系统
111+
105112
## 安装部署 📥
106113

114+
> 不想部署的可以直接下载安装包,解压直接使用
115+
- **Windows** 版本下载地址
116+
- 百度网盘: https://pan.baidu.com/s/1BB3SGtAFTytzFLS5t2d8Gg?pwd=5bry
117+
118+
### 前提条件
107119
- 尽量不要使用 **中文路径**,避免出现一些无法预料的问题
108120
- 请确保你的 **网络** 是正常的,VPN需要打开`全局流量`模式
109121

@@ -230,8 +242,8 @@ python main.py
230242

231243
当前支持2种字幕生成方式:
232244

233-
- edge: 生成速度更快,性能更好,对电脑配置没有要求,但是质量可能不稳定
234-
- whisper: 生成速度较慢,性能较差,对电脑配置有一定要求,但是质量更可靠
245+
- **edge**: 生成`速度快`,性能更好,对电脑配置没有要求,但是质量可能不稳定
246+
- **whisper**: 生成`速度慢`,性能较差,对电脑配置有一定要求,但是`质量更可靠`
235247

236248
可以修改 `config.toml` 配置文件中的 `subtitle_provider` 进行切换
237249

@@ -241,6 +253,25 @@ python main.py
241253
1. whisper 模式下需要到 HuggingFace 下载一个模型文件,大约 3GB 左右,请确保网络通畅
242254
2. 如果留空,表示不生成字幕。
243255

256+
> 由于国内无法访问 HuggingFace,可以使用以下方法下载 `whisper-large-v3` 的模型文件
257+
258+
下载地址:
259+
- 百度网盘: https://pan.baidu.com/s/11h3Q6tsDtjQKTjUu3sc5cA?pwd=xjs9
260+
- 夸克网盘:https://pan.quark.cn/s/3ee3d991d64b
261+
262+
模型下载后解压,整个目录放到 `.\MoneyPrinterTurbo\models` 里面,
263+
最终的文件路径应该是这样: `.\MoneyPrinterTurbo\models\whisper-large-v3`
264+
```
265+
MoneyPrinterTurbo
266+
├─models
267+
│ └─whisper-large-v3
268+
│ config.json
269+
│ model.bin
270+
│ preprocessor_config.json
271+
│ tokenizer.json
272+
│ vocabulary.json
273+
```
274+
244275
## 背景音乐 🎵
245276
246277
用于视频的背景音乐,位于项目的 `resource/songs` 目录下。
@@ -375,14 +406,6 @@ pip install Pillow==8.4.0
375406

376407
- 可以提交 [issue](https://github.com/harry0703/MoneyPrinterTurbo/issues)
377408
或者 [pull request](https://github.com/harry0703/MoneyPrinterTurbo/pulls)
378-
- 也可以关注我的 **抖音****视频号**`网旭哈瑞.AI`
379-
- 我会在上面发布一些 **使用教程****纯技术** 分享。
380-
- 如果有更新和优化,我也会在上面 **及时通知**
381-
- 有问题也可以在上面 **留言**,我会 **尽快回复**
382-
383-
| 抖音 | | 视频号 |
384-
|:---------------------------------------:|:------------:|:-------------------------------------------:|
385-
| <img src="docs/douyin.jpg" width="180"> | | <img src="docs/shipinghao.jpg" width="200"> |
386409

387410
## 参考项目 📚
388411

app/config/config.py

+32-31
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,45 @@
11
import os
22
import socket
33
import toml
4+
import shutil
45
from loguru import logger
56

67
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
78
config_file = f"{root_dir}/config.toml"
8-
if not os.path.isfile(config_file):
9-
example_file = f"{root_dir}/config.example.toml"
10-
if os.path.isfile(example_file):
11-
import shutil
129

13-
shutil.copyfile(example_file, config_file)
14-
logger.info(f"copy config.example.toml to config.toml")
1510

16-
logger.info(f"load config from file: {config_file}")
11+
def load_config():
12+
# fix: IsADirectoryError: [Errno 21] Is a directory: '/MoneyPrinterTurbo/config.toml'
13+
if os.path.isdir(config_file):
14+
shutil.rmtree(config_file)
1715

18-
try:
19-
_cfg = toml.load(config_file)
20-
except Exception as e:
21-
logger.warning(f"load config failed: {str(e)}, try to load as utf-8-sig")
22-
with open(config_file, mode="r", encoding='utf-8-sig') as fp:
23-
_cfg_content = fp.read()
24-
_cfg = toml.loads(_cfg_content)
16+
if not os.path.isfile(config_file):
17+
example_file = f"{root_dir}/config.example.toml"
18+
if os.path.isfile(example_file):
19+
shutil.copyfile(example_file, config_file)
20+
logger.info(f"copy config.example.toml to config.toml")
2521

22+
logger.info(f"load config from file: {config_file}")
23+
24+
try:
25+
_config_ = toml.load(config_file)
26+
except Exception as e:
27+
logger.warning(f"load config failed: {str(e)}, try to load as utf-8-sig")
28+
with open(config_file, mode="r", encoding='utf-8-sig') as fp:
29+
_cfg_content = fp.read()
30+
_config_ = toml.loads(_cfg_content)
31+
return _config_
32+
33+
34+
def save_config():
35+
with open(config_file, "w", encoding="utf-8") as f:
36+
_cfg["app"] = app
37+
_cfg["whisper"] = whisper
38+
_cfg["pexels"] = pexels
39+
f.write(toml.dumps(_cfg))
40+
41+
42+
_cfg = load_config()
2643
app = _cfg.get("app", {})
2744
whisper = _cfg.get("whisper", {})
2845
pexels = _cfg.get("pexels", {})
@@ -36,7 +53,7 @@
3653
project_name = _cfg.get("project_name", "MoneyPrinterTurbo")
3754
project_description = _cfg.get("project_description",
3855
"<a href='https://github.com/harry0703/MoneyPrinterTurbo'>https://github.com/harry0703/MoneyPrinterTurbo</a>")
39-
project_version = _cfg.get("project_version", "1.0.1")
56+
project_version = _cfg.get("project_version", "1.1.0")
4057
reload_debug = False
4158

4259
imagemagick_path = app.get("imagemagick_path", "")
@@ -46,19 +63,3 @@
4663
ffmpeg_path = app.get("ffmpeg_path", "")
4764
if ffmpeg_path and os.path.isfile(ffmpeg_path):
4865
os.environ["IMAGEIO_FFMPEG_EXE"] = ffmpeg_path
49-
50-
51-
# __cfg = {
52-
# "hostname": hostname,
53-
# "listen_host": listen_host,
54-
# "listen_port": listen_port,
55-
# }
56-
# logger.info(__cfg)
57-
58-
59-
def save_config():
60-
with open(config_file, "w", encoding="utf-8") as f:
61-
_cfg["app"] = app
62-
_cfg["whisper"] = whisper
63-
_cfg["pexels"] = pexels
64-
f.write(toml.dumps(_cfg))

app/services/llm.py

+60-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from loguru import logger
66
from openai import OpenAI
77
from openai import AzureOpenAI
8+
from openai.types.chat import ChatCompletion
9+
810
from app.config import config
911

1012

@@ -57,6 +59,11 @@ def _generate_response(prompt: str) -> str:
5759
api_key = config.app.get("qwen_api_key")
5860
model_name = config.app.get("qwen_model_name")
5961
base_url = "***"
62+
elif llm_provider == "cloudflare":
63+
api_key = config.app.get("cloudflare_api_key")
64+
model_name = config.app.get("cloudflare_model_name")
65+
account_id = config.app.get("cloudflare_account_id")
66+
base_url = "***"
6067
else:
6168
raise ValueError("llm_provider is not set, please set it in the config.toml file.")
6269

@@ -69,17 +76,31 @@ def _generate_response(prompt: str) -> str:
6976

7077
if llm_provider == "qwen":
7178
import dashscope
79+
from dashscope.api_entities.dashscope_response import GenerationResponse
7280
dashscope.api_key = api_key
7381
response = dashscope.Generation.call(
7482
model=model_name,
7583
messages=[{"role": "user", "content": prompt}]
7684
)
77-
content = response["output"]["text"]
78-
return content.replace("\n", "")
85+
if response:
86+
if isinstance(response, GenerationResponse):
87+
status_code = response.status_code
88+
if status_code != 200:
89+
raise Exception(
90+
f"[{llm_provider}] returned an error response: \"{response}\"")
91+
92+
content = response["output"]["text"]
93+
return content.replace("\n", "")
94+
else:
95+
raise Exception(
96+
f"[{llm_provider}] returned an invalid response: \"{response}\"")
97+
else:
98+
raise Exception(
99+
f"[{llm_provider}] returned an empty response")
79100

80101
if llm_provider == "gemini":
81102
import google.generativeai as genai
82-
genai.configure(api_key=api_key)
103+
genai.configure(api_key=api_key, transport='rest')
83104

84105
generation_config = {
85106
"temperature": 0.5,
@@ -111,10 +132,30 @@ def _generate_response(prompt: str) -> str:
111132
generation_config=generation_config,
112133
safety_settings=safety_settings)
113134

114-
convo = model.start_chat(history=[])
115-
116-
convo.send_message(prompt)
117-
return convo.last.text
135+
try:
136+
response = model.generate_content(prompt)
137+
candidates = response.candidates
138+
generated_text = candidates[0].content.parts[0].text
139+
except (AttributeError, IndexError) as e:
140+
print("Gemini Error:", e)
141+
142+
return generated_text
143+
144+
if llm_provider == "cloudflare":
145+
import requests
146+
response = requests.post(
147+
f"https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run/{model_name}",
148+
headers={"Authorization": f"Bearer {api_key}"},
149+
json={
150+
"messages": [
151+
{"role": "system", "content": "You are a friendly assistant"},
152+
{"role": "user", "content": prompt}
153+
]
154+
}
155+
)
156+
result = response.json()
157+
logger.info(result)
158+
return result["result"]["response"]
118159

119160
if llm_provider == "azure":
120161
client = AzureOpenAI(
@@ -133,7 +174,15 @@ def _generate_response(prompt: str) -> str:
133174
messages=[{"role": "user", "content": prompt}]
134175
)
135176
if response:
136-
content = response.choices[0].message.content
177+
if isinstance(response, ChatCompletion):
178+
content = response.choices[0].message.content
179+
else:
180+
raise Exception(
181+
f"[{llm_provider}] returned an invalid response: \"{response}\", please check your network "
182+
f"connection and try again.")
183+
else:
184+
raise Exception(
185+
f"[{llm_provider}] returned an empty response, please check your network connection and try again.")
137186

138187
return content.replace("\n", "")
139188

@@ -149,9 +198,9 @@ def generate_script(video_subject: str, language: str = "", paragraph_number: in
149198
1. the script is to be returned as a string with the specified number of paragraphs.
150199
2. do not under any circumstance reference this prompt in your response.
151200
3. get straight to the point, don't start with unnecessary things like, "welcome to this video".
152-
4. you must not include any type of markdown or formatting in the script, never use a title.
153-
5. only return the raw content of the script.
154-
6. do not include "voiceover", "narrator" or similar indicators of what should be spoken at the beginning of each paragraph or line.
201+
4. you must not include any type of markdown or formatting in the script, never use a title.
202+
5. only return the raw content of the script.
203+
6. do not include "voiceover", "narrator" or similar indicators of what should be spoken at the beginning of each paragraph or line.
155204
7. you must not mention the prompt, or anything about the script itself. also, never talk about the amount of paragraphs or lines. just write the script.
156205
8. respond in the same language as the video subject.
157206

app/services/subtitle.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import os.path
23
import re
34

45
from faster_whisper import WhisperModel
@@ -17,8 +18,13 @@
1718
def create(audio_file, subtitle_file: str = ""):
1819
global model
1920
if not model:
20-
logger.info(f"loading model: {model_size}, device: {device}, compute_type: {compute_type}")
21-
model = WhisperModel(model_size_or_path=model_size,
21+
model_path = f"{utils.root_dir()}/models/whisper-{model_size}"
22+
model_bin_file = f"{model_path}/model.bin"
23+
if not os.path.isdir(model_path) or not os.path.isfile(model_bin_file):
24+
model_path = model_size
25+
26+
logger.info(f"loading model: {model_path}, device: {device}, compute_type: {compute_type}")
27+
model = WhisperModel(model_size_or_path=model_path,
2228
device=device,
2329
compute_type=compute_type)
2430

0 commit comments

Comments
 (0)