aboutsummaryrefslogtreecommitdiff
path: root/index.js
blob: 657f471d2ae5bdebe84a5353e5372aaa2c15a5cc (plain)
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
'use strict';

(async () => {
    function die(message, code) {
        if (message) { console.log(message) }
        if (code) {
            process.exit(code);
        } else {
            process.exit(0);
        }
    }

    String.prototype.isBase64 = function () {
        return Buffer.from(this, 'base64').toString('base64').split("=")[0].substring(0, Buffer.from(this, 'base64').toString('base64').split("=")[0].length - 1) === this.substring(0, this.length - 1);
    }

    const exec = require('child_process').execFileSync;
    const fs = require('fs');
    const axios = require('axios');

    if (process.argv[2] !== undefined) {
        global.id = process.argv[2];
        if (!id.isBase64() || id.length !== 34) {
            die("Invalid playlist ID");
        }
    } else {
        console.log("Please specify playlist ID");
    }

    console.log("Preparing folder structure...");
    if (!fs.existsSync("./_youtoo")) fs.mkdirSync("./_youtoo");
    if (!fs.existsSync("./_youtoo/Dumps")) fs.mkdirSync("./_youtoo/Dumps");
    if (!fs.existsSync("./_youtoo/Result")) fs.mkdirSync("./_youtoo/Result");
    if (!fs.existsSync("./_youtoo/Metadata")) fs.mkdirSync("./_youtoo/Metadata");
    if (!fs.existsSync("./_youtoo/AlbumArts")) fs.mkdirSync("./_youtoo/AlbumArts");

    console.log("Gathering playlist data...");
    let playlist = JSON.parse(exec("yt-dlp", [ "--flat-playlist", "--dump-single-json", "https://www.youtube.com/playlist?list=" + id ]).toString());
    fs.writeFileSync("./_youtoo/Dumps/PlaylistDataRaw.json", JSON.stringify(playlist, null, 4));
    let songs = playlist.entries.map(i => i.id);
    fs.writeFileSync("./_youtoo/Dumps/PlaylistDataList.json", JSON.stringify(songs, null, 4));
    console.log("Found " + songs.length + " songs");

    for (let song of songs) {
        if (!fs.existsSync("./_youtoo/Result/" + song + ".mp3")) {
            console.log("[" + song + "] Gathering metadata...");
            let metadata = JSON.parse(exec("yt-dlp", [ "--skip-download", "-j", "https://www.youtube.com/watch?v=" + song ]).toString());
            fs.writeFileSync("./_youtoo/Dumps/MetadataRaw" + song + ".json", JSON.stringify(metadata, null, 4));

            console.log("[" + song + "] Parsing metadata...");
            let partists = (metadata.track ?? metadata.alt_title ?? metadata.title).replace(/^([:\"'a-zA-Z0-9 .\-*\_\/\\,&]*)-(.*)/gm, "$1") !== (metadata.track ?? metadata.alt_title ?? metadata.title) ? (metadata.track ?? metadata.alt_title ?? metadata.title).replace(/^([:\"'a-zA-Z0-9 .\-*\_\/\\,&]*)-(.*)/gm, "$1") : "";
            let partists2 = (metadata.track ?? metadata.alt_title ?? metadata.title).replace(/(.*)\((feat|ft|with)(\.|:| |)(| |)([:\"'a-zA-Z0-9 .\-*\_\/\\,&]*)\)(.*)/gmi, "$5") !== (metadata.track ?? metadata.alt_title ?? metadata.title) ? (metadata.track ?? metadata.alt_title ?? metadata.title).replace(/(.*)\((feat|ft|with)(\.|:| |)(| |)([:\"'a-zA-Z0-9 .\-*\_\/\\,&]*)\)(.*)/gmi, "$5") : "";
            let rmeta = {
                video: metadata.title,
                title: (metadata.track ?? metadata.alt_title ?? metadata.title).replace(/^([:\"'a-zA-Z0-9 .\-*\_\/\\,&]*)-( |)(.*)(\[(.*)\]|)/gm, "$3").replace(/(.*)\((feat|ft|with)(\.|:| |)(| |)([:\"'a-zA-Z0-9 .\-*\_\/\\,&]*)\)(.*)/gmi, "$1").replace(/(.*)\[(.*)\](.*)/gm, "$1").replace(/(.*)\((from|in|by)(\.|:| |)(| |)([:\"'a-zA-Z0-9 .\-*\_\/\\,&]*)\)(.*)/gmi, "$1").trim(),
                date: metadata.release_year ?? (metadata.upload_date.substring(0, 4) - 1 +1),
                album: metadata.album ?? ((metadata.track ?? metadata.alt_title ?? metadata.title).replace(/^([:\"'a-zA-Z0-9 .\-*\_\/\\,&]*)-( |)(.*)(\[(.*)\]|)/gm, "$3").replace(/(.*)\((feat|ft|with)(\.|:| |)(| |)([:\"'a-zA-Z0-9 .\-*\_\/\\,&]*)\)(.*)/gmi, "$1").replace(/(.*)\[(.*)\](.*)/gm, "$1").replace(/(.*)\((from|in|by)(\.|:| |)(| |)([:\"'a-zA-Z0-9 .\-*\_\/\\,&]*)\)(.*)/gmi, "$1").trim()),
                potentialArtists: partists,
                potentialArtists2: partists2,
                artists: Array.from(new Set([...(metadata.artist ?? metadata.creator ?? metadata.uploader).replace(/(, |,|and|And|&)/gm, "|").split("|").map(i => i.trim()), ...partists.replace(/(, |,|and|And|&)/gm, "|").split("|").map(i => i.trim()), ...partists2.replace(/(, |,|and|And|&)/gm, "|").split("|").map(i => i.trim())])).filter(i => i.trim() !== ""),
            }
            fs.writeFileSync("./_youtoo/Metadata/" + song + ".json", JSON.stringify(rmeta, null, 4));
            console.log("[" + song + "] Downloading audio...");
            exec("yt-dlp", [ "-f", "bestaudio[ext=m4a]", "-x", "-o", "./_youtoo/_audio.m4a", "--audio-format", "m4a", "https://www.youtube.com/watch?v=" + song ], { stdio: "inherit" });
            console.log("[" + song + "] Downloading thumbnail...");
            fs.writeFileSync("./_youtoo/_audio.webp", (await axios.get(metadata.thumbnail, { responseType: "arraybuffer" })).data);
            console.log("[" + song + "] Processing thumbnail...");
            exec("magick", [ "-define", "jpeg:size=1024x1024", "./_youtoo/_audio.webp", "-thumbnail", "512x512^", "-gravity", "center", "-extent", "512x512", "./_youtoo/_audio.jpg" ]);
            console.log("[" + song + "] Processing audio...");
            exec("ffmpeg", [ "-y", "-i", "./_youtoo/_audio.m4a", "-metadata", "artist=" + rmeta.artists.join(", ") + "", "-metadata", "title=" + rmeta.title, "-metadata", "album=" + rmeta.album, "-metadata", "publisher=YouTube", "-metadata", "copyright=© " + rmeta.date.toString() + " " + rmeta.artists[0], "-metadata", "date=" + rmeta.date.toString(), "-metadata", "encoded_by=" + require('./package.json').name + "/" + require('./package.json').version, "./_youtoo/_audio.1.mp3" ], { stdio: "inherit" });
            exec("ffmpeg", [ "-y", "-i", "./_youtoo/_audio.1.mp3", "-i", "./_youtoo/_audio.jpg", "-map", "0:0", "-map", "1:0", "-c", "copy", "-id3v2_version", "3", "-metadata:s:v", "title=Album cover", "-metadata:s:v", "comment=Cover (front)", "./_youtoo/_audio.mp3" ], { stdio: "inherit" });
            fs.renameSync("./_youtoo/_audio.mp3", "./_youtoo/Result/" + song + ".mp3");
            fs.renameSync("./_youtoo/_audio.jpg", "./_youtoo/AlbumArts/" + song + ".jpg");
            console.log("[" + song + "] Cleaning up...");
            if (fs.existsSync("./_youtoo/_audio.1.mp3")) { fs.rmSync("./_youtoo/_audio.1.mp3"); }
            if (fs.existsSync("./_youtoo/_audio.jpg")) { fs.rmSync("./_youtoo/_audio.jpg"); }
            if (fs.existsSync("./_youtoo/_audio.webp")) { fs.rmSync("./_youtoo/_audio.webp"); }
            if (fs.existsSync("./_youtoo/_audio.m4a")) { fs.rmSync("./_youtoo/_audio.m4a"); }
        } else {
            console.log("[" + song + "] Song already in destination");
        }
    }

    console.log("Deleting removed songs...")
    for (let file of fs.readdirSync("./_youtoo/Result")) {
        let song = file.substring(0, file.length - 4);
        if (!songs.includes(song)) {
            console.log("[" + song + "] Deleting...");
            fs.unlinkSync("./_youtoo/Result/" + file);
            if (fs.existsSync("./_youtoo/Metadata/" + song + ".json")) fs.unlinkSync("./_youtoo/Metadata/" + song + ".json");
        }
    }

    console.log("Cleaning up...")
    if (fs.existsSync("./_youtoo/Dumps")) { fs.rmSync("./_youtoo/Dumps", { recursive: true }); }
})()