
简介AI-Media2Doc是一套面向开发者与内容创作者的Web工具源码基于AI大模型将视频、音频一键转换为小红书、公众号、知识笔记、思维导图等多种风格文档采用MIT协议开源支持本地部署且无需登录注册兼顾隐私安全与二次开发需求。资源包共115个文件约15.2MB以Vue组件、Python后端脚本、JavaScript与TypeScript逻辑代码为主辅以PNG、JPG、SVG等界面素材以及Dockerfile、YAML、Makefile等部署配置另有Markdown说明与LICENSE授权文件结构完整、层次清晰。前端借助ffmpeg wasm实现浏览器端音视频处理无需本地安装ffmpeg并支持AI对话与字幕导出后端依赖火山引擎Arkitect SDK需配置环境变量前端需Node.js 20。部署方式涵盖Docker一键部署与本地前后端分离部署适合希望快速搭建音视频转文档工具、研究AI应用集成或进行源码定制的开发者参考已有40人学习。1. 从一段视频到一份文档AI-Media2Doc 到底在解决什么手里攒了一堆会议录屏、课程回放、访谈素材想整理成文字稿传统做法是三步走先跑语音识别拿到带时间戳的原始文本再手动切段落、去口水词最后排版成 Markdown 或 Word。三步里最耗人的不是识别而是后两步——一段两小时的录音识别只要几分钟整理却要一两个小时。AI-Media2Doc 这类工具瞄准的正是这个断层它把「媒体文件进、结构化文档出」当成一条流水线来做而不是只给你一个转写结果。它适合的人很明确需要批量处理音视频素材的内容运营、做会议纪要的行政与产品岗、要把课程录像转成讲义的教研人员以及想在自己项目里嵌入「媒体转文档」能力的开发者。源码形态意味着你可以本地跑、可以改提示词、可以换模型而不是被某个在线服务的额度卡住。这一章先把这条流水线的骨架讲清楚后面几章再拆每一步怎么落地、参数怎么调、哪里容易翻车。2. 拆解 AI-Media2Doc 的流水线四个阶段与选型逻辑2.1 为什么不能一步到位ASR 与 LLM 的职责边界很多人第一反应是「直接把音频丢给大模型不就行了」。目前主流的多模态大模型确实能吃音频但在长音频场景下有两个硬伤一是时长限制两小时的会议录音很难一次性塞进去二是成本按音频 token 计费通常比纯文本贵一个量级。所以稳妥的架构是两段式先用专门的语音识别ASR模型把音频转成带时间戳的文本这一步只做「听写」不做理解再把文本交给大语言模型做结构化处理。这个分工的好处是每一段都可以单独替换和调优。ASR 换更强的模型转写准确率就上去LLM 换提示词输出的文档结构就变。如果混在一起做成端到端任何一环出问题都得整体重来调试起来就是黑匣子。我一般会把 ASR 的输出先落盘存成中间文件这样重跑结构化阶段时不用重新识别省时间也省钱。选型上ASR 侧常见做法是用 Whisper 系列或其蒸馏版本中文场景下 large-v3 的准确率比较稳但显存占用高如果只是做会议纪要medium 或 small 在清晰录音上够用。LLM 侧则看你要的输出形态要严格 Markdown 结构就用指令遵循强的模型要摘要提炼就用长上下文能力好的模型。两者不必是同一家。2.2 媒体预处理抽音频、切分与格式统一在识别之前得先把视频里的音频抽出来并且统一成 ASR 模型能吃的格式。常见做法是用 ffmpeg 抽成 16kHz 单声道 WAV这个采样率是 Whisper 系列的标准输入。如果原始文件很长还要考虑切分——不是随便切而是尽量按静音段切避免把一句话切成两半。# 从视频中抽取 16kHz 单声道 WAV-vn 表示丢弃视频流 ffmpeg -i input.mp4 -vn -ac 1 -ar 16000 -c:a pcm_s16le audio.wav # 按静音切分超过 30 秒的静音处切开最多保留 0.5 秒静音 ffmpeg -i audio.wav -af silencedetectnoise-30dB:d0.5 -f null - 2 silence.log第一行命令里-ac 1是单声道-ar 16000是采样率pcm_s16le是无损 PCM 编码这三个参数决定了输出能不能直接被 ASR 读取。第二行用silencedetect滤镜扫描静音段noise-30dB是静音判定阈值d0.5表示持续 0.5 秒以上才算静音段结果输出到日志里后续可以据此计算切分点。阈值调高比如 -20dB会把更多环境音判成静音切得更碎调低-40dB则可能漏掉真正的停顿。安静环境下 -30dB 是个不错的起点。切分粒度建议控制在 5 到 15 分钟一段。太短会导致上下文断裂LLM 难以理解跨段指代太长则单次识别耗时长、失败重试成本高。切分点最好落在段落之间的长停顿上而不是固定时间硬切。2.3 语音识别模型、语言与时间戳参数识别阶段的核心是把音频转成文本同时保留时间戳方便后续定位和校对。以 Whisper 为例关键参数有几个language指定语言能显著提升准确率并加快速度task选 transcribe 是转写translate 是翻译成英文word_timestamps开启后能拿到词级时间戳做字幕或精确定位时有用但会慢一些。import whisper # 加载模型medium 在准确率和显存之间比较平衡 model whisper.load_model(medium) result model.transcribe( audio.wav, languagezh, # 明确指定中文避免自动检测误判 tasktranscribe, # 只转写不翻译 word_timestampsTrue, # 输出词级时间戳 initial_prompt以下是普通话会议内容涉及产品和技术讨论。 # 引导词提升专有名词识别 ) for seg in result[segments]: print(f[{seg[start]:.1f}-{seg[end]:.1f}] {seg[text]})languagezh是血泪经验不指定时模型有时会把中文方言或口音判成其他语言输出一堆乱码。initial_prompt是个容易被忽略的参数它把一段示例文本作为解码的上下文能明显改善专有名词和术语的识别——比如公司内部的项目代号、技术缩写。但引导词别写太长否则会挤占解码空间。word_timestamps按需开启纯做文档时其实用不上关掉能省不少时间。识别完的文本通常还带着口语特征重复、语气词、断句混乱。这些不在 ASR 阶段处理留给下一步的 LLM。2.4 结构化生成提示词设计与输出格式约束这一步是把原始转写文本变成可读文档。核心手段是提示词工程要点有三明确角色和任务、给出输出格式模板、约束不要编造。很多人在这里翻车是因为提示词太笼统模型自由发挥输出格式每次都不一样。from openai import OpenAI client OpenAI() SYSTEM_PROMPT 你是一名专业的会议纪要整理助手。 规则 1. 只依据用户提供的转写文本整理不得添加原文没有的信息。 2. 删除口语重复、语气词但不得改变原意。 3. 按主题分段每段加小标题。 4. 输出 Markdown 格式包含「会议主题」「关键结论」「待办事项」三部分。 5. 待办事项需标注负责人若原文提及。 def structure_doc(transcript: str) - str: resp client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: transcript} ], temperature0.3, # 低温度减少自由发挥 max_tokens4000 ) return resp.choices[0].message.contenttemperature0.3是关键整理类任务要的是稳定和忠实不是创意温度高了模型会开始「合理推测」把没说的话补进去。max_tokens要留够长会议的输出可能超过 2000 token设太小会被截断。系统提示词里「不得添加原文没有的信息」这句必须写否则模型很容易自作主张补充背景。如果转写文本超过模型上下文需要先分段处理再合并。常见做法是按主题切块每块单独结构化最后用一个汇总提示词把各块拼起来。切块时保留上一块的结尾作为下一块的开头上下文能减少指代丢失。3. 本地跑通 AI-Media2Doc环境、依赖与最小可运行示例3.1 环境准备与依赖安装本地跑这套流水线硬件上建议有独立显卡显存 8GB 起步能跑 medium 模型16GB 以上可以上 large。纯 CPU 也能跑但两小时音频可能要等很久。软件上需要 Python 3.10 以上、ffmpeg、以及 ASR 和 LLM 两套依赖。# 创建虚拟环境避免依赖冲突 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装核心依赖 pip install openai-whisper # ASR pip install openai # LLM 调用 pip install ffmpeg-python # 媒体处理封装 # 系统级安装 ffmpegUbuntu 示例 sudo apt-get install ffmpegopenai-whisper会连带安装 PyTorch如果机器有 CUDA建议先按官方指引装好对应版本的 torch再装 whisper否则可能装成 CPU 版。ffmpeg 是系统级依赖pip 装的那个只是 Python 封装真正的二进制还得系统装。这一步翻车最多的是 CUDA 版本和 torch 版本对不上报错通常是「no kernel image is available」遇到就回去核对版本矩阵。3.2 串起完整流程的脚本把前面几段拼起来就是一个最小可运行的端到端脚本。它的职责是抽音频、识别、结构化、落盘。import subprocess, whisper, os from openai import OpenAI client OpenAI() def extract_audio(video_path: str, audio_path: str): 抽取 16kHz 单声道音频 subprocess.run([ ffmpeg, -y, -i, video_path, -vn, -ac, 1, -ar, 16000, -c:a, pcm_s16le, audio_path ], checkTrue) def transcribe(audio_path: str) - str: 语音识别返回纯文本 model whisper.load_model(medium) result model.transcribe(audio_path, languagezh, tasktranscribe) return \n.join(seg[text] for seg in result[segments]) def structure(transcript: str) - str: 调用 LLM 结构化 resp client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 把以下转写整理成 Markdown 会议纪要分主题加小标题不添加原文没有的信息。}, {role: user, content: transcript} ], temperature0.3 ) return resp.choices[0].message.content if __name__ __main__: video meeting.mp4 audio meeting.wav extract_audio(video, audio) raw transcribe(audio) with open(raw.txt, w, encodingutf-8) as f: f.write(raw) # 中间结果落盘方便重跑 doc structure(raw) with open(output.md, w, encodingutf-8) as f: f.write(doc) print(完成输出 output.md)extract_audio里的-y表示覆盖已存在文件避免交互卡住。transcribe把各段时间戳文本拼成纯文本如果后续要做字幕就保留时间戳。中间结果raw.txt落盘是刻意设计——LLM 调用可能失败或要换提示词重试有了原始转写就不用重新识别。structure的提示词是最简版实际用的时候按上一章的方法补全格式约束。3.3 参数怎么调一张对照表不同素材对参数的要求不一样下面这张表是我踩坑后总结的常用配置。场景ASR 模型切分粒度temperature备注清晰会议录音medium10 分钟0.3默认配置平衡嘈杂访谈large-v35 分钟0.2短段减少错误累积课程讲座medium15 分钟0.4长段保留上下文快速预览small20 分钟0.3牺牲准确率换速度嘈杂环境用 large-v3 是因为它对噪声更鲁棒但显存要求高没有条件就退而求其次用 medium 加降噪预处理。切分粒度短一些能减少单段错误对整体的影响代价是上下文变碎。temperature 在整理任务里不建议超过 0.5否则输出会开始飘。4. 避坑与排查那些让流水线跑不通的细节4.1 识别结果全是重复文本现象输出里同一句话反复出现或者整段变成「谢谢大家谢谢大家」。原因通常是音频里有长段静音或纯噪声Whisper 在解码时陷入循环。解决办法是在识别前用 VAD语音活动检测裁掉静音段或者给transcribe加condition_on_previous_textFalse切断对前文的依赖。这个参数默认是 True长音频里反而容易引发重复短音频影响不大。4.2 中文识别夹杂英文乱码现象中文句子里突然冒出无意义的英文单词或符号。原因多半是语言检测误判模型在中文和英文之间反复横跳。解决是显式指定languagezh并加initial_prompt用中文示例引导。如果素材本身是中英混杂那就别强行指定单一语言改用支持多语言的模型并接受一定错误率。4.3 LLM 输出格式每次都不一样现象有时输出带小标题有时是纯段落待办事项时有时无。原因是提示词约束不够硬模型在自由发挥。解决是把输出格式写成模板明确要求「必须包含以下三个二级标题」并在系统提示里强调「严格按模板输出」。如果还不稳可以用 JSON 模式先输出结构化数据再用代码渲染成 Markdown这样格式由代码保证不依赖模型自觉。4.4 长音频跑到一半显存爆了现象处理长文件时程序崩溃报 CUDA out of memory。原因是整段音频一次性加载进显存。解决是切分后再识别每段单独加载、识别完释放。Whisper 的transcribe本身对长音频有内部分块但分块大小不可控手动切分更稳。另外识别完的模型对象及时del并清缓存能缓解显存压力。4.5 时间戳和文本对不上现象拿词级时间戳做字幕时字幕和语音错位。原因是word_timestamps在某些模型上精度有限尤其是语速快或口音重时。解决是别依赖词级时间戳做精确对齐改用段级时间戳或者用专门的对齐工具做二次校准。纯做文档的话时间戳精度其实不影响可以关掉省时间。5. 进阶把 AI-Media2Doc 做成可复用的批处理服务单文件跑通只是起点真正省时间的是批量化。我一般的做法是把整条流水线包成一个带队列的服务监控一个输入目录有新文件就自动处理输出到另一个目录失败的重试并记录日志。这样丢一批素材进去去干别的回来收文档。关键设计有三点。第一是幂等每个文件用内容哈希做标识处理过的跳过避免重复跑。第二是断点续跑中间结果音频、原始转写都落盘任何一步失败都能从最近的成功步骤继续不用从头来。第三是并发控制ASR 吃显存LLM 吃网络两者并发数要分开限制ASR 一般串行或并发 1LLM 可以并发 3 到 5。import hashlib, os, json from pathlib import Path def file_hash(path: str) - str: 计算文件内容哈希用于幂等判断 h hashlib.md5() with open(path, rb) as f: for chunk in iter(lambda: f.read(8192), b): h.update(chunk) return h.hexdigest() def process_one(video_path: str, workdir: str): fid file_hash(video_path) state_file Path(workdir) / f{fid}.json state json.loads(state_file.read_text()) if state_file.exists() else {} # 按阶段推进已完成阶段跳过 if audio not in state: extract_audio(video_path, f{workdir}/{fid}.wav) state[audio] True if raw not in state: state[raw] transcribe(f{workdir}/{fid}.wav) if doc not in state: state[doc] structure(state[raw]) state_file.write_text(json.dumps(state, ensure_asciiFalse)) Path(f{workdir}/{fid}.md).write_text(state[doc], encodingutf-8)file_hash用内容哈希而不是文件名避免改了名就重跑。state字典记录每个阶段的完成状态任何一步失败重跑时只补缺失的阶段。这个模式看起来朴素但在批量处理几百个文件时能省下大量重复计算。验证方法也简单故意在结构化阶段断网重跑时应该直接从raw继续而不是重新识别音频。一个具体技巧是给 LLM 调用加超时和重试。网络抖动很常见一次失败就整个文件重来太亏。用指数退避重试三次还失败就标记该文件待人工处理不阻塞队列里其他文件。这套东西搭好之后处理素材就从「守着跑」变成「丢进去等结果」这才是源码形态真正的价值——你能按自己的流程改而不是迁就某个固定服务。希望帮到你。本文还有配套的精品资源点击获取