原因:
在网上看到一个视频没有字幕。
记者问小泉纯一郎 (前日本首相),我只是好奇,想知道Y说的是什么。
上面这个帖子里的视频:https://x.com/i/status/1834489208398115295
视频没有字幕,那就自己做一个
计划实现的功能:
转录音频内容(语音转文字)
翻译转录的文本
生成双语字幕文件
在网页上显示字幕内容,并提供下载和复制功能
使用进度条显示处理进度
注:字幕效果不好,每次运行,内容都不一样,可能是我的laptop陈旧了? 复制了一个复杂代码,想实现进度条功能,只能说有个颜色条而已,如果您完善了,请更新。
程序结构
主程序: app.py
子目录: ./static
存放 script.js
子目录: ./templates
存放 index.html
子目录: ./subtitle
存放 视频文件名的字幕文件
#updated on Sep14. added last 2 rows and modified .. to . and javascript.js to script.js
代码篇
准备:
以下是复制于我做的笔记,因为是速记目的,最终可能有变化,以"代码"为准。
安装必要的库
首先,您需要安装以下库:
ffmpeg:用于处理音视频文件。 https://ffmpeg.org/download.html
openai-whisper:用于语音识别。
pytorch:Whisper 模型需要。
numpy:处理数组。
os # 用于操作文件和目录
tempfile 创建临时文件和目录
srt 处理 SRT 字幕文件
GoogleTranslator # 导入 GoogleTranslator
...
pip3 install git+https://github.com/openai/whisper.git
pip3 install torch numpy # 如果你有NVIDA显卡,需要安装 匹配的pytorch 至少能提高性能
笔记本电脑配备有 NVIDIA GPU
1:验证 NVIDIA GPU 并安装驱动程序
检查 GPU 型号和驱动程序版本:
按下 Win + R,输入 dxdiag,然后按下回车。
在“显示”或“显示 1”选项卡下,您可以查看显卡型号和驱动程序版本。
更新 NVIDIA 驱动程序:
访问 NVIDIA 驱动程序下载页面。
输入您的 GPU 型号和操作系统信息,然后下载最新的驱动程序。
安装驱动程序,并根据提示重新启动计算机。
2:安装 CUDA Toolkit
下载 CUDA Toolkit https://developer.nvidia.com/cuda-downloads
安装 CUDA Toolkit:
在安装过程中,选择“Express(默认)”或“Custom”安装。
确保安装了 CUDA Toolkit 和 CUDA Samples。
完成安装,并根据需要重新启动计算机。
3:安装 cuDNN 库(可选推荐)
下载 cuDNN:
https://developer.nvidia.com/cudnn
注册或登录 NVIDIA 开发者账户。
选择与您安装的 CUDA 版本兼容的 cuDNN 版本。
下载 Windows 版本的 cuDNN。
安装 cuDNN:
解压下载的 cuDNN 文件。
将 bin、include 和 lib 文件夹中的文件复制到对应的 CUDA 安装目录中,通常为:
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\
4:安装支持 CUDA 的 PyTorch
卸载现有的 PyTorch(如果已安装)
pip uninstall torch torchvision torchaudio
安装 CUDA 版本的 PyTorch
https://pytorch.org/
在“Get Started”页面,选择以下选项
如:pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
完成后,会看到:
“Installing collected packages: torch, torchvision, torchaudio
Successfully installed torch-2.4.1+cu124 torchaudio-2.4.1+cu124 torchvision-0.19.1+cu124”
验证 PyTorch 是否能使用 CUDA
python coding:
import torch
print(torch.cuda.is_available())
">>> import torch
>>> print(torch.cuda.is_available())
True"
5:调整 Python 代码以使用 GPU
#transcribe_audio 函数
def transcribe_audio(audio_path, include_timecodes=True, language=None):
# 加载 Whisper 模型,指定设备为 'cuda'
model = whisper.load_model("base", device="cuda")
options = {}
if language:
options['language'] = language
if include_timecodes:
options.update({'task': 'transcribe', 'verbose': False})
result = model.transcribe(audio_path, **options)
return result
确保音频处理在 GPU 上进行
6:测试程序
监控 GPU 使用情况
cmd: nvidia-smi
主程序: app.py
1. 导入必要的库
from flask import Flask, request, send_file, jsonify, render_template
import os
import tempfile
import whisper
import srt
import subprocess
from datetime import timedelta
import shutil
from deep_translator import GoogleTranslator # 导入 GoogleTranslator
import threading
import uuid
2. 初始化 Flask 应用和全局变量
app = Flask(__name__, static_folder='static', template_folder='templates')
# 设置字幕输出目录
base_dir = os.path.abspath(os.path.dirname(__file__))
subtitle_dir = os.path.join(base_dir, '.', 'subtitle') #注意是一个 点, 别跟我一样,写成2点,目录在上一级了。
os.makedirs(subtitle_dir, exist_ok=True)
# 全局字典存储任务进度
tasks_progress = {}
3. 定义辅助函数
3.1 提取音频
def extract_audio(video_path, audio_path):
command = f'ffmpeg -i "{video_path}" -ac 1 -ar 16000 -vn "{audio_path}" -y'
subprocess.run(command, shell=True, check=True)
#使用 FFmpeg 从视频文件中提取音频,转换为单声道、16kHz 采样率的 WAV 文件
3.2 转录音频
def transcribe_audio(audio_path, language=None):
model = whisper.load_model("base", device="cuda")
options = {'task': 'transcribe', 'verbose': False}
if language:
options['language'] = language
result = model.transcribe(audio_path, **options)
return result
#使用 OpenAI 的 Whisper 模型将音频文件转录为文本
3.3 翻译文本
def translate_text(text, target_language='zh-CN'):
"""
使用 deep-translator 的 GoogleTranslator 翻译文本。
"""
try:
translated_text = GoogleTranslator(source='auto', target=target_language).translate(text)
return translated_text
except Exception as e:
app.logger.error(f'Error translating text: {e}')
return text # 如果翻译失败,返回原文
# 将输入文本翻译为中文
3.4 合并字幕
def merge_subtitles(original_subtitles, translated_subtitles):
merged_subtitles = []
for original, translated in zip(original_subtitles, translated_subtitles):
merged_subtitle = srt.Subtitle(
index=original.index,
start=original.start,
end=original.end,
content=f"{original.content}\n{translated.content}"
)
merged_subtitles.append(merged_subtitle)
return merged_subtitles
#形成双语字幕
3.5 保存 SRT 字幕文件
def save_srt(subtitles, output_path):
with open(output_path, 'w', encoding='utf-8') as f:
f.write(srt.compose(subtitles))
#将字幕列表保存为 SRT 格式的文件
4. 定义路由和处理逻辑
4.1 首页路由
@app.route('/')
def index():
return render_template('index.html')#打开index.html
,供使用上传视频
4.2 处理视频上传和任务创建
@app.route('/process_video', methods=['POST'])
def process_video():
language = request.form.get('language')
video_file = request.files.get('video')
if not video_file:
return jsonify({'error': 'No video file uploaded.'}), 400
# 生成唯一的任务ID
task_id = str(uuid.uuid4())
tasks_progress[task_id] = {'progress': 0}
# 在主线程中创建临时目录
temp_dir = tempfile.mkdtemp()
# 在主线程中保存上传的文件
video_path = os.path.join(temp_dir, 'input_video.mp4')
video_file.save(video_path)
# 在后台线程中处理视频,传递文件路径和临时目录路径
thread = threading.Thread(target=process_task, args=(task_id, video_path, language, temp_dir))
thread.start()
# 返回任务ID给前端
return jsonify({'task_id': task_id})
#处理上传的视频文件,创建任务并启动后台线程进行处理
4.3 后台处理任务
def process_task(task_id, video_path, language, temp_dir):
try:
tasks_progress[task_id]['progress'] = 5
audio_path = os.path.join(temp_dir, 'extracted_audio.wav')
output_srt_filename = f"{os.path.splitext(os.path.basename(video_path))[0]}_subtitles.srt"
output_srt_path = os.path.join(subtitle_dir, output_srt_filename)
# 提取音频
extract_audio(video_path, audio_path)
tasks_progress[task_id]['progress'] = 30
# 转录音频
result = transcribe_audio(audio_path, language)
tasks_progress[task_id]['progress'] = 60
# 生成原文字幕列表
original_subtitles = []
for segment in result['segments']:
subtitle = srt.Subtitle(
index=segment['id'],
start=timedelta(seconds=segment['start']),
end=timedelta(seconds=segment['end']),
content=segment['text'].strip()
)
original_subtitles.append(subtitle)
tasks_progress[task_id]['progress'] = 70
# 翻译字幕内容
texts_to_translate = [subtitle.content for subtitle in original_subtitles]
translated_texts = []
for text in texts_to_translate:
translated_text = translate_text(text, target_language='zh-CN')
translated_texts.append(translated_text)
tasks_progress[task_id]['progress'] = 85
# 创建翻译后的字幕列表
translated_subtitles = []
for subtitle, translated_text in zip(original_subtitles, translated_texts):
translated_subtitle = srt.Subtitle(
index=subtitle.index,
start=subtitle.start,
end=subtitle.end,
content=translated_text
)
translated_subtitles.append(translated_subtitle)
# 合并双语字幕
merged_subtitles = merge_subtitles(original_subtitles, translated_subtitles)
tasks_progress[task_id]['progress'] = 90
# 保存合并后的双语字幕文件到指定目录
save_srt(merged_subtitles, output_srt_path)
tasks_progress[task_id]['progress'] = 95
# 读取字幕内容
with open(output_srt_path, 'r', encoding='utf-8') as f:
subtitles_content = f.read()
# 更新进度为100,并保存结果
tasks_progress[task_id]['progress'] = 100
tasks_progress[task_id]['result'] = {
'message': 'Subtitle generated successfully.',
'subtitles_content': subtitles_content,
'download_url': f'/download_subtitle/{output_srt_filename}'
}
except Exception as e:
app.logger.error(f'Error processing video: {e}')
tasks_progress[task_id]['progress'] = -1 # 用于表示处理失败
finally:
# 清理临时目录
shutil.rmtree(temp_dir)
#后台线程中执行视频处理任务,包括提取音频、转录、翻译、生成字幕等
4.4 查询任务进度
@app.route('/progress/<task_id>')
def progress(task_id):
if task_id in tasks_progress:
progress = tasks_progress[task_id]['progress']
if progress == 100:
# 返回处理结果
result = tasks_progress[task_id].get('result', {})
# 删除任务进度信息
del tasks_progress[task_id]
return jsonify({'progress': progress, 'result': result})
elif progress == -1:
# 处理失败
del tasks_progress[task_id]
return jsonify({'progress': progress, 'error': 'An error occurred during processing.'})
else:
return jsonify({'progress': progress})
else:
return jsonify({'error': 'Invalid task ID.'}), 404
# 通过任务 ID 查询处理进度和结果, 这里有很大的提升空间,没去研究
4.5 提供字幕文件下载
@app.route('/download_subtitle/<filename>')
def download_subtitle(filename):
return send_file(
os.path.join(subtitle_dir, filename),
as_attachment=True,
download_name=filename,
mimetype='text/plain'
)
5. 启动Flask应用程序
if __name__ == '__main__':
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 * 1024 # 1GB
app.run(host='0.0.0.0', port=5000)
# MAX_CONTENT_LENGTH
:设置上传文件的最大大小为 1GB,防止过大的文件导致服务器压力过大。
监听端口还是5000 更以前的coding一样,浏览器里: 127.0.0.1:5000
网页文件 index.html
用于视频转字幕的网页前端,用户可以在网页上上传视频文件,选择语言,然后提交表单生成字幕。网页还提供了显示字幕内容、下载字幕文件和复制全部内容的功能。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>视频转字幕</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
#subtitle-content {
width: 100%;
height: 300px;
margin-top: 20px;
white-space: pre-wrap;
overflow-y: scroll;
border: 1px solid #ccc;
padding: 10px;
}
#buttons {
margin-top: 10px;
}
</style>
</head>
<body>
<h1>视频转字幕</h1>
<form id="upload-form">
<div style="display:none; margin-top: 20px;">
<div style="width: 0%; height: 20px; background-color: green;"></div></div>
<label for="video">选择视频文件:</label>
<input type="file" name="video" accept="video/*" required><br><br>
<label for="language">选择语言:</label>
<select name="language">
<option value="">自动检测</option>
<option value="en">英语</option>
<option value="zh">中文</option>
<option value="jp">日文</option>
<!-- 添加其他语言选项 -->
</select><br><br>
<button type="submit">上传并生成字幕</button>
</form>
<div style="display:none;"></div>
<div style="display:none;">
<button id="download-btn">下载字幕文件</button>
<button id="copy-btn">复制全部内容</button>
</div>
<script src="https://blog.csdn.net/static/script.js"></script>
</body>
</html>
网页够简洁?可以直接看懂的。
设置页面的字符编码为 UTF-8,确保中文字符正确显示<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
:引入 jQuery 库
引入外部的 JavaScript 文件 script.js
,包含前端的逻辑处理代码,如表单提交、进度条更新、结果显示等
页面工作流程
打开网页 选择视频文件,并选择语言(或保持默认的自动检测)推荐选择语言,这样更准确些 点击“上传并生成字幕”按钮,提交事件。 JavaScript 接管提交: 使用 AJAX 请求将视频文件和语言参数发送到后端/process_video
路由。
显示进度条,开始轮询后端的进度接口 /progress/<task_id>
。
后端处理视频,更新任务进度。
前端根据任务进度,实时更新进度条的显示。
处理完成后:
前端收到处理结果,显示字幕内容区域和下载、复制按钮。
用户可以在网页上查看字幕内容。
点击“下载字幕文件”按钮,下载生成的字幕文件。
点击“复制全部内容”按钮,将字幕内容复制到剪贴板。
JavaScript 代码 script.js文件
# updated to added script.js filename on above row.
使用 jQuery 库,处理网页上的提交、与后端进行 AJAX 通信、更新进度条、以及在处理完成后显示结果。
这个网上拼的,只是在这里达到能用。
$(document).ready(function() {
$('#upload-form').on('submit', function(event) {
event.preventDefault();
var formData = new FormData();
var videoFile = $('#video')[0].files[0];
var language = $('#language').val();
if (!videoFile) {
alert('请选择一个视频文件。');
return;
}
formData.append('video', videoFile);
formData.append('language', language);
// 显示进度条
$('#progress-bar-container').show();
$('#progress-bar').css('width', '0%');
$.ajax({
url: '/process_video',
type: 'POST',
data: formData,
contentType: false,
processData: false,
success: function(data) {
if (data.error) {
alert(data.error);
$('#progress-bar-container').hide();
} else {
var taskId = data.task_id;
// 开始轮询进度
pollProgress(taskId);
}
},
error: function(jqXHR, textStatus, errorThrown) {
alert('处理过程中发生错误,请稍后再试。');
$('#progress-bar-container').hide();
}
});
});
function pollProgress(taskId) {
$.ajax({
url: '/progress/' + taskId,
type: 'GET',
success: function(data) {
if (data.progress >= 0 && data.progress < 100) {
// 更新进度条
$('#progress-bar').css('width', data.progress + '%');
// 继续轮询
setTimeout(function() {
pollProgress(taskId);
}, 1000);
} else if (data.progress == 100) {
// 处理完成
$('#progress-bar').css('width', '100%');
// 显示结果
var result = data.result;
$('#subtitle-content').text(result.subtitles_content).show();
$('#buttons').show();
// 设置下载按钮的链接
$('#download-btn').off('click').on('click', function() {
window.location.href = result.download_url;
});
// 复制按钮功能
$('#copy-btn').off('click').on('click', function() {
navigator.clipboard.writeText(result.subtitles_content).then(function() {
alert('字幕内容已复制到剪贴板。');
}, function(err) {
alert('复制失败:' + err);
});
});
// 隐藏进度条
$('#progress-bar-container').hide();
} else if (data.progress == -1) {
// 处理失败
alert('处理过程中发生错误,请稍后再试。');
$('#progress-bar-container').hide();
} else if (data.error) {
alert(data.error);
$('#progress-bar-container').hide();
}
},
error: function() {
// 请求失败,继续轮询
setTimeout(function() {
pollProgress(taskId);
}, 1000);
}
});
}
});
总结:
我的Laptop很旧,运行whisper模型比较慢,我使用的是 model = whisper.load_model("base", device="cuda") base级别对于我GPU已经是极限
如果需要更快的速度或更高的准确性,可以选择 small
, medium
, large
等不同大小的模型,最小的是Tiny
whisper/model-card.md at main · openai/whisper · GitHub
下一步还是需移到NAS Docker,NAS有一颗赛扬处理器 J4125 ,内存4GB,GPU 是Intel HD 600,要简单更改代码希望能跑。 要花些时间用在做容器上Container.
总结