Improve handling of extended MP4 fields

This commit is contained in:
Martchus 2016-11-14 22:59:19 +01:00
parent 1ddaa4f693
commit f6d0f3a003
8 changed files with 203 additions and 14 deletions

View File

@ -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)

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -28,6 +28,7 @@ const char *iTunes = "com.apple.iTunes";
*/
namespace Mp4TagExtendedNameIds {
const char *cdec = "cdec";
const char *label = "LABEL";
}
/*!

View File

@ -125,6 +125,7 @@ extern const char *iTunes;
namespace Mp4TagExtendedNameIds {
extern const char *cdec;
extern const char *label;
}
namespace Mp4MediaTypeIds {

View File

@ -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) {

View File

@ -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

View File

@ -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();
}