diff options
Diffstat (limited to '.bin/music')
-rwxr-xr-x | .bin/music | 174 |
1 files changed, 104 insertions, 70 deletions
@@ -1,70 +1,104 @@ -#!/bin/sh - - -MUSIC_DIR="${HOME}/music" -MUSIC_LIST= -MUSIC_FILE= -MUSIC_YT_OPTIONS= - -log() { - echo "[${0} ] ${@}" -} - -main() { - # arguments - while getopts "c:" arg; do - case "${arg}" in - c) - MUSIC_FILE="${OPTARG}" - ;; - h) - exit 0 - ;; - esac - done - - # ensure parameters are correct - [ ! -f "${MUSIC_FILE}" ] && exit 1 - - - while read -r line; do - - # skip comments - line=$(echo ${line} | grep -v -e "^$" -e "^#") - [ -z "${line}" ] && continue - - # retrieve playlist params - url=$(echo "${line}" | cut -d " " -f 1) - dir=$(echo "${line}" | cut -d " " -f 2) - - dir="${MUSIC_DIR}/${dir}" - - [ -d "${dir}" ] && - log "${dir}: directory already exists" && - continue - - mkdir "${dir}" - log "${dir} ${url}: download" - - yt-dlp --rm-cache-dir >/dev/null - yt-dlp \ - --extract-audio \ - --audio-format mp3 \ - --prefer-ffmpeg \ - --audio-quality 0 \ - --embed-thumbnail \ - --metadata-from-title "%(artist)s - %(title)s" \ - --no-warnings \ - --ignore-errors \ - --no-overwrites \ - --continue \ - --add-metadata \ - --user-agent "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \ - --output "${dir}/'%(title)s.%(ext)s'" \ - "${url}" - - - done < "${MUSIC_FILE}" -} - -main ${@} +#!/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()) |