8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/conversion/stringconversion.h>
40 return m_majorVersion > 3;
42 using namespace Id3v2FrameIds;
44 case sUserDefinedText:
45 case lUserDefinedText:
54 case sUnsynchronizedLyrics:
55 case lUnsynchronizedLyrics:
56 case sSynchronizedLyrics:
57 case lSynchronizedLyrics:
58 case lPublisherWebpage:
68 for (
auto &field :
fields()) {
69 auto &
value = field.second.value();
80 if (!field.value().isEmpty()) {
81 values.emplace_back(&field.value());
83 for (
const auto &
value : field.additionalValues()) {
104 auto range =
fields().equal_range(
id);
105 auto frameIterator = range.first;
108 auto valuesIterator =
values.cbegin();
109 if (frameIterator != range.second) {
112 if (valuesIterator !=
values.cend()) {
113 frameIterator->second.setValue(*valuesIterator);
116 frameIterator->second.value().clearDataAndMetadata();
120 if (valuesIterator ==
values.cend()) {
124 frameIterator =
fields().insert(make_pair(
id,
Id3v2Frame(
id, *valuesIterator)));
129 frameIterator->second.additionalValues() = vector<TagValue>(valuesIterator,
values.cend());
132 for (; range.first != range.second; ++range.first) {
133 range.first->second.setValue(
TagValue());
140 using namespace Id3v2FrameIds;
141 if (m_majorVersion >= 3) {
150 return lRecordingTime;
158 return lTrackPosition;
160 return lDiskPosition;
174 return lEncoderSettings;
176 return lUnsynchronizedLyrics;
178 return lSynchronizedLyrics;
180 return lContentGroupDescription;
198 return lEncodingTime;
200 return lOriginalReleaseTime;
202 return lOriginalMediaType;
208 return lPublisherWebpage;
210 return lPerformerWebpage;
212 return lPaymentWebpage;
224 return lRecordingTime;
230 return sTrackPosition;
232 return sDiskPosition;
246 return sEncoderSettings;
248 return sUnsynchronizedLyrics;
250 return sSynchronizedLyrics;
252 return sContentGroupDescription;
268 return sOriginalMediaType;
272 return sPublisherWebpage;
274 return sPerformerWebpage;
283 using namespace Id3v2FrameIds;
314 case lEncoderSettings:
316 case lUnsynchronizedLyrics:
318 case lSynchronizedLyrics:
326 case lContentGroupDescription:
336 case lOriginalReleaseTime:
338 case lOriginalMediaType:
352 case lPublisherWebpage:
354 case lPerformerWebpage:
356 case lPaymentWebpage:
386 case sEncoderSettings:
388 case sUnsynchronizedLyrics:
390 case sSynchronizedLyrics:
406 case sOriginalMediaType:
412 case sContentGroupDescription:
414 case sPublisherWebpage:
416 case sPerformerWebpage:
425 using namespace Id3v2FrameIds;
462void Id3v2Tag::convertOldRecordDateFields(
const std::string &diagContext,
Diagnostics &diag)
471 auto year = 1, month = 1, day = 1, hour = 0, minute = 0;
473 expr.parts |= DateTimeParts::Year;
475 year = v.toInteger();
476 }
catch (
const ConversionException &e) {
477 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
481 expr.parts |= DateTimeParts::Day | DateTimeParts::Month;
483 auto str = v.toString();
484 if (str.size() != 4) {
485 throw ConversionException(
"format is not DDMM");
487 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
488 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
489 }
catch (
const ConversionException &e) {
490 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
494 expr.parts |= DateTimeParts::Hour | DateTimeParts::Minute;
496 auto str = v.toString();
497 if (str.size() != 4) {
498 throw ConversionException(
"format is not HHMM");
500 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
501 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
502 }
catch (
const ConversionException &e) {
503 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
508 if (expr.parts == DateTimeParts::None) {
512 expr.value = DateTime::fromDateAndTime(year, month, day, hour, minute);
514 }
catch (
const ConversionException &e) {
517 expr.parts = DateTimeParts::Year;
518 expr.value = DateTime::fromDate(year);
522 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
524 }
catch (
const ConversionException &) {
527 DiagLevel::Critical, argsToString(
"Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
541 static const string context(
"parsing ID3v2 tag");
542 BinaryReader reader(&stream);
543 const auto startOffset =
static_cast<std::uint64_t
>(stream.tellg());
546 if (maximalSize && maximalSize < 10) {
547 diag.emplace_back(
DiagLevel::Critical,
"ID3v2 header is truncated (at least 10 bytes expected).", context);
552 if (reader.readUInt24BE() != 0x494433u) {
560 m_flags = reader.readByte();
561 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
562 m_size = 10 + m_sizeExcludingHeader;
563 if (m_sizeExcludingHeader == 0) {
570 diag.emplace_back(
DiagLevel::Critical,
"The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
576 if (maximalSize && maximalSize < 14) {
577 diag.emplace_back(
DiagLevel::Critical,
"Extended header denoted but not present.", context);
580 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
581 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
585 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
589 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
590 if (maximalSize && bytesRemaining > maximalSize) {
591 bytesRemaining =
static_cast<std::uint32_t
>(maximalSize);
596 auto pos =
static_cast<std::uint64_t
>(stream.tellg());
597 while (bytesRemaining) {
599 stream.seekg(
static_cast<streamoff
>(pos));
607 fields().emplace(frame.
id(), std::move(frame));
610 m_paddingSize = startOffset +
m_size - pos;
617 if (frame.
totalSize() <= bytesRemaining) {
621 pos += bytesRemaining;
627 convertOldRecordDateFields(context, diag);
634 if (maximalSize &&
m_size + 10 < maximalSize) {
636 stream.seekg(
static_cast<streamoff
>(startOffset + (
m_size += 10)));
637 if (reader.readUInt24LE() != 0x494433u) {
641 stream.seekg(7, ios_base::cur);
706 if (lhsLong != rhsLong) {
712 }
else if (!rhsLong) {
735 if (lhstextfield && !rhstextfield) {
738 if (!lhstextfield && rhstextfield) {
761void Id3v2Tag::removeOldRecordDateRelatedFields()
771void Id3v2Tag::prepareRecordDataForMaking(
const std::string &diagContext, Diagnostics &diag)
777 removeOldRecordDateRelatedFields();
785 if (recordingTimeFieldIterator ==
fields().cend()) {
789 const auto &recordingTime = recordingTimeFieldIterator->second.value();
790 if (recordingTime.isEmpty()) {
791 removeOldRecordDateRelatedFields();
796 const auto dateTimeExpr = recordingTime.toDateTimeExpression();
797 const auto &asDateTime = dateTimeExpr.value;
799 removeOldRecordDateRelatedFields();
802 if (dateTimeExpr.parts & DateTimeParts::Year) {
803 year << std::setfill(
'0') << std::setw(4) << asDateTime.year();
806 if (dateTimeExpr.parts & (DateTimeParts::Day | DateTimeParts::Month)) {
807 date << std::setfill(
'0') << std::setw(2) << asDateTime.day() << std::setfill(
'0') << std::setw(2) << asDateTime.month();
810 if (dateTimeExpr.parts & DateTimeParts::Time) {
811 time << std::setfill(
'0') << std::setw(2) << asDateTime.hour() << std::setfill(
'0') << std::setw(2) << asDateTime.minute();
814 if (dateTimeExpr.parts & (DateTimeParts::Second | DateTimeParts::SubSecond)) {
816 "The recording time field (TDRC) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
820 }
catch (
const ConversionException &e) {
823 argsToString(
"Unable to convert recording time field (TDRC) with the value \"", recordingTime.toString(),
824 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
826 }
catch (
const ConversionException &) {
828 argsToString(
"Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
840Id3v2TagMaker::Id3v2TagMaker(
Id3v2Tag &tag, Diagnostics &diag)
844 static const string context(
"making ID3v2 tag");
848 if (!tag.isVersionSupported()) {
850 throw VersionNotSupportedException();
854 tag.prepareRecordDataForMaking(context, diag);
858 m_maker.reserve(tag.fields().size());
859 for (
auto &pair : tag.fields()) {
861 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
862 m_framesSize += m_maker.back().requiredSize();
863 }
catch (
const Failure &) {
869 m_requiredSize = 10 + m_framesSize;
881 CPP_UTILITIES_UNUSED(diag)
883 BinaryWriter writer(&stream);
887 writer.writeUInt24BE(0x494433u);
892 writer.writeByte(m_tag.
flags() & 0xBF);
894 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
897 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
IdentifierType fieldId(KnownField value) 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.