> [!NOTE] > Status: Historical snapshot. Current refactor results and validated baseline are tracked in `CHANGELOG.md` (updated 2026-02-16). # VLM 代码改进清单 本文档记录对 Video Library Manager (VLM) 项目的代码审查发现的问题及对应解决方案。排除 AI/OpenAPI 相关问题。 --- ## 高优先级(确信度 ≥ 0.9) ### 1. 时间戳未统一使用 UTC **问题描述** 多处使用 `datetime.now()` 未指定 timezone,与项目约定「timestamps in UTC」不一致,可能导致: - 序列化为 ISO 时缺少 `+00:00` 后缀 - 多环境部署时依赖本地时区,行为不一致 **涉及文件** | 文件 | 行号 | |------|------| | `planner.py` | 51 | | `executor.py` | 89, 119, 471 | | `state.py` | 104, 139, 173 | | `quarantine.py` | 61, 529 | **确信度**: 0.95 **解决方案** 1. 在 `vlm/utils.py` 或现有模块中定义: ```python from datetime import datetime, timezone def utc_now() -> datetime: """Return current UTC time (timezone-aware).""" return datetime.now(timezone.utc) ``` 2. 全局替换所有 `datetime.now()` 为 `utc_now()` 或 `datetime.now(timezone.utc)` 3. 在 `load_plan`、`load_rollback_log` 等反序列化时,对 naive datetime 做 `replace(tzinfo=timezone.utc)` 以保持向后兼容 --- ### 2. 未使用的依赖 ffmpeg-python **问题描述** `pyproject.toml` 声明 `ffmpeg-python>=0.2.0`,但代码中未 import。scanner 使用 `subprocess` 直接调用 ffprobe。 **确信度**: 0.95 **解决方案** 从 `pyproject.toml` 的 dependencies 中移除 `ffmpeg-python`。若未来改用 ffmpeg-python 库再添加。 --- ### 3. Parser 中 video extensions 硬编码 **问题描述** `parser.py` 第 100、169 行使用固定扩展名列表 `['.mp4', '.mkv', ...]`,与 `config.video_extensions` 不一致。 - 用户在 config 中新增扩展(如 `.ts`),scan 能发现,但 parse 去扩展名时不会匹配 - 如 `Movie (2020).ts` 可能得到错误的 title 解析 **确信度**: 0.9 **解决方案** 1. `parse_movie` / `parse_series` 增加可选参数 `extensions: list[str]` 2. CLI parse 命令调用时传入 `config.video_extensions` 3. 默认值使用与 config 相同的列表以保持向后兼容 --- ### 4. 抽出统一的 I/O 层 **问题描述** 数据读取和转换分散在各 CLI 命令中,同一份 identities JSON 在 analyze、plan 等处有重复且略有不同的转换逻辑。新增字段时需多处同步,易遗漏。 **确信度**: 0.9 **解决方案** 新增 `vlm/io.py`,集中: - `load_inventory_csv(path) -> list[VideoFile]` - `save_inventory_csv(files, path, library_root)` - `load_identities_json(path) -> dict` - `save_identities_json(data, path)` - `identities_to_plan_input(data) -> list[(VideoFile, Identity | None)]` - `identities_to_analysis_input(data) -> (list[MovieIdentity], list[SeriesIdentity], list[VideoFile])` CLI 只调用这些函数,不再直接解析和构造 dataclass。 --- ### 5. Analyze 阶段 VideoFile metadata 丢失 **问题描述** `identities.json` 不含 `size_bytes`、`resolution`、`codec` 等,CLI 构造 `VideoFile` 时用 0 或 None,导致 `compare_quality()` 无法有效比较,duplicate 报告信息不足。 **确信度**: 0.9 **解决方案** 1. **方案 A**:analyze 命令同时接受 `--inventory`,从 inventory.csv 加载 metadata 并与 identities 按 path 合并 2. **方案 B**:parse 输出时在 identities 中附带 size/resolution/codec(从 inventory 合并),避免 analyze 再读 inventory --- ## 中优先级(确信度 0.8–0.89) ### 6. 拆分 CLI 为 commands 子模块 **问题描述** `cli.py` 约 1700 行,混合参数定义、业务逻辑、I/O、输出展示,维护和单测困难。 **确信度**: 0.85 **解决方案** ``` src/vlm/ cli.py # main、参数、ctx 传递、调用 commands commands/ __init__.py scan.py # scan_cmd(ctx, ...) parse.py # parse_cmd(ctx, ...) enrich.py analyze.py plan.py execute.py report.py # 或按子命令拆分 quarantine.py state.py config_cmd.py ``` 每个 `*_cmd` 接收 `ctx` 和参数,CLI 只做装饰与调用。单测可直接测 `*_cmd` 函数。 --- ### 7. Provider last_request_count 非正式接口 **问题描述** `enrichment.py` 使用 `getattr(provider, "last_request_count", 1)` 统计 API 调用,依赖实现细节。 **确信度**: 0.85 **解决方案** 1. 在 `providers/base.py` 的 `EnrichmentProvider` 协议中显式声明 `last_request_count: int` 属性 2. TMDBProvider 确保实现该属性 3. enrichment 通过协议访问,去掉 `getattr` --- ### 8. 异常捕获过宽 **问题描述** 约 20+ 处 `except Exception`,容易吞掉逻辑错误,难以区分可恢复错误与编程错误。 **涉及**:`cli.py`、`enrichment.py`、`executor.py`、`quarantine.py` 等。 **确信度**: 0.8 **解决方案** - 针对预期异常(`FileNotFoundError`、`json.JSONDecodeError`、`ValueError`)分别处理 - 保留顶层 `except Exception` 作为兜底,记录完整 traceback 后 `sys.exit(1)` - 避免在业务逻辑深处宽泛捕获 --- ### 9. Enrichment 主循环过长 **问题描述** `enrich_identities_data` 主循环约 80 行,混合迭代、缓存、API 调用、统计、payload 合并,可读性和可测性差。 **确信度**: 0.8 **解决方案** 拆分为: - `_process_single_record(record, media_type, ...) -> None` - `_fetch_from_providers(record, media_type, providers, ...) -> tuple[dict, int, list, str]` - `_update_stats(stats, ...) -> None` - 主循环只负责迭代与调用上述函数 --- ## 较低优先级(确信度 0.7–0.79) ### 10. Duplicate 检测 file_map 使用 filename 作为 key **问题描述** `analysis.py` 第 87 行: ```python file_map = {file.filename: file for file in files} ``` 同 filename 不同路径会互相覆盖(如 `/a/Movie.mkv` 与 `/b/Movie.mkv`),导致 identity 映射到错误 VideoFile。 **确信度**: 0.75 **解决方案** - 使用 `str(file.path)` 作为 key - 确保 identity 与 VideoFile 的关联方式一致(如通过 path 或 (path, filename) 建立映射) --- ### 11. MovieIdentity / SeriesIdentity 字段重复 **问题描述** 两个 dataclass 有约 10 个共同 enrichment 字段,新增时需改两处,合并逻辑需分支处理。 **确信度**: 0.7 **解决方案** - **方案 A**:抽取 `EnrichmentMixin` 基类,`MovieIdentity` / `SeriesIdentity` 继承 - **方案 B(推荐)**:引入 `EnrichmentPayload` dataclass,两个 Identity 通过 `enrichment: EnrichmentPayload` 组合,侵入较小 --- ### 12. Config 体积膨胀 **问题描述** `Config` 约 50 个字段,enrichment/TMDB 相关占多数,职责混杂。 **确信度**: 0.7 **解决方案** 拆分 `EnrichmentConfig`、`TMDBConfig` 等子配置,通过嵌套或组合放入主 Config。 --- ### 13. review_status 与 needs_review 语义重叠 **问题描述** `review_status`(pending/approved/rejected)与 `needs_review`(bool)含义重叠,易混淆。 **确信度**: 0.75 **解决方案** - 在 docstring 或文档中明确定义:`needs_review = (review_status == 'pending') and ...` - 或在模型中合并为单一状态枚举,避免两个字段语义交叉 --- ## 低优先级(确信度 < 0.7) ### 14. config_init 与 ctx 一致性 **问题描述** `config init` 未使用 `@pass_context`,与同组其他命令风格不一致,但当前不依赖 ctx,非功能性 bug。 **确信度**: 0.5 **解决方案** 若其他 config 子命令均用 `@pass_context`,可统一为 `config_init` 也接收 ctx 以保持风格一致;否则可保持现状。 --- ### 15. CLI 延迟 import **问题描述** 各命令在函数体内才 `import`,错误在首次执行该命令时才暴露,依赖关系不直观。 **确信度**: 0.6 **解决方案** 可接受;若希望启动时即发现依赖问题,可改为模块级 import,但会增加启动开销。 --- ## 执行建议 | 阶段 | 项目 | 说明 | |------|------|------| | 第一批 | 1, 2, 3 | 改动小、风险低、收益明确 | | 第二批 | 4, 5 | 需要一定重构,与 I/O 设计相关 | | 第三批 | 6, 7, 8, 9 | 结构性改进,建议分步完成 | | 第四批 | 10–15 | 按需和档期安排 | --- *文档生成日期:2025-02-10*