# 本地视频语音转写 4 小时长视频优化编码方案 V002 创建人员:`dev.developer.project` 文件职责:在 V001 已冻结合同上,补齐正式输出提交的中断回滚,以及 4 小时验收的外部资源采样和正确性证据合同。 管理规范/模板:`../../../dev-doc/编码规范.md`;`../../../dev-doc/开发审计规范.md`;`../../../../common/dev-doc/编码规范.md`。 引用文件:`CODE-DESIGN-PROJECT-INFO-LOCAL-MEDIA-TRANSCRIBE-LONG-VIDEO-OPT-V001.md`;`../../../dev-doc/开发审计报告.md`;`../../../ai-media-processor/draft/本地视频语音转写_4小时长视频性能与资源优化需求_v0.1.md`;`../../../dev/project-dev/transcribe_media.py`;`../../../dev/project-dev/test/test_transcribe_media.py`。 记录方式:V001 的后继方案;只闭环审核 `F1/F2`,V001 已通过合同保持冻结,复审通过前不实现。 ## 1. 事项、前序结论与变更边界 - 事项:`DEV-PROJECT-INFO-LOCAL-MEDIA-TRANSCRIBE-LONG-VIDEO-OPT-20260801-001`。 - 前序审核:`DEV-AUDIT-PROJECT-INFO-LOCAL-MEDIA-TRANSCRIBE-LONG-VIDEO-OPT-DESIGN-20260801-001=HOLD/2`。 - 本版只增加两项:`F1` 的 `BaseException` 提交回滚合同与注入测试;`F2` 的外部只读资源采样器和完整验收证据清单。 - V001 已通过内容全部冻结:公开 CLI/四文件名不变;`duration <= 1200 s` 走原路径;长视频使用 `1200 s` 核心块、两侧 `5 s` 重叠、首尾裁边和中点唯一归属;模型一次加载、首块语言检测后复用;完整 FLAC 加最多一个临时块;不扩展 CPU/模型降级、GUI、API、数据库、批处理或断点续跑。 ## 2. F1:正式输出提交的中断安全 ### 2.1 实现合同 `_commit_outputs` 仍按 FLAC、TXT、SRT、JSON 的既有顺序逐项 `Path.replace`,外部签名和无覆盖检查不变,但提交区的捕获边界从 `Exception` 扩大为 `BaseException`: 1. 记录本次调用实际创建的正式目录,以及每个已经成功移动到正式位置的文件。 2. 捕获任何 `BaseException` 后,对已移动文件逐项执行无覆盖目标的删除;只有本次调用创建且已为空的正式目录才删除。回滚本身采用尽力但不遮蔽原异常的清理分支,并保留回滚失败详情供普通异常错误消息使用。 3. 若原异常是 `KeyboardInterrupt`、`SystemExit` 或其他非 `Exception` 的 `BaseException`,回滚结束后以裸 `raise` 原样重抛;`main` 继续按现有合同把 `KeyboardInterrupt` 转为退出码 `130`,不会伪装成普通转写错误。 4. 若原异常是普通 `Exception`,回滚后继续包装为现有 `MediaTranscriptionError("提交输出文件失败,已回滚已写结果……")`;不改变 CLI 退出码或用户可见合同。 5. 外层 `transcribe_video(...): finally` 继续删除相邻暂存目录;提交函数不删除原视频、不覆盖既有正式文件,也不删除调用前已经存在的输出目录。 如果某个正式文件无法回滚,错误信息必须明确列出残留路径;不得打印“未提交输出文件”或将该运行判为成功。该情况不改变本需求的正常验收路径,但防止回滚失败被静默掩盖。 ### 2.2 中断注入测试 在系统临时目录使用四个合成暂存文件,参数化注入 `KeyboardInterrupt`:分别在第 `1`、`2`、`3` 个文件成功移动后、下一个提交动作开始前中断。每个用例必须断言: - 捕获到的仍是原 `KeyboardInterrupt`,不是 `MediaTranscriptionError`; - 四个正式文件全部不存在; - 若正式目录由本次调用创建,则目录不存在; - 调用 `transcribe_video` 的集成清理用例中,相邻暂存目录不存在; - 原视频/输入暂存之外的文件未变化。 另保留一个普通 `OSError` 注入用例,证明仍被包装为 `MediaTranscriptionError` 且已移动正式文件回滚。现有 6 项测试必须全部回归。 ## 3. F2:外部只读资源采样器 ### 3.1 执行与归属 新增仅供真实验收使用的 `dev/project-dev/test/monitor_transcribe_resources.ps1`;它不进入生产 CLI,不修改被测脚本,也不读取或写入模型、源视频或正式输出内容。采样器负责启动隔离环境中的 Python 命令并把该 Python PID 冻结为 `root_python_pid`。 - 固定采样周期:`500 ms`,从根 Python 创建后开始,到根进程退出且最后一次样本落盘后结束;周期和实际采样时间均写证据。 - 进程归属:每次通过 Windows `ParentProcessId` 递归构造根 Python 的后代集合;仅名称为 `ffmpeg.exe` 的后代计入 FFmpeg 工作集。每条样本保存根 PID、后代 PID/父 PID/进程名;不把系统中其他 Python 或 FFmpeg 进程计入。 - RAM:记录根 Python `WorkingSet64`、全部归属 FFmpeg 子进程 `WorkingSet64` 之和,以及同一采样时点的两者合计;任务和逐块 RAM 峰值均取“同一时点合计”的最大值,不把两个不同时点的独立峰值相加。 - GPU:每个周期读取 `nvidia-smi --query-compute-apps=pid,used_gpu_memory --format=csv,noheader,nounits`,只汇总根 Python 和其后代 PID;另记录 GPU 型号。系统全局显存可作诊断列,但不得用于替代本任务 PID 归属值。 - 块号:被测程序用立即刷新的稳定行 `正在转写分块 i/n` 宣告当前块;采样器解析 stdout 并把最后一次已宣告的 `block_index/block_total` 绑定到样本。完整 FLAC 提取和模型加载阶段块号为空,仍计入任务全局峰值。 - 临时媒体:每周期只枚举本次相邻暂存目录中的 `*.flac`,记录完整 FLAC、分块 FLAC的路径类别、数量和字节;临时块指标单独排除正式候选完整 FLAC,只允许同时最多 `1` 个分块文件且单块峰值 `<500 MB`。 若根 PID、进程树、GPU 查询、块号关联或必填样本字段缺失,采样器必须把 `sampling_valid=false` 和具体原因写入汇总;该次运行不得宣告资源验收 PASS。采样器只在下述任务临时根写证据,不触碰原视频及其目录。 ### 3.2 固定证据文件 真实 19:54 回归与 4 小时运行分别使用独立子目录,并至少保存: - `command.json`:原样参数数组、环境 Python 绝对路径、开始/结束时间、退出码、墙钟耗时、采样周期和根 PID; - `stdout.log`、`stderr.log`:完整且按产生顺序写入; - `resource_samples.csv`:`timestamp_utc,elapsed_seconds,root_python_pid,descendant_processes,block_index,block_total,python_working_set_bytes,ffmpeg_working_set_bytes,combined_working_set_bytes,task_gpu_memory_mib,temp_chunk_count,temp_chunk_bytes`; - `resource_block_peaks.csv`:每块的首末采样时间、样本数、Python/FFmpeg/合计 RAM 峰值、任务 GPU 峰值、临时块数量和字节峰值; - `resource_summary.json`:采样有效性、GPU 型号、全局峰值、逐块峰值、门禁值和逐项 PASS/FAIL; - `acceptance_evidence.json`:源文件前后指纹、媒体/四文件属性、时间轴与边界核验结果。 `resource_summary.json` 必须直接计算并保存: `ram_growth_bytes = max(last_3_block_ram_peaks) - max(first_3_block_ram_peaks)` 其中 `block_ram_peak` 是该块内 `combined_working_set_bytes` 的最大样本;4 小时 12 块运行的门禁为 `ram_growth_bytes <= 2 * 1024^3`。同时要求任务全局 `combined_working_set_bytes <= 12 * 1024^3`、任务 GPU 峰值 `< 10 * 1024 MiB`、临时分块数量 `<=1`、临时分块峰值 `<500 * 1024^2`。不得用平均值、进程全局值或前三/后三块以外窗口替代。 ## 4. 完整正确性与源文件不变证据 `acceptance_evidence.json` 的自动核验不得省略以下项目: 1. 原始 `G:\熊猫财经\20260731-155802.mp4` 在任何生成/回归动作前、全部动作结束后分别记录 `size_bytes`、`CreationTimeUtc`、`LastWriteTimeUtc`、`SHA-256`,四项逐一相等;原视频不移动、重命名、覆盖或删除。 2. 19:54 回归必须证明 `duration <= 1200 s`、未出现分块进度、四文件完整、FLAC 为 `16000 Hz/1 ch`,并核对既有 705 段基线及 TXT/SRT/JSON 对应。 3. 4 小时副本只位于专属任务临时根;记录生成命令、实际时长、大小和 SHA-256。其完整运行必须 exit `0`、墙钟 `<=100 min`,四文件均存在且没有额外正式输出。 4. 用 FFprobe 保存正式 FLAC 的 codec、采样率、声道、时长;要求 `flac/16000/1`,且其时长与测试媒体音轨在明确的 FFmpeg 封装容差内。 5. JSON 必须可解析并为 `large-v3/cuda/float16/vad_filter=true`;每段满足 `0 <= start <= end <= flac_duration`。最终段按 `(start,end,text)` 非递减排序;SRT 序号严格为 `1..N`,时间合法且与对应 JSON 段一致;TXT 非空行、SRT 块、JSON 段的数量和文本逐项一致。 6. 对每个核心边界 `1200,2400,...,13200 s` 自动导出 `[boundary-15,boundary+15]` 内的 JSON/SRT/TXT 对照行;逐边界记录无重复段、无逆序、无非法时间,并人工抽查可读性后记录 `reviewer/time/result`。不得只抽首、中、末三个位置。 7. 逐块进度必须严格为 `1..12` 且只出现一次开始宣告;全局输出段保持连续 SRT 序号。开始前三块与最后三块必须各有样本和峰值,否则 RAM 增长结论无效。 ## 5. 实施、测试和放行顺序 1. 本方案复审 `PASS` 后才修改代码与测试。 2. 先执行语法编译、现有回归以及 F1/分块/时间轴新增单元测试;失败不进入真实媒体运行。 3. 再对 19:54 源视频执行完整短路径回归并生成独立证据。 4. 最后从只读源在专属临时根生成约 4 小时副本,运行一次完整采样验收;采样或正确性证据任一必填项缺失即 FAIL,不用推断补齐。 5. 测试媒体、四文件输出和全部证据保留到 `case_analysis.media_processor` 完成只读复核;不在本事项中扩展修复范围。 ## 6. 审核问题逐项映射 | 审核项 | V002 闭环位置 | 验收门禁 | |---|---|---| | `F1` 用户中断可留下部分正式输出 | 第 2 节 | 捕获 `BaseException`、先回滚、非普通异常原样重抛;第 1/2/3 次成功移动后注入中断,四正式文件和暂存均不存在 | | `F2` 资源采样不可复核 | 第 3 节 | 外部采样器、500 ms、根 PID/FFmpeg 后代归属、逐样本/逐块/汇总证据、固定 RAM 增长公式 | | `F2` 正确性和源不变证据不完整 | 第 4 节 | 全部边界 ±15 s、合法范围、排序/连续序号、四文件一致、源大小/创建与修改时间/SHA-256 前后对照 | ## 7. 当前状态 `PENDING_INDEPENDENT_DESIGN_REREVIEW`