Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28c4318b00 | ||
|
|
ce457fbd17 | ||
|
|
aed13dc18f |
@@ -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
@@ -1,12 +1,32 @@
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "content.hpp"
|
||||
#include "playlist.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 {
|
||||
std::vector<std::string> urls;
|
||||
std::deque<std::string> urls;
|
||||
|
||||
printf(
|
||||
"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::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{[¤tContent, &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([¤tContent, &musicDir]() { currentContent->rip(musicDir); });
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
+50
-54
@@ -22,41 +22,74 @@ Playlist::Playlist(const std::string& playlistUrl) {
|
||||
}
|
||||
|
||||
auto Playlist::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(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()) {
|
||||
printf("ERROR: Failed to download playlsit\n");
|
||||
writeToLog("ERROR: Failed to download playlsit\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!writeMetadata(data)) {
|
||||
printf("ERROR: Failed to write metatdata for playlsit\n");
|
||||
return false;
|
||||
}
|
||||
percent = 80;
|
||||
|
||||
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());
|
||||
|
||||
// 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)) {
|
||||
printf("ERROR: Failed to move files to music folder\n");
|
||||
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()) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -76,49 +109,12 @@ auto Playlist::download() -> bool {
|
||||
for (auto& future : futures) {
|
||||
future.get();
|
||||
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");
|
||||
|
||||
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 - "), "");
|
||||
writeToLog("Finished downloading playlist.\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -139,7 +135,7 @@ auto Playlist::urlsFromPlaylist(const std::string& url) -> std::vector<std::stri
|
||||
|
||||
std::ifstream f(urlsFileLocation);
|
||||
if (!f.is_open()) {
|
||||
printf("ERROR: Couldn't open urls.txt\n");
|
||||
writeToLog("ERROR: Couldn't open urls.txt\n");
|
||||
return urls;
|
||||
}
|
||||
|
||||
@@ -148,6 +144,6 @@ auto Playlist::urlsFromPlaylist(const std::string& url) -> std::vector<std::stri
|
||||
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;
|
||||
}
|
||||
|
||||
+7
-3
@@ -4,16 +4,20 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Playlist {
|
||||
#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 writeMetadata(const nlohmann::json& data) -> bool;
|
||||
auto aristAndAlbumName(const nlohmann::json& data, std::string& artist, std::string& album) -> bool;
|
||||
auto urlsFromPlaylist(const std::string& url) -> std::vector<std::string>;
|
||||
};
|
||||
|
||||
+43
-11
@@ -17,6 +17,20 @@
|
||||
|
||||
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());
|
||||
@@ -26,6 +40,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 {
|
||||
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 "
|
||||
@@ -39,9 +54,8 @@ auto metadata(const std::string& file, const std::string& artist, const std::str
|
||||
"-metadata artist=\"{}\" "
|
||||
"-metadata album=\"{}\" "
|
||||
"-metadata track=\"{}\" "
|
||||
"\"{}/{}_{}.mp3\"",
|
||||
BIN_FFMPEG, file, TMP_DOWNLOAD_FOLDER, name, artist, album, index, TMP_DOWNLOAD_FOLDER, index,
|
||||
name)
|
||||
"\"{}/{}.mp3\"",
|
||||
BIN_FFMPEG, file, TMP_DOWNLOAD_FOLDER, name, artist, album, index, TMP_DOWNLOAD_FOLDER, name)
|
||||
.c_str());
|
||||
}
|
||||
|
||||
@@ -54,18 +68,21 @@ 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) {
|
||||
printf("ERROR: Failed to initialize curl.\n");
|
||||
writeToLog("ERROR: Failed to initialize curl.\n");
|
||||
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";
|
||||
FILE* file = std::fopen(outputPath.string().c_str(), "wb");
|
||||
if (!file) {
|
||||
curl_easy_cleanup(curl);
|
||||
printf("ERROR: Failed to create cover file.\n");
|
||||
writeToLog("ERROR: Failed to create cover file.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -80,7 +97,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool {
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK) {
|
||||
printf("ERROR: %s\n", curl_easy_strerror(res));
|
||||
writeToLog("ERROR: %s\n", curl_easy_strerror(res));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -89,7 +106,7 @@ auto downloadImageToTmpFolder(std::string url) -> bool {
|
||||
|
||||
auto downloadVideo(const std::string& url) -> bool {
|
||||
if (url == "") {
|
||||
printf("ERROR: Cannot download video with empty url\n");
|
||||
writeToLog("ERROR: Cannot download video with empty url\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -98,11 +115,11 @@ auto downloadVideo(const std::string& url) -> bool {
|
||||
"--quiet "
|
||||
"-f \"bestaudio/best\" "
|
||||
"-x --audio-format mp3 "
|
||||
"-o \"{}/%(title)s.%(ext)s\" "
|
||||
"-o \"{}/_%(title)s.%(ext)s\" "
|
||||
"{}",
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -117,7 +134,7 @@ auto writeAndReturnDataJson(const std::string& url) -> json {
|
||||
|
||||
std::ifstream f("tmp/data.json");
|
||||
if (!f) {
|
||||
printf("ERROR: Couldn't open data.json\n");
|
||||
writeToLog("ERROR: Couldn't open data.json\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -164,3 +181,18 @@ auto musicFolder() -> std::string {
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <cstdarg>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <string>
|
||||
|
||||
@@ -20,3 +21,7 @@ auto moveAndSortTmpFilesToFolder(const std::string& folder, const std::string& a
|
||||
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;
|
||||
|
||||
+43
-32
@@ -22,48 +22,69 @@ Video::Video(const std::string& videoUrl) {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 album;
|
||||
aristAndAlbumName(data, artist, album);
|
||||
|
||||
if (!moveAndSortTmpFilesToFolder(musicFolder, artist, album)) {
|
||||
printf("ERROR: Failed to move files to music folder\n");
|
||||
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 == "") {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!downloadVideo(videoUrl)) {
|
||||
printf("ERROR: Failed to download video\n");
|
||||
writeToLog("ERROR: Failed to download video\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("Finished downloading video %s\n", videoUrl.c_str());
|
||||
writeToLog("Finished downloading video %s\n", videoUrl.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -72,25 +93,15 @@ auto Video::writeMetadata(const nlohmann::json& data) -> bool {
|
||||
std::string 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 album cover.\n");
|
||||
writeToLog("Finding thumbnail.\n");
|
||||
std::string coverUrl = data["thumbnails"][1]["url"];
|
||||
downloadImageToTmpFolder(coverUrl);
|
||||
|
||||
printf("Writting data to files.\n");
|
||||
writeToLog("Writting data to files.\n");
|
||||
writeMetadataToTmpFolder(artist, album, 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);
|
||||
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());
|
||||
writeToLog("Finished writting metadata for %s by %s.\n", album.c_str(), artist.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+7
-1
@@ -3,12 +3,18 @@
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <string>
|
||||
|
||||
class Video {
|
||||
#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;
|
||||
|
||||
Reference in New Issue
Block a user