#include #include #ifdef _WIN32 #include #include #include #include #include #endif #include "filesystem.hpp" #ifndef GUI #include #include #include #endif #include "functions.hpp" #include "tv_rename.hpp" #include "json.hpp" using json = nlohmann::json; #ifdef _WIN32 constexpr const char_t *dir_divider = L"\\"; #define cout std::wcout #define cerr std::wcerr #define cin std::wcin #else constexpr const char_t *dir_divider = "/"; #define cout std::cout #define cerr std::cerr #define cin std::cin #define TEXT( a ) a #endif std::string api_token = ""; Curl c; std::vector< std::pair< string, string > > searchShow( const string &show, const string &language ) { c.addHeader( "Accept: application/json" ); c.addHeader( "Authorization: Bearer " + api_token ); c.addHeader( "Accept-Language: " + language ); auto encoded_show = encodeUrl( show ); auto j = json::parse( c.get( "https://api.thetvdb.com/search/series?name=" + encoded_show ) ); std::vector< json > results; if( j["data"].is_array() ) { results = j["data"].get< std::vector< json > >(); } else { cout << j << std::endl; return {}; } std::vector< std::pair< string, string > > ret; // find all possible shows for ( auto &x : results ) { ret.emplace_back( x["seriesName"].get< string >(), std::to_string( x["id"].get< int >() ) ); } c.clearHeader(); return ret; } // get show's ID string getShowId( string &show, const string &language ) { size_t order{}, pos{}; auto search_results = searchShow( show, language ); for( const auto &x : search_results ) { cout << ++order << ". " << x.first << std::endl; } cout << "Which TV Show is the right one? "; cin >> pos; cin.clear(); cin.ignore( 1, '\n' ); return search_results[pos-1].second; } string showNameFromId( const string &id, const string &language ) { c.addHeader( "Accept: application/json" ); c.addHeader( "Authorization: Bearer " + api_token ); c.addHeader( "Accept-Language: " + language ); auto j = json::parse( c.get( "https://api.thetvdb.com/series/" + id ) ); return j["data"].get< json >()["seriesName"].get< string >(); } // get names for all episodes for a given season std::vector< string > getEpisodeNames( const string &id, const string &season, const string &language, bool dvd = false ) { c.addHeader( "Accept: application/json" ); c.addHeader( "Authorization: Bearer " + api_token ); c.addHeader( "Accept-Language: " + language ); string page = "1"; string season_query = "airedSeason="; if( dvd ) season_query = "dvdSeason="; std::vector< string > episodes; do { episodes.resize( episodes.size() * 2 ); auto j = json::parse( c.get( "https://api.thetvdb.com/series/" + id + "/episodes/query?" + season_query + season + "&page=" + page ) ); if( j["data"].is_array() ) { auto epdata = j["data"].get< std::vector< json > >(); if( episodes.size() < epdata.size() ) episodes.resize( epdata.size() ); for ( auto &x : epdata ) { if( x["episodeName"].is_string() ) { if( dvd ) { size_t index = x["dvdEpisodeNumber"].get< size_t >(); if( index > episodes.size() ) episodes.resize( index ); index--; episodes[index] = x["episodeName"].get< string >(); } else { size_t index = x["airedEpisodeNumber"].get< size_t >(); if( index > episodes.size() ) episodes.resize( index ); index--; episodes[index] = x["episodeName"].get(); } } } } else { cerr << "Couldn't find episode names for season " << season << " of show " << showNameFromId( id, language ) << std::endl; } if( j["links"]["next"].is_null() ) break; page = std::to_string( j["links"]["next"].get< size_t >() ); } while( 1 ); c.clearHeader(); return episodes; } std::vector< std::pair< string, std::pair< string, string > > > getRenamedFiles( const string &show, int season, const string id, const string &language, const string &pattern, const bool &linux, const std::set< string > &files, bool dvd = false ) { #ifdef _WIN32 auto season_num = std::to_wstring( season ); auto episodes = parseEpisodeNames( utf8_to_wstring( season_code ), language ); #else auto season_num = std::to_string( season ); auto episodes = getEpisodeNames( id, season_num, language, dvd ); #endif if ( episodes.empty() ) return {}; if ( files.empty() ) return {}; std::vector< std::pair< string, std::pair< string, string > > > renamed_files; size_t pos = 0; for ( const auto &x : files ) { auto last = x.find_last_of( dir_divider ); string og_name; string dir; if ( last == string::npos ) { og_name = x; dir = TEXT( "." ); } else { og_name = x.substr( last + 1 ); dir = x.substr( 0, last ); } unsigned long num; // get file's episode number if ( searchSpecificSeason( x.c_str(), pos, season_num ) ) { num = std::stoi( x.c_str() + pos ); } else { continue; } num--; if ( num < episodes.size() ) { auto pos = og_name.find_last_of( TEXT( "." ) ); // get desired filename auto name = compilePattern( pattern, season, num + 1, og_name.substr( 0, pos ), episodes[num], show ) + og_name.substr( pos ); // replace '/' with '|' for ( size_t i = 0; i < name.size(); i++ ) { if ( name[i] == '/' ) { name[i] = '|'; } } // replace characters illegal in windows if desired if ( !linux ) { name.erase( std::remove_if( name.begin(), name.end(), []( char_t x ) { return x == '?' || x == '"' || x == '\\' || x == '*'; } ), name.end() ); size_t max{ name.size() }; for ( size_t i = 0; i < max; i++ ) { if ( name[i] == '|' ) { name[i] = '-'; } else if ( name[i] == '<' ) { name[i] = 'i'; name.insert( i + 1, TEXT( "s less than" ) ); max += 11; } else if ( name[i] == '>' ) { name[i] = 'i'; name.insert( i + 1, TEXT( "s more than" ) ); max += 11; } else if ( name[i] == ':' ) { name[i] = ' '; name.insert( i + 1, 1, '-' ); max++; } } for ( size_t i = 0; i < max; i++ ) { if ( name[i] == ' ' && name[i + 1] == ' ' ) { name.erase( i, 1 ); max--; i--; } } } renamed_files.emplace_back( dir, std::pair< string, string >( og_name, name ) ); } } return renamed_files; } void singleSeason( const string &path, string &show, int season, string id, const string &language, const string &pattern, const bool &linux, const bool &trust, std::set< string > const *files_ptr, bool print, bool dvd ) { if ( id.empty() ) id = getShowId( show, language ); std::set< string > *found_files = nullptr; if ( files_ptr == nullptr ) { found_files = new std::set< string >; findSeason( *found_files, season, path ); files_ptr = found_files; } auto renamed_files = getRenamedFiles( show, season, id, language, pattern, linux, *files_ptr, dvd ); if( print || !trust ) { for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed ) { cout << renamed->second.first << " --> " << renamed->second.second << std::endl; } if ( !trust ) { cout << "Does this seem ok? (y/n) "; string response; cin >> response; cin.clear(); cin.ignore( 1, '\n' ); if ( response[0] != 'y' && response[0] != 'Y' ) return; } } for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed ) { FSLib::rename( renamed->first + dir_divider + renamed->second.first, renamed->first + dir_divider + renamed->second.second ); } if ( found_files != nullptr ) { delete found_files; } } #ifndef GUI void multipleSeasons( const string &path, string &show, const std::map< int, std::set< string > > &seasons, const string &language, const string &pattern, const bool &linux, const bool &trust, bool dvd ) { auto id = getShowId( show, language ); for ( const auto &x : seasons ) { singleSeason( path, show, x.first, id, language, pattern, linux, trust, &x.second, dvd ); } } void multipleSeasons( const string &path, string &show, const std::set< int > seasons, const string &language, const string &pattern, const bool &linux, const bool &trust, bool dvd ) { std::map< int, std::set< string > > season_map; findSeasons( season_map, path, seasons ); multipleSeasons( path, show, season_map, language, pattern, linux, trust, dvd ); } void allSeasons( const string &path, string &show, const string &language, const string &pattern, const bool &linux, const bool &trust, bool dvd ) { std::map< int, std::set< string > > seasons; // get all season number from this directory and subdirectories iterateFS( seasons, path ); multipleSeasons( path, show, seasons, language, pattern, linux, trust, dvd ); } std::vector< std::pair< std::string, std::string > > getLangs() { std::vector< std::pair< std::string, std::string > > langs; c.addHeader( "Accept: application/json" ); c.addHeader( "Authorization: Bearer " + api_token ); auto j = json::parse( c.get( "https://api.thetvdb.com/languages" ) ); c.clearHeader(); auto langs_json = j["data"].get< std::vector< json > >(); for ( auto &x : langs_json ) { langs.emplace_back( x["abbreviation"].get< std::string >(), x["name"].get< std::string >() ); } return langs; } void printLangs() { for ( auto &x : getLangs() ) cout << x.first << " - " << x.second << std::endl; } bool findLanguage( const char_t *language ) { for ( auto &x : getLangs() ) { if ( x.first == language ) return true; } return false; } bool authenticate( const std::string &api_key ) { c.addHeader( "Accept: application/json" ); c.addHeader( "Content-Type: application/json" ); auto j = json::parse( c.post( "https://api.thetvdb.com/login", "{ \"apikey\": \"" + api_key + "\" }" ) ); api_token = j["token"].get< std::string >(); c.clearHeader(); // TODO check return code return true; } #endif