208 lines
13 KiB
Markdown
208 lines
13 KiB
Markdown
# AutoIngest
|
|
|
|
**A Windows tray-resident photo importer for ecommerce sellers.**
|
|
Jeremy Anderson · [dcos.net](https://dcos.net) · info@dcos.net
|
|
Version 3.1 · MIT licensed
|
|
|
|
---
|
|
|
|
## What it does
|
|
|
|
Plug in a camera, SD card, USB reader, or phone. AutoIngest moves the photos into a dated folder (`Pictures\YYYY-MM-DD\`), converts any non-JPG format to progressive JPG, applies your branding, and gets out of the way. Built for a seller who photographs items and needs JPGs in a folder with zero clicks.
|
|
|
|
The four pillars:
|
|
|
|
1. **Import.** Photos move off the device into `~/Pictures/<today>/`. JPGs copy straight through; RAW/HEIC/PNG convert to JPG via Magick.NET. Same-day dedupe (size then SHA-256) skips re-imports.
|
|
2. **Trust.** An opt-in device registry decides what imports. A friend's USB stick or phone is never touched. Each device is fingerprinted by volume serial (SD/USB) or PTP serial (phone) — never by drive letter.
|
|
3. **Brand.** A welcome wizard on first launch sets up your store identity: a text or logo watermark burned into every converted photo, plus EXIF copyright that survives metadata stripping.
|
|
4. **Maintain.** Optional retention sweeps dated folders older than a threshold to the Recycle Bin. Optional autostart installs the exe to a stable home and launches it at login.
|
|
|
|
## Features
|
|
|
|
### Import pipeline
|
|
- Polls mass-storage drives (kernel32 `GetDriveType` / `GetVolumeInformationW`) and MTP phones (`Shell.Application` COM on an STA thread) every 4 seconds
|
|
- Mass storage: probes DCIM / Pictures / root / 3-level deep scan
|
|
- MTP: walks the device shell namespace for DCIM-equivalent folders
|
|
- JPG/JPEG files copy through; everything else converts via Magick.NET (30+ formats: RAW, HEIC, PNG, TIFF, WebP, SVG, …)
|
|
- Output is progressive, optimized JPG at native resolution
|
|
- Verify-before-delete: every source is reclaimed only after the destination is confirmed present, correctly sized, and (for converts) re-decoded
|
|
- Same-day duplicate aware: byte-identical files skipped; name collisions with different content get `_1`, `_2` suffixes (capped at 9999, GUID fallback)
|
|
- Single-instance enforcement via named mutex
|
|
|
|
### Device registry (opt-in trust)
|
|
- First time a device connects, choose **Register & Import**, **Import Once**, or **Ignore**
|
|
- Registered devices are keyed by a stable fingerprint:
|
|
- SD/USB → volume serial from `GetVolumeInformationW`
|
|
- Phone → PTP/MTP serial via the shell namespace
|
|
- No serial available → weak fallback (label + size), flagged in the UI
|
|
- A friend's unregistered device is silently skipped (or prompts, per your setting)
|
|
- Manage from the tray: **Settings… → Registered devices**
|
|
|
|
### Image quality toggles (Options ▸)
|
|
- **Auto-orient (default on)** — honors the EXIF orientation tag by rotating the pixels, so phone photos do not land sideways in browser uploads
|
|
- **Strip metadata (default on)** — removes EXIF/XMP/GPS on export so listing photos do not leak the seller's location
|
|
- **Delete from phone after import (default off)** — phones only; mirrors the move behavior SD/USB have always had. SD/USB always move/convert as before
|
|
|
|
### Branding (optional, off by default)
|
|
Set up via the first-launch welcome wizard, or later from **Options ▸ Branding ▸ Edit branding…**:
|
|
- **Text watermark** — store name / handle / URL burned onto the photo (position + opacity configurable, live preview)
|
|
- **Logo watermark** — PNG overlaid in a corner (scale + opacity configurable)
|
|
- **EXIF copyright** — Artist / Copyright / Description embedded as the *only* surviving metadata: strip runs first (camera/GPS/software tags gone), then your copyright writes back
|
|
- **Brand tray icon** — the tray icon renders your logo or store initial on a rounded tile whose background auto-contrasts the mark (light mark → dark tile, dark mark → light tile)
|
|
- Applies to converted files only (same scope as auto-orient / strip)
|
|
|
|
### Photo retention (optional, off by default)
|
|
- Dated folders older than a chosen age go to the **Recycle Bin** on a daily sweep (and once on startup)
|
|
- Age is measured by most recent activity across the folder's files — access a file and the folder's clock resets
|
|
- Presets: 1 week, 1 month, 3 months, 6 months, 12 months, 2 years, 7 years
|
|
- Safety rails: today's folder is exempt; a 10-minute freshness guard protects active imports against midnight rollover; non-dated folders are never touched; everything is recoverable
|
|
- Access-tracking aware: detects NTFS `LastAccessTime` state and falls back to import date when Windows disables access updates (the default)
|
|
|
|
### Start with Windows (optional toggle)
|
|
- **Options ▸ Start with Windows** copies the exe to `%LocalAppData%\Programs\AutoIngest\` and places a shortcut in the user's Startup folder (`shell:startup`)
|
|
- No admin rights, no UAC, no registry entry. Per-user. Takes effect at next login
|
|
- The shortcut's existence is the source of truth — the checkbox reflects reality, refreshed every time the menu opens
|
|
|
|
### Toast UI
|
|
- Lives in the system tray by default — no window at startup
|
|
- A borderless toast pops into the bottom-right corner during an import (or when an unknown device connects)
|
|
- Live status line (`Moving 3/12: IMG_004.jpg`) and a scrolling log
|
|
- Auto-hides ~4 seconds after the import completes
|
|
- Left-click the tray icon to peek; right-click for the menu
|
|
|
|
## Architecture
|
|
|
|
```
|
|
AutoIngest/
|
|
├── AutoIngest.csproj .NET 8 / WinForms / single NuGet dependency
|
|
├── App/ UI layer (namespace AutoIngest.App)
|
|
│ ├── Program.cs Entry point, single-instance mutex
|
|
│ ├── MainForm.cs Tray icon, toast, coordinator wiring, Options menu
|
|
│ ├── MainForm.Designer.cs Toast popup layout (WinForms Designer)
|
|
│ ├── MainForm.resx Designer resource header
|
|
│ ├── SettingsForm.cs Settings: device registry + retention + access tracking
|
|
│ ├── WelcomeForm.cs First-run welcome / branding wizard (new installs only)
|
|
│ ├── BrandingForm.cs Branding editor opened from the tray
|
|
│ ├── BrandingEditorPanel.cs Shared branding fields + live preview
|
|
│ ├── TrayIconFactory.cs Builds the tray icon as a micro brand mark
|
|
│ ├── AutostartManager.cs Self-install + Startup-folder shortcut (IShellLinkW COM)
|
|
│ ├── Theme.cs Shared dark-theme palette
|
|
│ └── app.manifest Windows 10/11 compatibility manifest
|
|
├── Core/ Config/models layer (namespace AutoIngest.Core)
|
|
│ ├── ConfigManager.cs JSON config persistence (atomic write, .bak fallback)
|
|
│ ├── DeviceRegistry.cs In-memory registry of trusted devices
|
|
│ └── FileLogger.cs Rolling persistent log at %LocalAppData%\AutoIngest\
|
|
└── Engine/ Import/conversion layer (namespace AutoIngest.Engine)
|
|
├── IDeviceSource.cs Interface for a source of connected devices
|
|
├── DeviceIdentity.cs Connected-device DTO
|
|
├── IPhotoProvider.cs Interface for enumerating + removing source photos
|
|
├── SDCardMonitor.cs Mass-storage source (SD/USB) — volume-serial ids
|
|
├── MtpDeviceSource.cs Phone source — Shell.Application COM on an STA thread
|
|
├── DeviceImporter.cs Shared photo-discovery + move/convert pipeline
|
|
├── ImportCoordinator.cs Polling loop, registry gate, retention sweep, event fan-out
|
|
├── FolderRetentionPolicy.cs Enumerates dated folders + sweeps aged ones to Recycle Bin
|
|
├── AccessTimeTracker.cs Detects/enables NTFS last-access-time tracking
|
|
├── BrandingRenderer.cs Burns text/logo watermarks into a photo
|
|
├── FileSize.cs Shared human-readable byte formatting
|
|
└── ImageConverter.cs Magick.NET wrapper: orient → brand → strip → copyright → JPG
|
|
```
|
|
|
|
**Single NuGet dependency:** `Magick.NET-Q16-AnyCPU`. Everything else (drive detection, shell COM, recycle bin, shortcut creation, access-time probing) uses OS-provided APIs.
|
|
|
|
**Layering:** `App → Core`, `App → Engine`, `Engine → Core`. No reverse references. Each class owns one responsibility.
|
|
|
|
## Design decisions
|
|
|
|
| Decision | Rationale |
|
|
|----------|-----------|
|
|
| Move, not copy | The card is the source of truth; duplicates after import cause confusion. Move once, done. |
|
|
| Copy-and-leave for phones by default | Phones are not a filesystem you can `File.Move` from; auto-deleting from a phone is risky. Opt-in toggle mirrors the move behavior. |
|
|
| Opt-in device registry | A friend's USB stick or phone must never auto-import. Each device is approved once and remembered by serial, not drive letter. |
|
|
| Strip-everything-then-write-branding | Camera model, GPS, and software tags are wiped; the seller's copyright writes back as the sole surviving metadata. Privacy and attribution in one pipeline. |
|
|
| Retention off by default | Auto-deleting photos is dangerous. The seller picks a threshold; nothing happens until they do. |
|
|
| Recycle Bin, not permanent delete | Mistakes recover. Aged folders go to the Recycle Bin. |
|
|
| Filesystem timestamps are the access list | Any access resets a folder's clock. No parallel skip-list to maintain or drift. |
|
|
| Honest access-tracking detection | Windows disables last-access times by default; we detect it, explain it, and offer to enable it. |
|
|
| Startup-folder shortcut | The classic autorun mechanism, fully supported on Windows 11. No admin, no UAC. |
|
|
| `%LocalAppData%\Programs` install target | User-writable, per-user, survives reboots. The system root (`C:\Windows\`) is admin-owned and Defender-scrutinized. |
|
|
| C# / .NET 8 over Python | Single-exe deployment. No runtime or pip on the target PC. |
|
|
| WinForms over WPF | Direct control layout, no XAML complexity. |
|
|
| Magick.NET over System.Drawing | One library handles all 30+ formats including RAW, HEIC, SVG. |
|
|
| Polling (4 s) over WMI events | Deterministic, no COM apartment threading, consistent across Windows versions. |
|
|
| Flat output directory | Matches the workflow: pull to date folder, open in browser, attach to listing. |
|
|
|
|
## Building
|
|
|
|
### Prerequisites
|
|
- Windows 10/11
|
|
- [Visual Studio 2022](https://visualstudio.microsoft.com/vs/community/) with the **.NET desktop development** workload, or the .NET 8 SDK standalone
|
|
|
|
### Visual Studio
|
|
1. Open `AutoIngest.sln`
|
|
2. Build → Build Solution (Ctrl+Shift+B)
|
|
3. Output: `AutoIngest/bin/Debug/net8.0-windows/AutoIngest.exe`
|
|
|
|
### Command line
|
|
```bat
|
|
cd AutoIngest
|
|
dotnet restore
|
|
dotnet build -c Release
|
|
```
|
|
|
|
### Publish a single exe
|
|
```bat
|
|
REM Framework-dependent (~15 MB, requires .NET 8 Desktop Runtime on target)
|
|
publish.bat
|
|
|
|
REM Self-contained (~150-200 MB, runs on any Windows 10/11 PC)
|
|
publish-standalone.bat
|
|
```
|
|
|
|
See **QUICKSTART.md** for the 5-minute end-to-end flow.
|
|
|
|
## Deployment
|
|
|
|
Copy `AutoIngest.exe` to the target PC. No installer required.
|
|
|
|
- **Framework-dependent build** requires the .NET 8 Desktop Runtime. Windows 11 includes it; Windows 10 may need it from [dotnet.microsoft.com](https://dotnet.microsoft.com/download/dotnet/8.0).
|
|
- **Self-contained build** has no prerequisites. Runs on any Windows 10/11 PC.
|
|
|
|
## Configuration
|
|
|
|
Config file: `~/.autoingest_config.json` — created on first run. Fields:
|
|
|
|
| Field | Default | Purpose |
|
|
|-------|---------|---------|
|
|
| `JpgQuality` | 92 | Output JPG quality |
|
|
| `AutoImport` | true | Whether the monitor runs on startup |
|
|
| `PromptOnUnknown` | true | Whether an unregistered device prompts vs. is silently skipped |
|
|
| `Devices` | `[]` | Opt-in registry: stable id, name, kind, added date |
|
|
| `RetentionEnabled` | false | Aged-folder auto-recycle |
|
|
| `RetentionDays` | 180 | Age threshold in days |
|
|
| `AutoOrient` | true | EXIF orientation correction |
|
|
| `StripExif` | true | Strip EXIF/GPS/XMP metadata |
|
|
| `DeleteFromPhoneAfterImport` | false | Phone-side delete after verified copy (phones only) |
|
|
| `SetupCompleted` | true | Welcome-wizard gating marker |
|
|
| `Branding` | (object) | Enabled flag, store identity, watermark settings, EXIF copyright |
|
|
|
|
The registry, retention, and branding are editable from the UI (tray → **Settings…** and **Options ▸ Branding ▸ Edit branding…**). Hand-editing the JSON is supported but unnecessary.
|
|
|
|
## Tech stack
|
|
|
|
| Component | Technology |
|
|
|-----------|------------|
|
|
| Language | C# 12 / .NET 8 |
|
|
| UI framework | Windows Forms |
|
|
| Image processing | Magick.NET-Q16-AnyCPU (ImageMagick 7) |
|
|
| Mass-storage detection | kernel32 `GetDriveType` + `GetVolumeInformationW` via P/Invoke |
|
|
| Phone (MTP) detection | `Shell.Application` COM interop on an STA thread |
|
|
| Recycle Bin | `Microsoft.VisualBasic.FileIO` (BCL) |
|
|
| Shortcut creation | `IShellLinkW` + `IPersistFile` COM interop |
|
|
| Access-time probe | `fsutil` via `System.Diagnostics.Process` |
|
|
| Serialization | System.Text.Json |
|
|
| Target | Windows 10 1809+ / Windows 11 |
|
|
|
|
## License
|
|
|
|
MIT License. See [LICENSE](LICENSE).
|