Skip to content

章节梗概 (1/9):统一 segments 带时间片段读取适配器(T3) - #15

Merged
zj1123581321 merged 4 commits into
mainfrom
stack/chapters-01-subtitle-timeline
Jul 21, 2026
Merged

zj1123581321 merged 4 commits into
mainfrom
stack/chapters-01-subtitle-timeline

Conversation

@zj1123581321

@zj1123581321 zj1123581321 commented Jul 20, 2026 •

Copy link
Copy Markdown
Collaborator

范围

Stacked PR 系列第 1 个(共 9 个,feat/chapters-wiring 拆分)。本 PR 只交付 T3:统一「带时间片段」读取适配器 segments.py(load_segments 多来源回退、normalize_segments 防御式解析、parse_time_to_seconds 严格校验),为后续 ChaptersProcessor 提供输入契约。

字幕解析三路时间戳保留(T2)与 ChaptersProcessor(T5)在第 2 个 PR。

配置/Schema 触点

无配置变更。

验证证据

  • uv run --frozen --extra dev pytest tests/unit 全绿(2016 passed @ 本 PR head)
  • 已含 gate-r1/r2 轮修复:load_segments 捕获 UnicodeDecodeError(非法 UTF-8 字节降级回退)、parse_time_to_seconds 拒绝 inf/nan/溢出

Stacked 结构

  1. 本 PR(main ← stack/chapters-01)T3 segments 适配器
  2. T2 字幕时间戳 + T5 ChaptersProcessor
    3-4. gate 安全加固 r1–r28
  3. 缓存层 timeline 读写
  4. 协调器/llm_ops 接线
  5. T8 plain 结构化校对
  6. T9/T10 验收与文档
  7. T11 章节 UI 重设计

合并顺序需按栈序。

TDD 先写 33 条失败测试覆盖时间解析(数字/HH:MM:SS/MM:SS/垃圾值/负数)、
segments 规范化("文本永不丢失"铁律、start_time|start 与 end_time|end
双命名兼容、speaker 缺失不编造)、以及从缓存目录按优先级
(transcript_funasr.json > transcript_capswriter.json)加载并在损坏/
空结果时降级到下一来源,再实现至全绿。全新文件,不改动任何既有模块。

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Reviewed commit: d220407dded8ee59f5a4e68d5c162b00886ba9b1

🤖 Review 门禁 — 判定: pass(主审: claude-glm)

本 PR 交付的 segments 读取适配器(parse_time_to_seconds / normalize_segments / load_segments)实现正确、防御性强、测试覆盖充分,与 PR 意图(T3:为后续 ChaptersProcessor 提供输入契约)完全一致。

逐项核实结论:

  1. parse_time_to_seconds 对 None/bool/inf/nan/溢出("1e309")/负数/垃圾类型/错误冒段数全部正确返回 None,从不抛异常;int/float 直传、纯数字串、"HH:MM:SS"/"MM:SS" 解析均正确。bool 子类陷阱、float("1e309") 静默溢出为 inf 等边界均已用 math.isfinite 兜住。
  2. normalize_segments 正确实现"文本永不丢失"不变式——只要 text 非空即保留条目(时间脏则置 None),text 空/缺才跳过;dict 与 bare-list 两种形态、start_time|start / end_time|end 字段优先级(优先 _time 后缀)、speaker 透传(缺失不编造占位)均与文档一致。
  3. load_segments 按 funasr > capswriter 优先级回退;UnicodeDecodeError(ValueError 子类、非 OSError)已被显式列入 except 元组,gate-r1/r2 修复到位;损坏/空条目来源正确降级到下一优先级,全程不抛异常。
  4. 导入 from ..utils.logging import setup_logger 经核实路径正确(utils/logging 为子包,导出 setup_logger);与仓库既有 logger=setup_logger("xxx") 模式一致。
  5. 测试文件 35 个用例覆盖三条公共函数的主路径与大量边界,与 CLAUDE.md「测试 console 输出纯英文、无 emoji/中文」约定一致。

未发现任何 blocker 或 major 级正确性/安全/设计偏差问题。下方仅列出 3 个 nit/minor 级观察,均不影响合并判定。

覆盖:single;1 个审查单元;511 行 diff。

  • nit · correctness · correctness-negative-colon-component src/video_transcript_api/transcriber/segments.py:81 — PR 声称 parse_time_to_seconds 为「严格校验」、文档声明「负数被视为非法时间」,但冒号格式里单个负分量若被其它正分量抵消为正总数,可绕过非负校验。生产 FunASR/capswriter 时间戳不会出现此类输入,实际风险极低,属严格性边角缺口而非功能 bug。
    • 触发:parse_time_to_seconds 冒号分支只对每个分量做 math.isfinite 检查,未校验各分量非负;最终仅校验 total>=0。
    • 证据:第 81 行 if not all(math.isfinite(n) for n in numbers): return None 之后直接进入加权求和;第 88-89 行 total = hours*3600 + minutes*60 + seconds_part; return total if math.isfinite(total) and total >= 0 else None。输入 "01:-02:03" → numbers=[1.0,-2.0,3.0],total=3600-120+3=3483.0≥0,会被当作合法时间返回,而 -2 分钟在语义上非法。测试 test_negative_number_returns_none 只覆盖了整体为负(-5/"-5")的情况,未覆盖冒号分量含负数的情形。
    • 验收:在冒号分支 numbers 计算后、加权求和前增加 if not all(n >= 0 for n in numbers): return None(与既有 math.isfinite 检查并列),并补一条用例覆盖 "01:-02:03" / "-01:02:03" 等输入返回 None。若刻意保留当前行为,则在 docstring 显式说明「仅校验总数非负」。
  • minor · testing · testing-empty-segments-fallback-gap tests/unit/test_segments_adapter.py:197 — 「合法 JSON 但零有效条目→降级到下一来源」是生产中可发生(FunASR 返回空文本段)且为本适配器设计契约明确声明的行为,缺少直接回归测试,后续重构易被悄悄破坏。
    • 触发:load_segments 中「文件存在且 JSON 合法、但 normalize_segments 返回 None(全部条目无效)→ 尝试下一优先级来源」这条降级路径(segments.py 第 197-201 行)没有被任何测试直接覆盖。
    • 证据:现有 load_segments 测试覆盖了:文件缺失、JSON 损坏(test_corrupted_funasr_falls_back_to_valid_capswriter)、非法 UTF-8(test_invalid_utf8_in_funasr_falls_back_to_valid_capswriter)三类降级,但没有任何用例构造「transcript_funasr.json 是合法 JSON 且可解析、但 segments 全为空文本(如 {"segments":[{"text":""}]})」并同时存在可用 capswriter 来源,以验证此条 normalizes→None→降级 的分支。该分支与「异常→降级」是两条独立代码路径。
    • 验收:新增一个 TestLoadSegments 用例:在 tmp_path 写入 transcript_funasr.json(内容为 {"segments":[{"start_time":0.0,"end_time":1.0,"text":""}]})与有效的 transcript_capswriter.json,断言 load_segments 返回 capswriter 的规范化结果,而非 None。
  • nit · correctness · correctness-empty-speaker-preserved src/video_transcript_api/transcriber/segments.py:153 — 空 speaker 被保留为 "" 可能令下游消费者收到一个语义上等同于缺失的空标签,与「缺失则不编造占位」的总体稳健性基调略有出入;实际影响极小。
    • 触发:normalize_segments 对 speaker 字段仅判断 is not None,未对空串/纯空白做与 text 同等的清洗。
    • 证据:第 153-155 行 speaker = item.get("speaker"); if speaker is not None: entry["speaker"] = str(speaker)。当原始数据 speaker 为 "" 或 " " 时,会被原样保留为 entry["speaker"]="",而 text 字段在同样情况下会被判定为无效并整条跳过——两字段的「空值」处理策略不一致。生产 FunASR speaker 为 SPEAKER_00 类实标签、capswriter-compat 无 speaker 字段,故空串不会实际出现。
    • 验收:可选:在 speaker is not None 之后追加 and isinstance(speaker, str) and speaker.strip()(或对 str(speaker).strip() 判空)才写入 entry;或在 docstring 显式声明 speaker 仅按 None/缺失判定、空串视为有效值。维持现状亦可接受。

@github-actions

github-actions Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

📒 Review ledger state

  • Commit: d220407dded8ee59f5a4e68d5c162b00886ba9b1
  • Round: 3
  • Status / findings: pass / 3
  • Comparison: same_head_rerun; stable/missing/appeared = 0/0/3

完整数据保存在 codex-review-ledger artifact;此 sticky comment 仅保存跨 rerun 的连续游标。

zj1123581321 and others added 3 commits July 20, 2026 07:12
"1e309" 等超大数字字符串在 Python 里会静默溢出为 float('inf'),之前被当作合法
时间戳放行,下游对返回值做 int() 转换时会直接崩溃。用 math.isfinite 收口所有
数值入口(int/float 直传、纯数字字符串、HH:MM:SS 字符串),非有限值一律视为
非法时间返回 None。

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zj1123581321 zj1123581321 changed the title 章节梗概 (1/9):字幕时间戳保留与统一 segments 适配器 章节梗概 (1/9):统一 segments 带时间片段读取适配器(T3) Jul 20, 2026
@zj1123581321
zj1123581321 merged commit 730f121 into main Jul 21, 2026
3 of 4 checks passed
@zj1123581321
zj1123581321 deleted the stack/chapters-01-subtitle-timeline branch July 21, 2026 02:24
zj1123581321 added a commit that referenced this pull request Jul 23, 2026
- 260723-0214 HANDOFF 头部标注已被 260723-0246 取代(范围锁定 Stage 1)
- 章节 plan §12:接线批 T1/T6-T10 与计划外 T11 实际已于 2026-07-19
  全部完成并部署生产(stacked PR #15-#25),checkbox 此前未同步

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zj1123581321 added a commit that referenced this pull request Oct 15, 2026
章节梗概 (1/9):统一 segments 带时间片段读取适配器(T3)
zj1123581321 added a commit that referenced this pull request Oct 15, 2026
- 260723-0214 HANDOFF 头部标注已被 260723-0246 取代(范围锁定 Stage 1)
- 章节 plan §12:接线批 T1/T6-T10 与计划外 T11 实际已于 2026-07-19
  全部完成并部署生产(stacked PR #15-#25),checkbox 此前未同步

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant