From 01b51efa63e8f103eac955a5e9686c8bc101b18f Mon Sep 17 00:00:00 2001 From: "Ethan J. Hadley" Date: Wed, 19 Aug 2026 23:17:16 -0600 Subject: [PATCH] Downloaded full album with cover art and metadata --- CMakeLists.txt | 13 ++++----- src/main.cpp | 77 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d0f617..d73e1ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,14 +16,13 @@ target_include_directories(YTMusicDL26 PUBLIC include(cmake/CPM.cmake) -# CPMAddPackage( -# NAME id3v2lib -# GIT_REPOSITORY https://github.com/larsbs/id3v2lib.git -# GIT_TAG master -# ) +CPMAddPackage("gh:nlohmann/json#v3.12.0") +CPMAddPackage("gh:curl/curl#curl-8_21_0") -# target_link_libraries(YTMusicDL26 PUBLIC -# ) +target_link_libraries(YTMusicDL26 PUBLIC + nlohmann_json::nlohmann_json + CURL::libcurl +) file(COPY ${CMAKE_SOURCE_DIR}/bin DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/src/main.cpp b/src/main.cpp index 2950391..41d72f6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,6 @@ +#include +#include + #include #include #include @@ -5,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -12,6 +16,8 @@ #define TMP_DOWNLOAD_FOLDER "tmp/download" #define MAX_THREADS 4 +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/linux/yt-dlp {}", args).c_str()); @@ -20,16 +26,22 @@ auto ytdlp(const std::string& args) -> bool { auto metadata(const std::string& file, const std::string& artist, const std::string& album, size_t index) { std::string name = std::filesystem::path(file).filename().string(); + name = std::regex_replace(name, std::regex(".mp3"), ""); system(std::format("bin/linux/ffmpeg " "-hide_banner -loglevel error " - "-i \"{}\" -c copy " + "-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\"", - file, name, artist, album, index, TMP_DOWNLOAD_FOLDER, index, name) + file, TMP_DOWNLOAD_FOLDER, name, artist, album, index, TMP_DOWNLOAD_FOLDER, index, name) .c_str()); } @@ -41,6 +53,38 @@ auto cleanTmpDownloadFolder() -> bool { return true; } +auto downloadImageToTmpFolder(const std::string& url) -> bool { + CURL* curl = curl_easy_init(); + if (!curl) { + printf("ERROR: Failed to initialize curl.\n"); + return false; + } + + FILE* file = std::fopen(std::format("{}/cover.jpg", TMP_DOWNLOAD_FOLDER).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 urlsFromPlaylist(const std::string& url) -> std::vector { std::vector urls; @@ -97,7 +141,7 @@ auto playlistTitle(const std::string& playlistUrl) { return s; } -auto channelName(const std::string& playlistUrl) { +auto channelNamePlaylist(const std::string& playlistUrl) { std::string s = dataFromYtdlpPlaylist(playlistUrl, "channel"); s = std::regex_replace(s, std::regex(" - Topic"), ""); return s; @@ -161,19 +205,42 @@ auto downloadPlaylist(std::string playlistUrl) -> bool { } auto writeAlbumMetadataToTmpFolder(const std::string& playlistUrl) -> bool { - std::string artist = channelName(playlistUrl); + std::string artist = channelNamePlaylist(playlistUrl); std::string album = playlistTitle(playlistUrl); printf("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str()); + printf("Finding album cover.\n"); + std::filesystem::remove("tmp/data.json"); + ytdlp( + std::format("--js-runtimes \"deno:bin/linux/deno\" " + "--dump-single-json " + "\"{}\" " + "> tmp/data.json", + playlistUrl)); + + std::ifstream f("tmp/data.json"); + if (!f) { + printf("ERROR: Couldn't open data.json\n"); + return false; + } + + json data = json::parse(f); + 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 writing metadata for %s by %s.\n", album.c_str(), artist.c_str()); + printf("Finished writting metadata for %s by %s.\n", album.c_str(), artist.c_str()); return true; }