tv_rename/tv_rename.cpp
zvon afd6ca6607 Change variable linux to unix_names
Turns out that compilers have `#define linux 1` somewhere inside them
and for some reason this wasn't a problem when compiling with the `-c`
flag
2020-04-16 15:11:46 +02:00

447 lines
15 KiB
C++
Raw 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( "." ) );
// get desired filename
auto name = compilePattern( pattern, season, x.first,
og_name.substr( 0, pos ),
episodes[ep_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 ( !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, 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, 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;
}