No description
  • C# 79%
  • HTML 17.9%
  • Python 3.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Tiago Águeda 82bc7144bd
All checks were successful
build / dotnet (push) Successful in 1m57s
build / release (push) Successful in 2m24s
Channel number offset and guide time zone settings
- "Channel number offset", 0 by default: Jellyfin's guide sorts every
  tuner's channels by number together, so each plugin can keep a block of
  its own (DIGI TV starts at 5001).
- "Guide time zone": Orange's guide gives exact times (TF1's JT 20h at
  19:00 UTC in November), so the default corrects nothing. A zone reads
  the times as clock times there and places them in Paris, or La Reunion
  for its guide, should Orange ever change. Applied while writing, so the
  cached guide is never corrected twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:39:53 +01:00
.forgejo/workflows Scan in the plugin and set up Live TV by itself 2026-11-02 04:08:05 +01:00
assets Target Jellyfin 12, list scrambled channels, add French, publish via Forgejo 2026-11-01 18:44:55 +01:00
src/Jellyfin.Plugin.OrangeFrance Channel number offset and guide time zone settings 2026-11-04 11:39:53 +01:00
tests/Jellyfin.Plugin.OrangeFrance.Tests Channel number offset and guide time zone settings 2026-11-04 11:39:53 +01:00
tools Scan in the plugin and set up Live TV by itself 2026-11-02 04:08:05 +01:00
.gitignore Scan in the plugin and set up Live TV by itself 2026-11-02 04:08:05 +01:00
build.yaml Channel number offset and guide time zone settings 2026-11-04 11:39:53 +01:00
image.png Target Jellyfin 12, list scrambled channels, add French, publish via Forgejo 2026-11-01 18:44:55 +01:00
Jellyfin.Plugin.OrangeFrance.sln Target Jellyfin 12, list scrambled channels, add French, publish via Forgejo 2026-11-01 18:44:55 +01:00
README.md Channel number offset and guide time zone settings 2026-11-04 11:39:53 +01:00

jellyfin-plugin-orange-france

Live TV from an Orange France subscription, inside Jellyfin.

Orange delivers linear TV to the Livebox as RTP multicast on the local network. This plugin finds those streams, and turns them into an M3U playlist and an XMLTV guide for Jellyfin's own M3U tuner and XMLTV provider, which it sets up itself. The channels appear in the guide alongside everything else, and Jellyfin's DVR can record them.

Two limits decide what actually plays:

  • Scrambling. Most channels are scrambled under Orange's conditional access, which only the Orange decoder can descramble. The plugin still lists them, with their guide, marked unavailable (GULLI ❌); they do not play. Arte is the clear channel seen so far.
  • Eight channels at once. The Livebox serves at most eight multicast channels at a time to the whole network. The plugin limits Jellyfin to six by default and keeps its own scans within the rest. See How the Livebox serves channels.

Orange's over-the-top streams are not used for playback. They are Widevine protected, and Jellyfin has no CDM on the server or in any client, so they are not decodable. See Design for what that rules in and out.

Status

Early. What works today:

  • Multicast discovery and channel identification, inside Jellyfin
  • M3U playlist and XMLTV guide, added to Live TV automatically
  • Recording through Jellyfin's own DVR
  • Orange metadata: lineup, logos for dark or light themes, preset numbers
  • EPG from the Orange guide API, with season and episode numbers
  • Scrambled channels listed as unavailable
  • La Réunion feeds, with La Réunion's own guide and presets
  • Status page: last scan and update, every known channel, Live TV setup
  • Catchup / start-over (OTT only, so blocked on DRM)

Requirements

  • Jellyfin 12.0 or later
  • An Orange France subscription with the TV option, and a Livebox
  • The Jellyfin host wired to the Livebox — multicast does not survive WiFi
  • Docker: host networking. Bridge networking drops multicast entirely.

Install

Add the plugin repository in Dashboard → Plugins → Repositories:

https://source.tiagoagueda.com/tiagoagueda/jellyfin-orangetv/raw/branch/manifest/manifest.json

then install Orange France from the catalogue and restart Jellyfin. That is the whole setup. At startup the plugin:

  1. scans the Orange multicast range for the channels the Livebox serves (about 40 minutes, first run only),
  2. matches them to Orange's lineup and fetches the guide,
  3. writes the playlist and guide under <data>/orange-france/, e.g. /config/data/data/orange-france/ in the linuxserver image,
  4. adds to Dashboard → Live TV an M3U Tuner named "Orange France", limited to six streams, and an XMLTV guide enabled for that tuner only.

Dashboard → Plugins → Orange France then shows what was found.

Settings and status

The settings page follows each viewer's interface language, English or French.

Status shows the last scan and update, whether Live TV is set up, and every known channel: its preset, whether it is clear or scrambled, when it last answered, and whether it has a guide. Scan now sweeps the range again.

Two scheduled tasks, under Dashboard → Scheduled Tasks → Live TV, keep things current:

Task When What
Orange France: update channels and guide startup, every 12 h, on saving the settings checks each known channel (answers? scrambled?), rewrites the playlist and guide, refreshes the Live TV guide
Orange France: scan multicast channels Mondays 04:00, and Scan now tries all 3,327 addresses from 232.0.1.1 to 232.0.13.255 for new channels, then runs the update

A channel that has not answered for 30 days is dropped at the next scan.

Settings:

  • Region: metropolitan France (default), La Réunion, or both. The Livebox serves La Réunion feeds too. They take their guide and presets from La Réunion's guide; listed next to metropolitan channels they are named "… Réunion" and numbered from 2000.
  • Picture quality: "HD only" (default) lists each channel once, in its best version: UHD, then HD, then SD for a channel that has no HD. "SD and HD" lists every version Orange sends.
  • List scrambled channels: on by default; off leaves them out. With "HD only", a channel whose HD version is scrambled but SD version clear still shows, in SD.
  • Channels at once: how many Orange channels Jellyfin may play or record together, six by default. Applied to the Orange France tuner. Keep it below eight: the Livebox's limit covers the whole network.
  • Unavailable mark: a red cross after the name (the default), or the text "(unavailable)" for clients whose font has no emoji
  • Channel logos: for dark themes (default) or light ones. Orange draws some logos twice, with white or black lettering.
  • Channel number offset: added to every channel number, 0 by default. Jellyfin's guide sorts every tuner's channels by number together, so a different offset per plugin keeps each in a block (DIGI TV starts at 5001).
  • Guide time zone: Orange's guide gives exact times, so leave it on Automatic. A zone reads the times as clock times there, should Orange ever change that.
  • Interface IP: only needed on a multi-homed host
  • Language: English or French for the "(unavailable)" text. Automatic follows the server's display language. It is one setting for the whole server because channel names are shared by every user.
  • Set up Live TV automatically: on by default, see below

Choosing channels

Jellyfin puts every tuner's channels in one list, and its guide filters only by programme type and favourites, so the plugin offers two ways to narrow down its own channels:

  • For everyone: untick a channel in the status table. It leaves the playlist and guide at the next update, which saving starts.

  • Per user: after each guide refresh the plugin tags its channels, for Dashboard → Users → (user) → Parental control → allowed or blocked tags:

    Tag On
    Orange France every channel of the plugin
    Orange France: clear / Orange France: scrambled channels that play / do not
    Orange France: metropolitan / Orange France: La Reunion each region's feeds

    For example, blocking Orange France: scrambled shows a user only the channels that play. The tags are the same in every language, so these rules survive a change of the language setting. Tags added to a channel by hand are kept.

The playlist cannot carry tags itself: Jellyfin's M3U tuner reads group-title but does nothing with it.

Channel streams

The settings page's Channel streams tab gives a channel a stream of your own, played instead of Orange's multicast feed: for example the broadcaster's free stream for a channel Orange scrambles. Such a channel loses its ❌, is tagged Orange France: clear, and keeps Orange's guide; with "HD only", the version with a stream wins over a scrambled one.

The URL must be absolute, with a scheme Jellyfin's tuner opens (http, https, udp, rtp, rtsp, rtmp, rtmps or srt), a host and no spaces. Whether the stream actually plays is not checked. An invalid URL is kept, so it shows again for fixing, but left out of the playlist.

Live TV setup

An M3U tuner or XMLTV guide that already reads the plugin's file is reused, as is one reading a file of the same name elsewhere (it is repointed): a setup made by hand for an earlier version carries over. The guide is kept limited to the plugin's tuner, so Orange programmes never map onto another tuner's channels. If you delete either entry it stays deleted; untick and tick Set up Live TV automatically to have it added again.

With automatic setup off, add the two entries yourself; the settings page shows the file paths.

Upgrading from 0.2 or earlier

Earlier versions read a multicast_map.json written by a Python scanner. On the first update the plugin imports that map if it has none of its own, and repoints a tuner and guide added by hand to its new files. The old files can then be deleted.

Releasing

Push a v* tag (git tag v0.3.0 && git push origin v0.3.0). CI builds the package with the tag as its version, attaches it to a Forgejo release and adds it to manifest.json on the manifest branch. Update changelog in build.yaml first: it becomes the release notes Jellyfin shows.

Building by hand

dotnet build -c Release src/Jellyfin.Plugin.OrangeFrance

Copy Jellyfin.Plugin.OrangeFrance.dll into <jellyfin-config>/plugins/Orange France/ and restart the server.

For a proper package, use JPRM:

jprm plugin build .

Design

Two delivery paths exist for Orange France. This plugin deliberately uses the first:

Multicast (used). RTP-encapsulated MPEG-TS on the LAN, linear channels only, on 232.0.x.x port 8200. ffmpeg opens it directly, but only the clear channels decode: most are scrambled under Orange's conditional access, with only their SI tables left in the clear (about 97% of packets carry scrambling bits). The scanner records which is which, and the plugin lists scrambled channels as unavailable rather than descrambling them, which would be circumvention.

The SDT on the live feed carries a Globecast slug rather than a display name, e.g. arte-hdplus-t401-dlv-01. ChannelNameMatcher.CleanServiceName keeps the part before the quality tag (arte) for matching against the lineup, and a short alias table covers slugs that differ from the lineup name (equidia_live is "EQUIDIA"). France 3's regional feeds are f3_<region> for "FRANCE 3 "; three of them spell the region differently (f3_champagne, f3_nord_pdc, f3_midi_pyrennees). The distribution code tells the regions apart: dlv-01 is metropolitan, dlv-02 La Réunion.

How the Livebox serves channels

Measured on one line, with no Orange decoder connected:

  • A plain IGMPv2 join from the host starts a channel at once, within 0.15 s, and leaving stops it within seconds.
  • The Livebox serves at most eight groups at once, to the whole network. The ninth join is silently ignored, which looks just like an address with nothing behind it.
  • A join to an empty address takes a slot too, and a slot can take several seconds to come back after a leave. If a leave goes unanswered, the membership only expires after about four minutes.

An earlier version misread this: a scan that probed eight addresses at once filled the slots, later joins failed, and the groups still holding slots looked like a set the Livebox chose by itself.

So the scanner holds one address at a time and, after every 20, rejoins a channel known to answer. If that stays silent, the slots are full, from the scan or from someone watching, and the batch is redone once they free up. Jellyfin's tuner is limited to six streams so that viewing and recording leave room for that.

OTT (not used)

mediation-tv.orange.fr serves DASH behind Widevine. Kodi's addon plays it because InputStream Adaptive runs a CDM on the client, which is where a CDM belongs. Jellyfin's server-side ffmpeg pipeline has no CDM and no client implements EME, so these streams cannot be decoded. A proxy does not change this: relaying encrypted bytes leaves them encrypted, and decrypting them means stripping DRM, which this project does not do.

The legitimate long-term path would pass the MPD and license URL through to a client that holds a real CDM, the browser's via EME. That needs client-side work that does not exist yet.

Why a tuner, not a Live TV service

The plugin first implemented Jellyfin's ILiveTvService. Jellyfin sends recordings for a service's channels to that service, so recording would have meant reimplementing the DVR: schedules, series rules, padding, conflicts. Feeding the built-in M3U tuner and XMLTV provider instead gets all of it, plus Jellyfin's own stream probing.

A tuner of the plugin's own (ITunerHost) would avoid the files, but the code Jellyfin's tuners share for opening and buffering streams lives in Jellyfin.LiveTv, which plugins cannot reference, so the DVR's stream handling would have to be rewritten. Adding the built-in tuner through ITunerHostManager and IListingsManager keeps to public API.

Metadata

The OTT API is still used for metadata, which needs no authentication:

Data Endpoint Key fields
Lineup pds/v1/live/ew lcn, idEPG, name, logos, technicalChannels
Guide live/v3/applications/STB4PC/programs channelId, diffusionDate, duration, season, episodeNumber

The guide joins to the lineup on program.channelId == channel.idEPG, and the lineup joins to multicast on the folded channel name — see ChannelNameMatcher. Across the 433 published channels the fold is unique for 432; the lone clash is a UHD simulcast, resolved by preferring the lower lcn.

Logos come in four variants. mobileAppliDark (183×183, white lettering) is used for dark themes and mobileAppli for light ones; their paths are relative to https://proxymedia.woopic.com/api/v1/images/2090.

Quirks established from live responses rather than documentation: programType only ever takes EPISODE or OTHER, so films are recognised by genre == "Film"; csa is a category from 1 to 5 that maps to the French ratings (2 → -10, 5 → -18), not a number of years; and the programme id changes with every broadcast, so episodes are told apart by season and episode number instead.

tools/probe_orange_api.py dumps the lineup and a slice of the guide to data/ with a schema summary, for when Orange changes a field.

Layout

build.yaml                        JPRM manifest
image.png                         plugin catalogue image (16:9, from assets/)
assets/orange-logo.svg            source logo
src/Jellyfin.Plugin.OrangeFrance/
  Plugin.cs                       plugin entry point
  PluginServiceRegistrator.cs     DI registration
  Configuration/                  settings + dashboard page (en/fr)
  Export/                         M3U + XMLTV writers, update task, Live TV setup
  Lineup/                         joins channels to the Orange lineup, region filter
  Localization/                   server-side strings (en/fr)
  Multicast/                      MPEG-TS parsing, scanner, scan task, channel store
  Orange/                         metadata API client, models, name matcher
  Status/                         status model and the settings page's API
tests/
  Jellyfin.Plugin.OrangeFrance.Tests/
tools/
  probe_orange_api.py             metadata API schema probe
dotnet test tests/Jellyfin.Plugin.OrangeFrance.Tests