Allow setting track meta-data
This commit is contained in:
parent
297e95a90b
commit
6fb6d3dcbe
165
cli/helper.cpp
165
cli/helper.cpp
|
@ -7,6 +7,7 @@
|
|||
#include <tagparser/id3/id3v2tag.h>
|
||||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
#include <c++utilities/conversion/stringbuilder.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
@ -313,6 +314,7 @@ FieldDenotations parseFieldDenotations(const Argument &fieldsArg, bool readOnly)
|
|||
cerr << "Warning: The \"tag\"-specifier has been used with no value(s) and hence is ignored. Possible values are: id3,id3v1,id3v2,itunes,vorbis,matroska,all" << endl;
|
||||
} else {
|
||||
TagType tagType = TagType::Unspecified;
|
||||
bool error = false;
|
||||
for(const auto &part : splitString(fieldDenotationString + 4, ",", EmptyPartsTreat::Omit)) {
|
||||
if(part == "id3v1") {
|
||||
tagType |= TagType::Id3v1Tag;
|
||||
|
@ -331,15 +333,44 @@ FieldDenotations parseFieldDenotations(const Argument &fieldsArg, bool readOnly)
|
|||
break;
|
||||
} else {
|
||||
cerr << "Warning: The value provided with the \"tag\"-specifier is invalid and will be ignored. Possible values are: id3,id3v1,id3v2,itunes,vorbis,matroska,all" << endl;
|
||||
tagType = scope.tagType;
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
scope.tagType = tagType;
|
||||
break;
|
||||
if(!error) {
|
||||
scope.tagType = tagType;
|
||||
scope.allTracks = false;
|
||||
scope.trackIds.clear();
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} else if(applyTargetConfiguration(scope.tagTarget, fieldDenotationString)) {
|
||||
continue;
|
||||
} else if(!strncmp(fieldDenotationString, "track=", 6)) {
|
||||
const vector<string> parts = splitString<vector<string>>(fieldDenotationString + 6, ",", EmptyPartsTreat::Omit);
|
||||
bool allTracks = scope.allTracks;
|
||||
vector<uint64> trackIds;
|
||||
trackIds.reserve(parts.size());
|
||||
bool error = false;
|
||||
for(const auto &part : parts) {
|
||||
if(part == "all" || part == "any") {
|
||||
allTracks = true;
|
||||
break;
|
||||
} else {
|
||||
try {
|
||||
trackIds.emplace_back(stringToNumber<uint64>(part));
|
||||
} catch(const ConversionException &) {
|
||||
cerr << "Warning: The value provided with the \"track\"-specifier is invalid and will be ignored. It must be a comma-separated list of track IDs." << endl;
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!error) {
|
||||
scope.allTracks = allTracks;
|
||||
scope.trackIds = move(trackIds);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// check whether field name starts with + indicating an additional value
|
||||
bool additionalValue = *fieldDenotationString == '+';
|
||||
|
@ -376,7 +407,11 @@ FieldDenotations parseFieldDenotations(const Argument &fieldsArg, bool readOnly)
|
|||
}
|
||||
// parse the denoted field ID
|
||||
try {
|
||||
scope.field = FieldId::fromDenotation(fieldDenotationString, fieldNameLen);
|
||||
if(scope.isTrack()) {
|
||||
scope.field = FieldId::fromTrackDenotation(fieldDenotationString, fieldNameLen);
|
||||
} else {
|
||||
scope.field = FieldId::fromTagDenotation(fieldDenotationString, fieldNameLen);
|
||||
}
|
||||
} catch(const ConversionException &e) {
|
||||
// unable to parse field ID denotation -> discard the field denotation
|
||||
cerr << "Warning: The field denotation \"" << string(fieldDenotationString, fieldNameLen) << "\" could not be parsed and will be ignored: " << e.what() << endl;
|
||||
|
@ -397,7 +432,7 @@ FieldDenotations parseFieldDenotations(const Argument &fieldsArg, bool readOnly)
|
|||
// file index might have been specified explicitely
|
||||
// if not (mult == 1) use the index of the last value and increase it by one if the value is not an additional one
|
||||
// if there are no previous values, just use the index 0
|
||||
fieldValues.emplace_back(FieldValue(type, mult == 1 ? (fieldValues.empty() ? 0 : fieldValues.back().fileIndex + (additionalValue ? 0 : 1)) : fileIndex, (equationPos + 1)));
|
||||
fieldValues.allValues.emplace_back(type, mult == 1 ? (fieldValues.allValues.empty() ? 0 : fieldValues.allValues.back().fileIndex + (additionalValue ? 0 : 1)) : fileIndex, equationPos + 1);
|
||||
}
|
||||
}
|
||||
if(additionalValue && readOnly) {
|
||||
|
@ -426,9 +461,9 @@ bool setValuesForNativeField(const char *idString, std::size_t idStringSize, Tag
|
|||
return static_cast<ConcreteTag *>(tag)->setValues(ConcreteTag::fieldType::fieldIdFromString(idString, idStringSize), values);
|
||||
}
|
||||
|
||||
inline FieldId::FieldId(const char *nativeField, const GetValuesForNativeFieldType &valuesForNativeField, const SetValuesForNativeFieldType &setValuesForNativeField) :
|
||||
inline FieldId::FieldId(const char *nativeField, std::size_t nativeFieldSize, const GetValuesForNativeFieldType &valuesForNativeField, const SetValuesForNativeFieldType &setValuesForNativeField) :
|
||||
m_knownField(KnownField::Invalid),
|
||||
m_nativeField(nativeField),
|
||||
m_nativeField(nativeField, nativeFieldSize),
|
||||
m_valuesForNativeField(valuesForNativeField),
|
||||
m_setValuesForNativeField(setValuesForNativeField)
|
||||
{}
|
||||
|
@ -438,13 +473,13 @@ template<class ConcreteTag>
|
|||
FieldId FieldId::fromNativeField(const char *nativeFieldId, std::size_t nativeFieldIdSize)
|
||||
{
|
||||
return FieldId(
|
||||
nativeFieldId,
|
||||
nativeFieldId, nativeFieldIdSize,
|
||||
bind(&valuesForNativeField<ConcreteTag>, nativeFieldId, nativeFieldIdSize, _1, _2),
|
||||
bind(&setValuesForNativeField<ConcreteTag>, nativeFieldId, nativeFieldIdSize, _1, _2, _3)
|
||||
);
|
||||
}
|
||||
|
||||
FieldId FieldId::fromDenotation(const char *denotation, size_t denotationSize)
|
||||
FieldId FieldId::fromTagDenotation(const char *denotation, size_t denotationSize)
|
||||
{
|
||||
// check for native, format-specific denotation
|
||||
if(!strncmp(denotation, "mkv:", 4)) {
|
||||
|
@ -461,68 +496,54 @@ FieldId FieldId::fromDenotation(const char *denotation, size_t denotationSize)
|
|||
}
|
||||
|
||||
// determine KnownField for generic denotation
|
||||
if(!strncmp(denotation, "title", denotationSize)) {
|
||||
return KnownField::Title;
|
||||
} else if(!strncmp(denotation, "album", denotationSize)) {
|
||||
return KnownField::Album;
|
||||
} else if(!strncmp(denotation, "artist", denotationSize)) {
|
||||
return KnownField::Artist;
|
||||
} else if(!strncmp(denotation, "genre", denotationSize)) {
|
||||
return KnownField::Genre;
|
||||
} else if(!strncmp(denotation, "year", denotationSize)) {
|
||||
return KnownField::Year;
|
||||
} else if(!strncmp(denotation, "comment", denotationSize)) {
|
||||
return KnownField::Comment;
|
||||
} else if(!strncmp(denotation, "bpm", denotationSize)) {
|
||||
return KnownField::Bpm;
|
||||
} else if(!strncmp(denotation, "bps", denotationSize)) {
|
||||
return KnownField::Bps;
|
||||
} else if(!strncmp(denotation, "lyricist", denotationSize)) {
|
||||
return KnownField::Lyricist;
|
||||
} else if(!strncmp(denotation, "track", denotationSize)) {
|
||||
return KnownField::TrackPosition;
|
||||
} else if(!strncmp(denotation, "disk", denotationSize)) {
|
||||
return KnownField::DiskPosition;
|
||||
} else if(!strncmp(denotation, "part", denotationSize)) {
|
||||
return KnownField::PartNumber;
|
||||
} else if(!strncmp(denotation, "totalparts", denotationSize)) {
|
||||
return KnownField::TotalParts;
|
||||
} else if(!strncmp(denotation, "encoder", denotationSize)) {
|
||||
return KnownField::Encoder;
|
||||
} else if(!strncmp(denotation, "recorddate", denotationSize)) {
|
||||
return KnownField::RecordDate;
|
||||
} else if(!strncmp(denotation, "performers", denotationSize)) {
|
||||
return KnownField::Performers;
|
||||
} else if(!strncmp(denotation, "duration", denotationSize)) {
|
||||
return KnownField::Length;
|
||||
} else if(!strncmp(denotation, "language", denotationSize)) {
|
||||
return KnownField::Language;
|
||||
} else if(!strncmp(denotation, "encodersettings", denotationSize)) {
|
||||
return KnownField::EncoderSettings;
|
||||
} else if(!strncmp(denotation, "lyrics", denotationSize)) {
|
||||
return KnownField::Lyrics;
|
||||
} else if(!strncmp(denotation, "synchronizedlyrics", denotationSize)) {
|
||||
return KnownField::SynchronizedLyrics;
|
||||
} else if(!strncmp(denotation, "grouping", denotationSize)) {
|
||||
return KnownField::Grouping;
|
||||
} else if(!strncmp(denotation, "recordlabel", denotationSize)) {
|
||||
return KnownField::RecordLabel;
|
||||
} else if(!strncmp(denotation, "cover", denotationSize)) {
|
||||
return KnownField::Cover;
|
||||
} else if(!strncmp(denotation, "composer", denotationSize)) {
|
||||
return KnownField::Composer;
|
||||
} else if(!strncmp(denotation, "rating", denotationSize)) {
|
||||
return KnownField::Rating;
|
||||
} else if(!strncmp(denotation, "description", denotationSize)) {
|
||||
return KnownField::Description;
|
||||
} else {
|
||||
throw ConversionException("generic field name is unknown");
|
||||
static const struct {
|
||||
const char *knownDenotation;
|
||||
KnownField knownField;
|
||||
} fieldMapping[] = {
|
||||
{"title", KnownField::Title},
|
||||
{"album", KnownField::Album},
|
||||
{"artist", KnownField::Artist},
|
||||
{"genre", KnownField::Genre},
|
||||
{"year", KnownField::Year},
|
||||
{"comment", KnownField::Comment},
|
||||
{"bpm", KnownField::Bpm},
|
||||
{"bps", KnownField::Bps},
|
||||
{"lyricist", KnownField::Lyricist},
|
||||
{"track", KnownField::TrackPosition},
|
||||
{"disk", KnownField::DiskPosition},
|
||||
{"part", KnownField::PartNumber},
|
||||
{"totalparts", KnownField::TotalParts},
|
||||
{"encoder", KnownField::Encoder},
|
||||
{"recorddate", KnownField::RecordDate},
|
||||
{"performers", KnownField::Performers},
|
||||
{"duration", KnownField::Length},
|
||||
{"language", KnownField::Language},
|
||||
{"encodersettings", KnownField::EncoderSettings},
|
||||
{"lyrics", KnownField::Lyrics},
|
||||
{"synchronizedlyrics", KnownField::SynchronizedLyrics},
|
||||
{"grouping", KnownField::Grouping},
|
||||
{"recordlabel", KnownField::RecordLabel},
|
||||
{"cover", KnownField::Cover},
|
||||
{"composer", KnownField::Composer},
|
||||
{"rating", KnownField::Rating},
|
||||
{"description", KnownField::Description},
|
||||
};
|
||||
for(const auto &mapping : fieldMapping) {
|
||||
if(!strncmp(denotation, mapping.knownDenotation, denotationSize)) {
|
||||
return FieldId(mapping.knownField, nullptr, 0);
|
||||
}
|
||||
}
|
||||
throw ConversionException("generic field name is unknown");
|
||||
}
|
||||
|
||||
FieldId FieldId::fromTrackDenotation(const char *denotation, size_t denotationSize)
|
||||
{
|
||||
return FieldId(KnownField::Invalid, denotation, denotationSize);
|
||||
}
|
||||
|
||||
std::vector<const TagValue *> FieldId::values(const Tag *tag, TagType tagType) const
|
||||
{
|
||||
if(m_nativeField) {
|
||||
if(!m_nativeField.empty()) {
|
||||
return m_valuesForNativeField(tag, tagType);
|
||||
} else {
|
||||
return tag->values(m_knownField);
|
||||
|
@ -531,7 +552,7 @@ std::vector<const TagValue *> FieldId::values(const Tag *tag, TagType tagType) c
|
|||
|
||||
bool FieldId::setValues(Tag *tag, TagType tagType, const std::vector<TagValue> &values) const
|
||||
{
|
||||
if(m_nativeField) {
|
||||
if(!m_nativeField.empty()) {
|
||||
return m_setValuesForNativeField(tag, tagType, values);
|
||||
} else {
|
||||
return tag->setValues(m_knownField, values);
|
||||
|
@ -554,4 +575,14 @@ string tagName(const Tag *tag)
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
bool stringToBool(const string &str)
|
||||
{
|
||||
if(str == "yes" || str == "true" || str == "1") {
|
||||
return true;
|
||||
} else if(str == "no" || str == "false" || str == "0") {
|
||||
return false;
|
||||
}
|
||||
throw ConversionException(argsToString('\"', str, " is not yes or no"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
72
cli/helper.h
72
cli/helper.h
|
@ -54,37 +54,42 @@ inline TagType &operator|= (TagType &lhs, TagType rhs)
|
|||
|
||||
class FieldId
|
||||
{
|
||||
friend struct std::hash<FieldId>;
|
||||
|
||||
public:
|
||||
FieldId(KnownField m_knownField = KnownField::Invalid);
|
||||
static FieldId fromDenotation(const char *denotation, std::size_t denotationSize);
|
||||
FieldId(KnownField m_knownField = KnownField::Invalid, const char *denotation = nullptr, std::size_t denotationSize = 0);
|
||||
static FieldId fromTagDenotation(const char *denotation, std::size_t denotationSize);
|
||||
static FieldId fromTrackDenotation(const char *denotation, std::size_t denotationSize);
|
||||
bool operator ==(const FieldId &other) const;
|
||||
KnownField knownField() const;
|
||||
const char *nativeField() const;
|
||||
const char *name() const;
|
||||
bool denotes(const char *knownDenotation) const;
|
||||
const std::string &denotation() const;
|
||||
std::vector<const TagValue *> values(const Tag *tag, TagType tagType) const;
|
||||
bool setValues(Tag *tag, TagType tagType, const std::vector<TagValue> &values) const;
|
||||
|
||||
private:
|
||||
typedef std::function<std::vector<const TagValue *>(const Tag *, TagType)> GetValuesForNativeFieldType;
|
||||
typedef std::function<bool(Tag *, TagType, const std::vector<TagValue> &)> SetValuesForNativeFieldType;
|
||||
FieldId(const char *m_nativeField, const GetValuesForNativeFieldType &valuesForNativeField, const SetValuesForNativeFieldType &setValuesForNativeField);
|
||||
FieldId(const char *nativeField, std::size_t nativeFieldSize, const GetValuesForNativeFieldType &valuesForNativeField, const SetValuesForNativeFieldType &setValuesForNativeField);
|
||||
template<class ConcreteTag>
|
||||
static FieldId fromNativeField(const char *nativeFieldId, std::size_t nativeFieldIdSize = std::string::npos);
|
||||
static FieldId fromNativeField(const char *nativeFieldId, std::size_t nativeFieldIdSize);
|
||||
|
||||
KnownField m_knownField;
|
||||
const char *m_nativeField;
|
||||
std::string m_denotation;
|
||||
std::string m_nativeField;
|
||||
GetValuesForNativeFieldType m_valuesForNativeField;
|
||||
SetValuesForNativeFieldType m_setValuesForNativeField;
|
||||
};
|
||||
|
||||
inline FieldId::FieldId(KnownField knownField) :
|
||||
inline FieldId::FieldId(KnownField knownField, const char *denotation, std::size_t denotationSize) :
|
||||
m_knownField(knownField),
|
||||
m_nativeField(nullptr)
|
||||
m_denotation(denotation, denotationSize)
|
||||
{}
|
||||
|
||||
inline bool FieldId::operator ==(const FieldId &other) const
|
||||
{
|
||||
return m_knownField == other.m_knownField && m_nativeField == other.m_nativeField;
|
||||
return (m_knownField == other.m_knownField || m_denotation == other.m_denotation) && m_nativeField == other.m_nativeField;
|
||||
}
|
||||
|
||||
inline KnownField FieldId::knownField() const
|
||||
|
@ -92,29 +97,39 @@ inline KnownField FieldId::knownField() const
|
|||
return m_knownField;
|
||||
}
|
||||
|
||||
inline const char *FieldId::nativeField() const
|
||||
{
|
||||
return m_nativeField;
|
||||
}
|
||||
|
||||
inline const char *FieldId::name() const
|
||||
{
|
||||
return m_nativeField ? m_nativeField : Settings::KnownFieldModel::fieldName(m_knownField);
|
||||
return !m_nativeField.empty() ? m_nativeField.data() : Settings::KnownFieldModel::fieldName(m_knownField);
|
||||
}
|
||||
|
||||
inline bool FieldId::denotes(const char *knownDenotation) const
|
||||
{
|
||||
return !std::strncmp(m_denotation.data(), knownDenotation, m_denotation.size());
|
||||
}
|
||||
|
||||
inline const std::string &FieldId::denotation() const
|
||||
{
|
||||
return m_denotation;
|
||||
}
|
||||
|
||||
struct FieldScope
|
||||
{
|
||||
FieldScope(KnownField field = KnownField::Invalid, TagType tagType = TagType::Unspecified, TagTarget tagTarget = TagTarget());
|
||||
bool operator ==(const FieldScope &other) const;
|
||||
bool isTrack() const;
|
||||
|
||||
FieldId field;
|
||||
TagType tagType;
|
||||
TagTarget tagTarget;
|
||||
bool allTracks;
|
||||
std::vector<uint64> trackIds;
|
||||
};
|
||||
|
||||
inline FieldScope::FieldScope(KnownField field, TagType tagType, TagTarget tagTarget) :
|
||||
field(field),
|
||||
tagType(tagType),
|
||||
tagTarget(tagTarget)
|
||||
tagTarget(tagTarget),
|
||||
allTracks(false)
|
||||
{}
|
||||
|
||||
inline bool FieldScope::operator ==(const FieldScope &other) const
|
||||
|
@ -122,6 +137,11 @@ inline bool FieldScope::operator ==(const FieldScope &other) const
|
|||
return field == other.field && tagType == other.tagType && tagTarget == other.tagTarget;
|
||||
}
|
||||
|
||||
inline bool FieldScope::isTrack() const
|
||||
{
|
||||
return allTracks || !trackIds.empty();
|
||||
}
|
||||
|
||||
struct FieldValue
|
||||
{
|
||||
FieldValue(DenotationType type, unsigned int fileIndex, const char *value);
|
||||
|
@ -181,7 +201,7 @@ template <> struct hash<TagTarget>
|
|||
{
|
||||
using std::hash;
|
||||
return ((hash<uint64>()(target.level())
|
||||
^ (hash<TagTarget::IdContainerType>()(target.tracks()) << 1)) >> 1)
|
||||
^ (hash<TagTarget::IdContainerType>()(target.tracks()) << 1)) >> 1)
|
||||
^ (hash<TagTarget::IdContainerType>()(target.attachments()) << 1);
|
||||
}
|
||||
};
|
||||
|
@ -191,8 +211,8 @@ template <> struct hash<FieldId>
|
|||
std::size_t operator()(const FieldId &id) const
|
||||
{
|
||||
using std::hash;
|
||||
return (hash<KnownField>()(id.knownField())
|
||||
^ (hash<const char *>()(id.nativeField()) << 1));
|
||||
return ((id.knownField() != KnownField::Invalid) ? hash<KnownField>()(id.knownField()) : hash<string>()(id.denotation()))
|
||||
^ (hash<string>()(id.m_nativeField) << 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -201,9 +221,10 @@ template <> struct hash<FieldScope>
|
|||
std::size_t operator()(const FieldScope &scope) const
|
||||
{
|
||||
using std::hash;
|
||||
return ((hash<FieldId>()(scope.field)
|
||||
^ (hash<TagType>()(scope.tagType) << 1)) >> 1)
|
||||
^ (hash<TagTarget>()(scope.tagTarget) << 1);
|
||||
return (hash<FieldId>()(scope.field)
|
||||
^ (hash<TagType>()(scope.tagType) << 1) >> 1)
|
||||
^ (hash<TagTarget>()(scope.tagTarget) ^ (static_cast<unsigned long>(scope.allTracks) << 4)
|
||||
^ (hash<vector<uint64>>()(scope.trackIds) << 1) >> 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -211,7 +232,11 @@ template <> struct hash<FieldScope>
|
|||
|
||||
namespace Cli {
|
||||
|
||||
typedef std::vector<FieldValue> FieldValues;
|
||||
struct FieldValues
|
||||
{
|
||||
std::vector<FieldValue> allValues;
|
||||
std::vector<FieldValue *> relevantValues;
|
||||
};
|
||||
typedef std::unordered_map<FieldScope, FieldValues> FieldDenotations;
|
||||
|
||||
// declare/define actual helpers
|
||||
|
@ -265,6 +290,7 @@ TagTarget::IdContainerType parseIds(const std::string &concatenatedIds);
|
|||
bool applyTargetConfiguration(TagTarget &target, const std::string &configStr);
|
||||
FieldDenotations parseFieldDenotations(const ApplicationUtilities::Argument &fieldsArg, bool readOnly);
|
||||
std::string tagName(const Tag *tag);
|
||||
bool stringToBool(const std::string &str);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -161,6 +161,9 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const
|
|||
printProperty("ID", track->id(), nullptr, true);
|
||||
printProperty("Name", track->name());
|
||||
printProperty("Type", track->mediaTypeName());
|
||||
if(track->language() != "und") {
|
||||
printProperty("Language", track->language());
|
||||
}
|
||||
const char *fmtName = track->formatName(), *fmtAbbr = track->formatAbbreviation();
|
||||
printProperty("Format", fmtName);
|
||||
if(strcmp(fmtName, fmtAbbr)) {
|
||||
|
@ -188,6 +191,29 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const
|
|||
printProperty("Sample count", track->sampleCount());
|
||||
printProperty("Creation time", track->creationTime());
|
||||
printProperty("Modification time", track->modificationTime());
|
||||
vector<string> labels;
|
||||
labels.reserve(7);
|
||||
if(track->isInterlaced()) {
|
||||
labels.emplace_back("interlaced");
|
||||
}
|
||||
if(!track->isEnabled()) {
|
||||
labels.emplace_back("disabled");
|
||||
}
|
||||
if(track->isDefault()) {
|
||||
labels.emplace_back("default");
|
||||
}
|
||||
if(track->isForced()) {
|
||||
labels.emplace_back("forced");
|
||||
}
|
||||
if(track->hasLacing()) {
|
||||
labels.emplace_back("has lacing");
|
||||
}
|
||||
if(track->isEncrypted()) {
|
||||
labels.emplace_back("encrypted");
|
||||
}
|
||||
if(!labels.empty()) {
|
||||
printProperty("Labeled as", joinStrings(labels, ", "));
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
} else {
|
||||
|
@ -323,7 +349,7 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
vector<TagTarget> requiredTargets;
|
||||
for(const auto &fieldDenotation : fields) {
|
||||
const FieldScope &scope = fieldDenotation.first;
|
||||
if(find(requiredTargets.cbegin(), requiredTargets.cend(), scope.tagTarget) == requiredTargets.cend()) {
|
||||
if(!scope.isTrack() && find(requiredTargets.cbegin(), requiredTargets.cend(), scope.tagTarget) == requiredTargets.cend()) {
|
||||
requiredTargets.push_back(scope.tagTarget);
|
||||
}
|
||||
}
|
||||
|
@ -411,8 +437,29 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
cerr << "Warning: Setting the document title is not supported for the file." << endl;
|
||||
}
|
||||
}
|
||||
// select the relevant values for the current file index
|
||||
for(auto &fieldDenotation : fields) {
|
||||
FieldValues &denotedValues = fieldDenotation.second;
|
||||
vector<FieldValue *> &relevantDenotedValues = denotedValues.relevantValues;
|
||||
denotedValues.relevantValues.clear();
|
||||
unsigned int currentFileIndex = 0;
|
||||
for(FieldValue &denotatedValue : denotedValues.allValues) {
|
||||
if(denotatedValue.fileIndex <= fileIndex) {
|
||||
if(relevantDenotedValues.empty() || (denotatedValue.fileIndex >= currentFileIndex)) {
|
||||
if(currentFileIndex != denotatedValue.fileIndex) {
|
||||
currentFileIndex = denotatedValue.fileIndex;
|
||||
relevantDenotedValues.clear();
|
||||
}
|
||||
relevantDenotedValues.push_back(&denotatedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileInfo.tags(tags);
|
||||
if(!tags.empty()) {
|
||||
if(tags.empty()) {
|
||||
fileInfo.addNotification(NotificationType::Critical, "Can not create appropriate tags for file.", context);
|
||||
} else {
|
||||
// iterate through all tags
|
||||
for(auto *tag : tags) {
|
||||
// clear current values if option is present
|
||||
|
@ -432,30 +479,15 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
}
|
||||
}
|
||||
// iterate through all denoted field values
|
||||
for(auto &fieldDenotation : fields) {
|
||||
for(const auto &fieldDenotation : fields) {
|
||||
const FieldScope &denotedScope = fieldDenotation.first;
|
||||
FieldValues &denotedValues = fieldDenotation.second;
|
||||
// decide whether the scope of the denotation matches of the current tag
|
||||
if((denotedScope.tagType == TagType::Unspecified
|
||||
if(!denotedScope.isTrack() && (denotedScope.tagType == TagType::Unspecified
|
||||
|| (denotedScope.tagType & tagType) != TagType::Unspecified)
|
||||
&& (!targetSupported || denotedScope.tagTarget == tagTarget)) {
|
||||
// select the relevant values for the current file index
|
||||
vector<FieldValue *> relevantDenotedValues;
|
||||
unsigned int currentFileIndex = 0;
|
||||
for(FieldValue &denotatedValue : denotedValues) {
|
||||
if(denotatedValue.fileIndex <= fileIndex) {
|
||||
if(relevantDenotedValues.empty() || (denotatedValue.fileIndex >= currentFileIndex)) {
|
||||
if(currentFileIndex != denotatedValue.fileIndex) {
|
||||
currentFileIndex = denotatedValue.fileIndex;
|
||||
relevantDenotedValues.clear();
|
||||
}
|
||||
relevantDenotedValues.push_back(&denotatedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
// convert the values to TagValue
|
||||
vector<TagValue> convertedValues;
|
||||
for(FieldValue *relevantDenotedValue : relevantDenotedValues) {
|
||||
for(const FieldValue *relevantDenotedValue : fieldDenotation.second.relevantValues) {
|
||||
// one of the denoted values
|
||||
if(!relevantDenotedValue->value.empty()) {
|
||||
if(relevantDenotedValue->type == DenotationType::File) {
|
||||
|
@ -478,9 +510,6 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
}
|
||||
} else {
|
||||
convertedValues.emplace_back(relevantDenotedValue->value, TagTextEncoding::Utf8, usedEncoding);
|
||||
if(relevantDenotedValue->type == DenotationType::Increment && tag == tags.back()) {
|
||||
relevantDenotedValue->value = incremented(relevantDenotedValue->value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if the denoted value is empty, just assign an empty TagValue to remove the field
|
||||
|
@ -496,8 +525,48 @@ void setTagInfo(const SetTagInfoArgs &args)
|
|||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fileInfo.addNotification(NotificationType::Critical, "Can not create appropriate tags for file.", context);
|
||||
}
|
||||
for(AbstractTrack *track : fileInfo.tracks()) {
|
||||
for(const auto &fieldDenotation : fields) {
|
||||
const auto &values = fieldDenotation.second.relevantValues;
|
||||
if(values.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const FieldScope &denotedScope = fieldDenotation.first;
|
||||
// decide whether the scope of the denotation matches of the current track
|
||||
if(denotedScope.allTracks || find(denotedScope.trackIds.cbegin(), denotedScope.trackIds.cend(), track->id()) != denotedScope.trackIds.cend()) {
|
||||
const FieldId &field = denotedScope.field;
|
||||
const string &value = values.front()->value;
|
||||
try {
|
||||
if(field.denotes("name")) {
|
||||
track->setName(value);
|
||||
} else if(field.denotes("language")) {
|
||||
track->setLanguage(value);
|
||||
} else if(field.denotes("tracknumber")) {
|
||||
track->setTrackNumber(stringToNumber<uint32>(value));
|
||||
} else if(field.denotes("enabled")) {
|
||||
track->setEnabled(stringToBool(value));
|
||||
} else if(field.denotes("forced")) {
|
||||
track->setForced(stringToBool(value));
|
||||
} else if(field.denotes("default")) {
|
||||
track->setDefault(stringToBool(value));
|
||||
} else {
|
||||
fileInfo.addNotification(NotificationType::Critical, argsToString("Denoted track property name \"", field.denotation(), "\" is invalid"), argsToString("setting meta-data of track ", track->id()));
|
||||
}
|
||||
} catch(const ConversionException &e) {
|
||||
fileInfo.addNotification(NotificationType::Critical, argsToString("Unable to parse value for track property \"", field.denotation(), "\": ", e.what()), argsToString("setting meta-data of track ", track->id()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// increment relevant values
|
||||
for(auto &fieldDenotation : fields) {
|
||||
for(FieldValue *relevantDenotedValue : fieldDenotation.second.relevantValues) {
|
||||
if(!relevantDenotedValue->value.empty() && relevantDenotedValue->type == DenotationType::Increment) {
|
||||
relevantDenotedValue->value = incremented(relevantDenotedValue->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool attachmentsModified = false;
|
||||
if(args.addAttachmentArg.isPresent() || args.updateAttachmentArg.isPresent() || args.removeAttachmentArg.isPresent() || args.removeExistingAttachmentsArg.isPresent()) {
|
||||
|
|
|
@ -42,6 +42,7 @@ class CliTests : public TestFixture
|
|||
CPPUNIT_TEST(testMultipleValuesPerField);
|
||||
CPPUNIT_TEST(testHandlingAttachments);
|
||||
CPPUNIT_TEST(testDisplayingInfo);
|
||||
CPPUNIT_TEST(testSettingTrackMetaData);
|
||||
CPPUNIT_TEST(testExtraction);
|
||||
CPPUNIT_TEST(testReadingAndWritingDocumentTitle);
|
||||
CPPUNIT_TEST(testFileLayoutOptions);
|
||||
|
@ -63,6 +64,7 @@ public:
|
|||
void testMultipleValuesPerField();
|
||||
void testHandlingAttachments();
|
||||
void testDisplayingInfo();
|
||||
void testSettingTrackMetaData();
|
||||
void testExtraction();
|
||||
void testReadingAndWritingDocumentTitle();
|
||||
void testFileLayoutOptions();
|
||||
|
@ -639,6 +641,94 @@ void CliTests::testDisplayingInfo()
|
|||
" Modification time 2014-12-10 16:22:41"}));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests setting track meta-data.
|
||||
*/
|
||||
void CliTests::testSettingTrackMetaData()
|
||||
{
|
||||
cout << "\nSetting track meta-data" << endl;
|
||||
string stdout, stderr;
|
||||
|
||||
// test Matroska file
|
||||
const string mkvFile(workingCopyPath("matroska_wave1/test2.mkv"));
|
||||
const string mp4File(workingCopyPath("mtx-test-data/aac/he-aacv2-ps.m4a"));
|
||||
const char *const args1[] = {"tageditor", "set", "title=title of tag",
|
||||
"track=1863976627", "name=video track",
|
||||
"track=3134325680", "name=audio track", "language=ger", "default=yes", "forced=yes",
|
||||
"tag=any", "artist=setting tag value again",
|
||||
"track=any", "name1=sbr and ps", "language1=eng",
|
||||
"-f", mkvFile.data(), mp4File.data(), nullptr};
|
||||
const char *const args2[] = {"tageditor", "info", "-f", mkvFile.data(), nullptr};
|
||||
const char *const args3[] = {"tageditor", "get", "-f", mkvFile.data(), nullptr};
|
||||
TESTUTILS_ASSERT_EXEC(args1);
|
||||
TESTUTILS_ASSERT_EXEC(args2);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
" Container format: Matroska\n"
|
||||
" Document type matroska\n"
|
||||
" Read version 1\n"
|
||||
" Version 1\n"
|
||||
" Document read version 2\n"
|
||||
" Document version 2\n"
|
||||
" Duration 47 s 509 ms\n"
|
||||
" Tag position before data\n"
|
||||
" Index position before data\n",
|
||||
" Tracks:\n"
|
||||
" ID 1863976627\n"
|
||||
" Name video track\n"
|
||||
" Type Video\n"
|
||||
" Format Advanced Video Coding Main Profile\n"
|
||||
" Abbreviation H.264\n"
|
||||
" Raw format ID V_MPEG4/ISO/AVC\n"
|
||||
" FPS 24\n",
|
||||
" ID 3134325680\n"
|
||||
" Name audio track\n"
|
||||
" Type Audio\n"
|
||||
" Language ger\n"
|
||||
" Format Advanced Audio Coding Low Complexity Profile\n"
|
||||
" Abbreviation MPEG-4 AAC-LC\n"
|
||||
" Raw format ID A_AAC\n"
|
||||
" Channel config 2 channels: front-left, front-right\n"
|
||||
" Sampling frequency 48000 Hz\n"
|
||||
" Labeled as default, forced"}));
|
||||
TESTUTILS_ASSERT_EXEC(args3);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
"Matroska tag targeting \"level 50 'album, opera, concert, movie, episode'\"\n"
|
||||
" Title title of tag\n"
|
||||
" Artist setting tag value again\n"
|
||||
" Year 2010\n"
|
||||
" Comment Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC"
|
||||
}));
|
||||
|
||||
const char *const args4[] = {"tageditor", "info", "-f", mp4File.data(), nullptr};
|
||||
TESTUTILS_ASSERT_EXEC(args4);
|
||||
CPPUNIT_ASSERT(testContainsSubstrings(stdout, {
|
||||
" Container format: MPEG-4 Part 14\n"
|
||||
" Document type mp42\n"
|
||||
" Duration 3 min\n"
|
||||
" Creation time 2014-12-10 16:22:41\n"
|
||||
" Modification time 2014-12-10 16:22:41\n",
|
||||
" Tracks:\n"
|
||||
" ID 1\n"
|
||||
" Name sbr and ps\n"
|
||||
" Type Audio\n"
|
||||
" Language eng\n"
|
||||
" Format Advanced Audio Coding Low Complexity Profile\n"
|
||||
" Abbreviation MPEG-4 AAC-LC\n"
|
||||
" Extensions Spectral Band Replication and Parametric Stereo / HE-AAC v2\n"
|
||||
" Raw format ID mp4a\n"
|
||||
" Size 879.65 KiB (900759 byte)\n"
|
||||
" Duration 3 min 138 ms\n"
|
||||
" Channel config 1 channel: front-center\n"
|
||||
" Extension channel config 2 channels: front-left, front-right\n"
|
||||
" Bitrate 40 kbit/s\n"
|
||||
" Bits per sample 16\n"
|
||||
" Sampling frequency 24000 Hz\n"
|
||||
" Extension sampling frequency 48000 Hz\n"
|
||||
" Sample count 4222\n"
|
||||
" Creation time 2014-12-10 16:22:41\n"
|
||||
" Modification time 2014-12-10 16:22:41"}));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests extraction of field values (used to extract cover or other binary fields).
|
||||
* \remarks Extraction of attachments is already tested in testHandlingAttachments().
|
||||
|
|
Loading…
Reference in New Issue