9 Featured WorksProjects
  1. Home
  2. /Projects
  3. /Shutter
full stack2026Featured

Shutter

Personal Movie Library

Visit live siteStart a project
shutter-cinematic.vercel.app
01

The challenge

Movie collectors keep films and series spread across several external hard drives, so the collection can only be browsed with every drive plugged in, and messy release filenames defeat most media-server matching.

02

The approach

Built a local-first Tauri 2 (Rust) desktop app with a React/TypeScript UI over a Rust workspace of focused crates.

What shipped
  • A release-name parser
  • Stable drive identity with plug/unplug detection
  • An incremental SQLite catalog scanner
  • Ffprobe media info with ffmpeg frame thumbnails
  • An mpv player embedded in the app window over JSON IPC

A Next.js and MongoDB API on Vercel adds Google sign-in with rotating refresh tokens, a cached TMDB metadata proxy and push/pull sync of watch history.

03

The outcome

One Netflix-style library across every drive that stays browsable while the drives are unplugged and plays 4K Dolby Vision and TrueHD at full quality. The filename parser reads 98.5% of 459 real files correctly and 97.2% of titles match TMDB automatically, with a marketing and download site alongside.

At a glance

Year
2026
Type
full stack
Stack
7 technologies
Scope
5 core capabilities
Live at
shutter-cinematic.vercel.app
Visit live site

Built with

TauriRustReactTypeScriptSQLiteNext.jsMongoDB
System design

How Shutter is put together

A local-first Tauri 2 app built as a Rust workspace of focused crates. The catalog is SQLite on the user’s machine and keeps working with every drive unplugged; a small Next.js API on Vercel adds Google sign-in, a cached TMDB proxy and cross-device sync.

filenames parsed correctly (459 real files)
98.5%
titles matched to TMDB automatically
97.2%
Rust crates in the workspace
5
confidence needed to auto-match
≥ 0.85

01 · Desktop UI

client

Library UI

React · TypeScript · Vite

02 · Rust core

gateway

Tauri core

commands, artwork:// protocol

service

Player

mpv · JSON IPC

embedded via --wid

03 · Engine crates

worker

Drive watcher

stable identity, USB + SMB

service

Scanner & matcher

incremental, one transaction

service

Release-name parser

worker

Media probe

ffprobe · ffmpeg

HDR, audio, thumbnails

04 · Local

database

SQLite catalog

rusqlite

object store

Artwork cache

app data, offline

third-party

Drives & shares

read-only while scanning

05 · Cloud

service

Shutter API

Next.js · Vercel

auth, meta proxy, sync

database

MongoDB

accounts, sync, TTL meta cache

third-party

TMDB

third-party

Google OAuth

PKCE, loopback

Hover a component to trace its connections, or a request-flow step to follow it through the system.

clientgatewayserviceworkerdatabaseobject storethird-partyrequest / responseasync · event · schedule

14 connections

  • Library UI→Tauri coreinvoke
  • Tauri core→Scanner & matcher
  • Tauri core→Playerplay file
  • Drive watcher→Scanner & matcherplug events · async
  • Scanner & matcher→Release-name parserfilenames
  • Scanner & matcher→Media probemedia info · async
  • Scanner & matcher→SQLite catalogtransaction
  • Scanner & matcher→Drives & shareswalk
  • Player→Drives & sharesstream
  • Tauri core→Artwork cache
  • Tauri core→Shutter APImeta · sync · auth
  • Tauri core→Google OAuthPKCE sign-in
  • Shutter API→MongoDB
  • Shutter API→TMDBcached proxy
›14 connections as text
  • Library UI→Tauri coreinvoke
  • Tauri core→Scanner & matcher
  • Tauri core→Playerplay file
  • Drive watcher→Scanner & matcherplug events · async
  • Scanner & matcher→Release-name parserfilenames
  • Scanner & matcher→Media probemedia info · async
  • Scanner & matcher→SQLite catalogtransaction
  • Scanner & matcher→Drives & shareswalk
  • Player→Drives & sharesstream
  • Tauri core→Artwork cache
  • Tauri core→Shutter APImeta · sync · auth
  • Tauri core→Google OAuthPKCE sign-in
  • Shutter API→MongoDB
  • Shutter API→TMDBcached proxy

Request flow · Plugging in a drive

  1. 1The drive watcher recognises the disk by a stable identity rather than its letter or mount point and raises a plug event.
  2. 2The scanner walks the drive; files with an unchanged path, size and mtime are only marked seen. New or changed files are quick-hashed and parsed.
  3. 3A vanished file and a new one with the same quick hash are a move: the row keeps its id and its watch history. The whole scan is one transaction, so pulling the drive mid-scan leaves the catalog untouched.
  4. 4ffprobe adds resolution, HDR and audio tracks; titles are matched through the API’s cached TMDB proxy (auto at ≥ 0.85 confidence, review from 0.60) and posters land in the local artwork cache.

Design decisions

Scanning never writes to drives
Catalog, artwork and thumbnails live in app data, so drives work read-only and duplicate copies share one image. The optional organizer renames files only after a dry-run plan, with an operation log and one-click undo.
History follows the title, not the file
Progress and watch counts attach to the movie or episode, so a copy on another drive shares the same history.
A name alone is never proof
Matching scores TMDB candidates against parsed names and probed media details, with thresholds for auto-match, review and unmatched.
Conflict rules per field
Sync uses last-write-wins for playback position, max for last-watched and append-only for watch events.
Secrets stay server-side
The TMDB key never ships in the app; the API proxies and caches responses in MongoDB with a TTL. Sign-in uses PKCE, so an intercepted loopback code is useless alone.

Trade-offs & known limits

  • macOS code paths for drive detection and SMB are written but not yet run on a Mac.
  • mpv runs as a child process: robust playback for every format, at the cost of bundling the binary.

Have something similar in mind?

I'm open for freelance and contract work — let's talk about what you're building.

Get in touchSee more workBack to home
←PreviousHomeFlowfull stackNext→PromptSensefull stack
Rohith. A

© 2026 All rights reserved.

Available for work