4grayandClaude Fable 5 dc05a2566e feat(tmdb): opt-in TMDB metadata enrichment for Xtream and Stalker portals (#1123)
* feat(tmdb): opt-in TMDB metadata enrichment for Xtream and Stalker portals

Adds an opt-in TMDB integration (Settings > Metadata) that enriches
detail views with a field-level merge — the provider stays authoritative
for stream data, TMDB fills editorial fields when the match is confident.

Enrichment:
- Movie/series details: plot, cast (avatar chips), director, genres,
  rating, poster/backdrop, official YouTube trailers
- Confidence-gated matching: provider tmdb_id trusted; otherwise
  normalized-title search with year gate (±1; series accept earlier
  premieres), season-suffix stripping, Cyrillic search-language override,
  and language-prefix fallback variants
- Lazy season/episode enrichment: real episode names, overviews, stills
- "Similar" rail (Xtream): TMDB recommendations matched to the catalog
- Actor pages per portal with full filmography, availability filter and
  an Electron-only "All portals" scope backed by a batched DB_MATCH_TITLES
  worker op over the trigram FTS index

Infrastructure:
- SQLite cache table tmdb_metadata (details, search verdicts, seasons,
  persons; per-language, TTL-guarded), in-memory fallback for the PWA
- Settings: enable toggle, own-API-key override with a live "check key"
  button; TMDB attribution in Settings and About
- Embedded key stays an empty placeholder; CI injects TMDB_API_KEY via
  tools/tmdb/inject-tmdb-key.mjs when the secret is configured
- normalizeTitle shared between renderer and DB worker
- CSP: allow YouTube embeds (frame-src was 'none'; trailers never worked)

Fixes and refactors along the way:
- fix(stalker): Advanced Search sent bare get_ordered_list requests and
  skipped the auth handshake when isFullStalkerPortal was missing on the
  active-playlist meta — full portals answered "Authorization failed."
  and search looked empty; now mirrors the catalog request shape and
  routes through makeAuthenticatedRequest with URL-based detection
- fix(stalker): TMDB fields survive info re-normalization; detail views
  prefer the store copy patched by async enrichment over stale snapshots
- refactor(xtream): split oversized vod/serial detail components into
  component-scoped playback services; detail routes re-initialize on
  route param changes (router reuses them for detail-to-detail nav)
- i18n: all new keys translated across the 18 locales

Docs: docs/architecture/tmdb-metadata-enrichment.md + CLAUDE.md updates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmdb): provide route params observable to inline collection details, linearize regexes

The global-collection inline detail host builds a fake ActivatedRoute for
VodDetailsRouteComponent/SerialDetailsComponent with only snapshot.params.
Since the detail components now read route.params via toSignal() (detail->
detail re-init), the missing observable crashed component construction and
the content hero never rendered — broke dashboard-activation, favorites and
recent Electron E2E on all platforms. Provide the params observable
alongside the snapshot and assert it in the component spec.

Also resolves both CodeQL js/polynomial-redos alerts: bracket-stripping in
normalizeTitle now excludes opening delimiters inside the classes, and
youtubeEmbedUrl extracts watch?v= ids with a linear two-pass match instead
of "watch\?.*v=". Combining-diacritics range rewritten as explicit \u
escapes (greptile note).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmdb): surface TMDB-only VOD score in the rating badge, drop youtube.com from CSP

Review follow-ups on PR #1123: the Xtream VOD detail badge renders
rating_imdb, but the merge wrote the TMDB score only into `rating`, so a
TMDB-only score was never displayed (Codex P2) — fill rating_imdb when the
provider left it empty, mirroring the Stalker merge. All trailer iframes
are normalized to youtube-nocookie.com, so the extra youtube.com frame-src
allowance was dead surface (greptile) — removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmdb): resolve confirmed review findings — matching correctness, race guards, cache schema

Fixes the confirmed findings from the PR #1123 code review:

- Stalker search: setSelectedContentType now runs BEFORE setSelectedItem,
  so the TMDB enrichment gate in the selection hook no longer sees the
  content type of the previously open tab (wrong/no enrichment after
  ITV -> search -> movie).
- Title normalization is now two-tier (normalizeTitleKeys): the exact
  normalized form keeps a trailing year, the base form strips it and
  remembers the tag. Year stripping is anchored to the end of the title
  ("2001: A Space Odyssey" keeps its year) and language-prefix stripping
  is UPPERCASE-only ("It: Chapter Two" is no longer amputated).
- All catalog matching (similar rail, actor pages, DB worker
  DB_MATCH_TITLES) compares exact forms first and only accepts
  year-stripped matches when the stripped tag is year-compatible (+-1)
  with the TMDB year — "Blade Runner" (1982) can no longer claim a
  catalog "Blade Runner 2049". CatalogTitleMatch carries the stripped
  trailingYear so the renderer can apply the guard to worker matches.
- mergedBackdrops tolerates a plain-string backdrop_path; enrichment
  merge+patch blocks are wrapped in try/catch so a malformed provider
  payload can no longer become an unhandled rejection.
- loadGlobalMatches (both actor routes) guards against actor->actor
  navigation races — a slow match for the previous person no longer
  overwrites the current one's results.
- tmdb_metadata media_type CHECK widened to ('movie','tv','person') and
  person rows now use the honest 'person' type (TmdbCacheMediaType).
  Pre-release dev DBs with the narrow CHECK are rebuilt in place — the
  table is a pure cache, so the migration is a self-healing
  drop-and-recreate keyed off sqlite_master.

Docs updated (tmdb-metadata-enrichment.md, CLAUDE.md). New regression
coverage: title-normalization.util.spec.ts, two-tier cases in
tmdb-similar.util.spec.ts and title-match.operations.spec.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 17:07:46 +02:00
2019-10-06 17:34:49 +02:00
2023-01-23 22:42:23 +01:00
2023-03-11 21:01:25 +01:00
2020-09-27 20:33:37 +02:00
2025-10-07 18:02:13 +02:00
2021-02-07 00:19:57 +01:00
2020-09-27 20:33:37 +02:00
2020-09-27 20:33:37 +02:00

IPTVnator - IPTV Player Application

IPTVnator icon

Release GitHub Workflow Status Releases Codecov Telegram Bluesky

🌐 Website | Telegram channel for discussions | Buy me a coffee | GitHub Sponsors

IPTVnator is a video player application that provides support for IPTV playlist playback (m3u, m3u8). The application allows users to import playlists using remote URLs or by uploading files from the local file system. Additionally, it supports EPG information in XMLTV format which can be provided via URL.

The application is a cross-platform, open-source project built with Electron and Angular.

⚠️ Note: IPTVnator does not provide any playlists or other digital content. The channels and pictures in the screenshots are for demonstration purposes only.

IPTVnator: Channels list, player and epg list

Features

  • M3u and M3u8 playlist support 📺
  • Radio playlist support with dedicated audio player 📻
  • Xtream Code (XC) and Stalker portal (STB) support
  • External player support - MPV and VLC; macOS accepts mpv.app / VLC.app bundle paths. IINA can be launched via its executable path on macOS (best-effort: controls and position polling are MPV IPC only)
  • Add playlists from the file system or remote URLs 📂
  • Automatic playlist updates on application startup
  • Channel search functionality 🔍
  • EPG support (TV Guide) with detailed information
  • TV archive/catchup/timeshift functionality
  • Group-based channel list
  • Read-only M3U channel details from channel context menus, including favorites and recently viewed
  • Favorite channels management
  • Global favorites aggregated from all playlists
  • Recently viewed live channel removal from row actions and context menus
  • HTML video player with HLS.js support or Video.js-based player
  • Internationalization with support for 16 languages:
    • Arabic
    • Moroccan arabic
    • English
    • Russian
    • German
    • Korean
    • Spanish
    • Chinese
    • Traditional chinese
    • French
    • Italian
    • Turkish
    • Japanese
    • Dutch
    • Belarusian
    • Polish
  • Custom "User Agent" header configuration for playlists
  • Light and Dark themes
  • Docker image available for self-hosting the PWA and web backend together

Keyboard shortcuts

Press ? or Shift+/ in the workspace to open the in-app shortcuts list.

Area Shortcut Action
Global Ctrl/Cmd+K Open command palette
Global Ctrl/Cmd+F Open global search in the desktop app
Global Ctrl/Cmd+R Open recently viewed in the desktop app
Global Enter in workspace search Submit the current search
Navigation Ctrl/Cmd+B Toggle the live sidebar
Navigation 0-9 Select an M3U channel by number
Playback Space / K Play or pause embedded MPV playback in the desktop app
Playback F Toggle embedded MPV fullscreen in the desktop app
Playback ArrowLeft / ArrowRight Seek embedded MPV playback by 5 seconds in the desktop app
Playback ArrowUp / ArrowDown Adjust volume by 5%
Playback M Mute audio
Dialogs and lists ArrowUp / ArrowDown Move command palette selection
Dialogs and lists Enter Run the selected command or open a focused item
Dialogs and lists Escape Close dialogs and dismiss overlays

Screenshots:

Dashboard with recently watched content Live channels with inline player and EPG
Dashboard with recently watched content Live channels with inline player and EPG
Add playlist dialog for M3U, Xtream, and Stalker Live category channel list
Add playlist dialog for M3U, Xtream, and Stalker Live category channel list
Global search across live TV, movies, and series Manage visible live categories
Global search across live TV, movies, and series Manage visible live categories
Movie category grid with sorting and pagination Recently added movies and series
Movie category grid with sorting and pagination Recently added movies and series
VOD details with playback and download actions Download manager
VOD details with playback and download actions Download manager
Multi-channel EPG grid External MPV player support
Multi-channel EPG grid External MPV player support
Radio playback with dedicated audio player Light theme
Radio playback with dedicated audio player Light theme
Application settings
Application settings

Note: First version of the application which was developed as a PWA is available in an extra git branch.

Self-hosted PWA

The Docker setup builds the Angular PWA and the monorepo web backend into one image. The backend handles remote M3U parsing plus Xtream and Stalker proxy requests under /api, so a separate 4gray/iptvnator-backend container is not required for the default self-hosted flow.

docker compose -f docker/docker-compose.yml up --build -d

The application is available at http://localhost:4333. See docker/docker-compose.yml for the ready-to-run compose file and docker/README.md for environment variables, reverse proxy notes, PWA limitations, and build details.

The self-hosted image runs the browser PWA rather than the Electron desktop app: EPG/XMLTV panels, Embedded MPV, managed MPV/VLC launching, the download manager, and Electron remote-control features are not available there. If browser playback fails, copy the stream URL and open it manually in an external player such as MPV, VLC, or IINA.

Download

Download the latest version of the application for macOS, Windows, and Linux from the release page.

Alternatively, you can install the application using one of the following package managers:

Homebrew

$ brew install iptvnator

Snap

$ sudo snap install iptvnator

Arch

Also available as an Arch PKG, iptvnator-bin, in the AUR (using your favourite AUR-helper, .e.g. yay)

$ yay -S iptvnator-bin

Gentoo

You can install IPTVnator from the gentoo-zh overlay

sudo eselect repository enable gentoo-zh
sudo emerge --sync gentoo-zh
sudo emerge iptvnator-bin

Linux Embedded MPV Support

Embedded MPV on Linux is experimental and currently supports x64 desktop sessions where IPTVnator runs under X11 or Xwayland. Native Wayland embedding is not supported yet. Linux package launchers request X11 with --ozone-platform=x11, so Wayland desktops still need Xwayland available.

The Linux backend starts a system mpv executable with --wid, so mpv must be installed and available on PATH. CI validates the Linux native addon and standard packages on Ubuntu 22.04, with Flatpak packaging built on Ubuntu 24.04. Expected user targets are Ubuntu/Debian .deb, Arch/Manjaro pacman, RPM distributions, and AppImage on x64 systems with X11/Xwayland plus mpv installed. Flatpak and Snap builds remain available, but embedded MPV is not announced as supported there yet because those sandboxed formats do not expose the host mpv executable to the embedded backend by default.

Get it from the Snap Store

Buy Me A Coffee

Troubleshooting

macOS: "App is damaged and can't be opened"

Older unsigned macOS builds may require removing the quarantine flag from the downloaded application:

xattr -c /Applications/IPTVnator.app

Alternatively, if the app is located in a different directory:

xattr -c ~/Downloads/IPTVnator.app

Linux: chrome-sandbox Issues

If you encounter the following error when launching IPTVnator:

The SUID sandbox helper binary was found, but is not configured correctly.
Rather than run without sandboxing I'm aborting now.
You need to make sure that chrome-sandbox is owned by root and has mode 4755.

Solution 1: Fix chrome-sandbox permissions (Recommended for .deb/.rpm installations)

Navigate to the IPTVnator installation directory and run:

sudo chown root:root chrome-sandbox
sudo chmod 4755 chrome-sandbox

Solution 2: Launch with --no-sandbox flag

Edit the desktop launcher file to add the --no-sandbox flag:

  1. Find your desktop file location:

    • Ubuntu/Debian: ~/.local/share/applications/iptvnator.desktop
    • System-wide: /usr/share/applications/iptvnator.desktop
  2. Edit the file and modify the Exec line:

    Exec=iptvnator --no-sandbox %U
    
  3. Save the file and relaunch the application from your application menu.

Alternatively, you can launch IPTVnator from the terminal with the flag:

iptvnator --no-sandbox

GNU/Linux: Wayland startup failure

If IPTVnator exits on GNU/Linux with errors about failing to connect to Wayland or initialize the Ozone platform, force X11/XWayland instead:

iptvnator --ozone-platform=x11

This workaround is mainly for older or problematic Linux graphics stacks. The Snap package already includes this X11 override by default. For AppImage, direct binaries, and other Linux package formats, pass the flag manually when needed.

How to Build and Develop

Requirements:

  • Node.js with pnpm (via Corepack)
  1. Clone this repository and install project dependencies:

    $ corepack enable
    $ pnpm install
    
  2. Start the application:

    $ pnpm run serve:backend
    

This will open the Electron app in a separate window, while the Angular dev server will run at http://localhost:4200.

The equivalent Nx command is:

$ nx serve electron-backend

To start Electron with an empty, isolated data directory instead of your normal ~/.iptvnator folder, set IPTVNATOR_E2E_DATA_DIR for that run:

$ rm -rf .tmp/iptvnator-empty && mkdir -p .tmp/iptvnator-empty
$ IPTVNATOR_E2E_DATA_DIR="$PWD/.tmp/iptvnator-empty" pnpm run serve:backend

This redirects the SQLite database, Electron user data, and local config under the given directory. Delete that directory whenever you want a fresh empty state.

If you need to debug renderer freezes or GPU/compositor issues in Electron, you can disable hardware acceleration for a run:

$ IPTVNATOR_DISABLE_HARDWARE_ACCELERATION=1 pnpm run serve:backend

If you need startup diagnostics for a white screen or a frozen route, you can also turn on opt-in Electron tracing. These logs are written to the Electron terminal output so they still help when the renderer DevTools never open:

$ IPTVNATOR_TRACE_STARTUP=1 pnpm run serve:backend

Nx equivalent:

$ IPTVNATOR_TRACE_STARTUP=1 nx serve electron-backend

Useful narrower flags:

  • IPTVNATOR_TRACE_IPC=1 logs renderer window.electron.* calls reaching the Electron bridge
  • IPTVNATOR_TRACE_DB=1 logs DB worker requests and request-scoped DB events
  • IPTVNATOR_TRACE_SQL=1 logs SQLite statements in both the main connection and DB worker connection
  • IPTVNATOR_TRACE_WINDOW=1 logs BrowserWindow load, navigation, and unresponsive events
  • IPTVNATOR_TRACE_RENDERER_CONSOLE=1 mirrors renderer console messages into the Electron terminal output

Security-sensitive network compatibility flags are opt-in:

  • IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS=1 permits strict EPG fetches from playlist metadata (x-tvg-url, url-tvg, or tvg-url) to resolve to localhost, LAN, or other private addresses. Directly configured Xtream/Stalker portals and private playlist servers remain supported without this flag. Prefer the in-app source-scoped “Allow source” action for a trusted EPG URL.
  • IPTVNATOR_ALLOW_INSECURE_TLS=1 disables certificate validation for remote playlist imports and refreshes for the whole Electron process. Prefer the in-app host-scoped trust action for a trusted provider with a self-signed or otherwise invalid certificate.

If the local Nx daemon gets into a bad state before rerunning Electron, reset it:

$ pnpm nx reset

To run only the Angular app without Electron, use:

$ pnpm run serve:frontend

Disclaimer

IPTVnator doesn't provide any playlists or other digital content.

Trademark

The name "IPTVnator" and the IPTVnator logo are unregistered trademarks of the project owner. The MIT license covers the source code only — it does not grant rights to the name or logo. Forks and redistributions (including app-store submissions) must use a different name and their own icon. See TRADEMARK.md for details.

All Contributors

Languages
TypeScript 81.4%
JavaScript 8.9%
HTML 3.3%
SCSS 2.8%
Astro 1.3%
Other 2.1%