8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/conversion/stringconversion.h>
40 return m_majorVersion > 3;
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();
70 value.convertDataEncoding(encoding);
71 value.convertDescriptionEncoding(encoding);
80 if (!field.value().isEmpty()) {
81 values.emplace_back(&field.value());
83 for (
const auto &
value : field.additionalValues()) {
84 if (!
value.isEmpty()) {
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());
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;
214 return lSubtitleOrDescriptionRefinement;
216 return lLongDescription;
228 return lRecordingTime;
234 return sTrackPosition;
236 return sDiskPosition;
250 return sEncoderSettings;
252 return sUnsynchronizedLyrics;
254 return sSynchronizedLyrics;
256 return sContentGroupDescription;
272 return sOriginalMediaType;
276 return sPublisherWebpage;
278 return sPerformerWebpage;
280 return sSubtitleOrDescriptionRefinement;
320 case lEncoderSettings:
322 case lUnsynchronizedLyrics:
324 case lSynchronizedLyrics:
332 case lContentGroupDescription:
342 case lOriginalReleaseTime:
344 case lOriginalMediaType:
358 case lPublisherWebpage:
360 case lPerformerWebpage:
362 case lPaymentWebpage:
364 case lSubtitleOrDescriptionRefinement:
366 case lLongDescription:
396 case sEncoderSettings:
398 case sUnsynchronizedLyrics:
400 case sSynchronizedLyrics:
416 case sOriginalMediaType:
422 case sContentGroupDescription:
424 case sPublisherWebpage:
426 case sPerformerWebpage:
428 case sSubtitleOrDescriptionRefinement:
474void Id3v2Tag::convertOldRecordDateFields(
const std::string &diagContext,
Diagnostics &diag)
483 auto year = 1, month = 1, day = 1, hour = 0, minute = 0;
485 expr.parts |= DateTimeParts::Year;
487 year = v.toInteger();
488 }
catch (
const ConversionException &e) {
489 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
493 expr.parts |= DateTimeParts::Day | DateTimeParts::Month;
495 auto str = v.toString();
496 if (str.size() != 4) {
497 throw ConversionException(
"format is not DDMM");
499 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
500 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
501 }
catch (
const ConversionException &e) {
502 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
506 expr.parts |= DateTimeParts::Hour | DateTimeParts::Minute;
508 auto str = v.toString();
509 if (str.size() != 4) {
510 throw ConversionException(
"format is not HHMM");
512 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
513 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
514 }
catch (
const ConversionException &e) {
515 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
520 if (expr.parts == DateTimeParts::None) {
524 expr.value = DateTime::fromDateAndTime(year, month, day, hour, minute);
526 }
catch (
const ConversionException &e) {
529 expr.parts = DateTimeParts::Year;
530 expr.value = DateTime::fromDate(year);
534 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
536 }
catch (
const ConversionException &) {
539 DiagLevel::Critical, argsToString(
"Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
553 static const string context(
"parsing ID3v2 tag");
554 BinaryReader reader(&stream);
555 const auto startOffset =
static_cast<std::uint64_t
>(stream.tellg());
558 if (maximalSize && maximalSize < 10) {
559 diag.emplace_back(
DiagLevel::Critical,
"ID3v2 header is truncated (at least 10 bytes expected).", context);
564 if (reader.readUInt24BE() != 0x494433u) {
572 m_flags = reader.readByte();
573 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
574 m_size = 10 + m_sizeExcludingHeader;
575 if (m_sizeExcludingHeader == 0) {
582 diag.emplace_back(
DiagLevel::Critical,
"The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
588 if (maximalSize && maximalSize < 14) {
589 diag.emplace_back(
DiagLevel::Critical,
"Extended header denoted but not present.", context);
592 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
593 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
597 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
601 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
602 if (maximalSize && bytesRemaining > maximalSize) {
603 bytesRemaining =
static_cast<std::uint32_t
>(maximalSize);
608 auto pos =
static_cast<std::uint64_t
>(stream.tellg());
609 while (bytesRemaining) {
611 stream.seekg(
static_cast<streamoff
>(pos));
619 fields().emplace(frame.
id(), std::move(frame));
622 m_paddingSize = startOffset +
m_size - pos;
629 if (frame.
totalSize() <= bytesRemaining) {
633 pos += bytesRemaining;
639 convertOldRecordDateFields(context, diag);
646 if (maximalSize &&
m_size + 10 < maximalSize) {
648 stream.seekg(
static_cast<streamoff
>(startOffset + (
m_size += 10)));
649 if (reader.readUInt24LE() != 0x494433u) {
653 stream.seekg(7, ios_base::cur);
718 if (lhsLong != rhsLong) {
724 }
else if (!rhsLong) {
747 if (lhstextfield && !rhstextfield) {
750 if (!lhstextfield && rhstextfield) {
773void Id3v2Tag::removeOldRecordDateRelatedFields()
783void Id3v2Tag::prepareRecordDataForMaking(
const std::string &diagContext,
Diagnostics &diag)
789 removeOldRecordDateRelatedFields();
797 if (recordingTimeFieldIterator ==
fields().cend()) {
801 const auto &recordingTime = recordingTimeFieldIterator->second.value();
802 if (recordingTime.isEmpty()) {
803 removeOldRecordDateRelatedFields();
808 const auto dateTimeExpr = recordingTime.toDateTimeExpression();
809 const auto &asDateTime = dateTimeExpr.value;
811 removeOldRecordDateRelatedFields();
814 if (dateTimeExpr.parts & DateTimeParts::Year) {
815 year << std::setfill(
'0') << std::setw(4) << asDateTime.year();
818 if (dateTimeExpr.parts & (DateTimeParts::Day | DateTimeParts::Month)) {
819 date << std::setfill(
'0') << std::setw(2) << asDateTime.day() << std::setfill(
'0') << std::setw(2) << asDateTime.month();
822 if (dateTimeExpr.parts & DateTimeParts::Time) {
823 time << std::setfill(
'0') << std::setw(2) << asDateTime.hour() << std::setfill(
'0') << std::setw(2) << asDateTime.minute();
826 if (dateTimeExpr.parts & (DateTimeParts::Second | DateTimeParts::SubSecond)) {
828 "The recording time field (TDRC) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
832 }
catch (
const ConversionException &e) {
835 argsToString(
"Unable to convert recording time field (TDRC) with the value \"", recordingTime.toString(),
836 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
838 }
catch (
const ConversionException &) {
840 argsToString(
"Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
856 static const string context(
"making ID3v2 tag");
860 if (!tag.isVersionSupported()) {
862 throw VersionNotSupportedException();
866 tag.prepareRecordDataForMaking(context, diag);
870 m_maker.reserve(tag.fields().size());
871 for (
auto &pair : tag.fields()) {
873 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
874 m_framesSize += m_maker.back().requiredSize();
875 }
catch (
const Failure &) {
881 m_requiredSize = 10 + m_framesSize;
893 CPP_UTILITIES_UNUSED(diag)
895 BinaryWriter writer(&stream);
899 writer.writeUInt24BE(0x494433u);
901 writer.writeByte(m_tag.majorVersion());
902 writer.writeByte(m_tag.revisionVersion());
904 writer.writeByte(m_tag.flags() & 0xBF);
906 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
909 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.
void make(std::ostream &stream, std::uint32_t padding, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
Implementation of TagParser::Tag for ID3v2 tags.
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 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.
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...
Encapsulates the most common ID3v2 frame IDs and related functions.
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.