Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09ad53a60e | ||
|
|
bb4333db95 | ||
|
|
dc460c25d2 | ||
|
|
4ca8d8d8ae | ||
|
|
37e56239d9 | ||
|
|
ba8613baeb | ||
|
|
fd8ed3bd69 | ||
|
|
1b8b65f642 | ||
|
|
2128a5fae5 | ||
|
|
2976652509 | ||
|
|
eb2b07b615 | ||
|
|
289c06799a | ||
|
|
afb4eff3e5 | ||
|
|
bdf7af0a12 | ||
|
|
ee931d1933 | ||
|
|
cbd3495426 | ||
|
|
a864be83da | ||
|
|
fee226a149 | ||
|
|
b3f549b4db | ||
|
|
4c21a23a8b | ||
|
|
8d84b5b530 | ||
|
|
6de3d6eedc | ||
|
|
ee680d24cc | ||
|
|
fb3aadeccc | ||
|
|
5254577e5c | ||
|
|
c15a3bdd13 | ||
|
|
d8d8a5b602 | ||
|
|
e258617d4f | ||
|
|
ce64602c08 | ||
|
|
f729124a48 | ||
|
|
367018b3f7 | ||
|
|
b055755689 | ||
|
|
c8894a851b | ||
|
|
28c5bc372f | ||
|
|
7ed2603442 | ||
|
|
1f8b41d2b3 | ||
|
|
ee09cf64d5 | ||
|
|
4e886a1a73 | ||
|
|
661d8cb5ab | ||
|
|
4de02f4429 | ||
|
|
5d06530a39 | ||
|
|
4596804bcf | ||
|
|
c161ab3124 | ||
|
|
376955d4a0 | ||
|
|
b08b79f9cf | ||
|
|
7582022fa9 | ||
|
|
8dbce05344 | ||
|
|
4d91a83858 | ||
|
|
8e93dd7ca0 | ||
|
|
d2277715df | ||
|
|
8861526e0a | ||
|
|
f38fc60394 | ||
|
|
e77bff3ffb | ||
|
|
59a518ce9d | ||
|
|
d922ff2576 | ||
|
|
84b3ef13c0 | ||
|
|
a53da162ac | ||
|
|
e77389ffb5 | ||
|
|
2be09365d9 | ||
|
|
f85c11118d | ||
|
|
419abd760e | ||
|
|
5c0a905c09 | ||
|
|
3c5ef29775 | ||
|
|
bc45d4bcf3 | ||
|
|
e38c79bfad | ||
|
|
c7c7b4847e | ||
|
|
6a5c4e9e73 | ||
|
|
2eb6d4b5cc | ||
|
|
13f3abffd0 | ||
|
|
6164920eaa | ||
|
|
a8d528f41c | ||
|
|
259b3e94fc | ||
|
|
ffcb52fd46 | ||
|
|
ee4337e847 | ||
|
|
16bcec96ee | ||
|
|
cc231e0b62 | ||
|
|
cb2c53334e | ||
|
|
643b0fb30c | ||
|
|
6b97e7dbd4 | ||
|
|
4804aa1e04 | ||
|
|
c04e3988fe | ||
|
|
c9eb16e0a9 | ||
|
|
2d599db892 | ||
|
|
ec50cd0184 | ||
|
|
a1a1a51881 | ||
|
|
0066bab3ec | ||
|
|
2f461d961c | ||
|
|
77c250ce18 | ||
|
|
3f941c8dd9 | ||
|
|
ab5ae7072b | ||
|
|
93da539519 | ||
|
|
5280159f41 | ||
|
|
729c407c30 | ||
|
|
73ec0cf7ad | ||
|
|
a5273b31b3 | ||
|
|
f83c374ef4 | ||
|
|
da010c476f | ||
|
|
78f5ce7cdd | ||
|
|
27553114b0 | ||
|
|
4ae022c059 | ||
|
|
70ed2b5c82 | ||
|
|
89f001742a | ||
|
|
6c8b4f665d | ||
|
|
1c35e50563 | ||
|
|
add34a92f7 | ||
|
|
a44016a7cf | ||
|
|
0521d46826 | ||
|
|
f48fb24dcf | ||
|
|
996cd55462 | ||
|
|
97640d2199 | ||
|
|
f6857d63f9 | ||
|
|
abe12abd7b | ||
|
|
7e8c901fd4 | ||
|
|
1cee2bbb8d | ||
|
|
4d176c6107 | ||
|
|
e499f8a1c4 | ||
|
|
d82b5f76e0 | ||
|
|
1ac643f4d0 | ||
|
|
fea396585f | ||
|
|
3fe6ff42c8 | ||
|
|
a71e54fd7c | ||
|
|
fee660fb8c |
33
.github/workflows/gh-pages.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
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 }}
|
||||||
10
.gitignore
vendored
@@ -11,4 +11,12 @@
|
|||||||
.vscode
|
.vscode
|
||||||
/**/.streamlit
|
/**/.streamlit
|
||||||
__pycache__
|
__pycache__
|
||||||
logs/
|
logs/
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
# VuePress 默认临时文件目录
|
||||||
|
/sites/docs/.vuepress/.temp
|
||||||
|
# VuePress 默认缓存目录
|
||||||
|
/sites/docs/.vuepress/.cache
|
||||||
|
# VuePress 默认构建生成的静态文件目录
|
||||||
|
/sites/docs/.vuepress/dist
|
||||||
|
|||||||
22
CHANGELOG.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<!-- 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).
|
||||||
12
Dockerfile
@@ -1,9 +1,12 @@
|
|||||||
# Use an official Python runtime as a parent image
|
# Use an official Python runtime as a parent image
|
||||||
FROM python:3.10-slim
|
FROM python:3.10-slim-bullseye
|
||||||
|
|
||||||
# Set the working directory in the container
|
# Set the working directory in the container
|
||||||
WORKDIR /MoneyPrinterTurbo
|
WORKDIR /MoneyPrinterTurbo
|
||||||
|
|
||||||
|
# 设置/MoneyPrinterTurbo目录权限为777
|
||||||
|
RUN chmod 777 /MoneyPrinterTurbo
|
||||||
|
|
||||||
ENV PYTHONPATH="/MoneyPrinterTurbo"
|
ENV PYTHONPATH="/MoneyPrinterTurbo"
|
||||||
|
|
||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
@@ -16,12 +19,15 @@ RUN apt-get update && apt-get install -y \
|
|||||||
# Fix security policy for ImageMagick
|
# Fix security policy for ImageMagick
|
||||||
RUN sed -i '/<policy domain="path" rights="none" pattern="@\*"/d' /etc/ImageMagick-6/policy.xml
|
RUN sed -i '/<policy domain="path" rights="none" pattern="@\*"/d' /etc/ImageMagick-6/policy.xml
|
||||||
|
|
||||||
# Copy the current directory contents into the container at /MoneyPrinterTurbo
|
# Copy only the requirements.txt first to leverage Docker cache
|
||||||
COPY . .
|
COPY requirements.txt ./
|
||||||
|
|
||||||
# Install Python dependencies
|
# Install Python dependencies
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Now copy the rest of the codebase into the image
|
||||||
|
COPY . .
|
||||||
|
|
||||||
# Expose the port the app runs on
|
# Expose the port the app runs on
|
||||||
EXPOSE 8501
|
EXPOSE 8501
|
||||||
|
|
||||||
|
|||||||
42
README-en.md
@@ -10,9 +10,9 @@
|
|||||||
|
|
||||||
<h3>English | <a href="README.md">简体中文</a></h3>
|
<h3>English | <a href="README.md">简体中文</a></h3>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
> Thanks to [RootFTW](https://github.com/Root-FTW) for the translation
|
<a href="https://trendshift.io/repositories/8731" target="_blank"><img src="https://trendshift.io/api/badge/repositories/8731" alt="harry0703%2FMoneyPrinterTurbo | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
Simply provide a <b>topic</b> or <b>keyword</b> for a video, and it will automatically generate the video copy, video
|
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.
|
materials, video subtitles, and video background music before synthesizing a high-definition short video.
|
||||||
@@ -59,8 +59,7 @@ https://reccloud.com
|
|||||||
- [x] Supports integration with various models such as **OpenAI**, **moonshot**, **Azure**, **gpt4free**, **one-api**,
|
- [x] Supports integration with various models such as **OpenAI**, **moonshot**, **Azure**, **gpt4free**, **one-api**,
|
||||||
**qianwen**, **Google Gemini**, **Ollama** and more
|
**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-)
|
❓[How to Use the Free OpenAI GPT-3.5 Model?](https://github.com/harry0703/MoneyPrinterTurbo/blob/main/README-en.md#common-questions-)
|
||||||
|
|
||||||
|
|
||||||
### Future Plans 📅
|
### Future Plans 📅
|
||||||
|
|
||||||
@@ -111,6 +110,11 @@ https://reccloud.com
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
## System Requirements 📦
|
||||||
|
|
||||||
|
- Recommended minimum 4 CPU cores or more, 8G of memory or more, GPU is not required
|
||||||
|
- Windows 10 or MacOS 11.0, and their later versions
|
||||||
|
|
||||||
## Installation & Deployment 📥
|
## Installation & Deployment 📥
|
||||||
|
|
||||||
- Try to avoid using **Chinese paths** to prevent unpredictable issues
|
- Try to avoid using **Chinese paths** to prevent unpredictable issues
|
||||||
@@ -245,8 +249,28 @@ You can switch between them by modifying the `subtitle_provider` in the `config.
|
|||||||
It is recommended to use `edge` mode, and switch to `whisper` mode if the quality of the subtitles generated is not
|
It is recommended to use `edge` mode, and switch to `whisper` mode if the quality of the subtitles generated is not
|
||||||
satisfactory.
|
satisfactory.
|
||||||
|
|
||||||
|
> Note:
|
||||||
> If left blank, it means no subtitles will be generated.
|
> If left blank, it means no subtitles will be generated.
|
||||||
|
|
||||||
|
**Download whisper**
|
||||||
|
- Please ensure a good internet connectivity
|
||||||
|
- `whisper` model can be downloaded from HuggingFace: https://huggingface.co/openai/whisper-large-v3/tree/main
|
||||||
|
|
||||||
|
After downloading the model to local machine, copy the whole folder and put it into the following path: `.\MoneyPrinterTurbo\models`
|
||||||
|
|
||||||
|
This is what the final path should look like: `.\MoneyPrinterTurbo\models\whisper-large-v3`
|
||||||
|
|
||||||
|
```
|
||||||
|
MoneyPrinterTurbo
|
||||||
|
├─models
|
||||||
|
│ └─whisper-large-v3
|
||||||
|
│ config.json
|
||||||
|
│ model.bin
|
||||||
|
│ preprocessor_config.json
|
||||||
|
│ tokenizer.json
|
||||||
|
│ vocabulary.json
|
||||||
|
```
|
||||||
|
|
||||||
## Background Music 🎵
|
## Background Music 🎵
|
||||||
|
|
||||||
Background music for videos is located in the project's `resource/songs` directory.
|
Background music for videos is located in the project's `resource/songs` directory.
|
||||||
@@ -261,12 +285,16 @@ own fonts.
|
|||||||
## Common Questions 🤔
|
## Common Questions 🤔
|
||||||
|
|
||||||
### ❓How to Use the Free OpenAI GPT-3.5 Model?
|
### ❓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.
|
|
||||||
|
[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:
|
**Ensure you have Docker installed and running**. Execute the following command to start the Docker service:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker run -p 3040:3040 missuo/freegpt35
|
docker run -p 3040:3040 missuo/freegpt35
|
||||||
```
|
```
|
||||||
|
|
||||||
Once successfully started, modify the `config.toml` configuration as follows:
|
Once successfully started, modify the `config.toml` configuration as follows:
|
||||||
|
|
||||||
- Set `llm_provider` to `openai`
|
- Set `llm_provider` to `openai`
|
||||||
@@ -341,4 +369,4 @@ Click to view the [`LICENSE`](LICENSE) file
|
|||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
[](https://star-history.com/#harry0703/MoneyPrinterTurbo&Date)
|
[](https://star-history.com/#harry0703/MoneyPrinterTurbo&Date)
|
||||||
|
|||||||
187
README.md
@@ -9,6 +9,9 @@
|
|||||||
</p>
|
</p>
|
||||||
<br>
|
<br>
|
||||||
<h3>简体中文 | <a href="README-en.md">English</a></h3>
|
<h3>简体中文 | <a href="README-en.md">English</a></h3>
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://trendshift.io/repositories/8731" target="_blank"><img src="https://trendshift.io/api/badge/repositories/8731" alt="harry0703%2FMoneyPrinterTurbo | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
</div>
|
||||||
<br>
|
<br>
|
||||||
只需提供一个视频 <b>主题</b> 或 <b>关键词</b> ,就可以全自动生成视频文案、视频素材、视频字幕、视频背景音乐,然后合成一个高清的短视频。
|
只需提供一个视频 <b>主题</b> 或 <b>关键词</b> ,就可以全自动生成视频文案、视频素材、视频字幕、视频背景音乐,然后合成一个高清的短视频。
|
||||||
<br>
|
<br>
|
||||||
@@ -26,7 +29,6 @@
|
|||||||
## 特别感谢 🙏
|
## 特别感谢 🙏
|
||||||
|
|
||||||
由于该项目的 **部署** 和 **使用**,对于一些小白用户来说,还是 **有一定的门槛**,在此特别感谢
|
由于该项目的 **部署** 和 **使用**,对于一些小白用户来说,还是 **有一定的门槛**,在此特别感谢
|
||||||
|
|
||||||
**录咖(AI智能 多媒体服务平台)** 网站基于该项目,提供的免费`AI视频生成器`服务,可以不用部署,直接在线使用,非常方便。
|
**录咖(AI智能 多媒体服务平台)** 网站基于该项目,提供的免费`AI视频生成器`服务,可以不用部署,直接在线使用,非常方便。
|
||||||
|
|
||||||
- 中文版:https://reccloud.cn
|
- 中文版:https://reccloud.cn
|
||||||
@@ -34,6 +36,14 @@
|
|||||||
|
|
||||||

|

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

|
||||||
|
|
||||||
## 功能特性 🎯
|
## 功能特性 🎯
|
||||||
|
|
||||||
- [x] 完整的 **MVC架构**,代码 **结构清晰**,易于维护,支持 `API` 和 `Web界面`
|
- [x] 完整的 **MVC架构**,代码 **结构清晰**,易于维护,支持 `API` 和 `Web界面`
|
||||||
@@ -42,15 +52,15 @@
|
|||||||
- [x] 竖屏 9:16,`1080x1920`
|
- [x] 竖屏 9:16,`1080x1920`
|
||||||
- [x] 横屏 16:9,`1920x1080`
|
- [x] 横屏 16:9,`1920x1080`
|
||||||
- [x] 支持 **批量视频生成**,可以一次生成多个视频,然后选择一个最满意的
|
- [x] 支持 **批量视频生成**,可以一次生成多个视频,然后选择一个最满意的
|
||||||
- [x] 支持 **视频片段时长**设置,方便调节素材切换频率
|
- [x] 支持 **视频片段时长** 设置,方便调节素材切换频率
|
||||||
- [x] 支持 **中文** 和 **英文** 视频文案
|
- [x] 支持 **中文** 和 **英文** 视频文案
|
||||||
- [x] 支持 **多种语音** 合成
|
- [x] 支持 **多种语音** 合成,可 **实时试听** 效果
|
||||||
- [x] 支持 **字幕生成**,可以调整 `字体`、`位置`、`颜色`、`大小`,同时支持`字幕描边`设置
|
- [x] 支持 **字幕生成**,可以调整 `字体`、`位置`、`颜色`、`大小`,同时支持`字幕描边`设置
|
||||||
- [x] 支持 **背景音乐**,随机或者指定音乐文件,可设置`背景音乐音量`
|
- [x] 支持 **背景音乐**,随机或者指定音乐文件,可设置`背景音乐音量`
|
||||||
- [x] 视频素材来源 **高清**,而且 **无版权**
|
- [x] 视频素材来源 **高清**,而且 **无版权**,也可以使用自己的 **本地素材**
|
||||||
- [x] 支持 **OpenAI**、**moonshot**、**Azure**、**gpt4free**、**one-api**、**通义千问**、**Google Gemini**、**Ollama** 等多种模型接入
|
- [x] 支持 **OpenAI**、**Moonshot**、**Azure**、**gpt4free**、**one-api**、**通义千问**、**Google Gemini**、**Ollama**、
|
||||||
|
**DeepSeek** 等多种模型接入
|
||||||
❓[如何使用免费的 **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-)
|
- 中国用户建议使用 **DeepSeek** 或 **Moonshot** 作为大模型提供商(国内可直接访问,不需要VPN。注册就送额度,基本够用)
|
||||||
|
|
||||||
### 后期计划 📅
|
### 后期计划 📅
|
||||||
|
|
||||||
@@ -59,15 +69,12 @@
|
|||||||
- [ ] 增加视频转场效果,使其看起来更加的流畅
|
- [ ] 增加视频转场效果,使其看起来更加的流畅
|
||||||
- [ ] 增加更多视频素材来源,优化视频素材和文案的匹配度
|
- [ ] 增加更多视频素材来源,优化视频素材和文案的匹配度
|
||||||
- [ ] 增加视频长度选项:短、中、长
|
- [ ] 增加视频长度选项:短、中、长
|
||||||
- [ ] 打包成一键启动包(Windows,macOS),方便使用
|
- [ ] 支持更多的语音合成服务商,比如 OpenAI TTS
|
||||||
- [ ] 增加免费网络代理,让访问OpenAI和素材下载不再受限
|
|
||||||
- [ ] 可以使用自己的素材
|
|
||||||
- [ ] 朗读声音和背景音乐,提供实时试听
|
|
||||||
- [ ] 支持更多的语音合成服务商,比如 OpenAI TTS, Azure TTS
|
|
||||||
- [ ] 自动上传到YouTube平台
|
- [ ] 自动上传到YouTube平台
|
||||||
|
|
||||||
## 交流讨论 💬
|
## 交流讨论 💬
|
||||||
<img src="docs/wechat-03.jpg" width="300">
|
|
||||||
|
<img src="docs/wechat-group.jpg" width="250">
|
||||||
|
|
||||||
## 视频演示 📺
|
## 视频演示 📺
|
||||||
|
|
||||||
@@ -77,12 +84,14 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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>
|
||||||
|
<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>
|
<th align="center"><g-emoji class="g-emoji" alias="arrow_forward">▶️</g-emoji> 《生命的意义是什么》</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<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/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>
|
<td align="center"><video src="https://github.com/harry0703/MoneyPrinterTurbo/assets/4928832/112c9564-d52b-4472-99ad-970b75f66476"></video></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -106,16 +115,30 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
## 配置要求 📦
|
## 配置要求 📦
|
||||||
|
|
||||||
- 建议最低 CPU 4核或以上,内存 8G 或以上,显卡非必须
|
- 建议最低 CPU 4核或以上,内存 8G 或以上,显卡非必须
|
||||||
- Windows 10 或 MacOS 11.0 以上系统
|
- Windows 10 或 MacOS 11.0 以上系统
|
||||||
|
|
||||||
|
## 快速开始 🚀
|
||||||
|
|
||||||
|
下载一键启动包,解压直接使用(路径不要有 **中文** 和 **空格**)
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
- 百度网盘: https://pan.baidu.com/s/1jKF1mgsjfN8fBk6uTEHArQ?pwd=jrp7 提取码: jrp7
|
||||||
|
|
||||||
|
下载后,建议先**双击执行** `update.bat` 更新到**最新代码**,然后双击 `start.bat` 启动
|
||||||
|
|
||||||
|
启动后,会自动打开浏览器(如果打开是空白,建议换成 **Chrome** 或者 **Edge** 打开)
|
||||||
|
|
||||||
|
### 其他系统
|
||||||
|
|
||||||
|
还没有制作一键启动包,看下面的 **安装部署** 部分,建议使用 **docker** 部署,更加方便。
|
||||||
|
|
||||||
## 安装部署 📥
|
## 安装部署 📥
|
||||||
|
|
||||||
> 不想部署的可以直接下载安装包,解压直接使用
|
|
||||||
- **Windows** 版本下载地址
|
|
||||||
- 百度网盘: https://pan.baidu.com/s/1BB3SGtAFTytzFLS5t2d8Gg?pwd=5bry
|
|
||||||
|
|
||||||
### 前提条件
|
### 前提条件
|
||||||
|
|
||||||
- 尽量不要使用 **中文路径**,避免出现一些无法预料的问题
|
- 尽量不要使用 **中文路径**,避免出现一些无法预料的问题
|
||||||
- 请确保你的 **网络** 是正常的,VPN需要打开`全局流量`模式
|
- 请确保你的 **网络** 是正常的,VPN需要打开`全局流量`模式
|
||||||
|
|
||||||
@@ -131,14 +154,6 @@ git clone https://github.com/harry0703/MoneyPrinterTurbo.git
|
|||||||
- 按照 `config.toml` 文件中的说明,配置好 `pexels_api_keys` 和 `llm_provider`,并根据 llm_provider 对应的服务商,配置相关的
|
- 按照 `config.toml` 文件中的说明,配置好 `pexels_api_keys` 和 `llm_provider`,并根据 llm_provider 对应的服务商,配置相关的
|
||||||
API Key
|
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
|
#### ① 启动Docker
|
||||||
@@ -146,6 +161,7 @@ git clone https://github.com/harry0703/MoneyPrinterTurbo.git
|
|||||||
如果未安装 Docker,请先安装 https://www.docker.com/products/docker-desktop/
|
如果未安装 Docker,请先安装 https://www.docker.com/products/docker-desktop/
|
||||||
|
|
||||||
如果是Windows系统,请参考微软的文档:
|
如果是Windows系统,请参考微软的文档:
|
||||||
|
|
||||||
1. https://learn.microsoft.com/zh-cn/windows/wsl/install
|
1. https://learn.microsoft.com/zh-cn/windows/wsl/install
|
||||||
2. https://learn.microsoft.com/zh-cn/windows/wsl/tutorials/wsl-containers
|
2. https://learn.microsoft.com/zh-cn/windows/wsl/tutorials/wsl-containers
|
||||||
|
|
||||||
@@ -183,29 +199,24 @@ pip install -r requirements.txt
|
|||||||
|
|
||||||
#### ② 安装好 ImageMagick
|
#### ② 安装好 ImageMagick
|
||||||
|
|
||||||
###### Windows:
|
- Windows:
|
||||||
|
- 下载 https://imagemagick.org/script/download.php 选择Windows版本,切记一定要选择 **静态库** 版本,比如
|
||||||
|
ImageMagick-7.1.1-32-Q16-x64-**static**.exe
|
||||||
|
- 安装下载好的 ImageMagick,**注意不要修改安装路径**
|
||||||
|
- 修改 `配置文件 config.toml` 中的 `imagemagick_path` 为你的 **实际安装路径**
|
||||||
|
|
||||||
- 下载 https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-30-Q16-x64-static.exe
|
- MacOS:
|
||||||
- 安装下载好的 ImageMagick,注意不要修改安装路径
|
```shell
|
||||||
- 修改 `配置文件 config.toml` 中的 `imagemagick_path` 为你的实际安装路径(如果安装的时候没有修改路径,直接取消注释即可)
|
brew install imagemagick
|
||||||
|
````
|
||||||
###### MacOS:
|
- Ubuntu
|
||||||
|
```shell
|
||||||
```shell
|
sudo apt-get install imagemagick
|
||||||
brew install imagemagick
|
```
|
||||||
````
|
- CentOS
|
||||||
|
```shell
|
||||||
###### Ubuntu
|
sudo yum install ImageMagick
|
||||||
|
```
|
||||||
```shell
|
|
||||||
sudo apt-get install imagemagick
|
|
||||||
```
|
|
||||||
|
|
||||||
###### CentOS
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo yum install ImageMagick
|
|
||||||
```
|
|
||||||
|
|
||||||
#### ③ 启动Web界面 🌐
|
#### ③ 启动Web界面 🌐
|
||||||
|
|
||||||
@@ -224,7 +235,8 @@ webui.bat
|
|||||||
conda activate MoneyPrinterTurbo
|
conda activate MoneyPrinterTurbo
|
||||||
sh webui.sh
|
sh webui.sh
|
||||||
```
|
```
|
||||||
启动后,会自动打开浏览器
|
|
||||||
|
启动后,会自动打开浏览器(如果打开是空白,建议换成 **Chrome** 或者 **Edge** 打开)
|
||||||
|
|
||||||
#### ④ 启动API服务 🚀
|
#### ④ 启动API服务 🚀
|
||||||
|
|
||||||
@@ -238,6 +250,8 @@ python main.py
|
|||||||
|
|
||||||
所有支持的声音列表,可以查看:[声音列表](./docs/voice-list.txt)
|
所有支持的声音列表,可以查看:[声音列表](./docs/voice-list.txt)
|
||||||
|
|
||||||
|
2024-04-16 v1.1.2 新增了9种Azure的语音合成声音,需要配置API KEY,该声音合成的更加真实。
|
||||||
|
|
||||||
## 字幕生成 📜
|
## 字幕生成 📜
|
||||||
|
|
||||||
当前支持2种字幕生成方式:
|
当前支持2种字幕生成方式:
|
||||||
@@ -250,17 +264,20 @@ python main.py
|
|||||||
建议使用 `edge` 模式,如果生成的字幕质量不好,再切换到 `whisper` 模式
|
建议使用 `edge` 模式,如果生成的字幕质量不好,再切换到 `whisper` 模式
|
||||||
|
|
||||||
> 注意:
|
> 注意:
|
||||||
|
|
||||||
1. whisper 模式下需要到 HuggingFace 下载一个模型文件,大约 3GB 左右,请确保网络通畅
|
1. whisper 模式下需要到 HuggingFace 下载一个模型文件,大约 3GB 左右,请确保网络通畅
|
||||||
2. 如果留空,表示不生成字幕。
|
2. 如果留空,表示不生成字幕。
|
||||||
|
|
||||||
> 由于国内无法访问 HuggingFace,可以使用以下方法下载 `whisper-large-v3` 的模型文件
|
> 由于国内无法访问 HuggingFace,可以使用以下方法下载 `whisper-large-v3` 的模型文件
|
||||||
|
|
||||||
下载地址:
|
下载地址:
|
||||||
|
|
||||||
- 百度网盘: https://pan.baidu.com/s/11h3Q6tsDtjQKTjUu3sc5cA?pwd=xjs9
|
- 百度网盘: https://pan.baidu.com/s/11h3Q6tsDtjQKTjUu3sc5cA?pwd=xjs9
|
||||||
- 夸克网盘:https://pan.quark.cn/s/3ee3d991d64b
|
- 夸克网盘:https://pan.quark.cn/s/3ee3d991d64b
|
||||||
|
|
||||||
模型下载后解压,整个目录放到 `.\MoneyPrinterTurbo\models` 里面,
|
模型下载后解压,整个目录放到 `.\MoneyPrinterTurbo\models` 里面,
|
||||||
最终的文件路径应该是这样: `.\MoneyPrinterTurbo\models\whisper-large-v3`
|
最终的文件路径应该是这样: `.\MoneyPrinterTurbo\models\whisper-large-v3`
|
||||||
|
|
||||||
```
|
```
|
||||||
MoneyPrinterTurbo
|
MoneyPrinterTurbo
|
||||||
├─models
|
├─models
|
||||||
@@ -284,25 +301,32 @@ MoneyPrinterTurbo
|
|||||||
## 常见问题 🤔
|
## 常见问题 🤔
|
||||||
|
|
||||||
### ❓如何使用免费的OpenAI GPT-3.5模型?
|
### ❓如何使用免费的OpenAI GPT-3.5模型?
|
||||||
|
|
||||||
[OpenAI宣布ChatGPT里面3.5已经免费了](https://openai.com/blog/start-using-chatgpt-instantly),有开发者将其封装成了API,可以直接调用
|
[OpenAI宣布ChatGPT里面3.5已经免费了](https://openai.com/blog/start-using-chatgpt-instantly),有开发者将其封装成了API,可以直接调用
|
||||||
|
|
||||||
**确保你安装和启动了docker服务**,执行以下命令启动docker服务
|
**确保你安装和启动了docker服务**,执行以下命令启动docker服务
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker run -p 3040:3040 missuo/freegpt35
|
docker run -p 3040:3040 missuo/freegpt35
|
||||||
```
|
```
|
||||||
|
|
||||||
启动成功后,修改 `config.toml` 中的配置
|
启动成功后,修改 `config.toml` 中的配置
|
||||||
|
|
||||||
- `llm_provider` 设置为 `openai`
|
- `llm_provider` 设置为 `openai`
|
||||||
- `openai_api_key` 随便填写一个即可,比如 '123456'
|
- `openai_api_key` 随便填写一个即可,比如 '123456'
|
||||||
- `openai_base_url` 改为 `http://localhost:3040/v1/`
|
- `openai_base_url` 改为 `http://localhost:3040/v1/`
|
||||||
- `openai_model_name` 改为 `gpt-3.5-turbo`
|
- `openai_model_name` 改为 `gpt-3.5-turbo`
|
||||||
|
|
||||||
|
> 注意:该方式稳定性较差
|
||||||
|
|
||||||
### ❓AttributeError: 'str' object has no attribute 'choices'`
|
### ❓AttributeError: 'str' object has no attribute 'choices'`
|
||||||
|
|
||||||
这个问题是由于 OpenAI 或者其他 LLM,没有返回正确的回复导致的。
|
这个问题是由于大模型没有返回正确的回复导致的。
|
||||||
|
|
||||||
大概率是网络原因, 使用 **VPN**,或者设置 `openai_base_url` 为你的代理 ,应该就可以解决了。
|
大概率是网络原因, 使用 **VPN**,或者设置 `openai_base_url` 为你的代理 ,应该就可以解决了。
|
||||||
|
|
||||||
|
同时建议使用 **Moonshot** 或 **DeepSeek** 作为大模型提供商,这两个服务商在国内访问速度更快,更加稳定。
|
||||||
|
|
||||||
### ❓RuntimeError: No ffmpeg exe could be found
|
### ❓RuntimeError: No ffmpeg exe could be found
|
||||||
|
|
||||||
通常情况下,ffmpeg 会被自动下载,并且会被自动检测到。
|
通常情况下,ffmpeg 会被自动下载,并且会被自动检测到。
|
||||||
@@ -321,52 +345,14 @@ Install ffmpeg on your system, or set the IMAGEIO_FFMPEG_EXE environment variabl
|
|||||||
ffmpeg_path = "C:\\Users\\harry\\Downloads\\ffmpeg.exe"
|
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相关的操作
|
### ❓ImageMagick的安全策略阻止了与临时文件@/tmp/tmpur5hyyto.txt相关的操作
|
||||||
|
|
||||||
[issue 92](https://github.com/harry0703/MoneyPrinterTurbo/issues/92)
|
|
||||||
|
|
||||||
可以在ImageMagick的配置文件policy.xml中找到这些策略。
|
可以在ImageMagick的配置文件policy.xml中找到这些策略。
|
||||||
这个文件通常位于 /etc/ImageMagick-`X`/ 或 ImageMagick 安装目录的类似位置。
|
这个文件通常位于 /etc/ImageMagick-`X`/ 或 ImageMagick 安装目录的类似位置。
|
||||||
修改包含`pattern="@"`的条目,将`rights="none"`更改为`rights="read|write"`以允许对文件的读写操作。
|
修改包含`pattern="@"`的条目,将`rights="none"`更改为`rights="read|write"`以允许对文件的读写操作。
|
||||||
|
|
||||||
感谢 [@chenhengzh](https://github.com/chenhengzh)的研究探索
|
|
||||||
|
|
||||||
### ❓OSError: [Errno 24] Too many open files
|
### ❓OSError: [Errno 24] Too many open files
|
||||||
|
|
||||||
[issue 100](https://github.com/harry0703/MoneyPrinterTurbo/issues/100)
|
|
||||||
|
|
||||||
这个问题是由于系统打开文件数限制导致的,可以通过修改系统的文件打开数限制来解决。
|
这个问题是由于系统打开文件数限制导致的,可以通过修改系统的文件打开数限制来解决。
|
||||||
|
|
||||||
查看当前限制
|
查看当前限制
|
||||||
@@ -381,26 +367,20 @@ ulimit -n
|
|||||||
ulimit -n 10240
|
ulimit -n 10240
|
||||||
```
|
```
|
||||||
|
|
||||||
### ❓AttributeError: module 'PIL.Image' has no attribute 'ANTIALIAS'
|
### ❓Whisper 模型下载失败,出现如下错误
|
||||||
|
|
||||||
[issue 101](https://github.com/harry0703/MoneyPrinterTurbo/issues/101),
|
LocalEntryNotfoundEror: Cannot find an appropriate cached snapshotfolderfor the specified revision on the local disk and
|
||||||
[issue 83](https://github.com/harry0703/MoneyPrinterTurbo/issues/83),
|
outgoing trafic has been disabled.
|
||||||
[issue 70](https://github.com/harry0703/MoneyPrinterTurbo/issues/70)
|
To enablerepo look-ups and downloads online, pass 'local files only=False' as input.
|
||||||
|
|
||||||
先看下当前的 Pillow 版本是多少
|
或者
|
||||||
|
|
||||||
```shell
|
An error occured while synchronizing the model Systran/faster-whisper-large-v3 from the Hugging Face Hub:
|
||||||
pip list |grep Pillow
|
An error happened while trying to locate the files on the Hub and we cannot find the appropriate snapshot folder for the
|
||||||
```
|
specified revision on the local disk. Please check your internet connection and try again.
|
||||||
|
Trying to load the model directly from the local cache, if it exists.
|
||||||
|
|
||||||
如果是 10.x 的版本,可以尝试下降级看看,有用户反馈降级后正常
|
解决方法:[点击查看如何从网盘手动下载模型](#%E5%AD%97%E5%B9%95%E7%94%9F%E6%88%90-)
|
||||||
|
|
||||||
```shell
|
|
||||||
pip uninstall Pillow
|
|
||||||
pip install Pillow==9.5.0
|
|
||||||
# 或者降级到 8.4.0
|
|
||||||
pip install Pillow==8.4.0
|
|
||||||
```
|
|
||||||
|
|
||||||
## 反馈建议 📢
|
## 反馈建议 📢
|
||||||
|
|
||||||
@@ -416,7 +396,6 @@ pip install Pillow==8.4.0
|
|||||||
|
|
||||||
点击查看 [`LICENSE`](LICENSE) 文件
|
点击查看 [`LICENSE`](LICENSE) 文件
|
||||||
|
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
[](https://star-history.com/#harry0703/MoneyPrinterTurbo&Date)
|
[](https://star-history.com/#harry0703/MoneyPrinterTurbo&Date)
|
||||||
@@ -34,8 +34,6 @@ def load_config():
|
|||||||
def save_config():
|
def save_config():
|
||||||
with open(config_file, "w", encoding="utf-8") as f:
|
with open(config_file, "w", encoding="utf-8") as f:
|
||||||
_cfg["app"] = app
|
_cfg["app"] = app
|
||||||
_cfg["whisper"] = whisper
|
|
||||||
_cfg["pexels"] = pexels
|
|
||||||
_cfg["azure"] = azure
|
_cfg["azure"] = azure
|
||||||
_cfg["ui"] = ui
|
_cfg["ui"] = ui
|
||||||
f.write(toml.dumps(_cfg))
|
f.write(toml.dumps(_cfg))
|
||||||
@@ -44,7 +42,7 @@ def save_config():
|
|||||||
_cfg = load_config()
|
_cfg = load_config()
|
||||||
app = _cfg.get("app", {})
|
app = _cfg.get("app", {})
|
||||||
whisper = _cfg.get("whisper", {})
|
whisper = _cfg.get("whisper", {})
|
||||||
pexels = _cfg.get("pexels", {})
|
proxy = _cfg.get("proxy", {})
|
||||||
azure = _cfg.get("azure", {})
|
azure = _cfg.get("azure", {})
|
||||||
ui = _cfg.get("ui", {})
|
ui = _cfg.get("ui", {})
|
||||||
|
|
||||||
@@ -56,7 +54,7 @@ listen_port = _cfg.get("listen_port", 8080)
|
|||||||
project_name = _cfg.get("project_name", "MoneyPrinterTurbo")
|
project_name = _cfg.get("project_name", "MoneyPrinterTurbo")
|
||||||
project_description = _cfg.get("project_description",
|
project_description = _cfg.get("project_description",
|
||||||
"<a href='https://github.com/harry0703/MoneyPrinterTurbo'>https://github.com/harry0703/MoneyPrinterTurbo</a>")
|
"<a href='https://github.com/harry0703/MoneyPrinterTurbo'>https://github.com/harry0703/MoneyPrinterTurbo</a>")
|
||||||
project_version = _cfg.get("project_version", "1.1.2")
|
project_version = _cfg.get("project_version", "1.1.9")
|
||||||
reload_debug = False
|
reload_debug = False
|
||||||
|
|
||||||
imagemagick_path = app.get("imagemagick_path", "")
|
imagemagick_path = app.get("imagemagick_path", "")
|
||||||
|
|||||||
57
app/controllers/manager/base_manager.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import threading
|
||||||
|
from typing import Callable, Any, Dict
|
||||||
|
|
||||||
|
|
||||||
|
class TaskManager:
|
||||||
|
def __init__(self, max_concurrent_tasks: int):
|
||||||
|
self.max_concurrent_tasks = max_concurrent_tasks
|
||||||
|
self.current_tasks = 0
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.queue = self.create_queue()
|
||||||
|
|
||||||
|
def create_queue(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def add_task(self, func: Callable, *args: Any, **kwargs: Any):
|
||||||
|
with self.lock:
|
||||||
|
if self.current_tasks < self.max_concurrent_tasks:
|
||||||
|
print(f"add task: {func.__name__}, current_tasks: {self.current_tasks}")
|
||||||
|
self.execute_task(func, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
print(f"enqueue task: {func.__name__}, current_tasks: {self.current_tasks}")
|
||||||
|
self.enqueue({"func": func, "args": args, "kwargs": kwargs})
|
||||||
|
|
||||||
|
def execute_task(self, func: Callable, *args: Any, **kwargs: Any):
|
||||||
|
thread = threading.Thread(target=self.run_task, args=(func, *args), kwargs=kwargs)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def run_task(self, func: Callable, *args: Any, **kwargs: Any):
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
self.current_tasks += 1
|
||||||
|
func(*args, **kwargs) # 在这里调用函数,传递*args和**kwargs
|
||||||
|
finally:
|
||||||
|
self.task_done()
|
||||||
|
|
||||||
|
def check_queue(self):
|
||||||
|
with self.lock:
|
||||||
|
if self.current_tasks < self.max_concurrent_tasks and not self.is_queue_empty():
|
||||||
|
task_info = self.dequeue()
|
||||||
|
func = task_info['func']
|
||||||
|
args = task_info.get('args', ())
|
||||||
|
kwargs = task_info.get('kwargs', {})
|
||||||
|
self.execute_task(func, *args, **kwargs)
|
||||||
|
|
||||||
|
def task_done(self):
|
||||||
|
with self.lock:
|
||||||
|
self.current_tasks -= 1
|
||||||
|
self.check_queue()
|
||||||
|
|
||||||
|
def enqueue(self, task: Dict):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def dequeue(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def is_queue_empty(self):
|
||||||
|
raise NotImplementedError()
|
||||||
18
app/controllers/manager/memory_manager.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from queue import Queue
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from app.controllers.manager.base_manager import TaskManager
|
||||||
|
|
||||||
|
|
||||||
|
class InMemoryTaskManager(TaskManager):
|
||||||
|
def create_queue(self):
|
||||||
|
return Queue()
|
||||||
|
|
||||||
|
def enqueue(self, task: Dict):
|
||||||
|
self.queue.put(task)
|
||||||
|
|
||||||
|
def dequeue(self):
|
||||||
|
return self.queue.get()
|
||||||
|
|
||||||
|
def is_queue_empty(self):
|
||||||
|
return self.queue.empty()
|
||||||
48
app/controllers/manager/redis_manager.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import json
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import redis
|
||||||
|
|
||||||
|
from app.controllers.manager.base_manager import TaskManager
|
||||||
|
from app.models.schema import VideoParams
|
||||||
|
from app.services import task as tm
|
||||||
|
|
||||||
|
FUNC_MAP = {
|
||||||
|
'start': tm.start,
|
||||||
|
# 'start_test': tm.start_test
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RedisTaskManager(TaskManager):
|
||||||
|
def __init__(self, max_concurrent_tasks: int, redis_url: str):
|
||||||
|
self.redis_client = redis.Redis.from_url(redis_url)
|
||||||
|
super().__init__(max_concurrent_tasks)
|
||||||
|
|
||||||
|
def create_queue(self):
|
||||||
|
return "task_queue"
|
||||||
|
|
||||||
|
def enqueue(self, task: Dict):
|
||||||
|
task_with_serializable_params = task.copy()
|
||||||
|
|
||||||
|
if 'params' in task['kwargs'] and isinstance(task['kwargs']['params'], VideoParams):
|
||||||
|
task_with_serializable_params['kwargs']['params'] = task['kwargs']['params'].dict()
|
||||||
|
|
||||||
|
# 将函数对象转换为其名称
|
||||||
|
task_with_serializable_params['func'] = task['func'].__name__
|
||||||
|
self.redis_client.rpush(self.queue, json.dumps(task_with_serializable_params))
|
||||||
|
|
||||||
|
def dequeue(self):
|
||||||
|
task_json = self.redis_client.lpop(self.queue)
|
||||||
|
if task_json:
|
||||||
|
task_info = json.loads(task_json)
|
||||||
|
# 将函数名称转换回函数对象
|
||||||
|
task_info['func'] = FUNC_MAP[task_info['func']]
|
||||||
|
|
||||||
|
if 'params' in task_info['kwargs'] and isinstance(task_info['kwargs']['params'], dict):
|
||||||
|
task_info['kwargs']['params'] = VideoParams(**task_info['kwargs']['params'])
|
||||||
|
|
||||||
|
return task_info
|
||||||
|
return None
|
||||||
|
|
||||||
|
def is_queue_empty(self):
|
||||||
|
return self.redis_client.llen(self.queue) == 0
|
||||||
@@ -10,6 +10,8 @@ from loguru import logger
|
|||||||
|
|
||||||
from app.config import config
|
from app.config import config
|
||||||
from app.controllers import base
|
from app.controllers import base
|
||||||
|
from app.controllers.manager.memory_manager import InMemoryTaskManager
|
||||||
|
from app.controllers.manager.redis_manager import RedisTaskManager
|
||||||
from app.controllers.v1.base import new_router
|
from app.controllers.v1.base import new_router
|
||||||
from app.models.exception import HttpException
|
from app.models.exception import HttpException
|
||||||
from app.models.schema import TaskVideoRequest, TaskQueryResponse, TaskResponse, TaskQueryRequest, \
|
from app.models.schema import TaskVideoRequest, TaskQueryResponse, TaskResponse, TaskQueryRequest, \
|
||||||
@@ -22,6 +24,35 @@ from app.utils import utils
|
|||||||
# router = new_router(dependencies=[Depends(base.verify_token)])
|
# router = new_router(dependencies=[Depends(base.verify_token)])
|
||||||
router = new_router()
|
router = new_router()
|
||||||
|
|
||||||
|
_enable_redis = config.app.get("enable_redis", False)
|
||||||
|
_redis_host = config.app.get("redis_host", "localhost")
|
||||||
|
_redis_port = config.app.get("redis_port", 6379)
|
||||||
|
_redis_db = config.app.get("redis_db", 0)
|
||||||
|
_redis_password = config.app.get("redis_password", None)
|
||||||
|
_max_concurrent_tasks = config.app.get("max_concurrent_tasks", 5)
|
||||||
|
|
||||||
|
redis_url = f"redis://:{_redis_password}@{_redis_host}:{_redis_port}/{_redis_db}"
|
||||||
|
# 根据配置选择合适的任务管理器
|
||||||
|
if _enable_redis:
|
||||||
|
task_manager = RedisTaskManager(max_concurrent_tasks=_max_concurrent_tasks, redis_url=redis_url)
|
||||||
|
else:
|
||||||
|
task_manager = InMemoryTaskManager(max_concurrent_tasks=_max_concurrent_tasks)
|
||||||
|
|
||||||
|
# @router.post("/videos-test", response_model=TaskResponse, summary="Generate a short video")
|
||||||
|
# async def create_video_test(request: Request, body: TaskVideoRequest):
|
||||||
|
# task_id = utils.get_uuid()
|
||||||
|
# request_id = base.get_task_id(request)
|
||||||
|
# try:
|
||||||
|
# task = {
|
||||||
|
# "task_id": task_id,
|
||||||
|
# "request_id": request_id,
|
||||||
|
# "params": body.dict(),
|
||||||
|
# }
|
||||||
|
# task_manager.add_task(tm.start_test, task_id=task_id, params=body)
|
||||||
|
# return utils.get_response(200, task)
|
||||||
|
# except ValueError as e:
|
||||||
|
# raise HttpException(task_id=task_id, status_code=400, message=f"{request_id}: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/videos", response_model=TaskResponse, summary="Generate a short video")
|
@router.post("/videos", response_model=TaskResponse, summary="Generate a short video")
|
||||||
def create_video(background_tasks: BackgroundTasks, request: Request, body: TaskVideoRequest):
|
def create_video(background_tasks: BackgroundTasks, request: Request, body: TaskVideoRequest):
|
||||||
@@ -34,7 +65,8 @@ def create_video(background_tasks: BackgroundTasks, request: Request, body: Task
|
|||||||
"params": body.dict(),
|
"params": body.dict(),
|
||||||
}
|
}
|
||||||
sm.state.update_task(task_id)
|
sm.state.update_task(task_id)
|
||||||
background_tasks.add_task(tm.start, task_id=task_id, params=body)
|
# background_tasks.add_task(tm.start, task_id=task_id, params=body)
|
||||||
|
task_manager.add_task(tm.start, task_id=task_id, params=body)
|
||||||
logger.success(f"video created: {utils.to_json(task)}")
|
logger.success(f"video created: {utils.to_json(task)}")
|
||||||
return utils.get_response(200, task)
|
return utils.get_response(200, task)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
|||||||
@@ -6,3 +6,6 @@ PUNCTUATIONS = [
|
|||||||
TASK_STATE_FAILED = -1
|
TASK_STATE_FAILED = -1
|
||||||
TASK_STATE_COMPLETE = 1
|
TASK_STATE_COMPLETE = 1
|
||||||
TASK_STATE_PROCESSING = 4
|
TASK_STATE_PROCESSING = 4
|
||||||
|
|
||||||
|
FILE_TYPE_VIDEOS = ['mp4', 'mov', 'mkv', 'webm']
|
||||||
|
FILE_TYPE_IMAGES = ['jpg', 'jpeg', 'png', 'bmp']
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional, List
|
||||||
|
|
||||||
|
import pydantic
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
@@ -28,6 +29,11 @@ class VideoAspect(str, Enum):
|
|||||||
return 1080, 1920
|
return 1080, 1920
|
||||||
|
|
||||||
|
|
||||||
|
class _Config:
|
||||||
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
|
|
||||||
|
@pydantic.dataclasses.dataclass(config=_Config)
|
||||||
class MaterialInfo:
|
class MaterialInfo:
|
||||||
provider: str = "pexels"
|
provider: str = "pexels"
|
||||||
url: str = ""
|
url: str = ""
|
||||||
@@ -73,7 +79,7 @@ class MaterialInfo:
|
|||||||
# ]
|
# ]
|
||||||
|
|
||||||
|
|
||||||
class VideoParams:
|
class VideoParams(BaseModel):
|
||||||
"""
|
"""
|
||||||
{
|
{
|
||||||
"video_subject": "",
|
"video_subject": "",
|
||||||
@@ -95,6 +101,9 @@ class VideoParams:
|
|||||||
video_clip_duration: Optional[int] = 5
|
video_clip_duration: Optional[int] = 5
|
||||||
video_count: Optional[int] = 1
|
video_count: Optional[int] = 1
|
||||||
|
|
||||||
|
video_source: Optional[str] = "pexels"
|
||||||
|
video_materials: Optional[List[MaterialInfo]] = None # 用于生成视频的素材
|
||||||
|
|
||||||
video_language: Optional[str] = "" # auto detect
|
video_language: Optional[str] = "" # auto detect
|
||||||
|
|
||||||
voice_name: Optional[str] = ""
|
voice_name: Optional[str] = ""
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from openai.types.chat import ChatCompletion
|
|||||||
|
|
||||||
from app.config import config
|
from app.config import config
|
||||||
|
|
||||||
|
_max_retries = 5
|
||||||
|
|
||||||
|
|
||||||
def _generate_response(prompt: str) -> str:
|
def _generate_response(prompt: str) -> str:
|
||||||
content = ""
|
content = ""
|
||||||
@@ -64,6 +66,12 @@ def _generate_response(prompt: str) -> str:
|
|||||||
model_name = config.app.get("cloudflare_model_name")
|
model_name = config.app.get("cloudflare_model_name")
|
||||||
account_id = config.app.get("cloudflare_account_id")
|
account_id = config.app.get("cloudflare_account_id")
|
||||||
base_url = "***"
|
base_url = "***"
|
||||||
|
elif llm_provider == "deepseek":
|
||||||
|
api_key = config.app.get("deepseek_api_key")
|
||||||
|
model_name = config.app.get("deepseek_model_name")
|
||||||
|
base_url = config.app.get("deepseek_base_url")
|
||||||
|
if not base_url:
|
||||||
|
base_url = "https://api.deepseek.com"
|
||||||
else:
|
else:
|
||||||
raise ValueError("llm_provider is not set, please set it in the config.toml file.")
|
raise ValueError("llm_provider is not set, please set it in the config.toml file.")
|
||||||
|
|
||||||
@@ -213,11 +221,8 @@ Generate a script for a video, depending on the subject of the video.
|
|||||||
|
|
||||||
final_script = ""
|
final_script = ""
|
||||||
logger.info(f"subject: {video_subject}")
|
logger.info(f"subject: {video_subject}")
|
||||||
logger.debug(f"prompt: \n{prompt}")
|
|
||||||
response = _generate_response(prompt=prompt)
|
|
||||||
|
|
||||||
# Return the generated script
|
def format_response(response):
|
||||||
if response:
|
|
||||||
# Clean the script
|
# Clean the script
|
||||||
# Remove asterisks, hashes
|
# Remove asterisks, hashes
|
||||||
response = response.replace("*", "")
|
response = response.replace("*", "")
|
||||||
@@ -234,15 +239,30 @@ Generate a script for a video, depending on the subject of the video.
|
|||||||
selected_paragraphs = paragraphs[:paragraph_number]
|
selected_paragraphs = paragraphs[:paragraph_number]
|
||||||
|
|
||||||
# Join the selected paragraphs into a single string
|
# Join the selected paragraphs into a single string
|
||||||
final_script = "\n\n".join(selected_paragraphs)
|
return "\n\n".join(selected_paragraphs)
|
||||||
|
|
||||||
# Print to console the number of paragraphs used
|
for i in range(_max_retries):
|
||||||
# logger.info(f"number of paragraphs used: {len(selected_paragraphs)}")
|
try:
|
||||||
else:
|
response = _generate_response(prompt=prompt)
|
||||||
logging.error("gpt returned an empty response")
|
if response:
|
||||||
|
final_script = format_response(response)
|
||||||
|
else:
|
||||||
|
logging.error("gpt returned an empty response")
|
||||||
|
|
||||||
|
# g4f may return an error message
|
||||||
|
if final_script and "当日额度已消耗完" in final_script:
|
||||||
|
raise ValueError(final_script)
|
||||||
|
|
||||||
|
if final_script:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"failed to generate script: {e}")
|
||||||
|
|
||||||
|
if i < _max_retries:
|
||||||
|
logger.warning(f"failed to generate video script, trying again... {i + 1}")
|
||||||
|
|
||||||
logger.success(f"completed: \n{final_script}")
|
logger.success(f"completed: \n{final_script}")
|
||||||
return final_script
|
return final_script.strip()
|
||||||
|
|
||||||
|
|
||||||
def generate_terms(video_subject: str, video_script: str, amount: int = 5) -> List[str]:
|
def generate_terms(video_subject: str, video_script: str, amount: int = 5) -> List[str]:
|
||||||
@@ -273,25 +293,28 @@ Please note that you must use English for generating video search terms; Chinese
|
|||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
logger.info(f"subject: {video_subject}")
|
logger.info(f"subject: {video_subject}")
|
||||||
logger.debug(f"prompt: \n{prompt}")
|
|
||||||
response = _generate_response(prompt)
|
|
||||||
search_terms = []
|
search_terms = []
|
||||||
|
for i in range(_max_retries):
|
||||||
|
try:
|
||||||
|
response = _generate_response(prompt)
|
||||||
|
search_terms = json.loads(response)
|
||||||
|
if not isinstance(search_terms, list) or not all(isinstance(term, str) for term in search_terms):
|
||||||
|
logger.error("response is not a list of strings.")
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
except Exception as e:
|
||||||
search_terms = json.loads(response)
|
match = re.search(r'\[.*]', response)
|
||||||
if not isinstance(search_terms, list) or not all(isinstance(term, str) for term in search_terms):
|
if match:
|
||||||
raise ValueError("response is not a list of strings.")
|
try:
|
||||||
|
search_terms = json.loads(match.group())
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
except (json.JSONDecodeError, ValueError):
|
if search_terms and len(search_terms) > 0:
|
||||||
# logger.warning(f"gpt returned an unformatted response. attempting to clean...")
|
break
|
||||||
# Attempt to extract list-like string and convert to list
|
if i < _max_retries:
|
||||||
match = re.search(r'\["(?:[^"\\]|\\.)*"(?:,\s*"[^"\\]*")*\]', response)
|
logger.warning(f"failed to generate video terms, trying again... {i + 1}")
|
||||||
if match:
|
|
||||||
try:
|
|
||||||
search_terms = json.loads(match.group())
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
logger.error(f"could not parse response: {response}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
logger.success(f"completed: \n{search_terms}")
|
logger.success(f"completed: \n{search_terms}")
|
||||||
return search_terms
|
return search_terms
|
||||||
@@ -300,8 +323,8 @@ Please note that you must use English for generating video search terms; Chinese
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
video_subject = "生命的意义是什么"
|
video_subject = "生命的意义是什么"
|
||||||
script = generate_script(video_subject=video_subject, language="zh-CN", paragraph_number=1)
|
script = generate_script(video_subject=video_subject, language="zh-CN", paragraph_number=1)
|
||||||
# print("######################")
|
print("######################")
|
||||||
# print(script)
|
print(script)
|
||||||
# search_terms = generate_terms(video_subject=video_subject, video_script=script, amount=5)
|
search_terms = generate_terms(video_subject=video_subject, video_script=script, amount=5)
|
||||||
# print("######################")
|
print("######################")
|
||||||
# print(search_terms)
|
print(search_terms)
|
||||||
|
|||||||
@@ -14,33 +14,33 @@ from app.utils import utils
|
|||||||
requested_count = 0
|
requested_count = 0
|
||||||
|
|
||||||
|
|
||||||
def round_robin_api_key():
|
def get_api_key(cfg_key: str):
|
||||||
pexels_api_keys = config.app.get("pexels_api_keys")
|
api_keys = config.app.get(cfg_key)
|
||||||
if not pexels_api_keys:
|
if not api_keys:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"\n\n##### pexels_api_keys is not set #####\n\nPlease set it in the config.toml file: {config.config_file}\n\n{utils.to_json(config.app)}")
|
f"\n\n##### {cfg_key} is not set #####\n\nPlease set it in the config.toml file: {config.config_file}\n\n"
|
||||||
|
f"{utils.to_json(config.app)}")
|
||||||
|
|
||||||
# if only one key is provided, return it
|
# if only one key is provided, return it
|
||||||
if isinstance(pexels_api_keys, str):
|
if isinstance(api_keys, str):
|
||||||
return pexels_api_keys
|
return api_keys
|
||||||
|
|
||||||
global requested_count
|
global requested_count
|
||||||
requested_count += 1
|
requested_count += 1
|
||||||
return pexels_api_keys[requested_count % len(pexels_api_keys)]
|
return api_keys[requested_count % len(api_keys)]
|
||||||
|
|
||||||
|
|
||||||
def search_videos(search_term: str,
|
def search_videos_pexels(search_term: str,
|
||||||
minimum_duration: int,
|
minimum_duration: int,
|
||||||
video_aspect: VideoAspect = VideoAspect.portrait,
|
video_aspect: VideoAspect = VideoAspect.portrait,
|
||||||
) -> List[MaterialInfo]:
|
) -> List[MaterialInfo]:
|
||||||
aspect = VideoAspect(video_aspect)
|
aspect = VideoAspect(video_aspect)
|
||||||
video_orientation = aspect.name
|
video_orientation = aspect.name
|
||||||
video_width, video_height = aspect.to_resolution()
|
video_width, video_height = aspect.to_resolution()
|
||||||
|
api_key = get_api_key("pexels_api_keys")
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": round_robin_api_key()
|
"Authorization": api_key
|
||||||
}
|
}
|
||||||
proxies = config.pexels.get("proxies", None)
|
|
||||||
# Build URL
|
# Build URL
|
||||||
params = {
|
params = {
|
||||||
"query": search_term,
|
"query": search_term,
|
||||||
@@ -48,10 +48,10 @@ def search_videos(search_term: str,
|
|||||||
"orientation": video_orientation
|
"orientation": video_orientation
|
||||||
}
|
}
|
||||||
query_url = f"https://api.pexels.com/videos/search?{urlencode(params)}"
|
query_url = f"https://api.pexels.com/videos/search?{urlencode(params)}"
|
||||||
logger.info(f"searching videos: {query_url}, with proxies: {proxies}")
|
logger.info(f"searching videos: {query_url}, with proxies: {config.proxy}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.get(query_url, headers=headers, proxies=proxies, verify=False)
|
r = requests.get(query_url, headers=headers, proxies=config.proxy, verify=False, timeout=(30, 60))
|
||||||
response = r.json()
|
response = r.json()
|
||||||
video_items = []
|
video_items = []
|
||||||
if "videos" not in response:
|
if "videos" not in response:
|
||||||
@@ -83,6 +83,59 @@ def search_videos(search_term: str,
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def search_videos_pixabay(search_term: str,
|
||||||
|
minimum_duration: int,
|
||||||
|
video_aspect: VideoAspect = VideoAspect.portrait,
|
||||||
|
) -> List[MaterialInfo]:
|
||||||
|
aspect = VideoAspect(video_aspect)
|
||||||
|
|
||||||
|
video_width, video_height = aspect.to_resolution()
|
||||||
|
|
||||||
|
api_key = get_api_key("pixabay_api_keys")
|
||||||
|
# Build URL
|
||||||
|
params = {
|
||||||
|
"q": search_term,
|
||||||
|
"video_type": "all", # Accepted values: "all", "film", "animation"
|
||||||
|
"per_page": 50,
|
||||||
|
"key": api_key
|
||||||
|
}
|
||||||
|
query_url = f"https://pixabay.com/api/videos/?{urlencode(params)}"
|
||||||
|
logger.info(f"searching videos: {query_url}, with proxies: {config.proxy}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(query_url, proxies=config.proxy, verify=False, timeout=(30, 60))
|
||||||
|
response = r.json()
|
||||||
|
video_items = []
|
||||||
|
if "hits" not in response:
|
||||||
|
logger.error(f"search videos failed: {response}")
|
||||||
|
return video_items
|
||||||
|
videos = response["hits"]
|
||||||
|
# loop through each video in the result
|
||||||
|
for v in videos:
|
||||||
|
duration = v["duration"]
|
||||||
|
# check if video has desired minimum duration
|
||||||
|
if duration < minimum_duration:
|
||||||
|
continue
|
||||||
|
video_files = v["videos"]
|
||||||
|
# loop through each url to determine the best quality
|
||||||
|
for video_type in video_files:
|
||||||
|
video = video_files[video_type]
|
||||||
|
w = int(video["width"])
|
||||||
|
h = int(video["height"])
|
||||||
|
if w >= video_width:
|
||||||
|
item = MaterialInfo()
|
||||||
|
item.provider = "pixabay"
|
||||||
|
item.url = video["url"]
|
||||||
|
item.duration = duration
|
||||||
|
video_items.append(item)
|
||||||
|
break
|
||||||
|
return video_items
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"search videos failed: {str(e)}")
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def save_video(video_url: str, save_dir: str = "") -> str:
|
def save_video(video_url: str, save_dir: str = "") -> str:
|
||||||
if not save_dir:
|
if not save_dir:
|
||||||
save_dir = utils.storage_dir("cache_videos")
|
save_dir = utils.storage_dir("cache_videos")
|
||||||
@@ -101,9 +154,8 @@ def save_video(video_url: str, save_dir: str = "") -> str:
|
|||||||
return video_path
|
return video_path
|
||||||
|
|
||||||
# if video does not exist, download it
|
# if video does not exist, download it
|
||||||
proxies = config.pexels.get("proxies", None)
|
|
||||||
with open(video_path, "wb") as f:
|
with open(video_path, "wb") as f:
|
||||||
f.write(requests.get(video_url, proxies=proxies, verify=False, timeout=(60, 240)).content)
|
f.write(requests.get(video_url, proxies=config.proxy, verify=False, timeout=(60, 240)).content)
|
||||||
|
|
||||||
if os.path.exists(video_path) and os.path.getsize(video_path) > 0:
|
if os.path.exists(video_path) and os.path.getsize(video_path) > 0:
|
||||||
try:
|
try:
|
||||||
@@ -124,6 +176,7 @@ def save_video(video_url: str, save_dir: str = "") -> str:
|
|||||||
|
|
||||||
def download_videos(task_id: str,
|
def download_videos(task_id: str,
|
||||||
search_terms: List[str],
|
search_terms: List[str],
|
||||||
|
source: str = "pexels",
|
||||||
video_aspect: VideoAspect = VideoAspect.portrait,
|
video_aspect: VideoAspect = VideoAspect.portrait,
|
||||||
video_contact_mode: VideoConcatMode = VideoConcatMode.random,
|
video_contact_mode: VideoConcatMode = VideoConcatMode.random,
|
||||||
audio_duration: float = 0.0,
|
audio_duration: float = 0.0,
|
||||||
@@ -132,8 +185,11 @@ def download_videos(task_id: str,
|
|||||||
valid_video_items = []
|
valid_video_items = []
|
||||||
valid_video_urls = []
|
valid_video_urls = []
|
||||||
found_duration = 0.0
|
found_duration = 0.0
|
||||||
|
search_videos = search_videos_pexels
|
||||||
|
if source == "pixabay":
|
||||||
|
search_videos = search_videos_pixabay
|
||||||
|
|
||||||
for search_term in search_terms:
|
for search_term in search_terms:
|
||||||
# logger.info(f"searching videos for '{search_term}'")
|
|
||||||
video_items = search_videos(search_term=search_term,
|
video_items = search_videos(search_term=search_term,
|
||||||
minimum_duration=max_clip_duration,
|
minimum_duration=max_clip_duration,
|
||||||
video_aspect=video_aspect)
|
video_aspect=video_aspect)
|
||||||
@@ -178,4 +234,4 @@ def download_videos(task_id: str,
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
download_videos("test123", ["cat"], audio_duration=100)
|
download_videos("test123", ["Money Exchange Medium"], audio_duration=100, source="pixabay")
|
||||||
|
|||||||
@@ -24,9 +24,18 @@ def create(audio_file, subtitle_file: str = ""):
|
|||||||
model_path = model_size
|
model_path = model_size
|
||||||
|
|
||||||
logger.info(f"loading model: {model_path}, device: {device}, compute_type: {compute_type}")
|
logger.info(f"loading model: {model_path}, device: {device}, compute_type: {compute_type}")
|
||||||
model = WhisperModel(model_size_or_path=model_path,
|
try:
|
||||||
device=device,
|
model = WhisperModel(model_size_or_path=model_path,
|
||||||
compute_type=compute_type)
|
device=device,
|
||||||
|
compute_type=compute_type)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"failed to load model: {e} \n\n"
|
||||||
|
f"********************************************\n"
|
||||||
|
f"this may be caused by network issue. \n"
|
||||||
|
f"please download the model manually and put it in the 'models' folder. \n"
|
||||||
|
f"see [README.md FAQ](https://github.com/harry0703/MoneyPrinterTurbo) for more details.\n"
|
||||||
|
f"********************************************\n\n")
|
||||||
|
return None
|
||||||
|
|
||||||
logger.info(f"start, output file: {subtitle_file}")
|
logger.info(f"start, output file: {subtitle_file}")
|
||||||
if not subtitle_file:
|
if not subtitle_file:
|
||||||
@@ -120,6 +129,9 @@ def create(audio_file, subtitle_file: str = ""):
|
|||||||
|
|
||||||
|
|
||||||
def file_to_subtitles(filename):
|
def file_to_subtitles(filename):
|
||||||
|
if not filename or not os.path.isfile(filename):
|
||||||
|
return []
|
||||||
|
|
||||||
times_texts = []
|
times_texts = []
|
||||||
current_times = None
|
current_times = None
|
||||||
current_text = ""
|
current_text = ""
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ def start(task_id, params: VideoParams):
|
|||||||
else:
|
else:
|
||||||
logger.debug(f"video script: \n{video_script}")
|
logger.debug(f"video script: \n{video_script}")
|
||||||
|
|
||||||
|
if not video_script:
|
||||||
|
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
|
||||||
|
logger.error("failed to generate video script.")
|
||||||
|
return
|
||||||
|
|
||||||
sm.state.update_task(task_id, state=const.TASK_STATE_PROCESSING, progress=10)
|
sm.state.update_task(task_id, state=const.TASK_STATE_PROCESSING, progress=10)
|
||||||
|
|
||||||
logger.info("\n\n## generating video terms")
|
logger.info("\n\n## generating video terms")
|
||||||
@@ -60,6 +65,11 @@ def start(task_id, params: VideoParams):
|
|||||||
|
|
||||||
logger.debug(f"video terms: {utils.to_json(video_terms)}")
|
logger.debug(f"video terms: {utils.to_json(video_terms)}")
|
||||||
|
|
||||||
|
if not video_terms:
|
||||||
|
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
|
||||||
|
logger.error("failed to generate video terms.")
|
||||||
|
return
|
||||||
|
|
||||||
script_file = path.join(utils.task_dir(task_id), f"script.json")
|
script_file = path.join(utils.task_dir(task_id), f"script.json")
|
||||||
script_data = {
|
script_data = {
|
||||||
"script": video_script,
|
"script": video_script,
|
||||||
@@ -78,7 +88,11 @@ def start(task_id, params: VideoParams):
|
|||||||
if sub_maker is None:
|
if sub_maker is None:
|
||||||
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
|
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
|
||||||
logger.error(
|
logger.error(
|
||||||
"failed to generate audio, maybe the network is not available. if you are in China, please use a VPN.")
|
"""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
|
return
|
||||||
|
|
||||||
audio_duration = voice.get_audio_duration(sub_maker)
|
audio_duration = voice.get_audio_duration(sub_maker)
|
||||||
@@ -110,14 +124,29 @@ def start(task_id, params: VideoParams):
|
|||||||
|
|
||||||
sm.state.update_task(task_id, state=const.TASK_STATE_PROCESSING, progress=40)
|
sm.state.update_task(task_id, state=const.TASK_STATE_PROCESSING, progress=40)
|
||||||
|
|
||||||
logger.info("\n\n## downloading videos")
|
downloaded_videos = []
|
||||||
downloaded_videos = material.download_videos(task_id=task_id,
|
if params.video_source == "local":
|
||||||
search_terms=video_terms,
|
logger.info("\n\n## preprocess local materials")
|
||||||
video_aspect=params.video_aspect,
|
materials = video.preprocess_video(materials=params.video_materials, clip_duration=max_clip_duration)
|
||||||
video_contact_mode=params.video_concat_mode,
|
print(materials)
|
||||||
audio_duration=audio_duration * params.video_count,
|
|
||||||
max_clip_duration=max_clip_duration,
|
if not materials:
|
||||||
)
|
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
|
||||||
|
logger.error("no valid materials found, please check the materials and try again.")
|
||||||
|
return
|
||||||
|
for material_info in materials:
|
||||||
|
print(material_info)
|
||||||
|
downloaded_videos.append(material_info.url)
|
||||||
|
else:
|
||||||
|
logger.info(f"\n\n## downloading videos from {params.video_source}")
|
||||||
|
downloaded_videos = material.download_videos(task_id=task_id,
|
||||||
|
search_terms=video_terms,
|
||||||
|
source=params.video_source,
|
||||||
|
video_aspect=params.video_aspect,
|
||||||
|
video_contact_mode=params.video_concat_mode,
|
||||||
|
audio_duration=audio_duration * params.video_count,
|
||||||
|
max_clip_duration=max_clip_duration,
|
||||||
|
)
|
||||||
if not downloaded_videos:
|
if not downloaded_videos:
|
||||||
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
|
sm.state.update_task(task_id, state=const.TASK_STATE_FAILED)
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -173,3 +202,8 @@ def start(task_id, params: VideoParams):
|
|||||||
}
|
}
|
||||||
sm.state.update_task(task_id, state=const.TASK_STATE_COMPLETE, progress=100, **kwargs)
|
sm.state.update_task(task_id, state=const.TASK_STATE_COMPLETE, progress=100, **kwargs)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
# def start_test(task_id, params: VideoParams):
|
||||||
|
# print(f"start task {task_id} \n")
|
||||||
|
# time.sleep(5)
|
||||||
|
# print(f"task {task_id} finished \n")
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import glob
|
import glob
|
||||||
import random
|
import random
|
||||||
from typing import List
|
from typing import List
|
||||||
from PIL import ImageFont
|
from PIL import ImageFont, Image
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from moviepy.editor import *
|
from moviepy.editor import *
|
||||||
from moviepy.video.tools.subtitles import SubtitlesClip
|
from moviepy.video.tools.subtitles import SubtitlesClip
|
||||||
|
|
||||||
from app.models.schema import VideoAspect, VideoParams, VideoConcatMode
|
from app.models import const
|
||||||
|
from app.models.schema import VideoAspect, VideoParams, VideoConcatMode, MaterialInfo
|
||||||
from app.utils import utils
|
from app.utils import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -48,14 +49,29 @@ def combine_videos(combined_video_path: str,
|
|||||||
|
|
||||||
clips = []
|
clips = []
|
||||||
video_duration = 0
|
video_duration = 0
|
||||||
|
|
||||||
|
raw_clips = []
|
||||||
|
for video_path in video_paths:
|
||||||
|
clip = VideoFileClip(video_path).without_audio()
|
||||||
|
clip_duration = clip.duration
|
||||||
|
start_time = 0
|
||||||
|
|
||||||
|
while start_time < clip_duration:
|
||||||
|
end_time = min(start_time + max_clip_duration, clip_duration)
|
||||||
|
split_clip = clip.subclip(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
|
||||||
|
if video_concat_mode.value == VideoConcatMode.sequential.value:
|
||||||
|
break
|
||||||
|
|
||||||
|
# random video_paths order
|
||||||
|
if video_concat_mode.value == VideoConcatMode.random.value:
|
||||||
|
random.shuffle(raw_clips)
|
||||||
|
|
||||||
# Add downloaded clips over and over until the duration of the audio (max_duration) has been reached
|
# Add downloaded clips over and over until the duration of the audio (max_duration) has been reached
|
||||||
while video_duration < audio_duration:
|
while video_duration < audio_duration:
|
||||||
# random video_paths order
|
for clip in raw_clips:
|
||||||
if video_concat_mode.value == VideoConcatMode.random.value:
|
|
||||||
random.shuffle(video_paths)
|
|
||||||
|
|
||||||
for video_path in video_paths:
|
|
||||||
clip = VideoFileClip(video_path).without_audio()
|
|
||||||
# Check if clip is longer than the remaining audio
|
# Check if clip is longer than the remaining audio
|
||||||
if (audio_duration - video_duration) < clip.duration:
|
if (audio_duration - video_duration) < clip.duration:
|
||||||
clip = clip.subclip(0, (audio_duration - video_duration))
|
clip = clip.subclip(0, (audio_duration - video_duration))
|
||||||
@@ -268,55 +284,101 @@ def generate_video(video_path: str,
|
|||||||
logger.success(f"completed")
|
logger.success(f"completed")
|
||||||
|
|
||||||
|
|
||||||
|
def preprocess_video(materials: List[MaterialInfo], clip_duration=4):
|
||||||
|
for material in materials:
|
||||||
|
if not material.url:
|
||||||
|
continue
|
||||||
|
|
||||||
|
ext = utils.parse_extension(material.url)
|
||||||
|
try:
|
||||||
|
clip = VideoFileClip(material.url)
|
||||||
|
except Exception as e:
|
||||||
|
clip = ImageClip(material.url)
|
||||||
|
|
||||||
|
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}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ext in const.FILE_TYPE_IMAGES:
|
||||||
|
logger.info(f"processing image: {material.url}")
|
||||||
|
# 创建一个图片剪辑,并设置持续时间为3秒钟
|
||||||
|
clip = ImageClip(material.url).set_duration(clip_duration).set_position("center")
|
||||||
|
# 使用resize方法来添加缩放效果。这里使用了lambda函数来使得缩放效果随时间变化。
|
||||||
|
# 假设我们想要从原始大小逐渐放大到120%的大小。
|
||||||
|
# t代表当前时间,clip.duration为视频总时长,这里是3秒。
|
||||||
|
# 注意:1 表示100%的大小,所以1.2表示120%的大小
|
||||||
|
zoom_clip = clip.resize(lambda t: 1 + (clip_duration * 0.03) * (t / clip.duration))
|
||||||
|
|
||||||
|
# 如果需要,可以创建一个包含缩放剪辑的复合视频剪辑
|
||||||
|
# (这在您想要在视频中添加其他元素时非常有用)
|
||||||
|
final_clip = CompositeVideoClip([zoom_clip])
|
||||||
|
|
||||||
|
# 输出视频
|
||||||
|
video_file = f"{material.url}.mp4"
|
||||||
|
final_clip.write_videofile(video_file, fps=30, logger=None)
|
||||||
|
final_clip.close()
|
||||||
|
material.url = video_file
|
||||||
|
logger.success(f"completed: {video_file}")
|
||||||
|
return materials
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
txt_en = "Here's your guide to travel hacks for budget-friendly adventures"
|
m = MaterialInfo()
|
||||||
txt_zh = "测试长字段这是您的旅行技巧指南帮助您进行预算友好的冒险"
|
m.url = "/Users/harry/Downloads/IMG_2915.JPG"
|
||||||
font = utils.resource_dir() + "/fonts/STHeitiMedium.ttc"
|
m.provider = "local"
|
||||||
for txt in [txt_en, txt_zh]:
|
materials = preprocess_video([m], clip_duration=4)
|
||||||
t, h = wrap_text(text=txt, max_width=1000, font=font, fontsize=60)
|
print(materials)
|
||||||
print(t)
|
|
||||||
|
|
||||||
task_id = "aa563149-a7ea-49c2-b39f-8c32cc225baf"
|
# txt_en = "Here's your guide to travel hacks for budget-friendly adventures"
|
||||||
task_dir = utils.task_dir(task_id)
|
# txt_zh = "测试长字段这是您的旅行技巧指南帮助您进行预算友好的冒险"
|
||||||
video_file = f"{task_dir}/combined-1.mp4"
|
# font = utils.resource_dir() + "/fonts/STHeitiMedium.ttc"
|
||||||
audio_file = f"{task_dir}/audio.mp3"
|
# for txt in [txt_en, txt_zh]:
|
||||||
subtitle_file = f"{task_dir}/subtitle.srt"
|
# t, h = wrap_text(text=txt, max_width=1000, font=font, fontsize=60)
|
||||||
output_file = f"{task_dir}/final.mp4"
|
# print(t)
|
||||||
|
|
||||||
# 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,
|
# task_id = "aa563149-a7ea-49c2-b39f-8c32cc225baf"
|
||||||
# audio_file=audio_file,
|
# task_dir = utils.task_dir(task_id)
|
||||||
# video_paths=video_paths,
|
# video_file = f"{task_dir}/combined-1.mp4"
|
||||||
# video_aspect=VideoAspect.portrait,
|
# audio_file = f"{task_dir}/audio.mp3"
|
||||||
# video_concat_mode=VideoConcatMode.random,
|
# subtitle_file = f"{task_dir}/subtitle.srt"
|
||||||
# max_clip_duration=5,
|
# output_file = f"{task_dir}/final.mp4"
|
||||||
# threads=2)
|
#
|
||||||
|
# # video_paths = []
|
||||||
cfg = VideoParams()
|
# # for file in os.listdir(utils.storage_dir("test")):
|
||||||
cfg.video_aspect = VideoAspect.portrait
|
# # if file.endswith(".mp4"):
|
||||||
cfg.font_name = "STHeitiMedium.ttc"
|
# # video_paths.append(os.path.join(utils.storage_dir("test"), file))
|
||||||
cfg.font_size = 60
|
# #
|
||||||
cfg.stroke_color = "#000000"
|
# # combine_videos(combined_video_path=video_file,
|
||||||
cfg.stroke_width = 1.5
|
# # audio_file=audio_file,
|
||||||
cfg.text_fore_color = "#FFFFFF"
|
# # video_paths=video_paths,
|
||||||
cfg.text_background_color = "transparent"
|
# # video_aspect=VideoAspect.portrait,
|
||||||
cfg.bgm_type = "random"
|
# # video_concat_mode=VideoConcatMode.random,
|
||||||
cfg.bgm_file = ""
|
# # max_clip_duration=5,
|
||||||
cfg.bgm_volume = 1.0
|
# # threads=2)
|
||||||
cfg.subtitle_enabled = True
|
#
|
||||||
cfg.subtitle_position = "bottom"
|
# cfg = VideoParams()
|
||||||
cfg.n_threads = 2
|
# cfg.video_aspect = VideoAspect.portrait
|
||||||
cfg.paragraph_number = 1
|
# cfg.font_name = "STHeitiMedium.ttc"
|
||||||
|
# cfg.font_size = 60
|
||||||
cfg.voice_volume = 1.0
|
# cfg.stroke_color = "#000000"
|
||||||
|
# cfg.stroke_width = 1.5
|
||||||
generate_video(video_path=video_file,
|
# cfg.text_fore_color = "#FFFFFF"
|
||||||
audio_path=audio_file,
|
# cfg.text_background_color = "transparent"
|
||||||
subtitle_path=subtitle_file,
|
# cfg.bgm_type = "random"
|
||||||
output_file=output_file,
|
# cfg.bgm_file = ""
|
||||||
params=cfg
|
# 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
|
||||||
|
# )
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from app.utils import utils
|
|||||||
|
|
||||||
def get_all_azure_voices(filter_locals=None) -> list[str]:
|
def get_all_azure_voices(filter_locals=None) -> list[str]:
|
||||||
if filter_locals is None:
|
if filter_locals is None:
|
||||||
filter_locals = ["zh-CN", "en-US", "zh-HK", "zh-TW"]
|
filter_locals = ["zh-CN", "en-US", "zh-HK", "zh-TW", "vi-VN"]
|
||||||
voices_str = """
|
voices_str = """
|
||||||
Name: af-ZA-AdriNeural
|
Name: af-ZA-AdriNeural
|
||||||
Gender: Female
|
Gender: Female
|
||||||
@@ -1023,7 +1023,6 @@ def parse_voice_name(name: str):
|
|||||||
|
|
||||||
def is_azure_v2_voice(voice_name: str):
|
def is_azure_v2_voice(voice_name: str):
|
||||||
voice_name = parse_voice_name(voice_name)
|
voice_name = parse_voice_name(voice_name)
|
||||||
print(voice_name)
|
|
||||||
if voice_name.endswith("-V2"):
|
if voice_name.endswith("-V2"):
|
||||||
return voice_name.replace("-V2", "").strip()
|
return voice_name.replace("-V2", "").strip()
|
||||||
return ""
|
return ""
|
||||||
@@ -1036,6 +1035,7 @@ def tts(text: str, voice_name: str, voice_file: str) -> [SubMaker, None]:
|
|||||||
|
|
||||||
|
|
||||||
def azure_tts_v1(text: str, voice_name: str, voice_file: str) -> [SubMaker, None]:
|
def azure_tts_v1(text: str, voice_name: str, voice_file: str) -> [SubMaker, None]:
|
||||||
|
voice_name = parse_voice_name(voice_name)
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -67,10 +67,13 @@ def root_dir():
|
|||||||
return os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
|
return os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
def storage_dir(sub_dir: str = ""):
|
def storage_dir(sub_dir: str = "", create: bool = False):
|
||||||
d = os.path.join(root_dir(), "storage")
|
d = os.path.join(root_dir(), "storage")
|
||||||
if sub_dir:
|
if sub_dir:
|
||||||
d = os.path.join(d, sub_dir)
|
d = os.path.join(d, sub_dir)
|
||||||
|
if create and not os.path.exists(d):
|
||||||
|
os.makedirs(d)
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
@@ -219,3 +222,7 @@ def load_locales(i18n_dir):
|
|||||||
with open(os.path.join(root, file), "r", encoding="utf-8") as f:
|
with open(os.path.join(root, file), "r", encoding="utf-8") as f:
|
||||||
_locales[lang] = json.loads(f.read())
|
_locales[lang] = json.loads(f.read())
|
||||||
return _locales
|
return _locales
|
||||||
|
|
||||||
|
|
||||||
|
def parse_extension(filename):
|
||||||
|
return os.path.splitext(filename)[1].strip().lower().replace(".", "")
|
||||||
|
|||||||
17
changelog.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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,
|
||||||
|
)
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
[app]
|
[app]
|
||||||
|
|
||||||
|
video_source = "pexels" # "pexels" or "pixabay"
|
||||||
# Pexels API Key
|
# Pexels API Key
|
||||||
# Register at https://www.pexels.com/api/ to get your API key.
|
# Register at https://www.pexels.com/api/ to get your API key.
|
||||||
# You can use multiple keys to avoid rate limits.
|
# You can use multiple keys to avoid rate limits.
|
||||||
@@ -6,6 +8,13 @@
|
|||||||
# 特别注意格式,Key 用英文双引号括起来,多个Key用逗号隔开
|
# 特别注意格式,Key 用英文双引号括起来,多个Key用逗号隔开
|
||||||
pexels_api_keys = []
|
pexels_api_keys = []
|
||||||
|
|
||||||
|
# Pixabay API Key
|
||||||
|
# Register at https://pixabay.com/api/docs/ to get your API key.
|
||||||
|
# You can use multiple keys to avoid rate limits.
|
||||||
|
# For example: pixabay_api_keys = ["123adsf4567adf89","abd1321cd13efgfdfhi"]
|
||||||
|
# 特别注意格式,Key 用英文双引号括起来,多个Key用逗号隔开
|
||||||
|
pixabay_api_keys = []
|
||||||
|
|
||||||
# 如果你没有 OPENAI API Key,可以使用 g4f 代替,或者使用国内的 Moonshot API
|
# 如果你没有 OPENAI API Key,可以使用 g4f 代替,或者使用国内的 Moonshot API
|
||||||
# If you don't have an OPENAI API Key, you can use g4f instead
|
# If you don't have an OPENAI API Key, you can use g4f instead
|
||||||
|
|
||||||
@@ -31,7 +40,7 @@
|
|||||||
# No need to set it unless you want to use your own proxy
|
# No need to set it unless you want to use your own proxy
|
||||||
openai_base_url = ""
|
openai_base_url = ""
|
||||||
# Check your available models at https://platform.openai.com/account/limits
|
# Check your available models at https://platform.openai.com/account/limits
|
||||||
openai_model_name = "gpt-4-turbo-preview"
|
openai_model_name = "gpt-4-turbo"
|
||||||
|
|
||||||
########## Moonshot API Key
|
########## Moonshot API Key
|
||||||
# Visit https://platform.moonshot.cn/console/api-keys to get your API key.
|
# Visit https://platform.moonshot.cn/console/api-keys to get your API key.
|
||||||
@@ -48,7 +57,7 @@
|
|||||||
########## G4F
|
########## G4F
|
||||||
# Visit https://github.com/xtekky/gpt4free to get more details
|
# Visit https://github.com/xtekky/gpt4free to get more details
|
||||||
# Supported model list: https://github.com/xtekky/gpt4free/blob/main/g4f/models.py
|
# Supported model list: https://github.com/xtekky/gpt4free/blob/main/g4f/models.py
|
||||||
g4f_model_name = "gpt-3.5-turbo-16k-0613"
|
g4f_model_name = "gpt-3.5-turbo"
|
||||||
|
|
||||||
########## Azure API Key
|
########## Azure API Key
|
||||||
# Visit https://learn.microsoft.com/zh-cn/azure/ai-services/openai/ to get more details
|
# Visit https://learn.microsoft.com/zh-cn/azure/ai-services/openai/ to get more details
|
||||||
@@ -68,9 +77,15 @@
|
|||||||
# https://tongyi.aliyun.com/qianwen/
|
# https://tongyi.aliyun.com/qianwen/
|
||||||
# https://help.aliyun.com/zh/dashscope/developer-reference/model-introduction
|
# https://help.aliyun.com/zh/dashscope/developer-reference/model-introduction
|
||||||
qwen_api_key = ""
|
qwen_api_key = ""
|
||||||
qwen_model_name = "qwen-max"
|
qwen_model_name = "qwen-max"
|
||||||
|
|
||||||
|
|
||||||
|
########## DeepSeek API Key
|
||||||
|
# Visit https://platform.deepseek.com/api_keys to get your API key
|
||||||
|
deepseek_api_key = ""
|
||||||
|
deepseek_base_url = "https://api.deepseek.com"
|
||||||
|
deepseek_model_name = "deepseek-chat"
|
||||||
|
|
||||||
# Subtitle Provider, "edge" or "whisper"
|
# Subtitle Provider, "edge" or "whisper"
|
||||||
# If empty, the subtitle will not be generated
|
# If empty, the subtitle will not be generated
|
||||||
subtitle_provider = "edge"
|
subtitle_provider = "edge"
|
||||||
@@ -134,6 +149,15 @@
|
|||||||
redis_host = "localhost"
|
redis_host = "localhost"
|
||||||
redis_port = 6379
|
redis_port = 6379
|
||||||
redis_db = 0
|
redis_db = 0
|
||||||
|
redis_password = ""
|
||||||
|
|
||||||
|
# 文生视频时的最大并发任务数
|
||||||
|
max_concurrent_tasks = 5
|
||||||
|
|
||||||
|
# webui界面是否显示配置项
|
||||||
|
# webui hide baisc config panel
|
||||||
|
hide_config = false
|
||||||
|
|
||||||
|
|
||||||
[whisper]
|
[whisper]
|
||||||
# Only effective when subtitle_provider is "whisper"
|
# Only effective when subtitle_provider is "whisper"
|
||||||
@@ -153,15 +177,15 @@
|
|||||||
device="CPU"
|
device="CPU"
|
||||||
compute_type="int8"
|
compute_type="int8"
|
||||||
|
|
||||||
[pexels]
|
|
||||||
video_concat_mode="sequential" # "random" or "sequential"
|
[proxy]
|
||||||
[pexels.proxies]
|
### Use a proxy to access the Pexels API
|
||||||
### Use a proxy to access the Pexels API
|
### Format: "http://<username>:<password>@<proxy>:<port>"
|
||||||
### Format: "http://<username>:<password>@<proxy>:<port>"
|
### Example: "http://user:pass@proxy:1234"
|
||||||
### Example: "http://user:pass@proxy:1234"
|
### Doc: https://requests.readthedocs.io/en/latest/user/advanced/#proxies
|
||||||
### Doc: https://requests.readthedocs.io/en/latest/user/advanced/#proxies
|
|
||||||
# http = "http://10.10.1.10:3128"
|
# http = "http://10.10.1.10:3128"
|
||||||
# https = "http://10.10.1.10:1080"
|
# https = "http://10.10.1.10:1080"
|
||||||
|
|
||||||
[azure]
|
[azure]
|
||||||
# Azure Speech API Key
|
# Azure Speech API Key
|
||||||
|
|||||||
BIN
docs/picwish.jpg
Normal file
|
After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 165 KiB |
BIN
docs/wechat-group.jpg
Normal file
|
After Width: | Height: | Size: 190 KiB |
@@ -6,20 +6,21 @@ edge_tts~=6.1.10
|
|||||||
uvicorn~=0.27.1
|
uvicorn~=0.27.1
|
||||||
fastapi~=0.110.0
|
fastapi~=0.110.0
|
||||||
tomli~=2.0.1
|
tomli~=2.0.1
|
||||||
streamlit~=1.32.0
|
streamlit~=1.33.0
|
||||||
loguru~=0.7.2
|
loguru~=0.7.2
|
||||||
aiohttp~=3.9.3
|
aiohttp~=3.9.3
|
||||||
urllib3~=2.2.1
|
urllib3~=2.2.1
|
||||||
pillow~=9.5.0
|
pillow~=10.3.0
|
||||||
pydantic~=2.6.3
|
pydantic~=2.6.3
|
||||||
g4f~=0.2.5.4
|
g4f~=0.3.0.4
|
||||||
dashscope~=1.15.0
|
dashscope~=1.15.0
|
||||||
google.generativeai~=0.4.1
|
google.generativeai~=0.4.1
|
||||||
python-multipart~=0.0.9
|
python-multipart~=0.0.9
|
||||||
redis==5.0.3
|
redis==5.0.3
|
||||||
# if you use pillow~=10.3.0, you will get "PIL.Image' has no attribute 'ANTIALIAS'" error when resize video
|
# if you use pillow~=10.3.0, you will get "PIL.Image' has no attribute 'ANTIALIAS'" error when resize video
|
||||||
# please install opencv-python to fix "PIL.Image' has no attribute 'ANTIALIAS'" error
|
# please install opencv-python to fix "PIL.Image' has no attribute 'ANTIALIAS'" error
|
||||||
opencv-python
|
opencv-python~=4.9.0.80
|
||||||
# for azure speech
|
# for azure speech
|
||||||
# https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/9-more-realistic-ai-voices-for-conversations-now-generally/ba-p/4099471
|
# https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/9-more-realistic-ai-voices-for-conversations-now-generally/ba-p/4099471
|
||||||
azure-cognitiveservices-speech~=1.37.0
|
azure-cognitiveservices-speech~=1.37.0
|
||||||
|
git-changelog~=2.5.2
|
||||||
|
|||||||
BIN
resource/fonts/MicrosoftYaHeiBold.ttc
Normal file
BIN
resource/fonts/MicrosoftYaHeiNormal.ttc
Normal file
BIN
resource/fonts/UTM Kabel KT.ttf
Normal file
208
sites/docs/.vuepress/config.ts
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
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"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
BIN
sites/docs/.vuepress/public/api.jpg
Normal file
|
After Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
BIN
sites/docs/.vuepress/public/hero.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
sites/docs/.vuepress/public/icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
sites/docs/.vuepress/public/icons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
sites/docs/.vuepress/public/icons/apple-touch-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
sites/docs/.vuepress/public/icons/apple-touch-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
sites/docs/.vuepress/public/icons/apple-touch-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
sites/docs/.vuepress/public/icons/apple-touch-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
sites/docs/.vuepress/public/icons/apple-touch-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
sites/docs/.vuepress/public/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
sites/docs/.vuepress/public/icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
sites/docs/.vuepress/public/icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
sites/docs/.vuepress/public/icons/msapplication-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
sites/docs/.vuepress/public/icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
149
sites/docs/.vuepress/public/icons/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<?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>
|
||||||
|
After Width: | Height: | Size: 10 KiB |
BIN
sites/docs/.vuepress/public/logo.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
21
sites/docs/.vuepress/public/manifest.webmanifest
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
sites/docs/.vuepress/public/picwish.jpg
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
sites/docs/.vuepress/public/reccloud.cn.jpg
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
sites/docs/.vuepress/public/reccloud.com.jpg
Normal file
|
After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
941
sites/docs/.vuepress/public/voice-list.txt
Normal file
@@ -0,0 +1,941 @@
|
|||||||
|
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
|
||||||
BIN
sites/docs/.vuepress/public/webui copy.jpg
Normal file
|
After Width: | Height: | Size: 340 KiB |
BIN
sites/docs/.vuepress/public/webui-en.jpg
Normal file
|
After Width: | Height: | Size: 384 KiB |
BIN
sites/docs/.vuepress/public/webui.jpg
Normal file
|
After Width: | Height: | Size: 340 KiB |
BIN
sites/docs/.vuepress/public/wechat-04.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
16
sites/docs/README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
---
|
||||||
134
sites/docs/guide/README.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
## 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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### API Interface
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
[](https://star-history.com/#harry0703/MoneyPrinterTurbo&Date)
|
||||||
5
sites/docs/guide/background-music.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
## 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.
|
||||||
70
sites/docs/guide/faq.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
## 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
|
||||||
34
sites/docs/guide/features.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
## 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
|
||||||
4
sites/docs/guide/feedback.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## Feedback & Suggestions 📢
|
||||||
|
|
||||||
|
- You can submit an [issue](https://github.com/harry0703/MoneyPrinterTurbo/issues) or
|
||||||
|
a [pull request](https://github.com/harry0703/MoneyPrinterTurbo/pulls).
|
||||||
4
sites/docs/guide/reference-project.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## 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.
|
||||||
3
sites/docs/guide/speech-synthesis.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## Voice Synthesis 🗣
|
||||||
|
|
||||||
|
A list of all supported voices can be viewed here: [Voice List](/voice-list.txt)
|
||||||
4
sites/docs/guide/subtitle-font.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## Subtitle Fonts 🅰
|
||||||
|
|
||||||
|
Fonts for rendering video subtitles are located in the project's `resource/fonts` directory, and you can also add your
|
||||||
|
own fonts.
|
||||||
15
sites/docs/guide/subtitle-generation.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
## 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.
|
||||||
35
sites/docs/guide/video-demonstration.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
## 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>
|
||||||
16
sites/docs/zh/README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
---
|
||||||
157
sites/docs/zh/guide/README.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
## 快速开始 🚀
|
||||||
|
|
||||||
|
<br>
|
||||||
|
只需提供一个视频 <b>主题</b> 或 <b>关键词</b> ,就可以全自动生成视频文案、视频素材、视频字幕、视频背景音乐,然后合成一个高清的短视频。
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<h4>Web界面</h4>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<h4>API界面</h4>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
下载一键启动包,解压直接使用
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
[](https://star-history.com/#harry0703/MoneyPrinterTurbo&Date)
|
||||||
4
sites/docs/zh/guide/background-music.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## 背景音乐 🎵
|
||||||
|
|
||||||
|
用于视频的背景音乐,位于项目的 `resource/songs` 目录下。
|
||||||
|
> 当前项目里面放了一些默认的音乐,来自于 YouTube 视频,如有侵权,请删除。
|
||||||
4
sites/docs/zh/guide/configuration-requirements.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## 配置要求 📦
|
||||||
|
|
||||||
|
- 建议最低 CPU 4核或以上,内存 8G 或以上,显卡非必须
|
||||||
|
- Windows 10 或 MacOS 11.0 以上系统
|
||||||
123
sites/docs/zh/guide/faq.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
## 常见问题 🤔
|
||||||
|
|
||||||
|
### ❓如何使用免费的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
|
||||||
|
```
|
||||||
31
sites/docs/zh/guide/features.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## 功能特性 🎯
|
||||||
|
|
||||||
|
- [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平台
|
||||||
4
sites/docs/zh/guide/feedback.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## 反馈建议 📢
|
||||||
|
|
||||||
|
- 可以提交 [issue](https://github.com/harry0703/MoneyPrinterTurbo/issues)
|
||||||
|
或者 [pull request](https://github.com/harry0703/MoneyPrinterTurbo/pulls)。
|
||||||
4
sites/docs/zh/guide/reference-project.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## 参考项目 📚
|
||||||
|
|
||||||
|
该项目基于 https://github.com/FujiwaraChoki/MoneyPrinter 重构而来,做了大量的优化,增加了更多的功能。
|
||||||
|
感谢原作者的开源精神。
|
||||||
9
sites/docs/zh/guide/special-thanks.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
## 特别感谢 🙏
|
||||||
|
|
||||||
|
由于该项目的 **部署** 和 **使用**,对于一些小白用户来说,还是 **有一定的门槛**,在此特别感谢
|
||||||
|
**录咖(AI智能 多媒体服务平台)** 网站基于该项目,提供的免费`AI视频生成器`服务,可以不用部署,直接在线使用,非常方便。
|
||||||
|
|
||||||
|
- 中文版:https://reccloud.cn
|
||||||
|
- 英文版:https://reccloud.com
|
||||||
|
|
||||||
|

|
||||||
5
sites/docs/zh/guide/speech-synthesis.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
## 语音合成 🗣
|
||||||
|
|
||||||
|
所有支持的声音列表,可以查看:[声音列表](/voice-list.txt)
|
||||||
|
|
||||||
|
2024-04-16 v1.1.2 新增了9种Azure的语音合成声音,需要配置API KEY,该声音合成的更加真实。
|
||||||
3
sites/docs/zh/guide/subtitle-font.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## 字幕字体 🅰
|
||||||
|
|
||||||
|
用于视频字幕的渲染,位于项目的 `resource/fonts` 目录下,你也可以放进去自己的字体。
|
||||||
36
sites/docs/zh/guide/subtitle-generation.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
## 字幕生成 📜
|
||||||
|
|
||||||
|
当前支持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
|
||||||
|
```
|
||||||
7
sites/docs/zh/guide/thanks-for-sponsoring.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## 感谢赞助 🙏
|
||||||
|
|
||||||
|
感谢佐糖 https://picwish.cn 对该项目的支持和赞助,使得该项目能够持续的更新和维护。
|
||||||
|
|
||||||
|
佐糖专注于**图像处理领域**,提供丰富的**图像处理工具**,将复杂操作极致简化,真正实现让图像处理更简单。
|
||||||
|
|
||||||
|

|
||||||
37
sites/docs/zh/guide/video-demonstration.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
## 视频演示 📺
|
||||||
|
|
||||||
|
### 竖屏 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>
|
||||||
24
sites/package.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
2284
sites/pnpm-lock.yaml
generated
Normal file
14
sites/tsconfig.config.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
24
sites/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
350
webui/Main.py
@@ -30,10 +30,11 @@ st.set_page_config(page_title="MoneyPrinterTurbo",
|
|||||||
"video.\n\nhttps://github.com/harry0703/MoneyPrinterTurbo"
|
"video.\n\nhttps://github.com/harry0703/MoneyPrinterTurbo"
|
||||||
})
|
})
|
||||||
|
|
||||||
from app.models.schema import VideoParams, VideoAspect, VideoConcatMode
|
from app.models.schema import VideoParams, VideoAspect, VideoConcatMode, MaterialInfo
|
||||||
from app.services import task as tm, llm, voice
|
from app.services import task as tm, llm, voice
|
||||||
from app.utils import utils
|
from app.utils import utils
|
||||||
from app.config import config
|
from app.config import config
|
||||||
|
from app.models.const import FILE_TYPE_VIDEOS, FILE_TYPE_IMAGES
|
||||||
|
|
||||||
hide_streamlit_style = """
|
hide_streamlit_style = """
|
||||||
<style>#root > div:nth-child(1) > div > div > div > div > section > div {padding-top: 0rem;}</style>
|
<style>#root > div:nth-child(1) > div > div > div > div > section > div {padding-top: 0rem;}</style>
|
||||||
@@ -41,11 +42,14 @@ hide_streamlit_style = """
|
|||||||
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|
||||||
st.title(f"MoneyPrinterTurbo v{config.project_version}")
|
st.title(f"MoneyPrinterTurbo v{config.project_version}")
|
||||||
|
|
||||||
|
support_locales = ["zh-CN", "zh-HK", "zh-TW", "de-DE", "en-US", "fr-FR", "vi-VN", "th-TH"]
|
||||||
|
|
||||||
font_dir = os.path.join(root_dir, "resource", "fonts")
|
font_dir = os.path.join(root_dir, "resource", "fonts")
|
||||||
song_dir = os.path.join(root_dir, "resource", "songs")
|
song_dir = os.path.join(root_dir, "resource", "songs")
|
||||||
i18n_dir = os.path.join(root_dir, "webui", "i18n")
|
i18n_dir = os.path.join(root_dir, "webui", "i18n")
|
||||||
config_file = os.path.join(root_dir, "webui", ".streamlit", "webui.toml")
|
config_file = os.path.join(root_dir, "webui", ".streamlit", "webui.toml")
|
||||||
system_locale = utils.get_system_locale()
|
system_locale = utils.get_system_locale()
|
||||||
|
# print(f"******** system locale: {system_locale} ********")
|
||||||
|
|
||||||
if 'video_subject' not in st.session_state:
|
if 'video_subject' not in st.session_state:
|
||||||
st.session_state['video_subject'] = ''
|
st.session_state['video_subject'] = ''
|
||||||
@@ -147,83 +151,206 @@ def tr(key):
|
|||||||
|
|
||||||
st.write(tr("Get Help"))
|
st.write(tr("Get Help"))
|
||||||
|
|
||||||
with st.expander(tr("Basic Settings"), expanded=False):
|
llm_provider = config.app.get("llm_provider", "").lower()
|
||||||
config_panels = st.columns(3)
|
|
||||||
left_config_panel = config_panels[0]
|
|
||||||
middle_config_panel = config_panels[1]
|
|
||||||
right_config_panel = config_panels[2]
|
|
||||||
with left_config_panel:
|
|
||||||
display_languages = []
|
|
||||||
selected_index = 0
|
|
||||||
for i, code in enumerate(locales.keys()):
|
|
||||||
display_languages.append(f"{code} - {locales[code].get('Language')}")
|
|
||||||
if code == st.session_state['ui_language']:
|
|
||||||
selected_index = i
|
|
||||||
|
|
||||||
selected_language = st.selectbox(tr("Language"), options=display_languages,
|
if not config.app.get("hide_config", False):
|
||||||
index=selected_index)
|
with st.expander(tr("Basic Settings"), expanded=False):
|
||||||
if selected_language:
|
config_panels = st.columns(3)
|
||||||
code = selected_language.split(" - ")[0].strip()
|
left_config_panel = config_panels[0]
|
||||||
st.session_state['ui_language'] = code
|
middle_config_panel = config_panels[1]
|
||||||
config.ui['language'] = code
|
right_config_panel = config_panels[2]
|
||||||
|
with left_config_panel:
|
||||||
|
display_languages = []
|
||||||
|
selected_index = 0
|
||||||
|
for i, code in enumerate(locales.keys()):
|
||||||
|
display_languages.append(f"{code} - {locales[code].get('Language')}")
|
||||||
|
if code == st.session_state['ui_language']:
|
||||||
|
selected_index = i
|
||||||
|
|
||||||
with middle_config_panel:
|
selected_language = st.selectbox(tr("Language"), options=display_languages,
|
||||||
# openai
|
index=selected_index)
|
||||||
# moonshot (月之暗面)
|
if selected_language:
|
||||||
# oneapi
|
code = selected_language.split(" - ")[0].strip()
|
||||||
# g4f
|
st.session_state['ui_language'] = code
|
||||||
# azure
|
config.ui['language'] = code
|
||||||
# qwen (通义千问)
|
|
||||||
# gemini
|
|
||||||
# ollama
|
|
||||||
llm_providers = ['OpenAI', 'Moonshot', 'Azure', 'Qwen', 'Gemini', 'Ollama', 'G4f', 'OneAPI', "Cloudflare"]
|
|
||||||
saved_llm_provider = config.app.get("llm_provider", "OpenAI").lower()
|
|
||||||
saved_llm_provider_index = 0
|
|
||||||
for i, provider in enumerate(llm_providers):
|
|
||||||
if provider.lower() == saved_llm_provider:
|
|
||||||
saved_llm_provider_index = i
|
|
||||||
break
|
|
||||||
|
|
||||||
llm_provider = st.selectbox(tr("LLM Provider"), options=llm_providers, index=saved_llm_provider_index)
|
with middle_config_panel:
|
||||||
llm_provider = llm_provider.lower()
|
# openai
|
||||||
config.app["llm_provider"] = llm_provider
|
# moonshot (月之暗面)
|
||||||
|
# oneapi
|
||||||
|
# g4f
|
||||||
|
# azure
|
||||||
|
# qwen (通义千问)
|
||||||
|
# gemini
|
||||||
|
# ollama
|
||||||
|
llm_providers = ['OpenAI', 'Moonshot', 'Azure', 'Qwen', 'DeepSeek', 'Gemini', 'Ollama', 'G4f', 'OneAPI',
|
||||||
|
"Cloudflare"]
|
||||||
|
saved_llm_provider = config.app.get("llm_provider", "OpenAI").lower()
|
||||||
|
saved_llm_provider_index = 0
|
||||||
|
for i, provider in enumerate(llm_providers):
|
||||||
|
if provider.lower() == saved_llm_provider:
|
||||||
|
saved_llm_provider_index = i
|
||||||
|
break
|
||||||
|
|
||||||
llm_api_key = config.app.get(f"{llm_provider}_api_key", "")
|
llm_provider = st.selectbox(tr("LLM Provider"), options=llm_providers, index=saved_llm_provider_index)
|
||||||
llm_base_url = config.app.get(f"{llm_provider}_base_url", "")
|
llm_helper = st.container()
|
||||||
llm_model_name = config.app.get(f"{llm_provider}_model_name", "")
|
llm_provider = llm_provider.lower()
|
||||||
llm_account_id = config.app.get(f"{llm_provider}_account_id", "")
|
config.app["llm_provider"] = llm_provider
|
||||||
st_llm_api_key = st.text_input(tr("API Key"), value=llm_api_key, type="password")
|
|
||||||
st_llm_base_url = st.text_input(tr("Base Url"), value=llm_base_url)
|
|
||||||
st_llm_model_name = st.text_input(tr("Model Name"), value=llm_model_name)
|
|
||||||
if st_llm_api_key:
|
|
||||||
config.app[f"{llm_provider}_api_key"] = st_llm_api_key
|
|
||||||
if st_llm_base_url:
|
|
||||||
config.app[f"{llm_provider}_base_url"] = st_llm_base_url
|
|
||||||
if st_llm_model_name:
|
|
||||||
config.app[f"{llm_provider}_model_name"] = st_llm_model_name
|
|
||||||
|
|
||||||
if llm_provider == 'cloudflare':
|
llm_api_key = config.app.get(f"{llm_provider}_api_key", "")
|
||||||
st_llm_account_id = st.text_input(tr("Account ID"), value=llm_account_id)
|
llm_base_url = config.app.get(f"{llm_provider}_base_url", "")
|
||||||
if st_llm_account_id:
|
llm_model_name = config.app.get(f"{llm_provider}_model_name", "")
|
||||||
config.app[f"{llm_provider}_account_id"] = st_llm_account_id
|
llm_account_id = config.app.get(f"{llm_provider}_account_id", "")
|
||||||
|
|
||||||
with right_config_panel:
|
tips = ""
|
||||||
pexels_api_keys = config.app.get("pexels_api_keys", [])
|
if llm_provider == 'ollama':
|
||||||
if isinstance(pexels_api_keys, str):
|
if not llm_model_name:
|
||||||
pexels_api_keys = [pexels_api_keys]
|
llm_model_name = "qwen:7b"
|
||||||
pexels_api_key = ", ".join(pexels_api_keys)
|
if not llm_base_url:
|
||||||
|
llm_base_url = "http://localhost:11434/v1"
|
||||||
|
|
||||||
pexels_api_key = st.text_input(tr("Pexels API Key"), value=pexels_api_key, type="password")
|
with llm_helper:
|
||||||
pexels_api_key = pexels_api_key.replace(" ", "")
|
tips = """
|
||||||
if pexels_api_key:
|
##### Ollama配置说明
|
||||||
config.app["pexels_api_keys"] = pexels_api_key.split(",")
|
- **API Key**: 随便填写,比如 123
|
||||||
|
- **Base Url**: 一般为 http://localhost:11434/v1
|
||||||
|
- 如果 `MoneyPrinterTurbo` 和 `Ollama` **不在同一台机器上**,需要填写 `Ollama` 机器的IP地址
|
||||||
|
- 如果 `MoneyPrinterTurbo` 是 `Docker` 部署,建议填写 `http://host.docker.internal:11434/v1`
|
||||||
|
- **Model Name**: 使用 `ollama list` 查看,比如 `qwen:7b`
|
||||||
|
"""
|
||||||
|
|
||||||
|
if llm_provider == 'openai':
|
||||||
|
if not llm_model_name:
|
||||||
|
llm_model_name = "gpt-3.5-turbo"
|
||||||
|
with llm_helper:
|
||||||
|
tips = """
|
||||||
|
##### OpenAI 配置说明
|
||||||
|
> 需要VPN开启全局流量模式
|
||||||
|
- **API Key**: [点击到官网申请](https://platform.openai.com/api-keys)
|
||||||
|
- **Base Url**: 可以留空
|
||||||
|
- **Model Name**: 填写**有权限**的模型,[点击查看模型列表](https://platform.openai.com/settings/organization/limits)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if llm_provider == 'moonshot':
|
||||||
|
if not llm_model_name:
|
||||||
|
llm_model_name = "moonshot-v1-8k"
|
||||||
|
with llm_helper:
|
||||||
|
tips = """
|
||||||
|
##### Moonshot 配置说明
|
||||||
|
- **API Key**: [点击到官网申请](https://platform.moonshot.cn/console/api-keys)
|
||||||
|
- **Base Url**: 固定为 https://api.moonshot.cn/v1
|
||||||
|
- **Model Name**: 比如 moonshot-v1-8k,[点击查看模型列表](https://platform.moonshot.cn/docs/intro#%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if llm_provider == 'qwen':
|
||||||
|
if not llm_model_name:
|
||||||
|
llm_model_name = "qwen-max"
|
||||||
|
with llm_helper:
|
||||||
|
tips = """
|
||||||
|
##### 通义千问Qwen 配置说明
|
||||||
|
- **API Key**: [点击到官网申请](https://dashscope.console.aliyun.com/apiKey)
|
||||||
|
- **Base Url**: 留空
|
||||||
|
- **Model Name**: 比如 qwen-max,[点击查看模型列表](https://help.aliyun.com/zh/dashscope/developer-reference/model-introduction#3ef6d0bcf91wy)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if llm_provider == 'g4f':
|
||||||
|
if not llm_model_name:
|
||||||
|
llm_model_name = "gpt-3.5-turbo"
|
||||||
|
with llm_helper:
|
||||||
|
tips = """
|
||||||
|
##### gpt4free 配置说明
|
||||||
|
> [GitHub开源项目](https://github.com/xtekky/gpt4free),可以免费使用GPT模型,但是**稳定性较差**
|
||||||
|
- **API Key**: 随便填写,比如 123
|
||||||
|
- **Base Url**: 留空
|
||||||
|
- **Model Name**: 比如 gpt-3.5-turbo,[点击查看模型列表](https://github.com/xtekky/gpt4free/blob/main/g4f/models.py#L308)
|
||||||
|
"""
|
||||||
|
if llm_provider == 'azure':
|
||||||
|
with llm_helper:
|
||||||
|
tips = """
|
||||||
|
##### Azure 配置说明
|
||||||
|
> [点击查看如何部署模型](https://learn.microsoft.com/zh-cn/azure/ai-services/openai/how-to/create-resource)
|
||||||
|
- **API Key**: [点击到Azure后台创建](https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/OpenAI)
|
||||||
|
- **Base Url**: 留空
|
||||||
|
- **Model Name**: 填写你实际的部署名
|
||||||
|
"""
|
||||||
|
|
||||||
|
if llm_provider == 'gemini':
|
||||||
|
if not llm_model_name:
|
||||||
|
llm_model_name = "gemini-1.0-pro"
|
||||||
|
|
||||||
|
with llm_helper:
|
||||||
|
tips = """
|
||||||
|
##### Gemini 配置说明
|
||||||
|
> 需要VPN开启全局流量模式
|
||||||
|
- **API Key**: [点击到官网申请](https://ai.google.dev/)
|
||||||
|
- **Base Url**: 留空
|
||||||
|
- **Model Name**: 比如 gemini-1.0-pro
|
||||||
|
"""
|
||||||
|
|
||||||
|
if llm_provider == 'deepseek':
|
||||||
|
if not llm_model_name:
|
||||||
|
llm_model_name = "deepseek-chat"
|
||||||
|
if not llm_base_url:
|
||||||
|
llm_base_url = "https://api.deepseek.com"
|
||||||
|
with llm_helper:
|
||||||
|
tips = """
|
||||||
|
##### DeepSeek 配置说明
|
||||||
|
- **API Key**: [点击到官网申请](https://platform.deepseek.com/api_keys)
|
||||||
|
- **Base Url**: 固定为 https://api.deepseek.com
|
||||||
|
- **Model Name**: 固定为 deepseek-chat
|
||||||
|
"""
|
||||||
|
|
||||||
|
if tips and config.ui['language'] == 'zh':
|
||||||
|
st.warning(
|
||||||
|
"中国用户建议使用 **DeepSeek** 或 **Moonshot** 作为大模型提供商\n- 国内可直接访问,不需要VPN \n- 注册就送额度,基本够用")
|
||||||
|
st.info(tips)
|
||||||
|
|
||||||
|
st_llm_api_key = st.text_input(tr("API Key"), value=llm_api_key, type="password")
|
||||||
|
st_llm_base_url = st.text_input(tr("Base Url"), value=llm_base_url)
|
||||||
|
st_llm_model_name = st.text_input(tr("Model Name"), value=llm_model_name)
|
||||||
|
|
||||||
|
if st_llm_api_key:
|
||||||
|
config.app[f"{llm_provider}_api_key"] = st_llm_api_key
|
||||||
|
if st_llm_base_url:
|
||||||
|
config.app[f"{llm_provider}_base_url"] = st_llm_base_url
|
||||||
|
if st_llm_model_name:
|
||||||
|
config.app[f"{llm_provider}_model_name"] = st_llm_model_name
|
||||||
|
|
||||||
|
if llm_provider == 'cloudflare':
|
||||||
|
st_llm_account_id = st.text_input(tr("Account ID"), value=llm_account_id)
|
||||||
|
if st_llm_account_id:
|
||||||
|
config.app[f"{llm_provider}_account_id"] = st_llm_account_id
|
||||||
|
|
||||||
|
with right_config_panel:
|
||||||
|
def get_keys_from_config(cfg_key):
|
||||||
|
api_keys = config.app.get(cfg_key, [])
|
||||||
|
if isinstance(api_keys, str):
|
||||||
|
api_keys = [api_keys]
|
||||||
|
api_key = ", ".join(api_keys)
|
||||||
|
return api_key
|
||||||
|
|
||||||
|
|
||||||
|
def save_keys_to_config(cfg_key, value):
|
||||||
|
value = value.replace(" ", "")
|
||||||
|
if value:
|
||||||
|
config.app[cfg_key] = value.split(",")
|
||||||
|
|
||||||
|
|
||||||
|
pexels_api_key = get_keys_from_config("pexels_api_keys")
|
||||||
|
pexels_api_key = st.text_input(tr("Pexels API Key"), value=pexels_api_key, type="password")
|
||||||
|
save_keys_to_config("pexels_api_keys", pexels_api_key)
|
||||||
|
|
||||||
|
pixabay_api_key = get_keys_from_config("pixabay_api_keys")
|
||||||
|
pixabay_api_key = st.text_input(tr("Pixabay API Key"), value=pixabay_api_key, type="password")
|
||||||
|
save_keys_to_config("pixabay_api_keys", pixabay_api_key)
|
||||||
|
|
||||||
panel = st.columns(3)
|
panel = st.columns(3)
|
||||||
left_panel = panel[0]
|
left_panel = panel[0]
|
||||||
middle_panel = panel[1]
|
middle_panel = panel[1]
|
||||||
right_panel = panel[2]
|
right_panel = panel[2]
|
||||||
|
|
||||||
params = VideoParams()
|
params = VideoParams(video_subject="")
|
||||||
|
uploaded_files = []
|
||||||
|
|
||||||
with left_panel:
|
with left_panel:
|
||||||
with st.container(border=True):
|
with st.container(border=True):
|
||||||
@@ -234,7 +361,7 @@ with left_panel:
|
|||||||
video_languages = [
|
video_languages = [
|
||||||
(tr("Auto Detect"), ""),
|
(tr("Auto Detect"), ""),
|
||||||
]
|
]
|
||||||
for code in ["zh-CN", "zh-TW", "de-DE", "en-US"]:
|
for code in support_locales:
|
||||||
video_languages.append((code, code))
|
video_languages.append((code, code))
|
||||||
|
|
||||||
selected_index = st.selectbox(tr("Script Language"),
|
selected_index = st.selectbox(tr("Script Language"),
|
||||||
@@ -277,6 +404,32 @@ with middle_panel:
|
|||||||
(tr("Sequential"), "sequential"),
|
(tr("Sequential"), "sequential"),
|
||||||
(tr("Random"), "random"),
|
(tr("Random"), "random"),
|
||||||
]
|
]
|
||||||
|
video_sources = [
|
||||||
|
(tr("Pexels"), "pexels"),
|
||||||
|
(tr("Pixabay"), "pixabay"),
|
||||||
|
(tr("Local file"), "local"),
|
||||||
|
(tr("TikTok"), "douyin"),
|
||||||
|
(tr("Bilibili"), "bilibili"),
|
||||||
|
(tr("Xiaohongshu"), "xiaohongshu"),
|
||||||
|
]
|
||||||
|
|
||||||
|
saved_video_source_name = config.app.get("video_source", "pexels")
|
||||||
|
saved_video_source_index = [v[1] for v in video_sources].index(saved_video_source_name)
|
||||||
|
|
||||||
|
selected_index = st.selectbox(tr("Video Source"),
|
||||||
|
options=range(len(video_sources)),
|
||||||
|
format_func=lambda x: video_sources[x][0],
|
||||||
|
index=saved_video_source_index
|
||||||
|
)
|
||||||
|
params.video_source = video_sources[selected_index][1]
|
||||||
|
config.app["video_source"] = params.video_source
|
||||||
|
|
||||||
|
if params.video_source == 'local':
|
||||||
|
_supported_types = FILE_TYPE_VIDEOS + FILE_TYPE_IMAGES
|
||||||
|
uploaded_files = st.file_uploader("Upload Local Files",
|
||||||
|
type=["mp4", "mov", "avi", "flv", "mkv", "jpg", "jpeg", "png"],
|
||||||
|
accept_multiple_files=True)
|
||||||
|
|
||||||
selected_index = st.selectbox(tr("Video Concat Mode"),
|
selected_index = st.selectbox(tr("Video Concat Mode"),
|
||||||
index=1,
|
index=1,
|
||||||
options=range(len(video_concat_modes)), # 使用索引作为内部选项值
|
options=range(len(video_concat_modes)), # 使用索引作为内部选项值
|
||||||
@@ -299,7 +452,12 @@ with middle_panel:
|
|||||||
index=0)
|
index=0)
|
||||||
with st.container(border=True):
|
with st.container(border=True):
|
||||||
st.write(tr("Audio Settings"))
|
st.write(tr("Audio Settings"))
|
||||||
voices = voice.get_all_azure_voices(filter_locals=["zh-CN", "zh-HK", "zh-TW", "de-DE", "en-US", "fr-FR"])
|
|
||||||
|
# tts_providers = ['edge', 'azure']
|
||||||
|
# tts_provider = st.selectbox(tr("TTS Provider"), tts_providers)
|
||||||
|
|
||||||
|
voices = voice.get_all_azure_voices(
|
||||||
|
filter_locals=support_locales)
|
||||||
friendly_names = {
|
friendly_names = {
|
||||||
v: v.
|
v: v.
|
||||||
replace("Female", tr("Female")).
|
replace("Female", tr("Female")).
|
||||||
@@ -312,7 +470,7 @@ with middle_panel:
|
|||||||
saved_voice_name_index = list(friendly_names.keys()).index(saved_voice_name)
|
saved_voice_name_index = list(friendly_names.keys()).index(saved_voice_name)
|
||||||
else:
|
else:
|
||||||
for i, v in enumerate(voices):
|
for i, v in enumerate(voices):
|
||||||
if v.lower().startswith(st.session_state['ui_language'].lower()):
|
if v.lower().startswith(st.session_state['ui_language'].lower()) and "V2" not in v:
|
||||||
saved_voice_name_index = i
|
saved_voice_name_index = i
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -323,6 +481,27 @@ with middle_panel:
|
|||||||
voice_name = list(friendly_names.keys())[list(friendly_names.values()).index(selected_friendly_name)]
|
voice_name = list(friendly_names.keys())[list(friendly_names.values()).index(selected_friendly_name)]
|
||||||
params.voice_name = voice_name
|
params.voice_name = voice_name
|
||||||
config.ui['voice_name'] = voice_name
|
config.ui['voice_name'] = voice_name
|
||||||
|
|
||||||
|
if st.button(tr("Play Voice")):
|
||||||
|
play_content = params.video_subject
|
||||||
|
if not play_content:
|
||||||
|
play_content = params.video_script
|
||||||
|
if not play_content:
|
||||||
|
play_content = tr("Voice Example")
|
||||||
|
with st.spinner(tr("Synthesizing Voice")):
|
||||||
|
temp_dir = utils.storage_dir("temp", create=True)
|
||||||
|
audio_file = os.path.join(temp_dir, f"tmp-voice-{str(uuid4())}.mp3")
|
||||||
|
sub_maker = voice.tts(text=play_content, voice_name=voice_name, voice_file=audio_file)
|
||||||
|
# if the voice file generation failed, try again with a default content.
|
||||||
|
if not sub_maker:
|
||||||
|
play_content = "This is a example voice. if you hear this, the voice synthesis failed with the original content."
|
||||||
|
sub_maker = voice.tts(text=play_content, voice_name=voice_name, voice_file=audio_file)
|
||||||
|
|
||||||
|
if sub_maker and os.path.exists(audio_file):
|
||||||
|
st.audio(audio_file, format="audio/mp3")
|
||||||
|
if os.path.exists(audio_file):
|
||||||
|
os.remove(audio_file)
|
||||||
|
|
||||||
if voice.is_azure_v2_voice(voice_name):
|
if voice.is_azure_v2_voice(voice_name):
|
||||||
saved_azure_speech_region = config.azure.get(f"speech_region", "")
|
saved_azure_speech_region = config.azure.get(f"speech_region", "")
|
||||||
saved_azure_speech_key = config.azure.get(f"speech_key", "")
|
saved_azure_speech_key = config.azure.get(f"speech_key", "")
|
||||||
@@ -344,10 +523,10 @@ with middle_panel:
|
|||||||
format_func=lambda x: bgm_options[x][0] # 显示给用户的是标签
|
format_func=lambda x: bgm_options[x][0] # 显示给用户的是标签
|
||||||
)
|
)
|
||||||
# 获取选择的背景音乐类型
|
# 获取选择的背景音乐类型
|
||||||
bgm_type = bgm_options[selected_index][1]
|
params.bgm_type = bgm_options[selected_index][1]
|
||||||
|
|
||||||
# 根据选择显示或隐藏组件
|
# 根据选择显示或隐藏组件
|
||||||
if bgm_type == "custom":
|
if params.bgm_type == "custom":
|
||||||
custom_bgm_file = st.text_input(tr("Custom Background Music File"))
|
custom_bgm_file = st.text_input(tr("Custom Background Music File"))
|
||||||
if custom_bgm_file and os.path.exists(custom_bgm_file):
|
if custom_bgm_file and os.path.exists(custom_bgm_file):
|
||||||
params.bgm_file = custom_bgm_file
|
params.bgm_file = custom_bgm_file
|
||||||
@@ -410,11 +589,34 @@ if start_button:
|
|||||||
scroll_to_bottom()
|
scroll_to_bottom()
|
||||||
st.stop()
|
st.stop()
|
||||||
|
|
||||||
if not config.app.get("pexels_api_keys", ""):
|
if params.video_source not in ["pexels", "pixabay", "local"]:
|
||||||
|
st.error(tr("Please Select a Valid Video Source"))
|
||||||
|
scroll_to_bottom()
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
if params.video_source == "pexels" and not config.app.get("pexels_api_keys", ""):
|
||||||
st.error(tr("Please Enter the Pexels API Key"))
|
st.error(tr("Please Enter the Pexels API Key"))
|
||||||
scroll_to_bottom()
|
scroll_to_bottom()
|
||||||
st.stop()
|
st.stop()
|
||||||
|
|
||||||
|
if params.video_source == "pixabay" and not config.app.get("pixabay_api_keys", ""):
|
||||||
|
st.error(tr("Please Enter the Pixabay API Key"))
|
||||||
|
scroll_to_bottom()
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
if uploaded_files:
|
||||||
|
local_videos_dir = utils.storage_dir("local_videos", create=True)
|
||||||
|
for file in uploaded_files:
|
||||||
|
file_path = os.path.join(local_videos_dir, f"{file.file_id}_{file.name}")
|
||||||
|
with open(file_path, "wb") as f:
|
||||||
|
f.write(file.getbuffer())
|
||||||
|
m = MaterialInfo()
|
||||||
|
m.provider = "local"
|
||||||
|
m.url = file_path
|
||||||
|
if not params.video_materials:
|
||||||
|
params.video_materials = []
|
||||||
|
params.video_materials.append(m)
|
||||||
|
|
||||||
log_container = st.empty()
|
log_container = st.empty()
|
||||||
log_records = []
|
log_records = []
|
||||||
|
|
||||||
@@ -433,12 +635,16 @@ if start_button:
|
|||||||
scroll_to_bottom()
|
scroll_to_bottom()
|
||||||
|
|
||||||
result = tm.start(task_id=task_id, params=params)
|
result = tm.start(task_id=task_id, params=params)
|
||||||
|
if not result or "videos" not in result:
|
||||||
|
st.error(tr("Video Generation Failed"))
|
||||||
|
logger.error(tr("Video Generation Failed"))
|
||||||
|
scroll_to_bottom()
|
||||||
|
st.stop()
|
||||||
|
|
||||||
video_files = result.get("videos", [])
|
video_files = result.get("videos", [])
|
||||||
st.success(tr("Video Generation Completed"))
|
st.success(tr("Video Generation Completed"))
|
||||||
try:
|
try:
|
||||||
if video_files:
|
if video_files:
|
||||||
# center the video player
|
|
||||||
player_cols = st.columns(len(video_files) * 2 + 1)
|
player_cols = st.columns(len(video_files) * 2 + 1)
|
||||||
for i, url in enumerate(video_files):
|
for i, url in enumerate(video_files):
|
||||||
player_cols[i * 2 + 1].video(url)
|
player_cols[i * 2 + 1].video(url)
|
||||||
|
|||||||
@@ -50,9 +50,11 @@
|
|||||||
"Generating Video": "Video wird erstellt, bitte warten...",
|
"Generating Video": "Video wird erstellt, bitte warten...",
|
||||||
"Start Generating Video": "Beginne mit der Generierung",
|
"Start Generating Video": "Beginne mit der Generierung",
|
||||||
"Video Generation Completed": "Video erfolgreich generiert",
|
"Video Generation Completed": "Video erfolgreich generiert",
|
||||||
|
"Video Generation Failed": "Video Generierung fehlgeschlagen",
|
||||||
"You can download the generated video from the following links": "Sie können das generierte Video über die folgenden Links herunterladen",
|
"You can download the generated video from the following links": "Sie können das generierte Video über die folgenden Links herunterladen",
|
||||||
"Basic Settings": "**Grunde Instellungen**",
|
"Basic Settings": "**Grunde Instellungen**",
|
||||||
"Pexels API Key": "Pexels API Key (:red[Required] [Get API Key](https://www.pexels.com/api/))",
|
"Pexels API Key": "Pexels API Key ([Get API Key](https://www.pexels.com/api/))",
|
||||||
|
"Pixabay API Key": "Pixabay API Key ([Get API Key](https://pixabay.com/api/docs/#api_search_videos))",
|
||||||
"Language": "Language",
|
"Language": "Language",
|
||||||
"LLM Provider": "LLM Provider",
|
"LLM Provider": "LLM Provider",
|
||||||
"API Key": "API Key (:red[Required])",
|
"API Key": "API Key (:red[Required])",
|
||||||
@@ -60,6 +62,16 @@
|
|||||||
"Model Name": "Model Name",
|
"Model Name": "Model Name",
|
||||||
"Please Enter the LLM API Key": "Please Enter the **LLM API Key**",
|
"Please Enter the LLM API Key": "Please Enter the **LLM API Key**",
|
||||||
"Please Enter the Pexels API Key": "Please Enter the **Pexels API Key**",
|
"Please Enter the Pexels API Key": "Please Enter the **Pexels API Key**",
|
||||||
"Get Help": "If you need help, or have any questions, you can join discord for help: https://harryai.cc"
|
"Please Enter the Pixabay API Key": "Please Enter the **Pixabay API Key**",
|
||||||
|
"Get Help": "If you need help, or have any questions, you can join discord for help: https://harryai.cc",
|
||||||
|
"Video Source": "Video Source",
|
||||||
|
"TikTok": "TikTok (TikTok support is coming soon)",
|
||||||
|
"Bilibili": "Bilibili (Bilibili support is coming soon)",
|
||||||
|
"Xiaohongshu": "Xiaohongshu (Xiaohongshu support is coming soon)",
|
||||||
|
"Local file": "Local file",
|
||||||
|
"Play Voice": "Play Voice",
|
||||||
|
"Voice Example": "This is an example text for testing speech synthesis",
|
||||||
|
"Synthesizing Voice": "Synthesizing voice, please wait...",
|
||||||
|
"TTS Provider": "Select the voice synthesis provider"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,8 +50,10 @@
|
|||||||
"Generating Video": "Generating video, please wait...",
|
"Generating Video": "Generating video, please wait...",
|
||||||
"Start Generating Video": "Start Generating Video",
|
"Start Generating Video": "Start Generating Video",
|
||||||
"Video Generation Completed": "Video Generation Completed",
|
"Video Generation Completed": "Video Generation Completed",
|
||||||
|
"Video Generation Failed": "Video Generation Failed",
|
||||||
"You can download the generated video from the following links": "You can download the generated video from the following links",
|
"You can download the generated video from the following links": "You can download the generated video from the following links",
|
||||||
"Pexels API Key": "Pexels API Key (:red[Required] [Get API Key](https://www.pexels.com/api/))",
|
"Pexels API Key": "Pexels API Key ([Get API Key](https://www.pexels.com/api/))",
|
||||||
|
"Pixabay API Key": "Pixabay API Key ([Get API Key](https://pixabay.com/api/docs/#api_search_videos))",
|
||||||
"Basic Settings": "**Basic Settings** (:blue[Click to expand])",
|
"Basic Settings": "**Basic Settings** (:blue[Click to expand])",
|
||||||
"Language": "Language",
|
"Language": "Language",
|
||||||
"LLM Provider": "LLM Provider",
|
"LLM Provider": "LLM Provider",
|
||||||
@@ -61,6 +63,16 @@
|
|||||||
"Model Name": "Model Name",
|
"Model Name": "Model Name",
|
||||||
"Please Enter the LLM API Key": "Please Enter the **LLM API Key**",
|
"Please Enter the LLM API Key": "Please Enter the **LLM API Key**",
|
||||||
"Please Enter the Pexels API Key": "Please Enter the **Pexels API Key**",
|
"Please Enter the Pexels API Key": "Please Enter the **Pexels API Key**",
|
||||||
"Get Help": "If you need help, or have any questions, you can join discord for help: https://harryai.cc"
|
"Please Enter the Pixabay API Key": "Please Enter the **Pixabay API Key**",
|
||||||
|
"Get Help": "If you need help, or have any questions, you can join discord for help: https://harryai.cc",
|
||||||
|
"Video Source": "Video Source",
|
||||||
|
"TikTok": "TikTok (TikTok support is coming soon)",
|
||||||
|
"Bilibili": "Bilibili (Bilibili support is coming soon)",
|
||||||
|
"Xiaohongshu": "Xiaohongshu (Xiaohongshu support is coming soon)",
|
||||||
|
"Local file": "Local file",
|
||||||
|
"Play Voice": "Play Voice",
|
||||||
|
"Voice Example": "This is an example text for testing speech synthesis",
|
||||||
|
"Synthesizing Voice": "Synthesizing voice, please wait...",
|
||||||
|
"TTS Provider": "Select the voice synthesis provider"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
78
webui/i18n/vi.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"Language": "Tiếng Việt",
|
||||||
|
"Translation": {
|
||||||
|
"Video Script Settings": "**Cài Đặt Kịch Bản Video**",
|
||||||
|
"Video Subject": "Chủ Đề Video (Cung cấp một từ khóa, :red[AI sẽ tự động tạo ra] kịch bản video)",
|
||||||
|
"Script Language": "Ngôn Ngữ cho Việc Tạo Kịch Bản Video (AI sẽ tự động xuất ra dựa trên ngôn ngữ của chủ đề của bạn)",
|
||||||
|
"Generate Video Script and Keywords": "Nhấn để sử dụng AI để tạo [Kịch Bản Video] và [Từ Khóa Video] dựa trên **chủ đề**",
|
||||||
|
"Auto Detect": "Tự Động Phát Hiện",
|
||||||
|
"Video Script": "Kịch Bản Video (:blue[① Tùy chọn, AI tạo ra ② Dấu câu chính xác giúp việc tạo phụ đề)",
|
||||||
|
"Generate Video Keywords": "Nhấn để sử dụng AI để tạo [Từ Khóa Video] dựa trên **kịch bản**",
|
||||||
|
"Please Enter the Video Subject": "Vui lòng Nhập Kịch Bản Video Trước",
|
||||||
|
"Generating Video Script and Keywords": "AI đang tạo kịch bản video và từ khóa...",
|
||||||
|
"Generating Video Keywords": "AI đang tạo từ khóa video...",
|
||||||
|
"Video Keywords": "Từ Khóa Video (:blue[① Tùy chọn, AI tạo ra ② Sử dụng dấu phẩy **Tiếng Anh** để phân tách, chỉ sử dụng Tiếng Anh])",
|
||||||
|
"Video Settings": "**Cài Đặt Video**",
|
||||||
|
"Video Concat Mode": "Chế Độ Nối Video",
|
||||||
|
"Random": "Nối Ngẫu Nhiên (Được Khuyến Nghị)",
|
||||||
|
"Sequential": "Nối Theo Thứ Tự",
|
||||||
|
"Video Ratio": "Tỷ Lệ Khung Hình Video",
|
||||||
|
"Portrait": "Dọc 9:16",
|
||||||
|
"Landscape": "Ngang 16:9",
|
||||||
|
"Clip Duration": "Thời Lượng Tối Đa Của Đoạn Video (giây)",
|
||||||
|
"Number of Videos Generated Simultaneously": "Số Video Được Tạo Ra Đồng Thời",
|
||||||
|
"Audio Settings": "**Cài Đặt Âm Thanh**",
|
||||||
|
"Speech Synthesis": "Giọng Đọc Văn Bản",
|
||||||
|
"Speech Region": "Vùng(:red[Bắt Buộc,[Lấy Vùng](https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/SpeechServices)])",
|
||||||
|
"Speech Key": "Khóa API(:red[Bắt Buộc,[Lấy Khóa API](https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/SpeechServices)])",
|
||||||
|
"Speech Volume": "Âm Lượng Giọng Đọc (1.0 đại diện cho 100%)",
|
||||||
|
"Male": "Nam",
|
||||||
|
"Female": "Nữ",
|
||||||
|
"Background Music": "Âm Nhạc Nền",
|
||||||
|
"No Background Music": "Không Có Âm Nhạc Nền",
|
||||||
|
"Random Background Music": "Âm Nhạc Nền Ngẫu Nhiên",
|
||||||
|
"Custom Background Music": "Âm Nhạc Nền Tùy Chỉnh",
|
||||||
|
"Custom Background Music File": "Vui lòng nhập đường dẫn tệp cho âm nhạc nền tùy chỉnh:",
|
||||||
|
"Background Music Volume": "Âm Lượng Âm Nhạc Nền (0.2 đại diện cho 20%, âm nhạc nền không nên quá to)",
|
||||||
|
"Subtitle Settings": "**Cài Đặt Phụ Đề**",
|
||||||
|
"Enable Subtitles": "Bật Phụ Đề (Nếu không chọn, các cài đặt dưới đây sẽ không có hiệu lực)",
|
||||||
|
"Font": "Phông Chữ Phụ Đề",
|
||||||
|
"Position": "Vị Trí Phụ Đề",
|
||||||
|
"Top": "Trên",
|
||||||
|
"Center": "Giữa",
|
||||||
|
"Bottom": "Dưới (Được Khuyến Nghị)",
|
||||||
|
"Font Size": "Cỡ Chữ Phụ Đề",
|
||||||
|
"Font Color": "Màu Chữ Phụ Đề",
|
||||||
|
"Stroke Color": "Màu Viền Phụ Đề",
|
||||||
|
"Stroke Width": "Độ Rộng Viền Phụ Đề",
|
||||||
|
"Generate Video": "Tạo Video",
|
||||||
|
"Video Script and Subject Cannot Both Be Empty": "Chủ Đề Video và Kịch Bản Video không thể cùng trống",
|
||||||
|
"Generating Video": "Đang tạo video, vui lòng đợi...",
|
||||||
|
"Start Generating Video": "Bắt Đầu Tạo Video",
|
||||||
|
"Video Generation Completed": "Hoàn Tất Tạo Video",
|
||||||
|
"Video Generation Failed": "Tạo Video Thất Bại",
|
||||||
|
"You can download the generated video from the following links": "Bạn có thể tải video được tạo ra từ các liên kết sau",
|
||||||
|
"Pexels API Key": "Khóa API Pexels ([Lấy Khóa API](https://www.pexels.com/api/))",
|
||||||
|
"Pixabay API Key": "Pixabay API Key ([Get API Key](https://pixabay.com/api/docs/#api_search_videos))",
|
||||||
|
"Basic Settings": "**Cài Đặt Cơ Bản** (:blue[Nhấp để mở rộng])",
|
||||||
|
"Language": "Ngôn Ngữ",
|
||||||
|
"LLM Provider": "Nhà Cung Cấp LLM",
|
||||||
|
"API Key": "Khóa API (:red[Bắt Buộc])",
|
||||||
|
"Base Url": "Url Cơ Bản",
|
||||||
|
"Account ID": "ID Tài Khoản (Lấy từ bảng điều khiển Cloudflare)",
|
||||||
|
"Model Name": "Tên Mô Hình",
|
||||||
|
"Please Enter the LLM API Key": "Vui lòng Nhập **Khóa API LLM**",
|
||||||
|
"Please Enter the Pexels API Key": "Vui lòng Nhập **Khóa API Pexels**",
|
||||||
|
"Please Enter the Pixabay API Key": "Vui lòng Nhập **Pixabay API Key**",
|
||||||
|
"Get Help": "Nếu bạn cần giúp đỡ hoặc có bất kỳ câu hỏi nào, bạn có thể tham gia discord để được giúp đỡ: https://harryai.cc",
|
||||||
|
"Video Source": "Video Source",
|
||||||
|
"TikTok": "TikTok (TikTok support is coming soon)",
|
||||||
|
"Bilibili": "Bilibili (Bilibili support is coming soon)",
|
||||||
|
"Xiaohongshu": "Xiaohongshu (Xiaohongshu support is coming soon)",
|
||||||
|
"Local file": "Local file",
|
||||||
|
"Play Voice": "Play Voice",
|
||||||
|
"Voice Example": "This is an example text for testing speech synthesis",
|
||||||
|
"Synthesizing Voice": "Synthesizing voice, please wait...",
|
||||||
|
"TTS Provider": "Select the voice synthesis provider"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,12 +19,12 @@
|
|||||||
"Video Ratio": "视频比例",
|
"Video Ratio": "视频比例",
|
||||||
"Portrait": "竖屏 9:16(抖音视频)",
|
"Portrait": "竖屏 9:16(抖音视频)",
|
||||||
"Landscape": "横屏 16:9(西瓜视频)",
|
"Landscape": "横屏 16:9(西瓜视频)",
|
||||||
"Clip Duration": "视频片段最大时长(秒)",
|
"Clip Duration": "视频片段最大时长(秒)(**不是视频总长度**,是指每个**合成片段**的长度)",
|
||||||
"Number of Videos Generated Simultaneously": "同时生成视频数量",
|
"Number of Videos Generated Simultaneously": "同时生成视频数量",
|
||||||
"Audio Settings": "**音频设置**",
|
"Audio Settings": "**音频设置**",
|
||||||
"Speech Synthesis": "朗读声音(:red[尽量与文案语言保持一致])",
|
"Speech Synthesis": "朗读声音(:red[**与文案语言保持一致**。注意:V2版效果更好,但是需要API KEY])",
|
||||||
"Speech Region": "服务区域(:red[必填,[点击获取](https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/SpeechServices)])",
|
"Speech Region": "服务区域 (:red[必填,[点击获取](https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/SpeechServices)])",
|
||||||
"Speech Key": "API Key(:red[必填,[点击获取](https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/SpeechServices)])",
|
"Speech Key": "API Key (:red[必填,密钥1 或 密钥2 均可 [点击获取](https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/SpeechServices)])",
|
||||||
"Speech Volume": "朗读音量(1.0表示100%)",
|
"Speech Volume": "朗读音量(1.0表示100%)",
|
||||||
"Male": "男性",
|
"Male": "男性",
|
||||||
"Female": "女性",
|
"Female": "女性",
|
||||||
@@ -50,10 +50,12 @@
|
|||||||
"Generating Video": "正在生成视频,请稍候...",
|
"Generating Video": "正在生成视频,请稍候...",
|
||||||
"Start Generating Video": "开始生成视频",
|
"Start Generating Video": "开始生成视频",
|
||||||
"Video Generation Completed": "视频生成完成",
|
"Video Generation Completed": "视频生成完成",
|
||||||
|
"Video Generation Failed": "视频生成失败",
|
||||||
"You can download the generated video from the following links": "你可以从以下链接下载生成的视频",
|
"You can download the generated video from the following links": "你可以从以下链接下载生成的视频",
|
||||||
"Basic Settings": "**基础设置** (:blue[点击展开])",
|
"Basic Settings": "**基础设置** (:blue[点击展开])",
|
||||||
"Language": "界面语言",
|
"Language": "界面语言",
|
||||||
"Pexels API Key": "Pexels API Key (:red[必填] [点击获取](https://www.pexels.com/api/))",
|
"Pexels API Key": "Pexels API Key ([点击获取](https://www.pexels.com/api/))",
|
||||||
|
"Pixabay API Key": "Pixabay API Key ([点击获取](https://pixabay.com/api/docs/#api_search_videos))",
|
||||||
"LLM Provider": "大模型提供商",
|
"LLM Provider": "大模型提供商",
|
||||||
"API Key": "API Key (:red[必填,需要到大模型提供商的后台申请])",
|
"API Key": "API Key (:red[必填,需要到大模型提供商的后台申请])",
|
||||||
"Base Url": "Base Url (可选)",
|
"Base Url": "Base Url (可选)",
|
||||||
@@ -61,6 +63,16 @@
|
|||||||
"Model Name": "模型名称 (:blue[需要到大模型提供商的后台确认被授权的模型名称])",
|
"Model Name": "模型名称 (:blue[需要到大模型提供商的后台确认被授权的模型名称])",
|
||||||
"Please Enter the LLM API Key": "请先填写大模型 **API Key**",
|
"Please Enter the LLM API Key": "请先填写大模型 **API Key**",
|
||||||
"Please Enter the Pexels API Key": "请先填写 **Pexels API Key**",
|
"Please Enter the Pexels API Key": "请先填写 **Pexels API Key**",
|
||||||
"Get Help": "有任何问题或建议,可以加入 **微信群** 求助或讨论:https://harryai.cc"
|
"Please Enter the Pixabay API Key": "请先填写 **Pixabay API Key**",
|
||||||
|
"Get Help": "有任何问题或建议,可以加入 **微信群** 求助或讨论:https://harryai.cc",
|
||||||
|
"Video Source": "视频来源",
|
||||||
|
"TikTok": "抖音 (TikTok 支持中,敬请期待)",
|
||||||
|
"Bilibili": "哔哩哔哩 (Bilibili 支持中,敬请期待)",
|
||||||
|
"Xiaohongshu": "小红书 (Xiaohongshu 支持中,敬请期待)",
|
||||||
|
"Local file": "本地文件",
|
||||||
|
"Play Voice": "试听语音合成",
|
||||||
|
"Voice Example": "这是一段测试语音合成的示例文本",
|
||||||
|
"Synthesizing Voice": "语音合成中,请稍候...",
|
||||||
|
"TTS Provider": "语音合成提供商"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||