docs: add server API design for remote editing via Tauri client

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 13:20:13 +02:00
parent 7abf0b4d4c
commit 73dd7a1569
+207
View File
@@ -0,0 +1,207 @@
# 8-cut Server API Design
## Goal
Run 8-cut as a FastAPI server on Unraid (Docker) so a Tauri desktop client on Mac can edit remotely over WireGuard — no file transfers, no auth.
## Architecture
```
Unraid (Docker container):
FastAPI + ffmpeg + SQLite
├── /api/files list videos from mounted volumes
├── /api/stream/{path} transcoded video (cached, no audio)
├── /api/audio/{path} full-quality audio (cached, passthrough)
├── /api/video/{path} raw file (for reference/download)
├── /api/markers CRUD markers per profile
├── /api/profiles list/create profiles
├── /api/export trigger + manage exports
├── /api/labels label history
├── /api/hidden hidden file management
└── ws://…/ws/export real-time export progress
Mac (Tauri + Svelte + libmpv):
├── mpv plays stream URL (video) + audio URL separately
├── Canvas timeline + crop overlay + keyframes
├── Full UI: profiles, subprofiles, settings
└── Stateless — all state lives on server
```
## Docker mounts
| Mount | Purpose | Env var |
|-------------|--------------------------------|--------------|
| `/videos` | Source video files (read-only) | `MEDIA_DIRS` |
| `/exports` | Export output | `EXPORT_DIR` |
| `/data` | SQLite DB + transcode cache | `DB_PATH`, `CACHE_DIR` |
`MEDIA_DIRS` supports multiple paths: `/videos1,/videos2`.
## Video streaming with transcode cache
The client needs low-bitrate video for scrubbing over the network but full-quality audio for accurate editing.
**Flow:**
1. Client requests `/api/stream/{path}?quality=low`
2. Server checks cache: `{CACHE_DIR}/{quality}/{hash}.mp4`
3. If cached → serve with range requests (instant seeking)
4. If not → start background ffmpeg transcode, return `202 Accepted` with job ID
5. Client polls or gets WebSocket notification when ready
6. Audio: `/api/audio/{path}` extracts audio (passthrough, fast) to cache on first request
**Quality presets:**
| Preset | Resolution | Bitrate |
|----------|-----------|----------|
| `potato` | 480p | ~500 Kbps |
| `low` | 720p | ~2 Mbps |
| `medium` | 1080p | ~5 Mbps |
| `high` | original | ~10 Mbps |
Each quality level cached separately. Client can switch quality — mpv reloads the URL.
**mpv on client:**
```
video = http://server/api/stream/file.mp4?quality=low
audio = http://server/api/audio/file.mp4
```
mpv's `--audio-file=` flag plays both in sync with frame-accurate seeking.
## API endpoints
### Files
```
GET /api/files?root={root}
→ [{path, name, size, duration?, markers_count}]
GET /api/video/{path}
→ raw file with range requests
GET /api/stream/{path}?quality=low|medium|high|potato
→ cached transcoded video (no audio), range requests
→ 202 if transcode in progress
GET /api/audio/{path}
→ cached full-quality audio, range requests
→ 202 if extraction in progress
GET /api/cache/status/{path}
→ {qualities: {potato: "ready", low: "transcoding", ...}, audio: "ready"}
```
### Markers & profiles
```
GET /api/markers/{filename}?profile=default
→ [{start_time, marker_number, output_path}]
GET /api/profiles
→ ["default", "intense", ...]
GET /api/labels
→ ["dog barking", "rain", ...]
```
### Export
```
POST /api/export
body: {input_path, cursor, folder_suffix?, name, clips, spread,
short_side?, portrait_ratio?, crop_center, format,
label?, category?, profile, crop_keyframes?,
rand_portrait?, rand_square?, track_subject?}
→ {job_id}
GET /api/export/{job_id}
→ {status, completed, total, outputs: [...]}
DELETE /api/export/{output_path}
→ delete from DB + disk
WS /ws/export
→ server pushes: {type: "clip_done", path: "..."} | {type: "all_done"} | {type: "error", msg: "..."}
```
### Hidden files
```
POST /api/hidden/{filename}?profile=default
DELETE /api/hidden/{filename}?profile=default
GET /api/hidden?profile=default
→ ["file1.mp4", "file2.mp4"]
```
## Code reuse from main.py
**Extracted to shared module (used by both server and Qt app):**
- `ProcessedDB` — SQLite operations
- `build_ffmpeg_command` — ffmpeg command construction
- `build_audio_extract_command`
- `build_export_path` / `build_sequence_dir`
- `detect_hw_encoders`
- `upsert_clip_annotation` / `remove_clip_annotation`
- `apply_keyframes_to_jobs` / `resolve_keyframe`
- `track_centers_for_jobs` (subject tracking)
**Server-specific (new):**
- FastAPI app + route handlers
- Transcode cache manager
- Export worker (plain threading, replaces QThread-based ExportWorker)
- File listing / media root scanning
- WebSocket export progress broadcaster
**Tauri client (new, Svelte):**
- mpv integration via Tauri plugin or sidecar
- Canvas-based timeline widget
- Canvas-based crop overlay
- All UI controls
- API client module
## Dockerfile
```dockerfile
FROM python:3.12-slim
RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY server/ .
RUN pip install --no-cache-dir fastapi uvicorn
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
```
## Project structure
```
8-cut/
├── main.py (existing Qt app, unchanged)
├── core/ (shared logic, extracted from main.py)
│ ├── __init__.py
│ ├── db.py (ProcessedDB)
│ ├── ffmpeg.py (build commands, detect encoders)
│ ├── export.py (ExportWorker — plain threading)
│ ├── paths.py (build_export_path, build_sequence_dir)
│ └── annotations.py (dataset.json helpers)
├── server/
│ ├── app.py (FastAPI app)
│ ├── routes/
│ │ ├── files.py
│ │ ├── stream.py
│ │ ├── markers.py
│ │ ├── export.py
│ │ └── hidden.py
│ ├── cache.py (transcode cache manager)
│ ├── ws.py (WebSocket handler)
│ └── config.py (env vars, settings)
├── client/ (Tauri + Svelte — future)
│ └── ...
├── Dockerfile
└── docker-compose.yml
```
## Implementation order
1. Extract shared logic from main.py → `core/`
2. Update main.py to import from `core/` (verify Qt app still works)
3. Build FastAPI server with file listing + video serving
4. Add transcode cache + audio extraction
5. Add markers/profiles/labels/hidden API
6. Add export endpoint + WebSocket progress
7. Dockerfile + docker-compose
8. (Later) Tauri client