A self-hosted media server that browses your folder structure as-is, streams video with real-time transcoding, and provides AI-powered subtitle generation and translation.
Unlike Jellyfin or Plex, FTML doesn't scrape metadata or reorganize your files. Your folders are your library.
- HLS transcoding — Real-time transcoding with hardware acceleration (Intel VAAPI, NVIDIA NVENC) and automatic CPU fallback
- Codec negotiation — H.264, HEVC, AV1, VP9; server and browser negotiate the best option
- Passthrough — Skip transcoding when the browser can play the source codec directly
- Dynamic quality presets — Auto-generated based on source resolution (720p → 4K + passthrough + original)
- Multi-audio — Switch between audio tracks (e.g. Japanese, English, commentary)
- Subtitle overlay — Embedded, external, and AI-generated subtitles with style customization
- Resume playback — Automatically saves and restores playback position
- Next episode — Auto-advances to the next file in the folder with countdown
- Keyboard shortcuts — Full keyboard control (seek, volume, fullscreen, speed, subtitle toggle)
- Playback stats — Codec, bitrate, FPS, resolution, network overlay
- Whisper transcription — Generate subtitles from audio using OpenVINO GenAI WhisperPipeline
- Silero VAD for speech detection
- 4 preprocessing modes: adaptive (BGM-aware), vocal separation, raw, none
- Runtime model swapping via web UI
- Automatic VRAM release after idle timeout
- LLM translation — Translate subtitles using Gemini, OpenAI, or DeepL
- Custom translation presets (prompt templates for tone/style)
- Batch processing (50-cue chunks)
- Gemini safety-block recovery via binary subdivision retry
- Batch operations — Generate/translate subtitles for entire folders at once
- Format conversion — SRT ↔ VTT ↔ ASS
- Folder tree — Browse your actual directory structure, grid or list view
- File info — Codec, resolution, audio tracks, file size via ffprobe
- Thumbnails — Auto-generated video thumbnails
- Search — Filename search across the library
- Upload / Delete / Move — Admin file management with trash bin and restore
- User management — Admin/User roles, registration approval system
- Session monitoring — Active HLS sessions with codec, quality, heartbeat info
- File logs — Upload/delete/move audit trail
- Settings GUI — API keys, Gemini model selection, Whisper model management
- Jobs dashboard — Active jobs with progress/ETA, completed/failed history
- Translation presets — CRUD for custom translation prompts
- Rate limiting — Login/register brute-force protection
- Docker & Docker Compose
- A media folder with video files
- (Optional) Intel Arc GPU for hardware-accelerated transcoding and Whisper inference
git clone https://github.com/NR2BJ/FTML.git
cd FTMLcp .env.example .envEdit .env:
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your-secure-password # Change this!
JWT_SECRET=your-random-secret-key # Change this!
MEDIA_PATH=/path/to/your/videos
FTML_PORT=7979docker volume create ftml_data
docker volume create whisper_modelsdocker compose up -d --buildFor NVIDIA GPU users, add the override file:
docker compose -f docker-compose.yml -f docker-compose.nvidia.yml up -d --buildOpen http://localhost:7979 and log in with your admin credentials.
| Variable | Default | Description |
|---|---|---|
ADMIN_USERNAME |
admin |
Initial admin account |
ADMIN_PASSWORD |
changeme |
Initial admin password (change this) |
JWT_SECRET |
(random) | JWT signing key; set for persistent sessions across restarts |
MEDIA_PATH |
/mnt/storage/video |
Path to your media folder |
FTML_PORT |
7979 |
External access port |
CORS_ORIGINS |
* |
Allowed CORS origins |
WHISPER_DEVICE |
GPU |
Whisper inference device (GPU or CPU) |
RENDER_GID |
109 |
GPU render group ID (check getent group render) |
Configure these in the web UI under Settings after login:
- Gemini API Key — For Gemini translation engine
- OpenAI API Key — For OpenAI translation engine
- DeepL API Key — For DeepL translation engine
Caddy (automatic HTTPS):
ftml.yourdomain.com {
reverse_proxy localhost:7979
}
Nginx:
server {
listen 443 ssl http2;
server_name ftml.yourdomain.com;
location / {
proxy_pass http://localhost:7979;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}| Layer | Stack |
|---|---|
| Backend | Go + chi router + SQLite (WAL mode) |
| Frontend | React 18 + TypeScript + Vite + Zustand + Tailwind CSS |
| Media | FFmpeg (VAAPI / NVENC / SW), ffprobe |
| Subtitles | OpenVINO GenAI WhisperPipeline (FastAPI) |
| Translation | Gemini / OpenAI / DeepL |
| Player | hls.js + Media Source Extensions |
| Deploy | Docker Compose (3 containers) |
┌──────────────────────────────────────────────────────┐
│ Docker Compose │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Frontend │ │ Backend │ │
│ │ React + Nginx│──────▶│ Go :8080 │ │
│ │ :7979 │ │ │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │
│ ┌──────────────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌──────────┐ ┌──────────┐ │
│ │ FFmpeg │ │ SQLite │ │ Whisper │ │
│ │ HW Accel │ │ (DB) │ │ OpenVINO │ │
│ └─────┬──────┘ └──────────┘ │ :8178 │ │
│ ▼ └─────┬────┘ │
│ ┌───────────┐ ┌─────┴────┐ │
│ │ /media │ │ External │ │
│ │ (volume) │ │ APIs │ │
│ └───────────┘ └──────────┘ │
└──────────────────────────────────────────────────────┘
VAAPI (full GPU decode + encode)
↓ fails within 5s
Hybrid (CPU decode + GPU encode)
↓ fails within 5s
Software (libx264 / libx265 / libsvtav1)
Fallback results are cached per session — subsequent session recreation skips directly to the working encoder.
| Key | Action |
|---|---|
| Space | Play / Pause |
| ← / → | Seek ±5s |
| J / L | Seek ±10s |
| ↑ / ↓ | Volume |
| M | Mute toggle |
| F | Fullscreen toggle |
| C | Subtitle toggle |
| < / > | Playback speed |
| 0–9 | Jump to 0%–90% |
| I | Playback stats overlay |
MIT