132 lines
5.1 KiB
Markdown
132 lines
5.1 KiB
Markdown
# 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.js` for segmented playback (`/api/video-hls`).
|
|
- **Subtitles** — picks up sidecar `.srt`/`.vtt`/`.ass`/`.ssa` files, 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, `osascript` on macOS, `zenity` on 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
|
|
|
|
```bash
|
|
pnpm install
|
|
```
|
|
|
|
This builds the native deps (`better-sqlite3`, `sharp`).
|
|
|
|
### Set up the database
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
pnpm dev
|
|
```
|
|
|
|
Open <http://localhost:3001>.
|
|
|
|
> Pinkudex's dev script uses webpack rather than Turbopack. Long Turbopack sessions trigger an `AsyncHook` leak that crashes the server, so `pnpm dev` is pinned to `next dev --webpack`.
|
|
|
|
### Production
|
|
|
|
```bash
|
|
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:
|
|
|
|
```env
|
|
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](https://github.com/) 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.
|