tv_rename/tv_rename.cpp

457 lines
15 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <algorithm>
#include <iostream>
#include <map>
#include "filesystem.hpp"
#include "functions.hpp"
#include "tv_rename.hpp"
#include "rapidjson/include/rapidjson/document.h"
#ifndef GUI
#include <sstream>
#include <vector>
#endif
#ifdef _WIN32
#include "resources_windows.h"
#include <windows.h>
#include <codecvt>
#include <fcntl.h>
#include <io.h>
#include <locale>
constexpr const char_t *_tv_rename_dir_divider = L"\\";
#define cout std::wcout
#define cerr std::wcerr
#define cin std::wcin
#define toString( a ) utf8_to_wstring( a )
#else
#include "resources_linux.h"
constexpr const char_t *_tv_rename_dir_divider = "/";
#define cout std::cout
#define cerr std::cerr
#define cin std::cin
#define toString( a ) a
#endif
string _tv_rename_api_token;
Request _tv_rename_request;
std::vector< std::pair< string, string > >
searchShow( const string &show, const string &language ) {
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 );
rapidjson::Document json;
json.Parse(
request.get( TEXT( "/search/series?name=" ) + encoded_show ).c_str() );
if ( json.HasParseError() )
return {};
const rapidjson::Value &results = json["data"];
if ( !results.IsArray() ) {
return {};
}
std::vector< std::pair< string, string > > ret;
// find all possible shows
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() ) );
ret.emplace_back( show, id );
}
request.clearHeader();
return ret;
}
#ifndef GUI
// get show's ID
string getShowId( const 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_SHOW ) << " " << std::flush;
cin >> pos;
cin.clear();
cin.ignore( 1, '\n' );
return search_results[pos - 1].second;
}
#endif
string showNameFromId( const string &id, const string &language ) {
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( "" );
return toString( json["data"]["seriesName"].GetString() );
}
// 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 ) {
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?" );
if ( dvd )
baseurl += TEXT( "dvdSeason=" );
else
baseurl += TEXT( "airedSeason=" );
baseurl += season;
baseurl += TEXT( "&page=" );
string page = TEXT( "1" );
std::vector< string > episodes;
do {
episodes.resize( episodes.size() * 2 );
rapidjson::Document json;
json.Parse( request.get( baseurl + page ).c_str() );
if ( json.HasParseError() )
return {};
if ( json.FindMember( "data" ) == json.MemberEnd() )
break;
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 )
index = epdata[i]["dvdEpisodeNumber"].GetInt();
else
index = epdata[i]["airedEpisodeNumber"].GetInt();
if ( index == static_cast< size_t >( -1 ) )
continue;
if ( index > episodes.size() )
episodes.resize( index );
index--;
episodes[index] =
toString( epdata[i]["episodeName"].GetString() );
// some eps have whitespace at the end
while ( isspace( episodes[index].back() ) )
episodes[index].pop_back();
}
}
} else {
cerr << _( NOT_FOUND_SHOW_SEASON, season.c_str(),
showNameFromId( id, language ).c_str() )
<< std::endl;
}
if ( json["links"]["next"].IsNull() )
break;
page = toString( std::to_string( json["links"]["next"].GetInt() ) );
} while ( 1 );
request.clearHeader();
return episodes;
}
std::vector< std::tuple< int, string, string, string > >
getRenamedFiles( const string &show, int season, const string &id,
const string &language, const string &pattern,
const bool &unix_names, const std::map< int, string > &files,
bool dvd ) {
auto season_num = toString( std::to_string( season ) );
auto episodes = getEpisodeNames( id, season_num, language, dvd );
if ( episodes.empty() )
return {};
if ( files.empty() )
return {};
// vector of pairs <ep_num,dir>,<original_name,new_name>
std::vector< std::tuple< int, string, string, string > > renamed_files;
for ( const auto &x : files ) {
auto last = x.second.find_last_of( _tv_rename_dir_divider );
string og_name;
string dir;
if ( last == string::npos ) {
og_name = x.second;
dir = TEXT( "." );
} else {
og_name = x.second.substr( last + 1 );
dir = x.second.substr( 0, last );
}
unsigned long ep_num = x.first - 1;
if ( ep_num < episodes.size() ) {
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 );
}
// get desired filename
auto name = compilePattern( pattern, season, x.first,
og_name_without_extension,
episodes[ep_num], show ) +
og_name_extension;
// replace '/' with '|'
for ( size_t i = 0; i < name.size(); i++ ) {
if ( name[i] == '/' ) {
name[i] = '|';
}
}
// replace characters illegal in windows if desired
if ( !unix_names ) {
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.erase( i, 1 );
name.insert( i, TEXT( "" ) );
max = name.size();
} else if ( name[i] == '>' ) {
name.erase( i, 1 );
name.insert( i, TEXT( "" ) );
max = name.size();
} else if ( name[i] == ':' ) {
name[i] = ' ';
name.insert( i + 1, TEXT( "- " ) );
max = name.size();
}
}
for ( size_t i = 0; i < max; i++ ) {
if ( name[i] == ' ' && name[i + 1] == ' ' ) {
name.erase( i, 1 );
max--;
i--;
}
}
}
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;
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
}
return langs;
}
bool authenticate( const std::string &api_key ) {
Request &request = _tv_rename_request;
#ifdef _WIN32
request.setServer( TEXT( "api.thetvdb.com" ) );
#else
request.setServer( "https://api.thetvdb.com" );
#endif
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();
// 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,
std::map< int, string > *files_ptr, bool print, bool dvd ) {
#ifndef GUI
if ( id.empty() )
id = getShowId( show, language );
#endif
std::map< int, std::map< int, string > > *found_files = nullptr;
if ( files_ptr == nullptr ) {
found_files = new std::map< int, std::map< int, string > >;
iterateFS( *found_files, path );
if ( found_files->find( season ) != found_files->end() )
files_ptr = &( *found_files )[season];
}
if ( files_ptr == nullptr ) {
cerr << _( NOT_FOUND_FOR_SEASON ) << " " << season << std::endl;
return;
}
auto renamed_files = getRenamedFiles( show, season, id, language, pattern,
unix_names, *files_ptr, dvd );
if ( renamed_files.empty() )
goto end;
if ( print || !trust ) {
for ( const auto &renamed : renamed_files ) {
cout << std::get< 2 >( renamed ) << " --> "
<< std::get< 3 >( renamed ) << std::endl;
}
if ( !trust ) {
cout << _( CONFIRMATION ) << " (" << _( YES_1 ) << "/" << _( NO_1 )
<< ") " << std::flush;
string response;
cin >> response;
cin.clear();
cin.ignore( 1, '\n' );
if ( response[0] != _( YES_1 )[0] && response[0] != _( YES_2 )[0] )
return;
}
}
for ( const auto &renamed : renamed_files ) {
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 ) );
if ( found_files == nullptr ) {
files_ptr[0][std::get< 0 >( renamed )] = std::get< 1 >( renamed ) +
_tv_rename_dir_divider +
std::get< 3 >( renamed );
}
}
end:
if ( found_files != nullptr ) {
delete found_files;
}
}
void singleSeason( const string &path, const string &show, int season,
string id, const string &language, const string &pattern,
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 );
}
#ifndef GUI
void multipleSeasons( const string &path, const string &show,
const std::set< int > &seasons, const string &language,
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 );
for ( auto &x : season_map ) {
if ( seasons.find( x.first ) != seasons.end() ) {
singleSeason( path, show, x.first, id, language, pattern,
flags & TV_LINUX, flags & TV_TRUST, &x.second, true,
flags & TV_DVD );
}
}
}
void allSeasons( const string &path, const string &show, const string &language,
const string &pattern, const size_t &flags ) {
std::map< int, std::map< int, string > > seasons;
// get all season number from this directory and subdirectories
iterateFS( seasons, path );
auto id = getShowId( show, language );
for ( auto &x : seasons ) {
singleSeason( path, show, x.first, id, language, pattern,
flags & TV_LINUX, flags & TV_TRUST, &x.second, true,
flags & TV_DVD );
}
}
void printLangs() {
for ( auto &x : getLangs() )
cout << x.first << " - " << x.second << std::endl;
}
#endif
bool findLanguage( const char_t *language ) {
for ( auto &x : getLangs() ) {
if ( x.first == language )
return true;
}
return false;
}
bool validID( const string &id ) {
Request &request = _tv_rename_request;
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) +
_tv_rename_api_token );
request.get( TEXT( "/series/" ) + id );
return request.lastResponseCode() == 200;
}