{"id":37545208,"url":"https://github.com/kfstorm/sonarr-metadata-rewrite","last_synced_at":"2026-03-14T09:14:55.556Z","repository":{"id":311769621,"uuid":"1043733963","full_name":"kfstorm/sonarr-metadata-rewrite","owner":"kfstorm","description":"Automatically translates Sonarr TV show metadata files to your preferred language using TMDB","archived":false,"fork":false,"pushed_at":"2025-10-26T11:01:05.000Z","size":385,"stargazers_count":5,"open_issues_count":8,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-26T13:04:41.057Z","etag":null,"topics":["automation","docker","jellyfin","media-server","metadata","nfo","plex","python","sonarr","tmdb","translation","tv-shows"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kfstorm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-24T14:04:21.000Z","updated_at":"2025-10-26T11:01:07.000Z","dependencies_parsed_at":"2025-08-26T16:57:15.775Z","dependency_job_id":"9fee0e1d-8ef7-4bba-b9dc-35c270d55706","html_url":"https://github.com/kfstorm/sonarr-metadata-rewrite","commit_stats":null,"previous_names":["kfstorm/sonarr-metadata-rewrite"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/kfstorm/sonarr-metadata-rewrite","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfstorm%2Fsonarr-metadata-rewrite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfstorm%2Fsonarr-metadata-rewrite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfstorm%2Fsonarr-metadata-rewrite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfstorm%2Fsonarr-metadata-rewrite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kfstorm","download_url":"https://codeload.github.com/kfstorm/sonarr-metadata-rewrite/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfstorm%2Fsonarr-metadata-rewrite/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28478048,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T06:30:42.265Z","status":"ssl_error","status_checked_at":"2026-01-16T06:30:16.248Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","docker","jellyfin","media-server","metadata","nfo","plex","python","sonarr","tmdb","translation","tv-shows"],"created_at":"2026-01-16T08:50:17.362Z","updated_at":"2026-03-14T09:14:55.548Z","avatar_url":"https://github.com/kfstorm.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sonarr Metadata Rewrite\n\n![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/kfstorm/2eafe27677e3a2ebbda29cbd026ff32b/raw/coverage.json)\n[![CI](https://github.com/kfstorm/sonarr-metadata-rewrite/actions/workflows/ci.yml/badge.svg)](https://github.com/kfstorm/sonarr-metadata-rewrite/actions/workflows/ci.yml)\n\nEver been frustrated that Sonarr only gives you English metadata for your TV\nshows? This tool fixes that by watching for Sonarr's `.nfo` files and\nautomatically replacing them with translations in whatever language you prefer.\nIt can also rewrite poster and clearlogo images to language-specific variants\nwhen available (e.g., `poster.jpg`, `clearlogo.png`, `season01-poster.jpg`).\n\nIt's my solution to [Sonarr Issue #269](\nhttps://github.com/Sonarr/Sonarr/issues/269) and\n[Sonarr Issue #6663](https://github.com/Sonarr/Sonarr/issues/6663)\n. It turns out a lot of people want\ntheir media metadata in their native language, but Sonarr doesn't support this\nnatively.\n\n## What it does\n\nThe tool runs as a background service that:\n\n- Watches your media folders for when Sonarr creates or updates `.nfo` files\n- Grabs the TMDB ID from those files and fetches translations from TMDB's API\n- Replaces the English metadata with your preferred language(s)\n- Rewrites poster and clearlogo images based on your preferred language-country\n  codes (e.g., `en-US`, `ja-JP`) when such variants exist on TMDB\n- Does this fast enough that you barely notice it happening\n- Keeps backups of the original files in case you want them back\n- Caches everything so it doesn't spam TMDB's API\n\nYou can configure multiple languages with fallback priority - like Chinese\nfirst, then Japanese, then just leave it in English if neither is available.\n\n## Installation\n\nYou'll need Docker and a **TMDB API Read Access Token** from\n[themoviedb.org](https://www.themoviedb.org/settings/api).\n\n⚠️ **Important**: This application requires an **API Read Access Token**, NOT an\n\"API Key\". Get your API Read Access Token from the\n[TMDB API settings page](https://www.themoviedb.org/settings/api)\nunder the \"API Read Access Token\" section. Learn more about authentication at\nthe [TMDB Authentication Guide](https://developer.themoviedb.org/docs/authentication-application).\n\n**Docker (recommended):**\n\n```bash\ndocker run -d \\\n  --name sonarr-metadata-rewrite \\\n  --user $(id -u):$(id -g) \\\n  -e TMDB_API_KEY=your_api_key_here \\\n  -e REWRITE_ROOT_DIR=/media \\\n  -e PREFERRED_LANGUAGES=zh-CN,ja-JP \\\n  -v /path/to/your/tv/shows:/media \\\n  -v sonarr-metadata-cache:/app/cache \\\n  -v sonarr-metadata-backups:/app/backups \\\n  --restart unless-stopped \\\n  kfstorm/sonarr-metadata-rewrite:latest\n```\n\n**Docker Compose:**\n\n```yaml\nversion: '3.8'\nservices:\n  sonarr-metadata-rewrite:\n    image: kfstorm/sonarr-metadata-rewrite:latest\n    container_name: sonarr-metadata-rewrite\n    user: \"${UID:-1000}:${GID:-1000}\"\n    environment:\n      - TMDB_API_KEY=your_api_key_here\n      - REWRITE_ROOT_DIR=/media\n      - PREFERRED_LANGUAGES=zh-CN,ja-JP\n      # Optional: customize cache duration (default: 30 days)\n      # - CACHE_DURATION_HOURS=168\n    volumes:\n      - /path/to/your/tv/shows:/media\n      - sonarr-metadata-cache:/app/cache\n      - sonarr-metadata-backups:/app/backups\n    restart: unless-stopped\n\nvolumes:\n  sonarr-metadata-cache:\n  sonarr-metadata-backups:\n```\n\n## Configuration\n\n### Required Settings\n\n```bash\nTMDB_API_KEY=your_api_read_access_token_here  # Your TMDB API Read Access Token\nREWRITE_ROOT_DIR=/media              # Path to your TV shows (inside container)\n# Comma-separated language codes in priority order\nPREFERRED_LANGUAGES=zh-CN,ja-JP\n```\n\n### Optional Settings (with defaults)\n\n```bash\n# Scanning \u0026 Monitoring\nPERIODIC_SCAN_INTERVAL_SECONDS=86400  # How often to scan directory (default: daily)\nENABLE_FILE_MONITOR=true              # Real-time file monitoring (default: true)\nENABLE_FILE_SCANNER=true              # Periodic directory scanning (default: true)\n\n# NFO \u0026 Images\nENABLE_NFO_REWRITE=true               # Translate NFO files (default: true)\nENABLE_IMAGE_REWRITE=true             # Rewrite posters/clearlogos (default: true)\n\n# Caching \u0026 Storage\nCACHE_DURATION_HOURS=720              # Cache translations (default: 30 days)\nCACHE_DIR=./cache                     # Cache directory (default: ./cache)\n\n# TMDB API Rate Limiting\nTMDB_MAX_RETRIES=3                    # Max retry attempts (default: 3)\nTMDB_INITIAL_RETRY_DELAY=1.0          # Initial retry delay (default: 1.0)\nTMDB_MAX_RETRY_DELAY=60.0             # Max retry delay (default: 60.0)\n\n# Backup\nORIGINAL_FILES_BACKUP_DIR=./backups   # Backup original files (default: ./backups)\n                                      # Applies to both .nfo and images. Set to\n                                      # empty string to disable backups.\n\n# Service Mode\nSERVICE_MODE=rewrite                  # Service mode: 'rewrite' or 'rollback'\n                                      # (default: rewrite)\n```\n\n**Language codes** are ISO 639-1 format:\n- `zh-CN` - Chinese (Simplified)\n- `ja-JP` - Japanese\n- `ko-KR` - Korean\n- `fr-FR` - French\n- `de-DE` - German\n- `es-ES` - Spanish\n\nYou can list multiple languages separated by commas - it'll try them in\norder.\n\n### Which images are rewritten?\n\nIf image rewriting is enabled, the service recognizes these filenames:\n\n- Series-level: `poster.*`, `clearlogo.*`\n- Season-level: `seasonNN-poster.*` (e.g., `season01-poster.jpg`)\n- Specials: `season-specials-poster.*`\n\nSupported extensions: `.jpg`, `.jpeg`, `.png`.\n\nWhen rewriting, the tool selects the first TMDB image that exactly matches your\npreferred language-country codes in order. It then writes the image atomically,\nembeds a small JSON marker in the image metadata indicating the TMDB file path\nand language, and normalizes the extension to match TMDB while preserving the\noriginal filename stem. If the existing image already matches the selected\ncandidate (based on the embedded marker), it is skipped to avoid unnecessary\nwrites.\n\n### File Permissions (Important!)\n\nThe `--user` parameter in the Docker examples above is **critical** for\nmaintaining proper file permissions. Here's why:\n\n- Without `--user`, Docker runs the container as root by default\n- Files created by the container (rewritten .nfo files, cache, backups) will be\n  owned by root\n- This can cause permission issues when trying to access files from your host system\n- Sonarr or other applications may not be able to read the rewritten files\n\n**For Docker run:**\n\n```bash\n--user $(id -u):$(id -g)\n```\n\nThis automatically uses your current user's UID and GID.\n\n**For Docker Compose:**\n\n```yaml\nuser: \"${UID:-1000}:${GID:-1000}\"\n```\n\nSet the UID and GID environment variables first:\n\n```bash\nexport UID=$(id -u)\nexport GID=$(id -g)\ndocker compose up -d\n```\n\n**To check your user ID:**\n\n```bash\nid -u  # Shows your user ID (UID)\nid -g  # Shows your group ID (GID)\n```\n\n### Highly Recommended Volume Mappings\n\nWhile only the media directory is strictly required, these volumes will\nimprove your experience:\n\n```bash\n# Media directory (required)\n-v /path/to/your/tv/shows:/media\n\n# Cache directory (highly recommended - persists translation cache across\n# container restarts)\n-v sonarr-metadata-cache:/app/cache\n\n# Backup directory (highly recommended - keeps original .nfo and image files safe)\n-v sonarr-metadata-backups:/app/backups\n```\n\nWithout these volumes, you'll lose cached translations and backups when the\ncontainer is recreated.\n\n## Running it\n\nThe Docker container runs automatically once started. You can check the logs:\n\n```bash\ndocker logs sonarr-metadata-rewrite\n```\n\nYou'll see something like:\n\n```text\n🚀 Starting Sonarr Metadata Rewrite...\n✅ TMDB API key loaded (ending in ...xyz)\n📁 Monitoring directory: /media\n🌍 Preferred languages: ['zh-CN', 'ja-JP']\n✅ Service started successfully\n```\n\nThe container runs in the background. When Sonarr updates your shows, this\nwill automatically translate the metadata files.\n\nTo stop the container:\n\n```bash\ndocker stop sonarr-metadata-rewrite\n```\n\n## How it works\n\nThe service has a few main parts:\n\n**File monitoring** - Uses Python's `watchdog` to watch for file changes\n(.nfo and image files) in real-time\n\n**TMDB integration** - Extracts TMDB IDs from Sonarr's XML files and\nfetches translations via their API\n\n**Smart caching** - Stores translations locally so it doesn't hit the API\nrepeatedly for the same content\n\n**Batch processing** - Also scans your existing files periodically to catch\nanything it might have missed (both .nfo and image files)\n\n**Safe file handling** - Does atomic writes, embeds tiny markers in images to\navoid reprocessing, and keeps backups so you never lose data\n\nThe whole thing is designed to be invisible - just set it up once and forget\nabout it.\n\n## Restoring originals (rollback)\n\nIf you want to restore Sonarr's original English metadata (and images), you can\nuse the built-in rollback functionality to automatically restore all original\nfiles from backups.\n\n### Automated Rollback (Recommended)\n\nSet the service to rollback mode, which will restore all original files from backups:\n\n**Docker:**\n\n```bash\ndocker run --rm \\\n  --user $(id -u):$(id -g) \\\n  -e SERVICE_MODE=rollback \\\n  -e TMDB_API_KEY=your_api_key_here \\\n  -e REWRITE_ROOT_DIR=/media \\\n  -e PREFERRED_LANGUAGES=zh-CN,ja-JP \\\n  -v /path/to/your/tv/shows:/media \\\n  -v sonarr-metadata-backups:/app/backups \\\n  kfstorm/sonarr-metadata-rewrite:latest\n```\n\n**Docker Compose:**\n\n```yaml\n# Temporarily change your docker-compose.yml\nenvironment:\n  - SERVICE_MODE=rollback\n  # ... other environment variables\n```\n\nThe rollback service will:\n- Restore all original .nfo and image files from the backup directory\n- Skip any shows/episodes that have been deleted\n- Log the restoration progress\n- Hang after completion (preventing restart loops)\n\n**Important:** The service will hang after rollback completion to prevent\nrestart loops. Stop it manually when done:\n\n```bash\ndocker stop sonarr-metadata-rewrite\n```\n\n## Development\n\nWant to hack on this? Cool!\n\n### Setup\n\n```bash\ngit clone https://github.com/kfstorm/sonarr-metadata-rewrite.git\ncd sonarr-metadata-rewrite\n./scripts/setup-dev.sh\necho \"TMDB_API_KEY=your_key\" \u003e .env\n```\n\n**Run tests:**\n\n```bash\n./scripts/run-unit-tests.sh          # Fast unit tests\n./scripts/run-integration-tests.sh   # Slower integration tests (needs Docker)\n./scripts/combine-coverage.sh        # Combine coverage reports\n```\n\n**Code quality:**\n\n```bash\n./scripts/lint.sh                    # Fix formatting and imports\n./scripts/lint.sh --check            # Just check, don't fix\n```\n\nThe codebase uses modern Python tooling - `uv` for dependencies, Black for\nformatting, Ruff for linting, MyPy for type checking. There are pre-commit\nhooks that run all the checks automatically.\n\n## Troubleshooting\n\n### Service won't start\n\n- Check that your TMDB API Read Access Token is valid (see authentication\n  section above)\n- Make sure the media directory exists and is readable\n- Look at the error messages, they're usually pretty helpful\n\n### Authentication errors (401 Unauthorized)\n\nIf you see \"401 Unauthorized\" errors:\n\n- **Most common cause**: You're using an \"API Key\" instead of an\n  \"API Read Access Token\"\n- **Solution**: Get the correct \"API Read Access Token\" from\n  [TMDB API settings](https://www.themoviedb.org/settings/api)\n- **How to tell**: API Read Access Tokens are much longer than API Keys\n- **Reference**: [TMDB Authentication Guide](https://developer.themoviedb.org/docs/authentication-application)\n\n### Files aren't getting translated\n\n- Verify your `.nfo` files actually contain TMDB IDs (look for\n  `\u003cuniqueid type=\"tmdb\"\u003e123456\u003c/uniqueid\u003e`)\n- Check that TMDB has translations in your preferred language for that content\n- Make sure Sonarr is actually writing new `.nfo` files (try refreshing a series)\n\n### File permission issues\n\nIf you're seeing permission denied errors or files owned by root:\n\n- Make sure you're using the `--user` parameter in your Docker command\n- For existing containers, stop and recreate with the proper `--user` setting\n- Check file ownership: `ls -la /path/to/your/tv/shows/`\n- Files should be owned by your user, not root\n\n**Fix for existing root-owned files:**\n\n```bash\n# Stop the container first\ndocker stop sonarr-metadata-rewrite\n\n# Fix ownership (replace with your actual path and user)\nsudo chown -R $(id -u):$(id -g) /path/to/your/tv/shows/\n\n# Restart with proper --user parameter\n```\n\n### API rate limits\n\n- TMDB has rate limits, and the service includes automatic retry logic with\n  exponential backoff\n- Most requests are cached, so they only happen once per series/episode\n- If you have a huge library, the service will automatically handle rate\n  limits and pace itself\n- You can configure retry behavior with `TMDB_MAX_RETRIES`,\n  `TMDB_INITIAL_RETRY_DELAY`, and `TMDB_MAX_RETRY_DELAY` settings\n\n## License\n\nMIT License - do whatever you want with it.\n\n---\n\nThis project scratches my own itch of wanting Chinese metadata for my media\nlibrary. If it helps you too, that's awesome! Feel free to contribute or\nreport issues.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkfstorm%2Fsonarr-metadata-rewrite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkfstorm%2Fsonarr-metadata-rewrite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkfstorm%2Fsonarr-metadata-rewrite/lists"}