Improve handling of extended MP4 fields
This commit is contained in:
parent
1ddaa4f693
commit
f6d0f3a003
|
@ -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)
|
||||
|
|
|
@ -147,15 +147,15 @@ bool FieldMapBasedTag<FieldType, Compare>::setValue(const typename FieldType::id
|
|||
template <class FieldType, class Compare>
|
||||
bool FieldMapBasedTag<FieldType, Compare>::setValues(const typename FieldType::identifierType &id, const std::vector<TagValue> &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) {
|
||||
|
|
|
@ -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<Mp4Track>(*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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ const char *iTunes = "com.apple.iTunes";
|
|||
*/
|
||||
namespace Mp4TagExtendedNameIds {
|
||||
const char *cdec = "cdec";
|
||||
const char *label = "LABEL";
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -125,6 +125,7 @@ extern const char *iTunes;
|
|||
|
||||
namespace Mp4TagExtendedNameIds {
|
||||
extern const char *cdec;
|
||||
extern const char *label;
|
||||
}
|
||||
|
||||
namespace Mp4MediaTypeIds {
|
||||
|
|
112
mp4/mp4tag.cpp
112
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<fieldType>::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<fieldType>::value(Mp4TagAtomIds::RecordLabel);
|
||||
if(!value.isEmpty()) {
|
||||
return value;
|
||||
} else {
|
||||
return this->value(Mp4TagExtendedMeanIds::iTunes, Mp4TagExtendedNameIds::label);
|
||||
}
|
||||
}
|
||||
default:
|
||||
return FieldMapBasedTag<fieldType>::value(field);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const TagValue *> Mp4Tag::values(KnownField field) const
|
||||
{
|
||||
auto values = FieldMapBasedTag<fieldType>::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<const TagValue &(Mp4Tag::*)(const string &, const string &) const>(&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<fieldType>::setValue(field, value);
|
||||
}
|
||||
}
|
||||
|
||||
bool Mp4Tag::setValues(KnownField field, const std::vector<TagValue> &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<fieldType>::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<bool(Mp4Tag::*)(const string &, const string &, const TagValue &)>(&Mp4Tag::setValue))(mean, name, value);
|
||||
}
|
||||
|
||||
bool Mp4Tag::hasField(KnownField field) const
|
||||
{
|
||||
switch(field) {
|
||||
|
|
69
mp4/mp4tag.h
69
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<Mp4TagField>::value;
|
||||
const TagValue &value(KnownField value) const;
|
||||
std::vector<const TagValue *> 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<Mp4TagField>::setValue;
|
||||
bool setValue(KnownField field, const TagValue &value);
|
||||
bool setValues(KnownField field, const std::vector<TagValue> &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<Mp4TagField>::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
|
||||
|
|
|
@ -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<uint32> 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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue