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) {
239 using namespace Id3v2FrameIds;
325 using namespace Id3v2FrameIds;
357void 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) {
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);
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) {
452 m_flags = reader.readByte();
453 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
454 m_size = 10 + m_sizeExcludingHeader;
455 if (m_sizeExcludingHeader == 0) {
462 diag.emplace_back(
DiagLevel::Critical,
"The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
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))) {
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);
488 auto pos =
static_cast<std::uint64_t
>(stream.tellg());
489 while (bytesRemaining) {
491 stream.seekg(
static_cast<streamoff
>(pos));
499 fields().emplace(frame.
id(), move(frame));
502 m_paddingSize = startOffset +
m_size - pos;
509 if (frame.
totalSize() <= bytesRemaining) {
513 pos += bytesRemaining;
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) {
533 stream.seekg(7, ios_base::cur);
598 if (lhsLong != rhsLong) {
604 }
else if (!rhsLong) {
627 if (lhstextfield && !rhstextfield) {
630 if (!lhstextfield && rhstextfield) {
653void Id3v2Tag::removeOldRecordDateRelatedFields()
663void Id3v2Tag::prepareRecordDataForMaking(
const std::string &diagContext, Diagnostics &diag)
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()) {
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) {
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 &) {
713 argsToString(
"Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
725Id3v2TagMaker::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...
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.
@ 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.
bool operator()(std::uint32_t lhs, std::uint32_t rhs) const
Returns true if lhs goes before rhs; otherwise returns false.