Alumira

Alumira — Spécification

Statut : v1 — implémentée. Ce document décrit ce que fait Alumira, et pourquoi. Le code fait foi : quand ils divergent, c'est le code qui a raison et ce document est corrigé.


0. Vision & principes

Alumira est un serveur multimédia auto-hébergé pour les films et séries d'un foyer. Il lit les fichiers là où ils sont, les reconnaît, et les présente comme le ferait un service de streaming à soi : les visuels d'abord, un profil pour chacun, des suggestions, toutes les versions et toutes les langues d'un titre. Il se regarde sur un ordinateur, un téléphone, ou une télé avec sa télécommande.

Deux rôles. Le spectateur est passif : il choisit ce qu'il regarde et le regarde, rien de plus. Rien à régler, trier ni corriger : titres, visuels, versions, langues et sous-titres lui arrivent prêts. L'administrateur est éditorialiste. Il a la main sur tout, pour peaufiner le serveur dans le moindre détail : le fichier qui sert chaque version, le texte d'un titre, ses visuels et son âge limite, les collections et la page d'accueil, les sous-titres, les qualités, le matériel, qui voit quoi.

Chaque fichier est diffusé de l'une de deux façons, choisie pour son dossier, puis fichier par fichier :

Alumira tourne sur ce qu'un foyer a déjà : un NAS (Docker), un ordinateur (une application pour macOS et Windows), n'importe quel serveur. D'autres ordinateurs du réseau peuvent l'aider à encoder. Il se rend accessible de l'extérieur en un clic (Alumira Connect, §10.3).

Les modèles de langage sont des aides facultatives, choisies par l'administrateur : ils traduisent des sous-titres, lisent en texte des sous-titres en images, écrivent des sous-titres en écoutant, nomment des chapitres, reconnaissent la langue d'une piste.

Principes directeurs

  1. Media first. UI très graphique, simple, to the point ; UX simplissime. Une identité propre : on n'imite pas les plateformes de streaming.
  2. Des spectateurs passifs, un éditorialiste aux commandes. Les spectateurs ne décident que de ce qu'ils regardent : aucun réglage ne se met entre eux et l'image. Chaque choix revient à l'administrateur, et chacun peut se faire à la main, dans le détail ; ce qu'Alumira fait seul est un premier jet que l'administrateur peut reprendre.
  3. Des mots simples pour les spectateurs. Aucun jargon technique côté spectateur (codecs, protocoles, formats de sous-titres, systèmes de classification) : les termes grand public quand il en faut un (HDR10, 4K), sans infobulle pour les expliquer. L'administration garde les termes techniques là où ils sont le sujet.
  4. Préparé à l'avance plutôt qu'à la lecture. Tout ce qui peut être fait avant qu'on appuie sur lecture l'est : encodages, sous-titres, vignettes de navigation, chapitres, images. Le transcodage à la lecture n'existe que pour les fichiers en lecture directe, seulement sur la puce vidéo, dans un nombre fixe de places.
  5. Stems éclatés, recomposés au service. Chaque piste (vidéo par rendition, audio par langue et format, sous-titre) est un stem indépendant ; le client reçoit un manifest multi-piste / multi-bitrate généré à la volée.
  6. Évaluation paresseuse plutôt que liens. On stocke des identités (empreintes, règles) et on résout au moment du besoin plutôt que de maintenir des liens qui se désynchronisent.
  7. Les sources sont sacrées. Une store n'est jamais une source ; une source n'est jamais modifiée, sauf en mode inbox explicite.
  8. La bibliothèque est locale. Les métadonnées importées (TMDB) appartiennent à la bibliothèque une fois importées : modifiées sur place, jamais surchargées par une couche au-dessus. Seul un rafraîchissement explicite réimporte un titre.
  9. L'IA aide, n'est jamais requise. Tout fonctionne sans modèle. Les modèles sont appelés par des tâches, à la demande, avec ce qu'ils consomment affiché.
  10. Code impératif et simple. Pas d'OOP ; favoriser la vitesse de build et d'itération.

1. Périmètre

Aujourd'hui

Plus tard


2. Glossaire

Terme Définition
Source Emplacement (dossier/disque) scanné pour trouver des fichiers originaux. Lecture seule (watch) ou consommée (inbox).
Diffusion Comment un fichier se lit : pré-encodé (ses stems) ou lecture directe (le fichier lui-même, transcodé à la volée au besoin). Réglée par fichier.
Store Emplacement où Alumira écrit les stems normalisés. Nombre illimité. Une store n'est jamais une source.
Empreinte Identité d'un fichier source, indépendante de son chemin.
Catalogue des sources Index empreinte → chemins actuels (0..n).
Stem Une piste normalisée unique, stockée comme un fichier fMP4 CMAF (ex. « vidéo 1080p H264 SDR », « audio fr-FR AAC 2.0 », « sous-titre en WebVTT »).
Rendition Un niveau de qualité vidéo (résolution × codec × plage dynamique × profondeur). Une rendition = un stem vidéo.
Rung Un échelon de la ladder (définition d'une rendition à produire).
Ladder Ensemble ordonné de rungs + définitions audio/sous-titres à produire pour un média.
Profil d'encodage Configuration nommée et versionnée de la ladder, éditable par l'admin (Administration → Profils).
Pass Groupe de rungs encodés ensemble dans une même chaîne ; les passes sont ordonnées.
Work Œuvre : film, série, saison, épisode.
Edition Une version d'une œuvre (« 1977 Cut », « Blu-ray 2011 », « Deluxe »…), servie par un fichier. La première est la version principale.
Extra Bonus (making-of, interview…) rattaché à une œuvre ou à une édition.
Groupe Une collection : manuelle ou dynamique.
Découpage Comment une série est découpée en saisons : l'ordre de diffusion de TMDB ou l'un de ses groupes d'épisodes.
Découpage propre Une version d'une série découpée en saisons et épisodes à elle (un coffret DVD), pas ceux de TMDB.
Compte Une personne connectée : mot de passe, passkeys, appareils, limites. Il contient quelques profils.
Profil Un spectateur d'un compte : historique, favoris, langues, âge ; un profil Enfant ne voit que les titres pour enfants.
Public À qui s'adresse un titre : Enfant, Tous ou Adulte.
Worker Processus d'encodage (intégré au serveur ou distant).
Signature d'encodeur Identité d'une configuration d'encodage garantissant une sortie homogène (cf. §7.3).

3. Architecture générale

                 ┌───────────────────────────────────────────────┐
                 │                   Serveur                     │
 Sources ──scan──▶  Catalogue ─▶ Queues ─▶ Scheduler ──────┐     │
 (watch/inbox)   │  empreintes    │                        │     │
                 │                ├─ identification        ▼     │
                 │                ├─ normalisation   Worker intégré
                 │                ├─ images                │     │
                 │                ├─ QA                    │     │
                 │                └─ IA (modèles)          ▼     │
                 │  SQLite ◀── métadonnées           Stores (stems fMP4)
                 │                                         │     │
                 │  HTTP : app web + API + manifests HLS ◀─┘     │
                 │         + fichiers en lecture directe,        │
                 │           transcodés à la volée (puce vidéo)  │
                 └──────────────▲──────────────────────▲─────────┘
                                │                      │
                        Client web / TV          Workers LAN
                                                 (source streamée
                                                  en HTTP)

4. Sources, diffusion & catalogue

4.1 Modes de source

Mode Comportement
watch Lecture seule stricte. Alumira ne modifie, ne déplace, ne supprime jamais rien (seedbox, disque d'archive).
inbox Dossier de dépôt consommé : une fois l'encodage d'un média complet, son fichier source est supprimé, avec les sous-titres à côté et les dossiers qu'il laisse vides. Il n'y a pas de corbeille.

Dans un dossier inbox, il reste d'un fichier ce que garde son profil d'encodage :

L'admin est averti qu'un profil modifié plus tard ne pourra partir que du meilleur stem existant, ou de la copie de l'original.

À l'ajout d'un dossier, l'admin choisit entre trois façons, dites en mots simples : Pré-encodé, Pré-encodé, puis supprimé (inbox), Lecture directe, transcodée à la volée (un dossier watch dont les fichiers sont en lecture directe).

4.2 Diffusion

Chaque fichier a sa propre diffusion : pré-encodé ou lecture directe. Un nouveau fichier prend le choix de son dossier à son arrivée, puis garde le sien. Un fichier d'un dossier inbox est toujours pré-encodé.

L'admin la change dans Administration → Diffusion, fichier par fichier ou par lots (filtres : diffusion, non transcodable, dossier, chemin). Changer le choix d'un dossier vaut pour les fichiers à venir ; il propose ensuite de l'appliquer aux fichiers déjà là.

Un fichier qui change continue de se lire comme avant jusqu'à ce que la nouvelle façon soit prête :

La source d'un fichier en lecture directe n'est jamais supprimée. Quand elle a disparu, le fichier est illisible : la Maintenance le liste en source disparue, bloquante.

4.3 Fichiers en lecture directe

Rien n'est encodé ni stocké à l'avance, à part de petits extras. Chaque fichier est préparé une fois, à son arrivée :

Il se lit dès qu'il est identifié et préparé, en HLS comme les autres. Sa vidéo et ses pistes AAC sont des renditions virtuelles dont ffmpeg copie les segments depuis le fichier à la demande, sans encodage.

Chaque segment est lu avec les temps du fichier et coupé exactement à ses images clés. Toutes les pistes sont retardées des mêmes quelques millisecondes dont ses premières images se décodent en avance (jamais sous 0, en phase) : les fragments se suivent exactement, et un saut se lit aussitôt.

4.4 Transcodage à la volée

Un appareil qui ne sait pas décoder un fichier en lecture directe, ou dont la connexion est trop lente pour lui, reçoit des qualités transcodées à la volée.

Son échelle. Chaque profil d'encodage a une échelle à la volée, réglée sur la page Profils comme ses encodages : une résolution, un codec (H.264 ou HEVC) et un débit par barreau. Par défaut, en H.264 : 1080p 8 Mb/s, 720p 4,5 Mb/s, 480p 1,8 Mb/s, 360p 900 kb/s, 240p 450 kb/s. La changer ne réencode rien.

À côté du fichier. Ses qualités sont des renditions virtuelles à côté du fichier lui-même, coupées sur les mêmes segments : un lecteur passe de l'une à l'autre sans coupure. Chaque segment est fait en morceaux de 3 s au plus, envoyés à mesure : un démarrage ou un saut se lit en quelques secondes. Jamais au-dessus du fichier : deux barreaux au-dessus de sa taille n'en font qu'un, à sa taille ; quand le fichier lui-même se lit, seuls les barreaux sous les trois quarts de son débit sont proposés.

Sur la puce vidéo seulement. VideoToolbox, NVENC, Quick Sync, AMF, VA-API : sur NAS aussi (Synology, QNAP, Unraid avec un iGPU Intel ou une carte NVIDIA). Décodé par la puce quand elle le peut, sans images B ; un serveur sans encodeur matériel n'en propose aucune. Ses formats sont testés un par un au démarrage (codec et profondeur, images relues depuis la puce).

Sur une puce Intel l'image n'en sort pas. Elle y est réduite comme pour les encodages (§6.3) : par moitiés au plus, avec le meilleur filtre de la puce. Ses couleurs y sont fixées aussi (BT.709 en plage limitée, ou le HDR gardé), si bien que les sources SD et celles sans étiquette se convertissent aussi.

HDR. Un fichier HDR reste HDR pour un appareil qui l'affiche : HEVC 10 bits, en PQ ou HLG comme le fichier, quel que soit le codec de l'échelle, si bien que la luminosité ne saute jamais quand le lecteur change de qualité. Sans encodeur matériel pour cela, le fichier seul est proposé. Tout autre appareil le reçoit ramené en SDR.

Quand ce n'est pas possible. Un fichier que la puce ne sait pas décoder, et que le processeur ne peut pas suivre (seulement des fichiers légers : 8 bits SD, 720p des codecs légers), ne propose que lui-même. L'appareil reçoit la raison, et la Maintenance le liste (Transcodage indisponible, avec les appareils qui n'ont pas pu le lire), pour qu'il soit pré-encodé.

Partagé équitablement. Les segments sont faits à la demande, le suivant pendant ce temps, et gardés en mémoire (512 Mo) pour les retours en arrière et les autres spectateurs. Un segment en cours continue tant que quelqu'un l'attend. Un lecteur a 3 morceaux coupés ou faits à la fois (sa vidéo, son son, le suivant), le serveur 16 copies ; une demande au-delà attend son tour.

Un maximum de spectateurs sont transcodés à la fois : 2 par défaut, de 1 à 32, sur la page Profils (un encodeur matériel est autant occupé à 100 kb/s qu'à 25 Mb/s). Au-delà personne n'est servi moins bien : un appareil qui sait lire le fichier le lit tel quel, un autre est invité à réessayer dans quelques minutes ou sur un autre appareil. Une place se libère 45 s après son dernier segment.

4.5 Détection

4.6 Empreintes

4.7 Catalogue


5. Pipeline & identification

détection ─▶ probe ─┬─▶ identification ─────────────┐
                    ├─▶ normalisation (passes 1..n) ├─▶ visible
                    │   ou préparation (directe)    │
                    └─▶ images (dérivés)            ┘
                         QA (VMAF échantillonné) ─▶ maintenance
                         tâches IA (à la demande)

5.1 Fournisseurs de métadonnées

5.2 Identification

Chaque fichier est lu d'après son nom et ses dossiers : un film, un épisode, un bonus, quelle version (montage). Il est ensuite cherché sur TMDB.

Pourquoi un fichier n'est pas identifié est gardé sous forme de code, et dit en toutes lettres dans chaque langue : « Aucune série TMDB ne s'appelle “Les Schtroumpfs” (1981) », « Son nom dit S02E05, mais TMDB n'a pas de saison 2 pour cette série »…

La page Identification a quatre onglets : À identifier, Identifiés, Corrigés à la main, Tout.


6. Normalisation

Tout ce qui suit concerne les fichiers pré-encodés ; les fichiers en lecture directe en partagent les sous-titres, chapitres, images et vignettes de navigation.

6.1 Préparation de la source

Avant l'encodage, dans la chaîne de filtres :

6.2 Règles de cohérence des rungs

6.3 Chaîne de downscale

Point fondamental : la qualité des basses résolutions doit être impeccable.

6.4 Passes ordonnées

Les rungs d'une ladder sont répartis en passes, encodées dans l'ordre. Chaque pass = un décodage + une chaîne. Le manifest intègre les rungs dès qu'ils sont prêts.

L'installation propose deux points de départ, décrits par la façon dont la bibliothèque est regardée plutôt que par des codecs : Beaucoup de spectateurs, pleine qualité (la ladder par défaut ci-dessous) et À la maison, parfois en déplacement (peu de qualités, bien espacées : la 4K pour le salon, des versions très légères pour un voyage). L'un comme l'autre se modifie ensuite.

Ladder par défaut :

Pass Contenu
1 Vidéo H264 8 bit SDR : 1080p, 480p, 240p. Audio : toutes les langues, AAC 2.0 128 kb/s (downmix, loudnorm) + AAC 5.1 320 kb/s si source ≥ 5.1 + AAC 2.0 léger 64 kb/s. Sous-titres. Vignettes de navigation. Chapitres.
2 Vidéo HEVC 10 bit (HDR conservé si source HDR) : 2160p, 1080p ; qualités de secours HEVC 8 bit SDR : 480p, 240p (bien plus légères que le H264 à image égale sur une connexion faible). Audio : E-AC-3 5.1 448 kb/s si source ≥ 5.1, Opus 2.0 96 kb/s / 5.1 192 kb/s / 7.1 256 kb/s + Opus 2.0 léger 48 kb/s.
dernière Copie de l'original : les pistes de la source remuxées telles quelles vers la store (sans réencodage). Ne dépend de rien, la plus longue à finir. Par profil d'encodage : jamais, pour les dossiers inbox seulement (le défaut), ou pour tous les fichiers.

Une qualité qu'une passe ultérieure du même profil refait remplace la précédente : elle n'est plus proposée, et supprimée dès que plus personne n'a le média ouvert.

Codecs supportés : vidéo H264, HEVC, AV1 ; audio AAC-LC, Opus, E-AC-3 ; layouts 2.0, 5.1, 7.1.

6.5 Débit & images clés

6.6 Audio

Audio léger. Chaque langue a une version stéréo légère (palier light), dans un groupe audio à part (aac-light, opus-light). Le manifest l'attribue aux qualités vidéo de 480p et moins, pour que le son ne pèse pas plus lourd que l'image sur une connexion faible ; elle n'est utilisée que si chaque langue en a une.

Elle porte le nom de la piste stéréo qu'elle remplace : le lecteur garde le choix du spectateur d'un groupe à l'autre, et son menu ne liste que des langues et des formats (le codec est laissé au lecteur).

Chaque piste source à part. L'audio n'est jamais chunké (problèmes de priming aux jonctions) : il est encodé d'un seul tenant, pendant les chunks vidéo. Chaque piste a son propre FFmpeg (à plusieurs, une piste mono pouvait s'arrêter en route sans erreur), est gardée dès qu'elle est faite (jamais réencodée), et échoue seule.

Une piste impossible à encoder est laissée de côté : la pass continue, le média se lit sans elle, la Maintenance le signale (encodages incomplets), et un nouvel essai ne fait que les pistes manquantes.

6.7 Sous-titres

Source Traitement
Texte (SRT, WebVTT, mov_text…) → WebVTT.
ASS/SSA stylé → WebVTT (fallback universel) + original conservé pour un rendu riche côté client (libass wasm, JASSUB).
Image (PGS, VobSub) Overlay image : sous-titres conservés comme images, rendus par le client ; ou lus en texte par un modèle de langage, quand l'admin en choisit un pour cela (§6.9).

Les flags sont conservés et exposés : forcé, SDH (malentendants), langue BCP-47. Les spectateurs les voient en mots simples : une piste stylée n'est pas distinguée dans le lecteur (c'est la seule listée), et se lit « stylés » dans les téléchargements.

Une piste par langue et par usage (complets, forcés) : la meilleure.

Pistes en langue inconnue (sans étiquette dans leur fichier, und, mul) : les lecteurs les affichent ainsi, et elles ne comptent pour aucune langue. La Maintenance les liste, chacune avec la langue à lui donner (la langue originale du titre en premier) et ses premières répliques.

Forcé ou non. Une piste de sous-titres se dit forcée ou non depuis la fiche technique du média, par un interrupteur. C'est gardé par son flux, par-dessus le flag et le titre de son fichier ; celui d'un fichier de sous-titres est gardé par-dessus son nom aux scans.

6.8 Sous-titres venus d'ailleurs

Fichiers qui ne sont pas dans la source : trouvés à côté d'elle (<nom>.<langue>[.forced|.sdh].srt, ou dans un dossier Subs), ou téléchargés depuis OpenSubtitles.

OpenSubtitles (facultatif ; chaque installation apporte sa propre clé API, un compte augmente le quota de téléchargements par jour). Des sous-titres sont voulus dans les langues de sous-titres par défaut, sauf si le titre les a déjà, ou du son dans cette langue.

6.9 Sous-titres par modèles de langage

Chacune de ces fonctions est à la demande et facultative, avec un modèle choisi pour son usage dans les Paramètres (§13.3). Chaque résultat est un fichier de sous-titres comme un téléchargé ; ses stems sont marqués comme faits par une machine pour l'admin, jamais pour les spectateurs.

Traduction. Depuis la fiche d'un média, l'admin choisit les sous-titres texte à traduire (tous sauf une traduction ; les meilleurs proposés d'abord : complets, dans la langue originale, puis l'anglais) et les langues dans lesquelles les traduire (celles sans sous-titres ; celles par défaut qui manquent cochées). En lot depuis la Maintenance : les meilleurs sous-titres, dans chaque langue par défaut qui manque à chaque média.

Sous-titres en images lus en texte. Avec un modèle choisi pour cela, chaque import lit ses sous-titres en images ; ceux du catalogue se lisent à la demande depuis la Maintenance.

Sous-titres depuis la parole. Chaque piste audio peut avoir des sous-titres faits en l'écoutant.

Un modèle de parole choisi pour l'usage Écoute entend la piste (chez le fournisseur des paramètres IA : Grok Voice Transcribe de xAI pour l'instant ; facturé à l'heure de son).

Ses mots, chacun daté et son locuteur distingué, sont découpés en répliques comme le ferait un sous-titreur. Ce qu'un modèle invente sur la musique, des crédits de sous-titres ou une longue phrase en boucle est écarté.

Puis le modèle des sous-titres, s'il y en a un, reçoit les répliques par 80, quatre lots à la fois, avec la scène autour, le titre, le résumé et les noms des personnages. Il écrit ce qui est dit : mots et noms mal entendus corrigés, hésitations et faux départs retirés, condensé seulement au-delà de ce que la réplique peut tenir dans son temps (qui lui est donné), les tirets des locuteurs gardés, un bruit pris pour de la parole vidé. Les horaires ne passent jamais par lui.

Le résultat est un fichier transcription (stems nommés ext.stt-<flux>), dont les traductions peuvent partir. Il prend deux tâches dans la file IA, l'écoute et l'écriture ; ce qui a été entendu et chaque lot réécrit sont gardés jusqu'à ce que le fichier soit fait.

Reconnaissance de la langue. Depuis Langue inconnue dans la Maintenance, la Reconnaissance IA envoie au modèle des sous-titres, pour chaque piste en langue inconnue qui se lit en texte :

Le modèle répond la langue de chaque piste et si elle est forcée (une seule réplique suffit à dire une langue ; une ou quelques répliques sont des forcés). Les réponses s'appliquent comme celles de l'admin ; une piste laissée inconnue est journalisée.

6.10 Vignettes de navigation & chapitres

Chapitres sans nom : ceux numérotés par leur outil d'authoring : « Chapter 01 », « Scene 2 », « 第3章 », un horaire, rien. Ils sont reconnus dans toutes les langues sans modèle : leurs titres, sans numéros, horaires ni ponctuation, sont identiques. Trois au moins, ou la moitié des chapitres, et le média est listé dans la Maintenance (et dans Manquants de sa fiche).

À la demande, un modèle de langage (un usage à part) les nomme d'après les dialogues des meilleurs sous-titres texte, en un appel :

Les noms sont gardés à côté des chapitres avec leurs débuts (un réencodage qui extrait les mêmes chapitres les garde), aussi dans le source.json de la store. Le lecteur montre ceux de sa langue d'interface. Ils se retirent depuis la fiche du média (Ajouts).

Chapitres créés quand il n'y en a pas, pour un média de 15 minutes ou plus dont la source n'en a aucun, ou un seul.

À chaque encodage de la passe qui fait ses extras, ses changements de scène sont mesurés sur sa plus petite rendition et gardés : changements de plan et leur force, passages noirs, silences d'une rendition audio (une à deux minutes de décodage).

À la demande, les plus forts deviennent des points de coupure : un fondu, du noir, un changement de plan net, d'autant plus que personne ne parle et que rien ne sonne ; à 30 s au moins les uns des autres, le début d'abord. Le modèle reçoit les dialogues horodatés, les passages sans dialogue et les points de coupure. Il choisit environ un chapitre toutes les 7 minutes, chacun commençant à un point de coupure (une scène muette peut en ouvrir un : l'image dit où elle est), et les nomme comme ci-dessus.

Chaque début revient ensuite là où sa scène commence : avant l'échange qui le suit (une question, pas sa réponse), sur le meilleur repère 2 minutes avant au plus (la sortie d'un fondu d'abord, puis la fin d'un silence, puis un changement de plan net). Les repères où quelqu'un parle par-dessus le plan, ou entre deux répliques rapprochées, ne comptent presque pas. Les chapitres créés sont gardés avec leurs noms, et un réencodage les garde.

6.11 Contrôle qualité

La lecture ne propose à chaque appareil que les renditions qui valent leur débit. Celle qu'une autre rendition lisible bat (ou égale à 1 point près) pour moins de débit est écartée, sur cet étalon ; pas encore mesuré, sur le premier VMAF à hauteur égale, sinon le codec le plus efficace s'il est aussi moins cher. Les renditions HDR (le VMAF ne s'y applique pas) ne se départagent que par le codec.

Plus de débit apporte alors toujours une meilleure image : un H264 à deux fois le débit d'un HEVC aussi beau n'est jamais choisi.

6.12 Profils d'encodage : versionnage & résolution

Les profils sont versionnés, et un stem enregistre la version de profil qui l'a produit. Modifier un profil, ou en donner un autre à un média, rend ses stems obsolètes : une information dans la Maintenance, pas une erreur (ils se lisent toujours).

Rien n'est réencodé tout seul : l'admin les met à niveau, un média ou un lot.

La ladder effective d'un média est résolue paresseusement : défaut global → type (film / épisode / extra) → override média. L'override par média est prévu dans le modèle ; son UI viendra plus tard.

L'admin voit une estimation de taille lorsqu'il édite un profil. Le pré-encodage est refusé avec un profil qui ne fait aucune qualité ; Profils peut redonner à un tel profil les qualités équilibrées.


7. Workers & distribution

7.1 Workers

Les capacités sont auto-détectées au démarrage, par un encodage de test d'une seconde avec chaque encodeur connu du build (être compilé ne veut pas dire que le matériel est là) : codecs, matériel, 10 bit, HDR, et les formats que la puce vidéo décode.

Le résultat reste en cache jusqu'au changement de build FFmpeg (une semaine au plus) ; l'administration peut relancer les tests. Un test qui échoue lentement sur une machine chargée, ou qui contredit la détection précédente, est refait ; ce qui reste incertain est retesté 10 minutes plus tard.

7.2 Distribution par tranche de temps

Rien de fait n'est perdu.

Assemblage. Le serveur concatène les chunks en stems, chaque rendu une fois, en libérant ses chunks au fur et à mesure. Un chunk est vérifié avant d'être joint (ses fichiers couvrent sa tranche) : un chunk trop court est réencodé, seul.

Un seul encodage à la fois par média (une pass, un rendu retenté) : chacun remplace des stems que l'autre peut utiliser. Les encodages des autres médias continuent.

Un job en cours indique son étape sous la forme clé ou clé:détail (video:3/12 : chunk 3 sur 12), affichée de la même façon sur toutes les pages.

7.3 Signature d'encodeur

Pour éviter les inégalités de qualité au sein d'un même média :

7.4 Accès à la source


8. Stockage & service

8.1 Stores

Une store se décrit elle-même : aucun fichier de correspondance, aucune base nécessaire pour la comprendre.

Reprise. Une store ajoutée avec du contenu (remplie par une autre installation, ou avant la perte d'une base) est reprise par une tâche de fond. Chaque dossier de source devient un média avec ses stems, après vérification que chaque fichier est complet ; il se lit dès qu'il a image et son, identifié d'après le nom de la source. Quand le dernier utilisateur de la store semble encore actif (moins de deux jours), l'admin est prévenu ; rien n'est refusé.

Ses sources la rejoignent ensuite. Un fichier scanné dont l'empreinte a un dossier y prend sa place : il est analysé, et seul ce qui manque est encodé (rien quand ses stems suivent un autre profil : mis à niveau à la demande, §6.12). Les rendus sont mesurés à nouveau une fois la source là. Les sources sans stems s'encodent normalement ; les stems sans source restent lisibles (Maintenance : sources disparues sans perte).

La même source dans deux stores (un disque copié) : une copie est lue, l'autre est listée dans la Maintenance, à supprimer ou garder en secours.

8.2 Format des stems

8.3 Packaging

8.4 Téléchargement

8.5 Images

Formats. Une queue dédiée génère les dérivés (tailles multiples) en JPEG, plus AVIF quand la machine encode l'AV1, sinon WebP ; le navigateur reçoit le meilleur format qu'il accepte, servi avec un cache agressif.

Le flou d'abord. Chaque image garde un ThumbHash (environ 25 octets) envoyé avec les cartes et les pages : son flou s'affiche aussitôt, l'image par-dessus en fondu (50 ms) une fois entière, jamais dessinée en cours d'arrivée.

Jamais périmée. L'adresse d'une image porte sa version (quand ses tailles ont été faites) : gardée un an par le navigateur ; une image refaite, ou une nouvelle sous un numéro réutilisé, a une autre adresse. Là où la version n'est pas connue, le navigateur redemande à chaque fois (une réponse sans l'image tant qu'elle n'a pas changé).

Visuels. Posters, images de fond, logos transparents, portraits du cast, localisés ; couleurs dominantes extraites pour thématiser l'UI. Les images TMDB sont récupérées dans leur résolution originale, leurs tailles en sont tirées (images de fond jusqu'à 3840 px). Les images de fond sont choisies les plus nettes d'abord (par paliers : 4K, 2560, 1920), puis les mieux notées.

Derrière un titre (sa page, le grand visuel de l'accueil, le lecteur) : l'image de fond TMDB, ou un diaporama d'images prises dans le film (dans les épisodes pour une série : sa première saison d'elle-même).

Images choisies à la main. Au catalogue, l'admin repère un moment sur les vignettes de navigation, puis l'image exacte (une image ou une seconde à la fois), vue en grand, et la prend. Autant que voulu, réordonnées par glisser-déposer.

Images choisies automatiquement (sans modèle) : 6 images parmi les vignettes de navigation du film, entre 5 % et la moitié du film (rien de dévoilé).


9. Modèle de données

Esquisse conceptuelle (pas un schéma SQL).

SourceRoot     (id, path, mode: watch|inbox, direct)                  -- direct : diffusion par défaut de ses fichiers
SourceFile     (quick_fingerprint, full_hash?, size, probe, moviehash?) -- catalogue, gardé quand le fichier disparaît
SourcePath     (fingerprint, source_root, path, seen_at)               -- 0..n chemins
Store          (id, path, capacity, policy)
TrackLang      (fingerprint, stream, language)                         -- donnée par l'admin (ou un modèle)
TrackForced    (fingerprint, stream, forced)

Work           (id, kind: movie|series|season|episode, parent?, numéros,
                audience: kids|all|adults, age?, rating? (étoiles), boost?)
Edition        (id, work, name, media, position, champs d'override clairsemés)
SeriesVersion  (show, position, name, own_cut)                         -- versions d'une série
Layout         (show, kind: aired|group, saisons et épisodes)          -- importé une fois
Extra          (id, work|edition, kind, title)
MediaItem      (id, edition|extra, source_fingerprint, delivery: encoded|direct)
Stem           (id, media_item, kind: video|audio|sub|trickplay, spec, store,
                path, profile@version, pass, state, origin: source|external, external_sub?)
ExternalSub    (id, source_fingerprint, origin: file|opensubtitles|translation|ocr|transcription,
                path, language, forced, sdh, opensubtitles_id?, release?, matched: hash|title)

Localized      (entity, language, title, overview, …)                  -- métadonnées multilingues
Artwork        (entity, kind, language?, file)
Person         (id, name, …)
Credit         (person, work|edition, role, character?, audio_language?, image_override?)

Group          (id, kind: manual|dynamic, rules?, sort, parent?, pinned, landing_order)
GroupItem      (group, work|edition|group, position)                   -- groupes manuels

Profile        (id, version, rungs[], passes[], audio[], subs[], live[], keep_original)  -- encodage
LadderOverride (scope: type|media, profile)                            -- prévu, UI plus tard

User           (id, role, parent?, kids, birth_date, audio_langs[], sub_langs[],
                sub_mode, ui_lang, code?)                              -- un compte, ou un profil d'un compte (parent)
Limits         (account, downloads, max_kbps, max_streams, max_profiles, suspended, …)
Session        (token_hash, account, profile?, junior)                 -- « Qui regarde ? », mode Jeunesse
Login          (account, at, ip, agent, method)                        -- gardé un an
Invitation     (token, role, birth_date, expires)
Device         (id, user, name, token)                                 -- appairage TV
Progress       (profile, edition|episode-in-edition, position, updated) -- par édition
Watched        (profile, work)                                         -- par œuvre
Favorite       (profile, work)
Impression     (profile, work, shown)                                  -- suggestions montrées sans être ouvertes

9.1 Éditions

Chaque édition est servie par un fichier de l'œuvre, choisi par l'admin (Edition.media_id). Les éditions sont ordonnées, et la première est la version principale : lue par défaut, renommable, jamais supprimée.

Un fichier peut servir plusieurs éditions, ou aucune : c'est alors une copie de rechange, gardée et encodée mais proposée à personne (« Fichiers non servis » au catalogue, « Doublons probables » dans la Maintenance).

Override clairsemé : une édition ne stocke que ce qui diffère de la version principale ; lecture = edition.champ ?? canonique.champ (titre, résumé, visuels, cast, âge…).

Les éditions n'ont pas la même timeline : les pistes audio appartiennent à une édition, jamais partagées. Les spectateurs ne voient que les éditions qui ont un fichier ; une seule n'affiche pas de sélecteur.

9.2 Cast

9.3 Séries

Découpages. Une série est découpée en saisons par un découpage : l'ordre de diffusion de TMDB, ou l'un de ses groupes d'épisodes (par année, comme TheTVDB, par arc…). Les découpages sont importés une fois avec la série, et de nouveau seulement quand l'admin la rafraîchit depuis TMDB.

Lecture des noms de fichiers. La numérotation que suivent les noms des fichiers d'une série est trouvée d'après eux : l'ordre de diffusion, ou le découpage sous lequel le plus grand nombre tombe sur un épisode, leurs titres (dans n'importe quelle langue) et leurs durées concordant. C'est ainsi que se lisent les saisons TheTVDB de Sonarr. L'admin peut la fixer ; la changer réidentifie les fichiers, ceux placés à la main à part. Un découpage ne sert à lire les noms que s'il contient tous les épisodes (un arc jamais).

Les versions d'une série (VF, Remastered…) sont définies sur la série, dans l'ordre où les spectateurs les reçoivent ; la première qui a des fichiers est montrée d'abord. La version principale n'a pas besoin de nom. Un épisode a une édition par version dans laquelle il a un fichier.

Le spectateur choisit une version sur la page de la série et ne voit que les saisons et épisodes de cette version : rien ne se rabat sur une autre version. Une seule version n'affiche pas de sélecteur ; une seule saison, pas d'onglet ; au-delà de trois saisons, un menu.

Découpages propres. Une version peut avoir son propre découpage : la même série découpée autrement, comme dans un coffret DVD. Ses fichiers quittent les épisodes de TMDB pour des épisodes à elle, un par fichier, dans des saisons à elle (jamais la version principale). Les spectateurs voient ses saisons comme « Saison n », ses épisodes comme S1 · E1.

Fichiers et épisodes.

Épisodes & versions (au catalogue, pour chaque série) édite tout cela, dans le design de lot de l'Identification (§13.1) :

9.4 Groupes

9.5 Progression


10. Utilisateurs, accès & sécurité

10.1 Comptes & profils

Deux rôles : admin et user, dans une seule app dont l'administration est une section réservée. Un admin ne peut ni retirer son propre rôle, ni suspendre ou supprimer son propre compte.

Un compte contient des profils : 5 par défaut, lui compris (l'admin le change par compte). Une fois connecté, le spectateur en choisit un : Qui regarde ?

Les profils Enfant ne voient que les titres pour enfants et pour tous, dans leur âge. Ils ne téléchargent jamais, ne se modifient pas eux-mêmes, et en sortir demande le code du compte (le compte en a donc un).

Le mode Jeunesse : sur un appareil (sa session), un profil adulte montre ce que montrerait un profil Enfant : les titres pour enfants, et ceux pour tous classés tous publics. Il vaut partout (accueil, listes, recherche, Reprendre) ; l'historique et les goûts du profil restent les siens. Il s'active depuis le menu du profil ; le nom de l'app se lit alors Jeunesse en lettres pastel claires. Il n'a pas de verrou : c'est un profil Enfant qui protège un enfant.

Les utilisateurs n'ont aucun contrôle éditorial. Ils lisent, choisissent leurs pistes audio et sous-titres, ajoutent des favoris, téléchargent (quand c'est permis), et règlent leurs langues : audio, sous-titres (jamais, forcés seulement, toujours), métadonnées et interface. Elles pilotent le choix automatique des pistes.

10.2 Authentification

10.3 Exposition Internet

Alumira Connect, en un clic : un nom sous alumira.app (martin.alumira.app) qui mène à la maison, en HTTPS.

Sinon : TLS intégré avec ACME automatique (Let's Encrypt) pour un domaine à soi, ou un reverse proxy devant.

10.4 URLs signées

10.5 Limites d'âge & public

Limite d'âge. Aucun système de classification n'est exposé : tout est traduit en limite d'âge, affichée (-15) dans un rond blanc, sur le grand visuel de l'accueil et la page du titre seulement.

Public : à qui s'adresse un titre, fixé par l'admin.

Il est fixé une fois par une règle quand un titre est identifié (genres famille, animation ou enfants et un âge bas ; le genre enfants de TMDB : Enfant), puis seulement modifié. Les saisons et épisodes d'une série suivent la série.

Le filtrage est appliqué côté serveur, partout : listes, recherche, pages cast, groupes, accès URL direct, émission d'URLs signées, images, téléchargements. Une page d'admin dédiée modifie la limite d'âge de chaque titre, classé ou non, avec des actions par lot.


11. Bande passante & sessions

Contrainte : un manifest HLS VOD est chargé une fois par le lecteur ; on ne peut pas retirer un rung en cours de lecture.


12. Client web & UX

12.1 Contraintes

12.2 Accueil

Un grand visuel, puis des rangées, un titre n'apparaissant que dans une seule, dans cet ordre :

  1. Le grand visuel : tiré des meilleures suggestions qui ont une image de fond, pas celles des dernières visites.
  2. Reprendre : où chaque titre s'est arrêté, avec une image prise dans le film.
  3. Pour vous.
  4. Nouveautés : les plus récentes d'abord, jamais le fonds de la bibliothèque. Un titre est nouveau s'il est arrivé une semaine après que la moitié de la bibliothèque était là (une bibliothèque importée d'un coup n'a rien de nouveau tant que rien n'arrive).
  5. Les mieux notés : les meilleurs pas déjà montrés au-dessus.
  6. Les collections épinglées, dans l'ordre de l'admin.
  7. À découvrir.

Suggestions. Chaque titre qu'un profil peut voir est noté en points :

Les rangées sont tirées de ces notes, pondérées, et restent les mêmes pendant une visite. La page Suggestions de l'admin dit pourquoi chaque titre est où il est.

La carte d'une série dit combien de saisons il y a à voir (« 2022 • 3 saisons »).

12.3 Page du titre

12.4 Lecteur

Choix de la qualité (web/src/abr.ts, à la place de celui de hls.js) : chaque fragment arrivé aussi vite que le lien le porte est mesuré (sa vitesse, son attente du premier octet).

Un panneau de statistiques (technique par nature) montre le codec, la qualité et le débit de chaque flux, le segment en cours de chargement et la vitesse. Les admins peuvent lire n'importe quel fichier par-dessus une page pour voir ce que c'est, sans garder sa progression.

12.5 Localisation

12.6 Application installable & application de bureau

App web installable : un manifeste et ses icônes (dont une avec la marge d'Android). Chrome et Edge proposent « Installer », Safari « Ajouter au Dock » ou « Sur l'écran d'accueil » : Alumira s'ouvre dans une fenêtre à lui, sans les barres du navigateur (sur un iPhone ses cookies sont les siens : une connexion de plus).

Application de bureau (cmd/alumira-desktop, macOS et Windows, Wails v3 sur le moteur web du système : WebKit, WebView2) : l'app web dans une fenêtre à elle, et une icône dans la barre des menus ou la zone de notification tant qu'elle tourne (un clic : la fenêtre ; un clic droit : son menu).

À son premier lancement elle demande :

FFmpeg absent, un port pris, un serveur qui ne répond pas : dit dans la fenêtre, avec quoi faire et un bouton pour réessayer. Une seule instance à la fois ; lancée avec l'ordinateur, l'icône seulement. Sur macOS, le Dock la montre tant que sa fenêtre est ouverte.

Trouvé sur le réseau : un serveur qui écoute au-delà de sa machine le fait savoir (mDNS / DNS-SD, _alumira._tcp, sous son nom, sinon celui de sa machine) ; l'application de bureau liste ceux qu'elle entend. Sous Docker, seulement avec le réseau de l'hôte.

Paquets : une image disque pour macOS (une app pour puces Apple et Intel, macOS 13 et plus) et un installeur pour Windows (NSIS : Program Files, menu Démarrer, une règle de pare-feu pour le réseau privé ; la désinstallation laisse les données), construits par scripts/build-desktop.sh et inclus dans scripts/release.sh.

Les deux embarquent le FFmpeg d'Alumira (jellyfin-ffmpeg avec VMAF, construit depuis github.com/ejulien/jellyfin-ffmpeg), dans un dossier ffmpeg à côté du programme, là où Alumira regarde en premier : rien d'autre à installer. Ils ne sont pas signés : macOS demande d'autoriser l'app une fois, SmartScreen de l'exécuter quand même.


13. Administration

13.1 Pages

Le menu, dans son ordre :

L'édition par lot a partout le même aspect : la table de l'Identification, son en-tête de sélection et ses fenêtres d'outils. La vignette de chaque fichier dans l'administration le lit.

13.2 Maintenance

Dans la première partie du menu : ce qui demande une intervention et ce qui manque à la bibliothèque, le plus urgent d'abord, un onglet par sorte présente, avec des actions par lot (Tout sélectionner allant au-delà des éléments listés). La page suit les tâches qui la changent.

  1. Normalisations échouées.
  2. Encodages incomplets (pistes audio laissées de côté).
  3. Transcodage indisponible (§4.4).
  4. Sources disparues qui bloquent un encodage, ou un fichier en lecture directe.
  5. Sources trop abîmées pour être encodées (à remplacer).
  6. Qualité insuffisante (une rendition sous 85 VMAF).
  7. Doublons probables.
  8. Fichiers non identifiés.
  9. Incohérences d'épisodes (§9.3).
  10. Pistes en langue inconnue (une langue à donner à chacune, ou la Reconnaissance IA).
  11. Genres inconnus.
  12. Stems d'un autre profil (§6.12).
  13. Épisodes manquants : épisodes diffusés qu'aucun fichier n'apporte (certains peuvent être mis de côté).
  14. Épisodes sans image.
  15. Collections incomplètes.
  16. Qualité non mesurée.
  17. Sous-titres manquants dans une langue par défaut (recherche OpenSubtitles dans le quota du jour, faits en écoutant, traduits).
  18. Plusieurs sous-titres pour une langue.
  19. Sous-titres en images (à lire en texte).
  20. Chapitres sans nom ; sans chapitres.
  21. Images plus nettes disponibles ; films sans images du film.
  22. Copies en trop d'un média dans une autre store (§8.1).
  23. Sources disparues sans perte.

13.3 Fonctions IA

Dans les Paramètres : un fournisseur (OpenAI, Anthropic, Grok de xAI, ou toute API compatible avec celle d'OpenAI, y compris une locale : Ollama, LM Studio, vLLM…) et un modèle par usage, choisi dans la liste du fournisseur, ou aucun :

Les modèles ne sont appelés que par des tâches, à la demande, dans une file à part qui n'attend jamais un encodage ni n'en retient un. Combien tournent à la fois est un réglage (2 par défaut, 16 au plus). Le fournisseur facture chaque appel au titulaire du compte.

13.4 Onboarding

Au premier lancement, l'admin passe par un onboarding qui pose les questions clés pour obtenir une installation fonctionnelle. Principes :

Étapes :

  1. Compte admin : nom d'utilisateur, mot de passe ou passkey, date de naissance, langue de l'UI (qui fixe aussi les langues audio, sous-titres et métadonnées par défaut, et le pays des classifications d'âge).
  2. Accès réseau : Alumira Connect en un clic ; ou, à la main, URL publique, mode TLS (ACME intégré / reverse proxy / réseau local seulement), port.
  3. Stores : au moins une. Vérification d'écriture, espace libre, débit disque mesuré. Ordre de la liste (§8.1).
  4. Sources : zéro ou plusieurs dossiers, chacun pré-encodé, pré-encodé puis supprimé (inbox), ou en lecture directe. Pré-scan rapide (nombre et volume de fichiers).
  5. Encodage : détection des capacités du worker intégré (encodages de test), choix quality / speed.
  6. Ladder : un point de départ (§6.4), modifiable, garder ou non une copie de l'original, et l'estimation de stockage (volume des sources × ladder) face à la capacité des stores.
  7. Métadonnées : clé API TMDB, avec un lien vers la page de création de clé TMDB (ouvert dans un nouvel onglet) ; langues des métadonnées ; pays dont les classifications fixent les limites d'âge ; notes IMDb.
  8. Bande passante : mesure du débit montant (sur accord, via un service de test de débit externe ; saisie manuelle en repli), puis le budget (§11).
  9. Langues par défaut des nouveaux utilisateurs (audio, sous-titres et quand les afficher).
  10. Sauvegardes : emplacement et fréquence des snapshots de la base.
  11. Invitations : premiers utilisateurs (facultatif).
  12. Récapitulatif → le scan et les queues démarrent.

14. Stack technique

Élément Choix
Serveur & worker Go, style impératif (structs + fonctions), sans cgo → binaire statique, cross-compilation triviale.
Base de données SQLite (WAL), driver pur Go (modernc.org/sqlite), FTS5 pour la recherche ; un seul écrivain, des lecteurs en parallèle. Couche d'accès fine pour permettre Postgres plus tard.
Média FFmpeg / ffprobe en sous-process (isolation des crashs ; un programme à part, jamais lié), le build installé sur la machine.
Scaling / tone-mapping libplacebo (Vulkan / MoltenVK), zscale ; ceux de la puce vidéo pour le transcodage à la volée.
Packaging HLS CMAF fMP4, manifests dynamiques.
Client TypeScript, SolidJS, Vite, hls.js.
Application de bureau Wails v3 sur le moteur web du système.
Accès à distance Alumira Connect : son propre serveur DNS (miekg/dns), ACME via x/crypto/acme.

15. Ordres de grandeur

Alumira est fait pour la bibliothèque d'un foyer, et dimensionné pour qu'une grande tienne sur un matériel modeste : 20 000 films, 1 000 comptes.

Sujet Ordre de grandeur Conséquence
Stockage 20k × 10–20 Go ≈ 200–400 To (hors séries et copies de l'original) Plusieurs stores et le placement sont indispensables ; la lecture directe ne stocke rien deux fois.
Encodage ~40 000 h de contenu Un import initial de plusieurs semaines/mois → passes ordonnées et priorités cruciales.
Streaming 50–100 flux simultanés × ~15 Mbps (4K) ≈ 1–1,5 Gbps Limité par le disque et le réseau, pas le CPU → contrôle de bande passante (§11) ; transcodage à la volée limité à quelques places.
Base ~100k entrées média (épisodes compris) SQLite suffit avec un seul écrivain, des écritures de progression bufferisées, et des pages d'admin lues en quelques requêtes.