fix: 用Pillow替代ImageMagick,修复批量下载漏下载的4个逻辑漏洞
漏下载根因: 1. ImageMagick(magick)未安装,detect_card_rows/std_profile/count_blue_pixels 全部依赖magick命令,命令不存在时抛FileNotFoundError,sync_recent_days 捕获异常后直接跳过当天 → 整天漏下载 2. detect_card_rows两次返回空就误判'全部已打勾'直接滚动 → 漏掉当前屏所有卡片 3. 到底清扫时检测不到行就误判'清扫完成'提前结束 → 漏掉底部卡片 4. 点击超时未打勾的卡片不重试 → 点击落在间隙/标签上的卡片永远漏下载 5. sync_recent_days某天异常直接跳过不重试 → 偶发ANR导致整天漏下载 修复: - 全部magick调用(sample_pixel_rgb/count_blue_pixels/std_profile/date_label_hash/ screenshot_hash)替换为Pillow实现,消除系统级外部依赖 - detect_card_rows返回空时重试3次(逐次加长等待),仍为空则跳过本轮不盲点 - 到底清扫时检测不到行重试3次,仍为空才判底部无内容 - 点击超时未打勾的卡片重试点击一次,再等一轮 - sync_recent_days每天最多重试1次(共2次尝试)
This commit is contained in:
+127
-93
@@ -3,10 +3,12 @@
|
|||||||
萤石录像自动下载脚本
|
萤石录像自动下载脚本
|
||||||
通过 ADB 自动化模拟器中的萤石 App,下载前一天的所有报警录像。
|
通过 ADB 自动化模拟器中的萤石 App,下载前一天的所有报警录像。
|
||||||
"""
|
"""
|
||||||
import subprocess, time, os, shutil, struct, zlib
|
import subprocess, time, os, shutil, struct, zlib, io, hashlib, math
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
ADB = os.path.expanduser("~/Library/Android/sdk/platform-tools/adb")
|
ADB = os.path.expanduser("~/Library/Android/sdk/platform-tools/adb")
|
||||||
DEVICE = "emulator-5554"
|
DEVICE = "emulator-5554"
|
||||||
OUTPUT_DIR = Path("./recordings")
|
OUTPUT_DIR = Path("./recordings")
|
||||||
@@ -53,7 +55,7 @@ BLUE_THRESHOLD = 500 # 窗口内强蓝像素数 >= 此值判为已打勾(打
|
|||||||
# 灰占位是纯色 std≈0,据此切出"纹理带",每个带的竖直中心就是一行卡片的点击中心。
|
# 灰占位是纯色 std≈0,据此切出"纹理带",每个带的竖直中心就是一行卡片的点击中心。
|
||||||
ROW_Y0, ROW_Y1 = 1000, 2300 # 列表纵向扫描范围(避开顶部状态栏/预览/标题栏、底部手势条)
|
ROW_Y0, ROW_Y1 = 1000, 2300 # 列表纵向扫描范围(避开顶部状态栏/预览/标题栏、底部手势条)
|
||||||
ROW_STD_X0, ROW_STD_W = 350, 700 # 横向 std 采样区(避开左右圆角/阴影)
|
ROW_STD_X0, ROW_STD_W = 350, 700 # 横向 std 采样区(避开左右圆角/阴影)
|
||||||
ROW_STD_COLS = 175 # 降采样列数(一次 magick 折成每行 175 个样本算 std,避免逐行上百次 magick)
|
ROW_STD_COLS = 175 # 降采样列数(Pillow 一次折成每行 175 个样本算 std,避免逐行上百次采样)
|
||||||
ROW_STD_LOW = 28 # std 阈值:>= 判为卡片纹理,< 判为间隙/标签/占位(实测卡片≥46、非卡片=0,余量足)
|
ROW_STD_LOW = 28 # std 阈值:>= 判为卡片纹理,< 判为间隙/标签/占位(实测卡片≥46、非卡片=0,余量足)
|
||||||
ROW_MIN_H = 90 # 纹理带最小高度(滤掉分组标签、半截卡片、噪声)
|
ROW_MIN_H = 90 # 纹理带最小高度(滤掉分组标签、半截卡片、噪声)
|
||||||
ROW_MAX_H = 260 # 纹理带最大高度(超过视为两行粘连,按行距二次切分)
|
ROW_MAX_H = 260 # 纹理带最大高度(超过视为两行粘连,按行距二次切分)
|
||||||
@@ -204,18 +206,10 @@ def tap_safe(x, y, expect_activity_keyword, hint="", delay=1.5):
|
|||||||
|
|
||||||
|
|
||||||
def sample_pixel_rgb(x, y, png_path="/tmp/_pixchk.png"):
|
def sample_pixel_rgb(x, y, png_path="/tmp/_pixchk.png"):
|
||||||
"""截图并采样指定坐标 RGB。需要系统装了 ImageMagick (`magick`)。"""
|
"""截图并采样指定坐标 RGB。用 Pillow 实现,不依赖系统 ImageMagick。"""
|
||||||
import re
|
|
||||||
adb("shell", "screencap", "-p", "/sdcard/_pixchk.png")
|
adb("shell", "screencap", "-p", "/sdcard/_pixchk.png")
|
||||||
adb("pull", "/sdcard/_pixchk.png", png_path)
|
adb("pull", "/sdcard/_pixchk.png", png_path)
|
||||||
out = subprocess.run(
|
return sample_rgb_from_file(png_path, x, y)
|
||||||
["magick", png_path, "-format", "%[pixel:p{" + str(x) + "," + str(y) + "}]", "info:"],
|
|
||||||
capture_output=True, text=True,
|
|
||||||
)
|
|
||||||
m = re.match(r"srgba?\((\d+),(\d+),(\d+)", out.stdout.strip())
|
|
||||||
if not m:
|
|
||||||
return (0, 0, 0)
|
|
||||||
return tuple(map(int, m.groups()))
|
|
||||||
|
|
||||||
|
|
||||||
def screenshot(path="/tmp/ezviz_screen.png"):
|
def screenshot(path="/tmp/ezviz_screen.png"):
|
||||||
@@ -228,35 +222,35 @@ def screenshot(path="/tmp/ezviz_screen.png"):
|
|||||||
|
|
||||||
|
|
||||||
def sample_rgb_from_file(png_path, x, y):
|
def sample_rgb_from_file(png_path, x, y):
|
||||||
"""从已经拉取到本地的截图文件采样指定坐标 RGB(不重新截图,供批量取点复用)。"""
|
"""从已经拉取到本地的截图文件采样指定坐标 RGB(不重新截图,供批量取点复用)。
|
||||||
import re
|
用 Pillow 实现,不依赖系统 ImageMagick。"""
|
||||||
out = subprocess.run(
|
try:
|
||||||
["magick", png_path, "-format", "%[pixel:p{" + str(x) + "," + str(y) + "}]", "info:"],
|
with Image.open(png_path) as img:
|
||||||
capture_output=True, text=True,
|
img = img.convert("RGB")
|
||||||
)
|
r, g, b = img.getpixel((x, y))
|
||||||
m = re.match(r"srgba?\((\d+),(\d+),(\d+)", out.stdout.strip())
|
return (r, g, b)
|
||||||
if not m:
|
except Exception:
|
||||||
return (0, 0, 0)
|
return (0, 0, 0)
|
||||||
return tuple(map(int, m.groups()))
|
|
||||||
|
|
||||||
|
|
||||||
def count_blue_pixels(png_path, cx, cy):
|
def count_blue_pixels(png_path, cx, cy):
|
||||||
"""统计以 (cx,cy) 为中心、大小 BLUE_WIN 的窗口内"强蓝"像素数量。
|
"""统计以 (cx,cy) 为中心、大小 BLUE_WIN 的窗口内"强蓝"像素数量。
|
||||||
强蓝判据:b>150 且 b-r>80(蓝勾角标的高饱和蓝,灰/黑/彩色画面几乎不出现)。
|
强蓝判据:b>150 且 b-r>80(蓝勾角标的高饱和蓝,灰/黑/彩色画面几乎不出现)。
|
||||||
用窗口统计而非单点采样,是为了吸收卡片垂直位置的漂移(见 BADGE_OFFSET 说明)。"""
|
用窗口统计而非单点采样,是为了吸收卡片垂直位置的漂移(见 BADGE_OFFSET 说明)。
|
||||||
import re
|
用 Pillow 实现,不依赖系统 ImageMagick。"""
|
||||||
w, h = BLUE_WIN
|
w, h = BLUE_WIN
|
||||||
x0, y0 = cx - w // 2, cy - h // 2
|
x0, y0 = cx - w // 2, cy - h // 2
|
||||||
out = subprocess.run(
|
try:
|
||||||
["magick", png_path, "-crop", f"{w}x{h}+{x0}+{y0}", "txt:-"],
|
with Image.open(png_path) as img:
|
||||||
capture_output=True, text=True,
|
img = img.convert("RGB")
|
||||||
)
|
crop = img.crop((x0, y0, x0 + w, y0 + h))
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for m in re.finditer(r"srgba?\((\d+),(\d+),(\d+)", out.stdout):
|
for r, g, b in crop.getdata():
|
||||||
r, g, b = map(int, m.groups())
|
if b > 150 and b - r > 80:
|
||||||
if b > 150 and b - r > 80:
|
cnt += 1
|
||||||
cnt += 1
|
return cnt
|
||||||
return cnt
|
except Exception:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def get_checked_cells(png_path):
|
def get_checked_cells(png_path):
|
||||||
@@ -277,29 +271,27 @@ def std_profile(png_path):
|
|||||||
卡片间水平间隙、整点分组标签、灰色占位图都是纯色 → std≈0。据此可把"卡片行"
|
卡片间水平间隙、整点分组标签、灰色占位图都是纯色 → std≈0。据此可把"卡片行"
|
||||||
从"间隙/标签/占位"里分出来。
|
从"间隙/标签/占位"里分出来。
|
||||||
|
|
||||||
性能:不逐行调 magick(一屏上百次太慢),而是用一次 magick 把 ROW_STD_W 宽的
|
性能:不逐行采样(一屏上百次太慢),而是用 Pillow 把 ROW_STD_W 宽的采样区灰度图
|
||||||
采样区灰度图横向降采样到 ROW_STD_COLS 列(每列是原若干像素的平均),导出像素文本,
|
横向降采样到 ROW_STD_COLS 列(每列是原若干像素的平均),再在 Python 里按行算这
|
||||||
再在 Python 里按行算这 ROW_STD_COLS 个样本的标准差。实测降采样法的低谷/高峰位置
|
ROW_STD_COLS 个样本的标准差。用 Pillow 实现,不依赖系统 ImageMagick。"""
|
||||||
与逐行 %[fx:standard_deviation] 完全一致,且对比度更大(间隙 0 vs 卡片 60+)。"""
|
|
||||||
import re, math
|
|
||||||
h = ROW_Y1 - ROW_Y0
|
h = ROW_Y1 - ROW_Y0
|
||||||
out = subprocess.run(
|
try:
|
||||||
["magick", png_path, "-crop", f"{ROW_STD_W}x{h}+{ROW_STD_X0}+{ROW_Y0}", "+repage",
|
with Image.open(png_path) as img:
|
||||||
"-colorspace", "Gray", "-resize", f"{ROW_STD_COLS}x{h}!", "txt:-"],
|
img = img.convert("L")
|
||||||
capture_output=True, text=True,
|
crop = img.crop((ROW_STD_X0, ROW_Y0, ROW_STD_X0 + ROW_STD_W, ROW_Y0 + h))
|
||||||
)
|
small = crop.resize((ROW_STD_COLS, h), Image.Resampling.NEAREST)
|
||||||
rows = {}
|
pixels = list(small.getdata())
|
||||||
# txt 行形如 "x,y: (gray,alpha) ...",灰度值取第一个数
|
prof = []
|
||||||
for m in re.finditer(r"^\d+,(\d+): \((\d+)", out.stdout, re.M):
|
for rr in range(h):
|
||||||
rr, val = int(m.group(1)), int(m.group(2))
|
row_vals = pixels[rr * ROW_STD_COLS:(rr + 1) * ROW_STD_COLS]
|
||||||
rows.setdefault(rr, []).append(val)
|
if not row_vals:
|
||||||
prof = []
|
continue
|
||||||
for rr in sorted(rows):
|
mean = sum(row_vals) / len(row_vals)
|
||||||
vals = rows[rr]
|
var = sum((v - mean) ** 2 for v in row_vals) / len(row_vals)
|
||||||
mean = sum(vals) / len(vals)
|
prof.append((ROW_Y0 + rr, math.sqrt(var)))
|
||||||
var = sum((v - mean) ** 2 for v in vals) / len(vals)
|
return prof
|
||||||
prof.append((ROW_Y0 + rr, math.sqrt(var)))
|
except Exception:
|
||||||
return prof
|
return []
|
||||||
|
|
||||||
|
|
||||||
def detect_card_rows(png_path):
|
def detect_card_rows(png_path):
|
||||||
@@ -412,11 +404,11 @@ def date_label_hash(png_path="/tmp/_datechk.png"):
|
|||||||
import hashlib
|
import hashlib
|
||||||
adb("shell", "screencap", "-p", "/sdcard/_datechk.png")
|
adb("shell", "screencap", "-p", "/sdcard/_datechk.png")
|
||||||
adb("pull", "/sdcard/_datechk.png", png_path)
|
adb("pull", "/sdcard/_datechk.png", png_path)
|
||||||
crop_path = png_path.replace(".png", "_crop.png")
|
with Image.open(png_path) as img:
|
||||||
subprocess.run(["magick", png_path, "-crop", "400x90+40+1080", crop_path],
|
crop = img.crop((40, 1080, 440, 1170)) # 400x90+40+1080
|
||||||
capture_output=True)
|
buf = io.BytesIO()
|
||||||
with open(crop_path, "rb") as f:
|
crop.save(buf, format="PNG")
|
||||||
return hashlib.md5(f.read()).hexdigest()
|
return hashlib.md5(buf.getvalue()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def tap_and_check_date_changed(arrow_coord, timeout=12, interval=1.0):
|
def tap_and_check_date_changed(arrow_coord, timeout=12, interval=1.0):
|
||||||
@@ -523,12 +515,11 @@ def screenshot_hash(png_path="/tmp/_scrollchk.png"):
|
|||||||
import hashlib
|
import hashlib
|
||||||
adb("shell", "screencap", "-p", "/sdcard/_scrollchk.png")
|
adb("shell", "screencap", "-p", "/sdcard/_scrollchk.png")
|
||||||
adb("pull", "/sdcard/_scrollchk.png", png_path)
|
adb("pull", "/sdcard/_scrollchk.png", png_path)
|
||||||
crop_path = png_path.replace(".png", "_crop.png")
|
with Image.open(png_path) as img:
|
||||||
# 缩略图网格区域:y=1000 到屏幕底部,避开上方标题/预览/状态栏(Pixel 6 模拟器 1080x2400)
|
crop = img.crop((0, 1000, 1080, 2400)) # 1080x1400+0+1000
|
||||||
subprocess.run(["magick", png_path, "-crop", "1080x1400+0+1000", crop_path],
|
buf = io.BytesIO()
|
||||||
capture_output=True)
|
crop.save(buf, format="PNG")
|
||||||
with open(crop_path, "rb") as f:
|
return hashlib.md5(buf.getvalue()).hexdigest()
|
||||||
return hashlib.md5(f.read()).hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
def dismiss_dialog_if_any():
|
def dismiss_dialog_if_any():
|
||||||
@@ -699,11 +690,18 @@ def tap_all_thumbnails(baseline, max_rounds=300, poll_interval=4, max_wait_per_r
|
|||||||
# 的总根源),再对每行 × 固定 3 列逐格判打勾,只点还没下载完的。
|
# 的总根源),再对每行 × 固定 3 列逐格判打勾,只点还没下载完的。
|
||||||
grid_shot = screenshot("/tmp/_gridchk.png")
|
grid_shot = screenshot("/tmp/_gridchk.png")
|
||||||
full_rows = detect_card_rows(grid_shot)
|
full_rows = detect_card_rows(grid_shot)
|
||||||
if not full_rows:
|
# 检测不到行时重试最多3次:缩略图加载慢、ANR刚恢复、画面切换中都会导致
|
||||||
# 偶发一张都没检出(仍在加载/异常画面),重拍一次
|
# 临时检测不到行。之前只重试1次就放弃,会误判"全部已打勾"直接滚动,
|
||||||
time.sleep(1.5)
|
# 漏掉当前屏所有卡片(这是漏下载的重要原因之一)。
|
||||||
|
retry = 0
|
||||||
|
while not full_rows and retry < 3:
|
||||||
|
retry += 1
|
||||||
|
time.sleep(2.0 + retry * 1.0) # 逐次加长等待,给缩略图加载留时间
|
||||||
grid_shot = screenshot("/tmp/_gridchk.png")
|
grid_shot = screenshot("/tmp/_gridchk.png")
|
||||||
full_rows = detect_card_rows(grid_shot)
|
full_rows = detect_card_rows(grid_shot)
|
||||||
|
if not full_rows:
|
||||||
|
print(f" 轮 {r+1}: ⚠ 连续 {retry+1} 次未检出卡片行,跳过本轮滚动(不盲点)")
|
||||||
|
continue
|
||||||
# 每轮最多处理最上面 3 行(9 格)。萤石同时下载上限约 10 个,一屏可见 5~6 行,
|
# 每轮最多处理最上面 3 行(9 格)。萤石同时下载上限约 10 个,一屏可见 5~6 行,
|
||||||
# 若把检出的所有行都点了(12~14 个)会超并发:多点的卡片不进下载队列、滚上来
|
# 若把检出的所有行都点了(12~14 个)会超并发:多点的卡片不进下载队列、滚上来
|
||||||
# 仍未打勾,被反复重点,实测会让"跳过已打勾"退化成 0、重复率回升。多出的行
|
# 仍未打勾,被反复重点,实测会让"跳过已打勾"退化成 0、重复率回升。多出的行
|
||||||
@@ -751,10 +749,29 @@ def tap_all_thumbnails(baseline, max_rounds=300, poll_interval=4, max_wait_per_r
|
|||||||
done = len(tapped_cells) - len(pending)
|
done = len(tapped_cells) - len(pending)
|
||||||
total = get_remote_new_file_count(baseline)
|
total = get_remote_new_file_count(baseline)
|
||||||
if pending:
|
if pending:
|
||||||
# 个别卡片下载失败/超时一直不打勾,不能无限等,照常滚动。这几张
|
# 超时未打勾的卡片,先重试点击一次:可能是点击落在卡片间隙/分组标签上
|
||||||
# 下一轮可能被重复点一次,靠下游 analyze.py 的 MD5 查重兜底。
|
# 没生效(下载根本没触发),而不是下载慢。之前直接滚动会导致这些卡片
|
||||||
|
# 永远漏下载。重试后仍超时才滚动,靠下游 MD5 查重兜底重复。
|
||||||
print(f" 轮 {r+1}: 等待 {waited}s 超时,{done}/{len(tapped_cells)} 个已打勾,"
|
print(f" 轮 {r+1}: 等待 {waited}s 超时,{done}/{len(tapped_cells)} 个已打勾,"
|
||||||
f"仍有 {len(pending)} 个未完成,照常滚动(这几张可能重复,靠下游去重兜底)")
|
f"仍有 {len(pending)} 个未完成,重试点击未完成的卡片...")
|
||||||
|
for (gx, cy) in pending:
|
||||||
|
adb("shell", "input", "tap", str(gx), str(cy))
|
||||||
|
time.sleep(0.5)
|
||||||
|
# 重试后再等一轮
|
||||||
|
retry_waited = 0
|
||||||
|
while retry_waited < max_wait_per_round:
|
||||||
|
time.sleep(poll_interval)
|
||||||
|
retry_waited += poll_interval
|
||||||
|
ensure_not_anr()
|
||||||
|
retry_shot = screenshot("/tmp/_retrywait.png")
|
||||||
|
retry_chk = get_checked_cells_dynamic(retry_shot, row_centers)
|
||||||
|
still_pending = [c for c in pending if not retry_chk.get(c)]
|
||||||
|
if not still_pending:
|
||||||
|
break
|
||||||
|
if still_pending:
|
||||||
|
print(f" 轮 {r+1}: 重试后仍有 {len(still_pending)} 个未完成,照常滚动(靠下游去重兜底)")
|
||||||
|
else:
|
||||||
|
print(f" 轮 {r+1}: 重试后全部打勾完成")
|
||||||
else:
|
else:
|
||||||
print(f" 轮 {r+1}: {len(tapped_cells)} 个全部打勾完成(等待 {waited}s),累计新下载 {total} 个,开始滚动")
|
print(f" 轮 {r+1}: {len(tapped_cells)} 个全部打勾完成(等待 {waited}s),累计新下载 {total} 个,开始滚动")
|
||||||
else:
|
else:
|
||||||
@@ -800,6 +817,17 @@ def tap_all_thumbnails(baseline, max_rounds=300, poll_interval=4, max_wait_per_r
|
|||||||
time.sleep(THUMB_LOAD_WAIT)
|
time.sleep(THUMB_LOAD_WAIT)
|
||||||
sshot = screenshot("/tmp/_sweep.png")
|
sshot = screenshot("/tmp/_sweep.png")
|
||||||
srows = detect_card_rows(sshot)
|
srows = detect_card_rows(sshot)
|
||||||
|
# 清扫时检测不到行要重试:底部缩略图可能还在加载,直接判"无未下载"
|
||||||
|
# 会提前结束、漏掉底部最后几个卡片。
|
||||||
|
sweep_retry = 0
|
||||||
|
while not srows and sweep_retry < 3:
|
||||||
|
sweep_retry += 1
|
||||||
|
time.sleep(2.0)
|
||||||
|
sshot = screenshot("/tmp/_sweep.png")
|
||||||
|
srows = detect_card_rows(sshot)
|
||||||
|
if not srows:
|
||||||
|
print(f" → 清扫第 {sweep+1} 批:连续 {sweep_retry+1} 次未检出卡片行,视为底部已无内容")
|
||||||
|
break
|
||||||
schk = get_checked_cells_dynamic(sshot, srows)
|
schk = get_checked_cells_dynamic(sshot, srows)
|
||||||
todo = [(gx, cy) for cy in srows for gx in THUMB_X if not schk.get((gx, cy))]
|
todo = [(gx, cy) for cy in srows for gx in THUMB_X if not schk.get((gx, cy))]
|
||||||
if not todo:
|
if not todo:
|
||||||
@@ -957,27 +985,33 @@ def sync_recent_days(n_days=7):
|
|||||||
for d in range(n_days):
|
for d in range(n_days):
|
||||||
rec_date = (datetime.now() - timedelta(days=d)).strftime("%Y%m%d")
|
rec_date = (datetime.now() - timedelta(days=d)).strftime("%Y%m%d")
|
||||||
print(f"\n===== [{d+1}/{n_days}] 录像日期 {rec_date}(今天往前第 {d} 天)=====")
|
print(f"\n===== [{d+1}/{n_days}] 录像日期 {rec_date}(今天往前第 {d} 天)=====")
|
||||||
try:
|
for attempt in range(2): # 每天最多重试1次(共2次尝试),避免偶发ANR/网络问题导致整天漏下载
|
||||||
# 清空本地该天旧目录:重下的文件名是新的下载时刻,和旧文件名不同,
|
try:
|
||||||
# 不清会产生"内容相同、文件名不同"的重复副本。SD 卡是真源,重下可恢复。
|
# 清空本地该天旧目录:重下的文件名是新的下载时刻,和旧文件名不同,
|
||||||
day_dir = OUTPUT_DIR / "BH5455783" / rec_date
|
# 不清会产生"内容相同、文件名不同"的重复副本。SD 卡是真源,重下可恢复。
|
||||||
if day_dir.exists():
|
day_dir = OUTPUT_DIR / "BH5455783" / rec_date
|
||||||
shutil.rmtree(day_dir)
|
if day_dir.exists():
|
||||||
print(f" 已清空本地旧目录 {day_dir}(覆盖式重下)")
|
shutil.rmtree(day_dir)
|
||||||
launch_app()
|
print(f" 已清空本地旧目录 {day_dir}(覆盖式重下)")
|
||||||
clear_device_recordings() # 下载前清空,保证有空间、且设备只剩这天的
|
launch_app()
|
||||||
navigate_to_yesterday_batch(days_back=d)
|
clear_device_recordings() # 下载前清空,保证有空间、且设备只剩这天的
|
||||||
tap_all_thumbnails(set())
|
navigate_to_yesterday_batch(days_back=d)
|
||||||
wait_for_downloads(set())
|
tap_all_thumbnails(set())
|
||||||
day_files = get_all_remote_files() # 这一天下载的全部录像
|
wait_for_downloads(set())
|
||||||
if day_files:
|
day_files = get_all_remote_files() # 这一天下载的全部录像
|
||||||
pull_files(list(day_files), date_str=rec_date)
|
if day_files:
|
||||||
else:
|
pull_files(list(day_files), date_str=rec_date)
|
||||||
print(f"→ {rec_date} 没有下载到文件")
|
else:
|
||||||
clear_device_recordings() # 拉完即时清空,释放空间给下一天
|
print(f"→ {rec_date} 没有下载到文件")
|
||||||
except Exception as e:
|
clear_device_recordings() # 拉完即时清空,释放空间给下一天
|
||||||
print(f"⚠ {rec_date} 处理失败,跳过:{e}")
|
break # 成功则跳出重试循环
|
||||||
continue
|
except Exception as e:
|
||||||
|
print(f"⚠ {rec_date} 第 {attempt+1} 次尝试失败:{e}")
|
||||||
|
if attempt == 0:
|
||||||
|
print(f" 重试一次...")
|
||||||
|
time.sleep(5)
|
||||||
|
else:
|
||||||
|
print(f" {rec_date} 两次尝试均失败,跳过该天")
|
||||||
print(f"\n===== 最近 {n_days} 天下载完成 =====")
|
print(f"\n===== 最近 {n_days} 天下载完成 =====")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user