// C++ Libs #include #include #include #include #include // C Libs #include // 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 << " [path]" << endl; cout << " -1 " << endl; cout << " Single release" << endl; cout << " -j " << 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(file), std::istreambuf_iterator()); file.close(); j = json::parse(str); return j; } pair get_artist_feat(const string& str) { pair 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 get_artist_title(const json& obj) { pair result; pair 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 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 get_tags_from_release(const json& info) { vector 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 release_artist_title; int release_precision; bool is_single_dir; vector 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; }