commit eafb180b89d018ed8f74ec4cce849d4af93c66c7 Author: zvon Date: Sun Sep 23 00:50:42 2018 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32ddcad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +tv_rename diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..efb27c4 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CC ?= clang +CXX ?= clang++ +CFLAGS ?= -O2 -g -Wall -Wextra -std=c++17 + +default: tv_rename + +# using libc++ because libstdc++ has a bug in regex that causes seg fault with long lines + +tv_rename: functions.o tv_rename.cpp + clang++ $(CFLAGS) -stdlib=libc++ -o tv_rename tv_rename.cpp functions.o -lcurl -lc++experimental + +functions.o: functions.cpp + clang++ $(CFLAGS) -stdlib=libc++ -c functions.cpp + diff --git a/functions.cpp b/functions.cpp new file mode 100644 index 0000000..3cd5afe --- /dev/null +++ b/functions.cpp @@ -0,0 +1,125 @@ +#include "functions.hpp" +#include +#include +#include +#include + +size_t writeCallback( void *contents, size_t size, size_t nmemb, void *target ) { + *static_cast(target) += std::string(static_cast(contents), size*nmemb); + return size * nmemb; +} + +std::string getSource(const std::string &url) { + CURL *curl_handle; + CURLcode res; + + std::string source{}; + + curl_global_init(CURL_GLOBAL_ALL); + curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writeCallback); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, static_cast(&source)); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); + res = curl_easy_perform(curl_handle); + + curl_easy_cleanup(curl_handle); + curl_global_cleanup(); + + if(res != CURLE_OK) { + std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl; + return ""; + } + return source; +} + +std::string absolutePath(const std::string &path) { + char *buff = static_cast(malloc(PATH_MAX)); + char *ptr = realpath(path.c_str(), buff); + if( ptr == nullptr ) + return ""; + std::string abs_path(buff); + free(buff); + return abs_path; +} + +void findSeason(std::set &files, int season, const std::string &path) { + namespace fs = std::experimental::filesystem; + std::smatch match; + auto episode = std::regex("[sS][0]{0,2000}" + std::to_string(season) + "[eE][0-9]{1,2000}"); + for( auto& p: fs::directory_iterator(path) ) { + if(fs::is_directory(p)) { + findSeason(files, season, p.path()); + continue; + } + std::string a = p.path().filename(); + if( std::regex_search(a, episode) ) { + files.insert(p.path()); + } + } +} + +void iterateFS(std::set &seasons, const std::string &path) { + namespace fs = std::experimental::filesystem; + std::smatch match; + auto episode = std::regex("[sS][0-9]{1,2000}[eE][0-9]{1,2000}"); + for( auto& p: fs::directory_iterator(path) ) { + if(fs::is_directory(p)) { + iterateFS(seasons, p.path()); + continue; + } + std::string a = p.path().filename(); + if( std::regex_search(a, match, episode) ) { + a = match[0]; + a = std::regex_replace(a, std::regex("[eE][0-9]{1,2000}"), ""); + a = a.substr(1); + seasons.insert(std::stoi(a)); + } + } +} + +std::string getDefUrl( const std::string &show, const std::string &language ) { + using namespace std::string_literals; + auto search = std::regex_replace(show, std::regex(" "), "+"); + auto source_code = getSource("https://www.thetvdb.com/search?q=" + search + "&l=" + language); + auto series = std::regex(""); + int pos = 1; + std::vector urls; + for( std::sregex_iterator it(source_code.begin(), source_code.end(), series); it != std::sregex_iterator(); ++it) { + auto input = (*it).str(); + auto text = std::regex_replace(input, std::regex(".*series.*?>"), ""); + text = text.substr(0, text.size() - 9); + std::cout << pos << ". " << text << std::endl; + pos++; + urls.push_back(std::regex_replace(input, std::regex("\">.*"), "").substr(13)); + } + std::cout << "Which TV Show is the right one? "; + std::cin >> pos; + std::cin.clear(); + std::cin.ignore(1, '\n'); + return "https://www.thetvdb.com" + urls[pos-1]; +} + +void printHelp() { + std::cout << "usage: tv_rename [--help] [--show show name] [--season season number]" << std::endl; + std::cout << " [--correct-path] [--show-path show path] [--trust]" << std::endl; + std::cout << " [--linux] [--lang language] [--print-langs]" << std::endl; + std::cout << std::endl << "Rename TV episodes" << std::endl << std::endl << "optional arguments:" << std::endl; + std::cout << " -h, --help\t\tshow this help message and exit" << std::endl; + std::cout << " --show show name, -s show name" << std::endl; + std::cout << "\t\t\tTV show from which you want episode names (needs to be" << std::endl; + std::cout << "\t\t\tin quotation marks if it has more than one word)" << std::endl; + std::cout << " --season season number, -n season number" << std::endl; + std::cout << "\t\t\tSeason number/s (if multiple seasons, put them in" << std::endl; + std::cout << "\t\t\tquotation marks and seperate by one space)" << std::endl; + std::cout << "\t\t\tor 'all' for all seasons in selected subdirectory" << std::endl; + std::cout << " --correct-path, -c\tThis is the correct path, stop asking me!" << std::endl; + std::cout << " --show-path show path, -sp show path" << std::endl; + std::cout << "\t\t\tPath of the directory with episodes" << std::endl; + std::cout << " --trust, -t\t\tDon't ask whether the names are correct" << std::endl; + std::cout << " --linux, -x\t\tDon't replace characters characters that are illegal in Windows" << std::endl; + std::cout << " --lang language, -l language" << std::endl; + std::cout << "\t\t\tSelect which language the episode names shoud be in" << std::endl; + std::cout << " --print-langs, -p\tPring available language" << std::endl; +} + diff --git a/functions.hpp b/functions.hpp new file mode 100644 index 0000000..74f6376 --- /dev/null +++ b/functions.hpp @@ -0,0 +1,15 @@ +#ifndef GETPAGE_H +#define GETPAGE_H + +#include +#include +#include + +std::string getSource(const std::string &url); +std::string absolutePath(const std::string &path); +std::string getDefUrl( const std::string &show, const std::string &language ); +void findSeason(std::set &files, int season, const std::string &path); +void iterateFS(std::set &seasons, const std::string &path); +void printHelp(); + +#endif diff --git a/tv_rename.cpp b/tv_rename.cpp new file mode 100644 index 0000000..924ecac --- /dev/null +++ b/tv_rename.cpp @@ -0,0 +1,224 @@ +#include +#include +#include "functions.hpp" +#include +#include +#include +#include +#include +#include + +void singleSeason( const std::string &path, const std::string &show, int season, std::string url ); +void multipleSeasons( const std::string &path, const std::string &show, const std::set seasons ); +void allSeasons( const std::string &path, const std::string &show ); + +std::string language{"en"}; +bool trust{false}; +bool linux{false}; + +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::string seasons{""}; + std::set seasons_num; + std::string path{"."}; + bool change_dir{true}; + + 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])) ) { + seasons = std::regex_replace(argv[x+1], std::regex("[^[0-9 ]]*"), ""); + 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]); + if ( path[0] != '/' && path[0] != '.' ) + path = "./" + path; + x++; + if( absolutePath(path).empty() ) + 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 ) { + auto abs = absolutePath(path); + if( abs.empty() ) { + 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? " << absolutePath(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 ( path[0] != '/' && path[0] != '.' ) + path = "./" + path; + } + } + + path = absolutePath(path); + + if( show.empty() ) { + show = std::regex_replace(path, std::regex(".*/"), ""); + 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); + } +} + +void singleSeason( const std::string &path, const std::string &show, int season, std::string url) { + if( url.empty() ) + url = getDefUrl(show, language); + url += "/seasons/"; + url += std::to_string(season); + //get source code of season's page + auto season_code = getSource(url); + //remove newlines cause regex can't multiline + season_code = std::regex_replace(season_code, std::regex("[\r\n]"), ""); + //first 900 chars or so are useless to us, no need to regex through them + season_code = season_code.substr(900); + //get only the episode names + std::smatch season_match; + if( std::regex_search(season_code, season_match, std::regex(".*
")) ) { + season_code = season_match[0]; + } else { + return; + } + std::regex title(".*\\s*(.*?)\\s*?.*"); + std::regex episode_link(".*?"); + std::regex language_reg(""); + 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 ) { + std::string name = x.filename(); + std::string dir = std::regex_replace(x.c_str(), std::regex(name), ""); + unsigned long num; + try { + num = std::stoi(std::regex_replace(name, std::regex(".*[sS][0-9]{0,2000}[eE]([0-9]{0,2000}).*"), "$1")); + } catch (std::exception &e) { + continue; + } + num -= 1; + if( num < episodes.size() ) { + name = std::regex_replace(name, std::regex("(.*)\\.(.*)"), "$1 - " + episodes[num] + ".$2"); + 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"); + } + renamed_files.insert(dir + name); + renamed_episodes[num] = name; + } + } + auto orig = files.begin(); + namespace fs = std::experimental::filesystem; + for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) { + std::cout << std::string((*orig).filename()) << " -> " << std::string((*renamed).filename()) << 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(); + namespace fs = std::experimental::filesystem; + for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) { + fs::rename(*orig, *renamed); + ++orig; + } +} + +void multipleSeasons( const std::string &path, const std::string &show, const std::set seasons) { + auto url = getDefUrl(show, language); + 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); +} +