~cytrogen/ytm

ref: f884dc989c8c945af42bb3bcbc3a442529d2e027 ytm/ytdlp.go -rw-r--r-- 2.4 KiB
f884dc98 — HallowDem Initial commit: YTM - YouTube Music Downloader a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"

	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"
)

func isAcceptableError(err error) bool {
	if exitErr, ok := err.(*exec.ExitError); ok {
		return exitErr.ExitCode() == 1
	}
	return false
}

func runYtdlp(args []string) ([]byte, error) {
	cmd := exec.Command(cfg.YtdlpPath, args...)
	out, err := cmd.Output()
	if err != nil && !isAcceptableError(err) {
		return nil, fmt.Errorf("yt-dlp error: %w", err)
	}

	decoder := simplifiedchinese.GBK.NewDecoder()
	decoded, _, decErr := transform.Bytes(decoder, out)
	if decErr != nil {
		return out, nil
	}
	return decoded, nil
}

func runYtdlpStreaming(args []string) error {
	cmd := exec.Command(cfg.YtdlpPath, args...)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	cmd.Stderr = os.Stderr

	if err := cmd.Start(); err != nil {
		return err
	}

	reader := transform.NewReader(stdout, simplifiedchinese.GBK.NewDecoder())
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}

	err = cmd.Wait()
	if err != nil && !isAcceptableError(err) {
		return fmt.Errorf("yt-dlp error: %w", err)
	}
	return nil
}

func ListPlaylistTracks(olakID string) ([]Track, error) {
	out, err := runYtdlp([]string{
		"--flat-playlist",
		"--print", "%(playlist_index)s\t%(title)s\t%(id)s",
		"https://www.youtube.com/playlist?list=" + olakID,
	})
	if err != nil {
		return nil, err
	}

	var tracks []Track
	lines := strings.Split(strings.TrimSpace(string(out)), "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		parts := strings.SplitN(line, "\t", 3)
		if len(parts) < 3 {
			continue
		}
		idx, _ := strconv.Atoi(parts[0])
		tracks = append(tracks, Track{
			Index:   idx,
			Title:   parts[1],
			VideoID: parts[2],
		})
	}
	return tracks, nil
}

func DownloadPlaylist(olakID, outputDir string) error {
	return runYtdlpStreaming([]string{
		"-x", "--audio-quality", "0",
		"--embed-thumbnail", "--embed-metadata",
		"-o", outputDir + "/%(playlist_index)s. %(title)s.%(ext)s",
		"https://www.youtube.com/playlist?list=" + olakID,
	})
}

func DownloadSingle(videoID, outputDir string) error {
	return runYtdlpStreaming([]string{
		"-x", "--audio-quality", "0",
		"--embed-thumbnail", "--embed-metadata",
		"-o", outputDir + "/%(title)s.%(ext)s",
		"https://www.youtube.com/watch?v=" + videoID,
	})
}