# sdimport — 检测视频 SD 卡自动导入 插入记录仪 SD 卡 → 自动按拍摄日期归类 → 落盘 → 清空卡内副本 → 延时自动弹出。 macOS 与 Windows 共用同一套核心逻辑,系统差异全部收敛在 `src/sdimport/platforms/`。 ``` 插卡 → 认卡 → 按日期分组 → 对账 → 倒计时确认 → 拷贝 → 校验 → 清卡 → 完成提示 → 延时弹卡 ``` | 环节 | 规则 | |---|---| | **认卡** | 不认卷名,认卡内有没有 `VID-YYYYMMDD-HHMMSS.AVI`。换卡、换记录仪、卡被改名都不受影响 | | **分组** | 日期只取**文件名**里的八位数,不看卡上目录名(实测过目录名与拍摄日期错位) | | **范围** | 只扫 `scan_roots` 指定的目录,**绝不进 `VIDEO/` 的子目录** —— 那里面是历史归档 | | **对账** | 已落盘的跳过;同名但体积不符的另起名字,**绝不覆盖** | | **校验** | 体积 + 头尾各 64KB 指纹一致才算成功 | | **清卡** | 校验通过才删卡内源文件;单文件失败即整批中止,**不删任何源文件** | | **弹卡** | 完成后弹提示,N 分钟后自动弹出;期间可点「暂不弹出」,或执行 `sdimport keep` 取消 | --- ## 安装 ### macOS(已实测) ```bash 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(**尚未在真机实测**) 1. 装 Python 3.9+(),安装时勾选 **Add python.exe to PATH**。 2. 把本项目整个目录拷到那台 Windows 机器上,路径**不要含空格**最省事。 3. 先自检,再演练,最后安装: ```bat 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/检测视频` | 落盘根目录,按 `//` 组织 | | `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 卡 → `//` → 清卡」这一段。 电梯编号识别由 `elevator-video-classifier` skill 负责,那一步需要人工校对,两者不重叠。 - **Windows 侧代码未在真机实测**(开发机是 macOS)。写法上已尽量保守 (纯标准库优先、依赖缺失自动降级、无 GUI 会话也能工作),但仍建议先跑 `selftest` 与 `scan`。