refactor: remove container selection, extract helpers, add logging and tests
All checks were successful
CI / build (push) Successful in 51s

- Remove container picker UI, keyboard, settings, and ContainerOptions
  entirely — yt-dlp handles container format automatically
- Remove Container field from DownloadJob struct and related references
- Extract buildFormatList helper to deduplicate format list building
  between handleURLInput and handleBackStep
- Break runDownload into trackDownloadProgress + handleDownloadCompletion
- Add HTTP timeout (30s) to downloadFile helper
- Log applyQuota and AddHistory errors instead of discarding
- Log nil stepState warnings in all handler entry points
- Include job.StderrLog in download failure messages
- Add tests for buildFormatList, findFormatByID, determineMediaType,
  and buildFormatString
- Update README with search, DRM fallback, and quota feature docs
- Remove unused strings import from keyboard.go
This commit is contained in:
2026-06-25 23:41:26 +03:30
parent dfe946a41b
commit 37e7f918d0
10 changed files with 266 additions and 296 deletions

View File

@@ -265,28 +265,17 @@ func (c *Client) StartDownload(job *domain.DownloadJob, downloadDir string) (<-c
formatID := job.SelectedFormat.ID
var args []string
// Use explicit format string if set (video+audio combination)
// Use format string for the download — yt-dlp handles container choice
if job.FormatString != "" {
args = append(args, "-f", job.FormatString)
if job.Container != "" {
args = append(args, "--merge-output-format", job.Container)
}
} else {
// Build format string for merge
switch job.MediaType {
case domain.MediaTypeAudio:
args = append(args, "-f", formatID, "-x")
if job.Container != "" {
args = append(args, "--audio-format", job.Container)
}
case domain.MediaTypeVideo:
args = append(args, "-f", formatID)
case domain.MediaTypeVideoAudio:
// Download best video + best audio that match
args = append(args, "-f", formatID+"+bestaudio/best")
if job.Container != "" {
args = append(args, "--merge-output-format", job.Container)
}
}
}
@@ -296,7 +285,7 @@ func (c *Client) StartDownload(job *domain.DownloadJob, downloadDir string) (<-c
// Progress reporting using progress-template for machine-readable output
args = append(args, "--newline")
args = append(args, "--progress-template", "stderr:DLPROG|%(progress.percent)s|%(progress._speed_str)s|%(progress._eta_str)s")
args = append(args, "--progress-template", "stderr:DLPROG|%(progress.percent)s|%(progress.speed)s|%(progress.eta)s")
// Language selection if specified
if job.Language != "" {
@@ -527,17 +516,3 @@ func formatLabel(f domain.Format) string {
}
return f.ID
}
// ContainerOptions returns the container format options for a given media type.
func ContainerOptions(mt domain.MediaType) []string {
switch mt {
case domain.MediaTypeAudio:
return []string{"mp3", "m4a", "opus"}
case domain.MediaTypeVideo:
return []string{"mp4", "mkv", "webm"}
case domain.MediaTypeVideoAudio:
return []string{"mp4", "mkv"}
default:
return []string{"mp4"}
}
}

View File

@@ -52,37 +52,6 @@ func TestDeduplicateResolutions(t *testing.T) {
}
}
func TestContainerOptions(t *testing.T) {
tests := []struct {
mt domain.MediaType
want []string
}{
{domain.MediaTypeAudio, []string{"mp3", "m4a", "opus"}},
{domain.MediaTypeVideo, []string{"mp4", "mkv", "webm"}},
{domain.MediaTypeVideoAudio, []string{"mp4", "mkv"}},
}
for _, tt := range tests {
got := ContainerOptions(tt.mt)
if len(got) != len(tt.want) {
t.Errorf("ContainerOptions(%v) = %v, want %v", tt.mt, got, tt.want)
continue
}
for i, c := range got {
if c != tt.want[i] {
t.Errorf("ContainerOptions(%v)[%d] = %s, want %s", tt.mt, i, c, tt.want[i])
}
}
}
}
func TestContainerOptionsZeroValue(t *testing.T) {
got := ContainerOptions(domain.MediaType(0))
want := []string{"mp4"}
if len(got) != 1 || got[0] != want[0] {
t.Errorf("ContainerOptions(0) = %v, want %v", got, want)
}
}
func TestFormatLabel(t *testing.T) {
tests := []struct {
f domain.Format