Compare commits
6 Commits
master
...
windows_bi
Author | SHA1 | Date | |
---|---|---|---|
ccb5abcd41 | |||
c6f64a7839 | |||
917912b250 | |||
306ee0d4e5 | |||
aac6b93668 | |||
00efca3df9 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
*.o
|
||||
tv_rename
|
||||
tv_rename_gui
|
||||
test
|
24
LICENSE
24
LICENSE
@ -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>
|
85
Makefile
85
Makefile
@ -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
|
66
README.md
66
README.md
@ -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`
|
127
filesystem.hpp
127
filesystem.hpp
@ -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
|
560
functions.cpp
560
functions.cpp
@ -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;
|
||||
}
|
@ -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
12
gui.cpp
@ -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
262
main.cpp
@ -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 );
|
||||
}
|
||||
}
|
380
mainwindow.cpp
380
mainwindow.cpp
@ -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();
|
||||
}
|
@ -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
|
83
network.cpp
83
network.cpp
@ -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
|
34
network.hpp
34
network.hpp
@ -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
|
50
resource.h
50
resource.h
@ -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
|
@ -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();
|
||||
}
|
@ -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
tv_rename-2.0.1.exe
Executable file
BIN
tv_rename-2.0.1.exe
Executable file
Binary file not shown.
BIN
tv_rename-2.0.exe
Executable file
BIN
tv_rename-2.0.exe
Executable file
Binary file not shown.
284
tv_rename.cpp
284
tv_rename.cpp
@ -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
|
BIN
tv_rename.exe
Executable file
BIN
tv_rename.exe
Executable file
Binary file not shown.
@ -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
|
120
tv_rename.svg
120
tv_rename.svg
@ -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 |
BIN
tv_rename_cli-2.0.1.exe
Executable file
BIN
tv_rename_cli-2.0.1.exe
Executable file
Binary file not shown.
BIN
tv_rename_cli-2.0.2.exe
Executable file
BIN
tv_rename_cli-2.0.2.exe
Executable file
Binary file not shown.
BIN
tv_rename_cli-2.0.exe
Executable file
BIN
tv_rename_cli-2.0.exe
Executable file
Binary file not shown.
BIN
tv_rename_cli.exe
Executable file
BIN
tv_rename_cli.exe
Executable file
Binary file not shown.
@ -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 );
|
||||
}
|
@ -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 |
BIN
tv_rename_gui.rc
BIN
tv_rename_gui.rc
Binary file not shown.
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user