Allow setting multiple values per field via CLI

This commit is contained in:
Martchus 2016-08-14 22:47:50 +02:00
parent 1a7f87e21d
commit 71ca26d365
2 changed files with 56 additions and 25 deletions

View File

@ -28,6 +28,7 @@
#include <cstring>
#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace std;
using namespace ApplicationUtilities;
@ -419,6 +420,11 @@ FieldDenotations parseFieldDenotations(const Argument &fieldsArg, bool readOnly)
} else if(applyTargetConfiguration(scope.tagTarget, fieldDenotationString)) {
continue;
}
// check whether field name starts with + indicating an additional value
bool additionalValue = *fieldDenotationString == '+';
if(additionalValue) {
++fieldDenotationString;
}
// read field name
const auto equationPos = strchr(fieldDenotationString, '=');
size_t fieldNameLen = equationPos ? static_cast<size_t>(equationPos - fieldDenotationString) : strlen(fieldDenotationString);
@ -516,11 +522,14 @@ FieldDenotations parseFieldDenotations(const Argument &fieldsArg, bool readOnly)
cerr << "Warning: Specified value for \"" << string(fieldDenotationString, fieldNameLen) << "\" will be ignored." << endl;
} else {
// file index might have been specified explicitely
// if not (mult == 1) use the index of the last value and increase it by one
// 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 + 1) : fileIndex, (equationPos + 1)));
fieldValues.emplace_back(FieldValue(type, mult == 1 ? (fieldValues.empty() ? 0 : fieldValues.back().fileIndex + (additionalValue ? 0 : 1)) : fileIndex, (equationPos + 1)));
}
}
if(additionalValue && readOnly) {
cerr << "Warning: Indication of an additional value for \"" << string(fieldDenotationString, fieldNameLen) << "\" will be ignored." << endl;
}
}
}
return fields;
@ -963,7 +972,6 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A
cout << endl;
}
}
}
} else {
for(const auto &fieldDenotation : fields) {
@ -973,6 +981,13 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A
const char *fieldName = KnownFieldModel::fieldName(denotedScope.field);
const auto fieldNameLen = strlen(fieldName);
if(values.empty()) {
// write field name
const char *fieldName = KnownFieldModel::fieldName(denotedScope.field);
cout << ' ' << fieldName;
// write padding
for(auto i = fieldNameLen; i < 18; ++i) {
cout << ' ';
}
cout << "none";
} else {
for(const auto &value : values) {
@ -1155,22 +1170,29 @@ void setTagInfo(const SetTagInfoArgs &args)
if((denotedScope.tagType == TagType::Unspecified
|| (denotedScope.tagType & tagType) != TagType::Unspecified)
&& (!targetSupported || denotedScope.tagTarget == tagTarget)) {
// select the value for the current file index
FieldValue *selectedDenotedValue = nullptr;
// 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(!selectedDenotedValue || (denotatedValue.fileIndex > selectedDenotedValue->fileIndex)) {
selectedDenotedValue = &denotatedValue;
if(relevantDenotedValues.empty() || (denotatedValue.fileIndex >= currentFileIndex)) {
if(currentFileIndex != denotatedValue.fileIndex) {
currentFileIndex = denotatedValue.fileIndex;
relevantDenotedValues.clear();
}
relevantDenotedValues.push_back(&denotatedValue);
}
}
}
if(selectedDenotedValue) {
// convert the values to TagValue
vector<TagValue> convertedValues;
for(FieldValue *relevantDenotedValue : relevantDenotedValues) {
// one of the denoted values
if(!selectedDenotedValue->value.empty()) {
if(selectedDenotedValue->type == DenotationType::File) {
if(!relevantDenotedValue->value.empty()) {
if(relevantDenotedValue->type == DenotationType::File) {
try {
// assume the file refers to a picture
MediaFileInfo fileInfo(selectedDenotedValue->value);
MediaFileInfo fileInfo(relevantDenotedValue->value);
fileInfo.open(true);
fileInfo.parseContainerFormat();
auto buff = make_unique<char []>(fileInfo.size());
@ -1178,7 +1200,7 @@ void setTagInfo(const SetTagInfoArgs &args)
fileInfo.stream().read(buff.get(), fileInfo.size());
TagValue value(move(buff), fileInfo.size(), TagDataType::Picture);
value.setMimeType(fileInfo.mimeType());
tag->setValue(denotedScope.field, move(value));
convertedValues.emplace_back(move(value));
} catch(const Media::Failure &) {
fileInfo.addNotification(NotificationType::Critical, "Unable to parse specified cover file.", context);
} catch(...) {
@ -1190,16 +1212,18 @@ void setTagInfo(const SetTagInfoArgs &args)
if(!tag->canEncodingBeUsed(denotedEncoding)) {
usedEncoding = tag->proposedTextEncoding();
}
tag->setValue(denotedScope.field, TagValue(selectedDenotedValue->value, TagTextEncoding::Utf8, usedEncoding));
if(selectedDenotedValue->type == DenotationType::Increment && tag == tags.back()) {
selectedDenotedValue->value = incremented(selectedDenotedValue->value);
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
tag->setValue(denotedScope.field, TagValue());
convertedValues.emplace_back();
}
}
// finally set the values
tag->setValues(denotedScope.field, convertedValues);
}
}
}

View File

@ -348,27 +348,34 @@ void CliTests::testOutputFile()
/*!
* \brief Tests tagging multiple values per field.
* \remarks Fails because feature has not been implemented yet.
*/
void CliTests::testMultipleValuesPerField()
{
cout << "\nMultiple values per field" << endl;
string stdout, stderr;
const string mkvFile(workingCopyPath("matroska_wave1/test1.mkv"));
const char *const args1[] = {"tageditor", "get", "-f", mkvFile.data(), nullptr};
const char *const args2[] = {"tageditor", "set", "artist=test1", "+artist=test2", "+artist=test3", "-f", mkvFile.data(), nullptr};
TESTUTILS_ASSERT_EXEC(args2);
const string mkvFile1(workingCopyPath("matroska_wave1/test1.mkv"));
const string mkvFile2(workingCopyPath("matroska_wave1/test2.mkv"));
const char *const args1[] = {"tageditor", "set", "artist=test1", "+artist=test2", "+artist=test3", "artist=test4", "-f", mkvFile1.data(), mkvFile2.data(), nullptr};
TESTUTILS_ASSERT_EXEC(args1);
//cout << stdout << endl;
//cerr << stderr << endl;
const char *const args2[] = {"tageditor", "get", "-f", mkvFile1.data(), nullptr};
TESTUTILS_ASSERT_EXEC(args2);
CPPUNIT_ASSERT(containsSubstrings(stdout, {
"Artist test1",
"Artist test2",
"Artist test3"
}));
CPPUNIT_ASSERT(stdout.find("Artist test4") == string::npos); // should be in mkvFile2
remove(mkvFile.c_str());
remove((mkvFile + ".bak").c_str());
const char *const args3[] = {"tageditor", "get", "-f", mkvFile2.data(), nullptr};
TESTUTILS_ASSERT_EXEC(args3);
CPPUNIT_ASSERT(stdout.find("Artist test1") == string::npos);
CPPUNIT_ASSERT(stdout.find("Artist test2") == string::npos);
CPPUNIT_ASSERT(stdout.find("Artist test3") == string::npos);
CPPUNIT_ASSERT(stdout.find("Artist test4") != string::npos);
remove(mkvFile1.c_str()), remove((mkvFile1 + ".bak").c_str());
remove(mkvFile2.c_str()), remove((mkvFile2 + ".bak").c_str());
}
/*!