qbittorrent-orphaned is a lightweight utility that identifies orphaned files -- files that exist on disk but are not tracked by any torrent in your qBittorrent instance. It connects to the qBittorrent Web API v2, walks the directories you configure, cross-references every file against every torrent, and reports what does not belong.
When you remove a torrent from qBittorrent but keep the data on disk, or when external tools (transcoders, renaming scripts, etc.) create files that were never part of a torrent, those files become orphans. They consume storage without being seeded or managed. This tool finds them so you can decide what to keep and what to reclaim.
- Single-file, pure Python -- no build step, no complex dependencies, just
requests. - Web API v2 -- authenticates and queries qBittorrent over HTTP; works locally or across a network.
- Case-insensitive matching -- handles mixed-case filenames on Windows and Linux alike.
- Category-aware grouping -- results are organized by qBittorrent category, with uncategorized torrents collected under
__UNCATEGORIZED__. - Human-readable sizes -- every orphan is printed alongside its size in KiB, MiB, GiB, etc.
- Configurable metadata ignore list -- common metadata files (
.nfo,.jpg,.png,.srt,.sub,.idx,.txt,.bin,.svg) are skipped by default. You can extend this list. - Exclude patterns -- filter out known non-torrent files (e.g., transcoded 720p copies) by substring match.
- macOS-safe -- automatically skips
._resource fork files.
pip install requests
QBIT_HOST=http://localhost:8080 \
QBIT_USER=admin \
QBIT_PASS=yourpassword \
CATEGORY_FOLDERS="Films=/mnt/media/films;Shows=/mnt/media/shows" \
python orphan_detector.pyThere is no pre-built image yet, but you can run it easily with a one-liner:
docker run --rm \
-e QBIT_HOST=http://qbittorrent:8080 \
-e QBIT_USER=admin \
-e QBIT_PASS=yourpassword \
-e CATEGORY_FOLDERS="Films=/media/films;Shows=/media/shows" \
-v /mnt/media:/media:ro \
--network=host \
python:3-alpine sh -c "pip install --quiet requests && python /app/orphan_detector.py"Mount the script into the container if you prefer a cleaner approach:
docker run --rm \
-v "$(pwd)/orphan_detector.py:/app/orphan_detector.py:ro" \
-v /mnt/media:/media:ro \
-e QBIT_HOST=http://qbittorrent:8080 \
-e QBIT_USER=admin \
-e QBIT_PASS=yourpassword \
-e CATEGORY_FOLDERS="Films=/media/films;Shows=/media/shows" \
python:3-alpine sh -c "pip install --quiet requests && python /app/orphan_detector.py"Tip: If qBittorrent runs in its own container, make sure both containers share a Docker network (or use
--network=host) so the hostname resolves.
All configuration is done through environment variables.
| Variable | Default | Description |
|---|---|---|
QBIT_HOST |
http://qbittorrent:8080 |
qBittorrent Web UI URL |
QBIT_USER |
admin |
Username for Web UI authentication |
QBIT_PASS |
password |
Password for Web UI authentication |
CATEGORY_FOLDERS |
Films=W:\Films;Shows=X:\Series |
Semicolon-separated Category=Path pairs. Categories must match those configured in qBittorrent. |
EXCLUDE_PATTERNS |
(empty) | Comma-separated substrings. Any file whose relative path contains one of these patterns (case-insensitive) is skipped. |
IGNORE_SUFFIXES |
(empty) | Comma-separated file extensions to ignore in addition to the built-in list. Leading dots are optional (e.g., ass,ssa or .ass,.ssa). |
CATEGORY_NAME=ABSOLUTE_PATH;CATEGORY_NAME2=ABSOLUTE_PATH2
Each category name must match exactly what is configured in qBittorrent. Torrents with no category are grouped under the key __UNCATEGORIZED__.
===== Films =====
/mnt/media/films/Some.Movie.2023/Some.Movie.2023.mkv (4,215 MiB)
/mnt/media/films/Old.Film.1999/Old.Film.1999.avi (702 MiB)
===== Shows =====
/mnt/media/shows/Series.Name.S01/Episode.05.mkv (1,102 MiB)
When no orphans are found the output is simply:
No orphaned files found.
- Authenticate -- the script logs in to qBittorrent via
/api/v2/auth/loginand obtains a session cookie. - Fetch torrents -- it retrieves the full torrent list from
/api/v2/torrents/info, then for each torrent calls/api/v2/torrents/filesto get every file path the torrent manages. - Index by category -- all torrent file paths are normalized (forward slashes, lowercase) and grouped into a lookup set per category.
- Walk the filesystem -- for each configured category folder, the script recursively enumerates files, skipping ignored suffixes, macOS resource forks, and exclude-pattern matches.
- Cross-reference -- every disk file is checked against the corresponding category set. Files not present in any torrent are reported as orphans with their absolute path and human-readable size.