| name | import-media |
| description | Move or copy media from a connected Android phone into the local filesystem according to user-configured mappings in <workspace>/config.yaml. Each mapping defines a phone source path, a local destination, MIME-based routing, optional date folder and orientation split, and move-vs-copy semantics. Use when the user says "import from my phone", "pull my photos", "run the importer", or names a mapping label. |
android-media-importer: import-media
Drives off <workspace>/config.yaml written by onboard. See _shared/workspace.md for <workspace> resolution.
Procedure
0. Preflight
adb devices — must show one device.
- Load
<workspace>/config.yaml. If missing, tell the user to run onboard and stop.
- Tools required:
file, exiftool, ffprobe. Suggest install if missing.
1. Resolve which mappings to run
- If user named a label, run that mapping only.
- If user said "everything" / "all" / "import my phone", run all mappings in declared order.
- If ambiguous, list the mappings and ask.
2. Per-mapping execution
For each selected mapping:
2a. List phone files
recursive=true → adb shell find "$source" -type f
recursive=false → adb shell find "$source" -maxdepth 1 -type f
If incremental_default=true (and user didn't override with "full"), filter to files with mtime newer than the newest existing file in dest_root:
NEWEST=$(find "$dest_root" -type f -printf '%T@\n' 2>/dev/null | sort -n | tail -1 | cut -d. -f1)
[[ -z "$NEWEST" ]] && NEWEST=0
adb shell "find $source -type f -newermt @$NEWEST"
2b. Compute date token (if date_folder != none)
case "$date_folder" in
DDMM) TOKEN=$(date +%d%m) ;;
DD-MM) TOKEN=$(date +%d-%m) ;;
YYYY-MM-DD) TOKEN=$(date +%Y-%m-%d) ;;
YYYYMMDD) TOKEN=$(date +%Y%m%d) ;;
*) TOKEN="" ;;
esac
2c. Per-file pipeline
For each src on the phone:
-
Stage: pull to STAGE=$(mktemp -d); adb pull "$src" "$STAGE/". If the pull fails, log and skip.
-
Classify by MIME: mime=$(file -b --mime-type "$STAGE/<basename>"). Top-level type = ${mime%%/*} (image, video, audio, application, …).
-
Apply mime_filter: if the filter is not any and the top-level type is not in the filter, skip (clean up stage, leave phone copy alone).
-
Pick base destination:
- If
route_by_mime=true:
image/* → $dest_root/$image_dest
video/* → $dest_root/$video_dest
- other →
$dest_root (or skip, if filter excluded it)
- Else:
$dest_root.
-
Append date folder if TOKEN is non-empty: BASE="$BASE/$TOKEN".
-
Append orientation leaf if orientation_split=true and type is image or video:
2d. Tidy empty source dirs (recursive moves only)
If recursive=true and mode=move, prune empty descendants but never the root itself:
adb shell "find $source -mindepth 1 -type d -empty -delete"
3. Dry-run mode
If the user says "dry run" / "preview", do everything up to (but not including) the actual mv and adb shell rm. Report what would happen: per-mapping counts, total bytes, destination paths. No phone or local mutations.
4. Final report
One block per mapping:
[<label>] <N> files moved (or copied), <bytes> MB
image → <path> (portrait: X, landscape: Y)
video → <path> (portrait: X, landscape: Y)
skipped (mime filter): N
skipped (collision): N
kept on phone (size mismatch): N
Then a grand total.
Notes
- Routing is always MIME-based (
file --mime-type) — extension is ignored. Handles HEIC, DNG, WEBP, AVIF, MOV, MKV, 3GP, opus, m4a, etc. without code changes.
- The skill never deletes from the phone unless
mode=move AND byte-size verification passes.
- Incremental mode uses local mtime as the watermark; it's safe but conservative — if you wipe the destination, the next run pulls everything again.
- Run
onboard to add, edit, or remove mappings.