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) {
255 using namespace Id3v2FrameIds;
361 using namespace Id3v2FrameIds;
393void Id3v2Tag::convertOldRecordDateFields(
const std::string &diagContext,
Diagnostics &diag)
401 bool hasAnyValue =
false;
402 int year = 1, month = 1, day = 1, hour = 0, minute = 0;
406 year = v.toInteger();
407 }
catch (
const ConversionException &e) {
408 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
414 auto str = v.toString();
415 if (str.size() != 4) {
416 throw ConversionException(
"format is not DDMM");
418 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
419 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
420 }
catch (
const ConversionException &e) {
421 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
427 auto str = v.toString();
428 if (str.size() != 4) {
429 throw ConversionException(
"format is not HHMM");
431 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
432 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
433 }
catch (
const ConversionException &e) {
434 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
444 }
catch (
const ConversionException &e) {
450 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
452 }
catch (
const ConversionException &) {
455 DiagLevel::Critical, argsToString(
"Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
469 static const string context(
"parsing ID3v2 tag");
470 BinaryReader reader(&stream);
471 const auto startOffset =
static_cast<std::uint64_t
>(stream.tellg());
474 if (maximalSize && maximalSize < 10) {
475 diag.emplace_back(
DiagLevel::Critical,
"ID3v2 header is truncated (at least 10 bytes expected).", context);
480 if (reader.readUInt24BE() != 0x494433u) {
488 m_flags = reader.readByte();
489 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
490 m_size = 10 + m_sizeExcludingHeader;
491 if (m_sizeExcludingHeader == 0) {
498 diag.emplace_back(
DiagLevel::Critical,
"The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
504 if (maximalSize && maximalSize < 14) {
505 diag.emplace_back(
DiagLevel::Critical,
"Extended header denoted but not present.", context);
508 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
509 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
513 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
517 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
518 if (maximalSize && bytesRemaining > maximalSize) {
519 bytesRemaining =
static_cast<std::uint32_t
>(maximalSize);
524 auto pos =
static_cast<std::uint64_t
>(stream.tellg());
525 while (bytesRemaining) {
527 stream.seekg(
static_cast<streamoff
>(pos));
535 fields().emplace(frame.
id(), move(frame));
538 m_paddingSize = startOffset +
m_size - pos;
545 if (frame.
totalSize() <= bytesRemaining) {
549 pos += bytesRemaining;
555 convertOldRecordDateFields(context, diag);
562 if (maximalSize &&
m_size + 10 < maximalSize) {
564 stream.seekg(
static_cast<streamoff
>(startOffset + (
m_size += 10)));
565 if (reader.readUInt24LE() != 0x494433u) {
569 stream.seekg(7, ios_base::cur);
634 if (lhsLong != rhsLong) {
640 }
else if (!rhsLong) {
663 if (lhstextfield && !rhstextfield) {
666 if (!lhstextfield && rhstextfield) {
689void Id3v2Tag::removeOldRecordDateRelatedFields()
699void Id3v2Tag::prepareRecordDataForMaking(
const std::string &diagContext, Diagnostics &diag)
705 removeOldRecordDateRelatedFields();
713 if (recordingTimeFieldIterator ==
fields().cend()) {
717 const auto &recordingTime = recordingTimeFieldIterator->second.value();
718 if (recordingTime.isEmpty()) {
719 removeOldRecordDateRelatedFields();
724 const auto asDateTime = recordingTime.toDateTime();
726 removeOldRecordDateRelatedFields();
729 year << std::setfill(
'0') << std::setw(4) << asDateTime.year();
731 date << std::setfill(
'0') << std::setw(2) << asDateTime.day() << std::setfill(
'0') << std::setw(2) << asDateTime.month();
733 time << std::setfill(
'0') << std::setw(2) << asDateTime.hour() << std::setfill(
'0') << std::setw(2) << asDateTime.minute();
735 if (asDateTime.second() || asDateTime.millisecond()) {
737 "The recording time field (TRDA) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
741 }
catch (
const ConversionException &e) {
744 argsToString(
"Unable to convert recording time field (TRDA) with the value \"", recordingTime.toString(),
745 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
747 }
catch (
const ConversionException &) {
749 argsToString(
"Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
761Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
765 static const string context(
"making ID3v2 tag");
769 if (!tag.isVersionSupported()) {
771 throw VersionNotSupportedException();
775 tag.prepareRecordDataForMaking(context, diag);
779 m_maker.reserve(tag.fields().size());
780 for (
auto &pair : tag.fields()) {
782 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
783 m_framesSize += m_maker.back().requiredSize();
784 }
catch (
const Failure &) {
790 m_requiredSize = 10 + m_framesSize;
802 CPP_UTILITIES_UNUSED(diag)
804 BinaryWriter writer(&stream);
808 writer.writeUInt24BE(0x494433u);
813 writer.writeByte(m_tag.
flags() & 0xBF);
815 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
818 for (
auto &maker : m_maker) {
823 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.