tv_rename/tv_rename.cpp

368 lines
12 KiB
C++
Raw Normal View History

2019-01-07 22:24:28 +00:00
#include <algorithm>
2019-02-04 16:39:48 +00:00
#include <map>
#ifdef _WIN32
#include <codecvt>
#include <fcntl.h>
#include <io.h>
#include <locale>
#include <windows.h>
#endif
2019-01-23 19:46:03 +00:00
2019-07-12 21:10:40 +00:00
#include "filesystem.hpp"
2019-01-23 19:46:03 +00:00
#ifndef GUI
2018-09-22 22:50:42 +00:00
#include <iostream>
#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
#include "functions.hpp"
#include "tv_rename.hpp"
#include "json.hpp"
using json = nlohmann::json;
2019-02-04 16:39:48 +00:00
#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 >();
}
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,
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=";
2019-02-04 16:39:48 +00:00
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<string>();
}
}
}
2019-01-07 22:24:28 +00:00
} else {
cerr << "Couldn't find episode names for season " << season << " of show " << showNameFromId( id, language ) << std::endl;
2019-01-07 22:24:28 +00:00
}
if( j["links"]["next"].is_null() )
break;
page = std::to_string( j["links"]["next"].get< size_t >() );
} while( 1 );
c.clearHeader();
return episodes;
2019-01-07 22:24:28 +00:00
}
2019-02-04 16:39:48 +00:00
std::vector< std::pair< string, std::pair< string, string > > >
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 &linux, const std::set< string > &files, bool dvd = false ) {
2019-02-04 16:39:48 +00:00
#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 );
2019-02-04 16:39:48 +00:00
#endif
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
2019-02-04 16:39:48 +00:00
std::vector< std::pair< string, std::pair< string, string > > >
renamed_files;
size_t pos = 0;
2019-02-04 16:39:48 +00:00
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;
2019-02-04 16:39:48 +00:00
dir = TEXT( "." );
} else {
2019-02-04 16:39:48 +00:00
og_name = x.substr( last + 1 );
dir = x.substr( 0, last );
}
unsigned long num;
// get file's episode number
2019-02-04 16:39:48 +00:00
if ( searchSpecificSeason( x.c_str(), pos, season_num ) ) {
num = std::stoi( x.c_str() + pos );
2019-01-07 21:24:00 +00:00
} else {
continue;
}
num--;
2019-02-04 16:39:48 +00:00
if ( num < episodes.size() ) {
auto pos = og_name.find_last_of( TEXT( "." ) );
2019-01-23 13:08:40 +00:00
// get desired filename
2019-02-04 16:39:48 +00:00
auto name = compilePattern( pattern, season, num + 1,
og_name.substr( 0, pos ), episodes[num],
show ) +
og_name.substr( pos );
// 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
2019-02-04 16:39:48 +00:00
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] = '-';
2019-02-04 16:39:48 +00:00
} else if ( name[i] == '<' ) {
2019-01-07 22:24:28 +00:00
name[i] = 'i';
2019-02-04 16:39:48 +00:00
name.insert( i + 1, TEXT( "s less than" ) );
2019-01-07 22:24:28 +00:00
max += 11;
} else if ( name[i] == '>' ) {
name[i] = 'i';
2019-02-04 16:39:48 +00:00
name.insert( i + 1, TEXT( "s more than" ) );
2019-01-07 22:24:28 +00:00
max += 11;
} else if ( name[i] == ':' ) {
name[i] = ' ';
2019-02-04 16:39:48 +00:00
name.insert( i + 1, 1, '-' );
2019-01-07 22:24:28 +00:00
max++;
}
}
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--;
}
}
}
2019-02-04 16:39:48 +00:00
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,
2019-02-04 16:39:48 +00:00
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 );
2019-02-04 16:39:48 +00:00
std::set< string > *found_files = nullptr;
2019-02-04 16:39:48 +00:00
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 ) {
2019-06-04 19:54:00 +00:00
cout << renamed->second.first << " --> " << renamed->second.second
<< std::endl;
}
2019-01-07 22:24:28 +00:00
2019-06-04 19:54:00 +00:00
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;
}
}
2019-01-07 22:24:28 +00:00
2019-02-04 16:39:48 +00:00
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 );
}
2019-02-04 16:39:48 +00:00
if ( found_files != nullptr ) {
delete found_files;
}
2018-09-22 22:50:42 +00:00
}
2019-07-12 21:10:40 +00:00
#ifndef GUI
2019-02-04 16:39:48 +00:00
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 );
2019-02-04 16:39:48 +00:00
for ( const auto &x : seasons ) {
singleSeason( path, show, x.first, id, language, pattern, linux, trust,
&x.second, dvd );
}
}
2019-02-04 16:39:48 +00:00
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 ) {
2019-02-04 16:39:48 +00:00
std::map< int, std::set< string > > season_map;
findSeasons( season_map, path, seasons );
multipleSeasons( path, show, season_map, language, pattern, linux, trust, dvd );
}
2019-02-04 16:39:48 +00:00
void allSeasons( const string &path, string &show, const string &language,
const string &pattern, const bool &linux, const bool &trust, bool dvd ) {
2019-02-04 16:39:48 +00:00
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;
2019-01-17 00:24:04 +00:00
}
2019-01-23 19:46:03 +00:00
#endif