make use of padding when applying changes to MP3 files

This commit is contained in:
Martchus 2015-12-22 23:54:35 +01:00
parent 979427beb3
commit 6f0adce661
14 changed files with 704 additions and 376 deletions

View File

@ -8,11 +8,25 @@ The tag library can read and write the following tag formats:
- Vorbis comments (cover art via "METADATA_BLOCK_PICTURE" is supported)
- Matroska/WebM tags and attachments
## File layout options
### Tag position
The library allows you to choose whether tags should be placed at the beginning or at
the end of an MP4/Matroska file.
### Padding
Padding allows adding additional tag information without rewriting the entire file
or appending the tag. Usage of padding can be configured:
- minimum/maximum padding: The file is rewritten if the padding would fall below/exceed the specifed limits.
- preferred padding: If the file needs to be rewritten the preferred padding is used.
However, it is also possible to force rewriting the entire file.
## Additional features
The library can also display technical information such as the ID, format, language, bitrate,
duration, size, timestamps, sampling frequency, FPS and other information of the tracks.
It also allows to inspect and validate the element structure of MP4 and Matroska files.
## Usage
For examples check out the CLI interface of my Tag Editor (which is also on Git).
## Build instructions
@ -20,6 +34,5 @@ The tagparser library depends on c++utilities and is built in the same way.
It also depends on zlib.
## TODO
- Use padding to prevent rewriting the entire file to save tags.
- Support more tag formats (EXIF, PDF metadata, ...).
- Do tests with Matroska files which have multiple segments.

View File

@ -39,7 +39,7 @@ Id3v2Frame::Id3v2Frame() :
/*!
* \brief Constructs a new Id3v2Frame with the specified \a id, \a value, \a group and \a flag.
*/
Id3v2Frame::Id3v2Frame(const identifierType &id, const TagValue &value, byte group, int16 flag) :
Id3v2Frame::Id3v2Frame(const identifierType &id, const TagValue &value, const byte group, const int16 flag) :
TagField<Id3v2Frame>(id, value),
m_flag(flag),
m_group(group),
@ -59,41 +59,57 @@ Id3v2Frame::Id3v2Frame(const identifierType &id, const TagValue &value, byte gro
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
*/
void Id3v2Frame::parse(BinaryReader &reader, int32 version, uint32 maximalSize)
void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 maximalSize)
{
invalidateStatus();
string context("parsing ID3v2 frame");
Id3v2FrameHelper helper(frameIdString(), *this);
// parse header
if(version < 3) {
// parse header for ID3v2.1 and ID3v2.2
// -> read ID
setId(reader.readUInt24BE());
if((id() & 0xFFFF0000u) == 0) {
// padding reached
m_padding = true;
addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", context);
throw NoDataFoundException();
} else {
m_padding = false;
}
context = "parsing " + helper.id() + " frame";
// -> update context
context = "parsing " + frameIdString() + " frame";
// -> read size, check whether frame is truncated
m_dataSize = reader.readUInt24BE();
m_totalSize = m_dataSize + 6;
if(m_totalSize > maximalSize) {
addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", "parsing " + frameIdString() + " frame");
throw TruncatedDataException();
}
// -> no flags/group in ID3v2.2
m_flag = 0;
m_group = 0;
} else {
// parse header for ID3v2.3 and ID3v2.4
// -> read ID
setId(reader.readUInt32BE());
if((id() & 0xFF000000u) == 0) {
// padding reached
m_padding = true;
addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", context);
throw NoDataFoundException();
} else {
m_padding = false;
}
context = "parsing " + helper.id() + " frame";
// -> update context
context = "parsing " + frameIdString() + " frame";
// -> read size, check whether frame is truncated
m_dataSize = version >= 4
? reader.readSynchsafeUInt32BE()
: reader.readUInt32BE();
@ -102,27 +118,34 @@ void Id3v2Frame::parse(BinaryReader &reader, int32 version, uint32 maximalSize)
addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", context);
throw TruncatedDataException();
}
// -> read flags and group
m_flag = reader.readUInt16BE();
m_group = hasGroupInformation() ? reader.readByte() : 0;
if(isEncrypted()) {
// encryption is not implemented
addNotification(NotificationType::Critical, "Encrypted frames aren't supported.", context);
throw VersionNotSupportedException();
}
}
// frame size mustn't be 0
if(m_dataSize <= 0) {
addNotification(NotificationType::Critical, "The frame size is 0.", context);
throw InvalidDataException();
}
// parse the data
unique_ptr<char[]> buffer;
// -> decompress data if compressed; otherwise just read it
if(isCompressed()) {
// decompress compressed data
uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
if(decompressedSize < m_dataSize) {
addNotification(NotificationType::Critical, "The decompressed size is smaller then the compressed size.", context);
throw InvalidDataException();
}
unique_ptr<char[]> bufferCompressed = make_unique<char[]>(m_dataSize);;
auto bufferCompressed = make_unique<char[]>(m_dataSize);;
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)) {
@ -136,16 +159,21 @@ void Id3v2Frame::parse(BinaryReader &reader, int32 version, uint32 maximalSize)
addNotification(NotificationType::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
throw InvalidDataException();
case Z_OK:
;
break;
default:
addNotification(NotificationType::Critical, "Decompressing failed (unknown reason).", context);
throw InvalidDataException();
}
m_dataSize = decompressedSize;
} else {
buffer = make_unique<char[]>(m_dataSize);
reader.read(buffer.get(), m_dataSize);
}
if(Id3v2FrameIds::isTextfield(id())) {
// -> get tag value depending of field type
if(Id3v2FrameIds::isTextFrame(id())) {
// frame contains text
TagTextEncoding dataEncoding = helper.parseTextEncodingByte(*buffer.get()); // the first byte stores the encoding
TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer.get()); // the first byte stores the encoding
if((version >= 3 &&
(id() == Id3v2FrameIds::lTrackPosition || id() == Id3v2FrameIds::lDiskPosition))
|| (version < 3 && id() == Id3v2FrameIds::sTrackPosition)) {
@ -153,40 +181,42 @@ void Id3v2Frame::parse(BinaryReader &reader, int32 version, uint32 maximalSize)
try {
PositionInSet position;
if(characterSize(dataEncoding) > 1) {
position = PositionInSet(helper.parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
position = PositionInSet(parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
} else {
position = PositionInSet(helper.parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
position = PositionInSet(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
}
value().assignPosition(position);
} catch(ConversionException &) {
addNotification(NotificationType::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
}
} else if((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
// frame contains length
double milliseconds;
try {
if(characterSize(dataEncoding) > 1) {
wstring millisecondsStr = helper.parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
wstring millisecondsStr = parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
milliseconds = ConversionUtilities::stringToNumber<double, wstring>(millisecondsStr, 10);
} else {
milliseconds = ConversionUtilities::stringToNumber<double>(helper.parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding), 10);
milliseconds = ConversionUtilities::stringToNumber<double>(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding), 10);
}
value().assignTimeSpan(TimeSpan::fromMilliseconds(milliseconds));
} catch (ConversionException &) {
addNotification(NotificationType::Warning, "The value of the length frame is not numeric and will be ignored.", context);
}
} else if((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
// genre/content type
int genreIndex;
try {
if(characterSize(dataEncoding) > 1) {
wstring indexStr = helper.parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
wstring indexStr = parseWideString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
if(indexStr.front() == L'(' && indexStr.back() == L')') {
indexStr = indexStr.substr(1, indexStr.length() - 2);
}
genreIndex = ConversionUtilities::stringToNumber<int, wstring>(indexStr, 10);
} else {
string indexStr = helper.parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
string indexStr = parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding);
if(indexStr.front() == '(' && indexStr.back() == ')') {
indexStr = indexStr.substr(1, indexStr.length() - 2);
}
@ -196,169 +226,64 @@ void Id3v2Frame::parse(BinaryReader &reader, int32 version, uint32 maximalSize)
} catch(ConversionException &) {
// genre is specified as string
// string might be null terminated
auto substr = helper.parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
}
} else { // any other text frame
// string might be null terminated
auto substr = helper.parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
auto substr = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding);
value().assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
}
} else if(version >= 3 && id() == Id3v2FrameIds::lCover) {
// frame stores picture
byte type;
helper.parsePicture(buffer.get(), m_dataSize, value(), type);
parsePicture(buffer.get(), m_dataSize, value(), type);
setTypeInfo(type);
} else if(version < 3 && id() == Id3v2FrameIds::sCover) {
// frame stores legacy picutre
byte type;
helper.parseLegacyPicture(buffer.get(), m_dataSize, value(), type);
parseLegacyPicture(buffer.get(), m_dataSize, value(), type);
setTypeInfo(type);
} else if(((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
|| ((version >= 3 && id() == Id3v2FrameIds::lUnsynchronizedLyrics) || (version < 3 && id() == Id3v2FrameIds::sUnsynchronizedLyrics))) {
// comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
helper.parseComment(buffer.get(), m_dataSize, value());
parseComment(buffer.get(), m_dataSize, value());
} else {
// unknown frame
value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
}
}
/*!
* \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);
}
/*!
* \brief Writes the frame to a stream using the specified \a writer and the
* specified ID3v2 version.
* specified ID3v2 \a version.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Media::Failure or a derived exception when a making
* error occurs.
*/
void Id3v2Frame::make(IoUtilities::BinaryWriter &writer, int32 version)
void Id3v2Frame::make(BinaryWriter &writer, const uint32 version)
{
invalidateStatus();
Id3v2FrameHelper helper(frameIdString(), *this);
const string context("making " + helper.id() + " frame");
// check if a valid frame can be build from the data
if(value().isEmpty()) {
addNotification(NotificationType::Critical, "Cannot make an empty frame.", context);
throw InvalidDataException();
}
if(isEncrypted()) {
addNotification(NotificationType::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
throw InvalidDataException();
}
if(m_padding) {
addNotification(NotificationType::Critical, "Cannot make a frame which is marked as padding.", context);
throw InvalidDataException();
}
uint32 frameId = id();
if(version >= 3) {
if(Id3v2FrameIds::isShortId(frameId)) {
// try to convert the short frame id to its long equivalent
frameId = Id3v2FrameIds::convertToLongId(frameId);
if(frameId == 0) {
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);
throw InvalidDataException();
}
}
} else {
if(Id3v2FrameIds::isLongId(frameId)) {
// try to convert the long frame id to its short equivalent
frameId = Id3v2FrameIds::convertToShortId(frameId);
if(frameId == 0) {
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);
throw InvalidDataException();
}
}
}
if(version < 3 && (m_flag != 0 || m_group != 0)) {
addNotification(NotificationType::Warning, "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
}
// create actual data, depending on the frame type
unique_ptr<char[]> buffer;
uint32 decompressedSize;
// check if the frame to be written is a text frame
try {
if(Id3v2FrameIds::isTextfield(frameId)) {
if((version >= 3 && (frameId == Id3v2FrameIds::lTrackPosition || frameId == Id3v2FrameIds::lDiskPosition))
|| (version < 3 && frameId == Id3v2FrameIds::sTrackPosition)) {
// the track number or the disk number frame
helper.makeString(buffer, decompressedSize, value().toString(), TagTextEncoding::Latin1);
} else if((version >= 3 && frameId == Id3v2FrameIds::lLength)
|| (version < 3 && frameId == Id3v2FrameIds::sLength)) {
// the length
helper.makeString(buffer, decompressedSize, ConversionUtilities::numberToString(value().toTimeSpan().totalMilliseconds()), TagTextEncoding::Latin1);
} else if(value().type() == TagDataType::StandardGenreIndex && ((version >= 3 && frameId == Id3v2FrameIds::lGenre)
|| (version < 3 && frameId == Id3v2FrameIds::sGenre))) {
// genre/content type as standard genre index
helper.makeString(buffer, decompressedSize, ConversionUtilities::numberToString(value().toStandardGenreIndex()), TagTextEncoding::Latin1);
} else {
// any other text frame
helper.makeString(buffer, decompressedSize, value().toString(), value().dataEncoding()); // the same as a normal text frame
}
} else if(version >= 3 && frameId == Id3v2FrameIds::lCover) {
// picture frame
helper.makePicture(buffer, decompressedSize, value(), isTypeInfoAssigned() ? typeInfo() : 0);
} else if(version < 3 && frameId == Id3v2FrameIds::sCover) {
// legacy picture frame
helper.makeLegacyPicture(buffer, decompressedSize, value(), isTypeInfoAssigned() ? typeInfo() : 0);
} else if(((version >= 3 && id() == Id3v2FrameIds::lComment)
|| (version < 3 && id() == Id3v2FrameIds::sComment))
|| ((version >= 3 && id() == Id3v2FrameIds::lUnsynchronizedLyrics)
|| (version < 3 && id() == Id3v2FrameIds::sUnsynchronizedLyrics))) {
// the comment frame or the unsynchronized lyrics frame
helper.makeComment(buffer, decompressedSize, value());
} else {
// an unknown frame
// create buffer
buffer = make_unique<char[]>(decompressedSize = value().dataSize());
// just write the data
copy(value().dataPointer(), value().dataPointer() + value().dataSize(), buffer.get());
}
} catch(ConversionException &) {
addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context);
throw InvalidDataException();
}
unsigned long actualSize;
if(version >= 3 && isCompressed()) {
actualSize = compressBound(decompressedSize);
auto compressedBuffer = make_unique<char[]>(actualSize);
switch(compress(reinterpret_cast<Bytef *>(compressedBuffer.get()), &actualSize, reinterpret_cast<Bytef *>(buffer.get()), decompressedSize)) {
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_OK:
;
}
buffer.swap(compressedBuffer);
} else {
actualSize = decompressedSize;
}
if(version < 3) {
writer.writeUInt24BE(frameId);
writer.writeUInt24BE(actualSize);
} else {
writer.writeUInt32BE(frameId);
if(version >= 4) {
writer.writeSynchsafeUInt32BE(actualSize);
} else {
writer.writeUInt32BE(actualSize);
}
writer.writeUInt16BE(m_flag);
if(hasGroupInformation()) {
writer.writeByte(m_group);
}
if(isCompressed()) {
if(version >= 4) {
writer.writeSynchsafeUInt32BE(decompressedSize);
} else {
writer.writeUInt32BE(decompressedSize);
}
}
}
writer.write(buffer.get(), actualSize);
prepareMaking(version).make(writer);
}
/*!
@ -375,19 +300,183 @@ void Id3v2Frame::cleared()
}
/*!
* \class Media::Id3v2FrameHelper
* \brief The Id3v2FrameHelper class helps parsing and making ID3v2 frames.
* \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 The Id3v2FrameHelper class helps parsing and making ID3v2 frames.
* \param id Specifies the identifier of the current frame (used to print warnings).
* \param provider Specifies the status provider to store warnings.
* \brief Prepares making the specified \a frame.
* \sa See Id3v2Frame::prepareMaking() for more information.
*/
Id3v2FrameHelper::Id3v2FrameHelper(const std::string &id, StatusProvider &provider) :
m_id(id),
m_statusProvider(provider)
{}
Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, const byte version) :
m_frame(frame),
m_frameId(m_frame.id()),
m_version(version)
{
m_frame.invalidateStatus();
const string context("making " + m_frame.frameIdString() + " frame");
// validate assigned data
if(m_frame.value().isEmpty()) {
m_frame.addNotification(NotificationType::Critical, "Cannot make an empty frame.", context);
throw InvalidDataException();
}
if(m_frame.isEncrypted()) {
m_frame.addNotification(NotificationType::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
throw InvalidDataException();
}
if(m_frame.hasPaddingReached()) {
m_frame.addNotification(NotificationType::Critical, "Cannot make a frame which is marked as padding.", context);
throw InvalidDataException();
}
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
if(version >= 3) {
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);
throw InvalidDataException();
}
}
} else {
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);
throw InvalidDataException();
}
}
}
// make actual data depending on the frame ID
try {
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)
|| (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
// pre-defined genre frame
m_frame.makeString(m_data, m_decompressedSize, ConversionUtilities::numberToString(m_frame.value().toStandardGenreIndex()), TagTextEncoding::Latin1);
} else {
// any other text frame
m_frame.makeString(m_data, m_decompressedSize, m_frame.value().toString(), m_frame.value().dataEncoding()); // the same as a normal text frame
}
} else if(version >= 3 && m_frameId == Id3v2FrameIds::lCover) {
// picture frame
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) {
// legacy picture frame
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))) {
// the comment frame or the unsynchronized lyrics frame
m_frame.makeComment(m_data, m_decompressedSize, m_frame.value());
} else {
// an unknown frame
// create buffer
m_data = make_unique<char[]>(m_decompressedSize = m_frame.value().dataSize());
// just write the data
copy(m_frame.value().dataPointer(), m_frame.value().dataPointer() + m_decompressedSize, m_data.get());
}
} catch(ConversionException &) {
m_frame.addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context);
throw InvalidDataException();
}
// 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)) {
case Z_MEM_ERROR:
m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
throw InvalidDataException();
case Z_BUF_ERROR:
m_frame.addNotification(NotificationType::Critical, "Decompressing failed. The destination buffer was too small.", context);
throw InvalidDataException();
case Z_OK:
;
}
m_data.swap(compressedData);
} else {
m_dataSize = m_decompressedSize;
}
// calculate required size
// -> data size
m_requiredSize = m_dataSize;
if(version < 3) {
// -> header size
m_requiredSize += 3;
} else {
// -> header size
m_requiredSize += 10;
// -> group byte
if(m_frame.hasGroupInformation()) {
m_requiredSize += 1;
}
// -> decompressed size
if(version >= 3 && m_frame.isCompressed()) {
m_requiredSize += 4;
}
}
}
/*!
* \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.
*/
void Id3v2FrameMaker::make(BinaryWriter &writer)
{
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);
}
/*!
* \brief Returns the text encoding for the specified \a textEncodingByte.
@ -395,7 +484,7 @@ Id3v2FrameHelper::Id3v2FrameHelper(const std::string &id, StatusProvider &provid
* If the \a textEncodingByte doesn't match any encoding TagTextEncoding::Latin1 is
* returned and a parsing notification is added.
*/
TagTextEncoding Id3v2FrameHelper::parseTextEncodingByte(byte textEncodingByte)
TagTextEncoding Id3v2Frame::parseTextEncodingByte(byte textEncodingByte)
{
switch(textEncodingByte) {
case 0: // Ascii
@ -407,7 +496,7 @@ TagTextEncoding Id3v2FrameHelper::parseTextEncodingByte(byte textEncodingByte)
case 3: // Utf 8
return TagTextEncoding::Utf8;
default:
m_statusProvider.addNotification(NotificationType::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + m_id);
addNotification(NotificationType::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + frameIdString());
return TagTextEncoding::Latin1;
}
}
@ -415,7 +504,7 @@ TagTextEncoding Id3v2FrameHelper::parseTextEncodingByte(byte textEncodingByte)
/*!
* \brief Returns a text encoding byte for the specified \a textEncoding.
*/
byte Id3v2FrameHelper::makeTextEncodingByte(TagTextEncoding textEncoding)
byte Id3v2Frame::makeTextEncodingByte(TagTextEncoding textEncoding)
{
switch(textEncoding) {
case TagTextEncoding::Latin1:
@ -445,7 +534,7 @@ byte Id3v2FrameHelper::makeTextEncodingByte(TagTextEncoding textEncoding)
* \remarks The length is always returned as the number of bytes, not as the number of characters (makes a difference for
* UTF-16 encodings).
*/
tuple<const char *, size_t, const char *> Id3v2FrameHelper::parseSubstring(const char *buffer, size_t bufferSize, TagTextEncoding &encoding, bool addWarnings)
tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(const char *buffer, size_t bufferSize, TagTextEncoding &encoding, bool addWarnings)
{
tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
switch(encoding) {
@ -466,7 +555,7 @@ tuple<const char *, size_t, const char *> Id3v2FrameHelper::parseSubstring(const
get<1>(res) += 2;
} else {
if(addWarnings) {
m_statusProvider.addNotification(NotificationType::Warning, "Wide string in frame is not terminated proberly.", "parsing termination of frame " + m_id);
addNotification(NotificationType::Warning, "Wide string in frame is not terminated proberly.", "parsing termination of frame " + frameIdString());
}
break;
}
@ -485,7 +574,7 @@ tuple<const char *, size_t, const char *> Id3v2FrameHelper::parseSubstring(const
++get<1>(res);
} else {
if(addWarnings) {
m_statusProvider.addNotification(NotificationType::Warning, "String in frame is not terminated proberly.", "parsing termination of frame " + m_id);
addNotification(NotificationType::Warning, "String in frame is not terminated proberly.", "parsing termination of frame " + frameIdString());
}
break;
}
@ -500,9 +589,9 @@ tuple<const char *, size_t, const char *> Id3v2FrameHelper::parseSubstring(const
/*!
* \brief Parses a substring in the specified \a buffer.
*
* Same as Id3v2FrameHelper::parseSubstring() but returns the substring as string object.
* Same as Id3v2Frame::parseSubstring() but returns the substring as string object.
*/
string Id3v2FrameHelper::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
{
auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
return string(get<0>(substr), get<1>(substr));
@ -511,9 +600,9 @@ string Id3v2FrameHelper::parseString(const char *buffer, size_t dataSize, TagTex
/*!
* \brief Parses a substring in the specified \a buffer.
*
* Same as Id3v2FrameHelper::parseSubstring() but returns the substring as wstring object.
* Same as Id3v2Frame::parseSubstring() but returns the substring as wstring object.
*/
wstring Id3v2FrameHelper::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
wstring Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings)
{
auto substr = parseSubstring(buffer, dataSize, encoding, addWarnings);
return wstring(reinterpret_cast<wstring::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
@ -528,7 +617,7 @@ wstring Id3v2FrameHelper::parseWideString(const char *buffer, size_t dataSize, T
*
* \remarks This method is not used anymore and might be deleted.
*/
void Id3v2FrameHelper::parseBom(const char *buffer, size_t maxSize, TagTextEncoding &encoding)
void Id3v2Frame::parseBom(const char *buffer, size_t maxSize, TagTextEncoding &encoding)
{
switch(encoding) {
case TagTextEncoding::Utf16BigEndian:
@ -542,7 +631,7 @@ void Id3v2FrameHelper::parseBom(const char *buffer, size_t maxSize, TagTextEncod
default:
if((maxSize >= 3) && (ConversionUtilities::BE::toUInt24(buffer) == 0x00EFBBBF)) {
encoding = TagTextEncoding::Utf8;
m_statusProvider.addNotification(NotificationType::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + m_id);
addNotification(NotificationType::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte oder mark of frame " + frameIdString());
}
}
}
@ -554,11 +643,11 @@ void Id3v2FrameHelper::parseBom(const char *buffer, size_t maxSize, TagTextEncod
* \param tagValue Specifies the tag value used to store the results.
* \param typeInfo Specifies a byte used to store the type info.
*/
void Id3v2FrameHelper::parseLegacyPicture(const char *buffer, size_t maxSize, TagValue &tagValue, byte &typeInfo)
void Id3v2Frame::parseLegacyPicture(const char *buffer, size_t maxSize, TagValue &tagValue, byte &typeInfo)
{
static const string context("parsing ID3v2.2 picture frame");
if(maxSize < 6) {
m_statusProvider.addNotification(NotificationType::Critical, "Picture frame is incomplete.", context);
addNotification(NotificationType::Critical, "Picture frame is incomplete.", context);
throw TruncatedDataException();
}
const char *end = buffer + maxSize;
@ -568,7 +657,7 @@ void Id3v2FrameHelper::parseLegacyPicture(const char *buffer, size_t maxSize, Ta
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) {
m_statusProvider.addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
throw TruncatedDataException();
}
tagValue.assignData(get<2>(substr), end - get<2>(substr), TagDataType::Picture, dataEncoding);
@ -581,7 +670,7 @@ void Id3v2FrameHelper::parseLegacyPicture(const char *buffer, size_t maxSize, Ta
* \param tagValue Specifies the tag value used to store the results.
* \param typeInfo Specifies a byte used to store the type info.
*/
void Id3v2FrameHelper::parsePicture(const char *buffer, size_t maxSize, TagValue &tagValue, byte &typeInfo)
void Id3v2Frame::parsePicture(const char *buffer, size_t maxSize, TagValue &tagValue, byte &typeInfo)
{
static const string context("parsing ID3v2.3 picture frame");
const char *end = buffer + maxSize;
@ -592,18 +681,18 @@ void Id3v2FrameHelper::parsePicture(const char *buffer, size_t maxSize, TagValue
tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
}
if(get<2>(substr) >= end) {
m_statusProvider.addNotification(NotificationType::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
addNotification(NotificationType::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
throw TruncatedDataException();
}
typeInfo = static_cast<unsigned char>(*get<2>(substr));
if(++get<2>(substr) >= end) {
m_statusProvider.addNotification(NotificationType::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
addNotification(NotificationType::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
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) {
m_statusProvider.addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
addNotification(NotificationType::Critical, "Picture frame is incomplete (actual data is missing).", context);
throw TruncatedDataException();
}
tagValue.assignData(get<2>(substr), end - get<2>(substr), TagDataType::Picture, dataEncoding);
@ -615,12 +704,12 @@ void Id3v2FrameHelper::parsePicture(const char *buffer, size_t maxSize, TagValue
* \param dataSize Specifies the maximal number of bytes to read from the buffer.
* \param tagValue Specifies the tag value used to store the results.
*/
void Id3v2FrameHelper::parseComment(const char *buffer, size_t dataSize, TagValue &tagValue)
void Id3v2Frame::parseComment(const char *buffer, size_t dataSize, TagValue &tagValue)
{
static const string context("parsing comment frame");
const char *end = buffer + dataSize;
if(dataSize < 6) {
m_statusProvider.addNotification(NotificationType::Critical, "Comment frame is incomplete.", context);
addNotification(NotificationType::Critical, "Comment frame is incomplete.", context);
throw TruncatedDataException();
}
TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer);
@ -628,7 +717,7 @@ void Id3v2FrameHelper::parseComment(const char *buffer, size_t dataSize, TagValu
auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true);
tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
if(get<2>(substr) >= end) {
m_statusProvider.addNotification(NotificationType::Critical, "Comment frame is incomplete (description not terminated?).", context);
addNotification(NotificationType::Critical, "Comment frame is incomplete (description not terminated?).", context);
throw TruncatedDataException();
}
substr = parseSubstring(get<2>(substr), end - get<2>(substr), dataEncoding, false);
@ -641,9 +730,9 @@ void Id3v2FrameHelper::parseComment(const char *buffer, size_t dataSize, TagValu
* \param value Specifies the string to make.
* \param encoding Specifies the encoding of the string to make.
*/
void Id3v2FrameHelper::makeString(unique_ptr<char[]> &buffer, uint32 &bufferSize, const string &value, TagTextEncoding encoding)
void Id3v2Frame::makeString(unique_ptr<char[]> &buffer, uint32 &bufferSize, const string &value, TagTextEncoding encoding)
{
makeEncodingAndData(buffer, bufferSize, encoding, value.c_str(), value.length());
makeEncodingAndData(buffer, bufferSize, encoding, value.data(), value.size());
}
/*!
@ -653,7 +742,7 @@ void Id3v2FrameHelper::makeString(unique_ptr<char[]> &buffer, uint32 &bufferSize
* \param data Specifies the data.
* \param dataSize Specifies the data size.
*/
void Id3v2FrameHelper::makeEncodingAndData(unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, size_t dataSize)
void Id3v2Frame::makeEncodingAndData(unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, size_t dataSize)
{
// calculate buffer size
if(!data) {
@ -681,7 +770,7 @@ void Id3v2FrameHelper::makeEncodingAndData(unique_ptr<char[]> &buffer, uint32 &b
/*!
* \brief Writes the specified picture to the specified buffer (ID3v2.2).
*/
void Id3v2FrameHelper::makeLegacyPicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
void Id3v2Frame::makeLegacyPicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
{
// calculate needed buffer size and create buffer
TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
@ -724,7 +813,7 @@ void Id3v2FrameHelper::makeLegacyPicture(unique_ptr<char[]> &buffer, uint32 &buf
/*!
* \brief Writes the specified picture to the specified buffer (ID3v2.3).
*/
void Id3v2FrameHelper::makePicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
void Id3v2Frame::makePicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
{
// calculate needed buffer size and create buffer
TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
@ -760,18 +849,18 @@ void Id3v2FrameHelper::makePicture(unique_ptr<char[]> &buffer, uint32 &bufferSiz
/*!
* \brief Writes the specified comment to the specified buffer.
*/
void Id3v2FrameHelper::makeComment(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
{
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()) {
m_statusProvider.addNotification(NotificationType::Critical, "Data enoding and description encoding aren't equal.", context);
addNotification(NotificationType::Critical, "Data enoding and description encoding aren't equal.", context);
throw InvalidDataException();
}
const string &lng = comment.language();
if(lng.length() > 3) {
m_statusProvider.addNotification(NotificationType::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
addNotification(NotificationType::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
throw InvalidDataException();
}
// calculate needed buffer size and create buffer

View File

@ -19,35 +19,61 @@
namespace Media
{
class LIB_EXPORT Id3v2FrameHelper
class Id3v2Frame;
class LIB_EXPORT Id3v2FrameMaker
{
friend class Id3v2Frame;
public:
Id3v2FrameHelper(const std::string &id, StatusProvider &provider);
const std::string &id() const;
TagTextEncoding parseTextEncodingByte(byte textEncodingByte);
std::tuple<const char *, size_t, const char *> parseSubstring(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings = false);
std::string parseString(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings = false);
std::wstring parseWideString(const char *buffer, std::size_t dataSize, TagTextEncoding &encoding, bool addWarnings = false);
void parseLegacyPicture(const char *buffer, size_t maxSize, TagValue &tagValue, byte &typeInfo);
void parsePicture(const char *buffer, size_t maxSize, TagValue &tagValue, byte &typeInfo);
void parseComment(const char *buffer, size_t maxSize, TagValue &tagValue);
void parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding);
byte makeTextEncodingByte(TagTextEncoding textEncoding);
void makeString(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding);
void makeEncodingAndData(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, size_t m_dataSize);
void makeLegacyPicture(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo);
void makePicture(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo);
void makeComment(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment);
void make(IoUtilities::BinaryWriter &writer);
const Id3v2Frame &field() const;
const std::unique_ptr<char[]> &data() const;
uint32 dataSize() const;
uint32 requiredSize() const;
private:
std::string m_id;
StatusProvider &m_statusProvider;
Id3v2FrameMaker(Id3v2Frame &frame, const byte version);
Id3v2Frame &m_frame;
uint32 m_frameId;
const byte m_version;
std::unique_ptr<char[]> m_data;
uint32 m_dataSize;
uint32 m_decompressedSize;
uint32 m_requiredSize;
};
class Id3v2Frame;
/*!
* \brief Returns the associated frame.
*/
inline const Id3v2Frame &Id3v2FrameMaker::field() const
{
return m_frame;
}
/*!
* \brief Returns the frame data.
*/
inline const std::unique_ptr<char[]> &Id3v2FrameMaker::data() const
{
return m_data;
}
/*!
* \brief Returns the size of the array returned by data().
*/
inline uint32 Id3v2FrameMaker::dataSize() const
{
return m_dataSize;
}
/*!
* \brief Returns number of bytes which will be written when making the frame.
*/
inline uint32 Id3v2FrameMaker::requiredSize() const
{
return m_requiredSize;
}
/*!
* \brief Defines traits for the TagField implementation of the Id3v2Frame class.
@ -72,31 +98,26 @@ public:
typedef Id3v2Frame implementationType;
};
/*!
* \brief Returns the ID of the current frame.
*/
inline const std::string &Id3v2FrameHelper::id() const
{
return m_id;
}
class LIB_EXPORT Id3v2Frame : public TagField<Id3v2Frame>, public StatusProvider
{
friend class TagField<Id3v2Frame>;
public:
Id3v2Frame();
Id3v2Frame(const identifierType &id, const TagValue &value, byte group = 0, int16 flag = 0);
Id3v2Frame(const identifierType &id, const TagValue &value, const byte group = 0, const int16 flag = 0);
void parse(IoUtilities::BinaryReader &reader, int32 version, uint32 maximalSize = 0);
void make(IoUtilities::BinaryWriter &writer, int32 version);
// parsing/making
void parse(IoUtilities::BinaryReader &reader, const uint32 version, const uint32 maximalSize = 0);
Id3v2FrameMaker prepareMaking(const uint32 version);
void make(IoUtilities::BinaryWriter &writer, const uint32 version);
// member access
bool isAdditionalTypeInfoUsed() const;
bool isValid() const;
bool hasPaddingReached() const;
std::string frameIdString() const;
int16 flag() const;
void setFlag(int16 value);
uint16 flag() const;
void setFlag(uint16 value);
uint32 totalSize() const;
uint32 dataSize() const;
bool toDiscardWhenUnknownAndTagIsAltered() const;
@ -112,13 +133,31 @@ public:
int32 parsedVersion() const;
bool supportsNestedFields() const;
// parsing helper
TagTextEncoding parseTextEncodingByte(byte textEncodingByte);
std::tuple<const char *, size_t, const char *> parseSubstring(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings = false);
std::string parseString(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings = false);
std::wstring parseWideString(const char *buffer, std::size_t dataSize, TagTextEncoding &encoding, bool addWarnings = false);
void parseLegacyPicture(const char *buffer, size_t maxSize, TagValue &tagValue, byte &typeInfo);
void parsePicture(const char *buffer, size_t maxSize, TagValue &tagValue, byte &typeInfo);
void parseComment(const char *buffer, size_t maxSize, TagValue &tagValue);
void parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding);
// making helper
byte makeTextEncodingByte(TagTextEncoding textEncoding);
void makeString(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding);
void makeEncodingAndData(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, size_t m_dataSize);
void makeLegacyPicture(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo);
void makePicture(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo);
void makeComment(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment);
protected:
void cleared();
private:
uint16 m_flag;
byte m_group;
int32 m_parsedVersion;
uint32 m_parsedVersion;
uint32 m_dataSize;
uint32 m_totalSize;
bool m_padding;
@ -163,7 +202,7 @@ inline std::string Id3v2Frame::frameIdString() const
/*!
* \brief Returns the flags.
*/
inline int16 Id3v2Frame::flag() const
inline uint16 Id3v2Frame::flag() const
{
return m_flag;
}
@ -171,7 +210,7 @@ inline int16 Id3v2Frame::flag() const
/*!
* \brief Sets the flags.
*/
inline void Id3v2Frame::setFlag(int16 value)
inline void Id3v2Frame::setFlag(uint16 value)
{
m_flag = value;
}

View File

@ -81,7 +81,7 @@ inline bool isShortId(uint32 id)
/*!
* \brief Returns an indication whether the specified \a id is a text frame id.
*/
inline bool isTextfield(uint32 id)
inline bool isTextFrame(uint32 id)
{
if(isShortId(id)) {
return (id & 0x00FF0000u) == 0x00540000u;

View File

@ -138,7 +138,7 @@ TagDataType Id3v2Tag::proposedDataType(const uint32 &id) const
case lCover: case sCover:
return TagDataType::Picture;
default:
if(Id3v2FrameIds::isTextfield(id)) {
if(Id3v2FrameIds::isTextFrame(id)) {
return TagDataType::Text;
} else {
return TagDataType::Undefined;
@ -181,7 +181,7 @@ bool Id3v2Tag::setValue(const typename Id3v2Frame::identifierType &id, const Tag
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
*/
void Id3v2Tag::parse(istream &stream, uint64 maximalSize)
void Id3v2Tag::parse(istream &stream, const uint64 maximalSize)
{
// prepare parsing
invalidateStatus();
@ -229,7 +229,7 @@ void Id3v2Tag::parse(istream &stream, uint64 maximalSize)
// how many bytes remain for frames and padding?
uint32 bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
if(bytesRemaining > maximalSize) {
if(maximalSize && bytesRemaining > maximalSize) {
bytesRemaining = maximalSize;
addNotification(NotificationType::Critical, "Frames are truncated.", context);
}
@ -245,7 +245,7 @@ void Id3v2Tag::parse(istream &stream, uint64 maximalSize)
frame.parse(reader, majorVersion, bytesRemaining);
if(frame.id()) {
// add frame if parsing was successfull
if(Id3v2FrameIds::isTextfield(frame.id()) && fields().count(frame.id()) == 1) {
if(Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
addNotification(NotificationType::Warning, "The text frame " + frame.frameIdString() + " exists more than once.", context);
}
fields().insert(pair<fieldType::identifierType, fieldType>(frame.id(), frame));
@ -264,13 +264,18 @@ void Id3v2Tag::parse(istream &stream, uint64 maximalSize)
frame.invalidateNotifications();
// calculate next frame offset
bytesRemaining -= frame.totalSize();
pos += frame.totalSize();
if(frame.totalSize() <= bytesRemaining) {
pos += frame.totalSize();
bytesRemaining -= frame.totalSize();
} else {
pos += bytesRemaining;
bytesRemaining = 0;
}
}
// check for extended header
if(hasFooter()) {
if(m_size + 10 < maximalSize) {
if(maximalSize && m_size + 10 < maximalSize) {
// the footer does not provide additional information, just check the signature
stream.seekg(startOffset + (m_size += 10));
if(reader.readUInt24LE() != 0x494433u) {
@ -290,6 +295,21 @@ void Id3v2Tag::parse(istream &stream, uint64 maximalSize)
}
}
/*!
* \brief Prepares making.
* \returns Returns a Id3v2TagMaker object which can be used to actually make the tag.
* \remarks The tag must NOT be mutated after making is prepared when it is intended to actually
* make the tag 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 tag before making it.
* \sa make()
*/
Id3v2TagMaker Id3v2Tag::prepareMaking()
{
return Id3v2TagMaker(*this);
}
/*!
* \brief Writes tag information to the specified \a stream.
*
@ -297,50 +317,9 @@ void Id3v2Tag::parse(istream &stream, uint64 maximalSize)
* \throws Throws Media::Failure or a derived exception when a making
* error occurs.
*/
void Id3v2Tag::make(ostream &stream)
void Id3v2Tag::make(ostream &stream, uint32 padding)
{
// prepare making
invalidateStatus();
const string context("making ID3v2 tag");
// check if version is supported
// (the version could have been changed using setVersion(...)
if(!isVersionSupported()) {
addNotification(NotificationType::Critical, "The ID3v2 tag couldn't be created, because the target version isn't supported.", context);
throw VersionNotSupportedException();
}
// prepare for writing
BinaryWriter writer(&stream);
// write header
writer.writeUInt24BE(0x494433u); // signature
writer.writeByte(m_majorVersion); // major version
writer.writeByte(m_revisionVersion); // revision version
writer.writeByte(m_flags & 0xBF); // flags, but without extended header or compression bit set
stream.seekp(4, ios_base::cur); // size currently unknown, write it later
streamoff framesOffset = stream.tellp();
int framesWritten = 0;
for(auto i : fields()) {
Id3v2Frame &frame = i.second;
// write only valid frames
if(frame.isValid()) {
// make the frame
try {
frame.make(writer, m_majorVersion);
++framesWritten;
} catch(Failure &) {
// nothing to do here since notifications will be added anyways
}
// add making notifications
addNotifications(context, frame);
}
}
// calculate and write size
streamoff endOffset = stream.tellp();
stream.seekp(framesOffset - 4, ios_base::beg);
writer.writeSynchsafeUInt32BE(endOffset - framesOffset);
stream.seekp(endOffset, ios_base::beg);
if(framesWritten <= 0) { // add a warning notification if an empty ID3v2 tag has been written
addNotification(NotificationType::Warning, "No frames could be written, an empty ID3v2 tag has been written.", context);
}
prepareMaking().make(stream, padding);
}
/*!
@ -383,8 +362,8 @@ bool FrameComparer::operator()(const uint32 &lhs, const uint32 &rhs) const
if(rhs == Id3v2FrameIds::lTitle || rhs == Id3v2FrameIds::sTitle) {
return false;
}
bool lhstextfield = Id3v2FrameIds::isTextfield(lhs);
bool rhstextfield = Id3v2FrameIds::isTextfield(rhs);
bool lhstextfield = Id3v2FrameIds::isTextFrame(lhs);
bool rhstextfield = Id3v2FrameIds::isTextFrame(rhs);
if(lhstextfield && !rhstextfield) {
return true;
}
@ -400,4 +379,72 @@ bool FrameComparer::operator()(const uint32 &lhs, const uint32 &rhs) const
return lhs < rhs;
}
/*!
* \brief Prepares making the specified \a tag.
* \sa See Id3v2Tag::prepareMaking() for more information.
*/
Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag) :
m_tag(tag),
m_framesSize(0)
{
tag.invalidateStatus();
const string context("making ID3v2 tag");
// check if version is supported
// (the version could have been changed using setVersion())
if(!tag.isVersionSupported()) {
tag.addNotification(NotificationType::Critical, "The ID3v2 tag version isn't supported.", context);
throw VersionNotSupportedException();
}
// prepare frames
m_maker.reserve(tag.fields().size());
for(auto &pair : tag.fields()) {
try {
m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion()));
m_framesSize += m_maker.back().requiredSize();
} catch(const Failure &) {
// nothing to do here; notifications will be added anyways
}
m_tag.addNotifications(pair.second);
}
// calculate required size
// -> header + size of frames
m_requiredSize = 10 + m_framesSize;
}
/*!
* \brief Saves the tag (specified when constructing the object) to the
* specified \a stream.
* \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.
*/
void Id3v2TagMaker::make(std::ostream &stream, uint32 padding)
{
BinaryWriter writer(&stream);
// write header
// -> signature
writer.writeUInt24BE(0x494433u);
// -> version
writer.writeByte(m_tag.majorVersion());
writer.writeByte(m_tag.revisionVersion());
// -> flags, but without extended header or compression bit set
writer.writeByte(m_tag.flags() & 0xBF);
// -> size (excluding header)
writer.writeSynchsafeUInt32BE(m_framesSize + padding);
// write frames
for(auto &maker : m_maker) {
maker.make(writer);
}
// write padding
for(; padding; --padding) {
stream.put(0);
}
}
}

View File

@ -10,11 +10,48 @@
namespace Media
{
class Id3v2Tag;
struct LIB_EXPORT FrameComparer
{
bool operator()(const uint32& lhs, const uint32& rhs) const;
bool operator()(const uint32 &lhs, const uint32 &rhs) const;
};
class LIB_EXPORT Id3v2TagMaker
{
friend class Id3v2Tag;
public:
void make(std::ostream &stream, uint32 padding);
const Id3v2Tag &tag() const;
uint64 requiredSize() const;
private:
Id3v2TagMaker(Id3v2Tag &tag);
Id3v2Tag &m_tag;
uint32 m_framesSize;
uint32 m_requiredSize;
std::vector<Id3v2FrameMaker> m_maker;
};
/*!
* \brief Returns the associated tag.
*/
inline const Id3v2Tag &Id3v2TagMaker::tag() const
{
return m_tag;
}
/*!
* \brief Returns the number of bytes which will be written when making the tag.
* \remarks Excludes padding!
*/
inline uint64 Id3v2TagMaker::requiredSize() const
{
return m_requiredSize;
}
class LIB_EXPORT Id3v2Tag : public FieldMapBasedTag<Id3v2Frame, FrameComparer>
{
public:
@ -34,8 +71,9 @@ public:
bool supportsDescription(KnownField field) const;
bool supportsMimeType(KnownField field) const;
void parse(std::istream &sourceStream, uint64 maximalSize = 0);
void make(std::ostream &targetStream);
void parse(std::istream &sourceStream, const uint64 maximalSize = 0);
Id3v2TagMaker prepareMaking();
void make(std::ostream &targetStream, uint32 padding);
byte majorVersion() const;
byte revisionVersion() const;

View File

@ -1320,14 +1320,6 @@ nonRewriteCalculations:
throw;
}
// define variables needed to handle output stream and backup stream (required when rewriting the file)
string backupPath;
fstream &outputStream = fileInfo().stream();
fstream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
BinaryWriter outputWriter(&outputStream);
char buff[8]; // buffer used to make size denotations
if(isAborted()) {
throw OperationAbortedException();
}
@ -1335,6 +1327,14 @@ nonRewriteCalculations:
// setup stream(s) for writing
// -> update status
updateStatus("Preparing streams ...");
// -> define variables needed to handle output stream and backup stream (required when rewriting the file)
string backupPath;
fstream &outputStream = fileInfo().stream();
fstream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
BinaryWriter outputWriter(&outputStream);
char buff[8]; // buffer used to make size denotations
if(rewriteRequired) {
// move current file to temp dir and reopen it as backupStream, recreate original file
try {
@ -1351,7 +1351,6 @@ nonRewriteCalculations:
}
} else { // !rewriteRequired
// buffer currently assigned attachments
for(auto &maker : attachmentMaker) {
maker.bufferCurrentAttachments();
@ -1636,8 +1635,6 @@ nonRewriteCalculations:
// reparse what is written so far
updateStatus("Reparsing output file ...");
// -> report new size
fileInfo().reportSizeChanged(outputStream.tellp());
if(rewriteRequired) {
// report new size
fileInfo().reportSizeChanged(outputStream.tellp());

View File

@ -232,13 +232,13 @@ MatroskaTagMaker::MatroskaTagMaker(MatroskaTag &tag) :
}
m_tagSize = 2 + EbmlElement::calculateSizeDenotationLength(m_targetsSize) + m_targetsSize;
// calculate size of "SimpleTag" elements
m_makers.reserve(m_tag.fields().size());
m_maker.reserve(m_tag.fields().size());
m_simpleTagsSize = 0; // including ID and size
for(auto &pair : m_tag.fields()) {
try {
m_makers.emplace_back(pair.second.prepareMaking());
m_simpleTagsSize += m_makers.back().requiredSize();
} catch(Failure &) {
m_maker.emplace_back(pair.second.prepareMaking());
m_simpleTagsSize += m_maker.back().requiredSize();
} catch(const Failure &) {
// nothing to do here; notifications will be added anyways
}
m_tag.addNotifications(pair.second);
@ -298,7 +298,7 @@ void MatroskaTagMaker::make(ostream &stream) const
}
}
// write "SimpleTag" elements using maker objects prepared previously
for(const auto &maker : m_makers) {
for(const auto &maker : m_maker) {
maker.make(stream);
}
}

View File

@ -25,7 +25,7 @@ private:
MatroskaTag &m_tag;
uint64 m_targetsSize;
uint64 m_simpleTagsSize;
std::vector<MatroskaTagFieldMaker> m_makers;
std::vector<MatroskaTagFieldMaker> m_maker;
uint64 m_tagSize;
uint64 m_totalSize;
};

View File

@ -362,7 +362,7 @@ void MediaFileInfo::parseTags()
auto id3v2Tag = make_unique<Id3v2Tag>();
stream().seekg(offset, ios_base::beg);
try {
id3v2Tag->parse(stream());
id3v2Tag->parse(stream(), size() - offset);
m_paddingSize += id3v2Tag->paddingSize();
} catch(NoDataFoundException &) {
continue;
@ -622,22 +622,6 @@ void MediaFileInfo::applyChanges()
previousParsingSuccessful = false;
addNotification(NotificationType::Critical, "Tracks have to be parsed without critical errors before changes can be applied.", context);
}
// switch(chaptersParsingStatus()) {
// case ParsingStatus::Ok:
// case ParsingStatus::NotSupported:
// break;
// default:
// previousParsingSuccessful = false;
// addNotification(NotificationType::Critical, "Chapters have to be parsed without critical errors before changes can be applied.", context);
// }
// switch(attachmentsParsingStatus()) {
// case ParsingStatus::Ok:
// case ParsingStatus::NotSupported:
// break;
// default:
// previousParsingSuccessful = false;
// addNotification(NotificationType::Critical, "Attachments have to be parsed without critical errors before changes can be applied.", context);
// }
if(!previousParsingSuccessful) {
throw InvalidDataException();
}
@ -1359,12 +1343,12 @@ void MediaFileInfo::makeMp3File()
{
const string context("making MP3 file");
// there's no need to rewrite the complete file if there is just are not ID3v2 tags present or to be written
if(m_id3v2Tags.size() == 0 && m_actualId3v2TagOffsets.size() == 0) {
if(!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty()) {
if(m_actualExistingId3v1Tag) {
// there is currently an ID3v1 tag at the end of the file
if(m_id3v1Tag) {
// the file shall still have an ID3v1 tag
updateStatus("No need to rewrite the whole file, just writing ID3v1 tag ...");
updateStatus("Updating ID3v1 tag ...");
// ensure the file is still open / not readonly
open();
stream().seekp(-128, ios_base::end);
@ -1375,7 +1359,7 @@ void MediaFileInfo::makeMp3File()
}
} else {
// the currently existing ID3v1 tag shall be removed
updateStatus("No need to rewrite the whole file, just truncating it to remove ID3v1 tag ...");
updateStatus("Removing ID3v1 tag ...");
stream().close();
if(truncate(path().c_str(), size() - 128) == 0) {
reportSizeChanged(size() - 128);
@ -1388,7 +1372,7 @@ void MediaFileInfo::makeMp3File()
} else {
// there is currently no ID3v1 tag at the end of the file
if(m_id3v1Tag) {
updateStatus("No need to rewrite the whole file, just writing ID3v1 tag.");
updateStatus("Adding ID3v1 tag ...");
// ensure the file is still open / not readonly
open();
stream().seekp(0, ios_base::end);
@ -1403,49 +1387,110 @@ void MediaFileInfo::makeMp3File()
}
} else {
// ID3v2 needs to be modified -> file needs to be rewritten
// TODO: take advantage of possibly available padding
// ID3v2 needs to be modified
updateStatus("Updating ID3v2 tags ...");
// prepare for rewriting
updateStatus("Prepareing for rewriting MP3 file ...");
// prepare ID3v2 tags
vector<Id3v2TagMaker> makers;
makers.reserve(m_id3v2Tags.size());
uint32 tagsSize = 0;
for(auto &tag : m_id3v2Tags) {
try {
makers.emplace_back(tag->prepareMaking());
tagsSize += makers.back().requiredSize();
} catch(const Failure &) {
// nothing to do: notifications added anyways
}
addNotifications(*tag);
}
// determine padding, check whether rewrite is required
bool rewriteRequired = isForcingRewrite() || (tagsSize > static_cast<uint32>(m_containerOffset));
uint32 padding;
if(!rewriteRequired) {
padding = static_cast<uint32>(m_containerOffset) - tagsSize;
// check whether padding matches specifications
if(padding < minPadding() || padding > maxPadding()) {
rewriteRequired = true;
}
}
if(rewriteRequired) {
// use preferred padding when rewriting
padding = preferredPadding();
updateStatus("Preparing streams for rewriting ...");
} else {
updateStatus("Preparing streams for updating ...");
}
// setup stream(s) for writing
// -> define variables needed to handle output stream and backup stream (required when rewriting the file)
string backupPath;
fstream backupStream;
fstream &outputStream = stream();
fstream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
if(rewriteRequired) {
// move current file to temp dir and reopen it as backupStream, recreate original file
try {
// ensure the file is close before moving
close();
BackupHelper::createBackupFile(path(), backupPath, backupStream);
// recreate original file, define buffer variables
outputStream.open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
} catch(const ios_base::failure &) {
addNotification(NotificationType::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
throw;
}
} else { // !rewriteRequired
// reopen original file to ensure it is opened for writing
try {
close();
outputStream.open(path(), ios_base::in | ios_base::out | ios_base::binary);
} catch(const ios_base::failure &) {
addNotification(NotificationType::Critical, "Opening the file with write permissions failed.", context);
throw;
}
}
// start actual writing
try {
close();
BackupHelper::createBackupFile(path(), backupPath, backupStream);
backupStream.seekg(m_containerOffset);
// recreate original file with new/changed ID3 tags
stream().open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
updateStatus("Writing ID3v2 tag ...");
// write ID3v2 tags
unsigned int counter = 1;
for(auto &id3v2Tag : m_id3v2Tags) {
try {
id3v2Tag->make(stream());
} catch(const Failure &) {
if(m_id3v2Tags.size()) {
addNotification(NotificationType::Warning, "Unable to write " + ConversionUtilities::numberToString(counter) + ". ID3v2 tag.", context);
} else {
addNotification(NotificationType::Warning, "Unable to write ID3v2 tag.", context);
}
if(!makers.empty()) {
// write ID3v2 tags
updateStatus("Writing ID3v2 tag ...");
for(auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
i->make(outputStream, 0);
}
// include padding into the last ID3v2 tag
makers.back().make(outputStream, padding);
} else {
// no ID3v2 tags assigned -> just write padding
for(; padding; --padding) {
outputStream.put(0);
}
++counter;
}
// write media data
updateStatus("Writing MPEG audio frames ...");
uint64 bytesRemaining = size() - m_containerOffset;
// copy / skip media data
// -> determine media data size
uint64 mediaDataSize = size() - m_containerOffset;
if(m_actualExistingId3v1Tag) {
bytesRemaining -= 128;
mediaDataSize -= 128;
}
if(rewriteRequired) {
// copy data from original file
updateStatus("Writing MPEG audio frames ...");
backupStream.seekg(m_containerOffset);
CopyHelper<0x4000> copyHelper;
copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&StatusProvider::isAborted, this), bind(&StatusProvider::updatePercentage, this, _1));
updatePercentage(100.0);
} else {
// just skip media data
outputStream.seekp(mediaDataSize, ios_base::cur);
}
CopyHelper<0x4000> copyHelper;
copyHelper.callbackCopy(backupStream, stream(), bytesRemaining, bind(&StatusProvider::isAborted, this), bind(&StatusProvider::updatePercentage, this, _1));
// write ID3v1 tag
updateStatus("Writing ID3v1 tag ...");
if(m_id3v1Tag) {
updateStatus("Writing ID3v1 tag ...");
try {
m_id3v1Tag->make(stream());
} catch(Failure &) {
@ -1453,22 +1498,74 @@ void MediaFileInfo::makeMp3File()
}
}
// ensure everything has been actually written
stream().flush();
// report new size
reportSizeChanged(stream().tellp());
// stream is useless for further usage because it is write-only
close();
// handle streams
if(rewriteRequired) {
// report new size
reportSizeChanged(outputStream.tellp());
// stream is useless for further usage because it is write-only
outputStream.close();
} else {
const auto newSize = static_cast<uint64>(outputStream.tellp());
if(newSize < size()) {
// file is smaller after the modification -> truncate
// -> close stream before truncating
outputStream.close();
// -> truncate file
if(truncate(path().c_str(), newSize) == 0) {
reportSizeChanged(newSize);
} else {
addNotification(NotificationType::Critical, "Unable to truncate the file.", context);
}
} else {
// file is longer after the modification -> just report new size
reportSizeChanged(newSize);
}
}
} catch(const OperationAbortedException &) {
addNotification(NotificationType::Information, "Rewriting file to apply new tag information has been aborted.", context);
BackupHelper::restoreOriginalFileFromBackupFile(path(), backupPath, stream(), backupStream);
if(&stream() != &outputStream) {
// a temp/backup file has been created -> restore original file
addNotification(NotificationType::Information, "Rewriting the file to apply changed tag information has been aborted.", context);
try {
BackupHelper::restoreOriginalFileFromBackupFile(path(), backupPath, outputStream, backupStream);
addNotification(NotificationType::Information, "The original file has been restored.", context);
} catch(const ios_base::failure &ex) {
addNotification(NotificationType::Critical, ex.what(), context);
}
} else {
addNotification(NotificationType::Information, "Applying new tag information has been aborted.", context);
}
throw;
} catch(const ios_base::failure &ex) {
addNotification(NotificationType::Critical, "IO error occured when rewriting file to apply new tag information.\n" + string(ex.what()), context);
BackupHelper::restoreOriginalFileFromBackupFile(path(), backupPath, stream(), backupStream);
} catch(const Failure &) {
if(&stream() != &outputStream) {
// a temp/backup file has been created -> restore original file
addNotification(NotificationType::Critical, "Rewriting the file to apply changed tag information failed.", context);
try {
BackupHelper::restoreOriginalFileFromBackupFile(path(), backupPath, outputStream, backupStream);
addNotification(NotificationType::Information, "The original file has been restored.", context);
} catch(const ios_base::failure &ex) {
addNotification(NotificationType::Critical, ex.what(), context);
}
} else {
addNotification(NotificationType::Critical, "Applying new tag information failed.", context);
}
throw;
} catch(const ios_base::failure &) {
if(&stream() != &outputStream) {
// a temp/backup file has been created -> restore original file
addNotification(NotificationType::Critical, "An IO error occured when rewriting the file to apply changed tag information.", context);
try {
BackupHelper::restoreOriginalFileFromBackupFile(path(), backupPath, outputStream, backupStream);
addNotification(NotificationType::Information, "The original file has been restored.", context);
} catch(const ios_base::failure &ex) {
addNotification(NotificationType::Critical, ex.what(), context);
}
} else {
addNotification(NotificationType::Critical, "An IO error occured when applying tag information.", context);
}
throw;
}
// TODO: reduce code duplication
}
}

View File

@ -436,15 +436,16 @@ calculatePadding:
throw OperationAbortedException();
}
// define variables needed to handle output stream and backup stream (required when rewriting the file)
// setup stream(s) for writing
// -> update status
updateStatus("Preparing streams ...");
// -> define variables needed to handle output stream and backup stream (required when rewriting the file)
string backupPath;
fstream &outputStream = fileInfo().stream();
fstream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
BinaryWriter outputWriter(&outputStream);
// setup stream(s) for writing
// -> update status
updateStatus("Preparing streams ...");
if(rewriteRequired) {
// move current file to temp dir and reopen it as backupStream, recreate original file
try {
@ -461,7 +462,6 @@ calculatePadding:
}
} else { // !rewriteRequired
// reopen original file to ensure it is opened for writing
try {
fileInfo().close();

View File

@ -269,12 +269,17 @@ Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag) :
m_omitPreDefinedGenre(m_tag.fields().count(Mp4TagAtomIds::PreDefinedGenre) && m_tag.fields().count(Mp4TagAtomIds::Genre))
{
m_tag.invalidateStatus();
m_makers.reserve(m_tag.fields().size());
m_maker.reserve(m_tag.fields().size());
for(auto &field : m_tag.fields()) {
if(!field.second.value().isEmpty() &&
(!m_omitPreDefinedGenre || field.first == Mp4TagAtomIds::PreDefinedGenre)) {
m_makers.emplace_back(field.second.prepareMaking());
m_ilstSize += m_makers.back().requiredSize();
try {
m_maker.emplace_back(field.second.prepareMaking());
m_ilstSize += m_maker.back().requiredSize();
} catch(const Failure &) {
// nothing to do here; notifications will be added anyways
}
m_tag.addNotifications(field.second);
}
}
if(m_ilstSize != 8) {
@ -307,7 +312,7 @@ void Mp4TagMaker::make(ostream &stream)
writer.writeUInt32BE(m_ilstSize);
writer.writeUInt32BE(Mp4AtomIds::ItunesList);
// write fields
for(auto &maker : m_makers) {
for(auto &maker : m_maker) {
maker.make(stream);
}
} else {

View File

@ -24,7 +24,7 @@ private:
Mp4TagMaker(Mp4Tag &tag);
Mp4Tag &m_tag;
std::vector<Mp4TagFieldMaker> m_makers;
std::vector<Mp4TagFieldMaker> m_maker;
uint64 m_metaSize;
uint64 m_ilstSize;
bool m_omitPreDefinedGenre;

View File

@ -391,7 +391,7 @@ void Mp4TagField::cleared()
/*!
* \brief Prepares making the specified \a field.
* \sa See Mp4TagFieldMaker::prepareMaking() for more information.
* \sa See Mp4TagField::prepareMaking() for more information.
*/
Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field) :
m_field(field),
@ -409,6 +409,7 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field) :
m_field.addNotification(NotificationType::Critical, "No tag value assigned.", context);
throw InvalidDataException();
}
try {
// try to use appropriate raw data type
m_rawDataType = m_field.appropriateRawDataType();
@ -418,6 +419,7 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field) :
m_rawDataType = RawDataType::Utf8;
m_field.addNotification(NotificationType::Warning, "It was not possible to find an appropriate raw data type id. UTF-8 will be assumed.", context);
}
try {
if(!m_field.value().isEmpty()) { // there might be only mean and name info, but no data
m_convertedData.exceptions(std::stringstream::failbit | std::stringstream::badbit);
@ -480,6 +482,7 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field) :
}
throw InvalidDataException();
}
// calculate data size
m_dataSize = m_field.value().isEmpty()
? 0 : (m_convertedData.tellp() ? static_cast<size_t>(m_convertedData.tellp()) : m_field.value().dataSize());
@ -491,7 +494,7 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field) :
/*!
* \brief Saves the field (specified when constructing the object) to the
* specified \a stream. *
* specified \a stream.
* \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.