5 Commits
Author SHA1 Message Date
Ethanscharlie 43da2c7ac1 Working on windows with https 2026-08-30 12:49:32 -06:00
Ethanscharlie 92d11b5fbb Cleaned up code and using new interface 2026-08-22 16:29:02 -06:00
Ethanscharlie 28c4318b00 New interface 2026-08-21 13:40:12 -06:00
Ethanscharlie ce457fbd17 Fixed leading underscore issue 2026-08-21 12:49:23 -06:00
Ethanscharlie aed13dc18f Fixed album covers 2026-08-21 12:42:06 -06:00
11 changed files with 191 additions and 168 deletions
+3 -7
View File
@@ -20,6 +20,9 @@ target_include_directories(YTMusicDL26 PUBLIC
include(cmake/CPM.cmake) include(cmake/CPM.cmake)
set(CURL_USE_LIBPSL OFF CACHE BOOL "" FORCE) set(CURL_USE_LIBPSL OFF CACHE BOOL "" FORCE)
set(CURL_USE_SCHANNEL ON)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
CPMAddPackage("gh:nlohmann/json#v3.12.0") CPMAddPackage("gh:nlohmann/json#v3.12.0")
CPMAddPackage("gh:curl/curl#curl-8_21_0") CPMAddPackage("gh:curl/curl#curl-8_21_0")
@@ -38,13 +41,6 @@ if (WIN32)
-static-libstdc++ -static-libstdc++
-static -static
) )
add_custom_command(TARGET YTMusicDL26 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_RUNTIME_DLLS:YTMusicDL26>
$<TARGET_FILE_DIR:YTMusicDL26>
COMMAND_EXPAND_LISTS
)
endif () endif ()
+11
View File
@@ -0,0 +1,11 @@
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <functional>
#include <string>
class Content {
public:
virtual ~Content() = default;
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;
};
+48 -7
View File
@@ -1,15 +1,21 @@
#include <cstdio>
#include <filesystem> #include <filesystem>
#include <iostream> #include <iostream>
#include <queue>
#include <regex>
#include <string> #include <string>
#include "config.hpp" #include "config.hpp"
#include "content.hpp"
#include "playlist.hpp"
#include "utils.hpp" #include "utils.hpp"
#include "video.hpp"
auto main(int argc, char* argv[]) -> int { auto main(int argc, char* argv[]) -> int {
std::vector<std::string> urls; std::queue<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 videos from youtube or youtube music (youtube music is "
"better).\n"); "better).\n");
printf("Type \"end\" when your finished.\n"); printf("Type \"end\" when your finished.\n");
@@ -23,15 +29,50 @@ auto main(int argc, char* argv[]) -> int {
break; break;
} }
urls.push_back(input); urls.push(input);
index++; index++;
} }
printf("Starting rips...\n");
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); printf("\n");
while (!urls.empty()) {
const std::string& currentUrl = urls.front();
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);
}
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("\r\033[KGathering info... (this may take a while)");
fflush(stdout);
} else {
std::string artist;
std::string album;
content->info(artist, album);
printf("\r\033[K%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;
} }
} }
+49 -61
View File
@@ -21,33 +21,48 @@ Playlist::Playlist(const std::string& playlistUrl) {
this->playlistUrl = std::regex_replace(playlistUrl, std::regex("music.youtube"), "www.youtube"); this->playlistUrl = std::regex_replace(playlistUrl, std::regex("music.youtube"), "www.youtube");
} }
auto Playlist::rip(const std::string& musicFolder) -> bool { auto Playlist::rip(const std::string& musicFolder, std::function<void(int)> status) -> bool {
printf("Writting to data.json\n"); status(0);
json data = writeAndReturnDataJson(playlistUrl);
if (!download()) { data = writeAndReturnDataJson(playlistUrl);
// Get the name of the artist and album from data.json
artist = data["entries"][0]["uploader"];
artist = std::regex_replace(artist, std::regex(" - Topic"), "");
album = data["title"];
album = std::regex_replace(album, std::regex("Album - "), "");
status(30);
// Download the content as mp3
if (!download(status)) {
printf("ERROR: Failed to download playlsit\n"); printf("ERROR: Failed to download playlsit\n");
return false; return false;
} }
if (!writeMetadata(data)) { status(80);
printf("ERROR: Failed to write metatdata for playlsit\n");
return false;
}
std::string artist; std::string coverUrl = data["thumbnails"][1]["url"];
std::string album; writeMetadataToTmpFolder(artist, album, coverUrl);
aristAndAlbumName(data, artist, album);
status(95);
// Move the files into the users Music folder
if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) { if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) {
printf("ERROR: Failed to move files to music folder\n"); printf("ERROR: Failed to move files to music folder\n");
return false; return false;
} }
status(100);
return true; return true;
} }
auto Playlist::download() -> bool { 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(std::function<void(int)> status) -> bool {
std::vector<std::string> videoUrls = urlsFromPlaylist(playlistUrl); std::vector<std::string> videoUrls = urlsFromPlaylist(playlistUrl);
if (videoUrls.empty()) { if (videoUrls.empty()) {
@@ -60,65 +75,38 @@ auto Playlist::download() -> bool {
return false; 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) { auto Playlist::forEachConcurrent(const std::vector<std::string>& urls, std::function<void(int)> status,
std::vector<std::future<void>> futures; 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) { 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) { for (std::future<bool>& future : futures) {
future.get(); if (!future.get()) {
count++; printf("ERROR: Failed to download video from playlist concurrently\n");
printf("Finished [%d/%d] in playlist %s\n", count, (int)videoUrls.size(), playlistUrl.c_str()); return false;
}
} }
printf("Finished downloading playlist.\n"); percent += 50 / urls.size();
status(percent);
return true; }
}
auto Playlist::writeMetadata(const json& data) -> bool {
std::string artist;
std::string album;
aristAndAlbumName(data, artist, album);
printf("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str());
printf("Finding album cover.\n");
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().string()).filename().string() == "cover.jpg") continue;
metadata(entry.path().string(), artist, album, index);
printf("Finished writting metadata for %s.\n", entry.path().string().c_str());
std::filesystem::remove(entry.path().string());
index++;
} }
printf("Finished writting metadata for %s by %s.\n", album.c_str(), artist.c_str());
return true;
}
auto Playlist::aristAndAlbumName(const nlohmann::json& data, std::string& artist, std::string& album) -> bool {
artist = data["entries"][0]["uploader"];
artist = std::regex_replace(artist, std::regex(" - Topic"), "");
album = data["title"];
album = std::regex_replace(album, std::regex("Album - "), "");
return true; return true;
} }
+14 -6
View File
@@ -1,19 +1,27 @@
#pragma once #pragma once
#include <nlohmann/json_fwd.hpp> #include <functional>
#include <nlohmann/json.hpp>
#include <string> #include <string>
#include <vector> #include <vector>
class Playlist { #include "content.hpp"
class Playlist : public Content {
std::string playlistUrl; std::string playlistUrl;
std::string artist = "";
std::string album = "";
nlohmann::json data;
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, std::function<void(int)> status) -> bool;
auto info(std::string& artist, std::string& title) -> void;
private: private:
auto download() -> bool; auto download(std::function<void(int)> status) -> bool;
auto writeMetadata(const nlohmann::json& data) -> bool; auto forEachConcurrent(const std::vector<std::string>& urls, std::function<void(int)> status,
auto aristAndAlbumName(const nlohmann::json& data, std::string& artist, std::string& album) -> bool; std::function<bool(const std::string&)> onStart) -> bool;
auto urlsFromPlaylist(const std::string& url) -> std::vector<std::string>; auto urlsFromPlaylist(const std::string& url) -> std::vector<std::string>;
}; };
+20 -20
View File
@@ -12,8 +12,6 @@
#include <string> #include <string>
#include "config.hpp" #include "config.hpp"
#include "playlist.hpp"
#include "video.hpp"
using json = nlohmann::json; using json = nlohmann::json;
@@ -26,6 +24,7 @@ auto ytdlp(const std::string& args) -> bool {
auto metadata(const std::string& file, const std::string& artist, const std::string& album, size_t index) -> void { auto metadata(const std::string& file, const std::string& artist, const std::string& album, size_t index) -> void {
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"), ""); name = std::regex_replace(name, std::regex(".mp3"), "");
name.erase(0, 1); // Remove leading underscore
system(std::format("{} " system(std::format("{} "
"-hide_banner -loglevel error " "-hide_banner -loglevel error "
@@ -39,9 +38,8 @@ auto metadata(const std::string& file, const std::string& artist, const std::str
"-metadata artist=\"{}\" " "-metadata artist=\"{}\" "
"-metadata album=\"{}\" " "-metadata album=\"{}\" "
"-metadata track=\"{}\" " "-metadata track=\"{}\" "
"\"{}/{}_{}.mp3\"", "\"{}/{}.mp3\"",
BIN_FFMPEG, file, TMP_DOWNLOAD_FOLDER, name, artist, album, index, TMP_DOWNLOAD_FOLDER, index, BIN_FFMPEG, file, TMP_DOWNLOAD_FOLDER, name, artist, album, index, TMP_DOWNLOAD_FOLDER, name)
name)
.c_str()); .c_str());
} }
@@ -60,7 +58,8 @@ auto downloadImageToTmpFolder(std::string url) -> bool {
return false; return false;
} }
url = std::regex_replace(url, std::regex("https"), "http"); // TODO Breaks album covers but this is needed for windows
// url = std::regex_replace(url, std::regex("https"), "http");
auto outputPath = std::filesystem::path(TMP_DOWNLOAD_FOLDER) / "cover.jpg"; auto outputPath = std::filesystem::path(TMP_DOWNLOAD_FOLDER) / "cover.jpg";
FILE* file = std::fopen(outputPath.string().c_str(), "wb"); FILE* file = std::fopen(outputPath.string().c_str(), "wb");
if (!file) { if (!file) {
@@ -98,11 +97,10 @@ auto downloadVideo(const std::string& url) -> bool {
"--quiet " "--quiet "
"-f \"bestaudio/best\" " "-f \"bestaudio/best\" "
"-x --audio-format mp3 " "-x --audio-format mp3 "
"-o \"{}/%(title)s.%(ext)s\" " "-o \"{}/_%(title)s.%(ext)s\" "
"{}", "{}",
BIN_DENO, TMP_DOWNLOAD_FOLDER, url)); BIN_DENO, TMP_DOWNLOAD_FOLDER, url));
printf("Finished downloading video %s.\n", url.c_str());
return true; return true;
} }
@@ -141,18 +139,6 @@ auto moveAndSortTmpFilesToFolder(const std::string& folder, const std::string& a
return true; 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 { auto musicFolder() -> std::string {
#ifdef _WIN32 #ifdef _WIN32
const char* userProfile = std::getenv("USERPROFILE"); const char* userProfile = std::getenv("USERPROFILE");
@@ -164,3 +150,17 @@ auto musicFolder() -> std::string {
return ""; return "";
} }
auto writeMetadataToTmpFolder(const std::string& artist, const std::string& album, const std::string& coverUrl)
-> void {
downloadImageToTmpFolder(coverUrl);
size_t index = 1;
for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) {
if (std::filesystem::path(entry.path().string()).filename().string() == "cover.jpg") continue;
metadata(entry.path().string(), artist, album, index);
std::filesystem::remove(entry.path().string());
index++;
}
}
+2 -2
View File
@@ -17,6 +17,6 @@ auto writeAndReturnDataJson(const std::string& url) -> nlohmann::json;
auto moveAndSortTmpFilesToFolder(const std::string& folder, const std::string& artist, const std::string& album) auto moveAndSortTmpFilesToFolder(const std::string& folder, const std::string& artist, const std::string& album)
-> bool; -> bool;
auto autoDetermineRip(const std::string& musicFolder, const std::string& url) -> bool;
auto musicFolder() -> std::string; auto musicFolder() -> std::string;
auto writeMetadataToTmpFolder(const std::string& artist, const std::string& album, const std::string& coverUrl) -> void;
+22 -58
View File
@@ -1,18 +1,9 @@
#include "video.hpp" #include "video.hpp"
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <format>
#include <fstream>
#include <future>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <regex> #include <regex>
#include <string> #include <string>
#include <vector>
#include "config.hpp"
#include "utils.hpp" #include "utils.hpp"
using json = nlohmann::json; using json = nlohmann::json;
@@ -21,32 +12,44 @@ Video::Video(const std::string& videoUrl) {
this->videoUrl = std::regex_replace(videoUrl, std::regex("music.youtube"), "www.youtube"); this->videoUrl = std::regex_replace(videoUrl, std::regex("music.youtube"), "www.youtube");
} }
auto Video::rip(const std::string& musicFolder) -> bool { auto Video::rip(const std::string& musicFolder, std::function<void(int)> status) -> bool {
printf("Writting to data.json\n"); status(0);
json data = writeAndReturnDataJson(videoUrl);
data = writeAndReturnDataJson(videoUrl);
artist = data["uploader"];
artist = std::regex_replace(artist, std::regex(" - Topic"), "");
album = data["title"];
album = std::regex_replace(album, std::regex("Album - "), "");
status(50);
if (!download()) { if (!download()) {
printf("ERROR: Failed to download playlsit\n"); printf("ERROR: Failed to download playlsit\n");
return false; return false;
} }
if (!writeMetadata(data)) { status(80);
printf("ERROR: Failed to write metatdata for playlsit\n");
return false;
}
std::string artist; std::string coverUrl = data["thumbnails"][1]["url"];
std::string album; writeMetadataToTmpFolder(artist, album, coverUrl);
aristAndAlbumName(data, artist, album);
status(95);
if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) { if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) {
printf("ERROR: Failed to move files to music folder\n"); printf("ERROR: Failed to move files to music folder\n");
return false; return false;
} }
status(100);
return true; return true;
} }
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 { auto Video::download() -> bool {
if (videoUrl == "") { if (videoUrl == "") {
printf("ERROR: Tried to download empty video, %s\n", videoUrl.c_str()); printf("ERROR: Tried to download empty video, %s\n", videoUrl.c_str());
@@ -63,44 +66,5 @@ auto Video::download() -> bool {
return false; return false;
} }
printf("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);
printf("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str());
printf("Finding album cover.\n");
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().string()).filename().string() == "cover.jpg") continue;
metadata(entry.path().string(), artist, album, index);
printf("Finished writting metadata for %s.\n", entry.path().string().c_str());
std::filesystem::remove(entry.path().string());
index++;
}
printf("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; return true;
} }
+10 -5
View File
@@ -1,17 +1,22 @@
#pragma once #pragma once
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json.hpp>
#include <string> #include <string>
class Video { #include "content.hpp"
class Video : public Content {
std::string videoUrl; std::string videoUrl;
std::string artist = "";
std::string album = "";
nlohmann::json data;
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, std::function<void(int)> status) -> bool;
auto info(std::string& artist, std::string& title) -> void;
private: private:
auto download() -> bool; auto download() -> bool;
auto writeMetadata(const nlohmann::json& data) -> bool;
auto aristAndAlbumName(const nlohmann::json& data, std::string& artist, std::string& album) -> bool;
}; };
+1 -1
View File
@@ -1,6 +1,6 @@
mkdir build mkdir build
cd build cd build
cmake -DCMAKE_TOOLCHAIN_FILE=~/TC-mingw.cmake -GNinja .. cmake -DCMAKE_TOOLCHAIN_FILE=cmake/windows-x86_64-toolchain.cmake -GNinja ..
ninja ninja
wine YTMusicDL26.exe wine YTMusicDL26.exe
cd .. cd ..