tv_rename/tv_rename.cpp

457 lines
15 KiB
C++
Raw Permalink Normal View History

2019-01-07 22:24:28 +00:00
#include <algorithm>
2020-01-16 10:12:22 +00:00
#include <iostream>
2019-02-04 16:39:48 +00:00
#include <map>
2019-07-12 21:10:40 +00:00
#include "filesystem.hpp"
2020-01-15 21:20:44 +00:00
#include "functions.hpp"
#include "tv_rename.hpp"
#include "rapidjson/include/rapidjson/document.h"
2019-07-12 21:10:40 +00:00
2019-01-23 19:46:03 +00:00
#ifndef GUI
2018-09-22 22:50:42 +00:00
#include <sstream>
2019-01-07 22:24:28 +00:00
#include <vector>
2019-01-23 19:46:03 +00:00
#endif
2018-09-22 22:50:42 +00:00
2019-02-04 16:39:48 +00:00
#ifdef _WIN32
2020-04-01 11:46:28 +00:00
#include "resources_windows.h"
2020-04-01 14:13:39 +00:00
#include <windows.h>
2020-04-01 11:46:28 +00:00
2020-01-15 21:20:44 +00:00
#include <codecvt>
#include <fcntl.h>
#include <io.h>
#include <locale>
2020-01-31 21:38:11 +00:00
constexpr const char_t *_tv_rename_dir_divider = L"\\";
2019-02-04 16:39:48 +00:00
#define cout std::wcout
#define cerr std::wcerr
#define cin std::wcin
2020-01-15 21:20:44 +00:00
#define toString( a ) utf8_to_wstring( a )
2019-02-04 16:39:48 +00:00
#else
2020-04-01 11:46:28 +00:00
#include "resources_linux.h"
2020-01-31 21:38:11 +00:00
constexpr const char_t *_tv_rename_dir_divider = "/";
2019-02-04 16:39:48 +00:00
#define cout std::cout
#define cerr std::cerr
#define cin std::cin
2020-01-15 21:20:44 +00:00
#define toString( a ) a
2019-02-04 16:39:48 +00:00
#endif
2020-01-31 21:38:11 +00:00
string _tv_rename_api_token;
Request _tv_rename_request;
std::vector< std::pair< string, string > >
searchShow( const string &show, const string &language ) {
2020-01-31 21:38:11 +00:00
Request &request = _tv_rename_request;
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) +
_tv_rename_api_token );
request.addHeader( TEXT( "Accept-Language: " ) + language );
auto encoded_show = encodeUrl( show );
2020-01-31 21:38:11 +00:00
rapidjson::Document json;
json.Parse(
request.get( TEXT( "/search/series?name=" ) + encoded_show ).c_str() );
if ( json.HasParseError() )
return {};
2020-01-31 21:38:11 +00:00
const rapidjson::Value &results = json["data"];
if ( !results.IsArray() ) {
return {};
}
std::vector< std::pair< string, string > > ret;
// find all possible shows
2020-01-31 21:38:11 +00:00
for ( size_t i = 0; i < results.Size(); i++ ) {
auto show = toString( results[i]["seriesName"].GetString() );
auto id = toString( std::to_string( results[i]["id"].GetInt() ) );
2020-01-15 21:20:44 +00:00
ret.emplace_back( show, id );
}
2020-01-31 21:38:11 +00:00
request.clearHeader();
return ret;
}
2020-01-16 10:12:22 +00:00
#ifndef GUI
// get show's ID
2020-01-18 21:19:17 +00:00
string getShowId( const string &show, const string &language ) {
size_t order{}, pos{};
auto search_results = searchShow( show, language );
2020-01-17 13:03:22 +00:00
for ( const auto &x : search_results ) {
cout << ++order << ". " << x.first << std::endl;
}
2020-03-12 19:52:23 +00:00
cout << _( WHICH_SHOW ) << " " << std::flush;
cin >> pos;
cin.clear();
cin.ignore( 1, '\n' );
2020-01-17 13:03:22 +00:00
return search_results[pos - 1].second;
}
2020-01-16 10:12:22 +00:00
#endif
string showNameFromId( const string &id, const string &language ) {
2020-01-31 21:38:11 +00:00
Request &request = _tv_rename_request;
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) +
_tv_rename_api_token );
request.addHeader( TEXT( "Accept-Language: " ) + language );
rapidjson::Document json;
json.Parse( request.get( TEXT( "/series/" ) + id ).c_str() );
if ( json.HasParseError() )
return TEXT( "" );
2020-01-31 21:38:11 +00:00
return toString( json["data"]["seriesName"].GetString() );
}
2019-02-04 16:39:48 +00:00
// get names for all episodes for a given season
std::vector< string > getEpisodeNames( const string &id, const string &season,
2020-01-17 13:03:22 +00:00
const string &language,
bool dvd = false ) {
2020-01-31 21:38:11 +00:00
Request &request = _tv_rename_request;
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) +
_tv_rename_api_token );
request.addHeader( TEXT( "Accept-Language: " ) + language );
string baseurl = TEXT( "/series/" ) + id + TEXT( "/episodes/query?" );
2020-01-17 13:03:22 +00:00
if ( dvd )
2020-01-31 21:38:11 +00:00
baseurl += TEXT( "dvdSeason=" );
else
baseurl += TEXT( "airedSeason=" );
baseurl += season;
baseurl += TEXT( "&page=" );
string page = TEXT( "1" );
2019-02-04 16:39:48 +00:00
std::vector< string > episodes;
2020-01-31 21:38:11 +00:00
do {
episodes.resize( episodes.size() * 2 );
2020-01-31 21:38:11 +00:00
rapidjson::Document json;
json.Parse( request.get( baseurl + page ).c_str() );
if ( json.HasParseError() )
return {};
if ( json.FindMember( "data" ) == json.MemberEnd() )
2020-02-09 15:57:12 +00:00
break;
2020-01-31 21:38:11 +00:00
if ( json["data"].IsArray() ) {
rapidjson::Value &epdata = json["data"];
if ( episodes.size() < epdata.Size() )
episodes.resize( epdata.Size() );
for ( size_t i = 0; i < epdata.Size(); i++ ) {
if ( epdata[i]["episodeName"].IsString() ) {
size_t index = -1;
if ( dvd )
2020-01-31 21:38:11 +00:00
index = epdata[i]["dvdEpisodeNumber"].GetInt();
else
2020-01-31 21:38:11 +00:00
index = epdata[i]["airedEpisodeNumber"].GetInt();
2020-01-31 21:38:11 +00:00
if ( index == static_cast< size_t >( -1 ) )
continue;
if ( index > episodes.size() )
episodes.resize( index );
index--;
episodes[index] =
2020-01-31 21:38:11 +00:00
toString( epdata[i]["episodeName"].GetString() );
// some eps have whitespace at the end
while ( isspace( episodes[index].back() ) )
episodes[index].pop_back();
}
}
2019-01-07 22:24:28 +00:00
} else {
2020-03-12 19:52:23 +00:00
cerr << _( NOT_FOUND_SHOW_SEASON, season.c_str(),
showNameFromId( id, language ).c_str() )
<< std::endl;
2019-01-07 22:24:28 +00:00
}
2020-01-31 21:38:11 +00:00
if ( json["links"]["next"].IsNull() )
break;
2020-01-31 21:38:11 +00:00
page = toString( std::to_string( json["links"]["next"].GetInt() ) );
2020-01-17 13:03:22 +00:00
} while ( 1 );
2020-01-31 21:38:11 +00:00
request.clearHeader();
return episodes;
2019-01-07 22:24:28 +00:00
}
2020-01-31 21:51:43 +00:00
std::vector< std::tuple< int, string, string, string > >
2020-04-16 11:23:02 +00:00
getRenamedFiles( const string &show, int season, const string &id,
2019-02-04 16:39:48 +00:00
const string &language, const string &pattern,
const bool &unix_names, const std::map< int, string > &files,
2020-01-17 13:03:22 +00:00
bool dvd ) {
2020-01-15 21:20:44 +00:00
auto season_num = toString( std::to_string( season ) );
auto episodes = getEpisodeNames( id, season_num, language, dvd );
2019-01-07 22:24:28 +00:00
2019-02-04 16:39:48 +00:00
if ( episodes.empty() )
return {};
2019-02-04 16:39:48 +00:00
if ( files.empty() )
return {};
2019-01-23 19:46:03 +00:00
2020-01-31 21:38:11 +00:00
// vector of pairs <ep_num,dir>,<original_name,new_name>
2020-01-31 21:51:43 +00:00
std::vector< std::tuple< int, string, string, string > > renamed_files;
2019-02-04 16:39:48 +00:00
for ( const auto &x : files ) {
2020-01-31 21:38:11 +00:00
auto last = x.second.find_last_of( _tv_rename_dir_divider );
2019-02-04 16:39:48 +00:00
string og_name;
string dir;
if ( last == string::npos ) {
2020-01-15 21:20:44 +00:00
og_name = x.second;
2019-02-04 16:39:48 +00:00
dir = TEXT( "." );
} else {
2020-01-15 21:20:44 +00:00
og_name = x.second.substr( last + 1 );
dir = x.second.substr( 0, last );
}
2020-01-15 21:20:44 +00:00
unsigned long ep_num = x.first - 1;
2019-02-04 16:39:48 +00:00
2020-01-15 21:20:44 +00:00
if ( ep_num < episodes.size() ) {
2019-02-04 16:39:48 +00:00
auto pos = og_name.find_last_of( TEXT( "." ) );
string og_name_without_extension{};
string og_name_extension{};
if ( pos == string::npos ) {
og_name_without_extension = og_name;
} else {
og_name_without_extension = og_name.substr( 0, pos );
og_name_extension = og_name.substr( pos );
}
2019-01-23 13:08:40 +00:00
// get desired filename
2020-01-15 21:20:44 +00:00
auto name = compilePattern( pattern, season, x.first,
og_name_without_extension,
2020-01-17 13:03:22 +00:00
episodes[ep_num], show ) +
og_name_extension;
// replace '/' with '|'
2019-02-04 16:39:48 +00:00
for ( size_t i = 0; i < name.size(); i++ ) {
if ( name[i] == '/' ) {
name[i] = '|';
}
}
2019-01-23 13:08:40 +00:00
// replace characters illegal in windows if desired
if ( !unix_names ) {
2019-02-04 16:39:48 +00:00
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] = '-';
2019-02-04 16:39:48 +00:00
} else if ( name[i] == '<' ) {
2020-03-12 19:52:23 +00:00
name.erase( i, 1 );
name.insert( i, TEXT( "" ) );
max = name.size();
2019-01-07 22:24:28 +00:00
} else if ( name[i] == '>' ) {
2020-03-12 19:52:23 +00:00
name.erase( i, 1 );
name.insert( i, TEXT( "" ) );
max = name.size();
2019-01-07 22:24:28 +00:00
} else if ( name[i] == ':' ) {
name[i] = ' ';
2020-03-12 19:52:23 +00:00
name.insert( i + 1, TEXT( "- " ) );
max = name.size();
2019-01-07 22:24:28 +00:00
}
}
2019-02-04 16:39:48 +00:00
for ( size_t i = 0; i < max; i++ ) {
if ( name[i] == ' ' && name[i + 1] == ' ' ) {
name.erase( i, 1 );
max--;
i--;
}
}
}
2020-01-31 21:51:43 +00:00
renamed_files.emplace_back( x.first, dir, og_name, name );
}
}
return renamed_files;
}
#ifndef _WIN32
std::map< string, string > getLangs() {
std::map< string, string > langs;
#else
std::vector< std::pair< string, string > > getLangs() {
std::vector< std::pair< string, string > > langs;
#endif
Request &request = _tv_rename_request;
2020-01-31 21:38:11 +00:00
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) +
_tv_rename_api_token );
rapidjson::Document d;
d.Parse( request.get( TEXT( "/languages" ) ).c_str() );
request.clearHeader();
rapidjson::Value &lang_data = d["data"];
for ( size_t i = 0; i < lang_data.Size(); i++ ) {
#ifndef _WIN32
langs[toString( lang_data[i]["abbreviation"].GetString() )] =
toString( lang_data[i]["name"].GetString() );
#else
langs.emplace_back(
toString( lang_data[i]["abbreviation"].GetString() ),
toString( lang_data[i]["name"].GetString() ) );
#endif
2020-01-16 10:12:22 +00:00
}
return langs;
}
bool authenticate( const std::string &api_key ) {
2020-01-31 21:38:11 +00:00
Request &request = _tv_rename_request;
2020-01-16 10:12:22 +00:00
#ifdef _WIN32
2020-01-31 21:38:11 +00:00
request.setServer( TEXT( "api.thetvdb.com" ) );
2020-01-16 10:12:22 +00:00
#else
2020-01-31 21:38:11 +00:00
request.setServer( "https://api.thetvdb.com" );
2020-01-16 10:12:22 +00:00
#endif
2020-01-31 21:38:11 +00:00
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Content-Type: application/json" ) );
rapidjson::Document json;
json.Parse(
request.post( TEXT( "/login" ), "{ \"apikey\": \"" + api_key + "\" }" )
.c_str() );
if ( json.HasParseError() )
return false;
_tv_rename_api_token = toString( json["token"].GetString() );
request.clearHeader();
2020-01-16 10:12:22 +00:00
// TODO check return code
return true;
}
void singleSeason( const string &path, const string &show, int season,
string id, const string &language, const string &pattern,
const bool &unix_names, const bool &trust,
2020-01-15 21:20:44 +00:00
std::map< int, string > *files_ptr, bool print, bool dvd ) {
2020-01-16 10:12:22 +00:00
#ifndef GUI
if ( id.empty() )
id = getShowId( show, language );
2020-01-16 10:12:22 +00:00
#endif
2020-01-15 21:20:44 +00:00
std::map< int, std::map< int, string > > *found_files = nullptr;
2019-02-04 16:39:48 +00:00
if ( files_ptr == nullptr ) {
2020-01-15 21:20:44 +00:00
found_files = new std::map< int, std::map< int, string > >;
iterateFS( *found_files, path );
2020-01-17 13:03:22 +00:00
if ( found_files->find( season ) != found_files->end() )
files_ptr = &( *found_files )[season];
2020-01-15 21:20:44 +00:00
}
2020-01-17 13:03:22 +00:00
if ( files_ptr == nullptr ) {
2020-03-12 19:52:23 +00:00
cerr << _( NOT_FOUND_FOR_SEASON ) << " " << season << std::endl;
2020-01-15 21:20:44 +00:00
return;
}
auto renamed_files = getRenamedFiles( show, season, id, language, pattern,
unix_names, *files_ptr, dvd );
if ( renamed_files.empty() )
goto end;
2020-01-17 13:03:22 +00:00
if ( print || !trust ) {
for ( const auto &renamed : renamed_files ) {
2020-01-31 21:51:43 +00:00
cout << std::get< 2 >( renamed ) << " --> "
<< std::get< 3 >( renamed ) << std::endl;
2019-06-04 19:54:00 +00:00
}
2019-01-07 22:24:28 +00:00
2019-06-04 19:54:00 +00:00
if ( !trust ) {
2020-03-12 19:52:23 +00:00
cout << _( CONFIRMATION ) << " (" << _( YES_1 ) << "/" << _( NO_1 )
<< ") " << std::flush;
2019-06-04 19:54:00 +00:00
string response;
cin >> response;
cin.clear();
cin.ignore( 1, '\n' );
2020-03-12 19:52:23 +00:00
if ( response[0] != _( YES_1 )[0] && response[0] != _( YES_2 )[0] )
2019-06-04 19:54:00 +00:00
return;
}
}
2019-01-07 22:24:28 +00:00
for ( const auto &renamed : renamed_files ) {
2020-01-31 21:51:43 +00:00
FSLib::rename( std::get< 1 >( renamed ) + _tv_rename_dir_divider +
std::get< 2 >( renamed ),
std::get< 1 >( renamed ) + _tv_rename_dir_divider +
std::get< 3 >( renamed ) );
2020-01-17 13:03:22 +00:00
if ( found_files == nullptr ) {
2020-01-31 21:51:43 +00:00
files_ptr[0][std::get< 0 >( renamed )] = std::get< 1 >( renamed ) +
_tv_rename_dir_divider +
std::get< 3 >( renamed );
2020-01-15 21:20:44 +00:00
}
}
end:
2019-02-04 16:39:48 +00:00
if ( found_files != nullptr ) {
delete found_files;
}
2018-09-22 22:50:42 +00:00
}
void singleSeason( const string &path, const string &show, int season,
string id, const string &language, const string &pattern,
2020-01-15 21:20:44 +00:00
const size_t &flags, std::map< int, string > *files_ptr,
bool print ) {
singleSeason( path, show, season, id, language, pattern, flags & TV_LINUX,
flags & TV_TRUST, files_ptr, print, flags & TV_DVD );
}
2019-07-12 21:10:40 +00:00
#ifndef GUI
void multipleSeasons( const string &path, const string &show,
2020-04-16 11:23:02 +00:00
const std::set< int > &seasons, const string &language,
2020-01-15 21:20:44 +00:00
const string &pattern, const size_t &flags ) {
std::map< int, std::map< int, string > > season_map;
iterateFS( season_map, path );
auto id = getShowId( show, language );
2020-01-17 13:03:22 +00:00
for ( auto &x : season_map ) {
if ( seasons.find( x.first ) != seasons.end() ) {
singleSeason( path, show, x.first, id, language, pattern,
2020-01-31 21:38:11 +00:00
flags & TV_LINUX, flags & TV_TRUST, &x.second, true,
2020-01-17 13:03:22 +00:00
flags & TV_DVD );
2020-01-15 21:20:44 +00:00
}
}
}
void allSeasons( const string &path, const string &show, const string &language,
2020-01-15 21:20:44 +00:00
const string &pattern, const size_t &flags ) {
std::map< int, std::map< int, string > > seasons;
2019-02-04 16:39:48 +00:00
// get all season number from this directory and subdirectories
iterateFS( seasons, path );
2020-01-15 21:20:44 +00:00
auto id = getShowId( show, language );
2020-01-17 13:03:22 +00:00
for ( auto &x : seasons ) {
singleSeason( path, show, x.first, id, language, pattern,
2020-01-31 21:38:11 +00:00
flags & TV_LINUX, flags & TV_TRUST, &x.second, true,
2020-01-17 13:03:22 +00:00
flags & TV_DVD );
2020-01-15 21:20:44 +00:00
}
}
void printLangs() {
for ( auto &x : getLangs() )
cout << x.first << " - " << x.second << std::endl;
}
2020-01-17 13:03:22 +00:00
#endif
bool findLanguage( const char_t *language ) {
for ( auto &x : getLangs() ) {
if ( x.first == language )
return true;
}
return false;
}
2020-01-17 13:03:22 +00:00
bool validID( const string &id ) {
2020-01-31 21:38:11 +00:00
Request &request = _tv_rename_request;
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) +
_tv_rename_api_token );
2020-01-17 13:03:22 +00:00
2020-01-31 21:38:11 +00:00
request.get( TEXT( "/series/" ) + id );
2020-01-17 13:03:22 +00:00
2020-01-31 21:38:11 +00:00
return request.lastResponseCode() == 200;
2020-01-17 13:03:22 +00:00
}