2017-01-15 21:43:46 +01:00
# include "./helper.h"
# include <tagparser/mediafileinfo.h>
2017-01-23 00:27:21 +01:00
# include <tagparser/matroska/matroskatag.h>
# include <tagparser/mp4/mp4tag.h>
# include <tagparser/vorbis/vorbiscomment.h>
# include <tagparser/id3/id3v2tag.h>
2017-01-15 21:43:46 +01:00
# include <c++utilities/application/argumentparser.h>
2017-06-14 00:09:10 +02:00
# include <c++utilities/conversion/stringbuilder.h>
2017-09-22 00:19:24 +02:00
# include <c++utilities/io/ansiescapecodes.h>
2017-01-15 21:43:46 +01:00
2017-10-14 18:15:51 +02:00
# include <unistd.h>
2017-01-15 21:43:46 +01:00
# include <iostream>
2017-01-16 22:58:15 +01:00
# include <cstring>
2017-10-09 19:06:26 +02:00
# include <csignal>
2017-01-15 21:43:46 +01:00
using namespace std ;
2017-01-23 00:27:21 +01:00
using namespace std : : placeholders ;
2017-01-15 21:43:46 +01:00
using namespace ApplicationUtilities ;
using namespace ConversionUtilities ;
using namespace ChronoUtilities ;
using namespace Media ;
2017-01-16 22:58:15 +01:00
using namespace Settings ;
2017-10-09 19:06:26 +02:00
using namespace EscapeCodes ;
2017-01-15 21:43:46 +01:00
namespace Cli {
2017-11-29 22:57:32 +01:00
ChronoUtilities : : TimeSpanOutputFormat timeSpanOutputFormat = TimeSpanOutputFormat : : WithMeasures ;
2017-10-09 19:06:26 +02:00
2017-10-14 18:15:51 +02:00
/*!
* \ class InterruptHandler
* \ brief The InterruptHandler class allows to register an interrupt handler within a scope .
* \ remarks Only one instance of the class can exist at a time .
*/
2017-11-29 22:57:32 +01:00
std : : function < void ( ) > InterruptHandler : : s_handler ;
bool InterruptHandler : : s_handlerRegistered = false ;
2017-10-14 18:15:51 +02:00
/*!
* \ brief Registers the specified \ a handler for SIGINT as long as this object is alive .
* \ remarks The specified \ a handler should only call functions which are permitted to be used in signal handlers
* ( eg . use POSIX write ( ) instread of std : : cout ) .
* \ throws Throws std : : runtime_error when attempting to create a 2 nd instance .
*/
2017-10-09 19:06:26 +02:00
InterruptHandler : : InterruptHandler ( std : : function < void ( ) > handler )
{
// set handler function or throw if an instance has already been created
if ( s_handler ) {
throw runtime_error ( " Only one instance of InterruptHandler can exist at a time. " ) ;
}
s_handler = handler ;
// register handler if not registered yet
if ( ! s_handlerRegistered ) {
s_handlerRegistered = true ;
signal ( SIGINT , & InterruptHandler : : handler ) ;
}
}
2017-10-14 18:15:51 +02:00
/*!
* \ brief Unregisters the handler .
*/
2017-10-09 19:06:26 +02:00
InterruptHandler : : ~ InterruptHandler ( )
{
s_handler = function < void ( ) > ( ) ;
}
2017-10-14 18:15:51 +02:00
/*!
* \ brief Internal handler method .
*/
2017-10-09 19:06:26 +02:00
void InterruptHandler : : handler ( int signum )
{
// just exit if no custom handler has been defined
if ( ! s_handler ) {
2017-10-14 18:15:51 +02:00
_exit ( signum ) ;
2017-10-09 19:06:26 +02:00
}
2017-10-14 18:15:51 +02:00
// finalize log and print warning, prevent using std::cout which could lead to undefined behaviour
if ( ! logLineFinalized ) {
logLineFinalized = true ;
write ( STDOUT_FILENO , " \n " , 1 ) ;
}
2017-10-17 00:01:58 +02:00
if ( EscapeCodes : : enabled ) {
write ( STDOUT_FILENO , " \ e[1;33mWarning: \ e[0m \ e[1mInterrupt received, trying to abort ongoing process ... \ e[0m \n " , 84 ) ;
} else {
write ( STDOUT_FILENO , " Warning: Interrupt received, trying to abort ongoing process ... \n " , 65 ) ;
}
2017-10-09 19:06:26 +02:00
// call custom handler
s_handler ( ) ;
}
2017-01-15 21:43:46 +01:00
string incremented ( const string & str , unsigned int toIncrement )
{
string res ;
res . reserve ( str . size ( ) ) ;
unsigned int value = 0 ;
bool hasValue = false ;
for ( const char & c : str ) {
if ( toIncrement & & c > = ' 0 ' & & c < = ' 9 ' ) {
value = value * 10 + static_cast < unsigned int > ( c - ' 0 ' ) ;
hasValue = true ;
} else {
if ( hasValue ) {
res + = numberToString ( value + 1 ) ;
hasValue = false ;
- - toIncrement ;
}
res + = c ;
}
}
if ( hasValue ) {
res + = numberToString ( value + 1 ) ;
}
return res ;
}
void printNotifications ( NotificationList & notifications , const char * head , bool beVerbose )
{
2017-10-09 20:34:08 +02:00
if ( notifications . empty ( ) ) {
return ;
}
2017-01-15 21:43:46 +01:00
if ( ! beVerbose ) {
for ( const auto & notification : notifications ) {
switch ( notification . type ( ) ) {
case NotificationType : : Debug :
case NotificationType : : Information :
break ;
default :
goto printNotifications ;
}
}
return ;
}
2017-10-09 20:34:08 +02:00
2017-01-15 21:43:46 +01:00
printNotifications :
2017-10-09 20:34:08 +02:00
if ( head ) {
cout < < " - " < < head < < endl ;
}
Notification : : sortByTime ( notifications ) ;
for ( const auto & notification : notifications ) {
switch ( notification . type ( ) ) {
case NotificationType : : Debug :
if ( beVerbose ) {
cout < < " Debug " ;
2017-01-15 21:43:46 +01:00
break ;
2017-10-09 20:34:08 +02:00
} else {
continue ;
}
case NotificationType : : Information :
if ( beVerbose ) {
cout < < " Information " ;
2017-01-15 21:43:46 +01:00
break ;
2017-10-09 20:34:08 +02:00
} else {
continue ;
2017-01-15 21:43:46 +01:00
}
2017-10-09 20:34:08 +02:00
case NotificationType : : Warning :
cout < < " Warning " ;
break ;
case NotificationType : : Critical :
cout < < " Error " ;
break ;
default :
;
2017-01-15 21:43:46 +01:00
}
2017-10-09 20:34:08 +02:00
cout < < notification . creationTime ( ) . toString ( DateTimeOutputFormat : : TimeOnly ) < < " " ;
cout < < notification . context ( ) < < " : " ;
cout < < notification . message ( ) < < ' \n ' ;
2017-01-15 21:43:46 +01:00
}
}
void printNotifications ( const MediaFileInfo & fileInfo , const char * head , bool beVerbose )
{
NotificationList notifications ;
fileInfo . gatherRelatedNotifications ( notifications ) ;
printNotifications ( notifications , head , beVerbose ) ;
}
void printProperty ( const char * propName , const char * value , const char * suffix , Indentation indentation )
{
if ( * value ) {
cout < < indentation < < propName < < Indentation ( 30 - strlen ( propName ) ) < < value ;
if ( suffix ) {
cout < < ' ' < < suffix ;
}
cout < < ' \n ' ;
}
}
void printProperty ( const char * propName , ElementPosition elementPosition , const char * suffix , Indentation indentation )
{
2017-10-09 21:35:16 +02:00
const char * pos = nullptr ;
2017-01-15 21:43:46 +01:00
switch ( elementPosition ) {
case ElementPosition : : BeforeData :
pos = " before data " ;
break ;
case ElementPosition : : AfterData :
pos = " after data " ;
break ;
case ElementPosition : : Keep :
pos = nullptr ;
}
if ( pos ) {
printProperty ( propName , pos , suffix , indentation ) ;
}
}
2017-01-16 22:58:15 +01:00
void printFieldName ( const char * fieldName , size_t fieldNameLen )
{
2017-09-22 00:19:24 +02:00
cout < < " " < < fieldName ;
2017-01-16 22:58:15 +01:00
// also write padding
for ( auto i = fieldNameLen ; i < 18 ; + + i ) {
cout < < ' ' ;
}
}
2017-01-23 00:27:21 +01:00
void printField ( const FieldScope & scope , const Tag * tag , TagType tagType , bool skipEmpty )
2017-01-16 22:58:15 +01:00
{
2017-01-23 00:27:21 +01:00
// write field name
const char * fieldName = scope . field . name ( ) ;
const auto fieldNameLen = strlen ( fieldName ) ;
2017-01-16 22:58:15 +01:00
2017-01-23 00:27:21 +01:00
try {
2017-09-22 00:19:24 +02:00
// parse field denotation
2017-01-23 00:27:21 +01:00
const auto & values = scope . field . values ( tag , tagType ) ;
2017-09-22 00:19:24 +02:00
// skip empty values (unless prevented)
if ( skipEmpty & & values . empty ( ) ) {
return ;
}
// print empty value (if not prevented)
if ( values . empty ( ) ) {
printFieldName ( fieldName , fieldNameLen ) ;
cout < < " none \n " ;
return ;
}
// print values
for ( const auto & value : values ) {
printFieldName ( fieldName , fieldNameLen ) ;
try {
cout < < value - > toString ( TagTextEncoding : : Utf8 ) ;
} catch ( const ConversionException & ) {
// handle case when value can not be displayed as string
cout < < " can't display as string (see --extract) " ;
2017-01-16 22:58:15 +01:00
}
2017-09-22 00:19:24 +02:00
cout < < ' \n ' ;
2017-01-16 22:58:15 +01:00
}
2017-09-22 00:19:24 +02:00
2017-01-23 00:27:21 +01:00
} catch ( const ConversionException & e ) {
2017-09-22 00:19:24 +02:00
// handle conversion error which might happen when parsing field denotation
2017-01-23 00:27:21 +01:00
printFieldName ( fieldName , fieldNameLen ) ;
cout < < " unable to parse - " < < e . what ( ) < < ' \n ' ;
2017-01-16 22:58:15 +01:00
}
}
2017-11-29 22:57:32 +01:00
TimeSpanOutputFormat parseTimeSpanOutputFormat ( const Argument & timeSpanFormatArg , TimeSpanOutputFormat defaultFormat )
{
if ( timeSpanFormatArg . isPresent ( ) ) {
const auto & val = timeSpanFormatArg . values ( ) . front ( ) ;
if ( ! strcmp ( val , " measures " ) ) {
return TimeSpanOutputFormat : : WithMeasures ;
} else if ( ! strcmp ( val , " colons " ) ) {
return TimeSpanOutputFormat : : Normal ;
} else if ( ! strcmp ( val , " seconds " ) ) {
return TimeSpanOutputFormat : : TotalSeconds ;
} else {
cerr < < Phrases : : Warning < < " The specified time span format \" " < < val < < " \" is invalid and will be ignored. " < < Phrases : : EndFlush ;
}
}
return defaultFormat ;
}
2017-01-15 21:43:46 +01:00
TagUsage parseUsageDenotation ( const Argument & usageArg , TagUsage defaultUsage )
{
if ( usageArg . isPresent ( ) ) {
const auto & val = usageArg . values ( ) . front ( ) ;
if ( ! strcmp ( val , " never " ) ) {
return TagUsage : : Never ;
} else if ( ! strcmp ( val , " keepexisting " ) ) {
return TagUsage : : KeepExisting ;
} else if ( ! strcmp ( val , " always " ) ) {
return TagUsage : : Always ;
} else {
2017-10-09 20:34:08 +02:00
cerr < < Phrases : : Warning < < " The specified tag usage \" " < < val < < " \" is invalid and will be ignored. " < < Phrases : : EndFlush ;
2017-01-15 21:43:46 +01:00
}
}
return defaultUsage ;
}
TagTextEncoding parseEncodingDenotation ( const Argument & encodingArg , TagTextEncoding defaultEncoding )
{
if ( encodingArg . isPresent ( ) ) {
const auto & val = encodingArg . values ( ) . front ( ) ;
if ( ! strcmp ( val , " utf8 " ) ) {
return TagTextEncoding : : Utf8 ;
} else if ( ! strcmp ( val , " latin1 " ) ) {
return TagTextEncoding : : Latin1 ;
} else if ( ! strcmp ( val , " utf16be " ) ) {
return TagTextEncoding : : Utf16BigEndian ;
} else if ( ! strcmp ( val , " utf16le " ) ) {
return TagTextEncoding : : Utf16LittleEndian ;
} else if ( ! strcmp ( val , " auto " ) ) {
} else {
2017-10-09 20:34:08 +02:00
cerr < < Phrases : : Warning < < " The specified encoding \" " < < val < < " \" is invalid and will be ignored. " < < Phrases : : EndFlush ;
2017-01-15 21:43:46 +01:00
}
}
return defaultEncoding ;
}
ElementPosition parsePositionDenotation ( const Argument & posArg , const Argument & valueArg , ElementPosition defaultPos )
{
if ( posArg . isPresent ( ) ) {
const char * val = valueArg . values ( 0 ) . front ( ) ;
if ( ! strcmp ( val , " front " ) ) {
return ElementPosition : : BeforeData ;
} else if ( ! strcmp ( val , " back " ) ) {
return ElementPosition : : AfterData ;
} else if ( ! strcmp ( val , " keep " ) ) {
return ElementPosition : : Keep ;
} else {
2017-10-09 20:34:08 +02:00
cerr < < Phrases : : Warning < < " The specified position \" " < < val < < " \" is invalid and will be ignored. " < < Phrases : : EndFlush ;
2017-01-15 21:43:46 +01:00
}
}
return defaultPos ;
}
uint64 parseUInt64 ( const Argument & arg , uint64 defaultValue )
{
if ( arg . isPresent ( ) ) {
try {
if ( * arg . values ( ) . front ( ) = = ' 0 ' & & * ( arg . values ( ) . front ( ) + 1 ) = = ' x ' ) {
return stringToNumber < uint64 > ( arg . values ( ) . front ( ) + 2 , 16 ) ;
} else {
return stringToNumber < uint64 > ( arg . values ( ) . front ( ) ) ;
}
} catch ( const ConversionException & ) {
2017-10-09 20:34:08 +02:00
cerr < < Phrases : : Warning < < " The specified value \" " < < arg . values ( ) . front ( ) < < " \" is no valid unsigned integer and will be ignored. " < < Phrases : : EndFlush ;
2017-01-15 21:43:46 +01:00
}
}
return defaultValue ;
}
TagTarget : : IdContainerType parseIds ( const std : : string & concatenatedIds )
{
auto splittedIds = splitString ( concatenatedIds , " , " , EmptyPartsTreat : : Omit ) ;
TagTarget : : IdContainerType convertedIds ;
convertedIds . reserve ( splittedIds . size ( ) ) ;
for ( const auto & id : splittedIds ) {
try {
convertedIds . push_back ( stringToNumber < TagTarget : : IdType > ( id ) ) ;
} catch ( const ConversionException & ) {
2017-10-09 20:34:08 +02:00
cerr < < Phrases : : Warning < < " The specified ID \" " < < id < < " \" is invalid and will be ignored. " < < Phrases : : EndFlush ;
2017-01-15 21:43:46 +01:00
}
}
return convertedIds ;
}
bool applyTargetConfiguration ( TagTarget & target , const std : : string & configStr )
{
if ( ! configStr . empty ( ) ) {
if ( configStr . compare ( 0 , 13 , " target-level= " ) = = 0 ) {
try {
target . setLevel ( stringToNumber < uint64 > ( configStr . substr ( 13 ) ) ) ;
} catch ( const ConversionException & ) {
2017-10-09 20:34:08 +02:00
cerr < < Phrases : : Warning < < " The specified target level \" " < < configStr . substr ( 13 ) < < " \" is invalid and will be ignored. " < < Phrases : : EndFlush ;
2017-01-15 21:43:46 +01:00
}
} else if ( configStr . compare ( 0 , 17 , " target-levelname= " ) = = 0 ) {
target . setLevelName ( configStr . substr ( 17 ) ) ;
} else if ( configStr . compare ( 0 , 14 , " target-tracks= " ) = = 0 ) {
target . tracks ( ) = parseIds ( configStr . substr ( 14 ) ) ;
} else if ( configStr . compare ( 0 , 16 , " target-chapters= " ) = = 0 ) {
target . chapters ( ) = parseIds ( configStr . substr ( 16 ) ) ;
} else if ( configStr . compare ( 0 , 16 , " target-editions= " ) = = 0 ) {
target . editions ( ) = parseIds ( configStr . substr ( 16 ) ) ;
} else if ( configStr . compare ( 0 , 19 , " target-attachments= " ) = = 0 ) {
target . attachments ( ) = parseIds ( configStr . substr ( 17 ) ) ;
} else if ( configStr . compare ( 0 , 13 , " target-reset= " ) = = 0 ) {
if ( * ( configStr . data ( ) + 13 ) ) {
2017-10-09 20:34:08 +02:00
cerr < < Phrases : : Warning < < " Invalid assignment " < < ( configStr . data ( ) + 13 ) < < " for target-reset will be ignored. " < < Phrases : : EndFlush ;
2017-01-15 21:43:46 +01:00
}
target . clear ( ) ;
} else if ( configStr = = " target-reset " ) {
target . clear ( ) ;
} else {
return false ;
}
return true ;
} else {
return false ;
}
}
FieldDenotations parseFieldDenotations ( const Argument & fieldsArg , bool readOnly )
{
FieldDenotations fields ;
2017-10-09 20:34:08 +02:00
if ( ! fieldsArg . isPresent ( ) ) {
return fields ;
}
const vector < const char * > & fieldDenotations = fieldsArg . values ( ) ;
FieldScope scope ;
for ( const char * fieldDenotationString : fieldDenotations ) {
// check for tag or target specifier
const auto fieldDenotationLen = strlen ( fieldDenotationString ) ;
if ( ! strncmp ( fieldDenotationString , " tag= " , 4 ) ) {
if ( fieldDenotationLen = = 4 ) {
cerr < < Phrases : : Warning < < " The \" tag \" -specifier has been used with no value(s) and hence is ignored. Possible values are: id3,id3v1,id3v2,itunes,vorbis,matroska,all " < < Phrases : : EndFlush ;
} else {
TagType tagType = TagType : : Unspecified ;
2017-06-14 00:09:10 +02:00
bool error = false ;
2017-10-09 20:34:08 +02:00
for ( const auto & part : splitString ( fieldDenotationString + 4 , " , " , EmptyPartsTreat : : Omit ) ) {
if ( part = = " id3v1 " ) {
tagType | = TagType : : Id3v1Tag ;
} else if ( part = = " id3v2 " ) {
tagType | = TagType : : Id3v2Tag ;
} else if ( part = = " id3 " ) {
tagType | = TagType : : Id3v1Tag | TagType : : Id3v2Tag ;
} else if ( part = = " itunes " | | part = = " mp4 " ) {
tagType | = TagType : : Mp4Tag ;
} else if ( part = = " vorbis " ) {
tagType | = TagType : : VorbisComment ;
} else if ( part = = " matroska " ) {
tagType | = TagType : : MatroskaTag ;
} else if ( part = = " all " | | part = = " any " ) {
tagType = TagType : : Unspecified ;
2017-06-14 00:09:10 +02:00
break ;
} else {
2017-10-09 20:34:08 +02:00
cerr < < Phrases : : Warning < < " The value provided with the \" tag \" -specifier is invalid and will be ignored. Possible values are: id3,id3v1,id3v2,itunes,vorbis,matroska,all " < < Phrases : : EndFlush ;
error = true ;
break ;
2017-06-14 00:09:10 +02:00
}
}
if ( ! error ) {
2017-10-09 20:34:08 +02:00
scope . tagType = tagType ;
scope . allTracks = false ;
scope . trackIds . clear ( ) ;
2017-06-14 00:09:10 +02:00
}
2017-01-15 21:43:46 +01:00
}
2017-10-09 20:34:08 +02:00
continue ;
} else if ( applyTargetConfiguration ( scope . tagTarget , fieldDenotationString ) ) {
continue ;
} else if ( ! strncmp ( fieldDenotationString , " track= " , 6 ) ) {
const vector < string > parts = splitString < vector < string > > ( fieldDenotationString + 6 , " , " , EmptyPartsTreat : : Omit ) ;
bool allTracks = scope . allTracks ;
vector < uint64 > trackIds ;
trackIds . reserve ( parts . size ( ) ) ;
bool error = false ;
for ( const auto & part : parts ) {
if ( part = = " all " | | part = = " any " ) {
allTracks = true ;
2017-01-15 21:43:46 +01:00
break ;
2017-06-14 00:09:10 +02:00
} else {
2017-10-09 20:34:08 +02:00
try {
trackIds . emplace_back ( stringToNumber < uint64 > ( part ) ) ;
} catch ( const ConversionException & ) {
cerr < < Phrases : : Warning < < " The value provided with the \" track \" -specifier is invalid and will be ignored. It must be a comma-separated list of track IDs. " < < Phrases : : EndFlush ;
error = true ;
break ;
}
2017-06-14 00:09:10 +02:00
}
2017-01-15 21:43:46 +01:00
}
2017-10-09 20:34:08 +02:00
if ( ! error ) {
scope . allTracks = allTracks ;
scope . trackIds = move ( trackIds ) ;
}
continue ;
}
// check whether field name starts with + indicating an additional value
bool additionalValue = * fieldDenotationString = = ' + ' ;
if ( additionalValue ) {
+ + fieldDenotationString ;
}
// read field name
const auto equationPos = strchr ( fieldDenotationString , ' = ' ) ;
size_t fieldNameLen = equationPos ? static_cast < size_t > ( equationPos - fieldDenotationString ) : strlen ( fieldDenotationString ) ;
// field name might denote increment ("+") or path disclosure (">")
DenotationType type = DenotationType : : Normal ;
if ( fieldNameLen & & equationPos ) {
switch ( * ( equationPos - 1 ) ) {
case ' + ' :
type = DenotationType : : Increment ;
- - fieldNameLen ;
break ;
case ' > ' :
2017-01-23 00:27:21 +01:00
type = DenotationType : : File ;
2017-10-09 20:34:08 +02:00
- - fieldNameLen ;
break ;
default :
;
2017-01-23 00:27:21 +01:00
}
2017-10-09 20:34:08 +02:00
}
2017-01-23 00:27:21 +01:00
2017-10-09 20:34:08 +02:00
// field name might specify a file index
unsigned int fileIndex = 0 , mult = 1 ;
for ( const char * digitPos = fieldDenotationString + fieldNameLen - 1 ; fieldNameLen & & isDigit ( * digitPos ) ; - - fieldNameLen , - - digitPos , mult * = 10 ) {
fileIndex + = static_cast < unsigned int > ( * digitPos - ' 0 ' ) * mult ;
}
if ( ! fieldNameLen ) {
cerr < < Phrases : : Warning < < " Ignoring field denotation \" " < < fieldDenotationString < < " \" because no field name has been specified. " < < Phrases : : EndFlush ;
continue ;
}
// parse the denoted field ID
try {
if ( scope . isTrack ( ) ) {
scope . field = FieldId : : fromTrackDenotation ( fieldDenotationString , fieldNameLen ) ;
} else {
scope . field = FieldId : : fromTagDenotation ( fieldDenotationString , fieldNameLen ) ;
2017-01-15 21:43:46 +01:00
}
2017-10-09 20:34:08 +02:00
} catch ( const ConversionException & e ) {
// unable to parse field ID denotation -> discard the field denotation
cerr < < Phrases : : Warning < < " The field denotation \" " < < string ( fieldDenotationString , fieldNameLen ) < < " \" could not be parsed and will be ignored: " < < e . what ( ) < < Phrases : : EndFlush ;
continue ;
}
// read cover always from file
if ( scope . field . knownField ( ) = = KnownField : : Cover ) {
type = DenotationType : : File ;
}
// add field denotation scope
auto & fieldValues = fields [ scope ] ;
// add value to the scope (if present)
if ( equationPos ) {
if ( readOnly ) {
cerr < < Phrases : : Warning < < " Specified value for \" " < < string ( fieldDenotationString , fieldNameLen ) < < " \" will be ignored. " < < Phrases : : EndFlush ;
} else {
// file index might have been specified explicitely
// if not (mult == 1) use the index of the last value and increase it by one if the value is not an additional one
// if there are no previous values, just use the index 0
fieldValues . allValues . emplace_back ( type , mult = = 1 ? ( fieldValues . allValues . empty ( ) ? 0 : fieldValues . allValues . back ( ) . fileIndex + ( additionalValue ? 0 : 1 ) ) : fileIndex , equationPos + 1 ) ;
2017-01-15 21:43:46 +01:00
}
}
2017-10-09 20:34:08 +02:00
if ( additionalValue & & readOnly ) {
cerr < < Phrases : : Warning < < " Indication of an additional value for \" " < < string ( fieldDenotationString , fieldNameLen ) < < " \" will be ignored. " < < Phrases : : EndFlush ;
}
2017-01-15 21:43:46 +01:00
}
return fields ;
}
2017-01-23 00:27:21 +01:00
template < class ConcreteTag >
std : : vector < const TagValue * > valuesForNativeField ( const char * idString , std : : size_t idStringSize , const Tag * tag , TagType tagType )
{
if ( tagType ! = ConcreteTag : : tagType ) {
return vector < const TagValue * > ( ) ;
}
return static_cast < const ConcreteTag * > ( tag ) - > values ( ConcreteTag : : fieldType : : fieldIdFromString ( idString , idStringSize ) ) ;
}
template < class ConcreteTag >
bool setValuesForNativeField ( const char * idString , std : : size_t idStringSize , Tag * tag , TagType tagType , const std : : vector < TagValue > & values )
{
if ( tagType ! = ConcreteTag : : tagType ) {
return false ;
}
return static_cast < ConcreteTag * > ( tag ) - > setValues ( ConcreteTag : : fieldType : : fieldIdFromString ( idString , idStringSize ) , values ) ;
}
2017-06-14 00:09:10 +02:00
inline FieldId : : FieldId ( const char * nativeField , std : : size_t nativeFieldSize , const GetValuesForNativeFieldType & valuesForNativeField , const SetValuesForNativeFieldType & setValuesForNativeField ) :
2017-01-23 00:27:21 +01:00
m_knownField ( KnownField : : Invalid ) ,
2017-06-14 00:09:10 +02:00
m_nativeField ( nativeField , nativeFieldSize ) ,
2017-01-23 00:27:21 +01:00
m_valuesForNativeField ( valuesForNativeField ) ,
m_setValuesForNativeField ( setValuesForNativeField )
{ }
/// \remarks This wrapper is required because specifying c'tor template args is not possible.
template < class ConcreteTag >
FieldId FieldId : : fromNativeField ( const char * nativeFieldId , std : : size_t nativeFieldIdSize )
{
return FieldId (
2017-06-14 00:09:10 +02:00
nativeFieldId , nativeFieldIdSize ,
2017-01-23 00:27:21 +01:00
bind ( & valuesForNativeField < ConcreteTag > , nativeFieldId , nativeFieldIdSize , _1 , _2 ) ,
bind ( & setValuesForNativeField < ConcreteTag > , nativeFieldId , nativeFieldIdSize , _1 , _2 , _3 )
) ;
}
2017-06-14 00:09:10 +02:00
FieldId FieldId : : fromTagDenotation ( const char * denotation , size_t denotationSize )
2017-01-23 00:27:21 +01:00
{
// check for native, format-specific denotation
if ( ! strncmp ( denotation , " mkv: " , 4 ) ) {
return FieldId : : fromNativeField < MatroskaTag > ( denotation + 4 , denotationSize - 4 ) ;
} else if ( ! strncmp ( denotation , " mp4: " , 4 ) ) {
return FieldId : : fromNativeField < Mp4Tag > ( denotation + 4 , denotationSize - 4 ) ;
} else if ( ! strncmp ( denotation , " vorbis: " , 7 ) ) {
return FieldId : : fromNativeField < VorbisComment > ( denotation + 7 , denotationSize - 7 ) ;
} else if ( ! strncmp ( denotation , " id3: " , 7 ) ) {
return FieldId : : fromNativeField < Id3v2Tag > ( denotation + 4 , denotationSize - 4 ) ;
} else if ( ! strncmp ( denotation , " generic: " , 8 ) ) {
// allow prefix 'generic:' for consistency
denotation + = 8 , denotationSize - = 8 ;
}
// determine KnownField for generic denotation
2017-06-14 00:09:10 +02:00
static const struct {
const char * knownDenotation ;
KnownField knownField ;
} fieldMapping [ ] = {
{ " title " , KnownField : : Title } ,
{ " album " , KnownField : : Album } ,
{ " artist " , KnownField : : Artist } ,
{ " genre " , KnownField : : Genre } ,
{ " year " , KnownField : : Year } ,
{ " comment " , KnownField : : Comment } ,
{ " bpm " , KnownField : : Bpm } ,
{ " bps " , KnownField : : Bps } ,
{ " lyricist " , KnownField : : Lyricist } ,
{ " track " , KnownField : : TrackPosition } ,
{ " disk " , KnownField : : DiskPosition } ,
{ " part " , KnownField : : PartNumber } ,
{ " totalparts " , KnownField : : TotalParts } ,
{ " encoder " , KnownField : : Encoder } ,
{ " recorddate " , KnownField : : RecordDate } ,
{ " performers " , KnownField : : Performers } ,
{ " duration " , KnownField : : Length } ,
{ " language " , KnownField : : Language } ,
{ " encodersettings " , KnownField : : EncoderSettings } ,
{ " lyrics " , KnownField : : Lyrics } ,
{ " synchronizedlyrics " , KnownField : : SynchronizedLyrics } ,
{ " grouping " , KnownField : : Grouping } ,
{ " recordlabel " , KnownField : : RecordLabel } ,
{ " cover " , KnownField : : Cover } ,
{ " composer " , KnownField : : Composer } ,
{ " rating " , KnownField : : Rating } ,
{ " description " , KnownField : : Description } ,
} ;
for ( const auto & mapping : fieldMapping ) {
if ( ! strncmp ( denotation , mapping . knownDenotation , denotationSize ) ) {
return FieldId ( mapping . knownField , nullptr , 0 ) ;
}
2017-01-23 00:27:21 +01:00
}
2017-06-14 00:09:10 +02:00
throw ConversionException ( " generic field name is unknown " ) ;
}
FieldId FieldId : : fromTrackDenotation ( const char * denotation , size_t denotationSize )
{
return FieldId ( KnownField : : Invalid , denotation , denotationSize ) ;
2017-01-23 00:27:21 +01:00
}
std : : vector < const TagValue * > FieldId : : values ( const Tag * tag , TagType tagType ) const
{
2017-06-14 00:09:10 +02:00
if ( ! m_nativeField . empty ( ) ) {
2017-01-23 00:27:21 +01:00
return m_valuesForNativeField ( tag , tagType ) ;
} else {
return tag - > values ( m_knownField ) ;
}
}
bool FieldId : : setValues ( Tag * tag , TagType tagType , const std : : vector < TagValue > & values ) const
{
2017-06-14 00:09:10 +02:00
if ( ! m_nativeField . empty ( ) ) {
2017-01-23 00:27:21 +01:00
return m_setValuesForNativeField ( tag , tagType , values ) ;
} else {
return tag - > setValues ( m_knownField , values ) ;
}
}
2017-05-18 02:32:51 +02:00
string tagName ( const Tag * tag )
{
stringstream ss ;
const TagType tagType = tag - > type ( ) ;
// write tag name and target, eg. MP4/iTunes tag
ss < < tag - > typeName ( ) ;
if ( tagType = = TagType : : Id3v2Tag ) {
// version only interesting for ID3v2 tags?
ss < < " (version " < < tag - > version ( ) < < ' ) ' ;
}
if ( tagType = = TagType : : MatroskaTag | | ! tag - > target ( ) . isEmpty ( ) ) {
ss < < " targeting \" " < < tag - > targetString ( ) < < ' \" ' ;
}
return ss . str ( ) ;
}
2017-06-14 00:09:10 +02:00
bool stringToBool ( const string & str )
{
if ( str = = " yes " | | str = = " true " | | str = = " 1 " ) {
return true ;
} else if ( str = = " no " | | str = = " false " | | str = = " 0 " ) {
return false ;
}
2017-10-09 20:34:08 +02:00
throw ConversionException ( argsToString ( ' \" ' , str , " \" is not yes or no " ) ) ;
2017-06-14 00:09:10 +02:00
}
2017-09-22 00:19:24 +02:00
bool logLineFinalized = true ;
2017-10-09 19:03:54 +02:00
static string lastLoggedStatus ;
2017-09-22 00:19:24 +02:00
void logStatus ( const StatusProvider & statusProvider )
{
2017-10-09 19:03:54 +02:00
if ( statusProvider . currentStatus ( ) ! = lastLoggedStatus ) {
2017-09-22 00:19:24 +02:00
// the ongoing operation ("status") has changed
// -> finalize previous line and make new line
if ( ! logLineFinalized ) {
2017-10-09 19:03:54 +02:00
cout < < " \r - [100%] " < < lastLoggedStatus < < endl ;
2017-09-22 00:19:24 +02:00
logLineFinalized = true ;
}
// -> update lastStatus
2017-10-09 19:03:54 +02:00
lastLoggedStatus = statusProvider . currentStatus ( ) ;
2017-09-22 00:19:24 +02:00
}
// update current line if an operation is ongoing (status is not empty)
2017-10-09 19:03:54 +02:00
if ( ! lastLoggedStatus . empty ( ) ) {
2017-09-22 00:19:24 +02:00
int percentage = static_cast < int > ( statusProvider . currentPercentage ( ) * 100 ) ;
if ( percentage < 0 ) {
percentage = 0 ;
}
2017-10-09 19:03:54 +02:00
cout < < " \r - [ " < < setw ( 3 ) < < percentage < < " %] " < < lastLoggedStatus < < flush ;
2017-09-22 00:19:24 +02:00
logLineFinalized = false ;
}
}
void finalizeLog ( )
{
2017-10-09 20:34:08 +02:00
if ( logLineFinalized ) {
return ;
2017-09-22 00:19:24 +02:00
}
2017-10-09 20:34:08 +02:00
cout < < ' \n ' ;
logLineFinalized = true ;
lastLoggedStatus . clear ( ) ;
2017-09-22 00:19:24 +02:00
}
2017-01-15 21:43:46 +01:00
}