Sonarr Metadata Rewrite
Ever been frustrated that Sonarr only gives you English metadata for your TV
shows? This tool fixes that by watching for Sonarr's .nfo files and
automatically replacing them with translations in whatever language you prefer.
It can also rewrite poster and clearlogo images to language-specific variants
when available (e.g., poster.jpg, clearlogo.png, season01-poster.jpg).
It's my solution to Sonarr Issue #269 and Sonarr Issue #6663 . It turns out a lot of people want their media metadata in their native language, but Sonarr doesn't support this natively.
What it does
The tool runs as a background service that:
- Watches your media folders for when Sonarr creates or updates
.nfofiles - Grabs the TMDB ID from those files and fetches translations from TMDB's API
- Replaces the English metadata with your preferred language(s)
- Rewrites poster and clearlogo images based on your preferred language-country
codes (e.g.,
en-US,ja-JP) when such variants exist on TMDB - Does this fast enough that you barely notice it happening
- Keeps backups of the original files in case you want them back
- Caches everything so it doesn't spam TMDB's API
You can configure multiple languages with fallback priority - like Chinese first, then Japanese, then just leave it in English if neither is available.
Installation
You'll need Docker and a TMDB API Read Access Token from themoviedb.org.
β οΈ Important: This application requires an API Read Access Token, NOT an "API Key". Get your API Read Access Token from the TMDB API settings page under the "API Read Access Token" section. Learn more about authentication at the TMDB Authentication Guide.
Docker (recommended):
docker run -d \
--name sonarr-metadata-rewrite \
--user $(id -u):$(id -g) \
-e TMDB_API_KEY=your_api_key_here \
-e REWRITE_ROOT_DIRS=/tv,/anime \
-e PREFERRED_LANGUAGES=zh-CN,ja-JP \
-v /path/to/tv/shows:/tv \
-v /path/to/anime:/anime \
-v sonarr-metadata-cache:/app/cache \
-v sonarr-metadata-backups:/app/backups \
--restart unless-stopped \
kfstorm/sonarr-metadata-rewrite:latest
Docker Compose:
version: '3.8'
services:
sonarr-metadata-rewrite:
image: kfstorm/sonarr-metadata-rewrite:latest
container_name: sonarr-metadata-rewrite
user: "${UID:-1000}:${GID:-1000}"
environment:
- TMDB_API_KEY=your_api_key_here
- REWRITE_ROOT_DIRS=/tv,/anime
- PREFERRED_LANGUAGES=zh-CN,ja-JP
# Optional: customize cache duration (default: 30 days)
# - CACHE_DURATION_HOURS=168
volumes:
- /path/to/tv/shows:/tv
- /path/to/anime:/anime
- sonarr-metadata-cache:/app/cache
- sonarr-metadata-backups:/app/backups
restart: unless-stopped
volumes:
sonarr-metadata-cache:
sonarr-metadata-backups:
Configuration
Required Settings
TMDB_API_KEY=your_api_read_access_token_here # Your TMDB API Read Access Token
# One or more media directories (comma-separated) to watch for Sonarr .nfo files
REWRITE_ROOT_DIRS=/tv,/anime
# Comma-separated language codes in priority order
PREFERRED_LANGUAGES=zh-CN,ja-JP
REWRITE_ROOT_DIR (singular, one path) is also accepted for backward
compatibility. REWRITE_ROOT_DIRS takes priority when both are set.
Optional Settings (with defaults)
# Scanning & Monitoring
PERIODIC_SCAN_INTERVAL_SECONDS=86400 # How often to scan directory (default: daily)
ENABLE_FILE_MONITOR=true # Real-time file monitoring (default: true)
ENABLE_FILE_SCANNER=true # Periodic directory scanning (default: true)
# NFO & Images
ENABLE_NFO_REWRITE=true # Translate NFO files (default: true)
ENABLE_IMAGE_REWRITE=true # Rewrite posters/clearlogos (default: true)
# Caching & Storage
CACHE_DURATION_HOURS=720 # Cache translations (default: 30 days)
CACHE_DIR=./cache # Cache directory (default: ./cache)
# TMDB API Rate Limiting
TMDB_MAX_RETRIES=3 # Max retry attempts (default: 3)
TMDB_INITIAL_RETRY_DELAY=1.0 # Initial retry delay (default: 1.0)
TMDB_MAX_RETRY_DELAY=60.0 # Max retry delay (default: 60.0)
# Backup
ORIGINAL_FILES_BACKUP_DIR=./backups # Backup original files (default: ./backups)
# Applies to both .nfo and images. Set to
# empty string to disable backups.
# Files are stored under their full absolute
# path (e.g. /app/backups/tv/Show/tvshow.nfo)
# so backups from different root dirs never
# collide.
# Backups created by older single-root-dir
# versions are still found and restored
# automatically (backward compatible).
# Service Mode
SERVICE_MODE=rewrite # Service mode: 'rewrite' or 'rollback'
# (default: rewrite)
Language codes are ISO 639-1 format:
zh-CN- Chinese (Simplified)ja-JP- Japaneseko-KR- Koreanfr-FR- Frenchde-DE- Germanes-ES- Spanish
You can list multiple languages separated by commas - it'll try them in order.
Which images are rewritten?
If image rewriting is enabled, the service recognizes these filenames:
- Series-level:
poster.*,clearlogo.* - Season-level:
seasonNN-poster.*(e.g.,season01-poster.jpg) - Specials:
season-specials-poster.*
Supported extensions: .jpg, .jpeg, .png.
When rewriting, the tool selects the first TMDB image that exactly matches your preferred language-country codes in order. It then writes the image atomically, embeds a small JSON marker in the image metadata indicating the TMDB file path and language, and normalizes the extension to match TMDB while preserving the original filename stem. If the existing image already matches the selected candidate (based on the embedded marker), it is skipped to avoid unnecessary writes.
File Permissions (Important!)
The --user parameter in the Docker examples above is critical for
maintaining proper file permissions. Here's why:
- Without
--user, Docker runs the container as root by default - Files created by the container (rewritten .nfo files, cache, backups) will be owned by root
- This can cause permission issues when trying to access files from your host system
- Sonarr or other applications may not be able to read the rewritten files
For Docker run:
--user $(id -u):$(id -g)
This automatically uses your current user's UID and GID.
For Docker Compose:
user: "${UID:-1000}:${GID:-1000}"
Set the UID and GID environment variables first:
export UID=$(id -u)
export GID=$(id -g)
docker compose up -d
To check your user ID:
id -u # Shows your user ID (UID)
id -g # Shows your group ID (GID)
Highly Recommended Volume Mappings
While only the media directories are strictly required, these volumes will improve your experience:
# Media directories (required - mount each root dir separately)
-v /path/to/your/tv/shows:/tv
-v /path/to/your/anime:/anime
# Cache directory (highly recommended - persists translation cache across
# container restarts)
-v sonarr-metadata-cache:/app/cache
# Backup directory (highly recommended - keeps original .nfo and image files safe)
-v sonarr-metadata-backups:/app/backups
Without these volumes, you'll lose cached translations and backups when the container is recreated.
Running it
The Docker container runs automatically once started. You can check the logs:
docker logs sonarr-metadata-rewrite
You'll see something like:
π Starting Sonarr Metadata Rewrite...
β
TMDB API key loaded (ending in ...xyz)
π Monitoring directory: /tv
π Monitoring directory: /anime
π Preferred languages: ['zh-CN', 'ja-JP']
β
Service started successfully
The container runs in the background. When Sonarr updates your shows, this will automatically translate the metadata files.
To stop the container:
docker stop sonarr-metadata-rewrite
How it works
The service has a few main parts:
File monitoring - Uses Python's watchdog to watch for file changes
(.nfo and image files) in real-time
TMDB integration - Extracts TMDB IDs from Sonarr's XML files and fetches translations via their API
Smart caching - Stores translations locally so it doesn't hit the API repeatedly for the same content
Batch processing - Also scans your existing files periodically to catch anything it might have missed (both .nfo and image files)
Safe file handling - Does atomic writes, embeds tiny markers in images to avoid reprocessing, and keeps backups so you never lose data
The whole thing is designed to be invisible - just set it up once and forget about it.
Restoring originals (rollback)
If you want to restore Sonarr's original English metadata (and images), you can use the built-in rollback functionality to automatically restore all original files from backups.
Automated Rollback (Recommended)
Set the service to rollback mode, which will restore all original files from backups:
Docker:
docker run --rm \
--user $(id -u):$(id -g) \
-e SERVICE_MODE=rollback \
-e TMDB_API_KEY=your_api_key_here \
-e REWRITE_ROOT_DIRS=/tv,/anime \
-e PREFERRED_LANGUAGES=zh-CN,ja-JP \
-v /path/to/tv/shows:/tv \
-v /path/to/anime:/anime \
-v sonarr-metadata-backups:/app/backups \
kfstorm/sonarr-metadata-rewrite:latest
Docker Compose:
# Temporarily change your docker-compose.yml
environment:
- SERVICE_MODE=rollback
# ... other environment variables
The rollback service will:
- Restore all original .nfo and image files from the backup directory
- Skip any shows/episodes that have been deleted
- Log the restoration progress
- Hang after completion (preventing restart loops)
Important: The service will hang after rollback completion to prevent restart loops. Stop it manually when done:
docker stop sonarr-metadata-rewrite
Development
Want to hack on this? Cool!
Setup
git clone https://github.com/kfstorm/sonarr-metadata-rewrite.git
cd sonarr-metadata-rewrite
./scripts/setup-dev.sh
echo "TMDB_API_KEY=your_key" > .env
Run tests:
./scripts/run-unit-tests.sh # Fast unit tests
./scripts/run-integration-tests.sh # Slower integration tests (needs Docker)
./scripts/combine-coverage.sh # Combine coverage reports
Code quality:
./scripts/lint.sh # Fix formatting and imports
./scripts/lint.sh --check # Just check, don't fix
The codebase uses modern Python tooling - uv for dependencies, Black for
formatting, Ruff for linting, MyPy for type checking. There are pre-commit
hooks that run all the checks automatically.
Troubleshooting
Service won't start
- Check that your TMDB API Read Access Token is valid (see authentication section above)
- Make sure the media directory exists and is readable
- Look at the error messages, they're usually pretty helpful
Authentication errors (401 Unauthorized)
If you see "401 Unauthorized" errors:
- Most common cause: You're using an "API Key" instead of an "API Read Access Token"
- Solution: Get the correct "API Read Access Token" from TMDB API settings
- How to tell: API Read Access Tokens are much longer than API Keys
- Reference: TMDB Authentication Guide
Files aren't getting translated
- Verify your
.nfofiles actually contain TMDB IDs (look for<uniqueid type="tmdb">123456</uniqueid>) - Check that TMDB has translations in your preferred language for that content
- Make sure Sonarr is actually writing new
.nfofiles (try refreshing a series)
File permission issues
If you're seeing permission denied errors or files owned by root:
- Make sure you're using the
--userparameter in your Docker command - For existing containers, stop and recreate with the proper
--usersetting - Check file ownership:
ls -la /path/to/your/tv/shows/ - Files should be owned by your user, not root
Fix for existing root-owned files:
# Stop the container first
docker stop sonarr-metadata-rewrite
# Fix ownership (replace with your actual path and user)
sudo chown -R $(id -u):$(id -g) /path/to/your/tv/shows/
# Restart with proper --user parameter
API rate limits
- TMDB has rate limits, and the service includes automatic retry logic with exponential backoff
- Most requests are cached, so they only happen once per series/episode
- If you have a huge library, the service will automatically handle rate limits and pace itself
- You can configure retry behavior with
TMDB_MAX_RETRIES,TMDB_INITIAL_RETRY_DELAY, andTMDB_MAX_RETRY_DELAYsettings
License
MIT License - do whatever you want with it.
This project scratches my own itch of wanting Chinese metadata for my media library. If it helps you too, that's awesome! Feel free to contribute or report issues.