summaryrefslogblamecommitdiffstats
path: root/.bin/music
blob: a14aae9fb2f4c6cda749a7ded25c03c374e1191b (plain) (tree)







































































































                                                                                
#!/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.