From 6126ad5c2c2190de5eb02b045b81df74dd9afdcb Mon Sep 17 00:00:00 2001 From: hoelee Date: Tue, 13 Oct 2026 22:06:16 +0800 Subject: [PATCH] =?UTF-8?q?Batch=203a:=20library=5Frun.py=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20--work-dir=EF=BC=88=E5=B7=A5=E4=BD=9C=E6=A0=B9?= =?UTF-8?q?=E5=8F=AF=E6=8C=87=E5=AE=9A=EF=BC=8C=E9=BB=98=E8=AE=A4=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E4=B8=8D=E5=8F=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _apply_work_dir() 在 argv 解析后重绑 WORK/STAGE/LEDGER/LOCKF/POLICY;--stage-dir 仍只覆盖暂存。 不传 --work-dir 时行为与以前逐位一致(控制面 docs/DESIGN.md §18 是规则出处)。 STATE.md 记录本轮。验收:控制面 scripts/acceptance_work_dir.py 真跑 copy 档 PASS 8/8, 控制面 124 用例 OK。回滚点 tag pre-batch3a。 --- STATE.md | 18 ++++++++++++++++++ scripts/library_run.py | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/STATE.md b/STATE.md index fc54e2a..c5fd044 100644 --- a/STATE.md +++ b/STATE.md @@ -2,6 +2,24 @@ 给下一个会话的交接。**权威运行器:`scripts/library_run.py`**(不要再恢复旧的 `hevc_lib.py`)。 +## 2026-10-13:`--work-dir` —— 工作根可以指定了(Batch 3a) + +**改了什么**:`WORK`(默认 `C:\AI\transcode-run`)从「唯一真相」退化成**默认值**。`main()` 解析完 argv 后, +若给了 `--work-dir` 就调用新增的 `_apply_work_dir()` 重新绑定 `WORK / STAGE / LEDGER / LOCKF / POLICY` +(模块全局;所有引用点都是调用时查找,所以函数体一行没改)。`--stage-dir` 仍然只覆盖暂存。 + +**为什么**:控制面(`hoelee/hevc-transcode`)过去要用正则读本文件的 `WORK` 常量、并要求 `settings.work_root` +与它**完全一致**,否则 doctor FAIL —— 那是跨仓库的隐式契约。现在由控制面显式传,`work_root` 成为权威 +(规则见控制面仓 `docs/DESIGN.md` §18)。**根目录指错不会报错,只会读到一个空的 `gate_policy.json`**,表现是 +业主的 park/accept 决定「全都丢了」—— 所以默认值必须保持不变,只有显式传 `--work-dir` 才移动。 + +**手跑行为不变**:`python scripts/library_run.py …`(不传 `--work-dir`)写的地方和以前一模一样。 +**控制面**只在检测到本文件支持该参数时才传(`rt.config.engine_features()` 读源码判定)。 + +**验收**(在控制面仓):`scripts/acceptance_work_dir.py` 真跑一次 copy 档 → **PASS 8/8**(ledger 与 `stage/` +落在新根、默认根里**没有本项**的记录、源档 SHA-256 未变、安装件可 probe);控制面 124 用例 OK。 +⚠ 这类验收**不要**用「默认根 ledger 哈希没变」当断言 —— 生产 lane 同时在追加那份文件。 + ## 2026-10-11:画质策略落地(闸门对齐 / 门槛 / VMAF)—— 先读这一节 **结论:闸门以前把帧配错了对**,靠它判死过 14 个健康编码(9.6 h 里烧掉 12.4 h lane 时间、13 项被 park)。 diff --git a/scripts/library_run.py b/scripts/library_run.py index c13644a..0bd2dcd 100644 --- a/scripts/library_run.py +++ b/scripts/library_run.py @@ -99,10 +99,32 @@ def run_pinned(cmd, timeout=None, **kw): raise subprocess.TimeoutExpired(cmd, timeout, output=out, stderr=err) return subprocess.CompletedProcess(cmd, p.returncode, out, err) ROOT = r"Z:\Class\樊登读书\樊登读书会(每周更新)" +# The work root: ledger.jsonl + gate_policy.json + the per-tier locks + (by default) the scratch dir. +# `--work-dir` re-points ALL of it BEFORE a run starts (see _apply_work_dir below). The constant here +# is the compiled-in DEFAULT, kept so a hand-run engine writes exactly where it always did: an +# existing ledger and an existing gate_policy.json - the only record of what is parked and accepted - +# must never be silently swapped for an empty directory. The control plane passes --work-dir +# explicitly (Batch 3a), which is what makes `settings.work_root` authoritative instead of a number +# the two repositories had to keep in sync. WORK = r"C:\AI\transcode-run" STAGE = os.path.join(WORK, "stage") LEDGER = os.path.join(WORK, "ledger.jsonl") LOCKF = os.path.join(WORK, "run.lock") + + +def _apply_work_dir(path): + """Re-point WORK and everything derived from it. Called from main() AFTER argparse. + + Rebound as module globals so every existing call site keeps working unchanged; the ledger, the + locks and the policy file are all read through those names at call time, never captured at + import time. + """ + global WORK, STAGE, LEDGER, LOCKF, POLICY + WORK = os.path.abspath(path) + STAGE = os.path.join(WORK, "stage") + LEDGER = os.path.join(WORK, "ledger.jsonl") + LOCKF = os.path.join(WORK, "run.lock") + POLICY = os.path.join(WORK, "gate_policy.json") VIDEO_EXT = {".mkv", ".mp4", ".mov", ".avi", ".webm", ".flv", ".ts", ".m4v", ".wmv", ".mpg", ".mpeg", ".m2ts", ".3gp", ".ogv", ".vob", ".rmvb", ".rm", ".divx"} DECODE_CHECK = True @@ -1334,7 +1356,7 @@ def collect(root): def main(): - global DECODE_CHECK, STAGE + global DECODE_CHECK, STAGE # WORK/STAGE/LEDGER/LOCKF/POLICY move via _apply_work_dir ap = argparse.ArgumentParser() ap.add_argument("--cq", type=int, default=CQ_NVENC, help="nvenc tier quality (measured default 30)") ap.add_argument("--crf", type=int, default=CRF_X265, help="x265 tier quality (measured default 24)") @@ -1347,9 +1369,13 @@ def main(): help="hex CPU mask for the x265 tier (default = the E-core mask); the mask is " "inherited by the ffmpeg children and must agree with --pools") ap.add_argument("--stage-dir", default=None, - help="scratch directory for this run (default %s). The ledger, the locks and " - "the policy file stay in WORK - only the disposable per-item scratch " - "moves, so the dashboard can point it at a roomier drive." % STAGE) + help="scratch directory for this run (default /stage). The ledger, the " + "locks and the policy file stay in --work-dir - only the disposable " + "per-item scratch moves, so the dashboard can point it at a roomier drive.") + ap.add_argument("--work-dir", default=None, + help="work root for this run: ledger.jsonl, gate_policy.json and run-*.lock " + "(default %s, this file's compiled-in constant, so a hand-run engine is " + "unchanged). An explicit value moves ALL of them." % WORK) ap.add_argument("--tier", choices=["copy", "nvenc", "x265"], help="only touch files classified into this tier (run tiers separately)") ap.add_argument("--force-tier", choices=["copy", "nvenc", "x265"], default=None, @@ -1384,6 +1410,9 @@ def main(): "THROW THE OUTPUT AWAY - nothing is uploaded and nothing is replaced") args = ap.parse_args() DECODE_CHECK = not args.no_decode_check + if args.work_dir: + _apply_work_dir(args.work_dir) + log("=== work dir: %s (ledger, locks, gate_policy.json)" % WORK) if args.stage_dir: STAGE = os.path.abspath(args.stage_dir) log("=== stage dir: %s (ledger + locks stay in %s)" % (STAGE, WORK))