Free software · self-hosted

Your films and series, ready to watch.

Laterna turns your Jellyfin server into a real cinema for the whole household: a polished interface to watch, a console to run things, in a single container.

FreeAGPL-3.0Docker

Built on the services you already run
  • Jellyfin
  • TMDB
  • Jellyseerr
  • Radarr
  • Sonarr
  • Bazarr
  • qBittorrent
  • Jackett

A screening room to watch. A control room to run it.

One app, one session, the same services behind it. Everyone sees what concerns them.

Address /

Cinema

The screen everyone lands on. Every title is a poster, and everything you can do with it starts from that poster: play, resume, add to your list, request.

  • Full-bleed banner, rows of posters, search
  • Personal lists and title requests
  • In-page playback, draggable mini-player
Address /gestion

Management

All your services in a single console, wearing the same design as the cinema.

  • Downloads, service status, storage
  • Radarr and Sonarr libraries, calendar, statistics
  • Connections and settings made in the app

Two roles: admin and user. A user browses, watches, keeps lists and makes requests; changes to the underlying services belong to the admin and are checked by the server.

Pick, press, watch.

Posters, a banner, and the film starts in one tap, on a computer as on a phone.

A home screen that builds itself

Continue watching, My list, today's Top 10, recently added, one row per genre and recommendations.

One search

Your library, TMDB and people at once, sorted afterwards. Natural-language queries work.

"90s thriller"

My list

To watch, Requests and Watched. Requests update live, and a film finished on the TV shows as watched right away.

Request what's missing

A title outside the library is requested from its own page, with each person's Jellyseerr account. For a series, you pick the seasons.

Offline beta

Download a film, an episode or a whole season for the train or the plane. It plays without a connection, and the position syncs when the network is back.

On the big screen

Chromecast and AirPlay. The home screen is fully drivable with the arrow keys, which makes it usable with a TV remote.

An app on your home screen

Laterna installs to the home screen and sends notifications, iPhone and iPad included.

Generated subtitles

Optional: when a title only has subtitles in one language, an online model translates them while the film plays. Monthly budget and daily limit per account.

Four languages

English, French, Spanish and German. Audio and subtitle preferences are Jellyfin's own, shared with its other apps.

And books, too.

Optionally, a third category next to films and series: the EPUB files of your Jellyfin libraries, grouped by series, author and genre.

A reader made your way

Pages or continuous scroll, text size, font, line spacing, margins, and four themes: light, sepia, dark and black.

Your reading follows you

The book reopens at the right page on all your devices, and your settings are kept for the phone, the tablet and the computer.

Bookmarks, search and offline

Mark a page, search for a word across the whole book, and download it to read without a network.

All 16 book features

Lumen

The playback engine behind Laterna.

Lumen reads the file exactly as it sits on disk, repackages it in the browser tab and hands it to the device's video chip. 4K, HDR10, Dolby Vision and multichannel sound included.

0pixels re-encoded on the native path. The file's picture reaches the screen bit for bit.
15-56msto repackage a group of pictures, against about 2,500 to download it.
5audio decoders compiled to WebAssembly, loaded only when a film needs them.
7,000+automated tests, plus benches that put real files through their paces.
01Architecture

Seven parts, one path.

From the server's disk to the video chip, every stage has its own part. Bytes travel, pixels stay untouched.

PlaybackCoreorchestration: opening, seeking, pausing, resuming, rebuilding
server File

The original file

  • MKV · MP4 · MOV
  • untouched on disk
  • served by HTTP ranges
tab StreamCore

Byte reading

  • 1 MiB chunks, six ahead
  • start and end of the file in parallel
  • in-memory reserve, up to 5 min
tab MuxCore

Remuxing

  • Matroska and MP4, one description
  • decode times reconstructed
  • random access points verified
  • fragmented MP4, fragments ≤ 1.2 MB
tab BufferCore

Buffers

  • MediaSource and ManagedMediaSource
  • budget counted in bytes
  • sized for each engine
device <video>

Native player

  • hardware decoding
  • native HDR
  • system audio clock
CodecBridge

Codec compatibility

Before every film, Lumen asks the browser: this format, this encoder, this Dolby Vision profile. Its answer picks the path.

AudioCore

Audio decoding

Track accepted: copied bit for bit
Otherwise: decoded in WebAssembly, then re-encoded to AAC or Opus by the browser, every channel in its place
PlaybackBridge

Playback fallback

A format out of reach or a connection too slow: Jellyfin takes over in HLS, at the same second, with the same track.

02Formats

Everything Lumen plays.

Lumen recognises the format from the file's own bytes. Depending on the device, each track is copied as is or decoded in the tab, and Jellyfin's player takes the rest.

Video

HEVCH.265 · 8 and 10 bit · up to 4K
AV18 and 10 bit
H.264AVC · everywhere

HDR

Dolby Visionprofiles 5 and 8 · native where the device accepts it
HDR10native on an HDR screen
HDR10+played as HDR10, metadata passed on

Audio

Dolby Atmosin Dolby Digital Plus, passed through intact on Safari
Dolby TrueHDdecoded · 7.1WASM
DTS-HDMaster Audio · HRWASM
DTSthe DTS coreWASM
DTS ExpressDTS-HD LBRWASM
Dolby Digital PlusE-AC-3copied or WASM
Dolby DigitalAC-3copied or WASM
FLAC16 and 24 bitcopied or WASM
AACup to 7.1copied
Opusmono to multichannelcopied
MLPlosslessWASM
5.1 · 7.1every channel in its place, in the order the device expects
10 audio familiescopied as is or decoded on the device, without going through the server

Containers and subtitles

MKVMatroska
MP4 · MOVthrough the same pipeline
SRTadjustable size and position
MP4 textan MP4's own subtitles
ASS · SSAthe text
Externalfiles next to the film
Two at oncea second subtitle above the main one, each with its own offset
Generatedtranslated while the film plays, from a complete subtitleoptional
Forcedrecognised automatically
PGS · VobSubburned in by the server when casting to Chromecastserver

Played by Jellyfin

Everything elseJellyfin's server player takes over, at the same second and with the same track
VP9videoserver
VP8videoserver
MPEG-2videoserver
MPEG-4 ASPold .avi filesserver
AVIcontainerserver
Fragmented MP4containerserver
MP3audioserver
MP2audioserver
Vorbisaudioserver
PCMuncompressed audioserver

copied handed to the browser as isWASM decoded in the tab, then re-encoded to AAC or Opusserver played by Jellyfin's server player

03Engineering

The details that make the difference.

Decode times, reconstructed

Matroska stores only presentation times; MP4 needs decode times too. Lumen sorts each group's presentation times and deals them out in decode order: the exact timeline, whatever the reorder depth.

Access points, verified

Before starting or seeking, Lumen reads the frame's first NAL unit: only IDR, CRA and BLA open a segment. When the point it finds lands too far, it steps back through the index, 12 seconds at a time.

The network sets the pace

The computing is nearly free, so Lumen shapes its requests: six-chunk readahead, chunks of one read requested together, the file's header and index read in parallel at open, stale reads dropped on every seek.

Every channel in its place

Each system orders its channels differently. Lumen hands the device's encoder the order it expects: dialogue in the centre, effects at the back, an order checked system by system.

Memory sized for each engine

Safari caps each video buffer in bytes, about thirty seconds of 4K on an iPad. Lumen counts its fill in bytes actually appended and frees memory behind the playhead itself.

Decoders measured against FFmpeg

The embedded decoders were compared with FFmpeg channel by channel, on real files. The largest gap measured for TrueHD is a hundredth of a decibel.

A safety net

For a file the browser can't carry, Lumen hands over to Jellyfin, which chooses between direct play and transcoding. A panel shows the path taken and why. This fallback can be switched off in one setting.

Slow connection, steady playback

Lumen compares the bitrate it receives with the film's. When the link can't keep up, a lighter version is prepared in the background and the player switches to it at the same second, same language, same subtitles.

Black bars, gone

The server measures the real picture on the scrubbing thumbnails, without decoding any video, then the player enlarges the film to the edge of the screen. Only black is cropped.

Exact resume Instant resume from the device Chapters Scrubbing previews Skip intro Next episode preloaded Language switch at the same position Two subtitles at once Dialogue enhancement Playback speed

An interface designed for each screen.

Desktop and phone each get their own interface, with the same navigation.

On a large screen

A rail on the left that opens on hover, a banner that follows the title you land on, a grid you can drive with the keyboard.

On a phone

A bottom bar, a carousel of posters, sheets you swipe away and haptic feedback on Android.

Back goes back one screen

Every open screen lives in the address: the browser's Back button or an edge swipe returns to the previous screen, and a title's page can be shared by its link.

Your whole install, at a glance.

What's running, what's arriving, what's stuck: the admin's dashboard.

Libraries

Radarr and Sonarr views as a grid or a list, filters, sorting by rating, release search and adding series, all inside the app.

Activity and health

Active accounts, sessions playing now, the week's errors, recent imports, and a status page that shows the error each service itself returned.

Set up in the app

Service connections, playback, generated subtitles and defaults for new accounts: everything is set here.

Your server, your accounts, your rules.

Laterna runs entirely on your machine, with your household's Jellyfin accounts.

Self-hosted

One Docker container and one volume for its data.

Jellyfin accounts

Everyone signs in with their Jellyfin account. Each sign-in is a device you can revoke.

Discreet

A signed-out visitor only sees whether they can sign in and watch. Service details are for signed-in accounts.

Free and open

The code is public, under the AGPL-3.0 licence. You can read it, change it and share it.

From an empty folder to your cinema in ten minutes.

A compose file and your browser are all it takes: secrets are generated on first launch, and every setting is made in the setup assistant.

The compose file

Create an empty folder and save this file as docker-compose.yml. It works as it stands: the app answers on the server's port 3000, and its data lives in a named volume.

docker-compose.yml
# Laterna - minimal deployment
# No .env: secrets are generated on first launch,
# and every setting is made in the setup assistant.

services:
  cine-app:
    image: ghcr.io/lk59/cine-app:latest   # or :<sha> to pin a version
    container_name: cine-app
    restart: unless-stopped

    # Reachable from the server only (http://localhost:3000).
    # Behind a proxy on the same Docker network, remove these two lines.
    ports:
      - "127.0.0.1:3000:3000"

    # The Docker network of your reverse proxy (and ideally Jellyfin).
    # networks:
    #   - proxy

    volumes:
      # Database, settings, secrets and logs: all of the app's state.
      - cine-data:/app/data
      # Optional, read-only: your media, for the statistics.
      # - /path/to/media:/mnt/media/video:ro

    environment:
      - TZ=Etc/UTC
      # Behind an HTTPS proxy (step 3):
      # - COOKIE_SECURE=true
      # - TRUSTED_PROXIES=nginx-proxy-manager

    # Optional: a memory ceiling.
    # mem_limit: 2g

    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3000/api/ping"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 60s

volumes:
  cine-data:
    name: cine-app-data   # fixed name, used by backups

# networks:
#   proxy:
#     external: true   # the name `docker network ls` gives

Set TZ to your time zone (Europe/London, America/New_York...). To keep the data in a ./data folder next to the file, replace the volume line with - ./data:/app/data and create the folder first: mkdir data && sudo chown 1001:1001 data.

Every step in detail, Traefik, permissions and troubleshooting: QUICKSTART.md and DEPLOYMENT.md.

Good to know

Does Laterna replace Jellyfin?

No, it builds on it. Jellyfin stays the server that knows your files, your accounts and your play state; Laterna is its interface. What you mark as watched, your language preferences and your positions carry over to the other Jellyfin apps.

Do I need Radarr, Sonarr and the rest?

No. With Jellyfin and TMDB alone, the cinema shows your whole library. Radarr and Sonarr add management, Jellyseerr adds requests, qBittorrent adds download progress.

Will my server be transcoding all the time?

No. On the native path, the device decodes and the server serves the file. Transcoding only steps in when a file truly can't be played by the browser or the connection is too slow, and that fallback can be turned off.

Which devices does it run on?

Any recent browser: computer, tablet, phone. On iPhone, iPad and Android, Laterna installs to the home screen like an app. For the TV: Chromecast, AirPlay, or the TV's browser with a keyboard or remote.

Does anything leave my server?

Metadata comes from TMDB, as it does for Jellyfin. Generated subtitles, when enabled, send the subtitle text to the provider the admin chose. Your videos stay on your server.

Is it really free?

Yes. Laterna is free software under the GNU AGPL-3.0 licence, free in full.

Light the lantern.

Laterna is named after the magic lantern, the ancestor of cinema. Install it on your server in a few minutes.