diff --git a/CMakeLists.txt b/CMakeLists.txt index 76796bd..19c9d76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,6 +163,7 @@ set(META_VERSION_MINOR 0) set(META_VERSION_PATCH 1) set(META_PUBLIC_SHARED_LIB_DEPENDS c++utilities) set(META_PUBLIC_STATIC_LIB_DEPENDS c++utilities_static) +set(META_PRIVATE_COMPILE_DEFINITIONS LEGACY_API) # find c++utilities find_package(c++utilities 4.0.0 REQUIRED) diff --git a/fieldbasedtag.h b/fieldbasedtag.h index b25e666..1e9baea 100644 --- a/fieldbasedtag.h +++ b/fieldbasedtag.h @@ -147,15 +147,15 @@ bool FieldMapBasedTag::setValue(const typename FieldType::id template bool FieldMapBasedTag::setValues(const typename FieldType::identifierType &id, const std::vector &values) { - auto valuesIterator = values.begin(); + auto valuesIterator = values.cbegin(); auto range = m_fields.equal_range(id); - for(; valuesIterator != values.end() && range.first != range.second; ++valuesIterator) { + for(; valuesIterator != values.cend() && range.first != range.second; ++valuesIterator) { if(!valuesIterator->isEmpty()) { range.first->second.setValue(*valuesIterator); ++range.first; } } - for(; valuesIterator != values.end(); ++valuesIterator) { + for(; valuesIterator != values.cend(); ++valuesIterator) { m_fields.insert(std::make_pair(id, FieldType(id, *valuesIterator))); } for(; range.first != range.second; ++range.first) { diff --git a/mp4/mp4container.cpp b/mp4/mp4container.cpp index ccdd4bc..b1716a4 100644 --- a/mp4/mp4container.cpp +++ b/mp4/mp4container.cpp @@ -157,14 +157,14 @@ void Mp4Container::internalParseTracks() while(trakAtom) { try { trakAtom->parse(); - } catch(Failure &) { + } catch(const Failure &) { addNotification(NotificationType::Warning, "Unable to parse child atom of moov.", context); } // parse the trak atom using the Mp4Track class m_tracks.emplace_back(make_unique(*trakAtom)); try { // try to parse header m_tracks.back()->parseHeader(); - } catch(Failure &) { + } catch(const Failure &) { addNotification(NotificationType::Critical, "Unable to parse track " + ConversionUtilities::numberToString(trackNum) + ".", context); } trakAtom = trakAtom->siblingById(Mp4AtomIds::Track, false); // get next trak atom @@ -185,7 +185,7 @@ void Mp4Container::internalParseTracks() } } } - } catch(Failure &) { + } catch(const Failure &) { addNotification(NotificationType::Warning, "Unable to parse moov atom.", context); } } diff --git a/mp4/mp4ids.cpp b/mp4/mp4ids.cpp index 47cde57..78fd115 100644 --- a/mp4/mp4ids.cpp +++ b/mp4/mp4ids.cpp @@ -28,6 +28,7 @@ const char *iTunes = "com.apple.iTunes"; */ namespace Mp4TagExtendedNameIds { const char *cdec = "cdec"; +const char *label = "LABEL"; } /*! diff --git a/mp4/mp4ids.h b/mp4/mp4ids.h index def9341..b04aacb 100644 --- a/mp4/mp4ids.h +++ b/mp4/mp4ids.h @@ -125,6 +125,7 @@ extern const char *iTunes; namespace Mp4TagExtendedNameIds { extern const char *cdec; +extern const char *label; } namespace Mp4MediaTypeIds { diff --git a/mp4/mp4tag.cpp b/mp4/mp4tag.cpp index 1e3fef7..84f3fb2 100644 --- a/mp4/mp4tag.cpp +++ b/mp4/mp4tag.cpp @@ -14,6 +14,30 @@ using namespace ConversionUtilities; namespace Media { +/*! + * \class Media::Mp4ExtendedFieldId + * \brief The Mp4ExtendedFieldId specifies parameter for an extended field denoted via Mp4TagAtomIds::Extended. + */ + +/*! + * \brief Constructs a new instance for the specified \a field. + * \remarks The instance will be invalid if no extended field parameter for \a field are known. + */ +Mp4ExtendedFieldId::Mp4ExtendedFieldId(KnownField field) +{ + switch(field) { + case KnownField::EncoderSettings: + mean = Mp4TagExtendedMeanIds::iTunes, name = Mp4TagExtendedNameIds::cdec; + break; + case KnownField::RecordLabel: + mean = Mp4TagExtendedMeanIds::iTunes, name = Mp4TagExtendedNameIds::label; + updateOnly = true; // set record label via extended field only if extended field is already present + break; + default: + mean = nullptr; + } +} + /*! * \class Media::Mp4Tag * \brief Implementation of Media::Tag for the MP4 container. @@ -42,16 +66,41 @@ const TagValue &Mp4Tag::value(KnownField field) const return FieldMapBasedTag::value(Mp4TagAtomIds::PreDefinedGenre); } } case KnownField::EncoderSettings: - return value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::cdec); + return this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::cdec); + case KnownField::RecordLabel: { + const TagValue &value = FieldMapBasedTag::value(Mp4TagAtomIds::RecordLabel); + if(!value.isEmpty()) { + return value; + } else { + return this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label); + } + } default: return FieldMapBasedTag::value(field); } } +std::vector Mp4Tag::values(KnownField field) const +{ + auto values = FieldMapBasedTag::values(field); + const Mp4ExtendedFieldId extendedId(field); + if(extendedId) { + auto range = fields().equal_range(Mp4TagAtomIds::Extended); + for(auto i = range.first; i != range.second; ++i) { + if(extendedId.matches(i->second)) { + values.emplace_back(&i->second.value()); + } + } + } + return values; +} + /*! * \brief Returns the value of the field with the specified \a mean and \a name attributes. + * \remarks + * - If there are multiple fields with specified \a mean and \a name only the first value will be returned. */ -const TagValue &Mp4Tag::value(const string mean, const string name) const +const TagValue &Mp4Tag::value(const char *mean, const char *name) const { auto range = fields().equal_range(Mp4TagAtomIds::Extended); for(auto i = range.first; i != range.second; ++i) { @@ -62,6 +111,14 @@ const TagValue &Mp4Tag::value(const string mean, const string name) const return TagValue::empty(); } +/*! + * \brief Returns the value of the field with the specified \a mean and \a name attributes. + */ +const TagValue &Mp4Tag::value(const string mean, const string name) const +{ + return (this->*static_cast(&Mp4Tag::value))(mean, name); +} + uint32 Mp4Tag::fieldId(KnownField field) const { using namespace Mp4TagAtomIds; @@ -85,7 +142,6 @@ uint32 Mp4Tag::fieldId(KnownField field) const case KnownField::RecordLabel: return RecordLabel; case KnownField::Performers: return Performers; case KnownField::Lyricist: return Lyricist; - case KnownField::EncoderSettings: return Extended; default: return 0; } } @@ -131,15 +187,55 @@ bool Mp4Tag::setValue(KnownField field, const TagValue &value) } case KnownField::EncoderSettings: return setValue(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::cdec, value); + case KnownField::RecordLabel: + if(!this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label).isEmpty()) { + setValue(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label, value); + } + FALLTHROUGH; default: return FieldMapBasedTag::setValue(field, value); } } +bool Mp4Tag::setValues(KnownField field, const std::vector &values) +{ + const Mp4ExtendedFieldId extendedId(field); + if(extendedId) { + auto valuesIterator = values.cbegin(); + auto range = fields().equal_range(Mp4TagAtomIds::Extended); + for(; valuesIterator != values.cend() && range.first != range.second;) { + if(!valuesIterator->isEmpty()) { + if(extendedId.matches(range.first->second) + && (!extendedId.updateOnly || !range.first->second.value().isEmpty())) { + range.first->second.setValue(*valuesIterator); + ++valuesIterator; + } + ++range.first; + } else { + ++valuesIterator; + } + } + for(; valuesIterator != values.cend(); ++valuesIterator) { + Mp4TagField tagField(Mp4TagAtomIds::Extended, *valuesIterator); + tagField.setMean(extendedId.mean); + tagField.setName(extendedId.name); + fields().insert(std::make_pair(Mp4TagAtomIds::Extended, move(tagField))); + } + for(; range.first != range.second; ++range.first) { + range.first->second.setValue(TagValue()); + } + } + return FieldMapBasedTag::setValues(field, values); +} + /*! * \brief Assigns the given \a value to the field with the specified \a mean and \a name attributes. + * \remarks + * - If there are multiple fields with specified \a mean and \a name only the first will be altered. + * - If no field is present, a new one will be created. + * - If \a value is empty, the field will be removed. */ -bool Mp4Tag::setValue(const string mean, const string name, const TagValue &value) +bool Mp4Tag::setValue(const char *mean, const char *name, const TagValue &value) { auto range = fields().equal_range(Mp4TagAtomIds::Extended); for(auto i = range.first; i != range.second; ++i) { @@ -152,6 +248,14 @@ bool Mp4Tag::setValue(const string mean, const string name, const TagValue &valu return true; } +/*! + * \brief Assigns the given \a value to the field with the specified \a mean and \a name attributes. + */ +bool Mp4Tag::setValue(const string mean, const string name, const TagValue &value) +{ + return (this->*static_cast(&Mp4Tag::setValue))(mean, name, value); +} + bool Mp4Tag::hasField(KnownField field) const { switch(field) { diff --git a/mp4/mp4tag.h b/mp4/mp4tag.h index d0f3efa..3d87b24 100644 --- a/mp4/mp4tag.h +++ b/mp4/mp4tag.h @@ -11,6 +11,47 @@ namespace Media class Mp4Atom; class Mp4Tag; +struct TAG_PARSER_EXPORT Mp4ExtendedFieldId +{ + Mp4ExtendedFieldId(const char *mean = nullptr, const char *name = nullptr, bool updateOnly = false); + Mp4ExtendedFieldId(KnownField field); + + operator bool() const; + bool matches(const Mp4TagField &field) const; + + /// \brief mean parameter, usually Mp4TagExtendedMeanIds::iTunes + const char *mean; + /// \brief name parameter + const char *name; + /// \brief Whether only existing fields should be updated but *no* new extended field should be created + bool updateOnly; +}; + +/*! + * \brief Constructs a new instance with the specified parameter. + */ +inline Mp4ExtendedFieldId::Mp4ExtendedFieldId(const char *mean, const char *name, bool updateOnly) : + mean(mean), + name(name), + updateOnly(updateOnly) +{} + +/*! + * \brief Returns whether valid parameter are assigned. + */ +inline Mp4ExtendedFieldId::operator bool() const +{ + return mean && name; +} + +/*! + * \brief Returns whether the current parameter match the specified \a field. + */ +inline bool Mp4ExtendedFieldId::matches(const Mp4TagField &field) const +{ + return field.mean() == mean && field.name() == name; +} + class TAG_PARSER_EXPORT Mp4TagMaker { friend class Mp4Tag; @@ -56,14 +97,24 @@ public: TagTextEncoding proposedTextEncoding() const; bool canEncodingBeUsed(TagTextEncoding encoding) const; - uint32 fieldId(KnownField value) const; + uint32 fieldId(KnownField field) const; KnownField knownField(const uint32 &id) const; using FieldMapBasedTag::value; const TagValue &value(KnownField value) const; + std::vector values(KnownField field) const; +#ifdef LEGACY_API const TagValue &value(const std::string mean, const std::string name) const; +#endif + const TagValue &value(const std::string &mean, const std::string &name) const; + const TagValue &value(const char *mean, const char *name) const; using FieldMapBasedTag::setValue; bool setValue(KnownField field, const TagValue &value); + bool setValues(KnownField field, const std::vector &values); +#ifdef LEGACY_API bool setValue(const std::string mean, const std::string name, const TagValue &value); +#endif + bool setValue(const std::string &mean, const std::string &name, const TagValue &value); + bool setValue(const char *mean, const char *name, const TagValue &value); using FieldMapBasedTag::hasField; bool hasField(KnownField value) const; @@ -93,6 +144,22 @@ inline TagTextEncoding Mp4Tag::proposedTextEncoding() const return TagTextEncoding::Utf8; } +/*! + * \brief Returns the value of the field with the specified \a mean and \a name attributes. + */ +inline const TagValue &Mp4Tag::value(const std::string &mean, const std::string &name) const +{ + return value(mean.data(), name.data()); +} + +/*! + * \brief Assigns the given \a value to the field with the specified \a mean and \a name attributes. + */ +inline bool Mp4Tag::setValue(const std::string &mean, const std::string &name, const TagValue &value) +{ + return setValue(mean.data(), name.data(), value); +} + } #endif // MP4TAG_H diff --git a/mp4/mp4tagfield.cpp b/mp4/mp4tagfield.cpp index 800f917..b0ede01 100644 --- a/mp4/mp4tagfield.cpp +++ b/mp4/mp4tagfield.cpp @@ -245,7 +245,7 @@ void Mp4TagField::reparse(Mp4Atom &ilstChild) } else { addNotification(NotificationType::Warning, "Unkown child atom \"" + dataAtom->idToString() + "\" in tag atom (ilst child) found. (will be ignored)", context); } - } catch(Failure &) { + } catch(const Failure &) { addNotification(NotificationType::Warning, "Unable to parse all childs atom in tag atom (ilst child) found. (will be ignored)", context); } } @@ -311,7 +311,14 @@ std::vector Mp4TagField::expectedRawDataTypes() const res.push_back(RawDataType::Bmp); break; case Extended: - throw Failure(); + if(mean() == Mp4TagExtendedMeanIds::iTunes) { + // correct? + res.push_back(RawDataType::Utf8); + res.push_back(RawDataType::Utf16); + break; + } else { + throw Failure(); + } default: throw Failure(); } @@ -363,7 +370,15 @@ uint32 Mp4TagField::appropriateRawDataType() const } } case Extended: - throw Failure(); + if(mean() == Mp4TagExtendedMeanIds::iTunes) { + switch(value().dataEncoding()) { + case TagTextEncoding::Utf8: return RawDataType::Utf8; + case TagTextEncoding::Utf16BigEndian: return RawDataType::Utf16; + default: throw Failure(); + } + } else { + throw Failure(); + } default: throw Failure(); }