small improvements

- fixed some mostly ID3/MP3 related bugs
- added convenience methods/operators
This commit is contained in:
Martchus 2016-03-18 21:43:09 +01:00
parent 2af5ce997b
commit 899e2a97fe
15 changed files with 199 additions and 71 deletions

View File

@ -58,7 +58,7 @@ void BasicFileInfo::open(bool readOnly)
*/ */
void BasicFileInfo::reopen(bool readOnly) void BasicFileInfo::reopen(bool readOnly)
{ {
close(); invalidated();
m_file.open(m_path, (m_readOnly = readOnly) ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary); m_file.open(m_path, (m_readOnly = readOnly) ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary);
m_file.seekg(0, ios_base::end); m_file.seekg(0, ios_base::end);
m_size = m_file.tellg(); m_size = m_file.tellg();
@ -76,6 +76,14 @@ void BasicFileInfo::close()
m_file.clear(); m_file.clear();
} }
/*!
* \brief Invalidates the file info manually.
*/
void BasicFileInfo::invalidate()
{
invalidated();
}
/*! /*!
* \brief Sets the current file. * \brief Sets the current file.
* *
@ -202,7 +210,7 @@ string BasicFileInfo::containingDirectory() const
/*! /*!
* \brief This function is called when the BasicFileInfo gets invalidated. * \brief This function is called when the BasicFileInfo gets invalidated.
* This is the case when the current file changes. * This is the case when the current file changes or is reopened.
* *
* When subclassing and overwriting this virtual method invoke the base * When subclassing and overwriting this virtual method invoke the base
* implementation by calling BasicFileInfo::invalidated() before the reimplemented code. * implementation by calling BasicFileInfo::invalidated() before the reimplemented code.
@ -210,7 +218,6 @@ string BasicFileInfo::containingDirectory() const
void BasicFileInfo::invalidated() void BasicFileInfo::invalidated()
{ {
m_size = 0; m_size = 0;
m_path.clear();
close(); close();
} }

View File

@ -22,6 +22,7 @@ public:
bool isOpen() const; bool isOpen() const;
bool isReadOnly() const; bool isReadOnly() const;
void close(); void close();
void invalidate();
const std::string &path() const; const std::string &path() const;
void setPath(const std::string &path); void setPath(const std::string &path);

View File

@ -123,7 +123,7 @@ void Id3v1Tag::make(ostream &stream)
// track // track
try { try {
if(!m_trackPos.isEmpty() && m_trackPos.type() == TagDataType::PositionInSet) if(!m_trackPos.isEmpty() && m_trackPos.type() == TagDataType::PositionInSet)
buffer[1] = m_trackPos.toPositionIntSet().position(); buffer[1] = m_trackPos.toPositionInSet().position();
} catch(ConversionException &) { } catch(ConversionException &) {
addNotification(NotificationType::Warning, "Track position field can not be set because given value can not be converted appropriately.", context); addNotification(NotificationType::Warning, "Track position field can not be set because given value can not be converted appropriately.", context);
} }

View File

@ -108,6 +108,7 @@ int parseGenreIndex(const stringtype &denotation, bool isBigEndian = false)
void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 maximalSize) void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 maximalSize)
{ {
invalidateStatus(); invalidateStatus();
clear();
string context("parsing ID3v2 frame"); string context("parsing ID3v2 frame");
// parse header // parse header
@ -115,13 +116,13 @@ void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32
// parse header for ID3v2.1 and ID3v2.2 // parse header for ID3v2.1 and ID3v2.2
// -> read ID // -> read ID
setId(reader.readUInt24BE()); setId(reader.readUInt24BE());
if((id() & 0xFFFF0000u) == 0) { if(id() & 0xFFFF0000u) {
m_padding = false;
} else {
// padding reached // padding reached
m_padding = true; m_padding = true;
addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", context); addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", context);
throw NoDataFoundException(); throw NoDataFoundException();
} else {
m_padding = false;
} }
// -> update context // -> update context
@ -143,13 +144,13 @@ void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32
// parse header for ID3v2.3 and ID3v2.4 // parse header for ID3v2.3 and ID3v2.4
// -> read ID // -> read ID
setId(reader.readUInt32BE()); setId(reader.readUInt32BE());
if((id() & 0xFF000000u) == 0) { if(id() & 0xFF000000u) {
m_padding = false;
} else {
// padding reached // padding reached
m_padding = true; m_padding = true;
addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", context); addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", context);
throw NoDataFoundException(); throw NoDataFoundException();
} else {
m_padding = false;
} }
// -> update context // -> update context
@ -232,7 +233,7 @@ void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32
position = PositionInSet(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding)); position = PositionInSet(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding));
} }
value().assignPosition(position); value().assignPosition(position);
} catch(ConversionException &) { } catch(const ConversionException &) {
addNotification(NotificationType::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context); addNotification(NotificationType::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
} }
@ -246,7 +247,7 @@ void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32
milliseconds = ConversionUtilities::stringToNumber<double>(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)); value().assignTimeSpan(TimeSpan::fromMilliseconds(milliseconds));
} catch (ConversionException &) { } catch (const ConversionException &) {
addNotification(NotificationType::Warning, "The value of the length frame is not numeric and will be ignored.", context); addNotification(NotificationType::Warning, "The value of the length frame is not numeric and will be ignored.", context);
} }
@ -440,7 +441,7 @@ Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, const byte version) :
// just write the data // just write the data
copy(m_frame.value().dataPointer(), m_frame.value().dataPointer() + m_decompressedSize, m_data.get()); copy(m_frame.value().dataPointer(), m_frame.value().dataPointer() + m_decompressedSize, m_data.get());
} }
} catch(ConversionException &) { } catch(const ConversionException &) {
m_frame.addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context); m_frame.addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context);
throw InvalidDataException(); throw InvalidDataException();
} }
@ -739,21 +740,23 @@ void Id3v2Frame::parsePicture(const char *buffer, size_t maxSize, TagValue &tagV
} }
/*! /*!
* \brief Parses the comment from the specified \a buffer. * \brief Parses the comment/unsynchronized lyrics from the specified \a buffer.
* \param buffer Specifies the buffer holding the picture. * \param buffer Specifies the buffer holding the picture.
* \param dataSize Specifies the maximal number of bytes to read from the buffer. * \param dataSize Specifies the maximal number of bytes to read from the buffer.
* \param tagValue Specifies the tag value used to store the results. * \param tagValue Specifies the tag value used to store the results.
*/ */
void Id3v2Frame::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"); static const string context("parsing comment/unsynchronized lyrics frame");
const char *end = buffer + dataSize; const char *end = buffer + dataSize;
if(dataSize < 6) { if(dataSize < 6) {
addNotification(NotificationType::Critical, "Comment frame is incomplete.", context); addNotification(NotificationType::Critical, "Comment frame is incomplete.", context);
throw TruncatedDataException(); throw TruncatedDataException();
} }
TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer); TagTextEncoding dataEncoding = parseTextEncodingByte(*buffer);
tagValue.setLanguage(string(++buffer, 3)); if(*(++buffer)) {
tagValue.setLanguage(string(buffer, 3));
}
auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true); auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true);
tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding); tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
if(get<2>(substr) >= end) { if(get<2>(substr) >= end) {

View File

@ -21,7 +21,6 @@ enum KnownValue : uint32 {
lEncoder = 0x54454e43, lEncoder = 0x54454e43,
lBpm = 0x5442504d, lBpm = 0x5442504d,
lCover = 0x41504943, lCover = 0x41504943,
//lPerformers = 0x54504532,
lWriter = 0x54455854, lWriter = 0x54455854,
lLength = 0x544c454e, lLength = 0x544c454e,
lLanguage = 0x544c414e, lLanguage = 0x544c414e,
@ -45,7 +44,6 @@ enum KnownValue : uint32 {
sEncoder = 0x54454e, sEncoder = 0x54454e,
sBpm = 0x544250, sBpm = 0x544250,
sCover = 0x504943, sCover = 0x504943,
//sPerformers = 0x545032,
sWriter = 0x545854, sWriter = 0x545854,
sLength = 0x544c45, sLength = 0x544c45,
sLanguage = 0x544c41, sLanguage = 0x544c41,

View File

@ -204,7 +204,7 @@ inline LIB_EXPORT const char *icra() {
} }
inline LIB_EXPORT const char *dateRelease() { inline LIB_EXPORT const char *dateRelease() {
return "DATE_RELEASE"; return "DATE_RELEASED";
} }
inline LIB_EXPORT const char *dateRecorded() { inline LIB_EXPORT const char *dateRecorded() {
return "DATE_RECORDED"; return "DATE_RECORDED";

View File

@ -920,7 +920,7 @@ bool MediaFileInfo::removeAllId3v2Tags()
*/ */
Id3v2Tag *MediaFileInfo::createId3v2Tag() Id3v2Tag *MediaFileInfo::createId3v2Tag()
{ {
if(!m_id3v2Tags.size()) { if(m_id3v2Tags.empty()) {
m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>()); m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
} }
return m_id3v2Tags.front().get(); return m_id3v2Tags.front().get();
@ -1048,9 +1048,7 @@ bool MediaFileInfo::areTagsSupported() const
/*! /*!
* \brief Returns a pointer to the assigned MP4 tag or nullptr if none is assigned. * \brief Returns a pointer to the assigned MP4 tag or nullptr if none is assigned.
* * \remarks The MediaFileInfo keeps the ownership over the object which will be destroyed when the
* \remarks The MediaFileInfo keeps the ownership over the returned
* pointer. The returned MP4 tag will be destroyed when the
* MediaFileInfo is invalidated. * MediaFileInfo is invalidated.
*/ */
Mp4Tag *MediaFileInfo::mp4Tag() const Mp4Tag *MediaFileInfo::mp4Tag() const
@ -1061,7 +1059,6 @@ Mp4Tag *MediaFileInfo::mp4Tag() const
/*! /*!
* \brief Returns pointers to the assigned Matroska tags. * \brief Returns pointers to the assigned Matroska tags.
*
* \remarks The MediaFileInfo keeps the ownership over the returned * \remarks The MediaFileInfo keeps the ownership over the returned
* pointers. The returned Matroska tags will be destroyed when the * pointers. The returned Matroska tags will be destroyed when the
* MediaFileInfo is invalidated. * MediaFileInfo is invalidated.
@ -1077,11 +1074,20 @@ const vector<unique_ptr<MatroskaTag> > &MediaFileInfo::matroskaTags() const
} }
} }
/*!
* \brief Returns a pointer to the first assigned Vorbis comment or nullptr if none is assigned.
* \remarks The MediaFileInfo keeps the ownership over the object which will be destroyed when the
* MediaFileInfo is invalidated.
*/
VorbisComment *MediaFileInfo::vorbisComment() const
{
return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount() > 0 ? static_cast<OggContainer *>(m_container.get())->tags().front().get() : nullptr;
}
/*! /*!
* \brief Returns all chapters assigned to the current file. * \brief Returns all chapters assigned to the current file.
* * \remarks The MediaFileInfo keeps the ownership over the object which will be destroyed when the
* \remarks The MediaFileInfo keeps the ownership over the chapters which will be * MediaFileInfo is invalidated.
* destroyed when the MediaFileInfo is invalidated.
*/ */
vector<AbstractChapter *> MediaFileInfo::chapters() const vector<AbstractChapter *> MediaFileInfo::chapters() const
{ {
@ -1098,9 +1104,8 @@ vector<AbstractChapter *> MediaFileInfo::chapters() const
/*! /*!
* \brief Returns all attachments assigned to the current file. * \brief Returns all attachments assigned to the current file.
* * \remarks The MediaFileInfo keeps the ownership over the object which will be destroyed when the
* \remarks The MediaFileInfo keeps the ownership over the attachments which will be * MediaFileInfo is invalidated.
* destroyed when the MediaFileInfo is invalidated.
*/ */
vector<AbstractAttachment *> MediaFileInfo::attachments() const vector<AbstractAttachment *> MediaFileInfo::attachments() const
{ {
@ -1184,7 +1189,6 @@ NotificationType MediaFileInfo::worstNotificationTypeIncludingRelatedObjects() c
/*! /*!
* \brief Returns the notifications of the current instance and all related * \brief Returns the notifications of the current instance and all related
* objects (tracks, tags, container, ...). * objects (tracks, tags, container, ...).
*
* \remarks The specified list is not cleared before notifications are added. * \remarks The specified list is not cleared before notifications are added.
*/ */
void MediaFileInfo::gatherRelatedNotifications(NotificationList &notifications) const void MediaFileInfo::gatherRelatedNotifications(NotificationList &notifications) const
@ -1390,23 +1394,32 @@ void MediaFileInfo::makeMp3File()
addNotifications(*tag); addNotifications(*tag);
} }
// determine padding, check whether rewrite is required // check whether rewrite is required
bool rewriteRequired = isForcingRewrite() || (tagsSize > static_cast<uint32>(m_containerOffset)); bool rewriteRequired = isForcingRewrite() || (tagsSize > static_cast<uint32>(m_containerOffset));
uint32 padding; uint32 padding = 0;
if(!rewriteRequired) { if(!rewriteRequired) {
// rewriting is not forced and new tag is not too big for available space
// -> calculate new padding
padding = static_cast<uint32>(m_containerOffset) - tagsSize; padding = static_cast<uint32>(m_containerOffset) - tagsSize;
// check whether padding matches specifications // -> check whether the new padding matches specifications
if(padding < minPadding() || padding > maxPadding()) { if(padding < minPadding() || padding > maxPadding()) {
rewriteRequired = true; rewriteRequired = true;
} }
} }
if(rewriteRequired) { if(makers.empty()) {
// use preferred padding when rewriting // an ID3v2 tag shouldn't be written
// -> can't include padding
if(padding) {
// but padding would be present -> need to rewrite
padding = 0;
rewriteRequired = true;
}
} else if(rewriteRequired) {
// rewriting is forced or new ID3v2 tag is too big for available space
// -> use preferred padding when rewriting anyways
padding = preferredPadding(); padding = preferredPadding();
updateStatus("Preparing streams for rewriting ...");
} else {
updateStatus("Preparing streams for updating ...");
} }
updateStatus(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
// setup stream(s) for writing // setup stream(s) for writing
// -> define variables needed to handle output stream and backup stream (required when rewriting the file) // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
@ -1449,7 +1462,7 @@ void MediaFileInfo::makeMp3File()
// include padding into the last ID3v2 tag // include padding into the last ID3v2 tag
makers.back().make(outputStream, padding); makers.back().make(outputStream, padding);
} else { } else {
// no ID3v2 tags assigned -> just write padding // just write padding
for(; padding; --padding) { for(; padding; --padding) {
outputStream.put(0); outputStream.put(0);
} }

View File

@ -26,6 +26,7 @@ class MatroskaTag;
class AbstractTrack; class AbstractTrack;
class WaveAudioStream; class WaveAudioStream;
class MpegAudioFrameStream; class MpegAudioFrameStream;
class VorbisComment;
enum class MediaType; enum class MediaType;
enum class TagType : unsigned int; enum class TagType : unsigned int;
@ -117,6 +118,7 @@ public:
std::vector<Tag *> tags() const; std::vector<Tag *> tags() const;
Mp4Tag *mp4Tag() const; Mp4Tag *mp4Tag() const;
const std::vector<std::unique_ptr<MatroskaTag> > &matroskaTags() const; const std::vector<std::unique_ptr<MatroskaTag> > &matroskaTags() const;
VorbisComment *vorbisComment() const;
bool areTagsSupported() const; bool areTagsSupported() const;
// methods to create/remove tags // methods to create/remove tags

View File

@ -771,7 +771,7 @@ calculatePadding:
reset(); reset();
try { try {
parseTracks(); parseTracks();
} catch(Failure &) { } catch(const Failure &) {
addNotification(NotificationType::Critical, "Unable to reparse the header of the new file.", context); addNotification(NotificationType::Critical, "Unable to reparse the header of the new file.", context);
throw; throw;
} }
@ -931,11 +931,11 @@ void Mp4Container::updateOffsets(const std::vector<int64> &oldMdatOffsets, const
addNotification(NotificationType::Warning, "traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom.", context); addNotification(NotificationType::Warning, "traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom.", context);
} }
} }
} catch(Failure &) { } catch(const Failure &) {
addNotification(NotificationType::Critical, "Unable to parse childs of top-level atom moof.", context); addNotification(NotificationType::Critical, "Unable to parse childs of top-level atom moof.", context);
} }
} }
} catch(Failure &) { } catch(const Failure &) {
addNotification(NotificationType::Critical, "Unable to parse top-level atom moof.", context); addNotification(NotificationType::Critical, "Unable to parse top-level atom moof.", context);
} }
// update each track // update each track
@ -946,7 +946,7 @@ void Mp4Container::updateOffsets(const std::vector<int64> &oldMdatOffsets, const
if(!track->isHeaderValid()) { if(!track->isHeaderValid()) {
try { try {
track->parseHeader(); track->parseHeader();
} catch(Failure &) { } catch(const Failure &) {
addNotification(NotificationType::Warning, "The chunk offsets of track " + track->name() + " couldn't be updated because the track seems to be invalid..", context); addNotification(NotificationType::Warning, "The chunk offsets of track " + track->name() + " couldn't be updated because the track seems to be invalid..", context);
throw; throw;
} }
@ -954,7 +954,7 @@ void Mp4Container::updateOffsets(const std::vector<int64> &oldMdatOffsets, const
if(track->isHeaderValid()) { if(track->isHeaderValid()) {
try { try {
track->updateChunkOffsets(oldMdatOffsets, newMdatOffsets); track->updateChunkOffsets(oldMdatOffsets, newMdatOffsets);
} catch(Failure &) { } catch(const Failure &) {
addNotification(NotificationType::Warning, "The chunk offsets of track " + track->name() + " couldn't be updated.", context); addNotification(NotificationType::Warning, "The chunk offsets of track " + track->name() + " couldn't be updated.", context);
throw; throw;
} }

View File

@ -455,7 +455,7 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field) :
// track number and disk number are exceptions // track number and disk number are exceptions
// raw data type 0 is used, information is stored as pair of unsigned integers // raw data type 0 is used, information is stored as pair of unsigned integers
case Mp4TagAtomIds::TrackPosition: case Mp4TagAtomIds::DiskPosition: { case Mp4TagAtomIds::TrackPosition: case Mp4TagAtomIds::DiskPosition: {
PositionInSet pos = m_field.value().toPositionIntSet(); PositionInSet pos = m_field.value().toPositionInSet();
m_writer.writeInt32BE(pos.position()); m_writer.writeInt32BE(pos.position());
if(pos.total() <= numeric_limits<int16>::max()) { if(pos.total() <= numeric_limits<int16>::max()) {
m_writer.writeInt16BE(static_cast<int16>(pos.total())); m_writer.writeInt16BE(static_cast<int16>(pos.total()));

View File

@ -280,12 +280,12 @@ void OggContainer::internalMakeFile()
} }
// clear iterator // clear iterator
m_iterator = OggIterator(fileInfo().stream(), startOffset(), fileInfo().size()); m_iterator = OggIterator(fileInfo().stream(), startOffset(), fileInfo().size());
} catch(OperationAbortedException &) { } catch(const OperationAbortedException &) {
addNotification(NotificationType::Information, "Rewriting file to apply new tag information has been aborted.", context); addNotification(NotificationType::Information, "Rewriting file to apply new tag information has been aborted.", context);
BackupHelper::restoreOriginalFileFromBackupFile(fileInfo().path(), backupPath, fileInfo().stream(), backupStream); BackupHelper::restoreOriginalFileFromBackupFile(fileInfo().path(), backupPath, fileInfo().stream(), backupStream);
m_iterator.setStream(fileInfo().stream()); m_iterator.setStream(fileInfo().stream());
throw; 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); addNotification(NotificationType::Critical, "IO error occured when rewriting file to apply new tag information.\n" + string(ex.what()), context);
BackupHelper::restoreOriginalFileFromBackupFile(fileInfo().path(), backupPath, fileInfo().stream(), backupStream); BackupHelper::restoreOriginalFileFromBackupFile(fileInfo().path(), backupPath, fileInfo().stream(), backupStream);
m_iterator.setStream(fileInfo().stream()); m_iterator.setStream(fileInfo().stream());

View File

@ -39,10 +39,10 @@ public:
bool bytesRemaining(size_t atLeast) const; bool bytesRemaining(size_t atLeast) const;
operator bool() const; operator bool() const;
OggIterator &operator ++(); OggIterator &operator++();
OggIterator operator ++(int); OggIterator operator++(int);
OggIterator &operator --(); OggIterator &operator--();
OggIterator operator --(int); OggIterator operator--(int);
private: private:
bool fetchNextPage(); bool fetchNextPage();
@ -256,7 +256,7 @@ inline bool OggIterator::bytesRemaining(size_t atLeast) const
/*! /*!
* \brief Increments the current position by one segment if the iterator is valid; otherwise nothing happens. * \brief Increments the current position by one segment if the iterator is valid; otherwise nothing happens.
*/ */
inline OggIterator &OggIterator::operator ++() inline OggIterator &OggIterator::operator++()
{ {
nextSegment(); nextSegment();
return *this; return *this;
@ -265,7 +265,7 @@ inline OggIterator &OggIterator::operator ++()
/*! /*!
* \brief Increments the current position by one segment if the iterator is valid; otherwise nothing happens. * \brief Increments the current position by one segment if the iterator is valid; otherwise nothing happens.
*/ */
inline OggIterator OggIterator::operator ++(int) inline OggIterator OggIterator::operator++(int)
{ {
OggIterator tmp = *this; OggIterator tmp = *this;
nextSegment(); nextSegment();
@ -275,7 +275,7 @@ inline OggIterator OggIterator::operator ++(int)
/*! /*!
* \brief Decrements the current position by one segment if the iterator is valid; otherwise nothing happens. * \brief Decrements the current position by one segment if the iterator is valid; otherwise nothing happens.
*/ */
inline OggIterator &OggIterator::operator --() inline OggIterator &OggIterator::operator--()
{ {
previousSegment(); previousSegment();
return *this; return *this;
@ -284,7 +284,7 @@ inline OggIterator &OggIterator::operator --()
/*! /*!
* \brief Decrements the current position by one segment if the iterator is valid; otherwise nothing happens. * \brief Decrements the current position by one segment if the iterator is valid; otherwise nothing happens.
*/ */
inline OggIterator OggIterator::operator --(int) inline OggIterator OggIterator::operator--(int)
{ {
OggIterator tmp = *this; OggIterator tmp = *this;
previousSegment(); previousSegment();

View File

@ -26,6 +26,7 @@ public:
constexpr int32 position() const; constexpr int32 position() const;
constexpr int32 total() const; constexpr int32 total() const;
constexpr bool isNull() const; constexpr bool isNull() const;
constexpr bool operator==(const PositionInSet &other) const;
template <typename StringType = std::string> template <typename StringType = std::string>
StringType toString() const; StringType toString() const;
@ -91,6 +92,14 @@ constexpr inline bool PositionInSet::isNull() const
return m_position == 0 && m_total == 0; return m_position == 0 && m_total == 0;
} }
/*!
* \brief Returns whether this instance equals \a other.
*/
constexpr inline bool PositionInSet::operator==(const PositionInSet &other) const
{
return m_position == other.m_position && m_total == other.m_total;
}
/*! /*!
* \brief Returns the string representation of the current PositionInSet. * \brief Returns the string representation of the current PositionInSet.
*/ */

View File

@ -9,6 +9,7 @@
#include <algorithm> #include <algorithm>
#include <utility> #include <utility>
#include <cstring>
using namespace std; using namespace std;
using namespace ConversionUtilities; using namespace ConversionUtilities;
@ -119,7 +120,7 @@ TagValue::TagValue(const PositionInSet &value) :
TagValue::TagValue(const TagValue &other) : TagValue::TagValue(const TagValue &other) :
m_size(other.m_size), m_size(other.m_size),
m_type(other.m_type), m_type(other.m_type),
m_dec(other.m_dec), m_desc(other.m_desc),
m_mimeType(other.m_mimeType), m_mimeType(other.m_mimeType),
m_lng(other.m_lng), m_lng(other.m_lng),
m_labeledAsReadonly(other.m_labeledAsReadonly), m_labeledAsReadonly(other.m_labeledAsReadonly),
@ -140,7 +141,7 @@ TagValue &TagValue::operator=(const TagValue &other)
if(this != &other) { if(this != &other) {
m_size = other.m_size; m_size = other.m_size;
m_type = other.m_type; m_type = other.m_type;
m_dec = other.m_dec; m_desc = other.m_desc;
m_mimeType = other.m_mimeType; m_mimeType = other.m_mimeType;
m_lng = other.m_lng; m_lng = other.m_lng;
m_labeledAsReadonly = other.m_labeledAsReadonly; m_labeledAsReadonly = other.m_labeledAsReadonly;
@ -156,6 +157,45 @@ TagValue &TagValue::operator=(const TagValue &other)
return *this; return *this;
} }
/*!
* \brief Returns whether both instances are equal.
*
* Both instances are only considered equal, if the data type, encodings (if relevant for the type) and meta data are equal.
*/
bool TagValue::operator==(const TagValue &other) const
{
if(m_type != other.m_type || m_desc != other.m_desc || (!m_desc.empty() && m_descEncoding != other.m_descEncoding)
|| m_mimeType != other.m_mimeType || m_lng != other.m_lng || m_labeledAsReadonly != other.m_labeledAsReadonly) {
return false;
}
switch(m_type) {
case TagDataType::Text:
if(m_size != other.m_size && m_encoding != other.m_encoding) {
return false;
}
return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
case TagDataType::PositionInSet:
return toPositionInSet() == other.toPositionInSet();
case TagDataType::Integer:
return toInteger() == other.toInteger();
case TagDataType::StandardGenreIndex:
return toStandardGenreIndex() == other.toStandardGenreIndex();
case TagDataType::TimeSpan:
return toTimeSpan() == other.toTimeSpan();
case TagDataType::DateTime:
return toDateTime() == other.toDateTime();
case TagDataType::Picture:
case TagDataType::Binary:
case TagDataType::Undefined:
if(m_size != other.m_size) {
return false;
}
return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
default:
return false;
}
}
/*! /*!
* \brief Destroys the TagValue. * \brief Destroys the TagValue.
*/ */
@ -171,7 +211,7 @@ TagValue::~TagValue()
*/ */
void TagValue::clearMetadata() void TagValue::clearMetadata()
{ {
m_dec.clear(); m_desc.clear();
m_mimeType.clear(); m_mimeType.clear();
m_lng.clear(); m_lng.clear();
m_labeledAsReadonly = false; m_labeledAsReadonly = false;
@ -260,7 +300,7 @@ int TagValue::toStandardGenreIndex() const
* PositionInSet representation. * PositionInSet representation.
* \throws Throws ConversionException an failure. * \throws Throws ConversionException an failure.
*/ */
PositionInSet TagValue::toPositionIntSet() const PositionInSet TagValue::toPositionInSet() const
{ {
if(!isEmpty()) { if(!isEmpty()) {
switch(m_type) { switch(m_type) {
@ -293,7 +333,7 @@ TimeSpan TagValue::toTimeSpan() const
if(!isEmpty()) { if(!isEmpty()) {
switch(m_type) { switch(m_type) {
case TagDataType::Text: case TagDataType::Text:
return TimeSpan::fromSeconds(ConversionUtilities::stringToNumber<int64>(string(m_ptr.get(), m_size))); return TimeSpan::fromString(string(m_ptr.get(), m_size));
case TagDataType::Integer: case TagDataType::Integer:
case TagDataType::TimeSpan: case TagDataType::TimeSpan:
switch(m_size) { switch(m_size) {
@ -366,7 +406,7 @@ void TagValue::toString(string &result) const
result = ConversionUtilities::numberToString(toInteger()); result = ConversionUtilities::numberToString(toInteger());
return; return;
case TagDataType::PositionInSet: case TagDataType::PositionInSet:
result = toPositionIntSet().toString(); result = toPositionInSet().toString();
return; return;
case TagDataType::StandardGenreIndex: case TagDataType::StandardGenreIndex:
if(const char *genreName = Id3Genres::stringFromIndex(toInteger())) { if(const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
@ -386,6 +426,56 @@ void TagValue::toString(string &result) const
result.clear(); result.clear();
} }
/*!
* \brief Converts the value of the current TagValue object to its equivalent
* std::wstring representation.
* \throws Throws ConversionException on failure.
* \remarks Use this only, if UTF-16 text is assigned.
*/
u16string TagValue::toWString() const
{
u16string res;
toWString(res);
return res;
}
/*!
* \brief Converts the value of the current TagValue object to its equivalent
* std::u16string representation.
* \throws Throws ConversionException on failure.
* \remarks Use this only, if UTF-16 text is assigned.
*/
void TagValue::toWString(u16string &result) const
{
if(!isEmpty()) {
switch(m_type) {
case TagDataType::Text:
result.assign(reinterpret_cast<typename u16string::value_type *>(m_ptr.get()), m_size / sizeof(typename u16string::value_type));
return;
case TagDataType::Integer:
result = ConversionUtilities::numberToString<int32, u16string>(toInteger());
return;
case TagDataType::PositionInSet:
result = toPositionInSet().toString<u16string>();
return;
case TagDataType::StandardGenreIndex:
if(const char *genreName = Id3Genres::stringFromIndex(toInteger())) {
// TODO: implement this
throw ConversionException("Wide default genre strings are currently not supported.");
} else {
throw ConversionException("No string representation for the assigned standard genre index available.");
}
break;
case TagDataType::TimeSpan:
// TODO: implement this
throw ConversionException("Wide time span string representations are currently not supported.");
default:
throw ConversionException("Can not convert binary data/picture to string.");
}
}
result.clear();
}
/*! /*!
* \brief Assigns a copy of the given \a text. * \brief Assigns a copy of the given \a text.
* \param text Specifies the text to be assigned. * \param text Specifies the text to be assigned.
@ -438,15 +528,17 @@ void TagValue::assignStandardGenreIndex(int index)
*/ */
void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding) void TagValue::assignData(const char *data, size_t length, TagDataType type, TagTextEncoding encoding)
{ {
m_size = length; if(length > m_size) {
m_type = type; m_ptr = make_unique<char[]>(length);
m_encoding = encoding; }
if(m_size > 0) { if(length) {
m_ptr.reset(new char[m_size]);
std::copy(data, data + length, m_ptr.get()); std::copy(data, data + length, m_ptr.get());
} else { } else {
m_ptr.reset(); m_ptr.reset();
} }
m_size = length;
m_type = type;
m_encoding = encoding;
} }
/*! /*!

View File

@ -75,6 +75,7 @@ public:
// operators // operators
TagValue &operator=(const TagValue &other); TagValue &operator=(const TagValue &other);
TagValue &operator=(TagValue &&other) = default; TagValue &operator=(TagValue &&other) = default;
bool operator==(const TagValue &other) const;
// methods // methods
bool isEmpty() const; bool isEmpty() const;
@ -84,9 +85,11 @@ public:
TagDataType type() const; TagDataType type() const;
std::string toString() const; std::string toString() const;
void toString(std::string &result) const; void toString(std::string &result) const;
std::u16string toWString() const;
void toWString(std::u16string &result) const;
int32 toInteger() const; int32 toInteger() const;
int toStandardGenreIndex() const; int toStandardGenreIndex() const;
PositionInSet toPositionIntSet() const; PositionInSet toPositionInSet() const;
ChronoUtilities::TimeSpan toTimeSpan() const; ChronoUtilities::TimeSpan toTimeSpan() const;
ChronoUtilities::DateTime toDateTime() const; ChronoUtilities::DateTime toDateTime() const;
size_t dataSize() const; size_t dataSize() const;
@ -118,7 +121,7 @@ private:
std::unique_ptr<char[]> m_ptr; std::unique_ptr<char[]> m_ptr;
std::string::size_type m_size; std::string::size_type m_size;
TagDataType m_type; TagDataType m_type;
std::string m_dec; std::string m_desc;
std::string m_mimeType; std::string m_mimeType;
std::string m_lng; std::string m_lng;
bool m_labeledAsReadonly; bool m_labeledAsReadonly;
@ -212,7 +215,7 @@ inline char *TagValue::dataPointer() const
*/ */
inline const std::string &TagValue::description() const inline const std::string &TagValue::description() const
{ {
return m_dec; return m_desc;
} }
/*! /*!
@ -225,7 +228,7 @@ inline const std::string &TagValue::description() const
*/ */
inline void TagValue::setDescription(const std::string &value, TagTextEncoding encoding) inline void TagValue::setDescription(const std::string &value, TagTextEncoding encoding)
{ {
m_dec = value; m_desc = value;
m_descEncoding = encoding; m_descEncoding = encoding;
} }