字幕批量翻译工具(DeepSeek API 离线 SRT 翻译脚本)
功能概述
基于 Python + DeepSeek 云端 API 开发的本地 SRT 字幕批量翻译工具,运行后弹窗选择字幕文件夹,自动批量翻译目录内所有原版.srt字幕;短台词、语气助词、纯标点 / 数字短句自动跳过翻译,减少 API 调用消耗,同时解决 AI 翻译序号错乱、漏译、接口超时中断等问题。
核心特性
- 可视化文件夹选择:内置弹窗选取字幕目录,无需手动修改代码路径;
- 智能短句过滤:字符少于指定长度、纯标点、纯数字字幕直接保留原文,不发起翻译请求,节省 Token 与翻译时长;
- 稳健翻译容错机制:API 请求失败自动重试,网络异常批次保留原始字幕不丢失;独立序号映射解析译文,杜绝多行台词、换行导致的字幕匹配错乱、漏翻;
- 文件输出规则:翻译完成文件与原字幕存放于同一文件夹,自动命名为
xxx.chn.srt;自动过滤已生成的.chn.srt文件,避免重复翻译; - 限流防护:每批次翻译添加请求间隔,防止 API 短时间高频调用被限流;
- 可选双语字幕:支持一键切换「仅中文译文」「原文 + 中文双语对照」两种字幕输出格式。
使用流程
- 配置 DeepSeek API 密钥、批量条数、短句过滤阈值等参数;
- 运行脚本,弹窗选中存放 SRT 字幕的文件夹;
- 程序自动筛选原版字幕,分片调用 DeepSeek 接口翻译;
- 同目录生成带
.chn后缀的中文字幕文件,全程控制台打印处理进度。
依赖环境
Python3.12,需提前安装 openai、srt 依赖库;网络可访问 DeepSeek API、账户拥有可用余额即可使用。
源代码
from openai import OpenAI
import srt
import os
import tkinter as tk
from tkinter import filedialog
import time
import string
# ===================== 【配置区自行修改】 =====================
DEEPSEEK_API_KEY = "sk-替换成你的密钥"
BATCH_SIZE = 5 # 调小,降低丢句概率
MODEL_NAME = "deepseek-v4-flash"
REQUEST_INTERVAL = 0.8 # 每批请求间隔,防API限流
MAX_RETRY = 2 # 接口失败重试次数
SKIP_MAX_CHAR = 4 # 少于等于该字符直接跳过翻译
SUFFIX_TAG = ".chn" # 翻译文件后缀标识
# ==========================================================
client = OpenAI(
api_key=DEEPSEEK_API_KEY,
base_url="https://api.deepseek.com"
)
def select_folder_dialog():
root = tk.Tk()
root.withdraw()
folder_path = filedialog.askdirectory(title="请选择存放SRT字幕的源文件夹")
return folder_path
def is_skip_text(text: str) -> bool:
"""判断是否为短句/语气词/纯符号,需要跳过翻译"""
txt = text.strip()
if len(txt) <= SKIP_MAX_CHAR:
return True
# 全是标点符号
all_punc = all(c in string.punctuation + " " for c in txt)
if all_punc:
return True
# 纯数字
if txt.isdigit():
return True
return False
def batch_translate(sub_batch):
# 拆分:需要翻译的条目 / 直接保留原文的条目
need_trans = []
keep_origin = []
for idx, sub in enumerate(sub_batch):
if is_skip_text(sub.content):
keep_origin.append((idx, sub))
else:
need_trans.append((idx, sub))
# 没有需要翻译的,直接返回原批次
if not need_trans:
time.sleep(REQUEST_INTERVAL)
return sub_batch
# 组装待翻译文本
send_text = ""
map_idx = {} # 记录内部序号对应原始下标
for inner_id, (ori_idx, sub) in enumerate(need_trans):
map_idx[inner_id + 1] = ori_idx
send_text += f"{inner_id+1}\n{sub.content.strip()}\n\n"
retry = 0
raw_result = ""
while retry <= MAX_RETRY:
try:
resp = client.chat.completions.create(
model=MODEL_NAME,
messages=[
{
"role": "system",
"content": """你是专业影视字幕翻译。
输入是带数字序号的台词,严格按顺序逐条翻译为通顺简体中文。
输出格式:每条译文前带上对应数字,一条序号对应一句译文,不要合并、不要漏译。
只输出译文内容,不加任何解释、注释、多余文字。"""
},
{"role": "user", "content": f"翻译以下台词:\n{send_text}"}
],
temperature=0.2,
timeout=15
)
raw_result = resp.choices[0].message.content.strip()
break
except Exception as e:
retry += 1
print(f"批次请求失败,重试{retry}/{MAX_RETRY},错误:{str(e)}")
time.sleep(1.2)
if retry > MAX_RETRY:
# 多次失败保留原文
time.sleep(REQUEST_INTERVAL)
return sub_batch
# 解析译文
trans_map = {}
current_num = None
current_text = []
for line in raw_result.splitlines():
line = line.strip()
if not line:
continue
if line.isdigit():
if current_num is not None and current_text:
trans_map[current_num] = " ".join(current_text)
current_num = int(line)
current_text = []
else:
current_text.append(line)
if current_num is not None and current_text:
trans_map[current_num] = " ".join(current_text)
# 回填翻译
for inner_seq, ori_idx in map_idx.items():
if inner_seq in trans_map and trans_map[inner_seq].strip():
sub_batch[ori_idx].content = trans_map[inner_seq]
# 双语开启替换上面这行为:
# sub_batch[ori_idx].content = sub_batch[ori_idx].content + "\n【译】" + trans_map[inner_seq]
time.sleep(REQUEST_INTERVAL)
return sub_batch
def process_single_srt(src_dir, filename):
src_path = os.path.join(src_dir, filename)
# 构造输出文件名:test.srt → test.chn.srt
name_no_ext, ext = os.path.splitext(filename)
out_filename = f"{name_no_ext}{SUFFIX_TAG}{ext}"
out_path = os.path.join(src_dir, out_filename)
print(f"\n==== 正在处理:{filename} → {out_filename} ====")
try:
with open(src_path, "r", encoding="utf-8") as f:
subs = list(srt.parse(f.read()))
except Exception as e:
print(f"读取字幕失败:{e}")
return
total = len(subs)
for start in range(0, total, BATCH_SIZE):
end = start + BATCH_SIZE
chunk = subs[start:end]
chunk = batch_translate(chunk)
subs[start:end] = chunk
print(f"进度:{min(end, total)}/{total}")
# 保存翻译后字幕到源文件夹
with open(out_path, "w", encoding="utf-8") as f:
f.write(srt.compose(subs))
print(f"文件完成输出:{out_path}")
def main():
input_dir = select_folder_dialog()
if not input_dir:
print("未选择文件夹,程序退出")
return
print(f"源目录:{input_dir}")
print(f"过滤规则:≤{SKIP_MAX_CHAR}字符、纯标点、纯数字自动跳过翻译")
print(f"输出规则:同目录生成 *.chn.srt 翻译字幕\n")
all_files = os.listdir(input_dir)
srt_files = [f for f in all_files if f.lower().endswith(".srt") and SUFFIX_TAG not in f]
if not srt_files:
print("文件夹内未找到未翻译的SRT字幕")
return
print(f"共检测到 {len(srt_files)} 个字幕文件,开始翻译")
for filename in srt_files:
process_single_srt(input_dir, filename)
print("\n==================== 全部任务结束 ====================")
print(f"翻译完成的 .chn.srt 文件保存在原字幕同一目录")
if __name__ == "__main__":
main()