downloader.go
1980 bytes
1package downloader
2
3import (
4 "context"
5 "fmt"
6 "log/slog"
7 "os/exec"
8 "path/filepath"
9)
10
11const defaultResolution = "1080"
12
13type Downloader interface {
14 Download(ctx context.Context, videoID string, title string, outputDir string, resolution string) (string, error)
15 ValidateInstallation() error
16}
17
18type ytdlpDownloader struct {
19 log *slog.Logger
20 cookiesFilePath string
21}
22
23func NewYtdlpDownloader(log *slog.Logger, cookiesFilePath string) *ytdlpDownloader {
24 return &ytdlpDownloader{log: log, cookiesFilePath: cookiesFilePath}
25}
26
27func (d *ytdlpDownloader) ValidateInstallation() error {
28 cmd := exec.Command("yt-dlp", "--version")
29 if err := cmd.Run(); err != nil {
30 return fmt.Errorf("yt-dlp not found or not executable: %w", err)
31 }
32 return nil
33}
34
35func formatSelector(resolution string) string {
36 return fmt.Sprintf(
37 "bestvideo[height<=%s]+bestaudio/best[height<=%s]",
38 resolution, resolution,
39 )
40}
41
42func (d *ytdlpDownloader) Download(
43 ctx context.Context,
44 videoID string,
45 title string,
46 outputDir string,
47 resolution string,
48) (string, error) {
49 if resolution == "" {
50 resolution = defaultResolution
51 }
52 outputTemplate := filepath.Join(outputDir, fmt.Sprintf("%s.%%(ext)s", videoID))
53 args := []string{
54 "--format", formatSelector(resolution),
55 "--output", outputTemplate,
56 "--no-playlist",
57 "--no-warnings",
58 "--quiet",
59 "--progress",
60 }
61 if d.cookiesFilePath != "" {
62 args = append(args, "--cookies", d.cookiesFilePath)
63 }
64 args = append(args, fmt.Sprintf("https://www.youtube.com/watch?v=%s", videoID))
65
66 cmd := exec.CommandContext(ctx, "yt-dlp", args...)
67
68 output, err := cmd.CombinedOutput()
69 if err != nil {
70 d.log.Error("yt-dlp failed", "output", string(output), "error", err)
71 return "", fmt.Errorf("download failed: %w", err)
72 }
73
74 matches, err := filepath.Glob(filepath.Join(outputDir, fmt.Sprintf("%s.*", videoID)))
75 if err != nil || len(matches) == 0 {
76 return "", fmt.Errorf("downloaded file not found")
77 }
78
79 return matches[0], nil
80}