#include "monstercat_dl.h" #include "common.h" #include "curl_dl.h" #include #include #include using std::endl; using std::map; using std::ofstream; using std::sort; using std::string; using std::stringstream; using std::to_string; using nlohmann::json; string Monstercat_DL::calc_proper_artist(const string& artist_raw) { size_t pos; string result; pos = artist_raw.find("feat."); if (string::npos == pos) { return artist_raw; } result = artist_raw.substr(0, pos); result = trim_whitespace(result); return result; } string Monstercat_DL::calc_proper_title(const string& artist_raw, const string& title_raw, const string& version_raw) { size_t pos; string result; result = trim_whitespace(title_raw); pos = artist_raw.find("feat."); if (string::npos != pos) { result += " ("; result += trim_whitespace(artist_raw.substr(pos)); result += ")"; } if (!version_raw.empty()) { result += " ("; result += trim_whitespace(version_raw); result += ")"; } return result; } Monstercat_DL::Monstercat_DL() : m_log(&std::cout), m_base_url("https://player.monstercat.app/api/"), m_is_logged_in(false) {} Monstercat_DL::~Monstercat_DL() { } bool Monstercat_DL::login(const string& user, const string& pass) { CURL_DL& curl = CURL_DL::get_handle(); bool ok; json data; string url; stringstream out; data["Email"] = user; data["Password"] = pass; url = m_base_url; url += "sign-in"; ok = curl.post_json(url, data, &out); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not post json" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return false; } data.clear(); out >> data; if (data.contains("Needs2FA") && data["Needs2FA"].is_boolean() && data["Needs2FA"] == false) { m_is_logged_in = true; return true; } return false; } bool Monstercat_DL::logout() { CURL_DL& curl = CURL_DL::get_handle(); bool ok; json data; string url; if (!m_is_logged_in) { return false; } url = m_base_url; url += "sign-out"; ok = curl.post_json(url, data, nullptr); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not post json" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return false; } return ok; } json Monstercat_DL::get_release_json(const string& catalog_id) { CURL_DL& curl = CURL_DL::get_handle(); bool ok; json result; string url; stringstream out; url = m_base_url; url += "catalog/release/"; url += catalog_id; ok = curl.download(url, &out); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not download json" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return result; } out >> result; return result; } json Monstercat_DL::get_browse_json(const string& release_id) { CURL_DL& curl = CURL_DL::get_handle(); bool ok; json result; string url; stringstream out; url = m_base_url; url += "catalog/browse?offset=0&limit=0&search=&sort=&nogold=false&releaseId="; url += release_id; ok = curl.download(url, &out); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not download json" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return result; } out >> result; return result; } Release Monstercat_DL::parse_release_json(const json& release_json) { Release result; json release_object; json tracks_object; string artist_raw; string title_raw; string version_raw; Track temp_track; if (release_json.contains("Release")) { release_object = release_json["Release"]; } else { return result; } // Parse Release json_extract(release_object, "CatalogId", result.catalog_id); json_extract(release_object, "Id", result.id); json_extract(release_object, "Type", result.type); json_extract(release_object, "ReleaseDate", result.release_date); { size_t pos = result.release_date.find('T'); if (pos != string::npos) { result.release_date.erase(pos); } } json_extract(release_object, "ArtistsTitle", artist_raw); json_extract(release_object, "Title", title_raw); json_extract(release_object, "Version", version_raw); result.artist = calc_proper_artist(artist_raw); result.title = calc_proper_title(artist_raw, title_raw, version_raw); if (release_json.contains("Tracks") && release_json["Tracks"].is_array()) { tracks_object = release_json["Tracks"]; } else { return result; } // Parse Tracks for (json& curr_track : tracks_object) { if (curr_track.contains("TrackNumber") && curr_track["TrackNumber"].is_number_integer()) { temp_track.number = curr_track["TrackNumber"]; } json_extract(curr_track, "Id", temp_track.id); json_extract(curr_track, "ArtistsTitle", artist_raw); json_extract(curr_track, "Title", title_raw); json_extract(curr_track, "Version", version_raw); temp_track.artist = calc_proper_artist(artist_raw); temp_track.title = calc_proper_title(artist_raw, title_raw, version_raw); result.tracks.push_back(temp_track); } // Sort tracks by number sort(result.tracks.begin(), result.tracks.end(), [](const Track& t1, const Track& t2) { return t1.number < t2.number; }); return result; } void Monstercat_DL::add_extended_mixes(Release& release, const json& browse_json) { int track_num; json data_obj; json file_obj; Track *track; string mime_type; if (browse_json.contains("Data") && browse_json["Data"].is_array()) { data_obj = browse_json["Data"]; } for (json& track_json : data_obj) { // Get the track number track_num = 0; if (track_json.contains("TrackNumber") && track_json["TrackNumber"].is_number_integer()) { track_num = track_json["TrackNumber"]; } // File must exist and be an object if (!track_json.contains("File") || !track_json["File"].is_object()) { continue; } // Find track track = nullptr; for (int i = 0; i < release.tracks.size(); ++i) { if (release.tracks[i].number == track_num) { track = &(release.tracks[i]); break; } } if (nullptr == track) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not find track number " << track_num << " for catalog id " << release.catalog_id << endl; } return; } file_obj = track_json["File"]; json_extract(file_obj, "Id", track->extended_mix_file_id); json_extract(file_obj, "MimeType", mime_type); if ("audio/wav" == mime_type) { track->extended_mix_extension = ".wav"; } else { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Unknown MIME type for catalog id " << release.catalog_id << " - " << mime_type << endl; } } return; } } string Monstercat_DL::calc_release_folder(const Release& release, const string& main_folder) { string result; string folder_name; ensure_folder(main_folder, release.type); folder_name = release.release_date; folder_name += " - "; folder_name += clean_filename(release.catalog_id); folder_name += " - "; folder_name += clean_filename(release.artist); folder_name += " - "; folder_name += clean_filename(release.title); result = build_fname(main_folder, release.type, folder_name); ensure_folder(result, ""); return result; } string Monstercat_DL::calc_track_filename(const Release& release, int track_num) { string result; bool use_number; bool use_artist; const Track *track = nullptr; // Find the track for (int i = 0; i < release.tracks.size(); ++i) { if (release.tracks[i].number == track_num) { track = &(release.tracks[i]); break; } } if (nullptr == track) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Cannot find track number " << track_num << " in catalog id " << release.catalog_id << endl; } return ""; } // Determine whether number is needed if (1 == release.tracks.size()) { use_number = false; } else { use_number = true; } // Determine whether artist is needed if (track->artist == release.artist) { use_artist = false; } else { use_artist = true; } // Build filename result = ""; if (use_number) { // Always use 2 digits result += i_to_str(track->number, 2, '0'); result += " - "; } if (use_artist) { result += track->artist; result += " - "; } result += track->title; return clean_filename(result); } bool Monstercat_DL::download_cover(const string& catalog_id, const string& path) { CURL_DL& curl = CURL_DL::get_handle(); bool ok; string url; stringstream out; ofstream out_file; map out_headers; string filepath; url = "https://www.monstercat.com/release/"; url += catalog_id; url += "/cover"; ok = curl.download(url, &out, nullptr, &out_headers); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not download image" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return false; } filepath = build_fname(path, "", "Cover"); if (0 == out_headers.count("content-type")) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "No content-type" << endl; } } else { if (out_headers["content-type"] == "image/jpeg") { filepath += ".jpg"; } else if (out_headers["content-type"] == "image/png") { filepath += ".png"; } else { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Unknown Content-Type for cover - " << out_headers["content-type"] << endl; } } } out_file.open(filepath, std::ios::binary); if (!out_file.is_open()) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not open file for write" << endl; *m_log << filepath << endl; } return false; } out_file << out.rdbuf(); out_file.close(); return true; } bool Monstercat_DL::download_track(const string& release_id, const string& track_id, const string& filepath, bool is_mp3) { CURL_DL& curl = CURL_DL::get_handle(); bool ok; string url; stringstream out_data; ofstream out_file; url = m_base_url; url += "release/"; url += release_id; url += "/track-download/"; url += track_id; if (is_mp3) { url += "?format=mp3_320"; } else { url += "?format=flac"; } ok = curl.download(url, &out_data); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not download track" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return false; } out_file.open(filepath, std::ios::binary); if (!out_file.is_open()) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not open file for write" << endl; *m_log << filepath << endl; } return false; } out_file << out_data.rdbuf(); out_file.close(); return true; } bool Monstercat_DL::download_file(const string& file_id, const string& filepath) { CURL_DL& curl = CURL_DL::get_handle(); bool ok; string url; stringstream out_data; ofstream out_file; url = m_base_url; url += "file/"; url += file_id; url += "/open?download=true"; ok = curl.download(url, &out_data); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not download track" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return false; } out_file.open(filepath, std::ios::binary); if (!out_file.is_open()) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not open file for write" << endl; *m_log << filepath << endl; } return false; } out_file << out_data.rdbuf(); out_file.close(); return true; }