初始化 sdimport:检测视频 SD 卡自动导入工具(macOS/Windows 跨平台)
This commit is contained in:
commit
64d2b63767
28 files changed
+4118
No files matched your search
@@ -0,0 +1,154 @@
|
||||
# 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+(<https://www.python.org/downloads/>),安装时勾选 **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/检测视频` | 落盘根目录,按 `<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-classifier` skill 负责,那一步需要人工校对,两者不重叠。
|
||||
- **Windows 侧代码未在真机实测**(开发机是 macOS)。写法上已尽量保守
|
||||
(纯标准库优先、依赖缺失自动降级、无 GUI 会话也能工作),但仍建议先跑 `selftest` 与 `scan`。
|
||||
Reference in new issue
Block a user