256 lines
8.3 KiB
C++
256 lines
8.3 KiB
C++
#include <iomanip>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include "../filesystem/filesystem.hpp"
|
|
#include "functions.hpp"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <codecvt>
|
|
#include <shlobj.h>
|
|
|
|
#define cout std::wcout
|
|
#define cerr std::wcerr
|
|
#define cin std::wcin
|
|
|
|
constexpr const char_t *dir_divider = L"\\";
|
|
|
|
#else // UNIX
|
|
|
|
#include <pwd.h>
|
|
#include <stdio.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
|
|
#define cout std::cout
|
|
#define cerr std::cerr
|
|
#define cin std::cin
|
|
|
|
constexpr const char_t *dir_divider = "/";
|
|
|
|
#endif // UNIX
|
|
|
|
#ifdef __APPLE__
|
|
void error( int status, int i_errno, const char *fmt, ... ) {
|
|
fprintf( stderr, "%s - ", strerror( i_errno ) );
|
|
va_list args;
|
|
va_start( args, fmt );
|
|
vfprintf( stderr, fmt, args );
|
|
va_end( args );
|
|
fputc( '\n', stderr );
|
|
exit( status );
|
|
}
|
|
#endif
|
|
|
|
#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 == '~' || 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 file contains S[0-9]+E[0-9]+, set
|
|
// season_pos to start of season number and ep_pos to start of episode number
|
|
bool searchSeason( const char_t *const path, size_t &season_pos,
|
|
size_t &ep_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++;
|
|
if ( path[cur_pos] == '\0' )
|
|
return false;
|
|
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] == '\0' )
|
|
return false;
|
|
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;
|
|
}
|
|
|
|
void iterateFS( std::map< int, std::map< int, string > > &seasons,
|
|
const string &path ) {
|
|
// season_pos - position of first digit of the season number
|
|
// ep_pos - position of first digit of the episode number
|
|
size_t season_pos{ string::npos };
|
|
size_t ep_pos{ string::npos };
|
|
if ( FSLib::isDirectory( path ) ) {
|
|
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, ep_pos ) )
|
|
seasons[std::stoi( p + season_pos )][std::stoi( p + ep_pos )] =
|
|
path + dir_divider + p;
|
|
}
|
|
} else if ( searchSeason( path.c_str(), season_pos, ep_pos ) ) {
|
|
seasons[std::stoi( path.c_str() + season_pos )][std::stoi( path.c_str() + ep_pos )] = path;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
/* 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::tuple< int, string, string, string > >
|
|
&renamed_files ) {
|
|
for ( const auto &renamed : renamed_files ) {
|
|
FSLib::rename(
|
|
std::get< 1 >( renamed ) + dir_divider + std::get< 2 >( renamed ),
|
|
std::get< 1 >( renamed ) + dir_divider + std::get< 3 >( renamed ) );
|
|
}
|
|
}
|
|
|
|
// TODO read config
|
|
const string getDefaultPattern() {
|
|
return "S%2seasonE%2episode - %epname";
|
|
}
|