summaryrefslogtreecommitdiffstats
path: root/.bin/music
blob: a14aae9fb2f4c6cda749a7ded25c03c374e1191b (plain) (blame)
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())
remember that computers suck.