Skip to content
This repository was archived by the owner on May 23, 2026. It is now read-only.

Latest commit

 

History

1,150 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Important

🚚 This project has moved!

Obscura has been renamed to Prismedia and development continues at:

This repository is archived. Please update your bookmarks, clones, and any references.

Obscura

Obscura

A modern, self-hosted private media browser.
Video-first, with first-class comics, images, galleries, and audio. Designed for a single trusted user on a private LAN.

Read the docs Quick start guide Plugin author guide

Docs · Quick Start · Highlights · Features · Subreddit · Configuration · Development

Obscura dashboard


What is Obscura?

Obscura is a private, self-hosted home for your entire media collection. Videos, movies, TV series, comics, manga, books, image galleries, and audio all live together in one refined interface — organized, searchable, and ready to play from any device on your local network.

It runs as a single Docker container: PostgreSQL, ffmpeg, and the web server are all bundled together. Mount your media directories, open a browser, and you're watching. No external databases, no cloud accounts, no data leaves your network.

Discussions and community updates live on the Obscura subreddit.


Highlights

  • Every media type, first-class — videos, movies, TV series, comics, manga, books, image galleries, and audio libraries are all treated equally. No second-class formats.
  • Mobile first — built for phones from day one. The desktop view is an expansion of the mobile layout, not the other way around.
  • Rich video playback — HLS adaptive streaming with on-demand ffmpeg transcoding, a scrollable frame strip, and one-click marker + thumbnail creation from any frame.
  • Subtitles & live transcripts — multi-language sidecar / embedded / uploaded tracks, three caption styles, and a dockable transcript panel that reads alongside the video on desktop.
  • Comic and book reader — cbz/zip archives and image folders scan as series, keep natural page order, import ComicInfo metadata, track reading progress, and open in paged spreads or vertical webtoon mode.
  • Audio libraries — organize and play your music and audio collection with the same metadata pipeline: albums, tracks, cover art, waveforms, performers, and shuffle support.
  • Plugin-powered metadata — TypeScript, Python, and Stash-compatible scraper plugins expose providers for videos, series, performers, galleries, and audio. StashDB is supported natively.
  • Bulk identify — select a batch of unmatched items and Obscura iterates every installed provider. No more identifying things one at a time.
  • Everything cross-referenced — videos, comics, audio, performers, studios, and tags all link to each other through the same rich metadata surface.
  • Automated scanning — point it at a folder, walk away. Obscura scans on a schedule and notices new files.
  • Command palette + global search⌘K from anywhere, or a dedicated search page spanning all library types.
  • Content filtering — mark library roots as restricted to keep sensitive content out of shared views; switch with a keyboard shortcut or a gesture on mobile.
  • Drag-and-drop uploads — add files from the browser, remove from the library, or delete from disk entirely.
  • One image, one port — everything runs in a single Docker container. No external Postgres, no Redis URLs, no env wrangling.

Quick Start

Obscura ships as a single Docker image with PostgreSQL, SvelteKit, ffmpeg, and the background worker bundled. No external databases. No configuration required.

Docker Run

docker run -d \
  --name obscura \
  -p 8008:8008 \
  -v obscura-data:/data \
  -v /path/to/your/media:/media \
  ghcr.io/pauljoda/obscura:latest

Docker Compose

services:
  obscura:
    image: ghcr.io/pauljoda/obscura:latest
    ports:
      - "8008:8008"
    volumes:
      - obscura-data:/data
      - /path/to/your/media:/media
    restart: unless-stopped

volumes:
  obscura-data:
docker compose up -d

Open http://localhost:8008 and you're done.

Volumes

Mount Purpose
/data Database, cache, thumbnails, trickplay sprites, HLS transcodes
/media Your media library. Mount one or more directories here

You can map as many subdirectories under /media as you like and register each one as a library root in Settings.

Image tags

Obscura publishes two channels to ghcr.io/pauljoda/obscura:

Tag What it is When to use
latest The most recent tagged release (vX.Y.Z) Default for end users. Stable, versioned, paired with a GitHub Release and changelog entry
X.Y.Z / X.Y / X A specific released version (or minor / major line) Pin to a known version for reproducible deploys
dev Every commit on main after CI passes Bleeding edge. Expect churn. Good for testing fixes before a release
sha-abc1234 A specific commit SHA on main Pin to an exact dev build for rollback or bisection
X.Y.Z-abc1234 A specific commit against the in-progress X.Y.Z-dev cycle Same as sha-… but self-describing — shows which release this dev build is headed toward

If you're running Obscura as your media browser and want things to Just Work, use :latest. If you want to try a change that hasn't shipped yet, use :dev. See CHANGELOG.md for everything that's gone into each release, and the GitHub Releases page for the same notes rendered with assets.


Features

Library Dashboard

An at-a-glance overview of your collection: totals, recent activity, queue state, and live job status.

Dashboard

Videos

A responsive grid of scene cards with thumbnails, duration, resolution badges, and quick metadata. Filter, sort, and view as cards or a compact list.

Video library

Folder Browse & Detail

The video library supports two browsing modes. Grid/List shows every scene in your library as a flat feed you can sort, filter, and search. Folders mirrors the directory structure on disk, letting you navigate into subfolders, see scene counts per folder, and drill down to scenes inside a specific directory.

Folder detail view with poster, backdrop, cast, and subfolders

Clicking into a folder opens a detail view inspired by media servers like Jellyfin. Each folder can carry its own metadata: a poster and backdrop image, description, studio, date, star rating, linked performers (shown as a scrollable Cast & Crew strip), and tags. The edit panel uses the same chip pickers and autocomplete as scene editing, so adding performers, tags, and studios is fast. Uploading or dragging files while viewing a folder places them directly into that folder's directory on disk — no library root picker needed.

Folders are also searchable from the command palette and the full search page, and appear on studio and tag detail pages when associated.

Rich Video Playback

Direct playback of common formats plus on-demand HLS transcoding for anything the browser won't play natively. The scrollable frame strip under the player lets you scrub by thumbnail — and turn any frame into a marker or a custom preview image with a single click.

Scene detail with player, frame strip, and metadata

Subtitles & Live Transcripts

Obscura treats subtitles as a first-class feature. Three ingestion paths are wired up end-to-end:

  • Sidecar discovery — drop a .srt / .vtt / .ass next to a video (optionally tagged with a language, e.g. movie.en.srt) and it gets picked up on the next library scan.
  • Embedded extraction — a background worker job runs ffmpeg against videos with soft-subtitle streams and converts each track to WebVTT automatically. Image-based subtitle codecs (PGS, VobSub) are skipped gracefully.
  • Manual upload — drop a file in from the scene page with an inline language picker.

All tracks land in a shared Transcript tab where you can rename them inline, delete them, or trigger a re-extract. The transcript itself shows the full cue list with the current line highlighted, past lines grayed but still clickable, and a single click on any cue seeks the player.

Transcript tab with track management and scrolling cue list

On desktop, a Dock next to video button pins the transcript directly beside the player with a draggable resize handle between them — watch and read at the same time, no context switching. The dock preference and last-used width persist in localStorage, follow you across scenes, and auto-collapse on scenes without subtitles so you never see empty space.

Docked transcript sidecar next to the video player

The player renders captions through a custom overlay with three visual styles you can switch between on the fly:

  • Stylized — Dark Room brass-edged plate with a subtle glow, matches the rest of the UI.
  • Classic — flat translucent-black box with plain white text, the look of most media players.
  • Outline — white text with a black stroke and no box, maximum transparency over the picture.

Text size and vertical position are continuously adjustable, and an in-player settings side panel lets you tweak everything live on top of whatever is currently playing. Per-user overrides persist locally; a reset button snaps you back to the library defaults.

In-player subtitle style side panel with live preview

Library-wide defaults — auto-enable on load, preferred-language priority list (first match wins, with ISO 639-1 ↔ 639-2 equivalence so en also matches eng), default caption style, text size, and position — are all configurable from the global settings page, with a live dummy-frame preview that updates as you tweak the controls.

Global subtitle settings with live preview

Rich Metadata, Everywhere

Every entity — videos, comics, books, audio, performers, studios — carries the same metadata surface: title, studio, performers, tags, ratings, custom notes, and provenance. Plugin-powered providers cover everything, and StashDB / community Stash scrapers are supported natively for existing collections.

Performers Studios

Bulk Scraping

Select a batch of unmatched scenes, galleries, or performers and Obscura will iterate every installed scraper to find a match. No more identifying things one at a time.

Bulk identify

Community Scrapers & Plugins

Browse, install, enable, and disable community scraper plugins directly from the UI. Stash-compatible scrapers and StashDB endpoints are supported alongside native TypeScript and Python plugins — the full public scraper index is built in, no manual file copying required.

Community scrapers

Galleries & Comics

Folder-based and archive-based galleries are first-class. Browse, tag, rate, link authors/performers and studios, and view them in grid or lightbox modes.

Comics and manga are part of the same gallery system — not a separate silo. Drop in cbz/zip archives or image folders, and Obscura keeps page filenames in natural reading order, imports ComicInfo metadata where available, groups chapter archives into series-style galleries, tracks read/unread progress, and opens them in a dedicated reader with paged spreads or vertical webtoon mode.

Galleries

Books

The book reader brings the same organized, metadata-rich experience to your reading collection. Books live alongside comics, galleries, and videos in one unified library — browsable, searchable, and readable from any device on your network.

Audio Libraries

Organize and play your audio collection with the same metadata pipeline — libraries, tracks, cover art, tags, and performer/studio linking. Includes a built-in player with shuffle and playlist support.

Audio libraries Audio library detail

Global Search + Command Palette

Press ⌘K (or Ctrl+K) from anywhere to jump to anything. There's also a dedicated search page that spans scenes, performers, studios, galleries, tags, and audio libraries.

Global search

Library Scanning + Settings

Register multiple library roots, choose whether generated assets (trickplays, sprites, previews, HLS cache) live next to your media files or in a dedicated cache volume, and enable automatic periodic scanning so new files show up on their own.

Settings and generation pipeline

Job Control

A live view of every queue and job — library scan, probe, fingerprint, thumbnail, sprite, HLS, import, scrape. Retry, cancel, and watch progress in real time.

Job control

Content Filtering

Mark any library root as restricted to keep specific content out of shared or public views. Flip the filter globally with a keyboard shortcut on desktop or a hidden gesture on mobile. The flag propagates to all videos, images, galleries, audio libraries, and tracks under that root.

First-Class Mobile

Every view is designed for a phone first. Navigation, playback, scanning, scraping — everything works on mobile, not just "sort of."

Mobile dashboard Mobile videos Mobile scene detail Mobile galleries

Uploads & Removal

Drag files directly into the browser, or use the upload picker. Remove items from the library, or remove them from disk entirely — your choice, per action.


Configuration

Obscura works out of the box with zero configuration. These environment variables are available for advanced use:

Variable Default Description
OBSCURA_CACHE_DIR /data/cache Directory for HLS cache, thumbnails, sprites, previews
OBSCURA_MAX_VIDEO_UPLOAD 21474836480 Max bytes for a single upload (default ~21 GiB)
OBSCURA_SECRET auto-generated at /data/.obscura-secret Secret key used to encrypt plugin credentials (e.g. TMDB API keys) at rest. Auto-generated on first boot and persisted in the data volume. Override only if you want to manage it externally; changing it invalidates all previously saved plugin credentials.

Multiple Media Directories

Mount as many media directories as you need:

docker run -d \
  --name obscura \
  -p 8008:8008 \
  -v obscura-data:/data \
  -v /mnt/nas/videos:/media/videos \
  -v /mnt/nas/photos:/media/photos \
  -v /mnt/nas/music:/media/music \
  ghcr.io/pauljoda/obscura:latest

Then add each one as a library root in Settings → Library. Per-root, you can choose whether generated assets live alongside the media files or in Obscura's dedicated cache directory.


Design Language

Obscura uses a Dark Control Room visual system inspired by Blackmagic DaVinci Resolve, high-end audio rack gear, and film color-grading suites.

  • Surface hierarchy — Five levels from near-black graphite to elevated panel gray
  • Accent — Burnished brass (#c49a5a), used sparingly for active and selected states, always with glow
  • Typography — Geist (headings), Inter (body), JetBrains Mono (metadata and utility)
  • Motion — Weighted and deliberate, precision machinery, no bounce
  • Shape — Sharp corners everywhere (border-radius: 0)

Full specification in docs/design-language.md.


What's Inside

The single image bundles:

Component Role
SvelteKit Web frontend and same-origin HTTP API ingress
pg-boss Background job queue (Postgres-backed, no Redis)
PostgreSQL 16 Database
ffmpeg Video and audio transcoding

All services run inside the container, coordinated by a single entrypoint. Port 8008 is the only exposed port.


Development

Prerequisites

  • Node.js 22+
  • pnpm 10+
  • Docker and Docker Compose (for PostgreSQL)

Setup

git clone https://github.com/pauljoda/obscura.git
cd obscura

pnpm install

# Start PostgreSQL (pg-boss manages the queue inside the DB)
docker compose -f infra/docker/docker-compose.yml up postgres -d

# Apply migrations
pnpm --filter @obscura/db db:migrate

# Start all services in dev mode
pnpm dev

The app and API both run at http://localhost:8008, with JSON served under /api/*.

Commands

Command Description
pnpm dev Start all services in development
pnpm build Build all apps and packages
pnpm check Lint and typecheck across the monorepo
pnpm release:check Validate version and changelog alignment
pnpm --filter @obscura/db db:generate Generate a new SQL migration from schema changes
pnpm --filter @obscura/db db:migrate Apply versioned migrations to PostgreSQL
pnpm --filter @obscura/db db:studio Open Drizzle Studio

Building the Docker Image Locally

# Dev-style build (does not require a matching CHANGELOG release heading)
docker build -f infra/docker/unified.Dockerfile -t obscura .

# Release-style build (enforces package.json version matches a released
# CHANGELOG heading — fails on -dev versions). Used by the Release workflow.
docker build -f infra/docker/unified.Dockerfile --build-arg RELEASE_STRICT=1 -t obscura .

docker run -p 8008:8008 -v obscura-data:/data -v /your/media:/media obscura

Releases

Obscura follows Semantic Versioning and Keep a Changelog.

  • Every commit to main rebuilds the :dev image and appends entries to ## [Unreleased] in CHANGELOG.md. Each unreleased section leads with a What's New TL;DR of the most impactful user-facing changes, followed by detailed entries grouped by Added / Changed / Fixed. The root package.json carries a X.Y.Z-dev marker between releases.
  • Releases are cut server-side by the Release GitHub Action. Maintainers trigger it from the Actions tab with a patch / minor / major bump (or an explicit X.Y.Z). The workflow:
    1. Runs scripts/release/cut.mjs --phase release: bumps every package.json, promotes ## [Unreleased] to ## [X.Y.Z] - YYYY-MM-DD, and writes RELEASE_NOTES.md from the new section.
    2. Creates commit chore(release): vX.Y.Z and tag vX.Y.Z.
    3. Runs scripts/release/cut.mjs --phase post: bumps to X.Y.(Z+1)-dev and commits chore(release): begin vX.Y.(Z+1)-dev cycle.
    4. Pushes main + tag.
    5. Builds the unified Docker image with RELEASE_STRICT=1 from the release tag and pushes latest, X.Y.Z, X.Y, and X to GHCR.
    6. Creates a GitHub Release for vX.Y.Z with the extracted changelog as the body.
  • Users consuming :latest always get the most recent released build — never a half-finished main. Users who want to test unreleased changes can pin :dev or a specific :sha-… tag.

See the release runbook and full policy in CLAUDE.md.


License

Licensed under CC BY-NC-SA 4.0.

You are free to share and adapt this work for non-commercial purposes, with attribution, under the same license terms. See LICENSE for details.

About

Self host media server, Stash alternative, supports Stash scrapers and hash databases

Resources

Stars

89 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages