Automatically sync TMDb keywords as Plex labels or genres for movies and TV shows
Lightweight Docker container that bridges Plex with The Movie Database, adding searchable keywords to your media.
version: '3.8'
services:
labelarr:
image: ghcr.io/nullable-eth/labelarr:latest
container_name: labelarr
restart: unless-stopped
volumes:
- ./labelarr-data:/data
- ./exports:/data/exports # Mount host directory for export files
environment:
# Required - Get from Plex Web (F12 → Network → X-Plex-Token)
- PLEX_TOKEN=your_plex_token_here
# Required - Get from https://www.themoviedb.org/settings/api
- TMDB_READ_ACCESS_TOKEN=your_tmdb_read_access_token
# Required - Your Plex server details
- PLEX_SERVER=plex
- PLEX_PORT=32400
- PLEX_REQUIRES_HTTPS=true
# Process all libraries (recommended for first-time users)
- MOVIE_PROCESS_ALL=true
- TV_PROCESS_ALL=true
# Optional settings
- PROCESS_TIMER=1h
- UPDATE_FIELD=label # or 'genre'
# Optional Radarr/Sonarr integration
# - USE_RADARR=true
# - RADARR_URL=http://radarr:7878
# - RADARR_API_KEY=your_radarr_api_key
# - USE_SONARR=true
# - SONARR_URL=http://sonarr:8989
# - SONARR_API_KEY=your_sonarr_api_key
# Optional export functionality
# - EXPORT_LABELS=action,comedy,thriller,documentary,kids
# - EXPORT_LOCATION=/data/exports
Run: docker-compose up -d
✅ Detects TMDb IDs from Plex metadata, Radarr/Sonarr APIs, or file paths (e.g., {tmdb-12345}
)
✅ Fetches keywords from TMDb API for movies and TV shows
✅ Normalizes keywords with proper capitalization and spelling
✅ Adds as Plex labels/genres - never removes existing values
✅ Runs automatically on configurable timer (default: 1 hour)
✅ Multi-architecture support (AMD64 + ARM64)
- 🚀 Radarr/Sonarr Integration - Automatically detect TMDb IDs from your media managers
- 💾 Persistent Storage - Tracks processed items across container restarts
- 🔍 Verbose Logging - Detailed debugging information for troubleshooting
- 📝 Keyword Normalization - Intelligent formatting with pattern recognition
- 🔄 Force Update Mode - Reprocess all items regardless of previous processing status
- 🧹 Smart Duplicate Cleaning - Automatically removes old unnormalized keywords when adding normalized versions
- 🔒 Enhanced Error Handling - Better authentication and connection testing
- 📤 Export Functionality - Generate file lists for specific labels to sync content or create backups
docker run -d --name labelarr \
-e PLEX_TOKEN=your_plex_token_here \
-e TMDB_READ_ACCESS_TOKEN=your_tmdb_read_access_token \
-e PLEX_SERVER=localhost -e PLEX_PORT=32400 -e PLEX_REQUIRES_HTTPS=true \
-e MOVIE_PROCESS_ALL=true -e TV_PROCESS_ALL=true \
ghcr.io/nullable-eth/labelarr:latest
To avoid Labelarr startup errors when Plex is not yet ready, use Docker Compose's depends_on with condition: service_healthy and add a healthcheck to your Plex service. This ensures Labelarr only starts after Plex is healthy.
version: '3.8'
services:
plex:
image: plexinc/pms-docker:latest
container_name: plex
# ... your plex configuration ...
healthcheck:
test: curl --connect-timeout 15 --silent --show-error --fail http://localhost:32400/identity
interval: 1m00s
timeout: 15s
retries: 3
start_period: 1m00s
labelarr:
image: ghcr.io/nullable-eth/labelarr:latest
container_name: labelarr
restart: unless-stopped
depends_on:
plex:
condition: service_healthy
environment:
- PLEX_SERVER=localhost
- PLEX_PORT=32400
- PLEX_REQUIRES_HTTPS=false
- PLEX_TOKEN=your_plex_token_here
- TMDB_READ_ACCESS_TOKEN=your_tmdb_read_access_token
- MOVIE_PROCESS_ALL=true
- TV_PROCESS_ALL=true
Required Settings:
PLEX_TOKEN
- Get from Plex Web (F12 → Network → X-Plex-Token)TMDB_READ_ACCESS_TOKEN
- Get from TMDb API SettingsPLEX_SERVER
- Your Plex server address (e.g.,localhost
)PLEX_PORT
- Usually32400
Library Selection (choose one approach):
MOVIE_PROCESS_ALL=true
+TV_PROCESS_ALL=true
- Process all libraries (recommended)MOVIE_LIBRARY_ID=1
+TV_LIBRARY_ID=2
- Process specific libraries only
Optional Settings:
PLEX_REQUIRES_HTTPS=true
- Use HTTPS (default:true
)UPDATE_FIELD=label
- Field to update:label
orgenre
(default:label
)PROCESS_TIMER=1h
- How often to run 24h, 5m, 2h30m etc. (default:1h
)REMOVE=lock
- Clean mode:lock
orunlock
(runs once and exits)VERBOSE_LOGGING=true
- Enable detailed lookup information (default:false
)DATA_DIR=/data
- Directory for persistent storage (default:/data
)FORCE_UPDATE=true
- Force reprocess all items regardless of previous processing (default:false
)
Radarr Integration (Optional):
USE_RADARR=true
- Enable Radarr integration (default:false
)RADARR_URL=http://localhost:7878
- Your Radarr instance URLRADARR_API_KEY=your_api_key
- Your Radarr API key
Sonarr Integration (Optional):
USE_SONARR=true
- Enable Sonarr integration (default:false
)SONARR_URL=http://localhost:8989
- Your Sonarr instance URLSONARR_API_KEY=your_sonarr_api_key
- Your Sonarr API key
Export Integration (Optional):
EXPORT_LABELS=action,comedy,thriller
- Comma-separated list of labels to export file paths forEXPORT_LOCATION=/path/to/export
- Directory where export files will be createdEXPORT_MODE=txt
- Export format:txt
(default) orjson
- Movie Processing: Iterates through all movies in the library
- TMDb ID Extraction: Gets TMDb IDs from:
- Plex metadata Guid field
- File/folder names with
{tmdb-12345}
format
- Keyword Fetching: Retrieves keywords from TMDb API
- Label Synchronization: Adds new keywords as labels (preserves existing labels)
- Progress Tracking: Remembers processed movies to avoid re-processing
Labelarr now supports automatic TMDb ID detection through Radarr and Sonarr APIs, eliminating the need for TMDb IDs in file paths!
- ✅ No file renaming required - Works with your existing file structure
- ✅ Multiple matching methods - Title, year, IMDb ID, TVDb ID, file path
- ✅ Automatic fallback - If Radarr/Sonarr doesn't have the item, falls back to file path detection
- ✅ Optional integration - Enable only if you use Radarr/Sonarr
- File path detection is faster - If your file paths consistently contain TMDb IDs, file path detection is significantly faster than API calls
- Radarr/Sonarr integration adds latency - Each API lookup introduces network overhead and processing time
- Recommendation: Use file path detection (with TMDb IDs in filenames/folders) as your primary method for best performance
- When to use APIs: Only enable Radarr/Sonarr integration if your file paths don't contain TMDb IDs or are inconsistently formatted
-
For Movies (Radarr):
- Matches by title and year
- Falls back to IMDb ID from Plex
- Checks file paths against Radarr's database
- Extracts TMDb ID from matched movie
-
For TV Shows (Sonarr):
- Matches by title and year
- Uses TVDb ID from Plex if available
- Falls back to IMDb ID
- Checks episode file paths against Sonarr's database
- Extracts TMDb ID from matched series
services:
labelarr:
image: ghcr.io/nullable-eth/labelarr:latest
environment:
# ... other config ...
# Enable Radarr integration
- USE_RADARR=true
- RADARR_URL=http://radarr:7878
- RADARR_API_KEY=your_radarr_api_key
# Enable Sonarr integration
- USE_SONARR=true
- SONARR_URL=http://sonarr:8989
- SONARR_API_KEY=your_sonarr_api_key
Radarr: Settings → General → Security → API Key
Sonarr: Settings → General → Security → API Key
The application can find TMDb IDs from multiple sources and supports flexible formats:
- Plex Metadata: Standard TMDb agent IDs
- Radarr/Sonarr APIs: Automatic matching (when enabled)
- File Paths: Flexible TMDb ID detection in filenames or directory names
The TMDb ID detection is very flexible and supports various formats:
Direct Concatenation:
/movies/The Matrix (1999) tmdb603/file.mkv
/movies/Inception (2010) TMDB27205/file.mkv
/movies/Avatar (2009) Tmdb19995/file.mkv
With Separators:
/movies/Interstellar (2014) tmdb:157336/file.mkv
/movies/The Dark Knight (2008) tmdb-155/file.mkv
/movies/Pulp Fiction (1994) tmdb_680/file.mkv
/movies/Fight Club (1999) tmdb=550/file.mkv
/movies/The Shawshank Redemption (1994) tmdb 278/file.mkv
With Brackets/Braces:
/movies/Goodfellas (1990) {tmdb634}/file.mkv
/movies/Forrest Gump (1994) [tmdb-13]/file.mkv
/movies/The Godfather (1972) (tmdb:238)/file.mkv
/movies/Taxi Driver (1976) {tmdb=103}/file.mkv
/movies/Casablanca (1942) (tmdb 289)/file.mkv
Mixed Examples:
/movies/Citizen Kane (1941) something tmdb: 15678 extra/file.mkv
/movies/Vertigo (1958) {tmdb=194884}/file.mkv
/movies/Psycho (1960) [ tmdb-539 ]/file.mkv
mytmdb12345
(preceded by alphanumeric characters)tmdb12345abc
(followed by alphanumeric characters)tmdb
(no digits following)
/movies/The Matrix (1999) [tmdb-603]/The Matrix.mkv
/movies/Inception (2010) (tmdb:27205)/Inception.mkv
/movies/Avatar (2009) tmdb19995/Avatar.mkv
/movies/Interstellar (2014) TMDB_157336/Interstellar.mkv
/movies/Edge Case - {tmdb=12345}/file.mkv
/movies/Colon: [tmdb:54321]/file.mkv
/movies/Semicolon; (tmdb;67890)/file.mkv
/movies/Underscore_tmdb_11111/file.mkv
/movies/ExtraSuffix tmdb-22222_extra/file.mkv
/movies/Direct tmdb194884 format/file.mkv
Labelarr can automatically export file paths for media items that have specific labels, creating organized lists perfect for syncing content to alternate locations or creating backup sets.
- Scans all processed media for items containing specified labels
- Creates separate text files for each export label (e.g.,
action.txt
,comedy.txt
) - Lists full file paths of matching movies and TV show episodes
- Updates files after each processing run with current results
- Preserves existing files until new export data is ready
Add these environment variables to enable export functionality:
environment:
# Specify which labels to export (comma-separated, case-insensitive)
- EXPORT_LABELS=action,comedy,thriller,documentary
# Directory where export files will be created
- EXPORT_LOCATION=/data/exports
# Export format: txt (default) creates separate files, json creates single comprehensive file
- EXPORT_MODE=txt
# ... other labelarr config ...
Complete Docker Compose Example with Export:
services:
labelarr:
image: ghcr.io/nullable-eth/labelarr:latest
container_name: labelarr
restart: unless-stopped
volumes:
- ./labelarr-data:/data
- ./exports:/data/exports # Mount host directory for export files
environment:
- PLEX_TOKEN=your_plex_token_here
- TMDB_READ_ACCESS_TOKEN=your_tmdb_token
- PLEX_SERVER=plex
- PLEX_PORT=32400
- MOVIE_PROCESS_ALL=true
- TV_PROCESS_ALL=true
# Export configuration
- EXPORT_LABELS=action,comedy,thriller,documentary,kids
- EXPORT_LOCATION=/data/exports
- EXPORT_MODE=txt
With EXPORT_LABELS=action,comedy,kids
and EXPORT_LOCATION=/data/exports
, Labelarr will create library-specific subdirectories:
/data/exports/
├── summary.txt # Detailed statistics and file sizes
├── Movies/ # Movie library exports
│ ├── action.txt # Action movies only
│ ├── comedy.txt # Comedy movies only
│ └── kids.txt # Kids movies only
└── TV Shows/ # TV show library exports
├── action.txt # Action TV shows only
├── comedy.txt # Comedy TV shows only
└── kids.txt # Kids TV shows only
With EXPORT_MODE=json
, Labelarr creates a single comprehensive JSON file:
/data/exports/
└── export.json # Complete export data with statistics
Each file contains full paths to matching media from that specific library:
Movies/action.txt:
/data/movies/John Wick (2014)/John Wick (2014) (Bluray-1080p).mkv
/data/movies/Mad Max Fury Road (2015)/Mad Max Fury Road (2015) (Bluray-2160p).mkv
/data/movies/The Dark Knight (2008)/The Dark Knight (2008) (Bluray-2160p).mkv
TV Shows/action.txt:
/data/tv/Breaking Bad (2008)/Season 01/Breaking Bad S01E01 (1080p).mkv
/data/tv/Breaking Bad (2008)/Season 01/Breaking Bad S01E02 (1080p).mkv
/data/tv/24 (2001)/Season 01/24 S01E01 (1080p).mkv
export.json structure:
{
"generated_at": "2024-01-15 14:30:25",
"export_mode": "json",
"libraries": {
"Movies": {
"action": [
{
"path": "/data/movies/John Wick (2014)/John Wick (2014) (Bluray-1080p).mkv",
"size": 4832716800
},
{
"path": "/data/movies/Mad Max Fury Road (2015)/Mad Max Fury Road (2015) (Bluray-2160p).mkv",
"size": 8945283072
}
],
"comedy": [
{
"path": "/data/movies/The Hangover (2009)/The Hangover (2009) (Bluray-1080p).mkv",
"size": 3221225472
}
]
},
"TV Shows": {
"action": [
{
"path": "/data/tv/Breaking Bad (2008)/Season 01/Breaking Bad S01E01 (1080p).mkv",
"size": 2147483648
}
]
}
},
"summary": {
"total_files": 1247,
"total_size": 2748779069440,
"total_size_formatted": "2.5 TB",
"library_stats": {
"Movies": {
"total_files": 156,
"total_size": 790495232000,
"total_size_formatted": "736.0 GB",
"labels": {
"action": {
"count": 89,
"size": 478150656000,
"size_formatted": "445.2 GB"
},
"comedy": {
"count": 67,
"size": 312344576000,
"size_formatted": "290.8 GB"
}
}
}
},
"label_totals": {
"action": {
"count": 632,
"size": 1797564416000,
"size_formatted": "1.6 TB"
},
"comedy": {
"count": 615,
"size": 1052901376000,
"size_formatted": "980.3 GB"
}
}
}
}
summary.txt:
Labelarr Export Summary
Generated: 2024-01-15 14:30:25
📁 Export Files Generated:
Movies/action.txt
Movies/comedy.txt
TV Shows/action.txt
TV Shows/comedy.txt
📊 Overall Statistics:
Total files: 1,247
Total size: 2.5 TB (2,748,779,069,440 bytes)
📚 Library Breakdown:
Movies:
action.txt: 89 files, 445.2 GB (478,150,656,000 bytes)
comedy.txt: 67 files, 290.8 GB (312,344,576,000 bytes)
Library total: 156 files, 736.0 GB (790,495,232,000 bytes)
TV Shows:
action.txt: 543 files, 1.2 TB (1,319,413,760,000 bytes)
comedy.txt: 548 files, 689.5 GB (740,556,800,000 bytes)
Library total: 1,091 files, 1.8 TB (2,059,970,560,000 bytes)
🏷️ Label Totals (All Libraries):
action: 632 files, 1.6 TB (1,797,564,416,000 bytes)
comedy: 615 files, 980.3 GB (1,052,901,376,000 bytes)
Content Syncing:
- Export specific genres to sync to mobile devices or remote locations
- Create curated collections for different family members
- Sync action movies to gaming setup, kids content to tablets
- Sync specific content to alternate Plex servers for distributed media setups
- Separate movie and TV exports for different sync destinations
- JSON format for programmatic processing of export data with file sizes and metadata
Backup Management:
- Generate lists of premium content for priority backup
- Create separate backup sets by genre or rating
- Export documentary collections for educational archives
- Create targeted backup lists for specific movies/TV shows
- Library-specific backup strategies (movies vs TV shows)
- JSON export for automated backup tools that need file size information
Media Organization:
- Generate playlists for external media players
- Create file lists for batch operations (transcoding, moving, etc.)
- Export specific content types for different storage tiers
- Organize exports by library type for easier management
- API integration with JSON format for custom media management tools
- Memory efficient: Accumulates paths during processing, writes once at completion
- Atomic updates: Existing export files preserved until new data is ready
- Minimal overhead: Only ~2-5 MB RAM usage for large libraries (10K+ items)
- Label names are case-insensitive:
Action
,action
, andACTION
all match - Multiple labels per item: Movies with both "action" and "comedy" labels appear in both export files
- Empty files created: Labels with no matches still get empty
.txt
files for consistency (text mode) - File paths included: Both movie files and all TV show episode files are included
- Library separation: Files are organized by Plex library (e.g.,
Movies/action.txt
vsTV Shows/action.txt
) in text mode - Library names sanitized: Special characters in library names are replaced with underscores for valid folder names
- Summary statistics:
summary.txt
provides detailed file counts, sizes, and breakdowns by library and label (text mode) - File sizes from Plex: Uses Plex metadata for accurate file sizes without filesystem access
- JSON export includes everything: Single file with all data, file sizes, and comprehensive statistics
- Choose format based on use case: Use
txt
for simple file lists,json
for programmatic processing
Container File Paths:
- Exported file paths reflect your Plex container's internal file system
- If using volume mounts (e.g.,
-v /host/media:/data/media
), paths may need processing - Example: Plex sees
/data/media/movies/...
but host filesystem has/mnt/nas/movies/...
- Consider path mapping/replacement when using exported files outside the container environment
Path Processing Example:
# If Plex container mounts: -v /mnt/nas/media:/data/media
# Export shows: /data/media/movies/Action Movie.mkv
# You may need: /mnt/nas/media/movies/Action Movie.mkv
🔍 Finding Library IDs
To find your library's ID, open your Plex web app, click on the desired library, and look for source=
in the URL:
https://app.plex.tv/desktop/#!/media/xxxx/com.plexapp.plugins.library?source=1
- Here, the library ID is
1
MOVIE_LIBRARY_ID=1
- Process only specific movie libraryMOVIE_PROCESS_ALL=true
- Process all movie libraries (recommended)- Neither set: Movies are NOT processed
🏷️ Labels vs Genres (UPDATE_FIELD)
Control whether TMDb keywords are synced as Plex labels (default) or genres:
UPDATE_FIELD=label
(default): Syncs keywords as Plex labelsUPDATE_FIELD=genre
: Syncs keywords as Plex genres
The chosen field will be locked after update to prevent Plex from overwriting it.
🗑️ Removing Keywords (REMOVE)
Remove only TMDb keywords while preserving custom labels/genres:
REMOVE=lock
: Removes TMDb keywords and locks the fieldREMOVE=unlock
: Removes TMDb keywords and unlocks the field for Plex to update
Use lock when: You manually manage labels/genres
Use unlock when: You want Plex to refresh metadata naturally
# Example: Remove TMDb keywords from labels and lock field
docker run --rm \
-e PLEX_TOKEN=... -e TMDB_READ_ACCESS_TOKEN=... \
-e REMOVE=lock -e UPDATE_FIELD=label \
-e MOVIE_PROCESS_ALL=true -e TV_PROCESS_ALL=true \
ghcr.io/nullable-eth/labelarr:latest
🔒 Field Locking & Plex Metadata
Locked fields in Plex are protected from automatic updates:
- ✅ Labelarr can still modify them
- ✅ Manual edits in Plex UI still work
- ❌ Plex cannot overwrite during metadata refresh
- 🔒 Lock icon appears in Plex UI
Unlocked fields can be updated by Plex during metadata refreshes.
Labelarr's behavior:
- Adding keywords: Always locks the field
- Remove with lock: Keeps field locked after removing keywords
- Remove with unlock: Unlocks field for Plex to manage
🔄 Migration from Previous Version
Old behavior: Auto-selected first movie library
New behavior: Must specify which libraries to process
Migration steps:
# Before (auto-selected movies)
-e LIBRARY_ID=1
# After (explicit selection)
-e MOVIE_LIBRARY_ID=1 # Specific library
# OR
-e MOVIE_PROCESS_ALL=true # All movie libraries
-e TV_PROCESS_ALL=true # All TV libraries
New Features:
- 📺 TV show support
- 🔇 Reduced verbose output
- 📊 Better progress tracking
- 🛡️ Enhanced error handling
Field locking is a crucial concept in Plex that determines whether Plex can automatically update metadata fields during library scans and metadata refreshes. Understanding how this works with Labelarr is essential for managing your media library effectively.
🔐 What is Field Locking?
When a field is locked in Plex:
- ✅ The field value is protected from automatic changes
- ✅ Plex cannot overwrite the field during metadata refresh
- ✅ Manual edits in Plex UI are still possible
- ✅ External tools (like Labelarr) can still modify the field
- 🔒 A lock icon appears next to the field in Plex UI
When a field is unlocked in Plex:
- 🔄 Plex can update the field during metadata refresh
- 🔄 New metadata agents can overwrite existing values
- 🔄 "Refresh Metadata" will update the field with fresh data
- 🔓 No lock icon appears in Plex UI
🎯 Labelarr's Field Locking Behavior
Labelarr always locks the field after adding TMDb keywords to prevent Plex from accidentally removing them during future metadata refreshes.
REMOVE=lock
: Removes TMDb keywords but keeps the field lockedREMOVE=unlock
: Removes TMDb keywords and unlocks the field
📋 Practical Examples
You have movies with:
- 🏷️ TMDb keywords:
action
,thriller
,heist
- 🏷️ Custom labels:
watched
,favorites
,4k-remaster
Using REMOVE=lock
:
- ✅ Removes only:
action
,thriller
,heist
- ✅ Keeps:
watched
,favorites
,4k-remaster
- 🔒 Field remains locked - Plex won't add new genres
- 💡 Best for: Users who manually manage labels alongside TMDb keywords
Using REMOVE=unlock
:
- ✅ Removes only:
action
,thriller
,heist
- ✅ Keeps:
watched
,favorites
,4k-remaster
- 🔓 Field becomes unlocked - Plex can add new metadata
- 💡 Best for: Users who want Plex to manage metadata going forward
You want to completely reset your library's metadata:
- Step 1:
REMOVE=unlock
- Removes TMDb keywords and unlocks fields - Step 2: Use Plex's "Refresh All Metadata" to restore original metadata
- Result: Clean slate with Plex's default metadata
🛡️ Best Practices
- ✅ You manually curate labels/genres
- ✅ You use labels for organization (playlists, collections, etc.)
- ✅ You want to prevent accidental metadata overwrites
- ✅ You share your library and need consistent metadata
- ✅ You want to return to Plex's default metadata behavior
- ✅ You're switching to a different metadata agent
- ✅ You want Plex to automatically update metadata in the future
- ✅ You're troubleshooting metadata issues
Enable verbose logging to see detailed information about TMDb ID lookups and matching attempts.
When VERBOSE_LOGGING=true
, you'll see:
- 📋 All available Plex GUIDs for each item
- 🎬 Radarr lookup attempts (title, file path, IMDb ID)
- 📺 Sonarr lookup attempts (title, TVDb ID, IMDb ID, file paths)
- 📁 File path pattern matching attempts
- ✅ Successful matches with source information
- ❌ Failed lookup attempts with reasons
🔍 Starting TMDb ID lookup for movie: The Matrix (1999)
📋 Available Plex GUIDs:
- imdb://tt0133093
- tmdb://603
✅ Found TMDb ID in Plex metadata: 603
🔍 Starting TMDb ID lookup for movie: Inception (2010)
📋 Available Plex GUIDs:
- imdb://tt1375666
🎬 Checking Radarr for movie match...
→ Searching by title: "Inception" year: 2010
✅ Found match in Radarr: Inception (TMDb: 27205)
🔍 Starting TMDb ID lookup for TV show: Breaking Bad (2008)
📋 Available Plex GUIDs:
- tvdb://81189
- imdb://tt0903747
📺 Checking Sonarr for series match...
→ Searching by title: "Breaking Bad" year: 2008
❌ No match found by title/year
→ Searching by TVDb ID: 81189
✅ Found match by TVDb ID: Breaking Bad (TMDb: 1396)
environment:
- VERBOSE_LOGGING=true
This is especially useful for:
- Troubleshooting why certain items aren't being matched
- Understanding which data source provided the TMDb ID
- Debugging Radarr/Sonarr integration issues
Labelarr automatically normalizes keywords from TMDb using intelligent pattern recognition and proper capitalization rules.
- Smart Title Casing: Proper capitalization with article/preposition handling
- Acronym Recognition: Automatically detects "fbi" → "FBI", "usa" → "USA"
- Pattern-Based Rules: Dynamic handling of common patterns without hardcoding every keyword
- Critical Replacements: Known abbreviations like "sci-fi" → "Sci-Fi", "romcom" → "Romantic Comedy"
- Intelligent Patterns: Recognizes relationships, locations, decades, and compound terms
- Duplicate Removal: Removes duplicates after normalization
Before normalization:
sci-fi, action, fbi, based on novel, time travel, woman in peril
After normalization:
Sci-Fi, Action, FBI, Based on Novel, Time Travel, Woman in Peril
- Critical Replacements:
sci-fi
,scifi
,sci fi
→Sci-Fi
- Relationships:
father daughter
→Father Daughter Relationship
- Locations:
san francisco, california
→San Francisco, California
- Versus Patterns:
man vs nature
→Man vs Nature
- Based On:
based on novel
→Based on Novel
- Decades:
1940s
→1940s
(preserved) - Ethnicity:
african american lead
→African American Lead
- General Terms: Any multi-word keyword gets proper title casing
Labelarr automatically cleans up duplicate keywords when applying normalization:
- Removes old versions: If you have "sci-fi" and we add "Sci-Fi", the old version is removed
- Preserves manual keywords: Custom tags you've added manually are always kept
- Handles complex patterns: Works with all normalization patterns (agencies, centuries, etc.)
With VERBOSE_LOGGING=true
, you'll see normalization and cleaning in action:
📝 Normalized: "sci-fi" → "Sci-Fi"
📝 Normalized: "fbi" → "FBI"
📝 Normalized: "based on novel" → "Based on Novel"
🧹 Cleaned 2 duplicate/unnormalized keywords
Use force update mode to reprocess all items in your library, regardless of whether they've been processed before. This is especially useful after implementing keyword normalization or when you want to refresh all metadata.
- After enabling keyword normalization - Update existing keywords with proper formatting
- Configuration changes - When switching between label/genre fields
- Keyword cleanup - Refresh all TMDb keywords with latest data
- Initial migration - When moving from another labeling system
environment:
- FORCE_UPDATE=true
When FORCE_UPDATE=true
:
- ✅ Processes all items regardless of previous processing status
- ✅ Reapplies keywords even if they already exist
- ✅ Updates storage with latest processing information
- ✅ Shows "FORCE UPDATE MODE" message in logs
✅ Found 1250 movies in library
🔄 FORCE UPDATE MODE: All items will be reprocessed regardless of previous processing
⏳ Processing movies...
VERBOSE_LOGGING=true
to monitor progress.
- Open Plex Web App in browser
- Press F12 → Network tab
- Refresh the page
- Find any request with
X-Plex-Token
in headers - Copy the token value
- Visit TMDb API Settings
- Create account and generate API key
- Use the Read Access Token (not the API key)
401 Unauthorized from Plex
- Verify your Plex token is correct
- Check if your Plex server requires HTTPS
401 Unauthorized from TMDb
- Ensure you're using a valid API token.
No TMDb ID found
- Check if your movies have TMDb metadata
- Verify file naming includes
{tmdb-12345}
format - Ensure TMDb agent is used in Plex
Connection refused
- Check PLEX_SERVER and PLEX_PORT values
- Try setting PLEX_REQUIRES_HTTPS=false for local servers
If you're using Radarr to manage your movie collection, follow these steps to ensure Labelarr can detect TMDb IDs from your file paths:
Radarr can automatically include TMDb IDs in your movie file and folder names. Update your naming scheme in Radarr settings:
Recommended Settings:
-
Movie Folder Format:
{Movie CleanTitle} ({Release Year}) {tmdb-{TmdbId}}
Example:
The Matrix (1999) {tmdb-603}
-
Movie File Format:
{Movie CleanTitle} ({Release Year}) {tmdb-{TmdbId}} - {[Quality Full]}{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels]}{[MediaInfo VideoCodec]}{-Release Group}
Example:
The Matrix (1999) {tmdb-603} - [Bluray-1080p][x264][DTS 5.1]-GROUP
If you prefer different bracket styles, these formats also work with Labelarr:
- Square brackets:
{Movie CleanTitle} ({Release Year}) [tmdb-{TmdbId}]
- Parentheses:
{Movie CleanTitle} ({Release Year}) (tmdb-{TmdbId})
- Different delimiters:
{Movie CleanTitle} ({Release Year}) {tmdb:{TmdbId}}
or{Movie CleanTitle} ({Release Year}) {tmdb;{TmdbId}}
❌ Avoid these common mistakes:
- Missing TMDb ID in paths: Default Radarr naming like
{Movie CleanTitle} ({Release Year})
doesn't include TMDb IDs - Using only IMDb IDs:
{imdb-{ImdbId}}
won't work - Labelarr specifically needs TMDb IDs - Folder vs. file naming: Ensure TMDb ID is in at least one location (folder name OR file name)
After updating Radarr naming:
- For new movies: TMDb IDs will be included automatically
- For existing movies: Use Radarr's "Rename Files" feature:
- Go to Movies → Select movies → Mass Editor
- Choose your root folder and click "Yes, move files"
- This will rename existing files to match your new naming scheme
- New Plex Movie Agent: Works with any naming scheme above
- Legacy Plex Movie Agent: May require specific TMDb ID placement for optimal matching
- Best practice: Include TMDb ID in folder names for maximum compatibility
/movies/
├── The Matrix (1999) {tmdb-603}/
│ └── The Matrix (1999) {tmdb-603} - [Bluray-1080p].mkv
├── Inception (2010) [tmdb-27205]/
│ └── Inception (2010) [tmdb-27205] - [WEBDL-1080p].mkv
└── Avatar (2009) (tmdb:19995)/
└── Avatar (2009) (tmdb:19995) - [Bluray-2160p].mkv
If you have an existing movie library without TMDb IDs in file paths:
- Update Radarr naming scheme as shown above
- Use Radarr's mass rename feature to update existing files
- Wait for Plex to detect the changes (or manually scan library)
- Run Labelarr - it will now detect TMDb IDs from the updated file paths
If you're using Sonarr to manage your TV show collection and want to apply new folder naming that includes TMDb IDs, here's how to rename existing folders:
To actually rename existing folders:
-
Go to the Series tab
-
Click the Mass Editor (three sliders icon)
-
Select the shows you want to rename
-
At the bottom, click "Edit"
-
In the popup:
- Set the Root Folder to the same one it's already using (e.g.,
/mnt/user/TV
) - Click "Save"
- Set the Root Folder to the same one it's already using (e.g.,
-
Sonarr will interpret this as a move and apply the new folder naming format without physically moving the files—just renaming the folders.
After applying the new naming format, your TV show folders will include TMDb IDs:
/tv/Batman [tmdb-2287]/Season 3/Batman - S03E17 - The Joke's on Catwoman Bluray-1080p [tmdb-2287].mkv
💡 Pro Tip: This method works for renaming folders without actually moving files, making it safe and efficient for large TV libraries.
- Go 1.23+
- Git
# Clone the repository
git clone https://github.com/nullable-eth/labelarr.git
cd labelarr
# Initialize Go modules
go mod tidy
# Set environment variables
export PLEX_SERVER=localhost
export PLEX_PORT=32400
export PLEX_TOKEN=your_plex_token
export TMDB_READ_ACCESS_TOKEN=your_tmdb_read_access_token
export MOVIE_PROCESS_ALL=true
export TV_PROCESS_ALL=true
# Run the application
go run main.go
# Build for current platform
go build -o labelarr main.go
# Build for Linux (Docker)
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o labelarr main.go
# Docker logs
docker logs labelarr
# Follow logs
docker logs -f labelarr
- Processing progress with movie counts
- TMDb ID detection results
- Label synchronization status
- API error handling and retries
- Detailed processing summaries
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
- GitHub: https://github.com/nullable-eth/labelarr
- Issues: Report bugs and feature requests
- Logs: Check container logs for troubleshooting with
docker logs labelarr
This project is licensed under the MIT License - see the LICENSE file for details.
Tags: plex, tmdb, automation, movies, tv shows, labels, genres, docker, go, selfhosted, media management
⭐ If you find this project helpful, please consider giving it a star!