13 Commits
Author SHA1 Message Date
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
Ethanscharlie fe6dc020ca Packing for windows and linux 2026-08-20 12:42:22 -06:00
Ethanscharlie 8001311b52 Working on windows 2026-08-20 12:39:26 -06:00
Ethanscharlie 4fc7457052 Bin constant 2026-08-20 12:14:39 -06:00
Ethanscharlie 96276d7639 packaging 2026-08-20 12:09:42 -06:00
Ethanscharlie 070e583bd2 New input system 2026-08-20 11:53:20 -06:00
Ethanscharlie 280c84ac6e Fully functional 2026-08-20 11:29:41 -06:00
Ethanscharlie 98d54c58a0 Can download videos as singles 2026-08-20 11:13:34 -06:00
Ethanscharlie ee378b19a3 Fully working album download 2026-08-20 11:02:06 -06:00
Ethanscharlie a33cab8326 OOP structure 2026-08-20 10:27:29 -06:00
Ethanscharlie 01b51efa63 Downloaded full album with cover art and metadata 2026-08-19 23:17:16 -06:00
16 changed files with 696 additions and 181 deletions
+1
View File
@@ -1,2 +1,3 @@
build
.cache
builds
+12 -7
View File
@@ -7,6 +7,9 @@ project(YTMusicDL26)
add_executable(YTMusicDL26
src/main.cpp
src/playlist.cpp
src/video.cpp
src/utils.cpp
)
target_include_directories(YTMusicDL26 PUBLIC
@@ -16,16 +19,18 @@ target_include_directories(YTMusicDL26 PUBLIC
include(cmake/CPM.cmake)
# CPMAddPackage(
# NAME id3v2lib
# GIT_REPOSITORY https://github.com/larsbs/id3v2lib.git
# GIT_TAG master
# )
set(CURL_USE_LIBPSL OFF CACHE BOOL "" FORCE)
# target_link_libraries(YTMusicDL26 PUBLIC
# )
CPMAddPackage("gh:nlohmann/json#v3.12.0")
CPMAddPackage("gh:curl/curl#curl-8_21_0")
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}/config DESTINATION ${CMAKE_BINARY_DIR})
if (WIN32)
target_link_options(YTMusicDL26 PRIVATE
Binary file not shown.
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
{
}
+40
View File
@@ -0,0 +1,40 @@
mkdir builds
cd builds
# Build for linux
mkdir linux
cd linux
cmake -GNinja ../..
ninja
cd ..
# Linux release
rm -r linux-release
mkdir linux-release
cd linux-release
cp ../linux/YTMusicDL26 .
mkdir bin
cp -r ../linux/bin/linux bin/
cp -r ../linux/config .
cd ..
zip -r linux-release.zip linux-release
# Build for windows
mkdir windows
cd windows
cmake -DCMAKE_TOOLCHAIN_FILE=~/TC-mingw.cmake -GNinja ../..
ninja
cd ..
# Windows release
rm -r windows-release
mkdir windows-release
cd windows-release
cp ../windows/YTMusicDL26.exe .
mkdir bin
cp -r ../windows/bin/windows bin/
cp -r ../windows/config .
cd ..
zip -r windows-release.zip windows-release
cd ..
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#define TMP_DOWNLOAD_FOLDER "tmp/download"
#define MAX_THREADS 4
#ifdef _WIN32
#define BIN_FOLDER "bin/windows"
#define BIN_YT_DLP "bin/windows/yt-dlp.exe"
#define BIN_FFMPEG "bin/windows/ffmpeg.exe"
#define BIN_DENO "bin/windows/deno.exe"
#else
#define BIN_FOLDER "bin/linux"
#define BIN_YT_DLP "bin/linux/yt-dlp"
#define BIN_FFMPEG "bin/linux/ffmpeg"
#define BIN_DENO "bin/linux/deno"
#endif
+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;
};
+72 -174
View File
@@ -1,190 +1,88 @@
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <chrono>
#include <deque>
#include <filesystem>
#include <format>
#include <fstream>
#include <future>
#include <iostream>
#include <queue>
#include <regex>
#include <string>
#include <vector>
#include <thread>
#define TMP_DOWNLOAD_FOLDER "tmp/download"
#define MAX_THREADS 4
#include "config.hpp"
#include "content.hpp"
#include "playlist.hpp"
#include "utils.hpp"
#include "video.hpp"
// 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());
return true;
auto createContent(const std::string& currentUrl) -> Content* {
if (std::regex_search(currentUrl, std::regex("youtube.com/playlist"))) {
return new Playlist(currentUrl);
}
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();
system(std::format("bin/linux/ffmpeg "
"-hide_banner -loglevel error "
"-i \"{}\" -c copy "
"-metadata title=\"{}\" "
"-metadata artist=\"{}\" "
"-metadata album=\"{}\" "
"-metadata track=\"{}\" "
"\"{}/{}_{}.mp3\"",
file, name, artist, album, index, TMP_DOWNLOAD_FOLDER, index, name)
.c_str());
if (std::regex_search(currentUrl, std::regex("youtube.com/watch"))) {
return new Video(currentUrl);
}
auto cleanTmpDownloadFolder() -> bool {
for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) {
std::filesystem::remove_all(entry.path());
}
return true;
}
auto urlsFromPlaylist(const std::string& url) -> std::vector<std::string> {
std::vector<std::string> urls;
std::filesystem::remove("urls.txt");
ytdlp(
std::format("--js-runtimes \"deno:bin/linux/deno\" "
"--quiet "
"--flat-playlist "
"--print-to-file \"%(webpage_url)s\" "
"urls.txt \"{}\"",
url));
std::ifstream f("urls.txt");
if (!f.is_open()) {
printf("ERROR: Couldn't open urls.txt\n");
return urls;
}
std::string line;
while (std::getline(f, line)) {
urls.push_back(line);
}
if (urls.empty()) printf("ERROR: Couldn't find any urls for playlist %s\n", url.c_str());
return urls;
}
auto dataFromYtdlpPlaylist(const std::string& playlistUrl, const std::string& data) -> std::string {
ytdlp(
std::format("--quiet "
"--no-warnings "
"--flat-playlist "
"--playlist-items 1 "
"--print-to-file \"%({})s\" data.txt \"{}\"",
data, playlistUrl));
std::ifstream f("data.txt");
if (!f.is_open()) {
printf("ERROR: Couldn't open data.txt\n");
}
std::string s;
std::string line;
while (std::getline(f, line)) {
s = line;
}
return s;
};
auto playlistTitle(const std::string& playlistUrl) {
std::string s = dataFromYtdlpPlaylist(playlistUrl, "playlist_title");
s = std::regex_replace(s, std::regex("Album - "), "");
return s;
}
auto channelName(const std::string& playlistUrl) {
std::string s = dataFromYtdlpPlaylist(playlistUrl, "channel");
s = std::regex_replace(s, std::regex(" - Topic"), "");
return s;
}
auto downloadVideo(const std::string& url) -> bool {
if (url == "") {
printf("ERROR: Cannot download video with empty url\n");
return false;
}
ytdlp(
std::format("--js-runtimes \"deno:bin/linux/deno\" "
"--quiet "
"-f \"bestaudio/best\" "
"-x --audio-format mp3 "
"-o \"{}/%(title)s.%(ext)s\" "
"{}",
TMP_DOWNLOAD_FOLDER, url));
printf("Finished downloading video %s.\n", url.c_str());
return true;
}
auto downloadPlaylist(std::string playlistUrl) -> bool {
std::vector<std::string> videoUrls = urlsFromPlaylist(playlistUrl);
if (videoUrls.empty()) {
printf("ERROR: Tried to download empty playlist, %s\n", playlistUrl.c_str());
return false;
}
if (!cleanTmpDownloadFolder()) {
printf("ERROR: Playlist download couldn't clean tmp download folder\n");
return false;
}
playlistUrl = std::regex_replace(playlistUrl, std::regex("music.youtube"), "www.youtube");
int count = 0;
for (size_t i = 0; i < videoUrls.size(); i += MAX_THREADS) {
std::vector<std::future<void>> futures;
size_t end = std::min(i + MAX_THREADS, videoUrls.size());
for (size_t j = i; j < end; ++j) {
futures.push_back(std::async(std::launch::async, [&videoUrls, j]() { downloadVideo(videoUrls[j]); }));
}
for (auto& future : futures) {
future.get();
count++;
printf("Finished [%d/%d] in playlist %s\n", count, (int)videoUrls.size(), playlistUrl.c_str());
}
}
printf("Finished downloading playlist.\n");
return true;
}
auto writeAlbumMetadataToTmpFolder(const std::string& playlistUrl) -> bool {
std::string artist = channelName(playlistUrl);
std::string album = playlistTitle(playlistUrl);
printf("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str());
size_t index = 1;
for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) {
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());
return true;
return nullptr;
}
auto main(int argc, char* argv[]) -> int {
auto musicDir = "/home/ethanscharlie/Downloads/tmp";
std::string url = "https://music.youtube.com/playlist?list=OLAK5uy_kwG9Ln_fS_R-Oa8F-JIgs8PPBoRgDe8ok";
std::deque<std::string> urls;
printf(
"Welcome to YTMusicDL26! You can use playlists or video from youtube or youtube music (youtube music is "
"better).\n");
printf("Type \"end\" when your finished.\n");
int index = 0;
while (true) {
std::string input;
printf("%d: ", index);
std::cin >> input;
if (input == "end") {
break;
}
urls.push_back(input);
index++;
}
printf("Starting rips...\n");
std::string musicDir = musicFolder();
std::filesystem::create_directories(TMP_DOWNLOAD_FOLDER);
url = std::regex_replace(url, std::regex("music.youtube"), "www.youtube");
downloadPlaylist(url);
writeAlbumMetadataToTmpFolder(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));
}
}
+149
View File
@@ -0,0 +1,149 @@
#include "playlist.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;
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...";
writeToLog("Writting to data.json\n");
json data = writeAndReturnDataJson(playlistUrl);
// Get the name of the artist and album from data.json
std::string artist = data["entries"][0]["uploader"];
artist = std::regex_replace(artist, std::regex(" - Topic"), "");
std::string album = data["title"];
album = std::regex_replace(album, std::regex("Album - "), "");
currentArtist = artist;
currentAlbum = album;
percent = 30;
// Download the content as mp3
if (!download()) {
writeToLog("ERROR: Failed to download playlsit\n");
return false;
}
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;
}
auto Playlist::info(std::string& artist, std::string& title) -> int {
artist = currentArtist;
title = currentAlbum;
return percent;
}
auto Playlist::download() -> bool {
std::vector<std::string> videoUrls = urlsFromPlaylist(playlistUrl);
if (videoUrls.empty()) {
writeToLog("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");
return false;
}
playlistUrl = std::regex_replace(playlistUrl, std::regex("music.youtube"), "www.youtube");
int count = 0;
for (size_t i = 0; i < videoUrls.size(); i += MAX_THREADS) {
std::vector<std::future<void>> futures;
size_t end = std::min(i + MAX_THREADS, videoUrls.size());
for (size_t j = i; j < end; ++j) {
futures.push_back(std::async(std::launch::async, [&videoUrls, j]() { downloadVideo(videoUrls[j]); }));
}
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());
}
}
writeToLog("Finished downloading playlist.\n");
return true;
}
auto Playlist::urlsFromPlaylist(const std::string& url) -> std::vector<std::string> {
std::vector<std::string> urls;
std::string urlsFileLocation = "tmp/urls.txt";
std::filesystem::remove(urlsFileLocation);
ytdlp(
std::format("--js-runtimes \"deno:{}\" "
"--quiet "
"--flat-playlist "
"--print-to-file \"%(webpage_url)s\" "
"{} \"{}\"",
BIN_DENO, urlsFileLocation, url));
std::ifstream f(urlsFileLocation);
if (!f.is_open()) {
writeToLog("ERROR: Couldn't open urls.txt\n");
return urls;
}
std::string line;
while (std::getline(f, line)) {
urls.push_back(line);
}
if (urls.empty()) writeToLog("ERROR: Couldn't find any urls for playlist %s\n", url.c_str());
return urls;
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <nlohmann/json_fwd.hpp>
#include <string>
#include <vector>
#include "content.hpp"
class Playlist : public Content {
std::string playlistUrl;
int percent = 0;
std::string currentArtist;
std::string currentAlbum;
public:
Playlist(const std::string& playlistUrl);
auto rip(const std::string& musicFolder) -> bool;
auto info(std::string& artist, std::string& title) -> int;
private:
auto download() -> bool;
auto urlsFromPlaylist(const std::string& url) -> std::vector<std::string>;
};
+198
View File
@@ -0,0 +1,198 @@
#include <curl/curl.h>
#include <curl/easy.h>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <format>
#include <fstream>
#include <nlohmann/json.hpp>
#include <regex>
#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());
return true;
}
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();
name = std::regex_replace(name, std::regex(".mp3"), "");
name.erase(0, 1); // Remove leading underscore
system(std::format("{} "
"-hide_banner -loglevel error "
"-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\"",
BIN_FFMPEG, file, TMP_DOWNLOAD_FOLDER, name, artist, album, index, TMP_DOWNLOAD_FOLDER, name)
.c_str());
}
auto cleanTmpDownloadFolder() -> bool {
for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) {
std::filesystem::remove_all(entry.path().string());
}
return true;
}
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");
return false;
}
// 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";
FILE* file = std::fopen(outputPath.string().c_str(), "wb");
if (!file) {
curl_easy_cleanup(curl);
writeToLog("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) {
writeToLog("ERROR: %s\n", curl_easy_strerror(res));
return false;
}
return true;
}
auto downloadVideo(const std::string& url) -> bool {
if (url == "") {
writeToLog("ERROR: Cannot download video with empty url\n");
return false;
}
ytdlp(
std::format("--js-runtimes \"deno:{}\" "
"--quiet "
"-f \"bestaudio/best\" "
"-x --audio-format mp3 "
"-o \"{}/_%(title)s.%(ext)s\" "
"{}",
BIN_DENO, TMP_DOWNLOAD_FOLDER, url));
writeToLog("Finished downloading video %s.\n", url.c_str());
return true;
}
auto writeAndReturnDataJson(const std::string& url) -> json {
std::filesystem::remove("tmp/data.json");
ytdlp(
std::format("--js-runtimes \"deno:{}\" "
"--dump-single-json "
"\"{}\" "
"> tmp/data.json",
BIN_DENO, url));
std::ifstream f("tmp/data.json");
if (!f) {
writeToLog("ERROR: Couldn't open data.json\n");
return false;
}
return json::parse(f);
}
auto moveAndSortTmpFilesToFolder(const std::string& folder, const std::string& artist, const std::string& album)
-> bool {
std::filesystem::path folderPath(folder);
std::filesystem::path path = folderPath / artist / album;
std::filesystem::create_directories(path);
for (const auto& entry : std::filesystem::directory_iterator(path)) {
std::filesystem::remove_all(entry.path().string());
}
for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) {
std::filesystem::rename(entry, path / entry.path().filename());
}
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");
if (userProfile) return std::format("{}/Music", userProfile);
#else
const char* home = std::getenv("HOME");
if (home) return std::filesystem::path(home) / "Music";
#endif
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);
writeToLog("Finished writting metadata for %s.\n", entry.path().string().c_str());
std::filesystem::remove(entry.path().string());
index++;
}
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <cstdarg>
#include <nlohmann/json_fwd.hpp>
#include <string>
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 cleanTmpDownloadFolder() -> bool;
auto downloadImageToTmpFolder(std::string url) -> bool;
auto downloadVideo(const std::string& url) -> bool;
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;
+117
View File
@@ -0,0 +1,117 @@
#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;
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...";
writeToLog("Writting to data.json\n");
json data = writeAndReturnDataJson(videoUrl);
std::string artist;
std::string album;
aristAndAlbumName(data, artist, album);
currentAlbum = album;
currentArtist = artist;
percent = 50;
if (!download()) {
writeToLog("ERROR: Failed to download playlsit\n");
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;
}
auto Video::info(std::string& artist, std::string& title) -> int {
artist = currentArtist;
title = currentAlbum;
return percent;
}
auto Video::download() -> bool {
if (videoUrl == "") {
writeToLog("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");
return false;
}
if (!downloadVideo(videoUrl)) {
writeToLog("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;
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <nlohmann/json_fwd.hpp>
#include <string>
#include "content.hpp"
class Video : public Content {
std::string videoUrl;
int percent = 0;
std::string currentArtist;
std::string currentAlbum;
public:
Video(const std::string& videoUrl);
auto rip(const std::string& musicFolder) -> bool;
auto info(std::string& artist, std::string& title) -> int;
private:
auto download() -> bool;
auto writeMetadata(const nlohmann::json& data) -> bool;
auto aristAndAlbumName(const nlohmann::json& data, std::string& artist, std::string& album) -> bool;
};
+6
View File
@@ -0,0 +1,6 @@
mkdir build
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=~/TC-mingw.cmake -GNinja ..
ninja
wine YTMusicDL26.exe
cd ..