Automated media archiver: a background service that automatically downloads media from connected sources and helps you sort them with a UI.
Detection and download are decoupled through a durable SQLite work queue — no external services, no broker, no Docker:
- Detectors list a source's items, skip already-imported ones, and enqueue the rest into the queue (a table in the same SQLite DB as the dedup index).
- Workers (a thread pool) claim jobs, download, verify, and atomically file each item into the unsorted inbox, recording a content hash so nothing is duplicated. Delivery is at-least-once via short leases: a job stays claimed until it succeeds, and a crashed worker's lease lapses so the job is retried.
- A source owns its availability: while unavailable, its detection is silent
and its queued jobs simply aren't claimed (the pause), and idle workers park —
no busy polling. A
set_available(True)wakes everything and its backlog drains.
Because the queue lives in the shared DB, archiver folder enqueues even when the
daemon isn't running; the daemon drains the backlog when it next runs.
Sources are not declared in the config file. They are added at runtime and persisted in the SQLite state DB, so they survive a reboot. Folders are a one-shot exception: they aren't persisted as sources. Recurring sources (gdrive/phone) are persisted and begin polling when the daemon next starts.
archiver folder ~/Downloads/to-archive # scan + queue for import
archiver source add gdrive --name gdrive
archiver source list
archiver source remove gdriveInterval sources all poll on one program-wide cadence (poll_interval_seconds
in the config), not a per-source interval.
Requires Python 3.11+. Install with pipx so the app gets
its own isolated environment and the archiver command lands on your PATH. The
UI and every source are included by default — no extras to pick:
pipx install "archiver @ git+<repo-url>"
archiver setuparchiver setup writes a starter config, enables run-at-login, and launches the
app. Launching the app starts both the tray/window and the background daemon
(a separate process the tray supervises) — and the same happens automatically at
every login. That's the whole install.
pipx installcan't run code on its own, so the one-timearchiver setupdoes the config + autostart + launch. After that it's automatic.
Works on Windows, macOS, and Linux. On Windows/macOS, tkinter (for the UI)
ships with the python.org installer. On Linux, the tray additionally needs the
system packages python3-tk and gir1.2-ayatanaappindicator3-0.1.
Prefer to do it by hand, or run headless? The pieces setup orchestrates are also
separate commands:
archiver init # write a starter config
archiver autostart install # run the app at login
archiver ui # launch tray + window (also starts the daemon)
archiver run # run just the daemon in the foregroundQuit from the tray's Exit to stop the app and its daemon; otherwise it stops with the machine.
The Google Drive source reads media from your Drive over the Drive API. Because Google requires each app to identify itself with its own OAuth credentials, you create a small (free) Google Cloud "app" once, paste its credentials into the archiver, and grant access from your browser. You stay the only user of your own app, so no Google review/verification is ever required.
The Google Drive client libraries are installed by default — nothing extra to do.
In the Google Cloud Console:
- Create a project (top bar → project picker → New Project).
- Enable the Drive API: APIs & Services → Library → search Google Drive API → Enable.
- Configure the OAuth consent screen (APIs & Services → OAuth consent
screen):
- User type: External.
- Fill in the app name and your email.
- Scopes: add
.../auth/drive.readonly(needed to download file content). - Test users: add your own Google account. Leave the app in Testing — you don't need to publish it.
- Create credentials (APIs & Services → Credentials → Create credentials →
OAuth client ID):
- Application type: Desktop app.
- This gives you a Client ID and Client secret.
The Client ID and Client secret identify your Cloud app and are shared by every Google Drive source, so they're stored once as Google Drive settings. Open the UI, go to Settings → Google Drive, paste both values, and click Save:
archiver ui # or launch the tray appEach Google Drive source is tied to one Google account. Adding a source runs the OAuth consent flow: your browser opens Google's sign-in/consent screen, and on success the account's refresh token is stored with that source.
archiver source add gdrive --name my-drive(Because the app is in Testing you'll see an "unverified app" warning — continue past it, since it's your own app.) The archiver then fetches fresh access tokens from the stored refresh token automatically — no further sign-in needed — and the daemon begins polling the source on its next poll cycle.
To connect a second account, add another source with a different --name.