New interface

This commit is contained in:
2026-08-21 13:40:12 -06:00
parent ce457fbd17
commit 28c4318b00
8 changed files with 192 additions and 60 deletions
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <string>
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;
};
+54 -3
View File
@@ -1,12 +1,32 @@
#include <chrono>
#include <deque>
#include <filesystem> #include <filesystem>
#include <iostream> #include <iostream>
#include <queue>
#include <regex>
#include <string> #include <string>
#include <thread>
#include "config.hpp" #include "config.hpp"
#include "content.hpp"
#include "playlist.hpp"
#include "utils.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 { auto main(int argc, char* argv[]) -> int {
std::vector<std::string> urls; std::deque<std::string> urls;
printf( 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 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::string musicDir = musicFolder();
std::filesystem::create_directories(TMP_DOWNLOAD_FOLDER); 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{[&currentContent, &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([&currentContent, &musicDir]() { currentContent->rip(musicDir); });
}
std::this_thread::sleep_for(std::chrono::seconds(1));
} }
} }
+48 -26
View File
@@ -22,14 +22,12 @@ Playlist::Playlist(const std::string& playlistUrl) {
} }
auto Playlist::rip(const std::string& musicFolder) -> bool { auto Playlist::rip(const std::string& musicFolder) -> bool {
printf("Writting to data.json\n"); percent = 0;
json data = writeAndReturnDataJson(playlistUrl); currentArtist = "Finding...";
currentAlbum = "Finding...";
// Download the content as mp3 writeToLog("Writting to data.json\n");
if (!download()) { json data = writeAndReturnDataJson(playlistUrl);
printf("ERROR: Failed to download playlsit\n");
return false;
}
// Get the name of the artist and album from data.json // Get the name of the artist and album from data.json
std::string artist = data["entries"][0]["uploader"]; std::string artist = data["entries"][0]["uploader"];
@@ -37,38 +35,61 @@ auto Playlist::rip(const std::string& musicFolder) -> bool {
std::string album = data["title"]; std::string album = data["title"];
album = std::regex_replace(album, std::regex("Album - "), ""); 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 percent = 30;
printf("Finding album cover.\n");
std::string coverUrl = data["thumbnails"][1]["url"];
printf("Writting data to files.\n"); // Download the content as mp3
writeMetadataToTmpFolder(artist, album, coverUrl); if (!download()) {
writeToLog("ERROR: Failed to download playlsit\n");
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");
return false; 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; return true;
} }
auto Playlist::info(std::string& artist, std::string& title) -> int {
artist = currentArtist;
title = currentAlbum;
return percent;
}
auto Playlist::download() -> bool { auto Playlist::download() -> bool {
std::vector<std::string> videoUrls = urlsFromPlaylist(playlistUrl); std::vector<std::string> videoUrls = urlsFromPlaylist(playlistUrl);
if (videoUrls.empty()) { 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; return false;
} }
if (!cleanTmpDownloadFolder()) { 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; return false;
} }
@@ -88,11 +109,12 @@ auto Playlist::download() -> bool {
for (auto& future : futures) { for (auto& future : futures) {
future.get(); future.get();
count++; 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; return true;
} }
@@ -113,7 +135,7 @@ auto Playlist::urlsFromPlaylist(const std::string& url) -> std::vector<std::stri
std::ifstream f(urlsFileLocation); std::ifstream f(urlsFileLocation);
if (!f.is_open()) { if (!f.is_open()) {
printf("ERROR: Couldn't open urls.txt\n"); writeToLog("ERROR: Couldn't open urls.txt\n");
return urls; return urls;
} }
@@ -122,6 +144,6 @@ auto Playlist::urlsFromPlaylist(const std::string& url) -> std::vector<std::stri
urls.push_back(line); urls.push_back(line);
} }
if (urls.empty()) printf("ERROR: Couldn't find any urls for playlist %s\n", url.c_str()); if (urls.empty()) writeToLog("ERROR: Couldn't find any urls for playlist %s\n", url.c_str());
return urls; return urls;
} }
+7 -1
View File
@@ -4,12 +4,18 @@
#include <string> #include <string>
#include <vector> #include <vector>
class Playlist { #include "content.hpp"
class Playlist : public Content {
std::string playlistUrl; std::string playlistUrl;
int percent = 0;
std::string currentArtist;
std::string currentAlbum;
public: public:
Playlist(const std::string& playlistUrl); Playlist(const std::string& playlistUrl);
auto rip(const std::string& musicFolder) -> bool; auto rip(const std::string& musicFolder) -> bool;
auto info(std::string& artist, std::string& title) -> int;
private: private:
auto download() -> bool; auto download() -> bool;
+22 -8
View File
@@ -17,6 +17,20 @@
using json = nlohmann::json; 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 // 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_YT_DLP, args).c_str()); system(std::format("{} {}", BIN_YT_DLP, args).c_str());
@@ -54,11 +68,11 @@ auto cleanTmpDownloadFolder() -> bool {
} }
auto downloadImageToTmpFolder(std::string url) -> 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(); CURL* curl = curl_easy_init();
if (!curl) { if (!curl) {
printf("ERROR: Failed to initialize curl.\n"); writeToLog("ERROR: Failed to initialize curl.\n");
return false; return false;
} }
@@ -68,7 +82,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool {
FILE* file = std::fopen(outputPath.string().c_str(), "wb"); FILE* file = std::fopen(outputPath.string().c_str(), "wb");
if (!file) { if (!file) {
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
printf("ERROR: Failed to create cover file.\n"); writeToLog("ERROR: Failed to create cover file.\n");
return false; return false;
} }
@@ -83,7 +97,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool {
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
if (res != CURLE_OK) { if (res != CURLE_OK) {
printf("ERROR: %s\n", curl_easy_strerror(res)); writeToLog("ERROR: %s\n", curl_easy_strerror(res));
return false; return false;
} }
@@ -92,7 +106,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool {
auto downloadVideo(const std::string& url) -> bool { auto downloadVideo(const std::string& url) -> bool {
if (url == "") { if (url == "") {
printf("ERROR: Cannot download video with empty url\n"); writeToLog("ERROR: Cannot download video with empty url\n");
return false; return false;
} }
@@ -105,7 +119,7 @@ auto downloadVideo(const std::string& url) -> bool {
"{}", "{}",
BIN_DENO, TMP_DOWNLOAD_FOLDER, url)); 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; return true;
} }
@@ -120,7 +134,7 @@ auto writeAndReturnDataJson(const std::string& url) -> json {
std::ifstream f("tmp/data.json"); std::ifstream f("tmp/data.json");
if (!f) { if (!f) {
printf("ERROR: Couldn't open data.json\n"); writeToLog("ERROR: Couldn't open data.json\n");
return false; 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; if (std::filesystem::path(entry.path().string()).filename().string() == "cover.jpg") continue;
metadata(entry.path().string(), artist, album, index); 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()); std::filesystem::remove(entry.path().string());
index++; index++;
} }
+3
View File
@@ -1,4 +1,5 @@
#pragma once #pragma once
#include <cstdarg>
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json_fwd.hpp>
#include <string> #include <string>
@@ -22,3 +23,5 @@ auto autoDetermineRip(const std::string& musicFolder, const std::string& url) ->
auto musicFolder() -> std::string; auto musicFolder() -> std::string;
auto writeMetadataToTmpFolder(const std::string& artist, const std::string& album, const std::string& coverUrl) -> void; auto writeMetadataToTmpFolder(const std::string& artist, const std::string& album, const std::string& coverUrl) -> void;
auto writeToLog(const char* format, ...) -> void;
+42 -21
View File
@@ -22,48 +22,69 @@ Video::Video(const std::string& videoUrl) {
} }
auto Video::rip(const std::string& musicFolder) -> bool { 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); 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 artist;
std::string album; std::string album;
aristAndAlbumName(data, artist, album); aristAndAlbumName(data, artist, album);
if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) { currentAlbum = album;
printf("ERROR: Failed to move files to music folder\n"); currentArtist = artist;
percent = 50;
if (!download()) {
writeToLog("ERROR: Failed to download playlsit\n");
return false; 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; return true;
} }
auto Video::info(std::string& artist, std::string& title) -> int {
artist = currentArtist;
title = currentAlbum;
return percent;
}
auto Video::download() -> bool { auto Video::download() -> bool {
if (videoUrl == "") { 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; return false;
} }
if (!cleanTmpDownloadFolder()) { 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; return false;
} }
if (!downloadVideo(videoUrl)) { if (!downloadVideo(videoUrl)) {
printf("ERROR: Failed to download video\n"); writeToLog("ERROR: Failed to download video\n");
return false; return false;
} }
printf("Finished downloading video %s\n", videoUrl.c_str()); writeToLog("Finished downloading video %s\n", videoUrl.c_str());
return true; return true;
} }
@@ -72,15 +93,15 @@ auto Video::writeMetadata(const nlohmann::json& data) -> bool {
std::string album; std::string album;
aristAndAlbumName(data, artist, 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"]; std::string coverUrl = data["thumbnails"][1]["url"];
printf("Writting data to files.\n"); writeToLog("Writting data to files.\n");
writeMetadataToTmpFolder(artist, album, coverUrl); 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; return true;
} }
+7 -1
View File
@@ -3,12 +3,18 @@
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json_fwd.hpp>
#include <string> #include <string>
class Video { #include "content.hpp"
class Video : public Content {
std::string videoUrl; std::string videoUrl;
int percent = 0;
std::string currentArtist;
std::string currentAlbum;
public: public:
Video(const std::string& videoUrl); Video(const std::string& videoUrl);
auto rip(const std::string& musicFolder) -> bool; auto rip(const std::string& musicFolder) -> bool;
auto info(std::string& artist, std::string& title) -> int;
private: private:
auto download() -> bool; auto download() -> bool;