18 Commits

Author SHA1 Message Date
Harry
63c3402c94 Update version to 1.2.2 2024-12-06 13:45:43 +08:00
Harry
5a6dd6c7a5 Merge pull request #541 from yyhhyyyyyy/update-requirements
⬆️ deps: Upgrade dependencies to latest versions and address minor issues
2024-12-05 11:02:14 +08:00
yyhhyy
8c226322a0 Merge branch 'main' into update-requirements 2024-12-05 10:59:41 +08:00
Harry
3a7888937f Merge pull request #536 from Felix3322/main
better requirements.txt
2024-12-05 10:47:26 +08:00
yyhhyyyyyy
6760a0ad00 📝 docs: Update documentation 2024-12-05 10:34:09 +08:00
yyhhyyyyyy
6288b70ae2 ⬆️ deps: Upgrade dependencies to latest versions and address minor issues 2024-12-05 10:16:38 +08:00
Jiaying Liu
4adc010388 Update requirements.txt 2024-11-27 15:04:46 -05:00
Harry
162b5e17c3 Merge pull request #508 from flingjie/main
allow api key empty when using ollama
2024-11-20 15:45:40 +08:00
Harry
0d43ba2124 Merge pull request #505 from LucasHenriqueDiniz/main
feat: add PT-BR translation
2024-11-20 15:45:18 +08:00
Harry
080d8d82b4 Merge pull request #504 from Dreyfi/fix-403-error-pexels-request
Fix the response 403 from pexels - search_videos_pexels - failed to download videos, maybe the network is not available. if you are in China, please use a VPN.
2024-11-20 15:44:46 +08:00
Harry
fc50e16bc5 Merge pull request #486 from FLY-Open-AI/main
[Readme]Docker部署,启动命令优化。
2024-11-20 15:44:08 +08:00
Jie.F
345b6d59a1 allow api key empty when using ollama
the ollama API key is not required
2024-10-08 09:44:39 +08:00
Dreyfi
4ec19fd56a Add headers with user_agent to save_video request 2024-09-30 15:48:54 +10:00
Lucas Diniz
136630ec60 feat: add PT-BR translation 2024-09-29 19:30:12 -03:00
Dreyfi
9d3d99a595 Fix the response 403 from pexels
search_videos_pexels - failed to download videos, maybe the network is not available. if you are in China, please use a VPN.
2024-09-28 16:25:53 +10:00
wangyanfei
747c745ec0 [Readme]Docker部署,启动命令优化。最新版的docker安装时会自动以插件的形式安装docker compose,启动命令调整为docker compose up 2024-08-31 07:22:05 +08:00
Harry
a53ca843e8 Merge pull request #467 from harry0703/dev
update readme
2024-07-26 18:23:52 +08:00
harry
8b18d84d8a update readme 2024-07-26 18:23:04 +08:00
14 changed files with 189 additions and 71 deletions

View File

@@ -1,5 +1,5 @@
# Use an official Python runtime as a parent image # Use an official Python runtime as a parent image
FROM python:3.10-slim-bullseye FROM python:3.11-slim-bullseye
# Set the working directory in the container # Set the working directory in the container
WORKDIR /MoneyPrinterTurbo WORKDIR /MoneyPrinterTurbo

View File

@@ -152,6 +152,8 @@ cd MoneyPrinterTurbo
docker-compose up docker-compose up
``` ```
> NoteThe latest version of docker will automatically install docker compose in the form of a plug-in, and the start command is adjusted to `docker compose up `
#### ② Access the Web Interface #### ② Access the Web Interface
Open your browser and visit http://0.0.0.0:8501 Open your browser and visit http://0.0.0.0:8501
@@ -170,7 +172,7 @@ using [conda](https://conda.io/projects/conda/en/latest/user-guide/install/index
```shell ```shell
git clone https://github.com/harry0703/MoneyPrinterTurbo.git git clone https://github.com/harry0703/MoneyPrinterTurbo.git
cd MoneyPrinterTurbo cd MoneyPrinterTurbo
conda create -n MoneyPrinterTurbo python=3.10 conda create -n MoneyPrinterTurbo python=3.11
conda activate MoneyPrinterTurbo conda activate MoneyPrinterTurbo
pip install -r requirements.txt pip install -r requirements.txt
``` ```

View File

@@ -124,7 +124,7 @@
下载一键启动包,解压直接使用(路径不要有 **中文**、**特殊字符**、**空格** 下载一键启动包,解压直接使用(路径不要有 **中文**、**特殊字符**、**空格**
### Windows ### Windows
- 百度网盘1.2.0 最新版本): https://pan.baidu.com/s/1gVmFSCleHybiIiW-8ETk_A?pwd=dwt8 提取码: dwt8 - 百度网盘1.2.1 最新版本): https://pan.baidu.com/s/1pSNjxTYiVENulTLm6zieMQ?pwd=g36q 提取码: g36q
下载后,建议先**双击执行** `update.bat` 更新到**最新代码**,然后双击 `start.bat` 启动 下载后,建议先**双击执行** `update.bat` 更新到**最新代码**,然后双击 `start.bat` 启动
@@ -169,6 +169,8 @@ cd MoneyPrinterTurbo
docker-compose up docker-compose up
``` ```
> 注意最新版的docker安装时会自动以插件的形式安装docker compose启动命令调整为docker compose up
#### ② 访问Web界面 #### ② 访问Web界面
打开浏览器,访问 http://0.0.0.0:8501 打开浏览器,访问 http://0.0.0.0:8501
@@ -191,7 +193,7 @@ docker-compose up
```shell ```shell
git clone https://github.com/harry0703/MoneyPrinterTurbo.git git clone https://github.com/harry0703/MoneyPrinterTurbo.git
cd MoneyPrinterTurbo cd MoneyPrinterTurbo
conda create -n MoneyPrinterTurbo python=3.10 conda create -n MoneyPrinterTurbo python=3.11
conda activate MoneyPrinterTurbo conda activate MoneyPrinterTurbo
pip install -r requirements.txt pip install -r requirements.txt
``` ```

View File

@@ -56,7 +56,7 @@ project_description = _cfg.get(
"project_description", "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.2.1") project_version = _cfg.get("project_version", "1.2.2")
reload_debug = False reload_debug = False
imagemagick_path = app.get("imagemagick_path", "") imagemagick_path = app.get("imagemagick_path", "")

View File

@@ -1,6 +1,6 @@
import warnings import warnings
from enum import Enum from enum import Enum
from typing import Any, List, Optional from typing import Any, List, Optional, Union
import pydantic import pydantic
from pydantic import BaseModel from pydantic import BaseModel
@@ -122,7 +122,7 @@ class VideoParams(BaseModel):
custom_position: float = 70.0 custom_position: float = 70.0
font_name: Optional[str] = "STHeitiMedium.ttc" font_name: Optional[str] = "STHeitiMedium.ttc"
text_fore_color: Optional[str] = "#FFFFFF" text_fore_color: Optional[str] = "#FFFFFF"
text_background_color: Optional[str] = "transparent" text_background_color: Union[bool, str] = True
font_size: int = 60 font_size: int = 60
stroke_color: Optional[str] = "#000000" stroke_color: Optional[str] = "#000000"
@@ -143,7 +143,7 @@ class SubtitleRequest(BaseModel):
subtitle_position: Optional[str] = "bottom" subtitle_position: Optional[str] = "bottom"
font_name: Optional[str] = "STHeitiMedium.ttc" font_name: Optional[str] = "STHeitiMedium.ttc"
text_fore_color: Optional[str] = "#FFFFFF" text_fore_color: Optional[str] = "#FFFFFF"
text_background_color: Optional[str] = "transparent" text_background_color: Union[bool, str] = True
font_size: int = 60 font_size: int = 60
stroke_color: Optional[str] = "#000000" stroke_color: Optional[str] = "#000000"
stroke_width: float = 1.5 stroke_width: float = 1.5

View File

@@ -40,7 +40,10 @@ def search_videos_pexels(
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") api_key = get_api_key("pexels_api_keys")
headers = {"Authorization": api_key} headers = {
"Authorization": api_key,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
}
# Build URL # Build URL
params = {"query": search_term, "per_page": 20, "orientation": video_orientation} params = {"query": search_term, "per_page": 20, "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)}"
@@ -158,11 +161,15 @@ def save_video(video_url: str, save_dir: str = "") -> str:
logger.info(f"video already exists: {video_path}") logger.info(f"video already exists: {video_path}")
return video_path return video_path
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
}
# if video does not exist, download it # if video does not exist, download it
with open(video_path, "wb") as f: with open(video_path, "wb") as f:
f.write( f.write(
requests.get( requests.get(
video_url, proxies=config.proxy, verify=False, timeout=(60, 240) video_url, headers=headers, proxies=config.proxy, verify=False, timeout=(60, 240)
).content ).content
) )

View File

@@ -1,9 +1,10 @@
import glob import glob
import os
import random import random
from typing import List from typing import List
from loguru import logger from loguru import logger
from moviepy.editor import * from moviepy import *
from moviepy.video.tools.subtitles import SubtitlesClip from moviepy.video.tools.subtitles import SubtitlesClip
from PIL import ImageFont from PIL import ImageFont
@@ -60,7 +61,7 @@ def combine_videos(
while start_time < clip_duration: while start_time < clip_duration:
end_time = min(start_time + max_clip_duration, clip_duration) end_time = min(start_time + max_clip_duration, clip_duration)
split_clip = clip.subclip(start_time, end_time) split_clip = clip.subclipped(start_time, end_time)
raw_clips.append(split_clip) 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}") # 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 start_time = end_time
@@ -76,11 +77,11 @@ def combine_videos(
for clip in raw_clips: for clip in raw_clips:
# 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.subclipped(0, (audio_duration - video_duration))
# Only shorten clips if the calculated clip length (req_dur) is shorter than the actual clip to prevent still image # Only shorten clips if the calculated clip length (req_dur) is shorter than the actual clip to prevent still image
elif req_dur < clip.duration: elif req_dur < clip.duration:
clip = clip.subclip(0, req_dur) clip = clip.subclipped(0, req_dur)
clip = clip.set_fps(30) clip = clip.with_fps(30)
# Not all videos are same size, so we need to resize them # Not all videos are same size, so we need to resize them
clip_w, clip_h = clip.size clip_w, clip_h = clip.size
@@ -90,7 +91,7 @@ def combine_videos(
if clip_ratio == video_ratio: if clip_ratio == video_ratio:
# 等比例缩放 # 等比例缩放
clip = clip.resize((video_width, video_height)) clip = clip.resized((video_width, video_height))
else: else:
# 等比缩放视频 # 等比缩放视频
if clip_ratio > video_ratio: if clip_ratio > video_ratio:
@@ -102,15 +103,15 @@ def combine_videos(
new_width = int(clip_w * scale_factor) new_width = int(clip_w * scale_factor)
new_height = int(clip_h * scale_factor) new_height = int(clip_h * scale_factor)
clip_resized = clip.resize(newsize=(new_width, new_height)) clip_resized = clip.resized(new_size=(new_width, new_height))
background = ColorClip( background = ColorClip(
size=(video_width, video_height), color=(0, 0, 0) size=(video_width, video_height), color=(0, 0, 0)
) )
clip = CompositeVideoClip( clip = CompositeVideoClip(
[ [
background.set_duration(clip.duration), background.with_duration(clip.duration),
clip_resized.set_position("center"), clip_resized.with_position("center"),
] ]
) )
@@ -119,13 +120,13 @@ def combine_videos(
) )
if clip.duration > max_clip_duration: if clip.duration > max_clip_duration:
clip = clip.subclip(0, max_clip_duration) clip = clip.subclipped(0, max_clip_duration)
clips.append(clip) clips.append(clip)
video_duration += clip.duration video_duration += clip.duration
video_clip = concatenate_videoclips(clips) video_clip = concatenate_videoclips(clips)
video_clip = video_clip.set_fps(30) video_clip = video_clip.with_fps(30)
logger.info("writing") logger.info("writing")
# https://github.com/harry0703/MoneyPrinterTurbo/issues/111#issuecomment-2032354030 # https://github.com/harry0703/MoneyPrinterTurbo/issues/111#issuecomment-2032354030
video_clip.write_videofile( video_clip.write_videofile(
@@ -231,29 +232,30 @@ def generate_video(
logger.info(f"using font: {font_path}") logger.info(f"using font: {font_path}")
def create_text_clip(subtitle_item): def create_text_clip(subtitle_item):
params.font_size = int(params.font_size)
params.stroke_width = int(params.stroke_width)
phrase = subtitle_item[1] phrase = subtitle_item[1]
max_width = video_width * 0.9 max_width = video_width * 0.9
wrapped_txt, txt_height = wrap_text( wrapped_txt, txt_height = wrap_text(
phrase, max_width=max_width, font=font_path, fontsize=params.font_size phrase, max_width=max_width, font=font_path, fontsize=params.font_size
) )
_clip = TextClip( _clip = TextClip(
wrapped_txt, text=wrapped_txt,
font=font_path, font=font_path,
fontsize=params.font_size, font_size=params.font_size,
color=params.text_fore_color, color=params.text_fore_color,
bg_color=params.text_background_color, bg_color=params.text_background_color,
stroke_color=params.stroke_color, stroke_color=params.stroke_color,
stroke_width=params.stroke_width, stroke_width=params.stroke_width,
print_cmd=False,
) )
duration = subtitle_item[0][1] - subtitle_item[0][0] duration = subtitle_item[0][1] - subtitle_item[0][0]
_clip = _clip.set_start(subtitle_item[0][0]) _clip = _clip.with_start(subtitle_item[0][0])
_clip = _clip.set_end(subtitle_item[0][1]) _clip = _clip.with_end(subtitle_item[0][1])
_clip = _clip.set_duration(duration) _clip = _clip.with_duration(duration)
if params.subtitle_position == "bottom": if params.subtitle_position == "bottom":
_clip = _clip.set_position(("center", video_height * 0.95 - _clip.h)) _clip = _clip.with_position(("center", video_height * 0.95 - _clip.h))
elif params.subtitle_position == "top": elif params.subtitle_position == "top":
_clip = _clip.set_position(("center", video_height * 0.05)) _clip = _clip.with_position(("center", video_height * 0.05))
elif params.subtitle_position == "custom": elif params.subtitle_position == "custom":
# 确保字幕完全在屏幕内 # 确保字幕完全在屏幕内
margin = 10 # 额外的边距,单位为像素 margin = 10 # 额外的边距,单位为像素
@@ -261,16 +263,25 @@ def generate_video(
min_y = margin min_y = margin
custom_y = (video_height - _clip.h) * (params.custom_position / 100) custom_y = (video_height - _clip.h) * (params.custom_position / 100)
custom_y = max(min_y, min(custom_y, max_y)) # 限制 y 值在有效范围内 custom_y = max(min_y, min(custom_y, max_y)) # 限制 y 值在有效范围内
_clip = _clip.set_position(("center", custom_y)) _clip = _clip.with_position(("center", custom_y))
else: # center else: # center
_clip = _clip.set_position(("center", "center")) _clip = _clip.with_position(("center", "center"))
return _clip return _clip
video_clip = VideoFileClip(video_path) video_clip = VideoFileClip(video_path)
audio_clip = AudioFileClip(audio_path).volumex(params.voice_volume) audio_clip = AudioFileClip(audio_path).with_effects(
[afx.MultiplyVolume(params.voice_volume)]
)
if subtitle_path and os.path.exists(subtitle_path): if subtitle_path and os.path.exists(subtitle_path):
sub = SubtitlesClip(subtitles=subtitle_path, encoding="utf-8") generator = lambda text: TextClip(
text=text,
font=font_path,
font_size=params.font_size,
)
sub = SubtitlesClip(
subtitles=subtitle_path, encoding="utf-8", make_textclip=generator
)
text_clips = [] text_clips = []
for item in sub.subtitles: for item in sub.subtitles:
clip = create_text_clip(subtitle_item=item) clip = create_text_clip(subtitle_item=item)
@@ -280,15 +291,18 @@ def generate_video(
bgm_file = get_bgm_file(bgm_type=params.bgm_type, bgm_file=params.bgm_file) bgm_file = get_bgm_file(bgm_type=params.bgm_type, bgm_file=params.bgm_file)
if bgm_file: if bgm_file:
try: try:
bgm_clip = ( bgm_clip = AudioFileClip(bgm_file).with_effects(
AudioFileClip(bgm_file).volumex(params.bgm_volume).audio_fadeout(3) [
afx.MultiplyVolume(params.voice_volume),
afx.AudioFadeOut(3),
afx.AudioLoop(duration=video_clip.duration),
]
) )
bgm_clip = afx.audio_loop(bgm_clip, duration=video_clip.duration)
audio_clip = CompositeAudioClip([audio_clip, bgm_clip]) audio_clip = CompositeAudioClip([audio_clip, bgm_clip])
except Exception as e: except Exception as e:
logger.error(f"failed to add bgm: {str(e)}") logger.error(f"failed to add bgm: {str(e)}")
video_clip = video_clip.set_audio(audio_clip) video_clip = video_clip.with_audio(audio_clip)
video_clip.write_videofile( video_clip.write_videofile(
output_file, output_file,
audio_codec="aac", audio_codec="aac",
@@ -324,14 +338,14 @@ def preprocess_video(materials: List[MaterialInfo], clip_duration=4):
# 创建一个图片剪辑并设置持续时间为3秒钟 # 创建一个图片剪辑并设置持续时间为3秒钟
clip = ( clip = (
ImageClip(material.url) ImageClip(material.url)
.set_duration(clip_duration) .with_duration(clip_duration)
.set_position("center") .with_position("center")
) )
# 使用resize方法来添加缩放效果。这里使用了lambda函数来使得缩放效果随时间变化。 # 使用resize方法来添加缩放效果。这里使用了lambda函数来使得缩放效果随时间变化。
# 假设我们想要从原始大小逐渐放大到120%的大小。 # 假设我们想要从原始大小逐渐放大到120%的大小。
# t代表当前时间clip.duration为视频总时长这里是3秒。 # t代表当前时间clip.duration为视频总时长这里是3秒。
# 注意1 表示100%的大小所以1.2表示120%的大小 # 注意1 表示100%的大小所以1.2表示120%的大小
zoom_clip = clip.resize( zoom_clip = clip.resized(
lambda t: 1 + (clip_duration * 0.03) * (t / clip.duration) lambda t: 1 + (clip_duration * 0.03) * (t / clip.duration)
) )

View File

@@ -302,21 +302,33 @@ Gender: Female
Name: en-US-AnaNeural Name: en-US-AnaNeural
Gender: Female Gender: Female
Name: en-US-AndrewMultilingualNeural
Gender: Male
Name: en-US-AndrewNeural Name: en-US-AndrewNeural
Gender: Male Gender: Male
Name: en-US-AriaNeural Name: en-US-AriaNeural
Gender: Female Gender: Female
Name: en-US-AvaMultilingualNeural
Gender: Female
Name: en-US-AvaNeural Name: en-US-AvaNeural
Gender: Female Gender: Female
Name: en-US-BrianMultilingualNeural
Gender: Male
Name: en-US-BrianNeural Name: en-US-BrianNeural
Gender: Male Gender: Male
Name: en-US-ChristopherNeural Name: en-US-ChristopherNeural
Gender: Male Gender: Male
Name: en-US-EmmaMultilingualNeural
Gender: Female
Name: en-US-EmmaNeural Name: en-US-EmmaNeural
Gender: Female Gender: Female
@@ -602,12 +614,24 @@ Gender: Male
Name: it-IT-ElsaNeural Name: it-IT-ElsaNeural
Gender: Female Gender: Female
Name: it-IT-GiuseppeNeural Name: it-IT-GiuseppeMultilingualNeural
Gender: Male Gender: Male
Name: it-IT-IsabellaNeural Name: it-IT-IsabellaNeural
Gender: Female Gender: Female
Name: iu-Cans-CA-SiqiniqNeural
Gender: Female
Name: iu-Cans-CA-TaqqiqNeural
Gender: Male
Name: iu-Latn-CA-SiqiniqNeural
Gender: Female
Name: iu-Latn-CA-TaqqiqNeural
Gender: Male
Name: ja-JP-KeitaNeural Name: ja-JP-KeitaNeural
Gender: Male Gender: Male
@@ -644,7 +668,7 @@ Gender: Male
Name: kn-IN-SapnaNeural Name: kn-IN-SapnaNeural
Gender: Female Gender: Female
Name: ko-KR-HyunsuNeural Name: ko-KR-HyunsuMultilingualNeural
Gender: Male Gender: Male
Name: ko-KR-InJoonNeural Name: ko-KR-InJoonNeural
@@ -758,7 +782,7 @@ Gender: Male
Name: pt-BR-FranciscaNeural Name: pt-BR-FranciscaNeural
Gender: Female Gender: Female
Name: pt-BR-ThalitaNeural Name: pt-BR-ThalitaMultilingualNeural
Gender: Female Gender: Female
Name: pt-PT-DuarteNeural Name: pt-PT-DuarteNeural

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 KiB

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 275 KiB

View File

@@ -1,26 +1,14 @@
requests~=2.31.0 moviepy==2.1.1
moviepy~=2.0.0.dev2 streamlit==1.40.2
openai~=1.13.3 edge_tts==6.1.19
faster-whisper~=1.0.1 fastapi==0.115.6
edge_tts~=6.1.10 uvicorn==0.32.1
uvicorn~=0.27.1 openai==1.56.1
fastapi~=0.110.0 faster-whisper==1.1.0
tomli~=2.0.1 loguru==0.7.2
streamlit~=1.33.0 google.generativeai==0.8.3
loguru~=0.7.2 dashscope==1.20.14
aiohttp~=3.9.3 g4f==0.3.8.1
urllib3~=2.2.1 azure-cognitiveservices-speech==1.41.1
pillow~=10.3.0 redis==5.2.0
pydantic~=2.6.3 python-multipart==0.0.19
g4f~=0.3.0.4
dashscope~=1.15.0
google.generativeai~=0.4.1
python-multipart~=0.0.9
redis==5.0.3
# 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
opencv-python~=4.9.0.80
# 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
azure-cognitiveservices-speech~=1.37.0
git-changelog~=2.5.2

View File

@@ -479,7 +479,7 @@ with left_panel:
st.session_state["video_terms"] = ", ".join(terms) st.session_state["video_terms"] = ", ".join(terms)
params.video_terms = st.text_area( params.video_terms = st.text_area(
tr("Video Keywords"), value=st.session_state["video_terms"], height=50 tr("Video Keywords"), value=st.session_state["video_terms"]
) )
with middle_panel: with middle_panel:
@@ -734,7 +734,7 @@ if start_button:
scroll_to_bottom() scroll_to_bottom()
st.stop() st.stop()
if llm_provider != "g4f" and not config.app.get(f"{llm_provider}_api_key", ""): if llm_provider != "g4f" and llm_provider != 'ollama' and not config.app.get(f"{llm_provider}_api_key", ""):
st.error(tr("Please Enter the LLM API Key")) st.error(tr("Please Enter the LLM API Key"))
scroll_to_bottom() scroll_to_bottom()
st.stop() st.stop()

81
webui/i18n/pt.json Normal file
View File

@@ -0,0 +1,81 @@
{
"Language": "Português Brasileiro",
"Translation": {
"Video Script Settings": "**Configurações do Roteiro do Vídeo**",
"Video Subject": "Tema do Vídeo (Forneça uma palavra-chave, :red[a IA irá gerar automaticamente] o roteiro do vídeo)",
"Script Language": "Idioma para Gerar o Roteiro do Vídeo (a IA irá gerar automaticamente com base no idioma do seu tema)",
"Generate Video Script and Keywords": "Clique para usar a IA para gerar o [Roteiro do Vídeo] e as [Palavras-chave do Vídeo] com base no **tema**",
"Auto Detect": "Detectar Automaticamente",
"Video Script": "Roteiro do Vídeo (:blue[① Opcional, gerado pela IA  ② Pontuação adequada ajuda na geração de legendas])",
"Generate Video Keywords": "Clique para usar a IA para gerar [Palavras-chave do Vídeo] com base no **roteiro**",
"Please Enter the Video Subject": "Por favor, insira o Roteiro do Vídeo primeiro",
"Generating Video Script and Keywords": "A IA está gerando o roteiro do vídeo e as palavras-chave...",
"Generating Video Keywords": "A IA está gerando as palavras-chave do vídeo...",
"Video Keywords": "Palavras-chave do Vídeo (:blue[① Opcional, gerado pela IA ② Use **vírgulas em inglês** para separar, somente em inglês])",
"Video Settings": "**Configurações do Vídeo**",
"Video Concat Mode": "Modo de Concatenação de Vídeo",
"Random": "Concatenação Aleatória (Recomendado)",
"Sequential": "Concatenação Sequencial",
"Video Ratio": "Proporção do Vídeo",
"Portrait": "Retrato 9:16",
"Landscape": "Paisagem 16:9",
"Clip Duration": "Duração Máxima dos Clipes de Vídeo (segundos)",
"Number of Videos Generated Simultaneously": "Número de Vídeos Gerados Simultaneamente",
"Audio Settings": "**Configurações de Áudio**",
"Speech Synthesis": "Voz de Síntese de Fala",
"Speech Region": "Região(:red[Obrigatório[Obter Região](https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/SpeechServices)])",
"Speech Key": "Chave da API(:red[Obrigatório[Obter Chave da API](https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/SpeechServices)])",
"Speech Volume": "Volume da Fala (1.0 representa 100%)",
"Speech Rate": "Velocidade da Fala (1.0 significa velocidade 1x)",
"Male": "Masculino",
"Female": "Feminino",
"Background Music": "Música de Fundo",
"No Background Music": "Sem Música de Fundo",
"Random Background Music": "Música de Fundo Aleatória",
"Custom Background Music": "Música de Fundo Personalizada",
"Custom Background Music File": "Por favor, insira o caminho do arquivo para a música de fundo personalizada:",
"Background Music Volume": "Volume da Música de Fundo (0.2 representa 20%, a música de fundo não deve ser muito alta)",
"Subtitle Settings": "**Configurações de Legendas**",
"Enable Subtitles": "Ativar Legendas (Se desmarcado, as configurações abaixo não terão efeito)",
"Font": "Fonte da Legenda",
"Position": "Posição da Legenda",
"Top": "Superior",
"Center": "Centralizar",
"Bottom": "Inferior (Recomendado)",
"Custom": "Posição personalizada (70, indicando 70% abaixo do topo)",
"Font Size": "Tamanho da Fonte da Legenda",
"Font Color": "Cor da Fonte da Legenda",
"Stroke Color": "Cor do Contorno da Legenda",
"Stroke Width": "Largura do Contorno da Legenda",
"Generate Video": "Gerar Vídeo",
"Video Script and Subject Cannot Both Be Empty": "O Tema do Vídeo e o Roteiro do Vídeo não podem estar ambos vazios",
"Generating Video": "Gerando vídeo, por favor aguarde...",
"Start Generating Video": "Começar a Gerar Vídeo",
"Video Generation Completed": "Geração do Vídeo Concluída",
"Video Generation Failed": "Falha na Geração do Vídeo",
"You can download the generated video from the following links": "Você pode baixar o vídeo gerado a partir dos seguintes links",
"Pexels API Key": "Chave da API do Pexels ([Obter Chave da API](https://www.pexels.com/api/))",
"Pixabay API Key": "Chave da API do Pixabay ([Obter Chave da API](https://pixabay.com/api/docs/#api_search_videos))",
"Basic Settings": "**Configurações Básicas** (:blue[Clique para expandir])",
"Language": "Idioma",
"LLM Provider": "Provedor LLM",
"API Key": "Chave da API (:red[Obrigatório])",
"Base Url": "URL Base",
"Account ID": "ID da Conta (Obter no painel do Cloudflare)",
"Model Name": "Nome do Modelo",
"Please Enter the LLM API Key": "Por favor, insira a **Chave da API LLM**",
"Please Enter the Pexels API Key": "Por favor, insira a **Chave da API do Pexels**",
"Please Enter the Pixabay API Key": "Por favor, insira a **Chave da API do Pixabay**",
"Get Help": "Se precisar de ajuda ou tiver alguma dúvida, você pode entrar no discord para obter ajuda: https://harryai.cc",
"Video Source": "Fonte do Vídeo",
"TikTok": "TikTok (Suporte para TikTok em breve)",
"Bilibili": "Bilibili (Suporte para Bilibili em breve)",
"Xiaohongshu": "Xiaohongshu (Suporte para Xiaohongshu em breve)",
"Local file": "Arquivo local",
"Play Voice": "Reproduzir Voz",
"Voice Example": "Este é um exemplo de texto para testar a síntese de fala",
"Synthesizing Voice": "Sintetizando voz, por favor aguarde...",
"TTS Provider": "Selecione o provedor de síntese de voz",
"Hide Log": "Ocultar Log"
}
}