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
32 case KnownField::RecordDate:
33 case KnownField::ReleaseDate:
41 case KnownField::Length:
42 case KnownField::Language:
43 case KnownField::EncoderSettings:
48 return m_majorVersion > 3;
53 case KnownField::SynchronizedLyrics:
60 void Id3v2Tag::ensureTextValuesAreProperlyEncoded()
62 const auto encoding = proposedTextEncoding();
63 for (
auto &field : fields()) {
64 auto &value = field.second.value();
65 value.convertDataEncoding(encoding);
66 value.convertDescriptionEncoding(encoding);
73 std::vector<const TagValue *> Id3v2Tag::internallyGetValues(
const IdentifierType &
id)
const
75 auto range = fields().equal_range(
id);
76 std::vector<const TagValue *> values;
77 for (
auto i = range.first; i != range.second; ++i) {
78 const auto &frame(i->second);
79 if (!frame.value().isEmpty()) {
80 values.push_back(&frame.value());
82 for (
const auto &value : frame.additionalValues()) {
83 values.push_back(&value);
95 bool Id3v2Tag::internallySetValues(
const IdentifierType &
id,
const std::vector<TagValue> &values)
99 return CRTPBase::internallySetValues(
id, values);
103 auto range = fields().equal_range(
id);
104 auto frameIterator = range.first;
107 auto valuesIterator = values.cbegin();
108 if (frameIterator != range.second) {
111 if (valuesIterator != values.cend()) {
112 frameIterator->second.setValue(*valuesIterator);
115 frameIterator->second.value().clearDataAndMetadata();
119 if (valuesIterator == values.cend()) {
123 frameIterator = fields().insert(make_pair(
id,
Id3v2Frame(
id, *valuesIterator)));
128 frameIterator->second.additionalValues() = vector<TagValue>(valuesIterator, values.cend());
131 for (; range.first != range.second; ++range.first) {
132 range.first->second.setValue(
TagValue());
139 using namespace Id3v2FrameIds;
140 if (m_majorVersion >= 3) {
148 case KnownField::RecordDate:
151 case KnownField::ReleaseDate:
169 case KnownField::Length:
171 case KnownField::Language:
173 case KnownField::EncoderSettings:
177 case KnownField::SynchronizedLyrics:
199 case KnownField::RecordDate:
218 case KnownField::Length:
220 case KnownField::Language:
222 case KnownField::EncoderSettings:
226 case KnownField::SynchronizedLyrics:
244 KnownField Id3v2Tag::internallyGetKnownField(
const IdentifierType &
id)
const
246 using namespace Id3v2FrameIds;
256 return KnownField::RecordDate;
274 return KnownField::Language;
276 return KnownField::Length;
278 return KnownField::EncoderSettings;
282 return KnownField::SynchronizedLyrics;
296 return KnownField::RecordDate;
312 return KnownField::Language;
314 return KnownField::Length;
316 return KnownField::EncoderSettings;
320 return KnownField::SynchronizedLyrics;
326 return KnownField::Invalid;
330 TagDataType Id3v2Tag::internallyGetProposedDataType(
const std::uint32_t &
id)
const
332 using namespace Id3v2FrameIds;
336 return TagDataType::TimeSpan;
341 return TagDataType::Integer;
345 return TagDataType::PositionInSet;
348 return TagDataType::Picture;
351 return TagDataType::Text;
364 void Id3v2Tag::convertOldRecordDateFields(
const std::string &diagContext,
Diagnostics &diag)
372 int year = 1, month = 1, day = 1, hour = 0, minute = 0;
375 year = v.toInteger();
376 }
catch (
const ConversionException &e) {
377 diag.emplace_back(DiagLevel::Critical, argsToString(
"Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
382 auto str = v.toString();
383 if (str.size() != 4) {
384 throw ConversionException(
"format is not DDMM");
386 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
387 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
388 }
catch (
const ConversionException &e) {
389 diag.emplace_back(DiagLevel::Critical, argsToString(
"Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
394 auto str = v.toString();
395 if (str.size() != 4) {
396 throw ConversionException(
"format is not HHMM");
398 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
399 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
400 }
catch (
const ConversionException &e) {
401 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 convertOldRecordDateFields(context, diag);
524 if (maximalSize && m_size + 10 < maximalSize) {
526 stream.seekg(
static_cast<streamoff
>(startOffset + (m_size += 10)));
527 if (reader.readUInt24LE() != 0x494433u) {
528 diag.emplace_back(DiagLevel::Critical,
"Footer signature is invalid.", context);
531 stream.seekg(7, ios_base::cur);
533 diag.emplace_back(DiagLevel::Critical,
"Footer denoted but not present.", context);
560 void Id3v2Tag::make(ostream &stream, std::uint32_t padding,
Diagnostics &diag)
562 prepareMaking(diag).make(stream, padding, diag);
569 void Id3v2Tag::setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
571 m_majorVersion = majorVersion;
572 m_revisionVersion = revisionVersion;
573 m_version = argsToString(
'2',
'.', majorVersion,
'.', revisionVersion);
588 bool FrameComparer::operator()(std::uint32_t lhs, std::uint32_t rhs)
const
596 if (lhsLong != rhsLong) {
602 }
else if (!rhsLong) {
625 if (lhstextfield && !rhstextfield) {
628 if (!lhstextfield && rhstextfield) {
651 void Id3v2Tag::removeOldRecordDateRelatedFields()
654 fields().erase(field);
661 void Id3v2Tag::prepareRecordDataForMaking(
const std::string &diagContext, Diagnostics &diag)
666 if (majorVersion() >= 4) {
667 removeOldRecordDateRelatedFields();
675 if (recordingTimeFieldIterator == fields().cend()) {
679 const auto &recordingTime = recordingTimeFieldIterator->second.value();
680 if (recordingTime.isEmpty()) {
681 removeOldRecordDateRelatedFields();
686 const auto asDateTime = recordingTime.toDateTime();
688 removeOldRecordDateRelatedFields();
690 std::stringstream year,
date, time;
691 year << std::setfill(
'0') << std::setw(4) << asDateTime.year();
693 date << std::setfill(
'0') << std::setw(2) << asDateTime.day() << std::setfill(
'0') << std::setw(2) << asDateTime.month();
695 time << std::setfill(
'0') << std::setw(2) << asDateTime.hour() << std::setfill(
'0') << std::setw(2) << asDateTime.minute();
697 if (asDateTime.second() || asDateTime.millisecond()) {
698 diag.emplace_back(DiagLevel::Warning,
699 "The recording time field (TRDA) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
703 }
catch (
const ConversionException &e) {
705 diag.emplace_back(DiagLevel::Critical,
706 argsToString(
"Unable to convert recording time field (TRDA) with the value \"", recordingTime.toString(),
707 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
709 }
catch (
const ConversionException &) {
710 diag.emplace_back(DiagLevel::Critical,
711 argsToString(
"Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
723 Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
727 static const string context(
"making ID3v2 tag");
731 if (!tag.isVersionSupported()) {
733 throw VersionNotSupportedException();
736 tag.prepareRecordDataForMaking(context, diag);
739 m_maker.reserve(tag.fields().size());
740 for (
auto &pair : tag.fields()) {
742 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
743 m_framesSize += m_maker.back().requiredSize();
744 }
catch (
const Failure &) {
750 m_requiredSize = 10 + m_framesSize;
762 CPP_UTILITIES_UNUSED(diag)
764 BinaryWriter writer(&stream);
768 writer.writeUInt24BE(0x494433u);
773 writer.writeByte(m_tag.
flags() & 0xBF);
775 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
778 for (
auto &maker : m_maker) {
783 for (; padding; --padding) {