Role
Application architecture, implementation, and operations
Web stack
PHP, JavaScript, JSON catalogs, Caddy
Processing stack
Python, SQLite, FFmpeg / ffprobe, systemd
Status
Operating private application with scheduled compatibility repair

I designed the catalog, interface, delivery path, and player as parts of one system.

The filesystem is the source of truth. Media lives on disk, and one ingest tool reconciles the library into structured JSON catalogs. PHP and JavaScript turn those catalogs into a responsive browsing interface. Authenticated endpoints deliver the media and every supporting asset.

That gives me a straight line from a file on disk to the card in the browser and the exact byte range reaching the player. Every layer is visible, understandable, and mine to maintain.

Catalog ingest built for safe changes

The ingest process does more than scan folders. It preserves existing metadata and enriches new titles from OMDb when available. It reconciles artwork, builds category manifests and playlists, and prunes generated files whose source media no longer exists. It runs as a dry run by default, showing additions, updates, and removals before it changes anything.

The dangerous cases are designed to fail closed. Before reconciliation, the tool checks the media root and validates bind-mounted libraries with sentinel files and device IDs. A commit snapshots the JSON tree first, and a deletion circuit breaker protects catalog manifests and hand-authored skip data unless the removal is explicitly forced. A dropped mount cannot quietly turn into a mass catalog deletion.

A narrow, protected delivery path

The session boundary covers more than the PHP pages. Catalog JSON and posters pass through authenticated routes, as do JavaScript, CSS, icons, and fonts. Raw media follows the same protected path. Caddy directs each protected path to a small PHP endpoint, where resolved files are constrained to their expected roots.

Media is streamed in chunks with HTTP byte-range responses, so the browser can seek without exposing absolute filesystem paths or loading an entire file into memory. Play history uses locked access and atomic replacement rather than introducing a database for one small piece of runtime state.

An interface shaped by the library

The interface follows the content instead of forcing every collection into one view. Films and series use metadata-rich cards with era, recency, and live-action or animated filters. Music and long-form audio use a recursive collection browser with no hard-coded depth. Series open into season and episode views, while lightweight browser state remembers filters, last-opened items, active playlist tracks, and playback position.

A player shaped by real browser failures

Audio and video share one custom player with playlists, auto-advance, saved position, and recent history. Keyboard controls, fullscreen video, and optional per-series intro and outro skipping are built into the same interface.

Writing the controls was the easy part. The deeper work came from long variable-bitrate audio, interrupted mobile streams, and iOS restoring a media element in a state where the interface appears to seek while playback continues from zero. The player treats that as a recoverable failure. It first verifies that the requested seek landed. If not, it reloads the wedged element in place, waits for a valid seekable range, restores the target position, and resumes only when safe. Pending recovery is cancelled the moment the user takes control.

Compatibility repair before playback

I built a separate Python pipeline to prepare video for iOS playback. A scheduled scanner finds incompatible files, waits for new arrivals to stabilize, and queues work in SQLite. A systemd runner processes one item at a time. FFmpeg copies compatible streams into fast-start MP4 containers and converts unsupported streams only when needed. The pipeline also detects poorly interleaved audio and video in otherwise compatible MP4s.

Publication requires output verification. Checks cover codec compatibility, timing, chapters, and stream layout; copied streams are hash-checked as well. Source fingerprints, mount checks, destination-collision guards, and projected disk-space limits protect the library. Verified output is published atomically on its destination filesystem before the superseded source is removed.

Durable job state separates conversion from catalog ingest. An interrupted conversion restarts at a safe item boundary; an ingest failure retries the already-published file without encoding it again. Each stage has bounded attempts and retains actionable failures. The pipeline then runs catalog reconciliation and validation, with successful scratch and logs removed after use. Playback serves prepared files and never transcodes on demand.

How it stays operable

The application runs on a security-hardened platform I host from home on a repurposed ThinkPad. Cloudflare and Caddy carry requests from the public edge into an isolated PHP-FPM pool; encrypted storage holds the library; and the firewall exposes only the services the platform actually uses.

Code moves from the authoring tree into a receive-only production folder through Syncthing, while automated Git history records the deployed tree outside the webroot. Ingest runs beside the mounted library, then returns its generated catalogs and artwork to the authoring path so production does not become a second, hidden source of truth.

What this demonstrates

  • End-to-end application engineering across ingest, PHP rendering, browser state, protected delivery, and playback
  • Safety-conscious filesystem reconciliation using dry runs, mount validation, deletion circuit breakers, snapshots, and atomic writes
  • Authenticated, path-constrained media delivery with HTTP byte-range support
  • Interface architecture for metadata catalogs, recursive collections, series navigation, and persistent playback state
  • Playback resilience built from observed iOS and WebKit failure modes, including verified seeks and in-place recovery
  • Durable media-processing jobs with selective conversion, verified publication, and separate recovery for conversion and ingest
  • Home-hosted application operations across Cloudflare, Caddy, PHP-FPM, encrypted storage, synchronized deployment, monitoring, and recovery