diff --git a/modules/nixos/tasks/multimedia-archive.nix b/modules/nixos/tasks/multimedia-archive.nix deleted file mode 100644 index 806b8595..00000000 --- a/modules/nixos/tasks/multimedia-archive.nix +++ /dev/null @@ -1,206 +0,0 @@ -{ config, options, lib, pkgs, ... }: - -let - cfg = config.tasks.multimedia-archive; - mountName = "/mnt/archives"; -in { - options.tasks.multimedia-archive.enable = - lib.mkEnableOption "multimedia archiving setup"; - - config = lib.mkIf cfg.enable (let - yt-dlp-args = [ - # Make a global list of successfully downloaded videos as a cache for yt-dlp. - "--download-archive ${config.services.yt-dlp.archivePath}/videos" - - # No overwriting of videos and related files. - "--no-force-overwrites" - - # Embed metadata in the file. - "--write-info-json" - - # Embed chapter markers, if possible. - "--embed-chapters" - - # Write the subtitle file. - "--write-subs" - - # Write the description in a separate file. - "--write-description" - - # The global output for all of the jobs. - "--output '%(uploader,artist,creator|Unknown)s/%(release_date>%F,upload_date>%F|Unknown)s-%(title)s.%(ext)s'" - - # Select only the most optimal format for my usecases. - "--format '(webm,mkv,mp4)[height<=?1280]'" - - # Prefer MKV whenever possible for video formats. - "--merge-output-format mkv" - - # Don't download any videos that are originally live streams. - "--match-filters '!was_live'" - - # Prefer Vorbis when audio-only downloads are used. - "--audio-format vorbis" - "--audio-quality 2" - ]; - yt-dlp-archive-variant = pkgs.writeScriptBin "yt-dlp-archive-variant" '' - ${pkgs.yt-dlp}/bin/yt-dlp ${lib.escapeShellArgs yt-dlp-args} - ''; - in { - environment.systemPackages = [ yt-dlp-archive-variant ]; - fileSystems."${mountName}" = { - device = "/dev/disk/by-uuid/6ba86a30-5fa4-41d9-8354-fa8af0f57f49"; - fsType = "btrfs"; - noCheck = true; - options = [ - # These are btrfs-specific mount options which can found in btrfs.5 - # manual page. - "subvol=@" - "noatime" - "compress=zstd:9" - "space_cache=v2" - - # General mount options from mount.5 manual page. - "noauto" - "nofail" - "user" - - # See systemd.mount.5 and systemd.automount.5 manual page for more - # details. - "x-systemd.automount" - "x-systemd.idle-timeout=2" - "x-systemd.device-timeout=2" - ]; - }; - - services.yt-dlp = { - enable = true; - archivePath = "${mountName}/yt-dlp-service"; - - # This is applied on all jobs. It is best to be minimal as much as - # possible for this. - extraArgs = yt-dlp-args; - - jobs = { - arts = { - urls = [ - "https://www.youtube.com/channel/UCjdHbo8_vh3rxQ-875XGkvw" # 3DSage - "https://www.youtube.com/channel/UCHv_hNLkxqlcY20MwVyayfw" # Ali Bahabadi - "https://www.youtube.com/c/boroCG" # BoroCG - "https://www.youtube.com/c/DavidRevoy" # David Revoy - "https://www.youtube.com/channel/UCGMyyn2FdEFcDfP1wQRh5lQ" # Erindale - "https://www.youtube.com/c/Jazza" # Jazza - "https://www.youtube.com/channel/UCcBnT6LsxANZjUWqpjR8Jpw" # Marcello Barenghi - "https://www.youtube.com/c/ronillust" # ronillust - ]; - startAt = "Friday"; - extraArgs = [ - "--playlist-end 20" # Only check the first N videos. - ]; - }; - - compsci = { - urls = [ - "https://www.youtube.com/channel/UC_mYaQAE6-71rjSN6CeCA-g" # NeetCode - "https://www.youtube.com/c/ThePrimeagen" # ThePrimeagen - "https://www.youtube.com/c/EasyTheory" # EasyTheory - "https://www.youtube.com/c/K%C3%A1rolyZsolnai" # Two Minute Papers - "https://www.youtube.com/c/TheCodingTrain" # TheCodingTrain - ]; - startAt = "Thursday"; - extraArgs = [ - "--playlist-end 20" # Only check the first N videos. - ]; - }; - - cooking = { - urls = [ - "https://www.youtube.com/channel/UCJHA_jMfCvEnv-3kRjTCQXw" # Babish Culinary Universe - "https://www.youtube.com/channel/UCb5QRUn5w8_g0j8QVaWzcjQ" # BORE.D - "https://www.youtube.com/channel/UCzqbfYjQmf9nLQPMxVgPhiA" # emmymade - "https://www.youtube.com/channel/UCgmOd6sRQRK7QoSazOfaIjQ" # Emma's Goodies - "https://www.youtube.com/channel/UCcp9uRaBwInxl_SZqGRksDA" # Hidamari Cooking - "https://www.youtube.com/channel/UCvQrjgLj841wiQAKDgtKFOw" # Ninong Ry - "https://www.youtube.com/channel/UCekQr9znsk2vWxBo3YiLq2w" # You Suck at Cooking - "https://www.youtube.com/channel/UCUAKaXyq2hVBCph1LOUtuqg" # 집밥요리 Home Cooking - ]; - startAt = "Sunday"; - extraArgs = [ - "--playlist-end 15" # Check the first N videos. - ]; - }; - }; - }; - - services.archivebox = { - enable = true; - archivePath = "${mountName}/archivebox-service"; - withDependencies = true; - webserver.enable = true; - - jobs = { - arts = { - links = [ - "https://www.davidrevoy.com/feed/rss" - "https://librearts.org/index.xml" - ]; - startAt = "monthly"; - }; - - computer = { - links = [ - "https://blog.mozilla.org/en/feed/" - "https://distill.pub/rss.xml" - "https://drewdevault.com/blog/index.xml" - "https://fasterthanli.me/index.xml" - "https://jvns.ca/atom.xml" - "https://www.bytelab.codes/rss/" - "https://www.collabora.com/feed" - "https://www.jntrnr.com/atom.xml" - "https://yosoygames.com.ar/wp/?feed=rss" - "https://simblob.blogspot.com/feeds/posts/default" - ]; - startAt = "weekly"; - }; - - projects = { - links = [ - "https://veloren.net/rss.xml" - "https://guix.gnu.org/feeds/blog.atom" - "https://fedoramagazine.org/feed/" - "https://nixos.org/blog/announcements-rss.xml" - ]; - # Practically every 14 days. - startAt = "Mon *-*-1/14"; - }; - }; - }; - - services.gallery-dl = { - enable = true; - archivePath = "${mountName}/gallery-dl-service"; - - extraArgs = [ - # Record all downloaded files in an archive file. - "--download-archive ${config.services.gallery-dl.archivePath}/photos" - - # Write metadata to separate JSON files. - "--write-metadata" - ]; - - jobs = { - arts = { - urls = [ - "https://www.deviantart.com/xezeno" # Xezeno - #"https://www.pixiv.net/en/users/60562229" # Ravioli - "https://www.artstation.com/kuvshinov_ilya" # Ilya Kuvshinov - "https://www.artstation.com/meiipng" # Meiiart - "https://www.artstation.com/bassem_wageeh" # Bassem wageeh - "https://hyperjerk.newgrounds.com" # HyperJerk - ]; - startAt = "weekly"; - }; - }; - }; - }); -} diff --git a/modules/nixos/tasks/multimedia-archive/README.adoc b/modules/nixos/tasks/multimedia-archive/README.adoc new file mode 100644 index 00000000..a17b6eec --- /dev/null +++ b/modules/nixos/tasks/multimedia-archive/README.adoc @@ -0,0 +1,40 @@ += Multimedia archiving +:toc: + +More like offline delivery, really. +Just wait for the task to complete and you have your videos, pictures, music, and whatever questionable files you want to download. +It's a nice offline repository for it especially that internet usually randomly disconnects so that's nice while I still have something working, yeah? + + + + +== Integrating with Newpipe subscriptions + +In this task, I usually just download videos from YouTube. +While I could note every preferred creator manually, I could automate them by getting a list of subscriptions from my Newpipe config which I use surprisingly more often than I thought. +This is done by running the link:./convert-newpipe-db-to-json[`./convert-newpipe-db-to-json`] script and specifying the exported Newpipe database (as a ZIP file). + +[CAUTION] +==== +Please don't run the task with all of the subscriptions. +You should select only a few categories and clean them up. +==== + +[source, sh] +---- +./convert-newpipe-db-to-json ~/Downloads/NewPipeData-20220714_185126.zip +---- + +You can run the script with the `-h` flag for more information. +There are nifty things you can do with the script. +Such as the following code block which you can interactively select which folders to export. + +[source, sh] +---- +./convert-newpipe-db-to-json ~/Downloads/NewPipeData-20220714_185126.zip --list-categories \ + | fzf --multi --prompt "Choose which categories to export " \ + | ./convert-newpipe-db-to-json ~/Downloads/NewPipeData-20220714_185126.zip -o ./newpipe-db.json +---- + +Remember the larger the list, the larger the chances for a throttling. +Thus, it is heavily encouraged that you clean up your list (and/or get good at organizing your categories) before activating the updated version. diff --git a/modules/nixos/tasks/multimedia-archive/convert-newpipe-db-to-json b/modules/nixos/tasks/multimedia-archive/convert-newpipe-db-to-json new file mode 100755 index 00000000..931c66a6 --- /dev/null +++ b/modules/nixos/tasks/multimedia-archive/convert-newpipe-db-to-json @@ -0,0 +1,106 @@ +#!/usr/bin/env nix-shell +#! nix-shell -i python3 -p python3 + +# This script is used for generating a JSON object from a Newpipe database to +# be used for multimedia archive task (i.e., +# `config.tasks.multimedia-archive`). + +import argparse +import sys +import sqlite3 +import json +import re +import os +import shutil +import tempfile +import fileinput +from pathlib import Path + + +def kebab_case(string): + string = string.lower() + string = re.sub("\s+", "-", string) + string = re.sub("[^a-zA-Z0-9-]", "", string) + string = re.sub("-+", "-", string) + string = re.sub("^-|-$", "", string) + return string + + +def extract_categories_from_db(db_file, categories): + with sqlite3.connect(db_file) as db: + db.row_factory = sqlite3.Row + query = ''' + SELECT subscriptions.name AS name, subscriptions.url AS url, feed_group.name AS tag + FROM subscriptions + INNER JOIN feed_group_subscription_join AS subs_join + INNER JOIN feed_group + ON subs_join.subscription_id = subscriptions.uid AND feed_group.uid = subs_join.group_id + ORDER BY name COLLATE NOCASE; + ''' + + data = { kebab_case(category) : [] for category in categories } + + for row in db.execute(query): + tag = row["tag"] + if tag in categories: + data[kebab_case(tag)].append({ "url": row["url"], "name": row["name"] }) + + return data + + +def list_categories(db_file): + with sqlite3.connect(db_file) as db: + query = ''' + SELECT name FROM feed_group ORDER BY name; + ''' + data = [] + for row in db.execute(query): + data.append(row[0]) + return data + + +def extract_db(newpipe_archive): + tmpdir = tempfile.mkdtemp(suffix="convert-newpipe-db") + + shutil.unpack_archive(newpipe_archive, tmpdir) + return Path(tmpdir) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("newpipe_db", metavar="NEWPIPE_DB", help="Newpipe database file (as a zip file) exported straight from the app.") + parser.add_argument("categories", metavar="CATEGORIES", nargs="*", help="A list of categories to be extracted. If absent, it will extract with all categories.") + parser.add_argument("--list-categories", "-l", action="store_true", help="List all categories from the database.") + parser.add_argument("--output", "-o", action="store", metavar="FILE", help="If present, store the output in the given file") + + args = parser.parse_args() + newpipe_archive = args.newpipe_db + tmpdir = extract_db(newpipe_archive) + db_file = tmpdir / "newpipe.db" + + if args.list_categories: + for category in list_categories(db_file): + print(category) + else: + categories = [] + if not sys.stdin.isatty(): + for line in sys.stdin: + categories.append(line.strip()) + + if len(args.categories) > 0: + categories = args.categories + elif len(categories) == 0: + categories = list_categories(db_file) + + data = extract_categories_from_db(db_file, categories) + + output_file = args.output + if output_file: + with open(output_file, mode="w", encoding="UTF-8") as file: + json.dump(data, file, sort_keys=True, indent=2) + else: + print(json.dumps(data, sort_keys=True, indent=2)) + + shutil.rmtree(tmpdir) + +# vi:ft=python:ts=4 diff --git a/modules/nixos/tasks/multimedia-archive/default.nix b/modules/nixos/tasks/multimedia-archive/default.nix new file mode 100644 index 00000000..b9cc25a0 --- /dev/null +++ b/modules/nixos/tasks/multimedia-archive/default.nix @@ -0,0 +1,185 @@ +{ config, options, lib, pkgs, ... }: + +let + cfg = config.tasks.multimedia-archive; + mountName = "/mnt/archives"; +in +{ + options.tasks.multimedia-archive.enable = + lib.mkEnableOption "multimedia archiving setup"; + + config = lib.mkIf cfg.enable ( + let + yt-dlp-args = [ + # Make a global list of successfully downloaded videos as a cache for yt-dlp. + "--download-archive ${config.services.yt-dlp.archivePath}/videos" + + # No overwriting of videos and related files. + "--no-force-overwrites" + + # Embed metadata in the file. + "--write-info-json" + + # Embed chapter markers, if possible. + "--embed-chapters" + + # Write the subtitle file. + "--write-subs" + + # Write the description in a separate file. + "--write-description" + + # The global output for all of the jobs. + "--output '%(uploader,artist,creator|Unknown)s/%(release_date>%F,upload_date>%F|Unknown)s-%(title)s.%(ext)s'" + + # Select only the most optimal format for my usecases. + "--format '(webm,mkv,mp4)[height<=?1280]'" + + # Prefer MKV whenever possible for video formats. + "--merge-output-format mkv" + + # Don't download any videos that are originally live streams. + "--match-filters '!was_live'" + + # Prefer Vorbis when audio-only downloads are used. + "--audio-format vorbis" + "--audio-quality 2" + ]; + yt-dlp-archive-variant = pkgs.writeScriptBin "yt-dlp-archive-variant" '' + ${pkgs.yt-dlp}/bin/yt-dlp ${lib.escapeShellArgs yt-dlp-args} + ''; + + # Given an attribute set of URLs, create a yt-dlp service config that does the following. + create-yt-dlp-service-config = newpipe-db: + let + days = [ "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday" ]; + categories = lib.zipListsWith + (index: category: { inherit index; data = category; }) + (lib.lists.range 1 (lib.length (lib.attrValues newpipe-db))) + (lib.mapAttrsToList (name: value: { inherit name; subscriptions = value; }) newpipe-db); + jobsList = builtins.map + (category: { + name = category.data.name; + value = { + urls = builtins.map (subscription: subscription.url) category.data.subscriptions; + startAt = lib.elemAt days (lib.mod category.index (lib.length days)); + extraArgs = [ + "--playlist-end 20" # Only check the last 20 videos. + ]; + }; + }) + categories; + in + lib.listToAttrs jobsList; + in + { + environment.systemPackages = [ yt-dlp-archive-variant ]; + fileSystems."${mountName}" = { + device = "/dev/disk/by-uuid/6ba86a30-5fa4-41d9-8354-fa8af0f57f49"; + fsType = "btrfs"; + noCheck = true; + options = [ + # These are btrfs-specific mount options which can found in btrfs.5 + # manual page. + "subvol=@" + "noatime" + "compress=zstd:9" + "space_cache=v2" + + # General mount options from mount.5 manual page. + "noauto" + "nofail" + "user" + + # See systemd.mount.5 and systemd.automount.5 manual page for more + # details. + "x-systemd.automount" + "x-systemd.idle-timeout=2" + "x-systemd.device-timeout=2" + ]; + }; + + services.yt-dlp = { + enable = true; + archivePath = "${mountName}/yt-dlp-service"; + + # This is applied on all jobs. It is best to be minimal as much as + # possible for this. + extraArgs = yt-dlp-args; + + jobs = create-yt-dlp-service-config (builtins.fromJSON (builtins.readFile ./newpipe-db.json)); + }; + + services.archivebox = { + enable = true; + archivePath = "${mountName}/archivebox-service"; + withDependencies = true; + webserver.enable = true; + + jobs = { + arts = { + links = [ + "https://www.davidrevoy.com/feed/rss" + "https://librearts.org/index.xml" + ]; + startAt = "monthly"; + }; + + computer = { + links = [ + "https://blog.mozilla.org/en/feed/" + "https://distill.pub/rss.xml" + "https://drewdevault.com/blog/index.xml" + "https://fasterthanli.me/index.xml" + "https://jvns.ca/atom.xml" + "https://www.bytelab.codes/rss/" + "https://www.collabora.com/feed" + "https://www.jntrnr.com/atom.xml" + "https://yosoygames.com.ar/wp/?feed=rss" + "https://simblob.blogspot.com/feeds/posts/default" + ]; + startAt = "weekly"; + }; + + projects = { + links = [ + "https://veloren.net/rss.xml" + "https://guix.gnu.org/feeds/blog.atom" + "https://fedoramagazine.org/feed/" + "https://nixos.org/blog/announcements-rss.xml" + ]; + # Practically every 14 days. + startAt = "Mon *-*-1/14"; + }; + }; + }; + + services.gallery-dl = { + enable = true; + archivePath = "${mountName}/gallery-dl-service"; + + extraArgs = [ + # Record all downloaded files in an archive file. + "--download-archive ${config.services.gallery-dl.archivePath}/photos" + + # Write metadata to separate JSON files. + "--write-metadata" + ]; + + jobs = { + arts = { + urls = [ + "https://www.deviantart.com/xezeno" # Xezeno + #"https://www.pixiv.net/en/users/60562229" # Ravioli + "https://www.artstation.com/kuvshinov_ilya" # Ilya Kuvshinov + "https://www.artstation.com/meiipng" # Meiiart + "https://www.artstation.com/bassem_wageeh" # Bassem wageeh + "https://hyperjerk.newgrounds.com" # HyperJerk + ]; + startAt = "weekly"; + }; + }; + }; + } + ); +} diff --git a/modules/nixos/tasks/multimedia-archive/newpipe-db.json b/modules/nixos/tasks/multimedia-archive/newpipe-db.json new file mode 100644 index 00000000..ceb4e9f6 --- /dev/null +++ b/modules/nixos/tasks/multimedia-archive/newpipe-db.json @@ -0,0 +1,518 @@ +{ + "3d-modelling": [ + { + "name": "3DSage", + "url": "https://www.youtube.com/channel/UCjdHbo8_vh3rxQ-875XGkvw" + }, + { + "name": "Andy Front", + "url": "https://www.youtube.com/channel/UCmNewVWrKOn667okSlM0OlA" + }, + { + "name": "Blender Secrets", + "url": "https://www.youtube.com/channel/UCp7EwodJcppc6GqiRcnCpOw" + }, + { + "name": "CG Boost", + "url": "https://www.youtube.com/channel/UCWWybvw9jnpOdJq_6wTHryA" + }, + { + "name": "CG Fast Track", + "url": "https://www.youtube.com/channel/UCsvgY1GWmJwvk3o6UeXVxAg" + }, + { + "name": "CGMatter", + "url": "https://www.youtube.com/channel/UCy1f4m64dwCwk8CBZ_vHfPg" + }, + { + "name": "CrossMind Studio", + "url": "https://www.youtube.com/channel/UCHihootMqyGz175gqOPahtw" + }, + { + "name": "Derek Elliott", + "url": "https://www.youtube.com/channel/UCk7IufzS4r8v76NeWR6A3dg" + }, + { + "name": "Ducky 3D", + "url": "https://www.youtube.com/channel/UCuNhGhbemBkdflZ1FGJ0lUQ" + }, + { + "name": "Erindale", + "url": "https://www.youtube.com/channel/UCGMyyn2FdEFcDfP1wQRh5lQ" + }, + { + "name": "Grant Abbitt", + "url": "https://www.youtube.com/channel/UCZFUrFoqvqlN8seaAeEwjlw" + }, + { + "name": "henning", + "url": "https://www.youtube.com/channel/UCidwqcHWZf1qBnqoUx9N6rA" + }, + { + "name": "Hoolopee", + "url": "https://www.youtube.com/channel/UCE_gw5ybJdGAcyZFy6TfScw" + }, + { + "name": "IanHubert", + "url": "https://www.youtube.com/channel/UCbmxZRQk-X0p-TOxd6PEYJA" + }, + { + "name": "Jhanrell 3D", + "url": "https://www.youtube.com/channel/UCsl5Po2vPkFOmi27EL8qFGg" + }, + { + "name": "Markom3D", + "url": "https://www.youtube.com/channel/UCdlNVsQys37ETeTDqQqiHFQ" + }, + { + "name": "Polyfjord", + "url": "https://www.youtube.com/channel/UC1MmrnDaPnNa55twYBiH4NA" + }, + { + "name": "Shonzo", + "url": "https://www.youtube.com/channel/UCVwc3XV94ifVOonbFP6-7tw" + }, + { + "name": "t3ssel8r", + "url": "https://www.youtube.com/channel/UCIjUIjWig0r5DIixQrt6A3A" + }, + { + "name": "William Landgren", + "url": "https://www.youtube.com/channel/UC_v-Rg-FYBUfkF4GLcMDEcg" + }, + { + "name": "YanSculpts", + "url": "https://www.youtube.com/channel/UCfjswDVU0XHyBN7UFG0Mi5Q" + } + ], + "art": [ + { + "name": "Aki-Anyway", + "url": "https://www.youtube.com/channel/UCldLZEAgmkEO0vep4OvabPQ" + }, + { + "name": "Ali Bahabadi", + "url": "https://www.youtube.com/channel/UCHv_hNLkxqlcY20MwVyayfw" + }, + { + "name": "Blackthornprod", + "url": "https://www.youtube.com/channel/UC9Z1XWw1kmnvOOFsj6Bzy2g" + }, + { + "name": "Bobby Duke Arts", + "url": "https://www.youtube.com/channel/UCSC1HqVmTaE4Shn32ihbC7w" + }, + { + "name": "Brandon James Greer", + "url": "https://www.youtube.com/channel/UCC26K7LTSrJK0BPAUyyvtQg" + }, + { + "name": "Challenge Clay Craft", + "url": "https://www.youtube.com/channel/UCkKyQpfQCClQLJreqiORliw" + }, + { + "name": "Corridor Crew", + "url": "https://www.youtube.com/channel/UCSpFnDQr88xCZ80N-X7t0nQ" + }, + { + "name": "creosfera", + "url": "https://www.youtube.com/channel/UCAHVy9XS5Fz1WMRHIzAodUQ" + }, + { + "name": "D_NOT_So_Good_Artist", + "url": "https://www.youtube.com/channel/UC505T67IfBzIQRyDOen3seg" + }, + { + "name": "Danny Casale", + "url": "https://www.youtube.com/channel/UClNHWmlNIgEXLotLtlY2mLw" + }, + { + "name": "David Revoy", + "url": "https://www.youtube.com/channel/UCnAbNwJjusY7zQ__sQyJlSA" + }, + { + "name": "Denis Godyna", + "url": "https://www.youtube.com/channel/UCTJiWCp2fhugnic2tCTqsLg" + }, + { + "name": "Design Theory", + "url": "https://www.youtube.com/channel/UCdgUN8rX3SEb9L7FDub3I6A" + }, + { + "name": "dillongoo", + "url": "https://www.youtube.com/channel/UC-B06UJxJ20HYv15lzrm9mA" + }, + { + "name": "Dollarwang", + "url": "https://www.youtube.com/channel/UCQf7HdP7F_UoA8TfjEfzr4Q" + }, + { + "name": "Emanuele Colombo", + "url": "https://www.youtube.com/channel/UCSvf1bh9DEL7glMzX1aYQUA" + }, + { + "name": "Erindale", + "url": "https://www.youtube.com/channel/UCGMyyn2FdEFcDfP1wQRh5lQ" + }, + { + "name": "Henry Segerman", + "url": "https://www.youtube.com/channel/UC4zzTEL5tuIgGMvzjk1Ozbg" + }, + { + "name": "Jazza", + "url": "https://www.youtube.com/channel/UCHu2KNu6TtJ0p4hpSW7Yv7Q" + }, + { + "name": "JujuArts", + "url": "https://www.youtube.com/channel/UCuTcJu9dZM8ZdgCKOxQLXuw" + }, + { + "name": "Logos By Nick", + "url": "https://www.youtube.com/channel/UCEQXp_fcqwPcqrzNtWJ1w9w" + }, + { + "name": "Lucifer King", + "url": "https://www.youtube.com/channel/UCIu9_S0kuKXgA2h5_egIx-A" + }, + { + "name": "Marcello Barenghi", + "url": "https://www.youtube.com/channel/UCcBnT6LsxANZjUWqpjR8Jpw" + }, + { + "name": "Marco Bucci", + "url": "https://www.youtube.com/channel/UCsDxB-CSMQ0Vu_hTag7-2UQ" + }, + { + "name": "ronillust", + "url": "https://www.youtube.com/channel/UCAaXLLoXnRF8EPUy5b5b6Vw" + }, + { + "name": "Sinix Design", + "url": "https://www.youtube.com/channel/UCUQTqWAaSzhAKRanOpes1nA" + }, + { + "name": "t3ssel8r", + "url": "https://www.youtube.com/channel/UCIjUIjWig0r5DIixQrt6A3A" + } + ], + "comsci": [ + { + "name": "Andreas Kling", + "url": "https://www.youtube.com/channel/UC3ts8coMP645hZw9JSD3pqQ" + }, + { + "name": "Bisqwit", + "url": "https://www.youtube.com/channel/UCKTehwyGCKF-b2wo0RKwrcg" + }, + { + "name": "Bits inside by Ren\u00e9 Rebe", + "url": "https://www.youtube.com/channel/UCJLLl6AraX1POemgLfhirwg" + }, + { + "name": "carykh", + "url": "https://www.youtube.com/channel/UC9z7EZAbkphEMg0SP7rw44A" + }, + { + "name": "CodeParade", + "url": "https://www.youtube.com/channel/UCrv269YwJzuZL3dH5PCgxUw" + }, + { + "name": "Computerphile", + "url": "https://www.youtube.com/channel/UC9-y-6csu5WGm29I7JiwpnA" + }, + { + "name": "Creel", + "url": "https://www.youtube.com/channel/UCq7dxy_qYNEBcHqQVCbc20w" + }, + { + "name": "CS50", + "url": "https://www.youtube.com/channel/UCcabW7890RKJzL968QWEykA" + }, + { + "name": "Easy Theory", + "url": "https://www.youtube.com/channel/UC3VY6RTXegnoSD_q446oBdg" + }, + { + "name": "Junferno", + "url": "https://www.youtube.com/channel/UCRb6Mw3fJ6OFzp-cB9X29aA" + }, + { + "name": "NeetCode", + "url": "https://www.youtube.com/channel/UC_mYaQAE6-71rjSN6CeCA-g" + }, + { + "name": "The Coding Train", + "url": "https://www.youtube.com/channel/UCvjgXvBlbQiydffZU7m1_aw" + }, + { + "name": "Two Minute Papers", + "url": "https://www.youtube.com/c/K%C3%A1rolyZsolnai" + }, + { + "name": "ThePrimeagen", + "url": "https://www.youtube.com/channel/UC8ENHE5xdFSwx71u3fDH5Xw" + } + ], + "food": [ + { + "name": "Adam Ragusea", + "url": "https://www.youtube.com/channel/UC9_p50tH3WmMslWRWKnM7dQ" + }, + { + "name": "Apron", + "url": "https://www.youtube.com/channel/UCgzJrXg7oh7lj-bMC-pzkgw" + }, + { + "name": "Babish Culinary Universe", + "url": "https://www.youtube.com/channel/UCJHA_jMfCvEnv-3kRjTCQXw" + }, + { + "name": "BORED", + "url": "https://www.youtube.com/channel/UCb5QRUn5w8_g0j8QVaWzcjQ" + }, + { + "name": "Cookrate - Cakes", + "url": "https://www.youtube.com/channel/UCy3QtOQ7NiSEOs7JO-7vq7A" + }, + { + "name": "Emma's Goodies", + "url": "https://www.youtube.com/channel/UCgmOd6sRQRK7QoSazOfaIjQ" + }, + { + "name": "emmymade", + "url": "https://www.youtube.com/channel/UCzqbfYjQmf9nLQPMxVgPhiA" + }, + { + "name": "Ethan Chlebowski", + "url": "https://www.youtube.com/channel/UCDq5v10l4wkV5-ZBIJJFbzQ" + }, + { + "name": "HidaMari Cooking", + "url": "https://www.youtube.com/channel/UCcp9uRaBwInxl_SZqGRksDA" + }, + { + "name": "Judy Ann's Kitchen", + "url": "https://www.youtube.com/channel/UC5xdS3lFApjvOs9zdys3eOw" + }, + { + "name": "Kirbyyy", + "url": "https://www.youtube.com/channel/UCyC964gYH7eV4_icDGg8qVw" + }, + { + "name": "Made With Lau", + "url": "https://www.youtube.com/channel/UCsIF9vk-I_PV1P-ShDFA84A" + }, + { + "name": "Nino's Home", + "url": "https://www.youtube.com/channel/UCKetFmtqdh-kn915crdf72A" + }, + { + "name": "Ninong Ry", + "url": "https://www.youtube.com/channel/UCvQrjgLj841wiQAKDgtKFOw" + }, + { + "name": "Pinoy Easy Recipes", + "url": "https://www.youtube.com/channel/UCk5KE4BdmU32sdV4kvkAv5w" + }, + { + "name": "You Suck At Cooking", + "url": "https://www.youtube.com/channel/UCekQr9znsk2vWxBo3YiLq2w" + }, + { + "name": "\uc9d1\ubc25\uc694\ub9ac Home Cooking", + "url": "https://www.youtube.com/channel/UCUAKaXyq2hVBCph1LOUtuqg" + }, + { + "name": "\ud478\ub4dc\ud0b9\ub364 Food Kingdom", + "url": "https://www.youtube.com/channel/UC4BfinFCS1o6t1tAsl0RVWQ" + } + ], + "music": [ + { + "name": "500L/g", + "url": "https://www.youtube.com/channel/UCjZjUymRDAhp9c1rb0X6aww" + }, + { + "name": "acrouzet", + "url": "https://www.youtube.com/channel/UClv1kZDpIA9LcXPYY4KTU-w" + }, + { + "name": "AJR", + "url": "https://www.youtube.com/channel/UCQ5w3fSomzziZfO7neK7eAg" + }, + { + "name": "ALAMAT", + "url": "https://www.youtube.com/channel/UCOnUfJpp-Fg8X2TnuH_JD7w" + }, + { + "name": "Andre Antunes", + "url": "https://www.youtube.com/channel/UC8zTlrhQ0w1-TZjc2-jdcag" + }, + { + "name": "Asian Shoegaze", + "url": "https://www.youtube.com/channel/UCWubfmD-UY92kgCct3PXvZQ" + }, + { + "name": "Ayase / YOASOBI", + "url": "https://www.youtube.com/channel/UCvpredjG93ifbCP1Y77JyFA" + }, + { + "name": "Beyond The Guitar", + "url": "https://www.youtube.com/channel/UC8LgDpDsFXiwYupTihnTF5g" + }, + { + "name": "Boid", + "url": "https://www.youtube.com/channel/UCp8BU3mHY3Vw9WBPLjAJVcQ" + }, + { + "name": "BOZESTYLE", + "url": "https://www.youtube.com/channel/UCm5j5Ls1w4EQsZ_9GUT7mXg" + }, + { + "name": "Bulby", + "url": "https://www.youtube.com/channel/UCz6zvgkf6eKpgqlUZQstOtQ" + }, + { + "name": "CAP'STONE", + "url": "https://soundcloud.com/capcom-sound" + }, + { + "name": "Chip Jockey", + "url": "https://www.youtube.com/channel/UCfbi12o4I5f27w13Mkqv7Sg" + }, + { + "name": "Dylan Tallchief", + "url": "https://www.youtube.com/channel/UCIu2Fj4x_VMn2dgSB1bFyQA" + }, + { + "name": "FalKKonE", + "url": "https://www.youtube.com/channel/UChAHYPBvyaQIpjyTSdQhOMQ" + }, + { + "name": "Forrest Brazeal", + "url": "https://www.youtube.com/channel/UCt5LsaDWTEBY7FThtp37LfQ" + }, + { + "name": "GaMetal", + "url": "https://www.youtube.com/channel/UCK9Hl6LXPaooxMTvsOutw3A" + }, + { + "name": "George Collier", + "url": "https://www.youtube.com/channel/UCigygyPkHm07o-wQvkET7Og" + }, + { + "name": "HALIDONMUSIC", + "url": "https://www.youtube.com/channel/UCyOfqgtsQaM3S-VZnsYnHjQ" + }, + { + "name": "insaneintherainmusic", + "url": "https://www.youtube.com/channel/UC_OtnV-9QZmBj6oWBelMoZw" + }, + { + "name": "John Tay", + "url": "https://www.youtube.com/channel/UCbaO2SkJlbWwQyFCEPgLLow" + }, + { + "name": "Kiichi Kobayashi", + "url": "https://www.youtube.com/channel/UCQMNXjJN-LdNSkUblrxPpCg" + }, + { + "name": "King Gnu official YouTube channel", + "url": "https://www.youtube.com/channel/UCkB8HnJSDSJ2hkLQFUc-YrQ" + }, + { + "name": "losprimerosVIIVI", + "url": "https://www.youtube.com/channel/UCMSVWxNp1lkEGpDClzm8Qvw" + }, + { + "name": "Maximix", + "url": "https://www.youtube.com/channel/UCnvKCS1NzLmzOd4-LynA_jw" + }, + { + "name": "MayTree", + "url": "https://www.youtube.com/channel/UC3mY2SKYhPjqImtBBXsR6_Q" + }, + { + "name": "Pan Piano", + "url": "https://www.youtube.com/channel/UCI7ktPB6toqucpkkCiolwLg" + }, + { + "name": "RichaadEB", + "url": "https://www.youtube.com/channel/UCPM1bCbT-dVAHAEIpUUpVLQ" + }, + { + "name": "Ruscel Torres", + "url": "https://www.youtube.com/channel/UCjo880FeW792wHeNlaUXMjQ" + }, + { + "name": "Shady Cicada", + "url": "https://www.youtube.com/channel/UC-90KuSWRVLImW4xHWFYMnQ" + }, + { + "name": "SID", + "url": "https://www.youtube.com/channel/UC7jHTm2dDkhckKyOWUavuXg" + }, + { + "name": "Tee Lopes", + "url": "https://www.youtube.com/channel/UC3Va-8NnzTuV-Yv-JlyuQsQ" + }, + { + "name": "The8BitDrummer", + "url": "https://www.youtube.com/channel/UCEHXNknwbsRu73QsakWIdzQ" + }, + { + "name": "\u30b2\u30b9\u306e\u6975\u307f\u4e59\u5973 Official YouTube Channel", + "url": "https://www.youtube.com/channel/UC0pHUMEOtul5NlaT-Rt-34w" + }, + { + "name": "\u548c\u306c\u304b", + "url": "https://www.youtube.com/channel/UCm7rYZ6xOw3m5GaTMuUJjVw" + } + ], + "random-stuff": [ + { + "name": "Atomic Shrimp", + "url": "https://www.youtube.com/channel/UCSl5Uxu2LyaoAoMMGp6oTJA" + }, + { + "name": "Cody'sLab", + "url": "https://www.youtube.com/channel/UCu6mSoMNzHQiBIOCkHUa2Aw" + }, + { + "name": "JK Brickworks", + "url": "https://www.youtube.com/channel/UCUaiGrBfRCaC6pL7ZnZjWbg" + }, + { + "name": "Junferno", + "url": "https://www.youtube.com/channel/UCRb6Mw3fJ6OFzp-cB9X29aA" + }, + { + "name": "LockPickingLawyer", + "url": "https://www.youtube.com/channel/UCm9K6rby98W8JigLoZOh6FQ" + }, + { + "name": "Nick Zammeti", + "url": "https://www.youtube.com/channel/UC3-0S7vXfwYY2jj5EkMpymA" + }, + { + "name": "Origami with Jo Nakashima", + "url": "https://www.youtube.com/channel/UC3ICcukYYeSn26KlCRnhOhA" + }, + { + "name": "Peter Knetter", + "url": "https://www.youtube.com/channel/UCNMyoMaXJZITZaRKCz7G23Q" + }, + { + "name": "Sabaton History", + "url": "https://www.youtube.com/channel/UCaG4CBbZih6nLzD08bTBGfw" + }, + { + "name": "Steve1989MREInfo", + "url": "https://www.youtube.com/channel/UC2I6Et1JkidnnbWgJFiMeHA" + }, + { + "name": "WAY OUT WEST with Sandra and Tim", + "url": "https://www.youtube.com/channel/UCSViWfOV4pEcYnzpV6w548Q" + } + ] +}