2026-02-16 12:31:26 +08:00
|
|
|
|
> [!NOTE]
|
2026-04-07 11:07:01 +08:00
|
|
|
|
> Status: Historical snapshot. Current refactor results and validated baseline are tracked in `CHANGELOG.md` (updated 2026-04-07).
|
2026-02-16 12:31:26 +08:00
|
|
|
|
|
2026-02-10 16:56:17 +08:00
|
|
|
|
# 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*
|