8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/conversion/stringconversion.h>
48 return m_majorVersion > 3;
63 for (
auto &field :
fields()) {
64 auto &
value = field.second.value();
75 if (!field.value().isEmpty()) {
76 values.emplace_back(&field.value());
78 for (
const auto &
value : field.additionalValues()) {
99 auto range =
fields().equal_range(
id);
100 auto frameIterator = range.first;
103 auto valuesIterator =
values.cbegin();
104 if (frameIterator != range.second) {
107 if (valuesIterator !=
values.cend()) {
108 frameIterator->second.setValue(*valuesIterator);
111 frameIterator->second.value().clearDataAndMetadata();
115 if (valuesIterator ==
values.cend()) {
119 frameIterator =
fields().insert(make_pair(
id,
Id3v2Frame(
id, *valuesIterator)));
124 frameIterator->second.additionalValues() = vector<TagValue>(valuesIterator,
values.cend());
127 for (; range.first != range.second; ++range.first) {
128 range.first->second.setValue(
TagValue());
135 using namespace Id3v2FrameIds;
136 if (m_majorVersion >= 3) {
145 return lRecordingTime;
153 return lTrackPosition;
155 return lDiskPosition;
169 return lEncoderSettings;
171 return lUnsynchronizedLyrics;
173 return lSynchronizedLyrics;
175 return lContentGroupDescription;
193 return lEncodingTime;
195 return lOriginalReleaseTime;
209 return lRecordingTime;
215 return sTrackPosition;
217 return sDiskPosition;
231 return sEncoderSettings;
233 return sUnsynchronizedLyrics;
235 return sSynchronizedLyrics;
237 return sContentGroupDescription;
260 using namespace Id3v2FrameIds;
291 case lEncoderSettings:
293 case lUnsynchronizedLyrics:
295 case lSynchronizedLyrics:
303 case lContentGroupDescription:
311 case lOriginalReleaseTime:
347 case sEncoderSettings:
349 case sUnsynchronizedLyrics:
351 case sSynchronizedLyrics:
374 using namespace Id3v2FrameIds;
411void Id3v2Tag::convertOldRecordDateFields(
const std::string &diagContext,
Diagnostics &diag)
420 auto year = 1, month = 1, day = 1, hour = 0, minute = 0;
422 expr.parts |= DateTimeParts::Year;
424 year = v.toInteger();
425 }
catch (
const ConversionException &e) {
426 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
430 expr.parts |= DateTimeParts::Day | DateTimeParts::Month;
432 auto str = v.toString();
433 if (str.size() != 4) {
434 throw ConversionException(
"format is not DDMM");
436 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
437 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
438 }
catch (
const ConversionException &e) {
439 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
443 expr.parts |= DateTimeParts::Hour | DateTimeParts::Minute;
445 auto str = v.toString();
446 if (str.size() != 4) {
447 throw ConversionException(
"format is not HHMM");
449 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
450 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
451 }
catch (
const ConversionException &e) {
452 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
457 if (expr.parts == DateTimeParts::None) {
461 expr.value = DateTime::fromDateAndTime(year, month, day, hour, minute);
463 }
catch (
const ConversionException &e) {
466 expr.parts = DateTimeParts::Year;
467 expr.value = DateTime::fromDate(year);
471 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
473 }
catch (
const ConversionException &) {
476 DiagLevel::Critical, argsToString(
"Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
490 static const string context(
"parsing ID3v2 tag");
491 BinaryReader reader(&stream);
492 const auto startOffset =
static_cast<std::uint64_t
>(stream.tellg());
495 if (maximalSize && maximalSize < 10) {
496 diag.emplace_back(
DiagLevel::Critical,
"ID3v2 header is truncated (at least 10 bytes expected).", context);
501 if (reader.readUInt24BE() != 0x494433u) {
509 m_flags = reader.readByte();
510 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
511 m_size = 10 + m_sizeExcludingHeader;
512 if (m_sizeExcludingHeader == 0) {
519 diag.emplace_back(
DiagLevel::Critical,
"The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
525 if (maximalSize && maximalSize < 14) {
526 diag.emplace_back(
DiagLevel::Critical,
"Extended header denoted but not present.", context);
529 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
530 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
534 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
538 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
539 if (maximalSize && bytesRemaining > maximalSize) {
540 bytesRemaining =
static_cast<std::uint32_t
>(maximalSize);
545 auto pos =
static_cast<std::uint64_t
>(stream.tellg());
546 while (bytesRemaining) {
548 stream.seekg(
static_cast<streamoff
>(pos));
556 fields().emplace(frame.
id(), std::move(frame));
559 m_paddingSize = startOffset +
m_size - pos;
566 if (frame.
totalSize() <= bytesRemaining) {
570 pos += bytesRemaining;
576 convertOldRecordDateFields(context, diag);
583 if (maximalSize &&
m_size + 10 < maximalSize) {
585 stream.seekg(
static_cast<streamoff
>(startOffset + (
m_size += 10)));
586 if (reader.readUInt24LE() != 0x494433u) {
590 stream.seekg(7, ios_base::cur);
655 if (lhsLong != rhsLong) {
661 }
else if (!rhsLong) {
684 if (lhstextfield && !rhstextfield) {
687 if (!lhstextfield && rhstextfield) {
710void Id3v2Tag::removeOldRecordDateRelatedFields()
720void Id3v2Tag::prepareRecordDataForMaking(
const std::string &diagContext, Diagnostics &diag)
726 removeOldRecordDateRelatedFields();
734 if (recordingTimeFieldIterator ==
fields().cend()) {
738 const auto &recordingTime = recordingTimeFieldIterator->second.value();
739 if (recordingTime.isEmpty()) {
740 removeOldRecordDateRelatedFields();
745 const auto dateTimeExpr = recordingTime.toDateTimeExpression();
746 const auto &asDateTime = dateTimeExpr.value;
748 removeOldRecordDateRelatedFields();
751 if (dateTimeExpr.parts & DateTimeParts::Year) {
752 year << std::setfill(
'0') << std::setw(4) << asDateTime.year();
755 if (dateTimeExpr.parts & (DateTimeParts::Day | DateTimeParts::Month)) {
756 date << std::setfill(
'0') << std::setw(2) << asDateTime.day() << std::setfill(
'0') << std::setw(2) << asDateTime.month();
759 if (dateTimeExpr.parts & DateTimeParts::Time) {
760 time << std::setfill(
'0') << std::setw(2) << asDateTime.hour() << std::setfill(
'0') << std::setw(2) << asDateTime.minute();
763 if (dateTimeExpr.parts & (DateTimeParts::Second | DateTimeParts::SubSecond)) {
765 "The recording time field (TDRC) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
769 }
catch (
const ConversionException &e) {
772 argsToString(
"Unable to convert recording time field (TDRC) with the value \"", recordingTime.toString(),
773 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
775 }
catch (
const ConversionException &) {
777 argsToString(
"Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
789Id3v2TagMaker::Id3v2TagMaker(
Id3v2Tag &tag, Diagnostics &diag)
793 static const string context(
"making ID3v2 tag");
797 if (!tag.isVersionSupported()) {
799 throw VersionNotSupportedException();
803 tag.prepareRecordDataForMaking(context, diag);
807 m_maker.reserve(tag.fields().size());
808 for (
auto &pair : tag.fields()) {
810 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
811 m_framesSize += m_maker.back().requiredSize();
812 }
catch (
const Failure &) {
818 m_requiredSize = 10 + m_framesSize;
830 CPP_UTILITIES_UNUSED(diag)
832 BinaryWriter writer(&stream);
836 writer.writeUInt24BE(0x494433u);
841 writer.writeByte(m_tag.
flags() & 0xBF);
843 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
846 for (
auto &maker : m_maker) {
The Diagnostics class is a container for DiagMessage.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
bool setValue(const IdentifierType &id, const TagValue &value)
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType::IdentifierType IdentifierType
const TagValue & value(const IdentifierType &id) const
const std::multimap< IdentifierType, FieldType, Compare > & fields() const
bool internallySetValues(const IdentifierType &id, const std::vector< TagValue > &values)
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType FieldType
std::vector< const TagValue * > values(const IdentifierType &id) const
The Id3v2Frame class is used by Id3v2Tag to store the fields.
void parse(CppUtilities::BinaryReader &reader, std::uint32_t version, std::uint32_t maximalSize, Diagnostics &diag)
Parses a frame from the stream read using the specified reader.
std::uint32_t totalSize() const
Returns the total size of the frame in bytes.
bool hasPaddingReached() const
Returns whether the padding has reached.
The Id3v2TagMaker class helps writing ID3v2 tags.
void make(std::ostream &stream, std::uint32_t padding, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
std::uint8_t revisionVersion() const
Returns the revision version if known; otherwise returns 0.
void make(std::ostream &targetStream, std::uint32_t padding, Diagnostics &diag)
Writes tag information to the specified stream.
bool hasExtendedHeader() const
Returns an indication whether an extended header is used.
bool isVersionSupported() const
Returns an indication whether the version is supported by the Id3v2Tag class.
bool supportsMultipleValues(KnownField field) const override
Allows multiple values for some fields.
void setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
Sets the version to the specified majorVersion and the specified revisionVersion.
KnownField internallyGetKnownField(const IdentifierType &id) const
std::uint8_t flags() const
Returns the flags read from the ID3v2 header.
std::uint8_t majorVersion() const
Returns the major version if known; otherwise returns 0.
bool hasFooter() const
Returns an indication whether a footer is present.
void internallyGetValuesFromField(const FieldType &field, std::vector< const TagValue * > &values) const
Adds additional values as well.
bool internallySetValues(const IdentifierType &id, const std::vector< TagValue > &values)
Uses default implementation for non-text frames and applies special handling to text frames.
TagTextEncoding proposedTextEncoding() const override
Returns the proposed text encoding.
TagDataType internallyGetProposedDataType(const std::uint32_t &id) const
Id3v2TagMaker prepareMaking(Diagnostics &diag)
Prepares making.
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
IdentifierType internallyGetFieldId(KnownField field) const
friend class Id3v2TagMaker
void parse(std::istream &sourceStream, const std::uint64_t maximalSize, Diagnostics &diag)
Parses tag information from the specified stream.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
The exception that is thrown when the data to be parsed holds no parsable information (e....
IdentifierType & id()
Returns the id of the current TagField.
std::string idToString() const
Returns the id of the current TagField as string.
The TagValue class wraps values of different types.
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
bool isEmpty() const
Returns whether no or an empty value is assigned.
void convertDescriptionEncoding(TagTextEncoding encoding)
Converts the assigned description to use the specified encoding.
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
The exception that is thrown when an operation fails because the detected or specified version is not...
constexpr bool isLongId(std::uint32_t id)
Returns an indication whether the specified id is a long frame id.
constexpr bool isTextFrame(std::uint32_t id)
Returns an indication whether the specified id is a text frame id.
TAG_PARSER_EXPORT std::uint32_t convertToLongId(std::uint32_t id)
Converts the specified short frame ID to the equivalent long frame ID.
Contains all classes and functions of the TagInfo library.
KnownField
Specifies the field.
@ ConvertRecordDateFields
TagDataType
Specifies the data type.
bool operator()(std::uint32_t lhs, std::uint32_t rhs) const
Returns true if lhs goes before rhs; otherwise returns false.