Ubuntu 20下用Python将视频/音频转文字的脚本,主要使用 whisper(OpenAI开源)或 faster-whisper(更快更省资源)。

纯 CPU 方案(无显卡)

使用 int8 量化,CPU也能流畅运行

python3 transcribe.py audio.mp3 --model base --device cpu --compute int8

模型目录结构要求

./faster-whisper-base/ 目录下需要包含这些文件:

./faster-whisper-base/
├── model.bin          (模型权重,约145MB for base)
├── config.json        (模型配置)
├── tokenizer.json     (分词器)
└── vocabulary.txt     (词表)

faster-whisper 支持的模型及其特点:

模型 参数量 磁盘大小 速度 准确率 适用场景 显存需求
tiny 39M ~75MB 最快 一般 实时字幕、快速草稿、资源极有限 ~1GB
tiny.en 39M ~75MB 最快 一般(英文优化) 仅英文实时场景 ~1GB
base 74M ~150MB 很快 够用 日常使用、播客、会议记录 ~1GB
base.en 74M ~150MB 很快 够用(英文优化) 仅英文日常场景 ~1GB
small 244M ~480MB 较好 平衡选择,推荐大多数场景 ~2GB
small.en 244M ~480MB 较好(英文优化) 仅英文高质量场景 ~2GB
medium 769M ~1.5GB 中等 对准确率要求高 ~5GB
medium.en 769M ~1.5GB 中等 好(英文优化) 仅英文高精度场景 ~5GB
large-v1 1.5B ~3GB 很好 专业转录、多语言 ~10GB
large-v2 1.5B ~3GB 较慢 很好 推荐的大模型版本 ~10GB
large-v3 1.5B ~3GB 较慢 最好 最新版,多语言最强 ~10GB
large-v3-turbo 1.5B ~3GB 接近large-v3 速度与准确率的最佳平衡 ~6GB

关键说明

.en 后缀模型

  • 仅针对英语优化,其他语言效果差
  • 如果你只处理中文内容,不要用 .en 模型

速度对比(参考值,CPU int8)

模型 1小时音频处理时间
tiny ~5分钟
base ~10分钟
small ~20分钟
medium ~1小时
large-v3 ~2-3小时

选择建议

你的场景 推荐模型
快速测试/草稿 tiny
日常中文视频/播客 basesmall
会议记录(要准确) smallmedium
专业字幕/翻译 mediumlarge-v3
多语言混合内容 large-v3
追求速度+准确率平衡 large-v3-turbo

你的 CPU 场景建议

# 日常够用,速度快
python3 transcribe.py audio.mp3 --model base --device cpu --compute int8

# 想要更好效果(推荐)
python3 transcribe.py audio.mp3 --model small --device cpu --compute int8

# 如果内存够(8G+),追求准确
python3 transcribe.py audio.mp3 --model medium --device cpu --compute int8

large-v3 在纯 CPU 上会很慢,除非内容特别重要,否则不建议。

如何使用HF-Mirror🌟

本站域名 hf-mirror.com,用于镜像 huggingface.co 域名。作为一个公益项目,致力于帮助国内AI开发者快速、稳定的下载模型、数据集。捐赠支持请看网页左下角,感谢支持!

更多详细用法请看《这篇教程》

方法一:网页下载

在本站搜索,并在模型主页的Files and Version中下载文件。

方法二:huggingface-cli

huggingface-cli 是 Hugging Face 官方提供的命令行工具,自带完善的下载功能。使用方法如下:

1. 安装依赖

pip install -U huggingface_hubCopy

2. 设置环境变量
Linux

export HF_ENDPOINT=https://hf-mirror.comCopy

Windows Powershell

$env:HF_ENDPOINT = "https://hf-mirror.com"Copy

建议将上面这一行写入 ~/.bashrc
3.1 下载模型

huggingface-cli download --resume-download gpt2 --local-dir gpt2Copy

3.2 下载数据集

huggingface-cli download --repo-type dataset --resume-download wikitext --local-dir wikitextCopy

可以添加 --local-dir-use-symlinks False 参数禁用文件软链接,这样下载路径下所见即所得,详细解释请见上面提到的教程。

方法三:使用 hfd

hfd 是本站开发的 huggingface 专用下载工具,基于成熟工具 aria2,可以做到稳定高速下载不断线。

1. 下载hfd

wget https://hf-mirror.com/hfd/hfd.sh
chmod a+x hfd.shCopy

2. 设置环境变量
Linux

export HF_ENDPOINT=https://hf-mirror.comCopy

Windows Powershell

$env:HF_ENDPOINT = "https://hf-mirror.com"Copy

3.1 下载模型

./hfd.sh gpt2Copy

3.2 下载数据集

./hfd.sh wikitext --datasetCopy

方法四:使用环境变量(非侵入式)

非侵入式,能解决大部分情况。huggingface 工具链会获取HF_ENDPOINT环境变量来确定下载文件所用的网址,所以可以使用通过设置变量来解决。

HF_ENDPOINT=https://hf-mirror.com python your_script.pyCopy

不过有些数据集有内置的下载脚本,那就需要手动改一下脚本内的地址来实现了。

常见问题

Q: 有些项目需要登录,如何下载?

A:部分 Gated Repo 需登录申请许可。为保障账号安全,本站不支持登录,需先前往 Hugging Face 官网登录、申请许可,在官网这里获取 Access Token 后回镜像站用命令行下载。
部分工具下载 Gated Repo 的方法:

huggingface-cli: 添加--token参数

huggingface-cli download --token hf_*** --resume-download meta-llama/Llama-2-7b-hf --local-dir Llama-2-7b-hfCopy

hfd: 添加--hf_username``--hf_token参数

hfd meta-llama/Llama-2-7b --hf_username YOUR_HF_USERNAME --hf_token hf_***Copy

其余如from_pretrainedwgetcurl如何设置认证 token,详见上面第一段提到的教程。

transcribe.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
视频/音频转文字脚本 (Ubuntu 20)
使用 faster-whisper,支持 GPU/CPU
"""

import os
import sys
import argparse
import subprocess
import json
from pathlib import Path
from datetime import timedelta

# 尝试导入 faster-whisper,失败则提示安装
try:
    from faster_whisper import WhisperModel
except ImportError:
    print("错误:未安装 faster-whisper")
    print("请运行: pip install faster-whisper")
    sys.exit(1)


def format_timestamp(seconds: float) -> str:
    """将秒数转换为 SRT 时间格式 HH:MM:SS,mmm"""
    td = timedelta(seconds=seconds)
    total_seconds = int(td.total_seconds())
    hours = total_seconds // 3600
    minutes = (total_seconds % 3600) // 60
    secs = total_seconds % 60
    millis = int((seconds - int(seconds)) * 1000)
    return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"


def extract_audio(video_path: str, output_audio: str = None) -> str:
    """
    从视频文件中提取音频(使用 ffmpeg)
    """
    if output_audio is None:
        output_audio = video_path.rsplit('.', 1)[0] + '.wav'

    # 检查是否已有音频文件
    if os.path.exists(output_audio):
        print(f"音频文件已存在: {output_audio}")
        return output_audio

    print(f"正在从视频提取音频: {video_path}")

    cmd = [
        'ffmpeg', '-y', '-i', video_path,
        '-vn',                    # 不处理视频
        '-acodec', 'pcm_s16le',   # 16位PCM编码
        '-ar', '16000',           # 采样率16kHz(whisper推荐)
        '-ac', '1',               # 单声道
        output_audio
    ]

    try:
        result = subprocess.run(
            cmd, 
            capture_output=True, 
            text=True,
            check=True
        )
        print(f"音频提取完成: {output_audio}")
        return output_audio

    except subprocess.CalledProcessError as e:
        print(f"ffmpeg 提取音频失败: {e.stderr}")
        sys.exit(1)
    except FileNotFoundError:
        print("错误:未找到 ffmpeg,请先安装: sudo apt install ffmpeg")
        sys.exit(1)


def transcribe(
    audio_path: str,
    model_size: str = "base",
    device: str = "auto",
    compute_type: str = "default",
    language: str = "zh",
    output_format: str = "txt",
    output_path: str = None,
    vad_filter: bool = True
) -> list:
    """
    使用 faster-whisper 进行语音识别

    参数:
        audio_path: 音频文件路径
        model_size: 模型大小 (tiny/base/small/medium/large/large-v2/large-v3)
        device: 计算设备 (cuda/cpu/auto)
        compute_type: 计算精度 (float16/int8/int8_float16/float32)
        language: 语言代码 (zh/en/ja/...)
        output_format: 输出格式 (txt/srt/json)
        output_path: 输出文件路径
        vad_filter: 是否启用语音活动检测(过滤静音)
    """

    # 自动选择设备
    if device == "auto":
        import torch
        device = "cuda" if torch.cuda.is_available() else "cpu"
        print(f"自动选择设备: {device}")

    # 根据设备调整计算类型
    if compute_type == "default":
        compute_type = "float16" if device == "cuda" else "int8"

    print(f"加载模型: {model_size} (设备: {device}, 精度: {compute_type})")

    # 加载模型(优先使用 ./faster-whisper-base 目录下的本地模型)
    model_dir = os.environ.get("WHISPER_MODEL_DIR", "./faster-whisper-base")
    print(f"模型目录: {os.path.abspath(model_dir)}")

    # 加载模型(优先使用 ./faster-whisper-base 目录下的本地模型)
    if os.path.exists(model_dir):
        print("使用本地模型...")
        # 直接传模型目录路径给第一个参数
        model = WhisperModel(model_dir, device=device, compute_type=compute_type)
    else:
        print("本地模型不存在,尝试下载...")
        model = WhisperModel(model_size, device=device, compute_type=compute_type)

    print(f"开始识别: {audio_path}")
    print(f"语言: {language}, VAD过滤: {vad_filter}")

    # 执行识别
    segments, info = model.transcribe(
        audio_path,
        language=language,
        vad_filter=vad_filter,
        vad_parameters=dict(min_silence_duration_ms=500),
        condition_on_previous_text=True
    )

    print(f"检测到语言: {info.language} (概率: {info.language_probability:.2f})")
    print(f"预计时长: {info.duration:.2f} 秒")

    # 收集结果
    results = []
    for i, segment in enumerate(segments, 1):
        result = {
            "id": i,
            "start": segment.start,
            "end": segment.end,
            "text": segment.text.strip(),
            "start_formatted": format_timestamp(segment.start),
            "end_formatted": format_timestamp(segment.end),
        }
        results.append(result)

        # 实时打印
        print(f"[{result['start_formatted']} --> {result['end_formatted']}] {result['text']}")

    # 保存结果
    if output_path is None:
        output_path = audio_path.rsplit('.', 1)[0] + f'.{output_format}'

    save_results(results, output_path, output_format)
    print(f"\n结果已保存: {output_path}")

    return results


def save_results(results: list, output_path: str, format_type: str):
    """保存识别结果到文件"""

    if format_type == "txt":
        # 纯文本格式
        with open(output_path, 'w', encoding='utf-8') as f:
            for r in results:
                f.write(r["text"] + "\n")

    elif format_type == "srt":
        # SRT 字幕格式
        with open(output_path, 'w', encoding='utf-8') as f:
            for r in results:
                f.write(f"{r['id']}\n")
                f.write(f"{r['start_formatted']} --> {r['end_formatted']}\n")
                f.write(f"{r['text']}\n\n")

    elif format_type == "json":
        # JSON 格式
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(results, f, ensure_ascii=False, indent=2)

    elif format_type == "vtt":
        # WebVTT 格式
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write("WEBVTT\n\n")
            for r in results:
                # VTT 使用点号分隔毫秒
                start = r['start_formatted'].replace(',', '.')
                end = r['end_formatted'].replace(',', '.')
                f.write(f"{start} --> {end}\n")
                f.write(f"{r['text']}\n\n")


def batch_process(
    input_paths: list,
    model_size: str = "base",
    **kwargs
):
    """批量处理多个文件"""
    for path in input_paths:
        print(f"\n{'='*50}")
        print(f"处理文件: {path}")
        print(f"{'='*50}")

        ext = Path(path).suffix.lower()

        # 如果是视频,先提取音频
        if ext in ['.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv', '.webm']:
            audio_path = extract_audio(path)
        else:
            audio_path = path

        # 转文字
        transcribe(audio_path, model_size=model_size, **kwargs)


def main():
    parser = argparse.ArgumentParser(
        description='视频/音频转文字工具 (基于 faster-whisper)',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  # 识别单个音频文件
  python3 transcribe.py audio.mp3

  # 识别视频并生成字幕
  python3 transcribe.py video.mp4 -f srt -l zh

  # 使用大模型提高准确率(需要更多显存)
  python3 transcribe.py audio.mp3 --model large-v3 --device cuda

  # 批量处理
  python3 transcribe.py *.mp3 --model small

  # 输出JSON(带时间戳)
  python3 transcribe.py audio.mp3 -f json

模型大小说明:
  tiny    - 39M  参数, 最快, 准确率一般
  base    - 74M  参数, 较快, 日常够用
  small   - 244M 参数, 平衡选择
  medium  - 769M 参数, 较准, 较慢
  large-v3 - 1.5B 参数, 最准, 最慢, 需8G+显存
        """
    )

    parser.add_argument('input', nargs='+', help='输入文件路径(支持视频/音频)')
    parser.add_argument('-m', '--model', default='base', 
                       choices=['tiny', 'base', 'small', 'medium', 'large', 'large-v2', 'large-v3'],
                       help='模型大小 (默认: base)')
    parser.add_argument('-d', '--device', default='auto',
                       choices=['cuda', 'cpu', 'auto'],
                       help='计算设备 (默认: auto)')
    parser.add_argument('-c', '--compute', default='default',
                       choices=['float16', 'float32', 'int8', 'int8_float16', 'default'],
                       help='计算精度 (默认: 自动)')
    parser.add_argument('-l', '--language', default='zh',
                       help='语言代码,如 zh, en, ja (默认: zh)')
    parser.add_argument('-f', '--format', default='txt',
                       choices=['txt', 'srt', 'json', 'vtt'],
                       help='输出格式 (默认: txt)')
    parser.add_argument('-o', '--output', default=None,
                       help='输出文件路径 (默认: 同输入文件名)')
    parser.add_argument('--no-vad', action='store_true',
                       help='禁用语音活动检测')

    args = parser.parse_args()

    # 批量处理
    batch_process(
        args.input,
        model_size=args.model,
        device=args.device,
        compute_type=args.compute,
        language=args.language,
        output_format=args.format,
        output_path=args.output,
        vad_filter=not args.no_vad
    )


if __name__ == "__main__":
    main()