DeviceDock 1.0 ============== Import a phone's camera folder into a proper photo library, filed by the photo's REAL capture date read from its EXIF metadata. DeviceDock is a single self-contained command-line program. No installer, no runtime, no configuration file, no network access, no third-party libraries - it is built from the Go standard library alone. WHAT IT IS FOR -------------- You plug in a phone (or open an exported backup), and you have a folder called DCIM/Camera full of files called IMG_0042.jpg. You want those photos in a sensible dated library on your computer, without copying the same photo twice every time you do it. The hard part is not the copying. The hard part is the DATE. Copying files off a phone routinely destroys their timestamps - the copy is stamped with the moment it was copied, not the moment the photo was taken. Filing by file timestamp therefore files a decade of photos under "today". DeviceDock opens each JPEG and reads the date the camera actually wrote inside the file: the EXIF DateTimeOriginal tag. That date survives copying, because it is part of the image file itself. THE THREE COMMANDS ------------------ devicedock scan [--json] Tells you what is on the card before you touch anything: how many media files, how much space, the date range and a per-month breakdown, how many photos carry a usable EXIF date versus how many have none, and the date read from every single file with a note saying where that date came from. devicedock import --library [--layout year/month|year-month|flat] [--apply] [--json] Copies each photo into the library under a path derived from its EXIF date. Photos already in the library are skipped. Dry run by default. devicedock verify --library [--json] Audits an existing library: photos stored somewhere that does not match their own EXIF date, and identical content stored under more than one name. Reports only; never changes anything. devicedock help (also -h, --help) SAFETY MODEL ------------ These rules are absolute, and they are why the tool is safe to point at a phone you care about: * The source folder is READ-ONLY. DeviceDock opens files there for reading and never writes, renames, moves, or removes anything in it. It is a phone; it is not ours to modify. * Photos are COPIED, never moved. * NOTHING IS EVER DELETED. There is no delete command, no delete flag, no code path that removes a file. Even the duplicates that verify reports are only reported, for you to deal with as you see fit. * import is a DRY RUN until you pass --apply. Without --apply it prints exactly the plan it would carry out and writes nothing at all - not even the library directory. * A copy never overwrites an existing library file. If two different photos want the same name in the same month, the second gets -1, -2, and so on inserted before its extension. IDENTITY IS CONTENT, NOT FILENAME --------------------------------- "Have I already imported this photo?" is answered by the SHA-256 hash of the file's contents, not by its name. This matters more than it sounds. Filenames off a phone are unstable: IMG_0042.jpg is reused after a factory reset, a file gets renamed, an export tool adds "(1)", a backup tool prefixes a date. Any tool that decides identity by name will either import the same photo repeatedly or silently skip a different photo that happens to share a name. Because DeviceDock hashes contents: * Re-running the same import copies nothing and creates nothing. * Renaming a file on the phone and importing again still recognises it as already imported - it is the same photo, whatever it is called. * Two identical files in the same source folder are imported once. * Two DIFFERENT photos that share a filename are both kept. The cost is honest: every file is read in full and hashed on every run. That is I/O-bound, and on a large library it is the slowest part of the program. A persistent index keyed by size+mtime would avoid re-hashing unchanged library files; that is a roadmap item, not present today. HOW THE DATE IS DECIDED ----------------------- For each file, in order: 1. EXIF tag 0x9003 DateTimeOriginal - the moment the shutter fired. This is the tag we want and it is what almost every camera writes. 2. EXIF tag 0x0132 DateTime - a fallback, used when DateTimeOriginal is absent. It is nominally the file-change time, but on many devices it is the only date present. 3. The file's modification time - used only when the file carries no usable EXIF date at all (no EXIF segment, an EXIF segment with no date tag, a non-JPEG such as PNG, or corrupt metadata). Case 3 is reported explicitly. scan counts files "without usable EXIF date", and import prints "[mtime fallback]" per file and a count in the summary, so you always know how many photos were filed on a guess rather than on real metadata. The EXIF parser is written from scratch here. It walks the JPEG marker chain to find the APP1 segment, checks the "Exif\0\0" signature, reads the TIFF header in EITHER byte order ("II" Intel little-endian or "MM" Motorola big-endian - both are used by real cameras and both are supported), walks IFD0, follows the pointer at tag 0x8769 into the Exif sub-IFD, and reads the date tags there. Every offset is bounds-checked before it is dereferenced, entry counts are clamped to the space actually available, and the IFD chain is followed with both a visited-offset set and a hard hop limit, so a corrupt or malicious file cannot make it read out of bounds or loop forever. A file whose metadata cannot be understood falls back to its mtime and is reported; it is never an error. LAYOUTS ------- --layout year/month (default) /2023/2023-05/IMG_0042.jpg --layout year-month /2023-05/IMG_0042.jpg --layout flat /20230514-IMG_0042.jpg verify accepts any of the three, so a library built with one layout is not flagged wholesale when you check it later. Each imported copy has its modification time set to the photo's own capture date, so the library reads correctly even in tools that only look at file timestamps. WHAT THIS DOES NOT DO --------------------- DeviceDock reads a FOLDER. It works on a phone folder that the operating system has already mounted for you, or on an exported backup, or on a memory card in a reader, or on any directory of photos at all. It does NOT talk to phones. There is no USB support, no MTP, no AFC, no iTunes/Finder backup parsing, no Wi-Fi or network transfer, no pairing, no device drivers. Those require platform-specific device APIs - libmtp or the Windows WPD/MTP stack on Android, and Apple's AFC/lockdown protocol over usbmuxd for iOS - along with per-platform driver and permission handling. That is a substantial, genuinely platform-dependent body of work and it is deliberately out of scope for a single-file standard-library program. If your phone does not appear as a folder, DeviceDock cannot see it, and it will say so rather than pretend. Also not done today: * Video files are copied like any other media file only if you add their extension; video container metadata (QuickTime/MP4 creation_time) is not parsed, so videos would be filed by mtime. * HEIC/HEIF images are recognised as media but their metadata is not parsed (the EXIF parser targets the JPEG APP1 segment), so they fall back to mtime. * No timezone handling. EXIF dates are naive wall-clock times with no zone, and are interpreted in the machine's local time. * No sub-second ordering, no burst grouping, no live-photo pairing. ROADMAP ------- * Direct device access: MTP on Android and AFC/lockdown on iOS, so the phone can be read without mounting it as a folder first. * Video metadata: parse the QuickTime/MP4 'mvhd' creation time so clips are filed by their real capture date like photos. * Live photo and burst grouping: recognise the still+video pairs and the rapid-fire sequences phones produce, and keep each group together in the library instead of scattering its members. * A persistent content index so re-imports do not re-hash the whole library. * HEIC/HEIF and raw metadata parsing. EXIT CODES ---------- 0 success (including "problems found" from verify - it is a report) 1 bad invocation, unreadable source folder, or unreadable library Usage errors print to stderr; -h, --help and help exit 0. BUILDING -------- go build -o devicedock . Requires Go 1.24+. There are no dependencies, so it builds with the module proxy switched off entirely (GOPROXY=off). Prebuilt binaries for Windows, macOS (Intel and Apple Silicon) and Linux are in dist/. EXAMPLES -------- # What is on the card? devicedock scan /Volumes/PHONE/DCIM # What WOULD an import do? (this changes nothing) devicedock import /Volumes/PHONE/DCIM --library ~/Photos # Do it. devicedock import /Volumes/PHONE/DCIM --library ~/Photos --apply # Do it again next month - only the new photos are copied. devicedock import /Volumes/PHONE/DCIM --library ~/Photos --apply # Audit the library. devicedock verify --library ~/Photos