2015-09-06 19:57:33 +02:00
# include "./id3v2frame.h"
# include "./id3genres.h"
# include "./id3v2frameids.h"
2015-09-06 15:42:18 +02:00
2015-09-06 19:57:33 +02:00
# include "../exceptions.h"
2015-04-22 19:22:01 +02:00
# include <c++utilities/conversion/stringconversion.h>
2017-01-27 18:59:22 +01:00
# include <c++utilities/conversion/stringbuilder.h>
2015-04-22 19:22:01 +02:00
# include <zlib.h>
# include <algorithm>
# include <cstring>
2017-02-05 21:02:40 +01:00
# include <memory>
2015-04-22 19:22:01 +02:00
using namespace std ;
using namespace ConversionUtilities ;
using namespace ChronoUtilities ;
using namespace IoUtilities ;
namespace Media {
/*!
* \ class Media : : Id3v2Frame
* \ brief The Id3v2Frame class is used by Id3v2Tag to store the fields .
*/
/*!
* \ brief Constructs a new Id3v2Frame .
*/
Id3v2Frame : : Id3v2Frame ( ) :
m_flag ( 0 ) ,
m_group ( 0 ) ,
m_parsedVersion ( 0 ) ,
m_dataSize ( 0 ) ,
2015-12-22 17:01:25 +01:00
m_totalSize ( 0 ) ,
2015-04-22 19:22:01 +02:00
m_padding ( false )
{ }
/*!
* \ brief Constructs a new Id3v2Frame with the specified \ a id , \ a value , \ a group and \ a flag .
*/
2015-12-22 23:54:35 +01:00
Id3v2Frame : : Id3v2Frame ( const identifierType & id , const TagValue & value , const byte group , const int16 flag ) :
2015-04-22 19:22:01 +02:00
TagField < Id3v2Frame > ( id , value ) ,
m_flag ( flag ) ,
m_group ( group ) ,
m_parsedVersion ( 0 ) ,
m_dataSize ( 0 ) ,
2015-12-22 17:01:25 +01:00
m_totalSize ( 0 ) ,
2015-04-22 19:22:01 +02:00
m_padding ( false )
{ }
2016-02-23 20:33:00 +01:00
/*!
* \ brief Helper function to parse the genre index .
* \ returns Returns the genre index or - 1 if the specified string does not denote a genre index .
*/
template < class stringtype >
int parseGenreIndex ( const stringtype & denotation , bool isBigEndian = false )
{
int index = - 1 ;
for ( auto c : denotation ) {
if ( sizeof ( typename stringtype : : value_type ) = = 2 & & isBigEndian ! = CONVERSION_UTILITIES_IS_BYTE_ORDER_BIG_ENDIAN ) {
c = swapOrder ( static_cast < uint16 > ( c ) ) ;
}
if ( index = = - 1 ) {
switch ( c ) {
case ' ' :
break ;
case ' ( ' :
index = 0 ;
break ;
case ' \0 ' :
return - 1 ;
default :
if ( c > = ' 0 ' & & c < = ' 9 ' ) {
index = c - ' 0 ' ;
} else {
return - 1 ;
}
}
} else {
switch ( c ) {
case ' ) ' :
return index ;
case ' \0 ' :
return index ;
default :
if ( c > = ' 0 ' & & c < = ' 9 ' ) {
index = index * 10 + c - ' 0 ' ;
} else {
return - 1 ;
}
}
}
}
return index ;
}
2015-04-22 19:22:01 +02:00
/*!
* \ brief Parses a frame from the stream read using the specified \ a reader .
*
* The position of the current character in the input stream is expected to be
* at the beginning of the frame to be parsed .
*
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
* \ throws Throws Media : : Failure or a derived exception when a parsing
* error occurs .
*/
2015-12-22 23:54:35 +01:00
void Id3v2Frame : : parse ( BinaryReader & reader , const uint32 version , const uint32 maximalSize )
2015-04-22 19:22:01 +02:00
{
invalidateStatus ( ) ;
2016-03-18 21:43:09 +01:00
clear ( ) ;
2017-02-06 18:52:21 +01:00
static const string defaultContext ( " parsing ID3v2 frame " ) ;
string context ;
2015-12-22 23:54:35 +01:00
// parse header
2015-04-22 19:22:01 +02:00
if ( version < 3 ) {
// parse header for ID3v2.1 and ID3v2.2
2015-12-22 23:54:35 +01:00
// -> read ID
2015-04-22 19:22:01 +02:00
setId ( reader . readUInt24BE ( ) ) ;
2016-03-18 21:43:09 +01:00
if ( id ( ) & 0xFFFF0000u ) {
m_padding = false ;
} else {
2015-12-22 23:54:35 +01:00
// padding reached
2015-04-22 19:22:01 +02:00
m_padding = true ;
2017-02-06 18:52:21 +01:00
addNotification ( NotificationType : : Debug , " Frame ID starts with null-byte -> padding reached. " , defaultContext ) ;
2015-04-22 19:22:01 +02:00
throw NoDataFoundException ( ) ;
}
2015-12-22 23:54:35 +01:00
// -> update context
2017-01-27 18:59:22 +01:00
context = " parsing " % frameIdString ( ) + " frame " ;
2015-12-22 23:54:35 +01:00
// -> read size, check whether frame is truncated
2015-04-22 19:22:01 +02:00
m_dataSize = reader . readUInt24BE ( ) ;
2015-12-22 17:01:25 +01:00
m_totalSize = m_dataSize + 6 ;
if ( m_totalSize > maximalSize ) {
2017-01-27 18:59:22 +01:00
addNotification ( NotificationType : : Warning , " The frame is truncated and will be ignored. " , context ) ;
2015-04-22 19:22:01 +02:00
throw TruncatedDataException ( ) ;
}
2015-12-22 23:54:35 +01:00
// -> no flags/group in ID3v2.2
2015-04-22 19:22:01 +02:00
m_flag = 0 ;
m_group = 0 ;
2015-12-22 23:54:35 +01:00
2015-04-22 19:22:01 +02:00
} else {
// parse header for ID3v2.3 and ID3v2.4
2015-12-22 23:54:35 +01:00
// -> read ID
2015-04-22 19:22:01 +02:00
setId ( reader . readUInt32BE ( ) ) ;
2016-03-18 21:43:09 +01:00
if ( id ( ) & 0xFF000000u ) {
m_padding = false ;
} else {
2015-12-22 23:54:35 +01:00
// padding reached
2015-04-22 19:22:01 +02:00
m_padding = true ;
2017-02-06 18:52:21 +01:00
addNotification ( NotificationType : : Debug , " Frame ID starts with null-byte -> padding reached. " , defaultContext ) ;
2015-04-22 19:22:01 +02:00
throw NoDataFoundException ( ) ;
}
2015-12-22 23:54:35 +01:00
// -> update context
2017-01-27 18:59:22 +01:00
context = " parsing " % frameIdString ( ) + " frame " ;
2015-12-22 23:54:35 +01:00
// -> read size, check whether frame is truncated
2015-04-22 19:22:01 +02:00
m_dataSize = version > = 4
? reader . readSynchsafeUInt32BE ( )
: reader . readUInt32BE ( ) ;
2015-12-22 17:01:25 +01:00
m_totalSize = m_dataSize + 10 ;
if ( m_totalSize > maximalSize ) {
2015-04-22 19:22:01 +02:00
addNotification ( NotificationType : : Warning , " The frame is truncated and will be ignored. " , context ) ;
throw TruncatedDataException ( ) ;
}
2015-12-22 23:54:35 +01:00
// -> read flags and group
2015-04-22 19:22:01 +02:00
m_flag = reader . readUInt16BE ( ) ;
m_group = hasGroupInformation ( ) ? reader . readByte ( ) : 0 ;
if ( isEncrypted ( ) ) {
2015-12-22 23:54:35 +01:00
// encryption is not implemented
2015-04-22 19:22:01 +02:00
addNotification ( NotificationType : : Critical , " Encrypted frames aren't supported. " , context ) ;
throw VersionNotSupportedException ( ) ;
}
}
2015-12-22 23:54:35 +01:00
// frame size mustn't be 0
2015-04-22 19:22:01 +02:00
if ( m_dataSize < = 0 ) {
addNotification ( NotificationType : : Critical , " The frame size is 0. " , context ) ;
throw InvalidDataException ( ) ;
}
2015-12-22 23:54:35 +01:00
2015-04-22 19:22:01 +02:00
// parse the data
2015-09-19 22:34:07 +02:00
unique_ptr < char [ ] > buffer ;
2015-12-22 23:54:35 +01:00
// -> decompress data if compressed; otherwise just read it
2015-04-22 19:22:01 +02:00
if ( isCompressed ( ) ) {
uLongf decompressedSize = version > = 4 ? reader . readSynchsafeUInt32BE ( ) : reader . readUInt32BE ( ) ;
if ( decompressedSize < m_dataSize ) {
2017-03-01 18:21:00 +01:00
addNotification ( NotificationType : : Critical , " The decompressed size is smaller than the compressed size. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-12-22 23:54:35 +01:00
auto bufferCompressed = make_unique < char [ ] > ( m_dataSize ) ; ;
2015-09-19 22:34:07 +02:00
reader . read ( bufferCompressed . get ( ) , m_dataSize ) ;
buffer = make_unique < char [ ] > ( decompressedSize ) ;
switch ( uncompress ( reinterpret_cast < Bytef * > ( buffer . get ( ) ) , & decompressedSize , reinterpret_cast < Bytef * > ( bufferCompressed . get ( ) ) , m_dataSize ) ) {
2015-04-22 19:22:01 +02:00
case Z_MEM_ERROR :
addNotification ( NotificationType : : Critical , " Decompressing failed. The source buffer was too small. " , context ) ;
throw InvalidDataException ( ) ;
case Z_BUF_ERROR :
addNotification ( NotificationType : : Critical , " Decompressing failed. The destination buffer was too small. " , context ) ;
throw InvalidDataException ( ) ;
case Z_DATA_ERROR :
addNotification ( NotificationType : : Critical , " Decompressing failed. The input data was corrupted or incomplete. " , context ) ;
throw InvalidDataException ( ) ;
case Z_OK :
2015-12-22 23:54:35 +01:00
break ;
default :
addNotification ( NotificationType : : Critical , " Decompressing failed (unknown reason). " , context ) ;
throw InvalidDataException ( ) ;
2015-04-22 19:22:01 +02:00
}
m_dataSize = decompressedSize ;
} else {
2015-09-19 22:34:07 +02:00
buffer = make_unique < char [ ] > ( m_dataSize ) ;
reader . read ( buffer . get ( ) , m_dataSize ) ;
2015-04-22 19:22:01 +02:00
}
2015-12-22 23:54:35 +01:00
// -> get tag value depending of field type
if ( Id3v2FrameIds : : isTextFrame ( id ( ) ) ) {
2015-04-22 19:22:01 +02:00
// frame contains text
2015-12-22 23:54:35 +01:00
TagTextEncoding dataEncoding = parseTextEncodingByte ( * buffer . get ( ) ) ; // the first byte stores the encoding
2015-04-22 19:22:01 +02:00
if ( ( version > = 3 & &
( id ( ) = = Id3v2FrameIds : : lTrackPosition | | id ( ) = = Id3v2FrameIds : : lDiskPosition ) )
| | ( version < 3 & & id ( ) = = Id3v2FrameIds : : sTrackPosition ) ) {
2015-09-19 22:34:07 +02:00
// the track number or the disk number frame
2015-04-22 19:22:01 +02:00
try {
PositionInSet position ;
if ( characterSize ( dataEncoding ) > 1 ) {
2015-12-22 23:54:35 +01:00
position = PositionInSet ( parseWideString ( buffer . get ( ) + 1 , m_dataSize - 1 , dataEncoding ) ) ;
2015-04-22 19:22:01 +02:00
} else {
2015-12-22 23:54:35 +01:00
position = PositionInSet ( parseString ( buffer . get ( ) + 1 , m_dataSize - 1 , dataEncoding ) ) ;
2015-04-22 19:22:01 +02:00
}
value ( ) . assignPosition ( position ) ;
2016-03-18 21:43:09 +01:00
} catch ( const ConversionException & ) {
2015-04-22 19:22:01 +02:00
addNotification ( NotificationType : : Warning , " The value of track/disk position frame is not numeric and will be ignored. " , context ) ;
}
2015-12-22 23:54:35 +01:00
2015-04-22 19:22:01 +02:00
} else if ( ( version > = 3 & & id ( ) = = Id3v2FrameIds : : lLength ) | | ( version < 3 & & id ( ) = = Id3v2FrameIds : : sLength ) ) {
2015-09-19 22:34:07 +02:00
// frame contains length
2015-04-22 19:22:01 +02:00
try {
2017-02-05 22:51:27 +01:00
string milliseconds ;
if ( dataEncoding = = TagTextEncoding : : Utf16BigEndian | | dataEncoding = = TagTextEncoding : : Utf16LittleEndian ) {
const auto parsedStringRef = parseSubstring ( buffer . get ( ) + 1 , m_dataSize - 1 , dataEncoding ) ;
const auto convertedStringData = dataEncoding = = TagTextEncoding : : Utf16BigEndian
? convertUtf16BEToUtf8 ( get < 0 > ( parsedStringRef ) , get < 1 > ( parsedStringRef ) )
: convertUtf16LEToUtf8 ( get < 0 > ( parsedStringRef ) , get < 1 > ( parsedStringRef ) ) ;
milliseconds = string ( convertedStringData . first . get ( ) , convertedStringData . second ) ;
} else { // Latin-1 or UTF-8
milliseconds = parseString ( buffer . get ( ) + 1 , m_dataSize - 1 , dataEncoding ) ;
2015-04-22 19:22:01 +02:00
}
2017-02-05 22:51:27 +01:00
value ( ) . assignTimeSpan ( TimeSpan : : fromMilliseconds ( stringToNumber < double > ( milliseconds ) ) ) ;
2016-03-18 21:43:09 +01:00
} catch ( const ConversionException & ) {
2015-04-22 19:22:01 +02:00
addNotification ( NotificationType : : Warning , " The value of the length frame is not numeric and will be ignored. " , context ) ;
}
2015-12-22 23:54:35 +01:00
2015-04-22 19:22:01 +02:00
} else if ( ( version > = 3 & & id ( ) = = Id3v2FrameIds : : lGenre ) | | ( version < 3 & & id ( ) = = Id3v2FrameIds : : sGenre ) ) {
2015-09-19 22:34:07 +02:00
// genre/content type
2015-04-22 19:22:01 +02:00
int genreIndex ;
2016-02-23 20:33:00 +01:00
if ( characterSize ( dataEncoding ) > 1 ) {
auto genreDenotation = parseWideString ( buffer . get ( ) + 1 , m_dataSize - 1 , dataEncoding ) ;
genreIndex = parseGenreIndex ( genreDenotation , dataEncoding = = TagTextEncoding : : Utf16BigEndian ) ;
} else {
auto genreDenotation = parseString ( buffer . get ( ) + 1 , m_dataSize - 1 , dataEncoding ) ;
genreIndex = parseGenreIndex ( genreDenotation ) ;
}
if ( genreIndex ! = - 1 ) {
// genre is specified as ID3 genre number
value ( ) . assignStandardGenreIndex ( genreIndex ) ;
} else {
2015-04-22 19:22:01 +02:00
// genre is specified as string
// string might be null terminated
2015-12-22 23:54:35 +01:00
auto substr = parseSubstring ( buffer . get ( ) + 1 , m_dataSize - 1 , dataEncoding ) ;
2015-04-22 19:22:01 +02:00
value ( ) . assignData ( get < 0 > ( substr ) , get < 1 > ( substr ) , TagDataType : : Text , dataEncoding ) ;
}
} else { // any other text frame
// string might be null terminated
2015-12-22 23:54:35 +01:00
auto substr = parseSubstring ( buffer . get ( ) + 1 , m_dataSize - 1 , dataEncoding ) ;
2015-04-22 19:22:01 +02:00
value ( ) . assignData ( get < 0 > ( substr ) , get < 1 > ( substr ) , TagDataType : : Text , dataEncoding ) ;
}
2015-12-22 23:54:35 +01:00
2015-09-19 22:34:07 +02:00
} else if ( version > = 3 & & id ( ) = = Id3v2FrameIds : : lCover ) {
// frame stores picture
byte type ;
2015-12-22 23:54:35 +01:00
parsePicture ( buffer . get ( ) , m_dataSize , value ( ) , type ) ;
2015-09-19 22:34:07 +02:00
setTypeInfo ( type ) ;
2015-12-22 23:54:35 +01:00
2015-09-19 22:34:07 +02:00
} else if ( version < 3 & & id ( ) = = Id3v2FrameIds : : sCover ) {
// frame stores legacy picutre
2015-04-22 19:22:01 +02:00
byte type ;
2015-12-22 23:54:35 +01:00
parseLegacyPicture ( buffer . get ( ) , m_dataSize , value ( ) , type ) ;
2015-04-22 19:22:01 +02:00
setTypeInfo ( type ) ;
2015-12-22 23:54:35 +01:00
2015-04-22 19:22:01 +02:00
} else if ( ( ( version > = 3 & & id ( ) = = Id3v2FrameIds : : lComment ) | | ( version < 3 & & id ( ) = = Id3v2FrameIds : : sComment ) )
| | ( ( version > = 3 & & id ( ) = = Id3v2FrameIds : : lUnsynchronizedLyrics ) | | ( version < 3 & & id ( ) = = Id3v2FrameIds : : sUnsynchronizedLyrics ) ) ) {
2015-09-19 22:34:07 +02:00
// comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
2015-12-22 23:54:35 +01:00
parseComment ( buffer . get ( ) , m_dataSize , value ( ) ) ;
2015-04-22 19:22:01 +02:00
} else {
2015-09-19 22:34:07 +02:00
// unknown frame
value ( ) . assignData ( buffer . get ( ) , m_dataSize , TagDataType : : Undefined ) ;
2015-04-22 19:22:01 +02:00
}
}
2015-12-22 23:54:35 +01:00
/*!
* \ brief Prepares making .
* \ returns Returns a Id3v2FrameMaker object which can be used to actually make the frame .
* \ remarks The field must NOT be mutated after making is prepared when it is intended to actually
* make the field using the make method of the returned object .
* \ throws Throws Media : : Failure or a derived exception when a making
* error occurs .
*
* This method might be useful when it is necessary to know the size of the field before making it .
*/
Id3v2FrameMaker Id3v2Frame : : prepareMaking ( const uint32 version )
{
return Id3v2FrameMaker ( * this , version ) ;
}
2015-04-22 19:22:01 +02:00
/*!
* \ brief Writes the frame to a stream using the specified \ a writer and the
2015-12-22 23:54:35 +01:00
* specified ID3v2 \ a version .
2015-04-22 19:22:01 +02:00
*
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
* \ throws Throws Media : : Failure or a derived exception when a making
* error occurs .
*/
2015-12-22 23:54:35 +01:00
void Id3v2Frame : : make ( BinaryWriter & writer , const uint32 version )
2015-04-22 19:22:01 +02:00
{
2015-12-22 23:54:35 +01:00
prepareMaking ( version ) . make ( writer ) ;
}
/*!
* \ brief Ensures the field is cleared .
*/
void Id3v2Frame : : cleared ( )
{
m_flag = 0 ;
m_group = 0 ;
m_parsedVersion = 0 ;
m_dataSize = 0 ;
m_totalSize = 0 ;
m_padding = false ;
}
/*!
* \ class Media : : Id3v2FrameMaker
* \ brief The Id3v2FrameMaker class helps making ID3v2 frames .
* It allows to calculate the required size .
* \ sa See Id3v2FrameMaker : : prepareMaking ( ) for more information .
*/
/*!
* \ brief Prepares making the specified \ a frame .
* \ sa See Id3v2Frame : : prepareMaking ( ) for more information .
*/
Id3v2FrameMaker : : Id3v2FrameMaker ( Id3v2Frame & frame , const byte version ) :
m_frame ( frame ) ,
m_frameId ( m_frame . id ( ) ) ,
m_version ( version )
{
m_frame . invalidateStatus ( ) ;
2017-01-27 18:59:22 +01:00
const string context ( " making " % m_frame . frameIdString ( ) + " frame " ) ;
2015-12-22 23:54:35 +01:00
// validate assigned data
if ( m_frame . value ( ) . isEmpty ( ) ) {
m_frame . addNotification ( NotificationType : : Critical , " Cannot make an empty frame. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-12-22 23:54:35 +01:00
if ( m_frame . isEncrypted ( ) ) {
m_frame . addNotification ( NotificationType : : Critical , " Cannot make an encrypted frame (isn't supported by this tagging library). " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-12-22 23:54:35 +01:00
if ( m_frame . hasPaddingReached ( ) ) {
m_frame . addNotification ( NotificationType : : Critical , " Cannot make a frame which is marked as padding. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-12-22 23:54:35 +01:00
if ( version < 3 & & m_frame . isCompressed ( ) ) {
m_frame . addNotification ( NotificationType : : Warning , " Compression is not supported by the version of ID3v2 and won't be applied. " , context ) ;
}
if ( version < 3 & & ( m_frame . flag ( ) | | m_frame . group ( ) ) ) {
m_frame . addNotification ( NotificationType : : Warning , " The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted. " , context ) ;
}
// convert frame ID if necessary
2015-04-22 19:22:01 +02:00
if ( version > = 3 ) {
2015-12-22 23:54:35 +01:00
if ( Id3v2FrameIds : : isShortId ( m_frameId ) ) {
// try to convert the short frame ID to its long equivalent
if ( ! ( m_frameId = Id3v2FrameIds : : convertToLongId ( m_frameId ) ) ) {
m_frame . addNotification ( NotificationType : : Critical , " The short frame ID can't be converted to its long equivalent which is needed to use the frame in a newer version of ID3v2. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
}
} else {
2015-12-22 23:54:35 +01:00
if ( Id3v2FrameIds : : isLongId ( m_frameId ) ) {
// try to convert the long frame ID to its short equivalent
if ( ! ( m_frameId = Id3v2FrameIds : : convertToShortId ( m_frameId ) ) ) {
m_frame . addNotification ( NotificationType : : Critical , " The long frame ID can't be converted to its short equivalent which is needed to use the frame in the old version of ID3v2. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
}
}
2015-12-22 23:54:35 +01:00
// make actual data depending on the frame ID
2015-04-22 19:22:01 +02:00
try {
2015-12-22 23:54:35 +01:00
if ( Id3v2FrameIds : : isTextFrame ( m_frameId ) ) {
// it is a text frame
if ( ( version > = 3 & & ( m_frameId = = Id3v2FrameIds : : lTrackPosition | | m_frameId = = Id3v2FrameIds : : lDiskPosition ) )
| | ( version < 3 & & m_frameId = = Id3v2FrameIds : : sTrackPosition ) ) {
// track number or the disk number frame
m_frame . makeString ( m_data , m_decompressedSize , m_frame . value ( ) . toString ( ) , TagTextEncoding : : Latin1 ) ;
} else if ( ( version > = 3 & & m_frameId = = Id3v2FrameIds : : lLength )
| | ( version < 3 & & m_frameId = = Id3v2FrameIds : : sLength ) ) {
// length frame
m_frame . makeString ( m_data , m_decompressedSize , ConversionUtilities : : numberToString ( m_frame . value ( ) . toTimeSpan ( ) . totalMilliseconds ( ) ) , TagTextEncoding : : Latin1 ) ;
} else if ( m_frame . value ( ) . type ( ) = = TagDataType : : StandardGenreIndex & & ( ( version > = 3 & & m_frameId = = Id3v2FrameIds : : lGenre )
2016-11-13 23:57:59 +01:00
| | ( version < 3 & & m_frameId = = Id3v2FrameIds : : sGenre ) ) ) {
2015-12-22 23:54:35 +01:00
// pre-defined genre frame
m_frame . makeString ( m_data , m_decompressedSize , ConversionUtilities : : numberToString ( m_frame . value ( ) . toStandardGenreIndex ( ) ) , TagTextEncoding : : Latin1 ) ;
2015-04-22 19:22:01 +02:00
} else {
// any other text frame
2015-12-22 23:54:35 +01:00
m_frame . makeString ( m_data , m_decompressedSize , m_frame . value ( ) . toString ( ) , m_frame . value ( ) . dataEncoding ( ) ) ; // the same as a normal text frame
2015-04-22 19:22:01 +02:00
}
2015-12-22 23:54:35 +01:00
} else if ( version > = 3 & & m_frameId = = Id3v2FrameIds : : lCover ) {
2015-04-22 19:22:01 +02:00
// picture frame
2015-12-22 23:54:35 +01:00
m_frame . makePicture ( m_data , m_decompressedSize , m_frame . value ( ) , m_frame . isTypeInfoAssigned ( ) ? m_frame . typeInfo ( ) : 0 ) ;
} else if ( version < 3 & & m_frameId = = Id3v2FrameIds : : sCover ) {
2015-09-19 22:34:07 +02:00
// legacy picture frame
2015-12-22 23:54:35 +01:00
m_frame . makeLegacyPicture ( m_data , m_decompressedSize , m_frame . value ( ) , m_frame . isTypeInfoAssigned ( ) ? m_frame . typeInfo ( ) : 0 ) ;
} else if ( ( ( version > = 3 & & m_frameId = = Id3v2FrameIds : : lComment )
| | ( version < 3 & & m_frameId = = Id3v2FrameIds : : sComment ) )
| | ( ( version > = 3 & & m_frameId = = Id3v2FrameIds : : lUnsynchronizedLyrics )
| | ( version < 3 & & m_frameId = = Id3v2FrameIds : : sUnsynchronizedLyrics ) ) ) {
2015-04-22 19:22:01 +02:00
// the comment frame or the unsynchronized lyrics frame
2015-12-22 23:54:35 +01:00
m_frame . makeComment ( m_data , m_decompressedSize , m_frame . value ( ) ) ;
2015-04-22 19:22:01 +02:00
} else {
// an unknown frame
// create buffer
2015-12-22 23:54:35 +01:00
m_data = make_unique < char [ ] > ( m_decompressedSize = m_frame . value ( ) . dataSize ( ) ) ;
2015-04-22 19:22:01 +02:00
// just write the data
2015-12-22 23:54:35 +01:00
copy ( m_frame . value ( ) . dataPointer ( ) , m_frame . value ( ) . dataPointer ( ) + m_decompressedSize , m_data . get ( ) ) ;
2015-04-22 19:22:01 +02:00
}
2016-03-18 21:43:09 +01:00
} catch ( const ConversionException & ) {
2015-12-22 23:54:35 +01:00
m_frame . addNotification ( NotificationType : : Critical , " Assigned value can not be converted appropriately. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-12-22 23:54:35 +01:00
// apply compression if frame should be compressed
if ( version > = 3 & & m_frame . isCompressed ( ) ) {
m_dataSize = compressBound ( m_decompressedSize ) ;
auto compressedData = make_unique < char [ ] > ( m_decompressedSize ) ;
switch ( compress ( reinterpret_cast < Bytef * > ( compressedData . get ( ) ) , reinterpret_cast < uLongf * > ( & m_dataSize ) , reinterpret_cast < Bytef * > ( m_data . get ( ) ) , m_decompressedSize ) ) {
2015-04-22 19:22:01 +02:00
case Z_MEM_ERROR :
2015-12-22 23:54:35 +01:00
m_frame . addNotification ( NotificationType : : Critical , " Decompressing failed. The source buffer was too small. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
case Z_BUF_ERROR :
2015-12-22 23:54:35 +01:00
m_frame . addNotification ( NotificationType : : Critical , " Decompressing failed. The destination buffer was too small. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
case Z_OK :
;
}
2015-12-22 23:54:35 +01:00
m_data . swap ( compressedData ) ;
2015-08-16 23:39:42 +02:00
} else {
2015-12-22 23:54:35 +01:00
m_dataSize = m_decompressedSize ;
2015-04-22 19:22:01 +02:00
}
2015-12-22 23:54:35 +01:00
// calculate required size
// -> data size
m_requiredSize = m_dataSize ;
2015-04-22 19:22:01 +02:00
if ( version < 3 ) {
2015-12-22 23:54:35 +01:00
// -> header size
2016-08-05 01:22:46 +02:00
m_requiredSize + = 6 ;
2015-04-22 19:22:01 +02:00
} else {
2015-12-22 23:54:35 +01:00
// -> header size
m_requiredSize + = 10 ;
// -> group byte
if ( m_frame . hasGroupInformation ( ) ) {
m_requiredSize + = 1 ;
2015-04-22 19:22:01 +02:00
}
2015-12-22 23:54:35 +01:00
// -> decompressed size
if ( version > = 3 & & m_frame . isCompressed ( ) ) {
m_requiredSize + = 4 ;
2015-04-22 19:22:01 +02:00
}
}
}
/*!
2015-12-22 23:54:35 +01:00
* \ brief Saves the frame ( specified when constructing the object ) using
* the specified \ a writer .
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
* \ throws Throws Assumes the data is already validated and thus does NOT
* throw Media : : Failure or a derived exception .
2015-04-22 19:22:01 +02:00
*/
2015-12-22 23:54:35 +01:00
void Id3v2FrameMaker : : make ( BinaryWriter & writer )
2015-04-22 19:22:01 +02:00
{
2015-12-22 23:54:35 +01:00
if ( m_version < 3 ) {
writer . writeUInt24BE ( m_frameId ) ;
writer . writeUInt24BE ( m_dataSize ) ;
} else {
writer . writeUInt32BE ( m_frameId ) ;
if ( m_version > = 4 ) {
writer . writeSynchsafeUInt32BE ( m_dataSize ) ;
} else {
writer . writeUInt32BE ( m_dataSize ) ;
}
writer . writeUInt16BE ( m_frame . flag ( ) ) ;
if ( m_frame . hasGroupInformation ( ) ) {
writer . writeByte ( m_frame . group ( ) ) ;
}
if ( m_version > = 3 & & m_frame . isCompressed ( ) ) {
if ( m_version > = 4 ) {
writer . writeSynchsafeUInt32BE ( m_decompressedSize ) ;
} else {
writer . writeUInt32BE ( m_decompressedSize ) ;
}
}
}
writer . write ( m_data . get ( ) , m_dataSize ) ;
2015-04-22 19:22:01 +02:00
}
/*!
* \ brief Returns the text encoding for the specified \ a textEncodingByte .
*
* If the \ a textEncodingByte doesn ' t match any encoding TagTextEncoding : : Latin1 is
* returned and a parsing notification is added .
*/
2015-12-22 23:54:35 +01:00
TagTextEncoding Id3v2Frame : : parseTextEncodingByte ( byte textEncodingByte )
2015-04-22 19:22:01 +02:00
{
switch ( textEncodingByte ) {
case 0 : // Ascii
return TagTextEncoding : : Latin1 ;
case 1 : // Utf 16 with bom
return TagTextEncoding : : Utf16LittleEndian ;
case 2 : // Utf 16 without bom
return TagTextEncoding : : Utf16BigEndian ;
case 3 : // Utf 8
return TagTextEncoding : : Utf8 ;
default :
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Warning , " The charset of the frame is invalid. Latin-1 will be used. " , " parsing encoding of frame " + frameIdString ( ) ) ;
2015-04-22 19:22:01 +02:00
return TagTextEncoding : : Latin1 ;
}
}
/*!
* \ brief Returns a text encoding byte for the specified \ a textEncoding .
*/
2015-12-22 23:54:35 +01:00
byte Id3v2Frame : : makeTextEncodingByte ( TagTextEncoding textEncoding )
2015-04-22 19:22:01 +02:00
{
switch ( textEncoding ) {
case TagTextEncoding : : Latin1 :
return 0 ;
case TagTextEncoding : : Utf8 :
return 3 ;
case TagTextEncoding : : Utf16LittleEndian :
return 1 ;
case TagTextEncoding : : Utf16BigEndian :
return 2 ;
default :
return 0 ;
}
}
/*!
* \ brief Parses a substring in the specified \ a buffer .
*
* This method ensures that byte order marks and termination characters for the specified \ a encoding are omitted .
* It might add a waring if the substring is not terminated .
*
* \ param buffer Specifies a pointer to the possibly terminated string .
* \ param bufferSize Specifies the size of the string in byte .
* \ param encoding Specifies the encoding of the string . Might be adjusted if a byte order marks is found .
* \ param addWarnings Specifies whether warnings should be added to the status provider if the string is not terminated .
* \ returns Returns the start offset , the length of the string ( without termination ) and the end offset ( after termination ) .
* \ remarks The length is always returned as the number of bytes , not as the number of characters ( makes a difference for
2017-05-18 02:26:25 +02:00
* Unicode encodings ) .
2015-04-22 19:22:01 +02:00
*/
2016-06-10 23:08:01 +02:00
tuple < const char * , size_t , const char * > Id3v2Frame : : parseSubstring ( const char * buffer , std : : size_t bufferSize , TagTextEncoding & encoding , bool addWarnings )
2015-04-22 19:22:01 +02:00
{
tuple < const char * , size_t , const char * > res ( buffer , 0 , buffer + bufferSize ) ;
switch ( encoding ) {
case TagTextEncoding : : Utf16BigEndian :
case TagTextEncoding : : Utf16LittleEndian : {
2016-11-13 23:57:59 +01:00
if ( bufferSize > = 2 ) {
if ( ConversionUtilities : : LE : : toUInt16 ( buffer ) = = 0xFEFF ) {
if ( encoding ! = TagTextEncoding : : Utf16LittleEndian ) {
addNotification ( NotificationType : : Critical , " Denoted character set doesn't match present BOM - assuming UTF-16 Little Endian. " , " parsing frame " + frameIdString ( ) ) ;
encoding = TagTextEncoding : : Utf16LittleEndian ;
2015-04-22 19:22:01 +02:00
}
2016-11-13 23:57:59 +01:00
get < 0 > ( res ) + = 2 ;
} else if ( ConversionUtilities : : BE : : toUInt16 ( buffer ) = = 0xFEFF ) {
if ( encoding ! = TagTextEncoding : : Utf16BigEndian ) {
addNotification ( NotificationType : : Critical , " Denoted character set doesn't match present BOM - assuming UTF-16 Big Endian. " , " parsing frame " + frameIdString ( ) ) ;
encoding = TagTextEncoding : : Utf16BigEndian ;
}
get < 0 > ( res ) + = 2 ;
2015-04-22 19:22:01 +02:00
}
2016-11-13 23:57:59 +01:00
}
const uint16 * pos = reinterpret_cast < const uint16 * > ( get < 0 > ( res ) ) ;
for ( ; * pos ! = 0x0000 ; + + pos ) {
if ( pos < reinterpret_cast < const uint16 * > ( get < 2 > ( res ) ) ) {
get < 1 > ( res ) + = 2 ;
} else {
if ( addWarnings ) {
addNotification ( NotificationType : : Warning , " Wide string in frame is not terminated proberly. " , " parsing termination of frame " + frameIdString ( ) ) ;
2015-04-22 19:22:01 +02:00
}
2016-11-13 23:57:59 +01:00
break ;
2015-04-22 19:22:01 +02:00
}
}
2016-11-13 23:57:59 +01:00
get < 2 > ( res ) = reinterpret_cast < const char * > ( + + pos ) ;
break ;
}
2015-04-22 19:22:01 +02:00
default : {
2016-11-13 23:57:59 +01:00
if ( ( bufferSize > = 3 ) & & ( ConversionUtilities : : BE : : toUInt24 ( buffer ) = = 0x00EFBBBF ) ) {
get < 0 > ( res ) + = 3 ;
if ( encoding ! = TagTextEncoding : : Utf8 ) {
addNotification ( NotificationType : : Critical , " Denoted character set doesn't match present BOM - assuming UTF-8. " , " parsing frame " + frameIdString ( ) ) ;
encoding = TagTextEncoding : : Utf8 ;
2015-04-22 19:22:01 +02:00
}
2016-11-13 23:57:59 +01:00
}
const char * pos = get < 0 > ( res ) ;
for ( ; * pos ! = 0x00 ; + + pos ) {
if ( pos < get < 2 > ( res ) ) {
+ + get < 1 > ( res ) ;
} else {
if ( addWarnings ) {
addNotification ( NotificationType : : Warning , " String in frame is not terminated proberly. " , " parsing termination of frame " + frameIdString ( ) ) ;
2015-04-22 19:22:01 +02:00
}
2016-11-13 23:57:59 +01:00
break ;
2015-04-22 19:22:01 +02:00
}
}
2016-11-13 23:57:59 +01:00
get < 2 > ( res ) = + + pos ;
break ;
}
2015-04-22 19:22:01 +02:00
}
return res ;
}
/*!
* \ brief Parses a substring in the specified \ a buffer .
*
2015-12-22 23:54:35 +01:00
* Same as Id3v2Frame : : parseSubstring ( ) but returns the substring as string object .
2015-04-22 19:22:01 +02:00
*/
2015-12-22 23:54:35 +01:00
string Id3v2Frame : : parseString ( const char * buffer , size_t dataSize , TagTextEncoding & encoding , bool addWarnings )
2015-04-22 19:22:01 +02:00
{
auto substr = parseSubstring ( buffer , dataSize , encoding , addWarnings ) ;
return string ( get < 0 > ( substr ) , get < 1 > ( substr ) ) ;
}
/*!
* \ brief Parses a substring in the specified \ a buffer .
*
2016-11-13 23:56:49 +01:00
* Same as Id3v2Frame : : parseSubstring ( ) but returns the substring as u16string object
*
* \ remarks Converts byte order to match host byte order ( otherwise it wouldn ' t make much sense to use the resulting u16string ) .
2015-04-22 19:22:01 +02:00
*/
2016-02-23 20:33:00 +01:00
u16string Id3v2Frame : : parseWideString ( const char * buffer , size_t dataSize , TagTextEncoding & encoding , bool addWarnings )
2015-04-22 19:22:01 +02:00
{
auto substr = parseSubstring ( buffer , dataSize , encoding , addWarnings ) ;
2016-11-13 23:56:49 +01:00
u16string res ( reinterpret_cast < u16string : : const_pointer > ( get < 0 > ( substr ) ) , get < 1 > ( substr ) / 2 ) ;
if ( encoding ! =
2016-11-13 23:57:59 +01:00
# if defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)
2016-11-13 23:56:49 +01:00
TagTextEncoding : : Utf16LittleEndian
2016-11-13 23:57:59 +01:00
# elif defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN)
2016-11-13 23:56:49 +01:00
TagTextEncoding : : Utf16BigEndian
2016-11-13 23:57:59 +01:00
# else
# error "Host byte order not supported"
# endif
2016-11-13 23:56:49 +01:00
) {
// ensure byte order matches host byte order
for ( auto & c : res ) {
c = ( ( c > > 8 ) & 0x00FF ) | ( ( c < < 8 ) & 0xFF00 ) ;
}
}
return res ;
2015-04-22 19:22:01 +02:00
}
/*!
* \ brief Parses a byte order mark from the specified \ a buffer .
*
* \ param buffer Specifies the buffer holding the byte order mark .
* \ param maxSize Specifies the maximal number of bytes to read from the buffer .
* \ param encoding Specifies the encoding of the string . Might be reset if a byte order mark is found .
*
* \ remarks This method is not used anymore and might be deleted .
*/
2015-12-22 23:54:35 +01:00
void Id3v2Frame : : parseBom ( const char * buffer , size_t maxSize , TagTextEncoding & encoding )
2015-04-22 19:22:01 +02:00
{
switch ( encoding ) {
case TagTextEncoding : : Utf16BigEndian :
case TagTextEncoding : : Utf16LittleEndian :
if ( ( maxSize > = 2 ) & & ( ConversionUtilities : : BE : : toUInt16 ( buffer ) = = 0xFFFE ) ) {
encoding = TagTextEncoding : : Utf16LittleEndian ;
} else if ( ( maxSize > = 2 ) & & ( ConversionUtilities : : BE : : toUInt16 ( buffer ) = = 0xFEFF ) ) {
encoding = TagTextEncoding : : Utf16BigEndian ;
}
break ;
default :
if ( ( maxSize > = 3 ) & & ( ConversionUtilities : : BE : : toUInt24 ( buffer ) = = 0x00EFBBBF ) ) {
encoding = TagTextEncoding : : Utf8 ;
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Warning , " UTF-8 byte order mark found in text frame. " , " parsing byte oder mark of frame " + frameIdString ( ) ) ;
2015-04-22 19:22:01 +02:00
}
}
}
/*!
2015-09-19 22:34:07 +02:00
* \ brief Parses the ID3v2 .2 picture from the specified \ a buffer .
* \ param buffer Specifies the buffer holding the picture .
* \ param maxSize Specifies the maximal number of bytes to read from the buffer .
* \ param tagValue Specifies the tag value used to store the results .
* \ param typeInfo Specifies a byte used to store the type info .
*/
2016-06-10 23:08:01 +02:00
void Id3v2Frame : : parseLegacyPicture ( const char * buffer , std : : size_t maxSize , TagValue & tagValue , byte & typeInfo )
2015-09-19 22:34:07 +02:00
{
static const string context ( " parsing ID3v2.2 picture frame " ) ;
if ( maxSize < 6 ) {
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Critical , " Picture frame is incomplete. " , context ) ;
2015-09-19 22:34:07 +02:00
throw TruncatedDataException ( ) ;
}
const char * end = buffer + maxSize ;
auto dataEncoding = parseTextEncodingByte ( * buffer ) ; // the first byte stores the encoding
//auto imageFormat = parseSubstring(buffer + 1, 3, TagTextEncoding::Latin1);
typeInfo = static_cast < unsigned char > ( * ( buffer + 4 ) ) ;
auto substr = parseSubstring ( buffer + 5 , end - 5 - buffer , dataEncoding , true ) ;
tagValue . setDescription ( string ( get < 0 > ( substr ) , get < 1 > ( substr ) ) , dataEncoding ) ;
if ( get < 2 > ( substr ) > = end ) {
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Critical , " Picture frame is incomplete (actual data is missing). " , context ) ;
2015-09-19 22:34:07 +02:00
throw TruncatedDataException ( ) ;
}
tagValue . assignData ( get < 2 > ( substr ) , end - get < 2 > ( substr ) , TagDataType : : Picture , dataEncoding ) ;
}
/*!
* \ brief Parses the ID3v2 .3 picture from the specified \ a buffer .
2015-04-22 19:22:01 +02:00
* \ param buffer Specifies the buffer holding the picture .
* \ param maxSize Specifies the maximal number of bytes to read from the buffer .
* \ param tagValue Specifies the tag value used to store the results .
* \ param typeInfo Specifies a byte used to store the type info .
*/
2016-06-10 23:08:01 +02:00
void Id3v2Frame : : parsePicture ( const char * buffer , std : : size_t maxSize , TagValue & tagValue , byte & typeInfo )
2015-04-22 19:22:01 +02:00
{
2015-09-19 22:34:07 +02:00
static const string context ( " parsing ID3v2.3 picture frame " ) ;
2015-04-22 19:22:01 +02:00
const char * end = buffer + maxSize ;
auto dataEncoding = parseTextEncodingByte ( * buffer ) ; // the first byte stores the encoding
2015-09-19 22:34:07 +02:00
auto mimeTypeEncoding = TagTextEncoding : : Latin1 ;
auto substr = parseSubstring ( buffer + 1 , maxSize - 1 , mimeTypeEncoding , true ) ;
2015-04-22 19:22:01 +02:00
if ( get < 1 > ( substr ) ) {
tagValue . setMimeType ( string ( get < 0 > ( substr ) , get < 1 > ( substr ) ) ) ;
}
if ( get < 2 > ( substr ) > = end ) {
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Critical , " Picture frame is incomplete (type info, description and actual data are missing). " , context ) ;
2015-04-22 19:22:01 +02:00
throw TruncatedDataException ( ) ;
}
typeInfo = static_cast < unsigned char > ( * get < 2 > ( substr ) ) ;
if ( + + get < 2 > ( substr ) > = end ) {
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Critical , " Picture frame is incomplete (description and actual data are missing). " , context ) ;
2015-04-22 19:22:01 +02:00
throw TruncatedDataException ( ) ;
}
substr = parseSubstring ( get < 2 > ( substr ) , end - get < 2 > ( substr ) , dataEncoding , true ) ;
tagValue . setDescription ( string ( get < 0 > ( substr ) , get < 1 > ( substr ) ) , dataEncoding ) ;
if ( get < 2 > ( substr ) > = end ) {
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Critical , " Picture frame is incomplete (actual data is missing). " , context ) ;
2015-04-22 19:22:01 +02:00
throw TruncatedDataException ( ) ;
}
tagValue . assignData ( get < 2 > ( substr ) , end - get < 2 > ( substr ) , TagDataType : : Picture , dataEncoding ) ;
}
/*!
2016-03-18 21:43:09 +01:00
* \ brief Parses the comment / unsynchronized lyrics from the specified \ a buffer .
2015-04-22 19:22:01 +02:00
* \ param buffer Specifies the buffer holding the picture .
* \ param dataSize Specifies the maximal number of bytes to read from the buffer .
* \ param tagValue Specifies the tag value used to store the results .
*/
2016-06-10 23:08:01 +02:00
void Id3v2Frame : : parseComment ( const char * buffer , std : : size_t dataSize , TagValue & tagValue )
2015-04-22 19:22:01 +02:00
{
2016-03-18 21:43:09 +01:00
static const string context ( " parsing comment/unsynchronized lyrics frame " ) ;
2015-04-22 19:22:01 +02:00
const char * end = buffer + dataSize ;
2016-05-06 21:59:19 +02:00
if ( dataSize < 5 ) {
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Critical , " Comment frame is incomplete. " , context ) ;
2015-04-22 19:22:01 +02:00
throw TruncatedDataException ( ) ;
}
TagTextEncoding dataEncoding = parseTextEncodingByte ( * buffer ) ;
2016-03-18 21:43:09 +01:00
if ( * ( + + buffer ) ) {
tagValue . setLanguage ( string ( buffer , 3 ) ) ;
}
2015-04-22 19:22:01 +02:00
auto substr = parseSubstring ( buffer + = 3 , dataSize - = 4 , dataEncoding , true ) ;
tagValue . setDescription ( string ( get < 0 > ( substr ) , get < 1 > ( substr ) ) , dataEncoding ) ;
2016-05-06 21:59:19 +02:00
if ( get < 2 > ( substr ) > end ) {
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Critical , " Comment frame is incomplete (description not terminated?). " , context ) ;
2015-04-22 19:22:01 +02:00
throw TruncatedDataException ( ) ;
}
substr = parseSubstring ( get < 2 > ( substr ) , end - get < 2 > ( substr ) , dataEncoding , false ) ;
tagValue . assignData ( get < 0 > ( substr ) , get < 1 > ( substr ) , TagDataType : : Text , dataEncoding ) ;
}
/*!
* \ brief Writes an encoding denoation and the specified string \ a value to a \ a buffer .
* \ param buffer Specifies the buffer .
2016-06-15 22:53:39 +02:00
* \ param bufferSize Specifies the size of \ a buffer .
2015-04-22 19:22:01 +02:00
* \ param value Specifies the string to make .
* \ param encoding Specifies the encoding of the string to make .
*/
2016-06-10 23:08:01 +02:00
void Id3v2Frame : : makeString ( std : : unique_ptr < char [ ] > & buffer , uint32 & bufferSize , const std : : string & value , TagTextEncoding encoding )
2015-04-22 19:22:01 +02:00
{
2015-12-22 23:54:35 +01:00
makeEncodingAndData ( buffer , bufferSize , encoding , value . data ( ) , value . size ( ) ) ;
2015-04-22 19:22:01 +02:00
}
/*!
* \ brief Writes an encoding denoation and the specified \ a data to a \ a buffer .
* \ param buffer Specifies the buffer .
2016-06-10 23:08:01 +02:00
* \ param bufferSize Specifies the size of \ a buffer .
2015-04-22 19:22:01 +02:00
* \ param encoding Specifies the data encoding .
* \ param data Specifies the data .
2016-06-10 23:08:01 +02:00
* \ param dataSize Specifies size of \ a data .
2015-04-22 19:22:01 +02:00
*/
2016-06-10 23:08:01 +02:00
void Id3v2Frame : : makeEncodingAndData ( std : : unique_ptr < char [ ] > & buffer , uint32 & bufferSize , TagTextEncoding encoding , const char * data , std : : size_t dataSize )
2015-04-22 19:22:01 +02:00
{
// calculate buffer size
if ( ! data ) {
dataSize = 0 ;
}
2017-05-18 02:26:25 +02:00
char * bufferDataAddress ;
2015-04-22 19:22:01 +02:00
switch ( encoding ) {
case TagTextEncoding : : Latin1 :
case TagTextEncoding : : Utf8 :
case TagTextEncoding : : Unspecified : // assumption
2016-11-13 23:57:59 +01:00
// allocate buffer
2015-08-16 23:39:42 +02:00
buffer = make_unique < char [ ] > ( bufferSize = 1 + dataSize + 1 ) ;
2017-05-18 02:26:25 +02:00
buffer [ 0 ] = makeTextEncodingByte ( encoding ) ; // set text encoding byte
bufferDataAddress = buffer . get ( ) + 1 ;
2015-04-22 19:22:01 +02:00
break ;
case TagTextEncoding : : Utf16LittleEndian :
case TagTextEncoding : : Utf16BigEndian :
2015-08-16 23:39:42 +02:00
// allocate buffer
2017-05-18 02:26:25 +02:00
buffer = make_unique < char [ ] > ( bufferSize = 1 + 2 + dataSize + 2 ) ;
buffer [ 0 ] = makeTextEncodingByte ( encoding ) ; // set text encoding byte
ConversionUtilities : : LE : : getBytes ( encoding = = TagTextEncoding : : Utf16LittleEndian ? static_cast < uint16 > ( 0xFEFF ) : static_cast < uint16 > ( 0xFFFE ) , buffer . get ( ) + 1 ) ;
bufferDataAddress = buffer . get ( ) + 3 ;
2015-04-22 19:22:01 +02:00
break ;
}
2017-05-18 02:26:25 +02:00
if ( dataSize ) {
copy ( data , data + dataSize , bufferDataAddress ) ; // write string data
}
}
/*!
* \ brief Writes the BOM for the specified \ a encoding to the specified \ a buffer .
* \ returns Returns the number of bytes written to the buffer .
*/
size_t Id3v2Frame : : makeBom ( char * buffer , TagTextEncoding encoding )
{
switch ( encoding ) {
case TagTextEncoding : : Utf16LittleEndian :
ConversionUtilities : : LE : : getBytes ( static_cast < uint16 > ( 0xFEFF ) , buffer ) ;
return 2 ;
case TagTextEncoding : : Utf16BigEndian :
ConversionUtilities : : BE : : getBytes ( static_cast < uint16 > ( 0xFEFF ) , buffer ) ;
return 2 ;
default :
return 0 ;
2015-04-22 19:22:01 +02:00
}
}
/*!
2015-09-19 22:34:07 +02:00
* \ brief Writes the specified picture to the specified buffer ( ID3v2 .2 ) .
*/
2015-12-22 23:54:35 +01:00
void Id3v2Frame : : makeLegacyPicture ( unique_ptr < char [ ] > & buffer , uint32 & bufferSize , const TagValue & picture , byte typeInfo )
2015-09-19 22:34:07 +02:00
{
// calculate needed buffer size and create buffer
2017-05-18 02:26:25 +02:00
const TagTextEncoding descriptionEncoding = picture . descriptionEncoding ( ) ;
const uint32 dataSize = picture . dataSize ( ) ;
string : : size_type descriptionLength = picture . description ( ) . find ( descriptionEncoding = = TagTextEncoding : : Utf16BigEndian | | descriptionEncoding = = TagTextEncoding : : Utf16LittleEndian ? " \0 \0 " : " \0 " ) ;
2015-09-19 22:34:07 +02:00
if ( descriptionLength = = string : : npos ) {
descriptionLength = picture . description ( ) . length ( ) ;
}
2017-05-18 02:26:25 +02:00
buffer = make_unique < char [ ] > ( bufferSize = 1 + 3 + 1 + descriptionLength + ( descriptionEncoding = = TagTextEncoding : : Utf16BigEndian | | descriptionEncoding = = TagTextEncoding : : Utf16LittleEndian ? 4 : 1 ) + dataSize ) ;
2015-09-19 22:34:07 +02:00
// note: encoding byte + image format + picture type byte + description length + 1 or 2 null bytes (depends on encoding) + data size
char * offset = buffer . get ( ) ;
// write encoding byte
* offset = makeTextEncodingByte ( descriptionEncoding ) ;
// write mime type
const char * imageFormat ;
if ( picture . mimeType ( ) = = " image/jpeg " ) {
imageFormat = " JPG " ;
} else if ( picture . mimeType ( ) = = " image/png " ) {
imageFormat = " PNG " ;
} else if ( picture . mimeType ( ) = = " image/gif " ) {
imageFormat = " GIF " ;
} else if ( picture . mimeType ( ) = = " --> " ) {
imageFormat = picture . mimeType ( ) . data ( ) ;
} else {
imageFormat = " UND " ;
}
strncpy ( + + offset , imageFormat , 3 ) ;
// write picture type
* ( offset + = 3 ) = typeInfo ;
// write description
2017-05-18 02:26:25 +02:00
offset + = makeBom ( offset + 1 , descriptionEncoding ) ;
2015-09-19 22:34:07 +02:00
picture . description ( ) . copy ( + + offset , descriptionLength ) ;
* ( offset + = descriptionLength ) = 0x00 ; // terminate description and increase data offset
if ( descriptionEncoding = = TagTextEncoding : : Utf16BigEndian | | descriptionEncoding = = TagTextEncoding : : Utf16LittleEndian ) {
* ( + + offset ) = 0x00 ;
}
// write actual data
copy ( picture . dataPointer ( ) , picture . dataPointer ( ) + picture . dataSize ( ) , + + offset ) ;
}
/*!
* \ brief Writes the specified picture to the specified buffer ( ID3v2 .3 ) .
2015-04-22 19:22:01 +02:00
*/
2015-12-22 23:54:35 +01:00
void Id3v2Frame : : makePicture ( unique_ptr < char [ ] > & buffer , uint32 & bufferSize , const TagValue & picture , byte typeInfo )
2015-04-22 19:22:01 +02:00
{
// calculate needed buffer size and create buffer
2017-05-18 02:26:25 +02:00
const TagTextEncoding descriptionEncoding = picture . descriptionEncoding ( ) ;
const uint32 dataSize = picture . dataSize ( ) ;
2015-04-22 19:22:01 +02:00
string : : size_type mimeTypeLength = picture . mimeType ( ) . find ( ' \0 ' ) ;
if ( mimeTypeLength = = string : : npos ) {
mimeTypeLength = picture . mimeType ( ) . length ( ) ;
}
2017-05-18 02:26:25 +02:00
string : : size_type descriptionLength = picture . description ( ) . find ( descriptionEncoding = = TagTextEncoding : : Utf16BigEndian | | descriptionEncoding = = TagTextEncoding : : Utf16LittleEndian ? " \0 \0 " : " \0 " ) ;
2015-04-22 19:22:01 +02:00
if ( descriptionLength = = string : : npos ) {
descriptionLength = picture . description ( ) . length ( ) ;
}
2017-05-18 02:26:25 +02:00
buffer = make_unique < char [ ] > ( bufferSize = 1 + mimeTypeLength + 1 + 1 + descriptionLength + ( descriptionEncoding = = TagTextEncoding : : Utf16BigEndian | | descriptionEncoding = = TagTextEncoding : : Utf16LittleEndian ? 4 : 1 ) + dataSize ) ;
// note: encoding byte + mime type length + 0 byte + picture type byte + description length + 1 or 4 null bytes (depends on encoding) + data size
2015-08-16 23:39:42 +02:00
char * offset = buffer . get ( ) ;
2015-04-22 19:22:01 +02:00
// write encoding byte
* offset = makeTextEncodingByte ( descriptionEncoding ) ;
// write mime type
picture . mimeType ( ) . copy ( + + offset , mimeTypeLength ) ;
2015-09-19 22:34:07 +02:00
* ( offset + = mimeTypeLength ) = 0x00 ; // terminate mime type
2015-04-22 19:22:01 +02:00
// write picture type
* ( + + offset ) = typeInfo ;
// write description
2017-05-18 02:26:25 +02:00
offset + = makeBom ( offset + 1 , descriptionEncoding ) ;
2015-04-22 19:22:01 +02:00
picture . description ( ) . copy ( + + offset , descriptionLength ) ;
2015-09-19 22:34:07 +02:00
* ( offset + = descriptionLength ) = 0x00 ; // terminate description and increase data offset
2015-04-22 19:22:01 +02:00
if ( descriptionEncoding = = TagTextEncoding : : Utf16BigEndian | | descriptionEncoding = = TagTextEncoding : : Utf16LittleEndian ) {
* ( + + offset ) = 0x00 ;
}
// write actual data
copy ( picture . dataPointer ( ) , picture . dataPointer ( ) + picture . dataSize ( ) , + + offset ) ;
}
/*!
* \ brief Writes the specified comment to the specified buffer .
*/
2015-12-22 23:54:35 +01:00
void Id3v2Frame : : makeComment ( unique_ptr < char [ ] > & buffer , uint32 & bufferSize , const TagValue & comment )
2015-04-22 19:22:01 +02:00
{
static const string context ( " making comment frame " ) ;
// check type and other values are valid
TagTextEncoding encoding = comment . dataEncoding ( ) ;
if ( ! comment . description ( ) . empty ( ) & & encoding ! = comment . descriptionEncoding ( ) ) {
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Critical , " Data enoding and description encoding aren't equal. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
const string & lng = comment . language ( ) ;
if ( lng . length ( ) > 3 ) {
2015-12-22 23:54:35 +01:00
addNotification ( NotificationType : : Critical , " The language must be 3 bytes long (ISO-639-2). " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
// calculate needed buffer size and create buffer
2017-05-18 02:26:25 +02:00
string : : size_type descriptionLength = comment . description ( ) . find ( encoding = = TagTextEncoding : : Utf16BigEndian | | encoding = = TagTextEncoding : : Utf16LittleEndian ? " \0 \0 " : " \0 " ) ;
2016-08-05 01:22:46 +02:00
if ( descriptionLength = = string : : npos ) {
2015-04-22 19:22:01 +02:00
descriptionLength = comment . description ( ) . length ( ) ;
2016-08-05 01:22:46 +02:00
}
const auto data = comment . toString ( ) ;
2017-05-18 02:26:25 +02:00
buffer = make_unique < char [ ] > ( bufferSize = 1 + 3 + descriptionLength + data . size ( ) + ( encoding = = TagTextEncoding : : Utf16BigEndian | | encoding = = TagTextEncoding : : Utf16LittleEndian ? 6 : 1 ) + data . size ( ) ) ;
// note: encoding byte + language + description length + actual data size + BOMs and termination
2015-08-16 23:39:42 +02:00
char * offset = buffer . get ( ) ;
2015-04-22 19:22:01 +02:00
// write encoding
* offset = makeTextEncodingByte ( encoding ) ;
// write language
for ( unsigned int i = 0 ; i < 3 ; + + i ) {
2016-08-05 01:22:46 +02:00
* ( + + offset ) = ( lng . length ( ) > i ) ? lng [ i ] : 0x00 ;
2015-04-22 19:22:01 +02:00
}
// write description
2017-05-18 02:26:25 +02:00
offset + = makeBom ( offset + 1 , encoding ) ;
2015-04-22 19:22:01 +02:00
comment . description ( ) . copy ( + + offset , descriptionLength ) ;
offset + = descriptionLength ;
* offset = 0x00 ; // terminate description and increase data offset
if ( encoding = = TagTextEncoding : : Utf16BigEndian | | encoding = = TagTextEncoding : : Utf16LittleEndian ) {
* ( + + offset ) = 0x00 ;
}
// write actual data
2017-05-18 02:26:25 +02:00
offset + = makeBom ( offset + 1 , encoding ) ;
2016-08-05 01:22:46 +02:00
data . copy ( + + offset , data . size ( ) ) ;
2015-04-22 19:22:01 +02:00
}
}