#include #include #include #include #include #include #include #include #include #include #include #include "config.hpp" #include "playlist.hpp" #include "video.hpp" using json = nlohmann::json; // This runs in shell, if put through a website interface it would allow for injection auto ytdlp(const std::string& args) -> bool { system(std::format("{} {}", BIN_YT_DLP, args).c_str()); return true; } auto metadata(const std::string& file, const std::string& artist, const std::string& album, size_t index) -> void { std::string name = std::filesystem::path(file).filename().string(); name = std::regex_replace(name, std::regex(".mp3"), ""); name.erase(0, 1); // Remove leading underscore system(std::format("{} " "-hide_banner -loglevel error " "-i \"{}\" " "-i \"{}/cover.jpg\" " "-map 0:0 " "-map 1:0 " "-c copy " "-id3v2_version 3 -metadata:s:v title=\"Album cover\" -metadata:s:v comment=\"Cover (front)\" " "-metadata title=\"{}\" " "-metadata artist=\"{}\" " "-metadata album=\"{}\" " "-metadata track=\"{}\" " "\"{}/{}.mp3\"", BIN_FFMPEG, file, TMP_DOWNLOAD_FOLDER, name, artist, album, index, TMP_DOWNLOAD_FOLDER, name) .c_str()); } auto cleanTmpDownloadFolder() -> bool { for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) { std::filesystem::remove_all(entry.path().string()); } return true; } auto downloadImageToTmpFolder(std::string url) -> bool { printf("Downloading image with url %s\n", url.c_str()); CURL* curl = curl_easy_init(); if (!curl) { printf("ERROR: Failed to initialize curl.\n"); return false; } // TODO Breaks album covers but this is needed for windows // url = std::regex_replace(url, std::regex("https"), "http"); auto outputPath = std::filesystem::path(TMP_DOWNLOAD_FOLDER) / "cover.jpg"; FILE* file = std::fopen(outputPath.string().c_str(), "wb"); if (!file) { curl_easy_cleanup(curl); printf("ERROR: Failed to create cover file.\n"); return false; } curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, nullptr); curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); CURLcode res = curl_easy_perform(curl); std::fclose(file); curl_easy_cleanup(curl); if (res != CURLE_OK) { printf("ERROR: %s\n", curl_easy_strerror(res)); return false; } return true; } auto downloadVideo(const std::string& url) -> bool { if (url == "") { printf("ERROR: Cannot download video with empty url\n"); return false; } ytdlp( std::format("--js-runtimes \"deno:{}\" " "--quiet " "-f \"bestaudio/best\" " "-x --audio-format mp3 " "-o \"{}/_%(title)s.%(ext)s\" " "{}", BIN_DENO, TMP_DOWNLOAD_FOLDER, url)); printf("Finished downloading video %s.\n", url.c_str()); return true; } auto writeAndReturnDataJson(const std::string& url) -> json { std::filesystem::remove("tmp/data.json"); ytdlp( std::format("--js-runtimes \"deno:{}\" " "--dump-single-json " "\"{}\" " "> tmp/data.json", BIN_DENO, url)); std::ifstream f("tmp/data.json"); if (!f) { printf("ERROR: Couldn't open data.json\n"); return false; } return json::parse(f); } auto moveAndSortTmpFilesToFolder(const std::string& folder, const std::string& artist, const std::string& album) -> bool { std::filesystem::path folderPath(folder); std::filesystem::path path = folderPath / artist / album; std::filesystem::create_directories(path); for (const auto& entry : std::filesystem::directory_iterator(path)) { std::filesystem::remove_all(entry.path().string()); } for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) { std::filesystem::rename(entry, path / entry.path().filename()); } return true; } auto autoDetermineRip(const std::string& musicFolder, const std::string& url) -> bool { if (std::regex_search(url, std::regex("youtube.com/playlist"))) { return Playlist(url).rip(musicFolder); } if (std::regex_search(url, std::regex("youtube.com/watch"))) { return Video(url).rip(musicFolder); } return false; } auto musicFolder() -> std::string { #ifdef _WIN32 const char* userProfile = std::getenv("USERPROFILE"); if (userProfile) return std::format("{}/Music", userProfile); #else const char* home = std::getenv("HOME"); if (home) return std::filesystem::path(home) / "Music"; #endif return ""; } auto writeMetadataToTmpFolder(const std::string& artist, const std::string& album, const std::string& coverUrl) -> void { downloadImageToTmpFolder(coverUrl); size_t index = 1; for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) { if (std::filesystem::path(entry.path().string()).filename().string() == "cover.jpg") continue; metadata(entry.path().string(), artist, album, index); printf("Finished writting metadata for %s.\n", entry.path().string().c_str()); std::filesystem::remove(entry.path().string()); index++; } }