<Project-3 Video2SubTitle> Python coding Flask应用:从视频中,提取对

[复制链接]
发表于 2024-10-10 23:49:05 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
缘故原由:

在网上看到一个视频没有字幕。

记者问小泉纯一郎 (前日本首相),我只是好奇,想知道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 id="progress-bar-container" style="display:none; margin-top: 20px;">
        <div id="progress-bar" style="width: 0%; height: 20px; background-color: green;"></div></div>
        <label for="video">选择视频文件:</label>
        <input type="file" id="video" name="video" accept="video/*" required><br><br>
        <label for="language">选择语言:</label>
        <select id="language" 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 id="subtitle-content" style="display:none;"></div>
    <div id="buttons" style="display:none;">
        <button id="download-btn">下载字幕文件</button>
        <button id="copy-btn">复制全部内容</button>
    </div>

    <script src="/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
SizeParametersEnglish-only modelMultilingual modeltiny39 M✓✓base74 M✓✓small244 M✓✓medium769 M✓✓large1550 M✓ whisper/model-card.md at main · openai/whisper · GitHub
下一步照旧需移到NAS Docker,NAS有一颗赛扬处理器 J4125 ,内存4GB,GPU 是Intel HD 600,要简朴更改代码希望能跑。 要花些时间用在做容器上Container.

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表