mirror of
https://github.com/bybrooklyn/alchemist.git
synced 2026-04-18 09:53:33 -04:00
Add Docusaurus docs site and GitHub Pages release packaging
This commit is contained in:
373
docs/api.md
Normal file
373
docs/api.md
Normal 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
106
docs/architecture.md
Normal 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
139
docs/changelog.md
Normal 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
85
docs/codecs.md
Normal 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 AV1’s 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.
|
||||
149
docs/configuration-reference.md
Normal file
149
docs/configuration-reference.md
Normal 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"
|
||||
```
|
||||
52
docs/contributing/development.md
Normal file
52
docs/contributing/development.md
Normal 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
|
||||
```
|
||||
31
docs/contributing/overview.md
Normal file
31
docs/contributing/overview.md
Normal 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
189
docs/database-schema.md
Normal 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
67
docs/docker.md
Normal 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
37
docs/engine-modes.md
Normal 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.
|
||||
28
docs/environment-variables.md
Normal file
28
docs/environment-variables.md
Normal 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
76
docs/faq.md
Normal 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 30–70%, 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?
|
||||
|
||||
Netflix’s 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
51
docs/first-run.md
Normal 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
214
docs/gpu-passthrough.md
Normal 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
40
docs/hardware.md
Normal 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
104
docs/hardware/amd.md
Normal 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
56
docs/hardware/apple.md
Normal 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
60
docs/hardware/cpu.md
Normal 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.5–1.5x realtime |
|
||||
| HEVC | `medium` | ~1–3x realtime |
|
||||
| H.264 | `medium` | ~3–8x 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
104
docs/hardware/intel.md
Normal 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
108
docs/hardware/nvidia.md
Normal 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
95
docs/installation.md
Normal 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
32
docs/library-doctor.md
Normal 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
43
docs/library-setup.md
Normal 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
32
docs/notifications.md
Normal 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
67
docs/overview.md
Normal 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
39
docs/profiles.md
Normal 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
44
docs/quick-start.md
Normal 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
33
docs/scheduling.md
Normal 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
64
docs/skip-decisions.md
Normal 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
61
docs/stream-rules.md
Normal 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
70
docs/troubleshooting.md
Normal 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
60
docs/web-interface.md
Normal 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) |
|
||||
Reference in New Issue
Block a user