feat: 新增视频分析流水线、Web界面、孤儿清理工具;修复健壮性问题
- 新增 analysis/ 模块:MOG2活动检测、YOLO人物跟踪、MediaPipe摔倒检测、Ollama活动打标、缩略图抽取 - 新增 webapp/:Flask事件浏览界面,按需HEVC→H264转码缓存 - 新增 analyze.py:7阶段状态机流水线(scan→backfill→motion→person→fall→activity→thumbnail) - 新增 cleanup_orphans.py:清理文件已删除的数据库孤儿记录 - 修复:motion/person/activity三阶段加单视频异常兜底,避免批次中断 - 修复:ollama_client加健康检查和3次重试,num_ctx从8192降到2048 - 修复:transcode_cache加fcntl文件锁,防止并发转码同一视频 - 修复:SQLite开WAL模式+busy_timeout,支持读写并发 - 修复:sync.py ffmpeg路径改为shutil.which查找,删除automate.py死代码PYTHON - 修复:thumbnail失败加3次重试上限,避免无限重试损坏文件 - 新增:requirements.lock锁定版本、README文档 - 更新:.gitignore排除.playwright-mcp、截图、备份文件
This commit is contained in:
+86
-21
@@ -7,8 +7,6 @@ import subprocess, time, os, shutil, struct, zlib
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
PYTHON = "/usr/local/bin/python3.12"
|
||||
|
||||
ADB = os.path.expanduser("~/Library/Android/sdk/platform-tools/adb")
|
||||
DEVICE = "emulator-5554"
|
||||
OUTPUT_DIR = Path("./recordings")
|
||||
@@ -435,8 +433,9 @@ def tap_and_check_date_changed(arrow_coord, timeout=12, interval=1.0):
|
||||
return False
|
||||
|
||||
|
||||
def navigate_to_yesterday_batch():
|
||||
"""导航到昨天的 SD 卡批量下载模式。
|
||||
def navigate_to_yesterday_batch(days_back=1):
|
||||
"""导航到"今天往前第 days_back 天"的 SD 卡批量下载模式。
|
||||
days_back=0 是今天,1 是昨天,2 是前天……(默认 1 保持原行为)。
|
||||
关键节点用 tap_text 通过 UI dump 定位,避免坐标错位时误触首页底部
|
||||
Tab(萤石/对话/商城/社区/我的,"商城"中心 x=540 跟录像 Tab 撞车)。
|
||||
"""
|
||||
@@ -485,11 +484,12 @@ def navigate_to_yesterday_batch():
|
||||
else:
|
||||
print(" ⚠ 连续 8 次翻页仍在变化,按当前位置继续")
|
||||
|
||||
print("→ 从今日退一天到昨天")
|
||||
if not tap_and_check_date_changed(COORD["prev_day_arrow"]):
|
||||
print(" ! 首次未检测到日期变化,重试一次")
|
||||
print(f"→ 从今日往前退 {days_back} 天")
|
||||
for step in range(days_back):
|
||||
if not tap_and_check_date_changed(COORD["prev_day_arrow"]):
|
||||
raise RuntimeError("点击'上一天'箭头后日期未变化,无法切换到昨天")
|
||||
print(f" ! 第 {step+1} 次点'上一天'未检测到日期变化,重试一次")
|
||||
if not tap_and_check_date_changed(COORD["prev_day_arrow"]):
|
||||
raise RuntimeError(f"点击'上一天'箭头后日期未变化,无法退到第 {days_back} 天")
|
||||
# 等日期切换后的录像列表稳定加载
|
||||
time.sleep(3)
|
||||
|
||||
@@ -777,15 +777,24 @@ def tap_all_thumbnails(baseline, max_rounds=300, poll_interval=4, max_wait_per_r
|
||||
|
||||
def _median(vals):
|
||||
return sorted(vals)[len(vals) // 2] if vals else -1
|
||||
scrolled = not (pre_rows and post_rows
|
||||
and abs(_median(pre_rows) - _median(post_rows)) < 40)
|
||||
def _moved(a, b):
|
||||
return bool(a and b and abs(_median(a) - _median(b)) >= 40)
|
||||
|
||||
if not scrolled:
|
||||
# 到底了(滚不动)。此时当前屏可能还有被"每轮只处理前 3 行"限制漏下的
|
||||
# 底部未下载卡片(它们本该靠继续滚动移到前 3 行处理,但到底滚不动了)。
|
||||
# 清扫:对当前屏【所有】检出行判打勾,分批(每批≤9 不超并发)点未下载的、
|
||||
if not _moved(pre_rows, post_rows):
|
||||
# 疑似到底,但一次没滚动也可能是偶发滚动手势失效 / 下载中大文件卡顿吸收了
|
||||
# 手势(实测 26、27 号就因单次误判、提前结束、漏下几百个)。再滚一次确认:
|
||||
# 只有连续两次都没推进才判真到底。
|
||||
time.sleep(2)
|
||||
adb("shell", "input", "swipe", "540", "1820", "540", "1414", "400")
|
||||
time.sleep(1.5)
|
||||
post2_rows = detect_card_rows(screenshot("/tmp/_postscroll2.png"))
|
||||
if _moved(pre_rows, post2_rows):
|
||||
print(f" 轮 {r+1}: 首次滚动疑似卡顿未生效,二次滚动已推进,继续")
|
||||
continue
|
||||
# 连续两次滚动行位置都未变,确认到底。此时当前屏可能还有被"每轮只处理前
|
||||
# 3 行"限制漏下的底部未下载卡片,清扫:对当前屏所有检出行分批点未下载的、
|
||||
# 等打勾,直到当前屏没有未下载卡片,再结束循环。
|
||||
print(f" 轮 {r+1}: 滚动后行位置未变,已到达列表底部,清扫当前屏剩余未下载卡片")
|
||||
print(f" 轮 {r+1}: 连续两次滚动都未推进,确认到达列表底部,清扫当前屏剩余")
|
||||
for sweep in range(6): # 最多 6 批,防意外死循环
|
||||
ensure_not_anr()
|
||||
time.sleep(THUMB_LOAD_WAIT)
|
||||
@@ -860,11 +869,14 @@ def wait_for_downloads(baseline_files, timeout=600):
|
||||
return list(all_files - baseline_files)
|
||||
|
||||
|
||||
def pull_files(remote_files):
|
||||
"""把下载好的文件 adb pull 到本地"""
|
||||
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y%m%d")
|
||||
# 本地目录按录像日期(昨天)命名,方便归档
|
||||
local_dir = OUTPUT_DIR / "BH5455783" / yesterday
|
||||
def pull_files(remote_files, date_str=None):
|
||||
"""把下载好的文件 adb pull 到本地。
|
||||
date_str 为录像日期(YYYYMMDD),归档到 recordings/BH5455783/{date_str}/。
|
||||
不传则默认昨天(保持原行为)。"""
|
||||
if date_str is None:
|
||||
date_str = (datetime.now() - timedelta(days=1)).strftime("%Y%m%d")
|
||||
# 本地目录按录像日期命名,方便归档
|
||||
local_dir = OUTPUT_DIR / "BH5455783" / date_str
|
||||
local_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(f"→ 拉取 {len(remote_files)} 个文件到 {local_dir}")
|
||||
@@ -922,5 +934,58 @@ def sync():
|
||||
print("===== 同步完成 =====")
|
||||
|
||||
|
||||
def clear_device_recordings():
|
||||
"""清空模拟器上萤石下载的录像文件 + 相册里的录像副本,释放存储空间。
|
||||
只删录像(*_BH* 下载文件、YS 相册 *.mp4),不碰 App 的日志/播放器资源等其它文件。
|
||||
多天连续下载时必须每天清,否则录像(含相册双份)会很快撑满模拟器 10G 存储。"""
|
||||
today = datetime.now().strftime("%Y%m%d")
|
||||
remote_dir = f"/sdcard/Android/data/{PKG}/files/{today}"
|
||||
adb("shell", f"find {remote_dir} -name '*_BH*' -type f -delete")
|
||||
adb("shell", "find /sdcard/DCIM/YS -iname '*.mp4' -type f -delete")
|
||||
|
||||
|
||||
def sync_recent_days(n_days=7):
|
||||
"""下载最近 n_days 天的录像(今天、昨天……往前 n_days-1 天),按录像日期归档。
|
||||
|
||||
每天独立重启 App 导航(比"退出批量下载页再切日期"稳妥)。关键:每天下载【前】
|
||||
先清空模拟器上的录像+相册,让这一天从零开始——这样:①模拟器不会被多天累积撑满
|
||||
(10G 存储,含相册双份,几天就满);②下完后设备上就是这一天的全部录像,直接
|
||||
全量拉取到 recordings/{录像日期}/,不用再靠前后差集区分。拉完再清一次即时释放。
|
||||
某天若无录像(点下载弹不出菜单)则跳过。
|
||||
"""
|
||||
print(f"===== 开始下载最近 {n_days} 天录像 =====")
|
||||
for d in range(n_days):
|
||||
rec_date = (datetime.now() - timedelta(days=d)).strftime("%Y%m%d")
|
||||
print(f"\n===== [{d+1}/{n_days}] 录像日期 {rec_date}(今天往前第 {d} 天)=====")
|
||||
try:
|
||||
# 清空本地该天旧目录:重下的文件名是新的下载时刻,和旧文件名不同,
|
||||
# 不清会产生"内容相同、文件名不同"的重复副本。SD 卡是真源,重下可恢复。
|
||||
day_dir = OUTPUT_DIR / "BH5455783" / rec_date
|
||||
if day_dir.exists():
|
||||
shutil.rmtree(day_dir)
|
||||
print(f" 已清空本地旧目录 {day_dir}(覆盖式重下)")
|
||||
launch_app()
|
||||
clear_device_recordings() # 下载前清空,保证有空间、且设备只剩这天的
|
||||
navigate_to_yesterday_batch(days_back=d)
|
||||
tap_all_thumbnails(set())
|
||||
wait_for_downloads(set())
|
||||
day_files = get_all_remote_files() # 这一天下载的全部录像
|
||||
if day_files:
|
||||
pull_files(list(day_files), date_str=rec_date)
|
||||
else:
|
||||
print(f"→ {rec_date} 没有下载到文件")
|
||||
clear_device_recordings() # 拉完即时清空,释放空间给下一天
|
||||
except Exception as e:
|
||||
print(f"⚠ {rec_date} 处理失败,跳过:{e}")
|
||||
continue
|
||||
print(f"\n===== 最近 {n_days} 天下载完成 =====")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sync()
|
||||
import sys
|
||||
# 用法:python automate.py → 只下昨天(原行为)
|
||||
# python automate.py 7 → 下最近 7 天
|
||||
if len(sys.argv) > 1:
|
||||
sync_recent_days(int(sys.argv[1]))
|
||||
else:
|
||||
sync()
|
||||
|
||||
Reference in New Issue
Block a user