Add Docusaurus docs site and GitHub Pages release packaging

This commit is contained in:
2026-03-28 18:58:36 -04:00
parent b2c65467a0
commit 46e8ffee2a
43 changed files with 19351 additions and 2 deletions

373
docs/api.md Normal file
View File

@@ -0,0 +1,373 @@
---
title: API
description: REST and SSE API reference for Alchemist.
---
All API routes require the `alchemist_session` auth cookie
except:
- `/api/auth/*`
- `/api/health`
- `/api/ready`
- setup-mode exceptions: `/api/setup/*`, `/api/fs/*`,
`/api/settings/bundle`, `/api/system/hardware`
Authentication is established by `POST /api/auth/login`.
The backend also accepts `Authorization: Bearer <token>`,
but the web UI uses the session cookie.
## Authentication
### `POST /api/auth/login`
Request:
```json
{
"username": "admin",
"password": "secret"
}
```
Response:
```http
HTTP/1.1 200 OK
Set-Cookie: alchemist_session=...; HttpOnly; SameSite=Lax; Path=/; Max-Age=2592000
```
```json
{
"status": "ok"
}
```
### `POST /api/auth/logout`
Clears the session cookie and deletes the server-side
session if one exists.
```json
{
"status": "ok"
}
```
## Jobs
### `GET /api/jobs`
Canonical job listing endpoint. Supports query params such
as `limit`, `page`, `status`, `search`, `sort_by`,
`sort_desc`, and `archived`.
Example:
```bash
curl -b cookie.txt \
'http://localhost:3000/api/jobs?status=queued,failed&limit=50&page=1'
```
### `GET /api/jobs/:id/details`
Returns the job row, any available analyzed metadata,
encode stats for completed jobs, recent job logs, and a
failure summary for failed jobs.
Example response shape:
```json
{
"job": {
"id": 42,
"input_path": "/media/movies/example.mkv",
"status": "completed"
},
"metadata": {
"codec_name": "h264",
"width": 1920,
"height": 1080
},
"encode_stats": {
"input_size_bytes": 8011223344,
"output_size_bytes": 4112233445,
"compression_ratio": 1.95,
"encode_speed": 2.4,
"vmaf_score": 93.1
},
"job_logs": [],
"job_failure_summary": null
}
```
### `POST /api/jobs/:id/cancel`
Cancels a queued or active job if the current state allows
it.
### `POST /api/jobs/:id/restart`
Restarts a non-active job by sending it back to `queued`.
### `POST /api/jobs/:id/priority`
Request:
```json
{
"priority": 100
}
```
Response:
```json
{
"id": 42,
"priority": 100
}
```
### `POST /api/jobs/batch`
Supported `action` values: `cancel`, `restart`, `delete`.
```json
{
"action": "restart",
"ids": [41, 42, 43]
}
```
Response:
```json
{
"count": 3
}
```
### `POST /api/jobs/restart-failed`
Response:
```json
{
"count": 2,
"message": "Queued 2 failed or cancelled jobs for retry."
}
```
### `POST /api/jobs/clear-completed`
Archives completed jobs from the visible queue while
preserving historical encode stats.
```json
{
"count": 12,
"message": "Cleared 12 completed jobs from the queue. Historical stats were preserved."
}
```
## Engine
### `POST /api/engine/pause`
```json
{
"status": "paused"
}
```
### `POST /api/engine/resume`
```json
{
"status": "running"
}
```
### `POST /api/engine/drain`
```json
{
"status": "draining"
}
```
### `POST /api/engine/stop-drain`
```json
{
"status": "running"
}
```
### `GET /api/engine/status`
Response fields:
- `status`
- `mode`
- `concurrent_limit`
- `manual_paused`
- `scheduler_paused`
- `draining`
- `is_manual_override`
Example:
```json
{
"status": "paused",
"manual_paused": true,
"scheduler_paused": false,
"draining": false,
"mode": "balanced",
"concurrent_limit": 2,
"is_manual_override": false
}
```
### `GET /api/engine/mode`
Returns current mode, whether a manual override is active,
the current concurrent limit, CPU count, and computed mode
limits.
### `POST /api/engine/mode`
Request:
```json
{
"mode": "balanced",
"concurrent_jobs_override": 2,
"threads_override": 0
}
```
Response:
```json
{
"status": "ok",
"mode": "balanced",
"concurrent_limit": 2,
"is_manual_override": true
}
```
## Stats
### `GET /api/stats/aggregated`
```json
{
"total_input_bytes": 1234567890,
"total_output_bytes": 678901234,
"total_savings_bytes": 555666656,
"total_time_seconds": 81234.5,
"total_jobs": 87,
"avg_vmaf": 92.4
}
```
### `GET /api/stats/daily`
Returns the last 30 days of encode activity.
### `GET /api/stats/detailed`
Returns the most recent detailed encode stats rows.
### `GET /api/stats/savings`
Returns the storage-savings summary used by the statistics
dashboard.
## Settings
### `GET /api/settings/transcode`
Returns the transcode settings payload currently loaded by
the backend.
### `POST /api/settings/transcode`
Request:
```json
{
"concurrent_jobs": 2,
"size_reduction_threshold": 0.3,
"min_bpp_threshold": 0.1,
"min_file_size_mb": 50,
"output_codec": "av1",
"quality_profile": "balanced",
"threads": 0,
"allow_fallback": true,
"hdr_mode": "preserve",
"tonemap_algorithm": "hable",
"tonemap_peak": 100.0,
"tonemap_desat": 0.2,
"subtitle_mode": "copy",
"stream_rules": {
"strip_audio_by_title": ["commentary"],
"keep_audio_languages": ["eng"],
"keep_only_default_audio": false
}
}
```
## System
### `GET /api/system/hardware`
Returns the current detected hardware backend, supported
codecs, backends, and any detection notes.
### `GET /api/system/hardware/probe-log`
Returns the per-encoder probe log with success/failure
status and stderr excerpts.
### `GET /api/system/resources`
Returns live resource data:
- `cpu_percent`
- `memory_used_mb`
- `memory_total_mb`
- `memory_percent`
- `uptime_seconds`
- `active_jobs`
- `concurrent_limit`
- `cpu_count`
- `gpu_utilization`
- `gpu_memory_percent`
## Server-Sent Events
### `GET /api/events`
Internal event types are `JobStateChanged`, `Progress`,
`Decision`, and `Log`. The SSE stream exposed to clients
emits lower-case event names:
- `status`
- `progress`
- `decision`
- `log`
Additional config/system events may also appear, including
`config_updated`, `scan_started`, `scan_completed`,
`engine_status_changed`, and `hardware_state_changed`.
Example:
```text
event: progress
data: {"job_id":42,"percentage":61.4,"time":"00:11:32"}
```

106
docs/architecture.md Normal file
View File

@@ -0,0 +1,106 @@
---
title: Architecture
description: Internal pipeline, state machine, and source layout.
---
Alchemist is a single Rust application that serves the API,
embeds the frontend, runs the scan/plan/encode pipeline, and
persists state in SQLite.
## Pipeline
```text
Scanner
-> Agent
-> FfmpegAnalyzer
-> BasicPlanner
-> FfmpegExecutor
-> post-encode checks and promotion
-> database update
```
Practical flow:
1. `Scanner` finds files and enqueues jobs.
2. `Agent` in `src/media/processor.rs` claims queued jobs
and applies engine-state and concurrency rules.
3. `FfmpegAnalyzer` runs `ffprobe` with a 120-second timeout
and builds normalized media metadata.
4. `BasicPlanner` decides skip, remux, or transcode and
selects the best available encoder.
5. `FfmpegExecutor` runs FFmpeg.
6. Post-encode logic optionally runs VMAF, promotes the temp
output, records decisions and stats, and updates job state.
## Engine state machine
States:
- `Running`
- `Paused`
- `Draining`
- `SchedulerPaused`
Behavior:
- `Running`: jobs start up to the active concurrency limit
- `Paused`: no new jobs start; active jobs freeze
- `Draining`: active jobs finish; no new jobs start
- `SchedulerPaused`: pause state enforced by schedule windows
## Engine modes
| Mode | Formula |
|------|---------|
| Background | `1` |
| Balanced | `floor(cpu_count / 2)`, minimum `1`, maximum `4` |
| Throughput | `floor(cpu_count / 2)`, minimum `1`, uncapped |
Manual concurrency overrides can replace the computed limit
without changing the mode.
## Source layout
### `src/server/`
- `mod.rs`: `AppState`, router assembly, static asset serving
- `auth.rs`: login, logout, session cookies
- `jobs.rs`: queue endpoints, engine control, job details
- `scan.rs`: manual scan endpoints
- `settings.rs`: config and projection handlers
- `stats.rs`: stats and savings endpoints
- `system.rs`: health, readiness, resources, hardware, setup FS helpers
- `sse.rs`: SSE multiplexing
- `middleware.rs`: auth, security headers, rate limiting
- `wizard.rs`: first-run setup flow
### `src/media/`
- `pipeline.rs`: pipeline interfaces and plan types
- `planner.rs`: `BasicPlanner`, skip/remux/transcode decisions
- `analyzer.rs`: FFprobe wrapper with 120-second timeout
- `executor.rs`: FFmpeg execution path
- `processor.rs`: `Agent` loop and engine-state handling
- `scanner.rs`: filesystem scanning
- `health.rs`: Library Doctor checks
- `ffmpeg/`: encoder-specific FFmpeg builders
### Other core files
- `src/db.rs`: SQLite access layer and projections
- `src/config.rs`: TOML config structs, defaults, validation
- `src/orchestrator.rs`: FFmpeg subprocess control and cancellation
## Tech stack
| Layer | Technology |
|------|------------|
| Language | Rust 2024 |
| MSRV | Rust 1.85 |
| Async runtime | Tokio |
| HTTP | Axum 0.7 |
| Database | `sqlx` 0.8 |
| Storage | SQLite WAL |
| Frontend | Astro + React + TypeScript |
| Styling | Tailwind |
| Embedding | `rust-embed` |

139
docs/changelog.md Normal file
View File

@@ -0,0 +1,139 @@
---
title: Changelog
description: Release history for Alchemist.
---
## [0.3.0] - 2026-03-28
### Rust & Backend
- Upgraded from Rust 2021 to 2024, set the MSRV to 1.85, upgraded `sqlx` to `0.8` with `runtime-tokio-rustls`, and upgraded `rand` to `0.9`.
- Removed `async-trait`, adopted native `async fn` in traits, and added `trait-variant` so `ExecutionObserver` remained object-safe behind `Arc<dyn ...>`.
- Fixed the Apple VideoToolbox probe by using synthetic `lavfi` frames with `format=yuv420p` and `-allow_sw 1`, allowing HEVC and AV1 detection to succeed on Apple Silicon.
- Split `server.rs` into `src/server/` submodules for auth, jobs, scan, settings, stats, system, SSE, middleware, wizard, and tests.
- Moved `ffprobe` execution to `tokio::process::Command` with a 120-second timeout.
- Added poisoned cancellation lock recovery in the orchestrator and truncated oversized FFmpeg stderr lines before logging.
- Invalid notification event JSON now warned instead of silently disabling the target, and invalid schedule day JSON now warned instead of being treated as empty.
### UI & Frontend
- Reworked the app shell by removing page `h1` headers, replacing the old header block with a thin engine control strip, and dropping the sidebar icon container for wordmark-only branding.
- Flattened engine controls into one horizontal row with the status dot, Start/Pause/Stop actions, a divider, About, and Logout.
- Moved the Background, Balanced, and Throughput mode pills into Settings -> Runtime and wired them to the engine mode API.
- Rebuilt the setup wizard inside the main app shell with a grayed sidebar, removed the centered card layout, and replaced progress with a 2px solar line at the top of the content area.
- Setup wizard errors now surfaced as toast notifications instead of an inline error block below the content.
- Redesigned library selection around a flat recommendation list with Add buttons, selected-folder chips, and equal Browse/manual path entry; the preview panel was removed.
- Restructured the dashboard around a compact stat row, a savings summary, and a larger Recent Activity panel.
- Updated the log viewer to group entries by `job_id` into collapsible job sections while rendering system logs inline.
- Normalized job status badges to sentence case without letter spacing, aligned table column headers, and standardized the job detail modal radius and section headers.
- Cleaned banned CSS patterns from `ServerDirectoryPicker`, `ToastRegion`, and `SavingsOverview`, and modernized the login page with wordmark-only branding and tighter field styling.
- Darkened the global background gradient slightly, reduced scrollbar width to 6px, increased border radius one step across cards and buttons, and gave inputs more contrast against the page.
### CI/CD
- Added a nightly workflow that runs on every push to `main` after Rust checks pass, builds all platforms, publishes `ghcr.io/bybrooklyn/alchemist:nightly`, and replaces the previous nightly pre-release with `{VERSION}-nightly+{short-sha}`.
- Extracted a shared reusable `build.yml` workflow so nightly and release builds use the same pipeline.
- Simplified `docker.yml` to PR preview builds only, removed the `workflow_run` trigger, and removed the `main` branch trigger from `ci.yml`.
- Frontend validation in CI now runs the e2e reliability suite after the frontend check passes.
### Docs & Tooling
- Added `CLAUDE.md` and `GEMINI.md` as agent context files, added `CONTRIBUTING.md` with GPL-3.0 CLA terms, and started `CHANGELOG.md`.
- Hardened the `justfile` with safer process cleanup, a stronger `db-reset`, and `--frozen-lockfile` docs installation.
## [v0.2.10-rc.5] - 2026-03-22
### Runtime & Queue Control
- Engine runtime modes now support `background`, `balanced`, and `throughput`, with manual concurrency overrides and drain/resume controls exposed through the API and dashboard header.
- Engine status reporting now includes pause source, drain state, concurrent-limit metadata, and mode visibility for troubleshooting.
- Queue management continued to harden with safer active-job controls, clearer failure surfacing, and better per-job operational feedback.
### Media Processing & Library Health
- VAAPI-first Intel handling, remux planning, subtitle sidecars, and library health issue reporting were expanded across the planner, FFmpeg integration, and dashboard.
- Hardware detection and probe logging were improved to make CPU/GPU backend selection and diagnostics easier to understand.
- Stream rules landed for commentary stripping, audio-language filtering, and keeping only the default audio track when needed.
### Paths, Setup & Docs
- Default config and database paths were normalized around the `alchemist` runtime home, and the repo now ships a `justfile` for common dev, release, Docker, and database workflows.
- The docs site moved onto Starlight and now builds locally with a proper content config, localized collection wiring, and a corrected splash-page schema.
- Release automation now bumps repo-wide version manifests, supports checkpoint commits before release validation, and isolates web-e2e onto a separate port so a local server on `3000` does not block release verification.
### CI/CD & Release Tooling
- Docker publishing is now gated behind successful validation, and local release verification covers Rust checks/tests, frontend verification, docs build, `actionlint`, and the reliability Playwright suite.
- `just update` does not create the release commit, git tag, or push until the full validation gate passes.
## [v0.2.10-rc.2] - 2026-03-21
### Stability & Reliability
- VMAF quality gating: encodes falling below a configurable minimum score are now rejected rather than silently promoted.
- Exponential retry backoff for failed jobs: 5 / 15 / 60 / 360 minute delays based on attempt count prevent tight failure loops.
- Orphaned temp file cleanup on startup: interrupted encodes no longer leave `.alchemist.tmp` files on disk indefinitely.
- Log table pruning: configurable retention period (default 30 days) prevents unbounded log growth on busy servers.
- Auth session cleanup: expired sessions are pruned on startup and every 24 hours.
- Resource endpoint caching: `/api/system/resources` is cached for 500ms to prevent redundant OS probes from multiple open browser tabs.
### New Features
- Per-library profiles: each watch folder can have its own transcoding profile, with four built-in presets (Space Saver, Quality First, Balanced, Streaming) usable as starting points.
- Storage savings dashboard: the Stats page now shows total space recovered, average reduction percentage, a savings-over-time chart, and per-codec breakdowns.
- Library Doctor: scan your library for corrupt or broken files directly from System Settings.
- `/api/jobs` added as a canonical alias for `/api/jobs/table`.
### Beginner Experience
- Plain-English skip reasons: skipped jobs now show a human-readable explanation with technical detail available in an expandable section.
- Auto-redirect to the setup wizard for first-time users with no watch directories configured.
- Setup wizard field descriptions: CRF, BPP, concurrent jobs, and CPU preset now include plain-English explanations inline.
- Telemetry is now opt-in by default, with a detailed explanation of exactly what is collected.
### Job Management
- Skipped tab: dedicated tab in the Job Manager for skipped jobs.
- Archived tab: cleared completed jobs are now visible in an Archived tab rather than disappearing permanently.
- Sort controls: the job list can now be sorted by last updated, date added, file name, or file size.
### UI & Design
- Font updated from Space Grotesk to DM Sans.
- Sidebar active state redesigned: a left accent bar replaces the filled background.
- Border radius tightened throughout with a more consistent scale across cards, buttons, and badges.
- Setup wizard refactored into composable step components.
### Infrastructure
- CI/CD workflows fully rewritten: Rust caching, TypeScript typechecking, and a frontend build shared across all platforms.
- Multi-arch Docker images are now published for `linux/amd64` and `linux/arm64`.
- Release binaries now ship as `.tar.gz` archives with SHA256 checksums; AppImage and `.app` bundles were removed.
- Dockerfile now uses the stable Rust image with pinned FFmpeg checksums.
- E2E test coverage added for all new features.
## [v0.2.10-rc.1] - 2026-03-07
- Job lifecycle safety hardening: queued vs active cancel handling, active-job delete/restart blocking, batch-action conflict reporting, and stricter status/stat persistence.
- Output handling now supports mirrored `output_root` destinations plus temp-file promotion so replace mode preserves the last good artifact until encode, size, and quality gates pass.
- Scheduler, setup, and watch-folder parity updates shipped together: immediate schedule reevaluation, Intel Arc H.264 detection fix, H.264 setup option, canonicalized watch folders, and recursive watch configuration in the UI.
- Jobs and settings UX now expose per-job priority controls, output-root file settings, active-job-safe actions, and the Astro router deprecation cleanup.
- CI/CD rewrite for `0.2.10-rc.1`: cached Rust checks, frontend typecheck/build validation, multi-arch Docker publishing, and unified prerelease metadata handling across workflows.
- Release packaging cleanup: Linux and macOS now ship plain `.tar.gz` binaries, Windows ships `.exe`, and every release asset includes a SHA256 checksum file.
## [v0.2.9] - 2026-03-06
- Runtime reliability pass: watcher/scanner hardening, resilient event consumers, config reload improvements, and live hardware refresh.
- Admin UX refresh across dashboard, settings, setup, logs, jobs, charts, and system status with stronger error handling and feedback.
- Frontend workflow standardized on Bun, Playwright reliability coverage added under `web-e2e`, and deploy/docs/container updates shipped together.
## [v0.2.8] - 2026-01-12
- Setup wizard auth fixes, scheduler time validation, and watcher reliability improvements.
- DB stability pass (WAL, FK enforcement, indexes, session cleanup, legacy watch_dirs compatibility).
- Build pipeline updates (rustls for reqwest, cross-platform build script, WiX workflow fix).
- Documentation and design philosophy updates.
- More themes!!
## [v0.2.5] - 2026-01-11
### Fixes
- **Dashboard Crash**: Fixed a critical bug where the dashboard would render as a blank screen if GPU utilization was `null`. Added strict null checks before `toFixed()` calls in `ResourceMonitor.tsx`.
- **Animation Glitch**: Resolved an issue where the "Engine Status" button would fly in from the top-left corner on page navigation. Implemented unique `layoutId` generation using `useId()` to maintain the morph animation while preventing cross-page artifacts.
- **Migration Checksum**: Fixed a startup error caused by a modified migration file. Reverted the original migration to restore checksum integrity and created a new migration for the version bump.
### Improvements
- **Resource Monitor Layout**: Repositioned the GPU Usage section to appear between "Active Jobs" and "Uptime" for better logical flow.
- **Animation Timing**: Adjusted staggered animation delays in the Resource Monitor to match the new layout order.
### Documentation
- **Codebase Overview**: Added `codebase_overview.md` explaining the monolith architecture (Rust + API + Frontend) and directory structure.
- **Migration Policy**: Updated `MIGRATIONS.md` to explicitly forbid modifying existing migration files to prevent checksum errors.
- **Walkthrough**: Updated `walkthrough.md` with detailed debugging logs and verification steps for all recent changes.
### Infrastructure
- **Version Bump**: Updated project version to `0.2.5` in `Cargo.toml`, `web/package.json`, and `VERSION`.
- **Database**: Established `0.2.5` as the new minimum compatible version schema baseline.

85
docs/codecs.md Normal file
View File

@@ -0,0 +1,85 @@
---
title: Codecs
description: AV1, HEVC, H.264, and the BPP model Alchemist uses.
---
Alchemist targets three video codecs: AV1, HEVC, and H.264.
They are not interchangeable. Pick based on storage,
playback compatibility, and available hardware.
## Comparison
| Codec | File size | Quality efficiency | Compatibility | Encoding speed |
|------|-----------|--------------------|---------------|----------------|
| AV1 | Smallest | Best | Growing | Slowest |
| HEVC | Smaller | Very high | Good on modern devices | Medium |
| H.264 | Largest | Lowest of the three | Universal | Fastest |
## When to choose AV1
Use AV1 when saving the most space matters and your playback
devices are modern enough to handle it.
Hardware support:
- NVIDIA: RTX 30/40 class NVENC
- Intel: 12th gen+ for AV1 encode
- AMD: RDNA 2+ depending on driver/FFmpeg stack
- Apple: M3+
- CPU: always available through SVT-AV1
## When to choose HEVC
Use HEVC when you want most of AV1s storage benefit with
better playback compatibility across TVs, phones, and
set-top boxes.
Hardware support:
- NVIDIA: Maxwell+
- Intel: 6th gen+
- AMD: Polaris+
- Apple: M1+/T2 and newer
- CPU: x265
## When to choose H.264
Use H.264 when compatibility is the priority and storage
efficiency is secondary.
Hardware support:
- NVIDIA: broadly available
- Intel: broadly available
- AMD: broadly available
- Apple: broadly available
- CPU: x264
## Hardware summary by vendor
| Vendor | AV1 | HEVC | H.264 |
|--------|-----|------|-------|
| NVIDIA | RTX 30/40 | Maxwell+ | Yes |
| Intel | 12th gen+ | 6th gen+ | Yes |
| AMD | RDNA 2+ | Polaris+ | Yes |
| Apple | M3+ | M1+/T2 | Yes |
| CPU | Yes | Yes | Yes |
## BPP
BPP means bits per pixel. It measures how much video data is
being spent per rendered pixel and frame, which makes it
more useful than plain bitrate when you compare files across
different resolutions and frame rates.
Typical ranges:
- `> 0.15`: high quality, usually still worth evaluating
- `~0.10`: medium quality, often already efficient
- `< 0.05`: heavily compressed, likely to look blocky
Alchemist uses BPP because bitrate alone lies. A 4K file and
a 1080p file can share the same bitrate and look completely
different. BPP normalizes for resolution and frame rate, so
the planner can skip files that are already efficiently
compressed instead of re-encoding on guesswork.

View File

@@ -0,0 +1,149 @@
---
title: Configuration Reference
description: Full configuration reference for Alchemist.
---
Default config file location:
- Linux/macOS: `~/.config/alchemist/config.toml`
- Linux/macOS with XDG: `$XDG_CONFIG_HOME/alchemist/config.toml`
- Windows: `%APPDATA%\Alchemist\config.toml`
- Override: `ALCHEMIST_CONFIG_PATH`
## `[transcode]`
| Field | Type | Default | Description |
|------|------|---------|-------------|
| `size_reduction_threshold` | float | `0.3` | Minimum predicted size reduction required before a transcode is worth doing |
| `min_bpp_threshold` | float | `0.1` | Minimum bits-per-pixel threshold used by the planner to decide whether a file is already efficiently compressed |
| `min_file_size_mb` | int | `50` | Skip files smaller than this size |
| `concurrent_jobs` | int | `1` | Max jobs Alchemist may run at once before engine-mode overrides |
| `threads` | int | `0` | CPU thread count per job; `0` means automatic |
| `quality_profile` | string | `"balanced"` | Quality/speed tradeoff preset |
| `output_codec` | string | `"av1"` | Target codec: `av1`, `hevc`, or `h264` |
| `allow_fallback` | bool | `true` | Allow codec fallback when the requested codec is unavailable |
| `hdr_mode` | string | `"preserve"` | Preserve HDR metadata or tonemap to SDR |
| `tonemap_algorithm` | string | `"hable"` | HDR tonemapping algorithm |
| `tonemap_peak` | float | `100.0` | Tonemap peak luminance target |
| `tonemap_desat` | float | `0.2` | Tonemap desaturation factor |
| `subtitle_mode` | string | `"copy"` | Subtitle handling: `copy`, `burn`, `extract`, or `none` |
## `[transcode.stream_rules]`
| Field | Type | Default | Description |
|------|------|---------|-------------|
| `strip_audio_by_title` | list | `[]` | Remove audio tracks whose title contains any configured case-insensitive substring |
| `keep_audio_languages` | list | `[]` | Keep only audio tracks with matching ISO 639-2 language tags; untagged tracks are kept |
| `keep_only_default_audio` | bool | `false` | Keep only the default audio track after other filters run |
## `[hardware]`
| Field | Type | Default | Description |
|------|------|---------|-------------|
| `preferred_vendor` | string | `auto` | Pin hardware selection to `nvidia`, `intel`, `amd`, `apple`, or `cpu` |
| `device_path` | string | optional | Explicit render node such as `/dev/dri/renderD128` on Linux |
| `allow_cpu_fallback` | bool | `true` | Allow fallback to CPU when no supported GPU path succeeds |
| `cpu_preset` | string | `"medium"` | CPU encoder speed/quality preset |
| `allow_cpu_encoding` | bool | `true` | Allow software encoding at all |
## `[scanner]`
| Field | Type | Default | Description |
|------|------|---------|-------------|
| `directories` | list | `[]` | Library directories to scan |
| `watch_enabled` | bool | `false` | Enable realtime watch behavior for configured directories |
| `extra_watch_dirs` | list | `[]` | Extra watch objects with `path` and `is_recursive` |
## `[notifications]`
| Field | Type | Default | Description |
|------|------|---------|-------------|
| `enabled` | bool | `false` | Master switch for notifications |
| `targets` | list | `[]` | Notification target objects with `name`, `target_type`, `endpoint_url`, `auth_token`, `events`, and `enabled` |
## `[files]`
| Field | Type | Default | Description |
|------|------|---------|-------------|
| `delete_source` | bool | `false` | Delete the original file after a verified successful transcode |
| `output_extension` | string | `"mkv"` | Output file extension |
| `output_suffix` | string | `"-alchemist"` | Suffix added to the output filename |
| `replace_strategy` | string | `"keep"` | Replace behavior for output collisions |
| `output_root` | string | optional | Mirror outputs into another root path instead of writing beside the source |
## `[schedule]`
| Field | Type | Default | Description |
|------|------|---------|-------------|
| `windows` | list | `[]` | Time window objects; each window has `start_time`, `end_time`, and `days_of_week` |
`days_of_week` uses integers `0-6`. The config validator
requires at least one day in every window.
## `[quality]`
| Field | Type | Default | Description |
|------|------|---------|-------------|
| `enable_vmaf` | bool | `false` | Run VMAF scoring after encode |
| `min_vmaf_score` | float | `90.0` | Minimum acceptable VMAF score |
| `revert_on_low_quality` | bool | `true` | Revert the transcode if quality falls below the threshold |
## `[system]`
| Field | Type | Default | Description |
|------|------|---------|-------------|
| `monitoring_poll_interval` | float | `2.0` | Poll interval for system monitoring and dashboard resource refresh |
| `enable_telemetry` | bool | `false` | Opt-in anonymous telemetry switch |
| `log_retention_days` | int | `30` | Log retention period in days |
| `engine_mode` | string | `"balanced"` | Runtime engine mode: `background`, `balanced`, or `throughput` |
## Example
```toml
[transcode]
size_reduction_threshold = 0.3
min_bpp_threshold = 0.1
min_file_size_mb = 50
concurrent_jobs = 1
threads = 0
quality_profile = "balanced"
output_codec = "av1"
allow_fallback = true
hdr_mode = "preserve"
tonemap_algorithm = "hable"
tonemap_peak = 100.0
tonemap_desat = 0.2
subtitle_mode = "copy"
[transcode.stream_rules]
strip_audio_by_title = ["commentary", "description"]
keep_audio_languages = ["eng"]
keep_only_default_audio = false
[hardware]
preferred_vendor = "intel"
allow_cpu_fallback = true
cpu_preset = "medium"
allow_cpu_encoding = true
[scanner]
directories = ["/media/movies", "/media/tv"]
watch_enabled = true
[files]
delete_source = false
output_extension = "mkv"
output_suffix = "-alchemist"
replace_strategy = "keep"
[quality]
enable_vmaf = false
min_vmaf_score = 90.0
revert_on_low_quality = true
[system]
monitoring_poll_interval = 2.0
enable_telemetry = false
log_retention_days = 30
engine_mode = "balanced"
```

View File

@@ -0,0 +1,52 @@
---
title: Development Setup
description: Setting up a local Alchemist development environment.
---
## Prerequisites
- Rust 1.85+ (`rustup update stable`)
- [Bun](https://bun.sh/) — frontend package manager
- FFmpeg — required for local testing
- [just](https://github.com/casey/just) — task runner
Node.js is not required. Alchemist uses Bun for all
frontend tooling.
## Clone and run
```bash
git clone https://github.com/bybrooklyn/alchemist.git
cd alchemist
just dev # backend watch mode + frontend dev server
```
## Common tasks
```bash
just check # fmt + clippy + typecheck + build (mirrors CI)
just test # cargo test
just test-e2e # Playwright reliability suite
just db-reset # wipe dev DB, keep config
just db-reset-all # wipe DB and config (re-triggers wizard)
just bump 0.3.0 # bump version in all files
just update 0.3.0 # full guarded release flow
```
## Frontend only
```bash
cd web
bun install --frozen-lockfile
bun run dev # dev server on :4321
bun run typecheck # TypeScript check
bun run build # production build
```
## Backend only
```bash
cargo run # starts on :3000
cargo clippy -- -D warnings
cargo fmt
```

View File

@@ -0,0 +1,31 @@
---
title: Contributing Overview
description: How to report bugs, suggest features, and contribute to Alchemist.
---
Alchemist is GPLv3 open source. Contributions are welcome.
## Reporting bugs
1. Go to [GitHub Issues](https://github.com/bybrooklyn/alchemist/issues)
2. Search for existing reports first
3. Use the Bug Report template
4. Include your logs, OS, GPU vendor, and Alchemist version
## Suggesting features
Use the Feature Request template on
[GitHub Issues](https://github.com/bybrooklyn/alchemist/issues).
Explain why the feature is useful, not just what you want.
## License and CLA
By submitting a pull request or patch, you agree that your
contribution will be licensed under GPLv3 and you grant
Brooklyn Halmstad (the project owner) a perpetual,
irrevocable right to use, modify, and redistribute your
contribution as part of Alchemist.
You keep copyright to your individual contribution.
Low-effort pull requests will be closed without discussion.

189
docs/database-schema.md Normal file
View File

@@ -0,0 +1,189 @@
---
title: Database Schema
description: SQLite schema reference and migration policy.
---
Database location:
- Linux/macOS: `~/.config/alchemist/alchemist.db`
- Linux/macOS with XDG: `$XDG_CONFIG_HOME/alchemist/alchemist.db`
- Windows: `%APPDATA%\Alchemist\alchemist.db`
- Override: `ALCHEMIST_DB_PATH`
## `jobs`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `input_path` | TEXT | Unique source path |
| `output_path` | TEXT | Planned output path |
| `status` | TEXT | Current job state |
| `mtime_hash` | TEXT | File modification fingerprint |
| `priority` | INTEGER | Queue priority |
| `progress` | REAL | Progress percentage |
| `attempt_count` | INTEGER | Retry count |
| `created_at` | DATETIME | Creation timestamp |
| `updated_at` | DATETIME | Last update timestamp |
| `archived` | BOOLEAN | Archived flag for cleared completed jobs |
| `health_issues` | TEXT | Serialized health issues from Library Doctor |
| `last_health_check` | TEXT | Last library health check timestamp |
## `encode_stats`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `job_id` | INTEGER | Unique foreign key to `jobs.id` |
| `input_size_bytes` | INTEGER | Original size |
| `output_size_bytes` | INTEGER | Output size |
| `compression_ratio` | REAL | Compression ratio |
| `encode_time_seconds` | REAL | Total encode duration |
| `encode_speed` | REAL | Reported encode speed |
| `avg_bitrate_kbps` | REAL | Average output bitrate |
| `vmaf_score` | REAL | Optional VMAF score |
| `created_at` | DATETIME | Insert timestamp |
| `output_codec` | TEXT | Output codec recorded with the stats row |
## `decisions`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `job_id` | INTEGER | Foreign key to `jobs.id` |
| `action` | TEXT | Planner or post-encode action |
| `reason` | TEXT | Machine-readable reason string |
| `created_at` | DATETIME | Insert timestamp |
## `users`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `username` | TEXT | Unique login name |
| `password_hash` | TEXT | Argon2 password hash |
| `created_at` | DATETIME | Insert timestamp |
## `sessions`
| Column | Type | Description |
|--------|------|-------------|
| `token` | TEXT | Primary key session token |
| `user_id` | INTEGER | Foreign key to `users.id` |
| `expires_at` | DATETIME | Expiration timestamp |
| `created_at` | DATETIME | Insert timestamp |
## `logs`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `level` | TEXT | Log level |
| `job_id` | INTEGER | Optional job association |
| `message` | TEXT | Log message |
| `created_at` | DATETIME | Insert timestamp |
## `ui_preferences`
| Column | Type | Description |
|--------|------|-------------|
| `key` | TEXT | Primary key |
| `value` | TEXT | Stored preference value |
| `updated_at` | DATETIME | Last update timestamp |
## `watch_dirs`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `path` | TEXT | Unique watched path |
| `enabled` | INTEGER | Enabled flag from the legacy watch-dir projection |
| `recursive` | INTEGER | Recursive watch flag |
| `extensions` | TEXT | Optional serialized extension filter list |
| `created_at` | DATETIME | Insert timestamp |
| `profile_id` | INTEGER | Optional foreign key to `library_profiles.id` |
## `notification_targets`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `name` | TEXT | Target name |
| `target_type` | TEXT | `gotify`, `discord`, or `webhook` |
| `endpoint_url` | TEXT | Destination URL |
| `auth_token` | TEXT | Optional auth token |
| `events` | TEXT | Serialized event list |
| `enabled` | BOOLEAN | Enabled flag |
| `created_at` | DATETIME | Insert timestamp |
## `schedule_windows`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `start_time` | TEXT | Window start time |
| `end_time` | TEXT | Window end time |
| `days_of_week` | TEXT | Serialized day list |
| `enabled` | BOOLEAN | Enabled flag |
## `file_settings`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Singleton row key (`1`) |
| `delete_source` | BOOLEAN | Delete original after success |
| `output_extension` | TEXT | Output extension |
| `output_suffix` | TEXT | Filename suffix |
| `replace_strategy` | TEXT | Collision policy |
| `output_root` | TEXT | Optional mirrored output root |
## `library_profiles`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `name` | TEXT | Profile name |
| `preset` | TEXT | Preset identifier |
| `codec` | TEXT | Output codec |
| `quality_profile` | TEXT | Quality preset |
| `hdr_mode` | TEXT | HDR behavior |
| `audio_mode` | TEXT | Audio policy |
| `crf_override` | INTEGER | Optional CRF override |
| `notes` | TEXT | Optional notes |
| `created_at` | TEXT | Insert timestamp |
| `updated_at` | TEXT | Last update timestamp |
## `health_scan_runs`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `started_at` | TEXT | Scan start timestamp |
| `completed_at` | TEXT | Scan completion timestamp |
| `files_checked` | INTEGER | Files examined in the run |
| `issues_found` | INTEGER | Issues found in the run |
## `schema_info`
| Column | Type | Description |
|--------|------|-------------|
| `key` | TEXT | Primary key |
| `value` | TEXT | Version or compatibility value |
Common keys include `schema_version` and
`min_compatible_version`.
## Migration policy
Compatibility baseline: `v0.2.5`.
Migration rules:
- `CREATE TABLE IF NOT EXISTS`
- `ALTER TABLE ... ADD COLUMN` only with `NULL` allowed or a `DEFAULT`
- `CREATE INDEX IF NOT EXISTS`
- Never remove columns
- Never rename columns
- Never change column types
The policy is additive only. Existing migration files are
immutable.

67
docs/docker.md Normal file
View File

@@ -0,0 +1,67 @@
---
title: Docker
description: Docker Compose configuration, volumes, environment variables, and updates.
---
## Recommended Compose file
```yaml
services:
alchemist:
image: ghcr.io/bybrooklyn/alchemist:latest
container_name: alchemist
ports:
- "3000:3000"
volumes:
- /path/to/config:/app/config
- /path/to/data:/app/data
- /path/to/media:/media
- /tmp/alchemist:/tmp # optional: fast SSD for temp files
environment:
- ALCHEMIST_CONFIG_PATH=/app/config/config.toml
- ALCHEMIST_DB_PATH=/app/data/alchemist.db
restart: unless-stopped
```
## Volumes
| Mount | Purpose |
|-------|---------|
| `/app/config` | `config.toml` — persists across restarts |
| `/app/data` | `alchemist.db` (SQLite) — persists across restarts |
| `/media` | Your media library — mount read-write |
| `/tmp` (optional) | Temp dir for in-progress encodes — use a fast SSD |
## Environment variables
| Variable | Description |
|----------|-------------|
| `ALCHEMIST_CONFIG_PATH` | Path to `config.toml` inside the container |
| `ALCHEMIST_DB_PATH` | Path to the SQLite database inside the container |
| `ALCHEMIST_CONFIG_MUTABLE` | Set `false` to block runtime config writes |
| `RUST_LOG` | Log verbosity: `info`, `debug`, `alchemist=trace` |
Alchemist does not use `PUID`/`PGID`. Handle permissions
at the host level.
## Hardware acceleration
See [GPU Passthrough](/gpu-passthrough) for vendor-specific
Docker configuration.
## Updating
```bash
docker compose pull && docker compose up -d
```
Migrations run automatically on startup. Config and database
are preserved in mounted volumes.
## Nightly builds
```yaml
image: ghcr.io/bybrooklyn/alchemist:nightly
```
Published on every push to `main` that passes Rust checks.

37
docs/engine-modes.md Normal file
View File

@@ -0,0 +1,37 @@
---
title: Engine Modes
description: Background, Balanced, and Throughput — what they mean and when to use each.
---
Engine modes set the concurrent job limit.
## Modes
| Mode | Concurrent jobs | Use when |
|------|----------------|----------|
| Background | 1 | Server in active use |
| Balanced (default) | floor(cpu_count / 2), min 1, max 4 | General shared server |
| Throughput | floor(cpu_count / 2), min 1, no cap | Dedicated server, clear a backlog |
## Manual override
Override the computed limit in **Settings → Runtime**. Takes
effect immediately. A "manual" badge appears in engine
status. Switching modes clears the override.
## States vs. modes
Modes determine *how many* jobs run. States determine
*whether* they run.
| State | Behavior |
|-------|----------|
| Running | Jobs start up to the mode's limit |
| Paused | No jobs start; active jobs freeze |
| Draining | Active jobs finish; no new jobs start |
| Scheduler paused | Paused by a schedule window |
## Changing modes
**Settings → Runtime**. Takes effect immediately; in-progress
jobs are not cancelled.

View File

@@ -0,0 +1,28 @@
---
title: Environment Variables
description: All environment variables Alchemist reads at startup.
---
| Variable | Default | Description |
|----------|---------|-------------|
| `ALCHEMIST_CONFIG_PATH` | `~/.config/alchemist/config.toml` | Path to config file |
| `ALCHEMIST_CONFIG` | (alias) | Alias for `ALCHEMIST_CONFIG_PATH` |
| `ALCHEMIST_DB_PATH` | `~/.config/alchemist/alchemist.db` | Path to SQLite database |
| `ALCHEMIST_DATA_DIR` | (none) | Sets data dir; `alchemist.db` placed here |
| `ALCHEMIST_CONFIG_MUTABLE` | `true` | Set `false` to block runtime config writes |
| `RUST_LOG` | `info` | Log level: `info`, `debug`, `alchemist=trace` |
Default paths: XDG on Linux/macOS, `%APPDATA%\Alchemist\`
on Windows.
## Docker
Always set path variables explicitly to paths inside your
mounted volumes. Without this, files are lost when the
container is removed.
```yaml
environment:
- ALCHEMIST_CONFIG_PATH=/app/config/config.toml
- ALCHEMIST_DB_PATH=/app/data/alchemist.db
```

76
docs/faq.md Normal file
View File

@@ -0,0 +1,76 @@
---
title: FAQ
description: Common questions from self-hosters running Alchemist.
---
## 1. Is Alchemist free?
Yes. Alchemist is GPLv3 open source.
## 2. Will it ruin my quality?
No by default. The planner uses BPP analysis and optional
VMAF gating to avoid pointless or low-quality transcodes.
## 3. How much space will I save?
Typical results are 3070%, depending on source quality,
codec choice, and how much waste is in the original file.
## 4. Do I need a GPU?
No. CPU encoding works. GPU encoding is much faster and more
power-efficient.
## 5. Does it work with Plex, Jellyfin, or Emby?
Yes. Point Alchemist at the same media directories your
server already uses.
## 6. What happens to my originals?
By default they stay in place. Nothing is deleted unless you
enable `delete_source`.
## 7. Does it support 4K and HDR?
Yes. It can preserve HDR metadata or tonemap to SDR.
## 8. Can I run multiple instances?
Not against the same SQLite database. Use one Alchemist
instance per DB.
## 9. What is BPP?
Bits-per-pixel. It is the compression density measurement
Alchemist uses to decide whether a file is already efficient.
## 10. What is VMAF?
Netflixs perceptual quality metric. It is optional and
slows down post-encode validation.
## 11. How do I update?
```bash
docker compose pull && docker compose up -d
```
## 12. What if hardware is not detected?
CPU fallback is automatic unless you disabled it. Check the
hardware probe log to fix the GPU path.
## 13. Can I control when it runs?
Yes. Use **Settings → Schedule** to define allowed windows.
## 14. Does it handle subtitles?
Yes. Subtitle mode supports copy, burn, extract, and drop.
## 15. Why did it skip my file?
Open the skipped job and read the reason, or start with
[Skip Decisions](/skip-decisions).

51
docs/first-run.md Normal file
View File

@@ -0,0 +1,51 @@
---
title: First Run & Setup Wizard
description: Getting through the setup wizard and starting your first scan.
---
When you first open Alchemist at `http://localhost:3000`
the setup wizard runs automatically. It takes about two
minutes.
## Wizard steps
**1. Admin account** — Set a username and password. Telemetry
is opt-in and off by default.
**2. Library selection** — Add the server folders Alchemist
should scan. In Docker these are the container-side paths
(right side of your volume mount). If you mounted
`/mnt/media` as `/media`, enter `/media` here.
Alchemist auto-discovers likely media roots and shows them
as suggestions. Add any path manually or browse the server
filesystem.
**3. Processing settings** — Target codec (AV1 default),
quality profile, output rules. Defaults are sensible.
Everything is changeable later.
**4. Hardware, notifications & schedule** — GPU is detected
automatically. You can pin a vendor, configure Discord or
webhook notifications, and restrict encoding to schedule
windows.
**5. Review & complete** — Summary of all choices. Click
**Complete Setup** to write the config and start the first
library scan.
## After setup
The engine starts **paused** after setup. Click **Start**
in the header bar to begin processing.
The initial scan runs automatically in the background. Watch
files enter the queue in the **Jobs** tab.
## Resetting
To fully reset and re-run the wizard:
```bash
just db-reset-all
```

214
docs/gpu-passthrough.md Normal file
View File

@@ -0,0 +1,214 @@
---
title: GPU Passthrough
description: Docker GPU passthrough for NVIDIA, Intel, and AMD.
---
Use `ghcr.io/bybrooklyn/alchemist:latest`. Alchemist does
not use `PUID` or `PGID`. Handle device permissions with
Docker device mappings and host groups.
## NVIDIA
Install `nvidia-container-toolkit` on the host before you
start the container.
### Host setup
```bash
distribution=$(. /etc/os-release; echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L "https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list" | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt update
sudo apt install -y nvidia-container-toolkit
sudo systemctl restart docker
```
### Docker Compose
```yaml
services:
alchemist:
image: ghcr.io/bybrooklyn/alchemist:latest
ports:
- "3000:3000"
volumes:
- /path/to/config:/app/config
- /path/to/data:/app/data
- /path/to/media:/media
environment:
- ALCHEMIST_CONFIG_PATH=/app/config/config.toml
- ALCHEMIST_DB_PATH=/app/data/alchemist.db
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
restart: unless-stopped
```
### docker run
```bash
docker run -d \
--name alchemist \
--gpus all \
-p 3000:3000 \
-v /path/to/config:/app/config \
-v /path/to/data:/app/data \
-v /path/to/media:/media \
-e ALCHEMIST_CONFIG_PATH=/app/config/config.toml \
-e ALCHEMIST_DB_PATH=/app/data/alchemist.db \
--restart unless-stopped \
ghcr.io/bybrooklyn/alchemist:latest
```
### Verify
On the host:
```bash
nvidia-smi
```
In the container:
```bash
ffmpeg -encoders | grep nvenc
```
You should see `h264_nvenc`, `hevc_nvenc`, and on supported
cards `av1_nvenc`.
## Intel
Intel passthrough on Linux uses `/dev/dri`. Pass the device
into the container and add the `video` and `render` groups.
For modern Intel iGPUs, set `LIBVA_DRIVER_NAME=iHD`.
### Docker Compose
```yaml
services:
alchemist:
image: ghcr.io/bybrooklyn/alchemist:latest
ports:
- "3000:3000"
volumes:
- /path/to/config:/app/config
- /path/to/data:/app/data
- /path/to/media:/media
devices:
- /dev/dri:/dev/dri
group_add:
- video
- render
environment:
- ALCHEMIST_CONFIG_PATH=/app/config/config.toml
- ALCHEMIST_DB_PATH=/app/data/alchemist.db
- LIBVA_DRIVER_NAME=iHD
restart: unless-stopped
```
### docker run
```bash
docker run -d \
--name alchemist \
--device /dev/dri:/dev/dri \
--group-add video \
--group-add render \
-p 3000:3000 \
-v /path/to/config:/app/config \
-v /path/to/data:/app/data \
-v /path/to/media:/media \
-e ALCHEMIST_CONFIG_PATH=/app/config/config.toml \
-e ALCHEMIST_DB_PATH=/app/data/alchemist.db \
-e LIBVA_DRIVER_NAME=iHD \
--restart unless-stopped \
ghcr.io/bybrooklyn/alchemist:latest
```
### Verify
Inside the container:
```bash
vainfo --display drm --device /dev/dri/renderD128
```
If the device is exposed correctly, `vainfo` reports the
Intel VAAPI driver and supported profiles. Alchemist tries
VAAPI first for Intel, then QSV as fallback.
## AMD
AMD on Linux uses the same `/dev/dri` passthrough model as
Intel, but the VAAPI driver should be `radeonsi`.
### Docker Compose
```yaml
services:
alchemist:
image: ghcr.io/bybrooklyn/alchemist:latest
ports:
- "3000:3000"
volumes:
- /path/to/config:/app/config
- /path/to/data:/app/data
- /path/to/media:/media
devices:
- /dev/dri:/dev/dri
group_add:
- video
- render
environment:
- ALCHEMIST_CONFIG_PATH=/app/config/config.toml
- ALCHEMIST_DB_PATH=/app/data/alchemist.db
- LIBVA_DRIVER_NAME=radeonsi
restart: unless-stopped
```
### docker run
```bash
docker run -d \
--name alchemist \
--device /dev/dri:/dev/dri \
--group-add video \
--group-add render \
-p 3000:3000 \
-v /path/to/config:/app/config \
-v /path/to/data:/app/data \
-v /path/to/media:/media \
-e ALCHEMIST_CONFIG_PATH=/app/config/config.toml \
-e ALCHEMIST_DB_PATH=/app/data/alchemist.db \
-e LIBVA_DRIVER_NAME=radeonsi \
--restart unless-stopped \
ghcr.io/bybrooklyn/alchemist:latest
```
### Verify
Inside the container:
```bash
vainfo --display drm --device /dev/dri/renderD128
```
On Windows, AMD uses AMF. No device passthrough is required.
If NVIDIA and Intel are absent, Alchemist uses AMF
automatically when FFmpeg exposes the AMF encoders.
## Troubleshooting
| Problem | Cause | Fix |
|--------|-------|-----|
| Permission denied on `/dev/dri` | Container can see the device but lacks group access | Add `group_add: [video, render]` or the equivalent `--group-add` flags |
| No encoder found | FFmpeg in the container does not expose the backend | Check `ffmpeg -encoders` inside the container; confirm the host driver/toolkit is installed |
| CPU fallback despite GPU | Device passthrough or toolkit missing, or probe failed | Check **Settings → Hardware → Probe Log**, verify `/dev/dri` or NVIDIA toolkit, then restart the container |

40
docs/hardware.md Normal file
View File

@@ -0,0 +1,40 @@
---
title: Hardware Acceleration
description: GPU detection, vendor selection, and fallback behavior.
---
Alchemist detects hardware automatically at startup and
selects the best available encoder. Override in
**Settings → Hardware**.
## Detection order (auto mode)
1. Apple VideoToolbox (macOS only)
2. NVIDIA NVENC (checks `/dev/nvidiactl`)
3. Intel VAAPI, then QSV fallback (checks `/dev/dri/renderD128`)
4. AMD VAAPI (Linux) or AMF (Windows)
5. CPU fallback (SVT-AV1, x265, x264)
## Encoder support by vendor
| Vendor | AV1 | HEVC | H.264 | Notes |
|--------|-----|------|-------|-------|
| NVIDIA NVENC | RTX 30/40 | Maxwell+ | All | Best for speed |
| Intel QSV | 12th gen+ | 6th gen+ | All | Best for power efficiency |
| AMD VAAPI/AMF | RDNA 2+ | Polaris+ | All | Linux VAAPI / Windows AMF |
| Apple VideoToolbox | M3+ | M1+/T2 | All | Binary install recommended |
| CPU | All | All | All | Always available |
## Hardware probe
Alchemist probes each encoder at startup with a test encode.
See results in **Settings → Hardware → Probe Log**. Probe
failures include the FFmpeg stderr explaining why.
## Vendor-specific guides
- [NVIDIA (NVENC)](/hardware/nvidia)
- [Intel (QSV / VAAPI)](/hardware/intel)
- [AMD (VAAPI / AMF)](/hardware/amd)
- [Apple (VideoToolbox)](/hardware/apple)
- [CPU Encoding](/hardware/cpu)

104
docs/hardware/amd.md Normal file
View File

@@ -0,0 +1,104 @@
---
title: AMD VAAPI / AMF
description: AMD GPU setup for Linux VAAPI and Windows AMF.
---
AMD uses VAAPI on Linux and AMF on Windows. Set
**Settings → Hardware → Preferred Vendor** to `amd` if you
want to pin it instead of using auto detection.
## Supported hardware
| Codec | Support |
|------|---------|
| H.264 | Polaris+ |
| HEVC | Polaris+ |
| AV1 | RDNA 2+ |
## Linux
### Host setup
Install the Mesa VAAPI drivers and verification tool:
```bash
sudo apt install mesa-va-drivers vainfo
```
Verify the render node on the host:
```bash
vainfo --display drm --device /dev/dri/renderD128
```
### Docker
Pass `/dev/dri` into the container and set the AMD VAAPI
driver name to `radeonsi`.
```yaml
services:
alchemist:
image: ghcr.io/bybrooklyn/alchemist:latest
ports:
- "3000:3000"
volumes:
- /path/to/config:/app/config
- /path/to/data:/app/data
- /path/to/media:/media
devices:
- /dev/dri:/dev/dri
group_add:
- video
- render
environment:
- ALCHEMIST_CONFIG_PATH=/app/config/config.toml
- ALCHEMIST_DB_PATH=/app/data/alchemist.db
- LIBVA_DRIVER_NAME=radeonsi
restart: unless-stopped
```
`docker run` equivalent:
```bash
docker run -d \
--name alchemist \
--device /dev/dri:/dev/dri \
--group-add video \
--group-add render \
-p 3000:3000 \
-v /path/to/config:/app/config \
-v /path/to/data:/app/data \
-v /path/to/media:/media \
-e ALCHEMIST_CONFIG_PATH=/app/config/config.toml \
-e ALCHEMIST_DB_PATH=/app/data/alchemist.db \
-e LIBVA_DRIVER_NAME=radeonsi \
--restart unless-stopped \
ghcr.io/bybrooklyn/alchemist:latest
```
### Verify
```bash
vainfo --display drm --device /dev/dri/renderD128
ffmpeg -encoders | grep vaapi
```
## Windows
Windows AMD support uses AMF. No device passthrough is
required. Install current AMD graphics drivers and confirm
FFmpeg exposes the AMF encoders:
```powershell
ffmpeg -encoders | findstr amf
```
If NVIDIA and Intel are absent, Alchemist uses AMF
automatically when AMF probing succeeds.
## In Alchemist
Set **Settings → Hardware → Preferred Vendor → amd**.
On Linux, only set **Device Path** if you need to force a
specific render node.

56
docs/hardware/apple.md Normal file
View File

@@ -0,0 +1,56 @@
---
title: Apple VideoToolbox
description: Apple VideoToolbox setup on macOS.
---
Apple VideoToolbox is the native macOS hardware encode path.
Binary installs are strongly recommended. Docker on macOS
has limited VideoToolbox access and is not the reliable path
for production encoding.
## Supported hardware
| Hardware | H.264 | HEVC | AV1 | Notes |
|---------|-------|------|-----|------|
| Intel + T2 | Yes | Yes | No | HEVC depends on T2-capable hardware |
| M1 / M2 | Yes | Yes | No | Native media engines |
| M3+ | Yes | Yes | Yes | AV1 encode support |
## Install path
Use the Alchemist macOS binary plus a Homebrew FFmpeg build:
```bash
brew install ffmpeg
ffmpeg -encoders | grep videotoolbox
```
Expected encoders include:
- `h264_videotoolbox`
- `hevc_videotoolbox`
- `av1_videotoolbox` on M3+
## Critical probe note
VideoToolbox fails with error `-12908` if you probe it with
a synthetic `lavfi` frame without `-allow_sw 1` and
`-vf format=yuv420p`. Alchemist `0.3.0` includes that fix in
hardware detection automatically.
If you want to verify the probe manually, use this exact
command:
```bash
ffmpeg -f lavfi -i color=c=black:s=64x64:d=0.1 \
-vf format=yuv420p \
-c:v hevc_videotoolbox \
-allow_sw 1 \
-frames:v 1 -f null -
```
## In Alchemist
Set **Settings → Hardware → Preferred Vendor → apple**.
Do not set a device path. VideoToolbox is not exposed as a
Linux-style render node.

60
docs/hardware/cpu.md Normal file
View File

@@ -0,0 +1,60 @@
---
title: CPU Encoding
description: Software encoding with SVT-AV1, x265, and x264.
---
CPU encoding is the fallback path when no supported GPU is
available, and it is also the right choice when you want the
best software quality and do not care about throughput.
## Encoders
| Codec | Encoder |
|------|---------|
| AV1 | SVT-AV1 |
| HEVC | x265 |
| H.264 | x264 |
## Presets
`cpu_preset` controls the software speed/quality tradeoff.
| Preset | Effect |
|-------|--------|
| `slow` | Best compression, lowest throughput |
| `medium` | Balanced default |
| `fast` | Lower CPU time, larger output |
## Thread configuration
`threads = 0` means automatic. Alchemist lets FFmpeg choose
the thread count per job. Set a manual value only if you are
tuning around a busy shared server or a known NUMA/core
layout.
## Performance expectations
These are reasonable 1080p expectations on modern CPUs:
| Codec | Preset | Expected speed |
|------|--------|----------------|
| AV1 | `medium` | ~0.51.5x realtime |
| HEVC | `medium` | ~13x realtime |
| H.264 | `medium` | ~38x realtime |
## When to use CPU
- No supported GPU is present
- Maximum software quality matters more than speed
- The batch is small enough that wall-clock time does not matter
## Thread allocation
| CPU cores | Suggested starting point |
|----------|--------------------------|
| 4 | 1 job, auto threads |
| 8 | 1 job, auto threads or 2 jobs with care |
| 16 | 2 jobs, auto threads |
| 32+ | 2-4 jobs, benchmark before going wider |
Use **Settings → Hardware** to allow or disable CPU encoding.

104
docs/hardware/intel.md Normal file
View File

@@ -0,0 +1,104 @@
---
title: Intel QSV / VAAPI
description: Intel iGPU setup for VAAPI and QSV.
---
Alchemist tries Intel VAAPI first and QSV second. That
matches current Intel Linux reality: Intel Arc uses VAAPI
through the `i915` or `xe` driver, not QSV. QSV remains a
fallback path for older Intel hardware and FFmpeg setups.
## Supported generations
| Generation | H.264 | HEVC | AV1 | Notes |
|-----------|-------|------|-----|------|
| Intel iGPU, all supported generations | Yes | 6th gen+ | 12th gen+ | Alchemist prefers VAAPI, then QSV |
## Critical note for Intel Arc
Intel Arc uses VAAPI via the Linux DRM stack. Do not force
QSV for Arc unless you have a specific reason and have
verified it works in your FFmpeg build. The expected path is
VAAPI first.
## Host setup
Install the Intel VAAPI driver and verification tools.
```bash
sudo apt install intel-media-va-driver-non-free vainfo
```
Verify the render node directly on the host:
```bash
vainfo --display drm --device /dev/dri/renderD128
```
For newer systems, `renderD129` may be the Intel node. Check
`ls -l /dev/dri` if `renderD128` is not Intel.
## Docker
Pass `/dev/dri` into the container and add the `video` and
`render` groups. Set `LIBVA_DRIVER_NAME=iHD` for modern
Intel media drivers.
### Docker Compose
```yaml
services:
alchemist:
image: ghcr.io/bybrooklyn/alchemist:latest
ports:
- "3000:3000"
volumes:
- /path/to/config:/app/config
- /path/to/data:/app/data
- /path/to/media:/media
devices:
- /dev/dri:/dev/dri
group_add:
- video
- render
environment:
- ALCHEMIST_CONFIG_PATH=/app/config/config.toml
- ALCHEMIST_DB_PATH=/app/data/alchemist.db
- LIBVA_DRIVER_NAME=iHD
restart: unless-stopped
```
### docker run
```bash
docker run -d \
--name alchemist \
--device /dev/dri:/dev/dri \
--group-add video \
--group-add render \
-p 3000:3000 \
-v /path/to/config:/app/config \
-v /path/to/data:/app/data \
-v /path/to/media:/media \
-e ALCHEMIST_CONFIG_PATH=/app/config/config.toml \
-e ALCHEMIST_DB_PATH=/app/data/alchemist.db \
-e LIBVA_DRIVER_NAME=iHD \
--restart unless-stopped \
ghcr.io/bybrooklyn/alchemist:latest
```
## Verify inside the container
```bash
vainfo --display drm --device /dev/dri/renderD128
ffmpeg -encoders | grep -E 'vaapi|qsv'
```
If VAAPI is healthy, Alchemist should probe `av1_vaapi`,
`hevc_vaapi`, and `h264_vaapi` first, then the QSV encoders.
## In Alchemist
Set **Settings → Hardware → Preferred Vendor → intel**.
Only set **Device Path** if auto detection chooses the wrong
render node on a multi-GPU Linux host.

108
docs/hardware/nvidia.md Normal file
View File

@@ -0,0 +1,108 @@
---
title: NVIDIA NVENC
description: NVIDIA NVENC setup for Docker and binary installs.
---
Alchemist uses NVENC when NVIDIA is available and selected.
Set **Settings → Hardware → Preferred Vendor** to `nvidia`
if you want to pin it instead of using auto detection.
## Supported generations
| Generation | Example cards | H.264 | HEVC | AV1 | Notes |
|-----------|---------------|-------|------|-----|------|
| Pascal | GTX 10-series | Yes | Yes | No | 2 concurrent encode streams on consumer cards |
| Turing | GTX 16 / RTX 20 | Yes | Yes | No | Better quality than Pascal |
| Ampere | RTX 30 | Yes | Yes | Yes | First NVENC generation with AV1 |
| Ada Lovelace | RTX 40 | Yes | Yes | Yes | Dual AV1 encoders on supported SKUs |
## Docker
Install `nvidia-container-toolkit` on the host first.
```bash
distribution=$(. /etc/os-release; echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L "https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list" | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt update
sudo apt install -y nvidia-container-toolkit
sudo systemctl restart docker
```
### Docker Compose
```yaml
services:
alchemist:
image: ghcr.io/bybrooklyn/alchemist:latest
ports:
- "3000:3000"
volumes:
- /path/to/config:/app/config
- /path/to/data:/app/data
- /path/to/media:/media
environment:
- ALCHEMIST_CONFIG_PATH=/app/config/config.toml
- ALCHEMIST_DB_PATH=/app/data/alchemist.db
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
restart: unless-stopped
```
### docker run
```bash
docker run -d \
--name alchemist \
--gpus all \
-p 3000:3000 \
-v /path/to/config:/app/config \
-v /path/to/data:/app/data \
-v /path/to/media:/media \
-e ALCHEMIST_CONFIG_PATH=/app/config/config.toml \
-e ALCHEMIST_DB_PATH=/app/data/alchemist.db \
--restart unless-stopped \
ghcr.io/bybrooklyn/alchemist:latest
```
## Binary installs
Verify the driver first:
```bash
nvidia-smi
```
Then verify FFmpeg exposes NVENC:
```bash
ffmpeg -encoders | grep nvenc
```
Expected encoders:
- `h264_nvenc`
- `hevc_nvenc`
- `av1_nvenc` on RTX 30/40 class hardware
## In Alchemist
Set **Settings → Hardware → Preferred Vendor → nvidia**.
Leave **Device Path** empty. NVENC is detected from the
driver and `/dev/nvidiactl`.
## Troubleshooting
| Problem | Cause | Fix |
|--------|-------|-----|
| No encoder found | FFmpeg lacks NVENC or the driver is missing | Run `ffmpeg -encoders | grep nvenc` and `nvidia-smi`; update driver or container runtime |
| Container cannot see GPU | NVIDIA runtime/toolkit not installed or `--gpus all` missing | Reinstall `nvidia-container-toolkit`, restart Docker, and test with `docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi` |
| Driver version mismatch | Host driver and container runtime stack disagree | Update the NVIDIA driver and toolkit together, then restart Docker |

95
docs/installation.md Normal file
View File

@@ -0,0 +1,95 @@
---
title: Installation
description: Install Alchemist via Docker, binary, or from source.
---
Alchemist ships as a single binary with the web UI embedded.
The Docker image bundles FFmpeg — nothing else to install.
## Docker (recommended)
**Docker Compose:**
```yaml
services:
alchemist:
image: ghcr.io/bybrooklyn/alchemist:latest
container_name: alchemist
ports:
- "3000:3000"
volumes:
- /path/to/config:/app/config
- /path/to/data:/app/data
- /path/to/media:/media
environment:
- ALCHEMIST_CONFIG_PATH=/app/config/config.toml
- ALCHEMIST_DB_PATH=/app/data/alchemist.db
restart: unless-stopped
```
```bash
docker compose up -d
```
Open [http://localhost:3000](http://localhost:3000). The
setup wizard runs on first visit.
For GPU passthrough (NVIDIA, Intel, AMD) see
[GPU Passthrough](/gpu-passthrough).
**docker run:**
```bash
docker run -d \
--name alchemist \
-p 3000:3000 \
-v /path/to/config:/app/config \
-v /path/to/data:/app/data \
-v /path/to/media:/media \
-e ALCHEMIST_CONFIG_PATH=/app/config/config.toml \
-e ALCHEMIST_DB_PATH=/app/data/alchemist.db \
--restart unless-stopped \
ghcr.io/bybrooklyn/alchemist:latest
```
## Binary
Download from [GitHub Releases](https://github.com/bybrooklyn/alchemist/releases).
Available for Linux x86_64, Linux ARM64, Windows x86_64,
macOS Apple Silicon, and macOS Intel.
FFmpeg must be installed separately:
```bash
sudo apt install ffmpeg # Debian / Ubuntu
sudo dnf install ffmpeg # Fedora
sudo pacman -S ffmpeg # Arch
brew install ffmpeg # macOS
winget install Gyan.FFmpeg # Windows
```
```bash
./alchemist # Linux / macOS
alchemist.exe # Windows
```
## From source
```bash
git clone https://github.com/bybrooklyn/alchemist.git
cd alchemist
cd web && bun install --frozen-lockfile && bun run build && cd ..
cargo build --release
./target/release/alchemist
```
Requires Rust 1.85+. Run `rustup update stable` first.
## Nightly builds
```bash
docker pull ghcr.io/bybrooklyn/alchemist:nightly
```
Nightly builds publish on every push to `main` after Rust
checks pass. Use `:latest` for stable releases.

32
docs/library-doctor.md Normal file
View File

@@ -0,0 +1,32 @@
---
title: Library Doctor
description: Scan for corrupt, truncated, and unreadable media files.
---
Library Doctor scans your configured directories for files
that are corrupt, truncated, or unreadable by FFprobe.
Run from **Settings → Runtime → Library Doctor → Run Scan**.
## What it checks
| Check | What it detects |
|-------|-----------------|
| Probe failure | Files FFprobe cannot read at all |
| No video stream | Files with no detectable video track |
| Zero duration | Files reporting 0 seconds of content |
| Truncated file | Files that appear to end prematurely |
| Missing codec data | Files missing metadata needed to plan a transcode |
## What to do with results
Library Doctor reports issues — it does not repair or delete
files automatically.
- **Re-download** — interrupted download
- **Re-rip** — disc read errors
- **Delete** — duplicate or unrecoverable
- **Ignore** — player handles it despite FFprobe failing
Files that fail Library Doctor also fail the Analyzing
stage of a transcode job and appear as Failed in Jobs.

43
docs/library-setup.md Normal file
View File

@@ -0,0 +1,43 @@
---
title: Library Setup
description: Adding watch directories and organizing your media.
---
## Adding directories
Go to **Settings → Library** and add your media directory
paths. In Docker, use the container-side path (right side
of your volume mount).
Assign a **Profile** to each directory (Space Saver,
Balanced, Quality First, Streaming, or custom) to control
how files in that folder are transcoded.
## Watch folders
Enable **Watch Folders** to monitor directories in real
time. New files are queued automatically within a few
seconds.
Extra watch directories can be added in
**Settings → Watch Folders**.
## Recommended structure
```text
/media/
├── movies/ → Quality First profile
├── tv/ → Balanced profile
└── home-videos/ → Space Saver profile
```
## Triggering a manual scan
**Settings → Library → Trigger Scan** picks up newly added
files without waiting for the file watcher.
## Library Doctor
Run a health scan from **Settings → Runtime → Library Doctor**
to find corrupt or unreadable files before they fail as
encode jobs. See [Library Doctor](/library-doctor).

32
docs/notifications.md Normal file
View File

@@ -0,0 +1,32 @@
---
title: Notifications
description: Configure Discord, Gotify, and webhook alerts.
---
Configure notification targets in **Settings → Notifications**.
## Supported targets
### Discord webhook
Create a webhook in your Discord channel settings
(channel → Integrations → Webhooks). Paste the URL into
Alchemist.
### Gotify
Enter your Gotify server URL and app token.
### Generic webhook
Alchemist sends a JSON POST to any URL you configure.
Works with Home Assistant, ntfy, Apprise, and custom scripts.
## Troubleshooting
If notifications aren't arriving:
1. Check the URL or token for extra whitespace
2. Check **Logs** — Alchemist logs notification failures
with response code and body
3. Verify the server has network access to the target

67
docs/overview.md Normal file
View File

@@ -0,0 +1,67 @@
---
title: Alchemist Overview
description: What Alchemist is, what it does, and where to start.
slug: /
---
Alchemist scans your media library, analyzes every file, and
decides whether transcoding it would actually save meaningful
space. If a file is already efficiently compressed, it skips
it and tells you exactly why in plain English. If it can save
space without hurting quality, it encodes it — using whatever
hardware you have — automatically, while you sleep.
Your originals are never touched until the new file passes
quality validation. Nothing is deleted until you say so.
## What it does
- Scans configured library directories and queues files for analysis
- Runs FFprobe on each file to extract codec, resolution, bitrate, and HDR metadata
- Applies BPP (bits-per-pixel) analysis and size thresholds to decide whether transcoding is worth it
- Selects the best available encoder automatically (NVIDIA NVENC, Intel QSV, AMD VAAPI/AMF, Apple VideoToolbox, CPU fallback)
- Encodes to AV1, HEVC, or H.264 based on your configured target
- Validates output quality (optional VMAF scoring) before promoting the result
- Tells you exactly why every skipped file was skipped
## What it is not
Alchemist is not Tdarr. There are no flow editors, no plugin
stacks, no separate services to install. It is a single
binary that does one thing without asking you to become an
FFmpeg expert.
## Hardware support
| Vendor | AV1 | HEVC | H.264 | Notes |
|--------|-----|------|-------|-------|
| NVIDIA NVENC | RTX 30/40 | Maxwell+ | All | Best for speed |
| Intel QSV | 12th gen+ | 6th gen+ | All | Best for power efficiency |
| AMD VAAPI/AMF | RDNA 2+ | Polaris+ | All | Linux VAAPI / Windows AMF |
| Apple VideoToolbox | M3+ | M1+ / T2 | All | Binary install recommended |
| CPU (SVT-AV1/x265/x264) | All | All | All | Always available |
## Where to start
| Goal | Start here |
|------|-----------|
| Get it running | [Installation](/installation) |
| Docker setup | [Docker](/docker) |
| Get your GPU working | [Hardware](/hardware) |
| Understand skip decisions | [Skip Decisions](/skip-decisions) |
| Tune per-library behavior | [Profiles](/profiles) |
## Nightly builds
```bash
docker pull ghcr.io/bybrooklyn/alchemist:nightly
```
Published on every push to `main` that passes Rust checks.
Version format: `0.3.0-dev.3-nightly+abc1234`.
Stable: `ghcr.io/bybrooklyn/alchemist:latest`
## License
GPLv3. Free to use, modify, and distribute under the same
license. Genuinely open source — not source-available.

39
docs/profiles.md Normal file
View File

@@ -0,0 +1,39 @@
---
title: Profiles
description: Per-library transcoding profiles and what each controls.
---
Profiles define how Alchemist handles files in a given
directory. Each watch directory gets its own profile.
## Built-in presets
| Profile | Codec | Best for |
|---------|-------|----------|
| Space Saver | AV1 | Old TV, content you rarely rewatch |
| Balanced | AV1 | General libraries (default) |
| Quality First | HEVC | Movies, anything you care about |
| Streaming | H.264 | Remote access, older or limited devices |
## What profiles control
- **Output codec** — AV1, HEVC, or H.264
- **Quality profile** — speed/quality tradeoff preset
- **BPP threshold** — minimum bits-per-pixel to transcode
- **Size reduction threshold** — minimum predicted savings
- **Min file size** — skip files below this (MB)
- **Stream rules** — which audio tracks to keep or strip
- **Subtitle mode** — copy, burn, extract, or drop
- **HDR mode** — preserve metadata or tonemap to SDR
## Assigning profiles
Select a profile when adding a directory in
**Settings → Library**. Changeable at any time — jobs
already queued use the profile they were planned with.
## Smart skipping
Files already meeting the profile's targets are skipped
automatically. Every skip is recorded with a reason. See
[Skip Decisions](/skip-decisions).

44
docs/quick-start.md Normal file
View File

@@ -0,0 +1,44 @@
---
title: Quick Start
description: The essentials for getting Alchemist processing your library.
---
Assumes you've completed the setup wizard. If not, see
[First Run](/first-run).
## Start the engine
The engine starts paused after setup. Click **Start** in
the header bar.
## Watch the queue
Go to **Jobs**. Files move through:
`Queued → Analyzing → Encoding → Completed`
Skipped files appear in the **Skipped** tab with a
plain-English reason. A high skip rate is normal — it means
files are already efficiently compressed. See
[Skip Decisions](/skip-decisions).
## Check hardware detection
Go to **Settings → Hardware**. Confirm your GPU is the
active backend. If you see `CPU (Software)` with a supported
GPU, see [GPU Passthrough](/gpu-passthrough).
## See your savings
Once jobs complete, **Statistics** shows total space
recovered, compression ratios, and a savings chart.
## Key controls
| Action | Where |
|--------|-------|
| Pause encoding | Header → Pause |
| Drain (finish active, stop new) | Header → Stop |
| Cancel a job | Jobs → ⋯ → Cancel |
| Boost priority | Jobs → ⋯ → Boost |
| Trigger manual scan | Settings → Library → Scan |
| Change engine mode | Settings → Runtime |

33
docs/scheduling.md Normal file
View File

@@ -0,0 +1,33 @@
---
title: Scheduling
description: Restrict encoding to specific time windows.
---
The scheduler restricts when the engine may start new jobs.
## Creating a window
Go to **Settings → Schedule** and add a window with:
- **Start time** — when encoding may begin (e.g. `22:00`)
- **End time** — when it must stop (e.g. `07:00`)
- **Days of week** — which days apply
Multiple windows are supported. Windows spanning midnight
are handled correctly.
## At the window boundary
When a window ends mid-encode, the engine pauses. The
in-progress job is suspended and resumes at the next window
start. No data is lost.
## Manual override
With a schedule active, you can still force-start from the
header bar. The override lasts until the next boundary.
## No schedule
If no windows are configured, the engine runs whenever it
is in Running state.

64
docs/skip-decisions.md Normal file
View File

@@ -0,0 +1,64 @@
---
title: Skip Decisions
description: Why Alchemist skipped a file and what each reason means.
---
Every skipped file has a machine-readable reason string
recorded in the database and shown as plain English in the
job detail panel.
## Skip reasons
### already_target_codec
The file is already in the target codec at 10-bit depth.
Re-encoding would not save meaningful space.
**Action:** None. Correct behavior.
### bpp_below_threshold
Bits-per-pixel is below the configured minimum. The file is
already efficiently compressed.
The threshold is resolution-adjusted (4K gets a lower
effective threshold) and confidence-adjusted based on
bitrate measurement reliability.
**Action:** Lower `min_bpp_threshold` in Settings →
Transcoding (default: 0.10).
### below_min_file_size
The file is smaller than `min_file_size_mb` (default: 50 MB).
**Action:** Lower `min_file_size_mb` if you want small files
processed.
### size_reduction_insufficient
The predicted output would not be meaningfully smaller
(below `size_reduction_threshold`, default: 30%).
**Action:** Lower `size_reduction_threshold` in Settings →
Transcoding.
### no_suitable_encoder
No encoder available for the target codec. Usually means
hardware detection failed with CPU fallback disabled.
**Action:** Check Settings → Hardware. Enable CPU fallback,
or fix hardware detection.
### incomplete_metadata
FFprobe could not determine resolution, duration, or
bitrate. Cannot make a valid transcoding decision.
**Action:** Check if the file is corrupt using Library Doctor.
## Why a high skip rate is fine
A high skip rate means files are already efficiently
compressed. An 80% skip rate on a mixed library is normal.

61
docs/stream-rules.md Normal file
View File

@@ -0,0 +1,61 @@
---
title: Stream Rules
description: Control which audio tracks survive transcoding.
---
Stream rules filter audio tracks during the planning phase,
before FFmpeg is invoked.
## Why use them
Blu-ray rips often contain three to eight audio tracks:
TrueHD, DTS-HD, AC3, commentary, descriptive audio, foreign
dubs. Stream rules strip unwanted tracks automatically.
## Available rules
### strip_audio_by_title
Removes tracks whose title contains any of the specified
strings (case-insensitive).
```toml
[transcode.stream_rules]
strip_audio_by_title = ["commentary", "director", "description", "ad"]
```
### keep_audio_languages
Keeps only tracks whose ISO 639-2 language tag matches.
Tracks with no language tag are always kept.
```toml
[transcode.stream_rules]
keep_audio_languages = ["eng", "jpn"]
```
### keep_only_default_audio
Keeps only the track flagged as default in the source.
```toml
[transcode.stream_rules]
keep_only_default_audio = true
```
## Evaluation order
1. `strip_audio_by_title`
2. `keep_audio_languages`
3. `keep_only_default_audio`
4. Fallback: if no tracks survive, the original default is kept
## Example: lean English-only output
```toml
[transcode.stream_rules]
strip_audio_by_title = ["commentary", "description", "ad"]
keep_audio_languages = ["eng"]
```
Configure in **Settings → Transcoding → Stream Rules**.

70
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,70 @@
---
title: Troubleshooting
description: Common failure modes and how to isolate them.
---
## No hardware encoder detected
Check **Settings → Hardware → Probe Log** first. The probe
log shows the exact FFmpeg encoder test that failed.
Then verify the platform path:
- NVIDIA: confirm `nvidia-container-toolkit` and `nvidia-smi`
- Intel/AMD on Linux: confirm `/dev/dri` passthrough and
container group access
- Apple: confirm `ffmpeg -encoders | grep videotoolbox`
## Jobs stuck in Queued
The engine may be paused. Check the header state first.
If the engine is already running, check:
- active concurrent limit in **Settings → Runtime**
- schedule window state
- current failures in the logs
## CPU fallback despite GPU
Most cases are one of these:
- `/dev/dri` not passed through on Linux
- `nvidia-container-toolkit` missing for NVIDIA
- probe failure for the requested codec/backend
Check **Settings → Hardware → Probe Log**. If the probe log
shows a backend error, fix that before changing planner
thresholds.
## Files skipped unexpectedly
See [Skip Decisions](/skip-decisions). The common causes are:
- `min_bpp_threshold` is too high
- `size_reduction_threshold` is too high
- the file is below `min_file_size_mb`
- the file is already in the target codec
## VMAF scores not appearing
VMAF is optional. Check that FFmpeg was built with VMAF
support:
```bash
ffmpeg -filters | grep vmaf
```
If nothing matches, VMAF scoring is unavailable in that
FFmpeg build.
## Log locations
- Docker: `docker logs -f alchemist`
- Binary: stdout, or redirect with `./alchemist > alchemist.log 2>&1`
- systemd: `journalctl -u alchemist -f`
## More help
GitHub Issues:
[https://github.com/bybrooklyn/alchemist/issues](https://github.com/bybrooklyn/alchemist/issues)

60
docs/web-interface.md Normal file
View File

@@ -0,0 +1,60 @@
---
title: Web Interface
description: What each section of the Alchemist dashboard does.
---
Served by the same binary as the backend. Default:
`http://localhost:3000`.
## Header bar
Visible on every page. Shows engine state and provides
**Start**, **Pause**, and **Stop** controls.
- **Start** — begins processing
- **Pause** — freezes active jobs mid-encode, stops new jobs
- **Stop** — drain mode: active jobs finish, no new jobs start
## Dashboard
- Engine state and stat row (active, completed, failed, total)
- Recent Activity — last five jobs with status and timestamps
- Resource Monitor — CPU, memory, GPU (updated via SSE)
## Jobs
Tabs: Active / Queued / Completed / Failed / Skipped / Archived
Click any job to open the detail panel:
- Input metadata (codec, resolution, bitrate, duration, HDR)
- Output stats (size, compression ratio, speed, VMAF)
- Skip or failure reason in plain English
- Full FFmpeg log
Bulk actions via checkboxes: restart, cancel, delete.
## Logs
Real-time log viewer (SSE). Entries grouped by job — click
a header to expand. System logs appear at the top.
Filterable by level, searchable.
## Statistics
Space savings area chart, per-codec breakdown, aggregate
totals. Fills in as jobs complete.
## Settings tabs
| Tab | Controls |
|-----|---------|
| Library | Watch folders, scan trigger |
| Watch Folders | Extra monitored directories |
| Transcoding | Codec, quality, thresholds, stream rules |
| Hardware | GPU vendor, device path, fallback |
| File Settings | Output extension, suffix, output root, replace strategy |
| Quality | VMAF scoring, minimum score, revert on failure |
| Notifications | Discord, Gotify, webhook targets |
| Schedule | Time windows |
| Runtime | Engine mode, concurrent jobs override, Library Doctor |
| Appearance | Color theme (35+ themes) |