Alumira

Alumira — Specification

Status: v1 — implemented. This document describes what Alumira does and why. The code is the reference: where they disagree, the code wins and this document is corrected.


0. Vision & principles

Alumira is a self-hosted media server for a household's films and series. It reads the files where they are, recognises them, and presents them as a streaming service of one's own would: artwork first, a profile for each person, suggestions, every version and language of a title. It plays on a computer, a phone, or a TV with its remote.

Two roles. The viewer is passive: they choose what to watch and watch it, nothing more. Nothing to set up, sort or fix: titles, artwork, versions, languages and subtitles come to them ready. The administrator is the editor. They have a hand on everything, to fine-tune the server down to the last detail: the file that serves each version, a title's text, artwork and age limit, the collections and the home page, the subtitles, the qualities, the hardware, who sees what.

Each file is delivered in one of two ways, chosen for its folder, then file by file:

Alumira runs on what a household already has: a NAS (Docker), a desktop computer (an app for macOS and Windows), any server. Other computers of the network can help it encode. It can be reached from outside in one click (Alumira Connect, §10.3).

Language models are optional helpers, chosen by the administrator: they translate subtitles, read picture subtitles into text, write subtitles by listening, name chapters, recognise the language of a track.

Guiding principles

  1. Media first. A very visual, simple, to-the-point UI; a dead simple UX. Its own identity: we do not imitate the streaming platforms.
  2. Passive viewers, an editor at the helm. Viewers decide nothing but what to watch: no setting stands between them and the picture. Every choice is the administrator's, and every one can be made, by hand, down to the detail; what Alumira does by itself is a first draft the administrator can take back.
  3. Plain words for viewers. No technical jargon on the viewer side (codecs, protocols, subtitle formats, rating systems): the usual consumer terms when a term is needed (HDR10, 4K), no tooltips to explain them. The administration keeps technical terms where they are the subject.
  4. Prepared ahead, rather than at playback. Whatever can be done before someone presses play is: encodes, subtitles, seek thumbnails, chapters, pictures. Transcoding at playback exists only for files played directly, only on the video chip, within a fixed number of places.
  5. Split stems, recombined when served. Every track (video per rendition, audio per language and layout, subtitle) is an independent stem; the client receives a multi-track / multi-bitrate manifest generated on the fly.
  6. Lazy evaluation rather than links. We store identities (fingerprints, rules) and resolve them when needed, rather than maintaining links that drift out of sync.
  7. Sources are sacred. A store is never a source; a source is never modified, except in explicit inbox mode.
  8. The library is local. Imported metadata (TMDB) belongs to the library once imported: edited in place, never overridden by a layer on top. Only an explicit refresh imports a title again.
  9. AI helps, never required. Every feature works without a model. Models are called by jobs, on demand, with what they use shown.
  10. Simple, imperative code. No OOP; favour build and iteration speed.

1. Scope

Today

Later


2. Glossary

Term Definition
Source Location (folder/drive) scanned for original files. Read-only (watch) or consumed (inbox).
Delivery How a file plays: pre-encoded (its stems) or played directly (the file itself, transcoded on the fly when needed). Set per file.
Store Location where Alumira writes normalized stems. Unlimited number. A store is never a source.
Fingerprint Identity of a source file, independent of its path.
Source catalogue Index fingerprint → current paths (0..n).
Stem A single normalized track, stored as one CMAF fMP4 file (e.g. "1080p H264 SDR video", "fr-FR AAC 2.0 audio", "English WebVTT subtitle").
Rendition A video quality level (resolution × codec × dynamic range × bit depth). One rendition = one video stem.
Rung One step of the ladder (definition of a rendition to produce).
Ladder Ordered set of rungs + audio/subtitle definitions to produce for a media item.
Encoding profile Named, versioned ladder configuration, editable by the admin (Administration → Profiles).
Pass Group of rungs encoded together in one chain; passes are ordered.
Work A title: movie, series, season, episode.
Edition A version of a work ("1977 Cut", "Blu-ray 2011", "Deluxe"…), served by one file. The first one is the main version.
Extra Bonus material (making-of, interview…) attached to a work or an edition.
Group A collection: manual or dynamic.
Layout How a show is cut into seasons: TMDB's aired order or one of its episode groups.
Own cut A version of a show cut into seasons and episodes of its own (a DVD box), not TMDB's.
Account A person signed in: password, passkeys, devices, limits. It holds up to a few profiles.
Profile One viewer of an account: history, favorites, languages, age; a Kids profile sees only titles for children.
Audience Who a title is for: Child, Everyone or Adult.
Worker Encoding process (built into the server or remote).
Encoder signature Identity of an encoding configuration that guarantees consistent output (see §7.3).

3. Overall architecture

                 ┌───────────────────────────────────────────────┐
                 │                    Server                     │
 Sources ──scan──▶  Fingerprint ─▶ Queues ─▶ Scheduler ────┐     │
 (watch/inbox)   │  catalogue     │                        │     │
                 │                ├─ identification        ▼     │
                 │                ├─ normalization   Built-in worker
                 │                ├─ images                │     │
                 │                ├─ QA                    │     │
                 │                └─ AI (models)           ▼     │
                 │  SQLite ◀── metadata              Stores (fMP4 stems)
                 │                                         │     │
                 │  HTTP: web app + API + HLS manifests ◀──┘     │
                 │        + files played directly, transcoded    │
                 │          on the fly (video chip)              │
                 └──────────────▲──────────────────────▲─────────┘
                                │                      │
                        Web / TV client          LAN workers
                                                 (source streamed
                                                  over HTTP)

4. Sources, delivery & catalogue

4.1 Source modes

Mode Behaviour
watch Strictly read-only. Alumira never modifies, moves or deletes anything (seedbox, archive drive).
inbox Consumed drop folder: once a media is fully encoded, its source file is deleted, with the subtitles beside it and the folders it leaves empty. There is no trash.

In an inbox folder, what is left of a file is what its encoding profile keeps:

The admin is warned that a profile changed later can only start from the best existing stem, or from the original copy.

When adding a folder, the admin chooses among three ways, said in plain words: Pre-encoded, Pre-encoded, then removed (inbox), Played directly, transcoded on the fly (a watch folder whose files are played directly).

4.2 Delivery

Each file has its own delivery: pre-encoded or played directly. A new file takes its folder's choice when it comes in, and keeps its own from then on. A file in an inbox folder is always pre-encoded.

The admin changes it in Administration → Delivery, file by file or in batches (filters: delivery, not transcodable, folder, path). Changing a folder's choice applies to the files to come; it then offers to apply it to the files already there.

A file switching keeps playing as before until the new way is ready:

The source of a file played directly is never deleted. When it is gone, the file is unplayable: Maintenance lists it as a missing source, blocking.

4.3 Files played directly

Nothing is encoded nor stored ahead, apart from small extras. Each file is prepared once, as it comes in:

It is playable as soon as it is identified and prepared, through HLS like the others. Its video and AAC tracks are virtual renditions whose segments ffmpeg copies from the file when asked, without encoding.

Each segment is read with the file's own times and cut exactly at its keyframes. Every track is delayed by the same few milliseconds its first frames decode ahead (times never below 0, in step): fragments follow on exactly, and a seek plays at once.

4.4 Transcoding on the fly

A device that cannot decode a file played directly, or whose connection is too slow for it, gets qualities transcoded on the fly.

Its ladder. Each encoding profile has a live ladder, set on the Profiles page like its encodes: a resolution, a codec (H.264 or HEVC) and a bitrate per rung. By default, in H.264: 1080p 8 Mb/s, 720p 4.5 Mb/s, 480p 1.8 Mb/s, 360p 900 kb/s, 240p 450 kb/s. Changing it re-encodes nothing.

Beside the file. Its qualities are virtual renditions beside the file itself, cut on the same segments, so a player switches between them seamlessly. Each segment is made in parts of 3 s at most, sent as they are made: a start or a jump plays within a few seconds. Never above the file: two rungs above its size give one, at its size; when the file itself plays, only rungs under three quarters of its bitrate are offered.

On the video chip only. VideoToolbox, NVENC, Quick Sync, AMF, VA-API: on NAS too (Synology, QNAP, Unraid with an Intel iGPU or an NVIDIA card). Decoded by the chip when it can, without B-frames; a server without a hardware encoder offers none. Its formats are tested one by one at start (codec and depth, frames read back from the chip).

On an Intel chip the picture never leaves it. It is scaled there as encodes are (§6.3): by halves at most, with the chip's best filter. Its colours are set there too (BT.709 limited range, or the HDR kept), so SD sources and untagged ones convert as well.

HDR. An HDR file stays HDR for a device that shows it: HEVC 10 bits, the file's PQ or HLG, whatever the ladder's codec, so brightness never changes when the player switches. Without a hardware encoder for it, the file alone is offered. Any other device gets it tone-mapped to SDR.

When it cannot. A file the chip cannot decode, and the processor cannot keep up with (only light files: 8 bits SD, 720p of the light codecs), offers itself only. The device is told why, and Maintenance lists it (Transcoding unavailable, with the devices that could not play it), to be pre-encoded.

Shared fairly. Segments are made when asked, the next one meanwhile, and kept in memory (512 MB) for seeks back and other viewers. One being made goes on as long as someone waits for it. A player has 3 pieces cut or made at a time (its video, its sound, the next one), the server 16 copies; a request beyond waits its turn.

A maximum of viewers are transcoded at once: 2 by default, 1 to 32, on the Profiles page (a hardware encoder is as busy at 100 kb/s as at 25 Mb/s). Beyond it nobody is served worse: a device that can play the file plays it as it is, one that cannot is told to try again in a few minutes or on another device. A place is freed 45 s after its last segment.

4.5 Detection

4.6 Fingerprints

4.7 Catalogue


5. Pipeline & identification

detection ─▶ probe ─┬─▶ identification ─────────────┐
                    ├─▶ normalization (passes 1..n) ├─▶ visible
                    │   or preparation (direct)     │
                    └─▶ images (derivatives)        ┘
                         QA (sampled VMAF) ─▶ maintenance
                         AI jobs (on demand)

5.1 Metadata providers

5.2 Identification

Each file is read from its name and folders: a film, an episode, an extra, which version (cut). It is then looked up on TMDB.

Why a file is not identified is kept as a code, and said in words in every language: "No TMDB series is called “The Smurfs” (1981)", "Its name says S02E05, but TMDB has no season 2 for this show"…

The Identification page has four tabs: To identify, Identified, Fixed by hand, Everything.


6. Normalization

Everything in this section concerns pre-encoded files; files played directly share its subtitles, chapters, pictures and seek thumbnails.

6.1 Source preparation

Before encoding, in the filter chain:

6.2 Rung consistency rules

6.3 Downscale chain

A fundamental point: low resolutions must look flawless.

6.4 Ordered passes

The rungs of a ladder are split into passes, encoded in order. Each pass = one decode + one chain. The manifest includes rungs as soon as they are ready.

Setup offers two starting points, described by how the library is watched rather than by codecs: Many viewers, full quality (the default ladder below) and At home, sometimes on the go (few qualities, far apart: 4K for the living room, very light versions for a trip). Either is then editable.

Default ladder:

Pass Content
1 8 bit SDR H264 video: 1080p, 480p, 240p. Audio: every language, AAC 2.0 128 kb/s (downmix, loudnorm) + AAC 5.1 320 kb/s if the source is ≥ 5.1 + light AAC 2.0 64 kb/s. Subtitles. Seek thumbnails. Chapters.
2 10 bit HEVC video (HDR kept if the source is HDR): 2160p, 1080p; 8 bit SDR HEVC rescue qualities: 480p, 240p (far lighter than H264 for the same picture on a weak connection). Audio: E-AC-3 5.1 448 kb/s if the source is ≥ 5.1, Opus 2.0 96 kb/s / 5.1 192 kb/s / 7.1 256 kb/s + light Opus 2.0 48 kb/s.
last Original copy: the source's tracks remuxed as they are into the store (no re-encoding). Depends on nothing, slowest to finish. Per encoding profile: never, for inbox folders only (the default), or for every file.

A quality a later pass of the same profile makes again replaces the earlier one: it is no longer offered, and deleted once nobody has the media open.

Supported codecs: video H264, HEVC, AV1; audio AAC-LC, Opus, E-AC-3; layouts 2.0, 5.1, 7.1.

6.5 Bitrate & keyframes

6.6 Audio

Light audio. Each language has a light stereo version (light rung), in an audio group of its own (aac-light, opus-light). The manifest gives it to the video qualities of 480p and below, so that sound does not outweigh picture on a weak connection; it is used only when every language has one.

It bears the name of the stereo track it stands for: the player keeps the viewer's choice from one group to the other, and its menu only lists languages and layouts (the codec is left to the player).

Each source track on its own. Audio is never chunked (priming issues at the joins): it is encoded in one go, while the video chunks are. Each track has its own FFmpeg (sharing one, a mono track could stop early without any error), is kept as soon as it is made (never encoded again), and fails on its own.

A track that cannot be encoded is left out: the pass goes on, the media plays without it, Maintenance lists it (incomplete encodes), and a new try makes only the missing tracks.

6.7 Subtitles

Source Processing
Text (SRT, WebVTT, mov_text…) → WebVTT.
Styled ASS/SSA → WebVTT (universal fallback) + original kept for rich client-side rendering (libass wasm, JASSUB).
Image (PGS, VobSub) Image overlay: subtitles kept as images, rendered by the client; or read into text by a language model, when the admin chooses one for it (§6.9).

Flags are kept and exposed: forced, SDH (hearing impaired), BCP-47 language. Viewers see them in plain words: a styled track is not told apart in the player (it is the only one listed), and reads "styled" in downloads.

One track per language and use (complete, forced): the best one.

Tracks in an unknown language (no tag in their file, und, mul): players show them as such, and they count for no language. Maintenance lists them, each with the language to give it (the title's original one first), and the first lines of each.

Forced or not. A subtitle track is said forced or not from the media's technical sheet, with a toggle. It is kept by its stream, over its file's flag and title; a subtitle file's is kept over its name at scans.

6.8 Subtitles from elsewhere

Files that are not in the source: found next to it (<name>.<lang>[.forced|.sdh].srt, or in a Subs folder), or downloaded from OpenSubtitles.

OpenSubtitles (optional; each install brings its own API key, an account raises the daily download quota). Subtitles are wanted in the default subtitle languages, unless the title already has them, or sound in that language.

6.9 Subtitles by language models

Each of these is on demand and optional, with a model chosen for its use in Settings (§13.3). Each result is a subtitle file like a downloaded one; its stems are marked as machine-made for the admin, never for viewers.

Translation. From a media's details, the admin picks the text subtitles to translate from (any but a translation; the best ones proposed first: complete, in the original language, then English) and the languages to translate into (those without subtitles; the default ones it lacks checked). In batch from Maintenance: the best subtitles, into every default language each media lacks.

Picture subtitles read into text. With a model chosen for it, every import reads its picture subtitles; the catalogue's are read on demand from Maintenance.

Subtitles from speech. Each audio track can have subtitles made by listening to it.

A speech model chosen for the use Listening hears the track (from the provider of the AI settings: xAI's Grok Voice Transcribe for now; billed by the hour of sound).

Its words, each timed and its speaker told apart, are cut into cues as a subtitler would. What a model makes up over music, subtitle credits or a long phrase looping is left out.

Then the model of subtitles, when there is one, gets the cues by 80, four batches at a time, with the scene around them, the title, overview and characters' names. It writes what is said: misheard words and names corrected, hesitations and false starts out, condensed only past what the cue can hold in its time (given to it), the speakers' dashes kept, noise taken for speech emptied. The times never go through it.

The result is a transcription file (stems named ext.stt-<stream>), which translations can start from. It takes two jobs in the AI lane, listening and writing; what was heard and each batch rewritten are kept until the file is made.

Language recognition. From Maintenance's Unknown language, AI recognition sends the model for subtitles, for each track in an unknown language that can be read as text:

The model answers each track's language and whether it is forced (a single line is enough to tell a language; one or a few lines are forced). Answers are applied as the admin's own; a track left unknown is logged.

6.10 Seek thumbnails & chapters

Chapters without names are those numbered by their authoring tool: "Chapter 01", "Scene 2", "第3章", a time, nothing. They are told apart in any language without a model: their titles, numbers, times and punctuation taken out, are the same. Three of them at least, or half the chapters, and the media is listed in Maintenance (and Missing in its details).

On demand, a language model (a use of its own) names them from the dialogue of the best text subtitles, in one call:

The names are kept beside the chapters with their start times (a re-encode extracting the same chapters keeps them), in the store's source.json too. The player shows those of its interface language. They can be removed from the media's details (Additions).

Chapters made where there are none, for a media of 15 minutes or more whose source has none, or a single one.

At each encode of the pass making its extras, its scene changes are measured on its smallest rendition and kept: cuts and their strength, black stretches, silences of an audio rendition (a minute or two of decoding).

On demand, the strongest become break points: a fade, black, a sharp cut, the more where nobody speaks and nothing sounds; at least 30 s apart, the start first. The model gets the timed dialogue, the stretches without any and the break points. It chooses about one chapter every 7 minutes, each starting at a break point (a silent scene can start one: the picture tells where it is), and names them as above.

Each start then goes back to where its scene starts: before the exchange that follows it (a question, not its answer), on the best mark at most 2 minutes before (out of a fade first, then after a silence, then a sharp cut). Marks where someone speaks over a cut, or between two close lines, hardly count. The chapters made are kept with their names, so a re-encode keeps them.

6.11 Quality control

Playback offers each device only the renditions worth their bitrate. One that another playable rendition beats (or matches within 1 point) for less bitrate is dropped, on that yardstick; not measured yet, on the first VMAF at the same height, else the more efficient codec when also cheaper. HDR renditions (VMAF does not apply) only compete on codec.

More bitrate then always brings a better picture: an H264 at twice the bitrate of an HEVC that looks as good is never chosen.

6.12 Encoding profiles: versioning & resolution

Profiles are versioned, and a stem records the profile version that produced it. Editing a profile, or giving a media another one, makes its stems obsolete: an information in Maintenance, not an error (they still play).

Nothing is re-encoded by itself: the admin upgrades them, one media or a batch.

The effective ladder of a media item is resolved lazily: global default → type (movie / episode / extra) → media override. The per-media override is in the model; its UI comes later.

The admin sees a size estimate while editing a profile. Pre-encoding is refused with a profile that makes no qualities; Profiles can give such a profile the balanced qualities back.


7. Workers & distribution

7.1 Workers

Capabilities are auto-detected at startup, through a one-second test encode with every known encoder of the build (being compiled in does not mean the hardware is there): codecs, hardware, 10 bit, HDR, and which formats the video chip decodes.

The result is cached until the FFmpeg build changes (a week at most); the administration can run the tests again. A test that fails slowly on a busy machine, or that contradicts the previous detection, is tried again; what stays inconclusive is tested again 10 minutes later.

7.2 Time-slice distribution

Nothing done is lost.

Assembly. The server concatenates the chunks into stems, each rendition once, freeing its chunks as it goes. A chunk is checked before it joins (its files cover its range): a short one is encoded again, alone.

One encode at a time per media (a pass, a rendition tried again): each replaces stems the other may use. The encodes of other media go on.

A running job reports its stage as key or key:detail (video:3/12: chunk 3 of 12), shown the same way in every page.

7.3 Encoder signature

To avoid uneven quality within one media item:

7.4 Source access


8. Storage & serving

8.1 Stores

A store describes itself: no correspondence file, no database needed to understand it.

Adoption. A store added with content (filled by another installation, or before a database was lost) is taken in by a background job. Every source folder becomes a media with its stems, after a check that each file is complete; it is playable once it has picture and sound, identified from the source's name. When the store's last user looks still active (less than two days), the admin is warned; nothing is refused.

Its sources then join. A scanned file whose fingerprint has a folder takes its place there: it is analysed, and only what is missing is encoded (nothing when its stems follow another profile: upgraded on request, §6.12). Renditions are measured again once the source is there. Sources without stems are encoded as usual; stems without sources keep playing (Maintenance: missing sources, nothing lost).

The same source in two stores (a copied disk): one copy plays, the other is listed in Maintenance, to delete or keep as a spare.

8.2 Stem format

8.3 Packaging

8.4 Downloads

8.5 Images

Formats. A dedicated queue makes derivatives (several sizes) in JPEG, plus AVIF when the machine encodes AV1, else WebP; the browser gets the best format it takes, served with aggressive caching.

Blur first. Each picture keeps a ThumbHash (about 25 bytes) sent with the cards and pages: its blur shows at once, the picture fading in over it (50 ms) once whole, never drawn as it arrives.

Never stale. A picture's URL carries its version (when its sizes were made): kept a year by the browser; a picture made again, or a new one under a reused id, has another URL. Where the version is not known, the browser asks again each time (an answer without the image while it is unchanged).

Artwork. Localized posters, backdrops, transparent logos, cast portraits; dominant colours extracted to theme the UI. TMDB images are fetched in their original resolution and sized from it (backdrops up to 3840 wide). Backdrops are chosen the sharpest first (by steps: 4K, 2560, 1920), then the most liked.

Behind a title (its page, the home's hero, the player): TMDB's backdrop, or a slideshow of pictures taken from the film (from a series' episodes for a show: its first season by itself).

Pictures picked by hand. In the catalogue, the admin finds a moment on the seek thumbnails, then the very frame (a frame or a second at a time), seen in full size, and takes it. As many as wanted, dragged to reorder.

Pictures chosen automatically (no model): 6 pictures from the film's seek thumbnails, between 5 % and half of the film (nothing given away).


9. Data model

Conceptual sketch (not an SQL schema).

SourceRoot     (id, path, mode: watch|inbox, direct)                  -- direct: its files' delivery by default
SourceFile     (quick_fingerprint, full_hash?, size, probe, moviehash?) -- catalogue, kept when the file is gone
SourcePath     (fingerprint, source_root, path, seen_at)               -- 0..n paths
Store          (id, path, capacity, policy)
TrackLang      (fingerprint, stream, language)                         -- set by the admin (or by a model)
TrackForced    (fingerprint, stream, forced)

Work           (id, kind: movie|series|season|episode, parent?, numbers,
                audience: kids|all|adults, age?, rating? (stars), boost?)
Edition        (id, work, name, media, position, sparse override fields)
SeriesVersion  (show, position, name, own_cut)                         -- versions of a show
Layout         (show, kind: aired|group, seasons and episodes)         -- imported once
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, …)                  -- multilingual metadata
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)                   -- manual groups

Profile        (id, version, rungs[], passes[], audio[], subs[], live[], keep_original)  -- encoding
LadderOverride (scope: type|media, profile)                            -- planned, UI later

User           (id, role, parent?, kids, birth_date, audio_langs[], sub_langs[],
                sub_mode, ui_lang, code?)                              -- an account, or a profile of one (parent)
Limits         (account, downloads, max_kbps, max_streams, max_profiles, suspended, …)
Session        (token_hash, account, profile?, junior)                 -- "Who is watching?", Junior mode
Login          (account, at, ip, agent, method)                        -- kept a year
Invitation     (token, role, birth_date, expires)
Device         (id, user, name, token)                                 -- TV pairing
Progress       (profile, edition|episode-in-edition, position, updated) -- per edition
Watched        (profile, work)                                         -- per work
Favorite       (profile, work)
Impression     (profile, work, shown)                                  -- suggestions shown unopened

9.1 Editions

Each edition is served by one file of the work, chosen by the admin (Edition.media_id). The editions are ordered, and the first one is the main version: played by default, renamable, never removed.

A file may serve several editions, or none: it is then a spare copy, kept and encoded but offered to nobody ("Files not served" in the catalogue, "Likely duplicates" in Maintenance).

Sparse override: an edition only stores what differs from the main version; reading = edition.field ?? canonical.field (title, overview, artwork, cast, age…).

Editions do not share a timeline: audio tracks belong to an edition, never shared. Viewers only see the editions that have a file; a single one shows no picker.

9.2 Cast

9.3 Series

Layouts. A show is cut into seasons by a layout: TMDB's aired order, or one of its episode groups (by year, as TheTVDB has it, by story arc…). Layouts are imported once with the show, and again only when the admin refreshes it from TMDB.

Reading file names. The numbering a show's file names follow is found from them: the aired order, or the layout under which most land on an episode, their titles (in any language) and lengths agreeing. This is how Sonarr's TheTVDB seasons are read. The admin can set it; changing it identifies the files again, those placed by hand aside. A layout is used to read names only if it holds every episode (an arc never).

Versions of a show (a French dub, Remastered…) are defined on the show, in the order viewers get them; the first one holding files is shown first. The main version needs no name. An episode has an edition per version it has a file in.

The viewer picks a version on the show page and only sees the seasons and episodes that version has: nothing falls back on another version. One version only shows no picker; one season only shows no tab; beyond three seasons, a menu.

Own cuts. A version can have its own cut: the same show cut otherwise, as on a DVD box. Its files leave TMDB's episodes for episodes of its own, one per file, in seasons of its own (never the main version). Viewers see its seasons as "Season n", its episodes as S1 · E1.

Files and episodes.

Episodes & versions (in the catalogue, for each show) edits all of this, in Identification's batch design (§13.1):

9.4 Groups

9.5 Progress


10. Users, access & security

10.1 Accounts & profiles

Two roles: admin and user, in a single app whose administration is a restricted section. An admin can neither remove their own role, nor suspend or delete their own account.

An account holds profiles: 5 by default, itself counted (the admin changes it per account). Once signed in, the viewer chooses one: Who is watching?

Kids profiles see only the titles for children and for everyone, within their age. They never download, cannot edit themselves, and leaving one takes the account's code (so the account needs one).

Junior mode: on one device (its sign-in session), an adult profile shows what a Kids profile would: the titles for children, and those for everyone rated for all ages. It applies everywhere (home, lists, search, Continue watching); the profile's history and tastes stay its own. It is switched from the profile menu; the app's name then reads Junior in light pastel letters. It has no lock: a Kids profile is what protects a child.

Users have no editorial control. They play, choose audio and subtitle tracks, add favorites, download (when allowed), and set their languages: audio, subtitles (never, forced only, always), metadata and UI. These drive the automatic choice of tracks.

10.2 Authentication

10.3 Internet exposure

Alumira Connect, in one click: a name under alumira.app (martin.alumira.app) that leads home, in HTTPS.

Otherwise: built-in TLS with automatic ACME (Let's Encrypt) for a domain of one's own, or a reverse proxy in front.

10.4 Signed URLs

10.5 Age limits & audience

Age limit. No rating systems are exposed: everything is translated into an age limit, shown as (-15) in a white circle, on the home's hero and the title's page only.

Audience: who a title is for, set by the admin.

It is set once by a rule when a title is identified (family, animation or kids genres and a low age; TMDB's kids genre: Child), then only edited. A show's seasons and episodes follow the show.

Filtering is applied server-side, everywhere: lists, search, cast pages, groups, direct URL access, signed URL issuing, pictures, downloads. A dedicated admin page edits the age limit of every title, rated or not, with batch actions.


11. Bandwidth & sessions

Constraint: a VOD HLS manifest is loaded once by the player; a rung cannot be removed during playback.


12. Web client & UX

12.1 Constraints

12.2 Landing page

A hero, then rows, a title appearing in one row only, in this order:

  1. The hero: drawn from the best suggestions with a backdrop, not those of the last visits.
  2. Continue watching: where each title stopped, with a picture taken from the film.
  3. For you.
  4. Recently added: the latest first, never the library's base. A title is new once it came in a week after half the library was there (a library imported at once has nothing new until something comes).
  5. Best rated: the best not already shown above.
  6. Pinned collections, in the admin's order.
  7. To discover.

Suggestions. Each title a profile may see is scored in points:

The rows are drawn from these scores, weighted, and stay the same within a visit. The admin's Suggestions page tells why each title is where it is.

A show's card says how many seasons there are to watch ("2022 • 3 seasons").

12.3 Title page

12.4 Player

Choosing the quality (web/src/abr.ts, in place of hls.js's own): each fragment that came as fast as the link carries it is measured (its speed, its wait for the first byte).

A statistics panel (technical by design) shows each stream's codec, quality and bitrate, the segment loading and the speed. Admins can play any file over a page to see what it is, its progress not kept.

12.5 Localization

12.6 Installed app & desktop app

Installable web app: a manifest and its icons (one with Android's margin). Chrome and Edge offer "Install", Safari "Add to Dock" or "Add to Home Screen": Alumira opens in a window of its own, without the browser's bars (on an iPhone its cookies are its own: signing in once more).

Desktop app (cmd/alumira-desktop, macOS and Windows, Wails v3 over the system's web engine: WebKit, WebView2): the web app in a window of its own, and an icon in the menu bar or the notification area while it runs (a click: the window; a right click: its menu).

At its first start it asks:

Missing FFmpeg, a port taken, a server that does not answer: said in the window, with what to do and a button to try again. One instance at a time; started with the computer, the icon only. On macOS, the Dock shows it while its window is open.

Found on the network: a server listening beyond its own machine says so (mDNS / DNS-SD, _alumira._tcp, under its name, else its machine's); the desktop app lists those it hears. In Docker, only with the host's network.

Packages: a disk image for macOS (one app for Apple chips and Intel, macOS 13 and later) and an installer for Windows (NSIS: Program Files, Start menu, a firewall rule for the private network; uninstalling leaves the data), built by scripts/build-desktop.sh and part of scripts/release.sh.

Both carry Alumira's FFmpeg (jellyfin-ffmpeg with VMAF, built from github.com/ejulien/jellyfin-ffmpeg), in an ffmpeg folder beside the program, where Alumira looks first: nothing else to install. They are not signed: macOS asks to allow the app once, SmartScreen to run it anyway.


13. Administration

13.1 Pages

The menu, in its order:

Batch editing looks the same everywhere: Identification's table, its selection header and its tool dialogs. Every file's thumbnail in the administration plays it.

13.2 Maintenance

In the first part of the menu: what needs a hand and what the library lacks, most urgent first, one tab per kind present, with batch actions (Select all reaching beyond the items listed). The page follows the jobs that change it.

  1. Failed normalizations.
  2. Incomplete encodes (audio tracks left out).
  3. Transcoding unavailable (§4.4).
  4. Missing sources that block an encode, or a file played directly.
  5. Sources too damaged to be encoded (to replace).
  6. Insufficient quality (a rendition under 85 VMAF).
  7. Likely duplicates.
  8. Unidentified files.
  9. Episode inconsistencies (§9.3).
  10. Tracks in an unknown language (a language to give each, or AI recognition).
  11. Unknown genres.
  12. Stems of another profile (§6.12).
  13. Missing episodes: aired episodes no file brings (some can be set aside).
  14. Episodes without a picture.
  15. Incomplete collections.
  16. Quality not measured.
  17. Missing subtitles in a default language (OpenSubtitles search within the day's quota, made by listening, translated).
  18. Several subtitles for one language.
  19. Picture subtitles (to read into text).
  20. Chapters without names; without chapters.
  21. Sharper pictures available; films without pictures of their own.
  22. Extra copies of a media in another store (§8.1).
  23. Missing sources with nothing lost.

13.3 AI features

In Settings: one provider (OpenAI, Anthropic, xAI's Grok, or any API compatible with OpenAI's, a local one included: Ollama, LM Studio, vLLM…) and a model for each use, chosen from the provider's list, or none:

Models are only called by jobs, on demand, in a lane of their own that never waits for an encode nor holds one. How many run at a time is a setting (2 by default, 16 at most). The provider bills its account's owner for each call.

13.4 Onboarding

On first launch the admin goes through an onboarding that asks the key questions to get a working install. Principles:

Steps:

  1. Admin account: username, password or passkey, birth date, UI language (which also sets the default audio, subtitle and metadata languages, and the country of age ratings).
  2. Network access: Alumira Connect in one click; or, by hand, public URL, TLS mode (built-in ACME / reverse proxy / local network only), port.
  3. Stores: at least one store. Write check, free space, measured disk throughput. List order (§8.1).
  4. Sources: zero or more folders, each pre-encoded, pre-encoded then removed (inbox), or played directly. Quick pre-scan (number and volume of files).
  5. Encoding: capability detection of the built-in worker (test encodes), quality / speed choice.
  6. Ladder: a starting point (§6.4), editable, whether to keep an original copy, and the storage estimate (source volume × ladder) against store capacity.
  7. Metadata: TMDB API key, with a link to the TMDB key creation page (opened in a new tab); metadata languages; country whose ratings set the age limits; IMDb ratings.
  8. Bandwidth: upload throughput measurement (opt-in, through an external speed test service; manual entry as a fallback), then the budget (§11).
  9. Default languages of new users (audio, subtitles and when to show them).
  10. Backups: location and frequency of database snapshots.
  11. Invitations: first users (optional).
  12. Summary → scanning and queues start.

14. Technical stack

Element Choice
Server & worker Go, imperative style (structs + functions), no cgo → static binary, trivial cross-compilation.
Database SQLite (WAL), pure Go driver (modernc.org/sqlite), FTS5 for search; one writer, readers in parallel. Thin access layer to allow Postgres later.
Media FFmpeg / ffprobe as subprocesses (crash isolation; a separate program, never linked), the build installed on the machine.
Scaling / tone-mapping libplacebo (Vulkan / MoltenVK), zscale; the video chip's own for transcoding on the fly.
Packaging HLS CMAF fMP4, dynamic manifests.
Client TypeScript, SolidJS, Vite, hls.js.
Desktop app Wails v3 over the system's web engine.
Remote access Alumira Connect: its own DNS server (miekg/dns), ACME through x/crypto/acme.

15. Orders of magnitude

Alumira is built for a household's library, and sized so that a large one holds on modest hardware: 20,000 movies, 1,000 accounts.

Topic Order of magnitude Consequence
Storage 20k × 10–20 GB ≈ 200–400 TB (excluding series and original copies) Multiple stores and placement are essential; playing files directly stores nothing twice.
Encoding ~40,000 h of content An initial import of several weeks/months → ordered passes and priorities are critical.
Streaming 50–100 concurrent streams × ~15 Mbps (4K) ≈ 1–1.5 Gbps Disk/network bound, not CPU → bandwidth control (§11); transcoding on the fly limited to a few places.
Database ~100k media entries (episodes included) SQLite is enough with one writer, buffered progress writes, and admin pages read in a few queries.