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) {
253 using namespace Id3v2FrameIds;
353 using namespace Id3v2FrameIds;
385void Id3v2Tag::convertOldRecordDateFields(
const std::string &diagContext,
Diagnostics &diag)
393 bool hasAnyValue =
false;
394 int year = 1, month = 1, day = 1, hour = 0, minute = 0;
398 year = v.toInteger();
399 }
catch (
const ConversionException &e) {
400 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
406 auto str = v.toString();
407 if (str.size() != 4) {
408 throw ConversionException(
"format is not DDMM");
410 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
411 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
412 }
catch (
const ConversionException &e) {
413 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
419 auto str = v.toString();
420 if (str.size() != 4) {
421 throw ConversionException(
"format is not HHMM");
423 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
424 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
425 }
catch (
const ConversionException &e) {
426 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
436 }
catch (
const ConversionException &e) {
442 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
444 }
catch (
const ConversionException &) {
447 DiagLevel::Critical, argsToString(
"Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
461 static const string context(
"parsing ID3v2 tag");
462 BinaryReader reader(&stream);
463 const auto startOffset =
static_cast<std::uint64_t
>(stream.tellg());
466 if (maximalSize && maximalSize < 10) {
467 diag.emplace_back(
DiagLevel::Critical,
"ID3v2 header is truncated (at least 10 bytes expected).", context);
472 if (reader.readUInt24BE() != 0x494433u) {
480 m_flags = reader.readByte();
481 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
482 m_size = 10 + m_sizeExcludingHeader;
483 if (m_sizeExcludingHeader == 0) {
490 diag.emplace_back(
DiagLevel::Critical,
"The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
496 if (maximalSize && maximalSize < 14) {
497 diag.emplace_back(
DiagLevel::Critical,
"Extended header denoted but not present.", context);
500 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
501 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
505 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
509 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
510 if (maximalSize && bytesRemaining > maximalSize) {
511 bytesRemaining =
static_cast<std::uint32_t
>(maximalSize);
516 auto pos =
static_cast<std::uint64_t
>(stream.tellg());
517 while (bytesRemaining) {
519 stream.seekg(
static_cast<streamoff
>(pos));
527 fields().emplace(frame.
id(), move(frame));
530 m_paddingSize = startOffset +
m_size - pos;
537 if (frame.
totalSize() <= bytesRemaining) {
541 pos += bytesRemaining;
547 convertOldRecordDateFields(context, diag);
554 if (maximalSize &&
m_size + 10 < maximalSize) {
556 stream.seekg(
static_cast<streamoff
>(startOffset + (
m_size += 10)));
557 if (reader.readUInt24LE() != 0x494433u) {
561 stream.seekg(7, ios_base::cur);
626 if (lhsLong != rhsLong) {
632 }
else if (!rhsLong) {
655 if (lhstextfield && !rhstextfield) {
658 if (!lhstextfield && rhstextfield) {
681void Id3v2Tag::removeOldRecordDateRelatedFields()
691void Id3v2Tag::prepareRecordDataForMaking(
const std::string &diagContext, Diagnostics &diag)
697 removeOldRecordDateRelatedFields();
705 if (recordingTimeFieldIterator ==
fields().cend()) {
709 const auto &recordingTime = recordingTimeFieldIterator->second.value();
710 if (recordingTime.isEmpty()) {
711 removeOldRecordDateRelatedFields();
716 const auto asDateTime = recordingTime.toDateTime();
718 removeOldRecordDateRelatedFields();
721 year << std::setfill(
'0') << std::setw(4) << asDateTime.year();
723 date << std::setfill(
'0') << std::setw(2) << asDateTime.day() << std::setfill(
'0') << std::setw(2) << asDateTime.month();
725 time << std::setfill(
'0') << std::setw(2) << asDateTime.hour() << std::setfill(
'0') << std::setw(2) << asDateTime.minute();
727 if (asDateTime.second() || asDateTime.millisecond()) {
729 "The recording time field (TRDA) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
733 }
catch (
const ConversionException &e) {
736 argsToString(
"Unable to convert recording time field (TRDA) with the value \"", recordingTime.toString(),
737 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
739 }
catch (
const ConversionException &) {
741 argsToString(
"Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
753Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
757 static const string context(
"making ID3v2 tag");
761 if (!tag.isVersionSupported()) {
763 throw VersionNotSupportedException();
767 tag.prepareRecordDataForMaking(context, diag);
771 m_maker.reserve(tag.fields().size());
772 for (
auto &pair : tag.fields()) {
774 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
775 m_framesSize += m_maker.back().requiredSize();
776 }
catch (
const Failure &) {
782 m_requiredSize = 10 + m_framesSize;
794 CPP_UTILITIES_UNUSED(diag)
796 BinaryWriter writer(&stream);
800 writer.writeUInt24BE(0x494433u);
805 writer.writeByte(m_tag.
flags() & 0xBF);
807 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
810 for (
auto &maker : m_maker) {
815 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.