#include "playlist.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "config.hpp" #include "utils.hpp" using json = nlohmann::json; Playlist::Playlist(const std::string& playlistUrl) : playlistUrl(playlistUrl) {} auto Playlist::rip(const std::string& musicFolder) -> bool { printf("Writting to data.json\n"); json data = writeAndReturnDataJson(playlistUrl); if (!download()) { printf("ERROR: Failed to download playlsit\n"); return false; } if (!writeMetadata(data)) { printf("ERROR: Failed to write metatdata for playlsit\n"); return false; } std::string artist; std::string album; aristAndAlbumName(data, artist, album); if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) { printf("ERROR: Failed to move files to music folder\n"); return false; } return true; } auto Playlist::download() -> bool { std::vector videoUrls = urlsFromPlaylist(playlistUrl); if (videoUrls.empty()) { printf("ERROR: Tried to download empty playlist, %s\n", playlistUrl.c_str()); return false; } if (!cleanTmpDownloadFolder()) { printf("ERROR: Playlist download couldn't clean tmp download folder\n"); return false; } playlistUrl = std::regex_replace(playlistUrl, std::regex("music.youtube"), "www.youtube"); int count = 0; for (size_t i = 0; i < videoUrls.size(); i += MAX_THREADS) { std::vector> futures; size_t end = std::min(i + MAX_THREADS, videoUrls.size()); for (size_t j = i; j < end; ++j) { futures.push_back(std::async(std::launch::async, [&videoUrls, j]() { downloadVideo(videoUrls[j]); })); } for (auto& future : futures) { future.get(); count++; printf("Finished [%d/%d] in playlist %s\n", count, (int)videoUrls.size(), playlistUrl.c_str()); } } printf("Finished downloading playlist.\n"); return true; } auto Playlist::writeMetadata(const json& data) -> bool { std::string artist; std::string album; aristAndAlbumName(data, artist, album); printf("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str()); printf("Finding album cover.\n"); std::string coverUrl = data["thumbnails"][1]["url"]; downloadImageToTmpFolder(coverUrl); printf("Writting data to files.\n"); size_t index = 1; for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) { if (std::filesystem::path(entry.path()).filename().string() == "cover.jpg") continue; metadata(entry.path(), artist, album, index); printf("Finished writting metadata for %s.\n", entry.path().c_str()); std::filesystem::remove(entry.path()); index++; } printf("Finished writting metadata for %s by %s.\n", album.c_str(), artist.c_str()); return true; } auto Playlist::aristAndAlbumName(const nlohmann::json& data, std::string& artist, std::string& album) -> bool { artist = data["entries"][0]["uploader"]; artist = std::regex_replace(artist, std::regex(" - Topic"), ""); album = data["title"]; album = std::regex_replace(album, std::regex("Album - "), ""); return true; } auto Playlist::urlsFromPlaylist(const std::string& url) -> std::vector { std::vector urls; std::string urlsFileLocation = "tmp/urls.txt"; std::filesystem::remove(urlsFileLocation); ytdlp( std::format("--js-runtimes \"deno:bin/linux/deno\" " "--quiet " "--flat-playlist " "--print-to-file \"%(webpage_url)s\" " "{} \"{}\"", urlsFileLocation, url)); std::ifstream f(urlsFileLocation); if (!f.is_open()) { printf("ERROR: Couldn't open urls.txt\n"); return urls; } std::string line; while (std::getline(f, line)) { urls.push_back(line); } if (urls.empty()) printf("ERROR: Couldn't find any urls for playlist %s\n", url.c_str()); return urls; }