Automatically organizes and renames media files according to Jellyfin's naming conventions. Detects movies vs TV shows, fetches TMDB metadata, and handles transliteration of non-Latin filenames.
- Smart movie vs TV show detection
- TMDB integration for IDs and metadata
- Configurable naming via tokens
- Russian transliteration detection and conversion
- Daemon mode for continuous monitoring
1. Create a system user and add it to the media group:
sudo groupadd media
sudo useradd --system --no-create-home --shell /usr/sbin/nologin jfmo
sudo usermod -aG media jfmoMake sure your media directories are owned or readable by the media group:
sudo chown -R :media /data/media
sudo chmod -R 775 /data/media
sudo chmod g+s /data/media
sudo chmod g+s /data/media/download
sudo chmod g+s /data/media/download/manual
sudo chmod g+s /data/media/download/incomplete
sudo chmod g+s /data/media/movies
sudo chmod g+s /data/media/tv2. Set up the config:
Default config path: /etc/jfmo/config.yaml. See config.template.yaml for all options.
sudo mkdir -p /etc/jfmo
sudo vim /etc/jfmo/config.yaml
sudo chown -R jfmo:jfmo /etc/jfmo3. Install the package:
sudo pipx install jfmo --global4. Create the systemd unit /etc/systemd/system/jfmo.service:
[Unit]
Description=Jellyfin Format Media Organizer
After=network.target
[Service]
Type=simple
User=jfmo
Group=jfmo
ExecStart=/usr/local/bin/jfmo daemon
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target5. Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now jfmo
sudo systemctl status jfmoRun once manually (without stopping the daemon):
sudo -u jfmo jfmo run --applySee example of docker compose file in docker-compose.template.yaml.
Set user in docker-compose.yaml to the uid:gid of jfmo:jfmo (created above):
id jfmo # get uid
getent group jfmo # get gid1. Set up files:
sudo mkdir -p /opt/jfmo
cd /opt/jfmo
sudo vim docker-compose.yamlStart as a background daemon (restarts automatically on reboot):
sudo docker compose up -dRun once manually (e.g. to process a backlog):
# Dry-run preview — no files moved
sudo docker compose run --rm jfmo run
# Apply changes
sudo docker compose run --rm jfmo run --applysudo pipx upgrade jfmo --global
sudo systemctl restart jfmosudo docker compose pull
sudo docker compose up -djfmo run # dry-run preview (no files moved)
jfmo run --apply # apply changes
jfmo daemon # watch downloads directory continuously
jfmo --version
| Token | Description | Example |
|---|---|---|
{title} |
Media title | Inception |
{year} |
Release year | 2010 |
{tmdb_id} |
TMDB numeric ID | 27205 |
{quality} |
Resolution label | [1080p] |
{season} |
Season number, zero-padded | 01 |
{episode} |
Episode number, zero-padded | 04 |
{source} |
Release source | WEB-DL, BluRay, BDRip |
{codec} |
Video codec | x265, HEVC, AV1 |
{hdr} |
HDR format | HDR10, DV, DoVi |
{service} |
Streaming service | NF, AMZN, DSNP |
{release_group} |
Release group name | LostFilm, NOOBDL |
Each pattern only accepts a specific subset of tokens:
Pattern (naming.) |
Allowed tokens |
|---|---|
movie.file |
title, year, tmdb_id, quality, source, codec, hdr, service, release_group |
tv.folder |
title, year, tmdb_id |
tv.season |
season |
tv.file |
title, season, episode, quality, source, codec, hdr, service, release_group |
downloads/
├── Severance.S02E02.1080p.mkv
├── The.Accountant.2.2024.2160p.mkv
├── Podslushano.v.Rybinske.S01E01.2160p.mkv ← Russian transliteration
└── La Casa de Papel 3 - LostFilm [1080p]/
films/
└── The Accountant 2 (2024) [tmdbid-717559] - 2160p.mkv
tv/
├── Severance (2022) [tmdbid-95396]/
│ └── Season 02/
│ └── Severance S02E02 - 1080p.mkv
├── Подслушано в Рыбинске (2024) [tmdbid-245083]/ ← converted to Cyrillic
│ └── Season 01/
│ └── Подслушано в Рыбинске S01E01 - 2160p.mkv
└── La Casa de Papel (2017) [tmdbid-71446]/
└── Season 03/
└── La Casa de Papel S03E01 - 1080p.mkv
Most media organizers (Radarr, Sonarr, etc.) cannot handle files where the title is written in Latin-script transliteration of Russian — e.g. Podslushano.v.Rybinske.S01.mkv looks like English but is actually «Подслушано в Рыбинске».
JFMO detects this automatically using a custom character n-gram language model trained to distinguish genuine English titles from Russian titles written in transliteration. When a transliterated title is detected, JFMO converts it back to Cyrillic before searching TMDB — resulting in a correct match instead of a failed lookup.
Podslushano.v.Rybinske.S01E01.mkv
detected: Russian transliteration
converted: Подслушано в Рыбинске
TMDB match: tmdbid-XXXXXX
→ Подслушано в Рыбинске (2024) [tmdbid-XXXXXX]/Season 01/...
The model was trained on a custom dataset of ~2.5M titles (165k Russian + 2.4M English) built specifically for this project, achieving 93% accuracy on a diverse test set of 334 cases.
- Dataset: stafloker/media-transliterated (Kaggle)
- Inspired by: Language Identification for Texts Written in Transliteration
