1110 lines
19 KiB
C++
1110 lines
19 KiB
C++
// C++ Libs
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <utility>
|
|
|
|
// C Libs
|
|
#include <stdint.h>
|
|
#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;
|
|
constexpr bool IS_JPG = true;
|
|
constexpr bool IS_PNG = false;
|
|
constexpr uint32_t COVER_SIZE = 750;
|
|
constexpr const char ID3EDIT_INVOKE[] = "id3edit";
|
|
constexpr const char MAGICK_INVOKE[] = "magick";
|
|
|
|
#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 or releases array" << endl;
|
|
cout << " path - root path to download to" << endl;
|
|
cout << " Default: ." << endl;
|
|
}
|
|
|
|
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;
|
|
string version;
|
|
bool contains;
|
|
int pos;
|
|
|
|
artist_feat = get_artist_feat(obj["ArtistsTitle"]);
|
|
result.first = artist_feat.first;
|
|
|
|
contains = false;
|
|
|
|
str = obj["Title"];
|
|
if (!artist_feat.second.empty())
|
|
{
|
|
str += " (";
|
|
str += artist_feat.second;
|
|
str += ')';
|
|
contains = true;
|
|
}
|
|
|
|
// Empty does not work on json string
|
|
version = obj["Version"];
|
|
if (!version.empty())
|
|
{
|
|
str += contains ? " [" : " (";
|
|
str += 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, const string& release_prefix,
|
|
int release_num, const string& release_suffix, const string& artist,
|
|
const string& title, int precision)
|
|
{
|
|
string path;
|
|
|
|
path = main_path;
|
|
path += FOLDER_DELIM;
|
|
path += release_prefix;
|
|
path += FOLDER_DELIM;
|
|
path += num_to_str(release_num, precision);
|
|
path += release_suffix;
|
|
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 should_resize_JPG(const string& path)
|
|
{
|
|
// Do a resize by default
|
|
bool result = true;
|
|
ifstream file;
|
|
string filename;
|
|
int marker = 0;
|
|
int symbol;
|
|
|
|
filename = path;
|
|
filename += FOLDER_DELIM;
|
|
filename += "Cover";
|
|
file.open(filename);
|
|
|
|
// Check Header 1
|
|
symbol = file.get();
|
|
if (symbol != 0xFF)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
// Check Header 2
|
|
symbol = file.get();
|
|
if (symbol != 0xD8)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
while(1)
|
|
{
|
|
int discarded_bytes = 0;
|
|
|
|
if (file.eof())
|
|
{
|
|
return result;
|
|
}
|
|
marker = file.get();
|
|
|
|
// Seek to 0xFF
|
|
while (marker != 0xFF)
|
|
{
|
|
discarded_bytes++;
|
|
if (file.eof())
|
|
{
|
|
return result;
|
|
}
|
|
marker = file.get();
|
|
}
|
|
|
|
// Find end of 0xFF
|
|
do
|
|
{
|
|
if (file.eof())
|
|
{
|
|
return result;
|
|
}
|
|
marker = file.get();
|
|
}
|
|
while (marker == 0xFF);
|
|
|
|
// ???
|
|
if (0 == discarded_bytes)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
switch(marker)
|
|
{
|
|
// Read width & height
|
|
case 0xC0:
|
|
case 0xC1:
|
|
case 0xC2:
|
|
case 0xC3:
|
|
case 0xC5:
|
|
case 0xC6:
|
|
case 0xC7:
|
|
case 0xC9:
|
|
case 0xCA:
|
|
case 0xCB:
|
|
case 0xCD:
|
|
case 0xCE:
|
|
case 0xCF:
|
|
int16_t width;
|
|
int16_t height;
|
|
|
|
// Skip 3 bytes
|
|
file.get();
|
|
file.get();
|
|
file.get();
|
|
|
|
file.read(reinterpret_cast<char*>(&height), 2);
|
|
file.read(reinterpret_cast<char*>(&width), 2);
|
|
|
|
if ((width > COVER_SIZE) || (height > COVER_SIZE))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
// ???
|
|
case 0xDA:
|
|
case 0xD9:
|
|
return result;
|
|
break;
|
|
|
|
// Skip length-2 bytes
|
|
default:
|
|
int16_t length;
|
|
|
|
file.read(reinterpret_cast<char*>(&length), 2);
|
|
if (length < 2)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
length -= 2;
|
|
for (int i = 0; i < length; ++i)
|
|
{
|
|
file.get();
|
|
}
|
|
|
|
if (file.eof())
|
|
{
|
|
return result;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool should_resize_PNG(const string& path)
|
|
{
|
|
ifstream file;
|
|
string filename;
|
|
uint8_t buf[4];
|
|
uint32_t width;
|
|
uint32_t height;
|
|
|
|
filename = path;
|
|
filename += FOLDER_DELIM;
|
|
filename += "Cover";
|
|
file.open(filename);
|
|
|
|
// Seek
|
|
file.seekg(16);
|
|
|
|
// Read width
|
|
width = 0;
|
|
file.read(reinterpret_cast<char*>(&buf), 4);
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
width <<= 8;
|
|
width += buf[i];
|
|
}
|
|
|
|
// Read height
|
|
height = 0;
|
|
file.read(reinterpret_cast<char*>(&buf), 4);
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
height <<= 8;
|
|
height += buf[i];
|
|
}
|
|
|
|
file.close();
|
|
|
|
if ((width > COVER_SIZE) || (height > COVER_SIZE))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool get_cover_type(const string& path)
|
|
{
|
|
string filename;
|
|
ifstream file;
|
|
int header;
|
|
|
|
filename = path;
|
|
filename += FOLDER_DELIM;
|
|
filename += "Cover";
|
|
|
|
file.open(filename);
|
|
|
|
header = file.peek();
|
|
switch (header)
|
|
{
|
|
// JPG
|
|
case 0xFF:
|
|
return IS_JPG;
|
|
break;
|
|
case 0x89:
|
|
return IS_PNG;
|
|
break;
|
|
}
|
|
|
|
throw invalid_argument(filename);
|
|
}
|
|
|
|
bool resize_cover(const string& path, bool is_jpg)
|
|
{
|
|
string cmd;
|
|
bool should_resize;
|
|
|
|
// Should we actually resize
|
|
if (is_jpg == IS_JPG)
|
|
{
|
|
should_resize = should_resize_JPG(path);
|
|
}
|
|
else
|
|
{
|
|
should_resize = should_resize_PNG(path);
|
|
}
|
|
|
|
cmd = MAGICK_INVOKE;
|
|
cmd += " \"";
|
|
cmd += path;
|
|
cmd += FOLDER_DELIM;
|
|
cmd += "Cover";
|
|
cmd += "\" ";
|
|
|
|
if (should_resize)
|
|
{
|
|
cmd += "-resize ";
|
|
cmd += to_string(COVER_SIZE);
|
|
cmd += "x";
|
|
cmd += to_string(COVER_SIZE);
|
|
cmd += " ";
|
|
}
|
|
|
|
cmd += "\"";
|
|
cmd += path;
|
|
cmd += FOLDER_DELIM;
|
|
cmd += "Cover_small.jpg\"";
|
|
|
|
return system_command(cmd);
|
|
}
|
|
|
|
bool rename_cover(const string& path, bool is_jpg)
|
|
{
|
|
string filename;
|
|
string filename_new;
|
|
int error;
|
|
|
|
filename = path;
|
|
filename += FOLDER_DELIM;
|
|
filename += "Cover";
|
|
|
|
filename_new = filename;
|
|
if (is_jpg == IS_JPG)
|
|
{
|
|
filename_new += ".jpg";
|
|
}
|
|
else
|
|
{
|
|
filename_new += ".png";
|
|
}
|
|
|
|
error = rename(filename.c_str(), filename_new.c_str());
|
|
if (0 != error)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool tag_MP3(const string& path, const string& filename, bool is_single_dir,
|
|
const string& title, const string& album, const string& artist,
|
|
const string& tracknum)
|
|
{
|
|
string cmd;
|
|
|
|
cmd = ID3EDIT_INVOKE;
|
|
cmd += " --set-name \"";
|
|
cmd += title;
|
|
cmd += "\" --set-album \"";
|
|
cmd += album;
|
|
cmd += "\" --set-artist \"";
|
|
cmd += artist;
|
|
cmd += "\" --set-track ";
|
|
cmd += tracknum;
|
|
cmd += " --set-artwork \"";
|
|
cmd += path;
|
|
cmd += FOLDER_DELIM;
|
|
cmd += "Cover_small.jpg\" \"";
|
|
cmd += path;
|
|
cmd += FOLDER_DELIM;
|
|
if (!is_single_dir)
|
|
{
|
|
cmd += "MP3";
|
|
cmd += FOLDER_DELIM;
|
|
}
|
|
cmd += filename;
|
|
cmd += ".mp3\"";
|
|
|
|
return system_command(cmd);
|
|
}
|
|
|
|
bool tag_FLAC(const string& path, const string& filename, bool is_single_dir,
|
|
const string& title, const string& album, const string& artist,
|
|
const string& tracknum)
|
|
{
|
|
string cmd;
|
|
bool ok;
|
|
|
|
// First command - remove tags
|
|
cmd = "metaflac --preserve-modtime --no-utf8-convert ";
|
|
cmd += "--remove-tag=TITLE ";
|
|
cmd += "--remove-tag=ARTIST ";
|
|
cmd += "--remove-tag=ALBUM ";
|
|
cmd += "--remove-tag=TRACKNUMBER \"";
|
|
cmd += path;
|
|
cmd += FOLDER_DELIM;
|
|
if (!is_single_dir)
|
|
{
|
|
cmd += "FLAC";
|
|
cmd += FOLDER_DELIM;
|
|
}
|
|
cmd += filename;
|
|
cmd += ".flac\"";
|
|
|
|
ok = system_command(cmd);
|
|
if (!ok)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Second command - remove pictures
|
|
cmd = "metaflac --preserve-modtime --no-utf8-convert ";
|
|
cmd += "--remove --block-type=PICTURE \"";
|
|
cmd += path;
|
|
cmd += FOLDER_DELIM;
|
|
if (!is_single_dir)
|
|
{
|
|
cmd += "FLAC";
|
|
cmd += FOLDER_DELIM;
|
|
}
|
|
cmd += filename;
|
|
cmd += ".flac\"";
|
|
|
|
ok = system_command(cmd);
|
|
if (!ok)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Third command - set metadata
|
|
cmd = "metaflac --preserve-modtime --no-utf8-convert --dont-use-padding ";
|
|
cmd += "\"--import-picture-from=3|image/jpeg|||";
|
|
cmd += path;
|
|
cmd += FOLDER_DELIM;
|
|
cmd += "Cover_small.jpg\" \"--set-tag=TITLE=";
|
|
cmd += title;
|
|
cmd += "\" \"--set-tag=ARTIST=";
|
|
cmd += artist;
|
|
cmd += "\" \"--set-tag=ALBUM=";
|
|
cmd += album;
|
|
cmd += "\" --set-tag=TRACKNUMBER=";
|
|
cmd += tracknum;
|
|
cmd += " \"";
|
|
cmd += path;
|
|
cmd += FOLDER_DELIM;
|
|
if (!is_single_dir)
|
|
{
|
|
cmd += "FLAC";
|
|
cmd += FOLDER_DELIM;
|
|
}
|
|
cmd += filename;
|
|
cmd += ".flac\"";
|
|
|
|
return system_command(cmd);
|
|
}
|
|
|
|
void save_release_date(const string& path, const string& date)
|
|
{
|
|
ofstream file;
|
|
string filepath;
|
|
|
|
filepath = path;
|
|
filepath += FOLDER_DELIM;
|
|
filepath += "Date.txt";
|
|
|
|
file.open(filepath);
|
|
file << date << endl;
|
|
file.close();
|
|
}
|
|
|
|
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;
|
|
bool cover_is_jpg;
|
|
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_prefix, release_num,
|
|
release_suffix, release_artist_title.first, release_artist_title.second,
|
|
release_precision);
|
|
|
|
// Make Release Folder
|
|
ok = make_dir(release_dir);
|
|
if (!ok)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Save Release Date
|
|
save_release_date(release_dir, info["Release"]["ReleaseDate"]);
|
|
|
|
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(catalog_release_str, release_dir);
|
|
if (!ok)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Try to find out Cover type
|
|
try
|
|
{
|
|
cover_is_jpg = get_cover_type(release_dir);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Make 750x750 Cover JPEG Image
|
|
ok = resize_cover(release_dir, cover_is_jpg);
|
|
if (!ok)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Rename Cover Image (Proper Extension)
|
|
ok = rename_cover(release_dir, cover_is_jpg);
|
|
if (!ok)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Tag MP3
|
|
ok = tag_MP3(release_dir, track_filename, is_single_dir, tags[i].title,
|
|
release_artist_title.second, tags[i].artist, tags[i].number);
|
|
if (!ok)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Tag FLAC
|
|
ok = tag_FLAC(release_dir, track_filename, is_single_dir, tags[i].title,
|
|
release_artist_title.second, tags[i].artist, tags[i].number);
|
|
if (!ok)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool break_down_release_str(const string& release, string& prefix, int& num, string& suffix)
|
|
{
|
|
int pos;
|
|
int pos2;
|
|
string digits = "0123456789";
|
|
|
|
prefix = "";
|
|
num = 0;
|
|
suffix = "";
|
|
|
|
pos = release.find_first_of(digits);
|
|
if (string::npos == pos)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
prefix = release.substr(0, pos);
|
|
pos2 = release.find_first_not_of(digits, pos);
|
|
if (string::npos == pos2)
|
|
{
|
|
try
|
|
{
|
|
num = stod(release.substr(pos));
|
|
}
|
|
catch(const std::exception& e)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
num = stod(release.substr(pos, pos2 - pos));
|
|
}
|
|
catch(const std::exception& e)
|
|
{
|
|
return false;
|
|
}
|
|
suffix = release.substr(pos2);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
vector<string> args(&argv[1], &argv[argc]);
|
|
|
|
pair<bool, string> single_release = {false, ""};
|
|
pair<bool, string> json_option = {false, ""};
|
|
string path = ".";
|
|
|
|
if (argc < 3)
|
|
{
|
|
cout << "Too few arguments" << endl;
|
|
usage(argv[0]);
|
|
return -1;
|
|
}
|
|
|
|
// Ugly getopt
|
|
for (int i = 0; i < args.size(); ++i)
|
|
{
|
|
if ((args[i] == "-1") && (i + 1 < args.size()))
|
|
{
|
|
single_release.first = true;
|
|
single_release.second = args[i + 1];
|
|
args.erase(args.begin() + i);
|
|
args.erase(args.begin() + i);
|
|
--i;
|
|
continue;
|
|
}
|
|
|
|
if ((args[i] == "-j") && (i + 1 < args.size()))
|
|
{
|
|
json_option.first = true;
|
|
json_option.second = args[i + 1];
|
|
args.erase(args.begin() + i);
|
|
args.erase(args.begin() + i);
|
|
--i;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Both selected
|
|
if (single_release.first && json_option.first)
|
|
{
|
|
cout << "Both single release and json file are selected." << endl;
|
|
usage(argv[0]);
|
|
return -1;
|
|
}
|
|
|
|
// None selected
|
|
if (!single_release.first && !json_option.first)
|
|
{
|
|
cout << "Neither single release nor json file are selected." << endl;
|
|
usage(argv[0]);
|
|
return -1;
|
|
}
|
|
|
|
// Add path from arguments
|
|
if (!args.empty())
|
|
{
|
|
path = args[0];
|
|
}
|
|
|
|
// Remove trailing slash
|
|
if (path[path.size() - 1] == FOLDER_DELIM)
|
|
{
|
|
path.erase(path.size() - 1);
|
|
}
|
|
|
|
#if 1
|
|
// BEGIN TEST PRINTS
|
|
if (single_release.first)
|
|
{
|
|
cout << "SINGLE: " << single_release.second << endl;
|
|
}
|
|
if (json_option.first)
|
|
{
|
|
cout << "JSON: " << json_option.second << endl;
|
|
}
|
|
cout << "PATH: " << path << endl;
|
|
// END TEST PRINTS
|
|
#endif
|
|
|
|
vector<string> releases;
|
|
vector<string>::iterator it;
|
|
|
|
// Add single release
|
|
if (single_release.first)
|
|
{
|
|
releases.push_back(single_release.second);
|
|
}
|
|
|
|
// Parse JSON
|
|
if (json_option.first)
|
|
{
|
|
json j = parse_json_file(json_option.second);
|
|
|
|
if (j.contains("releases") && j["releases"].is_array())
|
|
{
|
|
for (int i = 0; i < j["releases"].size(); ++i)
|
|
{
|
|
releases.push_back(j["releases"][i]);
|
|
}
|
|
}
|
|
else if (j.contains("prefix") &&
|
|
j.contains("start") && j["start"].is_number_integer() &&
|
|
j.contains("end") && j["end"].is_number_integer())
|
|
{
|
|
string tmp;
|
|
for (int i = j["start"]; i <= j["end"]; ++i)
|
|
{
|
|
// Add release
|
|
tmp = j["prefix"];
|
|
tmp += num_to_str(i, 3);
|
|
releases.push_back(tmp);
|
|
|
|
// Add release with suffix
|
|
if (j.contains("suffix_try") && !j["suffix_try"].empty())
|
|
{
|
|
tmp += j["suffix_try"];
|
|
releases.push_back(tmp);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cout << "JSON not recognized" << endl;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
string release_prefix;
|
|
int release_num;
|
|
string release_suffix;
|
|
bool ok;
|
|
|
|
|
|
ok = login();
|
|
if (!ok)
|
|
{
|
|
cout << "Failed to login" << endl;
|
|
return -1;
|
|
}
|
|
|
|
// Break down release string
|
|
for (it = releases.begin(); it != releases.end(); ++it)
|
|
{
|
|
ok = break_down_release_str(*it, release_prefix, release_num, release_suffix);
|
|
if (!ok)
|
|
{
|
|
cout << "Failed to break down - " << *it << endl;
|
|
continue;
|
|
}
|
|
|
|
ok = full_donwload(path, release_prefix, release_num, release_suffix);
|
|
if (!ok)
|
|
{
|
|
cout << "Could not download - " << *it << endl;
|
|
}
|
|
}
|
|
|
|
ok = logout();
|
|
if (!ok)
|
|
{
|
|
cout << "Failed to logout" << endl;
|
|
}
|
|
|
|
return 0;
|
|
}
|