"""本地视频分析主入口:串联 扫描→时间戳回填→活动窗口→人物统计→摔倒检测→活动打标→缩略图 全流程。 手动/事后触发,独立于 automate.py 的下载流程,不影响现有下载逻辑。 流水线按 videos.status 状态机推进,重复运行是幂等的:每个阶段只处理"上一阶段 已完成、本阶段还没跑过"的视频,天然支持"每天下载完后跑一次,只处理当天新增"。 """ import argparse from analysis import activity_tagger, backfill_start_time, fall_detector, motion_filter, person_tracker, thumbnail, video_scanner from analysis.db import get_all_videos, update_video_status STAGES = ["scan", "backfill", "motion", "person", "fall", "activity", "thumbnail"] # 每个阶段完成后视频进入的状态,以及 --force 时应该回退到的"上一阶段完成"状态 _STAGE_STATUS = { "motion": ("pending", "motion_done"), "person": ("motion_done", "person_done"), "fall": ("person_done", "fall_done"), "activity": ("fall_done", "activity_done"), } def _force_reset(stage): """--force 时,把已经跑过该阶段的视频状态回退,让流水线重新处理一遍。""" if stage not in _STAGE_STATUS: return reset_to, done_status = _STAGE_STATUS[stage] for video in get_all_videos(status=done_status): update_video_status(video["id"], reset_to) def run_stage(stage, limit=None, force=False): if force: _force_reset(stage) if stage == "scan": result = video_scanner.scan_all() print(f"[scan] 处理 {result['processed']} 个文件,损坏 {result['corrupt']} 个,重复 {result['duplicates']} 个") elif stage == "backfill": result = backfill_start_time.process_all(limit=limit) print(f"[backfill] 处理 {result['total']} 条,成功回填精确时间 {result['updated']} 条,识别失败保留粗粒度 {result['fallback']} 条") elif stage == "motion": result = motion_filter.process_all(limit=limit) print(f"[motion] 处理 {result['processed']} 个视频,{result['no_motion']} 个无活动,失败 {result.get('failed', 0)} 个") elif stage == "person": result = person_tracker.process_all(limit=limit) print(f"[person] 处理 {result['processed']} 个视频,共 {result['total_tracks']} 段人物出现,失败 {result.get('failed', 0)} 个") elif stage == "fall": result = fall_detector.process_all(limit=limit) print(f"[fall] 处理 {result['processed']} 个视频,检测到 {result['total_falls']} 次疑似摔倒") elif stage == "activity": result = activity_tagger.process_all(limit=limit) print(f"[activity] 处理 {result['processed']} 个视频,标注 {result['total_segments']} 段活动,失败 {result.get('failed', 0)} 个") elif stage == "thumbnail": result = thumbnail.process_all(limit=limit) print(f"[thumbnail] 生成 {result['processed']} 张缩略图,失败 {result['failed']} 条") def main(): parser = argparse.ArgumentParser(description="本地视频分析主流程") parser.add_argument("--only-stage", choices=STAGES, help="只跑指定阶段,不跑完整流程") parser.add_argument("--force", action="store_true", help="强制重新跑该阶段(把已完成的视频状态回退)") parser.add_argument("--limit", type=int, default=None, help="限制本次处理的视频数量,用于小批量测试") args = parser.parse_args() stages = [args.only_stage] if args.only_stage else STAGES for stage in stages: run_stage(stage, limit=args.limit, force=args.force) if __name__ == "__main__": main()