Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 35 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

近后台计算,模型优化和翻译消耗费用不足 ¥0.01(以OpenAI官方价格为计算)

具体字幕和视频合成的效果的测试结果图片,请参考 [TED视频测试](./docs/test.md)
具体字幕和视频合成的效果的测试结果图片,请参考 [TED视频测试](./legacy-docs/test.md)

## 快速开始

Expand All @@ -51,26 +51,22 @@

3. LLM API 配置,(用于字幕断句、校正),可使用[本项目的中转站](https://api.videocaptioner.cn)

4. 翻译配置,选择是否启用翻译,翻译服务(默认使用微软翻译,质量一般,推荐使用大模型翻译
4. 翻译配置,选择是否启用翻译,翻译服务(默认使用微软翻译,质量一般,推荐配置自己的 API KEY 使用大模型翻译

5. 语音识别配置(默认使用B接口网络调用语音识别服务,中英以外的语言请使用本地转录)

6. 拖拽视频文件到软件窗口,即可全自动处理

提示:每一个步骤均支持单独处理,均支持文件拖拽。软件具体模型选择和参数配置说明,请查看下文。

### macOS / Linux 用户
### macOS 用户

#### 一键安装运行(推荐)

```bash
# 方式一:直接运行(自动安装 uv、克隆项目、安装依赖
curl -fsSL https://raw.githubusercontent.com/WEIFENG2333/VideoCaptioner/main/run.sh | bash
# 方式一:直接运行(自动安装 uv、克隆项目、安装相关依赖
curl -fsSL https://raw.githubusercontent.com/WEIFENG2333/VideoCaptioner/main/scripts/run.sh | bash

# 方式二:先克隆再运行
git clone https://github.com/WEIFENG2333/VideoCaptioner.git
cd VideoCaptioner
./run.sh
./scripts/run.sh
```

脚本会自动:
Expand All @@ -92,7 +88,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh
#### 2. 安装系统依赖(macOS)

```bash
brew install ffmpeg aria2
brew install ffmpeg
```

#### 3. 克隆并运行
Expand Down Expand Up @@ -130,7 +126,7 @@ LLM 大模型是用来字幕段句、字幕优化、以及字幕翻译(如果

| 配置项 | 说明 |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| SiliconCloud | [SiliconCloud 官网](https://cloud.siliconflow.cn/i/onCHcaDx)配置方法请参考[配置文档](./docs/llm_config.md)<br>该并发较低,建议把线程设置为5以下。 |
| SiliconCloud | [SiliconCloud 官网](https://cloud.siliconflow.cn/i/onCHcaDx)配置方法请参考[配置文档](https://weifeng2333.github.io/VideoCaptioner/config/llm)<br>该并发较低,建议把线程设置为5以下。 |
| DeepSeek | [DeepSeek 官网](https://platform.deepseek.com),建议使用 `deepseek-v3` 模型,<br>官方网站最近服务好像并不太稳定。 |
| OpenAI兼容接口 | 如果有其他服务商的API,可直接在软件中填写。base_url 和api_key [VideoCaptioner API](https://api.videocaptioner.cn) |

Expand All @@ -152,15 +148,15 @@ API-key: `个人中心-API 令牌页面自行获取。`

💡 模型选择建议 (本人在各质量层级中精选出的高性价比模型):

- 高质量之选: `gemini-2.5-pro``claude-sonnet-4-5-20250929` (耗费比例:3)
- 高质量之选: `gemini-3-pro``claude-sonnet-4-5-20250929` (耗费比例:3)

- 较高质量之选: `gpt-5-2025-08-07``claude-haiku-4-5-20251001` (耗费比例:1.2)

- 中质量之选: `gpt-5-mini``gemini-2.5-flash` (耗费比例:0.3)
- 中质量之选: `gpt-5-mini``gemini-3-flash` (耗费比例:0.3)

本站支持超高并发,软件中线程数直接拉满即可~ 处理速度非常快~

更详细的API配置教程:[中转站配置配置](./docs/llm_config.md#中转站配置)
更详细的API配置教程:[中转站配置](https://weifeng2333.github.io/VideoCaptioner/config/llm)

---

Expand All @@ -181,7 +177,7 @@ API-key: `个人中心-API 令牌页面自行获取。`
| B接口 | 仅支持中文、英文 | 在线 | 免费、速度较快 |
| J接口 | 仅支持中文、英文 | 在线 | 免费、速度较快 |
| WhisperCpp | 中文、日语、韩语、英文等 99 种语言,外语效果较好 | 本地 | (实际使用不稳定)需要下载转录模型<br>中文建议medium以上模型<br>英文等使用较小模型即可达到不错效果。 |
| fasterWhisper 👍 | 中文、英文等多99种语言,外语效果优秀,时间轴更准确 | 本地 | (🌟极力推荐🌟)需要下载程序和转录模型<br>支持CUDA,速度更快,转录准确。<br>超级准确的时间戳字幕。<br>建议优先使用 |
| fasterWhisper 👍 | 中文、英文等多99种语言,外语效果优秀,时间轴更准确 | 本地 | (🌟推荐🌟)需要下载程序和转录模型<br>支持CUDA,速度更快,转录准确。<br>超级准确的时间戳字幕。<br>仅支持 window |

### 4. 本地 Whisper 语音识别模型

Expand All @@ -197,7 +193,6 @@ Whisper 版本有 WhisperCpp 和 fasterWhisper(推荐) 两种,后者效果

推荐模型: `Large-v2` 稳定且质量较好。

注:以上模型国内网络可直接在软件内下载。

### 5. 文稿匹配

Expand All @@ -222,7 +217,7 @@ Whisper 版本有 WhisperCpp 和 fasterWhisper(推荐) 两种,后者效果
2. 只能下载较低分辨率的视频;
3. 网络条件较差时需要验证;

- 请参考 [Cookie 配置说明](./docs/get_cookies.md) 获取Cookie信息,并将cookies.txt文件放置到软件安装目录的 `AppData` 目录下,即可正常下载高质量视频。
- 请参考 [Cookie 配置说明](https://weifeng2333.github.io/VideoCaptioner/guide/cookies-config) 获取Cookie信息,并将cookies.txt文件放置到软件安装目录的 `AppData` 目录下,即可正常下载高质量视频。

## 软件流程介绍

Expand Down Expand Up @@ -291,16 +286,27 @@ Whisper 版本有 WhisperCpp 和 fasterWhisper(推荐) 两种,后者效果

```
VideoCaptioner/
├── runtime/ # 运行环境目录
├── resources/ # 软件资源文件目录(二进制程序、图标等,以及下载的faster-whisper程序)
├── work-dir/ # 工作目录,处理完成的视频和字幕文件保存在这里
├── app/ # 应用源代码目录
│ ├── common/ # 公共模块(配置、信号总线)
│ ├── components/ # UI 组件
│ ├── core/ # 核心业务逻辑(ASR、翻译、优化等)
│ ├── thread/ # 异步线程
│ └── view/ # 界面视图
├── resource/ # 资源文件目录
│ ├── assets/ # 图标、Logo 等
│ ├── bin/ # 二进制程序(FFmpeg、Whisper 等)
│ ├── fonts/ # 字体文件
│ ├── subtitle_style/ # 字幕样式模板
│ └── translations/ # 多语言翻译文件
├── work-dir/ # 工作目录(处理完成的视频和字幕)
├── AppData/ # 应用数据目录
├── cache/ # 缓存目录,缓存转录、大模型请求的数据。
├── models/ # 存放 Whisper 模型文件
├── logs/ # 日志目录,记录软件运行状态
├── settings.json # 存储用户设置
└── cookies.txt # 视频平台的 cookie 信息(下载高清视频时需要)
└── VideoCaptioner.exe # 主程序执行文件
│ ├── cache/ # 缓存目录(转录、LLM 请求)
│ ├── models/ # Whisper 模型文件
│ ├── logs/ # 日志文件
│ └── settings.json # 用户设置
├── scripts/ # 安装和运行脚本
├── main.py # 程序入口
└── pyproject.toml # 项目配置和依赖
```

## 📝 说明
Expand Down Expand Up @@ -328,8 +334,8 @@ VideoCaptioner/
<details>
<summary>捐助支持</summary>
<div align="center">
<img src="./docs/images/alipay.jpg" alt="支付宝二维码" width="30%">
<img src="./docs/images/wechat.jpg" alt="微信二维码" width="30%">
<img src="./legacy-docs/images/alipay.jpg" alt="支付宝二维码" width="30%">
<img src="./legacy-docs/images/wechat.jpg" alt="微信二维码" width="30%">
</div>
</details>

Expand Down
3 changes: 2 additions & 1 deletion app/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ class Config(QConfig):
EnumSerializer(TargetLanguage),
)
max_word_count_cjk = ConfigItem(
"Subtitle", "MaxWordCountCJK", 25, RangeValidator(8, 100)
"Subtitle", "MaxWordCountCJK", 28, RangeValidator(8, 100)
)
max_word_count_english = ConfigItem(
"Subtitle", "MaxWordCountEnglish", 20, RangeValidator(8, 100)
Expand All @@ -250,6 +250,7 @@ class Config(QConfig):
OptionsValidator(VideoQualityEnum),
EnumSerializer(VideoQualityEnum),
)
use_subtitle_style = ConfigItem("Video", "UseSubtitleStyle", False, BoolValidator())

# ------------------- 字幕样式配置 -------------------
subtitle_style_name = ConfigItem("SubtitleStyle", "StyleName", "default")
Expand Down
4 changes: 4 additions & 0 deletions app/common/signal_bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class SignalBus(QObject):
need_video_changed = pyqtSignal(bool)
# 视频质量信号
video_quality_changed = pyqtSignal(str)
# 使用样式信号
use_subtitle_style_changed = pyqtSignal(bool)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signal defined and connected but never emitted

Low Severity

The use_subtitle_style_changed signal is defined in signal_bus.py and connected to on_use_style_changed handler in video_synthesis_interface.py, but the signal is never emitted anywhere in the codebase. The handler on_use_style_changed is therefore dead code that will never be invoked. When the use style toggle is changed in on_use_style_action_triggered, only the config value is set directly without emitting the signal.

Additional Locations (1)

Fix in Cursor Fix in Web

# 渲染模式变更信号
subtitle_render_mode_changed = pyqtSignal(str)

# 新增视频控制相关信号
video_play = pyqtSignal() # 播放信号
Expand Down
1 change: 1 addition & 0 deletions app/core/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

# InfoBar 显示时长配置(单位:毫秒)
INFOBAR_DURATION_FOREVER = 24 * 60 * 60 * 1000 # 永久提示:1天
INFOBAR_DURATION_ERROR = 10000 # 错误提示:10秒
INFOBAR_DURATION_WARNING = 5000 # 警告提示:5秒
INFOBAR_DURATION_INFO = 3000 # 信息提示:3秒
Expand Down
2 changes: 1 addition & 1 deletion app/core/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class TranscribeOutputFormatEnum(Enum):
class LLMServiceEnum(Enum):
"""LLM服务"""

OPENAI = "OpenAI"
OPENAI = "OpenAI 兼容"
SILICON_CLOUD = "SiliconCloud"
DEEPSEEK = "DeepSeek"
OLLAMA = "Ollama"
Expand Down
4 changes: 2 additions & 2 deletions app/core/llm/check_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ def get_available_models(base_url: str, api_key: str) -> list[str]:
# 根据不同模型设置权重进行排序
def get_model_weight(model_name: str) -> int:
model_name = model_name.lower()
if model_name.startswith(("gpt-5", "claude-4", "gemini-2")):
if model_name.startswith(("gpt-5", "claude-4", "gemini-2", "gemini-3")):
return 10
elif model_name.startswith(("gpt-4")):
return 5
elif model_name.startswith(("deepseek", "glm", "qwen")):
elif model_name.startswith(("deepseek", "glm", "qwen", "doubao")):
return 3
return 0

Expand Down
4 changes: 2 additions & 2 deletions app/core/split/split_by_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
logger = setup_logger("split_by_llm")

MAX_WORD_COUNT = 20 # 英文单词或中文字符的最大数量
MAX_STEPS = 3 # Agent loop最大尝试次数
MAX_STEPS = 2 # Agent loop最大尝试次数


def split_by_llm(
Expand Down Expand Up @@ -95,7 +95,7 @@ def _split_with_agent_loop(

# 添加反馈到对话
logger.warning(
f"模型输出错误,断句验证失败,频繁出现建议更换更智能的模型。开始反馈循环 (第{step + 1}次尝试):\n {error_message}\n\n"
f"模型输出错误,断句验证失败,频繁出现建议更换更智能的模型或者调整最大字数限制。开始反馈循环 (第{step + 1}次尝试):\n {error_message}\n\n"
)
messages.append({"role": "assistant", "content": result_text})
messages.append(
Expand Down
2 changes: 0 additions & 2 deletions app/core/subtitle/rounded_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,6 @@ def render_rounded_video(
for seg in asr_data.segments
)
if not has_translation:
logger.warning("选择了'仅译文'但没有翻译文本,自动切换到'仅原文'")
layout = SubtitleLayoutEnum.ONLY_ORIGINAL
elif (
layout == SubtitleLayoutEnum.TRANSLATE_ON_TOP
Expand All @@ -317,7 +316,6 @@ def render_rounded_video(
for seg in asr_data.segments
)
if not has_translation:
logger.warning("没有翻译文本,自动切换到'仅原文'")
layout = SubtitleLayoutEnum.ONLY_ORIGINAL

# 获取视频信息
Expand Down
6 changes: 4 additions & 2 deletions app/core/task_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,16 @@ def create_synthesis_task(
Path(video_path).parent / f"【卡卡】{Path(video_path).stem}.mp4"
)

# 只有启用样式时才传入样式配置
use_style = cfg.use_subtitle_style.value
config = SynthesisConfig(
need_video=cfg.need_video.value,
soft_subtitle=cfg.soft_subtitle.value,
render_mode=cfg.subtitle_render_mode.value,
video_quality=cfg.video_quality.value,
subtitle_layout=cfg.subtitle_layout.value,
ass_style=TaskFactory.get_ass_style(cfg.subtitle_style_name.value),
rounded_style=TaskFactory.get_rounded_style(),
ass_style=TaskFactory.get_ass_style(cfg.subtitle_style_name.value) if use_style else "",
rounded_style=TaskFactory.get_rounded_style() if use_style else None,
)

return SynthesisTask(
Expand Down
Loading
Loading