monstercat-downloader/main.cpp

471 lines
8.5 KiB
C++

// C++ Libs
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <utility>
// C Libs
#include <stdlib.h>
// File Libs
#include "json.hpp"
struct tag
{
std::string artist;
std::string title;
std::string number;
};
constexpr const char RELEASE_JSON[] = "release.json";
constexpr bool IS_MP3 = true;
constexpr bool IS_FLAC = false;
#ifdef _WIN32
constexpr const char FOLDER_DELIM = '\\';
#else
constexpr const char FOLDER_DELIM = '/';
#endif
using namespace std;
using nlohmann::json;
// 1. Login
// 2. For each release
// 2.1 Download Release JSON
// 2.2 Parse JSON
// 2.3 Make Release Folder
// (Catalog Number %*d - Artist - Title)
// 2.4 Make MP3 & FLAC Folders ??? (If Tracks > 1)
// 2.5 Download Cover
// 2.6 Make 750x750 Cover JPEG Image
// 2.7 Rename Cover Image (Proper Extension)
// 2.8 Download Track (MP3)
// (Track Num - Artist - Title)
// 2.9 Tag MP3
// 2.10 Download Track (FLAC)
// (Track Num - Artist - Title)
// 2.11 Tag FLAC
// 3. Logout
void usage(const string& name)
{
cout << "Usage:" << endl;
cout << name << " <options> [path]" << endl;
cout << " -1 <catalog release id>" << endl;
cout << " Single release" << endl;
cout << " -j <json file>" << endl;
cout << " JSON file denoting release range" << endl;
cout << " path - root path to download to" << endl;
cout << " Default: ." << endl;
}
// string get_track_name(const json& info, int track_num)
// {
// return "";
// }
bool system_command(const string& cmd)
{
int result = system(cmd.c_str());
if (0 == result)
{
return true;
}
else
{
return false;
}
}
bool login()
{
string cmd;
cmd = "curl -f -L -c cookies.txt -b cookies.txt -X POST";
cmd += " https://player.monstercat.app/api/sign-in";
cmd += " -H \"Content-Type: application/json\" --data-binary \"@login.json\"";
return system_command(cmd);
}
bool logout()
{
string cmd;
cmd = "curl -f -L -c cookies.txt -b cookies.txt -X POST";
cmd += " https://player.monstercat.app/api/sign-out";
return system_command(cmd);
}
bool download_release_json(const string& catalog_release)
{
string cmd;
cmd = "curl -f -L -c cookies.txt -b cookies.txt";
cmd += " https://player.monstercat.app/api/catalog/release/";
cmd += catalog_release;
cmd += " -o ";
cmd += RELEASE_JSON;
return system_command(cmd);
}
bool download_cover(const string& release_id, const string& path)
{
string cmd;
cmd = "curl -f -L -c cookies.txt -b cookies.txt";
cmd += " \"https://www.monstercat.com/release/";
cmd += release_id;
cmd += "/cover\" -o \"";
cmd += path;
cmd += FOLDER_DELIM;
cmd += "Cover\"";
return system_command(cmd);
}
json parse_json_file(const string& filename)
{
json j;
ifstream file;
string str;
file.open(filename);
str.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
file.close();
j = json::parse(str);
return j;
}
pair<string, string> get_artist_feat(const string& str)
{
pair<string, string> result;
int pos;
pos = str.find("feat.");
if (string::npos == pos)
{
result.first = str;
result.second = "";
}
else
{
result.first = str.substr(0, pos - 1);
result.second = str.substr(pos);
}
return result;
}
pair<string, string> get_artist_title(const json& obj)
{
pair<string, string> result;
pair<string, string> artist_feat;
string str;
string feat;
bool contains;
int pos;
artist_feat = get_artist_feat(obj["Release"]["ArtistsTitle"]);
result.first = artist_feat.first;
contains = false;
str = obj["Release"]["Title"];
if (!artist_feat.second.empty())
{
str += " (";
str += artist_feat.second;
str += ')';
contains = true;
}
if (!(obj["Release"]["Version"].empty()))
{
if (contains)
{
str += " [";
}
else
{
str += " (";
}
str += obj["Release"]["Version"];
str += contains ? ']' : ')';
}
result.second = str;
return result;
}
tag get_single_tag(const json& track)
{
tag result;
pair<string, string> artist_title;
result.number = to_string(track["TrackNumber"]);
artist_title = get_artist_title(track);
result.artist = artist_title.first;
result.title = artist_title.second;
return result;
}
vector<tag> get_tags_from_release(const json& info)
{
vector<tag> result;
for (int i = 0; i < info["Tracks"].size(); ++i)
{
result.push_back(get_single_tag(info["Tracks"][i]));
}
return result;
}
bool make_dir(const string& path)
{
string cmd;
cmd = "mkdir \"";
cmd += path;
cmd += '\"';
return system_command(cmd);
}
bool download_track(const string& release_id, const string& track_id,
const string& path, const string& filename, bool single_folder, bool is_mp3)
{
string format_str;
string format_ext;
string format_folder;
string cmd;
if (is_mp3)
{
format_str = "mp3_320";
format_ext = "mp3";
format_folder = FOLDER_DELIM;
format_folder += "MP3";
}
else
{
format_str = "flac";
format_ext = "flac";
format_folder = FOLDER_DELIM;
format_folder += "FLAC";
}
cmd = "curl -f -L -c cookies.txt -b cookies.txt";
cmd += " \"https://player.monstercat.app/api/release/";
cmd += release_id;
cmd += "/track-download/";
cmd += track_id;
cmd += "?format=";
cmd += format_str;
cmd += "\" -o \"";
cmd += path;
if (!single_folder)
{
cmd += format_folder;
}
cmd += FOLDER_DELIM;
cmd += filename;
cmd += ".";
cmd += format_ext;
cmd += "\"";
return system_command(cmd);
}
string num_to_str(int num, int precision)
{
string result;
result = to_string(num);
while (result.size() < precision)
{
result.insert(result.begin(), '0');
}
return result;
}
string get_release_dir_name(const string& main_path, int release_num,
const string& artist, const string& title, int precision)
{
string path;
path = main_path;
path += FOLDER_DELIM;
path += num_to_str(release_num, precision);
path += " - ";
if ((artist != "Monstercat") && (artist != "Various Artists"))
{
path += artist;
path += " - ";
}
path += title;
return path;
}
string get_track_filename(int track_num, const string& artist,
const string& title, const string& album_artist)
{
string filename;
if (0 != track_num)
{
filename = num_to_str(track_num, 2);
filename += " - ";
}
if ((artist != "Monstercat") && (artist != "Various Artists") &&
(artist != album_artist))
{
filename += artist;
filename += " - ";
}
filename += title;
return filename;
}
bool full_donwload(const string& path, const string& release_prefix,
int release_num, const string& release_suffix)
{
bool ok;
string catalog_release_str;
json info;
string release_dir;
string track_filename;
pair<string, string> release_artist_title;
int release_precision;
bool is_single_dir;
vector<tag> tags;
catalog_release_str = release_prefix;
catalog_release_str += num_to_str(release_num, 3);
catalog_release_str == release_suffix;
release_precision = 3;
if (release_prefix == "MCS")
{
release_precision = 4;
}
// Download Release JSON
ok = download_release_json(catalog_release_str);
if (!ok)
{
return false;
}
// Parse JSON
info = parse_json_file(RELEASE_JSON);
release_artist_title = get_artist_title(info["Release"]);
release_dir = get_release_dir_name(path, release_num,
release_artist_title.first, release_artist_title.second,
release_precision);
// Make Release Folder
ok = make_dir(release_dir);
if (!ok)
{
return false;
}
is_single_dir = (info["Tracks"].size() == 1);
// Make MP3 & FLAC Folders (If more than 1 Track)
if (!is_single_dir)
{
string tmp;
tmp = release_dir;
tmp += FOLDER_DELIM;
tmp += "MP3";
ok = make_dir(tmp);
if (!ok)
{
return false;
}
tmp = release_dir;
tmp += FOLDER_DELIM;
tmp += "FLAC";
ok = make_dir(tmp);
if (!ok)
{
return false;
}
}
// Download Cover
ok = download_cover(info["Release"]["Id"], release_dir);
if (!ok)
{
return false;
}
// TODO: Make 750x750 Cover JPEG Image
// TODO: Rename Cover Image (Proper Extension)
tags = get_tags_from_release(info);
for (int i = 0; i < info["Tracks"].size(); ++i)
{
track_filename = get_track_filename(info["Tracks"][i]["TrackNumber"],
tags[i].artist, tags[i].title, release_artist_title.first);
// Download Track (MP3)
ok = download_track(info["Release"]["Id"], info["Tracks"][i]["Id"],
release_dir, track_filename, is_single_dir, IS_MP3);
if (!ok)
{
return false;
}
// TODO: Tag MP3
// Download Track (FLAC)
ok = download_track(info["Release"]["Id"], info["Tracks"][i]["Id"],
release_dir, track_filename, is_single_dir, IS_FLAC);
if (!ok)
{
return false;
}
// TODO: Tag FLAC
}
return true;
}
int main(int argc, char **argv)
{
// TODO: Implement
return 0;
}