"""清理数据库中文件已不存在的孤儿记录。 video_scanner 入库后,如果录像文件被手动删除或被 dedup_cleanup 清理, 数据库里的 videos/events 记录不会自动消失,导致: - analyze.py 对不存在的文件跑分析,全部失败或产生空结果 - webapp 点击播放 404 - 统计数据虚高 本脚本扫描 videos 表,删除 file_path 不存在的记录及其关联 events, 并同步清理孤儿缩略图文件。执行前请确保已备份 analysis.db。 """ import os import sqlite3 from analysis.config import PROJECT_ROOT, THUMBNAILS_DIR, DB_PATH def cleanup_orphans(dry_run=True): conn = sqlite3.connect(str(DB_PATH)) conn.row_factory = sqlite3.Row videos = conn.execute("SELECT id, file_path, status FROM videos").fetchall() to_delete = [] for v in videos: full_path = PROJECT_ROOT / v["file_path"] if not full_path.exists(): to_delete.append(v) print(f"videos 总数: {len(videos)}") print(f"文件不存在的孤儿记录: {len(to_delete)}") # 按状态统计 by_status = {} for v in to_delete: by_status[v["status"]] = by_status.get(v["status"], 0) + 1 for status, cnt in sorted(by_status.items(), key=lambda x: -x[1]): print(f" {status}: {cnt}") if dry_run: print("\n[dry-run] 未执行任何删除。加 --execute 实际执行。") conn.close() return # 删除关联 events 和缩略图 deleted_events = 0 deleted_thumbs = 0 for v in to_delete: events = conn.execute( "SELECT id, thumbnail_path FROM events WHERE video_id = ?", (v["id"],) ).fetchall() for e in events: if e["thumbnail_path"]: thumb_path = PROJECT_ROOT / e["thumbnail_path"] if thumb_path.exists(): thumb_path.unlink() deleted_thumbs += 1 conn.execute("DELETE FROM events WHERE video_id = ?", (v["id"],)) deleted_events += len(events) # 删除 video 记录 video_ids = [v["id"] for v in to_delete] if video_ids: placeholders = ",".join("?" * len(video_ids)) conn.execute(f"DELETE FROM videos WHERE id IN ({placeholders})", video_ids) conn.commit() # 清理孤儿缩略图(events 已删但文件还在的) event_thumbs = set( r[0] for r in conn.execute("SELECT thumbnail_path FROM events WHERE thumbnail_path IS NOT NULL") ) orphan_thumbs = 0 if THUMBNAILS_DIR.exists(): for f in THUMBNAILS_DIR.glob("*.jpg"): rel = str(f.relative_to(PROJECT_ROOT)) if rel not in event_thumbs: f.unlink() orphan_thumbs += 1 conn.close() print(f"\n已删除 videos 记录: {len(to_delete)}") print(f"已删除 events 记录: {deleted_events}") print(f"已清理缩略图文件: {deleted_thumbs + orphan_thumbs}") if __name__ == "__main__": import sys dry_run = "--execute" not in sys.argv cleanup_orphans(dry_run=dry_run)