dji-auto-upload
Plug in your DJI drone. Walk away. Footage shows up in your cloud.
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
.uploadedledger. 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-runprints 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 and macOS a progress window opens when a drone is plugged in, showing a live bar with speed and ETA; the offload runs in a separate process, so closing that window never interrupts the upload. On Linux (where the trigger runs as root, outside your desktop session) progress arrives as a desktop notification that updates in place. Every OS reports the outcome when it finishes.
- It tells you when it finished, on every OS. A desktop notification on
macOS and Linux, a popup on Windows, and
dji-auto-upload statusalways 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
diagnosecommand; 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:
- console.cloud.google.com → create a project
- Enable the Photos Library API
- OAuth consent screen → External → Publish App (so the token doesn't expire weekly)
- Credentials → Desktop app OAuth client → copy the
client_idandclient_secret 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 — works in principle, not yet confirmed on real hardware
Honest status: every code path here is implemented and unit-tested, but no one has yet plugged a real DJI device into a real Mac with this installed. If you try it,
dji-auto-upload diagnoseoutput in an issue would be genuinely useful — and expect the possibility of a rough edge.
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
updatecan 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.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dji_auto_upload-0.8.0.tar.gz.
File metadata
- Download URL: dji_auto_upload-0.8.0.tar.gz
- Upload date:
- Size: 117.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df6c46ab88798145815542d3933a78eb400e5e3bd1e99b041b381b94f345f379
|
|
| MD5 |
53a0dce9a43d1ff0a1931fa1a477f783
|
|
| BLAKE2b-256 |
cb279abfb701ca50bab46e178b9003c0ee8e160c85c4930676c9c49506240ee7
|
Provenance
The following attestation bundles were made for dji_auto_upload-0.8.0.tar.gz:
Publisher:
release.yml on szilvasolutions/dji-auto-upload
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dji_auto_upload-0.8.0.tar.gz -
Subject digest:
df6c46ab88798145815542d3933a78eb400e5e3bd1e99b041b381b94f345f379 - Sigstore transparency entry: 2334887401
- Sigstore integration time:
-
Permalink:
szilvasolutions/dji-auto-upload@7d92bccd634c27be117aad689fc101d0d5c6a7c8 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/szilvasolutions
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7d92bccd634c27be117aad689fc101d0d5c6a7c8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file dji_auto_upload-0.8.0-py3-none-any.whl.
File metadata
- Download URL: dji_auto_upload-0.8.0-py3-none-any.whl
- Upload date:
- Size: 89.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57045729193c093fa5c1d55a7c015d6b13a2f1a8d396369e90a63dd45a70735e
|
|
| MD5 |
e361ba9385561aa05824e24b00578a27
|
|
| BLAKE2b-256 |
35eda4630989384f90bf7fe6209e85489d0f3bfd914c9ac41fdd1fd7a92c255c
|
Provenance
The following attestation bundles were made for dji_auto_upload-0.8.0-py3-none-any.whl:
Publisher:
release.yml on szilvasolutions/dji-auto-upload
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dji_auto_upload-0.8.0-py3-none-any.whl -
Subject digest:
57045729193c093fa5c1d55a7c015d6b13a2f1a8d396369e90a63dd45a70735e - Sigstore transparency entry: 2334887405
- Sigstore integration time:
-
Permalink:
szilvasolutions/dji-auto-upload@7d92bccd634c27be117aad689fc101d0d5c6a7c8 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/szilvasolutions
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7d92bccd634c27be117aad689fc101d0d5c6a7c8 -
Trigger Event:
push
-
Statement type: