2015-09-06 20:20:00 +02:00
# include "./mainfeatures.h"
2015-09-06 15:41:17 +02:00
2015-09-06 20:20:00 +02:00
# include "../application/knownfieldmodel.h"
# include "../misc/utility.h"
# include "../misc/htmlinfo.h"
2015-04-22 19:33:53 +02:00
# include <tagparser/mediafileinfo.h>
# include <tagparser/tag.h>
2015-08-09 23:53:45 +02:00
# include <tagparser/abstracttrack.h>
# include <tagparser/abstractattachment.h>
# include <tagparser/abstractchapter.h>
2015-04-22 19:33:53 +02:00
# include <c++utilities/application/failure.h>
2015-09-01 20:20:15 +02:00
# include <c++utilities/application/commandlineutils.h>
2015-04-22 19:33:53 +02:00
# include <c++utilities/conversion/stringconversion.h>
# include <c++utilities/conversion/conversionexception.h>
# include <c++utilities/io/ansiescapecodes.h>
# include <c++utilities/misc/memory.h>
# include <QDir>
# include <iostream>
using namespace std ;
using namespace ApplicationUtilities ;
using namespace ConversionUtilities ;
using namespace ChronoUtilities ;
using namespace EscapeCodes ;
using namespace Utility ;
using namespace Settings ;
using namespace Media ;
namespace Cli {
enum class DenotationType
{
Normal ,
Increment ,
File
} ;
inline TagType operator | ( TagType lhs , TagType rhs )
{
return static_cast < TagType > ( static_cast < unsigned int > ( lhs ) | static_cast < unsigned int > ( rhs ) ) ;
}
inline TagType & operator | = ( TagType & lhs , TagType rhs )
{
return lhs = static_cast < TagType > ( static_cast < unsigned int > ( lhs ) | static_cast < unsigned int > ( rhs ) ) ;
}
struct FieldDenotation
{
2015-10-13 23:21:31 +02:00
FieldDenotation ( KnownField field ) ;
KnownField field ;
2015-04-22 19:33:53 +02:00
DenotationType type ;
TagType tagType ;
TagTarget tagTarget ;
std : : vector < std : : pair < unsigned int , QString > > values ;
} ;
2015-10-13 23:21:31 +02:00
FieldDenotation : : FieldDenotation ( KnownField field ) :
field ( field ) ,
2015-04-22 19:33:53 +02:00
type ( DenotationType : : Normal ) ,
tagType ( TagType : : Unspecified )
{ }
inline bool isDigit ( char c )
{
return c > = ' 0 ' & & c < = ' 9 ' ;
}
QString incremented ( const QString & str , unsigned int toIncrement = 1 )
{
QString res ;
res . reserve ( str . size ( ) ) ;
unsigned int value = 0 ;
bool hasValue = false ;
for ( const QChar & c : str ) {
if ( toIncrement & & c . isDigit ( ) ) {
value = value * 10 + static_cast < unsigned int > ( c . digitValue ( ) ) ;
hasValue = true ;
} else {
if ( hasValue ) {
res . append ( QString : : number ( value + 1 ) ) ;
hasValue = false ;
- - toIncrement ;
}
res . append ( c ) ;
}
}
return res ;
}
2015-10-06 22:41:02 +02:00
void printNotifications ( NotificationList & notifications , const char * head = nullptr , bool beVerbose = false )
2015-04-22 19:33:53 +02:00
{
2015-09-23 00:02:06 +02:00
if ( ! beVerbose ) {
for ( const auto & notification : notifications ) {
switch ( notification . type ( ) ) {
case NotificationType : : Debug :
case NotificationType : : Information :
break ;
default :
goto printNotifications ;
}
}
return ;
}
2015-04-22 19:33:53 +02:00
if ( ! notifications . empty ( ) ) {
2015-09-23 00:02:06 +02:00
printNotifications :
2015-04-22 19:33:53 +02:00
if ( head ) {
cout < < head < < endl ;
}
Notification : : sortByTime ( notifications ) ;
2015-09-23 00:02:06 +02:00
for ( const auto & notification : notifications ) {
2015-04-22 19:33:53 +02:00
switch ( notification . type ( ) ) {
case NotificationType : : Debug :
2015-09-23 00:02:06 +02:00
if ( beVerbose ) {
cout < < " Debug " ;
2015-10-06 22:41:02 +02:00
break ;
} else {
continue ;
2015-09-23 00:02:06 +02:00
}
case NotificationType : : Information :
if ( beVerbose ) {
cout < < " Information " ;
2015-10-06 22:41:02 +02:00
break ;
} else {
continue ;
2015-09-23 00:02:06 +02:00
}
2015-04-22 19:33:53 +02:00
case NotificationType : : Warning :
cout < < " Warning " ;
break ;
2015-09-23 00:02:06 +02:00
case NotificationType : : Critical :
cout < < " Error " ;
break ;
2015-04-22 19:33:53 +02:00
default :
2015-09-23 00:02:06 +02:00
;
2015-04-22 19:33:53 +02:00
}
cout < < notification . creationTime ( ) . toString ( DateTimeOutputFormat : : TimeOnly ) < < " " ;
cout < < notification . context ( ) < < " : " ;
cout < < notification . message ( ) < < endl ;
}
}
}
2015-10-06 22:41:02 +02:00
void printNotifications ( const MediaFileInfo & fileInfo , const char * head = nullptr , bool beVerbose = false )
{
NotificationList notifications ;
fileInfo . gatherRelatedNotifications ( notifications ) ;
printNotifications ( notifications , head , beVerbose ) ;
}
2015-04-22 19:33:53 +02:00
void printFieldNames ( const StringVector & parameterValues )
{
2015-09-01 20:20:15 +02:00
CMD_UTILS_START_CONSOLE ;
2015-04-22 19:33:53 +02:00
VAR_UNUSED ( parameterValues )
cout < < " title album artist genre year comment bpm bps lyricist track disk part totalparts encoder \n "
" recorddate performers duration language encodersettings lyrics synchronizedlyrics grouping \n "
" recordlabel cover composer rating description " < < endl ;
}
void removeBackupFiles ( const StringVector & parameterValues , const Argument & recursiveArg )
{
2015-09-01 20:20:15 +02:00
CMD_UTILS_START_CONSOLE ;
2015-04-22 19:33:53 +02:00
QDir dir ( QString : : fromStdString ( parameterValues . at ( 0 ) ) ) ;
QStringList affectedFiles ;
int filesFound = Utility : : removeBackupFiles ( dir , affectedFiles , & cout , recursiveArg . isPresent ( ) ) ;
cout < < affectedFiles . size ( ) < < " of " < < filesFound < < " backup files have been removed. " < < endl ;
}
TagUsage parseUsageDenotation ( const Argument & usageArg , TagUsage defaultUsage )
{
if ( usageArg . isPresent ( ) ) {
const auto & val = usageArg . values ( ) . front ( ) ;
if ( val = = " never " ) {
return TagUsage : : Never ;
} else if ( val = = " keepexisting " ) {
return TagUsage : : KeepExisting ;
} else if ( val = = " always " ) {
return TagUsage : : Always ;
} else {
cout < < " Warning: The specified tag usage \" " < < val < < " \" is invalid and will be ignored. " < < endl ;
}
}
return defaultUsage ;
}
TagTextEncoding parseEncodingDenotation ( const Argument & encodingArg , TagTextEncoding defaultEncoding )
{
if ( encodingArg . isPresent ( ) ) {
const auto & val = encodingArg . values ( ) . front ( ) ;
if ( val = = " utf8 " ) {
return TagTextEncoding : : Utf8 ;
} else if ( val = = " latin1 " ) {
return TagTextEncoding : : Latin1 ;
} else if ( val = = " utf16be " ) {
return TagTextEncoding : : Utf16BigEndian ;
} else if ( val = = " utf16le " ) {
return TagTextEncoding : : Utf16LittleEndian ;
} else if ( val = = " auto " ) {
} else {
cout < < " Warning: The specified encoding \" " < < val < < " \" is invalid and will be ignored. " < < endl ;
}
}
return defaultEncoding ;
}
2015-11-28 00:20:49 +01:00
ElementPosition parsePositionDenotation ( const Argument & posArg , ElementPosition defaultPos )
{
if ( posArg . isPresent ( ) ) {
const auto & val = posArg . values ( ) . front ( ) ;
if ( val = = " front " ) {
return ElementPosition : : BeforeData ;
} else if ( val = = " back " ) {
return ElementPosition : : AfterData ;
} else if ( val = = " keep " ) {
return ElementPosition : : Keep ;
} else {
cout < < " Warning: The specified position \" " < < val < < " \" is invalid and will be ignored. " < < endl ;
}
}
return defaultPos ;
}
uint64 parseUInt64 ( const Argument & arg , uint64 defaultValue )
{
if ( arg . isPresent ( ) ) {
try {
if ( startsWith < string > ( arg . values ( ) . front ( ) , " 0x " ) ) {
return stringToNumber < decltype ( parseUInt64 ( arg , defaultValue ) ) > ( arg . values ( ) . front ( ) . substr ( 2 ) , 16 ) ;
} else {
return stringToNumber < decltype ( parseUInt64 ( arg , defaultValue ) ) > ( arg . values ( ) . front ( ) ) ;
}
} catch ( const ConversionException & ) {
cout < < " Warning: The specified value \" " < < arg . values ( ) . front ( ) < < " \" is no valid unsigned integer and will be ignored. " < < endl ;
}
}
return defaultValue ;
}
2015-04-22 19:33:53 +02:00
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 ) ) ;
2015-11-28 00:20:49 +01:00
} catch ( const ConversionException & ) {
2015-04-22 19:33:53 +02:00
cout < < " Warning: The specified ID \" " < < id < < " \" is invalid and will be ignored. " < < endl ;
}
}
return convertedIds ;
}
bool applyTargetConfiguration ( TagTarget & target , const std : : string & configStr )
{
2015-10-13 23:21:31 +02:00
if ( ! configStr . empty ( ) ) {
if ( configStr . compare ( 0 , 13 , " target-level= " ) = = 0 ) {
try {
target . setLevel ( stringToNumber < uint64 > ( configStr . substr ( 13 ) ) ) ;
2015-11-28 00:20:49 +01:00
} catch ( const ConversionException & ) {
2015-10-13 23:21:31 +02:00
cout < < " Warning: The specified target level \" " < < configStr . substr ( 13 ) < < " \" is invalid and will be ignored. " < < endl ;
}
} 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 , 17 , " target-attachments= " ) = = 0 ) {
target . attachments ( ) = parseIds ( configStr . substr ( 17 ) ) ;
} else if ( configStr = = " target-reset " ) {
target . clear ( ) ;
} else {
return false ;
}
return true ;
2015-04-22 19:33:53 +02:00
} else {
return false ;
}
}
2015-10-13 23:21:31 +02:00
vector < FieldDenotation > parseFieldDenotations ( const StringVector & fieldDenotations , bool readOnly )
2015-04-22 19:33:53 +02:00
{
2015-10-13 23:21:31 +02:00
vector < FieldDenotation > fields ;
fields . reserve ( fieldDenotations . size ( ) ) ;
2015-04-22 19:33:53 +02:00
TagType currentTagType = TagType : : Unspecified ;
TagTarget currentTagTarget ;
for ( const string & fieldDenotationString : fieldDenotations ) {
// check for tag or target specifier
if ( strncmp ( fieldDenotationString . c_str ( ) , " tag: " , 4 ) = = 0 ) {
if ( fieldDenotationString . size ( ) = = 4 ) {
cout < < " 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 " < < endl ;
} else {
TagType tagType = TagType : : Unspecified ;
for ( const auto & part : splitString ( fieldDenotationString . substr ( 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 ;
break ;
} else {
cout < < " Warning: The value provided with the \" tag \" -specifier is invalid and will be ignored. Possible values are: id3,id3v1,id3v2,itunes,vorbis,matroska,all " < < endl ;
tagType = currentTagType ;
break ;
}
}
currentTagType = tagType ;
break ;
}
} else if ( applyTargetConfiguration ( currentTagTarget , fieldDenotationString ) ) {
continue ;
}
// read field name
auto equationPos = fieldDenotationString . find ( ' = ' ) ;
auto fieldName = equationPos ! = string : : npos ? fieldDenotationString . substr ( 0 , equationPos ) : fieldDenotationString ;
// field name might denote increment ("+") or path disclosure (">")
auto fieldNamePos = fieldName . size ( ) ;
DenotationType type = DenotationType : : Normal ;
if ( fieldNamePos ) {
switch ( fieldName . at ( - - fieldNamePos ) ) {
case ' + ' :
type = DenotationType : : Increment ;
- - fieldNamePos ;
break ;
case ' > ' :
type = DenotationType : : File ;
- - fieldNamePos ;
break ;
default :
;
}
}
// field name might specify a file index
unsigned int fileIndex = 0 , mult = 1 ;
for ( ; fieldNamePos ! = static_cast < string : : size_type > ( - 1 ) & & isDigit ( fieldName . at ( fieldNamePos ) ) ; - - fieldNamePos , mult * = 10 ) {
fileIndex + = static_cast < unsigned int > ( fieldName . at ( fieldNamePos ) - ' 0 ' ) * mult ;
}
if ( fieldNamePos = = static_cast < string : : size_type > ( - 1 ) ) {
cout < < " Warning: Ignoring field denotation \" " < < fieldDenotationString < < " \" because no field name has been specified. " < < endl ;
continue ;
} else if ( + + fieldNamePos < fieldName . size ( ) ) {
fieldName = fieldName . substr ( 0 , fieldNamePos ) ;
}
// parse the denoted filed
KnownField field ;
if ( fieldName = = " title " ) {
field = KnownField : : Title ;
} else if ( fieldName = = " album " ) {
field = KnownField : : Album ;
} else if ( fieldName = = " artist " ) {
field = KnownField : : Artist ;
} else if ( fieldName = = " genre " ) {
field = KnownField : : Genre ;
} else if ( fieldName = = " year " ) {
field = KnownField : : Year ;
} else if ( fieldName = = " comment " ) {
field = KnownField : : Comment ;
} else if ( fieldName = = " bpm " ) {
field = KnownField : : Bpm ;
} else if ( fieldName = = " bps " ) {
field = KnownField : : Bps ;
} else if ( fieldName = = " lyricist " ) {
field = KnownField : : Lyricist ;
} else if ( fieldName = = " track " ) {
field = KnownField : : TrackPosition ;
} else if ( fieldName = = " disk " ) {
field = KnownField : : DiskPosition ;
} else if ( fieldName = = " part " ) {
field = KnownField : : PartNumber ;
} else if ( fieldName = = " totalparts " ) {
field = KnownField : : TotalParts ;
} else if ( fieldName = = " encoder " ) {
field = KnownField : : Encoder ;
} else if ( fieldName = = " recorddate " ) {
field = KnownField : : RecordDate ;
} else if ( fieldName = = " performers " ) {
field = KnownField : : Performers ;
} else if ( fieldName = = " duration " ) {
field = KnownField : : Length ;
} else if ( fieldName = = " language " ) {
field = KnownField : : Language ;
} else if ( fieldName = = " encodersettings " ) {
field = KnownField : : EncoderSettings ;
} else if ( fieldName = = " lyrics " ) {
field = KnownField : : Lyrics ;
} else if ( fieldName = = " synchronizedlyrics " ) {
field = KnownField : : SynchronizedLyrics ;
} else if ( fieldName = = " grouping " ) {
field = KnownField : : Grouping ;
} else if ( fieldName = = " recordlabel " ) {
field = KnownField : : RecordLabel ;
} else if ( fieldName = = " cover " ) {
field = KnownField : : Cover ;
type = DenotationType : : File ; // read cover always from file
} else if ( fieldName = = " composer " ) {
field = KnownField : : Composer ;
} else if ( fieldName = = " rating " ) {
field = KnownField : : Rating ;
} else if ( fieldName = = " description " ) {
field = KnownField : : Description ;
} else {
// no "KnownField" value matching -> discard the field denotation
cout < < " The field name \" " < < fieldName < < " \" is unknown and will be ingored. " < < endl ;
continue ;
}
// add field denotation with parsed values
2015-10-13 23:21:31 +02:00
fields . emplace_back ( field ) ;
FieldDenotation & fieldDenotation = fields . back ( ) ;
2015-04-22 19:33:53 +02:00
fieldDenotation . type = type ;
fieldDenotation . tagType = currentTagType ;
fieldDenotation . tagTarget = currentTagTarget ;
if ( equationPos ! = string : : npos ) {
if ( readOnly ) {
cout < < " Warning: Specified value for \" " < < fieldName < < " \" will be ignored. " < < endl ;
} else {
2015-10-13 23:21:31 +02:00
fieldDenotation . values . emplace_back ( make_pair ( mult = = 1 ? fieldDenotation . values . size ( ) : fileIndex , QString : : fromLocal8Bit ( fieldDenotationString . data ( ) + equationPos + 1 ) ) ) ;
2015-04-22 19:33:53 +02:00
}
}
}
2015-10-13 23:21:31 +02:00
return fields ;
2015-04-22 19:33:53 +02:00
}
2015-10-06 22:41:02 +02:00
enum class AttachmentAction {
Add ,
UpdateById ,
UpdateByName ,
RemoveById ,
RemoveByName
} ;
class AttachmentInfo
{
public :
AttachmentInfo ( ) ;
void apply ( AbstractContainer * container ) ;
void apply ( AbstractAttachment * attachment ) ;
void reset ( ) ;
bool next ( AbstractContainer * container ) ;
AttachmentAction action ;
uint64 id ;
string path ;
string name ;
string mime ;
string desc ;
} ;
AttachmentInfo : : AttachmentInfo ( ) :
action ( AttachmentAction : : Add ) ,
id ( 0 )
{ }
void AttachmentInfo : : apply ( AbstractContainer * container )
{
static const string context ( " applying specified attachments " ) ;
AbstractAttachment * attachment = nullptr ;
bool attachmentFound = false ;
switch ( action ) {
case AttachmentAction : : Add :
if ( path . empty ( ) | | name . empty ( ) ) {
container - > addNotification ( NotificationType : : Critical , " No name or path specified for new attachment to be added. " , context ) ;
return ;
}
apply ( container - > createAttachment ( ) ) ;
break ;
case AttachmentAction : : UpdateById :
for ( size_t i = 0 , count = container - > attachmentCount ( ) ; i < count ; + + i ) {
attachment = container - > attachment ( i ) ;
if ( attachment - > id ( ) = = id ) {
apply ( attachment ) ;
attachmentFound = true ;
}
}
if ( ! attachmentFound = = true ) {
container - > addNotification ( NotificationType : : Critical , " Attachment with the specified ID \" " + numberToString ( id ) + " \" does not exist and hence can't be updated. " , context ) ;
}
break ;
case AttachmentAction : : UpdateByName :
for ( size_t i = 0 , count = container - > attachmentCount ( ) ; i < count ; + + i ) {
attachment = container - > attachment ( i ) ;
if ( attachment - > name ( ) = = name ) {
apply ( attachment ) ;
attachmentFound = true ;
}
}
if ( ! attachmentFound = = true ) {
container - > addNotification ( NotificationType : : Critical , " Attachment with the specified name \" " + name + " \" does not exist and hence can't be updated. " , context ) ;
}
break ;
case AttachmentAction : : RemoveById :
for ( size_t i = 0 , count = container - > attachmentCount ( ) ; i < count ; + + i ) {
attachment = container - > attachment ( i ) ;
if ( attachment - > id ( ) = = id ) {
attachment - > setIgnored ( true ) ;
attachmentFound = true ;
}
}
if ( ! attachmentFound = = true ) {
container - > addNotification ( NotificationType : : Critical , " Attachment with the specified ID \" " + numberToString ( id ) + " \" does not exist and hence can't be removed. " , context ) ;
}
break ;
case AttachmentAction : : RemoveByName :
for ( size_t i = 0 , count = container - > attachmentCount ( ) ; i < count ; + + i ) {
attachment = container - > attachment ( i ) ;
if ( attachment - > name ( ) = = name ) {
attachment - > setIgnored ( true ) ;
attachmentFound = true ;
}
}
if ( ! attachmentFound = = true ) {
container - > addNotification ( NotificationType : : Critical , " Attachment with the specified name \" " + name + " \" does not exist and hence can't be removed. " , context ) ;
}
break ;
}
}
void AttachmentInfo : : apply ( AbstractAttachment * attachment )
{
if ( id ) {
attachment - > setId ( id ) ;
}
if ( ! path . empty ( ) ) {
attachment - > setFile ( path ) ;
}
if ( ! name . empty ( ) ) {
attachment - > setName ( name ) ;
}
if ( ! mime . empty ( ) ) {
attachment - > setMimeType ( mime ) ;
}
if ( ! desc . empty ( ) ) {
attachment - > setDescription ( desc ) ;
}
}
void AttachmentInfo : : reset ( )
{
action = AttachmentAction : : Add ;
id = 0 ;
path . clear ( ) ;
name . clear ( ) ;
mime . clear ( ) ;
desc . clear ( ) ;
}
bool AttachmentInfo : : next ( AbstractContainer * container )
{
if ( ! id & & path . empty ( ) & & name . empty ( ) & & mime . empty ( ) & & desc . empty ( ) ) {
// skip empty attachment infos
return false ;
}
apply ( container ) ;
reset ( ) ;
return true ;
}
2015-04-22 19:33:53 +02:00
void generateFileInfo ( const StringVector & parameterValues , const Argument & inputFileArg , const Argument & outputFileArg , const Argument & validateArg )
{
2015-09-01 20:20:15 +02:00
CMD_UTILS_START_CONSOLE ;
VAR_UNUSED ( parameterValues )
2015-04-22 19:33:53 +02:00
try {
// parse tags
MediaFileInfo inputFileInfo ( inputFileArg . values ( ) . front ( ) ) ;
inputFileInfo . setForceFullParse ( validateArg . isPresent ( ) ) ;
inputFileInfo . open ( true ) ;
inputFileInfo . parseEverything ( ) ;
cout < < " Saving file info of \" " < < inputFileArg . values ( ) . front ( ) < < " \" ... " < < endl ;
NotificationList origNotify ;
QFile file ( QString : : fromLocal8Bit ( outputFileArg . values ( ) . front ( ) . c_str ( ) ) ) ;
if ( file . open ( QFile : : WriteOnly ) & & file . write ( HtmlInfo : : generateInfo ( inputFileInfo , origNotify ) ) & & file . flush ( ) ) {
cout < < " File information has been saved to \" " < < outputFileArg . values ( ) . front ( ) < < " \" . " < < endl ;
} else {
cout < < " Error: An IO error occured when writing the file \" " < < outputFileArg . values ( ) . front ( ) < < " \" . " < < endl ;
}
} catch ( ios_base : : failure & ) {
cout < < " Error: An IO failure occured when reading the file \" " < < inputFileArg . values ( ) . front ( ) < < " \" . " < < endl ;
} catch ( ApplicationUtilities : : Failure & ) {
cout < < " Error: A parsing failure occured when reading the file \" " < < inputFileArg . values ( ) . front ( ) < < " \" . " < < endl ;
}
}
2015-08-09 23:53:45 +02:00
void printProperty ( const char * propName , const char * value , const char * suffix = nullptr , size_t intention = 4 )
{
if ( * value ) {
for ( ; intention ; - - intention ) {
cout < < ' ' ;
}
cout < < propName ;
2015-09-24 01:15:48 +02:00
for ( intention = strlen ( propName ) ; intention < 30 ; + + intention ) {
2015-08-09 23:53:45 +02:00
cout < < ' ' ;
}
cout < < value ;
if ( suffix ) {
cout < < ' ' < < suffix ;
}
cout < < endl ;
}
}
void printProperty ( const char * propName , const string & value , const char * suffix = nullptr , size_t intention = 4 )
{
printProperty ( propName , value . data ( ) , suffix , intention ) ;
}
2015-10-14 19:49:48 +02:00
void printProperty ( const char * propName , TimeSpan timeSpan , const char * suffix = nullptr , size_t intention = 4 )
{
if ( ! timeSpan . isNull ( ) ) {
printProperty ( propName , timeSpan . toString ( TimeSpanOutputFormat : : WithMeasures ) , suffix , intention ) ;
}
}
void printProperty ( const char * propName , DateTime dateTime , const char * suffix = nullptr , size_t intention = 4 )
{
if ( ! dateTime . isNull ( ) ) {
printProperty ( propName , dateTime . toString ( ) , suffix , intention ) ;
}
}
2015-08-09 23:53:45 +02:00
template < typename intType >
void printProperty ( const char * propName , const intType value , const char * suffix = nullptr , bool force = false , size_t intention = 4 )
{
if ( value ! = 0 | | force ) {
printProperty ( propName , numberToString < intType > ( value ) , suffix , intention ) ;
}
}
2015-09-23 00:02:06 +02:00
void displayFileInfo ( const StringVector & , const Argument & filesArg , const Argument & verboseArg )
2015-08-09 23:53:45 +02:00
{
2015-09-01 20:20:15 +02:00
CMD_UTILS_START_CONSOLE ;
2015-08-09 23:53:45 +02:00
if ( ! filesArg . valueCount ( ) ) {
cout < < " Error: No files have been specified. " < < endl ;
return ;
}
MediaFileInfo fileInfo ;
for ( const auto & file : filesArg . values ( ) ) {
try {
// parse tags
fileInfo . setPath ( file ) ;
fileInfo . open ( true ) ;
fileInfo . parseTracks ( ) ;
fileInfo . parseAttachments ( ) ;
fileInfo . parseChapters ( ) ;
cout < < " Technical information for \" " < < file < < " \" : " < < endl ;
cout < < " Container format: " < < fileInfo . containerFormatName ( ) < < endl ;
2015-10-14 19:49:48 +02:00
{
if ( const auto container = fileInfo . container ( ) ) {
size_t segmentIndex = 0 ;
for ( const auto & title : container - > titles ( ) ) {
if ( segmentIndex ) {
printProperty ( " Title " , title + " (segment " + numberToString ( + + segmentIndex ) + " ) " ) ;
} else {
+ + segmentIndex ;
printProperty ( " Title " , title ) ;
}
}
printProperty ( " Document type " , container - > documentType ( ) ) ;
printProperty ( " Read version " , container - > readVersion ( ) ) ;
printProperty ( " Version " , container - > version ( ) ) ;
printProperty ( " Document read version " , container - > doctypeReadVersion ( ) ) ;
printProperty ( " Document version " , container - > doctypeVersion ( ) ) ;
printProperty ( " Duration " , container - > duration ( ) ) ;
printProperty ( " Creation time " , container - > creationTime ( ) ) ;
printProperty ( " Modification time " , container - > modificationTime ( ) ) ;
}
if ( fileInfo . paddingSize ( ) ) {
printProperty ( " Padding " , dataSizeToString ( fileInfo . paddingSize ( ) ) ) ;
}
}
2015-08-09 23:53:45 +02:00
{ // tracks
const auto tracks = fileInfo . tracks ( ) ;
if ( ! tracks . empty ( ) ) {
cout < < " Tracks: " < < endl ;
for ( const auto * track : tracks ) {
printProperty ( " ID " , track - > id ( ) , nullptr , true ) ;
printProperty ( " Name " , track - > name ( ) ) ;
printProperty ( " Type " , track - > mediaTypeName ( ) ) ;
2015-09-24 01:15:48 +02:00
const char * fmtName = track - > formatName ( ) , * fmtAbbr = track - > formatAbbreviation ( ) ;
printProperty ( " Format " , fmtName ) ;
if ( strcmp ( fmtName , fmtAbbr ) ) {
printProperty ( " Abbreviation " , fmtAbbr ) ;
}
2015-09-24 00:19:53 +02:00
printProperty ( " Extensions " , track - > format ( ) . extensionName ( ) ) ;
2015-08-09 23:53:45 +02:00
printProperty ( " Raw format ID " , track - > formatId ( ) ) ;
if ( track - > size ( ) ) {
printProperty ( " Size " , dataSizeToString ( track - > size ( ) , true ) ) ;
}
2015-10-14 19:49:48 +02:00
printProperty ( " Duration " , track - > duration ( ) ) ;
2015-08-09 23:53:45 +02:00
printProperty ( " FPS " , track - > fps ( ) ) ;
if ( track - > channelConfigString ( ) ) {
printProperty ( " Channel config " , track - > channelConfigString ( ) ) ;
} else {
printProperty ( " Channel count " , track - > channelCount ( ) ) ;
}
2015-09-24 01:15:48 +02:00
if ( track - > extensionChannelConfigString ( ) ) {
printProperty ( " Extension channel config " , track - > extensionChannelConfigString ( ) ) ;
}
2015-08-09 23:53:45 +02:00
printProperty ( " Bitrate " , track - > bitrate ( ) , " kbit/s " ) ;
printProperty ( " Bits per sample " , track - > bitsPerSample ( ) ) ;
2015-09-24 01:15:48 +02:00
printProperty ( " Sampling frequency " , track - > samplingFrequency ( ) , " Hz " ) ;
2015-08-09 23:53:45 +02:00
printProperty ( " Extension sampling frequency " , track - > extensionSamplingFrequency ( ) , " Hz " ) ;
printProperty ( " Sample count " , track - > sampleCount ( ) ) ;
2015-10-14 19:49:48 +02:00
printProperty ( " Creation time " , track - > creationTime ( ) ) ;
printProperty ( " Modification time " , track - > modificationTime ( ) ) ;
2015-08-09 23:53:45 +02:00
cout < < endl ;
}
} else {
cout < < " File has no (supported) tracks. " < < endl ;
}
}
{ // attachments
const auto attachments = fileInfo . attachments ( ) ;
if ( ! attachments . empty ( ) ) {
for ( const auto * attachment : attachments ) {
printProperty ( " ID " , attachment - > id ( ) ) ;
printProperty ( " Name " , attachment - > name ( ) ) ;
printProperty ( " MIME-type " , attachment - > mimeType ( ) ) ;
printProperty ( " Label " , attachment - > label ( ) ) ;
printProperty ( " Description " , attachment - > description ( ) ) ;
if ( attachment - > data ( ) ) {
printProperty ( " Size " , dataSizeToString ( attachment - > data ( ) - > size ( ) , true ) ) ;
}
cout < < endl ;
}
}
}
{ // chapters
const auto chapters = fileInfo . chapters ( ) ;
if ( ! chapters . empty ( ) ) {
2015-10-06 22:41:02 +02:00
for ( const auto * chapter : chapters ) {
2015-08-09 23:53:45 +02:00
printProperty ( " ID " , chapter - > id ( ) ) ;
if ( ! chapter - > names ( ) . empty ( ) ) {
printProperty ( " Name " , static_cast < string > ( chapter - > names ( ) . front ( ) ) ) ;
}
if ( ! chapter - > startTime ( ) . isNull ( ) ) {
printProperty ( " Start time " , chapter - > startTime ( ) . toString ( ) ) ;
}
if ( ! chapter - > endTime ( ) . isNull ( ) ) {
printProperty ( " End time " , chapter - > endTime ( ) . toString ( ) ) ;
}
cout < < endl ;
}
}
}
} catch ( ios_base : : failure & ) {
cout < < " Error: An IO failure occured when reading the file \" " < < file < < " \" . " < < endl ;
} catch ( ApplicationUtilities : : Failure & ) {
cout < < " Error: A parsing failure occured when reading the file \" " < < file < < " \" . " < < endl ;
}
2015-09-23 00:02:06 +02:00
printNotifications ( fileInfo , " Parsing notifications: " , verboseArg . isPresent ( ) ) ;
2015-08-09 23:53:45 +02:00
cout < < endl ;
}
}
2015-09-23 00:02:06 +02:00
void displayTagInfo ( const StringVector & parameterValues , const Argument & filesArg , const Argument & verboseArg )
2015-04-22 19:33:53 +02:00
{
2015-09-01 20:20:15 +02:00
CMD_UTILS_START_CONSOLE ;
2015-04-22 19:33:53 +02:00
if ( ! filesArg . valueCount ( ) ) {
cout < < " Error: No files have been specified. " < < endl ;
return ;
}
2015-10-13 23:21:31 +02:00
const auto fields = parseFieldDenotations ( parameterValues , true ) ;
2015-04-22 19:33:53 +02:00
MediaFileInfo fileInfo ;
for ( const auto & file : filesArg . values ( ) ) {
try {
// parse tags
fileInfo . setPath ( file ) ;
fileInfo . open ( true ) ;
fileInfo . parseTags ( ) ;
cout < < " Tag information for \" " < < file < < " \" : " < < endl ;
2015-08-09 23:53:45 +02:00
const auto tags = fileInfo . tags ( ) ;
2015-04-22 19:33:53 +02:00
if ( tags . size ( ) ) {
// iterate through all tags
for ( const auto * tag : tags ) {
// determine tag type
TagType tagType = tag - > type ( ) ;
// write tag name and target, eg. MP4/iTunes tag
cout < < tag - > typeName ( ) ;
if ( ! tag - > target ( ) . isEmpty ( ) ) {
2016-05-26 02:15:41 +02:00
cout < < " targeting \" " < < tag - > targetString ( ) < < " \" " ;
2015-04-22 19:33:53 +02:00
}
cout < < endl ;
// iterate through fields specified by the user
2015-10-13 23:21:31 +02:00
if ( fields . empty ( ) ) {
for ( auto field = firstKnownField ; field ! = KnownField : : Invalid ; field = nextKnownField ( field ) ) {
const auto & value = tag - > value ( field ) ;
if ( ! value . isEmpty ( ) ) {
// write field name
const char * fieldName = KnownFieldModel : : fieldName ( field ) ;
cout < < ' ' < < fieldName ;
// write padding
for ( auto i = strlen ( fieldName ) ; i < 18 ; + + i ) {
cout < < ' ' ;
}
// write value
2015-04-22 19:33:53 +02:00
try {
2015-10-06 22:41:02 +02:00
const auto textValue = tagValueToQString ( value ) ;
2015-04-22 19:33:53 +02:00
if ( textValue . isEmpty ( ) ) {
cout < < " can't display here (see --extract) " ;
} else {
cout < < textValue . toLocal8Bit ( ) . data ( ) ;
}
} catch ( ConversionException & ) {
cout < < " conversion error " ;
}
2015-10-13 23:21:31 +02:00
cout < < endl ;
}
}
} else {
for ( const FieldDenotation & fieldDenotation : fields ) {
const auto & value = tag - > value ( fieldDenotation . field ) ;
if ( fieldDenotation . tagType = = TagType : : Unspecified | | ( fieldDenotation . tagType | tagType ) ! = TagType : : Unspecified ) {
// write field name
const char * fieldName = KnownFieldModel : : fieldName ( fieldDenotation . field ) ;
cout < < ' ' < < fieldName ;
// write padding
for ( auto i = strlen ( fieldName ) ; i < 18 ; + + i ) {
cout < < ' ' ;
}
// write value
if ( value . isEmpty ( ) ) {
cout < < " none " ;
} else {
try {
const auto textValue = tagValueToQString ( value ) ;
if ( textValue . isEmpty ( ) ) {
cout < < " can't display here (see --extract) " ;
} else {
cout < < textValue . toLocal8Bit ( ) . data ( ) ;
}
} catch ( ConversionException & ) {
cout < < " conversion error " ;
}
}
cout < < endl ;
2015-04-22 19:33:53 +02:00
}
}
}
}
} else {
cout < < " File has no (supported) tag information. " < < endl ;
}
} catch ( ios_base : : failure & ) {
cout < < " Error: An IO failure occured when reading the file \" " < < file < < " \" . " < < endl ;
} catch ( ApplicationUtilities : : Failure & ) {
cout < < " Error: A parsing failure occured when reading the file \" " < < file < < " \" . " < < endl ;
}
2015-09-23 00:02:06 +02:00
printNotifications ( fileInfo , " Parsing notifications: " , verboseArg . isPresent ( ) ) ;
2015-08-09 23:53:45 +02:00
cout < < endl ;
2015-04-22 19:33:53 +02:00
}
}
2015-11-28 00:20:49 +01:00
void setTagInfo ( const StringVector & parameterValues , const SetTagInfoArgs & args )
2015-04-22 19:33:53 +02:00
{
2015-09-01 20:20:15 +02:00
CMD_UTILS_START_CONSOLE ;
2015-11-28 00:20:49 +01:00
if ( ! args . setTagInfoArg . valueCount ( ) ) {
2015-04-22 19:33:53 +02:00
cout < < " Error: No files have been specified. " < < endl ;
return ;
}
2015-10-13 23:21:31 +02:00
auto fields = parseFieldDenotations ( parameterValues , false ) ;
2015-11-28 00:20:49 +01:00
if ( fields . empty ( ) & & args . attachmentsArg . values ( ) . empty ( ) & & args . docTitleArg . values ( ) . empty ( ) ) {
2015-10-06 22:41:02 +02:00
cout < < " Error: No fields/attachments have been specified. " < < endl ;
2015-04-22 19:33:53 +02:00
return ;
}
2015-10-13 23:21:31 +02:00
// determine required targets
vector < TagTarget > requiredTargets ;
for ( const FieldDenotation & fieldDenotation : fields ) {
if ( find ( requiredTargets . cbegin ( ) , requiredTargets . cend ( ) , fieldDenotation . tagTarget ) = = requiredTargets . cend ( ) ) {
requiredTargets . push_back ( fieldDenotation . tagTarget ) ;
}
}
// determine targets to remove
vector < TagTarget > targetsToRemove ;
targetsToRemove . emplace_back ( ) ;
bool validRemoveTargetsSpecified = false ;
2015-11-28 00:20:49 +01:00
for ( const auto & targetDenotation : args . removeTargetsArg . values ( ) ) {
2015-10-13 23:21:31 +02:00
if ( targetDenotation = = " , " ) {
if ( validRemoveTargetsSpecified ) {
targetsToRemove . emplace_back ( ) ;
}
} else if ( applyTargetConfiguration ( targetsToRemove . back ( ) , targetDenotation ) ) {
validRemoveTargetsSpecified = true ;
} else {
cout < < " Warning: The given target specification \" " < < targetDenotation < < " \" is invalid and will be ignored. " < < endl ;
}
}
// parse other settings
2015-04-22 19:33:53 +02:00
uint32 id3v2Version = 3 ;
2015-11-28 00:20:49 +01:00
if ( args . id3v2VersionArg . isPresent ( ) ) {
2015-04-22 19:33:53 +02:00
try {
2015-11-28 00:20:49 +01:00
id3v2Version = stringToNumber < uint32 > ( args . id3v2VersionArg . values ( ) . front ( ) ) ;
2015-04-22 19:33:53 +02:00
if ( id3v2Version < 1 | | id3v2Version > 4 ) {
throw ConversionException ( ) ;
}
} catch ( ConversionException & ) {
id3v2Version = 3 ;
2015-11-28 00:20:49 +01:00
cout < < " Warning: The specified ID3v2 version \" " < < args . id3v2VersionArg . values ( ) . front ( ) < < " \" is invalid and will be ingored. " < < endl ;
2015-04-22 19:33:53 +02:00
}
}
2015-11-28 00:20:49 +01:00
const TagTextEncoding denotedEncoding = parseEncodingDenotation ( args . encodingArg , TagTextEncoding : : Utf8 ) ;
const TagUsage id3v1Usage = parseUsageDenotation ( args . id3v1UsageArg , TagUsage : : KeepExisting ) ;
const TagUsage id3v2Usage = parseUsageDenotation ( args . id3v2UsageArg , TagUsage : : Always ) ;
MediaFileInfo fileInfo ;
fileInfo . setMinPadding ( parseUInt64 ( args . minPaddingArg , 0 ) ) ;
fileInfo . setMaxPadding ( parseUInt64 ( args . maxPaddingArg , 0 ) ) ;
fileInfo . setPreferredPadding ( parseUInt64 ( args . prefPaddingArg , 0 ) ) ;
fileInfo . setTagPosition ( parsePositionDenotation ( args . tagPosArg , ElementPosition : : BeforeData ) ) ;
fileInfo . setForceTagPosition ( args . forceTagPosArg . isPresent ( ) ) ;
fileInfo . setIndexPosition ( parsePositionDenotation ( args . indexPosArg , ElementPosition : : BeforeData ) ) ;
fileInfo . setForceIndexPosition ( args . forceIndexPosArg . isPresent ( ) ) ;
fileInfo . setForceRewrite ( args . forceRewriteArg . isPresent ( ) ) ;
2015-10-13 23:21:31 +02:00
// iterate through all specified files
2015-04-22 19:33:53 +02:00
unsigned int fileIndex = 0 ;
2015-10-06 22:41:02 +02:00
static const string context ( " setting tags " ) ;
NotificationList notifications ;
2015-11-28 00:20:49 +01:00
for ( const auto & file : args . filesArg . values ( ) ) {
2015-04-22 19:33:53 +02:00
try {
// parse tags
2015-10-06 22:41:02 +02:00
cout < < " Setting tag information for \" " < < file < < " \" ... " < < endl ;
notifications . clear ( ) ;
2015-04-22 19:33:53 +02:00
fileInfo . setPath ( file ) ;
fileInfo . parseTags ( ) ;
2015-10-06 22:41:02 +02:00
fileInfo . parseTracks ( ) ;
2015-10-13 23:21:31 +02:00
vector < Tag * > tags ;
// remove tags with the specified targets
if ( validRemoveTargetsSpecified ) {
fileInfo . tags ( tags ) ;
for ( auto * tag : tags ) {
if ( find ( targetsToRemove . cbegin ( ) , targetsToRemove . cend ( ) , tag - > target ( ) ) ! = targetsToRemove . cend ( ) ) {
fileInfo . removeTag ( tag ) ;
}
}
tags . clear ( ) ;
}
// create new tags according to settings
2015-11-28 00:20:49 +01:00
fileInfo . createAppropriateTags ( args . treatUnknownFilesAsMp3FilesArg . isPresent ( ) , id3v1Usage , id3v2Usage , args . mergeMultipleSuccessiveTagsArg . isPresent ( ) , ! args . id3v2VersionArg . isPresent ( ) , id3v2Version , requiredTargets ) ;
2015-10-06 22:41:02 +02:00
auto container = fileInfo . container ( ) ;
2015-10-14 19:49:48 +02:00
bool docTitleModified = false ;
2015-11-28 00:20:49 +01:00
if ( ! args . docTitleArg . values ( ) . empty ( ) ) {
2015-12-27 19:49:17 +01:00
if ( container & & container - > supportsTitle ( ) ) {
2015-10-14 19:49:48 +02:00
size_t segmentIndex = 0 , segmentCount = container - > titles ( ) . size ( ) ;
2015-11-28 00:20:49 +01:00
for ( const auto & newTitle : args . docTitleArg . values ( ) ) {
2015-10-14 19:49:48 +02:00
if ( segmentIndex < segmentCount ) {
container - > setTitle ( newTitle , segmentIndex ) ;
docTitleModified = true ;
} else {
2015-12-27 19:49:17 +01:00
cout < < " Warning: The specified document title \" " < < newTitle < < " \" can not be set because the file has not that many segments. " < < endl ;
2015-10-14 19:49:48 +02:00
}
+ + segmentIndex ;
}
} else {
cout < < " Warning: Setting the document title is not supported for the file. " < < endl ;
}
}
2015-10-13 23:21:31 +02:00
fileInfo . tags ( tags ) ;
2015-10-06 22:41:02 +02:00
if ( ! tags . empty ( ) ) {
2015-04-22 19:33:53 +02:00
// iterate through all tags
for ( auto * tag : tags ) {
2015-11-28 00:20:49 +01:00
if ( args . removeOtherFieldsArg . isPresent ( ) ) {
2015-04-22 19:33:53 +02:00
tag - > removeAllFields ( ) ;
}
2015-10-13 23:21:31 +02:00
auto tagType = tag - > type ( ) ;
2015-04-22 19:33:53 +02:00
bool targetSupported = tag - > supportsTarget ( ) ;
2015-10-13 23:21:31 +02:00
auto tagTarget = tag - > target ( ) ;
2015-04-22 19:33:53 +02:00
for ( FieldDenotation & fieldDenotation : fields ) {
2015-10-13 23:21:31 +02:00
if ( ( fieldDenotation . tagType = = TagType : : Unspecified
| | ( fieldDenotation . tagType | tagType ) ! = TagType : : Unspecified )
& & ( ! targetSupported | | fieldDenotation . tagTarget = = tagTarget ) ) {
2015-04-22 19:33:53 +02:00
pair < unsigned int , QString > * selectedDenotatedValue = nullptr ;
for ( auto & someDenotatedValue : fieldDenotation . values ) {
if ( someDenotatedValue . first < = fileIndex ) {
if ( ! selectedDenotatedValue | | ( someDenotatedValue . first > selectedDenotatedValue - > first ) ) {
selectedDenotatedValue = & someDenotatedValue ;
}
}
}
if ( selectedDenotatedValue ) {
if ( fieldDenotation . type = = DenotationType : : File ) {
if ( selectedDenotatedValue - > second . isEmpty ( ) ) {
2015-10-13 23:21:31 +02:00
tag - > setValue ( fieldDenotation . field , TagValue ( ) ) ;
2015-04-22 19:33:53 +02:00
} else {
try {
MediaFileInfo fileInfo ( selectedDenotatedValue - > second . toLocal8Bit ( ) . constData ( ) ) ;
fileInfo . open ( true ) ;
fileInfo . parseContainerFormat ( ) ;
2015-08-16 23:41:49 +02:00
auto buff = make_unique < char [ ] > ( fileInfo . size ( ) ) ;
2015-04-22 19:33:53 +02:00
fileInfo . stream ( ) . seekg ( 0 ) ;
fileInfo . stream ( ) . read ( buff . get ( ) , fileInfo . size ( ) ) ;
TagValue value ( move ( buff ) , fileInfo . size ( ) , TagDataType : : Picture ) ;
value . setMimeType ( fileInfo . mimeType ( ) ) ;
2015-10-13 23:21:31 +02:00
tag - > setValue ( fieldDenotation . field , move ( value ) ) ;
2015-04-22 19:33:53 +02:00
} catch ( ios_base : : failure & ) {
2015-10-06 22:41:02 +02:00
fileInfo . addNotification ( NotificationType : : Critical , " An IO error occured when parsing the specified cover file. " , context ) ;
2015-04-22 19:33:53 +02:00
} catch ( Media : : Failure & ) {
2015-10-06 22:41:02 +02:00
fileInfo . addNotification ( NotificationType : : Critical , " Unable to parse specified cover file. " , context ) ;
2015-04-22 19:33:53 +02:00
}
}
} else {
2015-10-06 22:41:02 +02:00
TagTextEncoding usedEncoding = denotedEncoding ;
if ( ! tag - > canEncodingBeUsed ( denotedEncoding ) ) {
2015-04-22 19:33:53 +02:00
usedEncoding = tag - > proposedTextEncoding ( ) ;
}
2015-10-13 23:21:31 +02:00
tag - > setValue ( fieldDenotation . field , qstringToTagValue ( selectedDenotatedValue - > second , usedEncoding ) ) ;
2015-04-22 19:33:53 +02:00
if ( fieldDenotation . type = = DenotationType : : Increment & & tag = = tags . back ( ) ) {
selectedDenotatedValue - > second = incremented ( selectedDenotatedValue - > second ) ;
}
}
}
}
}
}
2015-10-06 22:41:02 +02:00
} else {
fileInfo . addNotification ( NotificationType : : Critical , " Can not create appropriate tags for file. " , context ) ;
}
bool attachmentsModified = false ;
2015-11-28 00:20:49 +01:00
if ( args . attachmentsArg . isPresent ( ) | | args . removeExistingAttachmentsArg . isPresent ( ) ) {
2015-10-06 22:41:02 +02:00
static const string context ( " setting attachments " ) ;
fileInfo . parseAttachments ( ) ;
if ( fileInfo . attachmentsParsingStatus ( ) = = ParsingStatus : : Ok ) {
if ( container ) {
2015-10-13 23:21:31 +02:00
// ignore all existing attachments if argument is specified
2015-11-28 00:20:49 +01:00
if ( args . removeExistingAttachmentsArg . isPresent ( ) ) {
2015-10-13 23:21:31 +02:00
for ( size_t i = 0 , count = container - > attachmentCount ( ) ; i < count ; + + i ) {
container - > attachment ( i ) - > setIgnored ( false ) ;
}
attachmentsModified = true ;
}
// add/update/remove attachments explicitely
2015-10-06 22:41:02 +02:00
AttachmentInfo currentInfo ;
2015-11-28 00:20:49 +01:00
for ( const auto & value : args . attachmentsArg . values ( ) ) {
2015-10-06 22:41:02 +02:00
const auto * data = value . data ( ) ;
if ( value = = " , " ) {
attachmentsModified | = currentInfo . next ( container ) ;
} else if ( value = = " add " ) {
currentInfo . action = AttachmentAction : : Add ;
} else if ( value = = " update-by-id " ) {
currentInfo . action = AttachmentAction : : UpdateById ;
} else if ( value = = " update-by-name " ) {
currentInfo . action = AttachmentAction : : UpdateByName ;
} else if ( value = = " remove-by-id " ) {
currentInfo . action = AttachmentAction : : RemoveById ;
} else if ( value = = " remove-by-name " ) {
currentInfo . action = AttachmentAction : : RemoveByName ;
} else if ( ! strncmp ( data , " id= " , 3 ) ) {
try {
currentInfo . id = stringToNumber < uint64 , string > ( data + 3 ) ;
} catch ( const ConversionException & ) {
container - > addNotification ( NotificationType : : Warning , " The specified attachment ID \" " + string ( data + 3 ) + " \" is invalid. " , context ) ;
}
} else if ( ! strncmp ( data , " path= " , 5 ) ) {
currentInfo . path = data + 5 ;
} else if ( ! strncmp ( data , " name= " , 5 ) ) {
currentInfo . name = data + 5 ;
} else if ( ! strncmp ( data , " mime= " , 5 ) ) {
currentInfo . mime = data + 5 ;
} else if ( ! strncmp ( data , " desc= " , 5 ) ) {
currentInfo . desc = data + 5 ;
} else {
container - > addNotification ( NotificationType : : Warning , " The attachment specification \" " + value + " \" is invalid and will be ignored. " , context ) ;
}
}
attachmentsModified | = currentInfo . next ( container ) ;
} else {
fileInfo . addNotification ( NotificationType : : Critical , " Unable to assign attachments because the container object has not been initialized. " , context ) ;
}
} else {
// notification will be added by the file info automatically
}
}
2015-10-14 19:49:48 +02:00
if ( ! tags . empty ( ) | | docTitleModified | | attachmentsModified ) {
2015-04-22 19:33:53 +02:00
try {
2015-10-06 22:41:02 +02:00
// save parsing notifications because notifications of sub objects like tags, tracks, ... will be gone after applying changes
fileInfo . gatherRelatedNotifications ( notifications ) ;
fileInfo . invalidateNotifications ( ) ;
2015-04-22 19:33:53 +02:00
fileInfo . applyChanges ( ) ;
2015-10-06 22:41:02 +02:00
fileInfo . gatherRelatedNotifications ( notifications ) ;
2015-04-22 19:33:53 +02:00
cout < < " Changes have been applied. " < < endl ;
2015-10-06 22:41:02 +02:00
} catch ( const ApplicationUtilities : : Failure & ) {
cout < < " Error: Failed to apply changes. " < < endl ;
2015-04-22 19:33:53 +02:00
}
2015-10-14 19:49:48 +02:00
} else {
cout < < " Warning: No changed to be applied. " < < endl ;
2015-04-22 19:33:53 +02:00
}
2015-10-06 22:41:02 +02:00
} catch ( const ios_base : : failure & ) {
2015-04-22 19:33:53 +02:00
cout < < " Error: An IO failure occured when reading/writing the file \" " < < file < < " \" . " < < endl ;
2015-10-06 22:41:02 +02:00
} catch ( const ApplicationUtilities : : Failure & ) {
2015-04-22 19:33:53 +02:00
cout < < " Error: A parsing failure occured when reading/writing the file \" " < < file < < " \" . " < < endl ;
}
2015-11-28 00:20:49 +01:00
printNotifications ( notifications , " Notifications: " , args . verboseArg . isPresent ( ) ) ;
2015-04-22 19:33:53 +02:00
+ + fileIndex ;
}
}
2015-09-23 00:02:06 +02:00
void extractField ( const StringVector & parameterValues , const Argument & inputFileArg , const Argument & outputFileArg , const Argument & verboseArg )
2015-04-22 19:33:53 +02:00
{
2015-09-01 20:20:15 +02:00
CMD_UTILS_START_CONSOLE ;
2015-10-13 23:21:31 +02:00
const auto fields = parseFieldDenotations ( parameterValues , true ) ;
if ( fields . size ( ) ! = 1 ) {
2015-04-22 19:33:53 +02:00
cout < < " Error: Excactly one field needs to be specified. " < < endl ;
return ;
}
MediaFileInfo inputFileInfo ;
try {
// parse tags
inputFileInfo . setPath ( inputFileArg . values ( ) . front ( ) ) ;
inputFileInfo . open ( true ) ;
inputFileInfo . parseTags ( ) ;
cout < < " Extracting " < < parameterValues . front ( ) < < " of \" " < < inputFileArg . values ( ) . front ( ) < < " \" ... " < < endl ;
auto tags = inputFileInfo . tags ( ) ;
vector < pair < const TagValue * , string > > values ;
// iterate through all tags
for ( const Tag * tag : tags ) {
for ( const auto & fieldDenotation : fields ) {
2015-10-13 23:21:31 +02:00
const auto & value = tag - > value ( fieldDenotation . field ) ;
if ( ! value . isEmpty ( ) ) {
values . emplace_back ( & value , joinStrings ( { tag - > typeName ( ) , numberToString ( values . size ( ) ) } , " - " ) ) ;
2015-04-22 19:33:53 +02:00
}
}
}
if ( values . empty ( ) ) {
cout < < " File has no (supported) " < < parameterValues . front ( ) < < " field. " < < endl ;
} else {
string outputFilePathWithoutExtension , outputFileExtension ;
if ( values . size ( ) > 1 ) {
outputFilePathWithoutExtension = BasicFileInfo : : pathWithoutExtension ( outputFileArg . values ( ) . front ( ) ) ;
outputFileExtension = BasicFileInfo : : extension ( outputFileArg . values ( ) . front ( ) ) ;
}
for ( const auto & value : values ) {
fstream outputFileStream ;
outputFileStream . exceptions ( ios_base : : failbit | ios_base : : badbit ) ;
auto path = values . size ( ) > 1 ? joinStrings ( { outputFilePathWithoutExtension , " - " , value . second , outputFileExtension } ) : outputFileArg . values ( ) . front ( ) ;
try {
outputFileStream . open ( path , ios_base : : out | ios_base : : binary ) ;
outputFileStream . write ( value . first - > dataPointer ( ) , value . first - > dataSize ( ) ) ;
outputFileStream . flush ( ) ;
cout < < " Value has been saved to \" " < < path < < " \" . " < < endl ;
} catch ( ios_base : : failure & ) {
cout < < " An IO error occured when writing the file \" " < < path < < " \" . " < < endl ;
}
}
}
} catch ( ios_base : : failure & ) {
cout < < " Error: An IO failure occured when reading the file \" " < < inputFileArg . values ( ) . front ( ) < < " \" . " < < endl ;
} catch ( ApplicationUtilities : : Failure & ) {
cout < < " Error: A parsing failure occured when reading the file \" " < < inputFileArg . values ( ) . front ( ) < < " \" . " < < endl ;
}
2015-09-23 00:02:06 +02:00
printNotifications ( inputFileInfo , " Parsing notifications: " , verboseArg . isPresent ( ) ) ;
2015-04-22 19:33:53 +02:00
}
}