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 .PHONY: clean
clean: clean:
rm -f *.o tv_rename rm -f *.o tv_rename tv_rename_gui
.PHONY: install .PHONY: install
install: tv_rename install: tv_rename
@ -50,3 +50,18 @@ functions_gui.o: functions.cpp
tv_rename_gui.o: tv_rename.cpp tv_rename_gui.o: tv_rename.cpp
$(CXX) $(CFLAGS) -c tv_rename.cpp -o tv_rename_gui.o -DGUI $(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 <limits.h>
#include <sys/stat.h>
#include <stdlib.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) { #include "filesystem.hpp"
struct stat path_stat;
#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; return stat( path.c_str(), &path_stat ) == 0;
#endif
} }
std::string FSLib::canonical(const std::string &path) { string FSLib::canonical( const string &path ) {
char *canonical_path = static_cast<char *>(malloc(PATH_MAX));
if( realpath(path.c_str(), canonical_path) == nullptr ) { #ifdef _WIN32
free(canonical_path); auto PATH_MAX = MAX_PATH;
return ""; #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; 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 #endif
bool FSLib::isDirectory(const std::string &path) { return S_ISDIR( path_stat.st_mode );
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; return ::rename( file_a.c_str(), file_b.c_str() ) == 0;
#endif
} }
FSLib::Directory::iterator FSLib::Directory::begin() { FSLib::Directory::iterator FSLib::Directory::begin() {
return Iterator(dir_path); return Iterator( *this );
} }
FSLib::Directory::const_iterator FSLib::Directory::begin() const { FSLib::Directory::const_iterator FSLib::Directory::begin() const {
return Iterator(dir_path); return Iterator( *this );
} }
FSLib::Directory::const_iterator FSLib::Directory::cbegin() const { FSLib::Directory::const_iterator FSLib::Directory::cbegin() const {
@ -46,44 +93,82 @@ FSLib::Directory::const_iterator FSLib::Directory::cbegin() const {
} }
FSLib::Directory::iterator FSLib::Directory::end() { 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 { 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 { FSLib::Directory::const_iterator FSLib::Directory::cend() const {
return end(); return end();
} }
const char *FSLib::Directory::path() const { const char_t *FSLib::Directory::path() const {
return dir_path.c_str(); 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; return current_entry->d_name;
#endif
} }
#ifdef _WIN32
FSLib::Directory::Iterator &FSLib::Directory::Iterator::operator++() { FSLib::Directory::Iterator &FSLib::Directory::Iterator::operator++() {
if( current_entry == nullptr ) if ( ended == true )
return *this; return *this;
current_entry = readdir(d); // skip . and ..
if( current_entry != nullptr && ( !strcmp(current_entry->d_name, ".") || !strcmp(current_entry->d_name, "..") ) ) 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 );
// skip . and ..
if ( current_entry != nullptr &&
( !strcmp( current_entry->d_name, "." ) ||
!strcmp( current_entry->d_name, ".." ) ) )
return operator++(); return operator++();
return *this; return *this;
} }
FSLib::Directory::Iterator FSLib::Directory::Iterator::operator++(int) { #endif
Iterator ret(*this);
FSLib::Directory::Iterator FSLib::Directory::Iterator::operator++( int ) {
Iterator ret( *this );
operator++(); operator++();
return ret; return ret;
} }
bool FSLib::Directory::Iterator::operator==(const Iterator &i_other) const { 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; return i_other.current_entry == current_entry;
#endif
} }
bool FSLib::Directory::Iterator::operator!=(const Iterator &i_other) const { bool FSLib::Directory::Iterator::operator!=( const Iterator &i_other ) const {
return i_other.current_entry != current_entry; return !( i_other == *this );
} }

View File

@ -1,58 +1,122 @@
#ifndef FSLIB_H #ifndef FSLIB_H
#define FSLIB_H #define FSLIB_H
#include <string>
#include <dirent.h>
#include <string.h> #include <string.h>
#include <string>
#ifdef _WIN32
#include <Windows.h>
#else
#include <dirent.h>
namespace FSLib{
#ifndef GUI
bool exists(const std::string &path);
std::string canonical(const std::string &path);
#endif #endif
bool isDirectory(const std::string &path);
bool rename(const std::string &file_a, const std::string &file_b);
class Directory { // set apropriate data types for each operating system
public: #ifdef _WIN32
Directory(const std::string &path_) : dir_path(path_) {}
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 string &path );
string canonical( const string &path );
#endif
bool isDirectory( const string &path );
bool rename( const string &file_a, const string &file_b );
class Directory {
public:
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() = delete;
Directory(const Directory &d) = default; Directory( const Directory &d ) = default;
Directory(Directory &&d) = default; Directory( Directory &&d ) = default;
class Iterator { class Iterator {
public: public:
Iterator( const Directory &d_ ) : d(opendir(d_.path())) { #ifdef _WIN32
current_entry = readdir(d); Iterator( const Directory &d_ ) {
if( current_entry != nullptr && ( !strcmp(current_entry->d_name, ".") || !strcmp(current_entry->d_name, "..") ) ) hFind = FindFirstFileW( d_.path(), &data );
++(*this); 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, ".." ) ) )
++( *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; Iterator() = delete;
Iterator(const Iterator &i) = default; Iterator( const Iterator &i ) = default;
Iterator(Iterator &&i) = default; Iterator( Iterator &&i ) = default;
~Iterator() { char_t const *operator*() const;
closedir(d);
}
char const *operator*() const;
Iterator &operator++(); Iterator &operator++();
Iterator operator++(int); Iterator operator++( int );
bool operator==(const Iterator &i_other) const; bool operator==( const Iterator &i_other ) const;
bool operator!=(const Iterator &i_other) const; bool operator!=( const Iterator &i_other ) const;
private: private:
#ifndef _WIN32
DIR *d; DIR *d;
const struct dirent *current_entry; const struct dirent *current_entry;
#else
HANDLE hFind;
WIN32_FIND_DATA data;
bool ended{ false };
#endif
}; };
using iterator = Iterator; using iterator = Iterator;
@ -70,11 +134,11 @@ namespace FSLib{
const_iterator cend() const; const_iterator cend() const;
const char *path() const; const char_t *path() const;
private: private:
std::string dir_path; string dir_path;
}; };
} //end FSLib } // namespace FSLib
#endif #endif

View File

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

View File

@ -1,10 +1,10 @@
#ifndef GETPAGE_H #ifndef TV_FUNCTIONS_H
#define GETPAGE_H #define TV_FUNCTIONS_H
#include <string> #include <iostream>
#include <set>
#include <map> #include <map>
#include "network.hpp" #include <set>
#include <string>
#ifdef GUI #ifdef GUI
@ -12,34 +12,62 @@
#endif #endif
#ifndef GUI #include "network.hpp"
std::string getDefUrl( std::string &show, const std::string &language, Curl &c ); #ifdef _WIN32
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();
bool searchSpecificSeason(const char *const p, const std::string &number); using string = std::wstring;
bool searchSeason(const char *const p, size_t &season_pos); using char_t = wchar_t;
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 );
#else #else
std::vector<std::pair<std::string, std::string>> getPossibleShows( std::string show, const std::string &language, Curl &c ); using string = std::string;
std::string userHome(); using char_t = char;
#endif #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::wstring utf8_to_wstring( const std::string &utf8 );
std::string compilePattern(const std::string &pattern, int season, int episode, const std::string &filename, const std::string &episodeName, const std::string &showName); #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 #endif

View File

@ -2,10 +2,11 @@
#include "mainwindow.hpp" #include "mainwindow.hpp"
int main(int argc, char **argv) { 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); MainWindow mw( app );
return app->run(mw); return app->run( mw );
} }

258
main.cpp
View File

@ -1,40 +1,63 @@
#include <getopt.h>
#include <iostream> #include <iostream>
#include <set>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#include <windows.h>
#else
#include <getopt.h>
#endif
#include "filesystem.hpp" #include "filesystem.hpp"
#include "functions.hpp" #include "functions.hpp"
#include "tv_rename.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
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' },
{"name-pattern", required_argument, 0, '1' },
{"help", no_argument, 0, 'h' }
};
while(1) { using char_t = wchar_t;
int option_index{0}; using string = std::wstring;
auto c = getopt_long(argc, argv, "s:n:cp:txl:01:h", long_options, &option_index);
if( c == -1 ) #define cerr std::wcerr
break; #define cout std::wcout
switch(c) { #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': case 's':
show = optarg; show = optional;
i++;
break; break;
case 'n': case 'n':
parseSeasonNumbers(seasons_num, optarg); parseSeasonNumbers( seasons_num, optional );
i++;
break; break;
case 'c': case 'c':
change_dir = false; change_dir = false;
break; break;
case 'p': case 'p':
path = std::string(optarg); path = string( optional );
i++;
// if path provided, assume it's correct // if path provided, assume it's correct
change_dir = false; change_dir = false;
break; break;
@ -45,13 +68,14 @@ int parseCommandLine(std::string &show, std::set<int> &seasons_num, std::string
linux = true; linux = true;
break; break;
case 'l': case 'l':
if( findLanguage(optarg) ) { if ( findLanguage( optional ) ) {
language = optarg; language = optional;
} else { } else {
std::cerr << "Invalid language choice" << std::endl; cerr << "Invalid language choice" << std::endl;
printLangs(); printLangs();
return -1; return -1;
} }
i++;
break; break;
case '0': case '0':
printLangs(); printLangs();
@ -60,78 +84,178 @@ int parseCommandLine(std::string &show, std::set<int> &seasons_num, std::string
printHelp(); printHelp();
return 1; return 1;
case '1': case '1':
pattern = optarg; pattern = optional;
i++;
break; break;
default: default:
return -1; 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' },
{ "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' },
{ "name-pattern", required_argument, 0, '1' },
{ "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 );
if ( c == -1 )
break;
auto res = handleArgument( c, show, seasons_num, change_dir, path,
trust, linux, language, pattern, optarg, i );
if ( res != 0 )
return res;
} }
return 0; return 0;
} }
int main(int argc, char** argv) { #endif
std::string show{};
std::set<int> seasons_num; // windows needs wmain for unicode support
std::string path{"."}; #ifdef _WIN32
bool change_dir{true}; int wmain
std::string language{"en"}; #else
bool linux{false}; int main
bool trust{false}; #endif
std::string pattern{"%filename - %epname"}; ( 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 };
bool linux{ false };
bool trust{ false };
string path{ TEXT( "." ) };
string language{ TEXT( "en" ) };
string pattern{ TEXT( "%filename - %epname" ) };
Curl c; Curl c;
{ {
auto tmp = parseCommandLine(show, seasons_num, path, change_dir, language, pattern, linux, trust, argc, argv); auto tmp =
if( tmp == -1 ) parseCommandLine( show, seasons_num, path, change_dir, language,
pattern, linux, trust, argc, argv );
if ( tmp == -1 )
return 1; return 1;
else if ( tmp == 1 ) else if ( tmp == 1 )
return 0; return 0;
} }
if( !FSLib::isDirectory(path) ) if ( !FSLib::isDirectory( path ) )
change_dir = true; change_dir = true;
while( change_dir ) { while ( change_dir ) {
if( !FSLib::isDirectory(path) ) { if ( !FSLib::isDirectory( path ) ) {
std::cout << "This directory doesn't exist, please insert a correct path: " << std::endl; cout << "This directory doesn't exist, please insert a correct "
std::getline(std::cin, path); "path: "
<< std::endl;
std::getline( cin, path );
continue; continue;
} }
std::cout << "Is this the right directory? " << FSLib::canonical(path) << std::endl; cout << "Is this the right directory? " << FSLib::canonical( path )
std::string response; << std::endl;
std::cin >> response; string response;
std::cin.ignore(1,'\n'); cin >> response;
std::cin.clear(); cin.ignore( 1, '\n' );
cin.clear();
if ( response[0] == 'y' || response[0] == 'Y' ) { if ( response[0] == 'y' || response[0] == 'Y' ) {
change_dir = false; change_dir = false;
} else { } else {
std::cout << "Insert correct path:" << std::endl; cout << "Insert correct path:" << std::endl;
std::getline(std::cin, path); std::getline( cin, path );
} }
} }
if( show.empty() ) { if ( show.empty() ) {
auto pos = show.find_last_of('/'); auto pos = show.find_last_of( '/' );
if( pos != std::string::npos ) if ( pos != string::npos )
show = show.substr(++pos); show = show.substr( ++pos );
std::cout << "Is this the right show name? " << show << std::endl; cout << "Is this the right show name? " << show << std::endl;
std::string response; string response;
std::cin >> response; cin >> response;
std::cin.ignore(1, '\n'); cin.ignore( 1, '\n' );
std::cin.clear(); cin.clear();
if( response[0] != 'y' && response[0] != 'Y' ) { if ( response[0] != 'y' && response[0] != 'Y' ) {
std::cout << "Insert the correct show name: " << std::endl; cout << "Insert the correct show name: " << std::endl;
std::getline(std::cin, show); std::getline( cin, show );
} }
} }
if( seasons_num.size() == 1 ) { 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 ) { } 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 { } else {
allSeasons(path, show, language, pattern, linux, trust, c); allSeasons( path, show, language, pattern, linux, trust, c );
} }
} }

View File

@ -1,36 +1,38 @@
#include "mainwindow.hpp" #include <fstream>
#include "functions.hpp"
#include "filesystem.hpp"
#include "tv_rename.hpp"
#include <gtkmm/liststore.h>
#include <gtkmm/filechooserdialog.h> #include <gtkmm/filechooserdialog.h>
#include <gtkmm/liststore.h>
#include <gtkmm/messagedialog.h> #include <gtkmm/messagedialog.h>
#include <gtkmm/textview.h> #include <gtkmm/textview.h>
#include <iostream> #include <iostream>
#include <fstream>
constexpr std::array<const char *, 46> languages{ #include "filesystem.hpp"
"en", "English", "sv", "Svenska", "no", "Norsk", "da", "Dansk", "fi", "Suomeksi", #include "functions.hpp"
"nl", "Nederlands", "de", "Deutsch", "it", "Italiano", "es", "Español", "fr", "Français", #include "mainwindow.hpp"
"pl", "Polski", "hu", "Magyar", "el", "Greek", "tr", "Turkish", "ru", "Russian", #include "tv_rename.hpp"
"he", "Hebrew", "ja", "Japanese", "pt", "Portuguese", "zh", "Chinese", "cs", "Czech",
"sl", "Slovenian", "hr", "Croatian", "ko","Korea" 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"
}; };
void MainWindow::chooseFile() { void MainWindow::chooseFile() {
// create a dialog for choosing directory // create a dialog for choosing directory
Gtk::FileChooserDialog dialog("Select a directory", Gtk::FileChooserDialog dialog( "Select a directory",
Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER );
dialog.set_transient_for(*this); dialog.set_transient_for( *this );
// add cancel and select buttons // add cancel and select buttons
dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); dialog.add_button( "_Cancel", Gtk::RESPONSE_CANCEL );
dialog.add_button("Select", Gtk::RESPONSE_OK); dialog.add_button( "Select", Gtk::RESPONSE_OK );
auto result = dialog.run(); auto result = dialog.run();
switch(result) { switch ( result ) {
case Gtk::RESPONSE_OK: case Gtk::RESPONSE_OK:
m_entry_dir.set_text(dialog.get_filename()); m_entry_dir.set_text( dialog.get_filename() );
std::cout << dialog.get_filename() << std::endl; std::cout << dialog.get_filename() << std::endl;
break; break;
case Gtk::RESPONSE_CANCEL: case Gtk::RESPONSE_CANCEL:
@ -48,74 +50,80 @@ void MainWindow::quit() {
} }
void MainWindow::patternHelp() { void MainWindow::patternHelp() {
Gtk::MessageDialog dialog(*this, "Pattern escape sequences"); 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" "%show - show name from thetvdb\n"
"%epname - episode name from thetvdb\n" "%epname - episode name from thetvdb\n"
"%season - season number\n" "%season - season number\n"
"%episode - episode 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" " right after %, like this: %2season.\n"
"Default pattern is \"%filename - %epname\", you might want to change this to" "Default pattern is \"%filename - %epname\", you might want to change "
" \"S%2seasonE%2episode - %epname\" or \"%show - S%2seasonE%2episode - %epname\"" "this to"
); " \"S%2seasonE%2episode - %epname\" or \"%show - S%2seasonE%2episode - "
"%epname\"" );
dialog.run(); dialog.run();
} }
void MainWindow::process() { void MainWindow::process() {
// check required fields are filled out // check required field is filled out
if( m_entry_show.get_text().empty() ) { if ( m_entry_show.get_text().empty() ) {
Gtk::MessageDialog dialog(*this, "Show field is empty"); Gtk::MessageDialog dialog( *this, "Show field is empty" );
dialog.run(); dialog.run();
return; return;
} }
// language code // 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 // 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 no possible shows were found, tell the user
if( possible_shows.size() == 0 ) { 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(); dialog.run();
return; return;
} }
//show widgets // show widgets
m_label_possible.show(); m_label_possible.show();
m_button_get_names.show(); m_button_get_names.show();
m_combo_possible.show(); m_combo_possible.show();
// fill up combo box with results from thetvdb // fill up combo box with results from thetvdb
auto model = Gtk::ListStore::create(m_columns_url); auto model = Gtk::ListStore::create( m_columns_url );
m_combo_possible.set_model(model); m_combo_possible.set_model( model );
auto row = *(model->append()); auto row = *( model->append() );
row[m_columns_url.m_col_show] = possible_shows[0].first; row[m_columns_url.m_col_show] = possible_shows[0].first;
row[m_columns_url.m_col_url] = possible_shows[0].second; row[m_columns_url.m_col_url] = possible_shows[0].second;
m_combo_possible.set_active(row); m_combo_possible.set_active( row );
for( size_t i = 1; i < possible_shows.size(); i++ ) { for ( size_t i = 1; i < possible_shows.size(); i++ ) {
auto row = *(model->append()); auto row = *( model->append() );
row[m_columns_url.m_col_show] = possible_shows[i].first; row[m_columns_url.m_col_show] = possible_shows[i].first;
row[m_columns_url.m_col_url] = possible_shows[i].second; row[m_columns_url.m_col_url] = possible_shows[i].second;
} }
} }
void MainWindow::getNames() { void MainWindow::getNames() {
// check required fields are filled out // check required field is filled out
if( m_entry_dir.get_text().empty() ) { if ( m_entry_dir.get_text().empty() ) {
Gtk::MessageDialog dialog(*this, "Directory field is empty"); Gtk::MessageDialog dialog( *this, "Directory field is empty" );
dialog.run(); dialog.run();
return; return;
} }
// check directory exists // check directory exists
if( !FSLib::isDirectory(m_entry_dir.get_text()) ) { if ( !FSLib::isDirectory( m_entry_dir.get_text() ) ) {
Gtk::MessageDialog dialog(*this, "Directory doesn't exist"); Gtk::MessageDialog dialog( *this, "Directory doesn't exist" );
dialog.run(); dialog.run();
return; return;
} }
@ -125,21 +133,22 @@ void MainWindow::getNames() {
selected.clear(); selected.clear();
files.clear(); files.clear();
std::vector<int> options; std::vector< int > options;
// get all files in path and seperate them in map `files` by season // get all files in path and seperate them in map `files` by season
iterateFS(files, path); iterateFS( files, path );
for( auto &x : files ) { for ( auto &x : files ) {
options.push_back(x.first); options.push_back( x.first );
} }
// create a window with possible seasons to rename // create a window with possible seasons to rename
// store selected seasons in `selected` // store selected seasons in `selected`
sw = new SeasonWindow(options, 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); app->add_window( *sw );
sw->show(); sw->show();
} }
@ -147,113 +156,123 @@ void MainWindow::getNames() {
* orig - original filenames * orig - original filenames
* renamed - renamed filenames (sorted in the same order as `orig`) * 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) { void renameFiles(
for(auto renamed_it = renamed.begin(); renamed_it != renamed.end(); ++renamed_it) { const std::vector<
std::cout << renamed_it->first << "/" << renamed_it->second.first << " --> " std::pair< std::string, std::pair< std::string, std::string > > >
<< renamed_it->first << "/" << renamed_it->second.second << std::endl; &renamed ) {
FSLib::rename(renamed_it->first + "/" + renamed_it->second.first for ( auto renamed_it = renamed.begin(); renamed_it != renamed.end();
, renamed_it->first + "/" + renamed_it->second.second); ++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 MainWindow::finishedSelection() { void MainWindow::finishedSelection() {
// remove created SeasonWindow and delete it from memory // remove created SeasonWindow and delete it from memory
app->remove_window(*sw); app->remove_window( *sw );
delete sw; delete sw;
auto iter = m_combo_possible.get_active(); auto iter = m_combo_possible.get_active();
// debug output // 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 // shouldn't ever happen, but just to be sure
if( url.empty() ) if ( url.empty() )
return; return;
std::cout << "https://www.thetvdb.com" << url << std::endl; std::cout << "https://www.thetvdb.com" << url << std::endl;
std::string input_pattern = m_entry_pattern.get_text(); std::string input_pattern = m_entry_pattern.get_text();
if( input_pattern != default_pattern ) { // store pattern to cache if it's different from default
std::ofstream file(userHome() + "/.cache/tv_rename_pattern"); if ( input_pattern != default_pattern ) {
if( file ) { std::ofstream file( userHome() + "/.cache/tv_rename_pattern" );
if ( file ) {
file << input_pattern; file << input_pattern;
} }
} }
for( auto &x : selected ) { for ( auto &x : selected ) {
// get renamed files for given season // get renamed files for given season
auto renamed_files = getRenamedFiles( static_cast<Glib::ustring>((*iter)[m_columns_url.m_col_show]), x, auto renamed_files = getRenamedFiles(
"https://www.thetvdb.com" + url, static_cast< Glib::ustring >( ( *iter )[m_columns_url.m_col_show] ),
language_code, x, "https://www.thetvdb.com" + url, language_code,
(input_pattern.empty() ? default_pattern : input_pattern), ( input_pattern.empty() ? default_pattern : input_pattern ),
!m_check_linux.get_active(), c, files[x] ); !m_check_linux.get_active(), c, files[x] );
if( renamed_files.empty() ) if ( renamed_files.empty() )
continue; continue;
// if trust checkbox is ticked, rename files // if trust checkbox is ticked, rename files
if( m_check_trust.get_active() ) { if ( m_check_trust.get_active() ) {
renameFiles(renamed_files); renameFiles( renamed_files );
continue; continue;
} }
// create a custom dialog box with textview of new episode names // create a custom dialog box with textview of new episode names
Gtk::Dialog dialog("Rename confirmation", *this); Gtk::Dialog dialog( "Rename confirmation", *this );
auto content = dialog.get_content_area(); auto content = dialog.get_content_area();
Gtk::TextView tx; Gtk::TextView tx;
content->pack_start(tx); content->pack_start( tx );
tx.set_editable(false); tx.set_editable( false );
dialog.add_button("_No", Gtk::RESPONSE_CANCEL); dialog.add_button( "_No", Gtk::RESPONSE_CANCEL );
dialog.add_button("_Yes", Gtk::RESPONSE_OK); dialog.add_button( "_Yes", Gtk::RESPONSE_OK );
tx.show(); tx.show();
auto buff = tx.get_buffer(); auto buff = tx.get_buffer();
buff->place_cursor(buff->begin()); buff->place_cursor( buff->begin() );
buff->insert_at_cursor(renamed_files[0].second.first.c_str()); buff->insert_at_cursor( renamed_files[0].second.first.c_str() );
buff->insert_at_cursor(" --> "); buff->insert_at_cursor( " --> " );
buff->insert_at_cursor(renamed_files[0].second.second.c_str()); buff->insert_at_cursor( renamed_files[0].second.second.c_str() );
for( size_t i = 1; i < renamed_files.size(); i++ ) { for ( size_t i = 1; i < renamed_files.size(); i++ ) {
buff->insert_at_cursor("\n"); buff->insert_at_cursor( "\n" );
buff->insert_at_cursor(renamed_files[i].second.first.c_str()); buff->insert_at_cursor( renamed_files[i].second.first.c_str() );
buff->insert_at_cursor(" --> "); buff->insert_at_cursor( " --> " );
buff->insert_at_cursor(renamed_files[i].second.second.c_str()); buff->insert_at_cursor( renamed_files[i].second.second.c_str() );
} }
auto response = dialog.run(); auto response = dialog.run();
// if user clicked "Yes" in dialog, rename files // if user clicked "Yes" in dialog, rename files
switch(response) { switch ( response ) {
case Gtk::RESPONSE_OK: case Gtk::RESPONSE_OK:
renameFiles(renamed_files); renameFiles( renamed_files );
default: default:
break; break;
} }
} }
} }
MainWindow::MainWindow(const Glib::RefPtr<Gtk::Application> &ptr) : app(ptr) { MainWindow::MainWindow( const Glib::RefPtr< Gtk::Application > &ptr )
set_title("TV Rename"); : app( ptr ) {
set_title( "TV Rename" );
set_default_size(400, 310); set_default_size( 400, 310 );
set_resizable(false); set_resizable( false );
{ {
std::ifstream file(userHome() + "/.cache/tv_rename_pattern"); // if cached pattern exists, load that instead of default
if( file ) { std::ifstream file( userHome() + "/.cache/tv_rename_pattern" );
if ( file ) {
std::getline( file, default_pattern ); std::getline( file, default_pattern );
} else { } else {
default_pattern = "%filename - %epname"; default_pattern = "%filename - %epname";
} }
} }
add( m_layout );
add(m_layout); // set widgets' location
m_layout.put( m_label_show, 5, 5 ); m_layout.put( m_label_show, 5, 5 );
m_layout.put( m_label_language, 190, 5 ); m_layout.put( m_label_language, 190, 5 );
m_layout.put( m_entry_show, 5, 25 ); m_layout.put( m_entry_show, 5, 25 );
@ -301,35 +320,45 @@ MainWindow::MainWindow(const Glib::RefPtr<Gtk::Application> &ptr) : app(ptr) {
m_button_get_names.set_size_request( 80, 30 ); m_button_get_names.set_size_request( 80, 30 );
// set default pattern // set default pattern
m_entry_pattern.set_text(default_pattern); m_entry_pattern.set_text( default_pattern );
// put languages in combo box // put languages in combo box
{ {
auto model = Gtk::ListStore::create(m_columns_language); auto model = Gtk::ListStore::create( m_columns_language );
m_combo_language.set_model(model); m_combo_language.set_model( model );
auto row = *(model->append());
auto row = *( model->append() );
row[m_columns_language.m_col_code] = "en"; row[m_columns_language.m_col_code] = "en";
row[m_columns_language.m_col_language] = "English"; row[m_columns_language.m_col_language] = "English";
m_combo_language.set_active(row);
for(size_t i = 2; i < languages.size(); i += 2) { m_combo_language.set_active( row );
row = *(model->append());
for ( size_t i = 2; i < languages.size(); i += 2 ) {
row = *( model->append() );
row[m_columns_language.m_col_code] = languages[i]; row[m_columns_language.m_col_code] = languages[i];
row[m_columns_language.m_col_language] = languages[i+1]; row[m_columns_language.m_col_language] = languages[i + 1];
} }
} }
// set column to be shown in comboboxes // set column to be shown in comboboxes
m_combo_language.pack_start(m_columns_language.m_col_language); m_combo_language.pack_start( m_columns_language.m_col_language );
m_combo_possible.pack_start(m_columns_url.m_col_show); m_combo_possible.pack_start( m_columns_url.m_col_show );
// set signals // set signals
m_button_dir.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::chooseFile)); m_button_dir.signal_clicked().connect(
m_button_quit.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::quit)); sigc::mem_fun( *this, &MainWindow::chooseFile ) );
m_button_process.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::process)); m_button_quit.signal_clicked().connect(
m_button_get_names.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::getNames)); sigc::mem_fun( *this, &MainWindow::quit ) );
m_entry_show.signal_activate().connect(sigc::mem_fun(*this, &MainWindow::process)); m_button_process.signal_clicked().connect(
m_entry_dir.signal_activate().connect(sigc::mem_fun(*this, &MainWindow::process)); sigc::mem_fun( *this, &MainWindow::process ) );
m_button_pattern.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::patternHelp)); 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 // show everything except possible shows and items related to them
m_layout.show(); m_layout.show();
@ -342,11 +371,10 @@ MainWindow::MainWindow(const Glib::RefPtr<Gtk::Application> &ptr) : app(ptr) {
m_button_quit.show(); m_button_quit.show();
m_button_dir.show(); m_button_dir.show();
m_check_linux.show(); m_check_linux.show();
m_check_linux.set_active(true); m_check_linux.set_active( true );
m_check_trust.show(); m_check_trust.show();
m_button_pattern.show(); m_button_pattern.show();
m_entry_pattern.show(); m_entry_pattern.show();
m_label_pattern.show(); m_label_pattern.show();
m_label_dir.show(); m_label_dir.show();
} }

View File

@ -5,8 +5,8 @@
#include <gtkmm/checkbutton.h> #include <gtkmm/checkbutton.h>
#include <gtkmm/combobox.h> #include <gtkmm/combobox.h>
#include <gtkmm/entry.h> #include <gtkmm/entry.h>
#include <gtkmm/layout.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <gtkmm/layout.h>
#include <gtkmm/window.h> #include <gtkmm/window.h>
#include <set> #include <set>
@ -15,7 +15,7 @@
class MainWindow : public Gtk::Window { class MainWindow : public Gtk::Window {
public: public:
MainWindow(const Glib::RefPtr<Gtk::Application> &ptr); MainWindow( const Glib::RefPtr< Gtk::Application > &ptr );
virtual ~MainWindow() = default; virtual ~MainWindow() = default;
private: private:
@ -55,37 +55,36 @@ protected:
class LanguageColumns : public Gtk::TreeModel::ColumnRecord { class LanguageColumns : public Gtk::TreeModel::ColumnRecord {
public: public:
LanguageColumns() { LanguageColumns() {
add(m_col_code); add( m_col_code );
add(m_col_language); add( m_col_language );
} }
Gtk::TreeModelColumn<std::string> m_col_code; Gtk::TreeModelColumn< std::string > m_col_code;
Gtk::TreeModelColumn<std::string> m_col_language; Gtk::TreeModelColumn< std::string > m_col_language;
}; };
class UrlColumns: public Gtk::TreeModel::ColumnRecord { class UrlColumns : public Gtk::TreeModel::ColumnRecord {
public: public:
UrlColumns() { UrlColumns() {
add(m_col_url); add( m_col_url );
add(m_col_show); add( m_col_show );
} }
Gtk::TreeModelColumn<Glib::ustring> m_col_url; Gtk::TreeModelColumn< Glib::ustring > m_col_url;
Gtk::TreeModelColumn<Glib::ustring> m_col_show; Gtk::TreeModelColumn< Glib::ustring > m_col_show;
}; };
LanguageColumns m_columns_language; LanguageColumns m_columns_language;
UrlColumns m_columns_url; UrlColumns m_columns_url;
Glib::RefPtr<Gtk::Application> app; Glib::RefPtr< Gtk::Application > app;
SeasonWindow *sw; SeasonWindow *sw;
std::vector<int> selected; std::vector< int > selected;
std::map<int, std::set<std::string>> files; std::map< int, std::set< std::string > > files;
std::string path; std::string path;
std::string language_code; std::string language_code;
std::string default_pattern; std::string default_pattern;
}; };
#endif // GTKMM_EXAMPLE_HELLOWORLD_H #endif // GTKMM_MAIN_WINDOW

View File

@ -1,32 +1,83 @@
#include <iostream> #include <iostream>
#include "network.hpp" #include "network.hpp"
size_t writeCallback( void *contents, size_t size, size_t nmemb, void *target ) { #ifdef _WIN32
*static_cast<std::string*>(target) += std::string(static_cast<char*>(contents), size*nmemb);
// 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; return size * nmemb;
} }
Curl::Curl() { Curl::Curl() {
curl_global_init(CURL_GLOBAL_ALL); curl_global_init( CURL_GLOBAL_ALL );
curl_handle = curl_easy_init(); curl_handle = curl_easy_init();
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writeCallback); curl_easy_setopt( curl_handle, CURLOPT_WRITEFUNCTION, writeCallback );
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); curl_easy_setopt( curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0" );
} }
Curl::~Curl() { Curl::~Curl() {
curl_easy_cleanup(curl_handle); curl_easy_cleanup( curl_handle );
curl_global_cleanup(); curl_global_cleanup();
} }
std::string Curl::execute(const std::string &url) { std::string Curl::execute( const std::string &url ) {
std::string source; std::string source;
source.reserve(100000); source.reserve( 100000 );
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, static_cast<void*>(&source)); curl_easy_setopt( curl_handle, CURLOPT_WRITEDATA,
curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str()); static_cast< void * >( &source ) );
auto res = curl_easy_perform(curl_handle); curl_easy_setopt( curl_handle, CURLOPT_URL, url.c_str() );
if(res != CURLE_OK) { auto res = curl_easy_perform( curl_handle );
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl; if ( res != CURLE_OK ) {
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror( res )
<< std::endl;
return ""; return "";
} }
return source; return source;
} }
#endif

View File

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

@ -5,9 +5,9 @@ void SeasonWindow::confirm() {
// go through all checkbuttons and save numbers // go through all checkbuttons and save numbers
// of checked boxes into returned vector // of checked boxes into returned vector
// then quit // then quit
for( auto &x : m_checks ) { for ( auto &x : m_checks ) {
if( x.get_active() ) { if ( x.get_active() ) {
returned.push_back(std::stoi(x.get_label())); returned.push_back( std::stoi( x.get_label() ) );
} }
} }
hide(); hide();
@ -15,34 +15,36 @@ void SeasonWindow::confirm() {
void SeasonWindow::select_all() { void SeasonWindow::select_all() {
// set all check boxes to checked // set all check boxes to checked
for( auto &x : m_checks ) { for ( auto &x : m_checks ) {
x.set_active(true); x.set_active( true );
} }
} }
void SeasonWindow::select_none() { void SeasonWindow::select_none() {
// set all check boxes to unchecked // set all check boxes to unchecked
for( auto &x : m_checks ) { for ( auto &x : m_checks ) {
x.set_active(false); x.set_active( false );
} }
} }
SeasonWindow::SeasonWindow(const std::vector<int> &seasons, std::vector<int> &_returned) : returned(_returned) { SeasonWindow::SeasonWindow( const std::vector< int > &seasons,
set_title("Choose seasons"); std::vector< int > &_returned )
: returned( _returned ) {
set_title( "Choose seasons" );
set_default_size(250, 250); set_default_size( 250, 250 );
set_resizable(false); set_resizable( false );
add(m_layout); add( m_layout );
size_t x{5}, y{25}; size_t x{ 5 }, y{ 25 };
// create a check box for each season // create a check box for each season
for( auto &s : seasons ) { for ( auto &s : seasons ) {
m_checks.emplace_back(std::to_string(s)); m_checks.emplace_back( std::to_string( s ) );
m_layout.put(m_checks.back(), x, y); m_layout.put( m_checks.back(), x, y );
if( x == 185 ) { if ( x == 185 ) {
x = 5; x = 5;
y += 20; y += 20;
} else { } else {
@ -50,24 +52,27 @@ SeasonWindow::SeasonWindow(const std::vector<int> &seasons, std::vector<int> &_r
} }
} }
m_layout.put(m_label, 5, 5); m_layout.put( m_label, 5, 5 );
m_label.set_label("Select seasons:"); m_label.set_label( "Select seasons:" );
m_layout.put(m_confirm, 165, 215); m_layout.put( m_confirm, 165, 215 );
m_layout.put(m_all, 130, 175); m_layout.put( m_all, 130, 175 );
m_layout.put(m_none, 5, 175); m_layout.put( m_none, 5, 175 );
m_confirm.set_size_request(80,30); m_confirm.set_size_request( 80, 30 );
m_all.set_size_request(80,30); m_all.set_size_request( 80, 30 );
m_none.set_size_request(80,30); m_none.set_size_request( 80, 30 );
m_confirm.set_label("Select"); m_confirm.set_label( "Select" );
m_all.set_label("Select All"); m_all.set_label( "Select All" );
m_none.set_label("Unselect All"); m_none.set_label( "Unselect All" );
m_confirm.signal_clicked().connect(sigc::mem_fun(*this, &SeasonWindow::confirm)); m_confirm.signal_clicked().connect(
m_all.signal_clicked().connect(sigc::mem_fun(*this, &SeasonWindow::select_all)); sigc::mem_fun( *this, &SeasonWindow::confirm ) );
m_none.signal_clicked().connect(sigc::mem_fun(*this, &SeasonWindow::select_none)); 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(); show_all_children();
} }

View File

@ -3,13 +3,14 @@
#include <gtkmm/button.h> #include <gtkmm/button.h>
#include <gtkmm/checkbutton.h> #include <gtkmm/checkbutton.h>
#include <gtkmm/layout.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <gtkmm/layout.h>
#include <gtkmm/window.h> #include <gtkmm/window.h>
class SeasonWindow : public Gtk::Window { class SeasonWindow : public Gtk::Window {
public: public:
SeasonWindow(const std::vector<int> &seasons, std::vector<int> &_returned); SeasonWindow( const std::vector< int > &seasons,
std::vector< int > &_returned );
virtual ~SeasonWindow() = default; virtual ~SeasonWindow() = default;
private: private:
@ -26,9 +27,9 @@ protected:
Gtk::Layout m_layout; Gtk::Layout m_layout;
std::vector<Gtk::CheckButton> m_checks; std::vector< Gtk::CheckButton > m_checks;
std::vector<int> &returned; std::vector< int > &returned;
}; };
#endif #endif

BIN
small.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,6 +1,15 @@
#include <algorithm> #include <algorithm>
#include "functions.hpp" #include <map>
#include "tv_rename.hpp"
#ifdef _WIN32
#include <codecvt>
#include <fcntl.h>
#include <io.h>
#include <locale>
#include <windows.h>
#endif
#ifndef GUI #ifndef GUI
@ -11,24 +20,53 @@
#endif #endif
std::vector<std::string> parseEpisodeNames( const std::string &season_code, const std::string &language) { #include "functions.hpp"
std::vector<std::string> episodes; #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; size_t pos = 0;
while( true ) { while ( true ) {
pos = season_code.find("ge=\"" + language + "\" ", pos); pos =
if( pos != std::string::npos ) { season_code.find( TEXT( "ge=\"" ) + language + TEXT( "\" " ), pos );
if( language == "en" ) if ( pos != string::npos ) {
if ( language == TEXT( "en" ) )
pos += 17; pos += 17;
else else
pos += 29; pos += 29;
while( isspace(season_code[pos]) ) while ( iswspace( season_code[pos] ) )
pos++; pos++;
auto end = season_code.find("<", pos);
auto end = season_code.find( TEXT( "<" ), pos );
end--; end--;
while( isspace(season_code[end]) )
while ( iswspace( season_code[end] ) )
end--; end--;
end++; end++;
episodes.push_back(season_code.substr(pos, end - pos)); episodes.push_back( season_code.substr( pos, end - pos ) );
} else { } else {
break; break;
} }
@ -36,96 +74,130 @@ std::vector<std::string> parseEpisodeNames( const std::string &season_code, cons
return episodes; 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 ) { std::vector< std::pair< string, std::pair< string, string > > >
url += "/seasons/" + std::to_string(season); getRenamedFiles( const string &show, int season, string url,
//get source code of season's page const string &language, const string &pattern,
auto season_code = c.execute(url); const bool &linux, Curl &c, const std::set< string > &files ) {
//remove newlines #ifdef _WIN32
season_code.erase(std::remove_if(season_code.begin(), season_code.end(), [](char x) {return x == '\r' || x == '\n';}), season_code.end()); auto season_num = std::to_wstring( season );
//first 900 chars or so are useless to us #else
season_code = season_code.substr(900); auto season_num = std::to_string( season );
//get only the episode names #endif
auto pos = season_code.find("\"translations\"");
if( pos != std::string::npos ) { url += TEXT( "/seasons/" );
season_code = season_code.substr(pos); url += season_num;
pos = season_code.find("</table>");
if( pos != std::string::npos ) // get source code of season's page
season_code = season_code.substr(0,pos); 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 != string::npos ) {
season_code = season_code.substr( pos );
pos = season_code.find( "</table>" );
if ( pos != string::npos )
season_code = season_code.substr( 0, pos );
else else
return {}; return {};
} else { } else {
return {}; return {};
} }
auto episodes = parseEpisodeNames(season_code, language); #ifdef _WIN32
auto episodes =
parseEpisodeNames( utf8_to_wstring( season_code ), language );
#else
auto episodes = parseEpisodeNames( season_code, language );
#endif
if( episodes.empty() ) if ( episodes.empty() )
return {}; return {};
std::vector<std::pair<std::string, std::pair<std::string, std::string>>> renamed_files; if ( files.empty() )
if( files.empty() )
return {}; return {};
for( const auto &x : files ) { std::vector< std::pair< string, std::pair< string, string > > >
auto last = x.find_last_of("/"); renamed_files;
std::string og_name;
std::string dir; for ( const auto &x : files ) {
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; og_name = x;
dir = "."; dir = TEXT( "." );
} else { } else {
og_name = x.substr(last+1); og_name = x.substr( last + 1 );
dir = x.substr(0, last); dir = x.substr( 0, last );
} }
unsigned long num; unsigned long num;
// get file's episode number // get file's episode number
if( searchSpecificSeason(x.c_str(), pos, std::to_string(season)) ) { if ( searchSpecificSeason( x.c_str(), pos, season_num ) ) {
num = atoi(x.c_str()+pos); num = std::stoi( x.c_str() + pos );
} else { } else {
continue; continue;
} }
num -= 1; num -= 1;
if( num < episodes.size() ) { if ( num < episodes.size() ) {
auto pos = og_name.find_last_of('.'); auto pos = og_name.find_last_of( TEXT( "." ) );
// get desired filename // 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 '|' // replace '/' with '|'
for( size_t i = 0; i < name.size(); i++ ) { for ( size_t i = 0; i < name.size(); i++ ) {
if( name[i] == '/' ) { if ( name[i] == '/' ) {
name[i] = '|'; name[i] = '|';
} }
} }
// replace characters illegal in windows if desired // replace characters illegal in windows if desired
if( !linux ) { 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(),
size_t max{name.size()}; []( char_t x ) {
for( size_t i = 0; i < max ; i++ ) { return x == '?' || x == '"' ||
if( name[i] == '|' ) { x == '\\' || x == '*';
} ),
name.end() );
size_t max{ name.size() };
for ( size_t i = 0; i < max; i++ ) {
if ( name[i] == '|' ) {
name[i] = '-'; name[i] = '-';
}else if( name[i] == '<' ) { } else if ( name[i] == '<' ) {
name[i] = 'i'; name[i] = 'i';
name.insert(i+1, "s less than"); name.insert( i + 1, TEXT( "s less than" ) );
max += 11; max += 11;
} else if ( name[i] == '>' ) { } else if ( name[i] == '>' ) {
name[i] = 'i'; name[i] = 'i';
name.insert(i+1, "s more than"); name.insert( i + 1, TEXT( "s more than" ) );
max += 11; max += 11;
} else if ( name[i] == ':' ) { } else if ( name[i] == ':' ) {
name[i] = ' '; name[i] = ' ';
name.insert(i+1, 1, '-'); name.insert( i + 1, 1, '-' );
max++; max++;
} }
} }
for( size_t i = 0; i < max; i++ ) { for ( size_t i = 0; i < max; i++ ) {
if ( name[i] == ' ' && name[i+1] == ' ' ) { if ( name[i] == ' ' && name[i + 1] == ' ' ) {
name.erase(i, 1); name.erase( i, 1 );
max--; max--;
i--; i--;
} }
} }
} }
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; return renamed_files;
@ -133,62 +205,80 @@ std::vector<std::pair<std::string, std::pair<std::string, std::string>>> getRena
#ifndef GUI #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,
if( url.empty() ) const string &language, const string &pattern,
url = getDefUrl(show, language, c); 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 ) { if ( files_ptr == nullptr ) {
found_files = new std::set<std::string>; found_files = new std::set< string >;
findSeason(*found_files, season, path); findSeason( *found_files, season, path );
files_ptr = found_files; 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) { for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end();
std::cout << renamed->second.first << " --> " << renamed->second.second << std::endl; ++renamed ) {
cout << renamed->second.first << " --> " << renamed->second.second
<< std::endl;
} }
if( !trust ) { if ( !trust ) {
std::cout << "Does this seem ok? (y/n) "; cout << "Does this seem ok? (y/n) ";
std::string response; string response;
std::cin >> response; cin >> response;
std::cin.clear(); cin.clear();
std::cin.ignore(1, '\n'); cin.ignore( 1, '\n' );
if( response[0] != 'y' && response[0] != 'Y' ) if ( response[0] != 'y' && response[0] != 'Y' )
return; return;
} }
for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) { for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end();
FSLib::rename(renamed->first + "/" + renamed->second.first, renamed->first + "/" + renamed->second.second); ++renamed ) {
FSLib::rename( renamed->first + dir_divider + renamed->second.first,
renamed->first + dir_divider + renamed->second.second );
} }
if( found_files != nullptr ) { if ( found_files != nullptr ) {
delete found_files; delete found_files;
} }
} }
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,
auto url = getDefUrl(show, language, c); const std::map< int, std::set< string > > &seasons,
for( const auto &x : seasons ) { const string &language, const string &pattern,
singleSeason( path, show, x.first, url, language, pattern, linux, trust, c, &x.second); 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 );
} }
} }
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 multipleSeasons( const string &path, string &show,
std::map<int, std::set<std::string>> season_map; const std::set< int > seasons, const string &language,
findSeasons(season_map, path, seasons); const string &pattern, const bool &linux,
multipleSeasons(path, show, season_map, language, pattern, linux, trust, c); 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 );
} }
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) { void allSeasons( const string &path, string &show, const string &language,
std::map<int, std::set<std::string>> seasons; const string &pattern, const bool &linux, const bool &trust,
//get all season number from this directory and subdirectories Curl &c ) {
iterateFS(seasons, path); std::map< int, std::set< string > > seasons;
multipleSeasons( path, show, seasons, language, pattern, linux, trust, c); // get all season number from this directory and subdirectories
iterateFS( seasons, path );
multipleSeasons( path, show, seasons, language, pattern, linux, trust, c );
} }
#endif #endif

View File

@ -2,19 +2,45 @@
#define TV_RENAME_HPP #define TV_RENAME_HPP
#include <set> #include <set>
#include "network.hpp"
#ifdef GUI #ifdef GUI
#include <vector> #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 #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); using string = std::string;
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); using char_t = char;
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);
#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 #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.