From 92d11b5fbb9a41911cb5d424ccc514ce2a5a9b6a Mon Sep 17 00:00:00 2001 From: "Ethan J. Hadley" Date: Sat, 22 Aug 2026 16:29:02 -0600 Subject: [PATCH] Cleaned up code and using new interface --- src/content.hpp | 5 ++- src/main.cpp | 86 +++++++++++++++++-------------------- src/playlist.cpp | 108 ++++++++++++++++++++++------------------------- src/playlist.hpp | 18 +++++--- src/utils.cpp | 42 +++--------------- src/utils.hpp | 5 --- src/video.cpp | 89 +++++++++----------------------------- src/video.hpp | 15 +++---- 8 files changed, 135 insertions(+), 233 deletions(-) diff --git a/src/content.hpp b/src/content.hpp index c3bc69b..eaa38e5 100644 --- a/src/content.hpp +++ b/src/content.hpp @@ -1,9 +1,10 @@ #pragma once +#include #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; + virtual auto rip(const std::string& musicFolder, std::function status) -> bool = 0; + virtual auto info(std::string& artist, std::string& title) -> void = 0; }; diff --git a/src/main.cpp b/src/main.cpp index 7e131f5..15537ef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,9 @@ -#include -#include +#include #include #include #include #include #include -#include #include "config.hpp" #include "content.hpp" @@ -13,23 +11,11 @@ #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::deque urls; + std::queue urls; printf( - "Welcome to YTMusicDL26! You can use playlists or video from youtube or youtube music (youtube music is " + "Welcome to YTMusicDL26! You can use playlists or videos from youtube or youtube music (youtube music is " "better).\n"); printf("Type \"end\" when your finished.\n"); @@ -43,46 +29,50 @@ auto main(int argc, char* argv[]) -> int { break; } - urls.push_back(input); + urls.push(input); index++; } - printf("Starting rips...\n"); - std::string musicDir = musicFolder(); std::filesystem::create_directories(TMP_DOWNLOAD_FOLDER); - // TODO quit if there are no urls + printf("\n"); - std::string currentUrl = urls.front(); - Content* currentContent = createContent(currentUrl); - std::thread ripper{[¤tContent, &musicDir]() { currentContent->rip(musicDir); }}; - std::string currentArtist; - std::string currentAlbum; + while (!urls.empty()) { + const std::string& currentUrl = urls.front(); - 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); }); + Content* content = nullptr; + if (std::regex_search(currentUrl, std::regex("youtube.com/playlist"))) { + content = new Playlist(currentUrl); + } else if (std::regex_search(currentUrl, std::regex("youtube.com/watch"))) { + content = new Video(currentUrl); } - std::this_thread::sleep_for(std::chrono::seconds(1)); + if (content == nullptr) { + printf("ERROR: Failed to create content object\n"); + urls.pop(); + delete content; + continue; + } + + if (!content->rip(musicDir, [&content, &urls](int percent) { + if (percent == 0) { + printf("\rGathering info... (this may take awhile)"); + fflush(stdout); + } else { + std::string artist; + std::string album; + content->info(artist, album); + printf("\r%s by %s at %d%%", album.c_str(), artist.c_str(), percent); + fflush(stdout); + } + })) { + printf("Failed to rip content\n"); + } + + printf("\n"); + + urls.pop(); + delete content; } } diff --git a/src/playlist.cpp b/src/playlist.cpp index c767fbd..af68ce3 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -21,100 +21,92 @@ Playlist::Playlist(const std::string& playlistUrl) { this->playlistUrl = std::regex_replace(playlistUrl, std::regex("music.youtube"), "www.youtube"); } -auto Playlist::rip(const std::string& musicFolder) -> bool { - percent = 0; - currentArtist = "Finding..."; - currentAlbum = "Finding..."; +auto Playlist::rip(const std::string& musicFolder, std::function status) -> bool { + status(0); - writeToLog("Writting to data.json\n"); - json data = writeAndReturnDataJson(playlistUrl); + data = writeAndReturnDataJson(playlistUrl); // Get the name of the artist and album from data.json - std::string artist = data["entries"][0]["uploader"]; + artist = data["entries"][0]["uploader"]; artist = std::regex_replace(artist, std::regex(" - Topic"), ""); - std::string album = data["title"]; + album = data["title"]; album = std::regex_replace(album, std::regex("Album - "), ""); - currentArtist = artist; - currentAlbum = album; - - percent = 30; + status(30); // Download the content as mp3 - if (!download()) { - writeToLog("ERROR: Failed to download playlsit\n"); + if (!download(status)) { + printf("ERROR: Failed to download playlsit\n"); return false; } - percent = 80; + status(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()); + status(95); // Move the files into the users Music folder if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) { - writeToLog("ERROR: Failed to move files to music folder\n"); + printf("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; - + status(100); return true; } -auto Playlist::info(std::string& artist, std::string& title) -> int { - artist = currentArtist; - title = currentAlbum; - return percent; +auto Playlist::info(std::string& artist, std::string& title) -> void { + artist = this->artist != "" ? this->artist : "Finding..."; + title = this->album != "" ? this->album : "Finding..."; } -auto Playlist::download() -> bool { +auto Playlist::download(std::function status) -> bool { std::vector videoUrls = urlsFromPlaylist(playlistUrl); if (videoUrls.empty()) { - writeToLog("ERROR: Tried to download empty playlist, %s\n", playlistUrl.c_str()); + printf("ERROR: Tried to download empty playlist, %s\n", playlistUrl.c_str()); return false; } if (!cleanTmpDownloadFolder()) { - writeToLog("ERROR: Playlist download couldn't clean tmp download folder\n"); + 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++; - percent += 50 / videoUrls.size(); - writeToLog("Finished [%d/%d] in playlist %s\n", count, (int)videoUrls.size(), playlistUrl.c_str()); - } + if (!forEachConcurrent(videoUrls, status, [this](const std::string& url) { return downloadVideo(url); })) { + printf("ERROR: Failed to download playlist currently\n"); + return false; } - writeToLog("Finished downloading playlist.\n"); + return true; +} + +auto Playlist::forEachConcurrent(const std::vector& urls, std::function status, + std::function onStart) -> bool { + int percent = 30; + + for (size_t i = 0; i < urls.size(); i += MAX_THREADS) { + std::vector> futures; + + size_t end = std::min(i + MAX_THREADS, urls.size()); + + for (size_t j = i; j < end; ++j) { + std::string url = urls[j]; + futures.push_back(std::async(std::launch::async, [&onStart, url]() { return onStart(url); })); + } + + for (std::future& future : futures) { + if (!future.get()) { + printf("ERROR: Failed to download video from playlist concurrently\n"); + return false; + } + + percent += 50 / urls.size(); + status(percent); + } + } return true; } @@ -135,7 +127,7 @@ auto Playlist::urlsFromPlaylist(const std::string& url) -> std::vector std::vector +#include +#include #include #include @@ -8,16 +9,19 @@ class Playlist : public Content { std::string playlistUrl; - int percent = 0; - std::string currentArtist; - std::string currentAlbum; + + std::string artist = ""; + std::string album = ""; + nlohmann::json data; public: Playlist(const std::string& playlistUrl); - auto rip(const std::string& musicFolder) -> bool; - auto info(std::string& artist, std::string& title) -> int; + auto rip(const std::string& musicFolder, std::function status) -> bool; + auto info(std::string& artist, std::string& title) -> void; private: - auto download() -> bool; + auto download(std::function status) -> bool; + auto forEachConcurrent(const std::vector& urls, std::function status, + std::function onStart) -> bool; auto urlsFromPlaylist(const std::string& url) -> std::vector; }; diff --git a/src/utils.cpp b/src/utils.cpp index 8f050a6..9b16d84 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -12,25 +12,9 @@ #include #include "config.hpp" -#include "playlist.hpp" -#include "video.hpp" 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()); @@ -68,11 +52,9 @@ auto cleanTmpDownloadFolder() -> bool { } auto downloadImageToTmpFolder(std::string url) -> bool { - writeToLog("Downloading image with url %s\n", url.c_str()); - CURL* curl = curl_easy_init(); if (!curl) { - writeToLog("ERROR: Failed to initialize curl.\n"); + printf("ERROR: Failed to initialize curl.\n"); return false; } @@ -82,7 +64,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool { FILE* file = std::fopen(outputPath.string().c_str(), "wb"); if (!file) { curl_easy_cleanup(curl); - writeToLog("ERROR: Failed to create cover file.\n"); + printf("ERROR: Failed to create cover file.\n"); return false; } @@ -97,7 +79,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool { curl_easy_cleanup(curl); if (res != CURLE_OK) { - writeToLog("ERROR: %s\n", curl_easy_strerror(res)); + printf("ERROR: %s\n", curl_easy_strerror(res)); return false; } @@ -106,7 +88,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool { auto downloadVideo(const std::string& url) -> bool { if (url == "") { - writeToLog("ERROR: Cannot download video with empty url\n"); + printf("ERROR: Cannot download video with empty url\n"); return false; } @@ -119,7 +101,6 @@ auto downloadVideo(const std::string& url) -> bool { "{}", BIN_DENO, TMP_DOWNLOAD_FOLDER, url)); - writeToLog("Finished downloading video %s.\n", url.c_str()); return true; } @@ -134,7 +115,7 @@ auto writeAndReturnDataJson(const std::string& url) -> json { std::ifstream f("tmp/data.json"); if (!f) { - writeToLog("ERROR: Couldn't open data.json\n"); + printf("ERROR: Couldn't open data.json\n"); return false; } @@ -158,18 +139,6 @@ auto moveAndSortTmpFilesToFolder(const std::string& folder, const std::string& a 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"); @@ -191,7 +160,6 @@ 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); - 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 f263c4b..c542f24 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -1,5 +1,4 @@ #pragma once -#include #include #include @@ -18,10 +17,6 @@ auto writeAndReturnDataJson(const std::string& url) -> nlohmann::json; auto moveAndSortTmpFilesToFolder(const std::string& folder, const std::string& artist, const std::string& album) -> bool; -auto autoDetermineRip(const std::string& musicFolder, const std::string& url) -> bool; - 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 ef6eb0c..cf52e8d 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -1,18 +1,9 @@ #include "video.hpp" -#include -#include -#include -#include -#include -#include -#include #include #include #include -#include -#include "config.hpp" #include "utils.hpp" using json = nlohmann::json; @@ -21,97 +12,59 @@ Video::Video(const std::string& videoUrl) { this->videoUrl = std::regex_replace(videoUrl, std::regex("music.youtube"), "www.youtube"); } -auto Video::rip(const std::string& musicFolder) -> bool { - percent = 0; - currentArtist = "Finding..."; - currentAlbum = "Finding..."; +auto Video::rip(const std::string& musicFolder, std::function status) -> bool { + status(0); - writeToLog("Writting to data.json\n"); - json data = writeAndReturnDataJson(videoUrl); + data = writeAndReturnDataJson(videoUrl); - std::string artist; - std::string album; - aristAndAlbumName(data, artist, album); + artist = data["uploader"]; + artist = std::regex_replace(artist, std::regex(" - Topic"), ""); + album = data["title"]; + album = std::regex_replace(album, std::regex("Album - "), ""); - currentAlbum = album; - currentArtist = artist; - - percent = 50; + status(50); if (!download()) { - writeToLog("ERROR: Failed to download playlsit\n"); + printf("ERROR: Failed to download playlsit\n"); return false; } - percent = 80; + status(80); - if (!writeMetadata(data)) { - writeToLog("ERROR: Failed to write metatdata for playlsit\n"); - return false; - } + std::string coverUrl = data["thumbnails"][1]["url"]; + writeMetadataToTmpFolder(artist, album, coverUrl); - percent = 95; + status(95); if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) { - writeToLog("ERROR: Failed to move files to music folder\n"); + printf("ERROR: Failed to move files to music folder\n"); return false; } - percent = 100; - + status(100); return true; } -auto Video::info(std::string& artist, std::string& title) -> int { - artist = currentArtist; - title = currentAlbum; - return percent; +auto Video::info(std::string& artist, std::string& title) -> void { + artist = this->artist != "" ? this->artist : "Finding..."; + title = this->album != "" ? this->album : "Finding..."; } auto Video::download() -> bool { if (videoUrl == "") { - writeToLog("ERROR: Tried to download empty video, %s\n", videoUrl.c_str()); + printf("ERROR: Tried to download empty video, %s\n", videoUrl.c_str()); return false; } if (!cleanTmpDownloadFolder()) { - writeToLog("ERROR: Video download couldn't clean tmp download folder\n"); + printf("ERROR: Video download couldn't clean tmp download folder\n"); return false; } if (!downloadVideo(videoUrl)) { - writeToLog("ERROR: Failed to download video\n"); + printf("ERROR: Failed to download video\n"); return false; } - writeToLog("Finished downloading video %s\n", videoUrl.c_str()); - return true; -} - -auto Video::writeMetadata(const nlohmann::json& data) -> bool { - std::string artist; - std::string album; - aristAndAlbumName(data, artist, album); - - writeToLog("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str()); - - writeToLog("Finding thumbnail.\n"); - std::string coverUrl = data["thumbnails"][1]["url"]; - - writeToLog("Writting data to files.\n"); - writeMetadataToTmpFolder(artist, album, coverUrl); - - writeToLog("Finished writting metadata for %s by %s.\n", album.c_str(), artist.c_str()); - - return true; -} - -auto Video::aristAndAlbumName(const nlohmann::json& data, std::string& artist, std::string& album) -> bool { - artist = data["uploader"]; - artist = std::regex_replace(artist, std::regex(" - Topic"), ""); - - album = data["title"]; - album = std::regex_replace(album, std::regex("Album - "), ""); - return true; } diff --git a/src/video.hpp b/src/video.hpp index 00ff52d..785809e 100644 --- a/src/video.hpp +++ b/src/video.hpp @@ -1,23 +1,22 @@ #pragma once -#include +#include #include #include "content.hpp" class Video : public Content { std::string videoUrl; - int percent = 0; - std::string currentArtist; - std::string currentAlbum; + + std::string artist = ""; + std::string album = ""; + nlohmann::json data; public: Video(const std::string& videoUrl); - auto rip(const std::string& musicFolder) -> bool; - auto info(std::string& artist, std::string& title) -> int; + auto rip(const std::string& musicFolder, std::function status) -> bool; + auto info(std::string& artist, std::string& title) -> void; private: auto download() -> bool; - auto writeMetadata(const nlohmann::json& data) -> bool; - auto aristAndAlbumName(const nlohmann::json& data, std::string& artist, std::string& album) -> bool; };