Allow setting track meta-data

This commit is contained in:
Martchus 2017-06-14 00:09:10 +02:00
parent 297e95a90b
commit 6fb6d3dcbe
4 changed files with 331 additions and 115 deletions

View File

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

View File

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

View File

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

View File

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