1 Commits
Author SHA1 Message Date
Ethanscharlie 92d11b5fbb Cleaned up code and using new interface 2026-08-22 16:29:02 -06:00
8 changed files with 135 additions and 233 deletions
+3 -2
View File
@@ -1,9 +1,10 @@
#pragma once
#include <functional>
#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;
virtual auto rip(const std::string& musicFolder, std::function<void(int)> status) -> bool = 0;
virtual auto info(std::string& artist, std::string& title) -> void = 0;
};
+36 -46
View File
@@ -1,11 +1,9 @@
#include <chrono>
#include <deque>
#include <cstdio>
#include <filesystem>
#include <iostream>
#include <queue>
#include <regex>
#include <string>
#include <thread>
#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<std::string> urls;
std::queue<std::string> 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{[&currentContent, &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;
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);
}
currentUrl = urls.front();
currentContent = createContent(currentUrl);
ripper = std::thread([&currentContent, &musicDir]() { currentContent->rip(musicDir); });
if (content == nullptr) {
printf("ERROR: Failed to create content object\n");
urls.pop();
delete content;
continue;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
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;
}
}
+43 -51
View File
@@ -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<void(int)> 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<void(int)> status) -> bool {
std::vector<std::string> 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");
if (!forEachConcurrent(videoUrls, status, [this](const std::string& url) { return downloadVideo(url); })) {
printf("ERROR: Failed to download playlist currently\n");
return false;
}
int count = 0;
return true;
}
for (size_t i = 0; i < videoUrls.size(); i += MAX_THREADS) {
std::vector<std::future<void>> futures;
auto Playlist::forEachConcurrent(const std::vector<std::string>& urls, std::function<void(int)> status,
std::function<bool(const std::string&)> onStart) -> bool {
int percent = 30;
size_t end = std::min(i + MAX_THREADS, videoUrls.size());
for (size_t i = 0; i < urls.size(); i += MAX_THREADS) {
std::vector<std::future<bool>> futures;
size_t end = std::min(i + MAX_THREADS, urls.size());
for (size_t j = i; j < end; ++j) {
futures.push_back(std::async(std::launch::async, [&videoUrls, j]() { downloadVideo(videoUrls[j]); }));
std::string url = urls[j];
futures.push_back(std::async(std::launch::async, [&onStart, url]() { return onStart(url); }));
}
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());
}
for (std::future<bool>& future : futures) {
if (!future.get()) {
printf("ERROR: Failed to download video from playlist concurrently\n");
return false;
}
writeToLog("Finished downloading playlist.\n");
percent += 50 / urls.size();
status(percent);
}
}
return true;
}
@@ -135,7 +127,7 @@ auto Playlist::urlsFromPlaylist(const std::string& url) -> std::vector<std::stri
std::ifstream f(urlsFileLocation);
if (!f.is_open()) {
writeToLog("ERROR: Couldn't open urls.txt\n");
printf("ERROR: Couldn't open urls.txt\n");
return urls;
}
@@ -144,6 +136,6 @@ auto Playlist::urlsFromPlaylist(const std::string& url) -> std::vector<std::stri
urls.push_back(line);
}
if (urls.empty()) writeToLog("ERROR: Couldn't find any urls for playlist %s\n", url.c_str());
if (urls.empty()) printf("ERROR: Couldn't find any urls for playlist %s\n", url.c_str());
return urls;
}
+11 -7
View File
@@ -1,6 +1,7 @@
#pragma once
#include <nlohmann/json_fwd.hpp>
#include <functional>
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
@@ -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<void(int)> status) -> bool;
auto info(std::string& artist, std::string& title) -> void;
private:
auto download() -> bool;
auto download(std::function<void(int)> status) -> bool;
auto forEachConcurrent(const std::vector<std::string>& urls, std::function<void(int)> status,
std::function<bool(const std::string&)> onStart) -> bool;
auto urlsFromPlaylist(const std::string& url) -> std::vector<std::string>;
};
+5 -37
View File
@@ -12,25 +12,9 @@
#include <string>
#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++;
}
-5
View File
@@ -1,5 +1,4 @@
#pragma once
#include <cstdarg>
#include <nlohmann/json_fwd.hpp>
#include <string>
@@ -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;
+21 -68
View File
@@ -1,18 +1,9 @@
#include "video.hpp"
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <format>
#include <fstream>
#include <future>
#include <nlohmann/json.hpp>
#include <regex>
#include <string>
#include <vector>
#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<void(int)> 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;
}
+7 -8
View File
@@ -1,23 +1,22 @@
#pragma once
#include <nlohmann/json_fwd.hpp>
#include <nlohmann/json.hpp>
#include <string>
#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<void(int)> 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;
};