Downloaded full album with cover art and metadata

This commit is contained in:
2026-08-19 23:17:16 -06:00
parent 176335dc07
commit 01b51efa63
2 changed files with 78 additions and 12 deletions
+6 -7
View File
@@ -16,14 +16,13 @@ target_include_directories(YTMusicDL26 PUBLIC
include(cmake/CPM.cmake) include(cmake/CPM.cmake)
# CPMAddPackage( CPMAddPackage("gh:nlohmann/json#v3.12.0")
# NAME id3v2lib CPMAddPackage("gh:curl/curl#curl-8_21_0")
# GIT_REPOSITORY https://github.com/larsbs/id3v2lib.git
# GIT_TAG master
# )
# 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}) file(COPY ${CMAKE_SOURCE_DIR}/bin DESTINATION ${CMAKE_BINARY_DIR})
+72 -5
View File
@@ -1,3 +1,6 @@
#include <curl/curl.h>
#include <curl/easy.h>
#include <cstddef> #include <cstddef>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
@@ -5,6 +8,7 @@
#include <format> #include <format>
#include <fstream> #include <fstream>
#include <future> #include <future>
#include <nlohmann/json.hpp>
#include <regex> #include <regex>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -12,6 +16,8 @@
#define TMP_DOWNLOAD_FOLDER "tmp/download" #define TMP_DOWNLOAD_FOLDER "tmp/download"
#define MAX_THREADS 4 #define MAX_THREADS 4
using json = nlohmann::json;
// This runs in shell, if put through a website interface it would allow for injection // This runs in shell, if put through a website interface it would allow for injection
auto ytdlp(const std::string& args) -> bool { auto ytdlp(const std::string& args) -> bool {
system(std::format("bin/linux/yt-dlp {}", args).c_str()); 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) { 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(); std::string name = std::filesystem::path(file).filename().string();
name = std::regex_replace(name, std::regex(".mp3"), "");
system(std::format("bin/linux/ffmpeg " system(std::format("bin/linux/ffmpeg "
"-hide_banner -loglevel error " "-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 title=\"{}\" "
"-metadata artist=\"{}\" " "-metadata artist=\"{}\" "
"-metadata album=\"{}\" " "-metadata album=\"{}\" "
"-metadata track=\"{}\" " "-metadata track=\"{}\" "
"\"{}/{}_{}.mp3\"", "\"{}/{}_{}.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()); .c_str());
} }
@@ -41,6 +53,38 @@ auto cleanTmpDownloadFolder() -> bool {
return true; 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::string> { auto urlsFromPlaylist(const std::string& url) -> std::vector<std::string> {
std::vector<std::string> urls; std::vector<std::string> urls;
@@ -97,7 +141,7 @@ auto playlistTitle(const std::string& playlistUrl) {
return s; return s;
} }
auto channelName(const std::string& playlistUrl) { auto channelNamePlaylist(const std::string& playlistUrl) {
std::string s = dataFromYtdlpPlaylist(playlistUrl, "channel"); std::string s = dataFromYtdlpPlaylist(playlistUrl, "channel");
s = std::regex_replace(s, std::regex(" - Topic"), ""); s = std::regex_replace(s, std::regex(" - Topic"), "");
return s; return s;
@@ -161,19 +205,42 @@ auto downloadPlaylist(std::string playlistUrl) -> bool {
} }
auto writeAlbumMetadataToTmpFolder(const 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); std::string album = playlistTitle(playlistUrl);
printf("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str()); 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; size_t index = 1;
for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) { 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); metadata(entry.path(), artist, album, index);
printf("Finished writting metadata for %s.\n", entry.path().c_str()); printf("Finished writting metadata for %s.\n", entry.path().c_str());
std::filesystem::remove(entry.path()); std::filesystem::remove(entry.path());
index++; 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; return true;
} }