4 #include "../diagnostics.h"
5 #include "../exceptions.h"
7 #include <c++utilities/conversion/stringbuilder.h>
8 #include <c++utilities/conversion/stringconversion.h>
26 bool Id3v2Tag::supportsMultipleValues(
KnownField field)
const
31 case KnownField::RecordDate:
32 case KnownField::ReleaseDate:
40 case KnownField::Length:
41 case KnownField::Language:
42 case KnownField::EncoderSettings:
47 return m_majorVersion > 3;
52 case KnownField::SynchronizedLyrics:
59 void Id3v2Tag::ensureTextValuesAreProperlyEncoded()
61 const auto encoding = proposedTextEncoding();
62 for (
auto &field : fields()) {
63 auto &value = field.second.value();
64 value.convertDataEncoding(encoding);
65 value.convertDescriptionEncoding(encoding);
72 void Id3v2Tag::internallyGetValuesFromField(
const Id3v2Tag::FieldType &field, std::vector<const TagValue *> &values)
const
74 if (!field.value().isEmpty()) {
75 values.emplace_back(&field.value());
77 for (
const auto &value : field.additionalValues()) {
78 if (!value.isEmpty()) {
79 values.emplace_back(&value);
90 bool Id3v2Tag::internallySetValues(
const IdentifierType &
id,
const std::vector<TagValue> &values)
94 return CRTPBase::internallySetValues(
id, values);
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) {
143 case KnownField::RecordDate:
145 case KnownField::ReleaseDate:
163 case KnownField::Length:
165 case KnownField::Language:
167 case KnownField::EncoderSettings:
171 case KnownField::SynchronizedLyrics:
193 case KnownField::RecordDate:
211 case KnownField::Length:
213 case KnownField::Language:
215 case KnownField::EncoderSettings:
219 case KnownField::SynchronizedLyrics:
237 KnownField Id3v2Tag::internallyGetKnownField(
const IdentifierType &
id)
const
239 using namespace Id3v2FrameIds;
249 return KnownField::RecordDate;
267 return KnownField::Language;
269 return KnownField::Length;
271 return KnownField::EncoderSettings;
275 return KnownField::SynchronizedLyrics;
289 return KnownField::RecordDate;
305 return KnownField::Language;
307 return KnownField::Length;
309 return KnownField::EncoderSettings;
313 return KnownField::SynchronizedLyrics;
319 return KnownField::Invalid;
323 TagDataType Id3v2Tag::internallyGetProposedDataType(
const std::uint32_t &
id)
const
325 using namespace Id3v2FrameIds;
329 return TagDataType::TimeSpan;
334 return TagDataType::Integer;
338 return TagDataType::PositionInSet;
341 return TagDataType::Picture;
344 return TagDataType::Text;
357 void Id3v2Tag::convertOldRecordDateFields(
const std::string &diagContext,
Diagnostics &diag)
365 bool hasAnyValue =
false;
366 int year = 1, month = 1, day = 1, hour = 0, minute = 0;
370 year = v.toInteger();
371 }
catch (
const ConversionException &e) {
372 diag.emplace_back(DiagLevel::Critical, argsToString(
"Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
378 auto str = v.toString();
379 if (str.size() != 4) {
380 throw ConversionException(
"format is not DDMM");
382 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
383 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
384 }
catch (
const ConversionException &e) {
385 diag.emplace_back(DiagLevel::Critical, argsToString(
"Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
391 auto str = v.toString();
392 if (str.size() != 4) {
393 throw ConversionException(
"format is not HHMM");
395 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
396 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
397 }
catch (
const ConversionException &e) {
398 diag.emplace_back(DiagLevel::Critical, argsToString(
"Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
408 }
catch (
const ConversionException &e) {
412 diag.emplace_back(DiagLevel::Critical,
414 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
416 }
catch (
const ConversionException &) {
419 DiagLevel::Critical, argsToString(
"Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
430 void Id3v2Tag::parse(istream &stream,
const std::uint64_t maximalSize,
Diagnostics &diag)
433 static const string context(
"parsing ID3v2 tag");
434 BinaryReader reader(&stream);
435 const auto startOffset =
static_cast<std::uint64_t
>(stream.tellg());
438 if (maximalSize && maximalSize < 10) {
439 diag.emplace_back(DiagLevel::Critical,
"ID3v2 header is truncated (at least 10 bytes expected).", context);
444 if (reader.readUInt24BE() != 0x494433u) {
445 diag.emplace_back(DiagLevel::Critical,
"Signature is invalid.", context);
449 const std::uint8_t majorVersion = reader.readByte();
450 const std::uint8_t revisionVersion = reader.readByte();
451 setVersion(majorVersion, revisionVersion);
452 m_flags = reader.readByte();
453 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
454 m_size = 10 + m_sizeExcludingHeader;
455 if (m_sizeExcludingHeader == 0) {
456 diag.emplace_back(DiagLevel::Warning,
"ID3v2 tag seems to be empty.", context);
461 if (!isVersionSupported()) {
462 diag.emplace_back(DiagLevel::Critical,
"The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
467 if (hasExtendedHeader()) {
468 if (maximalSize && maximalSize < 14) {
469 diag.emplace_back(DiagLevel::Critical,
"Extended header denoted but not present.", context);
472 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
473 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
474 diag.emplace_back(DiagLevel::Critical,
"Extended header is invalid/truncated.", context);
477 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
481 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
482 if (maximalSize && bytesRemaining > maximalSize) {
483 bytesRemaining =
static_cast<std::uint32_t
>(maximalSize);
484 diag.emplace_back(DiagLevel::Critical,
"Frames are truncated.", context);
488 auto pos =
static_cast<std::uint64_t
>(stream.tellg());
489 while (bytesRemaining) {
491 stream.seekg(
static_cast<streamoff
>(pos));
495 frame.
parse(reader, majorVersion, bytesRemaining, diag);
497 diag.emplace_back(DiagLevel::Warning,
"The text frame " % frame.
idToString() +
" exists more than once.", context);
499 fields().emplace(frame.
id(), move(frame));
502 m_paddingSize = startOffset + m_size - pos;
509 if (frame.
totalSize() <= bytesRemaining) {
513 pos += bytesRemaining;
518 if (m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
519 convertOldRecordDateFields(context, diag);
526 if (maximalSize && m_size + 10 < maximalSize) {
528 stream.seekg(
static_cast<streamoff
>(startOffset + (m_size += 10)));
529 if (reader.readUInt24LE() != 0x494433u) {
530 diag.emplace_back(DiagLevel::Critical,
"Footer signature is invalid.", context);
533 stream.seekg(7, ios_base::cur);
535 diag.emplace_back(DiagLevel::Critical,
"Footer denoted but not present.", context);
562 void Id3v2Tag::make(ostream &stream, std::uint32_t padding,
Diagnostics &diag)
564 prepareMaking(diag).make(stream, padding, diag);
571 void Id3v2Tag::setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
573 m_majorVersion = majorVersion;
574 m_revisionVersion = revisionVersion;
575 m_version = argsToString(
'2',
'.', majorVersion,
'.', revisionVersion);
590 bool FrameComparer::operator()(std::uint32_t lhs, std::uint32_t rhs)
const
598 if (lhsLong != rhsLong) {
604 }
else if (!rhsLong) {
627 if (lhstextfield && !rhstextfield) {
630 if (!lhstextfield && rhstextfield) {
653 void Id3v2Tag::removeOldRecordDateRelatedFields()
656 fields().erase(field);
663 void Id3v2Tag::prepareRecordDataForMaking(
const std::string &diagContext, Diagnostics &diag)
668 if (majorVersion() >= 4) {
669 removeOldRecordDateRelatedFields();
677 if (recordingTimeFieldIterator == fields().cend()) {
681 const auto &recordingTime = recordingTimeFieldIterator->second.value();
682 if (recordingTime.isEmpty()) {
683 removeOldRecordDateRelatedFields();
688 const auto asDateTime = recordingTime.toDateTime();
690 removeOldRecordDateRelatedFields();
693 year << std::setfill(
'0') << std::setw(4) << asDateTime.year();
695 date << std::setfill(
'0') << std::setw(2) << asDateTime.day() << std::setfill(
'0') << std::setw(2) << asDateTime.month();
697 time << std::setfill(
'0') << std::setw(2) << asDateTime.hour() << std::setfill(
'0') << std::setw(2) << asDateTime.minute();
699 if (asDateTime.second() || asDateTime.millisecond()) {
700 diag.emplace_back(DiagLevel::Warning,
701 "The recording time field (TRDA) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
705 }
catch (
const ConversionException &e) {
707 diag.emplace_back(DiagLevel::Critical,
708 argsToString(
"Unable to convert recording time field (TRDA) with the value \"", recordingTime.toString(),
709 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
711 }
catch (
const ConversionException &) {
712 diag.emplace_back(DiagLevel::Critical,
713 argsToString(
"Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
725 Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
729 static const string context(
"making ID3v2 tag");
733 if (!tag.isVersionSupported()) {
735 throw VersionNotSupportedException();
739 tag.prepareRecordDataForMaking(context, diag);
743 m_maker.reserve(tag.fields().size());
744 for (
auto &pair : tag.fields()) {
746 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
747 m_framesSize += m_maker.back().requiredSize();
748 }
catch (
const Failure &) {
754 m_requiredSize = 10 + m_framesSize;
766 CPP_UTILITIES_UNUSED(diag)
768 BinaryWriter writer(&stream);
772 writer.writeUInt24BE(0x494433u);
777 writer.writeByte(m_tag.
flags() & 0xBF);
779 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
782 for (
auto &maker : m_maker) {
787 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...
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType::IdentifierType IdentifierType
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.
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.
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....
const IdentifierType & id() const
Returns the id of the current TagField.
std::string idToString() const
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...
constexpr bool isLongId(std::uint32_t id)
Returns an indication whether the specified id is a long frame id.
@ lContentGroupDescription
@ sContentGroupDescription
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.