Compare commits

...

56 Commits
v1.2.4 ... main

Author SHA1 Message Date
Harry
951460b9f1
Merge pull request #715 from michaeltmk/feat/custom_audio
feat: add custom audio file support
2025-12-14 12:02:56 +08:00
Harry
6523509539
Merge pull request #714 from michaeltmk/api_local_video_source
feat: add video material upload and retrieval endpoints
2025-12-14 11:59:59 +08:00
Harry
68cb074a92
Merge pull request #698 from WITHOUTNAMESES/api-management
Add API management module
2025-12-14 11:59:11 +08:00
Harry
aa2f16eb3e
Merge pull request #704 from yrk111222/main
Add support for ModelScope API
2025-12-14 11:55:51 +08:00
Harry
6e3d54c811
Merge pull request #752 from zxdxjtu/feat/gemini_tts
Add Google Gemini TTS Integration
2025-12-14 11:53:53 +08:00
Harry
238f6e4b55
Merge pull request #778 from Mxucc/main
Optimize Dockerfile for China-friendly builds with fallback mirrors
2025-12-14 11:53:31 +08:00
Harry
7fdfbbcd3e
Merge pull request #793 from Ko1hozer/patch-1
Create ru.json
2025-12-14 11:51:16 +08:00
Harry
f8f10669cb
Merge pull request #809 from jsbxyyx/main
Refactor gemini provider configuration
2025-12-14 11:49:15 +08:00
Harry
f42b5f3710
Merge pull request #807 from muminkoykiran/feature/turkish-language-support
feat: add Turkish language support
2025-12-14 11:48:09 +08:00
Mümin Köykıran
aa945361ad fix: use proper Turkish characters in translations
Address review feedback:
- Use proper Turkish special characters (ı, ğ, ş, ç, ö, ü)
- Fix "Turkce" to "Türkçe"
- Remove extra keys not present in en.json for consistency
- Update all translations with correct Turkish orthography
2025-12-12 13:50:16 +03:00
jsbxyyx
3473ebf7f5
Refactor gemini provider configuration
Updated base URL retrieval for 'gemini' provider and adjusted configuration logic.
2025-12-11 10:24:49 +08:00
Mümin Köykıran
66fc858d05 feat: add Turkish language support
Add Turkish (tr) language support to the web UI:
- Create tr.json with all translated strings
- Add tr-TR to supported locales list

This enables Turkish-speaking users to use the application
in their native language.
2025-12-09 00:12:59 +03:00
Artur Gor
de106e77a6
Create ru.json 2025-10-04 13:06:49 +05:00
Mxu
b8c57f0088 enhancement: Optimize Dockerfile for China-friendly builds with prioritized domestic mirrors and fallback to global sources. 2025-09-26 17:36:25 +08:00
zhangxindong
d2706a5fe4 fix: make faster_whisper dependency optional
- Add try/except import for faster_whisper
- Gracefully handle missing dependency with warning
- Prevents import errors on systems without faster_whisper
2025-07-08 10:40:00 +08:00
zhangxindong
d65e126486 feat: integrate Google Gemini TTS with 15 voice options
- Add gemini_tts() function with proper PCM audio handling
- Support 15 Gemini voices (Zephyr, Puck, Kore, etc.)
- Fix audio data format issue preventing video generation
- Add Gemini TTS option to WebUI settings
- Update .gitignore to exclude debug files
2025-07-08 10:39:22 +08:00
michael tse
f6c40deec6 feat: add custom audio file support 2025-05-25 17:04:56 +08:00
michael tse
579d3c0948 fix: improve error message for unsupported file extensions in video upload 2025-05-25 01:15:56 +08:00
michael tse
017a95d051 feat: add video material upload and retrieval endpoints with corresponding response models 2025-05-25 01:13:29 +08:00
yrk
9314291748 Update README.md 2025-05-21 11:26:17 +08:00
yrk
18be15f446 update config.example.toml 2025-05-21 11:01:37 +08:00
yrk
b36caf63cf Modify llm.py 2025-05-21 10:40:55 +08:00
yrk
10f177adac Add support for ModelScope API 2025-05-21 10:37:40 +08:00
WITHOUTNAMESES
c8d11ac4c3 Add api management module to let users set their api more easily. 2025-05-19 01:38:47 +08:00
Harry
6cb5f23487
Merge pull request #692 from harry0703/dev
refactor: remove unnecessary close_clip calls in video processing
2025-05-16 11:03:36 +08:00
harry
83f0a54234 refactor: remove unnecessary close_clip calls in video processing 2025-05-16 11:02:59 +08:00
harry
235362b044 feat: update bug report and feature request templates 2025-05-13 16:10:53 +08:00
Harry
a789fe7e9a
Merge pull request #674 from harry0703/dev 2025-05-13 16:04:15 +08:00
Harry
9a20328d7a
Merge branch 'main' into dev 2025-05-13 15:51:59 +08:00
harry
fda81b2e9a feat: enhance bug report and feature request templates 2025-05-13 15:41:18 +08:00
harry
7ed4a1762d perf: set default font name to MicrosoftYaHeiBold.ttc in subtitle settings 2025-05-13 15:26:12 +08:00
harry
2bbbe5480e chore: remove PDM files and changelog script 2025-05-13 15:25:48 +08:00
harry
91e4d3ef72 feat: add Colab notebook and update documentation 2025-05-13 15:22:30 +08:00
Harry
4a33655ad7
feat: add provider ai pollinations (#667) (#671)
* feat: add provider ai pollinations

* update: enter line

---------

Co-authored-by: diepdo1810 <93646638+diepdo1810@users.noreply.github.com>
Co-authored-by: Diep Do <diepchiaser@gmail.com>
2025-05-13 10:53:32 +08:00
diepdo1810
95922908ce
feat: add provider ai pollinations (#667)
* feat: add provider ai pollinations

* update: enter line

---------

Co-authored-by: Diep Do <diepchiaser@gmail.com>
2025-05-13 10:48:52 +08:00
Harry
8449303a90
Merge pull request #665 from harry0703/dev
docs: update Baidu cloud drive link to version 1.2.6
2025-05-11 12:48:29 +08:00
harry
7bee963a18 docs: update Baidu cloud drive link to version 1.2.6 2025-05-11 12:48:10 +08:00
Harry
e8f0db25ee
Merge pull request #660 from harry0703/dev
Dev
2025-05-10 17:22:30 +08:00
harry
33245996c5 feat: add test for voice service 2025-05-10 17:21:13 +08:00
harry
4d5ca7f6f4 perf: validate Azure speech key and region before creating speech 2025-05-10 17:20:44 +08:00
Harry
0bfec956c5
Merge pull request #658 from harry0703/dev
bump version to 1.2.6
2025-05-10 14:14:42 +08:00
harry
fec3a8b6bd Merge branch 'add-siliconflow-tts' into dev 2025-05-10 14:13:37 +08:00
harry
3108c2e4e5 perf: bump version to 1.2.6 2025-05-10 14:13:18 +08:00
Harry
d8dd1f1acf
Merge pull request #657 from harry0703/add-siliconflow-tts
feat: update SiliconFlow API Key descriptions in localization files
2025-05-10 14:12:11 +08:00
Harry
208ea5c11b
Merge pull request #653 from yyhhyyyyyy/add-siliconflow-tts
feat: Increase SiliconFlow TTS services.
2025-05-10 14:11:26 +08:00
harry
71d791a9af feat: update SiliconFlow API Key descriptions in localization files 2025-05-10 14:10:42 +08:00
Harry
03a06f141c
Merge pull request #655 from harry0703/dev
Dev
2025-05-10 13:27:27 +08:00
harry
4c9ac5e6df feat: loop video clips to match audio duration 2025-05-10 13:26:24 +08:00
harry
4a64e211f9 fix: correct condition for subclipping 2025-05-10 12:35:45 +08:00
harry
97c631e696 feat: improve file extension parsing using pathlib 2025-05-10 12:34:53 +08:00
harry
a601705bf4 feat: add unit tests 2025-05-10 12:34:37 +08:00
yyhhyyyyyy
45f32756a3 feat: increase siliconflow TTS services 2025-05-09 23:31:04 +08:00
yyhhyyyyyy
22f47d90de feat: add TTS services provider selection list 2025-05-09 22:14:43 +08:00
Harry
c03dc9c984
Merge pull request #652 from harry0703/dev
perf: optimize memory usage and processing performance, bump version to 1.2.5
2025-05-09 20:56:14 +08:00
harry
7569c08a62 perf: bump version to 1.2.5 2025-05-09 20:55:36 +08:00
harry
f07e5802f7 perf: optimize memory usage and processing performance 2025-05-09 20:55:12 +08:00
113 changed files with 2033 additions and 6982 deletions

View File

@ -1,6 +1,6 @@
name: 🐛 Bug
description: 出现错误或未按预期工作
title: "请在此处填写标题"
name: 🐛 Bug | Bug Report
description: 报告错误或异常问题 | Report an error or unexpected behavior
title: "[Bug]: "
labels:
- bug
@ -8,74 +8,80 @@ body:
- type: markdown
attributes:
value: |
**在提交此问题之前,请确保您已阅读以下文档:[Getting Started (英文)](https://github.com/harry0703/MoneyPrinterTurbo/blob/main/README-en.md#system-requirements-) 或 [快速开始 (中文)](https://github.com/harry0703/MoneyPrinterTurbo/blob/main/README.md#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B-)。**
**请填写以下信息:**
- type: checkboxes
attributes:
label: 是否已存在类似问题?
description: |
请务必检查此问题是否已有用户反馈。
**提交问题前,请确保您已阅读以下文档:[Getting Started (English)](https://github.com/harry0703/MoneyPrinterTurbo/blob/main/README-en.md#system-requirements-) 或 [快速开始 (中文)](https://github.com/harry0703/MoneyPrinterTurbo/blob/main/README.md#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B-)。**
在提交新问题前,使用 GitHub 的问题搜索框(包括已关闭的问题)或通过 Google、StackOverflow 等工具搜索,确认该问题是否重复。
**Before submitting an issue, please make sure you've read the following documentation: [Getting Started (English)](https://github.com/harry0703/MoneyPrinterTurbo/blob/main/README-en.md#system-requirements-) or [快速开始 (Chinese)](https://github.com/harry0703/MoneyPrinterTurbo/blob/main/README.md#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B-).**
您可能已经可以找到解决问题的方法!
options:
- label: 我已搜索现有问题
required: true
- type: textarea
attributes:
label: 当前行为
description: 描述您当前遇到的情况。
placeholder: |
MoneyPrinterTurbo 未按预期工作。当我执行某个操作时,视频未成功生成/程序报错了...
validations:
required: true
- type: textarea
attributes:
label: 预期行为
description: 描述您期望发生的情况。
placeholder: |
当我执行某个操作时,程序应当...
validations:
required: true
- type: textarea
attributes:
label: 重现步骤
description: 描述重现问题的步骤。描述的越详细,越有助于定位和修复问题。
validations:
required: true
- type: textarea
attributes:
label: 堆栈追踪/日志
label: 问题描述 | Current Behavior
description: |
如果您有任何堆栈追踪或日志,请将它们粘贴在此处。(注意不要包含敏感信息)
validations:
required: true
- type: input
attributes:
label: Python 版本
description: 您遇到此问题时使用的 Python 版本。
placeholder: v3.13.0, v3.10.0 等
validations:
required: true
- type: input
attributes:
label: 操作系统
description: 您使用 MoneyPrinterTurbo 遇到问题时的操作系统信息。
placeholder: macOS 14.1, Windows 11 等
validations:
required: true
- type: input
attributes:
label: MoneyPrinterTurbo 版本
description: 您在哪个版本的 MoneyPrinterTurbo 中遇到了此问题?
placeholder: v1.2.2 等
描述您遇到的问题
Describe the issue you're experiencing
placeholder: |
当我执行...操作时,程序出现了...问题
When I perform..., the program shows...
validations:
required: true
- type: textarea
attributes:
label: 其他信息
description: 您还有什么其他信息想补充吗?例如问题的截图或视频记录。
label: 重现步骤 | Steps to Reproduce
description: |
详细描述如何重现此问题
Describe in detail how to reproduce this issue
placeholder: |
1. 打开...
2. 点击...
3. 出现错误...
1. Open...
2. Click on...
3. Error occurs...
validations:
required: true
- type: textarea
attributes:
label: 错误日志 | Error Logs
description: |
请提供相关错误信息或日志(注意不要包含敏感信息)
Please provide any error messages or logs (be careful not to include sensitive information)
placeholder: |
错误信息、日志或截图...
Error messages, logs, or screenshots...
validations:
required: true
- type: input
attributes:
label: Python 版本 | Python Version
description: |
您使用的 Python 版本
The Python version you're using
placeholder: v3.13.0, v3.10.0, etc.
validations:
required: true
- type: input
attributes:
label: 操作系统 | Operating System
description: |
您的操作系统信息
Your operating system information
placeholder: macOS 14.1, Windows 11, Ubuntu 22.04, etc.
validations:
required: true
- type: input
attributes:
label: MoneyPrinterTurbo 版本 | Version
description: |
您使用的 MoneyPrinterTurbo 版本
The version of MoneyPrinterTurbo you're using
placeholder: v1.2.2, etc.
validations:
required: true
- type: textarea
attributes:
label: 补充信息 | Additional Information
description: |
其他对解决问题有帮助的信息(如截图、视频等)
Any other information that might help solve the issue (screenshots, videos, etc.)
validations:
required: false

View File

@ -1,38 +1,29 @@
name: ✨ 增加功能
description: 为此项目提出一个新想法
title: "请在此处填写标题"
name: ✨ 增加功能 | Feature Request
description: 为此项目提出一个新想法或建议 | Suggest a new idea for this project
title: "[Feature]: "
labels:
- enhancement
body:
- type: checkboxes
attributes:
label: 是否已存在类似的功能请求?
description: 请确保此功能请求是否重复。
options:
- label: 我已搜索现有的功能请求
required: true
- type: textarea
attributes:
label: 痛点
description: 请解释您的功能请求。
placeholder: 我希望可以实现这一点
label: 需求描述 | Problem Statement
description: |
请描述您希望解决的问题或需求
Please describe the problem you want to solve
placeholder: |
我在使用过程中遇到了...
I encountered... when using this project
validations:
required: true
- type: textarea
attributes:
label: 建议的解决方案
description: 请描述您能想到的解决方案。
placeholder: 您可以添加这个功能 / 更改这个流程 / 使用某种方法
label: 建议的解决方案 | Proposed Solution
description: |
请描述您认为可行的解决方案或实现方式
Please describe your suggested solution or implementation
placeholder: |
可以考虑添加...功能来解决这个问题
Consider adding... feature to address this issue
validations:
required: true
- type: textarea
attributes:
label: 有用的资源
description: 请提供一些有助于实现您建议的资源。
- type: textarea
attributes:
label: 其他信息
description: 您还有什么其他想补充的信息吗?例如问题的截图或视频记录。
validations:
required: false
required: true

View File

@ -1,33 +0,0 @@
name: gp-pages
on:
push:
branches:
- main
paths:
- 'sites/**'
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set-up Node
uses: actions/setup-node@v1
with:
node-version: "18.15.0"
- name: Install pnpm
run: npm install -g pnpm
- name: Install dependencies
run: pnpm i
working-directory: ./sites
- name: Build gh-pages
run: pnpm docs:build
working-directory: ./sites
- name: Deploy to gh-pages
uses: crazy-max/ghaction-github-pages@v1
with:
target_branch: gh-pages
build_dir: sites/docs/.vuepress/dist
env:
GITHUB_TOKEN: ${{ secrets.MY_TOKEN }}

9
.gitignore vendored
View File

@ -25,4 +25,11 @@ node_modules
./models/*
venv/
.venv
.venv
# Debug and test files
CLAUDE.md
debug/
debug_*.py
test_*.py
streamlit.log

View File

@ -1 +0,0 @@
./MoneyPrinterTurbo/.venv/bin/python

View File

@ -1,22 +0,0 @@
<!-- insertion marker -->
## [1.1.2](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/releases/tag/1.1.2) - 2024-04-18
<small>[Compare with first commit](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/compare/d4f7b53b841e65da658e3d77822f9923286ddab6...1.1.2)</small>
### Features
- add support for maximum concurrency of /api/v1/videos ([abe12ab](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/commit/abe12abd7b78997651468ad5dd656985066f8bd9) by kevin.zhang).
- add task deletion endpoint ([d57434e](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/commit/d57434e0d31c8195dbcd3c86ff2763af96736cdf) by kevin.zhang).
- add redis support for task state management ([3d45348](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/commit/3d453486627234937c7bfe6f176890360074696b) by kevin.zhang).
- enable cors to allow play video through mounted videos url ([3b1871d](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/commit/3b1871d591873594bb4aa8dc17a1253b3a7563a3) by kevin.zhang).
- add /api/v1/get_bgm_list and /api/v1/upload_bgm_file ([6d8911f](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/commit/6d8911f5bf496e7c5dd718309a302df88d11817b) by cathy).
- return combined videos in /api/v1/tasks response ([28199c9](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/commit/28199c93b78f67e9a6bf50f290f1591078f63da8) by cathy).
- add Dockerfile ([f3b3c7f](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/commit/f3b3c7fb47b01ed4ecba44eaebf29f5d6d2cb7b5) by kevin.zhang).
### Bug Fixes
- response parsing bug for gemini ([ee7306d](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/commit/ee7306d216ea41e40855bbca396cacb094d572db) by elf-mouse).
### Code Refactoring
- Streaming MP4 files in the browser using video html element instead of waiting for the entire file to download before playing ([d13a3cf](https://github.com/KevinZhang19870314/MoneyPrinterTurbo/commit/d13a3cf6e911d1573c62b1f6459c3c0b7a1bc18d) by kevin.zhang).

View File

@ -9,12 +9,40 @@ RUN chmod 777 /MoneyPrinterTurbo
ENV PYTHONPATH="/MoneyPrinterTurbo"
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
imagemagick \
ffmpeg \
&& rm -rf /var/lib/apt/lists/*
# Install system dependencies with domestic mirrors first for stability
RUN echo "deb http://mirrors.aliyun.com/debian bullseye main" > /etc/apt/sources.list && \
echo "deb http://mirrors.aliyun.com/debian-security bullseye-security main" >> /etc/apt/sources.list && \
( \
for i in 1 2 3; do \
echo "Attempt $i: Using Aliyun mirror"; \
apt-get update && apt-get install -y --no-install-recommends \
git \
imagemagick \
ffmpeg && break || \
echo "Attempt $i failed, retrying..."; \
if [ $i -eq 3 ]; then \
echo "Aliyun mirror failed, switching to Tsinghua mirror"; \
sed -i 's/mirrors.aliyun.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list && \
sed -i 's/mirrors.aliyun.com\/debian-security/mirrors.tuna.tsinghua.edu.cn\/debian-security/g' /etc/apt/sources.list && \
( \
apt-get update && apt-get install -y --no-install-recommends \
git \
imagemagick \
ffmpeg || \
( \
echo "Tsinghua mirror failed, switching to default Debian mirror"; \
sed -i 's/mirrors.tuna.tsinghua.edu.cn/deb.debian.org/g' /etc/apt/sources.list && \
sed -i 's/mirrors.tuna.tsinghua.edu.cn\/debian-security/security.debian.org/g' /etc/apt/sources.list; \
apt-get update && apt-get install -y --no-install-recommends \
git \
imagemagick \
ffmpeg; \
); \
); \
fi; \
sleep 5; \
done \
) && rm -rf /var/lib/apt/lists/*
# Fix security policy for ImageMagick
RUN sed -i '/<policy domain="path" rights="none" pattern="@\*"/d' /etc/ImageMagick-6/policy.xml
@ -22,8 +50,10 @@ RUN sed -i '/<policy domain="path" rights="none" pattern="@\*"/d' /etc/ImageMagi
# Copy only the requirements.txt first to leverage Docker cache
COPY requirements.txt ./
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Install Python dependencies with domestic mirrors first and retry logic
RUN pip install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com --retries 3 --timeout 60 -r requirements.txt || \
pip install --no-cache-dir -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/ --trusted-host mirrors.tuna.tsinghua.edu.cn --retries 3 --timeout 60 -r requirements.txt || \
pip install --no-cache-dir --retries 3 --timeout 60 -r requirements.txt
# Now copy the rest of the codebase into the image
COPY . .

View File

@ -38,15 +38,15 @@ project. It allows for online use without deployment, which is very convenient.
- Chinese version: https://reccloud.cn
- English version: https://reccloud.com
![](docs/reccloud.cn.jpg)
![](docs/reccloud.com.jpg)
## Thanks for Sponsorship 🙏
Thanks to Picwish https://picwish.cn for supporting and sponsoring this project, enabling continuous updates and maintenance.
Thanks to Picwish https://picwish.com for supporting and sponsoring this project, enabling continuous updates and maintenance.
Picwish focuses on the **image processing field**, providing a rich set of **image processing tools** that extremely simplify complex operations, truly making image processing easier.
![picwish.jpg](docs/picwish.jpg)
![picwish.jpg](docs/picwish.com.jpg)
## Features 🎯
@ -65,11 +65,7 @@ Picwish focuses on the **image processing field**, providing a rich set of **ima
supports `subtitle outlining`
- [x] Supports **background music**, either random or specified music files, with adjustable `background music volume`
- [x] Video material sources are **high-definition** and **royalty-free**, and you can also use your own **local materials**
- [x] Supports integration with various models such as **OpenAI**, **Moonshot**, **Azure**, **gpt4free**, **one-api**,
**Qwen**, **Google Gemini**, **Ollama**, **DeepSeek**, **ERNIE** and more
- For users in China, it is recommended to use **DeepSeek** or **Moonshot** as the large model provider (directly accessible in China, no VPN needed. Free credits upon registration, generally sufficient for use)
❓[How to Use the Free OpenAI GPT-3.5 Model?](https://github.com/harry0703/MoneyPrinterTurbo/blob/main/README-en.md#common-questions-)
- [x] Supports integration with various models such as **OpenAI**, **Moonshot**, **Azure**, **gpt4free**, **one-api**, **Qwen**, **Google Gemini**, **Ollama**, **DeepSeek**, **ERNIE**, **Pollinations**, **ModelScope** and more
### Future Plans 📅
@ -119,15 +115,20 @@ Picwish focuses on the **image processing field**, providing a rich set of **ima
## System Requirements 📦
- Recommended minimum 4 CPU cores or more, 8G of memory or more, GPU is not required
- Recommended minimum 4 CPU cores or more, 4G of memory or more, GPU is not required
- Windows 10 or MacOS 11.0, and their later versions
## Quick Start 🚀
Download the one-click startup package, extract and use directly (the path should not contain **Chinese characters**, **special characters**, or **spaces**)
### Run in Google Colab
Want to try MoneyPrinterTurbo without setting up a local environment? Run it directly in Google Colab!
[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/harry0703/MoneyPrinterTurbo/blob/main/docs/MoneyPrinterTurbo.ipynb)
### Windows
- Baidu Netdisk (1.2.1 latest version): https://pan.baidu.com/s/1pSNjxTYiVENulTLm6zieMQ?pwd=g36q Extraction code: g36q
Google Drive (v1.2.6): https://drive.google.com/file/d/1HsbzfT7XunkrCrHw5ncUjFX8XX4zAuUh/view?usp=sharing
After downloading, it is recommended to **double-click** `update.bat` first to update to the **latest code**, then double-click `start.bat` to launch
@ -141,9 +142,6 @@ One-click startup packages have not been created yet. See the **Installation & D
### Prerequisites
- Try to avoid using **Chinese paths** to prevent unpredictable issues
- Ensure your **network** is stable, VPN needs to be in `global traffic` mode
#### ① Clone the Project
```shell
@ -183,19 +181,16 @@ Open your browser and visit http://0.0.0.0:8080/docs Or http://0.0.0.0:8080/redo
### Manual Deployment 📦
> Video tutorials
>
> - Complete usage demonstration: https://v.douyin.com/iFhnwsKY/
> - How to deploy on Windows: https://v.douyin.com/iFyjoW3M
#### ① Create a Python Virtual Environment
#### ① Install Dependencies
It is recommended to use [pdm](https://pdm-project.org/en/latest/#installation)
It is recommended to create a Python virtual environment using [conda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html)
```shell
git clone https://github.com/harry0703/MoneyPrinterTurbo.git
cd MoneyPrinterTurbo
pdm sync
conda create -n MoneyPrinterTurbo python=3.11
conda activate MoneyPrinterTurbo
pip install -r requirements.txt
```
#### ② Install ImageMagick
@ -310,34 +305,6 @@ own fonts.
## Common Questions 🤔
### ❓How to Use the Free OpenAI GPT-3.5 Model?
[OpenAI has announced that ChatGPT with 3.5 is now free](https://openai.com/blog/start-using-chatgpt-instantly), and
developers have wrapped it into an API for direct usage.
**Ensure you have Docker installed and running**. Execute the following command to start the Docker service:
```shell
docker run -p 3040:3040 missuo/freegpt35
```
Once successfully started, modify the `config.toml` configuration as follows:
- Set `llm_provider` to `openai`
- Fill in `openai_api_key` with any value, for example, '123456'
- Change `openai_base_url` to `http://localhost:3040/v1/`
- Set `openai_model_name` to `gpt-3.5-turbo`
> Note: This method may be unstable
### ❓AttributeError: 'str' object has no attribute 'choices'
This issue is caused by the large language model not returning a correct response.
It's likely a network issue. Use a **VPN**, or set `openai_base_url` to your proxy, which should solve the problem.
At the same time, it is recommended to use **Moonshot** or **DeepSeek** as the large model provider, as these service providers have faster access and are more stable in China.
### ❓RuntimeError: No ffmpeg exe could be found
Normally, ffmpeg will be automatically downloaded and detected.
@ -357,24 +324,6 @@ actual installation path.
ffmpeg_path = "C:\\Users\\harry\\Downloads\\ffmpeg.exe"
```
### ❓Error generating audio or downloading videos
[issue 56](https://github.com/harry0703/MoneyPrinterTurbo/issues/56)
```
failed to generate audio, maybe the network is not available.
if you are in China, please use a VPN.
```
[issue 44](https://github.com/harry0703/MoneyPrinterTurbo/issues/44)
```
failed to download videos, maybe the network is not available.
if you are in China, please use a VPN.
```
This is likely due to network issues preventing access to foreign services. Please use a VPN to resolve this.
### ❓ImageMagick is not installed on your computer
[issue 33](https://github.com/harry0703/MoneyPrinterTurbo/issues/33)
@ -431,11 +380,6 @@ Solution: [Click to see how to manually download the model from netdisk](#subtit
- You can submit an [issue](https://github.com/harry0703/MoneyPrinterTurbo/issues) or
a [pull request](https://github.com/harry0703/MoneyPrinterTurbo/pulls).
## Reference Projects 📚
This project is based on https://github.com/FujiwaraChoki/MoneyPrinter and has been refactored with a lot of
optimizations and added functionalities. Thanks to the original author for their spirit of open source.
## License 📝
Click to view the [`LICENSE`](LICENSE) file

View File

@ -58,10 +58,10 @@
- [x] 支持 **字幕生成**,可以调整 `字体`、`位置`、`颜色`、`大小`,同时支持`字幕描边`设置
- [x] 支持 **背景音乐**,随机或者指定音乐文件,可设置`背景音乐音量`
- [x] 视频素材来源 **高清**,而且 **无版权**,也可以使用自己的 **本地素材**
- [x] 支持 **OpenAI**、**Moonshot**、**Azure**、**gpt4free**、**one-api**、**通义千问**、**Google Gemini**、**Ollama**、
**DeepSeek****文心一言** 等多种模型接入
- [x] 支持 **OpenAI**、**Moonshot**、**Azure**、**gpt4free**、**one-api**、**通义千问**、**Google Gemini**、**Ollama**、**DeepSeek**、 **文心一言**, **Pollinations**、**ModelScope** 等多种模型接入
- 中国用户建议使用 **DeepSeek****Moonshot** 作为大模型提供商国内可直接访问不需要VPN。注册就送额度基本够用
### 后期计划 📅
- [ ] GPT-SoVITS 配音支持
@ -112,15 +112,24 @@
## 配置要求 📦
- 建议最低 CPU 4核或以上内存 8G 或以上,显卡非必须
- 建议最低 CPU **4核** 或以上,内存 **4G** 或以上,显卡非必须
- Windows 10 或 MacOS 11.0 以上系统
## 快速开始 🚀
### 在 Google Colab 中运行
免去本地环境配置,点击直接在 Google Colab 中快速体验 MoneyPrinterTurbo
[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/harry0703/MoneyPrinterTurbo/blob/main/docs/MoneyPrinterTurbo.ipynb)
### Windows一键启动包
下载一键启动包,解压直接使用(路径不要有 **中文**、**特殊字符**、**空格**
### Windows
- 百度网盘1.2.1 老版本): https://pan.baidu.com/s/1pSNjxTYiVENulTLm6zieMQ?pwd=g36q 提取码: g36q
- 百度网盘v1.2.6: https://pan.baidu.com/s/1wg0UaIyXpO3SqIpaq790SQ?pwd=sbqx 提取码: sbqx
- Google Drive (v1.2.6): https://drive.google.com/file/d/1HsbzfT7XunkrCrHw5ncUjFX8XX4zAuUh/view?usp=sharing
下载后,建议先**双击执行** `update.bat` 更新到**最新代码**,然后双击 `start.bat` 启动
@ -178,14 +187,16 @@ docker-compose up
- 完整的使用演示https://v.douyin.com/iFhnwsKY/
- 如何在Windows上部署https://v.douyin.com/iFyjoW3M
#### ① 依赖安装
#### ① 创建虚拟环境
建议使用 [pdm](https://pdm-project.org/en/latest/#installation)
建议使用 [conda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html) 创建 python 虚拟环境
```shell
git clone https://github.com/harry0703/MoneyPrinterTurbo.git
cd MoneyPrinterTurbo
pdm sync
conda create -n MoneyPrinterTurbo python=3.11
conda activate MoneyPrinterTurbo
pip install -r requirements.txt
```
#### ② 安装好 ImageMagick
@ -349,11 +360,6 @@ Trying to load the model directly from the local cache, if it exists.
- 可以提交 [issue](https://github.com/harry0703/MoneyPrinterTurbo/issues)
或者 [pull request](https://github.com/harry0703/MoneyPrinterTurbo/pulls)。
## 参考项目 📚
该项目基于 https://github.com/FujiwaraChoki/MoneyPrinter 重构而来,做了大量的优化,增加了更多的功能。
感谢原作者的开源精神。
## 许可证 📝
点击查看 [`LICENSE`](LICENSE) 文件

View File

@ -36,6 +36,7 @@ def save_config():
with open(config_file, "w", encoding="utf-8") as f:
_cfg["app"] = app
_cfg["azure"] = azure
_cfg["siliconflow"] = siliconflow
_cfg["ui"] = ui
f.write(toml.dumps(_cfg))
@ -45,9 +46,13 @@ app = _cfg.get("app", {})
whisper = _cfg.get("whisper", {})
proxy = _cfg.get("proxy", {})
azure = _cfg.get("azure", {})
ui = _cfg.get("ui", {
"hide_log": False,
})
siliconflow = _cfg.get("siliconflow", {})
ui = _cfg.get(
"ui",
{
"hide_log": False,
},
)
hostname = socket.gethostname()
@ -59,7 +64,7 @@ project_description = _cfg.get(
"project_description",
"<a href='https://github.com/harry0703/MoneyPrinterTurbo'>https://github.com/harry0703/MoneyPrinterTurbo</a>",
)
project_version = _cfg.get("project_version", "1.2.4")
project_version = _cfg.get("project_version", "1.2.6")
reload_debug = False
imagemagick_path = app.get("imagemagick_path", "")

View File

@ -25,6 +25,8 @@ from app.models.schema import (
TaskQueryResponse,
TaskResponse,
TaskVideoRequest,
VideoMaterialUploadResponse,
VideoMaterialRetrieveResponse
)
from app.services import state as sm
from app.services import task as tm
@ -222,6 +224,51 @@ def upload_bgm_file(request: Request, file: UploadFile = File(...)):
"", status_code=400, message=f"{request_id}: Only *.mp3 files can be uploaded"
)
@router.get(
"/video_materials", response_model=VideoMaterialRetrieveResponse, summary="Retrieve local video materials"
)
def get_video_materials_list(request: Request):
allowed_suffixes = ("mp4", "mov", "avi", "flv", "mkv", "jpg", "jpeg", "png")
local_videos_dir = utils.storage_dir("local_videos", create=True)
files = []
for suffix in allowed_suffixes:
files.extend(glob.glob(os.path.join(local_videos_dir, f"*.{suffix}")))
video_materials_list = []
for file in files:
video_materials_list.append(
{
"name": os.path.basename(file),
"size": os.path.getsize(file),
"file": file,
}
)
response = {"files": video_materials_list}
return utils.get_response(200, response)
@router.post(
"/video_materials",
response_model=VideoMaterialUploadResponse,
summary="Upload the video material file to the local videos directory",
)
def upload_video_material_file(request: Request, file: UploadFile = File(...)):
request_id = base.get_task_id(request)
# check file ext
allowed_suffixes = ("mp4", "mov", "avi", "flv", "mkv", "jpg", "jpeg", "png")
if file.filename.endswith(allowed_suffixes):
local_videos_dir = utils.storage_dir("local_videos", create=True)
save_path = os.path.join(local_videos_dir, file.filename)
# save file
with open(save_path, "wb+") as buffer:
# If the file already exists, it will be overwritten
file.file.seek(0)
buffer.write(file.file.read())
response = {"file": save_path}
return utils.get_response(200, response)
raise HttpException(
"", status_code=400, message=f"{request_id}: Only files with extensions {', '.join(allowed_suffixes)} can be uploaded"
)
@router.get("/stream/{file_path:path}")
async def stream_video(request: Request, file_path: str):

View File

@ -81,7 +81,8 @@ class VideoParams(BaseModel):
video_materials: Optional[List[MaterialInfo]] = (
None # Materials used to generate the video
)
custom_audio_file: Optional[str] = None # Custom audio file path, will ignore video_script and disable subtitle
video_language: Optional[str] = "" # auto detect
voice_name: Optional[str] = ""
@ -301,3 +302,33 @@ class BgmUploadResponse(BaseResponse):
"data": {"file": "/MoneyPrinterTurbo/resource/songs/example.mp3"},
},
}
class VideoMaterialRetrieveResponse(BaseResponse):
class Config:
json_schema_extra = {
"example": {
"status": 200,
"message": "success",
"data": {
"files": [
{
"name": "example.mp4",
"size": 12345678,
"file": "/MoneyPrinterTurbo/resource/videos/example.mp4",
}
]
},
},
}
class VideoMaterialUploadResponse(BaseResponse):
class Config:
json_schema_extra = {
"example": {
"status": 200,
"message": "success",
"data": {
"file": "/MoneyPrinterTurbo/resource/videos/example.mp4",
},
},
}

View File

@ -1,6 +1,7 @@
import json
import logging
import re
import requests
from typing import List
import g4f
@ -57,7 +58,7 @@ def _generate_response(prompt: str) -> str:
elif llm_provider == "gemini":
api_key = config.app.get("gemini_api_key")
model_name = config.app.get("gemini_model_name")
base_url = "***"
base_url = config.app.get("gemini_base_url", "")
elif llm_provider == "qwen":
api_key = config.app.get("qwen_api_key")
model_name = config.app.get("qwen_model_name")
@ -73,6 +74,12 @@ def _generate_response(prompt: str) -> str:
base_url = config.app.get("deepseek_base_url")
if not base_url:
base_url = "https://api.deepseek.com"
elif llm_provider == "modelscope":
api_key = config.app.get("modelscope_api_key")
model_name = config.app.get("modelscope_model_name")
base_url = config.app.get("modelscope_base_url")
if not base_url:
base_url = "https://api-inference.modelscope.cn/v1/"
elif llm_provider == "ernie":
api_key = config.app.get("ernie_api_key")
secret_key = config.app.get("ernie_secret_key")
@ -82,23 +89,61 @@ def _generate_response(prompt: str) -> str:
raise ValueError(
f"{llm_provider}: secret_key is not set, please set it in the config.toml file."
)
else:
raise ValueError(
"llm_provider is not set, please set it in the config.toml file."
)
elif llm_provider == "pollinations":
try:
base_url = config.app.get("pollinations_base_url", "")
if not base_url:
base_url = "https://text.pollinations.ai/openai"
model_name = config.app.get("pollinations_model_name", "openai-fast")
# Prepare the payload
payload = {
"model": model_name,
"messages": [
{"role": "user", "content": prompt}
],
"seed": 101 # Optional but helps with reproducibility
}
# Optional parameters if configured
if config.app.get("pollinations_private"):
payload["private"] = True
if config.app.get("pollinations_referrer"):
payload["referrer"] = config.app.get("pollinations_referrer")
headers = {
"Content-Type": "application/json"
}
# Make the API request
response = requests.post(base_url, headers=headers, json=payload)
response.raise_for_status()
result = response.json()
if result and "choices" in result and len(result["choices"]) > 0:
content = result["choices"][0]["message"]["content"]
return content.replace("\n", "")
else:
raise Exception(f"[{llm_provider}] returned an invalid response format")
except requests.exceptions.RequestException as e:
raise Exception(f"[{llm_provider}] request failed: {str(e)}")
except Exception as e:
raise Exception(f"[{llm_provider}] error: {str(e)}")
if not api_key:
raise ValueError(
f"{llm_provider}: api_key is not set, please set it in the config.toml file."
)
if not model_name:
raise ValueError(
f"{llm_provider}: model_name is not set, please set it in the config.toml file."
)
if not base_url:
raise ValueError(
f"{llm_provider}: base_url is not set, please set it in the config.toml file."
)
if llm_provider not in ["pollinations", "ollama"]: # Skip validation for providers that don't require API key
if not api_key:
raise ValueError(
f"{llm_provider}: api_key is not set, please set it in the config.toml file."
)
if not model_name:
raise ValueError(
f"{llm_provider}: model_name is not set, please set it in the config.toml file."
)
if not base_url:
raise ValueError(
f"{llm_provider}: base_url is not set, please set it in the config.toml file."
)
if llm_provider == "qwen":
import dashscope
@ -128,7 +173,10 @@ def _generate_response(prompt: str) -> str:
if llm_provider == "gemini":
import google.generativeai as genai
genai.configure(api_key=api_key, transport="rest")
if not base_url:
genai.configure(api_key=api_key, transport="rest")
else:
genai.configure(api_key=api_key, transport="rest", client_options={'api_endpoint': base_url})
generation_config = {
"temperature": 0.5,
@ -172,8 +220,6 @@ def _generate_response(prompt: str) -> str:
return generated_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}"},
@ -192,20 +238,15 @@ def _generate_response(prompt: str) -> str:
return result["result"]["response"]
if llm_provider == "ernie":
import requests
params = {
"grant_type": "client_credentials",
"client_id": api_key,
"client_secret": secret_key,
}
access_token = (
requests.post(
"https://aip.baidubce.com/oauth/2.0/token", params=params
)
.json()
.get("access_token")
response = requests.post(
"https://aip.baidubce.com/oauth/2.0/token",
params={
"grant_type": "client_credentials",
"client_id": api_key,
"client_secret": secret_key,
}
)
access_token = response.json().get("access_token")
url = f"{base_url}?access_token={access_token}"
payload = json.dumps(
@ -232,6 +273,34 @@ def _generate_response(prompt: str) -> str:
api_version=api_version,
azure_endpoint=base_url,
)
if llm_provider == "modelscope":
content = ''
client = OpenAI(
api_key=api_key,
base_url=base_url,
)
response = client.chat.completions.create(
model=model_name,
messages=[{"role": "user", "content": prompt}],
extra_body={"enable_thinking": False},
stream=True
)
if response:
for chunk in response:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
if delta and delta.content:
content += delta.content
if not content.strip():
raise ValueError("Empty content in stream response")
return content.replace("\n", "")
else:
raise Exception(f"[{llm_provider}] returned an empty response")
else:
client = OpenAI(
api_key=api_key,
@ -409,3 +478,4 @@ if __name__ == "__main__":
)
print("######################")
print(search_terms)

View File

@ -3,7 +3,10 @@ import os.path
import re
from timeit import default_timer as timer
from faster_whisper import WhisperModel
try:
from faster_whisper import WhisperModel
except ImportError:
WhisperModel = None
from loguru import logger
from app.config import config
@ -17,6 +20,9 @@ model = None
def create(audio_file, subtitle_file: str = ""):
global model
if WhisperModel is None:
logger.warning("faster_whisper not available, skipping whisper subtitle generation")
return ""
if not model:
model_path = f"{utils.root_dir()}/models/whisper-{model_size}"
model_bin_file = f"{model_path}/model.bin"

View File

@ -71,30 +71,66 @@ def save_script_data(task_id, video_script, video_terms, params):
def generate_audio(task_id, params, video_script):
'''
Generate audio for the video script.
If a custom audio file is provided, it will be used directly.
There will be no subtitle maker object returned in this case.
Otherwise, TTS will be used to generate the audio.
Returns:
- audio_file: path to the generated or provided audio file
- audio_duration: duration of the audio in seconds
- sub_maker: subtitle maker object if TTS is used, None otherwise
'''
logger.info("\n\n## generating audio")
audio_file = path.join(utils.task_dir(task_id), "audio.mp3")
sub_maker = voice.tts(
text=video_script,
voice_name=voice.parse_voice_name(params.voice_name),
voice_rate=params.voice_rate,
voice_file=audio_file,
)
if sub_maker is None:
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
logger.error(
"""failed to generate audio:
custom_audio_file = params.custom_audio_file
if not custom_audio_file or not os.path.exists(custom_audio_file):
if custom_audio_file:
logger.warning(
f"custom audio file not found: {custom_audio_file}, using TTS to generate audio."
)
else:
logger.info("no custom audio file provided, using TTS to generate audio.")
audio_file = path.join(utils.task_dir(task_id), "audio.mp3")
sub_maker = voice.tts(
text=video_script,
voice_name=voice.parse_voice_name(params.voice_name),
voice_rate=params.voice_rate,
voice_file=audio_file,
)
if sub_maker is None:
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
logger.error(
"""failed to generate audio:
1. check if the language of the voice matches the language of the video script.
2. check if the network is available. If you are in China, it is recommended to use a VPN and enable the global traffic mode.
""".strip()
)
return None, None, None
audio_duration = math.ceil(voice.get_audio_duration(sub_maker))
return audio_file, audio_duration, sub_maker
""".strip()
)
return None, None, None
audio_duration = math.ceil(voice.get_audio_duration(sub_maker))
if audio_duration == 0:
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
logger.error("failed to get audio duration.")
return None, None, None
return audio_file, audio_duration, sub_maker
else:
logger.info(f"using custom audio file: {custom_audio_file}")
audio_duration = voice.get_audio_duration(custom_audio_file)
if audio_duration == 0:
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
logger.error("failed to get audio duration from custom audio file.")
return None, None, None
return custom_audio_file, audio_duration, None
def generate_subtitle(task_id, params, video_script, sub_maker, audio_file):
if not params.subtitle_enabled:
'''
Generate subtitle for the video script.
If subtitle generation is disabled or no subtitle maker is provided, it will return an empty string.
Otherwise, it will generate the subtitle using the specified provider.
Returns:
- subtitle_path: path to the generated subtitle file
'''
logger.info("\n\n## generating subtitle")
if not params.subtitle_enabled or sub_maker is None:
return ""
subtitle_path = path.join(utils.task_dir(task_id), "subtitle.srt")

View File

@ -1,8 +1,10 @@
import glob
import itertools
import os
import random
import gc
import shutil
from typing import List
from loguru import logger
from moviepy import (
AudioFileClip,
@ -29,6 +31,72 @@ from app.models.schema import (
from app.services.utils import video_effects
from app.utils import utils
class SubClippedVideoClip:
def __init__(self, file_path, start_time=None, end_time=None, width=None, height=None, duration=None):
self.file_path = file_path
self.start_time = start_time
self.end_time = end_time
self.width = width
self.height = height
if duration is None:
self.duration = end_time - start_time
else:
self.duration = duration
def __str__(self):
return f"SubClippedVideoClip(file_path={self.file_path}, start_time={self.start_time}, end_time={self.end_time}, duration={self.duration}, width={self.width}, height={self.height})"
audio_codec = "aac"
video_codec = "libx264"
fps = 30
def close_clip(clip):
if clip is None:
return
try:
# close main resources
if hasattr(clip, 'reader') and clip.reader is not None:
clip.reader.close()
# close audio resources
if hasattr(clip, 'audio') and clip.audio is not None:
if hasattr(clip.audio, 'reader') and clip.audio.reader is not None:
clip.audio.reader.close()
del clip.audio
# close mask resources
if hasattr(clip, 'mask') and clip.mask is not None:
if hasattr(clip.mask, 'reader') and clip.mask.reader is not None:
clip.mask.reader.close()
del clip.mask
# handle child clips in composite clips
if hasattr(clip, 'clips') and clip.clips:
for child_clip in clip.clips:
if child_clip is not clip: # avoid possible circular references
close_clip(child_clip)
# clear clip list
if hasattr(clip, 'clips'):
clip.clips = []
except Exception as e:
logger.error(f"failed to close clip: {str(e)}")
del clip
gc.collect()
def delete_files(files: List[str] | str):
if isinstance(files, str):
files = [files]
for file in files:
try:
os.remove(file)
except:
pass
def get_bgm_file(bgm_type: str = "random", bgm_file: str = ""):
if not bgm_type:
@ -58,85 +126,73 @@ def combine_videos(
) -> str:
audio_clip = AudioFileClip(audio_file)
audio_duration = audio_clip.duration
logger.info(f"max duration of audio: {audio_duration} seconds")
logger.info(f"audio duration: {audio_duration} seconds")
# Required duration of each clip
req_dur = audio_duration / len(video_paths)
req_dur = max_clip_duration
logger.info(f"each clip will be maximum {req_dur} seconds long")
logger.info(f"maximum clip duration: {req_dur} seconds")
output_dir = os.path.dirname(combined_video_path)
aspect = VideoAspect(video_aspect)
video_width, video_height = aspect.to_resolution()
clips = []
processed_clips = []
subclipped_items = []
video_duration = 0
raw_clips = []
for video_path in video_paths:
clip = VideoFileClip(video_path).without_audio()
clip = VideoFileClip(video_path)
clip_duration = clip.duration
clip_w, clip_h = clip.size
close_clip(clip)
start_time = 0
while start_time < clip_duration:
end_time = min(start_time + max_clip_duration, clip_duration)
split_clip = clip.subclipped(start_time, end_time)
raw_clips.append(split_clip)
# logger.info(f"splitting from {start_time:.2f} to {end_time:.2f}, clip duration {clip_duration:.2f}, split_clip duration {split_clip.duration:.2f}")
start_time = end_time
end_time = min(start_time + max_clip_duration, clip_duration)
if clip_duration - start_time >= max_clip_duration:
subclipped_items.append(SubClippedVideoClip(file_path= video_path, start_time=start_time, end_time=end_time, width=clip_w, height=clip_h))
start_time = end_time
if video_concat_mode.value == VideoConcatMode.sequential.value:
break
# random video_paths order
# random subclipped_items order
if video_concat_mode.value == VideoConcatMode.random.value:
random.shuffle(raw_clips)
random.shuffle(subclipped_items)
logger.debug(f"total subclipped items: {len(subclipped_items)}")
# Add downloaded clips over and over until the duration of the audio (max_duration) has been reached
while video_duration < audio_duration:
for clip in raw_clips:
# Check if clip is longer than the remaining audio
if (audio_duration - video_duration) < clip.duration:
clip = clip.subclipped(0, (audio_duration - video_duration))
# Only shorten clips if the calculated clip length (req_dur) is shorter than the actual clip to prevent still image
elif req_dur < clip.duration:
clip = clip.subclipped(0, req_dur)
clip = clip.with_fps(30)
for i, subclipped_item in enumerate(subclipped_items):
if video_duration > audio_duration:
break
logger.debug(f"processing clip {i+1}: {subclipped_item.width}x{subclipped_item.height}, current duration: {video_duration:.2f}s, remaining: {audio_duration - video_duration:.2f}s")
try:
clip = VideoFileClip(subclipped_item.file_path).subclipped(subclipped_item.start_time, subclipped_item.end_time)
clip_duration = clip.duration
# Not all videos are same size, so we need to resize them
clip_w, clip_h = clip.size
if clip_w != video_width or clip_h != video_height:
clip_ratio = clip.w / clip.h
video_ratio = video_width / video_height
logger.debug(f"resizing clip, source: {clip_w}x{clip_h}, ratio: {clip_ratio:.2f}, target: {video_width}x{video_height}, ratio: {video_ratio:.2f}")
if clip_ratio == video_ratio:
# Resize proportionally
clip = clip.resized((video_width, video_height))
clip = clip.resized(new_size=(video_width, video_height))
else:
# Resize proportionally
if clip_ratio > video_ratio:
# Resize proportionally based on the target width
scale_factor = video_width / clip_w
else:
# Resize proportionally based on the target height
scale_factor = video_height / clip_h
new_width = int(clip_w * scale_factor)
new_height = int(clip_h * scale_factor)
clip_resized = clip.resized(new_size=(new_width, new_height))
background = ColorClip(
size=(video_width, video_height), color=(0, 0, 0)
)
clip = CompositeVideoClip(
[
background.with_duration(clip.duration),
clip_resized.with_position("center"),
]
)
logger.info(
f"resizing video to {video_width} x {video_height}, clip size: {clip_w} x {clip_h}"
)
background = ColorClip(size=(video_width, video_height), color=(0, 0, 0)).with_duration(clip_duration)
clip_resized = clip.resized(new_size=(new_width, new_height)).with_position("center")
clip = CompositeVideoClip([background, clip_resized])
shuffle_side = random.choice(["left", "right", "top", "bottom"])
if video_transition_mode.value == VideoTransitionMode.none.value:
clip = clip
@ -160,24 +216,93 @@ def combine_videos(
if clip.duration > max_clip_duration:
clip = clip.subclipped(0, max_clip_duration)
clips.append(clip)
# wirte clip to temp file
clip_file = f"{output_dir}/temp-clip-{i+1}.mp4"
clip.write_videofile(clip_file, logger=None, fps=fps, codec=video_codec)
close_clip(clip)
processed_clips.append(SubClippedVideoClip(file_path=clip_file, duration=clip.duration, width=clip_w, height=clip_h))
video_duration += clip.duration
clips = [CompositeVideoClip([clip]) for clip in clips]
video_clip = concatenate_videoclips(clips)
video_clip = video_clip.with_fps(30)
logger.info("writing")
# https://github.com/harry0703/MoneyPrinterTurbo/issues/111#issuecomment-2032354030
video_clip.write_videofile(
filename=combined_video_path,
threads=threads,
logger=None,
temp_audiofile_path=output_dir,
audio_codec="aac",
fps=30,
)
video_clip.close()
logger.success("completed")
except Exception as e:
logger.error(f"failed to process clip: {str(e)}")
# loop processed clips until the video duration matches or exceeds the audio duration.
if video_duration < audio_duration:
logger.warning(f"video duration ({video_duration:.2f}s) is shorter than audio duration ({audio_duration:.2f}s), looping clips to match audio length.")
base_clips = processed_clips.copy()
for clip in itertools.cycle(base_clips):
if video_duration >= audio_duration:
break
processed_clips.append(clip)
video_duration += clip.duration
logger.info(f"video duration: {video_duration:.2f}s, audio duration: {audio_duration:.2f}s, looped {len(processed_clips)-len(base_clips)} clips")
# merge video clips progressively, avoid loading all videos at once to avoid memory overflow
logger.info("starting clip merging process")
if not processed_clips:
logger.warning("no clips available for merging")
return combined_video_path
# if there is only one clip, use it directly
if len(processed_clips) == 1:
logger.info("using single clip directly")
shutil.copy(processed_clips[0].file_path, combined_video_path)
delete_files(processed_clips)
logger.info("video combining completed")
return combined_video_path
# create initial video file as base
base_clip_path = processed_clips[0].file_path
temp_merged_video = f"{output_dir}/temp-merged-video.mp4"
temp_merged_next = f"{output_dir}/temp-merged-next.mp4"
# copy first clip as initial merged video
shutil.copy(base_clip_path, temp_merged_video)
# merge remaining video clips one by one
for i, clip in enumerate(processed_clips[1:], 1):
logger.info(f"merging clip {i}/{len(processed_clips)-1}, duration: {clip.duration:.2f}s")
try:
# load current base video and next clip to merge
base_clip = VideoFileClip(temp_merged_video)
next_clip = VideoFileClip(clip.file_path)
# merge these two clips
merged_clip = concatenate_videoclips([base_clip, next_clip])
# save merged result to temp file
merged_clip.write_videofile(
filename=temp_merged_next,
threads=threads,
logger=None,
temp_audiofile_path=output_dir,
audio_codec=audio_codec,
fps=fps,
)
close_clip(base_clip)
close_clip(next_clip)
close_clip(merged_clip)
# replace base file with new merged file
delete_files(temp_merged_video)
os.rename(temp_merged_next, temp_merged_video)
except Exception as e:
logger.error(f"failed to merge clip: {str(e)}")
continue
# after merging, rename final result to target file name
os.rename(temp_merged_video, combined_video_path)
# clean temp files
clip_files = [clip.file_path for clip in processed_clips]
delete_files(clip_files)
logger.info("video combining completed")
return combined_video_path
@ -194,8 +319,6 @@ def wrap_text(text, max_width, font="Arial", fontsize=60):
if width <= max_width:
return text, height
# logger.warning(f"wrapping text, max_width: {max_width}, text_width: {width}, text: {text}")
processed = True
_wrapped_lines_ = []
@ -218,7 +341,6 @@ def wrap_text(text, max_width, font="Arial", fontsize=60):
_wrapped_lines_ = [line.strip() for line in _wrapped_lines_]
result = "\n".join(_wrapped_lines_).strip()
height = len(_wrapped_lines_) * height
# logger.warning(f"wrapped text: {result}")
return result, height
_wrapped_lines_ = []
@ -235,7 +357,6 @@ def wrap_text(text, max_width, font="Arial", fontsize=60):
_wrapped_lines_.append(_txt_)
result = "\n".join(_wrapped_lines_).strip()
height = len(_wrapped_lines_) * height
# logger.warning(f"wrapped text: {result}")
return result, height
@ -249,7 +370,7 @@ def generate_video(
aspect = VideoAspect(params.video_aspect)
video_width, video_height = aspect.to_resolution()
logger.info(f"start, video size: {video_width} x {video_height}")
logger.info(f"generating video: {video_width} x {video_height}")
logger.info(f" ① video: {video_path}")
logger.info(f" ② audio: {audio_path}")
logger.info(f" ③ subtitle: {subtitle_path}")
@ -268,7 +389,7 @@ def generate_video(
if os.name == "nt":
font_path = font_path.replace("\\", "/")
logger.info(f"using font: {font_path}")
logger.info(f" font: {font_path}")
def create_text_clip(subtitle_item):
params.font_size = int(params.font_size)
@ -314,7 +435,7 @@ def generate_video(
_clip = _clip.with_position(("center", "center"))
return _clip
video_clip = VideoFileClip(video_path)
video_clip = VideoFileClip(video_path).without_audio()
audio_clip = AudioFileClip(audio_path).with_effects(
[afx.MultiplyVolume(params.voice_volume)]
)
@ -353,15 +474,14 @@ def generate_video(
video_clip = video_clip.with_audio(audio_clip)
video_clip.write_videofile(
output_file,
audio_codec="aac",
audio_codec=audio_codec,
temp_audiofile_path=output_dir,
threads=params.n_threads or 2,
logger=None,
fps=30,
fps=fps,
)
video_clip.close()
del video_clip
logger.success("completed")
def preprocess_video(materials: List[MaterialInfo], clip_duration=4):
@ -378,7 +498,7 @@ def preprocess_video(materials: List[MaterialInfo], clip_duration=4):
width = clip.size[0]
height = clip.size[1]
if width < 480 or height < 480:
logger.warning(f"video is too small, width: {width}, height: {height}")
logger.warning(f"low resolution material: {width}x{height}, minimum 480x480 required")
continue
if ext in const.FILE_TYPE_IMAGES:
@ -405,68 +525,7 @@ def preprocess_video(materials: List[MaterialInfo], clip_duration=4):
# Output the video to a file.
video_file = f"{material.url}.mp4"
final_clip.write_videofile(video_file, fps=30, logger=None)
final_clip.close()
del final_clip
close_clip(clip)
material.url = video_file
logger.success(f"completed: {video_file}")
return materials
if __name__ == "__main__":
m = MaterialInfo()
m.url = "/Users/harry/Downloads/IMG_2915.JPG"
m.provider = "local"
materials = preprocess_video([m], clip_duration=4)
print(materials)
# txt_en = "Here's your guide to travel hacks for budget-friendly adventures"
# txt_zh = "测试长字段这是您的旅行技巧指南帮助您进行预算友好的冒险"
# font = utils.resource_dir() + "/fonts/STHeitiMedium.ttc"
# for txt in [txt_en, txt_zh]:
# t, h = wrap_text(text=txt, max_width=1000, font=font, fontsize=60)
# print(t)
#
# task_id = "aa563149-a7ea-49c2-b39f-8c32cc225baf"
# task_dir = utils.task_dir(task_id)
# video_file = f"{task_dir}/combined-1.mp4"
# audio_file = f"{task_dir}/audio.mp3"
# subtitle_file = f"{task_dir}/subtitle.srt"
# output_file = f"{task_dir}/final.mp4"
#
# # video_paths = []
# # for file in os.listdir(utils.storage_dir("test")):
# # if file.endswith(".mp4"):
# # video_paths.append(os.path.join(utils.storage_dir("test"), file))
# #
# # combine_videos(combined_video_path=video_file,
# # audio_file=audio_file,
# # video_paths=video_paths,
# # video_aspect=VideoAspect.portrait,
# # video_concat_mode=VideoConcatMode.random,
# # max_clip_duration=5,
# # threads=2)
#
# cfg = VideoParams()
# cfg.video_aspect = VideoAspect.portrait
# cfg.font_name = "STHeitiMedium.ttc"
# cfg.font_size = 60
# cfg.stroke_color = "#000000"
# cfg.stroke_width = 1.5
# cfg.text_fore_color = "#FFFFFF"
# cfg.text_background_color = "transparent"
# cfg.bgm_type = "random"
# cfg.bgm_file = ""
# cfg.bgm_volume = 1.0
# cfg.subtitle_enabled = True
# cfg.subtitle_position = "bottom"
# cfg.n_threads = 2
# cfg.paragraph_number = 1
#
# cfg.voice_volume = 1.0
#
# generate_video(video_path=video_file,
# audio_path=audio_file,
# subtitle_path=subtitle_file,
# output_file=output_file,
# params=cfg
# )
logger.success(f"image processed: {video_file}")
return materials

View File

@ -6,17 +6,78 @@ from typing import Union
from xml.sax.saxutils import unescape
import edge_tts
import requests
from edge_tts import SubMaker, submaker
from edge_tts.submaker import mktimestamp
from loguru import logger
from moviepy.video.tools import subtitles
from moviepy.audio.io.AudioFileClip import AudioFileClip
from app.config import config
from app.utils import utils
def get_siliconflow_voices() -> list[str]:
"""
获取硅基流动的声音列表
Returns:
声音列表格式为 ["siliconflow:FunAudioLLM/CosyVoice2-0.5B:alex", ...]
"""
# 硅基流动的声音列表和对应的性别(用于显示)
voices_with_gender = [
("FunAudioLLM/CosyVoice2-0.5B", "alex", "Male"),
("FunAudioLLM/CosyVoice2-0.5B", "anna", "Female"),
("FunAudioLLM/CosyVoice2-0.5B", "bella", "Female"),
("FunAudioLLM/CosyVoice2-0.5B", "benjamin", "Male"),
("FunAudioLLM/CosyVoice2-0.5B", "charles", "Male"),
("FunAudioLLM/CosyVoice2-0.5B", "claire", "Female"),
("FunAudioLLM/CosyVoice2-0.5B", "david", "Male"),
("FunAudioLLM/CosyVoice2-0.5B", "diana", "Female"),
]
# 添加siliconflow:前缀,并格式化为显示名称
return [
f"siliconflow:{model}:{voice}-{gender}"
for model, voice, gender in voices_with_gender
]
def get_gemini_voices() -> list[str]:
"""
获取Gemini TTS的声音列表
Returns:
声音列表格式为 ["gemini:Zephyr-Female", "gemini:Puck-Male", ...]
"""
# Gemini TTS支持的语音列表
voices_with_gender = [
("Zephyr", "Female"),
("Puck", "Male"),
("Charon", "Male"),
("Kore", "Female"),
("Fenrir", "Male"),
("Aoede", "Female"),
("Thalia", "Female"),
("Sage", "Male"),
("Echo", "Female"),
("Harmony", "Female"),
("Lux", "Female"),
("Nova", "Female"),
("Vale", "Male"),
("Orion", "Male"),
("Atlas", "Male"),
]
# 添加gemini:前缀,并格式化为显示名称
return [
f"gemini:{voice}-{gender}"
for voice, gender in voices_with_gender
]
def get_all_azure_voices(filter_locals=None) -> list[str]:
voices_str = """
azure_voices_str = """
Name: af-ZA-AdriNeural
Gender: Female
@ -1015,7 +1076,7 @@ Gender: Female
# 定义正则表达式模式,用于匹配 Name 和 Gender 行
pattern = re.compile(r"Name:\s*(.+)\s*Gender:\s*(.+)\s*", re.MULTILINE)
# 使用正则表达式查找所有匹配项
matches = pattern.findall(voices_str)
matches = pattern.findall(azure_voices_str)
for name, gender in matches:
# 应用过滤条件
@ -1045,11 +1106,54 @@ def is_azure_v2_voice(voice_name: str):
return ""
def is_siliconflow_voice(voice_name: str):
"""检查是否是硅基流动的声音"""
return voice_name.startswith("siliconflow:")
def is_gemini_voice(voice_name: str):
"""检查是否是Gemini TTS的声音"""
return voice_name.startswith("gemini:")
def tts(
text: str, voice_name: str, voice_rate: float, voice_file: str
text: str,
voice_name: str,
voice_rate: float,
voice_file: str,
voice_volume: float = 1.0,
) -> Union[SubMaker, None]:
if is_azure_v2_voice(voice_name):
return azure_tts_v2(text, voice_name, voice_file)
elif is_siliconflow_voice(voice_name):
# 从voice_name中提取模型和声音
# 格式: siliconflow:model:voice-Gender
parts = voice_name.split(":")
if len(parts) >= 3:
model = parts[1]
# 移除性别后缀,例如 "alex-Male" -> "alex"
voice_with_gender = parts[2]
voice = voice_with_gender.split("-")[0]
# 构建完整的voice参数格式为 "model:voice"
full_voice = f"{model}:{voice}"
return siliconflow_tts(
text, model, full_voice, voice_rate, voice_file, voice_volume
)
else:
logger.error(f"Invalid siliconflow voice name format: {voice_name}")
return None
elif is_gemini_voice(voice_name):
# 从voice_name中提取声音名称
# 格式: gemini:voice-Gender
parts = voice_name.split(":")
if len(parts) >= 2:
# 移除性别后缀,例如 "Zephyr-Female" -> "Zephyr"
voice_with_gender = parts[1]
voice = voice_with_gender.split("-")[0]
return gemini_tts(text, voice, voice_rate, voice_file, voice_volume)
else:
logger.error(f"Invalid gemini voice name format: {voice_name}")
return None
return azure_tts_v1(text, voice_name, voice_rate, voice_file)
@ -1098,6 +1202,144 @@ def azure_tts_v1(
return None
def siliconflow_tts(
text: str,
model: str,
voice: str,
voice_rate: float,
voice_file: str,
voice_volume: float = 1.0,
) -> Union[SubMaker, None]:
"""
使用硅基流动的API生成语音
Args:
text: 要转换为语音的文本
model: 模型名称 "FunAudioLLM/CosyVoice2-0.5B"
voice: 声音名称 "FunAudioLLM/CosyVoice2-0.5B:alex"
voice_rate: 语音速度范围[0.25, 4.0]
voice_file: 输出的音频文件路径
voice_volume: 语音音量范围[0.6, 5.0]需要转换为硅基流动的增益范围[-10, 10]
Returns:
SubMaker对象或None
"""
text = text.strip()
api_key = config.siliconflow.get("api_key", "")
if not api_key:
logger.error("SiliconFlow API key is not set")
return None
# 将voice_volume转换为硅基流动的增益范围
# 默认voice_volume为1.0对应gain为0
gain = voice_volume - 1.0
# 确保gain在[-10, 10]范围内
gain = max(-10, min(10, gain))
url = "https://api.siliconflow.cn/v1/audio/speech"
payload = {
"model": model,
"input": text,
"voice": voice,
"response_format": "mp3",
"sample_rate": 32000,
"stream": False,
"speed": voice_rate,
"gain": gain,
}
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
for i in range(3): # 尝试3次
try:
logger.info(
f"start siliconflow tts, model: {model}, voice: {voice}, try: {i + 1}"
)
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
# 保存音频文件
with open(voice_file, "wb") as f:
f.write(response.content)
# 创建一个空的SubMaker对象
sub_maker = SubMaker()
# 获取音频文件的实际长度
try:
# 尝试使用moviepy获取音频长度
from moviepy import AudioFileClip
audio_clip = AudioFileClip(voice_file)
audio_duration = audio_clip.duration
audio_clip.close()
# 将音频长度转换为100纳秒单位与edge_tts兼容
audio_duration_100ns = int(audio_duration * 10000000)
# 使用文本分割来创建更准确的字幕
# 将文本按标点符号分割成句子
sentences = utils.split_string_by_punctuations(text)
if sentences:
# 计算每个句子的大致时长(按字符数比例分配)
total_chars = sum(len(s) for s in sentences)
char_duration = (
audio_duration_100ns / total_chars if total_chars > 0 else 0
)
current_offset = 0
for sentence in sentences:
if not sentence.strip():
continue
# 计算当前句子的时长
sentence_chars = len(sentence)
sentence_duration = int(sentence_chars * char_duration)
# 添加到SubMaker
sub_maker.subs.append(sentence)
sub_maker.offset.append(
(current_offset, current_offset + sentence_duration)
)
# 更新偏移量
current_offset += sentence_duration
else:
# 如果无法分割,则使用整个文本作为一个字幕
sub_maker.subs = [text]
sub_maker.offset = [(0, audio_duration_100ns)]
except Exception as e:
logger.warning(f"Failed to create accurate subtitles: {str(e)}")
# 回退到简单的字幕
sub_maker.subs = [text]
# 使用音频文件的实际长度如果无法获取则假设为10秒
sub_maker.offset = [
(
0,
audio_duration_100ns
if "audio_duration_100ns" in locals()
else 10000000,
)
]
logger.success(f"siliconflow tts succeeded: {voice_file}")
print("s", sub_maker.subs, sub_maker.offset)
return sub_maker
else:
logger.error(
f"siliconflow tts failed with status code {response.status_code}: {response.text}"
)
except Exception as e:
logger.error(f"siliconflow tts failed: {str(e)}")
return None
def azure_tts_v2(text: str, voice_name: str, voice_file: str) -> Union[SubMaker, None]:
voice_name = is_azure_v2_voice(voice_name)
if not voice_name:
@ -1146,6 +1388,10 @@ def azure_tts_v2(text: str, voice_name: str, voice_file: str) -> Union[SubMaker,
# Creates an instance of a speech config with specified subscription key and service region.
speech_key = config.azure.get("speech_key", "")
service_region = config.azure.get("speech_region", "")
if not speech_key or not service_region:
logger.error("Azure speech key or region is not set")
return None
audio_config = speechsdk.audio.AudioOutputConfig(
filename=voice_file, use_default_speaker=True
)
@ -1189,6 +1435,130 @@ def azure_tts_v2(text: str, voice_name: str, voice_file: str) -> Union[SubMaker,
return None
def gemini_tts(
text: str,
voice_name: str,
voice_rate: float,
voice_file: str,
voice_volume: float = 1.0,
) -> Union[SubMaker, None]:
"""
使用Google Gemini TTS生成语音
Args:
text: 要转换的文本
voice_name: 语音名称 "Zephyr", "Puck"
voice_rate: 语音速率当前未使用
voice_file: 输出音频文件路径
voice_volume: 音频音量当前未使用
Returns:
SubMaker对象或None
"""
import base64
import json
import io
from pydub import AudioSegment
import google.generativeai as genai
try:
# 配置Gemini API
api_key = config.app.get("gemini_api_key", "")
if not api_key:
logger.error("Gemini API key is not set")
return None
genai.configure(api_key=api_key)
logger.info(f"start, voice name: {voice_name}, try: 1")
# 使用Gemini TTS API
model = genai.GenerativeModel("gemini-2.5-flash-preview-tts")
generation_config = {
"response_modalities": ["AUDIO"],
"speech_config": {
"voice_config": {
"prebuilt_voice_config": {
"voice_name": voice_name
}
}
}
}
response = model.generate_content(
contents=text,
generation_config=generation_config
)
# 检查响应
if not response.candidates or not response.candidates[0].content:
logger.error("No audio content received from Gemini TTS")
return None
# 获取音频数据
audio_data = None
for part in response.candidates[0].content.parts:
if hasattr(part, 'inline_data') and part.inline_data:
audio_data = part.inline_data.data
break
if not audio_data:
logger.error("No audio data found in response")
return None
# 音频数据已经是原始字节不需要base64解码
if isinstance(audio_data, str):
# 如果是字符串则需要base64解码
audio_bytes = base64.b64decode(audio_data)
else:
# 如果已经是字节,直接使用
audio_bytes = audio_data
# 尝试不同的音频格式 - Gemini可能返回不同的格式
audio_segment = None
# Gemini返回Linear PCM格式按照文档参数解析
try:
audio_segment = AudioSegment.from_file(
io.BytesIO(audio_bytes),
format="raw",
frame_rate=24000, # Gemini TTS默认采样率
channels=1, # 单声道
sample_width=2 # 16-bit
)
except Exception as e:
logger.error(f"Failed to load PCM audio: {e}")
return None
# 导出为MP3格式
audio_segment.export(voice_file, format="mp3")
logger.info(f"completed, output file: {voice_file}")
# 创建SubMaker对象用于字幕
sub_maker = SubMaker()
audio_duration = len(audio_segment) / 1000.0 # 转换为秒
# 将音频长度转换为100纳秒单位与edge_tts兼容
audio_duration_100ns = int(audio_duration * 10000000)
# 使用create_sub方法正确创建字幕项
sub_maker.create_sub(
(0, audio_duration_100ns),
text
)
return sub_maker
except ImportError as e:
logger.error(f"Missing required package for Gemini TTS: {str(e)}. Please install: pip install pydub")
return None
except Exception as e:
logger.error(f"Gemini TTS failed, error: {str(e)}")
return None
def _format_text(text: str) -> str:
# text = text.replace("\n", " ")
text = text.replace("[", " ")
@ -1219,7 +1589,7 @@ def create_subtitle(sub_maker: submaker.SubMaker, text: str, subtitle_file: str)
"""
start_t = mktimestamp(start_time).replace(".", ",")
end_t = mktimestamp(end_time).replace(".", ",")
return f"{idx}\n" f"{start_t} --> {end_t}\n" f"{sub_text}\n"
return f"{idx}\n{start_t} --> {end_t}\n{sub_text}\n"
start_time = -1.0
sub_items = []
@ -1291,7 +1661,7 @@ def create_subtitle(sub_maker: submaker.SubMaker, text: str, subtitle_file: str)
logger.error(f"failed, error: {str(e)}")
def get_audio_duration(sub_maker: submaker.SubMaker):
def _get_audio_duration_from_submaker(sub_maker: submaker.SubMaker):
"""
获取音频时长
"""
@ -1299,6 +1669,35 @@ def get_audio_duration(sub_maker: submaker.SubMaker):
return 0.0
return sub_maker.offset[-1][1] / 10000000
def _get_audio_duration_from_mp3(mp3_file: str) -> float:
"""
获取MP3音频时长
"""
if not os.path.exists(mp3_file):
logger.error(f"MP3 file does not exist: {mp3_file}")
return 0.0
try:
# Use moviepy to get the duration of the MP3 file
with AudioFileClip(mp3_file) as audio:
return audio.duration # Duration in seconds
except Exception as e:
logger.error(f"Failed to get audio duration from MP3: {str(e)}")
return 0.0
def get_audio_duration( target: Union[str, submaker.SubMaker]) -> float:
"""
获取音频时长
如果是SubMaker对象则从SubMaker中获取时长
如果是MP3文件则从MP3文件中获取时长
"""
if isinstance(target, submaker.SubMaker):
return _get_audio_duration_from_submaker(target)
elif isinstance(target, str) and target.endswith(".mp3"):
return _get_audio_duration_from_mp3(target)
else:
logger.error(f"Invalid target type: {type(target)}")
return 0.0
if __name__ == "__main__":
voice_name = "zh-CN-XiaoxiaoMultilingualNeural-V2-Female"

View File

@ -1,6 +1,7 @@
import json
import locale
import os
from pathlib import Path
import threading
from typing import Any
from uuid import uuid4
@ -226,4 +227,4 @@ def load_locales(i18n_dir):
def parse_extension(filename):
return os.path.splitext(filename)[1].strip().lower().replace(".", "")
return Path(filename).suffix.lower().lstrip('.')

View File

@ -1,17 +0,0 @@
from git_changelog.cli import build_and_render
# 运行这段脚本自动生成CHANGELOG.md文件
build_and_render(
repository=".",
output="CHANGELOG.md",
convention="angular",
provider="github",
template="keepachangelog",
parse_trailers=True,
parse_refs=False,
sections=["build", "deps", "feat", "fix", "refactor"],
versioning="pep440",
bump="1.1.2", # 指定bump版本
in_place=True,
)

View File

@ -30,8 +30,18 @@ pixabay_api_keys = []
# oneapi
# cloudflare
# ernie (文心一言)
# modelscope (魔搭社区)
llm_provider = "openai"
########## Pollinations AI Settings
# Visit https://pollinations.ai/ to learn more
# API Key is optional - leave empty for public access
pollinations_api_key = ""
# Default base URL for Pollinations API
pollinations_base_url = "https://pollinations.ai/api/v1"
# Default model for text generation
pollinations_model_name = "openai-fast"
########## Ollama Settings
# No need to set it unless you want to use your own proxy
ollama_base_url = ""
@ -90,6 +100,14 @@ deepseek_api_key = ""
deepseek_base_url = "https://api.deepseek.com"
deepseek_model_name = "deepseek-chat"
########## ModelScope API Key
# Visit https://modelscope.cn/docs/model-service/API-Inference/intro to get your API key
# And note that you need to bind your Alibaba Cloud account before using the API.
modelscope_api_key = ""
modelscope_base_url = "https://api-inference.modelscope.cn/v1/"
modelscope_model_name = "Qwen/Qwen3-32B"
# Subtitle Provider, "edge" or "whisper"
# If empty, the subtitle will not be generated
subtitle_provider = "edge"
@ -193,6 +211,11 @@ compute_type = "int8"
speech_key = ""
speech_region = ""
[siliconflow]
# SiliconFlow API Key
# Get your API key at https://siliconflow.cn
api_key = ""
[ui]
# UI related settings
# 是否隐藏日志信息

View File

@ -0,0 +1,118 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# MoneyPrinterTurbo Setup Guide\n",
"\n",
"This notebook will guide you through the process of setting up [MoneyPrinterTurbo](https://github.com/harry0703/MoneyPrinterTurbo)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Clone Repository and Install Dependencies\n",
"\n",
"First, we'll clone the repository from GitHub and install all required packages:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "S8Eu-aQarY_B"
},
"outputs": [],
"source": [
"!git clone https://github.com/harry0703/MoneyPrinterTurbo.git\n",
"%cd MoneyPrinterTurbo\n",
"!pip install -q -r requirements.txt\n",
"!pip install pyngrok --quiet"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Configure ngrok for Remote Access\n",
"\n",
"We'll use ngrok to create a secure tunnel to expose our local Streamlit server to the internet.\n",
"\n",
"**Important**: You need to get your authentication token from the [ngrok dashboard](https://dashboard.ngrok.com/get-started/your-authtoken) to use this service."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pyngrok import ngrok\n",
"\n",
"# Terminate any existing ngrok tunnels\n",
"ngrok.kill()\n",
"\n",
"# Set your authentication token\n",
"# Replace \"your_ngrok_auth_token\" with your actual token\n",
"ngrok.set_auth_token(\"your_ngrok_auth_token\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Launch Application and Generate Public URL\n",
"\n",
"Now we'll start the Streamlit server and create an ngrok tunnel to make it accessible online:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"collapsed": true,
"id": "oahsIOXmwjl9",
"outputId": "ee23a96c-af21-4207-deb7-9fab69e0c05e"
},
"outputs": [],
"source": [
"import subprocess\n",
"import time\n",
"\n",
"print(\"🚀 Starting MoneyPrinterTurbo...\")\n",
"# Start Streamlit server on port 8501\n",
"streamlit_proc = subprocess.Popen([\n",
" \"streamlit\", \"run\", \"./webui/Main.py\", \"--server.port=8501\"\n",
"])\n",
"\n",
"# Wait for the server to initialize\n",
"time.sleep(5)\n",
"\n",
"print(\"🌐 Creating ngrok tunnel to expose the MoneyPrinterTurbo...\")\n",
"public_url = ngrok.connect(8501, bind_tls=True)\n",
"\n",
"print(\"✅ Deployment complete! Access your MoneyPrinterTurbo at:\")\n",
"print(public_url)"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

BIN
docs/picwish.com.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

After

Width:  |  Height:  |  Size: 667 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

2008
pdm.lock

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +0,0 @@
[project]
name = "MoneyPrinterTurbo"
version = "1.2.3"
description = "Default template for PDM package"
authors = [
{name = "yyhhyyyyyy", email = "yyhhyyyyyy8@gmail.com"},
]
dependencies = [
"moviepy==2.1.1",
"streamlit==1.40.2",
"edge-tts==6.1.19",
"fastapi==0.115.6",
"uvicorn==0.32.1",
"openai==1.56.1",
"faster-whisper==1.1.0",
"loguru==0.7.2",
"google-generativeai==0.8.3",
"dashscope==1.20.14",
"g4f==0.3.8.1",
"azure-cognitiveservices-speech==1.41.1",
"redis==5.2.0",
"python-multipart==0.0.19",
"streamlit-authenticator==0.4.1",
"pyyaml",
]
requires-python = "==3.11.*"
readme = "README.md"
license = {text = "MIT"}
[tool.pdm]
distribution = false

View File

@ -12,4 +12,5 @@ g4f==0.5.2.2
azure-cognitiveservices-speech==1.41.1
redis==5.2.0
python-multipart==0.0.19
pyyaml
pyyaml
requests>=2.31.0

Binary file not shown.

Binary file not shown.

View File

@ -1,208 +0,0 @@
import { viteBundler } from "@vuepress/bundler-vite";
import { defaultTheme } from "@vuepress/theme-default";
import { defineUserConfig } from "vuepress";
const base = "MoneyPrinterTurbo";
const isProd = process.env.NODE_ENV === "production";
export default defineUserConfig({
lang: "zh-CN",
base: `/${base}/`,
bundler: viteBundler(),
theme: defaultTheme({
repo: "harry0703/MoneyPrinterTurbo",
docsDir: "sites/docs",
colorModeSwitch: true,
locales: {
"/": {
// navbar
navbar: [
{ text: "Guide", link: "/guide/" },
// { text: "Components", link: "/components/" },
],
selectLanguageText: "Languages",
selectLanguageName: "English",
selectLanguageAriaLabel: "Select language",
// sidebar
sidebar: {
"/guide/": [
{
text: "Guide",
children: [
{ text: "Get Started", link: "/guide/README.md" },
{ text: "Video Demonstration", link: "/guide/video-demonstration.md" },
{ text: "Features", link: "/guide/features.md" },
{ text: "Speech Synthesis", link: "/guide/speech-synthesis.md" },
{ text: "Subtitle Generation", link: "/guide/subtitle-generation.md" },
{ text: "Background Music", link: "/guide/background-music.md" },
{ text: "Subtitle Font", link: "/guide/subtitle-font.md" },
],
},
{
text: "Others",
children: [
{ text: "FAQ", link: "/guide/faq.md" },
{ text: "Feedback", link: "/guide/feedback.md" },
{ text: "Reference Project", link: "/guide/reference-project.md" },
],
},
],
// "/components/": getComponentsSidebar("Components", "Advanced"),
},
// page meta
editLinkText: "Edit this page on GitHub",
},
"/zh/": {
// navbar
navbar: [
{ text: "指南", link: "/zh/guide/" },
// { text: "组件", link: "/zh/components/" },
],
selectLanguageText: "选择语言",
selectLanguageName: "简体中文",
selectLanguageAriaLabel: "选择语言",
// sidebar
sidebar: {
"/zh/guide/": [
{
text: "指南",
children: [
{ text: "快速开始", link: "/zh/guide/README.md" },
{ text: "配置要求", link: "/zh/guide/configuration-requirements.md" },
{ text: "视频演示", link: "/zh/guide/video-demonstration.md" },
{ text: "功能", link: "/zh/guide/features.md" },
{ text: "语音合成", link: "/zh/guide/speech-synthesis.md" },
{ text: "字幕生成", link: "/zh/guide/subtitle-generation.md" },
{ text: "背景音乐", link: "/zh/guide/background-music.md" },
{ text: "字幕字体", link: "/zh/guide/subtitle-font.md" },
],
},
{
text: "其他",
children: [
{ text: "常见问题", link: "/zh/guide/faq.md" },
{ text: "反馈建议", link: "/zh/guide/feedback.md" },
{ text: "参考项目", link: "/zh/guide/reference-project.md" },
{ text: "特别感谢", link: "/zh/guide/special-thanks.md" },
{ text: "感谢赞助", link: "/zh/guide/thanks-for-sponsoring" },
],
},
],
// "/zh/others/": getComponentsSidebar("组件", "高级"),
},
// page meta
editLinkText: "在 GitHub 上编辑此页",
lastUpdatedText: "上次更新",
contributorsText: "贡献者",
// custom containers
tip: "提示",
warning: "注意",
danger: "警告",
// 404 page
notFound: [
"这里什么都没有",
"我们怎么到这来了?",
"这是一个 404 页面",
"看起来我们进入了错误的链接",
],
backToHome: "返回首页",
},
},
themePlugins: {
// only enable git plugin in production mode
git: isProd,
},
}),
locales: {
"/": {
lang: "en-US",
title: "MoneyPrinterTurbo",
description: "Generate short videos with one click using AI LLM.",
},
"/zh/": {
lang: "zh-CN",
title: "MoneyPrinterTurbo",
description: "利用AI大模型一键生成高清短视频。",
},
},
head: [
[
"link",
{
rel: "icon",
type: "image/png",
sizes: "16x16",
href: `/${base}/icons/favicon-16x16.png`,
},
],
[
"link",
{
rel: "icon",
type: "image/png",
sizes: "32x32",
href: `/${base}/icons/favicon-32x32.png`,
},
],
["meta", { name: "application-name", content: "MoneyPrinterTurbo" }],
[
"meta",
{ name: "apple-mobile-web-app-title", content: "MoneyPrinterTurbo" },
],
["meta", { name: "apple-mobile-web-app-capable", content: "yes" }],
[
"meta",
{ name: "apple-mobile-web-app-status-bar-style", content: "black" },
],
[
"link",
{
rel: "apple-touch-icon",
href: `/${base}/icons/apple-touch-icon-152x152.png`,
},
],
[
"link",
{
rel: "mask-icon",
href: "/${base}/icons/safari-pinned-tab.svg",
color: "#3eaf7c",
},
],
[
"meta",
{
name: "msapplication-TileImage",
content: "/${base}/icons/msapplication-icon-144x144.png",
},
],
["meta", { name: "msapplication-TileColor", content: "#000000" }],
["meta", { name: "theme-color", content: "#3eaf7c" }],
],
});
function getGuideSidebar(groupA: string, groupB: string) {
return [
{
text: groupA,
children: ["README.md", { text: "特别感谢", link: "/zh/guide/special-thanks.md" }, "2.md"],
},
{
text: groupB,
children: ["custom-validator.md", "1.md", "2.md", "3.md"],
},
];
}
function getComponentsSidebar(groupA: string, groupB: string) {
return [
{
text: groupA,
children: ["README.md", "1.md", "2.md"],
},
{
text: groupB,
children: ["custom-components.md"],
},
];
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,149 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,16.000000) scale(0.000320,-0.000320)"
fill="#000000" stroke="none">
<path d="M18 46618 c45 -75 122 -207 122 -211 0 -2 25 -45 55 -95 30 -50 55
-96 55 -102 0 -5 5 -10 10 -10 6 0 10 -4 10 -9 0 -5 73 -135 161 -288 89 -153
173 -298 187 -323 14 -25 32 -57 41 -72 88 -149 187 -324 189 -335 2 -7 8 -13
13 -13 5 0 9 -4 9 -10 0 -5 46 -89 103 -187 175 -302 490 -846 507 -876 8 -16
20 -36 25 -45 28 -46 290 -498 339 -585 13 -23 74 -129 136 -236 61 -107 123
-215 137 -240 14 -25 29 -50 33 -56 5 -5 23 -37 40 -70 18 -33 38 -67 44 -75
11 -16 21 -33 63 -109 14 -25 29 -50 33 -56 4 -5 21 -35 38 -65 55 -100 261
-455 269 -465 4 -5 14 -21 20 -35 15 -29 41 -75 103 -180 24 -41 52 -88 60
-105 9 -16 57 -100 107 -185 112 -193 362 -626 380 -660 8 -14 23 -38 33 -55
11 -16 23 -37 27 -45 4 -8 26 -46 48 -85 23 -38 53 -90 67 -115 46 -81 64
-113 178 -310 62 -107 121 -210 132 -227 37 -67 56 -99 85 -148 16 -27 32 -57
36 -65 4 -8 15 -27 25 -42 9 -15 53 -89 96 -165 44 -76 177 -307 296 -513 120
-206 268 -463 330 -570 131 -227 117 -203 200 -348 36 -62 73 -125 82 -140 10
-15 21 -34 25 -42 4 -8 20 -37 36 -65 17 -27 38 -65 48 -82 49 -85 64 -111 87
-153 13 -25 28 -49 32 -55 4 -5 78 -134 165 -285 87 -151 166 -288 176 -305
10 -16 26 -43 35 -59 9 -17 125 -217 257 -445 132 -229 253 -441 270 -471 17
-30 45 -79 64 -108 18 -29 33 -54 33 -57 0 -2 20 -37 44 -77 24 -40 123 -212
221 -383 97 -170 190 -330 205 -355 16 -25 39 -65 53 -90 13 -25 81 -144 152
-265 70 -121 137 -238 150 -260 12 -22 37 -65 55 -95 18 -30 43 -73 55 -95 12
-22 48 -85 80 -140 77 -132 163 -280 190 -330 13 -22 71 -123 130 -225 59
-102 116 -199 126 -217 10 -17 29 -50 43 -72 15 -22 26 -43 26 -45 0 -2 27
-50 60 -106 33 -56 60 -103 60 -105 0 -2 55 -98 90 -155 8 -14 182 -316 239
-414 13 -22 45 -79 72 -124 27 -46 49 -86 49 -89 0 -2 14 -24 30 -48 16 -24
30 -46 30 -49 0 -5 74 -135 100 -176 5 -8 24 -42 43 -75 50 -88 58 -101 262
-455 104 -179 199 -345 213 -370 14 -25 28 -49 32 -55 4 -5 17 -26 28 -45 10
-19 62 -109 114 -200 114 -197 133 -230 170 -295 16 -27 33 -57 38 -65 17 -28
96 -165 103 -180 4 -8 16 -28 26 -45 10 -16 77 -131 148 -255 72 -124 181
-313 243 -420 62 -107 121 -209 131 -227 35 -62 323 -560 392 -678 38 -66 83
-145 100 -175 16 -30 33 -59 37 -65 4 -5 17 -27 29 -47 34 -61 56 -100 90
-156 17 -29 31 -55 31 -57 0 -2 17 -32 39 -67 21 -35 134 -229 251 -433 117
-203 235 -407 261 -451 27 -45 49 -85 49 -88 0 -4 8 -19 19 -34 15 -21 200
-341 309 -533 10 -19 33 -58 51 -87 17 -29 31 -54 31 -56 0 -2 25 -44 55 -94
30 -50 55 -95 55 -98 0 -4 6 -15 14 -23 7 -9 27 -41 43 -71 17 -30 170 -297
342 -594 171 -296 311 -542 311 -547 0 -5 5 -9 10 -9 6 0 10 -4 10 -10 0 -5
22 -47 49 -92 27 -46 58 -99 68 -118 24 -43 81 -140 93 -160 5 -8 66 -114 135
-235 69 -121 130 -227 135 -235 12 -21 259 -447 283 -490 10 -19 28 -47 38
-62 11 -14 19 -29 19 -32 0 -3 37 -69 83 -148 99 -170 305 -526 337 -583 13
-22 31 -53 41 -70 11 -16 22 -37 26 -45 7 -14 82 -146 103 -180 14 -24 181
-311 205 -355 13 -22 46 -80 75 -130 29 -49 64 -110 78 -135 14 -25 51 -88 82
-140 31 -52 59 -102 63 -110 4 -8 18 -33 31 -55 205 -353 284 -489 309 -535
17 -30 45 -78 62 -106 18 -28 36 -60 39 -72 4 -12 12 -22 17 -22 5 0 9 -4 9
-10 0 -5 109 -197 241 -427 133 -230 250 -431 259 -448 51 -90 222 -385 280
-485 37 -63 78 -135 92 -160 14 -25 67 -117 118 -205 51 -88 101 -175 111
-193 34 -58 55 -95 149 -257 51 -88 101 -173 110 -190 9 -16 76 -131 147 -255
72 -124 140 -241 151 -260 61 -108 281 -489 355 -615 38 -66 77 -133 87 -150
35 -63 91 -161 100 -175 14 -23 99 -169 128 -220 54 -97 135 -235 142 -245 4
-5 20 -32 35 -60 26 -48 238 -416 276 -480 10 -16 26 -46 37 -65 30 -53 382
-661 403 -695 10 -16 22 -37 26 -45 4 -8 26 -48 50 -88 24 -41 43 -75 43 -77
0 -2 22 -40 50 -85 27 -45 50 -84 50 -86 0 -3 38 -69 83 -147 84 -142 302
-520 340 -587 10 -19 34 -60 52 -90 18 -30 44 -75 57 -100 14 -25 45 -79 70
-120 25 -41 56 -96 70 -121 14 -25 77 -133 138 -240 62 -107 122 -210 132
-229 25 -43 310 -535 337 -581 11 -19 26 -45 34 -59 17 -32 238 -414 266 -460
11 -19 24 -41 28 -49 3 -7 75 -133 160 -278 84 -146 153 -269 153 -274 0 -5 5
-9 10 -9 6 0 10 -4 10 -10 0 -5 82 -150 181 -322 182 -314 201 -346 240 -415
12 -21 80 -139 152 -263 71 -124 141 -245 155 -270 14 -25 28 -49 32 -55 6 -8
145 -248 220 -380 37 -66 209 -362 229 -395 11 -19 24 -42 28 -49 4 -8 67
-118 140 -243 73 -125 133 -230 133 -233 0 -2 15 -28 33 -57 19 -29 47 -78 64
-108 17 -30 53 -93 79 -139 53 -90 82 -141 157 -272 82 -142 115 -199 381
-659 142 -245 268 -463 281 -485 12 -22 71 -125 132 -230 60 -104 172 -298
248 -430 76 -132 146 -253 156 -270 11 -16 22 -36 26 -44 3 -8 30 -54 60 -103
29 -49 53 -91 53 -93 0 -3 18 -34 40 -70 22 -36 40 -67 40 -69 0 -2 37 -66 81
-142 45 -77 98 -168 119 -204 20 -36 47 -81 58 -100 12 -19 27 -47 33 -62 6
-16 15 -28 20 -28 5 0 9 -4 9 -9 0 -6 63 -118 140 -251 77 -133 140 -243 140
-245 0 -2 18 -33 41 -70 22 -37 49 -83 60 -101 10 -19 29 -51 40 -71 25 -45
109 -189 126 -218 7 -11 17 -29 22 -40 6 -11 22 -38 35 -60 14 -22 37 -62 52
-90 14 -27 35 -62 45 -77 11 -14 19 -29 19 -32 0 -3 18 -35 40 -71 22 -36 40
-67 40 -69 0 -2 19 -35 42 -72 23 -38 55 -94 72 -124 26 -47 139 -244 171
-298 6 -9 21 -36 34 -60 28 -48 37 -51 51 -19 6 12 19 36 29 52 10 17 27 46
38 65 11 19 104 181 208 360 103 179 199 345 213 370 14 25 42 74 64 109 21
34 38 65 38 67 0 2 18 33 40 69 22 36 40 67 40 69 0 3 177 310 199 346 16 26
136 234 140 244 2 5 25 44 52 88 27 44 49 81 49 84 0 2 18 34 40 70 22 36 40
67 40 69 0 2 20 36 43 77 35 58 169 289 297 513 9 17 50 86 90 155 40 69 86
150 103 180 16 30 35 62 41 70 6 8 16 24 22 35 35 64 72 129 167 293 59 100
116 199 127 220 11 20 30 53 41 72 43 72 1070 1850 1121 1940 14 25 65 113
113 195 48 83 96 166 107 185 10 19 28 50 38 68 11 18 73 124 137 235 64 111
175 303 246 427 71 124 173 299 225 390 52 91 116 202 143 248 27 45 49 85 49
89 0 4 6 14 14 22 7 9 28 43 46 76 26 47 251 436 378 655 11 19 29 51 40 70
11 19 101 176 201 348 99 172 181 317 181 323 0 5 5 9 10 9 6 0 10 5 10 11 0
6 8 23 18 37 11 15 32 52 49 82 16 30 130 228 253 440 122 212 234 405 248
430 13 25 39 70 57 100 39 65 69 117 130 225 25 44 50 87 55 95 12 19 78 134
220 380 61 107 129 224 150 260 161 277 222 382 246 425 15 28 47 83 71 123
24 41 43 78 43 83 0 5 4 9 8 9 4 0 13 12 19 28 7 15 23 45 36 67 66 110 277
478 277 483 0 3 6 13 14 21 7 9 27 41 43 71 17 30 45 80 63 110 34 57 375 649
394 685 6 11 16 27 22 35 6 8 26 42 44 75 18 33 41 74 51 90 10 17 24 41 32
55 54 97 72 128 88 152 11 14 19 28 19 30 0 3 79 141 175 308 96 167 175 305
175 308 0 3 6 13 14 21 7 9 26 39 41 66 33 60 276 483 338 587 24 40 46 80 50
88 4 8 13 24 20 35 14 23 95 163 125 215 11 19 52 91 92 160 40 69 80 139 90
155 9 17 103 179 207 360 105 182 200 346 211 365 103 181 463 802 489 845 7
11 15 27 19 35 4 8 29 51 55 95 64 110 828 1433 848 1470 9 17 24 41 33 55 9
14 29 48 45 77 15 28 52 93 82 145 30 51 62 107 71 123 17 30 231 398 400 690
51 88 103 179 115 202 12 23 26 48 32 55 6 7 24 38 40 68 17 30 61 107 98 170
37 63 84 144 103 180 19 36 41 72 48 81 8 8 14 18 14 21 0 4 27 51 59 106 32
55 72 124 89 154 16 29 71 125 122 213 51 88 104 180 118 205 13 25 28 50 32
55 4 6 17 26 28 45 11 19 45 80 77 135 31 55 66 116 77 135 11 19 88 152 171
295 401 694 620 1072 650 1125 11 19 87 152 170 295 83 143 158 273 166 288 9
16 21 36 26 45 6 9 31 52 55 96 25 43 54 94 66 115 11 20 95 164 186 321 91
157 173 299 182 315 9 17 26 46 37 65 12 19 66 114 121 210 56 96 108 186 117
200 8 14 24 40 34 59 24 45 383 664 412 713 5 9 17 29 26 45 15 28 120 210
241 419 36 61 68 117 72 125 4 8 12 23 19 34 35 57 245 420 262 453 11 20 35
61 53 90 17 29 32 54 32 56 0 3 28 51 62 108 33 57 70 119 80 138 10 19 23 42
28 50 5 8 32 53 59 100 27 47 149 258 271 470 122 212 234 405 248 430 30 53
62 108 80 135 6 11 15 27 19 35 4 8 85 150 181 315 96 165 187 323 202 350 31
56 116 202 130 225 5 8 25 42 43 75 19 33 92 159 162 280 149 257 157 271 202
350 19 33 38 67 43 75 9 14 228 392 275 475 12 22 55 96 95 165 40 69 80 139
90 155 24 42 202 350 221 383 9 15 27 47 41 72 14 25 75 131 136 236 61 106
121 210 134 232 99 172 271 470 279 482 5 8 23 40 40 70 18 30 81 141 142 245
60 105 121 210 135 235 14 25 71 124 127 220 56 96 143 247 194 335 51 88 96
167 102 175 14 24 180 311 204 355 23 43 340 590 356 615 5 8 50 87 101 175
171 301 517 898 582 1008 25 43 46 81 46 83 0 2 12 23 27 47 14 23 40 67 56
97 16 30 35 62 42 70 7 8 15 22 18 30 4 8 20 38 37 65 16 28 33 57 37 65 6 12
111 196 143 250 5 8 55 95 112 193 57 98 113 195 126 215 12 20 27 46 32 57 6
11 14 27 20 35 5 8 76 130 156 270 80 140 165 287 187 325 23 39 52 90 66 115
13 25 30 52 37 61 8 8 14 18 14 21 0 4 41 77 92 165 50 87 175 302 276 478
101 176 208 360 236 408 28 49 67 117 86 152 19 35 41 70 48 77 6 6 12 15 12
19 0 7 124 224 167 291 12 21 23 40 23 42 0 2 21 40 46 83 26 43 55 92 64 109
54 95 327 568 354 614 19 30 45 75 59 100 71 128 82 145 89 148 4 2 8 8 8 13
0 5 42 82 94 172 311 538 496 858 518 897 14 25 40 70 58 100 18 30 42 71 53
90 10 19 79 139 152 265 73 127 142 246 153 265 10 19 43 76 72 125 29 50 63
108 75 130 65 116 80 140 87 143 4 2 8 8 8 12 0 8 114 212 140 250 6 8 14 24
20 35 5 11 54 97 108 190 l100 170 -9611 3 c-5286 1 -9614 -1 -9618 -5 -5 -6
-419 -719 -619 -1068 -89 -155 -267 -463 -323 -560 -38 -66 -81 -140 -95 -165
-31 -56 -263 -457 -526 -910 -110 -190 -224 -388 -254 -440 -29 -52 -61 -109
-71 -125 -23 -39 -243 -420 -268 -465 -11 -19 -204 -352 -428 -740 -224 -388
-477 -826 -563 -975 -85 -148 -185 -322 -222 -385 -37 -63 -120 -207 -185
-320 -65 -113 -177 -306 -248 -430 -72 -124 -172 -297 -222 -385 -51 -88 -142
-245 -202 -350 -131 -226 -247 -427 -408 -705 -65 -113 -249 -432 -410 -710
-160 -278 -388 -673 -506 -877 -118 -205 -216 -373 -219 -373 -3 0 -52 82
-109 183 -58 100 -144 250 -192 332 -95 164 -402 696 -647 1120 -85 149 -228
396 -317 550 -212 365 -982 1700 -1008 1745 -10 19 -43 76 -72 125 -29 50 -64
110 -77 135 -14 25 -63 110 -110 190 -47 80 -96 165 -110 190 -14 25 -99 171
-188 325 -89 154 -174 300 -188 325 -13 25 -64 113 -112 195 -48 83 -140 242
-205 355 -65 113 -183 317 -263 454 -79 137 -152 264 -163 282 -50 89 -335
583 -354 614 -12 19 -34 58 -50 85 -15 28 -129 226 -253 440 -124 215 -235
408 -247 430 -12 22 -69 121 -127 220 -58 99 -226 389 -373 645 -148 256 -324
561 -392 678 -67 117 -134 232 -147 255 -13 23 -33 59 -46 80 l-22 37 -9615 0
-9615 0 20 -32z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -1,21 +0,0 @@
{
"name": "VuePress",
"short_name": "VuePress",
"description": "Vue-powered Static Site Generator",
"start_url": "/index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#3eaf7c",
"icons": [
{
"src": "/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

View File

@ -1,941 +0,0 @@
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

View File

@ -1,16 +0,0 @@
---
home: true
heroImage: /hero.png
actions:
- text: Get Started →
link: /guide/
type: primary
features:
- title: Multilingual
details: Supports video scripts in both Chinese and English; offers multiple voice synthesis options.
- title: Maintainability
details: Complete MVC architecture with clear code structure, easy to maintain, supports both API and Web interface.
- title: Multi-Model Support
details: Supports integration with multiple models including OpenAI, moonshot, Azure, gpt4free, one-api, Tongyi Qianwen, Google Gemini, Ollama, and others.
footer: MIT Licensed | Copyright © 2024-present MoneyPrinterTurbo
---

View File

@ -1,134 +0,0 @@
## Installation & Deployment 📥
Simply provide a <b>topic</b> or <b>keyword</b> 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.
### WebUI
![](/webui-en.jpg)
### API Interface
![](/api.jpg)
- Try to avoid using **Chinese paths** to prevent unpredictable issues
- Ensure your **network** is stable, meaning you can access foreign websites normally
#### ① Clone the Project
```shell
git clone https://github.com/harry0703/MoneyPrinterTurbo.git
```
#### ② Modify the Configuration File
- Copy the `config.example.toml` file and rename it to `config.toml`
- Follow the instructions in the `config.toml` file to configure `pexels_api_keys` and `llm_provider`, and according to
the llm_provider's service provider, set up the corresponding API Key
#### ③ Configure Large Language Models (LLM)
- To use `GPT-4.0` or `GPT-3.5`, you need an `API Key` from `OpenAI`. If you don't have one, you can set `llm_provider`
to `g4f` (a free-to-use GPT library https://github.com/xtekky/gpt4free)
### Docker Deployment 🐳
#### ① Launch the Docker Container
If you haven't installed Docker, please install it first https://www.docker.com/products/docker-desktop/
If you are using a Windows system, please refer to Microsoft's documentation:
1. https://learn.microsoft.com/en-us/windows/wsl/install
2. https://learn.microsoft.com/en-us/windows/wsl/tutorials/wsl-containers
```shell
cd MoneyPrinterTurbo
docker-compose up
```
#### ② Access the Web Interface
Open your browser and visit http://0.0.0.0:8501
#### ③ Access the API Interface
Open your browser and visit http://0.0.0.0:8080/docs Or http://0.0.0.0:8080/redoc
### Manual Deployment 📦
#### ① Create a Python Virtual Environment
It is recommended to create a Python virtual environment
using [conda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html)
```shell
git clone https://github.com/harry0703/MoneyPrinterTurbo.git
cd MoneyPrinterTurbo
conda create -n MoneyPrinterTurbo python=3.10
conda activate MoneyPrinterTurbo
pip install -r requirements.txt
```
#### ② Install ImageMagick
###### Windows:
- Download https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-29-Q16-x64-static.exe
- Install the downloaded ImageMagick, **do not change the installation path**
- Modify the `config.toml` configuration file, set `imagemagick_path` to your actual installation path (if you didn't
change the path during installation, just uncomment it)
###### MacOS:
```shell
brew install imagemagick
```
###### Ubuntu
```shell
sudo apt-get install imagemagick
```
###### CentOS
```shell
sudo yum install ImageMagick
```
#### ③ Launch the Web Interface 🌐
Note that you need to execute the following commands in the `root directory` of the MoneyPrinterTurbo project
###### Windows
```bat
conda activate MoneyPrinterTurbo
webui.bat
```
###### MacOS or Linux
```shell
conda activate MoneyPrinterTurbo
sh webui.sh
```
After launching, the browser will open automatically
#### ④ Launch the API Service 🚀
```shell
python main.py
```
After launching, you can view the `API documentation` at http://127.0.0.1:8080/docs and directly test the interface
online for a quick experience.
## License 📝
Click to view the [`LICENSE`](LICENSE) file
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=harry0703/MoneyPrinterTurbo&type=Date)](https://star-history.com/#harry0703/MoneyPrinterTurbo&Date)

View File

@ -1,5 +0,0 @@
## Background Music 🎵
Background music for videos is located in the project's `resource/songs` directory.
> The current project includes some default music from YouTube videos. If there are copyright issues, please delete
> them.

View File

@ -1,70 +0,0 @@
## Common Questions 🤔
### ❓How to Use the Free OpenAI GPT-3.5 Model?
[OpenAI has announced that ChatGPT with 3.5 is now free](https://openai.com/blog/start-using-chatgpt-instantly), and
developers have wrapped it into an API for direct usage.
**Ensure you have Docker installed and running**. Execute the following command to start the Docker service:
```shell
docker run -p 3040:3040 missuo/freegpt35
```
Once successfully started, modify the `config.toml` configuration as follows:
- Set `llm_provider` to `openai`
- Fill in `openai_api_key` with any value, for example, '123456'
- Change `openai_base_url` to `http://localhost:3040/v1/`
- Set `openai_model_name` to `gpt-3.5-turbo`
### ❓RuntimeError: No ffmpeg exe could be found
Normally, ffmpeg will be automatically downloaded and detected.
However, if your environment has issues preventing automatic downloads, you may encounter the following error:
```
RuntimeError: No ffmpeg exe could be found.
Install ffmpeg on your system, or set the IMAGEIO_FFMPEG_EXE environment variable.
```
In this case, you can download ffmpeg from https://www.gyan.dev/ffmpeg/builds/, unzip it, and set `ffmpeg_path` to your
actual installation path.
```toml
[app]
# Please set according to your actual path, note that Windows path separators are \\
ffmpeg_path = "C:\\Users\\harry\\Downloads\\ffmpeg.exe"
```
### ❓Error generating audio or downloading videos
[issue 56](https://github.com/harry0703/MoneyPrinterTurbo/issues/56)
```
failed to generate audio, maybe the network is not available.
if you are in China, please use a VPN.
```
[issue 44](https://github.com/harry0703/MoneyPrinterTurbo/issues/44)
```
failed to download videos, maybe the network is not available.
if you are in China, please use a VPN.
```
This is likely due to network issues preventing access to foreign services. Please use a VPN to resolve this.
### ❓ImageMagick is not installed on your computer
[issue 33](https://github.com/harry0703/MoneyPrinterTurbo/issues/33)
1. Follow the `example configuration` provided `download address` to
install https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-30-Q16-x64-static.exe, using the static library
2. Do not install in a path with Chinese characters to avoid unpredictable issues
[issue 54](https://github.com/harry0703/MoneyPrinterTurbo/issues/54#issuecomment-2017842022)
For Linux systems, you can manually install it, refer to https://cn.linux-console.net/?p=16978
Thanks to [@wangwenqiao666](https://github.com/wangwenqiao666) for their research and exploration

View File

@ -1,34 +0,0 @@
## Features 🎯
- [x] Complete **MVC architecture**, **clearly structured** code, easy to maintain, supports both `API`
and `Web interface`
- [x] Supports **AI-generated** video copy, as well as **customized copy**
- [x] Supports various **high-definition video** sizes
- [x] Portrait 9:16, `1080x1920`
- [x] Landscape 16:9, `1920x1080`
- [x] Supports **batch video generation**, allowing the creation of multiple videos at once, then selecting the most
satisfactory one
- [x] Supports setting the **duration of video clips**, facilitating adjustments to material switching frequency
- [x] Supports video copy in both **Chinese** and **English**
- [x] Supports **multiple voice** synthesis
- [x] Supports **subtitle generation**, with adjustable `font`, `position`, `color`, `size`, and also
supports `subtitle outlining`
- [x] Supports **background music**, either random or specified music files, with adjustable `background music volume`
- [x] Video material sources are **high-definition** and **royalty-free**
- [x] Supports integration with various models such as **OpenAI**, **moonshot**, **Azure**, **gpt4free**, **one-api**,
**qianwen**, **Google Gemini**, **Ollama** and more
❓[How to Use the Free OpenAI GPT-3.5 Model?](https://github.com/harry0703/MoneyPrinterTurbo/blob/main/README-en.md#common-questions-)
### Future Plans 📅
- [ ] Introduce support for GPT-SoVITS dubbing
- [ ] Enhance voice synthesis with large models for a more natural and emotionally resonant voice output
- [ ] Incorporate video transition effects to ensure a smoother viewing experience
- [ ] Improve the relevance of video content
- [ ] Add options for video length: short, medium, long
- [ ] Package the application into a one-click launch bundle for Windows and macOS for ease of use
- [ ] Enable the use of custom materials
- [ ] Offer voiceover and background music options with real-time preview
- [ ] Support a wider range of voice synthesis providers, such as OpenAI TTS, Azure TTS
- [ ] Automate the upload process to the YouTube platform

View File

@ -1,4 +0,0 @@
## Feedback & Suggestions 📢
- You can submit an [issue](https://github.com/harry0703/MoneyPrinterTurbo/issues) or
a [pull request](https://github.com/harry0703/MoneyPrinterTurbo/pulls).

View File

@ -1,4 +0,0 @@
## Reference Projects 📚
This project is based on https://github.com/FujiwaraChoki/MoneyPrinter and has been refactored with a lot of
optimizations and added functionalities. Thanks to the original author for their spirit of open source.

View File

@ -1,3 +0,0 @@
## Voice Synthesis 🗣
A list of all supported voices can be viewed here: [Voice List](/voice-list.txt)

View File

@ -1,4 +0,0 @@
## Subtitle Fonts 🅰
Fonts for rendering video subtitles are located in the project's `resource/fonts` directory, and you can also add your
own fonts.

View File

@ -1,15 +0,0 @@
## Subtitle Generation 📜
Currently, there are 2 ways to generate subtitles:
- edge: Faster generation speed, better performance, no specific requirements for computer configuration, but the
quality may be unstable
- whisper: Slower generation speed, poorer performance, specific requirements for computer configuration, but more
reliable quality
You can switch between them by modifying the `subtitle_provider` in the `config.toml` configuration file
It is recommended to use `edge` mode, and switch to `whisper` mode if the quality of the subtitles generated is not
satisfactory.
> If left blank, it means no subtitles will be generated.

View File

@ -1,35 +0,0 @@
## Video Demos 📺
### Portrait 9:16
<table>
<thead>
<tr>
<th align="center"><g-emoji class="g-emoji" alias="arrow_forward">▶️</g-emoji> How to Add Fun to Your Life </th>
<th align="center"><g-emoji class="g-emoji" alias="arrow_forward">▶️</g-emoji> What is the Meaning of Life</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><video src="https://github.com/harry0703/MoneyPrinterTurbo/assets/4928832/a84d33d5-27a2-4aba-8fd0-9fb2bd91c6a6"></video></td>
<td align="center"><video src="https://github.com/harry0703/MoneyPrinterTurbo/assets/4928832/112c9564-d52b-4472-99ad-970b75f66476"></video></td>
</tr>
</tbody>
</table>
### Landscape 16:9
<table>
<thead>
<tr>
<th align="center"><g-emoji class="g-emoji" alias="arrow_forward">▶️</g-emoji> What is the Meaning of Life</th>
<th align="center"><g-emoji class="g-emoji" alias="arrow_forward">▶️</g-emoji> Why Exercise</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><video src="https://github.com/harry0703/MoneyPrinterTurbo/assets/4928832/346ebb15-c55f-47a9-a653-114f08bb8073"></video></td>
<td align="center"><video src="https://github.com/harry0703/MoneyPrinterTurbo/assets/4928832/271f2fae-8283-44a0-8aa0-0ed8f9a6fa87"></video></td>
</tr>
</tbody>
</table>

View File

@ -1,16 +0,0 @@
---
home: true
heroImage: /hero.png
actions:
- text: 快速上手 →
link: /zh/guide/
type: primary
features:
- title: 多语言
details: 支持 中文 和 英文 视频文案;支持 多种语音 合成。
- title: 可维护性
details: 完整的 MVC架构代码 结构清晰,易于维护,支持 API 和 Web界面。
- title: 多模型支持
details: 支持 OpenAI、moonshot、Azure、gpt4free、one-api、通义千问、Google Gemini、Ollama 等多种模型接入。
footer: MIT Licensed | Copyright © 2024-present MoneyPrinterTurbo
---

View File

@ -1,157 +0,0 @@
## 快速开始 🚀
<br>
只需提供一个视频 <b>主题</b><b>关键词</b> ,就可以全自动生成视频文案、视频素材、视频字幕、视频背景音乐,然后合成一个高清的短视频。
<br>
<h4>Web界面</h4>
![](/webui.jpg)
<h4>API界面</h4>
![](/api.jpg)
下载一键启动包,解压直接使用
### Windows
- 百度网盘: https://pan.baidu.com/s/1bpGjgQVE5sADZRn3A6F87w?pwd=xt16 提取码: xt16
下载后,建议先**双击执行** `update.bat` 更新到**最新代码**,然后双击 `start.bat` 启动 Web 界面
### 其他系统
还没有制作一键启动包,看下面的 **安装部署** 部分,建议使用 **docker** 部署,更加方便。
## 安装部署 📥
### 前提条件
- 尽量不要使用 **中文路径**,避免出现一些无法预料的问题
- 请确保你的 **网络** 是正常的VPN 需要打开`全局流量`模式
#### ① 克隆代码
```shell
git clone https://github.com/harry0703/MoneyPrinterTurbo.git
```
#### ② 修改配置文件
- 将 `config.example.toml` 文件复制一份,命名为 `config.toml`
- 按照 `config.toml` 文件中的说明,配置好 `pexels_api_keys``llm_provider`,并根据 llm_provider 对应的服务商,配置相关的
API Key
#### ③ 配置大模型(LLM)
- 如果要使用 `GPT-4.0``GPT-3.5`,需要有 `OpenAI``API Key`,如果没有,可以将 `llm_provider` 设置为 `g4f` (
一个免费使用 GPT 的开源库 https://github.com/xtekky/gpt4free ,但是该免费的服务,稳定性较差,有时候可以用,有时候用不了)
- 或者可以使用到 [月之暗面](https://platform.moonshot.cn/console/api-keys) 申请。注册就送
15 元体验金,可以对话 1500 次左右。然后设置 `llm_provider="moonshot"``moonshot_api_key`
- 也可以使用 通义千问,具体请看配置文件里面的注释说明
### Docker 部署 🐳
#### ① 启动 Docker
如果未安装 Docker请先安装 https://www.docker.com/products/docker-desktop/
如果是 Windows 系统,请参考微软的文档:
1. https://learn.microsoft.com/zh-cn/windows/wsl/install
2. https://learn.microsoft.com/zh-cn/windows/wsl/tutorials/wsl-containers
```shell
cd MoneyPrinterTurbo
docker-compose up
```
#### ② 访问 Web 界面
打开浏览器,访问 http://0.0.0.0:8501
#### ③ 访问 API 文档
打开浏览器,访问 http://0.0.0.0:8080/docs 或者 http://0.0.0.0:8080/redoc
### 手动部署 📦
> 视频教程
- 完整的使用演示https://v.douyin.com/iFhnwsKY/
- 如何在 Windows 上部署https://v.douyin.com/iFyjoW3M
#### ① 创建虚拟环境
建议使用 [conda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html) 创建 python 虚拟环境
```shell
git clone https://github.com/harry0703/MoneyPrinterTurbo.git
cd MoneyPrinterTurbo
conda create -n MoneyPrinterTurbo python=3.10
conda activate MoneyPrinterTurbo
pip install -r requirements.txt
```
#### ② 安装好 ImageMagick
###### Windows:
- 下载 https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-30-Q16-x64-static.exe
- 安装下载好的 ImageMagick注意不要修改安装路径
- 修改 `配置文件 config.toml` 中的 `imagemagick_path` 为你的实际安装路径(如果安装的时候没有修改路径,直接取消注释即可)
###### MacOS:
```shell
brew install imagemagick
```
###### Ubuntu
```shell
sudo apt-get install imagemagick
```
###### CentOS
```shell
sudo yum install ImageMagick
```
#### ③ 启动 Web 界面 🌐
注意需要到 MoneyPrinterTurbo 项目 `根目录` 下执行以下命令
###### Windows
```bat
conda activate MoneyPrinterTurbo
webui.bat
```
###### MacOS or Linux
```shell
conda activate MoneyPrinterTurbo
sh webui.sh
```
启动后,会自动打开浏览器
#### ④ 启动 API 服务 🚀
```shell
python main.py
```
启动后,可以查看 `API文档` http://127.0.0.1:8080/docs 或者 http://127.0.0.1:8080/redoc 直接在线调试接口,快速体验。
## 许可证 📝
点击查看 [`LICENSE`](LICENSE) 文件
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=harry0703/MoneyPrinterTurbo&type=Date)](https://star-history.com/#harry0703/MoneyPrinterTurbo&Date)

View File

@ -1,4 +0,0 @@
## 背景音乐 🎵
用于视频的背景音乐,位于项目的 `resource/songs` 目录下。
> 当前项目里面放了一些默认的音乐,来自于 YouTube 视频,如有侵权,请删除。

View File

@ -1,4 +0,0 @@
## 配置要求 📦
- 建议最低 CPU 4核或以上内存 8G 或以上,显卡非必须
- Windows 10 或 MacOS 11.0 以上系统

View File

@ -1,123 +0,0 @@
## 常见问题 🤔
### ❓如何使用免费的OpenAI GPT-3.5模型?
[OpenAI宣布ChatGPT里面3.5已经免费了](https://openai.com/blog/start-using-chatgpt-instantly)有开发者将其封装成了API可以直接调用
**确保你安装和启动了docker服务**执行以下命令启动docker服务
```shell
docker run -p 3040:3040 missuo/freegpt35
```
启动成功后,修改 `config.toml` 中的配置
- `llm_provider` 设置为 `openai`
- `openai_api_key` 随便填写一个即可,比如 '123456'
- `openai_base_url` 改为 `http://localhost:3040/v1/`
- `openai_model_name` 改为 `gpt-3.5-turbo`
### ❓AttributeError: 'str' object has no attribute 'choices'`
这个问题是由于 OpenAI 或者其他 LLM没有返回正确的回复导致的。
大概率是网络原因, 使用 **VPN**,或者设置 `openai_base_url` 为你的代理 ,应该就可以解决了。
### ❓RuntimeError: No ffmpeg exe could be found
通常情况下ffmpeg 会被自动下载,并且会被自动检测到。
但是如果你的环境有问题,无法自动下载,可能会遇到如下错误:
```
RuntimeError: No ffmpeg exe could be found.
Install ffmpeg on your system, or set the IMAGEIO_FFMPEG_EXE environment variable.
```
此时你可以从 https://www.gyan.dev/ffmpeg/builds/ 下载ffmpeg解压后设置 `ffmpeg_path` 为你的实际安装路径即可。
```toml
[app]
# 请根据你的实际路径设置,注意 Windows 路径分隔符为 \\
ffmpeg_path = "C:\\Users\\harry\\Downloads\\ffmpeg.exe"
```
### ❓生成音频时报错或下载视频报错
[issue 56](https://github.com/harry0703/MoneyPrinterTurbo/issues/56)
```
failed to generate audio, maybe the network is not available.
if you are in China, please use a VPN.
```
[issue 44](https://github.com/harry0703/MoneyPrinterTurbo/issues/44)
```
failed to download videos, maybe the network is not available.
if you are in China, please use a VPN.
```
这个大概率是网络原因无法访问境外的服务请使用VPN解决。
### ❓ImageMagick is not installed on your computer
[issue 33](https://github.com/harry0703/MoneyPrinterTurbo/issues/33)
1. 按照 `示例配置` 里面提供的 `下载地址`
,安装 https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-29-Q16-x64-static.exe, 用静态库
2. 不要安装在中文路径里面,避免出现一些无法预料的问题
[issue 54](https://github.com/harry0703/MoneyPrinterTurbo/issues/54#issuecomment-2017842022)
如果是linux系统可以手动安装参考 https://cn.linux-console.net/?p=16978
感谢 [@wangwenqiao666](https://github.com/wangwenqiao666)的研究探索
### ❓ImageMagick的安全策略阻止了与临时文件@/tmp/tmpur5hyyto.txt相关的操作
[issue 92](https://github.com/harry0703/MoneyPrinterTurbo/issues/92)
可以在ImageMagick的配置文件policy.xml中找到这些策略。
这个文件通常位于 /etc/ImageMagick-`X`/ 或 ImageMagick 安装目录的类似位置。
修改包含`pattern="@"`的条目,将`rights="none"`更改为`rights="read|write"`以允许对文件的读写操作。
感谢 [@chenhengzh](https://github.com/chenhengzh)的研究探索
### ❓OSError: [Errno 24] Too many open files
[issue 100](https://github.com/harry0703/MoneyPrinterTurbo/issues/100)
这个问题是由于系统打开文件数限制导致的,可以通过修改系统的文件打开数限制来解决。
查看当前限制
```shell
ulimit -n
```
如果过低,可以调高一些,比如
```shell
ulimit -n 10240
```
### ❓AttributeError: module 'PIL.Image' has no attribute 'ANTIALIAS'
[issue 101](https://github.com/harry0703/MoneyPrinterTurbo/issues/101),
[issue 83](https://github.com/harry0703/MoneyPrinterTurbo/issues/83),
[issue 70](https://github.com/harry0703/MoneyPrinterTurbo/issues/70)
先看下当前的 Pillow 版本是多少
```shell
pip list |grep Pillow
```
如果是 10.x 的版本,可以尝试下降级看看,有用户反馈降级后正常
```shell
pip uninstall Pillow
pip install Pillow==9.5.0
# 或者降级到 8.4.0
pip install Pillow==8.4.0
```

View File

@ -1,31 +0,0 @@
## 功能特性 🎯
- [x] 完整的 **MVC架构**,代码 **结构清晰**,易于维护,支持 `API``Web界面`
- [x] 支持视频文案 **AI自动生成**,也可以**自定义文案**
- [x] 支持多种 **高清视频** 尺寸
- [x] 竖屏 9:16`1080x1920`
- [x] 横屏 16:9`1920x1080`
- [x] 支持 **批量视频生成**,可以一次生成多个视频,然后选择一个最满意的
- [x] 支持 **视频片段时长**设置,方便调节素材切换频率
- [x] 支持 **中文****英文** 视频文案
- [x] 支持 **多种语音** 合成
- [x] 支持 **字幕生成**,可以调整 `字体`、`位置`、`颜色`、`大小`,同时支持`字幕描边`设置
- [x] 支持 **背景音乐**,随机或者指定音乐文件,可设置`背景音乐音量`
- [x] 视频素材来源 **高清**,而且 **无版权**
- [x] 支持 **OpenAI**、**moonshot**、**Azure**、**gpt4free**、**one-api**、**通义千问**、**Google Gemini**、**Ollama** 等多种模型接入
❓[如何使用免费的 **OpenAI GPT-3.5
** 模型?](https://github.com/harry0703/MoneyPrinterTurbo?tab=readme-ov-file#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98-)
### 后期计划 📅
- [ ] GPT-SoVITS 配音支持
- [ ] 优化语音合成,利用大模型,使其合成的声音,更加自然,情绪更加丰富
- [ ] 增加视频转场效果,使其看起来更加的流畅
- [ ] 增加更多视频素材来源,优化视频素材和文案的匹配度
- [ ] 增加视频长度选项:短、中、长
- [ ] 增加免费网络代理让访问OpenAI和素材下载不再受限
- [ ] 可以使用自己的素材
- [ ] 朗读声音和背景音乐,提供实时试听
- [ ] 支持更多的语音合成服务商,比如 OpenAI TTS
- [ ] 自动上传到YouTube平台

View File

@ -1,4 +0,0 @@
## 反馈建议 📢
- 可以提交 [issue](https://github.com/harry0703/MoneyPrinterTurbo/issues)
或者 [pull request](https://github.com/harry0703/MoneyPrinterTurbo/pulls)。

View File

@ -1,4 +0,0 @@
## 参考项目 📚
该项目基于 https://github.com/FujiwaraChoki/MoneyPrinter 重构而来,做了大量的优化,增加了更多的功能。
感谢原作者的开源精神。

View File

@ -1,9 +0,0 @@
## 特别感谢 🙏
由于该项目的 **部署****使用**,对于一些小白用户来说,还是 **有一定的门槛**,在此特别感谢
**录咖AI智能 多媒体服务平台)** 网站基于该项目,提供的免费`AI视频生成器`服务,可以不用部署,直接在线使用,非常方便。
- 中文版https://reccloud.cn
- 英文版https://reccloud.com
![](/reccloud.cn.jpg)

View File

@ -1,5 +0,0 @@
## 语音合成 🗣
所有支持的声音列表,可以查看:[声音列表](/voice-list.txt)
2024-04-16 v1.1.2 新增了9种Azure的语音合成声音需要配置API KEY该声音合成的更加真实。

View File

@ -1,3 +0,0 @@
## 字幕字体 🅰
用于视频字幕的渲染,位于项目的 `resource/fonts` 目录下,你也可以放进去自己的字体。

View File

@ -1,36 +0,0 @@
## 字幕生成 📜
当前支持2种字幕生成方式
- **edge**: 生成`速度快`,性能更好,对电脑配置没有要求,但是质量可能不稳定
- **whisper**: 生成`速度慢`,性能较差,对电脑配置有一定要求,但是`质量更可靠`。
可以修改 `config.toml` 配置文件中的 `subtitle_provider` 进行切换
建议使用 `edge` 模式,如果生成的字幕质量不好,再切换到 `whisper` 模式
> 注意:
1. whisper 模式下需要到 HuggingFace 下载一个模型文件,大约 3GB 左右,请确保网络通畅
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
```

View File

@ -1,7 +0,0 @@
## 感谢赞助 🙏
感谢佐糖 https://picwish.cn 对该项目的支持和赞助,使得该项目能够持续的更新和维护。
佐糖专注于**图像处理领域**,提供丰富的**图像处理工具**,将复杂操作极致简化,真正实现让图像处理更简单。
![picwish.jpg](/picwish.jpg)

View File

@ -1,37 +0,0 @@
## 视频演示 📺
### 竖屏 9:16
<table>
<thead>
<tr>
<th align="center"><g-emoji class="g-emoji" alias="arrow_forward">▶️</g-emoji> 《如何增加生活的乐趣》</th>
<th align="center"><g-emoji class="g-emoji" alias="arrow_forward">▶️</g-emoji> 《金钱的作用》<br>更真实的合成声音</th>
<th align="center"><g-emoji class="g-emoji" alias="arrow_forward">▶️</g-emoji> 《生命的意义是什么》</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><video src="https://github.com/harry0703/MoneyPrinterTurbo/assets/4928832/a84d33d5-27a2-4aba-8fd0-9fb2bd91c6a6"></video></td>
<td align="center"><video src="https://github.com/harry0703/MoneyPrinterTurbo/assets/4928832/af2f3b0b-002e-49fe-b161-18ba91c055e8"></video></td>
<td align="center"><video src="https://github.com/harry0703/MoneyPrinterTurbo/assets/4928832/112c9564-d52b-4472-99ad-970b75f66476"></video></td>
</tr>
</tbody>
</table>
### 横屏 16:9
<table>
<thead>
<tr>
<th align="center"><g-emoji class="g-emoji" alias="arrow_forward">▶️</g-emoji>《生命的意义是什么》</th>
<th align="center"><g-emoji class="g-emoji" alias="arrow_forward">▶️</g-emoji>《为什么要运动》</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><video src="https://github.com/harry0703/MoneyPrinterTurbo/assets/4928832/346ebb15-c55f-47a9-a653-114f08bb8073"></video></td>
<td align="center"><video src="https://github.com/harry0703/MoneyPrinterTurbo/assets/4928832/271f2fae-8283-44a0-8aa0-0ed8f9a6fa87"></video></td>
</tr>
</tbody>
</table>

View File

@ -1,24 +0,0 @@
{
"name": "MoneyPrinterTurbo",
"version": "1.1.2",
"description": "利用AI大模型一键生成高清短视频 Generate short videos with one click using AI LLM.",
"main": "index.js",
"repository": "https://github.com/harry0703/MoneyPrinterTurbo",
"author": "harry0703",
"license": "MIT",
"devDependencies": {
"@vue/tsconfig": "^0.1.3",
"@vuepress/bundler-vite": "2.0.0-rc.9",
"@vuepress/theme-default": "2.0.0-rc.25",
"gh-pages": "^6.1.1",
"vue": "^3.4.23",
"vue-router": "^4.3.1",
"vuepress": "2.0.0-rc.9"
},
"scripts": {
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs",
"predeploy": "pnpm docs:build",
"deploy": "gh-pages -d docs/.vuepress/dist"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*"
],
"compilerOptions": {
"composite": true,
"types": [
"node"
]
}
}

View File

@ -1,24 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"module": "esnext",
"include": [
"env.d.ts",
"src/**/*",
"src/**/*.vue",
"docs/.vuepress/*.ts"
],
"compilerOptions": {
"baseUrl": ".",
"isolatedModules": true,
"paths": {
"@/*": [
"./src/*"
]
}
},
"references": [
{
"path": "./tsconfig.config.json"
}
]
}

40
test/README.md Normal file
View File

@ -0,0 +1,40 @@
# MoneyPrinterTurbo Test Directory
This directory contains unit tests for the **MoneyPrinterTurbo** project.
## Directory Structure
- `services/`: Tests for components in the `app/services` directory
- `test_video.py`: Tests for the video service
- `test_task.py`: Tests for the task service
- `test_voice.py`: Tests for the voice service
## Running Tests
You can run the tests using Pythons built-in `unittest` framework:
```bash
# Run all tests
python -m unittest discover -s test
# Run a specific test file
python -m unittest test/services/test_video.py
# Run a specific test class
python -m unittest test.services.test_video.TestVideoService
# Run a specific test method
python -m unittest test.services.test_video.TestVideoService.test_preprocess_video
````
## Adding New Tests
To add tests for other components, follow these guidelines:
1. Create test files prefixed with `test_` in the appropriate subdirectory
2. Use `unittest.TestCase` as the base class for your test classes
3. Name test methods with the `test_` prefix
## Test Resources
Place any resource files required for testing in the `test/resources` directory.

1
test/__init__.py Normal file
View File

@ -0,0 +1 @@
# Unit test package for test

BIN
test/resources/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
test/resources/1.png.mp4 Normal file

Binary file not shown.

BIN
test/resources/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
test/resources/2.png.mp4 Normal file

Binary file not shown.

BIN
test/resources/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
test/resources/3.png.mp4 Normal file

Binary file not shown.

BIN
test/resources/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
test/resources/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
test/resources/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
test/resources/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
test/resources/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Some files were not shown because too many files have changed in this diff Show More