Skip to content

Repository files navigation

Symlinker

Symlinker watches a torrent download directory, identifies likely movies and TV episodes with TMDB, and creates Jellyfin-friendly symlinks in separate library folders. The source file is never moved, renamed, or modified.

What it does

  • Polls a download directory for stable video files.
  • Cleans noisy release names and guesses movie vs TV before TMDB lookup.
  • Uses TMDB search to confirm the title and enrich naming.
  • Creates symlinks in a movie or TV library tree.
  • Sends unmatched or low-confidence items into a review queue visible in the web UI.
  • Stores the top TMDB candidates for review items and lets you choose the correct one manually from the dashboard.
  • Stores state and logs in SQLite so restarts do not duplicate work unnecessarily.

Default naming

Movies:

Movies/Title (Year)/Title (Year).mkv

TV:

TV/Show Name (Year)/Season 01/Show Name (Year) - s01e01 - Episode Title.mkv

Local run

python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
$env:APP_DATA_DIR = "./app_data"
uvicorn app.main:app --reload

Open http://localhost:8000 and update the settings page with your real paths and TMDB key.

Docker run

docker compose up --build

For TrueNAS SCALE, mount real host paths instead of the sample directories in docker-compose.yml or your app manifest:

  • /downloads -> your torrent completed-downloads dataset
  • /library/movies -> your Jellyfin movie dataset
  • /library/tv -> your Jellyfin TV dataset
  • /library/review -> optional holding or inspection dataset
  • /app/app_data -> persistent config, DB, and logs

TrueNAS SCALE Custom App

Yes, this can run as a TrueNAS SCALE Custom App.

The recommended deployment model is:

  1. Build the container image from this repo.
  2. Push it to a registry such as GitHub Container Registry.
  3. Create a Custom App in SCALE that points at that image.
  4. Mount your datasets into the container.
  5. Assign which mounted path is Downloads, Movies, TV, and Review from the app UI.

Required container settings

  • Image: ghcr.io/<your-user-or-org>/symlinker:latest
  • Port: 8000/TCP
  • Environment variable: APP_DATA_DIR=/app/app_data

Storage mounts

  • You can mount datasets to the default paths below, or to any other in-container paths you prefer.
  • After the app starts, go to /settings and choose which mounted path should be used for Downloads, Movies, TV, and Review.

Common defaults:

  • Host path or dataset for completed downloads -> /downloads
  • Host path or dataset for movies -> /library/movies
  • Host path or dataset for TV -> /library/tv
  • Host path or dataset for review -> /library/review
  • Host path or dataset for app state -> /app/app_data

Permissions notes

  • The container must be able to create directories and symlinks inside /library/movies and /library/tv.
  • Jellyfin must be allowed to traverse the symlinks created in those library datasets.
  • The source files in /downloads are read-only from the app's perspective. They are never renamed, moved, or modified.

Build and push an image manually

docker build -t ghcr.io/<your-user-or-org>/symlinker:latest .
echo <github-token> | docker login ghcr.io -u <github-user> --password-stdin
docker push ghcr.io/<your-user-or-org>/symlinker:latest

See docs/truenas-custom-app.md for a concrete Custom App configuration example.

GitHub Container Publishing

This repo now includes .github/workflows/publish-image.yml to publish a container image to GitHub Container Registry.

The workflow:

  • Builds the image from Dockerfile
  • Publishes latest from the default branch
  • Publishes tag versions when you push a Git tag such as v0.1.0

To use it:

  1. Push this repo to GitHub.
  2. Make sure Actions are enabled.
  3. Push to your default branch to publish ghcr.io/<owner>/symlinker:latest.
  4. Optionally push a version tag such as v0.1.0.

Notes for TrueNAS

  • This build assumes symlinks across datasets. That fits your stated requirement better than hardlinks.
  • The app uses polling rather than inotify so it behaves predictably across container mounts and restarts.
  • Files are only processed after they stop changing for the configured stability delay.
  • If TMDB confidence is too low, the app does not link the file automatically.

Main routes

  • / dashboard
  • /settings configuration page
  • /healthz basic health and config status
  • POST /scan manual scan trigger
  • POST /reprocess retry a review item

UI behavior

  • Dataset roles are configured from the UI after your container mounts are in place.
  • The settings page shows detected mounted paths so you can assign them without editing files in the container.
  • The TMDB API key can be added or changed from the settings page.
  • The activity log shows the source file, the linked library path, and the resulting Jellyfin-facing file name.
  • Review items can display the top TMDB candidates with title, year, score, and media type, and you can select one manually.

Current limitations

  • Best results come from conventional release names containing a year or S01E01 style episode markers.
  • Multi-episode files are not specially named yet.
  • Review items are listed in the UI but not moved into the review directory.
  • TMDB matching currently uses the top search result plus a simple confidence score.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages