Compare commits

...

5 Commits

Author SHA1 Message Date
21072a835e Linux packages 2020-05-10 00:56:50 +02:00
631707d5b2 Clear the branch 2020-05-10 00:55:22 +02:00
306ee0d4e5 Add Windows binaries 2019-05-18 15:32:43 +02:00
aac6b93668 Delete everything 2019-05-18 15:31:41 +02:00
00efca3df9 Delete everything 2019-05-18 15:31:17 +02:00
30 changed files with 0 additions and 2984 deletions

4
.gitignore vendored
View File

@ -1,4 +0,0 @@
*.o
tv_rename
tv_rename_gui
test

24
LICENSE
View File

@ -1,24 +0,0 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>

View File

@ -1,85 +0,0 @@
CC ?= clang
CXX ?= clang++
CFLAGS ?= -O2 -Wall -Wextra -std=c++11
PREFIX ?= /usr/local/bin
APPDIR ?= /usr/share/applications
ICONDIR ?= /usr/share/icons/hicolor
.PHONY: default
default: tv_rename
.PHONY: clean
clean:
rm -f *.o tv_rename tv_rename_gui
.PHONY: install
install: tv_rename
install -d $(PREFIX)/
install -m 755 tv_rename $(PREFIX)/
.PHONY: install_gui
install_gui: gui
install -d $(PREFIX)/
install -m 755 tv_rename_gui $(PREFIX)/
install -d $(APPDIR)
install -m 755 tv_rename_gui.desktop $(APPDIR)/
install -d $(ICONDIR)
install -m 644 tv_rename.svg $(ICONDIR)/scalable/apps/
gtk-update-icon-cache -f $(ICONDIR)
.PHONY: uninstall
uninstall:
rm $(PREFIX)/tv_rename
.PHONY: uninstall_gui
uninstall_gui:
rm $(PREFIX)/tv_rename_gui
rm $(APPDIR)/tv_rename_gui.desktop
rm $(ICONDIR)/scalable/apps/tv_rename.svg
gtk-update-icon-cache -f $(ICONDIR)
tv_rename: functions.o filesystem_u.o network.o tv_rename.o main.cpp
$(CXX) $(CFLAGS) -o tv_rename main.cpp tv_rename.o functions.o filesystem_u.o network.o -lcurl
filesystem_u.o: unix/filesystem.cpp
$(CXX) $(CFLAGS) -c unix/filesystem.cpp -o filesystem_u.o
functions.o: functions.cpp
$(CXX) $(CFLAGS) -c functions.cpp
network.o: network.cpp
$(CXX) $(CFLAGS) -c network.cpp
tv_rename.o: tv_rename.cpp
$(CXX) $(CFLAGS) -c tv_rename.cpp
.PHONY: gui
gui: tv_rename_gui
tv_rename_gui: gui.cpp mainwindow.cpp seasonwindow.cpp network.o functions_gui.o filesystem_u_gui.o tv_rename_gui.o
$(CXX) $(CFLAGS) -o tv_rename_gui gui.cpp mainwindow.cpp seasonwindow.cpp network.o functions_gui.o filesystem_u_gui.o tv_rename_gui.o `pkg-config gtkmm-3.0 --cflags --libs` -lcurl -DGUI
filesystem_u_gui.o: unix/filesystem.cpp
$(CXX) $(CFLAGS) -c unix/filesystem.cpp -o filesystem_u_gui.o -DGUI
functions_gui.o: functions.cpp
$(CXX) $(CFLAGS) -c functions.cpp -o functions_gui.o -DGUI
tv_rename_gui.o: tv_rename.cpp
$(CXX) $(CFLAGS) -c tv_rename.cpp -o tv_rename_gui.o -DGUI
.PHONY: windows
windows: tv_rename.exe
tv_rename.exe: tv_rename.cpp functions.cpp windows/filesystem.cpp network.cpp main.cpp
$(CXX) -MD -EHsc -Fe"tv_rename" tv_rename.cpp windows/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 windows/filesystem.cpp functions.cpp network.cpp
$(CXX) -MD -EHsc -Fe"tv_rename_gui" tv_rename_gui.cpp tv_rename.cpp windows/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,66 +0,0 @@
# Rename TV Show files
This program downloads episode names from http://thetvdb.com and adds them as a suffix to the appropriate files
For this to work your file needs to contain the season and episode number (in accordance to thetvdb) in this format: S#E# (doesn't matter if lower or upper case)
The program only supports the aired order so far
## Usage
Enter the directory in which the files you want to rename are (you can have subdirectories with different seasons, just not different shows) and call the program
Arguments:
`-s, --show` - Name of the show
`-n, --season` - Seasons to be renamed (numbers separated by space or 'all' for all seasons in the directory and sub directories)
`-p, --show-path` - Path to the show's directory
`-c, --correct-path` - Don't ask if path is correct
`-t, --trust` - Don't prompt before changing file names
`-x, --linux` - Don't replace characters that are illegal in NTFS
`-l, --lang` - Language of the episode titles
`--print_langs` - Print available languages
`--name-pattern` - Pattern by which the files should be renamed
- %filename - original filename
- %show - show name from thetvdb
- %epname - episode name from thetvdb
- %season - season number, possible to specify leading 0 like this: %2season (number means how many leading zeros)
- %episode - episode number, possible to specify leading 0 like this: %3season (number means how many leading zeros)
## Installation
For installation of command line version you'll need:
- *libcurl-dev* (or your distro's equivalent)
- some sort of C++ compiler (*clang++* for example)
- *make*
When you have all prerequisites installed you need to do this sequence of commands:
git clone 'https://gitlab.com/zvon/tv_rename_cpp'
cd tv_rename_cpp
make
sudo make install
For installation of GUI version you'll need:
- *libcurl-dev* (or your distro's equivalent)
- some sort of C++ compiler (*clang++* for example)
- *make*
- *libgtkmm* (or your distro's equivalent)
When you have all prerequisites installed you need to do this sequence of commands:
git clone 'https://gitlab.com/zvon/tv_rename_cpp'
cd tv_rename_cpp
make gui
sudo make install_gui
You can uninstall both versions by running either `sudo make uninstall` or `sudo make uninstall_gui`

View File

@ -1,127 +0,0 @@
#ifndef FSLIB_H
#define FSLIB_H
#include <string.h>
#include <string>
#ifdef _WIN32
#include <Windows.h>
#else
#include <dirent.h>
#endif
// set apropriate data types for each operating system
#ifdef _WIN32
using string = std::wstring;
using char_t = wchar_t;
#else
using string = std::string;
using char_t = char;
#endif
// windows version stolen from
// http://www.martinbroadhurst.com/list-the-files-in-a-directory-in-c.html
namespace FSLib {
#ifndef GUI
bool exists( const 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_ );
Directory() = delete;
Directory( const Directory &d ) = default;
Directory( Directory &&d ) = default;
class Iterator {
public:
Iterator( const Directory &d_ );
~Iterator();
#ifdef _WIN32
Iterator( bool ended_ );
#else
Iterator( const Directory &d_, const struct dirent *current_entry_ );
#endif
Iterator() = delete;
Iterator( const Iterator &i ) = default;
Iterator( Iterator &&i ) = default;
char_t const *operator*() const;
Iterator &operator++();
bool operator==( const Iterator &i_other ) const;
Iterator operator++( int ) {
Iterator ret( *this );
operator++();
return ret;
}
bool operator!=( const Iterator &i_other ) const {
return !( i_other == *this );
}
private:
#ifndef _WIN32
DIR *d;
const struct dirent *current_entry;
#else
HANDLE hFind;
WIN32_FIND_DATA data;
bool ended{ false };
#endif
};
using iterator = Iterator;
using const_iterator = Iterator;
iterator end();
const_iterator end() const;
iterator begin() {
return Iterator( *this );
}
const_iterator begin() const {
return Iterator( *this );
}
const_iterator cbegin() const {
return begin();
}
const_iterator cend() const {
return end();
}
const char_t *path() const {
return dir_path.c_str();
}
private:
string dir_path;
};
} // namespace FSLib
#endif

View File

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

View File

@ -1,69 +0,0 @@
#ifndef TV_FUNCTIONS_H
#define TV_FUNCTIONS_H
#include <iostream>
#include <map>
#include <set>
#include <string>
#ifdef GUI
#include <vector>
#endif
#include "network.hpp"
#ifdef _WIN32
using string = std::wstring;
using char_t = wchar_t;
std::wstring utf8_to_wstring( const std::string &utf8 );
#else
using string = std::string;
using char_t = char;
#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 // GUI
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

12
gui.cpp
View File

@ -1,12 +0,0 @@
#include <gtkmm/application.h>
#include "mainwindow.hpp"
int main( int argc, char **argv ) {
auto app = Gtk::Application::create(
argc, argv, "org.idonthaveanorganization.tvrename" );
MainWindow mw( app );
return app->run( mw );
}

262
main.cpp
View File

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

View File

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

View File

@ -1,90 +0,0 @@
#ifndef GTKMM_MAIN_WINDOW
#define GTKMM_MAIN_WINDOW
#include <gtkmm/button.h>
#include <gtkmm/checkbutton.h>
#include <gtkmm/combobox.h>
#include <gtkmm/entry.h>
#include <gtkmm/label.h>
#include <gtkmm/layout.h>
#include <gtkmm/window.h>
#include <set>
#include "network.hpp"
#include "seasonwindow.hpp"
class MainWindow : public Gtk::Window {
public:
MainWindow( const Glib::RefPtr< Gtk::Application > &ptr );
virtual ~MainWindow() = default;
private:
void quit();
void process();
void getNames();
void finishedSelection();
void chooseFile();
void patternHelp();
protected:
Gtk::Button m_button_dir;
Gtk::Button m_button_get_names;
Gtk::Button m_button_quit;
Gtk::Button m_button_process;
Gtk::Button m_button_pattern;
Gtk::CheckButton m_check_linux;
Gtk::CheckButton m_check_trust;
Gtk::ComboBox m_combo_language;
Gtk::ComboBox m_combo_possible;
Gtk::Entry m_entry_show;
Gtk::Entry m_entry_dir;
Gtk::Entry m_entry_pattern;
Gtk::Label m_label_language;
Gtk::Label m_label_possible;
Gtk::Label m_label_show;
Gtk::Label m_label_dir;
Gtk::Label m_label_pattern;
Gtk::Layout m_layout;
Curl c;
class LanguageColumns : public Gtk::TreeModel::ColumnRecord {
public:
LanguageColumns() {
add( m_col_code );
add( m_col_language );
}
Gtk::TreeModelColumn< std::string > m_col_code;
Gtk::TreeModelColumn< std::string > m_col_language;
};
class UrlColumns : public Gtk::TreeModel::ColumnRecord {
public:
UrlColumns() {
add( m_col_url );
add( m_col_show );
}
Gtk::TreeModelColumn< Glib::ustring > m_col_url;
Gtk::TreeModelColumn< Glib::ustring > m_col_show;
};
LanguageColumns m_columns_language;
UrlColumns m_columns_url;
Glib::RefPtr< Gtk::Application > app;
SeasonWindow *sw;
std::vector< int > selected;
std::map< int, std::set< std::string > > files;
std::string path;
std::string language_code;
std::string default_pattern;
};
#endif // GTKMM_MAIN_WINDOW

View File

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

View File

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

View File

@ -1,50 +0,0 @@
//{{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

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

View File

@ -1,35 +0,0 @@
#ifndef GTKMM_SEASON_WINDOW
#define GTKMM_SEASON_WINDOW
#include <gtkmm/button.h>
#include <gtkmm/checkbutton.h>
#include <gtkmm/label.h>
#include <gtkmm/layout.h>
#include <gtkmm/window.h>
class SeasonWindow : public Gtk::Window {
public:
SeasonWindow( const std::vector< int > &seasons,
std::vector< int > &_returned );
virtual ~SeasonWindow() = default;
private:
void confirm();
void select_all();
void select_none();
protected:
Gtk::Button m_confirm;
Gtk::Button m_all;
Gtk::Button m_none;
Gtk::Label m_label;
Gtk::Layout m_layout;
std::vector< Gtk::CheckButton > m_checks;
std::vector< int > &returned;
};
#endif

BIN
small.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

BIN
tv-rename-2.0.deb Normal file

Binary file not shown.

BIN
tv-rename-gui-2.0.deb Normal file

Binary file not shown.

BIN
tv_rename-2.0-1.x86_64.rpm Normal file

Binary file not shown.

View File

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

View File

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

View File

@ -1,120 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="119.99982mm"
height="121.24537mm"
viewBox="0 0 119.99982 121.24537"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="tvrename.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.70710678"
inkscape:cx="92.679615"
inkscape:cy="139.94547"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1025"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-48,-19.754633)">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2.95900011;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4518"
width="117.04082"
height="87.040817"
x="49.4795"
y="52.479683"
ry="15.234363"
rx="15.234363" />
<rect
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.95900011;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4518-3"
width="97.041"
height="67.041"
x="59.4795"
y="62.4795"
ry="15.234362"
rx="15.234363" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
transform="translate(-48,-19.754633)">
<path
style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 78.430058,32.98363 101.67757,52.070445 148.92262,21.266369"
id="path4536"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<g
id="g4552"
transform="matrix(0.2895779,0.28957789,-0.31316804,0.31316802,72.31496,22.170558)"
style="stroke-width:2.34808373">
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4540"
d="m 189.09319,118.46395 -4.93057,-8.53999 h 9.86038 z"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:3.52212548;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path4542"
d="m 184.16262,109.92396 -9.7466,-16.881597 H 203.77 l -9.747,16.881597 z"
style="fill:none;stroke:#000000;stroke-width:3.52212548;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path4544"
d="M 174.41602,93.042 V 16.776 H 203.77 v 76.266 z"
style="fill:none;stroke:#000000;stroke-width:3.52212548;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path4546"
d="M 189.093,16.776 V 93.042"
style="fill:none;stroke:#000000;stroke-width:3.52212548;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

View File

@ -1,385 +0,0 @@
// 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"
};
// filled with possible season numbers
std::vector< int > options;
// contains IDs of checboxes created for IDD_SEASONS
std::vector< int > checkboxes;
// contains season numbers that have been checked in IDD_SEASONS
std::vector< int > checked;
Curl c;
wchar_t lang_code[3] = L"en";
std::wstring default_pattern = L"%filename - %epname";
// files separated by season
std::map< int, std::set< string > > files;
// possible shows for given query
std::vector< std::pair< string, string > > possible_shows;
// files from currently previewed season
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: {
// fill with checkboxes for possible seasons
checked.clear();
checkboxes.clear();
int left{ 15 }, top{ 30 };
for ( int i = 0; i < options.size(); i++ ) {
if ( checkboxes.empty() ) {
// start IDs at 2000
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:
// store checked seasons in checked
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:
// check all seasons
for ( auto &x : checkboxes ) {
SendDlgItemMessage( hwnd, x, BM_SETCHECK, BST_CHECKED, 0 );
}
break;
case IDNONE:
// uncheck all seasons
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;
}
// choose directory, 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: {
// fill IDTEXT with how the rename would look
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 ) {
// get show name and url for selected show
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 user trusts us, just rename files
if ( SendDlgItemMessage( hwnd, IDTRUST, BM_GETCHECK, 0, 0 ) ) {
renameFiles( renamed_files );
continue;
}
// if user doesn't trust us show what rename would look like
current_renamed_files = &renamed_files;
DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_PREVIEW ),
hwnd, PreviewBox );
}
// if user trusted us, let them know when rename finished
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();
// get possible seasons
iterateFS( files, path );
for ( auto &x : files ) {
options.push_back( x.first );
}
// user selects which seasons should be renamed
DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_SEASONS ), hwnd,
SeasonsBox );
// process selected seasons
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 );
// get language code
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 );
// fill IDSHOWS with possible shows
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() );
}
// select first item
SendDlgItemMessage( hwnd, IDSHOWS, CB_SETCURSEL, 0, 0 );
}
// if stored pattern exists, read it and store the value
// in default_pattern
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: {
// fill IDLANG with possible languages
for ( int i = 1; i < languages.size(); i += 2 ) {
SendDlgItemMessage( hwnd, IDLANG, CB_ADDSTRING, 0,
( LPARAM )languages[i] );
}
// select English by default
SendDlgItemMessage( hwnd, IDLANG, CB_SETCURSEL, 5, 0 );
auto appdata = userHome() + L"\\tv_rename";
if ( !FSLib::isDirectory( appdata ) ) {
// create the directory so pattern can be stored when changed
CreateDirectory( appdata.c_str(), NULL );
} 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 );
}

View File

@ -1,9 +0,0 @@
[Desktop Entry]
Encoding=UTF-8
Value=1.0
Type=Application
Name=TV Rename
GenericName=TV Rename
Comment=TV Rename
Icon=tv_rename
Exec=tv_rename_gui

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

View File

@ -1,84 +0,0 @@
#include <sys/stat.h>
#include "../filesystem.hpp"
FSLib::Directory::Directory( const string &path_ ) : dir_path( path_ ) {}
FSLib::Directory::Iterator::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 );
}
FSLib::Directory::Iterator::Iterator( const Directory &d_, const struct dirent *current_entry_ )
: d( opendir( d_.path() ) ), current_entry( current_entry_ ) {}
FSLib::Directory::Iterator::~Iterator() {
closedir( d );
}
#ifndef GUI // these functions aren't needed in GUI
bool FSLib::exists( const string &path ) {
struct stat path_stat;
return stat( path.c_str(), &path_stat ) == 0;
}
string FSLib::canonical( const string &path ) {
char_t *canonical_path = new char_t[PATH_MAX];
auto failed = realpath( path.c_str(), canonical_path ) == nullptr;
if ( failed ) {
delete[] canonical_path;
return string();
}
string canonical_string{ canonical_path };
delete[] canonical_path;
return canonical_string;
}
#endif // ndef GUI
bool FSLib::isDirectory( const string &path ) {
struct stat path_stat;
stat( path.c_str(), &path_stat );
return S_ISDIR( path_stat.st_mode );
}
bool FSLib::rename( const string &file_a, const string &file_b ) {
return ::rename( file_a.c_str(), file_b.c_str() ) == 0;
}
FSLib::Directory::iterator FSLib::Directory::end() {
return Iterator( *this, nullptr );
}
FSLib::Directory::const_iterator FSLib::Directory::end() const {
return Iterator( *this, nullptr );
}
char_t const *FSLib::Directory::Iterator::operator*() const {
return current_entry->d_name;
}
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 *this;
}
bool FSLib::Directory::Iterator::operator==( const Iterator &i_other ) const {
return i_other.current_entry == current_entry;
}

View File

@ -1,96 +0,0 @@
#include <Shlwapi.h>
#include <windows.h>
#include "../filesystem.hpp"
FSLib::Directory::Directory( const string &path_ ) : dir_path( path_ ) {
// need to append \\* for windows to search files in directory
dir_path.append( L"\\*" );
}
FSLib::Directory::Iterator::Iterator( const Directory &d_ ) {
hFind = FindFirstFileW( d_.path(), &data );
if ( hFind != INVALID_HANDLE_VALUE ) {
if ( !wcscmp( data.cFileName, L"." ) ||
!wcscmp( data.cFileName, L".." ) ) {
++( *this );
}
} else {
ended = true;
}
}
FSLib::Directory::Iterator::~Iterator() {
if ( hFind )
FindClose( hFind );
}
// 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
FSLib::Directory::Iterator::Iterator( bool ended_ ) : ended( ended_ ) {}
#ifndef GUI // these functions aren't needed in GUI
bool FSLib::exists( const string &path ) {
struct _stat path_stat;
return _wstat( path.c_str(), &path_stat ) == 0;
}
string FSLib::canonical( const string &path ) {
char_t *canonical_path = new char_t[MAX_PATH];
auto failed = !PathCanonicalizeW( canonical_path, path.c_str() );
if ( failed ) {
delete[] canonical_path;
return string();
}
string canonical_string{ canonical_path };
delete[] canonical_path;
return canonical_string;
}
#endif // ndef GUI
bool FSLib::isDirectory( const string &path ) {
struct _stat path_stat;
_wstat( path.c_str(), &path_stat );
return path_stat.st_mode & _S_IFDIR;
}
bool FSLib::rename( const string &file_a, const string &file_b ) {
return MoveFileExW( file_a.c_str(), file_b.c_str(),
MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING );
}
FSLib::Directory::iterator FSLib::Directory::end() {
return Iterator( true );
}
FSLib::Directory::const_iterator FSLib::Directory::end() const {
return Iterator( true );
}
char_t const *FSLib::Directory::Iterator::operator*() const {
return data.cFileName;
}
FSLib::Directory::Iterator &FSLib::Directory::Iterator::operator++() {
if ( ended == true )
return *this;
// skip . and ..
if ( FindNextFileW( hFind, &data ) == 0 ) {
ended = true;
} else if ( !wcscmp( data.cFileName, L"." ) ||
!wcscmp( data.cFileName, L".." ) ) {
return operator++();
}
return *this;
}
bool FSLib::Directory::Iterator::operator==( const Iterator &i_other ) const {
return i_other.ended == ended;
}