4#include "../diagnostics.h"
5#include "../exceptions.h"
7#include <c++utilities/conversion/stringbuilder.h>
8#include <c++utilities/conversion/stringconversion.h>
47 return m_majorVersion > 3;
62 for (
auto &field :
fields()) {
63 auto &
value = field.second.value();
74 if (!field.value().isEmpty()) {
75 values.emplace_back(&field.value());
77 for (
const auto &
value : field.additionalValues()) {
98 auto range =
fields().equal_range(
id);
99 auto frameIterator = range.first;
102 auto valuesIterator =
values.cbegin();
103 if (frameIterator != range.second) {
106 if (valuesIterator !=
values.cend()) {
107 frameIterator->second.setValue(*valuesIterator);
110 frameIterator->second.value().clearDataAndMetadata();
114 if (valuesIterator ==
values.cend()) {
118 frameIterator =
fields().insert(make_pair(
id,
Id3v2Frame(
id, *valuesIterator)));
123 frameIterator->second.additionalValues() = vector<TagValue>(valuesIterator,
values.cend());
126 for (; range.first != range.second; ++range.first) {
127 range.first->second.setValue(
TagValue());
134 using namespace Id3v2FrameIds;
135 if (m_majorVersion >= 3) {
144 return lRecordingTime;
152 return lTrackPosition;
154 return lDiskPosition;
168 return lEncoderSettings;
170 return lUnsynchronizedLyrics;
172 return lSynchronizedLyrics;
174 return lContentGroupDescription;
192 return lEncodingTime;
194 return lOriginalReleaseTime;
208 return lRecordingTime;
214 return sTrackPosition;
216 return sDiskPosition;
230 return sEncoderSettings;
232 return sUnsynchronizedLyrics;
234 return sSynchronizedLyrics;
236 return sContentGroupDescription;
259 using namespace Id3v2FrameIds;
290 case lEncoderSettings:
292 case lUnsynchronizedLyrics:
294 case lSynchronizedLyrics:
302 case lContentGroupDescription:
310 case lOriginalReleaseTime:
346 case sEncoderSettings:
348 case sUnsynchronizedLyrics:
350 case sSynchronizedLyrics:
373 using namespace Id3v2FrameIds;
410void Id3v2Tag::convertOldRecordDateFields(
const std::string &diagContext,
Diagnostics &diag)
419 auto year = 1, month = 1, day = 1, hour = 0, minute = 0;
421 expr.parts |= DateTimeParts::Year;
423 year = v.toInteger();
424 }
catch (
const ConversionException &e) {
425 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
429 expr.parts |= DateTimeParts::Day | DateTimeParts::Month;
431 auto str = v.toString();
432 if (str.size() != 4) {
433 throw ConversionException(
"format is not DDMM");
435 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
436 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
437 }
catch (
const ConversionException &e) {
438 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
442 expr.parts |= DateTimeParts::Hour | DateTimeParts::Minute;
444 auto str = v.toString();
445 if (str.size() != 4) {
446 throw ConversionException(
"format is not HHMM");
448 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
449 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
450 }
catch (
const ConversionException &e) {
451 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
456 if (expr.parts == DateTimeParts::None) {
460 expr.value = DateTime::fromDateAndTime(year, month, day, hour, minute);
462 }
catch (
const ConversionException &e) {
465 expr.parts = DateTimeParts::Year;
466 expr.value = DateTime::fromDate(year);
470 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
472 }
catch (
const ConversionException &) {
475 DiagLevel::Critical, argsToString(
"Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
489 static const string context(
"parsing ID3v2 tag");
490 BinaryReader reader(&stream);
491 const auto startOffset =
static_cast<std::uint64_t
>(stream.tellg());
494 if (maximalSize && maximalSize < 10) {
495 diag.emplace_back(
DiagLevel::Critical,
"ID3v2 header is truncated (at least 10 bytes expected).", context);
500 if (reader.readUInt24BE() != 0x494433u) {
508 m_flags = reader.readByte();
509 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
510 m_size = 10 + m_sizeExcludingHeader;
511 if (m_sizeExcludingHeader == 0) {
518 diag.emplace_back(
DiagLevel::Critical,
"The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
524 if (maximalSize && maximalSize < 14) {
525 diag.emplace_back(
DiagLevel::Critical,
"Extended header denoted but not present.", context);
528 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
529 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
533 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
537 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
538 if (maximalSize && bytesRemaining > maximalSize) {
539 bytesRemaining =
static_cast<std::uint32_t
>(maximalSize);
544 auto pos =
static_cast<std::uint64_t
>(stream.tellg());
545 while (bytesRemaining) {
547 stream.seekg(
static_cast<streamoff
>(pos));
555 fields().emplace(frame.
id(), std::move(frame));
558 m_paddingSize = startOffset +
m_size - pos;
565 if (frame.
totalSize() <= bytesRemaining) {
569 pos += bytesRemaining;
575 convertOldRecordDateFields(context, diag);
582 if (maximalSize &&
m_size + 10 < maximalSize) {
584 stream.seekg(
static_cast<streamoff
>(startOffset + (
m_size += 10)));
585 if (reader.readUInt24LE() != 0x494433u) {
589 stream.seekg(7, ios_base::cur);
654 if (lhsLong != rhsLong) {
660 }
else if (!rhsLong) {
683 if (lhstextfield && !rhstextfield) {
686 if (!lhstextfield && rhstextfield) {
709void Id3v2Tag::removeOldRecordDateRelatedFields()
719void Id3v2Tag::prepareRecordDataForMaking(
const std::string &diagContext, Diagnostics &diag)
725 removeOldRecordDateRelatedFields();
733 if (recordingTimeFieldIterator ==
fields().cend()) {
737 const auto &recordingTime = recordingTimeFieldIterator->second.value();
738 if (recordingTime.isEmpty()) {
739 removeOldRecordDateRelatedFields();
744 const auto dateTimeExpr = recordingTime.toDateTimeExpression();
745 const auto &asDateTime = dateTimeExpr.value;
747 removeOldRecordDateRelatedFields();
750 if (dateTimeExpr.parts & DateTimeParts::Year) {
751 year << std::setfill(
'0') << std::setw(4) << asDateTime.year();
754 if (dateTimeExpr.parts & (DateTimeParts::Day | DateTimeParts::Month)) {
755 date << std::setfill(
'0') << std::setw(2) << asDateTime.day() << std::setfill(
'0') << std::setw(2) << asDateTime.month();
758 if (dateTimeExpr.parts & DateTimeParts::Time) {
759 time << std::setfill(
'0') << std::setw(2) << asDateTime.hour() << std::setfill(
'0') << std::setw(2) << asDateTime.minute();
762 if (dateTimeExpr.parts & (DateTimeParts::Second | DateTimeParts::SubSecond)) {
764 "The recording time field (TDRC) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
768 }
catch (
const ConversionException &e) {
771 argsToString(
"Unable to convert recording time field (TDRC) with the value \"", recordingTime.toString(),
772 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
774 }
catch (
const ConversionException &) {
776 argsToString(
"Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
788Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
792 static const string context(
"making ID3v2 tag");
796 if (!tag.isVersionSupported()) {
798 throw VersionNotSupportedException();
802 tag.prepareRecordDataForMaking(context, diag);
806 m_maker.reserve(tag.fields().size());
807 for (
auto &pair : tag.fields()) {
809 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
810 m_framesSize += m_maker.back().requiredSize();
811 }
catch (
const Failure &) {
817 m_requiredSize = 10 + m_framesSize;
829 CPP_UTILITIES_UNUSED(diag)
831 BinaryWriter writer(&stream);
835 writer.writeUInt24BE(0x494433u);
840 writer.writeByte(m_tag.
flags() & 0xBF);
842 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
845 for (
auto &maker : m_maker) {
850 for (; padding; --padding) {
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)
Assigns the given value to the field with the specified id.
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType::IdentifierType IdentifierType
const TagValue & value(const IdentifierType &id) const
Returns the value of the field with the specified id.
const std::multimap< IdentifierType, FieldType, Compare > & fields() const
Returns the fields of the tag by providing direct access to the field map of the tag.
bool internallySetValues(const IdentifierType &id, const std::vector< TagValue > &values)
Default implementation for setValues().
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType FieldType
std::vector< const TagValue * > values(const IdentifierType &id) const
Returns the values of the field with the specified id.
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.