Skip to content

Hrastovc/jellylink

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 

Repository files navigation

jellylink

A terminal UI for building and maintaining a Jellyfin-compatible symlink library — without touching your original files.

Jellyfin identifies media by strict naming conventions. Raw downloads rarely follow them. jellylink solves this non-destructively: it keeps your originals exactly where they are and builds a parallel directory tree of symlinks that Jellyfin can read cleanly.

/tank/media/                               /tank/jellyfin/
  Movies/                                    Movies/
    Inception.2010.1080p.BluRay.mkv   →       Inception (2010)/
                                                Inception (2010) - 1080p.mkv ⇢ ...
  Serije/                                    Serije/
    Breaking.Bad/                              Breaking Bad (2008) [imdbid-tt0903747]/
      S01/                                       Season 01/
        Breaking.Bad.S01E01.mkv         →          Breaking Bad S01E01.mkv ⇢ ...

Features

  • Non-destructive — originals are never moved, renamed, or modified
  • Lazy file tree — only reads directories as you expand them
  • Smart name parser — strips codec junk, extracts year and episode codes, recognises multi-episode and special-episode patterns
  • Live rule validation — highlights symlinks that violate Jellyfin naming rules in yellow so you can fix them immediately
  • Incremental cache — link creation and deletion update the UI state in place; no full re-scan on every keystroke
  • Undo — every create, rename, and delete is reversible within the session
  • Bulk auto-link — link an entire directory tree in one keypress
  • ISO extraction — call 7z to unpack an ISO in-place without leaving the TUI

Requirements

  • Python 3.10+
  • Linux / macOS (uses POSIX symlinks)
  • A colour terminal (xterm-256color or similar)
  • 7z (p7zip) — only needed for the ISO extraction feature

No third-party Python packages required.

Setup

1. Clone

git clone https://github.com/youruser/jellylink.git
cd jellylink

2. Configure

Edit the three constants at the top of jellylink.py:

MEDIA_ROOT   = "/tank/media"       # root of your raw media library
JELLY_ROOT   = "/tank/jellyfin"    # root of the symlink tree that Jellyfin reads
SERIES_CATS  = ["Serije", "AnimiraneSerije", "Telenovele"]

SERIES_CATS lists the first-level subdirectory names that contain TV series. Every other category is treated as movies. The same category names must exist (or will be created) under both roots.

3. Point Jellyfin at JELLY_ROOT

In Jellyfin → Dashboard → Libraries, add each category folder inside JELLY_ROOT as a library (e.g. /tank/jellyfin/Movies as a Movie library, /tank/jellyfin/Serije as a TV Shows library).

4. Run

python3 jellylink.py

A terminal width of at least 100 columns is recommended.

Interface

 MEDIA TREE [FILTER]               SIZE    JELLYFIN LINKS [2 broken]
 ▶ Movies                                                              ← cyan  (no links inside)
 ▼ Serije                                                              ← green (has links inside)
   ▼ Breaking Bad (2008) [imdbid-tt0903747]                           ← green
     ▼ Season 01
       • Breaking.Bad.S01E01.mkv           2.1 GB  -> Breaking Bad S01E01.mkv
       • Breaking.Bad.S01E02.mkv           1.9 GB  -> Breaking Bad S01E02.mkv
       • Breaking.Bad.S01E03.mkv           2.0 GB                            ← unlinked
     ▼ Season 02
       • Breaking.Bad.S02E01.mkv           2.3 GB  -> Breaking Bad S01E01.mkv ← yellow (rule violation)
   ▶ Dark (2017) [imdbid-tt5753856]                                   ← magenta (marked complete)
─────────────────────────────────────────────────────────────────────────────────────────────────
        | l:Link | a:Auto | e:Edit | f:Fix | d:Del | b:Bulk | z:Undo | c:Done | u:Undone | x:Iso |

The left pane is a lazy-loading tree rooted at MEDIA_ROOT. The right pane shows the symlink filename for every file that has one. The header shows active modifiers ([FILTER]) and the broken-link count when non-zero.

Colour reference

Left pane — directories

Colour Meaning
Cyan Directory with no linked descendants
Green Directory with at least one linked descendant
Magenta Directory marked complete (.is_complete file present)

Right pane — symlink names

Colour Meaning
Green Symlink exists and passes all naming rules
Yellow Symlink exists but violates at least one naming rule — use f to fix

Broken symlinks (target file has been moved or deleted) are shown in the header count. Use r to refresh the cache after resolving them externally.

Keybindings

Navigation

Key Action
/ Move cursor
Enter Expand / collapse directory
Backspace Collapse current entry and jump to its parent
q Quit

Linking

Key Action
l Link — create a symlink for the selected file. Opens a prompt pre-filled with the auto-generated name; edit freely, Enter to confirm, Esc to cancel.
a Auto — silently accept the auto-generated name without prompting.
e Edit — rename an existing symlink. Prompt is pre-filled with the current link name.
f Fix — discard the current link name and re-generate it from the source path. Opens a prompt so you can review before confirming. Useful after source folders are renamed.
d Delete — remove the symlink for the selected file.
b Bulk auto — recursively run a on every unlinked video file under the selected directory.
z Undo — reverse the last create, edit, or delete. Each z steps back one action.

Other

Key Action
F (Shift+f) Toggle unlinked filter — hide files that already have a symlink, showing only what still needs to be processed.
c Mark selected directory as complete — creates an empty .is_complete file, turning the folder magenta.
u Unmark complete — removes the .is_complete file.
x Extract ISO — runs 7z x on the selected .iso file in-place, then reloads the tree.
r Refresh — re-scan JELLY_ROOT and rebuild the symlink cache from disk.

Jellyfin naming rules

Every symlink is validated on creation. A yellow link in the right pane means one or more rules are broken. Press f on the file to regenerate the name.

Movies

JELLY_ROOT/
  <Category>/
    <Title> (<Year>) [imdbid-ttXXXXXXX]/
      <Title> (<Year>) [imdbid-ttXXXXXXX] - 1080p.mkv    ← symlink
Rule Detail
No reserved characters Filename must not contain `` < > : " / \
Filename matches folder When the folder carries an [imdbid-...] tag, the filename must start with the full folder name

TV series

JELLY_ROOT/
  <Category>/
    <Show> (<Year>) [imdbid-ttXXXXXXX]/
      Season 01/
        <Show> S01E01 - 1080p.mkv    ← symlink
      Season 00/
        OVA 1 - Recap.mkv            ← specials (relaxed rules)
Rule Detail
Minimum depth Path must be Category/Show/Season/File — at least 4 levels
SxxExx required Regular episode filenames must contain a season/episode code
Zero-padded season Season folder must be Season 01, not Season 1
Filename starts with show name Regular episodes must start with the show folder name (IMDB tag stripped)

Season 00 — Specials: Files inside Season 00 are exempt from the SxxExx and show-name-prefix rules, matching Jellyfin's own handling of specials folders.

Name auto-generation

When creating or fixing a link, jellylink derives a clean name from the source path. Metadata is gathered in priority order:

Priority Source Notes
1 Existing link's parent folder (fix mode only) Reuses the name already confirmed for this show/movie
2 Source path ancestor carrying [imdbid-...] Folder name used verbatim — most reliable
3 Nearest meaningful parent folder Codec/release junk stripped; . and _ converted to spaces
4 Year fallback Scanned from any path component; (2010) preferred over bare 2010

Junk tokens stripped from folder names: 1080p 720p 2160p 4k uhd bluray dvdrip x264 x265 hevc aac dts web-dl hdtv webrip brrip xvid h264 h265 and common container extensions.

Episode code patterns

Pattern Example source filename Parsed output
Standard SxxExx Show.S01E05.mkv S01E05
Alternate NNxNN Show.01x05.mkv S01E05
Multi-episode concatenated Show.S01E01E02.mkv S01E01-E02
Multi-episode range (dash) Show.S01E01-E03.mkv or S01E01-03 S01E01-E03
Numbered special OVA2.mkv, SP03.mkv, NCED1.mkv S00E02, S00E03, S00E01
Bare special keyword Special.mkv, OVA.mkv S00E01 (placeholder)

If any component cannot be determined, a placeholder is inserted (<name>, <year>, <SxxExx>) and the prompt opens so you can fill it in manually.

Workflow tips

Starting from scratch on a new import:

  1. Select the top-level category folder (e.g. Serije)
  2. Press b — Bulk auto-links every video file recursively
  3. Press F (Shift+f) to enable the unlinked filter — any files b couldn't handle appear immediately
  4. Work down the remaining files with l (Link)

Most reliable setup: Name your source folders as Show Name (2020) [imdbid-tt1234567] before importing. The IMDB tag in the folder name is the highest-priority metadata source; auto-generation will always produce a correct, unambiguous result.

Completion tracking: c / u toggle a .is_complete marker on any directory. Use this to distinguish fully-processed sections of the library (magenta) from in-progress ones (green) at a glance.

Undo scope: The undo stack is in-memory only and is cleared on quit. If you need to undo a bulk operation, press z once per file.

External changes: r (Refresh) re-scans JELLY_ROOT from disk. This is only needed if you modify symlinks outside jellylink — all in-app operations update the cache immediately without a re-scan.

Directory structure reference

MEDIA_ROOT/                              JELLY_ROOT/
  Movies/                                  Movies/
    Inception (2010)/                        Inception (2010) [imdbid-tt1375666]/
      Inception.2010.1080p.mkv      →          Inception (2010) [imdbid-tt1375666] - 1080p.mkv
                                                  └─ symlink → MEDIA_ROOT/Movies/...
  Serije/                                  Serije/
    Breaking Bad (2008)/                     Breaking Bad (2008) [imdbid-tt0903747]/
      S01/                                     Season 01/
        Breaking.Bad.S01E01.mkv     →              Breaking Bad S01E01.mkv
      Specials/                                Season 00/
        OVA.mkv                     →              Breaking Bad S00E01.mkv

Contributing

Bug reports and pull requests are welcome. When reporting an issue, please include:

  • The filename or folder name that was parsed incorrectly
  • What jellylink generated vs. what Jellyfin expects
  • Your SERIES_CATS configuration if it differs from the default

License

MIT

About

A terminal UI for building and maintaining a Jellyfin-compatible symlink library — without touching your original files.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages