Add Windows support

This commit is contained in:
zvon 2019-02-04 17:39:48 +01:00
parent 1b20d90c0b
commit 55a8f32f49
20 changed files with 1815 additions and 677 deletions

View File

@ -8,7 +8,7 @@ default: tv_rename
.PHONY: clean
clean:
rm -f *.o tv_rename
rm -f *.o tv_rename tv_rename_gui
.PHONY: install
install: tv_rename
@ -50,3 +50,18 @@ functions_gui.o: functions.cpp
tv_rename_gui.o: tv_rename.cpp
$(CXX) $(CFLAGS) -c tv_rename.cpp -o tv_rename_gui.o -DGUI
.PHONY: windows
windows: tv_rename.exe
tv_rename.exe: tv_rename.cpp filesystem.cpp functions.cpp network.cpp main.cpp
$(CXX) -MD -EHsc -Fe"tv_rename" tv_rename.cpp filesystem.cpp functions.cpp network.cpp main.cpp -D_WIN32 -DUNICODE -link wininet.lib shlwapi.lib
.PHONY: windows_gui
windows_gui: tv_rename_gui.exe
tv_rename_gui.exe: tv_rename_gui.res tv_rename_gui.cpp tv_rename.cpp filesystem.cpp functions.cpp network.cpp
$(CXX) -MD -EHsc -Fe"tv_rename_gui" tv_rename_gui.cpp tv_rename.cpp filesystem.cpp functions.cpp network.cpp -D_WIN32 -DUNICODE -DGUI -link wininet.lib shlwapi.lib ole32.lib shell32.lib gdi32.lib user32.lib tv_rename_gui.res
tv_rename_gui.res: tv_rename_gui.rc
rc tv_rename_gui.rc

View File

@ -1,44 +1,91 @@
#include "filesystem.hpp"
#include <sys/stat.h>
#include <limits.h>
#include <stdlib.h>
#include <linux/limits.h>
#include <sys/stat.h>
#ifndef GUI
#ifdef _WIN32
#include <Shlwapi.h>
#include <windows.h>
#endif
bool FSLib::exists(const std::string &path) {
struct stat path_stat;
#include "filesystem.hpp"
#ifdef _WIN32
#define S_ISDIR( a ) a & _S_IFDIR
using stat_t = struct _stat;
#else
using stat_t = struct stat;
#endif // _WIN32
#ifndef GUI // these functions aren't needed in GUI
bool FSLib::exists( const string &path ) {
stat_t path_stat;
#ifdef _WIN32
return _wstat( path.c_str(), &path_stat ) == 0;
#else
return stat( path.c_str(), &path_stat ) == 0;
#endif
}
std::string FSLib::canonical(const std::string &path) {
char *canonical_path = static_cast<char *>(malloc(PATH_MAX));
if( realpath(path.c_str(), canonical_path) == nullptr ) {
free(canonical_path);
return "";
string FSLib::canonical( const string &path ) {
#ifdef _WIN32
auto PATH_MAX = MAX_PATH;
#endif
char_t *canonical_path = new char_t[PATH_MAX];
#ifdef _WIN32
auto failed = !PathCanonicalizeW( canonical_path, path.c_str() );
#else
auto failed = realpath( path.c_str(), canonical_path ) == nullptr;
#endif
if ( failed ) {
delete[] canonical_path;
return string();
}
std::string canonical_string{canonical_path};
free(canonical_path);
string canonical_string{ canonical_path };
delete[] canonical_path;
return canonical_string;
}
#endif // ndef GUI
bool FSLib::isDirectory( const string &path ) {
stat_t path_stat;
#ifdef _WIN32
_wstat( path.c_str(), &path_stat );
#else
stat( path.c_str(), &path_stat );
#endif
bool FSLib::isDirectory(const std::string &path) {
struct stat path_stat;
stat( path.c_str(), &path_stat );
return S_ISDIR( path_stat.st_mode );
}
bool FSLib::rename(const std::string &file_a, const std::string &file_b) {
bool FSLib::rename( const string &file_a, const string &file_b ) {
#ifdef _WIN32
return MoveFileExW( file_a.c_str(), file_b.c_str(),
MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING );
#else
return ::rename( file_a.c_str(), file_b.c_str() ) == 0;
#endif
}
FSLib::Directory::iterator FSLib::Directory::begin() {
return Iterator(dir_path);
return Iterator( *this );
}
FSLib::Directory::const_iterator FSLib::Directory::begin() const {
return Iterator(dir_path);
return Iterator( *this );
}
FSLib::Directory::const_iterator FSLib::Directory::cbegin() const {
@ -46,34 +93,68 @@ FSLib::Directory::const_iterator FSLib::Directory::cbegin() const {
}
FSLib::Directory::iterator FSLib::Directory::end() {
return Iterator(dir_path, nullptr);
#ifdef _WIN32
return Iterator( true );
#else
return Iterator( *this, nullptr );
#endif
}
FSLib::Directory::const_iterator FSLib::Directory::end() const {
return Iterator(dir_path, nullptr);
#ifdef _WIN32
return Iterator( true );
#else
return Iterator( *this, nullptr );
#endif
}
FSLib::Directory::const_iterator FSLib::Directory::cend() const {
return end();
}
const char *FSLib::Directory::path() const {
const char_t *FSLib::Directory::path() const {
return dir_path.c_str();
}
char const *FSLib::Directory::Iterator::operator*() const {
char_t const *FSLib::Directory::Iterator::operator*() const {
#ifdef _WIN32
return data.cFileName;
#else
return current_entry->d_name;
#endif
}
#ifdef _WIN32
FSLib::Directory::Iterator &FSLib::Directory::Iterator::operator++() {
if ( ended == true )
return *this;
// skip . and ..
if ( FindNextFileW( hFind, &data ) == 0 ) {
ended = true;
} else if ( !wcscmp( data.cFileName, L"." ) ||
!wcscmp( data.cFileName, L".." ) ) {
return operator++();
}
return *this;
}
#else
FSLib::Directory::Iterator &FSLib::Directory::Iterator::operator++() {
if ( current_entry == nullptr )
return *this;
current_entry = readdir( d );
if( current_entry != nullptr && ( !strcmp(current_entry->d_name, ".") || !strcmp(current_entry->d_name, "..") ) )
// skip . and ..
if ( current_entry != nullptr &&
( !strcmp( current_entry->d_name, "." ) ||
!strcmp( current_entry->d_name, ".." ) ) )
return operator++();
return *this;
}
#endif
FSLib::Directory::Iterator FSLib::Directory::Iterator::operator++( int ) {
Iterator ret( *this );
operator++();
@ -81,9 +162,13 @@ FSLib::Directory::Iterator FSLib::Directory::Iterator::operator++(int) {
}
bool FSLib::Directory::Iterator::operator==( const Iterator &i_other ) const {
#ifdef _WIN32
return i_other.ended == ended;
#else
return i_other.current_entry == current_entry;
#endif
}
bool FSLib::Directory::Iterator::operator!=( const Iterator &i_other ) const {
return i_other.current_entry != current_entry;
return !( i_other == *this );
}

View File

@ -1,34 +1,96 @@
#ifndef FSLIB_H
#define FSLIB_H
#include <string>
#include <dirent.h>
#include <string.h>
#include <string>
#ifdef _WIN32
#include <Windows.h>
#else
#include <dirent.h>
#endif
// set apropriate data types for each operating system
#ifdef _WIN32
using string = std::wstring;
using char_t = wchar_t;
#else
using string = std::string;
using char_t = char;
#endif
// windows version stolen from
// http://www.martinbroadhurst.com/list-the-files-in-a-directory-in-c.html
namespace FSLib {
#ifndef GUI
bool exists(const std::string &path);
std::string canonical(const std::string &path);
bool exists( const string &path );
string canonical( const string &path );
#endif
bool isDirectory(const std::string &path);
bool rename(const std::string &file_a, const std::string &file_b);
bool isDirectory( const string &path );
bool rename( const string &file_a, const string &file_b );
class Directory {
public:
Directory(const std::string &path_) : dir_path(path_) {}
Directory( const string &path_ ) : dir_path( path_ ) {
#ifdef _WIN32
// need to append \\* for windows to search files in directory
dir_path.append( L"\\*" );
#endif
}
Directory() = delete;
Directory( const Directory &d ) = default;
Directory( Directory &&d ) = default;
class Iterator {
public:
#ifdef _WIN32
Iterator( const Directory &d_ ) {
hFind = FindFirstFileW( d_.path(), &data );
if ( hFind != INVALID_HANDLE_VALUE ) {
if ( !wcscmp( data.cFileName, L"." ) ||
!wcscmp( data.cFileName, L".." ) ) {
++( *this );
}
} else {
ended = true;
}
}
// this is definitely not a good way to create the "end" iterator
// but it was the only way I thought of with my limited knowledge of
// windows.h
Iterator( bool ended_ ) : ended( ended_ ) {}
~Iterator() {
if ( hFind )
FindClose( hFind );
}
#else
Iterator( const Directory &d_ ) : d( opendir( d_.path() ) ) {
current_entry = readdir( d );
if( current_entry != nullptr && ( !strcmp(current_entry->d_name, ".") || !strcmp(current_entry->d_name, "..") ) )
if ( current_entry != nullptr &&
( !strcmp( current_entry->d_name, "." ) ||
!strcmp( current_entry->d_name, ".." ) ) )
++( *this );
}
Iterator( const Directory &d_, const struct dirent *current_entry_) : d(opendir(d_.path())), current_entry(current_entry_) {}
Iterator( const Directory &d_, const struct dirent *current_entry_ )
: d( opendir( d_.path() ) ), current_entry( current_entry_ ) {}
~Iterator() {
closedir( d );
}
#endif
Iterator() = delete;
@ -36,11 +98,7 @@ namespace FSLib{
Iterator( Iterator &&i ) = default;
~Iterator() {
closedir(d);
}
char const *operator*() const;
char_t const *operator*() const;
Iterator &operator++();
@ -51,8 +109,14 @@ namespace FSLib{
bool operator!=( const Iterator &i_other ) const;
private:
#ifndef _WIN32
DIR *d;
const struct dirent *current_entry;
#else
HANDLE hFind;
WIN32_FIND_DATA data;
bool ended{ false };
#endif
};
using iterator = Iterator;
@ -70,11 +134,11 @@ namespace FSLib{
const_iterator cend() const;
const char *path() const;
const char_t *path() const;
private:
std::string dir_path;
string dir_path;
};
} //end FSLib
} // namespace FSLib
#endif

View File

@ -1,43 +1,108 @@
#include "functions.hpp"
#include "filesystem.hpp"
#include <algorithm>
#include <array>
#include <cctype>
#include <iomanip>
#include <sstream>
#include <vector>
#ifdef _WIN32
#include <codecvt>
#include <iostream>
#include <shlobj.h>
#else
#ifndef GUI
#include <iostream>
#include <curl/curl.h>
#include <iostream>
#include <stdlib.h>
#include <vector>
#else
#include <unistd.h>
#include <pwd.h>
#include <unistd.h>
#endif
#endif // GUI
#endif // _WIN32
#include "filesystem.hpp"
#include "functions.hpp"
#ifdef _WIN32
#define cout std::wcout
#define cerr std::wcerr
#define cin std::wcin
constexpr const char_t *dir_divider = L"\\";
#else
#define TEXT( a ) a
#define cout std::cout
#define cerr std::cerr
#define cin std::cin
constexpr const char_t *dir_divider = "/";
#endif // _WIN32
#ifndef GUI
constexpr std::array<const char *, 46> 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"
constexpr std::array< const char_t *, 46 > languages{
TEXT( "en" ), TEXT( "English" ), TEXT( "sv" ), TEXT( "Svenska" ),
TEXT( "no" ), TEXT( "Norsk" ), TEXT( "da" ), TEXT( "Dansk" ),
TEXT( "fi" ), TEXT( "Suomeksi" ), TEXT( "nl" ), TEXT( "Nederlands" ),
TEXT( "de" ), TEXT( "Deutsch" ), TEXT( "it" ), TEXT( "Italiano" ),
TEXT( "es" ), TEXT( "Español" ), TEXT( "fr" ), TEXT( "Français" ),
TEXT( "pl" ), TEXT( "Polski" ), TEXT( "hu" ), TEXT( "Magyar" ),
TEXT( "el" ), TEXT( "Greek" ), TEXT( "tr" ), TEXT( "Turkish" ),
TEXT( "ru" ), TEXT( "Russian" ), TEXT( "he" ), TEXT( "Hebrew" ),
TEXT( "ja" ), TEXT( "Japanese" ), TEXT( "pt" ), TEXT( "Portuguese" ),
TEXT( "zh" ), TEXT( "Chinese" ), TEXT( "cs" ), TEXT( "Czech" ),
TEXT( "sl" ), TEXT( "Slovenian" ), TEXT( "hr" ), TEXT( "Croatian" ),
TEXT( "ko" ), TEXT( "Korea" )
};
#endif
#endif // not GUI
#ifdef _WIN32
// functions to convert between string and wstring
std::string wstring_to_utf8( const std::wstring &wstring ) {
std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > wconv;
return wconv.to_bytes( wstring );
}
std::wstring utf8_to_wstring( const std::string &utf8 ) {
std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > wconv;
return wconv.from_bytes( utf8 );
}
#endif // _WIN32
// encode url so it's valid even with UTF-8 characters
std::string encodeUrl( const std::string &url ) {
//stolen from here - https://stackoverflow.com/questions/154536/encode-decode-urls-in-c
std::ostringstream encoded;
string encodeUrl( const string &url ) {
// stolen from here -
// https://stackoverflow.com/questions/154536/encode-decode-urls-in-c
#ifdef _WIN32
std::wstringstream encoded;
auto url_c = wstring_to_utf8( url );
#else
std::stringstream encoded;
const auto &url_c = url;
#endif
encoded.fill( '0' );
encoded << std::hex;
for( auto &x : url ) {
if( std::isalnum(x) || x == '-' || x == '_' || x == '.' || x == '~' ) {
for ( const auto &x : url_c ) {
if ( isalnum( static_cast< unsigned char >( x ) ) || x == '-' ||
x == '_' || x == '.' || x == '~' ) {
encoded << x;
continue;
}
@ -49,168 +114,237 @@ std::string encodeUrl( const std::string &url ) {
// return true if filename has specified season
// set ep_pos to position where episode number starts
bool searchSpecificSeason(const char *const p, size_t &ep_pos, const std::string &number) {
bool searchSpecificSeason( const char_t *const path, size_t &ep_pos,
const string &number ) {
size_t cur_pos{};
bool found_season{false};
while( p[cur_pos] != '\0' ) {
if( (p[cur_pos] == 's' || p[cur_pos] == 'S') && isdigit(p[cur_pos+1]) ) {
// search for S[0-9]+E[0-9]+
while ( path[cur_pos] != '\0' ) {
if ( ( path[cur_pos] == 's' || path[cur_pos] == 'S' ) &&
iswdigit( path[cur_pos + 1] ) ) {
cur_pos++;
while( p[cur_pos] == '0' )
while ( path[cur_pos] == '0' )
cur_pos++;
size_t offset{};
while( offset < number.size() && p[cur_pos + offset] == number[offset] )
offset++;
cur_pos += offset;
if( offset != number.size() )
// make sure season's number is the same as provided in argument
// `number`
#ifdef _WIN32
if ( wcsncmp( path + cur_pos, number.c_str(), number.size() ) )
#else
if ( strncmp( path + cur_pos, number.c_str(), number.size() ) )
#endif
continue;
if( (p[cur_pos] == 'e' || p[cur_pos] == 'E') && isdigit(p[cur_pos+1]) ) {
found_season = true;
cur_pos += number.size();
if ( ( path[cur_pos] == 'e' || path[cur_pos] == 'E' ) &&
iswdigit( path[cur_pos + 1] ) ) {
ep_pos = cur_pos + 1;
break;
return true;
}
}
cur_pos++;
}
return found_season;
return false;
}
bool searchSpecificSeason(const char *const p, const std::string &number) {
bool searchSpecificSeason( const char_t *const p, const string &number ) {
size_t tmp;
return searchSpecificSeason( p, tmp, number );
}
bool searchSeason(const char *const p, size_t &season_pos) {
// return true if file contains S[0-9]+E[0-9]+ nad set
// season_pos to start of season number
bool searchSeason( const char_t *const path, size_t &season_pos ) {
size_t cur_pos{};
bool found_season{false};
while( p[cur_pos] != '\0' ) {
if( (p[cur_pos] == 's' || p[cur_pos] == 'S') && isdigit(p[cur_pos+1]) ) {
while ( path[cur_pos] != '\0' ) {
if ( ( path[cur_pos] == 's' || path[cur_pos] == 'S' ) &&
iswdigit( path[cur_pos + 1] ) ) {
cur_pos++;
season_pos = cur_pos; // after ++ because we want the first pos to point to season's number
while( isdigit(p[cur_pos]) )
season_pos = cur_pos; // after ++ because we want the first pos to
// point to season's number
while ( iswdigit( path[cur_pos] ) )
cur_pos++;
if( (p[cur_pos] == 'e' || p[cur_pos] == 'E') && isdigit(p[cur_pos+1]) ) {
found_season = true;
break;
if ( ( path[cur_pos] == 'e' || path[cur_pos] == 'E' ) &&
iswdigit( path[cur_pos + 1] ) ) {
return true;
}
}
cur_pos++;
}
return found_season;
return false;
}
bool searchSeason(const char *const p) {
bool searchSeason( const char_t *const path ) {
size_t tmp{};
return searchSeason(p, tmp);
return searchSeason( path, tmp );
}
void iterateFS(std::map<int, std::set<std::string>> &seasons, const std::string &path) {
size_t season_pos{std::string::npos}; // season_pos - position of first digit of the season
void iterateFS( std::map< int, std::set< string > > &seasons,
const string &path ) {
// season_pos - position of first digit of the season
size_t season_pos{ string::npos };
for ( const auto p : FSLib::Directory( path ) ) {
if(FSLib::isDirectory(path + "/" + p)) {
iterateFS(seasons, path + "/" + p);
// if p is directory, iterate through it
if ( FSLib::isDirectory( path + dir_divider + p ) ) {
iterateFS( seasons, path + dir_divider + p );
continue;
}
// if file is a correct format, add it to file list
// for its season
if ( searchSeason( p, season_pos ) )
seasons[atoi(p+season_pos)].insert(path + "/" + p);
seasons[std::stoi( p + season_pos )].insert(
path + dir_divider + p );
}
}
#ifndef GUI
// following functions are only needed for CLI version
void findSeason(std::set<std::string> &files, int season, const std::string &path) {
// find all files for provided season in `path` and store it in `files`
void findSeason( std::set< string > &files, int season, const string &path ) {
#ifdef _WIN32
auto number = std::to_wstring( season );
#else
auto number = std::to_string( season );
#endif
for ( const auto p : FSLib::Directory( path ) ) {
if(FSLib::isDirectory(path + "/" + p)) {
findSeason(files, season, path + "/" + p);
// if p is directory, iterate through it
if ( FSLib::isDirectory( path + dir_divider + p ) ) {
findSeason( files, season, path + dir_divider + p );
continue;
}
if ( searchSpecificSeason( p, number ) )
files.insert(path + "/" + p);
files.insert( path + dir_divider + p );
}
}
void findSeasons(std::map<int, std::set<std::string>> &seasons, const std::string &path, const std::set<int> &season_numbers) {
size_t season_pos{std::string::npos}; // season_pos - position of first digit of the season
// find all files that comply with the S[0-9]+E[0-9]+ pattern
// and their season is in season_numbers and store tem in `seasons`
void findSeasons( std::map< int, std::set< string > > &seasons,
const string &path, const std::set< int > &season_numbers ) {
// season_pos - position of first digit of the season
size_t season_pos{ string::npos };
for ( const auto p : FSLib::Directory( path ) ) {
if(FSLib::isDirectory(path + "/" + p)) {
findSeasons(seasons, path + "/" + p, season_numbers);
// if p is directory, iterate through it
if ( FSLib::isDirectory( path + dir_divider + p ) ) {
findSeasons( seasons, path + dir_divider + p, season_numbers );
continue;
}
if ( searchSeason( p, season_pos ) ) {
auto num = atoi(p+season_pos);
auto num = std::stoi( p + season_pos );
if ( season_numbers.find( num ) != season_numbers.end() )
seasons[num].insert(path + "/" + p);
seasons[num].insert( path + dir_divider + p );
}
}
}
std::string getDefUrl( std::string &show, const std::string &language, Curl &c ) {
string getDefUrl( string &show, const string &language, Curl &c ) {
std::replace( show.begin(), show.end(), ' ', '+' );
auto source_code = c.execute("https://www.thetvdb.com/search?q=" + encodeUrl(show) + "&l=" + language);
string base_url = TEXT( "https://www.thetvdb.com" );
#ifdef _WIN32
string source_code = utf8_to_wstring(
c.execute( TEXT( "https://www.thetvdb.com/search?q=" ) +
encodeUrl( show ) + TEXT( "&l=" ) + language ) );
#else
string source_code =
c.execute( TEXT( "https://www.thetvdb.com/search?q=" ) +
encodeUrl( show ) + TEXT( "&l=" ) + language );
#endif
size_t order{}, pos{};
std::vector<std::pair<std::string, std::string>> urls;
std::vector< std::pair< string, string > > urls;
// find all possible shows
while ( true ) {
pos = source_code.find("/ser", pos);
if( pos != std::string::npos ) {
auto end = source_code.find(">", pos);
pos = source_code.find( TEXT( "/ser" ), pos );
if ( pos != string::npos ) {
auto end = source_code.find( TEXT( ">" ), pos );
end--;
auto end2 = source_code.find("<", end + 2);
urls.emplace_back(source_code.substr(end + 2, end2 - end - 2), source_code.substr(pos, end - pos));
std::cout << ++order << ". " << urls.back().first << std::endl;
auto end2 = source_code.find( TEXT( "<" ), end + 2 );
// store shows in urls, first is name, second is url
urls.emplace_back( source_code.substr( end + 2, end2 - end - 2 ),
source_code.substr( pos, end - pos ) );
cout << ++order << ". " << urls.back().first << std::endl;
pos = end2;
} else {
break;
}
}
std::cout << "Which TV Show is the right one? ";
std::cin >> pos;
std::cin.clear();
std::cin.ignore(1, '\n');
cout << "Which TV Show is the right one? ";
cin >> pos;
cin.clear();
cin.ignore( 1, '\n' );
show = urls[pos - 1].first;
return "https://www.thetvdb.com" + urls[pos-1].second;
return base_url + urls[pos - 1].second;
}
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 << " --show-path show path, -p show path" << std::endl;
std::cout << "\t\t\tPath of the directory with episodes" << std::endl;
std::cout << " --correct-path, -c\tThis is the correct path, stop asking me!" << std::endl;
std::cout << " --name-pattern pattern" << std::endl;
std::cout << "\t\t\tPattern to which change the file name. Possible sequences are:" << std::endl;
std::cout << "\t\t\t\t\%filename - original filename (without filetype extension)" << std::endl;
std::cout << "\t\t\t\t\%show - show name from thetvdb" << std::endl;
std::cout << "\t\t\t\t\%epname - episode name from thetvdb" << std::endl;
std::cout << "\t\t\t\t\%season - season number" << std::endl;
std::cout << "\t\t\t\t\tpossible to specify leading 0 like this: \%2season (number means how many leading zeros)" << std::endl;
std::cout << "\t\t\t\t\%episode - episode number" << std::endl;
std::cout << "\t\t\t\t\tpossible to specify leading 0 like this: \%2episode (number means how many leading zeros)" << std::endl;
std::cout << "\t\t\tDefault pattern is \"$filename - $epname\"" << 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\t\tPring available language" << std::endl;
cout << "usage: tv_rename [--help] [--show show name] [--season season "
"number]"
<< std::endl;
cout
<< " [--correct-path] [--show-path show path] [--trust]"
<< std::endl;
cout << " [--linux] [--lang language] [--print-langs]"
<< std::endl;
cout << std::endl
<< "Rename TV episodes" << std::endl
<< std::endl
<< "optional arguments:" << std::endl;
cout << " -h, --help\t\tshow this help message and exit" << std::endl;
cout << " --show show name, -s show name" << std::endl;
cout << "\t\t\tTV show from which you want episode names (needs to be"
<< std::endl;
cout << "\t\t\tin quotation marks if it has more than one word)"
<< std::endl;
cout << " --season season number, -n season number" << std::endl;
cout << "\t\t\tSeason number/s (if multiple seasons, put them in"
<< std::endl;
cout << "\t\t\tquotation marks and seperate by one space)" << std::endl;
cout << "\t\t\tor 'all' for all seasons in selected subdirectory"
<< std::endl;
cout << " --show-path show path, -p show path" << std::endl;
cout << "\t\t\tPath of the directory with episodes" << std::endl;
cout << " --correct-path, -c\tThis is the correct path, stop asking me!"
<< std::endl;
cout << " --name-pattern pattern" << std::endl;
cout << "\t\t\tPattern to which change the file name. Possible sequences "
"are:"
<< std::endl;
cout << "\t\t\t\t%filename - original filename (without filetype extension)"
<< std::endl;
cout << "\t\t\t\t%show - show name from thetvdb" << std::endl;
cout << "\t\t\t\t%epname - episode name from thetvdb" << std::endl;
cout << "\t\t\t\t%season - season number" << std::endl;
cout << "\t\t\t\ttpossible to specify leading 0 like this: %2season "
"(number means how many leading zeros)"
<< std::endl;
cout << "\t\t\t\t%episode - episode number" << std::endl;
cout << "\t\t\t\t\tpossible to specify leading 0 like this: %2episode "
"(number means how many leading zeros)"
<< std::endl;
cout << "\t\t\tDefault pattern is \"$filename - $epname\"" << std::endl;
cout << " --trust, -t\t\tDon't ask whether the names are correct"
<< std::endl;
cout << " --linux, -x\t\tDon't replace characters characters that are "
"illegal in Windows"
<< std::endl;
cout << " --lang language, -l language" << std::endl;
cout << "\t\t\tSelect which language the episode names shoud be in"
<< std::endl;
cout << " --print-langs\t\tPring available language" << std::endl;
}
void parseSeasonNumbers(std::set<int> &seasons_num, const char *argument) {
// parse command line argument --seasons (e.g. '1 2 3 4 5')
// and store season numbers as integers in seasons_num
void parseSeasonNumbers( std::set< int > &seasons_num,
const char_t *argument ) {
size_t pos{ 0 };
while(!isdigit(argument[pos]) && argument[pos] != '\0')
while ( !iswdigit( argument[pos] ) && argument[pos] != '\0' )
pos++;
if ( argument[pos] == '\0' ) {
@ -219,40 +353,65 @@ void parseSeasonNumbers(std::set<int> &seasons_num, const char *argument) {
}
int temp;
std::istringstream iss(argument + pos);
#ifdef _WIN32
std::wstringstream iss( argument + pos );
#else
std::stringstream iss( argument + pos );
#endif
while ( iss >> temp ) {
seasons_num.insert( temp );
}
}
// print possible language codes and their corresponding language
void printLangs() {
for ( size_t i = 0; i < languages.size(); i += 2 ) {
std::cout << languages[i] << " - " << languages[i+1] << std::endl;
cout << languages[i] << " - " << languages[i + 1] << std::endl;
}
}
bool findLanguage( const char *language ) {
// make sure language is a valide language code
bool findLanguage( const char_t *language ) {
for ( size_t i = 0; i < languages.size(); i += 2 ) {
#ifdef _WIN32
if ( !wcscmp( language, languages[i] ) )
#else
if ( !strcmp( language, languages[i] ) )
#endif
return true;
}
return false;
}
#else
// functions that are needed for GUI but not for CLI
std::vector<std::pair<std::string, std::string>> getPossibleShows( std::string show, const std::string &language, Curl &c ) {
// get possible shows for search query in `show`
std::vector< std::pair< string, string > >
getPossibleShows( string show, const string &language, Curl &c ) {
std::replace( show.begin(), show.end(), ' ', '+' );
auto source_code = c.execute("https://www.thetvdb.com/search?q=" + encodeUrl(show) + "&l=" + language);
#ifdef _WIN32
auto source_code = utf8_to_wstring(
c.execute( TEXT( "https://www.thetvdb.com/search?q=" ) +
encodeUrl( show ) + TEXT( "&l=" ) + language ) );
#else
auto source_code =
c.execute( TEXT( "https://www.thetvdb.com/search?q=" ) +
encodeUrl( show ) + TEXT( "&l=" ) + language );
#endif
size_t pos{};
std::vector<std::pair<std::string, std::string>> urls;
std::vector< std::pair< string, string > > urls;
while ( true ) {
pos = source_code.find("/ser", pos);
if( pos != std::string::npos ) {
auto end = source_code.find(">", pos);
auto end2 = source_code.find("<", end+1);
pos = source_code.find( TEXT( "/ser" ), pos );
if ( pos != string::npos ) {
auto end = source_code.find( TEXT( ">" ), pos );
auto end2 = source_code.find( TEXT( "<" ), end + 1 );
end--;
urls.emplace_back(source_code.substr(end+2, end2 - (end+2)), source_code.substr(pos, end - pos));
urls.emplace_back(
source_code.substr( end + 2, end2 - ( end + 2 ) ),
source_code.substr( pos, end - pos ) );
pos = end + 2;
} else {
break;
@ -261,13 +420,17 @@ std::vector<std::pair<std::string, std::string>> getPossibleShows( std::string s
return urls;
}
std::string userHome() {
uid_t user_uid;
#ifndef _WIN32
// get user's home directory
string userHome() {
uid_t user_uid; // current user's uid
{
uid_t eid;
uid_t sid;
getresuid( &user_uid, &eid, &sid ); // don't need eid and sid
}
// password file entry
auto user_passwd = getpwuid( user_uid );
if ( user_passwd == nullptr )
@ -276,26 +439,55 @@ std::string userHome() {
return user_passwd->pw_dir;
}
#else
// get user's %APPDATA% folder location
string userHome() {
wchar_t *dir = static_cast< wchar_t * >( CoTaskMemAlloc( MAX_PATH ) );
auto res = SHGetKnownFolderPath( FOLDERID_RoamingAppData, 0, NULL, &dir );
if ( res == S_OK ) {
string dir_s = dir;
CoTaskMemFree( dir );
return dir_s;
}
return L"";
}
#endif
std::string compilePattern(const std::string &pattern, int season, int episode, const std::string &filename, const std::string &episodeName, const std::string &showName) {
std::string output;
#endif // ndef GUI
// create file name based on given pattern
string compilePattern( const string &pattern, int season, int episode,
const string &filename, const string &episodeName,
const string &showName ) {
string output;
#ifdef _WIN32
auto season_num = std::to_wstring( season );
auto ep_num = std::to_wstring( episode );
#else
auto season_num = std::to_string( season );
auto ep_num = std::to_string( episode );
#endif
for ( size_t i = 0; i < pattern.size(); i++ ) {
// if current character is % check if a pattern follows, otherwise
// put %
if ( pattern[i] == '%' ) {
// if current character is % check if a pattern follows, otherwise put %
// check for numbers right after % indicating size of zero padding for numbers
auto pos = pattern.find_first_not_of("0123456789", i+1);
// check for numbers right after % indicating size of zero
// padding for numbers
auto pos = pattern.find_first_not_of( TEXT( "0123456789" ), i + 1 );
if( pattern.find( "season", pos - 1 ) == pos ) {
if ( pattern.find( TEXT( "season" ), pos - 1 ) == pos &&
pos != i + 1 ) {
// if season is AFTER numbers, put season number padded
// with zeros
// get number of leading zeros
auto leading = std::atoi( pattern.c_str() + i + 1 );
auto leading = std::stoi( pattern.c_str() + i + 1 );
// move i to the last char of 'season'
i = pos + 5;
auto season_num = std::to_string(season);
// get number of zeros to be put before the season number
leading -= season_num.size();
@ -303,36 +495,38 @@ std::string compilePattern(const std::string &pattern, int season, int episode,
leading = 0;
// add padded season to output
output += std::string(leading, '0') + season_num;
} else if ( pattern.find( "season", i ) == i + 1 ) {
// if season isn't after numbers, just put season number to output
output += string( leading, '0' ) + season_num;
} else if ( pattern.find( TEXT( "season" ), i ) == i + 1 ) {
// if season isn't after numbers, just put season number to
// output
i += 6;
output += std::to_string(season);
} else if ( pattern.find( "episode", pos - 1 ) == pos ) {
output += season_num;
} else if ( pattern.find( TEXT( "episode" ), pos - 1 ) == pos &&
pos != i + 1 ) {
// same principle as with season after number
auto leading = std::atoi( pattern.c_str() + i + 1 );
auto leading = std::stoi( pattern.c_str() + i + 1 );
i = pos + 6;
auto ep_num = std::to_string(episode);
leading -= ep_num.size();
if ( leading < 0 )
leading = 0;
output += std::string(leading, '0') + ep_num;
} else if ( pattern.find( "episode", i ) == i + 1 ) {
// if episode isn't after number, just put the episode number to output
output += string( leading, '0' ) + ep_num;
} else if ( pattern.find( TEXT( "episode" ), i ) == i + 1 ) {
// if episode isn't after number, just put the episode number to
// output
i += 7;
output += std::to_string(episode);
} else if ( pattern.find( "epname", i ) == i + 1 ) {
output += ep_num;
} else if ( pattern.find( TEXT( "epname" ), i ) == i + 1 ) {
// episode name from thetvdb
i += 6;
output += episodeName;
} else if ( pattern.find( "show", i ) == i + 1 ) {
} else if ( pattern.find( TEXT( "show" ), i ) == i + 1 ) {
// show name from thetvdb
i += 4;
output += showName;
} else if ( pattern.find( "filename", i ) == i + 1 ) {
} else if ( pattern.find( TEXT( "filename" ), i ) == i + 1 ) {
// original file name
i += 8;
output += filename;
@ -359,4 +553,3 @@ std::string compilePattern(const std::string &pattern, int season, int episode,
return output;
}

View File

@ -1,10 +1,10 @@
#ifndef GETPAGE_H
#define GETPAGE_H
#ifndef TV_FUNCTIONS_H
#define TV_FUNCTIONS_H
#include <string>
#include <set>
#include <iostream>
#include <map>
#include "network.hpp"
#include <set>
#include <string>
#ifdef GUI
@ -12,34 +12,62 @@
#endif
#ifndef GUI
#include "network.hpp"
std::string getDefUrl( std::string &show, const std::string &language, Curl &c );
void findSeason(std::set<std::string> &files, int season, const std::string &path);
void findSeasons(std::map<int, std::set<std::string>> &seasons, const std::string &path, const std::set<int> &season_numbers);
void printHelp();
#ifdef _WIN32
bool searchSpecificSeason(const char *const p, const std::string &number);
bool searchSeason(const char *const p, size_t &season_pos);
bool searchSeason(const char *const p);
void parseSeasonNumbers(std::set<int> &seasons_num, const char *argument);
void printLangs();
bool findLanguage( const char *language );
std::string encodeUrl( const std::string &url );
using string = std::wstring;
using char_t = wchar_t;
#else
std::vector<std::pair<std::string, std::string>> getPossibleShows( std::string show, const std::string &language, Curl &c );
std::string userHome();
using string = std::string;
using char_t = char;
#endif
void iterateFS(std::map<int, std::set<std::string>> &seasons, const std::string &path);
#ifdef _WIN32
bool searchSpecificSeason(const char *const p, size_t &ep_pos, const std::string &number);
std::string compilePattern(const std::string &pattern, int season, int episode, const std::string &filename, const std::string &episodeName, const std::string &showName);
std::wstring utf8_to_wstring( const std::string &utf8 );
#endif
#ifndef GUI
// CLI functions
string getDefUrl( string &show, const string &language, Curl &c );
void findSeason( std::set< string > &files, int season, const string &path );
void findSeasons( std::map< int, std::set< string > > &seasons,
const string &path, const std::set< int > &season_numbers );
void printHelp();
bool searchSpecificSeason( const char_t *const path, const string &number );
bool searchSeason( const char_t *const path, size_t &season_pos );
bool searchSeason( const char_t *const path );
void parseSeasonNumbers( std::set< int > &seasons_num, const char_t *argument );
void printLangs();
bool findLanguage( const char_t *language );
string encodeUrl( const string &url );
#else
// GUI functions
std::vector< std::pair< string, string > >
getPossibleShows( string show, const string &language, Curl &c );
string userHome();
#endif
void iterateFS( std::map< int, std::set< string > > &seasons,
const string &path );
bool searchSpecificSeason( const char_t *const path, size_t &ep_pos,
const string &number );
string compilePattern( const string &pattern, int season, int episode,
const string &filename, const string &episodeName,
const string &showName );
#endif

View File

@ -3,7 +3,8 @@
#include "mainwindow.hpp"
int main( int argc, char **argv ) {
auto app = Gtk::Application::create(argc, argv, "org.idonthaveanorganization.tvrename");
auto app = Gtk::Application::create(
argc, argv, "org.idonthaveanorganization.tvrename" );
MainWindow mw( app );

268
main.cpp
View File

@ -1,10 +1,151 @@
#include <getopt.h>
#include <iostream>
#include <set>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#include <windows.h>
#else
#include <getopt.h>
#endif
#include "filesystem.hpp"
#include "functions.hpp"
#include "tv_rename.hpp"
int parseCommandLine(std::string &show, std::set<int> &seasons_num, std::string &path, bool &change_dir, std::string &language, std::string &pattern, bool &linux, bool &trust, int argc, char **argv) {
#ifdef _WIN32
using char_t = wchar_t;
using string = std::wstring;
#define cerr std::wcerr
#define cout std::wcout
#define cin std::wcin
#else
using char_t = char;
using string = std::string;
#define cerr std::cerr
#define cout std::cout
#define cin std::cin
#define TEXT( a ) a
#endif
int handleArgument( char_t c, string &show, std::set< int > &seasons_num,
bool &change_dir, string &path, bool &trust, bool &linux,
string &language, string &pattern, char_t *optional,
int &i ) {
switch ( c ) {
case 's':
show = optional;
i++;
break;
case 'n':
parseSeasonNumbers( seasons_num, optional );
i++;
break;
case 'c':
change_dir = false;
break;
case 'p':
path = string( optional );
i++;
// if path provided, assume it's correct
change_dir = false;
break;
case 't':
trust = true;
break;
case 'x':
linux = true;
break;
case 'l':
if ( findLanguage( optional ) ) {
language = optional;
} else {
cerr << "Invalid language choice" << std::endl;
printLangs();
return -1;
}
i++;
break;
case '0':
printLangs();
return 1;
case 'h':
printHelp();
return 1;
case '1':
pattern = optional;
i++;
break;
default:
return -1;
}
return 0;
}
#ifdef _WIN32
string getOptions( const char_t *option ) {
if ( option[1] != '-' )
return option + 1;
if ( !wcscmp( option, L"--show" ) )
return L"s";
else if ( !wcscmp( option, L"--season" ) )
return L"n";
else if ( !wcscmp( option, L"--correct-path" ) )
return L"c";
else if ( !wcscmp( option, L"--show-path" ) )
return L"p";
else if ( !wcscmp( option, L"--trust" ) )
return L"t";
else if ( !wcscmp( option, L"--linux" ) )
return L"x";
else if ( !wcscmp( option, L"--lang" ) )
return L"l";
else if ( !wcscmp( option, L"--print-langs" ) )
return L"0";
else if ( !wcscmp( option, L"--name-pattern" ) )
return L"1";
else if ( !wcscmp( option, L"--help" ) )
return L"h";
return L"";
}
// there's no getopt for windows, so just use wcscmp
int parseCommandLine( string &show, std::set< int > &seasons_num, string &path,
bool &change_dir, string &language, string &pattern,
bool &linux, bool &trust, const int argc,
char_t **argv ) {
for ( auto i = 1; i < argc; i++ ) {
auto options = getOptions( argv[i] );
char_t *optional = ( i < argc - 1 ) ? argv[i + 1] : nullptr;
for ( const auto &x : options ) {
auto res =
handleArgument( x, show, seasons_num, change_dir, path, trust,
linux, language, pattern, optional, i );
if ( res != 0 )
return res;
}
}
return 0;
}
#else
// parse command line arguments using getopt
int parseCommandLine( string &show, std::set< int > &seasons_num, string &path,
bool &change_dir, string &language, string &pattern,
bool &linux, bool &trust, int argc, char **argv ) {
static struct option long_options[] = {
{ "show", required_argument, 0, 's' },
{ "season", required_argument, 0, 'n' },
@ -18,71 +159,50 @@ int parseCommandLine(std::string &show, std::set<int> &seasons_num, std::string
{ "help", no_argument, 0, 'h' }
};
int i{}; // this is useless, but needed for handleArgument
while ( 1 ) {
int option_index{ 0 };
auto c = getopt_long(argc, argv, "s:n:cp:txl:01:h", long_options, &option_index);
auto c = getopt_long( argc, argv, "s:n:cp:txl:01:h", 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 path provided, assume it's correct
change_dir = false;
break;
case 't':
trust = true;
break;
case 'x':
linux = true;
break;
case 'l':
if( findLanguage(optarg) ) {
language = optarg;
} else {
std::cerr << "Invalid language choice" << std::endl;
printLangs();
return -1;
}
break;
case '0':
printLangs();
return 1;
case 'h':
printHelp();
return 1;
case '1':
pattern = optarg;
break;
default:
return -1;
}
auto res = handleArgument( c, show, seasons_num, change_dir, path,
trust, linux, language, pattern, optarg, i );
if ( res != 0 )
return res;
}
return 0;
}
int main(int argc, char** argv) {
std::string show{};
std::set<int> seasons_num;
std::string path{"."};
#endif
// windows needs wmain for unicode support
#ifdef _WIN32
int wmain
#else
int main
#endif
( int argc, char_t **argv ) {
#ifdef _WIN32
// set console to unicode
_setmode( _fileno( stdout ), _O_U16TEXT );
#endif
string show{};
std::set< int > seasons_num{};
bool change_dir{ true };
std::string language{"en"};
bool linux{ false };
bool trust{ false };
std::string pattern{"%filename - %epname"};
string path{ TEXT( "." ) };
string language{ TEXT( "en" ) };
string pattern{ TEXT( "%filename - %epname" ) };
Curl c;
{
auto tmp = parseCommandLine(show, seasons_num, path, change_dir, language, pattern, linux, trust, argc, argv);
auto tmp =
parseCommandLine( show, seasons_num, path, change_dir, language,
pattern, linux, trust, argc, argv );
if ( tmp == -1 )
return 1;
else if ( tmp == 1 )
@ -94,44 +214,48 @@ int main(int argc, char** argv) {
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);
cout << "This directory doesn't exist, please insert a correct "
"path: "
<< std::endl;
std::getline( 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();
cout << "Is this the right directory? " << FSLib::canonical( path )
<< std::endl;
string response;
cin >> response;
cin.ignore( 1, '\n' );
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);
cout << "Insert correct path:" << std::endl;
std::getline( cin, path );
}
}
if ( show.empty() ) {
auto pos = show.find_last_of( '/' );
if( pos != std::string::npos )
if ( pos != 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();
cout << "Is this the right show name? " << show << std::endl;
string response;
cin >> response;
cin.ignore( 1, '\n' );
cin.clear();
if ( response[0] != 'y' && response[0] != 'Y' ) {
std::cout << "Insert the correct show name: " << std::endl;
std::getline(std::cin, show);
cout << "Insert the correct show name: " << std::endl;
std::getline( cin, show );
}
}
if ( seasons_num.size() == 1 ) {
singleSeason(path, show, *seasons_num.begin(), "", language, pattern, linux, trust, c);
singleSeason( path, show, *seasons_num.begin(), string(), language,
pattern, linux, trust, c );
} else if ( seasons_num.size() != 0 ) {
multipleSeasons(path, show, seasons_num, language, pattern, linux, trust, c);
multipleSeasons( path, show, seasons_num, language, pattern, linux,
trust, c );
} else {
allSeasons( path, show, language, pattern, linux, trust, c );
}
}

View File

@ -1,19 +1,21 @@
#include "mainwindow.hpp"
#include "functions.hpp"
#include "filesystem.hpp"
#include "tv_rename.hpp"
#include <gtkmm/liststore.h>
#include <fstream>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/liststore.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/textview.h>
#include <iostream>
#include <fstream>
#include "filesystem.hpp"
#include "functions.hpp"
#include "mainwindow.hpp"
#include "tv_rename.hpp"
constexpr std::array< const char *, 46 > 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",
"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"
};
@ -49,21 +51,24 @@ void MainWindow::quit() {
void MainWindow::patternHelp() {
Gtk::MessageDialog dialog( *this, "Pattern escape sequences" );
dialog.set_secondary_text( "%filename - original filename (without type extension)\n"
dialog.set_secondary_text(
"%filename - original filename (without type extension)\n"
"%show - show name from thetvdb\n"
"%epname - episode name from thetvdb\n"
"%season - season number\n"
"%episode - episode number\n"
"Both season number and episode number can be padded with zeros, just add width of padding"
"Both season number and episode number can be padded with zeros, just "
"add width of padding"
" right after %, like this: %2season.\n"
"Default pattern is \"%filename - %epname\", you might want to change this to"
" \"S%2seasonE%2episode - %epname\" or \"%show - S%2seasonE%2episode - %epname\""
);
"Default pattern is \"%filename - %epname\", you might want to change "
"this to"
" \"S%2seasonE%2episode - %epname\" or \"%show - S%2seasonE%2episode - "
"%epname\"" );
dialog.run();
}
void MainWindow::process() {
// check required fields are filled out
// check required field is filled out
if ( m_entry_show.get_text().empty() ) {
Gtk::MessageDialog dialog( *this, "Show field is empty" );
dialog.run();
@ -71,14 +76,17 @@ void MainWindow::process() {
}
// language code
language_code = (*m_combo_language.get_active())[m_columns_language.m_col_code];
language_code =
( *m_combo_language.get_active() )[m_columns_language.m_col_code];
// fill up m_combo_possible with possible tv shows
auto possible_shows = getPossibleShows( std::string(m_entry_show.get_text()), language_code, c );
auto possible_shows = getPossibleShows(
std::string( m_entry_show.get_text() ), language_code, c );
// if no possible shows were found, tell the user
if ( possible_shows.size() == 0 ) {
Gtk::MessageDialog dialog(*this, "No results found for given show name");
Gtk::MessageDialog dialog( *this,
"No results found for given show name" );
dialog.run();
return;
}
@ -107,7 +115,7 @@ void MainWindow::process() {
}
void MainWindow::getNames() {
// check required fields are filled out
// check required field is filled out
if ( m_entry_dir.get_text().empty() ) {
Gtk::MessageDialog dialog( *this, "Directory field is empty" );
dialog.run();
@ -137,7 +145,8 @@ void MainWindow::getNames() {
// create a window with possible seasons to rename
// store selected seasons in `selected`
sw = new SeasonWindow( options, selected );
sw->signal_hide().connect(sigc::mem_fun(*this, &MainWindow::finishedSelection));
sw->signal_hide().connect(
sigc::mem_fun( *this, &MainWindow::finishedSelection ) );
app->add_window( *sw );
sw->show();
@ -147,12 +156,17 @@ void MainWindow::getNames() {
* orig - original filenames
* renamed - renamed filenames (sorted in the same order as `orig`)
*/
void renameFiles(const std::vector<std::pair<std::string, std::pair<std::string, std::string>>> &renamed) {
for(auto renamed_it = renamed.begin(); renamed_it != renamed.end(); ++renamed_it) {
std::cout << renamed_it->first << "/" << renamed_it->second.first << " --> "
<< renamed_it->first << "/" << renamed_it->second.second << std::endl;
FSLib::rename(renamed_it->first + "/" + renamed_it->second.first
, renamed_it->first + "/" + renamed_it->second.second);
void renameFiles(
const std::vector<
std::pair< std::string, std::pair< std::string, std::string > > >
&renamed ) {
for ( auto renamed_it = renamed.begin(); renamed_it != renamed.end();
++renamed_it ) {
std::cout << renamed_it->first << "/" << renamed_it->second.first
<< " --> " << renamed_it->first << "/"
<< renamed_it->second.second << std::endl;
FSLib::rename( renamed_it->first + "/" + renamed_it->second.first,
renamed_it->first + "/" + renamed_it->second.second );
}
}
@ -164,9 +178,11 @@ void MainWindow::finishedSelection() {
auto iter = m_combo_possible.get_active();
// debug output
std::cout << (*iter)[m_columns_url.m_col_show] << " " << language_code << std::endl;
std::cout << ( *iter )[m_columns_url.m_col_show] << " " << language_code
<< std::endl;
std::string url = static_cast<Glib::ustring>((*iter)[m_columns_url.m_col_url]);
std::string url =
static_cast< Glib::ustring >( ( *iter )[m_columns_url.m_col_url] );
// shouldn't ever happen, but just to be sure
if ( url.empty() )
@ -176,6 +192,7 @@ void MainWindow::finishedSelection() {
std::string input_pattern = m_entry_pattern.get_text();
// store pattern to cache if it's different from default
if ( input_pattern != default_pattern ) {
std::ofstream file( userHome() + "/.cache/tv_rename_pattern" );
if ( file ) {
@ -185,9 +202,9 @@ void MainWindow::finishedSelection() {
for ( auto &x : selected ) {
// get renamed files for given season
auto renamed_files = getRenamedFiles( static_cast<Glib::ustring>((*iter)[m_columns_url.m_col_show]), x,
"https://www.thetvdb.com" + url,
language_code,
auto renamed_files = getRenamedFiles(
static_cast< Glib::ustring >( ( *iter )[m_columns_url.m_col_show] ),
x, "https://www.thetvdb.com" + url, language_code,
( input_pattern.empty() ? default_pattern : input_pattern ),
!m_check_linux.get_active(), c, files[x] );
@ -236,13 +253,15 @@ void MainWindow::finishedSelection() {
}
}
MainWindow::MainWindow(const Glib::RefPtr<Gtk::Application> &ptr) : app(ptr) {
MainWindow::MainWindow( const Glib::RefPtr< Gtk::Application > &ptr )
: app( ptr ) {
set_title( "TV Rename" );
set_default_size( 400, 310 );
set_resizable( false );
{
// if cached pattern exists, load that instead of default
std::ifstream file( userHome() + "/.cache/tv_rename_pattern" );
if ( file ) {
std::getline( file, default_pattern );
@ -251,9 +270,9 @@ MainWindow::MainWindow(const Glib::RefPtr<Gtk::Application> &ptr) : app(ptr) {
}
}
add( m_layout );
// set widgets' location
m_layout.put( m_label_show, 5, 5 );
m_layout.put( m_label_language, 190, 5 );
m_layout.put( m_entry_show, 5, 25 );
@ -307,10 +326,13 @@ MainWindow::MainWindow(const Glib::RefPtr<Gtk::Application> &ptr) : app(ptr) {
{
auto model = Gtk::ListStore::create( m_columns_language );
m_combo_language.set_model( model );
auto row = *( model->append() );
row[m_columns_language.m_col_code] = "en";
row[m_columns_language.m_col_language] = "English";
m_combo_language.set_active( row );
for ( size_t i = 2; i < languages.size(); i += 2 ) {
row = *( model->append() );
row[m_columns_language.m_col_code] = languages[i];
@ -323,13 +345,20 @@ MainWindow::MainWindow(const Glib::RefPtr<Gtk::Application> &ptr) : app(ptr) {
m_combo_possible.pack_start( m_columns_url.m_col_show );
// set signals
m_button_dir.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::chooseFile));
m_button_quit.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::quit));
m_button_process.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::process));
m_button_get_names.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::getNames));
m_entry_show.signal_activate().connect(sigc::mem_fun(*this, &MainWindow::process));
m_entry_dir.signal_activate().connect(sigc::mem_fun(*this, &MainWindow::process));
m_button_pattern.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::patternHelp));
m_button_dir.signal_clicked().connect(
sigc::mem_fun( *this, &MainWindow::chooseFile ) );
m_button_quit.signal_clicked().connect(
sigc::mem_fun( *this, &MainWindow::quit ) );
m_button_process.signal_clicked().connect(
sigc::mem_fun( *this, &MainWindow::process ) );
m_button_get_names.signal_clicked().connect(
sigc::mem_fun( *this, &MainWindow::getNames ) );
m_entry_show.signal_activate().connect(
sigc::mem_fun( *this, &MainWindow::process ) );
m_entry_dir.signal_activate().connect(
sigc::mem_fun( *this, &MainWindow::process ) );
m_button_pattern.signal_clicked().connect(
sigc::mem_fun( *this, &MainWindow::patternHelp ) );
// show everything except possible shows and items related to them
m_layout.show();
@ -349,4 +378,3 @@ MainWindow::MainWindow(const Glib::RefPtr<Gtk::Application> &ptr) : app(ptr) {
m_label_pattern.show();
m_label_dir.show();
}

View File

@ -5,8 +5,8 @@
#include <gtkmm/checkbutton.h>
#include <gtkmm/combobox.h>
#include <gtkmm/entry.h>
#include <gtkmm/layout.h>
#include <gtkmm/label.h>
#include <gtkmm/layout.h>
#include <gtkmm/window.h>
#include <set>
@ -55,7 +55,6 @@ protected:
class LanguageColumns : public Gtk::TreeModel::ColumnRecord {
public:
LanguageColumns() {
add( m_col_code );
add( m_col_language );
@ -88,4 +87,4 @@ protected:
std::string default_pattern;
};
#endif // GTKMM_EXAMPLE_HELLOWORLD_H
#endif // GTKMM_MAIN_WINDOW

View File

@ -1,8 +1,55 @@
#include <iostream>
#include "network.hpp"
size_t writeCallback( void *contents, size_t size, size_t nmemb, void *target ) {
*static_cast<std::string*>(target) += std::string(static_cast<char*>(contents), size*nmemb);
#ifdef _WIN32
// shamelessly stolen from http://www.cplusplus.com/forum/windows/109799/
Curl::Curl() {
connect = InternetOpen( L"WinInet/1.0", INTERNET_OPEN_TYPE_PRECONFIG,
nullptr, nullptr, 0 );
if ( !connect )
std::wcerr << "Something went wrong while connecting" << std::endl;
}
Curl::~Curl() {
if ( connect )
InternetCloseHandle( connect );
}
std::string Curl::execute( const std::wstring &url ) {
std::string source{};
source.reserve( 10000 );
HINTERNET openAddress = InternetOpenUrl(
connect, url.c_str(), nullptr, 0,
INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_KEEP_CONNECTION, 0 );
if ( !openAddress ) {
unsigned long errorNum = GetLastError();
std::wcout << "Failed to open URL" << std::endl
<< "Error No: " << errorNum << std::endl;
return "";
}
char dataReceived[4096];
unsigned long numberOfBytesRead = 0;
while ( InternetReadFile( openAddress, dataReceived, 4096,
&numberOfBytesRead ) &&
numberOfBytesRead ) {
source.append( dataReceived, numberOfBytesRead );
}
InternetCloseHandle( openAddress );
return source;
}
#else
size_t writeCallback( void *contents, size_t size, size_t nmemb,
void *target ) {
*static_cast< std::string * >( target ) +=
std::string( static_cast< char * >( contents ), size * nmemb );
return size * nmemb;
}
@ -21,12 +68,16 @@ Curl::~Curl() {
std::string Curl::execute( const std::string &url ) {
std::string source;
source.reserve( 100000 );
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, static_cast<void*>(&source));
curl_easy_setopt( curl_handle, CURLOPT_WRITEDATA,
static_cast< void * >( &source ) );
curl_easy_setopt( curl_handle, CURLOPT_URL, url.c_str() );
auto res = curl_easy_perform( curl_handle );
if ( res != CURLE_OK ) {
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror( res )
<< std::endl;
return "";
}
return source;
}
#endif

View File

@ -1,16 +1,34 @@
#ifndef NETWORK_HPP
#define NETWORK_HPP
#ifdef _WIN32
#include <Windows.h>
#include <WinInet.h>
using string = std::wstring;
#else
#include <curl/curl.h>
#include <string>
using string = std::string;
#endif
class Curl {
public:
Curl();
~Curl();
std::string execute( const std::string &url );
std::string execute( const string &url );
private:
#ifdef _WIN32
HINTERNET connect = nullptr;
#else
CURL *curl_handle;
#endif
};
#endif

50
resource.h Normal file
View File

@ -0,0 +1,50 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by tv_rename_gui.rc
//
#define IDC_MYICON 2
#define IDD_TV_RENAME_GUI_DIALOG 102
#define IDS_APP_TITLE 103
#define IDI_TV_RENAME_GUI 107
#define IDI_SMALL 108
#define IDC_TV_RENAME_GUI 109
#define IDR_MAINFRAME 128
#define IDD_MAIN 129
#define IDD_SEASONS 130
#define IDD_HELP 131
#define IDD_DIALOG1 132
#define IDD_PREVIEW 132
#define IDC_COMBO1 1000
#define IDLANG 1000
#define IDOK_MAIN 1001
#define ID 1002
#define IDEND 1002
#define IDALL 1003
#define IDC_BUTTON2 1004
#define IDNONE 1004
#define IDDIRB 1004
#define IDSHOWIN 1006
#define ID_STATIC 1007
#define IDLANGUAGE 1009
#define IDDIR 1010
#define IDPATTERN 1011
#define IDPROCESS 1013
#define IDTRUST 1015
#define IDSHOWS 1016
#define IDSEASONS 1017
#define IDC_STATIC6 1019
#define ID_STATIC6 1019
#define IDTEXT 1020
#define IDC_STATIC -1
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 133
#define _APS_NEXT_COMMAND_VALUE 32771
#define _APS_NEXT_CONTROL_VALUE 1021
#define _APS_NEXT_SYMED_VALUE 110
#endif
#endif

View File

@ -27,7 +27,9 @@ void SeasonWindow::select_none() {
}
}
SeasonWindow::SeasonWindow(const std::vector<int> &seasons, std::vector<int> &_returned) : returned(_returned) {
SeasonWindow::SeasonWindow( const std::vector< int > &seasons,
std::vector< int > &_returned )
: returned( _returned ) {
set_title( "Choose seasons" );
set_default_size( 250, 250 );
@ -65,9 +67,12 @@ SeasonWindow::SeasonWindow(const std::vector<int> &seasons, std::vector<int> &_r
m_all.set_label( "Select All" );
m_none.set_label( "Unselect All" );
m_confirm.signal_clicked().connect(sigc::mem_fun(*this, &SeasonWindow::confirm));
m_all.signal_clicked().connect(sigc::mem_fun(*this, &SeasonWindow::select_all));
m_none.signal_clicked().connect(sigc::mem_fun(*this, &SeasonWindow::select_none));
m_confirm.signal_clicked().connect(
sigc::mem_fun( *this, &SeasonWindow::confirm ) );
m_all.signal_clicked().connect(
sigc::mem_fun( *this, &SeasonWindow::select_all ) );
m_none.signal_clicked().connect(
sigc::mem_fun( *this, &SeasonWindow::select_none ) );
show_all_children();
}

View File

@ -3,13 +3,14 @@
#include <gtkmm/button.h>
#include <gtkmm/checkbutton.h>
#include <gtkmm/layout.h>
#include <gtkmm/label.h>
#include <gtkmm/layout.h>
#include <gtkmm/window.h>
class SeasonWindow : public Gtk::Window {
public:
SeasonWindow(const std::vector<int> &seasons, std::vector<int> &_returned);
SeasonWindow( const std::vector< int > &seasons,
std::vector< int > &_returned );
virtual ~SeasonWindow() = default;
private:

BIN
small.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,6 +1,15 @@
#include <algorithm>
#include "functions.hpp"
#include "tv_rename.hpp"
#include <map>
#ifdef _WIN32
#include <codecvt>
#include <fcntl.h>
#include <io.h>
#include <locale>
#include <windows.h>
#endif
#ifndef GUI
@ -11,22 +20,51 @@
#endif
std::vector<std::string> parseEpisodeNames( const std::string &season_code, const std::string &language) {
std::vector<std::string> episodes;
#include "functions.hpp"
#include "tv_rename.hpp"
#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
// get names for all episodes for a given season
std::vector< string > parseEpisodeNames( const string &season_code,
const string &language ) {
std::vector< string > episodes;
size_t pos = 0;
while ( true ) {
pos = season_code.find("ge=\"" + language + "\" ", pos);
if( pos != std::string::npos ) {
if( language == "en" )
pos =
season_code.find( TEXT( "ge=\"" ) + language + TEXT( "\" " ), pos );
if ( pos != string::npos ) {
if ( language == TEXT( "en" ) )
pos += 17;
else
pos += 29;
while( isspace(season_code[pos]) )
while ( iswspace( season_code[pos] ) )
pos++;
auto end = season_code.find("<", pos);
auto end = season_code.find( TEXT( "<" ), pos );
end--;
while( isspace(season_code[end]) )
while ( iswspace( season_code[end] ) )
end--;
end++;
episodes.push_back( season_code.substr( pos, end - pos ) );
} else {
@ -36,20 +74,38 @@ std::vector<std::string> parseEpisodeNames( const std::string &season_code, cons
return episodes;
}
std::vector<std::pair<std::string, std::pair<std::string, std::string>>> getRenamedFiles( const std::string &show, int season, std::string url, const std::string &language, const std::string &pattern, const bool &linux, Curl &c, const std::set<std::string> &files ) {
url += "/seasons/" + std::to_string(season);
std::vector< std::pair< string, std::pair< string, string > > >
getRenamedFiles( const string &show, int season, string url,
const string &language, const string &pattern,
const bool &linux, Curl &c, const std::set< string > &files ) {
#ifdef _WIN32
auto season_num = std::to_wstring( season );
#else
auto season_num = std::to_string( season );
#endif
url += TEXT( "/seasons/" );
url += season_num;
// 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());
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 ) {
if ( pos != string::npos ) {
season_code = season_code.substr( pos );
pos = season_code.find( "</table>" );
if( pos != std::string::npos )
if ( pos != string::npos )
season_code = season_code.substr( 0, pos );
else
return {};
@ -57,39 +113,49 @@ std::vector<std::pair<std::string, std::pair<std::string, std::string>>> getRena
return {};
}
#ifdef _WIN32
auto episodes =
parseEpisodeNames( utf8_to_wstring( season_code ), language );
#else
auto episodes = parseEpisodeNames( season_code, language );
#endif
if ( episodes.empty() )
return {};
std::vector<std::pair<std::string, std::pair<std::string, std::string>>> renamed_files;
if ( files.empty() )
return {};
std::vector< std::pair< string, std::pair< string, string > > >
renamed_files;
for ( const auto &x : files ) {
auto last = x.find_last_of("/");
std::string og_name;
std::string dir;
if( last == std::string::npos ) {
auto last = x.find_last_of( dir_divider );
string og_name;
string dir;
if ( last == string::npos ) {
og_name = x;
dir = ".";
dir = TEXT( "." );
} else {
og_name = x.substr( last + 1 );
dir = x.substr( 0, last );
}
unsigned long num;
// get file's episode number
if( searchSpecificSeason(x.c_str(), pos, std::to_string(season)) ) {
num = atoi(x.c_str()+pos);
if ( searchSpecificSeason( x.c_str(), pos, season_num ) ) {
num = std::stoi( x.c_str() + pos );
} else {
continue;
}
num -= 1;
if ( num < episodes.size() ) {
auto pos = og_name.find_last_of('.');
auto pos = og_name.find_last_of( TEXT( "." ) );
// get desired filename
auto name = compilePattern(pattern, season, num+1, og_name.substr(0, pos), episodes[num], show) + og_name.substr(pos);
auto name = compilePattern( pattern, season, num + 1,
og_name.substr( 0, pos ), episodes[num],
show ) +
og_name.substr( pos );
// replace '/' with '|'
for ( size_t i = 0; i < name.size(); i++ ) {
if ( name[i] == '/' ) {
@ -98,18 +164,23 @@ std::vector<std::pair<std::string, std::pair<std::string, std::string>>> getRena
}
// replace characters illegal in windows if desired
if ( !linux ) {
name.erase(std::remove_if(name.begin(), name.end(), [](char x) {return x == '?' || x == '"' || x == '\\' || x == '*';}), name.end());
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[i] = 'i';
name.insert(i+1, "s less than");
name.insert( i + 1, TEXT( "s less than" ) );
max += 11;
} else if ( name[i] == '>' ) {
name[i] = 'i';
name.insert(i+1, "s more than");
name.insert( i + 1, TEXT( "s more than" ) );
max += 11;
} else if ( name[i] == ':' ) {
name[i] = ' ';
@ -125,7 +196,8 @@ std::vector<std::pair<std::string, std::pair<std::string, std::string>>> getRena
}
}
}
renamed_files.emplace_back(dir, std::pair<std::string, std::string>(og_name, name));
renamed_files.emplace_back(
dir, std::pair< string, string >( og_name, name ) );
}
}
return renamed_files;
@ -133,36 +205,45 @@ std::vector<std::pair<std::string, std::pair<std::string, std::string>>> getRena
#ifndef GUI
void singleSeason( const std::string &path, std::string &show, int season, std::string url, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c, std::set<std::string> const *files_ptr) {
void singleSeason( const string &path, string &show, int season, string url,
const string &language, const string &pattern,
const bool &linux, const bool &trust, Curl &c,
std::set< string > const *files_ptr ) {
if ( url.empty() )
url = getDefUrl( show, language, c );
std::set<std::string> *found_files = nullptr;
std::set< string > *found_files = nullptr;
if ( files_ptr == nullptr ) {
found_files = new std::set<std::string>;
found_files = new std::set< string >;
findSeason( *found_files, season, path );
files_ptr = found_files;
}
auto renamed_files = getRenamedFiles(show, season, std::move(url), language, pattern, linux, c, *files_ptr);
auto renamed_files =
getRenamedFiles( show, season, std::move( url ), language, pattern,
linux, c, *files_ptr );
for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) {
std::cout << renamed->second.first << " --> " << renamed->second.second << std::endl;
for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end();
++renamed ) {
cout << renamed->second.first << " --> " << renamed->second.second
<< std::endl;
}
if ( !trust ) {
std::cout << "Does this seem ok? (y/n) ";
std::string response;
std::cin >> response;
std::cin.clear();
std::cin.ignore(1, '\n');
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;
}
for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) {
FSLib::rename(renamed->first + "/" + renamed->second.first, renamed->first + "/" + renamed->second.second);
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 );
}
if ( found_files != nullptr ) {
@ -170,25 +251,34 @@ void singleSeason( const std::string &path, std::string &show, int season, std::
}
}
void multipleSeasons( const std::string &path, std::string &show, const std::map<int, std::set<std::string>> &seasons, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c) {
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, Curl &c ) {
auto url = getDefUrl( show, language, c );
for ( const auto &x : seasons ) {
singleSeason( path, show, x.first, url, language, pattern, linux, trust, c, &x.second);
singleSeason( path, show, x.first, url, language, pattern, linux, trust,
c, &x.second );
}
}
void multipleSeasons( const std::string &path, std::string &show, const std::set<int> seasons, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c) {
std::map<int, std::set<std::string>> season_map;
void multipleSeasons( const string &path, string &show,
const std::set< int > seasons, const string &language,
const string &pattern, const bool &linux,
const bool &trust, Curl &c ) {
std::map< int, std::set< string > > season_map;
findSeasons( season_map, path, seasons );
multipleSeasons(path, show, season_map, language, pattern, linux, trust, c);
multipleSeasons( path, show, season_map, language, pattern, linux, trust,
c );
}
void allSeasons( const std::string &path, std::string &show, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c) {
std::map<int, std::set<std::string>> seasons;
void allSeasons( const string &path, string &show, const string &language,
const string &pattern, const bool &linux, const bool &trust,
Curl &c ) {
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, c );
}
#endif

View File

@ -2,19 +2,45 @@
#define TV_RENAME_HPP
#include <set>
#include "network.hpp"
#ifdef GUI
#include <vector>
#endif
std::vector<std::pair<std::string, std::pair<std::string, std::string>>> getRenamedFiles( const std::string &show, int season, std::string url, const std::string &language, const std::string &pattern, const bool &linux, Curl &c, const std::set<std::string> &files );
#include "network.hpp"
#ifdef _WIN32
using string = std::wstring;
using char_t = wchar_t;
#else
void singleSeason( const std::string &path, std::string &show, int season, std::string url, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c, std::set<std::string> const *files=nullptr);
void multipleSeasons( const std::string &path, std::string &show, const std::set<int> seasons, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c);
void allSeasons( const std::string &path, std::string &show, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c);
using string = std::string;
using char_t = char;
#endif
#ifdef GUI
std::vector< std::pair< string, std::pair< string, string > > >
getRenamedFiles( const string &show, int season, string url,
const string &language, const string &pattern,
const bool &linux, Curl &c, const std::set< string > &files );
#else
void singleSeason( const string &path, string &show, int season, string url,
const string &language, const string &pattern,
const bool &linux, const bool &trust, Curl &c,
std::set< string > const *files = nullptr );
void multipleSeasons( const string &path, string &show,
const std::set< int > seasons, const string &language,
const string &pattern, const bool &linux,
const bool &trust, Curl &c );
void allSeasons( const string &path, string &show, const string &language,
const string &pattern, const bool &linux, const bool &trust,
Curl &c );
#endif

360
tv_rename_gui.cpp Normal file
View File

@ -0,0 +1,360 @@
// many thanks to https://www.winprog.org/tutorial from where I got a lot of
// code for this
#include <Windows.h>
#include <array>
#include <fstream>
#include <shlobj.h>
#include <string>
#include <vector>
#include "filesystem.hpp"
#include "functions.hpp"
#include "resource.h"
#include "tv_rename.hpp"
constexpr std::array< const wchar_t *, 46 > languages{
L"en", L"English", L"sv", L"Svenska", L"no", L"Norsk",
L"da", L"Dansk", L"fi", L"Suomeksi", L"nl", L"Nederlands",
L"de", L"Deutsch", L"it", L"Italiano", L"es", L"Español",
L"fr", L"Français", L"pl", L"Polski", L"hu", L"Magyar",
L"el", L"Greek", L"tr", L"Turkish", L"ru", L"Russian",
L"he", L"Hebrew", L"ja", L"Japanese", L"pt", L"Portuguese",
L"zh", L"Chinese", L"cs", L"Czech", L"sl", L"Slovenian",
L"hr", L"Croatian", L"ko", L"Korea"
};
std::vector< int > options;
std::vector< int > checkboxes;
std::vector< int > checked;
Curl c;
wchar_t lang_code[3] = L"en";
std::wstring default_pattern = L"%filename - %epname";
std::map< int, std::set< string > > files;
std::vector< std::pair< string, string > > possible_shows;
std::vector< std::pair< string, std::pair< string, string > > >
*current_renamed_files{ nullptr };
BOOL CALLBACK HelpBox( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) {
switch ( message ) {
case WM_COMMAND:
switch ( LOWORD( wParam ) ) {
case IDOK:
EndDialog( hwnd, 0 );
default:
break;
}
break;
case WM_CLOSE:
EndDialog( hwnd, 0 );
break;
default:
return FALSE;
}
return TRUE;
}
BOOL CALLBACK SeasonsBox( HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam ) {
switch ( message ) {
case WM_INITDIALOG: {
checked.clear();
checkboxes.clear();
int left{ 15 }, top{ 30 };
for ( int i = 0; i < options.size(); i++ ) {
if ( checkboxes.empty() ) {
checkboxes.push_back( 2000 );
} else {
checkboxes.push_back( checkboxes.back() + 1 );
}
auto hCheckBox = CreateWindowEx(
0, L"Button", std::to_wstring( options[i] ).c_str(),
WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, left, top, 60, 20,
hwnd, ( HMENU )checkboxes.back(), GetModuleHandle( NULL ),
NULL );
if ( hCheckBox == NULL ) {
MessageBox( hwnd, L"Could not create checkbox", L"Error",
MB_OK | MB_ICONERROR );
return TRUE;
}
auto hfDefault = GetStockObject( DEFAULT_GUI_FONT );
SendMessage( hCheckBox, WM_SETFONT, ( WPARAM )hfDefault,
MAKELPARAM( FALSE, 0 ) );
if ( left == 195 ) {
left = 15;
top += 20;
} else {
left += 60;
}
}
}
return TRUE;
case WM_COMMAND:
switch ( LOWORD( wParam ) ) {
case IDOK:
for ( int i = 0; i < checkboxes.size(); i++ ) {
if ( SendDlgItemMessage( hwnd, checkboxes[i], BM_GETCHECK, 0,
0 ) )
checked.push_back( options[i] );
}
EndDialog( hwnd, 0 );
break;
case IDALL:
for ( auto &x : checkboxes ) {
SendDlgItemMessage( hwnd, x, BM_SETCHECK, BST_CHECKED, 0 );
}
break;
case IDNONE:
for ( auto &x : checkboxes ) {
SendDlgItemMessage( hwnd, x, BM_SETCHECK, BST_UNCHECKED, 0 );
}
break;
default:
break;
}
break;
case WM_CLOSE:
EndDialog( hwnd, 0 );
break;
default:
return FALSE;
}
return TRUE;
}
// stolen from
// https://www.codeproject.com/Articles/2604/Browse-Folder-dialog-search-folder-and-all-sub-fol
std::wstring getDir() {
wchar_t path[MAX_PATH];
BROWSEINFO bi{};
bi.lpszTitle = L"Choose a folder";
LPITEMIDLIST pidl = SHBrowseForFolder( &bi );
if ( pidl != 0 ) {
SHGetPathFromIDList( pidl, path );
IMalloc *imalloc = 0;
if ( SUCCEEDED( SHGetMalloc( &imalloc ) ) ) {
imalloc->Free( pidl );
imalloc->Release();
}
} else {
path[0] = '\0';
}
return path;
}
void renameFiles(
const std::vector< std::pair< string, std::pair< string, string > > >
&renamed ) {
for ( auto renamed_it = renamed.begin(); renamed_it != renamed.end();
++renamed_it ) {
FSLib::rename( renamed_it->first + L"\\" + renamed_it->second.first,
renamed_it->first + L"\\" + renamed_it->second.second );
}
}
BOOL CALLBACK PreviewBox( HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam ) {
switch ( message ) {
case WM_INITDIALOG: {
std::wstring text{};
for ( auto renamed_it = current_renamed_files->begin();
renamed_it != current_renamed_files->end(); ++renamed_it ) {
text += renamed_it->second.first + L" --> " +
renamed_it->second.second + L"\r\n";
}
SetDlgItemText( hwnd, IDTEXT, text.c_str() );
}
return TRUE;
case WM_COMMAND:
switch ( LOWORD( wParam ) ) {
case IDYES: {
renameFiles( *current_renamed_files );
EndDialog( hwnd, IDYES );
} break;
case IDNO:
EndDialog( hwnd, IDNO );
default:
break;
}
break;
case WM_CLOSE:
EndDialog( hwnd, 0 );
break;
default:
return FALSE;
}
return TRUE;
}
void finishedSelection( HWND hwnd ) {
auto index = SendDlgItemMessage( hwnd, IDSHOWS, CB_GETCURSEL, 0, 0 );
auto url = possible_shows[index].second;
auto show = possible_shows[index].first;
wchar_t input_pattern[100];
GetDlgItemText( hwnd, IDPATTERN, input_pattern, 100 );
if ( input_pattern != default_pattern ) {
std::wofstream file( userHome() + L"\\tv_rename\\pattern" );
if ( file ) {
file << input_pattern;
}
}
for ( auto &x : checked ) {
auto renamed_files = getRenamedFiles(
show, x, L"https://www.thetvdb.com" + url, lang_code,
( input_pattern[0] == '\0' ? default_pattern : input_pattern ),
false, c, files[x] );
if ( renamed_files.empty() )
continue;
if ( SendDlgItemMessage( hwnd, IDTRUST, BM_GETCHECK, 0, 0 ) ) {
renameFiles( renamed_files );
continue;
}
current_renamed_files = &renamed_files;
DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_PREVIEW ),
hwnd, PreviewBox );
}
if ( SendDlgItemMessage( hwnd, IDTRUST, BM_GETCHECK, 0, 0 ) ) {
MessageBox( hwnd, L"Finished renaming files", L"Info",
MB_OK | MB_ICONINFORMATION );
}
}
void getNames( HWND hwnd ) {
wchar_t path[MAX_PATH];
GetDlgItemText( hwnd, IDDIR, path, MAX_PATH );
if ( wcslen( path ) == 0 ) {
MessageBox( hwnd, L"Folder field is empty", L"Error",
MB_OK | MB_ICONERROR );
return;
}
if ( !FSLib::isDirectory( path ) ) {
MessageBox( hwnd, L"Folder doesn't exist", L"Error",
MB_OK | MB_ICONERROR );
return;
}
checked.clear();
checkboxes.clear();
options.clear();
files.clear();
iterateFS( files, path );
for ( auto &x : files ) {
options.push_back( x.first );
}
DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_SEASONS ), hwnd,
SeasonsBox );
finishedSelection( hwnd );
}
void process( HWND hwnd ) {
wchar_t show[100];
GetDlgItemText( hwnd, IDSHOWIN, show, 100 );
if ( wcslen( show ) == 0 ) {
MessageBox( hwnd, L"Show field is empty", L"Error",
MB_OK | MB_ICONERROR );
return;
}
wchar_t language[20];
GetDlgItemText( hwnd, IDLANG, language, 20 );
for ( int i = 1; i < languages.size(); i += 2 ) {
if ( !wcscmp( languages[i], language ) ) {
wcscpy_s( lang_code, languages[i - 1] );
break;
}
}
possible_shows = getPossibleShows( show, lang_code, c );
if ( possible_shows.size() == 0 ) {
MessageBox( hwnd, L"No results found for given show name", L"Error",
MB_OK | MB_ICONERROR );
return;
}
ShowWindow( GetDlgItem( hwnd, ID_STATIC6 ), SW_SHOW );
ShowWindow( GetDlgItem( hwnd, IDSHOWS ), SW_SHOW );
ShowWindow( GetDlgItem( hwnd, IDSEASONS ), SW_SHOW );
SendDlgItemMessage( hwnd, IDSHOWS, CB_RESETCONTENT, 0, 0 );
for ( int i = 0; i < possible_shows.size(); i++ ) {
SendDlgItemMessage( hwnd, IDSHOWS, CB_ADDSTRING, 0,
( LPARAM )possible_shows[i].first.c_str() );
}
SendDlgItemMessage( hwnd, IDSHOWS, CB_SETCURSEL, 0, 0 );
}
void readDefaultPattern( const string &base_dir ) {
std::wifstream file( base_dir + L"\\pattern" );
if ( file ) {
std::getline( file, default_pattern );
}
}
BOOL CALLBACK MainBox( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) {
switch ( message ) {
case WM_INITDIALOG: {
for ( int i = 1; i < languages.size(); i += 2 ) {
SendDlgItemMessage( hwnd, IDLANG, CB_ADDSTRING, 0,
( LPARAM )languages[i] );
}
SendDlgItemMessage( hwnd, IDLANG, CB_SETCURSEL, 5, 0 );
auto appdata = userHome() + L"\\tv_rename";
if ( !FSLib::isDirectory( appdata ) ) {
if ( CreateDirectory( appdata.c_str(), NULL ) ) {
readDefaultPattern( appdata );
}
} else {
readDefaultPattern( appdata );
}
SetDlgItemText( hwnd, IDPATTERN, default_pattern.c_str() );
}
return TRUE;
case WM_COMMAND:
switch ( LOWORD( wParam ) ) {
case IDPROCESS: {
process( hwnd );
} break;
case IDEND:
EndDialog( hwnd, IDOK_MAIN );
break;
case IDSEASONS:
getNames( hwnd );
break;
case IDDIRB:
SetDlgItemText( hwnd, IDDIR, getDir().c_str() );
break;
case IDHELP:
DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_HELP ),
hwnd, HelpBox );
break;
default:
break;
}
break;
case WM_CLOSE:
EndDialog( hwnd, 0 );
break;
default:
return FALSE;
}
return TRUE;
}
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow ) {
return DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_MAIN ),
NULL, MainBox );
}

BIN
tv_rename_gui.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
tv_rename_gui.rc Normal file

Binary file not shown.