Python ยท GPL-3.0 Quality Gate: Pass

Automate your
Plex library.

Rename, organize, and clean TV shows and movies after every download. Embeds subtitles, tags audio languages, and removes the torrent โ€” automatically.

Data loss disclaimer
Cleanup is intentionally aggressive. Only video files, in-progress .!qB downloads, and the organizer index are preserved. Everything else (subtitles, NFOs, artwork, sample files) is deleted after processing. Back up important data before running.

What it does

Plex Organizer is a Python utility triggered after a torrent finishes downloading. Given a download directory, it runs a sequential pipeline:

1

Embeds any external subtitle files into the video container

2

Fetches missing subtitles from online providers

3

Syncs subtitle timing to the audio track

4

Tags audio streams with the correct ISO 639-2 language code

5

Cleans up unwanted files, folders, and samples

6

Renames & moves files into a clean Plex layout

7

Indexes processed files so future runs skip already-organized content

8

Removes the completed torrent from qBittorrent


Quick start

Install as root (required for file operations):

sudo pipx install git+https://github.com/Toomas633/Plex-Organizer.git
sudo pipx ensurepath

Run manually:

sudo plex-organizer /mnt/media

Automated via qBittorrent "Run external program on torrent finished":

sudo plex-organizer "%D" "%I"

See the Installation guide for full details.


Key features

๐ŸŽฌ

Smart renaming

TV โ†’ Show S01E01 1080p.mkv
Movie โ†’ Title (2024) 1080p.mkv

๐Ÿ’ฌ

Subtitle pipeline

Embed โ†’ Fetch โ†’ Sync, fully automated from external files to online providers.

๐Ÿ”Š

Audio tagging

Detects language via faster-whisper and writes ISO 639-2 tags into the container.

๐Ÿ“

Quality detection

Parses filename tag or falls back to probing the video stream height via ffprobe.

๐Ÿ›ก๏ธ

Plex-safe

Skips and never modifies Plex-managed content inside Plex Versions/.

๐Ÿ“‹

Indexing

Per-library index skips already-processed files for fast incremental runs.

โšก

qBittorrent

Removes the torrent via Web API after successful processing.

๐Ÿ› ๏ธ

Management menu

--manage flag for logs, config migration, index generation, and more.


Directory transformation

Before โ€” raw torrent download

/mnt/media/
โ”œโ”€โ”€ movies/
โ”‚   โ”œโ”€โ”€ Venom.2018.BluRay.x264-YTS.mp4
โ”‚   โ”œโ”€โ”€ 1917 (2019) [1080p] [BluRay]/
โ”‚   โ”‚   โ””โ”€โ”€ Subs/
โ”‚   โ”‚       โ”œโ”€โ”€ English.srt
โ”‚   โ”‚       โ””โ”€โ”€ Spanish.srt
โ”‚   โ””โ”€โ”€ random_file.txt
โ””โ”€โ”€ tv/
    โ”œโ”€โ”€ Black Bird/
    โ”‚   โ”œโ”€โ”€ S01E01.mp4
    โ”‚   โ””โ”€โ”€ unwanted_file.txt
    โ””โ”€โ”€ Loki/
        โ””โ”€โ”€ S01E01.mp4

After โ€” clean Plex layout

/mnt/media/
โ”œโ”€โ”€ movies/
โ”‚   โ”œโ”€โ”€ 1917 (2019)/
โ”‚   โ”‚   โ””โ”€โ”€ 1917 (2019) 1080p.mp4
โ”‚   โ””โ”€โ”€ Venom (2018)/
โ”‚       โ””โ”€โ”€ Venom (2018).mp4
โ””โ”€โ”€ tv/
    โ”œโ”€โ”€ Black Bird/
    โ”‚   โ””โ”€โ”€ Season 1/
    โ”‚       โ””โ”€โ”€ Black Bird S01E01.mp4
    โ””โ”€โ”€ Loki/
        โ””โ”€โ”€ Season 1/
            โ””โ”€โ”€ Loki S01E01.mp4

Requirements

ffmpeg and ffprobe are bundled automatically via static-ffmpeg โ€” no separate install needed.

Note
Already running? Use sudo plex-organizer --manage to access logs, migrate config, generate indexes, and more.

Installation

Requirements

ffmpeg and ffprobe are bundled automatically via static-ffmpeg โ€” no separate install needed.


Install

Install directly from GitHub. Because the organizer requires root, install it into root's environment so the plex-organizer command is available on root's PATH:

sudo apt install pipx          # if pipx is not already installed
sudo pipx install git+https://github.com/Toomas633/Plex-Organizer.git
sudo pipx ensurepath
Note
sudo pipx ensurepath adds pipx's binary directory to root's PATH. You may need to restart the shell session or run source ~/.bashrc for the change to take effect.

Verify the install:

sudo plex-organizer --help

Update

sudo pipx upgrade plex-organizer

Data directory

By default, config.ini, log files, and the lock file are stored at:

/root/.config/plex-organizer/

You can override this with the PLEX_ORGANIZER_DIR environment variable, or by running the organizer from a directory that already contains a config.ini.


First run

On the first run, config.ini is automatically created with all default values. You can review and edit it before running again:

sudo plex-organizer /mnt/media   # creates config.ini and exits cleanly
cat /root/.config/plex-organizer/config.ini

See the Configuration page for all available options.


Interactive management menu

After installation, the management menu is available for maintenance tasks:

sudo plex-organizer --manage
Option Description
1 View data directory path
2 View log files
3 Migrate config from an older version
4 Generate/rebuild library indexes
5 Kill a running organizer process
6 Custom pipeline run
7 Migrate TV indexes (v5 โ†’ v6 format)
8 Edit configuration

Migrating from v5.0

Version 6.0 changed the install method. The old git clone + run.sh approach is no longer supported.

v5.0 (old) v6.0+ (new)
Install git clone + run.sh pipx install โ€ฆ
Update ./update.sh pipx upgrade plex-organizer
Run sudo bash run.sh <dir> sudo plex-organizer <dir>
Config Repo directory /root/.config/plex-organizer/

Migration steps

  1. Install pipx (if not already installed):

    sudo apt install pipx
    sudo pipx ensurepath
  2. Install Plex Organizer:

    sudo pipx install git+https://github.com/Toomas633/Plex-Organizer.git
  3. Move your existing config:

    mkdir -p /root/.config/plex-organizer
    cp /path/to/old/Plex-Organizer/config.ini /root/.config/plex-organizer/
  4. Migrate TV indexes to the new per-library format:

    sudo plex-organizer --manage   # choose option 7
  5. Update your qBittorrent automation command:

    sudo plex-organizer "%D" "%I"
  6. Remove the old clone once everything is working:

    rm -rf /path/to/old/Plex-Organizer

Configuration

All configuration is managed through config.ini, which is auto-created on first run and automatically maintained on every startup โ€” missing options are added and unknown options are removed.

Config file location

Location Description
/root/.config/plex-organizer/config.ini Default (when running as root)
$PLEX_ORGANIZER_DIR/config.ini Override via environment variable
./config.ini Current directory, if a config.ini already exists there

[qBittorrent]

Controls torrent removal after processing. Torrent removal is best-effort โ€” failures are logged and processing continues.

Option Default Description
host http://localhost:8081 Base URL for the qBittorrent Web API
username (empty) Web API username
password (empty) Web API password
Note
Torrent removal only runs when a torrent hash is passed as the second argument. See Usage.

[Settings]

Core organizer behaviour.

Option Default Description
delete_duplicates false When true, deletes the source file if the destination already exists
include_quality true Appends quality tag (e.g. 1080p) to renamed files. Falls back to probing video stream height via ffprobe when no quality tag is found in the filename
capitalize true Title-cases show and movie names
cpu_threads 4 Maximum CPU threads used for processing steps

Quality labels

When include_quality is enabled and no tag is found in the filename, ffprobe measures the video height and maps it:

Height Label
โ‰ฅ 2160 2160p
โ‰ฅ 1440 1440p
โ‰ฅ 1080 1080p
โ‰ฅ 720 720p
< 720 480p

[Logging]

Option Default Description
enable_logging true Writes errors to a log file
log_file plex_organizer.log Log file name
clear_log false Clears the log file at the start of each run
timestamped_log_files false Saves timestamped logs into a logs/ subfolder instead of a single file
level INFO Log level โ€” INFO or DEBUG

[Audio]

Controls automatic audio stream language tagging using AI-based detection.

Option Default Description
enable_audio_tagging true Detects missing audio track languages and writes ISO 639-2 tags into the container
whisper_model_size tiny faster-whisper model size. Larger models are more accurate but slower (tiny, base, small, medium, large)

How it works:

  1. ffprobe enumerates audio streams in the video file.
  2. For streams with a missing or und/unknown language tag, a short WAV sample is extracted with ffmpeg.
  3. faster-whisper infers the spoken language from the sample.
  4. The ISO 639-2 tag (e.g. eng, spa) is written back into the container via an ffmpeg -c copy remux.

[Subtitles]

Controls the full subtitle pipeline: embedding external subs, fetching from online providers, and syncing timing.

Option Default Description
enable_subtitle_embedding true Embeds external .srt/.ass files found alongside the video (including Subs/ subfolders) into the container before cleanup
analyze_embedded_subtitles true Also analyzes already-embedded subtitle streams for missing/unknown language or SDH tags and writes detected metadata back
fetch_subtitles eng Comma-separated ISO 639-2 language codes to fetch (e.g. eng, est). Leave empty to disable
subtitle_providers opensubtitles, podnapisi, gestdown, tvsubtitles Comma-separated list of subtitle providers to use for fetching
sync_subtitles true Synchronizes embedded subtitle timing to the audio track after all other subtitle operations

Subtitle pipeline order

1. embed_subtitles     โ€” embed external .srt/.ass files found on disk
2. fetch_subtitles     โ€” download missing languages from online providers
3. sync_subtitles      โ€” align subtitle timing to audio track
4. cleanup             โ€” remove subtitle files/folders from disk
Warning
Only text-based subtitle streams (SRT, ASS/SSA, mov_text, WebVTT) are synced. Bitmap formats (PGS, DVB, VobSub) are left unchanged by ffsubsync.

Full example config.ini

[qBittorrent]
host = http://localhost:8081
username = admin
password = adminadmin

[Settings]
delete_duplicates = false
include_quality = true
capitalize = true
cpu_threads = 4

[Logging]
enable_logging = true
log_file = plex_organizer.log
clear_log = false
timestamped_log_files = false
level = INFO

[Audio]
enable_audio_tagging = true
whisper_model_size = tiny

[Subtitles]
enable_subtitle_embedding = true
analyze_embedded_subtitles = true
fetch_subtitles = eng
subtitle_providers = opensubtitles, podnapisi, gestdown, tvsubtitles
sync_subtitles = true

Usage

Syntax

sudo plex-organizer <start_directory> [torrent_hash]
Argument Required Description
<start_directory> Yes The directory to process โ€” either a folder containing tv/ and/or movies/ subfolders, or a single torrent save path
[torrent_hash] No Torrent hash for automatic removal from qBittorrent after processing. Omit for manual runs or to skip removal
Important
Always wrap arguments in quotes when using shell variables or paths that may contain spaces: sudo plex-organizer "%D" "%I"

Start directory modes

The organizer supports two modes, detected automatically from the path:

Main folder mode

The start directory contains tv/ and/or movies/ subfolders:

/mnt/media/
โ”œโ”€โ”€ tv/
โ”‚   โ””โ”€โ”€ ...
โ””โ”€โ”€ movies/
    โ””โ”€โ”€ ...
sudo plex-organizer /mnt/media

In this mode, the organizer processes all unindexed content under both tv/ and movies/. TV show names are derived from the subdirectory name inside tv/.

Single folder mode

The start directory is the torrent save path โ€” its path must contain tv or movies as a path component:

/mnt/media/movies/Venom.2018.BluRay.x264-YTS/
/mnt/media/tv/Black.Bird.S01/
sudo plex-organizer /mnt/media/movies/Venom.2018.BluRay.x264-YTS

This is the recommended mode for qBittorrent automation (use %D). Only the specific download folder is processed rather than the entire library.

Unrecognized directories

If the path does not contain tv or movies and is not a main folder, the organizer exits immediately without modifying any files. The torrent is removed if a hash was provided.


Manual running

Run the full pipeline on your media library:

sudo plex-organizer /mnt/media

Test run on a specific folder (no torrent removal):

sudo plex-organizer /mnt/media/movies/SomeMovie.2024.1080p

Automated running with qBittorrent

In qBittorrent, go to Tools โ†’ Options โ†’ Downloads and set the "Run external program on torrent finished" field:

sudo plex-organizer "%D" "%I"
qBittorrent variable Description
%D Torrent save path (the download directory)
%I Torrent hash (for automatic removal after processing)
Note
Using %D (torrent save path) is recommended over a static path like /mnt/media because it targets only the newly finished torrent rather than re-scanning the entire library on every completion.

Rename formats

TV shows

Show Name SxxExx.ext
Show Name SxxExx Quality.ext      โ† when include_quality = true

Examples:

Files are moved to:

tv/<Show Name>/Season <x>/<Show Name> SxxExx [Quality].ext

Movies

Name (Year).ext
Name (Year) Quality.ext           โ† when include_quality = true

Examples:

Files are moved to:

movies/<Name (Year)>/<Name (Year)> [Quality].ext

What gets deleted

Warning
Cleanup is intentional and aggressive. Review before running on a new library.

Kept:

Deleted:

Important
Plex-managed paths (inside Plex Versions/) are never modified. The organizer detects and skips them automatically.

Indexing

The organizer keeps a per-library index file (.plex_organizer.index) to skip already-processed files on future runs:

Only files already in the final renamed layout are marked as indexed. Raw/unprocessed filenames are never added to the index.

To rebuild or generate indexes manually:

sudo plex-organizer --manage   # option 4

Flags

Flag Description
--manage Open the interactive management menu
--help Show usage information

Features

File renaming

Plex Organizer renames media files to a consistent, Plex-friendly format, correctly capitalizing names and optionally appending quality tags.

TV shows

Show Name SxxExx.ext
Show Name SxxExx Quality.ext   โ† include_quality = true

Movies

Name (Year).ext
Name (Year) Quality.ext        โ† include_quality = true

Quality detection fallback

When include_quality = true but the filename contains no quality tag, the organizer probes the video stream height via ffprobe and maps it to the nearest standard label:

Stream height Label
โ‰ฅ 2160 px 2160p
โ‰ฅ 1440 px 1440p
โ‰ฅ 1080 px 1080p
โ‰ฅ 720 px 720p
< 720 px 480p

Subtitle pipeline

Three separate stages handle subtitles, in order:

1. Subtitle embedding

Enabled by [Subtitles] enable_subtitle_embedding = true.

When analyze_embedded_subtitles = true (default), already-embedded subtitle streams with missing or unknown language tags are also analyzed and re-tagged.

2. Subtitle fetching

Enabled by [Subtitles] fetch_subtitles = eng (or other language codes).

3. Subtitle syncing

Enabled by [Subtitles] sync_subtitles = true.


Audio language tagging

Enabled by [Audio] enable_audio_tagging = true.

Many downloaded videos have audio streams with no language tag, or an incorrect und (undefined) tag. Plex relies on these tags for the audio track selection menu and "original language" feature.

How it works:

  1. ffprobe enumerates all audio streams
  2. For streams with a missing or und/unknown tag, a short WAV sample is extracted
  3. faster-whisper runs speech-to-text language detection on the sample
  4. The detected ISO 639-2 code (e.g. eng, spa, est) is written back into the container via an ffmpeg -c copy remux

The Whisper model size is configurable via [Audio] whisper_model_size. The default tiny model is fast and sufficient for most cases.


Aggressive cleanup

Cleanup runs before renaming and moving. It is intentionally aggressive so your Plex library stays clean.

What is kept

What is deleted

Type Examples
All non-video files .srt, .nfo, .txt, .jpg, .png, NFO files
Sample files Any file with sample in its name
Unwanted folders Subs/, Subtitles/, Extras/, Sample/, Featurettes/
Artwork folders Posters/, Fanart/, behind-the-scenes folders
Plex managed Plex Versions/ (detected and skipped, never modified)
Note
Subtitle embedding runs before cleanup, so Subs/ and external .srt files are embedded into the container before being removed from disk.

Per-library indexing

After processing, successfully renamed files are added to a .plex_organizer.index JSON file stored in the library root:

On future runs, indexed filenames are skipped immediately, making incremental runs fast regardless of library size.

Rules


qBittorrent integration

When a torrent hash is provided as the second argument, Plex Organizer calls the qBittorrent Web API to remove the torrent (without deleting files) after successful processing.


Plex Version safety

The organizer never modifies paths inside Plex Versions/ subfolders. These contain Plex's on-the-fly transcoded or optimized versions, and touching them would corrupt your library. Detection is automatic.


Single-instance locking

A file-based advisory lock prevents two instances from running simultaneously on the same data directory. If a second invocation detects the lock, it exits immediately. The lock is released automatically when the process ends.

To kill a stuck instance:

sudo plex-organizer --manage   # option 5

Frequently Asked Questions

General

Does Plex Organizer delete my files?

Cleanup is intentional and aggressive. Only video files (.mkv, .mp4), in-progress .!qB downloads, and .plex_organizer.index files are kept. Everything else โ€” subtitle files, NFOs, artwork, text files, sample files โ€” is deleted.

Subtitle files are embedded into the video container before cleanup runs (when enable_subtitle_embedding = true), so they are preserved inside the container.

Back up any files you want to keep before running the organizer on a new library.

Does it require root?

Yes. The organizer requires root privileges to move and rename files across mount points. Always run with sudo.

Can I run it without qBittorrent?

Yes. The torrent hash argument is optional. Omit it to skip torrent removal:

sudo plex-organizer /mnt/media
What happens if the organizer crashes mid-run?

Files that were already moved and indexed remain safe. The organizer uses a single-instance lock and a per-library index, so a subsequent run will skip already-processed files and resume from where it left off. Partially processed files may need to be re-run manually.

Will it re-process files I've already organized?

No. Once a file is in the final renamed layout (e.g. Black Bird S01E01.mp4 inside tv/Black Bird/Season 1/), it is added to the index and skipped on future runs.

Installation

Where is `plex-organizer` after installing?

The command is installed into root's pipx environment. After running sudo pipx ensurepath, it should be available as sudo plex-organizer. If not, check that /root/.local/bin is on root's PATH.

How do I uninstall?
sudo pipx uninstall plex-organizer

Configuration

Where is config.ini?

Default location: /root/.config/plex-organizer/config.ini

You can override this with the PLEX_ORGANIZER_DIR environment variable.

I added a custom option to config.ini and it disappeared.

The config is auto-managed: unknown options inside known sections are removed on startup. Only options listed in the Configuration reference are preserved.

How do I disable subtitle fetching?

Set fetch_subtitles to an empty value:

[Subtitles]
fetch_subtitles =
How do I disable the subtitle pipeline entirely?
[Subtitles]
enable_subtitle_embedding = false
fetch_subtitles =
sync_subtitles = false

Subtitle pipeline

External subtitles are being deleted โ€” how do I keep them?

Enable subtitle embedding before cleanup runs:

[Subtitles]
enable_subtitle_embedding = true

External subtitle files are embedded into the video container and then removed from disk. They are preserved inside the .mkv/.mp4 file itself.

Subtitle syncing is too slow.

Subtitle syncing uses ffsubsync, which processes the entire audio track. You can disable it:

[Subtitles]
sync_subtitles = false
What subtitle formats are supported for syncing?

Only text-based streams: SRT, ASS/SSA, mov_text, and WebVTT. Bitmap formats (PGS, DVB, VobSub) are left unchanged.

Indexing

How do I force re-processing of already-indexed files?

Delete the index file(s) and run again:

rm /mnt/media/movies/.plex_organizer.index
rm /mnt/media/tv/.plex_organizer.index
sudo plex-organizer /mnt/media
How do I generate an index for an existing library without re-processing?

Use the management menu:

sudo plex-organizer --manage   # option 4

This scans the library and adds all already-organized files to the index without moving or renaming anything.

I upgraded from an older version and TV files are being re-processed.

Older versions stored one index per show (tv/<Show>/.plex_organizer.index). Migrate them to the new root index:

sudo plex-organizer --manage   # option 7

This merges all per-show index files into a single tv/.plex_organizer.index and removes the old files.

Migrating from v5.0 to v6.0

What changed between v5.0 and v6.0?
v5.0 (old) v6.0+ (new)
Install git clone + run.sh pipx install git+โ€ฆ
Update ./update.sh pipx upgrade plex-organizer
Run sudo bash run.sh <dir> [hash] sudo plex-organizer <dir> [hash]
Kill โ€” sudo plex-organizer --manage โ†’ option 5
Config location Repo directory /root/.config/plex-organizer/

See the full migration steps on the Installation page.

Changelog

Release history for Plex Organizer. Full diffs and download links are available on the GitHub Releases page .

v6.1
Bug fixes and developer workflow improvements
  • Advisory lock fix โ€” The single-instance lock now keeps the file handle open for the lifetime of the process, preventing the advisory flock from being released prematurely.
  • Quieter index reads โ€” Removed spurious error log when an index file does not yet exist; a missing index is a normal first-run condition.
  • Test script portability โ€” test.sh now detects the installed plex-organizer command instead of relying on a hard-coded Python script path.
  • Validation checklist โ€” Added a post-change validation rule requiring SonarCloud scan, pylint, and pytest to pass before considering a task complete.
v6.0
Major rewrite โ€” packaged install, subtitle pipeline, and a full test suite

Breaking changes:

  • New install method โ€” Plex Organizer is now installed via pipx instead of cloning the repo and running shell scripts. See Migration from v5.0.
  • Reworked index structure โ€” TV indexes are now stored in the TV library root (tv/) instead of per-show directories. Migration tool available via plex-organizer --manage โ†’ option 7.

New features:

  • Subtitle fetching โ€” Searches OpenSubtitles, Podnapisi, Gestdown, and TVsubtitles for missing subtitles and embeds them.
  • Subtitle syncing โ€” Aligns embedded subtitle timing to the audio track using ffsubsync.
  • Analyze embedded subtitles toggle โ€” New analyze_embedded_subtitles option.
  • Interactive management menu โ€” plex-organizer --manage provides a menu-driven helper.
  • Quality detection fallback โ€” Probes video stream height via ffprobe when no quality tag is found in the filename.

Improvements:

  • Full pytest + pytest-cov test suite mirroring the source package layout.
  • CI pipeline โ€” SonarCloud analysis, pylint, and pytest run automatically on push.
  • Packaged via pyproject.toml with declared dependencies.
  • Restructured codebase into focused modules.
v5.0
Indexing and performance improvements
  • Per-library indexing โ€” Keeps track of processed files to speed up future runs on large libraries.
  • Improved debug logging entries.
  • Documentation updates and devcontainer config refactoring.
v4.0
qBittorrent authentication
  • Authentication flow โ€” Implemented a requests.Session-based authentication for the qBittorrent Web API.
  • Added username/password options to the [qBittorrent] config section.
  • More debug logging lines.
v3.1
Logging levels, locking, and stability
  • Log level option โ€” Added level to [Logging] supporting INFO and DEBUG.
  • Single-instance lock โ€” File-based lock prevents multiple instances from running simultaneously.
  • Temp file guard โ€” Added is_script_temp_file() utility to skip internal temp files.
v3.0
Dev Container and logging improvements
  • Timestamped log directories โ€” Log directory is now created automatically when timestamped logs are enabled.
  • Dev Container enhancements: runtime detection of NVIDIA, Intel, AMD, and Qualcomm GPU drivers.
  • Documentation updates and core codebase refactoring.
v2.0
Audio tagging, subtitle embedding, and a dev container
  • Audio language detection and tagging โ€” Detects and writes ISO 639-2 audio stream language tags.
  • Subtitle embedding โ€” Embeds external subtitle files into the video container.
  • Added VS Code Dev Container support.
v1.3
Quality of life improvements
  • Added support for using %D (torrent save path) instead of requiring the full root directory.
  • Added title-case capitalization for show/movie names.
  • Added more configuration options to config.ini, including enhanced logging.
  • Fixed file deletion bug and other minor fixes.
v1.0 โ€“ v1.2
Initial releases

Early releases establishing the core renaming, moving, and cleanup pipeline.