Plug your Android phone into a Linux machine and a window opens showing every photo and video on it, grouped by album, with a button to save what you want into a folder you choose.
It never writes to or deletes anything on the phone. Copy only, by design.
Getting photos off an Android phone on Linux is worse than it should be. MTP
mounts are flaky or missing entirely, cloud sync means uploading everything to
somebody else, and adb pull from a terminal means knowing the paths by heart.
Phone Bridge is the boring middle ground: plug in, look at your stuff, click what you want, choose where it goes.
It also finds media the Gallery scatters around — smart-glasses imports, for
instance, land in Download/Meta AI/ rather than anywhere you'd think to look.
Phone Bridge enumerates through Android's MediaStore, so it sees every album
the Gallery sees.
git clone https://github.com/mrmoe28/phonebridge.git
cd phonebridge
./install.shThen plug your phone in. It's also in your applications menu as Phone Bridge.
sudo apt install adb ffmpeg python3-gi python3-pil python3-pyudev gir1.2-webkit2-4.1install.sh checks for all of these and tells you what's missing.
Your phone needs USB debugging enabled (Settings → Developer options), and the first time you connect you'll get a prompt on the phone asking you to authorize the computer.
Tested on Linux Mint 22 / Ubuntu 24.04 with a Galaxy S25 on Android 16. It
should work on any Android device and any GTK3 desktop; if it doesn't, please
open an issue with the output of adb devices -l.
PHONEBRIDGE_DEMO=1 python3 -m phonebridgeRuns the whole app against a generated sample library — no device, no adb, no
setup. The sample images are drawn procedurally with Pillow on first run and
cached in ~/.cache/phonebridge/demo. That's what the screenshot above shows.
- Every album on the phone in a sidebar, with counts and sizes
- Filter by Photos / Videos / All
- Real thumbnails for photos and videos, cached on disk
- Click to select;
Ctrl+Aselects everything shown,Escclears - Per-album save folders — screenshots to one place, camera to another — each remembered between runs
- Verifies file size after every copy, so an unplug mid-transfer can't leave a silently truncated file behind
- "Open folder" straight from the save confirmation
- Opens itself when you plug the phone in
systemctl --user disable --now phonebridge-watch.service
rm -f ~/.config/systemd/user/phonebridge-watch.service
rm -f ~/.local/share/applications/phonebridge.desktop
rm -rf ~/.cache/phonebridge ~/.config/phonebridge| File | Job |
|---|---|
phonebridge/device.py |
adb + Android MediaStore: find the phone, list albums, copy files |
phonebridge/thumbs.py |
Thumbnails, cached in ~/.cache/phonebridge/thumbs |
phonebridge/config.py |
Per-album save folders, in ~/.config/phonebridge/config.json |
phonebridge/app.py |
GTK3 window + WebKit view + the JS↔Python bridge |
phonebridge/watcher.py |
Watches USB, opens the window on plug-in |
phonebridge/demo.py |
Generated sample library for PHONEBRIDGE_DEMO=1 |
ui/ |
The interface — plain HTML/CSS/JS, no build step, no framework |
No build step and no node_modules. Edit ui/app.css and restart.
Each of these cost real debugging time. They're written down so nobody has to rediscover them.
adb, not MTP. MTP support on Linux is inconsistent and frequently just
absent — no gvfsd-mtp, no jmtpfs, nothing in the file manager. adb is
already there for anyone with developer options on, and reads at roughly
20 MB/s.
Address the phone by serial, never by transport id. adb transport ids get
reassigned whenever the connection is re-established — one was seen going
2102 → 2103 within a single session. Code holding a transport id breaks
minutes later with no device with transport id.
Never match a phone by idProduct in udev. Phones report a different
product id per USB mode (Samsung uses 6860 for MTP), so a product-id match
silently stops working the moment the mode changes. Match the ADB interface
descriptor ff4201 instead — stable, and vendor-neutral.
udev cannot launch your GUI. A RUN+= rule executes as root with no
DISPLAY, and udev kills anything still running when the event completes. The
auto-open is a systemd --user service running a pyudev monitor inside the
desktop session, which has neither problem.
Android 16 removed the MediaStore thumbnails table. Querying
content://media/external/images/thumbnails returns "No result found" on
modern Android, so every thumbnail has to be generated locally.
Half of all videos open on a black frame. Extracting frame 0 gives you a
perfectly valid ~780-byte solid black JPEG. thumbs.py walks a seek ladder
(1s, 3s, 10% of duration) and only accepts a blank frame as a last resort.
Some MP4s put their index at the end. Where moov sits near the start, a
4 MB partial read thumbnails a 142 MB video in about 0.2s. Where it doesn't —
screen recordings especially — nothing short of the whole file works.
_video_thumb escalates 4 MB → 12 MB → full rather than guessing.
Don't pkill -f "python3 -m phonebridge". That pattern matches any shell
whose command line merely mentions it, including the one running the pkill.
It will kill your own terminal. Match on /proc/<pid>/cmdline argv instead.
Issues and PRs welcome. This was built against one phone on one distro, so reports from other hardware are genuinely useful — especially non-Samsung devices, where album layout and USB descriptors may differ.
PHONEBRIDGE_DEMO=1 lets you work on the UI without a device attached.
MIT — see LICENSE.
