592 lines
20 KiB
Markdown
Executable File
592 lines
20 KiB
Markdown
Executable File
# ferret
|
||
|
||
**A modern, accuracy-first video player for Linux, written in Rust.**
|
||
|
||
[](LICENSE)
|
||
[](#)
|
||
[](https://www.rust-lang.org)
|
||
|
||
ferret is a desktop video player built on libmpv, with a custom egui overlay UI
|
||
rendered in a transparent always-on-top window. The engine runs on its own
|
||
thread; the UI never blocks playback. File dialogs are drawn inside the overlay
|
||
— no external dialog processes, no z-order conflicts.
|
||
|
||
- **Author:** Jeremy Anderson
|
||
- **Website:** http://git.dcos.net/dcosnet/ferret
|
||
- **License:** GPL-2.0-or-later
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
- [Why ferret?](#why-ferret)
|
||
- [Architecture](#architecture)
|
||
- [Features](#features)
|
||
- [Crates](#crates)
|
||
- [Build](#build)
|
||
- [Run](#run)
|
||
- [Keyboard Shortcuts](#keyboard-shortcuts)
|
||
- [Menu Reference](#menu-reference)
|
||
- [Control Bar](#control-bar)
|
||
- [A/B Markers and Loop Export](#ab-markers-and-loop-export)
|
||
- [Subtitles](#subtitles)
|
||
- [Video Transforms](#video-transforms)
|
||
- [Accuracy-First Error Policy](#accuracy-first-error-policy)
|
||
- [Configuration](#configuration)
|
||
- [Roadmap](#roadmap)
|
||
- [Contributing](#contributing)
|
||
- [License](#license)
|
||
|
||
---
|
||
|
||
## Why ferret?
|
||
|
||
ferret sidesteps three root causes of playback stutter on Linux:
|
||
|
||
1. **Copy-back hardware decoding** shuttling frames over the PCIe bus. ferret
|
||
sets `hwdec=auto-safe` — only zero-copy hardware decode paths
|
||
(VA-API/NVDEC/Vulkan video decode), never copy-back modes.
|
||
2. **Software audio amplification** conflicting with PipeWire's logarithmic
|
||
volume curves. ferret hard-caps volume at 100% (`volume_max=1.0`); the
|
||
volume slider maps 1:1 with the system sink.
|
||
3. **Push-through error handling** causing visual smearing on corrupt frames.
|
||
ferret sets `framedrop=vo` — drop frames only at display, never on decode.
|
||
Corrupt frames are dropped, never smeared through.
|
||
|
||
The engine thread owns libmpv exclusively; UI thread hangs never cause AV
|
||
stutter because the engine has no knowledge of the UI.
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ ferret binary (player-app) │
|
||
│ │
|
||
│ winit event loop (main thread) │
|
||
│ ├─ Video window ──wid──▶ libmpv ──▶ FFmpeg/Vulkan │
|
||
│ │ │ │
|
||
│ │ ├─ decode │
|
||
│ │ ├─ AO (PipeWire/PulseAudio) │
|
||
│ │ └─ VO (Vulkan/OpenGL) │
|
||
│ │ │
|
||
│ └─ Overlay window (egui + wgpu, transparent, AlwaysOnTop) │
|
||
│ ├─ Control bar (play/pause, seek, volume, markers) │
|
||
│ ├─ Menu bar (File / Playback / Audio / Subtitles / etc) │
|
||
│ └─ In-UI file browser (replaces external file dialogs) │
|
||
│ │
|
||
│ Engine thread (named "ferret-engine") │
|
||
│ └─ owns MpvHandle, drains libmpv event queue, │
|
||
│ publishes EngineEvents on a bounded channel │
|
||
│ │
|
||
│ ffmpeg worker thread (named "ferret-ffmpeg") │
|
||
│ └─ spawned for A-B loop video export, sends result back │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Threading model:** thread-per-subsystem with `crossbeam` channels. No async.
|
||
- **Cmd channel** (UI → engine): `bounded::<Cmd>(64)` — bounded for natural backpressure.
|
||
- **Event channel** (engine → UI): `bounded::<EngineEvent>(256)` — single consumer.
|
||
- **State snapshot**: `Arc<Mutex<PlaybackState>>` (parking_lot) shared between threads.
|
||
|
||
**Two-window model:** libmpv renders directly into the video window via X11
|
||
`wid` embedding. The overlay window is transparent, borderless, and
|
||
`AlwaysOnTop` — it covers the video window and renders the UI with egui + wgpu.
|
||
Mouse clicks outside the control bar don't pass through (known limitation;
|
||
fix requires the libmpv render-context API — on the roadmap).
|
||
|
||
**X11 background pixel:** the video window's X11 background pixel is set to
|
||
`#141416` via `XSetWindowBackground` before libmpv attaches. This prevents the
|
||
X server from showing framebuffer garbage during window resize/move (the
|
||
"mirrored desktop" artifact).
|
||
|
||
**wgpu presentation:** forced to `PresentMode::Fifo` (vsync) for compatibility.
|
||
Surface format prefers non-sRGB (`Bgra8Unorm` → `Rgba8Unorm` → sRGB variants)
|
||
to avoid egui color-management warnings. Alpha mode is `Auto`.
|
||
|
||
---
|
||
|
||
## Features
|
||
|
||
### Core Playback
|
||
|
||
- **Play / Pause / Stop** — buttons + keyboard + menu
|
||
- **Seek bar** — drag-to-scrub, click-to-jump, hover preview line, A/B marker pins
|
||
- **Frame stepping** — step forward/backward one frame at a time
|
||
- **Volume** — slider + mute toggle, hard-capped at 100%
|
||
- **Fullscreen** — borderless fullscreen toggle
|
||
|
||
### Loop Modes
|
||
|
||
- **Off** — play once, stop at EOF
|
||
- **Loop File** — repeat the current file indefinitely (`loop-file=inf`)
|
||
- **Loop Playlist** — repeat the entire playlist indefinitely (`loop-playlist=inf`)
|
||
|
||
### Speed Control
|
||
|
||
- **Presets** — 0.25×, 0.50×, 0.75×, 1.00×, 1.25×, 1.50×, 2.00×, 3.00×, 4.00× (menu)
|
||
- **Quick presets** — 0.50×, 1.00×, 1.50×, 2.00× (control bar buttons)
|
||
- **Fine slider** — 0.25× to 4.0× in 0.01 increments (control bar)
|
||
- **Keyboard** — `-` / `=` nudge by 0.25×
|
||
|
||
### A/B Markers
|
||
|
||
- **Set A / Set B** — drop markers at current playback position
|
||
- **Marker pins** — drawn on the seek bar (red `#FF6464` for A, blue `#64B4FF` for B)
|
||
- **Toggle A-B Loop** — loop between markers (mpv's `ab-loop-a` / `ab-loop-b`)
|
||
- **Export A-B Loop Video** — renders the segment to a new file via ffmpeg
|
||
- **Import/Export Markers** — save/load marker positions as `.txt` or `.json`
|
||
|
||
### File Menu
|
||
|
||
- **Load File** — in-UI file browser (media extensions filtered)
|
||
- **Load Folder** — enumerate video files in a folder as a playlist
|
||
- **Load Playlist** — multi-select files (media + `.m3u`/`.m3u8`/`.pls`)
|
||
- **Export A-B Loop Video** — ffmpeg-rendered clip (MP4/MKV/WebM)
|
||
- **Import Markers** — load saved A/B positions from `.txt` or `.json`
|
||
- **Toggle Fullscreen**
|
||
- **Quit**
|
||
|
||
### Subtitles
|
||
|
||
- **Embedded track selection** — auto-detects from mpv's `track-list`
|
||
- **External subtitle loading** — `.srt`, `.ass`, `.ssa`, `.sub`, `.idx`, `.sup`, `.vtt`, `.smi`, `.lrc`
|
||
- **Visibility toggle** — hide subs without losing the selected track
|
||
- **Forced track support** — displays `[forced]` badge for forced tracks
|
||
|
||
### Audio Tracks
|
||
|
||
- **Track selection** — dropdown with all embedded audio tracks
|
||
- **Auto** — let mpv pick the default track
|
||
- **Labels** — `1: eng [default]`, `2: Commentary`, etc.
|
||
|
||
### Video Transforms
|
||
|
||
- **Rotation** — 0°, 90°, 180°, 270° (mpv `video-rotate` property)
|
||
- **Flip Horizontal** — mirror left-right (mpv `vf` filter `hflip`)
|
||
- **Flip Vertical** — upside-down (mpv `vf` filter `vflip`)
|
||
|
||
### In-UI File Browser
|
||
|
||
All file selection happens inside the egui overlay — no external dialog
|
||
processes (zenity, kdialog, rfd). This eliminates the z-order conflict where
|
||
external dialogs would pop under the overlay's `AlwaysOnTop` window. The
|
||
browser supports:
|
||
|
||
- Directory listing with sorted entries (directories first, then files)
|
||
- Extension filtering per dialog kind
|
||
- Single-select, multi-select (Load Playlist), and save modes (filename input)
|
||
- Path bar with Up / Home navigation
|
||
- Double-click to navigate into directories
|
||
|
||
---
|
||
|
||
## Crates
|
||
|
||
| Crate | Role |
|
||
|---|---|
|
||
| `mpv-bindings` | bindgen FFI to libmpv + safe wrapper (`MpvHandle`, `Event`, `Property`, `Command`) |
|
||
| `player-core` | Headless engine: `PlayerEngine`, `Cmd`/`EngineEvent` channels, accuracy-first options, `PlaybackState` |
|
||
| `player-ui` | egui + wgpu overlay renderer: control bar, menus, in-UI file browser, icons, theme |
|
||
| `player-app` | Binary: multi-window winit event loop, X11 wid embedding, keyboard input, ffmpeg export |
|
||
|
||
---
|
||
|
||
## Build
|
||
|
||
### Prerequisites
|
||
|
||
- **Rust** 1.75+ (install via [rustup](https://rustup.rs))
|
||
- **libmpv** 2.x (provided by `setup-libmpv.sh`)
|
||
- **libclang** (for bindgen; provided by `setup-libmpv.sh`)
|
||
- **ffmpeg** (for A-B loop video export; optional)
|
||
- **libX11** (linked directly for `XSetWindowBackground`; usually pre-installed)
|
||
|
||
### One-time setup (libmpv without root)
|
||
|
||
```bash
|
||
cd /path/to/ferret
|
||
./scripts/setup-libmpv.sh
|
||
source ./mpv-prefix/env.sh
|
||
```
|
||
|
||
This downloads libmpv2, libmpv-dev, and all runtime dependencies, extracts
|
||
them into `./mpv-prefix/`, and writes `./mpv-prefix/env.sh`. The script also
|
||
pulls in winit's xkbcommon runtime deps and mesa software rasterizers.
|
||
|
||
### Build the player
|
||
|
||
```bash
|
||
cargo build --release
|
||
```
|
||
|
||
The release binary is at `target/release/ferret`. rpath is baked in, so no
|
||
`LD_LIBRARY_PATH` is needed at runtime.
|
||
|
||
### Build flags
|
||
|
||
```bash
|
||
./scripts/build.sh # build release (default)
|
||
./scripts/build.sh --debug # build debug
|
||
./scripts/build.sh --clean # clean + rebuild
|
||
./scripts/build.sh --test # run cargo test --release
|
||
./scripts/build.sh --lint # cargo clippy + bracket audit + deref audit
|
||
./scripts/build.sh --ci # lint + test + build (full CI pass)
|
||
```
|
||
|
||
### Build profiles
|
||
|
||
```toml
|
||
[profile.release]
|
||
opt-level = 3
|
||
lto = "thin"
|
||
codegen-units = 1
|
||
strip = "symbols"
|
||
panic = "abort"
|
||
|
||
[profile.dev]
|
||
opt-level = 1 # wgpu/shaders benefit from at least -O1
|
||
debug = 1
|
||
```
|
||
|
||
---
|
||
|
||
## Run
|
||
|
||
```bash
|
||
ferret /path/to/video.mp4
|
||
```
|
||
|
||
Or launch with no arguments to get an empty dark grey window with the menu
|
||
bar — use **File → Load File...** to open the in-UI file browser.
|
||
|
||
---
|
||
|
||
## Keyboard Shortcuts
|
||
|
||
Every hotkey has a menu entry and/or a control-bar button. Menu items show
|
||
the shortcut in parentheses (e.g. `Play / Pause (Space)`).
|
||
|
||
| Key | Action | Menu / Control |
|
||
|-----|--------|----------------|
|
||
| Space | Play / pause | Playback menu, ▶/⏸ button |
|
||
| Q | Quit | File menu |
|
||
| F | Toggle fullscreen | File menu, ⛶ button |
|
||
| M | Toggle mute | Playback → Volume, 🔊 button |
|
||
| ← / → | Seek -5s / +5s (keyframes) | Playback → Seek |
|
||
| ↑ / ↓ | Volume +5% / -5% | Playback → Volume |
|
||
| , / . | Frame-step back / forward | Playback → Seek, ◀| / |▶ buttons |
|
||
| [ / ] | Set A / B marker | File menu, A/B buttons |
|
||
| \\ | Clear markers | File menu |
|
||
| L | Cycle loop mode (off/file/list) | Playback → Loop, ⟳ button |
|
||
| - / = | Speed -0.25× / +0.25× | Playback → Speed, slider |
|
||
| N / P | Playlist next / previous | Playback → Playlist, ⏭/⏮ buttons |
|
||
| V | Toggle subtitle visibility | Subtitles menu |
|
||
|
||
---
|
||
|
||
## Menu Reference
|
||
|
||
The menu bar is always visible at the top-left, even when the bottom control
|
||
bar has auto-hidden.
|
||
|
||
### File menu
|
||
|
||
| Item | Shortcut | Section |
|
||
|------|----------|---------|
|
||
| Load File... | — | Open |
|
||
| Load Folder... | — | Open |
|
||
| Load Playlist... | — | Open |
|
||
| Set A Marker | `[` | Markers |
|
||
| Set B Marker | `]` | Markers |
|
||
| Clear Markers | `\\` | Markers |
|
||
| Toggle A-B Loop | — | Markers |
|
||
| Export A-B Loop Video... | — | Markers |
|
||
| Import Markers... | — | Markers |
|
||
| Toggle Fullscreen | `F` | Window |
|
||
| Quit | `Q` | — |
|
||
|
||
### Playback menu
|
||
|
||
| Item | Shortcut | Section |
|
||
|------|----------|---------|
|
||
| Play / Pause | Space | — |
|
||
| Stop | — | — |
|
||
| Forward 5s | → | Seek |
|
||
| Backward 5s | ← | Seek |
|
||
| Frame Step Forward | `.` | Seek |
|
||
| Frame Step Backward | `,` | Seek |
|
||
| Next | N | Playlist |
|
||
| Previous | P | Playlist |
|
||
| Volume Up 5% | ↑ | Volume |
|
||
| Volume Down 5% | ↓ | Volume |
|
||
| Toggle Mute | M | Volume |
|
||
| Off / Loop File / Loop Playlist | — | Loop |
|
||
| Cycle Loop Mode | L | Loop |
|
||
| Speed Up +0.25× | = | Speed |
|
||
| Speed Down -0.25× | - | Speed |
|
||
| 0.25× through 4.0× presets | — | Speed |
|
||
|
||
### Audio menu
|
||
|
||
Appears only when the loaded file has audio tracks.
|
||
|
||
- **Auto** — let mpv pick the default track
|
||
- **List of embedded audio tracks** — `1: eng [default]`, `2: Commentary`, etc.
|
||
|
||
### Subtitles menu
|
||
|
||
- **Load Subtitle File...** — in-UI file browser (subtitle extensions)
|
||
- **Show Subtitles** `(V)` — toggle `sub-visibility`
|
||
- **None** — disable subtitles
|
||
- **List of embedded subtitle tracks** — with `[forced]` / `[default]` badges
|
||
|
||
### Video menu
|
||
|
||
- **Rotate:** 0° (normal), 90°, 180°, 270°
|
||
- **Flip:** Flip Horizontal (mirror), Flip Vertical (upside-down)
|
||
|
||
### Help menu
|
||
|
||
- **About ferret** — toggles the About panel (version, author, license)
|
||
- Version: `ferret 1.0.0`
|
||
- License: `GPL-2.0-or-later`
|
||
|
||
---
|
||
|
||
## Control Bar
|
||
|
||
The bottom control bar (118px tall) has three rows:
|
||
|
||
### Row 1 — Seek bar
|
||
- Current time label (monospace)
|
||
- Seek bar with A/B marker pins (red for A, blue for B)
|
||
- Duration label (monospace)
|
||
|
||
### Row 2 — Transport | time | volume + fullscreen
|
||
- Play/Pause, Stop, Previous, Next, Frame Back, Frame Forward
|
||
- Center: `MM:SS / MM:SS` time display
|
||
- Right: Fullscreen button, Volume slider, Volume/mute icon
|
||
|
||
### Row 3 — Markers | loop | speed | audio
|
||
- A marker button (shows time or "A"), B marker button (shows time or "B")
|
||
- AB-loop toggle, Loop-mode toggle (off/file/playlist)
|
||
- Speed presets: 0.50×, 1.00×, 1.50×, 2.00×
|
||
- Fine speed slider (0.25×–4.0×), current speed label
|
||
- Audio track dropdown
|
||
|
||
---
|
||
|
||
## A/B Markers and Loop Export
|
||
|
||
### Setting Markers
|
||
|
||
1. Play to the point where you want marker A.
|
||
2. Press `[` (or click **File → Set A Marker**).
|
||
3. Play to the point where you want marker B.
|
||
4. Press `]` (or click **File → Set B Marker**).
|
||
|
||
The markers appear as colored pins on the seek bar — red for A, blue for B.
|
||
|
||
### Looping A→B
|
||
|
||
When both markers are set, mpv automatically loops between them. The
|
||
**Toggle A-B Loop** menu item (or the AB-loop button in the control bar)
|
||
turns this on/off. When toggled off, the markers are cleared.
|
||
|
||
### Exporting the A-B Loop
|
||
|
||
**File → Export A-B Loop Video...** opens the in-UI save dialog, then runs
|
||
ffmpeg to extract the segment:
|
||
|
||
```bash
|
||
ffmpeg -y \
|
||
-ss <A> \
|
||
-i <input> \
|
||
-t <duration> \
|
||
-c:v libx264 -preset fast -crf 18 \
|
||
-c:a aac -b:a 192k \
|
||
<output>
|
||
```
|
||
|
||
`-ss` is placed before `-i` for fast seeking. Video is re-encoded (libx264,
|
||
CRF 18) for frame accuracy and maximum compatibility. Audio is re-encoded to
|
||
AAC at 192 kbps. ffmpeg must be installed and in your PATH. The export runs on
|
||
a background thread (`ferret-ffmpeg`) so the UI stays responsive.
|
||
|
||
### Importing/Exporting Markers
|
||
|
||
Markers can be saved to disk and reloaded later:
|
||
|
||
**Text format (.txt):**
|
||
```
|
||
# file: /path/to/video.mp4
|
||
# duration: 180.000s
|
||
# speed: 1.00x
|
||
|
||
A 00:01:23.456
|
||
B 00:02:45.000
|
||
LOOP on
|
||
```
|
||
|
||
**JSON format (.json):**
|
||
```json
|
||
{"file":"/path/to/video.mp4","duration":180.000,"speed":1.000,"a":83.456,"b":165.000,"loop":true}
|
||
```
|
||
|
||
---
|
||
|
||
## Subtitles
|
||
|
||
ferret supports both embedded subtitle tracks (from the container) and
|
||
external subtitle files.
|
||
|
||
### Embedded Tracks
|
||
|
||
When a file with subtitle tracks is loaded, the **Subtitles** menu populates
|
||
with all available tracks. Each track shows its id, language, and any badges
|
||
(`[forced]`, `[default]`).
|
||
|
||
### External Subtitles
|
||
|
||
**Subtitles → Load Subtitle File...** opens the in-UI file browser filtered
|
||
for: `.srt`, `.ass`, `.ssa`, `.sub`, `.idx`, `.sup`, `.vtt`, `.smi`, `.lrc`.
|
||
The loaded subtitle becomes active immediately.
|
||
|
||
### Visibility
|
||
|
||
**Subtitles → Show Subtitles** `(V)` toggles `sub-visibility` — hides
|
||
subtitles without losing the selected track, useful for quickly checking the
|
||
raw video.
|
||
|
||
---
|
||
|
||
## Video Transforms
|
||
|
||
### Rotation
|
||
|
||
**Video → Rotate** sets the `video-rotate` mpv property. Values: 0°, 90°,
|
||
180°, 270°. The rotation is applied immediately and reflected in the state.
|
||
|
||
### Flip
|
||
|
||
**Video → Flip** toggles mpv video filters:
|
||
- **Flip Horizontal** — adds/removes the `hflip` filter (mirror left-right)
|
||
- **Flip Vertical** — adds/removes the `vflip` filter (upside-down)
|
||
|
||
The filters are managed by reading the current `vf` property, adding or
|
||
removing the filter name, and writing it back.
|
||
|
||
---
|
||
|
||
## Accuracy-First Error Policy
|
||
|
||
ferret configures libmpv for visual accuracy over continuity:
|
||
|
||
- **`hr-seek=yes`** — exact seeks, not keyframe-snapped. Seeks take slightly
|
||
longer but land on the requested frame.
|
||
- **`framedrop=vo`** — only drop frames at the video output if behind. Never
|
||
drop on decode, so corrupt frames don't get smeared through.
|
||
- **`video-sync=display-resample`** — resample audio to match the display
|
||
refresh rate. Best A/V sync, eliminates audio drift.
|
||
- **`hwdec=auto-safe`** — use hardware decoding only when known-safe (no
|
||
copy-back modes that shuttle frames over the PCIe bus).
|
||
- **`volume-max=100`** — hard-coded. No software amplification past 100%,
|
||
killing the PipeWire volume clash.
|
||
|
||
These are baked into `EngineOptions::default()` and set as mpv options at
|
||
engine init time. User config (`~/.config/mpv/mpv.conf`) is explicitly
|
||
disabled (`config=no`) to prevent sneaking in different behavior.
|
||
|
||
Additional fixed mpv options: `terminal=no`, `msg-level=all=<level>`,
|
||
`input-default-bindings=no`, `input-builtin-bindings=no`, `osc=no`,
|
||
`cursor-autohide=no`, `input-vo-keyboard=no`, `vo=gpu`.
|
||
|
||
---
|
||
|
||
## Configuration
|
||
|
||
All options are code-defined in `EngineOptions::default()`:
|
||
(`crates/player-core/src/options.rs`)
|
||
|
||
| Field | Default | Purpose |
|
||
|-------|---------|---------|
|
||
| `hr_seek` | `true` | Exact seeks |
|
||
| `framedrop` | `"vo"` | Drop at display only |
|
||
| `video_sync` | `"display-resample"` | Resample audio to display |
|
||
| `hwdec` | `"auto-safe"` | Safe hardware decode only |
|
||
| `initial_volume` | `1.0` (100%) | Startup volume |
|
||
| `volume_max` | `1.0` (100%) | Hard cap — never higher |
|
||
| `log_level` | `"warn"` | libmpv log threshold |
|
||
| `wid` | `None` | Set at runtime from video window XID |
|
||
| `vo` | `"gpu"` | Video output driver |
|
||
| `loop_mode` | `Off` | Initial loop mode |
|
||
|
||
There is no config file yet (planned for a future release).
|
||
|
||
---
|
||
|
||
## Roadmap
|
||
|
||
### Done (v1.0)
|
||
|
||
- Multi-window winit + egui overlay (X11)
|
||
- libmpv 2.x FFI via bindgen
|
||
- Engine thread with full property observation
|
||
- Play/pause/stop/seek/frame-step
|
||
- Volume + mute (hard-capped at 100%)
|
||
- Loop modes (off/file/playlist)
|
||
- Speed control (presets + slider, 0.25×–4×)
|
||
- A/B markers + loop + video export via ffmpeg
|
||
- Marker import/export (txt + json)
|
||
- Audio track selection
|
||
- Subtitle track selection + external loading
|
||
- Video rotation (0/90/180/270) + flip (H/V)
|
||
- In-UI file browser (no external dialog dependency)
|
||
- Complete hotkey coverage — every shortcut has a menu entry and/or button
|
||
- Accuracy-first error policy
|
||
- X11 background pixel fix (eliminates resize/move "mirrored desktop" artifact)
|
||
- wgpu surface error recovery + forced `PresentMode::Fifo`
|
||
- CI tooling: bracket audit, deref audit, clippy config
|
||
|
||
### Planned
|
||
|
||
1. **Wayland support** via `mpv_render_context` + `MPV_RENDER_API_TYPE_OPENGL`.
|
||
Eliminates the wid/X11 dependency, unblocks macOS (Metal via wgpu) and
|
||
Windows (DX12 via wgpu).
|
||
2. **Config file** (`~/.config/ferret/ferret.toml`).
|
||
3. **Playlist UI** — drag-drop, reorder, repeat, shuffle.
|
||
4. **Media keys** (MPRIS on Linux).
|
||
5. **Single-window compositing** — render video as a wgpu texture inside egui,
|
||
eliminating the second window and the click-through limitation.
|
||
|
||
---
|
||
|
||
## Contributing
|
||
|
||
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code style,
|
||
coding standards (PEP 868 / POSIX / SEI CERT / MISRA), and pre-commit checks.
|
||
|
||
Patches are welcome at <http://git.dcos.net/dcosnet/ferret>.
|
||
|
||
---
|
||
|
||
## License
|
||
|
||
ferret is licensed under the [GNU General Public License v2.0](LICENSE)
|
||
or (at your option) any later version.
|
||
|
||
```
|
||
ferret - a modern, accuracy-first video player for Linux
|
||
Copyright (C) 2026 Jeremy Anderson
|
||
|
||
This program is free software; you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation; either version 2 of the License, or
|
||
(at your option) any later version.
|
||
```
|
||
|
||
libmpv (linked dynamically) is licensed under LGPL-2.1+ or GPL-2+ — the
|
||
dynamic linking keeps ferret's GPL-2.0 compatible.
|