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) {
259 using namespace Id3v2FrameIds;
373 using namespace Id3v2FrameIds;
410void Id3v2Tag::convertOldRecordDateFields(
const std::string &diagContext,
Diagnostics &diag)
418 bool hasAnyValue =
false;
419 int year = 1, month = 1, day = 1, hour = 0, minute = 0;
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);
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);
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);
461 }
catch (
const ConversionException &e) {
467 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
469 }
catch (
const ConversionException &) {
472 DiagLevel::Critical, argsToString(
"Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
486 static const string context(
"parsing ID3v2 tag");
487 BinaryReader reader(&stream);
488 const auto startOffset =
static_cast<std::uint64_t
>(stream.tellg());
491 if (maximalSize && maximalSize < 10) {
492 diag.emplace_back(
DiagLevel::Critical,
"ID3v2 header is truncated (at least 10 bytes expected).", context);
497 if (reader.readUInt24BE() != 0x494433u) {
505 m_flags = reader.readByte();
506 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
507 m_size = 10 + m_sizeExcludingHeader;
508 if (m_sizeExcludingHeader == 0) {
515 diag.emplace_back(
DiagLevel::Critical,
"The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
521 if (maximalSize && maximalSize < 14) {
522 diag.emplace_back(
DiagLevel::Critical,
"Extended header denoted but not present.", context);
525 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
526 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
530 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
534 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
535 if (maximalSize && bytesRemaining > maximalSize) {
536 bytesRemaining =
static_cast<std::uint32_t
>(maximalSize);
541 auto pos =
static_cast<std::uint64_t
>(stream.tellg());
542 while (bytesRemaining) {
544 stream.seekg(
static_cast<streamoff
>(pos));
552 fields().emplace(frame.
id(), move(frame));
555 m_paddingSize = startOffset +
m_size - pos;
562 if (frame.
totalSize() <= bytesRemaining) {
566 pos += bytesRemaining;
572 convertOldRecordDateFields(context, diag);
579 if (maximalSize &&
m_size + 10 < maximalSize) {
581 stream.seekg(
static_cast<streamoff
>(startOffset + (
m_size += 10)));
582 if (reader.readUInt24LE() != 0x494433u) {
586 stream.seekg(7, ios_base::cur);
651 if (lhsLong != rhsLong) {
657 }
else if (!rhsLong) {
680 if (lhstextfield && !rhstextfield) {
683 if (!lhstextfield && rhstextfield) {
706void Id3v2Tag::removeOldRecordDateRelatedFields()
716void Id3v2Tag::prepareRecordDataForMaking(
const std::string &diagContext, Diagnostics &diag)
722 removeOldRecordDateRelatedFields();
730 if (recordingTimeFieldIterator ==
fields().cend()) {
734 const auto &recordingTime = recordingTimeFieldIterator->second.value();
735 if (recordingTime.isEmpty()) {
736 removeOldRecordDateRelatedFields();
741 const auto asDateTime = recordingTime.toDateTime();
743 removeOldRecordDateRelatedFields();
746 year << std::setfill(
'0') << std::setw(4) << asDateTime.year();
748 date << std::setfill(
'0') << std::setw(2) << asDateTime.day() << std::setfill(
'0') << std::setw(2) << asDateTime.month();
750 time << std::setfill(
'0') << std::setw(2) << asDateTime.hour() << std::setfill(
'0') << std::setw(2) << asDateTime.minute();
752 if (asDateTime.second() || asDateTime.millisecond()) {
754 "The recording time field (TRDA) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
758 }
catch (
const ConversionException &e) {
761 argsToString(
"Unable to convert recording time field (TRDA) with the value \"", recordingTime.toString(),
762 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
764 }
catch (
const ConversionException &) {
766 argsToString(
"Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
778Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
782 static const string context(
"making ID3v2 tag");
786 if (!tag.isVersionSupported()) {
788 throw VersionNotSupportedException();
792 tag.prepareRecordDataForMaking(context, diag);
796 m_maker.reserve(tag.fields().size());
797 for (
auto &pair : tag.fields()) {
799 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
800 m_framesSize += m_maker.back().requiredSize();
801 }
catch (
const Failure &) {
807 m_requiredSize = 10 + m_framesSize;
819 CPP_UTILITIES_UNUSED(diag)
821 BinaryWriter writer(&stream);
825 writer.writeUInt24BE(0x494433u);
830 writer.writeByte(m_tag.
flags() & 0xBF);
832 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
835 for (
auto &maker : m_maker) {
840 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.