improved ID3v2 implementation
This commit is contained in:
parent
47e7f4eea4
commit
979427beb3
|
@ -32,7 +32,7 @@ Id3v2Frame::Id3v2Frame() :
|
|||
m_group(0),
|
||||
m_parsedVersion(0),
|
||||
m_dataSize(0),
|
||||
m_frameSize(0),
|
||||
m_totalSize(0),
|
||||
m_padding(false)
|
||||
{}
|
||||
|
||||
|
@ -45,7 +45,7 @@ Id3v2Frame::Id3v2Frame(const identifierType &id, const TagValue &value, byte gro
|
|||
m_group(group),
|
||||
m_parsedVersion(0),
|
||||
m_dataSize(0),
|
||||
m_frameSize(0),
|
||||
m_totalSize(0),
|
||||
m_padding(false)
|
||||
{}
|
||||
|
||||
|
@ -76,8 +76,8 @@ void Id3v2Frame::parse(BinaryReader &reader, int32 version, uint32 maximalSize)
|
|||
}
|
||||
context = "parsing " + helper.id() + " frame";
|
||||
m_dataSize = reader.readUInt24BE();
|
||||
m_frameSize = m_dataSize + 6;
|
||||
if(m_frameSize > maximalSize) {
|
||||
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();
|
||||
}
|
||||
|
@ -97,8 +97,8 @@ void Id3v2Frame::parse(BinaryReader &reader, int32 version, uint32 maximalSize)
|
|||
m_dataSize = version >= 4
|
||||
? reader.readSynchsafeUInt32BE()
|
||||
: reader.readUInt32BE();
|
||||
m_frameSize = m_dataSize + 10;
|
||||
if(m_frameSize > maximalSize) {
|
||||
m_totalSize = m_dataSize + 10;
|
||||
if(m_totalSize > maximalSize) {
|
||||
addNotification(NotificationType::Warning, "The frame is truncated and will be ignored.", context);
|
||||
throw TruncatedDataException();
|
||||
}
|
||||
|
@ -370,7 +370,7 @@ void Id3v2Frame::cleared()
|
|||
m_group = 0;
|
||||
m_parsedVersion = 0;
|
||||
m_dataSize = 0;
|
||||
m_frameSize = 0;
|
||||
m_totalSize = 0;
|
||||
m_padding = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@ public:
|
|||
Id3v2FrameHelper(const std::string &id, StatusProvider &provider);
|
||||
|
||||
const std::string &id() const;
|
||||
TagTextEncoding parseTextEncodingByte(byte textEncodingByte);
|
||||
byte makeTextEncodingByte(TagTextEncoding textEncoding);
|
||||
|
||||
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);
|
||||
|
@ -34,6 +34,8 @@ public:
|
|||
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);
|
||||
|
@ -95,7 +97,7 @@ public:
|
|||
std::string frameIdString() const;
|
||||
int16 flag() const;
|
||||
void setFlag(int16 value);
|
||||
uint32 frameSize() const;
|
||||
uint32 totalSize() const;
|
||||
uint32 dataSize() const;
|
||||
bool toDiscardWhenUnknownAndTagIsAltered() const;
|
||||
bool toDiscardWhenUnknownAndFileIsAltered() const;
|
||||
|
@ -118,7 +120,7 @@ private:
|
|||
byte m_group;
|
||||
int32 m_parsedVersion;
|
||||
uint32 m_dataSize;
|
||||
uint32 m_frameSize;
|
||||
uint32 m_totalSize;
|
||||
bool m_padding;
|
||||
};
|
||||
|
||||
|
@ -175,11 +177,11 @@ inline void Id3v2Frame::setFlag(int16 value)
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the size of the frame in bytes.
|
||||
* \brief Returns the total size of the frame in bytes.
|
||||
*/
|
||||
inline uint32 Id3v2Frame::frameSize() const
|
||||
inline uint32 Id3v2Frame::totalSize() const
|
||||
{
|
||||
return m_frameSize;
|
||||
return m_totalSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -195,7 +197,7 @@ inline uint32 Id3v2Frame::dataSize() const
|
|||
*/
|
||||
inline bool Id3v2Frame::toDiscardWhenUnknownAndTagIsAltered() const
|
||||
{
|
||||
return (m_flag & 0x8000) == 0x8000;
|
||||
return m_flag & 0x8000;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -203,7 +205,7 @@ inline bool Id3v2Frame::toDiscardWhenUnknownAndTagIsAltered() const
|
|||
*/
|
||||
inline bool Id3v2Frame::toDiscardWhenUnknownAndFileIsAltered() const
|
||||
{
|
||||
return (m_flag & 0x4000) == 0x4000;
|
||||
return m_flag & 0x4000;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -211,7 +213,7 @@ inline bool Id3v2Frame::toDiscardWhenUnknownAndFileIsAltered() const
|
|||
*/
|
||||
inline bool Id3v2Frame::isReadOnly() const
|
||||
{
|
||||
return (m_flag & 0x2000) == 0x2000;
|
||||
return m_flag & 0x2000;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -219,7 +221,7 @@ inline bool Id3v2Frame::isReadOnly() const
|
|||
*/
|
||||
inline bool Id3v2Frame::isCompressed() const
|
||||
{
|
||||
return m_parsedVersion >= 4 ? (m_flag & 0x8) == 0x8 : (m_flag & 0x80) == 0x80;
|
||||
return m_parsedVersion >= 4 ? m_flag & 0x8 : m_flag & 0x80;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -228,7 +230,7 @@ inline bool Id3v2Frame::isCompressed() const
|
|||
*/
|
||||
inline bool Id3v2Frame::isEncrypted() const
|
||||
{
|
||||
return m_parsedVersion >= 4 ? (m_flag & 0x4) == 0x8 : (m_flag & 0x40) == 0x40;
|
||||
return m_parsedVersion >= 4 ? m_flag & 0x4 : m_flag & 0x40;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -236,7 +238,7 @@ inline bool Id3v2Frame::isEncrypted() const
|
|||
*/
|
||||
inline bool Id3v2Frame::hasGroupInformation() const
|
||||
{
|
||||
return m_parsedVersion >= 4 ? (m_flag & 0x40) == 0x40 : (m_flag & 0x20) == 0x20;
|
||||
return m_parsedVersion >= 4 ? m_flag & 0x40 : m_flag & 0x20;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -244,7 +246,7 @@ inline bool Id3v2Frame::hasGroupInformation() const
|
|||
*/
|
||||
inline bool Id3v2Frame::isUnsynchronized() const
|
||||
{
|
||||
return m_parsedVersion >= 4 ? (m_flag & 0x2) == 0x2 : false;
|
||||
return m_parsedVersion >= 4 ? m_flag & 0x2 : false;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -252,7 +254,7 @@ inline bool Id3v2Frame::isUnsynchronized() const
|
|||
*/
|
||||
inline bool Id3v2Frame::hasDataLengthIndicator() const
|
||||
{
|
||||
return m_parsedVersion >= 4 ? (m_flag & 0x1) == 0x1 : isCompressed();
|
||||
return m_parsedVersion >= 4 ? m_flag & 0x1 : isCompressed();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -181,13 +181,20 @@ 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)
|
||||
void Id3v2Tag::parse(istream &stream, uint64 maximalSize)
|
||||
{
|
||||
// prepare parsing
|
||||
invalidateStatus();
|
||||
const string context("parsing ID3v2 tag");
|
||||
BinaryReader reader(&stream);
|
||||
uint64 startOffset = stream.tellg();
|
||||
|
||||
// check whether the header is truncated
|
||||
if(maximalSize && maximalSize < 10) {
|
||||
addNotification(NotificationType::Critical, "ID3v2 header is truncated (at least 10 bytes expected).", context);
|
||||
throw TruncatedDataException();
|
||||
}
|
||||
|
||||
// read signature: ID3
|
||||
if(reader.readUInt24BE() == 0x494433u) {
|
||||
// read header data
|
||||
|
@ -196,7 +203,7 @@ void Id3v2Tag::parse(istream &stream)
|
|||
setVersion(majorVersion, revisionVersion);
|
||||
m_flags = reader.readByte();
|
||||
m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
|
||||
m_size = m_sizeExcludingHeader + 10;
|
||||
m_size = 10 + m_sizeExcludingHeader;
|
||||
if(m_sizeExcludingHeader == 0) {
|
||||
addNotification(NotificationType::Warning, "ID3v2 tag seems to be empty.", context);
|
||||
} else {
|
||||
|
@ -205,50 +212,77 @@ void Id3v2Tag::parse(istream &stream)
|
|||
addNotification(NotificationType::Critical, "The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
|
||||
throw VersionNotSupportedException();
|
||||
}
|
||||
|
||||
// read extended header (if present)
|
||||
if(hasExtendedHeader()) {
|
||||
if(maximalSize && maximalSize < 14) {
|
||||
addNotification(NotificationType::Critical, "Extended header denoted but not present.", context);
|
||||
throw TruncatedDataException();
|
||||
}
|
||||
m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
|
||||
if(m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader) {
|
||||
addNotification(NotificationType::Critical, "Extended header is invalid.", context);
|
||||
throw InvalidDataException();
|
||||
if(m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
|
||||
addNotification(NotificationType::Critical, "Extended header is invalid/truncated.", context);
|
||||
throw TruncatedDataException();
|
||||
}
|
||||
stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
|
||||
}
|
||||
|
||||
// how many bytes remain for frames and padding?
|
||||
uint32 bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
|
||||
if(bytesRemaining > maximalSize) {
|
||||
bytesRemaining = maximalSize;
|
||||
addNotification(NotificationType::Critical, "Frames are truncated.", context);
|
||||
}
|
||||
|
||||
// read frames
|
||||
istream::pos_type pos = stream.tellg();
|
||||
uint32 frameSize;
|
||||
int32 bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
|
||||
auto pos = stream.tellg();
|
||||
Id3v2Frame frame;
|
||||
const uint32 &frameId = frame.id();
|
||||
do {
|
||||
while(bytesRemaining) {
|
||||
// seek to next frame
|
||||
stream.seekg(pos);
|
||||
// parse frame
|
||||
try {
|
||||
frame.parse(reader, majorVersion, bytesRemaining);
|
||||
if(frameId) {
|
||||
if(frame.id()) {
|
||||
// add frame if parsing was successfull
|
||||
if(Id3v2FrameIds::isTextfield(frameId) && fields().count(frame.id()) == 1) {
|
||||
if(Id3v2FrameIds::isTextfield(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>(frameId, frame));
|
||||
fields().insert(pair<fieldType::identifierType, fieldType>(frame.id(), frame));
|
||||
}
|
||||
} catch(NoDataFoundException &) {
|
||||
} catch(const NoDataFoundException &) {
|
||||
if(frame.hasPaddingReached()) {
|
||||
m_paddingSize = (startOffset + m_size) - pos;
|
||||
m_paddingSize = startOffset + m_size - pos;
|
||||
break;
|
||||
}
|
||||
} catch(Failure &) {
|
||||
} catch(const Failure &) {
|
||||
// nothing to do here since notifications will be added anyways
|
||||
}
|
||||
|
||||
// add parsing notifications of frame
|
||||
addNotifications(context, frame);
|
||||
frame.invalidateNotifications();
|
||||
|
||||
// calculate next frame offset
|
||||
frameSize = frame.frameSize();
|
||||
bytesRemaining -= frameSize;
|
||||
pos += frameSize;
|
||||
} while (bytesRemaining > 0);
|
||||
bytesRemaining -= frame.totalSize();
|
||||
pos += frame.totalSize();
|
||||
}
|
||||
|
||||
// check for extended header
|
||||
if(hasFooter()) {
|
||||
if(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) {
|
||||
addNotification(NotificationType::Critical, "Footer signature is invalid.", context);
|
||||
}
|
||||
// skip remaining footer
|
||||
stream.seekg(7, ios_base::cur);
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Footer denoted but not present.", context);
|
||||
throw TruncatedDataException();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Signature is invalid.", context);
|
||||
|
|
|
@ -34,7 +34,7 @@ public:
|
|||
bool supportsDescription(KnownField field) const;
|
||||
bool supportsMimeType(KnownField field) const;
|
||||
|
||||
void parse(std::istream &sourceStream);
|
||||
void parse(std::istream &sourceStream, uint64 maximalSize = 0);
|
||||
void make(std::ostream &targetStream);
|
||||
|
||||
byte majorVersion() const;
|
||||
|
@ -140,7 +140,7 @@ inline byte Id3v2Tag::flags() const
|
|||
*/
|
||||
inline bool Id3v2Tag::isUnsynchronisationUsed() const
|
||||
{
|
||||
return (m_flags & 0x80) == 0x80;
|
||||
return m_flags & 0x80;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -148,7 +148,7 @@ inline bool Id3v2Tag::isUnsynchronisationUsed() const
|
|||
*/
|
||||
inline bool Id3v2Tag::hasExtendedHeader() const
|
||||
{
|
||||
return (m_majorVersion >= 3) && ((m_flags & 0x40) == 0x40);
|
||||
return (m_majorVersion >= 3) && (m_flags & 0x40);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -156,7 +156,7 @@ inline bool Id3v2Tag::hasExtendedHeader() const
|
|||
*/
|
||||
inline bool Id3v2Tag::isExperimental() const
|
||||
{
|
||||
return (m_majorVersion >= 3) && ((m_flags & 0x20) == 0x20);
|
||||
return (m_majorVersion >= 3) && (m_flags & 0x20);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -164,7 +164,7 @@ inline bool Id3v2Tag::isExperimental() const
|
|||
*/
|
||||
inline bool Id3v2Tag::hasFooter() const
|
||||
{
|
||||
return (m_majorVersion >= 3) && ((m_flags & 0x10) == 0x10);
|
||||
return (m_majorVersion >= 3) && (m_flags & 0x10);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -38,8 +38,10 @@
|
|||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <system_error>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
using namespace IoUtilities;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace ChronoUtilities;
|
||||
|
@ -144,13 +146,16 @@ void MediaFileInfo::parseContainerFormat()
|
|||
// there's no need to read the container format twice
|
||||
return;
|
||||
}
|
||||
|
||||
invalidateStatus();
|
||||
static const string context("parsing file header");
|
||||
open(); // ensure the file is open
|
||||
m_containerFormat = ContainerFormat::Unknown;
|
||||
|
||||
// file size
|
||||
m_paddingSize = 0;
|
||||
m_containerOffset = 0;
|
||||
|
||||
// read signatrue
|
||||
char buff[16];
|
||||
const char *const buffEnd = buff + sizeof(buff), *buffOffset;
|
||||
|
@ -158,34 +163,50 @@ startParsingSignature:
|
|||
if(size() - m_containerOffset >= 16) {
|
||||
stream().seekg(m_containerOffset, ios_base::beg);
|
||||
stream().read(buff, sizeof(buff));
|
||||
|
||||
// skip zero bytes/padding
|
||||
size_t bytesSkipped = 0;
|
||||
for(buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped);
|
||||
if(bytesSkipped >= 4) {
|
||||
m_containerOffset += bytesSkipped;
|
||||
|
||||
// give up after 0x100 bytes
|
||||
if((m_paddingSize += bytesSkipped) >= 0x100u) {
|
||||
m_containerParsingStatus = ParsingStatus::NotSupported;
|
||||
m_containerFormat = ContainerFormat::Unknown;
|
||||
return;
|
||||
}
|
||||
|
||||
// try again
|
||||
goto startParsingSignature;
|
||||
}
|
||||
if(m_paddingSize) {
|
||||
addNotification(NotificationType::Warning, ConversionUtilities::numberToString(m_paddingSize) + " zero-bytes skipped at the beginning of the file.", context);
|
||||
}
|
||||
|
||||
// parse signature
|
||||
switch(m_containerFormat = parseSignature(buff, sizeof(buff))) {
|
||||
switch((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
|
||||
case ContainerFormat::Id2v2Tag:
|
||||
// save position of ID3v2 tag
|
||||
m_actualId3v2TagOffsets.push_back(m_containerOffset);
|
||||
if(m_actualId3v2TagOffsets.size() == 2) {
|
||||
addNotification(NotificationType::Warning, "There is more then just one ID3v2 header at the beginning of the file.", context);
|
||||
}
|
||||
stream().seekg(m_containerOffset + 6, ios_base::beg);
|
||||
stream().read(buff, 4);
|
||||
|
||||
// read ID3v2 header
|
||||
stream().seekg(m_containerOffset + 5, ios_base::beg);
|
||||
stream().read(buff, 5);
|
||||
|
||||
// set the container offset to skip ID3v2 header
|
||||
m_containerOffset += ConversionUtilities::toNormalInt(ConversionUtilities::BE::toUInt32(buff)) + 10;
|
||||
goto startParsingSignature; // read signature again
|
||||
m_containerOffset += ConversionUtilities::toNormalInt(ConversionUtilities::BE::toUInt32(buff + 1)) + 10;
|
||||
if((*buff) & 0x10) {
|
||||
// footer present
|
||||
m_containerOffset += 10;
|
||||
}
|
||||
|
||||
// continue reading signature
|
||||
goto startParsingSignature;
|
||||
|
||||
case ContainerFormat::Mp4: {
|
||||
m_container = make_unique<Mp4Container>(*this, m_containerOffset);
|
||||
NotificationList notifications;
|
||||
|
@ -196,6 +217,7 @@ startParsingSignature:
|
|||
}
|
||||
addNotifications(notifications);
|
||||
break;
|
||||
|
||||
} case ContainerFormat::Ebml: {
|
||||
auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
|
||||
NotificationList notifications;
|
||||
|
@ -226,6 +248,8 @@ startParsingSignature:
|
|||
;
|
||||
}
|
||||
}
|
||||
|
||||
// set parsing status
|
||||
if(m_containerParsingStatus == ParsingStatus::NotParsedYet) {
|
||||
if(m_containerFormat == ContainerFormat::Unknown) {
|
||||
m_containerParsingStatus = ParsingStatus::NotSupported;
|
||||
|
@ -1334,19 +1358,23 @@ void MediaFileInfo::invalidated()
|
|||
void MediaFileInfo::makeMp3File()
|
||||
{
|
||||
const string context("making MP3 file");
|
||||
// there's no need to rewrite the complete 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(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
|
||||
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 ...");
|
||||
open(); // ensure the file is still open
|
||||
// ensure the file is still open / not readonly
|
||||
open();
|
||||
stream().seekp(-128, ios_base::end);
|
||||
try {
|
||||
m_id3v1Tag->make(stream());
|
||||
} catch(Failure &) {
|
||||
addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
|
||||
}
|
||||
} else { // the currently existing id3v1 tag shall be removed
|
||||
} else {
|
||||
// the currently existing ID3v1 tag shall be removed
|
||||
updateStatus("No need to rewrite the whole file, just truncating it to remove ID3v1 tag ...");
|
||||
stream().close();
|
||||
if(truncate(path().c_str(), size() - 128) == 0) {
|
||||
|
@ -1356,10 +1384,13 @@ void MediaFileInfo::makeMp3File()
|
|||
throw ios_base::failure("Unable to truncate file to remove ID3v1 tag.");
|
||||
}
|
||||
}
|
||||
} else { // the doesn't file need to be rewritten
|
||||
|
||||
} 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.");
|
||||
open(); // ensure the file is still open
|
||||
// ensure the file is still open / not readonly
|
||||
open();
|
||||
stream().seekp(0, ios_base::end);
|
||||
try {
|
||||
m_id3v1Tag->make(stream());
|
||||
|
@ -1370,23 +1401,30 @@ void MediaFileInfo::makeMp3File()
|
|||
addNotification(NotificationType::Information, "Nothing to be changed.", context);
|
||||
}
|
||||
}
|
||||
} else { // the file needs to be rewritten
|
||||
|
||||
} else {
|
||||
// ID3v2 needs to be modified -> file needs to be rewritten
|
||||
// TODO: take advantage of possibly available padding
|
||||
|
||||
// prepare for rewriting
|
||||
updateStatus("Prepareing for rewriting MP3 file ...");
|
||||
close(); // close the file (if its opened)
|
||||
string backupPath;
|
||||
fstream backupStream;
|
||||
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
|
||||
int counter = 1;
|
||||
|
||||
// write ID3v2 tags
|
||||
unsigned int counter = 1;
|
||||
for(auto &id3v2Tag : m_id3v2Tags) {
|
||||
try {
|
||||
id3v2Tag->make(stream());
|
||||
} catch(Failure &) {
|
||||
} catch(const Failure &) {
|
||||
if(m_id3v2Tags.size()) {
|
||||
addNotification(NotificationType::Warning, "Unable to write " + ConversionUtilities::numberToString(counter) + ". ID3v2 tag.", context);
|
||||
} else {
|
||||
|
@ -1395,25 +1433,17 @@ void MediaFileInfo::makeMp3File()
|
|||
}
|
||||
++counter;
|
||||
}
|
||||
// recopy backup
|
||||
updateStatus("Writing mpeg audio frames ...");
|
||||
uint64 remainingBytes = size() - backupStream.tellg(), read;
|
||||
|
||||
// write media data
|
||||
updateStatus("Writing MPEG audio frames ...");
|
||||
uint64 bytesRemaining = size() - m_containerOffset;
|
||||
if(m_actualExistingId3v1Tag) {
|
||||
remainingBytes -= 128;
|
||||
bytesRemaining -= 128;
|
||||
}
|
||||
const unsigned int bufferSize = 0x4000;
|
||||
char buffer[bufferSize];
|
||||
while(remainingBytes > 0) {
|
||||
if(isAborted()) {
|
||||
throw OperationAbortedException();
|
||||
}
|
||||
backupStream.read(buffer, remainingBytes > bufferSize ? bufferSize : remainingBytes);
|
||||
read = backupStream.gcount();
|
||||
stream().write(buffer, read);
|
||||
remainingBytes -= read;
|
||||
updatePercentage(static_cast<double>(backupStream.tellg()) / static_cast<double>(size()));
|
||||
}
|
||||
// write id3v1 tag
|
||||
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) {
|
||||
try {
|
||||
|
@ -1422,14 +1452,19 @@ void MediaFileInfo::makeMp3File()
|
|||
addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
|
||||
}
|
||||
}
|
||||
stream().flush(); // ensure everything has been actually written
|
||||
reportSizeChanged(stream().tellp()); // report new size
|
||||
close(); // stream is useless for further usage because it is write only
|
||||
} catch(OperationAbortedException &) {
|
||||
|
||||
// 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();
|
||||
|
||||
} catch(const OperationAbortedException &) {
|
||||
addNotification(NotificationType::Information, "Rewriting file to apply new tag information has been aborted.", context);
|
||||
BackupHelper::restoreOriginalFileFromBackupFile(path(), backupPath, stream(), backupStream);
|
||||
throw;
|
||||
} catch(ios_base::failure &ex) {
|
||||
} 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);
|
||||
throw;
|
||||
|
|
Loading…
Reference in New Issue