Automatic movie theme song downloader for Plex, Jellyfin, Emby and Kodi libraries.
Releases · Proxmox Scripts · Reddit
Themearr reads your movie library and helps you add a theme.mp3 to every movie folder — the file Plex, Jellyfin, Emby and Kodi use to play background music while browsing.
It reads your library from either Plex or Radarr. Radarr is a first-class source: if you don't use Plex, you never need a Plex account to run Themearr.
- Read your library from Plex (OAuth sign-in) or Radarr (URL + API key)
- Browse your whole library as a poster grid
- Auto-search YouTube for each movie's theme
- One-click download to
theme.mp3 - Automatic background downloading across the whole library
- Paste any video URL to use a custom source
- Downloaded status tracked per movie, verified against what's on disk
- System page with health checks and scheduled tasks, arr-style
- An API key plus a Radarr webhook, so a theme is fetched the moment a movie is imported
Themes are stored full-length as received — there is no trimming or re-encoding — which works out to roughly 3.7 GB of theme audio per 1,000 films (measured on one production install: 1,438 films, 5.28 GB). Plan for that growth on the volume holding your media folders. Plex users: Plex also imports a copy of each theme into its own metadata bundle, so the space is used twice on the volume holding Plex's metadata directory.
Theme audio is fetched through the youtube-mp36 API on RapidAPI — Themearr does not bundle or shell out to yt-dlp or ffmpeg. Downloads will not work until you add your RapidAPI key and username in Settings → RapidAPI. Signing in and browsing your library work without it — only downloading a theme.mp3 needs it. The free RapidAPI tier is quota-limited, so Themearr backs off automatically when the quota is exhausted.
Run this on your Proxmox host:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/Themearr/ProxmoxVE/main/ct/themearr.sh)"The installer generates an access token, prints it at the end, and saves a copy to /root/themearr.creds. Open http://<container-ip>:8080, enter the token, then pick your library source — sign in with Plex, or choose "I don't use Plex" to connect Radarr instead.
A multi-arch image (amd64 / arm64) is published to GHCR on every release.
# 1. Get the compose file
curl -fsSL https://raw.githubusercontent.com/Themearr/themearr/main/docker-compose.yml -o docker-compose.yml
# 2. Generate the required access token
echo "THEMEARR_AUTH_TOKEN=$(openssl rand -hex 32)" > .env
# 3. Edit docker-compose.yml — point the movie volume at your library:
# - /path/to/your/movies:/movies
# It must be WRITABLE (no ":ro") — Themearr writes theme.mp3 into movie folders.
docker compose up -dOpen http://127.0.0.1:8080 and enter the token from .env.
The compose file publishes the port to
127.0.0.1only. For remote access, put a reverse proxy (Caddy/nginx) in front with its own TLS and auth.
The API refuses to start without THEMEARR_AUTH_TOKEN — there is no unauthenticated mode. The Proxmox installer generates one for you; for Docker you set it yourself (see above). Every client enters this token once.
This is the setting people most often get wrong, and it's what causes Skipping <title> — unresolved path during sync.
Themearr writes theme.mp3 into your movie folders, so it has to reach your files at a path it can see — which is usually not the path Plex reports.
- Local Library Paths — where your movie folders live as Themearr sees them (e.g.
/moviesin Docker, or/mnt/media/Moviesin an LXC). - Path Mappings — translate the path Plex reports into the path Themearr sees.
Example — Plex on Windows, Themearr in Docker:
| Plex reports | Themearr sees | Mapping to add |
|---|---|---|
P:\Movies\Heat (1995)\heat.mkv |
/movies/Heat (1995) |
P:\Movies → /movies |
If sync logs Skipping <title> — unresolved path: <path>, that logged path is exactly what Plex reported — map its parent folder to wherever it's mounted in Themearr. Windows-style (\) paths are handled automatically.
Also make sure the movie mount is writable — a read-only mount resolves fine but silently fails every download.
System → Health flags the things that silently break downloads: a library path that is missing or read-only, a Plex server that is unreachable or has rejected its token, a missing RapidAPI key, an exhausted quota, and a stalled auto-download worker. Only problems are listed, so an empty page means everything is fine.
System → Tasks shows when the library last synced and lets you trigger a sync immediately with Run now.
For external monitoring, Themearr exposes an unauthenticated /health endpoint
that returns {"status":"Healthy"} and nothing else — enough for Uptime Kuma or
Gatus, without leaking any configuration.
Themearr can read your movie list from either Plex or Radarr, chosen during setup and changeable later under Settings → Library source.
Radarr is what makes Themearr useful without Plex. It already knows every movie's
folder, title, year and whether the film is downloaded — and because theme.mp3 is
read by Jellyfin, Emby and Kodi as well as Plex, a Radarr-sourced library serves all
of them. You need a Radarr URL and API key (Radarr → Settings → General → API Key).
Radarr is local and cheap to poll, so it syncs every 15 minutes rather than daily — a newly imported movie usually has its theme within minutes.
Movies Radarr is monitoring but has not downloaded yet are skipped: there is no film for a theme to accompany. Path Mappings work exactly as they do for Plex, and are often needed, since Radarr in a container reports its own paths.
Note for existing installs (v1.42.0). You don't need to do anything. Themearr stays on Plex unless you change it — the library source defaults to Plex, your setup stays complete, and you won't be asked to run the wizard again.
Only read on if you plan to switch an existing library from Plex to Radarr.
Themearr identifies a movie by the folder its theme lives in, and both sources describe the same folders on disk, so anything both of them report keeps its downloaded status and its place in your history.
The catch is anything Radarr doesn't report. Radarr only knows about movies it manages, so hand-added rips, a second library, or anything imported before you started using Radarr are invisible to it — and the first Radarr sync removes those rows. Concretely:
- Movies you've ignored are kept, whichever source you're on.
- A removed movie's downloaded status is not lost in practice — status is read from
whether a
theme.mp3is actually on disk, so if the file is still there it comes back as downloaded the moment the movie reappears. - Its history entries are orphaned, though. They still show the film's title and year on the History page, they just no longer link to a movie.
So switching is safe if Radarr manages your whole library, and lossy at the edges if it doesn't. Check Radarr's movie count against Themearr's before you switch.
Settings → API key shows a key that external tools can use to talk to Themearr.
Send it as an X-Api-Key header on any /api/… request:
curl -H "X-Api-Key: <your key>" http://themearr:8080/api/system/tasksWarning: this is a full-access credential, not a read-only one. It authenticates exactly like the access token you sign in with, on every
/api/*endpoint except the two that manage the key itself. Whoever holds it can reset setup, trigger an update that restarts the service, and overwrite your stored Plex token, Radarr API key, and RapidAPI key — the only thing it can't do is read or regenerate itself. Handle it with the same care as the access token, and don't paste it anywhere you wouldn't paste that.
It is separate from the access token you sign in with, so you can regenerate it without logging anyone out — and regenerating immediately stops the old one working.
Instead of waiting for the next sync, have Radarr tell Themearr directly. In Radarr: Settings → Connect → Add → Webhook, then set:
| Field | Value |
|---|---|
| Notification Triggers | On Import (also tick On Upgrade if you want) |
| URL | http://themearr:8080/api/webhook/radarr |
| Method | POST |
| Headers | X-Api-Key = your key from Settings → API key |
Press Test — Themearr answers, so a wrong URL or key shows up immediately rather than at the next import.
Importing several movies at once is fine: Themearr collapses the burst into a single sync rather than one per movie.
Two caveats:
- This is most useful when Radarr is your library source. If you use Plex as the source and Radarr only downloads, the webhook still fires — but Plex may not have scanned the new file yet, so the theme may still wait for a later sync.
- Radarr builds from before custom webhook headers were added (upstream, late 2024)
cannot send
X-Api-Key.
- In-app: Settings → Updates. Downloads the latest release, preserves your data, and restarts.
- Docker:
docker compose pull && docker compose up -d - Proxmox / bare metal: the in-app updater, or re-run the community install script.
Upgrading a pre-.NET-10 install: releases from v1.39.10 onward need the ASP.NET Core 10 runtime. Containers created earlier were provisioned with .NET 9, so install the runtime first:
curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh bash /tmp/dotnet-install.sh --channel 10.0 --runtime aspnetcore --install-dir /usr/share/dotnetIf you forget, nothing breaks — the updater checks for the runtime before changing any files and aborts with these instructions, leaving your running install untouched. Docker is unaffected (the runtime ships in the image).
| Layer | Technology |
|---|---|
| API | .NET 10 Web API (ASP.NET Core, LTS) |
| Frontend | React 19 + Vite (static SPA, served by .NET) |
| Routing | React Router |
| Database | SQLite via Microsoft.Data.Sqlite |
| YouTube search | YoutubeExplode |
| Theme download | youtube-mp36 RapidAPI |
| Tests | xUnit |
- .NET 10 SDK
- Node.js 22+
- A youtube-mp36 RapidAPI key + username (added in Settings → RapidAPI) — required for downloads
# Terminal 1 — API (set any token you like for local dev)
THEMEARR_AUTH_TOKEN=dev-token-at-least-16-chars dotnet run --project src/Themearr.API
# Terminal 2 — Frontend (dev server with proxy to API)
cd src/Themearr.Web
npm install
npm run dev # proxies /api to the .NET backend on :5000Open http://localhost:3000. The frontend is a static SPA — in production it's built to src/Themearr.Web/out/ and served by the .NET app from wwwroot (with an SPA fallback, so deep links like /movies work).
dotnet test # .NET test suite
cd src/Themearr.Web
npm run lint # ESLint
npx tsc --noEmit # typecheck
npm run build # production build -> out/Push to main — GitHub Actions will automatically:
- Detect the semver bump from commit messages (
feat:→ minor,major:→ major, else patch) - Build the frontend (Vite) and publish .NET for
linux-x64andlinux-arm64 - Bundle the frontend into each publish output
- Create a GitHub release with both tarballs plus SHA-256 checksums (verified by
install.sh/deploy.sh) - Build and push the multi-arch Docker image to
ghcr.io/themearr/themearr(:latestand:vX.Y.Z)
Changes that don't affect the shipped app (docs, .gitignore, workflows) don't cut a release.
Releases follow semantic versioning driven by commit message prefixes:
| Prefix | Bump |
|---|---|
feat: |
minor |
major: / BREAKING CHANGE |
major |
| anything else | patch |
MIT