64d2b6376710ae769a554c34471e7709bea2f55b
sdimport — 检测视频 SD 卡自动导入
插入记录仪 SD 卡 → 自动按拍摄日期归类 → 落盘 → 清空卡内副本 → 延时自动弹出。
macOS 与 Windows 共用同一套核心逻辑,系统差异全部收敛在 src/sdimport/platforms/。
插卡 → 认卡 → 按日期分组 → 对账 → 倒计时确认 → 拷贝 → 校验 → 清卡 → 完成提示 → 延时弹卡
| 环节 | 规则 |
|---|---|
| 认卡 | 不认卷名,认卡内有没有 VID-YYYYMMDD-HHMMSS.AVI。换卡、换记录仪、卡被改名都不受影响 |
| 分组 | 日期只取文件名里的八位数,不看卡上目录名(实测过目录名与拍摄日期错位) |
| 范围 | 只扫 scan_roots 指定的目录,绝不进 VIDEO/ 的子目录 —— 那里面是历史归档 |
| 对账 | 已落盘的跳过;同名但体积不符的另起名字,绝不覆盖 |
| 校验 | 体积 + 头尾各 64KB 指纹一致才算成功 |
| 清卡 | 校验通过才删卡内源文件;单文件失败即整批中止,不删任何源文件 |
| 弹卡 | 完成后弹提示,N 分钟后自动弹出;期间可点「暂不弹出」,或执行 sdimport keep 取消 |
安装
macOS(已实测)
cd ~/WorkBuddy/检测视频自动导入
./bin/sdimport selftest # 环境自检
./bin/sdimport install # 装后台服务(launchd)
若 install 提示「未能在本次会话中加载」,双击项目根目录的 安装后台服务.command,
或在终端粘贴 launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.sdimport.agent.plist。
什么都不做也行 —— 注销或重启后 macOS 会自动加载。
触发方式:WatchPaths 监视 /Volumes(插卡即响应)+ 每 120 秒兜底检查一次。
Windows(尚未在真机实测)
- 装 Python 3.9+(https://www.python.org/downloads/),安装时勾选 Add python.exe to PATH。
- 把本项目整个目录拷到那台 Windows 机器上,路径不要含空格最省事。
- 先自检,再演练,最后安装:
bin\sdimport.cmd selftest
bin\sdimport.cmd scan
bin\sdimport.cmd once --no-delete
bin\sdimport.cmd install
或直接双击 install-service.cmd。
触发方式:常驻进程 sdimport daemon 订阅 WMI 卷插入事件(秒级响应)+ 每 30 秒轮询兜底,
由任务计划 sdimport 在登录时自动拉起。
可选提速:
pip install wmi之后插卡是秒级响应;不装也能用,只是最坏延迟 30 秒。
命令
| 命令 | 作用 |
|---|---|
sdimport scan |
演练:只显示会导入什么,不做任何写入 |
sdimport once |
立即执行一次导入 |
sdimport once --no-delete |
导入但保留卡内文件 |
sdimport status |
服务状态、待弹出的任务、最近导入记录 |
sdimport log |
查看最近日志 |
sdimport keep |
取消待执行的自动弹出(卡继续挂着) |
sdimport eject |
立刻弹出 SD 卡 |
sdimport selftest |
环境自检 |
sdimport install / uninstall |
安装 / 卸载后台服务 |
sdimport daemon |
常驻进程(Windows 用,一般无需手动跑) |
macOS 用 ./bin/sdimport,Windows 用 bin\sdimport.cmd,参数完全一致。
配置(config.json)
改动前请先读 .workbuddy/memory/MEMORY.md 的「铁律」一节。
| 键 | 默认 | 说明 |
|---|---|---|
target_root |
~/Downloads/检测视频 |
落盘根目录,按 <root>/<YYYYMMDD>/ 组织 |
filename_regex |
^VID-(\d{8})-(\d{6})\.(AVI|MP4|…)$ |
只碰匹配此模式的文件,其余一律不动 |
scan_roots |
["", "VIDEO", "DCIM:2", "MOVIE", "PRIVATE"] |
扫描根。"" 是卷根;:N 是该根下最多再进几层子目录 |
scan_max_depth |
0 |
未写 :N 时的默认下潜层数 |
min_video_bytes |
1024 |
只挡 0 字节 / 畸形残片 |
tiny_video_bytes |
1048576 |
小于此值的只标注、照样导入,不丢数据 |
delete_source_after_verify |
true |
校验通过后是否清卡 |
notify_countdown_seconds |
60 |
开始导入前的倒计时,期间可取消 |
eject_after_import |
true |
导入完成后是否自动弹出 |
eject_delay_seconds |
300 |
延时多久弹出 |
completion_dialog_seconds |
30 |
完成提示框停留秒数;设 0 则只发通知不弹框 |
eject_finder_fallback |
false |
macOS:改走「访达推出」通道。需先授予「自动化」权限,否则会弹框阻塞 |
windows_only_removable |
true |
Windows:只把可移动磁盘当候选卷(硬安全线,别轻易关) |
调试用环境变量:SDIMPORT_TARGET_ROOT、SDIMPORT_COUNTDOWN、SDIMPORT_EJECT_DELAY、
SDIMPORT_COMPLETION_DIALOG。
目录结构
install-service.cmd Windows:双击安装后台服务
uninstall-service.cmd Windows:双击卸载
bin/sdimport macOS / Linux 入口
bin/sdimport.cmd Windows 入口
bin/on-volume-change.sh launchd 触发入口
src/sdimport/
planner.py 按日期分组与落盘规划 (跨平台)
transfer.py 拷贝 → 校验 → 删源 (跨平台)
pipeline.py 主流程编排 (跨平台)
scanner.py 卷内录像扫描规则 (跨平台)
runner.py auto 一轮流程 (跨平台)
daemon.py 常驻进程(Windows 触发用) (跨平台)
journal.py 日志、状态、单实例锁 (跨平台)
paths.py 路径与配置 (跨平台)
volumes.py / notify.py / ejector.py 薄门面,委托平台
platforms/
base.py 接口与 Volume 模型
macos.py /Volumes + diskutil + osascript + launchd
windows.py 盘符 + kernel32 + PowerShell + 任务计划
generic.py 兜底(Linux 等)
runtime/logs/ import-YYYYMMDD.log、launchd.err.log、eject.log
runtime/state/ state.json、pending-eject.json
故障排查
| 现象 | 处置 |
|---|---|
| 插卡没反应 | sdimport status 看服务在不在;sdimport log 看最近日志 |
| 说「判定为非记录仪卡」 | 卡上确实没有符合 filename_regex 的文件;或卡上文件都在子目录里(按设计不纳入) |
| 说「另有 N 个录像不纳入」 | 那些在扫描根的子目录里,属归档范围,按设计不动。真要处理就改 scan_roots |
| macOS 卡弹不出来 | 多半是访达窗口开着这张卡。关掉窗口,或在访达侧边栏点「推出」 |
| Windows 卡弹不出来 | 关掉资源管理器里该盘的窗口;或右键该盘选「弹出」 |
| 想临时保住卡不弹 | sdimport keep |
排查第一站永远是 runtime/logs/ 下的日志,每次导入的每个文件都留了痕。
边界声明
- 本程序只做「SD 卡 →
<target_root>/<YYYYMMDD>/→ 清卡」这一段。 电梯编号识别由elevator-video-classifierskill 负责,那一步需要人工校对,两者不重叠。 - Windows 侧代码未在真机实测(开发机是 macOS)。写法上已尽量保守
(纯标准库优先、依赖缺失自动降级、无 GUI 会话也能工作),但仍建议先跑
selftest与scan。
Languages
Python
96.8%
Shell
2.4%
Batchfile
0.8%