diff --git a/README.md b/README.md index 3934659..38706c1 100644 --- a/README.md +++ b/README.md @@ -275,16 +275,22 @@ Here are some Bash examples which illustrate getting and setting tag information tageditor set cover=":front-cover" cover0="/path/to/back-cover.jpg:back-cover:The description" -f foo.mp3 ``` - - The syntax is `path:cover-type:description`. The cover type and description are optional. - - In this example the front cover is removed (by passing an empty path) and the back cover set to the specified - file. Other cover types are not affected. - - When specifying a cover without type, all existing covers are replaced and the new cover will be of the - type "other". - - To replace all existing covers when specifying a cover type - use e.g. `… cover= cover0="/path/to/back-cover.jpg:back-cover"`. - - The names of all cover types can be shown via `tageditor --print-field-names`. + - The general syntax is `path:cover-type:description`. The cover type and description are optional. - The `0` after the 2nd `cover` is required. Otherwise the 2nd cover would only be set in the 2nd file (which is not even specified in this example). + - In this example the front cover is removed (by passing an empty path) and the back cover set to the specified + file and description. Existing covers of other types and with other descriptions are not affected. + - When specifying a cover without type, all existing covers are replaced and the new cover will be of the + type "other". + - To replace all existing covers when specifying a cover type + use e.g. `… cover= cover0="/path/to/back-cover.jpg:back-cover"`. + - It is possible to add multiple covers of the same type with only different descriptions. However, when + leaving the description out (2nd `:` is absent), all existing covers of the specified type are replaced and + the new cover will have an empty description. + - To affect only the covers with an empty description, be sure to keep the trailing `:`. + - To remove all existing covers of a certain type but regardless of the description + use e.g. `… cover=":back-cover"`. + - The names of all cover types can be shown via `tageditor --print-field-names`. - This is only supported by the tag formats ID3v2 and Vorbis Comment. The type and description are ignored when dealing with a different format. diff --git a/cli/mainfeatures.cpp b/cli/mainfeatures.cpp index 8a35d61..baf9049 100644 --- a/cli/mainfeatures.cpp +++ b/cli/mainfeatures.cpp @@ -385,30 +385,47 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &showUnsupportedAr } } +struct Id3v2Cover { + explicit Id3v2Cover(TagValue &&value, CoverType type, std::optional description) + : value(std::move(value)) + , type(type) + , description(description) + { + } + TagValue value; + CoverType type; + std::optional description; +}; + template -bool fieldPredicate(CoverType coverType, const std::pair &pair) +bool fieldPredicate(CoverType coverType, std::optional description, + const std::pair &pair) { - return pair.second.isTypeInfoAssigned() ? (pair.second.typeInfo() == static_cast(coverType)) - : (coverType == 0); + const auto &[fieldId, field] = pair; + const auto typeMatches + = field.isTypeInfoAssigned() ? (field.typeInfo() == static_cast(coverType)) : (coverType == 0); + const auto descMatches = !description.has_value() || field.value().description() == description.value(); + return typeMatches && descMatches; } -template static void setId3v2CoverValues(TagType *tag, std::vector> &&values) +template static void setId3v2CoverValues(TagType *tag, std::vector &&values) { auto &fields = tag->fields(); const auto id = tag->fieldId(KnownField::Cover); const auto range = fields.equal_range(id); const auto first = range.first; - for (auto &[tagValue, coverType] : values) { - // check whether there is already a tag value with the current index/type - auto pair = find_if(first, range.second, std::bind(fieldPredicate, coverType, placeholders::_1)); + for (auto &[tagValue, coverType, description] : values) { + // check whether there is already a tag value with the current type and description + auto pair = std::find_if(first, range.second, std::bind(&fieldPredicate, coverType, description, placeholders::_1)); if (pair != range.second) { - // there is already a tag value with the current index/type + // there is already a tag value with the current type and description // -> update this value pair->second.setValue(tagValue); - // check whether there are more values with the current index/type assigned - while ((pair = find_if(++pair, range.second, std::bind(fieldPredicate, coverType, placeholders::_1))) != range.second) { - // -> remove these values as we only support one value of a type in the same tag + // check whether there are more values with the current type and description + while ((pair = std::find_if(++pair, range.second, std::bind(&fieldPredicate, coverType, description, placeholders::_1))) + != range.second) { + // -> remove these values as we only support one value of a type/description in the same tag pair->second.setValue(TagValue()); } } else if (!tagValue.isEmpty()) { @@ -645,7 +662,7 @@ void setTagInfo(const SetTagInfoArgs &args) } // convert the values to TagValue auto convertedValues = std::vector(); - auto convertedValuesWithCoverType = std::vector>(); + auto convertedId3v2CoverValues = std::vector(); for (const FieldValue *relevantDenotedValue : fieldDenotation.second.relevantValues) { // assign an empty TagValue to remove the field if denoted value is empty if (relevantDenotedValue->value.empty()) { @@ -675,8 +692,10 @@ void setTagInfo(const SetTagInfoArgs &args) value = TagValue(std::move(buff), coverFileInfo.size(), TagDataType::Picture); value.setMimeType(coverFileInfo.mimeType()); } + auto description = std::optional(); if (parts.size() > 2) { value.setDescription(parts[2], TagTextEncoding::Utf8); + description = parts[2]; } if (parts.size() > 1 && denotedScope.field.knownFieldForTag(tag, tagType) == KnownField::Cover && (tagType == TagType::Id3v2Tag || tagType == TagType::VorbisComment)) { @@ -686,7 +705,7 @@ void setTagInfo(const SetTagInfoArgs &args) argsToString("Specified cover type \"", parts[1], "\" is invalid. Ignoring the specified field/value."), context); } else { - convertedValuesWithCoverType.emplace_back(std::pair(std::move(value), coverType)); + convertedId3v2CoverValues.emplace_back(std::move(value), coverType, description); } } else { if (parts.size() > 1) { @@ -706,20 +725,20 @@ void setTagInfo(const SetTagInfoArgs &args) } // finally set the values try { - if (!convertedValues.empty() || convertedValuesWithCoverType.empty()) { + if (!convertedValues.empty() || convertedId3v2CoverValues.empty()) { denotedScope.field.setValues(tag, tagType, convertedValues); } } catch (const ConversionException &e) { diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse denoted field ID \"", denotedScope.field.name(), "\": ", e.what()), context); } - if (!convertedValuesWithCoverType.empty()) { + if (!convertedId3v2CoverValues.empty()) { switch (tagType) { case TagType::Id3v2Tag: - setId3v2CoverValues(static_cast(tag), std::move(convertedValuesWithCoverType)); + setId3v2CoverValues(static_cast(tag), std::move(convertedId3v2CoverValues)); break; case TagType::VorbisComment: - setId3v2CoverValues(static_cast(tag), std::move(convertedValuesWithCoverType)); + setId3v2CoverValues(static_cast(tag), std::move(convertedId3v2CoverValues)); break; default:; }