Normalize movie and TV series names for Jellyfin. Scans a media library, classifies files, validates parsed results, and looks up provider IDs from TMDb and TVDB.
Current status: This release is analysis-only. It scans, parses, validates, resolves basic provider matches, and writes review reports. Rename planning, dry-run execution, real filesystem changes, persistent approvals, and the interactive review UI are not implemented yet.
For the full project design, naming conventions, and implementation phases, see Project Description.
- Python 3.14 (
>=3.14,<3.15) - uv for dependency management
git clone https://github.com/pokornyIt/media-library-normalizer.git
cd media-library-normalizer
uv sync# Scan only — no API keys needed
uv run media-library-normalizer scan
# Full parse with provider lookup — requires API keys
export $(cat .env | grep -v '^#' | xargs)
uv run media-library-normalizer parseThe parse command:
- Scans the media library
- Classifies and normalizes filenames
- Validates parsed results
- Looks up provider IDs — first checks embedded IDs in folder names, then the local cache, then online APIs in this order: movie -> TMDb; tv_episode (series-level lookup) -> TMDb TV, then TVDB
- Writes
data/workspace/reports/parse-review-report.json - Writes
data/workspace/reports/parse-review-report.htmlfor human-friendly review triage - Writes
data/workspace/reports/unresolved-provider-report.jsonfor items without a resolved ID - Writes
data/workspace/reports/unresolved-provider-report.htmlfor human-friendly unresolved triage
All settings are read from environment variables. Create a .env file in the project root:
# Paths
MLN_LIBRARY_PATH=./data/library
MLN_WORKSPACE_PATH=./data/workspace
# Logging
MLN_LOG_LEVEL=INFO
MLN_LOG_FORMAT=text
# Safety
MLN_DRY_RUN=true
# Provider API keys
MLN_TMDB_API_KEY=your-tmdb-api-key
MLN_TVDB_API_KEY=your-tvdb-api-key
# How often to log provider lookup progress (default: every 100 items)
MLN_PROVIDER_LOOKUP_PROGRESS_INTERVAL=100| Variable | Default | Description |
|---|---|---|
MLN_APP_NAME |
media-library-normalizer |
Application name used in logs |
MLN_LIBRARY_PATH |
./data/library |
Root path of the media library to scan |
MLN_WORKSPACE_PATH |
./data/workspace |
Root path for generated files |
MLN_CACHE_PATH |
{workspace}/cache |
Provider ID cache directory |
MLN_REPORTS_PATH |
{workspace}/reports |
Report output directory |
MLN_MANIFESTS_PATH |
{workspace}/manifests |
Rename manifest directory |
MLN_LOGS_PATH |
{workspace}/logs |
Log file directory |
MLN_LOG_LEVEL |
INFO |
Logging level (DEBUG, INFO, WARNING, ERROR) |
MLN_LOG_FORMAT |
text |
Log format (text or json) |
MLN_DRY_RUN |
true |
Disable destructive operations by default |
MLN_TMDB_API_KEY |
(none) | TMDb API key for online movie lookup |
MLN_TVDB_API_KEY |
(none) | TVDB API key for online TV series lookup |
MLN_PROVIDER_LOOKUP_PROGRESS_INTERVAL |
100 |
Log progress every N items during provider lookup |
- Register at themoviedb.org and create a free account.
- Go to Settings → API and copy your API Key (v3 auth).
- Set
MLN_TMDB_API_KEYto that value.
TVDB API keys are project-based, not personal. You must register your application:
-
Visit thetvdb.com/api-information and create an account.
-
Click Sign Up to register a new project and fill in:
-
Company / Project Revenue:
Less than $50k per year -
Company or Project Name:
media-library-normalizer -
Description:
Non-commercial open-source tool for normalizing and validating media library names for Jellyfin. Uses TVDB data for TV series metadata matching. Project: https://github.com/pokornyIt/media-library-normalizer
-
-
Copy the API Key and set
MLN_TVDB_API_KEYto that value.
TVDB's free tier requires attribution. Comply with their licensing terms.
Note: Without API keys, provider lookup uses only the local cache. Previously resolved items are still matched; new items are left unresolved.
Scans the media library and prints a summary.
uv run media-library-normalizer scanOutput:
Discovered 13342 media files.
- Filmy/Akcni/Avatar (2009) - CZ.mkv
...
Scans, parses, validates, and performs provider ID lookup. This is the main analysis command.
export $(cat .env | grep -v '^#' | xargs)
uv run media-library-normalizer parse
# or with custom report path
uv run media-library-normalizer parse --output /path/to/custom-report.jsonOutput:
Parsed 13342 media files.
Validation summary: passed=13127, review_needed=215, failed=0
Provider lookup summary: resolved=12697 (cache=12695, online=0, embedded=2), unresolved=430
Review report written to: data/workspace/reports/parse-review-report.json
Review HTML report written to: data/workspace/reports/parse-review-report.html
Unresolved provider report written to: data/workspace/reports/unresolved-provider-report.json
Unresolved HTML report written to: data/workspace/reports/unresolved-provider-report.html
Provider ID resolution order:
- Embedded ID in folder name — e.g.
[imdbid-tt1234567]or[tmdbid-12345] - Local provider cache (
data/workspace/cache/provider_ids.json) - Online API (if API keys are configured), in this order: movie -> TMDb; tv_episode (series-level lookup) -> TMDb TV, then TVDB
Scans and parses, then writes a full JSON report of all parsed items.
uv run media-library-normalizer report-scan
uv run media-library-normalizer report-scan --output /custom/path/report.jsonDefault output: data/workspace/reports/report-scan-results.json
Initializes an empty provider cache file. Use this to reset cached provider matches.
uv run media-library-normalizer bootstrap-providersOutput:
Provider cache bootstrapped: data/workspace/cache/provider_ids.json
Displays current runtime settings.
uv run media-library-normalizer infoChecks whether a given path exists on the filesystem. Useful for diagnosing path configuration.
uv run media-library-normalizer validate-path /path/to/check| Tool | Purpose |
|---|---|
Python 3.14 (>=3.14,<3.15) |
Core language |
| uv | Dependency and environment management |
| ruff | Linting and formatting |
| pyright | Static type checking |
| pytest | Test framework |
# Run all tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=src/media_library_normalizer --cov-report=term-missing
# Lint and format
uv run ruff check src/ tests/
uv run ruff format src/ tests/
# Type check
uv run pyright