From 899e2a97fe8f890463199110fac1f2b39db41173 Mon Sep 17 00:00:00 2001 From: Martchus Date: Fri, 18 Mar 2016 21:43:09 +0100 Subject: [PATCH] small improvements - fixed some mostly ID3/MP3 related bugs - added convenience methods/operators --- basicfileinfo.cpp | 13 +++-- basicfileinfo.h | 1 + id3/id3v1tag.cpp | 2 +- id3/id3v2frame.cpp | 27 +++++----- id3/id3v2frameids.h | 2 - matroska/matroskatagid.h | 2 +- mediafileinfo.cpp | 55 +++++++++++-------- mediafileinfo.h | 2 + mp4/mp4container.cpp | 10 ++-- mp4/mp4tagfield.cpp | 2 +- ogg/oggcontainer.cpp | 4 +- ogg/oggiterator.h | 16 +++--- positioninset.h | 9 ++++ tagvalue.cpp | 114 +++++++++++++++++++++++++++++++++++---- tagvalue.h | 11 ++-- 15 files changed, 199 insertions(+), 71 deletions(-) diff --git a/basicfileinfo.cpp b/basicfileinfo.cpp index 02221a8..d8a704a 100644 --- a/basicfileinfo.cpp +++ b/basicfileinfo.cpp @@ -58,7 +58,7 @@ void BasicFileInfo::open(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.seekg(0, ios_base::end); m_size = m_file.tellg(); @@ -76,6 +76,14 @@ void BasicFileInfo::close() m_file.clear(); } +/*! + * \brief Invalidates the file info manually. + */ +void BasicFileInfo::invalidate() +{ + invalidated(); +} + /*! * \brief Sets the current file. * @@ -202,7 +210,7 @@ string BasicFileInfo::containingDirectory() const /*! * \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 * implementation by calling BasicFileInfo::invalidated() before the reimplemented code. @@ -210,7 +218,6 @@ string BasicFileInfo::containingDirectory() const void BasicFileInfo::invalidated() { m_size = 0; - m_path.clear(); close(); } diff --git a/basicfileinfo.h b/basicfileinfo.h index 631f0cb..b62aa90 100644 --- a/basicfileinfo.h +++ b/basicfileinfo.h @@ -22,6 +22,7 @@ public: bool isOpen() const; bool isReadOnly() const; void close(); + void invalidate(); const std::string &path() const; void setPath(const std::string &path); diff --git a/id3/id3v1tag.cpp b/id3/id3v1tag.cpp index 43b8e07..504eb58 100644 --- a/id3/id3v1tag.cpp +++ b/id3/id3v1tag.cpp @@ -123,7 +123,7 @@ void Id3v1Tag::make(ostream &stream) // track try { if(!m_trackPos.isEmpty() && m_trackPos.type() == TagDataType::PositionInSet) - buffer[1] = m_trackPos.toPositionIntSet().position(); + buffer[1] = m_trackPos.toPositionInSet().position(); } catch(ConversionException &) { addNotification(NotificationType::Warning, "Track position field can not be set because given value can not be converted appropriately.", context); } diff --git a/id3/id3v2frame.cpp b/id3/id3v2frame.cpp index 7755979..96a7847 100644 --- a/id3/id3v2frame.cpp +++ b/id3/id3v2frame.cpp @@ -108,6 +108,7 @@ int parseGenreIndex(const stringtype &denotation, bool isBigEndian = false) void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 maximalSize) { invalidateStatus(); + clear(); string context("parsing ID3v2 frame"); // parse header @@ -115,13 +116,13 @@ void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 // parse header for ID3v2.1 and ID3v2.2 // -> read ID setId(reader.readUInt24BE()); - if((id() & 0xFFFF0000u) == 0) { + if(id() & 0xFFFF0000u) { + m_padding = false; + } else { // padding reached m_padding = true; addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", context); throw NoDataFoundException(); - } else { - m_padding = false; } // -> update context @@ -143,13 +144,13 @@ void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 // parse header for ID3v2.3 and ID3v2.4 // -> read ID setId(reader.readUInt32BE()); - if((id() & 0xFF000000u) == 0) { + if(id() & 0xFF000000u) { + m_padding = false; + } else { // padding reached m_padding = true; addNotification(NotificationType::Debug, "Frame ID starts with null-byte -> padding reached.", context); throw NoDataFoundException(); - } else { - m_padding = false; } // -> 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)); } 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); } @@ -246,7 +247,7 @@ void Id3v2Frame::parse(BinaryReader &reader, const uint32 version, const uint32 milliseconds = ConversionUtilities::stringToNumber(parseString(buffer.get() + 1, m_dataSize - 1, dataEncoding), 10); } 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); } @@ -440,7 +441,7 @@ Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, const byte version) : // just write the data 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); 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 dataSize Specifies the maximal number of bytes to read from the buffer. * \param tagValue Specifies the tag value used to store the results. */ 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; if(dataSize < 6) { addNotification(NotificationType::Critical, "Comment frame is incomplete.", context); throw TruncatedDataException(); } 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); tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding); if(get<2>(substr) >= end) { diff --git a/id3/id3v2frameids.h b/id3/id3v2frameids.h index e50689c..b219339 100644 --- a/id3/id3v2frameids.h +++ b/id3/id3v2frameids.h @@ -21,7 +21,6 @@ enum KnownValue : uint32 { lEncoder = 0x54454e43, lBpm = 0x5442504d, lCover = 0x41504943, - //lPerformers = 0x54504532, lWriter = 0x54455854, lLength = 0x544c454e, lLanguage = 0x544c414e, @@ -45,7 +44,6 @@ enum KnownValue : uint32 { sEncoder = 0x54454e, sBpm = 0x544250, sCover = 0x504943, - //sPerformers = 0x545032, sWriter = 0x545854, sLength = 0x544c45, sLanguage = 0x544c41, diff --git a/matroska/matroskatagid.h b/matroska/matroskatagid.h index d94803c..35e7abf 100644 --- a/matroska/matroskatagid.h +++ b/matroska/matroskatagid.h @@ -204,7 +204,7 @@ inline LIB_EXPORT const char *icra() { } inline LIB_EXPORT const char *dateRelease() { - return "DATE_RELEASE"; + return "DATE_RELEASED"; } inline LIB_EXPORT const char *dateRecorded() { return "DATE_RECORDED"; diff --git a/mediafileinfo.cpp b/mediafileinfo.cpp index c4d07b8..ceaf402 100644 --- a/mediafileinfo.cpp +++ b/mediafileinfo.cpp @@ -920,7 +920,7 @@ bool MediaFileInfo::removeAllId3v2Tags() */ Id3v2Tag *MediaFileInfo::createId3v2Tag() { - if(!m_id3v2Tags.size()) { + if(m_id3v2Tags.empty()) { m_id3v2Tags.emplace_back(make_unique()); } 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. - * - * \remarks The MediaFileInfo keeps the ownership over the returned - * pointer. The returned MP4 tag will be destroyed when the + * \remarks The MediaFileInfo keeps the ownership over the object which will be destroyed when the * MediaFileInfo is invalidated. */ Mp4Tag *MediaFileInfo::mp4Tag() const @@ -1061,7 +1059,6 @@ Mp4Tag *MediaFileInfo::mp4Tag() const /*! * \brief Returns pointers to the assigned Matroska tags. - * * \remarks The MediaFileInfo keeps the ownership over the returned * pointers. The returned Matroska tags will be destroyed when the * MediaFileInfo is invalidated. @@ -1077,11 +1074,20 @@ const vector > &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(m_container.get())->tags().front().get() : nullptr; +} + /*! * \brief Returns all chapters assigned to the current file. - * - * \remarks The MediaFileInfo keeps the ownership over the chapters which will be - * destroyed when the MediaFileInfo is invalidated. + * \remarks The MediaFileInfo keeps the ownership over the object which will be destroyed when the + * MediaFileInfo is invalidated. */ vector MediaFileInfo::chapters() const { @@ -1098,9 +1104,8 @@ vector MediaFileInfo::chapters() const /*! * \brief Returns all attachments assigned to the current file. - * - * \remarks The MediaFileInfo keeps the ownership over the attachments which will be - * destroyed when the MediaFileInfo is invalidated. + * \remarks The MediaFileInfo keeps the ownership over the object which will be destroyed when the + * MediaFileInfo is invalidated. */ vector MediaFileInfo::attachments() const { @@ -1184,7 +1189,6 @@ NotificationType MediaFileInfo::worstNotificationTypeIncludingRelatedObjects() c /*! * \brief Returns the notifications of the current instance and all related * objects (tracks, tags, container, ...). - * * \remarks The specified list is not cleared before notifications are added. */ void MediaFileInfo::gatherRelatedNotifications(NotificationList ¬ifications) const @@ -1390,23 +1394,32 @@ void MediaFileInfo::makeMp3File() addNotifications(*tag); } - // determine padding, check whether rewrite is required + // check whether rewrite is required bool rewriteRequired = isForcingRewrite() || (tagsSize > static_cast(m_containerOffset)); - uint32 padding; + uint32 padding = 0; if(!rewriteRequired) { + // rewriting is not forced and new tag is not too big for available space + // -> calculate new padding padding = static_cast(m_containerOffset) - tagsSize; - // check whether padding matches specifications + // -> check whether the new padding matches specifications if(padding < minPadding() || padding > maxPadding()) { rewriteRequired = true; } } - if(rewriteRequired) { - // use preferred padding when rewriting + if(makers.empty()) { + // 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(); - 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 // -> 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 makers.back().make(outputStream, padding); } else { - // no ID3v2 tags assigned -> just write padding + // just write padding for(; padding; --padding) { outputStream.put(0); } diff --git a/mediafileinfo.h b/mediafileinfo.h index b291cd8..8118929 100644 --- a/mediafileinfo.h +++ b/mediafileinfo.h @@ -26,6 +26,7 @@ class MatroskaTag; class AbstractTrack; class WaveAudioStream; class MpegAudioFrameStream; +class VorbisComment; enum class MediaType; enum class TagType : unsigned int; @@ -117,6 +118,7 @@ public: std::vector tags() const; Mp4Tag *mp4Tag() const; const std::vector > &matroskaTags() const; + VorbisComment *vorbisComment() const; bool areTagsSupported() const; // methods to create/remove tags diff --git a/mp4/mp4container.cpp b/mp4/mp4container.cpp index 47b6761..dbf8b79 100644 --- a/mp4/mp4container.cpp +++ b/mp4/mp4container.cpp @@ -771,7 +771,7 @@ calculatePadding: reset(); try { parseTracks(); - } catch(Failure &) { + } catch(const Failure &) { addNotification(NotificationType::Critical, "Unable to reparse the header of the new file.", context); throw; } @@ -931,11 +931,11 @@ void Mp4Container::updateOffsets(const std::vector &oldMdatOffsets, const 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); } } - } catch(Failure &) { + } catch(const Failure &) { addNotification(NotificationType::Critical, "Unable to parse top-level atom moof.", context); } // update each track @@ -946,7 +946,7 @@ void Mp4Container::updateOffsets(const std::vector &oldMdatOffsets, const if(!track->isHeaderValid()) { try { 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); throw; } @@ -954,7 +954,7 @@ void Mp4Container::updateOffsets(const std::vector &oldMdatOffsets, const if(track->isHeaderValid()) { try { track->updateChunkOffsets(oldMdatOffsets, newMdatOffsets); - } catch(Failure &) { + } catch(const Failure &) { addNotification(NotificationType::Warning, "The chunk offsets of track " + track->name() + " couldn't be updated.", context); throw; } diff --git a/mp4/mp4tagfield.cpp b/mp4/mp4tagfield.cpp index 5e2e669..3ceae9a 100644 --- a/mp4/mp4tagfield.cpp +++ b/mp4/mp4tagfield.cpp @@ -455,7 +455,7 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field) : // track number and disk number are exceptions // raw data type 0 is used, information is stored as pair of unsigned integers case Mp4TagAtomIds::TrackPosition: case Mp4TagAtomIds::DiskPosition: { - PositionInSet pos = m_field.value().toPositionIntSet(); + PositionInSet pos = m_field.value().toPositionInSet(); m_writer.writeInt32BE(pos.position()); if(pos.total() <= numeric_limits::max()) { m_writer.writeInt16BE(static_cast(pos.total())); diff --git a/ogg/oggcontainer.cpp b/ogg/oggcontainer.cpp index 00f30bc..39b6d57 100644 --- a/ogg/oggcontainer.cpp +++ b/ogg/oggcontainer.cpp @@ -280,12 +280,12 @@ void OggContainer::internalMakeFile() } // clear iterator 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); BackupHelper::restoreOriginalFileFromBackupFile(fileInfo().path(), backupPath, fileInfo().stream(), backupStream); m_iterator.setStream(fileInfo().stream()); 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(fileInfo().path(), backupPath, fileInfo().stream(), backupStream); m_iterator.setStream(fileInfo().stream()); diff --git a/ogg/oggiterator.h b/ogg/oggiterator.h index b07d281..1c814c7 100644 --- a/ogg/oggiterator.h +++ b/ogg/oggiterator.h @@ -39,10 +39,10 @@ public: bool bytesRemaining(size_t atLeast) const; operator bool() const; - OggIterator &operator ++(); - OggIterator operator ++(int); - OggIterator &operator --(); - OggIterator operator --(int); + OggIterator &operator++(); + OggIterator operator++(int); + OggIterator &operator--(); + OggIterator operator--(int); private: 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. */ -inline OggIterator &OggIterator::operator ++() +inline OggIterator &OggIterator::operator++() { nextSegment(); 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. */ -inline OggIterator OggIterator::operator ++(int) +inline OggIterator OggIterator::operator++(int) { OggIterator tmp = *this; 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. */ -inline OggIterator &OggIterator::operator --() +inline OggIterator &OggIterator::operator--() { previousSegment(); 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. */ -inline OggIterator OggIterator::operator --(int) +inline OggIterator OggIterator::operator--(int) { OggIterator tmp = *this; previousSegment(); diff --git a/positioninset.h b/positioninset.h index d4d3139..b6220f1 100644 --- a/positioninset.h +++ b/positioninset.h @@ -26,6 +26,7 @@ public: constexpr int32 position() const; constexpr int32 total() const; constexpr bool isNull() const; + constexpr bool operator==(const PositionInSet &other) const; template StringType toString() const; @@ -91,6 +92,14 @@ constexpr inline bool PositionInSet::isNull() const 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. */ diff --git a/tagvalue.cpp b/tagvalue.cpp index eedcf62..6a6204f 100644 --- a/tagvalue.cpp +++ b/tagvalue.cpp @@ -9,6 +9,7 @@ #include #include +#include using namespace std; using namespace ConversionUtilities; @@ -119,7 +120,7 @@ TagValue::TagValue(const PositionInSet &value) : TagValue::TagValue(const TagValue &other) : m_size(other.m_size), m_type(other.m_type), - m_dec(other.m_dec), + m_desc(other.m_desc), m_mimeType(other.m_mimeType), m_lng(other.m_lng), m_labeledAsReadonly(other.m_labeledAsReadonly), @@ -140,7 +141,7 @@ TagValue &TagValue::operator=(const TagValue &other) if(this != &other) { m_size = other.m_size; m_type = other.m_type; - m_dec = other.m_dec; + m_desc = other.m_desc; m_mimeType = other.m_mimeType; m_lng = other.m_lng; m_labeledAsReadonly = other.m_labeledAsReadonly; @@ -156,6 +157,45 @@ TagValue &TagValue::operator=(const TagValue &other) 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. */ @@ -171,7 +211,7 @@ TagValue::~TagValue() */ void TagValue::clearMetadata() { - m_dec.clear(); + m_desc.clear(); m_mimeType.clear(); m_lng.clear(); m_labeledAsReadonly = false; @@ -260,7 +300,7 @@ int TagValue::toStandardGenreIndex() const * PositionInSet representation. * \throws Throws ConversionException an failure. */ -PositionInSet TagValue::toPositionIntSet() const +PositionInSet TagValue::toPositionInSet() const { if(!isEmpty()) { switch(m_type) { @@ -293,7 +333,7 @@ TimeSpan TagValue::toTimeSpan() const if(!isEmpty()) { switch(m_type) { case TagDataType::Text: - return TimeSpan::fromSeconds(ConversionUtilities::stringToNumber(string(m_ptr.get(), m_size))); + return TimeSpan::fromString(string(m_ptr.get(), m_size)); case TagDataType::Integer: case TagDataType::TimeSpan: switch(m_size) { @@ -366,7 +406,7 @@ void TagValue::toString(string &result) const result = ConversionUtilities::numberToString(toInteger()); return; case TagDataType::PositionInSet: - result = toPositionIntSet().toString(); + result = toPositionInSet().toString(); return; case TagDataType::StandardGenreIndex: if(const char *genreName = Id3Genres::stringFromIndex(toInteger())) { @@ -386,6 +426,56 @@ void TagValue::toString(string &result) const 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(m_ptr.get()), m_size / sizeof(typename u16string::value_type)); + return; + case TagDataType::Integer: + result = ConversionUtilities::numberToString(toInteger()); + return; + case TagDataType::PositionInSet: + result = toPositionInSet().toString(); + 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. * \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) { - m_size = length; - m_type = type; - m_encoding = encoding; - if(m_size > 0) { - m_ptr.reset(new char[m_size]); + if(length > m_size) { + m_ptr = make_unique(length); + } + if(length) { std::copy(data, data + length, m_ptr.get()); } else { m_ptr.reset(); } + m_size = length; + m_type = type; + m_encoding = encoding; } /*! diff --git a/tagvalue.h b/tagvalue.h index 775d328..861a0a7 100644 --- a/tagvalue.h +++ b/tagvalue.h @@ -75,6 +75,7 @@ public: // operators TagValue &operator=(const TagValue &other); TagValue &operator=(TagValue &&other) = default; + bool operator==(const TagValue &other) const; // methods bool isEmpty() const; @@ -84,9 +85,11 @@ public: TagDataType type() const; std::string toString() const; void toString(std::string &result) const; + std::u16string toWString() const; + void toWString(std::u16string &result) const; int32 toInteger() const; int toStandardGenreIndex() const; - PositionInSet toPositionIntSet() const; + PositionInSet toPositionInSet() const; ChronoUtilities::TimeSpan toTimeSpan() const; ChronoUtilities::DateTime toDateTime() const; size_t dataSize() const; @@ -118,7 +121,7 @@ private: std::unique_ptr m_ptr; std::string::size_type m_size; TagDataType m_type; - std::string m_dec; + std::string m_desc; std::string m_mimeType; std::string m_lng; bool m_labeledAsReadonly; @@ -212,7 +215,7 @@ inline char *TagValue::dataPointer() 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) { - m_dec = value; + m_desc = value; m_descEncoding = encoding; }