Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

18 Commits

Folders and files

Repository files navigation

Media Manager

Automatic video encoding and renaming service for your media library. Continuously watches directories for video files, encodes them to your preferred quality, and renames them with a clean, consistent format.

Built for Plex, Jellyfin, Emby, or any media server that benefits from standardized file naming.

Features

  • Continuous directory watching — runs as a background service, scanning for new/unprocessed files
  • Smart encoding — uses HandBrake with hardware acceleration (VideoToolbox on macOS) and x265 software fallback
  • Intelligent renaming — clean Title (Year).Resolution.Bitrate.mkv format for movies, Show S01E01 Title.Resolution.Bitrate.mkv for series
  • Auto-detection — automatically detects whether a directory contains movies or TV series
  • Duplicate resolution — when the same title exists twice, the best copy keeps the tagged name and the other one is cleaned up
  • Filename repair with TMDb verification — fix-names repairs damaged names (double spaces, R I P D → R.I.P.D., duplicate resolution tags) and verifies movie titles against TMDb so Plex always recognizes them
  • Container repair (lossless) — files whose extension lies about the container (an MP4 renamed to .mkv without remuxing breaks Direct Play) are automatically rebuilt as real MKVs with a bit-for-bit stream copy: zero quality loss, no re-encode
  • Subtitles follow the video — external .srt/.sub/.idx sidecars are renamed along with the file, so Plex never loses them
  • No-bloat protection — skips encoding if the output would be larger than the source
  • Bitrate tolerance — skips encoding if the file is already within ±5% of the target bitrate
  • Multiple watch directories — monitor as many directories as you need
  • 7 quality presets + custom — from UltraSaver to MaxPunch
  • Cross-platform — macOS (with VideoToolbox HW acceleration) and Linux
  • Service integration — LaunchAgent (macOS) or systemd (Linux) for auto-start at boot
  • Interactive installer — guided setup for dependencies, quality, directories, and service

File Naming Format

Type Format Example
Movies Title (Year).Resolution.Bitrate.mkv The Matrix (1999).1080p.6mb.mkv
Series Show S01E01 Title.Resolution.Bitrate.mkv Breaking Bad S01E01 Pilot.1080p.6mb.mkv

Resolution is always visible in the filename, allowing you to store multiple resolutions in the same directory.

Quick Start

One-Line Install

git clone https://github.com/ronaldjonkers/PlexScripts.git && cd PlexScripts && bash install.sh install

Upgrade

cd PlexScripts && bash install.sh upgrade

Uninstall

cd PlexScripts && bash install.sh uninstall

Quality Presets

# Preset 2160p (4K) 1080p 720p
1 UltraSaver 7 Mbps 3 Mbps 1 Mbps
2 DataDiet 8 Mbps 4 Mbps 1.5 Mbps
3 StreamSaver 10 Mbps 5 Mbps 2.5 Mbps
4 Netflix-ish 12 Mbps 6 Mbps 3 Mbps
5 CrispCable 16 Mbps 8 Mbps 4 Mbps
6 ArchivalLite 20 Mbps 10 Mbps 5 Mbps
7 MaxPunch 24 Mbps 12 Mbps 6 Mbps
8 Custom User-defined User-defined User-defined

Requirements

Dependency Purpose
HandBrakeCLI Video encoding (H.265/HEVC)
ffmpeg/ffprobe Media analysis (resolution, bitrate, duration)
python3 Numeric calculations

All dependencies are automatically installed by install.sh.

Project Structure

PlexScripts/
├── install.sh                  # Interactive installer/updater/uninstaller
├── README.md                   # This file
├── CHANGELOG.md                # Version history
├── .env.example                # Environment template (TMDb token lives in .env)
├── .gitignore                  # Git ignore rules
├── bin/
│   └── media-manager           # Main service executable
├── lib/
│   ├── utils.sh                # Shared utilities (OS detection, ffprobe helpers)
│   ├── encoding.sh             # Encoding logic (VideoToolbox + x265)
│   ├── dedupe.sh               # Duplicate resolution & subtitle sidecars
│   ├── naming.sh               # File naming & media type detection
│   ├── fixnames.sh             # fix-names command (library filename repair)
│   ├── remux.sh                # Container detection & lossless repair
│   └── fix_filename.py         # Name repair logic + TMDb title verification
├── config/
│   └── media-manager.conf.example  # Example configuration
├── service/
│   ├── com.media-manager.plist     # macOS LaunchAgent template
│   └── media-manager.service       # Linux systemd unit template
└── tests/
    ├── test_naming.sh          # Unit tests for naming logic
    ├── test_dedupe.sh          # Unit tests for duplicate handling
    ├── test_fixnames.sh        # Unit tests for filename repair
    └── test_remux.sh           # Unit tests for container repair

Configuration

Configuration is stored in config/media-manager.conf (created by the installer). Key settings:

# Quality profile
PROFILE_NAME="Netflix-ish"
VB2160=12000        # 4K target bitrate (kbps)
VB1080=6000         # 1080p target bitrate (kbps)
VB720=3000          # 720p target bitrate (kbps)

# Encoding
VT_PRESET="quality"     # VideoToolbox preset (macOS): fast|balanced|quality
X265_PRESET="slow"      # x265 software preset: fast|medium|slow|veryslow
TOL_PCT=5               # Skip encoding if bitrate is within ±5%

# File management
DELETE_ORIGINALS="no"           # Delete source files after encoding
DUPLICATE_ACTION="keep_best"    # Same title twice: keep_best | trash | skip
SIDECAR_EXTS="srt sub idx ass ssa vtt smi sup"   # Sidecars that follow a rename
REMUX_MISLABELED="yes"          # Losslessly rebuild files whose extension lies

# Service
SCAN_INTERVAL=300       # Seconds between scans

# Watch directories (path|type)
# Type: movies, series, or auto (auto-detect)
WATCH_DIRS=(
    "/Volumes/Media/Movies|movies"
    "/Volumes/Media/TV Shows|series"
    "/Volumes/Media/Downloads|auto"
)

Usage

Manual Commands

# Start the service (runs continuously)
./bin/media-manager start

# Run a single scan and exit
./bin/media-manager scan

# Start with verbose output (shows full HandBrakeCLI output)
./bin/media-manager start --verbose

# Single scan with verbose output (great for debugging)
./bin/media-manager scan -V

# Preview filename repairs (nothing is renamed)
./bin/media-manager fix-names --dry-run

# Repair filenames in all watch directories (TMDb-verified)
./bin/media-manager fix-names

# Repair one specific directory
./bin/media-manager fix-names /Volumes/Plex/Films

# Repair without TMDb (mechanical fixes only)
./bin/media-manager fix-names --no-tmdb

# Check if the service is running
./bin/media-manager status

# Stop the service
./bin/media-manager stop

# Use a custom config file
./bin/media-manager start -c /path/to/config.conf

# Show help
./bin/media-manager --help

Tip: Use --verbose / -V when running manually to see the full HandBrakeCLI encoder output in real-time. Without it, encoder output is only shown (last 20 lines) when an encode fails.

Service Management

macOS (LaunchAgent):

# Start
launchctl load ~/Library/LaunchAgents/com.media-manager.plist

# Stop
launchctl unload ~/Library/LaunchAgents/com.media-manager.plist

Linux (systemd user service):

# Start
systemctl --user start media-manager

# Stop
systemctl --user stop media-manager

# View logs
journalctl --user -u media-manager -f

How It Works

  1. Scan — The service recursively scans each configured watch directory for video files (.mp4, .mkv, .mov, .avi, .m4v, .wmv)
  2. Skip tagged — Files already in our naming format (Name.Resolution.Bitrate.mkv) are skipped
  3. Skip active — Files modified within the last 30 seconds are skipped (still being written/downloaded)
  4. Dedupe — If the title already has a tagged copy, only the best copy survives (see below)
  5. Analyze — Resolution, bitrate, duration, and audio streams are read via ffprobe
  6. Decide — If the current bitrate is within ±5% of the target: rename only. If the estimated output would be ≥98% of the source: rename only. Otherwise: encode.
  7. Encode — Uses HandBrake with VideoToolbox (macOS HW) or x265 (software fallback). All audio tracks and subtitles are preserved.
  8. Rename — Output is named with the clean format including resolution and bitrate, and external subtitles are renamed along with it
  9. Repeat — Waits for the configured interval, then scans again

Filename Repair (fix-names)

Plex matches movies on the exact Title (Year) stem, so a damaged filename means an unrecognized movie. Earlier renaming passes (and sloppy download sources) left three kinds of damage behind, all repaired by fix-names:

Damage Example Repaired to
" - " collapsed into a double space Movie Subtitle (2020).mkv Movie - Subtitle (2020).mkv
Abbreviations lost their dots R I P D - 2 - Rise of the Damned (2022).1080p.6mb.mkv R.I.P.D. 2 - Rise of the Damned (2022).1080p.6mb.mkv
Resolution tag duplicated Movie (2022).2160p.2160p.mkv Movie (2022).mkv (re-tagged on next scan)

TMDb verification

With a TMDb API token configured, movie titles are verified against The Movie Database: the title and year are parsed from the filename, looked up, and replaced by the official movie title — so Hansel Gretel Witch Hunters (2013) becomes Hansel & Gretel - Witch Hunters (2013). Characters that are illegal in filenames are converted safely (: becomes -).

To keep API usage low, only names that show damage are looked up — a name the mechanical pass leaves untouched is assumed correct and costs zero requests. Pass --tmdb-all to verify every single movie instead (useful for a one-time full audit; expect one or two requests per unique title). Series never use TMDb.

A rename is only accepted when TMDb confidently matches the title (year-anchored search with a similarity check) — unrecognized titles just get the mechanical fixes. Renames go through the same collision-safe path as the scanner, so duplicates are resolved and external subtitles travel along.

Setup

cp .env.example .env
# Edit .env and set your TMDb v4 Read Access Token
# (themoviedb.org → Settings → API → API Read Access Token):
#   TMDB_API_TOKEN=eyJhbGci...

.env is gitignored — the token never ends up in the repository. Without a token, fix-names still runs all mechanical repairs and simply skips TMDb verification.

Recommended workflow

# 1. Always preview first
./bin/media-manager fix-names --dry-run

# 2. Happy with the plan? Run it for real
./bin/media-manager fix-names

# 3. In Plex: Library → Scan Library Files, then refresh metadata if needed

The regular scan loop also applies the mechanical repairs to every new file it processes, so freshly downloaded files get a clean name from the start. TMDb verification only runs in fix-names (deliberately — the scan loop stays offline).

Container Repair (lossless)

Older versions of this tool renamed .mp4 files to .mkv without remuxing. The result is a file whose extension lies about its contents: Plex is told "Matroska", finds MP4 data (with mov_text subtitles and the audio track in front of the video track) and refuses Direct Play, even though the video and audio streams are perfectly compatible.

Every scan now checks the real container (a 12-byte magic-number read, so it costs almost nothing) and repairs mismatches automatically:

  1. Lossless remux — video and audio streams are bit-for-bit copied into a real MKV. No re-encode, zero quality loss, and it runs at network speed (~3% CPU).
  2. Subtitles preserved — mov_text subtitle streams are converted to SRT tracks embedded in the MKV (plain-text conversion, nothing is lost). External .srt sidecars are untouched and keep working, since the filename does not change.
  3. Verified before replacing — the copy must match the source duration (±2s) before it atomically replaces the original; a failed remux leaves the original untouched.
  4. A real MKV wearing a .mp4 name is repaired the other way around (remuxed to .mkv); an existing sibling file is never overwritten.

Handled cases: .mkv that is really MP4, .mp4/.m4v that is really Matroska. Disable with REMUX_MISLABELED="no" in the config.

Duplicate Handling

A rename can collide with a file that is already there — for example Movie (2020).mkv and Movie (2020).1080p.6mb.mkv both sitting in the same folder. Instead of leaving both behind, the service picks a winner:

  1. Byte-identical copies → the copy that already carries the tag is kept
  2. Higher resolution wins
  3. A copy within the bitrate policy beats an oversized copy
  4. Otherwise the higher bitrate wins (better quality, or the better source for re-encoding)

What happens to the losing copy is set by DUPLICATE_ACTION:

Value Behaviour
keep_best (default) The lesser copy is deleted
trash The lesser copy is moved to a .duplicates folder next to it
skip Nothing is touched, a warning is logged

Two tagged copies of the same title (e.g. .1080p.6mb.mkv next to .720p.3mb.mkv) are resolved the same way. When DELETE_ORIGINALS="no", an encoded source is renamed to Name.original.ext so it is never picked up and re-encoded again.

External Subtitles

Plex matches external subtitles on the filename stem, so a rename would orphan them: Fantasia (1940).nl.srt no longer belongs to Fantasia (1940).1080p.6mb.mkv. Sidecars therefore travel with the video everywhere it goes:

Situation What happens to the subtitles
Rename Movie (2020).nl.srt → Movie (2020).1080p.6mb.nl.srt
Encode Re-attached to the encoded file (not to a kept original)
Duplicate cleaned up Moved to the copy that survives, so they are never lost with it
Both copies have the same subtitle The winner keeps its own; the redundant one follows DUPLICATE_ACTION

Which files count as sidecars is set by SIDECAR_EXTS (default srt sub idx ass ssa vtt smi sup). Add nfo there if you want metadata files to move along too.

Media Type Detection

When a directory is set to auto, the service detects the media type by:

  1. Looking for Season XX subdirectories → series
  2. Looking for SxxExx patterns in filenames → series
  3. Default → movies

Running Tests

bash tests/test_naming.sh
bash tests/test_dedupe.sh
bash tests/test_fixnames.sh
bash tests/test_remux.sh

License

MIT

Version

1.3.0 — See CHANGELOG.md for history.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages