185 lines
5.5 KiB
C++
185 lines
5.5 KiB
C++
#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;
|
|
|
|
// 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 {
|
|
printf("Downloading image with url %s\n", url.c_str());
|
|
|
|
CURL* curl = curl_easy_init();
|
|
if (!curl) {
|
|
printf("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);
|
|
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:{}\" "
|
|
"--quiet "
|
|
"-f \"bestaudio/best\" "
|
|
"-x --audio-format mp3 "
|
|
"-o \"{}/_%(title)s.%(ext)s\" "
|
|
"{}",
|
|
BIN_DENO, TMP_DOWNLOAD_FOLDER, url));
|
|
|
|
printf("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) {
|
|
printf("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);
|
|
printf("Finished writting metadata for %s.\n", entry.path().string().c_str());
|
|
std::filesystem::remove(entry.path().string());
|
|
index++;
|
|
}
|
|
}
|