feat(config): set default values for WebRTC and transcoding in minimal TOML config
This commit is contained in:
parent
209ea38ecf
commit
26814ff6f7
3 changed files with 176 additions and 50 deletions
56
README.md
56
README.md
|
|
@ -382,6 +382,62 @@ enabled = true
|
||||||
country = "US"
|
country = "US"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Streaming reference
|
||||||
|
|
||||||
|
The in-browser player on torrentclaw.com streams from the daemon over WebRTC
|
||||||
|
(low-latency P2P) or HLS (HTTP fragments + ffmpeg transcode for codecs the
|
||||||
|
browser can't decode natively). Both are enabled by default — a fresh install
|
||||||
|
"just works" without editing the TOML. Disable surgically only if you have a
|
||||||
|
reason.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[downloads.webrtc]
|
||||||
|
enabled = true # master switch
|
||||||
|
trackers = ["wss://tracker.torrentclaw.com"] # signaling trackers
|
||||||
|
stun_servers = [ # NAT traversal
|
||||||
|
"stun:stun.l.google.com:19302",
|
||||||
|
"stun:stun1.l.google.com:19302",
|
||||||
|
]
|
||||||
|
turn_servers = [] # optional TURN relays
|
||||||
|
turn_user = ""
|
||||||
|
turn_pass = ""
|
||||||
|
|
||||||
|
[downloads.transcode]
|
||||||
|
enabled = true # master switch
|
||||||
|
hw_accel = "auto" # auto | none | nvenc | qsv | vaapi | videotoolbox
|
||||||
|
preset = "veryfast" # libx264 preset
|
||||||
|
video_bitrate = "" # e.g. "5M" caps -b:v; empty = engine fallback (5M)
|
||||||
|
audio_bitrate = "192k" # e.g. "128k", "192k", "256k"
|
||||||
|
max_height = 0 # 0 = no cap; e.g. 720 forces 720p max
|
||||||
|
max_concurrent = 2 # max simultaneous ffmpeg processes
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `[downloads.webrtc]`
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
|-----|------|---------|-------|
|
||||||
|
| `enabled` | bool | `true` | Browser↔daemon WebRTC peer for the in-browser P2P player. Disable to skip WebRTC tracker signalling (saves ~5MB RAM, blocks WebRTC streaming — HLS still works). |
|
||||||
|
| `trackers` | `[]string` | `["wss://tracker.torrentclaw.com"]` | Signaling trackers for peer discovery. |
|
||||||
|
| `stun_servers` | `[]string` | Google public STUN ×2 | ICE candidate gathering. |
|
||||||
|
| `turn_servers` | `[]string` | `[]` | Optional TURN relays for symmetric-NAT users. |
|
||||||
|
| `turn_user` / `turn_pass` | string | `""` | Credentials for authed TURN servers. Applied to all `turn_servers`. |
|
||||||
|
|
||||||
|
#### `[downloads.transcode]`
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
|-----|------|---------|-------|
|
||||||
|
| `enabled` | bool | `true` | Real-time HLS transcoding when source codec is browser-incompatible (HEVC, AV1, AC3, DTS). Requires `ffmpeg` + `ffprobe` on PATH. |
|
||||||
|
| `hw_accel` | string | `"auto"` | Hardware accel: `"auto"`, `"none"`, `"nvenc"` (NVIDIA), `"qsv"` (Intel), `"vaapi"` (Linux), `"videotoolbox"` (macOS). |
|
||||||
|
| `preset` | string | `"veryfast"` | libx264 preset. Slower preset = smaller files but higher CPU. Options: `ultrafast`, `superfast`, `veryfast`, `faster`, `fast`, `medium`, `slow`, `slower`, `veryslow`. |
|
||||||
|
| `video_bitrate` | string | `""` | E.g. `"5M"` caps `-b:v`. Empty falls back to the engine default (`5M`). |
|
||||||
|
| `audio_bitrate` | string | `"192k"` | E.g. `"128k"`, `"256k"`. |
|
||||||
|
| `max_height` | int | `0` | `0` = no cap. E.g. `720` forces 720p max — useful on weak GPUs. |
|
||||||
|
| `max_concurrent` | int | `2` | Max simultaneous ffmpeg processes. Increase if hosting multiple users on a beefy box. |
|
||||||
|
|
||||||
|
If `transcode.enabled = true` but `ffmpeg` / `ffprobe` aren't on PATH, the
|
||||||
|
daemon logs a warning at startup and HLS sessions are rejected at runtime
|
||||||
|
with a clear error — install ffmpeg or set `enabled = false`.
|
||||||
|
|
||||||
### Environment variables
|
### Environment variables
|
||||||
|
|
||||||
Environment variables override config file values:
|
Environment variables override config file values:
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,9 @@ type LibraryConfig struct {
|
||||||
AllowDelete bool `toml:"allow_delete"` // allow web UI to request file deletion from disk
|
AllowDelete bool `toml:"allow_delete"` // allow web UI to request file deletion from disk
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default returns a Config with sensible defaults.
|
// Default returns a Config with sensible defaults. Used both for fresh
|
||||||
|
// installs (no config file yet) and as the baseline for Load — fields not
|
||||||
|
// present in the user's TOML keep their Default() value.
|
||||||
func Default() Config {
|
func Default() Config {
|
||||||
return Config{
|
return Config{
|
||||||
Auth: AuthConfig{
|
Auth: AuthConfig{
|
||||||
|
|
@ -117,7 +119,7 @@ func Default() Config {
|
||||||
MaxConcurrent: 3,
|
MaxConcurrent: 3,
|
||||||
StreamPort: 11818,
|
StreamPort: 11818,
|
||||||
WebRTC: WebRTCConfig{
|
WebRTC: WebRTCConfig{
|
||||||
Enabled: false,
|
Enabled: true,
|
||||||
Trackers: []string{"wss://tracker.torrentclaw.com"},
|
Trackers: []string{"wss://tracker.torrentclaw.com"},
|
||||||
STUNServers: []string{"stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302"},
|
STUNServers: []string{"stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302"},
|
||||||
},
|
},
|
||||||
|
|
@ -125,7 +127,6 @@ func Default() Config {
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
HWAccel: "auto",
|
HWAccel: "auto",
|
||||||
Preset: "veryfast",
|
Preset: "veryfast",
|
||||||
VideoBitrate: "5M",
|
|
||||||
AudioBitrate: "192k",
|
AudioBitrate: "192k",
|
||||||
MaxConcurrent: 2,
|
MaxConcurrent: 2,
|
||||||
},
|
},
|
||||||
|
|
@ -167,67 +168,66 @@ func Load(path string) (Config, error) {
|
||||||
return cfg, fmt.Errorf("read config: %w", err)
|
return cfg, fmt.Errorf("read config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := toml.Unmarshal(data, &cfg); err != nil {
|
meta, err := toml.Decode(string(data), &cfg)
|
||||||
|
if err != nil {
|
||||||
return cfg, fmt.Errorf("parse config: %w", err)
|
return cfg, fmt.Errorf("parse config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-apply defaults for zero values that should have defaults
|
applyDefaults(&cfg, meta)
|
||||||
if cfg.Auth.APIURL == "" {
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyDefaults fills in sensible defaults for keys that the user did not
|
||||||
|
// define in the TOML file. We use MetaData (rather than zero-value checks) so
|
||||||
|
// that explicitly setting a field to its zero value (e.g. `enabled = false`)
|
||||||
|
// is respected — only truly missing keys get defaulted. This lets a fresh
|
||||||
|
// install work out of the box for streaming without forcing every user to
|
||||||
|
// edit the TOML, while still letting power users disable features.
|
||||||
|
func applyDefaults(cfg *Config, meta toml.MetaData) {
|
||||||
|
if !meta.IsDefined("auth", "api_url") {
|
||||||
cfg.Auth.APIURL = "https://torrentclaw.com"
|
cfg.Auth.APIURL = "https://torrentclaw.com"
|
||||||
}
|
}
|
||||||
if cfg.Download.PreferredMethod == "" {
|
if !meta.IsDefined("downloads", "preferred_method") {
|
||||||
cfg.Download.PreferredMethod = "auto"
|
cfg.Download.PreferredMethod = "auto"
|
||||||
}
|
}
|
||||||
if cfg.Download.MaxConcurrent == 0 {
|
if !meta.IsDefined("downloads", "max_concurrent") {
|
||||||
cfg.Download.MaxConcurrent = 3
|
cfg.Download.MaxConcurrent = 3
|
||||||
}
|
}
|
||||||
if cfg.General.Country == "" {
|
if !meta.IsDefined("downloads", "stream_port") {
|
||||||
cfg.General.Country = "US"
|
|
||||||
}
|
|
||||||
if cfg.Download.StreamPort == 0 {
|
|
||||||
cfg.Download.StreamPort = 11818
|
cfg.Download.StreamPort = 11818
|
||||||
}
|
}
|
||||||
// Re-apply WebRTC defaults only when the user enabled WebRTC but didn't
|
if !meta.IsDefined("general", "country") {
|
||||||
// supply trackers/STUN — leave both empty if disabled to keep config diffs clean.
|
cfg.General.Country = "US"
|
||||||
if cfg.Download.WebRTC.Enabled {
|
}
|
||||||
if len(cfg.Download.WebRTC.Trackers) == 0 {
|
|
||||||
cfg.Download.WebRTC.Trackers = []string{"wss://tracker.torrentclaw.com"}
|
if !meta.IsDefined("downloads", "webrtc", "enabled") {
|
||||||
}
|
cfg.Download.WebRTC.Enabled = true
|
||||||
if len(cfg.Download.WebRTC.STUNServers) == 0 {
|
}
|
||||||
cfg.Download.WebRTC.STUNServers = []string{
|
if !meta.IsDefined("downloads", "webrtc", "trackers") {
|
||||||
"stun:stun.l.google.com:19302",
|
cfg.Download.WebRTC.Trackers = []string{"wss://tracker.torrentclaw.com"}
|
||||||
"stun:stun1.l.google.com:19302",
|
}
|
||||||
}
|
if !meta.IsDefined("downloads", "webrtc", "stun_servers") {
|
||||||
}
|
cfg.Download.WebRTC.STUNServers = []string{
|
||||||
// Auto-enable transcode for the in-browser player when WebRTC is on
|
"stun:stun.l.google.com:19302",
|
||||||
// AND the user hasn't explicitly opted out. The struct's Enabled
|
"stun:stun1.l.google.com:19302",
|
||||||
// field is `false` for legacy configs because the field didn't
|
|
||||||
// exist when they were written; we treat "no transcode section at
|
|
||||||
// all" as "use defaults" rather than "off".
|
|
||||||
tc := &cfg.Download.Transcode
|
|
||||||
if !tc.Enabled && tc.HWAccel == "" && tc.Preset == "" && tc.VideoBitrate == "" {
|
|
||||||
tc.Enabled = true
|
|
||||||
}
|
|
||||||
if tc.Enabled {
|
|
||||||
if tc.HWAccel == "" {
|
|
||||||
tc.HWAccel = "auto"
|
|
||||||
}
|
|
||||||
if tc.Preset == "" {
|
|
||||||
tc.Preset = "veryfast"
|
|
||||||
}
|
|
||||||
if tc.VideoBitrate == "" {
|
|
||||||
tc.VideoBitrate = "5M"
|
|
||||||
}
|
|
||||||
if tc.AudioBitrate == "" {
|
|
||||||
tc.AudioBitrate = "192k"
|
|
||||||
}
|
|
||||||
if tc.MaxConcurrent == 0 {
|
|
||||||
tc.MaxConcurrent = 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg, nil
|
if !meta.IsDefined("downloads", "transcode", "enabled") {
|
||||||
|
cfg.Download.Transcode.Enabled = true
|
||||||
|
}
|
||||||
|
if !meta.IsDefined("downloads", "transcode", "hw_accel") {
|
||||||
|
cfg.Download.Transcode.HWAccel = "auto"
|
||||||
|
}
|
||||||
|
if !meta.IsDefined("downloads", "transcode", "preset") {
|
||||||
|
cfg.Download.Transcode.Preset = "veryfast"
|
||||||
|
}
|
||||||
|
if !meta.IsDefined("downloads", "transcode", "audio_bitrate") {
|
||||||
|
cfg.Download.Transcode.AudioBitrate = "192k"
|
||||||
|
}
|
||||||
|
if !meta.IsDefined("downloads", "transcode", "max_concurrent") {
|
||||||
|
cfg.Download.Transcode.MaxConcurrent = 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save writes config to the default or specified path using atomic write.
|
// Save writes config to the default or specified path using atomic write.
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,76 @@ func TestParseSpeed(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadMinimalTOMLAppliesStreamingDefaults(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
path := filepath.Join(tmp, "config.toml")
|
||||||
|
|
||||||
|
// Minimal config — only auth + agent. Nothing about webrtc / transcode.
|
||||||
|
os.WriteFile(path, []byte(`[auth]
|
||||||
|
api_key = "tc_minimal"
|
||||||
|
|
||||||
|
[agent]
|
||||||
|
id = "agent-uuid"
|
||||||
|
name = "Test"
|
||||||
|
`), 0o644)
|
||||||
|
|
||||||
|
cfg, err := Load(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebRTC should be on by default for fresh installs.
|
||||||
|
if !cfg.Download.WebRTC.Enabled {
|
||||||
|
t.Error("WebRTC.Enabled should default to true when [downloads.webrtc] is absent")
|
||||||
|
}
|
||||||
|
if len(cfg.Download.WebRTC.Trackers) == 0 {
|
||||||
|
t.Error("WebRTC.Trackers should default to torrentclaw tracker when absent")
|
||||||
|
}
|
||||||
|
if len(cfg.Download.WebRTC.STUNServers) == 0 {
|
||||||
|
t.Error("WebRTC.STUNServers should default to public STUN list when absent")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transcode should be on by default.
|
||||||
|
if !cfg.Download.Transcode.Enabled {
|
||||||
|
t.Error("Transcode.Enabled should default to true when [downloads.transcode] is absent")
|
||||||
|
}
|
||||||
|
if cfg.Download.Transcode.HWAccel != "auto" {
|
||||||
|
t.Errorf("Transcode.HWAccel = %q, want auto", cfg.Download.Transcode.HWAccel)
|
||||||
|
}
|
||||||
|
if cfg.Download.Transcode.Preset != "veryfast" {
|
||||||
|
t.Errorf("Transcode.Preset = %q, want veryfast", cfg.Download.Transcode.Preset)
|
||||||
|
}
|
||||||
|
if cfg.Download.Transcode.MaxConcurrent != 2 {
|
||||||
|
t.Errorf("Transcode.MaxConcurrent = %d, want 2", cfg.Download.Transcode.MaxConcurrent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadRespectsExplicitlyDisabledStreaming(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
path := filepath.Join(tmp, "config.toml")
|
||||||
|
|
||||||
|
// User explicitly opted out of webrtc + transcode. Defaults must NOT
|
||||||
|
// override them — that would silently re-enable features the user disabled.
|
||||||
|
os.WriteFile(path, []byte(`[downloads.webrtc]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[downloads.transcode]
|
||||||
|
enabled = false
|
||||||
|
`), 0o644)
|
||||||
|
|
||||||
|
cfg, err := Load(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Download.WebRTC.Enabled {
|
||||||
|
t.Error("WebRTC.Enabled = true, want false (user explicitly disabled)")
|
||||||
|
}
|
||||||
|
if cfg.Download.Transcode.Enabled {
|
||||||
|
t.Error("Transcode.Enabled = true, want false (user explicitly disabled)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadInvalidTOML(t *testing.T) {
|
func TestLoadInvalidTOML(t *testing.T) {
|
||||||
tmp := t.TempDir()
|
tmp := t.TempDir()
|
||||||
path := filepath.Join(tmp, "config.toml")
|
path := filepath.Join(tmp, "config.toml")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue