Parent directory

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}