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
|
#!/usr/bin/python3
import os
import sys
import yt_dlp
from dataclasses import dataclass
def _match_filter(info: dict, *, incomplete) -> str | None:
_duration = info.get("duration")
_duration_min = 60
if _duration and int(_duration) < _duration_min:
return "Duration too short: < _duration_min"
return None
@dataclass(frozen=True)
class Collection:
"""A music collection."""
title: str
links: frozenset[str]
def __eq__(self, other) -> bool:
if isinstance(other, Collection):
return self.title == other.title
raise NotImplementedError
def parse_raw_to_collections(raw_data: list[str]) -> frozenset[Collection]:
collections: set[Collection] = set()
_collection_data: list[str] = []
for index, line in enumerate(raw_data):
if line.startswith("#"):
continue
elif line == "" or index + 1 == len(raw_data):
if len(_collection_data) == 0:
continue
collections.add(
Collection(_collection_data[0], frozenset(_collection_data[1:]))
)
_collection_data.clear()
else:
_collection_data.append(line)
return frozenset(collections)
def get_ytdlp_options(output_dir: str) -> dict:
return {
"format": "bestaudio/best",
"match_filter": _match_filter,
"postprocessors": [
{
"key": "FFmpegExtractAudio",
#"preferredcodec": "m4a",
},
{
"key": "FFmpegMetadata",
"add_metadata": True,
},
{
"key": "EmbedThumbnail",
"already_have_thumbnail": False,
},
],
"outtmpl": f"{output_dir}/%(title)s.%(ext)s",
"restrictfilenames": True,
"ignoreerrors": True,
}
def download_collection(collection: Collection, parent_dir: str) -> None:
output_dir = os.path.join(parent_dir, collection.title)
if os.path.isdir(output_dir):
return
os.makedirs(output_dir, exist_ok=True)
with yt_dlp.YoutubeDL(get_ytdlp_options(output_dir)) as downloader:
downloader.download(collection.links)
def main() -> int:
# input handling
if len(sys.argv) != 2:
return 1
with open(sys.argv[1], "r") as file:
filedata = file.read().splitlines()
for collection in parse_raw_to_collections(filedata):
download_collection(collection, os.getcwd())
return 0
if __name__ == "__main__":
exit(main())
|