Pinkudex
A personal, self-hosted JAV cover-art library and video player. Pinkudex indexes a local folder of cover images, links them to their video files, and gives you a fast, keyboard-friendly UI to browse, tag, rate, and play.
It runs entirely on your own machine — no accounts, no cloud, no telemetry. The web UI is for your eyes; the API is locked to localhost (with an opt-in for trusted private/Tailscale networks).
Features
- Cover grid — virtualized masonry/grid of cover thumbnails (
react-virtuoso), with portrait and landscape views. - Metadata — code, title, studio, series, actresses, labels, tags, genres, ratings, and watch state, all stored in a local SQLite database.
- Inline video playback — click the play button on any card with a linked video; native
<video>for MP4,hls.jsfor segmented playback (/api/video-hls). - Subtitles — picks up sidecar
.srt/.vtt/.ass/.ssafiles, or transcodes embedded streams via ffmpeg on demand. Optional WhisperJAV integration for AI transcription. - Search & filters — by code, actress, studio, series, label, tag, genre, rating, watched/unwatched, VIP, favorite, owned.
- Collections & categories — group covers with custom covers of their own.
- Bulk actions — multi-select with a context menu (move, tag, rate, delete).
- Backup / restore — export and import the library as a zipped bundle.
- Native file/folder pickers — calls out to the OS dialog (PowerShell on Windows,
osascripton macOS,zenityon Linux) so you can point Pinkudex at files outside the indexed roots.
Stack
- Next.js 16 (App Router, webpack dev mode)
- React 19
- TypeScript
- Tailwind CSS v4
- SQLite via
better-sqlite3+ Drizzle ORM - sharp for thumbnail generation
- hls.js for adaptive video playback
- lucide-react for icons
Project layout
app/ Next.js App Router (pages + server actions + /api routes)
api/ Local-only HTTP endpoints (video, thumbs, jobs, backup, ...)
actions/ Server actions for mutations
components/ Client + server components, grouped by domain
lib/
api/ Shared API helpers (localOnly gate, asset serving)
db/ Drizzle schema, client, queries
video/ Video probing, subtitle access, HLS helpers
whisperjav/ WhisperJAV job runner and adapters
ingest/ Library scanning and indexing
data/ Runtime state (SQLite db, thumb cache, job state, portraits)
library/ Your cover image library (configurable)
drizzle.config.ts
Getting started
Prerequisites
- Node.js 20+
- pnpm
- ffmpeg in
PATH(required for HLS streaming and embedded subtitle extraction) - A folder of JAV cover images to index
Install
pnpm install
This builds the native deps (better-sqlite3, sharp).
Set up the database
pnpm db:generate # generate migrations from schema (when schema changes)
The SQLite file lives at data/library.db and is created on first run.
Run the dev server
pnpm dev
Open http://localhost:3001.
Pinkudex's dev script uses webpack rather than Turbopack. Long Turbopack sessions trigger an
AsyncHookleak that crashes the server, sopnpm devis pinned tonext dev --webpack.
Production
pnpm build
pnpm start
Configuration
Environment variables — set in .env.local (not committed):
| Variable | Default | Purpose |
|---|---|---|
PINKUDEX_TRUSTED_LAN |
unset | Set to 1 to allow API access from RFC1918 + CGNAT/Tailscale (100.64/10) IPs in addition to localhost. |
PINKUDEX_TRUSTED_HOSTNAMES |
unset | Comma-separated list of trusted bare hostnames (e.g. pinkudex.tailnet.ts.net). Only honored when PINKUDEX_TRUSTED_LAN=1. |
The library root is ./library/; thumbnail cache lives in ./data/thumbs/.
Accessing Pinkudex from another device
Pinkudex's filesystem-touching API endpoints (/api/video-*, /api/whisperjav-*, etc.) are restricted to localhost by default. To browse from a phone or another machine over Tailscale or a private LAN:
PINKUDEX_TRUSTED_LAN=1
Restart the dev server. Requests from 10.x.x.x, 172.16-31.x.x, 192.168.x.x, 100.64-127.x.x, and IPv6 ULA/link-local addresses will be accepted.
Do not expose port 3001 to the public internet. There is no authentication — the local-IP gate is the only access control.
Scripts
| Command | What it does |
|---|---|
pnpm dev |
Webpack dev server on port 3001 |
pnpm dev:turbo |
Turbopack dev server (use sparingly — known leak on long runs) |
pnpm build |
Production build |
pnpm start |
Production server on port 3001 |
pnpm lint |
ESLint |
pnpm db:generate |
Generate Drizzle migrations from lib/db/schema.ts |
pnpm db:studio |
Open Drizzle Studio against data/library.db |
Optional: WhisperJAV integration
Pinkudex can drive a separate WhisperJAV CLI to transcribe videos that lack subtitles. Jobs are tracked in data/whisperjav-jobs/. The integration is opt-in and surfaces in the per-cover detail view.
License
Private. Not distributed.