summaryrefslogtreecommitdiffstats
path: root/.bin/music
diff options
context:
space:
mode:
Diffstat (limited to '.bin/music')
-rwxr-xr-x.bin/music174
1 files changed, 104 insertions, 70 deletions
diff --git a/.bin/music b/.bin/music
index 6ef5ef8..a14aae9 100755
--- a/.bin/music
+++ b/.bin/music
@@ -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())
remember that computers suck.