# Pinakes A media cataloging and library management system written in Rust. Pinakes indexes files across configured directories, extracts metadata from audio, video, document, and text files, and provides full-text search with tagging, collections, and audit logging. It supports both SQLite and PostgreSQL backends. ## Building ```sh # Build all compilable crates cargo build -p pinakes-core -p pinakes-server -p pinakes-tui # The Dioxus UI requires GTK3 and libsoup system libraries: # On Debian/Ubuntu: apt install libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev # On Fedora: dnf install gtk3-devel libsoup3-devel webkit2gtk4.1-devel # On Nix: Use the dev shell, everything is provided :) cargo build -p pinakes-ui ``` ## Configuration Copy the example config and edit it: ```sh cp pinakes.toml.example pinakes.toml ``` Key settings: - `storage.backend` -- `"sqlite"` or `"postgres"` - `storage.sqlite.path` -- Path to the SQLite database file - `storage.postgres.*` -- PostgreSQL connection parameters - `directories.roots` -- Directories to scan for media files - `scanning.watch` -- Enable filesystem watching for automatic imports - `scanning.ignore_patterns` -- Patterns to skip during scanning (e.g., `".*"`, `"node_modules"`) - `server.host` / `server.port` -- Server bind address ## Running ### Server ```sh cargo run -p pinakes-server -- pinakes.toml # or cargo run -p pinakes-server -- --config pinakes.toml ``` The server starts on the configured host:port (default `127.0.0.1:3000`). ### TUI ```sh cargo run -p pinakes-tui # or with a custom server URL: cargo run -p pinakes-tui -- --server http://localhost:3000 ``` Keybindings: | Key | Action | | --------------------- | -------------------------------------------------------- | | `q` / `Ctrl-C` | Quit | | `j` / `k` | Navigate down / up | | `Enter` | Select / confirm | | `Esc` | Back | | `/` | Search | | `i` | Import file | | `o` | Open file | | `d` | Delete (media in library, tag/collection in their views) | | `t` | Tags view | | `c` | Collections view | | `a` | Audit log view | | `s` | Trigger scan | | `r` | Refresh current view | | `n` | Create new tag (in tags view) | | `+` | Tag selected media (in detail view) | | `-` | Untag selected media (in detail view) | | `Tab` / `Shift-Tab` | Next / previous tab | | `PageUp` / `PageDown` | Paginate | ### Desktop/Web UI ```sh cargo run -p pinakes-ui ``` Set `PINAKES_SERVER_URL` to point at the server if it is not on `localhost:3000`. ## API All endpoints are under `/api/v1`. ### Media | Method | Path | Description | | -------- | -------------------- | ------------------------------------- | | `POST` | `/media/import` | Import a file (`{"path": "..."}`) | | `GET` | `/media` | List media (query: `offset`, `limit`) | | `GET` | `/media/{id}` | Get media item | | `PATCH` | `/media/{id}` | Update metadata | | `DELETE` | `/media/{id}` | Delete media item | | `GET` | `/media/{id}/stream` | Stream file content | | `POST` | `/media/{id}/open` | Open with system viewer | ### Search | Method | Path | Description | | ------ | --------------- | ---------------------------------------------- | | `GET` | `/search?q=...` | Search (query: `q`, `sort`, `offset`, `limit`) | Search syntax: `term`, `"exact phrase"`, `field:value`, `type:pdf`, `tag:music`, `prefix*`, `fuzzy~`, `-excluded`, `a b` (AND), `a OR b`, `(grouped)`. ### Tags | Method | Path | Description | | -------- | --------------------------- | ------------------------------------------------ | | `POST` | `/tags` | Create tag (`{"name": "...", "parent_id": ...}`) | | `GET` | `/tags` | List all tags | | `GET` | `/tags/{id}` | Get tag | | `DELETE` | `/tags/{id}` | Delete tag | | `POST` | `/media/{id}/tags` | Tag media (`{"tag_id": "..."}`) | | `GET` | `/media/{id}/tags` | List media's tags | | `DELETE` | `/media/{id}/tags/{tag_id}` | Untag media | ### Collections | Method | Path | Description | | -------- | ---------------------------------- | ----------------- | | `POST` | `/collections` | Create collection | | `GET` | `/collections` | List collections | | `GET` | `/collections/{id}` | Get collection | | `DELETE` | `/collections/{id}` | Delete collection | | `POST` | `/collections/{id}/members` | Add member | | `GET` | `/collections/{id}/members` | List members | | `DELETE` | `/collections/{cid}/members/{mid}` | Remove member | Virtual collections (kind `"virtual"`) evaluate their `filter_query` as a search query when listing members, returning results dynamically. ### Audit & Scanning | Method | Path | Description | | ------ | -------- | ----------------------------------------------------------------------------- | | `GET` | `/audit` | List audit log (query: `offset`, `limit`) | | `POST` | `/scan` | Trigger directory scan (`{"path": "/..."}` or `{"path": null}` for all roots) | ## Testing ```sh # Unit and integration tests for the core library (SQLite in-memory) cargo test -p pinakes-core # API integration tests for the server cargo test -p pinakes-server ``` ## Supported Media Types | Category | Formats | | -------- | ------------------------------- | | Audio | MP3, FLAC, OGG, WAV, AAC, Opus | | Video | MP4, MKV, AVI, WebM | | Document | PDF, EPUB, DjVu | | Text | Markdown, Plain text | | Image | JPEG, PNG, GIF, WebP, SVG, AVIF | Metadata extraction uses lofty (audio, MP4), matroska (MKV), lopdf (PDF), epub (EPUB), and gray_matter (Markdown frontmatter). ## Storage Backends **SQLite** (default) -- Single-file database with WAL mode and FTS5 full-text search. Bundled SQLite guarantees FTS5 availability. **PostgreSQL** -- Native async with connection pooling (deadpool-postgres). Uses tsvector with weighted columns for full-text search and pg_trgm for fuzzy matching. Requires the `pg_trgm` extension.