Files
universal-video-transcode/STATE.md
T
hoelee bd415d2af5 fix: a MemoryError out of a share write no longer orphans the staged file
Host hit 0 GB physical free / 76.8 of 79.6 GB commit while both tiers ran (pagefile
capped at 32 GB on a 97%-full D:), and the Dokan write of the batch's largest output
raised MemoryError. except OSError does not catch that, so the item was logged
'!! FAIL MemoryError:' with an empty message and the complete 493 MB <live>.new.mp4
stayed on the share; 8 such orphans had accumulated.

- _sha_remote / copy_remote_to_local / copy_local_to_remote: except (OSError, MemoryError)
  so a transient host hiccup retries instead of failing the item
- same widening for both partial-file cleanups
- per-item handler: delete <dest>.new.mp4 unconditionally (os.remove + ignore
  FileNotFoundError, never gated on os.path.exists - stale metadata on this share)

Verified by injecting MemoryError: the upload path returns False with the partial
deleted, the source read retries then raises a clean IOError, the hash retries twice.
2026-10-09 11:38:40 +08:00

161 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# STATE — 樊登读书会媒体库转码(2026-10-09 11:40 更新)
给下一个会话的交接。**权威运行器:`scripts/library_run.py`**(不要再恢复旧的 `hevc_lib.py`)。
## 现在在跑什么
**两档都停着** —— 2026-10-09 11:30 业主叫停,先修 bug 再重启;进程已按 PID 全部清掉,GPU encoder 0 %。
**重启前先读「第 10 个 bug:MemoryError」和「8 个 `.new.mp4` 残留」两节**,并先腾内存。
原设计:两个 HEVC 档在跑,各自独立的锁(`run-x265.lock` / `run-nvenc.lock`;x265 = E 核,nvenc = GPU ASIC,互不争抢)。
**它们不在 Hermes 的进程管理里** → `process_manage` 看不到,也没有退出通知:只能看日志、按 PID 杀。
```bash
# 断点续跑(幂等:已转好的自动跳过,失败项文件未动,下一轮自动重做),每档最多重试 8 次
bash /d/tmp/xcode/relaunch_tiers.sh
# 看进度
tail -f /d/tmp/xcode/run_x265.log /d/tmp/xcode/run_nvenc.log
```
### 为什么改成"脱管"启动 —— 10:37 那次全停的教训(第 9 个 bug)
上一轮的 x265 / nvenc 是 Hermes 后台进程。**用户停会话时它们被一起杀了**:两个都停在编码到一半
(stage 里 `*_out.mp4` 和 `.err` 停在 10:37,日志里没有 `PASS-DONE`),所以 `persist_on_release` 在这个
环境里**不可靠**。现在两档由 `powershell Start-Process` 拉起的独立 bash 启动,父进程立刻退出 →
进程树成为孤儿,**Hermes 的清理碰不到它们**。
代价与规矩:没有退出通知(看日志);**停/杀只能按 PID**,绝不用进程名批量杀。
## 进度(2026-10-08 10:50,库内共 589 个文件)
| 档 | 已转好 | 待转 |
|---|---|---|
| copy | 160 | 4(见下) |
| x265 | 13 | 169 |
| nvenc | 25 | 174 |
| 无需处理(已达播发标准) | 44 | — |
| **合计** | **242** | **347** |
- 预期:x265 ~3.5 天、nvenc ~3 天跑完各自那档(这一轮 10:43 才重新起来,10:37 被上面那次全停打断)。
- 台账:`C:\AI\transcode-run\ledger.jsonl`(每项一行:分类/结果/大小/SSIM)。
- 进度只认台账与 state,**不看 `.mp4` 扩展名**(TubeSync 重命名会把它改回 `.mkv`)。
## 2026-10-08 新增的 4 集"每周更新"(copy 档)
`1004 反内卷` `0912 感官觉醒` `0905 如何成为情绪稳定的父母` `0822 超赞的一代`(都在 `樊登读书2026年(更新中)`):
H.264 + **mp3** 音轨,其中 `0822 超赞的一代` 其实是 **MPEG-TS** 只是挂着 .mp4 名 → iPad 播不了,
需要 copy 档(视频逐位不变 + 音轨转 AAC)。
⚠ **不要跑整轮 `--tier copy`**:copy 档唯一的跳过判据是"已经是 HEVC",而 copy 的产物是 H.264 ——
整轮会把 193 个已转好的 copy 文件全部重做一遍。改用单文件通道:
```bash
bash /d/tmp/xcode/convert_new4.sh # 逐个 --tier copy --path <file> --keep-source,共用 run-copy.lock
```
- `1004 反内卷` 10:48:53 完成 ✓(535 MB → 535 MB,视频 100% 逐位不变,音频转 AAC,149 s,rc=0)
## 图片闸门:re-do 用 0.975(业主决定 2026-10-08)
第一轮实测:一趟里 11 次编码**只差在 mean**(0.9655–0.9795)就被拒,而被接受的那些帧最低分反而更差
(0.33–0.90)—— 阈值在切一条连续的分布,不是在分开缺陷(`0718 马斯克原理`:min 0.887、**0.00%** 帧
<0.85,照样被拒)。被拒的文件本来就已能播(HEVC hvc1 + AAC),re-do 的唯一目的是清掉旧设定的膨胀;
而要够到 mean 0.98 得花掉超过源片自身的码率,会被 size ceiling 拦住 —— 结果是文件连同膨胀被永久搁置,
而且每一趟还白烧一次编码。
新规则(`library_run.SSIM_MEAN_MIN_REDO = 0.975`):**只有**"重做一个已经在播的文件"才用 0.975,条件为
live = HEVC hvc1 **且**音轨是 AAC(真能播)**且**本次是从快照原档重做;首次转换仍用 0.98。
帧占比判据(>1% 帧 <0.85)两档都不变 —— 它才是抓真缺陷的那条:`失控`(0.9655)仍会被拒,
`0808 陌生人效应`(0.9739)因为 live 是 H.264、根本不是 re-do,也仍走 0.98。
先在一个文件上验证(`0321 随他们去`,上一轮 mean 0.9789 被拒)→ 见 `/d/tmp/xcode/run_pilot.log`。
## 目录与日志
| 用途 | 路径 |
|---|---|
| 运行日志 | `/d/tmp/xcode/run_x265.log`、`run_nvenc.log`、`run_copy.log`、`run_new4.log` |
| 脱管启动器 / 新集转换 | `/d/tmp/xcode/relaunch_tiers.sh`、`convert_new4.sh`(+ `relauncher.out`);**源在仓库 `run/`**(改仓库那份再拷出来) |
| 单文件日志 | `/d/tmp/xcode/run_single.log` |
| 暂存目录(编码/闸门 raw) | `C:\AI\transcode-run\stage`(C: 盘,要留 ≥8 GB) |
| 台账(每项一行 JSON) | `C:\AI\transcode-run\ledger.jsonl` |
| 锁(每档一个 + 心跳) | `C:\AI\transcode-run\run-<tier>.lock` |
## 本轮修掉的 bug(按时间顺序,全部已提交推送)
1. **闸门判据太粗糙** → 改"缺陷形状"判:`mean < 0.98` 或 `>1% 帧 <0.85` 才判错,并**剔除近平坦帧**(SSIM 在黑场/淡入淡出上无意义;实测:好编码 0/1500、误判文件 0.33%、真缺陷 2.9%)。
2. **闸门用输入 seek 解码** → 改**两侧顺序解码**(open GOP 源从中间 seek 会解出错误画面:同一文件与自身比对 min 0.25)。
3. **闸门 raw 文件固定名** → 三个档并行时互踩(实测报出"100% 帧不符"的假缺陷)→ 改 **PID + tag** 命名。
4. **安装只用 ffprobe 元数据复核** → RaiDrive 返回**缓存属性**,rename 没落地也说成功(实测"ok 1085→478 MB"而线上仍是原档)→ 改**安装后回读哈希**。
5. **源拷贝只校验字节数** → 加 **SHA-256 比对共享盘副本**(长度对内容错是真风险)。
6. **probe 单次失败即判文件失败** → 加**重试 3 次**。
7. **看门狗用 stderr 管道**(本轮最贵的 bug)→ ffmpeg `-stats` 每 0.5 s 一行,64 KB 管道几分钟塞满 → ffmpeg 阻塞在 write() → 输出不增长 → 看门狗**误杀好编码**(整夜 ~270 杀、只转成 24 个、GPU 0%)。修:**stderr 写文件**,阈值 5→15 分钟。自检脚本 `D:\dev\hoelee-tubesync-management\cache\watchdog_selfcheck.py`。
8. 看门狗**保留**(它要防的是真实场景:用户更新 NVIDIA 驱动会把在跑的 NVENC 冻死 1.5 小时且不报错)。
9. **Hermes 后台进程会随会话一起被杀** —— 两个档在 10:37 同时死在编码中,日志里没有 `PASS-DONE`;改脱管启动(见上)。
## 未完成 / 待办
- [ ] 两档跑完 → **全库落地审计**:`D:\dev\hoelee-tubesync-management\cache\install_audit.py`(核对线上文件真实大小 + 编码器;曾一次抓到"报告成功没落地")+ `copy_land_check.py`(copy 档用音轨 aac/mp3 判定)。
- [ ] 失败项:无需清单,新一轮自动重做(失败时文件原样)。
- [ ] `2022年/0723 《走出强迫症》/走出强迫症.mp4` 用新下载(`D:\Download\0723走出强迫症.mp4`,618,696,954 B)已转好装好(450,502,893 B,HEVC+AAC,SSIM min 0.997,时长 3399.81 s)✓。**快照里那份仍是坏的**(618,697,011 B,moov 缺失)→ 待定:是否用干净副本替换快照里的坏档,以免将来"从原档重转"踩坑。
- [ ] 目标文件夹里曾出现一个 0 字节的 `0723走出强迫症.mp4`(现已不在)—— 若是用户自建请告知命名约定。
- [ ] 全库唯一真损坏:`走出强迫症.mp4` 的原档(上面已处理);其余 573 个可读文件截断审计 0 可疑。
## 用法速查
```bash
# 单文件(任意位置)转好并装回库,走同一套分档/闸门/回读
python scripts/library_run.py --path "D:/Download/x.mp4" --dest "Z:/.../x.mp4" --keep-source
# 只审计不落地(本地编码+闸门,输出丢弃)
python scripts/library_run.py --tier x265 --limit 3 --keep-local
# 分档预览
python scripts/library_run.py --dry-run --limit 20
```
## 第 10 个 bug:MemoryError 会在共享盘上留孤儿(2026-10-09)
现象:11:19:31 与 11:19:37 连续两次 `!! FAIL MemoryError:`(消息空白),两次相隔 6 秒 ——
一次是《崇祯传》**装盘**(写 493 MB 到 RaiDrive),一次是下一项**读源**。
根因是**整机内存/提交额度耗尽**,不是文件问题:
```
物理可用 0.0 GB / 48 GB 已提交 76.8 GB / 上限 79.6 GB(96.5 %)
pagefile d: 初始 8 GB / 上限 32 GB —— 但 D: 只剩 19 GB(97 % 满),长不上去
大户:Chrome ≈4 GB、Hermes ≈3.7 GB(3 个进程)、upscayl 1.17 GB、whisper-server 0.83 GB、Discord 0.76 GB
```
`copy_*` / `_sha_remote` 当时只 `except OSError`,**而 `MemoryError` 不是 OSError** → 直接冒到
per-item 兜底;删半截 `remote_tmp` 的清理写在 `except OSError` 里 → **半截/完整的 `.new.mp4` 留在共享盘上**。
已修(本轮,`scripts/library_run.py`):
1. `_sha_remote` / `copy_remote_to_local` / `copy_local_to_remote` 的 `except OSError` → `except (OSError, MemoryError)`(瞬时抖动变重试,不是 FAIL);
2. `copy_local_to_remote` 内的清理、读回不符时的清理同样放宽;
3. **per-item 兜底加了无条件删除 `<dest>.new.mp4`**(用 `os.remove` + 忽略 `FileNotFoundError`,**不用** `os.path.exists`,因为该共享盘的元数据可能是陈旧的)。
实测(注入 MemoryError):上传路径不再抛异常、半截文件被删;读源路径重试后抛干净的 `IOError`;
`_sha_remote` 重试 2 次后抛出。
**内存仍是硬前提**:物理可用 0 GB / 提交 96.5 % 时重启,第一项照样会 FAIL(现在至少不留残留)。
重启前建议:关掉 upscayl / 多余 Chrome 标签 / Discord 腾出 5-6 GB;pagefile 上限别放在 97 % 满的 D:。
## 8 个 `.new.mp4` 残留(2026-10-09 11:35 逐个探过)
原档都在,没丢东西。7 个是**半截前缀**(内容早停 → 没用,删掉让下一轮重做);
**只有一个是完整的、可以救回来**:
| 文件 | 暂存 | 判定 |
|---|---|---|
| 2022年/0115《崇祯传》 | 493 MB | ✅ **完整**(3616.8 s 与源一致、内容到 3609 s)→ 校验后可改名安装 |
| 0622人生海海 / » 0819 李白 / 0527 洪业 / 0807东京传 / 20190601被管教的勇气 / 1230 不安的哲学 / 0511原生家庭 | 8 MB–165 MB | ❌ 半截,删 |
## 红线(用户明确要求过)
- **绝不删除/覆盖快照原档**(`#snapshot` 是唯一的干净档案)。
- 失败时**不动 live 文件**;只有"结构 + 全解码 + 图片闸门 + 上传回读 + 安装回读"全过才替换。
- 杀进程**只按 PID**(绝不用进程名批量杀,`taskkill /IM ffmpeg.exe` 会连带杀掉别人的编码);一档一个锁;同一档不要跑两个实例。
- 动 GPU(更新驱动/跑 ComfyUI/加载大模型)前先暂停 nvenc 档。