A personal catalog for everything you have watched on Netflix, Prime Video, Disney+ or at the cinema. Import your streaming history, browse and filter it, track how many seasons of a series you have got through, and let TMDB fill in posters, ratings, genres and years. Optionally, Claude looks at your catalog and suggests what to watch next, refreshing itself automatically every few days.
It is an installable PWA: add it to your phone's home screen and it opens like an app, with an offline fallback and cached posters.
Stack: Next.js 16 (App Router) · React 19 · TypeScript · Tailwind CSS v4 · Prisma + SQLite / Turso · Serwist (service worker).
No TOTP_SECRET to generate up front: sign-in is set up from the app itself
on first run — see Getting the credentials below
for how to get the values Vercel will ask for.
- Catalog — one grid for every title, filtered by platform and by movie/series, searchable, sortable by date watched, title, TMDB rating or release year.
- History import — upload your Netflix and Prime Video exports; only titles that are not already in the catalog get added, so you can re-import after every new export.
- Seasons — series show "3 of 5 seasons"; the totals come from TMDB, the watched count from your history, and you can adjust it by hand.
- Add a title — search TMDB as you type and add anything, pick the platform you watched it on.
- Watchlist — a Watched / To watch switch; in "To watch" the search browses all of TMDB (hiding what you have already seen) and a click adds it. Right- click a waiting title and "Mark as watched" moves it over.
- Statistics — how much you watched and where, your top genres, titles per year, which decades they come from and your best-rated titles.
- TMDB enrichment — posters, backdrops, overviews, ratings, genres, years, season counts.
- AI recommendations (optional) — Claude suggests titles to watch next
based on your catalog, refreshing itself automatically every 5 days (every
batch kept as history); click one to watch its trailer. In "To watch" they
appear as a strip you can add from in one click, and "not interested" on any
of them keeps it out of every future batch. Off by default, enabled by
setting
ANTHROPIC_API_KEY. - Maintenance — a settings page for importing, merging series split across rows, fixing missing posters, exporting the whole catalog as JSON, and an edit mode for deleting titles.
- Single-user auth — a 6-digit TOTP code from your authenticator app; no passwords, no accounts, no third-party sign-in.
- Setup wizard — the first time you open the app it asks for a content language/region and walks you through scanning a QR code into your authenticator app. No secrets to generate or configure by hand.
| Self-hosted | Serverless (Vercel & co.) | |
|---|---|---|
| Database | SQLite file on a volume | Turso (hosted libSQL) |
| Cost | your own machine | free tiers are enough |
| Setup | docker compose up |
connect the repo, set env vars |
| Best when | you have a NAS, VPS or home server | you want a URL and no server |
Both run the exact same code — only the database differs, and that is decided
by whether TURSO_DATABASE_URL is set.
git clone https://github.com/ldtnz/cinemory.git
cd cinemory
cp .env.example .env # fill in SESSION_SECRET, TMDB_ACCESS_TOKEN
docker compose up -d --buildThe app is on http://localhost:3000. The database is a SQLite file on the
cinemory-data volume, and the container applies the migrations on every
start, so a fresh volume just works. The first request opens the setup
wizard — pick a content language/region, then scan the QR code to finish.
To update:
git pull
docker compose up -d --buildNode 22 or newer.
git clone https://github.com/ldtnz/cinemory.git
cd cinemory
npm install
cp .env.example .env # fill in SESSION_SECRET, TMDB_ACCESS_TOKEN
npx prisma migrate deploy # creates prisma/dev.db with the schema
npm run build
npm start # http://localhost:3000For development use npm run dev instead of build + start.
-
Create the database. Sign up at turso.tech (the free tier is plenty), then:
turso db create cinemory turso db show cinemory --url # -> TURSO_DATABASE_URL turso db tokens create cinemory # -> TURSO_AUTH_TOKEN
-
Create the schema. With both values in your local
.env:npm install npm run db:migrate-turso
-
Deploy. Import the repository on vercel.com and add these environment variables to the project:
SESSION_SECRET,TMDB_ACCESS_TOKEN,TURSO_DATABASE_URL,TURSO_AUTH_TOKEN. Deploy, then open the app: the first visit opens the setup wizard, which stores its own content language/region and TOTP secret in that Turso database.
Whenever the schema changes, run npm run db:migrate-turso again before
deploying — prisma migrate deploy talks to a SQLite file, not to Turso.
Every variable is documented in .env.example. In short:
| Variable | Required | What it is |
|---|---|---|
SESSION_SECRET |
yes | random string used to sign the session cookie |
TMDB_ACCESS_TOKEN |
yes* | TMDB v4 "API Read Access Token" |
TMDB_API_KEY |
yes* | TMDB v3 "API Key" — the alternative to the token |
ANTHROPIC_API_KEY |
no | enables the AI "what to watch next" recommendations |
DATABASE_URL |
self-hosted | path to the SQLite file |
TURSO_DATABASE_URL |
serverless | libSQL endpoint; when set, it wins over DATABASE_URL |
TURSO_AUTH_TOKEN |
serverless | token for that database |
* one of the two TMDB credentials. Nothing is baked into the client bundle: every TMDB call goes through the app's own API routes, so the key stays on the server.
The TOTP secret and the content language/region are not environment variables
at all — the setup wizard on first run stores them in the database. Only
SESSION_SECRET, the key that signs the session cookie, stays outside the
database: keeping it there means a leaked database alone cannot be used to
forge a session, only to read the catalog and the (equally database-stored)
TOTP secret.
TMDB (free). Create an account at
themoviedb.org/signup, then go to
Settings → API and request a
"Developer" key for personal use. Copy the API Read Access Token into
TMDB_ACCESS_TOKEN (or the shorter API Key (v3 auth) into TMDB_API_KEY).
SESSION_SECRET. Any long random string:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Claude API (optional). Create a key at
console.anthropic.com/settings/keys
and set ANTHROPIC_API_KEY. Without it the recommendations panel on the
settings page is simply hidden. Recommendations refresh themselves
automatically every 5 days (no manual step needed) and every batch is kept
as history; a refresh — automatic or an early manual one from the settings
page — is capped server-side to once per 5-day window, so the running cost
stays a few cents a month.
Open Settings (the gear, top right) → Import watch history and upload one or both CSVs. The format is detected from the header, and only titles that are not already in the catalog are added — so importing the same file twice changes nothing. Posters and metadata are fetched right afterwards.
- Netflix — Account → Profile → Viewing activity → Download all. You get
a
NetflixViewingHistory.csvwith two columns:Title,Date. - Prime Video — Amazon has no built-in export. Use Watch History Exporter for Amazon Prime Video: open primevideo.com/settings/watch-history, paste the script into the browser console and run it.
- IMDb (optional) — Your Ratings → Export. Handled by a script rather than the UI, see below.
prisma/seed-data/ ships a few fake *.example.csv files showing exactly what
each format looks like. Your own exports go in the same folder under the names
without .example, and are git-ignored.
npm run db:seed # wipes the catalog and rebuilds it from the two CSVs
npm run db:enrich # fetches posters/ratings/genres for anything missing themdb:seed replaces the catalog, so it is for the first build; afterwards use
the incremental import in the app. db:enrich -- --force re-fetches everything
rather than just the new titles.
For IMDb ratings there is a separate script that looks each title up by its exact IMDb ID and guesses the platform from TMDB's streaming providers:
npx tsx scripts/import-imdb.ts --dry-run # report only, writes nothing
npx tsx scripts/import-imdb.tsprisma/schema.prisma the data model (one Title table)
prisma/migrations/ SQL migrations
prisma/seed.ts builds the catalog from the CSVs
prisma/seed-data/ your exports + the bundled fake samples
scripts/enrich-tmdb.ts fills in TMDB data for existing titles
scripts/import-imdb.ts imports an IMDb ratings export
scripts/sync-turso.ts pulls Turso down into the local dev.db
scripts/migrate-turso.ts applies prisma/migrations to Turso
src/app/page.tsx the catalog page (server component)
src/app/settings/ import, seasons, missing posters, edit mode
src/app/stats/ the statistics page
src/app/api/ TMDB search, import, seasons, titles, login
src/app/sw.ts service worker (offline + poster cache)
src/components/ Catalog, FilterBar, TitleCard, AddTitleCard, …
src/lib/history.ts parses the Netflix and Prime Video exports
src/lib/stats.ts the numbers behind the statistics page
src/lib/tmdb.ts the TMDB client
src/lib/prisma.ts shared Prisma client (SQLite or Turso)
src/lib/auth.ts TOTP verification and session cookie
tests/ parser and statistics tests (run with npm test)
| Script | What it does |
|---|---|
npm run dev |
development server (syncs from Turso first, if configured) |
npm run build / npm start |
production build and server |
npm run lint |
ESLint |
npm test |
the tests (Node's built-in test runner, no framework) |
npm run db:seed |
rebuild the catalog from the CSV exports |
npm run db:enrich |
fetch TMDB data for titles that have none |
npm run db:sync |
copy the Turso database down into prisma/dev.db |
npm run db:migrate-turso |
apply prisma/migrations to Turso |
The app picks its database at runtime: if TURSO_DATABASE_URL is set it goes
through Prisma's libSQL adapter to Turso, otherwise it uses the local SQLite
file. That is the only difference between the two deployment options — same
schema, same queries, same migrations.
prisma/dev.db is git-ignored: it holds your own catalog.
Your watch history stays in your own database. The only outbound calls are to TMDB, for the title you are searching for or enriching, and TMDB's image CDN for posters. There is no analytics, no telemetry and no third-party account.
GNU AGPLv3 — free to use, copy and modify. If you distribute a modified version, or run one as a network service, you must make that version's source available to your users under the same license: see the license text for the exact terms.

