OOP structure
This commit is contained in:
@@ -7,6 +7,8 @@ project(YTMusicDL26)
|
|||||||
|
|
||||||
add_executable(YTMusicDL26
|
add_executable(YTMusicDL26
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
src/playlist.cpp
|
||||||
|
src/utils.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(YTMusicDL26 PUBLIC
|
target_include_directories(YTMusicDL26 PUBLIC
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define TMP_DOWNLOAD_FOLDER "tmp/download"
|
||||||
|
#define MAX_THREADS 4
|
||||||
+6
-238
@@ -1,249 +1,16 @@
|
|||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <curl/easy.h>
|
#include <curl/easy.h>
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <fstream>
|
|
||||||
#include <future>
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#define TMP_DOWNLOAD_FOLDER "tmp/download"
|
#include "config.hpp"
|
||||||
#define MAX_THREADS 4
|
#include "playlist.hpp"
|
||||||
|
#include "utils.hpp"
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
// 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 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();
|
|
||||||
name = std::regex_replace(name, std::regex(".mp3"), "");
|
|
||||||
|
|
||||||
system(std::format("bin/linux/ffmpeg "
|
|
||||||
"-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\"",
|
|
||||||
file, TMP_DOWNLOAD_FOLDER, name, artist, album, index, TMP_DOWNLOAD_FOLDER, index, name)
|
|
||||||
.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cleanTmpDownloadFolder() -> bool {
|
|
||||||
for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) {
|
|
||||||
std::filesystem::remove_all(entry.path());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto downloadImageToTmpFolder(const std::string& url) -> bool {
|
|
||||||
CURL* curl = curl_easy_init();
|
|
||||||
if (!curl) {
|
|
||||||
printf("ERROR: Failed to initialize curl.\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE* file = std::fopen(std::format("{}/cover.jpg", TMP_DOWNLOAD_FOLDER).c_str(), "wb");
|
|
||||||
if (!file) {
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
printf("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) {
|
|
||||||
printf("ERROR: %s\n", curl_easy_strerror(res));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 channelNamePlaylist(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 = channelNamePlaylist(playlistUrl);
|
|
||||||
std::string album = playlistTitle(playlistUrl);
|
|
||||||
printf("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str());
|
|
||||||
|
|
||||||
printf("Finding album cover.\n");
|
|
||||||
std::filesystem::remove("tmp/data.json");
|
|
||||||
ytdlp(
|
|
||||||
std::format("--js-runtimes \"deno:bin/linux/deno\" "
|
|
||||||
"--dump-single-json "
|
|
||||||
"\"{}\" "
|
|
||||||
"> tmp/data.json",
|
|
||||||
playlistUrl));
|
|
||||||
|
|
||||||
std::ifstream f("tmp/data.json");
|
|
||||||
if (!f) {
|
|
||||||
printf("ERROR: Couldn't open data.json\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
json data = json::parse(f);
|
|
||||||
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()).filename().string() == "cover.jpg") continue;
|
|
||||||
|
|
||||||
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 writting metadata for %s by %s.\n", album.c_str(), artist.c_str());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto main(int argc, char* argv[]) -> int {
|
auto main(int argc, char* argv[]) -> int {
|
||||||
auto musicDir = "/home/ethanscharlie/Downloads/tmp";
|
auto musicDir = "/home/ethanscharlie/Downloads/tmp";
|
||||||
@@ -252,6 +19,7 @@ auto main(int argc, char* argv[]) -> int {
|
|||||||
std::filesystem::create_directories(TMP_DOWNLOAD_FOLDER);
|
std::filesystem::create_directories(TMP_DOWNLOAD_FOLDER);
|
||||||
|
|
||||||
url = std::regex_replace(url, std::regex("music.youtube"), "www.youtube");
|
url = std::regex_replace(url, std::regex("music.youtube"), "www.youtube");
|
||||||
downloadPlaylist(url);
|
auto playlist = Playlist(url);
|
||||||
writeAlbumMetadataToTmpFolder(url);
|
playlist.download();
|
||||||
|
playlist.writeMetadata();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
#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) : playlistUrl(playlistUrl) {}
|
||||||
|
|
||||||
|
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());
|
||||||
|
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 Playlist::writeMetadata() -> bool {
|
||||||
|
std::string artist = channelNamePlaylist();
|
||||||
|
std::string album = playlistTitle();
|
||||||
|
printf("Starting to write metadata for %s by %s.\n", album.c_str(), artist.c_str());
|
||||||
|
|
||||||
|
printf("Finding album cover.\n");
|
||||||
|
std::filesystem::remove("tmp/data.json");
|
||||||
|
ytdlp(
|
||||||
|
std::format("--js-runtimes \"deno:bin/linux/deno\" "
|
||||||
|
"--dump-single-json "
|
||||||
|
"\"{}\" "
|
||||||
|
"> tmp/data.json",
|
||||||
|
playlistUrl));
|
||||||
|
|
||||||
|
std::ifstream f("tmp/data.json");
|
||||||
|
if (!f) {
|
||||||
|
printf("ERROR: Couldn't open data.json\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json data = json::parse(f);
|
||||||
|
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()).filename().string() == "cover.jpg") continue;
|
||||||
|
|
||||||
|
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 writting metadata for %s by %s.\n", album.c_str(), artist.c_str());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Playlist::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 Playlist::dataFromYtdlpPlaylist(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 Playlist::playlistTitle() -> std::string {
|
||||||
|
std::string s = dataFromYtdlpPlaylist("playlist_title");
|
||||||
|
s = std::regex_replace(s, std::regex("Album - "), "");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Playlist::channelNamePlaylist() -> std::string {
|
||||||
|
std::string s = dataFromYtdlpPlaylist("channel");
|
||||||
|
s = std::regex_replace(s, std::regex(" - Topic"), "");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Playlist {
|
||||||
|
std::string playlistUrl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Playlist(const std::string& playlistUrl);
|
||||||
|
auto download() -> bool;
|
||||||
|
auto writeMetadata() -> bool;
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto urlsFromPlaylist(const std::string& url) -> std::vector<std::string>;
|
||||||
|
auto dataFromYtdlpPlaylist(const std::string& data) -> std::string;
|
||||||
|
auto playlistTitle() -> std::string;
|
||||||
|
auto channelNamePlaylist() -> std::string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#include <curl/curl.h>
|
||||||
|
#include <curl/easy.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <format>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <regex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "config.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 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"), "");
|
||||||
|
|
||||||
|
system(std::format("bin/linux/ffmpeg "
|
||||||
|
"-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\"",
|
||||||
|
file, TMP_DOWNLOAD_FOLDER, name, artist, album, index, TMP_DOWNLOAD_FOLDER, index, name)
|
||||||
|
.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cleanTmpDownloadFolder() -> bool {
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(TMP_DOWNLOAD_FOLDER)) {
|
||||||
|
std::filesystem::remove_all(entry.path());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto downloadImageToTmpFolder(const std::string& url) -> bool {
|
||||||
|
CURL* curl = curl_easy_init();
|
||||||
|
if (!curl) {
|
||||||
|
printf("ERROR: Failed to initialize curl.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* file = std::fopen(std::format("{}/cover.jpg", TMP_DOWNLOAD_FOLDER).c_str(), "wb");
|
||||||
|
if (!file) {
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
printf("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) {
|
||||||
|
printf("ERROR: %s\n", curl_easy_strerror(res));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
#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(const std::string& url) -> bool;
|
||||||
|
|
||||||
|
auto downloadVideo(const std::string& url) -> bool;
|
||||||
Reference in New Issue
Block a user