Русская версия: README.ru.md
Production-oriented Python script for normalizing movie files and movie folders for Jellyfin and similar media libraries.
Unlike the TV-series version, this script keeps the existing high-level folder structure intact and renames movies conservatively inside that structure.
It supports common real-world layouts such as:
Movie.mkv
Movie.srt
Movie Folder/
├── movie.mkv
└── movie.srt
Collection/
├── Movie One.mkv
├── Movie One.eng.srt
├── Movie Two/
│ ├── movie two.mkv
│ └── movie two.forced_ru.srt
└── Movie Three.mkv
This script uses:
The script currently contains a bundled shared Kinopoisk Api Unofficial key in the source code for quick testing.
This is convenient for trying the script, but for regular use it is strongly recommended to replace it with your own Kinopoisk Api Unofficial key.
Why:
- shared keys can hit rate limits
- shared keys may stop working unexpectedly
- your own key is more reliable and predictable
For TMDb, you should set your own bearer token in:
DEFAULT_TMDB_BEARER = "..."If DEFAULT_TMDB_BEARER is empty or still contains the placeholder value, the script stops immediately with a clear error message.
If the token is present but invalid, the script shows the TMDb API error returned by the server.
TMDb is effectively required for real usage because:
- it is the primary source for the
intlprofile - it is used for enrichment, external IDs, and localized titles in the
ruprofile - multilingual title fields depend on TMDb translations
In short:
- bundled Kinopoisk key is acceptable for quick testing
- your own Kinopoisk key is strongly recommended
- your own TMDb bearer token is required
The script is designed to be interactive, conservative, and filesystem-safe:
- it can preview changes before applying them
- it asks for confirmation per movie
- it avoids aggressive guessing in ambiguous cases
- it keeps non-empty directories instead of deleting them blindly
- it preserves the existing high-level movie library structure
The script supports two metadata profiles:
ruintl
- primary source: Kinopoisk
- enrichment: TMDb
- IMDb id: from Kinopoisk when available, otherwise from TMDb
- primary title is usually Russian
Resolution flow:
kp -> tmdb ; imdb = kp if present else tmdb
- primary source: TMDb
- IMDb id: from TMDb
- Kinopoisk is not used as the primary metadata source
- primary title is usually English / international
Resolution flow:
tmdb -> imdb
The script always renders IDs in canonical Jellyfin-friendly form:
[kp-1234567][tmdbid-123456][imdbid-tt1234567]
Older input forms are still recognized during parsing.
Supported incoming variants include:
-
Kinopoisk:
[kp1234567][kp-1234567]
-
TMDb:
[tmdb12345][tmdb-12345][tmdbid-12345]
-
IMDb:
[tt1234567][imdbidtt1234567][imdbid=tt1234567][imdbid-tt1234567]
- searches movies via Kinopoisk or TMDb depending on profile
- supports direct
kpid input during manual Kinopoisk selection - supports direct
tmdbid input during manual TMDb selection - reuses IDs already embedded in file and folder names
- supports tolerant parsing of old and new ID formats
The script does not force a new folder per movie.
It keeps the existing structure intact:
- standalone movie files stay in their current folder
- single-movie folders stay as folders
- collection folders stay as collections
- nested movie folders inside collections stay inside the collection
Supports collection layouts such as:
- multiple movie files in one folder
- mixed collections where some movies are files and some are in subfolders
- subfolders that contain video plus paired subtitles
Each movie inside a collection is processed separately.
The collection folder itself is not treated as one movie.
--update-movie-folders
This mode:
- scans already normalized movie folders
- refreshes missing metadata and IDs
- renames only the movie folder when needed
- does not move movie or subtitle files
This is a safe metadata update mode.
Supports subtitle files:
.srt.ass.ssa.sub
Legacy subtitle metadata detection still recognizes common tokens such as:
- language:
ru,rus,russianen,eng,english
- subtype:
forcedfullsdh
External subtitles are handled conservatively:
- the matching video filename is used as the base
- everything after the video stem is treated as the subtitle suffix
- the suffix is preserved in the final subtitle filename
- suffix separators are normalized to dots
Examples:
Movie.srt→Movie.srtMovie_ru.srt→Movie.ru.srtMovie.en.srt→Movie.en.srtMovie_forced.rus.srt→Movie.forced.rus.srtMovie_full.V1.rus.srt→Movie.full.V1.rus.srt
For metadata search, the script uses a normalized query:
- dots, dashes, underscores, colons, and noisy punctuation are treated as separators
- if a year is detected, everything after the year is dropped from the search query
- release garbage after the year does not pollute search
- existing IDs still take precedence over text search
- ambiguous subtitle-only files are not guessed aggressively
- non-empty directories are not deleted
- already normalized movies are left untouched
Ctrl+Cor menu option0exits cleanly- cache and operations log failures are handled more gracefully on flaky or read-only paths
Default output naming:
{title} ({original_title}) ({year}) [kp-{kp}][tmdbid-{tmdb}][imdbid-{tt}]{ext}
{title} ({original_title}) ({year}) [kp-{kp}][tmdbid-{tmdb}][imdbid-{tt}]{sub_suffix}{ext}
{title} ({original_title}) ({year}) [tmdbid-{tmdb}][imdbid-{tt}]{ext}
{title} ({original_title}) ({year}) [tmdbid-{tmdb}][imdbid-{tt}]{sub_suffix}{ext}
Example inside an existing folder:
Достать ножи/
├── Достать ножи - Стеклянная луковица (Glass Onion) (2022) [kp-1343908][tmdbid-661374][imdbid-tt11564570].avi
├── Достать ножи - Стеклянная луковица (Glass Onion) (2022) [kp-1343908][tmdbid-661374][imdbid-tt11564570].forced.rus.srt
└── Достать ножи - Стеклянная луковица (Glass Onion) (2022) [kp-1343908][tmdbid-661374][imdbid-tt11564570].full.eng.srt
- Python 3.10+
- internet access for Kinopoisk and TMDb requests
No external Python dependencies are required.
python MoviesRenamer.py /path/to/Moviespython MoviesRenamer.py /path/to/Movies --dry-runpython MoviesRenamer.py /path/to/Movies --dry-firstpython MoviesRenamer.py /path/to/Movies --metadata-profile intlpython MoviesRenamer.py /path/to/Movies --mode manualpython MoviesRenamer.py /path/to/Movies --update-movie-folders --dry-runroot Root path to Movies
--cache Cache json path
--ops-log Operations log file path
--metadata-profile ru | intl
--mode smart | manual
--dry-first Show resulting rename plan for each movie and ask confirmation
--dry-run Do not modify filesystem; only print/log planned operations
--update-movie-folders Only update existing movie folder names using fresh metadata
For each detected movie item, the script:
- scans files
- tries to infer title from file/folder structure
- resolves movie metadata using the active profile
- builds a rename plan
- optionally shows preview
- applies changes after confirmation
Typical options:
0— exit the whole script98— skip current search result selection99— retry same searchEnter— accept the only result, when there is exactly one- any other text — new search query or direct
kp/tmdbid
Examples of valid manual input:
kp12345671234567tmdb661374Glass Onion
With --dry-first, the script shows a per-movie preview.
If nothing needs changing:
Already normalized: no moves needed
The script writes a human-readable operations log.
Default log file:
MoviesRenamer.operations.log
Typical entries:
============================================================
MOVIE Glass.Onion.A.Knives.Out.Mystery.2022.WEB-DLRip_от New-Team -> Достать ножи - Стеклянная луковица (Glass Onion) (2022) [kp-1343908][tmdbid-661374][imdbid-tt11564570]
MOVE Glass.Onion.A.Knives.Out.Mystery.2022.WEB-DLRip_от New-Team.avi => Достать ножи - Стеклянная луковица (Glass Onion) (2022) [kp-1343908][tmdbid-661374][imdbid-tt11564570].avi
MOVE Glass.Onion.A.Knives.Out.Mystery.2022.WEB-DLRip_от New-Team_[forced, rus].srt => Достать ножи - Стеклянная луковица (Glass Onion) (2022) [kp-1343908][tmdbid-661374][imdbid-tt11564570].forced.rus.srt
SUMMARY ops=2 removed_dirs=0 kept_dirs=0 errors=0
RENAME— rename movie folderMOVE— normal recognized rename or moveDELDIR— removed empty directoryKEEPDIR— directory left in place because it is not emptyERROR— operation failed
The script caches resolved metadata to reduce repeated manual work.
Default cache file:
.movies_rename_cache.json
Cached data may include:
- chosen title
- Kinopoisk ID
- TMDb ID
- IMDb ID
- year
- original title
- localized titles
When testing template or localization changes, it is recommended to use a fresh cache file or delete the old one.
If cache saving fails on a network path or due to permissions, the script prints a warning and continues shutdown cleanly.
The script uses templates for final movie names.
RU_MOVIE_FOLDER_TEMPLATE = "{title} ({original_title}) ({year}) [kp-{kp}][tmdbid-{tmdb}][imdbid-{tt}]"
INTL_MOVIE_FOLDER_TEMPLATE = "{title} ({original_title}) ({year}) [tmdbid-{tmdb}][imdbid-{tt}]"
RU_MOVIE_FILE_TEMPLATE = "{title} ({original_title}) ({year}) [kp-{kp}][tmdbid-{tmdb}][imdbid-{tt}]{ext}"
INTL_MOVIE_FILE_TEMPLATE = "{title} ({original_title}) ({year}) [tmdbid-{tmdb}][imdbid-{tt}]{ext}"
RU_SUBTITLE_FILE_TEMPLATE = "{title} ({original_title}) ({year}) [kp-{kp}][tmdbid-{tmdb}][imdbid-{tt}]{sub_suffix}{ext}"
INTL_SUBTITLE_FILE_TEMPLATE = "{title} ({original_title}) ({year}) [tmdbid-{tmdb}][imdbid-{tt}]{sub_suffix}{ext}"Primary fields:
{title}— primary display title for the active metadata profile{original_title}— original or provider-original title{title_local}— localized title for the active profile context
Localized title fields:
{title_ru}{title_en}{title_de}{title_fr}{title_ko}- other
title_XXfields, including regional variants such as{title_pt_BR}or{title_zh_CN}
Other fields:
{kp}— Kinopoisk ID without prefix{tmdb}— TMDb ID without prefix{tt}— IMDb ID includingtt{year}— release year{lang}— subtitle language token{subtype}— subtitle subtype token{sub_suffix}— preserved subtitle suffix after the matching video stem{ext}— file extension including dot
{title} is always the primary field.
Before rendering:
- if
{original_title}is equal to{title}, it is cleared - if any
{title_XX}is equal to{title}, it is cleared - if secondary fields duplicate each other, only the first unique value is kept and later duplicates are cleared
Deduplication uses normalized title keys:
- case-insensitive
- repeated spaces ignored
- separator and punctuation differences normalized
The script validates template fields on startup and fails fast if a template contains an unknown field.
The script is intentionally conservative.
- rename recognized movie files and folders
- preserve collection structure
- preserve subtitle suffixes when a matching video stem is found
- remove empty source directories when appropriate
- keep non-empty directories untouched
- update only movie folders in
--update-movie-foldersmode
- blindly create a new folder for every movie file
- treat a collection folder as one movie
- overwrite existing destination files
- aggressively guess arbitrary subtitle-only files
- delete non-empty directories
For new collections:
python MoviesRenamer.py /path/to/Movies --dry-firstThis is the safest mode:
- you see the plan before it is applied
- you can skip or re-search if needed
- you can stop anytime with
0orCtrl+C
Once you trust the current batch:
python MoviesRenamer.py /path/to/Movies- relies on Kinopoisk and TMDb availability
- does not try to understand every possible custom fan naming scheme
- ambiguous subtitle-only cases are intentionally not guessed too aggressively
- unusual collections may still need occasional manual selection
0in menus exits the whole script cleanlyCtrl+Calso exits cleanly- cache is still saved on exit when possible
- cache save failures are downgraded to warnings
- operations log finalization is handled more defensively on flaky paths
MIT