Skip to main content

dji-auto-upload

Plug in your DJI drone. Walk away. Footage shows up in your cloud.

CI License: MIT Python 3.10+ Tested on Linux/macOS/Windows Buy Me a Coffee

My DJI Neo 2 doesn't have an SD card. Everything it records lives on internal storage, and there are only two ways to get footage off it: the DJI app, which does a slow Wi-Fi transfer to my phone that I have to sit and watch, or a USB cable. Neither is something I want to deal with after a flight.

So I stopped dealing with it. Now I land, walk inside, and plug the drone into my computer. By the time I've set it down, the new clips are already uploading to the cloud and the drone's storage is clearing space for next time. I don't open an app, I don't drag any files, I don't click anything.

That's the whole idea: plug in a DJI device, walk away, footage shows up in your cloud. It works the same whether your footage sits on internal storage (like the Neo) or an SD card (like a Mini or a Mavic). Either way the storage never fills up, so you never have to stop and clear it by hand.

Which DJI devices work?

If your computer sees the device as a USB drive with a DCIM folder on it, this handles it. That's basically the whole lineup: the drones (Neo, Mini, Air, Mavic, Avata, FPV), the FPV Goggles (their recordings land in the same DCIM tree, and yes, it picks those up too), and the Osmo Action / Pocket cameras.

Detection doesn't actually require it to be a DJI at all. It looks for DJI's USB vendor ID and the DJI / DJIMEDIA volume labels first, but the real backstop is just "is there a DCIM folder here?", so new models tend to work on day one.

One thing to watch: a few devices (some Osmo cameras) ask you to pick a mode when you plug them in. Choose mass storage / USB drive, not MTP. An MTP device doesn't mount as a drive, so nothing that watches for drives can see it.

What you get

  • Plug and forget, on all three OSes. Linux watches with udev, macOS with a DiskArbitration agent, Windows with a Scheduled Task running a WMI watcher. Install it once and you never launch it again.
  • Your cloud, your choice. Uploads go through rclone, so Google Drive, Photos, Dropbox, OneDrive, S3, a NAS, whatever you already use. Setup just asks which one.
  • Sorted by the day you shot it. Clips are grouped by recording date, so a single plug-in can fill several folders (or albums) if the footage spans days.
  • You can't upload the same clip twice. Every batch keeps an .uploaded ledger. Re-run it, unplug mid-upload, whatever; it resumes exactly where it left off and never sends a file again.
  • It won't delete your footage behind your back. Out of the box it removes nothing from the drone. Card-trimming is something you switch on during setup, and even then a file is only removed if the upload ledger proves that exact file reached your cloud. Telemetry sidecars (.SRT, .LRF) are only removed along with the video they belong to, and if the drone's clock looks wrong the cleanup is skipped entirely rather than guessing.
  • Your laptop won't fall asleep on the job. "Walk away" is exactly when a machine decides to idle-sleep and freeze the upload halfway, so a run holds an OS sleep inhibitor until it's finished, then releases it. (Closing the lid still suspends, on any OS. Nothing is lost when it does; see below.)
  • Try it before you trust it. dji-auto-upload run --dry-run prints exactly what it would copy, upload, and delete, and changes nothing.
  • It tells you when it's safe to unplug. When the run finishes it ejects the drone and says so, instead of leaving you guessing mid-copy.
  • You can watch it, and closing the window is safe. On Windows a progress window opens when a drone is plugged in; the offload runs in a separate hidden process, so closing that window never interrupts the upload. A popup reports success or failure either way.
  • It tells you when it finished, on every OS. A desktop notification on macOS and Linux, a popup on Windows, and dji-auto-upload status always shows the last run's outcome. Telegram messages remain available and optional.
  • It tells you when something breaks. The failure popup/notification carries the log path and the diagnose command; the run's outcome is recorded durably.

Quick start

One command. It installs Python and rclone if you don't have them, installs dji-auto-upload, and walks you through setup — including the plug-in trigger.

Windows (paste into PowerShell):

irm https://raw.githubusercontent.com/szilvasolutions/dji-auto-upload/main/install.ps1 | iex

macOS / Linux (paste into a terminal):

curl -fsSL https://raw.githubusercontent.com/szilvasolutions/dji-auto-upload/main/install.sh | bash

Answer the questions, plug in your drone. That's it.

Prefer to install by hand?
pip install dji-auto-upload
dji-auto-upload setup            # interactive: rclone remote, retention, optional Telegram
dji-auto-upload install-trigger  # OS-specific auto-trigger (sudo on Linux)

You'll need Python 3.10+ and rclone installed yourself. The per-OS sections below have the details.

Setting up rclone

dji-auto-upload doesn't lock you into one cloud. It uploads through rclone, which speaks Google Drive, Dropbox, OneDrive, Google Photos, Amazon S3, Backblaze B2, a NAS over SFTP/SMB, and ~70 other backends. You pick the one you want; dji-auto-upload setup just asks for the remote's name.

Install rclone (once):

OS Command
macOS brew install rclone
Windows winget install Rclone.Rclone
Linux curl https://rclone.org/install.sh | sudo bash

Easy path (Drive / Dropbox / OneDrive / S3 / NAS). Run rclone config, choose "New remote", pick your provider, and follow the prompts. For the big consumer clouds it's a single browser sign-in. Click Allow and you're done. Give the remote a name (e.g. gdrive) and use that name in setup. In the config, a path template like DJI/{date} files each day's clips into its own folder.

Stuck, or using a provider not covered here? rclone's own docs have a step-by-step page for every backend: rclone.org/docs — Configure (for example Google Drive, Dropbox, OneDrive).

Advanced path: Google Photos (extra step, worth knowing)

Google Photos works, but rclone's shared OAuth client is rate-limited to ~10 GB/day. For your own quota you need your own OAuth client:

  1. console.cloud.google.com → create a project
  2. Enable the Photos Library API
  3. OAuth consent screen → ExternalPublish App (so the token doesn't expire weekly)
  4. Credentials → Desktop app OAuth client → copy the client_id and client_secret
  5. rclone config → new remote → google photos → paste those in

Then use a path template of album/DJI-{date} so each date becomes its own album. On a headless box, forward the OAuth callback port over SSH: ssh -L 53682:127.0.0.1:53682 user@host, then open the URL rclone prints.

Not sure which to choose? Google Drive is the least-friction option and the one most people should pick.

Per-OS installation

Linux

Requires rclone and udev (already on every desktop distro).

pip install dji-auto-upload
dji-auto-upload setup
sudo dji-auto-upload install-trigger    # writes /etc/udev/rules.d/99-dji-auto-upload.rules

The trigger uses systemd-run (no --scope, with --collect) so the offload runs as a child of PID 1, which escapes systemd-udevd's seccomp filter, which would otherwise block the mount(2) syscall. Logs land in journalctl.

Run setup as your normal user (not with sudo) so the config lands in your home directory. install-trigger then pins the udev rule to that config with --config and tells you which file it will use; without that the triggered run would execute as root, read /etc/dji-auto-upload/, and silently fall back to defaults. It also means rclone uses your rclone.conf.

macOS

Requires rclone. Install via Homebrew if needed:

brew install rclone
pip install dji-auto-upload
dji-auto-upload setup
dji-auto-upload install-trigger    # writes ~/Library/LaunchAgents/com.dji-auto-upload.watcher.plist

The trigger registers a per-user LaunchAgent that runs a tiny resident process subscribed to DiskArbitration disk-appeared events. Watch logs with:

log stream --predicate 'subsystem == "com.dji-auto-upload"'
Windows

Install rclone via winget:

winget install Rclone.Rclone
pip install dji-auto-upload
dji-auto-upload setup
dji-auto-upload install-trigger

The trigger registers a Scheduled Task triggered at logon that runs a PowerShell Win32_VolumeChangeEvent watcher. No admin needed for a per-user task. Run manually for testing:

schtasks /run /tn "DJI Auto Upload Watcher"

Configuration

dji-auto-upload setup writes a TOML config to your platform's config dir:

OS Path
Linux ~/.config/dji-auto-upload/config.toml
macOS ~/Library/Application Support/dji-auto-upload/config.toml
Windows %APPDATA%\dji-auto-upload\config.toml

Edit it freely; comments survive dji-auto-upload setup re-runs.

[remote]
name = "gphotos"
path_template = "album/DJI-{date}"   # {date} expands to YYYY-MM-DD

[paths]
# Where clips are kept on this computer while they upload. Empty = platform
# default. Use single quotes on Windows: in double quotes a backslash starts
# a TOML escape and the line is rejected.
stage_dir = ''     # e.g. 'D:\\DJI' on Windows, '~/Videos/DJI' elsewhere

[retention]
stage_days = 2     # delete local copies N days after upload (0 = keep forever)
drone_days = 0     # delete drone files older than N days (0 = never touch the drone)

[detect]
sidecar_extensions = ["srt", "lrf"]  # kept with their video, deleted only with it
strict_detect      = false           # true = only trigger on DJI vendor ID / volume label

[behaviour]
disk_headroom_mb   = 512
verify_after_copy  = true
delete_drone_files = false   # drone-side deletion is strictly opt-in (set during setup)
eject_when_done    = true    # eject the drone when finished, so it's safe to unplug
inhibit_sleep      = true    # keep the machine awake while a run is in progress

[notifier]
enabled = true
events  = ["start", "done_copy", "done_upload", "done", "fail"]

Telegram credentials live in a separate credentials.toml (chmod 0600). dji-auto-upload setup walks you through getting a bot token from @BotFather and auto-discovers your chat ID by watching for the next message you send to your bot, so there's no copy-pasting from getUpdates URLs.

CLI

dji-auto-upload setup              # interactive setup wizard
dji-auto-upload run [--device P]   # one offload pass (autodetects if --device omitted)
dji-auto-upload run --dry-run      # show the full plan, change nothing
dji-auto-upload install-trigger    # install per-OS auto-trigger
dji-auto-upload uninstall-trigger
dji-auto-upload status             # config summary, rclone reachable, telegram reachable
dji-auto-upload watch-run          # live progress of the current run (safe to close)
dji-auto-upload update             # pull the latest version and refresh the trigger
dji-auto-upload uninstall          # remove the trigger, and optionally config + staged files
dji-auto-upload diagnose           # write a support bundle to attach to a bug report
dji-auto-upload test-notify        # send a test message
dji-auto-upload prune              # manual stage prune
dji-auto-upload version

Just after a new release? PyPI is eventually consistent across its CDN, so update can briefly still see the previous version. Run it again a minute later rather than assuming it failed.

How it works

USB plug-in
    │
    ▼
┌────────────────────────────────────────────────┐
│  Per-OS trigger                                │
│  Linux: udev rule → systemd-run                │
│  macOS: LaunchAgent + DiskArbitration callback │
│  Windows: Scheduled Task + WMI watcher         │
└──────────────┬─────────────────────────────────┘
               │
               ▼
       dji-auto-upload run --device <volume>
               │
   ┌───────────┼───────────┬────────────┬──────────┐
   ▼           ▼           ▼            ▼          ▼
inventory   precheck     copy        upload     cleanup
 (group   (free space  (atomic     (rclone     (ledger-
  by mtime  + headroom)  .part →    --files-     gated
  date)               os.replace)   from)       prune)
                                       │
                                       ▼
                               .uploaded ledger
                                       │
                                       ▼
                              Telegram notification

See docs/architecture.md for the full state machine and docs/why.md for the bash → Python rewrite story.

What if the computer sleeps anyway?

While a run is active it holds a sleep inhibitor (SetThreadExecutionState on Windows, caffeinate on macOS, systemd-inhibit on Linux), so an idle machine won't suspend mid-transfer. That covers the normal "plug in and walk away" case.

It can't stop you closing the lid or choosing Sleep from the menu. If that happens mid-upload, nothing is lost and nothing is duplicated: files already confirmed are in the .uploaded ledger, and anything unconfirmed is simply retried. The drone is never trimmed for a file that isn't confirmed in the cloud. Replug (or run dji-auto-upload run) and it picks up exactly where it stopped.

Set inhibit_sleep = false if you'd rather the machine follow its normal power policy during a run.

Development

git clone https://github.com/szilvasolutions/dji-auto-upload
cd dji-auto-upload
pip install -e ".[dev]"
pytest                       # unit + integration
ruff check .
mypy src

The integration test (tests/integration/test_offload_pipeline.py) builds a synthetic DCIM tree, points the orchestrator at a fake rclone binary (tests/fixtures/fake_rclone.py), and asserts ledger contents, file placement, and notifier event ordering.

Support this project

This is free and I build it in my spare time. If it saved you the hassle of dragging files off a drone, or you want to nudge a feature along, you can buy me a coffee. Bug reports and pull requests are just as welcome.

Buy Me A Coffee

License

MIT. See LICENSE.

Uploads are performed by rclone, which is a separate MIT-licensed program that you install yourself; this project runs it, it does not include it.

DJI, Mavic, Mini, Air, Avata, Neo and Osmo are trademarks of SZ DJI Technology Co., Ltd. This is an independent hobby project and is not affiliated with, authorised by, or endorsed by DJI. The name describes what the tool works with, nothing more.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dji_auto_upload-0.5.2.tar.gz (103.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dji_auto_upload-0.5.2-py3-none-any.whl (81.0 kB view details)

Uploaded Python 3

File details

Details for the file dji_auto_upload-0.5.2.tar.gz.

File metadata

  • Download URL: dji_auto_upload-0.5.2.tar.gz
  • Upload date:
  • Size: 103.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dji_auto_upload-0.5.2.tar.gz
Algorithm Hash digest
SHA256 97fcf50904731c2136051595e7754960e78169d510f9222c146b8e030b4b4e23
MD5 2722188194880c048884d1a6ab201bf1
BLAKE2b-256 61d08fff55fd156065dd9fb3c4344fd8bb7ac43eae1c4d3b0a509d9fe4fefe4e

See more details on using hashes here.

Provenance

The following attestation bundles were made for dji_auto_upload-0.5.2.tar.gz:

Publisher: release.yml on szilvasolutions/dji-auto-upload

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dji_auto_upload-0.5.2-py3-none-any.whl.

File metadata

File hashes

Hashes for dji_auto_upload-0.5.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d9a9a6222ee1426a9790dde2f96719d555c0e9b6b99892deee83605c0c24cd1b
MD5 a6fb7a198d4ce0ac7040fc44c774a6ab
BLAKE2b-256 b94809e971a2d72794463c8a85cab8480a3c2d4568b52f8ead1879876be81d59

See more details on using hashes here.

Provenance

The following attestation bundles were made for dji_auto_upload-0.5.2-py3-none-any.whl:

Publisher: release.yml on szilvasolutions/dji-auto-upload

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page