From c014a059b6700590baface724bfd5ccf3d184ce5 Mon Sep 17 00:00:00 2001 From: zvon Date: Fri, 4 Jan 2019 20:18:18 +0100 Subject: [PATCH] Use getopt_long for command line parsing, fix seasons parsing (when only a-zA-Z were present it behaved unpredictably), spaces instead of tabs --- tv_rename.cpp | 433 +++++++++++++++++++++++++++----------------------- 1 file changed, 234 insertions(+), 199 deletions(-) diff --git a/tv_rename.cpp b/tv_rename.cpp index a221d3c..1956a43 100644 --- a/tv_rename.cpp +++ b/tv_rename.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "filesystem.hpp" void singleSeason( const std::string &path, const std::string &show, int season, std::string url ); @@ -15,221 +16,255 @@ void allSeasons( const std::string &path, const std::string &show ); std::string language{"en"}; bool trust{false}; bool linux{false}; -Curl c; +Curl c; // global so the connection doesn't close at every function + +std::map languages{ + {"en", "English"}, {"sv", "Svenska"}, {"no", "Norsk"}, {"da", "Dansk"}, {"fi", "Suomeksi"}, + {"nl", "Nederlands"}, {"de", "Deutsch"}, {"it", "Italiano"}, {"es", "Español"}, {"fr", "Français"}, + {"pl", "Polski"}, {"hu", "Magyar"}, {"el", "Greek"}, {"tr", "Turkish"}, {"ru", "Russian"}, + {"he", "Hebrew"}, {"ja", "Japanese"}, {"pt", "Portuguese"}, {"zh", "Chinese"}, {"cs", "Czech"}, + {"sl", "Slovenian"}, {"hr", "Croatian"}, {"ko","Korea"} +}; + +void parseSeasonNumbers(std::set &seasons_num, const char *argument) { + size_t pos{0}; + + while((argument[pos] < '0' || argument[pos] > '9') && argument[pos] != ' ' && argument[pos] != '\0') + pos++; + + if( argument[pos] == '\0' ) { + seasons_num.clear(); + return; + } + + int temp; + std::istringstream iss(&optarg[pos]); + while(iss >> temp) { + seasons_num.insert(temp); + } +} + +bool parseCommandLine(std::string &show, std::set &seasons_num, std::string &path, bool &change_dir, int argc, char **argv) { + static struct option long_options[] = { + {"show", required_argument, 0, 's' }, + {"season", required_argument, 0, 'n' }, + {"correct-path", no_argument, 0, 'c' }, + {"show-path", required_argument, 0, 'p' }, + {"trust", no_argument, 0, 't' }, + {"linux", no_argument, 0, 'x' }, + {"lang", required_argument, 0, 'l' }, + {"print-langs", no_argument, 0, 0 }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; + + while(1) { + int option_index{0}; + auto c = getopt_long(argc, argv, "s:n:cp:txl:0h", long_options, &option_index); + if( c == -1 ) + break; + switch(c) { + case 's': + show = optarg; + break; + case 'n': + parseSeasonNumbers(seasons_num, optarg); + break; + case 'c': + change_dir = false; + break; + case 'p': + path = std::string(optarg); + if( !FSLib::isDirectory(path) ) + change_dir = true; + break; + case 't': + trust = true; + break; + case 'x': + linux = true; + break; + case 'l': + if( languages.find(optarg) != languages.end() ) { + language = optarg; + } else { + std::cerr << "Invalid language choice" << std::endl; + return false; + } + break; + case '0': + for( const auto &x : languages ) { + std::cout << x.first << " - " << x.second << std::endl; + } + return true; + case 'h': + printHelp(); + return true; + default: + return false; + } + } + + return true; +} int main(int argc, char** argv) { - std::map languages{ - {"en", "English"}, {"sv", "Svenska"}, {"no", "Norsk"}, {"da", "Dansk"}, {"fi", "Suomeksi"}, - {"nl", "Nederlands"}, {"de", "Deutsch"}, {"it", "Italiano"}, {"es", "Español"}, {"fr", "Français"}, - {"pl", "Polski"}, {"hu", "Magyar"}, {"el", "Greek"}, {"tr", "Turkish"}, {"ru", "Russian"}, - {"he", "Hebrew"}, {"ja", "Japanese"}, {"pt", "Portuguese"}, {"zh", "Chinese"}, {"cs", "Czech"}, - {"sl", "Slovenian"}, {"hr", "Croatian"}, {"ko","Korea"} - }; + std::string show{}; + std::set seasons_num; + std::string path{"."}; + bool change_dir{true}; - std::string show{}; - std::string seasons{""}; - std::set seasons_num; - std::string path{"."}; - bool change_dir{true}; + parseCommandLine(show, seasons_num, path, change_dir, argc, argv); - int x = 1; - while ( x < argc ) { - if( !(strcmp("-s", argv[x]) && strcmp("--show", argv[x])) ) { - show = argv[x+1]; - x++; - } else if ( !(strcmp("-n", argv[x]) && strcmp("--season", argv[x])) ) { - size_t pos{0}; - while((argv[x+1][pos] < '0' || argv[x+1][pos] > '9') && argv[x+1][pos] != ' ') - pos++; - seasons = std::string(&argv[x+1][pos]); - x++; - } else if ( !(strcmp("-c", argv[x]) && strcmp("--correct-path", argv[x])) ) { - change_dir = false; - } else if ( !(strcmp("-sp", argv[x]) && strcmp("--show-path", argv[x])) ) { - path = std::string(argv[x+1]); - x++; - if( !FSLib::isDirectory(path) ) - change_dir = true; - } else if ( !(strcmp("-t", argv[x]) && strcmp("--trust", argv[x])) ) { - trust = true; - } else if ( !(strcmp("-x", argv[x]) && strcmp("--linux", argv[x])) ) { - linux = true; - } else if ( !(strcmp("-l", argv[x]) && strcmp("--lang", argv[x])) ) { - if( languages.find(argv[x+1]) != languages.end() ) { - language = argv[x+1]; - } else { - std::cerr << "Invalid language choice" << std::endl; - return 1; - } - x++; - } else if ( !(strcmp("-p", argv[x]) && strcmp("--print-langs", argv[x])) ) { - for( const auto &x : languages ) { - std::cout << x.first << " - " << x.second << std::endl; - } - return 0; - } else if ( !(strcmp("-h", argv[x]) && strcmp("--help", argv[x])) ) { - printHelp(); - return 0; - } - x++; - } + while( change_dir ) { + if( !FSLib::isDirectory(path) ) { + std::cout << "This directory doesn't exist, please insert a correct path: " << std::endl; + std::getline(std::cin, path); + continue; + } + std::cout << "Is this the right directory? " << FSLib::canonical(path) << std::endl; + std::string response; + std::cin >> response; + std::cin.ignore(1,'\n'); + std::cin.clear(); + if ( response[0] == 'y' || response[0] == 'Y' ) { + change_dir = false; + } else { + std::cout << "Insert correct path:" << std::endl; + std::getline(std::cin, path); + } + } - while( change_dir ) { - if( !FSLib::isDirectory(path) ) { - std::cout << "This directory doesn't exist, please insert a correct path: " << std::endl; - std::getline(std::cin, path); - continue; - } - std::cout << "Is this the right directory? " << FSLib::canonical(path) << std::endl; - std::string response; - std::cin >> response; - std::cin.ignore(1,'\n'); - std::cin.clear(); - if ( response[0] == 'y' || response[0] == 'Y' ) { - change_dir = false; - } else { - std::cout << "Insert correct path:" << std::endl; - std::getline(std::cin, path); - } - } + if( show.empty() ) { + auto pos = show.find_last_of('/'); + if( pos != std::string::npos ) + show = show.substr(++pos); + std::cout << "Is this the right show name? " << show << std::endl; + std::string response; + std::cin >> response; + std::cin.ignore(1, '\n'); + std::cin.clear(); + if( response[0] != 'y' && response[0] != 'Y' ) { + std::cout << "Insert the correct show name: " << std::endl; + std::getline(std::cin, show); + } + } - if( show.empty() ) { - auto pos = show.find_last_of('/'); - if( pos != std::string::npos ) - show = show.substr(++pos); - std::cout << "Is this the right show name? " << show << std::endl; - std::string response; - std::cin >> response; - std::cin.ignore(1, '\n'); - std::cin.clear(); - if( response[0] != 'y' && response[0] != 'Y' ) { - std::cout << "Insert the correct show name: " << std::endl; - std::getline(std::cin, show); - } - } - - if( !seasons.empty() ) { - int temp; - std::istringstream iss(seasons); - while(iss >> temp) { - seasons_num.insert(temp); - } - if( seasons_num.size() == 1 ) { - singleSeason(path, show, temp, ""); - } else { - multipleSeasons(path, show, seasons_num); - } - } else { - allSeasons(path, show); - } + if( seasons_num.size() == 1 ) { + singleSeason(path, show, *seasons_num.begin(), ""); + } else if ( seasons_num.size() != 0 ) { + multipleSeasons(path, show, seasons_num); + } else { + allSeasons(path, show); + } } void singleSeason( const std::string &path, const std::string &show, int season, std::string url) { - if( url.empty() ) - url = getDefUrl(show, language, c); + if( url.empty() ) + url = getDefUrl(show, language, c); url += "/seasons/"; - url += std::to_string(season); - //get source code of season's page - auto season_code = c.execute(url); - //remove newlines - season_code.erase(std::remove_if(season_code.begin(), season_code.end(), [](char x) {return x == '\r' || x == '\n';}), season_code.end()); - //first 900 chars or so are useless to us - season_code = season_code.substr(900); - //get only the episode names - auto pos = season_code.find("\"translations\""); - if( pos != std::string::npos ) { - season_code = season_code.substr(pos); - pos = season_code.find("table"); - if( pos != std::string::npos ) - season_code = season_code.substr(0,pos); - else - return; - } else { - return; - } - std::regex title(".*\\s*(.*?)\\s*?.*"); - std::regex episode_link(".*?"); - std::smatch ep_match; - std::vector episodes; - //get episode names in all languages - for( std::sregex_iterator it(season_code.begin(), season_code.end(), episode_link); it != std::sregex_iterator(); ++it) { - auto input = (*it).str(); - //only get the selected language - if( std::regex_search( input, title ) ) - episodes.push_back(std::regex_replace(input, title, "$1")); - } - if( episodes.empty() ) - return; - std::set files; - std::set renamed_files; - std::vector renamed_episodes; - renamed_episodes.resize(episodes.size()); - findSeason(files, season, path); - if( files.empty() ) - return; - for( const auto &x : files ) { - auto last = x.find_last_of("/"); - std::string name; - std::string dir; - if( last == static_cast(-1) ) { - name = x; - dir = "."; - } else { - name = x.substr(last+1); - dir = x.substr(0, last); - } - unsigned long num; - try { - num = std::stoi(std::regex_replace(name, std::regex(".*[sS][0]{0,2000}" + std::to_string(season) + "[eE]([0-9]{1,2000}).*"), "$1")); - } catch (std::exception &e) { - continue; - } - num -= 1; - if( num < episodes.size() ) { - auto pos = name.find_last_of('.'); - name.insert(pos, " - " + episodes[num]); - if( !linux ) { - name = std::regex_replace(name, std::regex("[\\?\"\\\\|\\*]"), ""); - name = std::regex_replace(name, std::regex("<"), "is less than"); - name = std::regex_replace(name, std::regex(">"), "is more than"); - name = std::regex_replace(name, std::regex(":"), " -"); - } - renamed_files.insert(dir + "/" + name); - renamed_episodes[num] = name; - } - } - auto orig = files.begin(); - for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) { - std::cout << *renamed << std::endl; - ++orig; - } - if( !trust ) { - std::cout << "Does this seem ok? (y/n) "; - std::string response; - std::cin >> response; - std::cin.clear(); - std::cin.ignore(1, '\n'); - if( response[0] != 'y' && response[0] != 'Y' ) - return; - } - orig = files.begin(); - for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) { - FSLib::rename(*orig, *renamed); - ++orig; - } + url += std::to_string(season); + //get source code of season's page + auto season_code = c.execute(url); + //remove newlines + season_code.erase(std::remove_if(season_code.begin(), season_code.end(), [](char x) {return x == '\r' || x == '\n';}), season_code.end()); + //first 900 chars or so are useless to us + season_code = season_code.substr(900); + //get only the episode names + auto pos = season_code.find("\"translations\""); + if( pos != std::string::npos ) { + season_code = season_code.substr(pos); + pos = season_code.find("table"); + if( pos != std::string::npos ) + season_code = season_code.substr(0,pos); + else + return; + } else { + return; + } + std::regex title(".*\\s*(.*?)\\s*?.*"); + std::regex episode_link(".*?"); + std::smatch ep_match; + std::vector episodes; + //get episode names in all languages + for( std::sregex_iterator it(season_code.begin(), season_code.end(), episode_link); it != std::sregex_iterator(); ++it) { + auto input = (*it).str(); + //only get the selected language + if( std::regex_search( input, title ) ) + episodes.push_back(std::regex_replace(input, title, "$1")); + } + if( episodes.empty() ) + return; + std::set files; + std::set renamed_files; + std::vector renamed_episodes; + renamed_episodes.resize(episodes.size()); + findSeason(files, season, path); + if( files.empty() ) + return; + for( const auto &x : files ) { + auto last = x.find_last_of("/"); + std::string name; + std::string dir; + if( last == static_cast(-1) ) { + name = x; + dir = "."; + } else { + name = x.substr(last+1); + dir = x.substr(0, last); + } + unsigned long num; + try { + num = std::stoi(std::regex_replace(name, std::regex(".*[sS][0]{0,2000}" + std::to_string(season) + "[eE]([0-9]{1,2000}).*"), "$1")); + } catch (std::exception &e) { + continue; + } + num -= 1; + if( num < episodes.size() ) { + auto pos = name.find_last_of('.'); + name.insert(pos, " - " + episodes[num]); + if( !linux ) { + name = std::regex_replace(name, std::regex("[\\?\"\\\\|\\*]"), ""); + name = std::regex_replace(name, std::regex("<"), "is less than"); + name = std::regex_replace(name, std::regex(">"), "is more than"); + name = std::regex_replace(name, std::regex(":"), " -"); + } + renamed_files.insert(dir + "/" + name); + renamed_episodes[num] = name; + } + } + auto orig = files.begin(); + for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) { + std::cout << *renamed << std::endl; + ++orig; + } + if( !trust ) { + std::cout << "Does this seem ok? (y/n) "; + std::string response; + std::cin >> response; + std::cin.clear(); + std::cin.ignore(1, '\n'); + if( response[0] != 'y' && response[0] != 'Y' ) + return; + } + orig = files.begin(); + for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) { + FSLib::rename(*orig, *renamed); + ++orig; + } } void multipleSeasons( const std::string &path, const std::string &show, const std::set seasons) { auto url = getDefUrl(show, language, c); - for( const auto &x : seasons ) { - singleSeason( path, show, x, url); - } + for( const auto &x : seasons ) { + singleSeason( path, show, x, url); + } } void allSeasons( const std::string &path, const std::string &show) { - std::set seasons; - //get all season number from this directory and subdirectories - iterateFS(seasons, path); - multipleSeasons( path, show, seasons); + std::set seasons; + //get all season number from this directory and subdirectories + iterateFS(seasons, path); + multipleSeasons( path, show, seasons); }