diff --git a/src/content.hpp b/src/content.hpp new file mode 100644 index 0000000..c3bc69b --- /dev/null +++ b/src/content.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +class Content { + public: + virtual ~Content() = default; + virtual auto rip(const std::string& musicFolder) -> bool = 0; + virtual auto info(std::string& artist, std::string& title) -> int = 0; +}; diff --git a/src/main.cpp b/src/main.cpp index 458c906..7e131f5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,32 @@ +#include +#include #include #include +#include +#include #include +#include #include "config.hpp" +#include "content.hpp" +#include "playlist.hpp" #include "utils.hpp" +#include "video.hpp" + +auto createContent(const std::string& currentUrl) -> Content* { + if (std::regex_search(currentUrl, std::regex("youtube.com/playlist"))) { + return new Playlist(currentUrl); + } + + if (std::regex_search(currentUrl, std::regex("youtube.com/watch"))) { + return new Video(currentUrl); + } + + return nullptr; +} auto main(int argc, char* argv[]) -> int { - std::vector urls; + std::deque urls; printf( "Welcome to YTMusicDL26! You can use playlists or video from youtube or youtube music (youtube music is " @@ -31,7 +51,38 @@ auto main(int argc, char* argv[]) -> int { std::string musicDir = musicFolder(); std::filesystem::create_directories(TMP_DOWNLOAD_FOLDER); - for (const std::string& url : urls) { - autoDetermineRip(musicDir, url); + + // TODO quit if there are no urls + + std::string currentUrl = urls.front(); + Content* currentContent = createContent(currentUrl); + std::thread ripper{[¤tContent, &musicDir]() { currentContent->rip(musicDir); }}; + std::string currentArtist; + std::string currentAlbum; + + while (true) { + int percent = currentContent->info(currentArtist, currentAlbum); + + printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + printf("----------------------------------------------------------------------------------\n"); + printf("Ripping: %s by %s at %d percent.\n", currentAlbum.c_str(), currentArtist.c_str(), percent); + printf("%d left in queue.\n", (int)urls.size() - 1); + printf("----------------------------------------------------------------------------------\n"); + + if (percent == 100) { + ripper.join(); + delete currentContent; + urls.pop_front(); + + if (urls.empty()) { + break; + } + + currentUrl = urls.front(); + currentContent = createContent(currentUrl); + ripper = std::thread([¤tContent, &musicDir]() { currentContent->rip(musicDir); }); + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); } } diff --git a/src/playlist.cpp b/src/playlist.cpp index 51f94d9..c767fbd 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -22,14 +22,12 @@ Playlist::Playlist(const std::string& playlistUrl) { } auto Playlist::rip(const std::string& musicFolder) -> bool { - printf("Writting to data.json\n"); - json data = writeAndReturnDataJson(playlistUrl); + percent = 0; + currentArtist = "Finding..."; + currentAlbum = "Finding..."; - // Download the content as mp3 - if (!download()) { - printf("ERROR: Failed to download playlsit\n"); - return false; - } + writeToLog("Writting to data.json\n"); + json data = writeAndReturnDataJson(playlistUrl); // Get the name of the artist and album from data.json std::string artist = data["entries"][0]["uploader"]; @@ -37,38 +35,61 @@ auto Playlist::rip(const std::string& musicFolder) -> bool { std::string album = data["title"]; album = std::regex_replace(album, std::regex("Album - "), ""); - printf("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str()); + currentArtist = artist; + currentAlbum = album; - // Find the album cover from data.json - printf("Finding album cover.\n"); - std::string coverUrl = data["thumbnails"][1]["url"]; + percent = 30; - printf("Writting data to files.\n"); - writeMetadataToTmpFolder(artist, album, coverUrl); - - printf("Finished writting metadata for %s by %s.\n", album.c_str(), artist.c_str()); - - // Move the files into the users Music folder - if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) { - printf("ERROR: Failed to move files to music folder\n"); + // Download the content as mp3 + if (!download()) { + writeToLog("ERROR: Failed to download playlsit\n"); return false; } - printf("Finished moving files for %s by %s.\n", album.c_str(), artist.c_str()); + percent = 80; + + writeToLog("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str()); + + // Find the album cover from data.json + writeToLog("Finding album cover.\n"); + std::string coverUrl = data["thumbnails"][1]["url"]; + + writeToLog("Writting data to files.\n"); + writeMetadataToTmpFolder(artist, album, coverUrl); + + percent = 95; + + writeToLog("Finished writting metadata for %s by %s.\n", album.c_str(), artist.c_str()); + + // Move the files into the users Music folder + if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) { + writeToLog("ERROR: Failed to move files to music folder\n"); + return false; + } + + writeToLog("Finished moving files for %s by %s.\n", album.c_str(), artist.c_str()); + + percent = 100; return true; } +auto Playlist::info(std::string& artist, std::string& title) -> int { + artist = currentArtist; + title = currentAlbum; + return percent; +} + auto Playlist::download() -> bool { std::vector videoUrls = urlsFromPlaylist(playlistUrl); if (videoUrls.empty()) { - printf("ERROR: Tried to download empty playlist, %s\n", playlistUrl.c_str()); + writeToLog("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"); + writeToLog("ERROR: Playlist download couldn't clean tmp download folder\n"); return false; } @@ -88,11 +109,12 @@ auto Playlist::download() -> bool { for (auto& future : futures) { future.get(); count++; - printf("Finished [%d/%d] in playlist %s\n", count, (int)videoUrls.size(), playlistUrl.c_str()); + percent += 50 / videoUrls.size(); + writeToLog("Finished [%d/%d] in playlist %s\n", count, (int)videoUrls.size(), playlistUrl.c_str()); } } - printf("Finished downloading playlist.\n"); + writeToLog("Finished downloading playlist.\n"); return true; } @@ -113,7 +135,7 @@ auto Playlist::urlsFromPlaylist(const std::string& url) -> std::vector std::vector #include -class Playlist { +#include "content.hpp" + +class Playlist : public Content { std::string playlistUrl; + int percent = 0; + std::string currentArtist; + std::string currentAlbum; public: Playlist(const std::string& playlistUrl); auto rip(const std::string& musicFolder) -> bool; + auto info(std::string& artist, std::string& title) -> int; private: auto download() -> bool; diff --git a/src/utils.cpp b/src/utils.cpp index bacfe86..8f050a6 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -17,6 +17,20 @@ using json = nlohmann::json; +auto writeToLog(const char* format, ...) -> void { + FILE* file = fopen("log.txt", "a"); + if (!file) return; + + va_list args; + va_start(args, format); + + vfprintf(file, format, args); + fprintf(file, "\n"); + + va_end(args); + fclose(file); +} + // 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()); @@ -54,11 +68,11 @@ auto cleanTmpDownloadFolder() -> bool { } auto downloadImageToTmpFolder(std::string url) -> bool { - printf("Downloading image with url %s\n", url.c_str()); + writeToLog("Downloading image with url %s\n", url.c_str()); CURL* curl = curl_easy_init(); if (!curl) { - printf("ERROR: Failed to initialize curl.\n"); + writeToLog("ERROR: Failed to initialize curl.\n"); return false; } @@ -68,7 +82,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool { FILE* file = std::fopen(outputPath.string().c_str(), "wb"); if (!file) { curl_easy_cleanup(curl); - printf("ERROR: Failed to create cover file.\n"); + writeToLog("ERROR: Failed to create cover file.\n"); return false; } @@ -83,7 +97,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool { curl_easy_cleanup(curl); if (res != CURLE_OK) { - printf("ERROR: %s\n", curl_easy_strerror(res)); + writeToLog("ERROR: %s\n", curl_easy_strerror(res)); return false; } @@ -92,7 +106,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool { auto downloadVideo(const std::string& url) -> bool { if (url == "") { - printf("ERROR: Cannot download video with empty url\n"); + writeToLog("ERROR: Cannot download video with empty url\n"); return false; } @@ -105,7 +119,7 @@ auto downloadVideo(const std::string& url) -> bool { "{}", BIN_DENO, TMP_DOWNLOAD_FOLDER, url)); - printf("Finished downloading video %s.\n", url.c_str()); + writeToLog("Finished downloading video %s.\n", url.c_str()); return true; } @@ -120,7 +134,7 @@ auto writeAndReturnDataJson(const std::string& url) -> json { std::ifstream f("tmp/data.json"); if (!f) { - printf("ERROR: Couldn't open data.json\n"); + writeToLog("ERROR: Couldn't open data.json\n"); return false; } @@ -177,7 +191,7 @@ auto writeMetadataToTmpFolder(const std::string& artist, const std::string& albu 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()); + writeToLog("Finished writting metadata for %s.\n", entry.path().string().c_str()); std::filesystem::remove(entry.path().string()); index++; } diff --git a/src/utils.hpp b/src/utils.hpp index 8171754..f263c4b 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include @@ -22,3 +23,5 @@ auto autoDetermineRip(const std::string& musicFolder, const std::string& url) -> auto musicFolder() -> std::string; auto writeMetadataToTmpFolder(const std::string& artist, const std::string& album, const std::string& coverUrl) -> void; + +auto writeToLog(const char* format, ...) -> void; diff --git a/src/video.cpp b/src/video.cpp index 0708c6d..ef6eb0c 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -22,48 +22,69 @@ Video::Video(const std::string& videoUrl) { } auto Video::rip(const std::string& musicFolder) -> bool { - printf("Writting to data.json\n"); + percent = 0; + currentArtist = "Finding..."; + currentAlbum = "Finding..."; + + writeToLog("Writting to data.json\n"); json data = writeAndReturnDataJson(videoUrl); - 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"); + currentAlbum = album; + currentArtist = artist; + + percent = 50; + + if (!download()) { + writeToLog("ERROR: Failed to download playlsit\n"); return false; } + percent = 80; + + if (!writeMetadata(data)) { + writeToLog("ERROR: Failed to write metatdata for playlsit\n"); + return false; + } + + percent = 95; + + if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) { + writeToLog("ERROR: Failed to move files to music folder\n"); + return false; + } + + percent = 100; + return true; } +auto Video::info(std::string& artist, std::string& title) -> int { + artist = currentArtist; + title = currentAlbum; + return percent; +} + auto Video::download() -> bool { if (videoUrl == "") { - printf("ERROR: Tried to download empty video, %s\n", videoUrl.c_str()); + writeToLog("ERROR: Tried to download empty video, %s\n", videoUrl.c_str()); return false; } if (!cleanTmpDownloadFolder()) { - printf("ERROR: Video download couldn't clean tmp download folder\n"); + writeToLog("ERROR: Video download couldn't clean tmp download folder\n"); return false; } if (!downloadVideo(videoUrl)) { - printf("ERROR: Failed to download video\n"); + writeToLog("ERROR: Failed to download video\n"); return false; } - printf("Finished downloading video %s\n", videoUrl.c_str()); + writeToLog("Finished downloading video %s\n", videoUrl.c_str()); return true; } @@ -72,15 +93,15 @@ auto Video::writeMetadata(const nlohmann::json& data) -> bool { std::string album; aristAndAlbumName(data, artist, album); - printf("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str()); + writeToLog("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str()); - printf("Finding thumbnail.\n"); + writeToLog("Finding thumbnail.\n"); std::string coverUrl = data["thumbnails"][1]["url"]; - printf("Writting data to files.\n"); + writeToLog("Writting data to files.\n"); writeMetadataToTmpFolder(artist, album, coverUrl); - printf("Finished writting metadata for %s by %s.\n", album.c_str(), artist.c_str()); + writeToLog("Finished writting metadata for %s by %s.\n", album.c_str(), artist.c_str()); return true; } diff --git a/src/video.hpp b/src/video.hpp index d28d01f..00ff52d 100644 --- a/src/video.hpp +++ b/src/video.hpp @@ -3,12 +3,18 @@ #include #include -class Video { +#include "content.hpp" + +class Video : public Content { std::string videoUrl; + int percent = 0; + std::string currentArtist; + std::string currentAlbum; public: Video(const std::string& videoUrl); auto rip(const std::string& musicFolder) -> bool; + auto info(std::string& artist, std::string& title) -> int; private: auto download() -> bool;