Allow setting multiple covers of the same type but different descriptions
The standard actually says that there might be different covers with only different descriptions: Description is a short description of the picture, represented as a terminated text string. There may be several pictures attached to one file, each in their individual "APIC" frame, but only one with the same content descriptor. There may only be one picture with the picture type declared as picture type $01 and $02 respectively. I assume "content descriptor" means combination of the type and description. This is in accordance with eyeD3. See https://github.com/Martchus/tageditor/issues/64#issuecomment-833952940
This commit is contained in:
parent
49b0cdb1cd
commit
1944773022
22
README.md
22
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.
|
||||
|
||||
|
|
|
@ -385,30 +385,47 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &showUnsupportedAr
|
|||
}
|
||||
}
|
||||
|
||||
struct Id3v2Cover {
|
||||
explicit Id3v2Cover(TagValue &&value, CoverType type, std::optional<std::string_view> description)
|
||||
: value(std::move(value))
|
||||
, type(type)
|
||||
, description(description)
|
||||
{
|
||||
}
|
||||
TagValue value;
|
||||
CoverType type;
|
||||
std::optional<std::string_view> description;
|
||||
};
|
||||
|
||||
template <class TagType>
|
||||
bool fieldPredicate(CoverType coverType, const std::pair<typename TagType::IdentifierType, typename TagType::FieldType> &pair)
|
||||
bool fieldPredicate(CoverType coverType, std::optional<std::string_view> description,
|
||||
const std::pair<typename TagType::IdentifierType, typename TagType::FieldType> &pair)
|
||||
{
|
||||
return pair.second.isTypeInfoAssigned() ? (pair.second.typeInfo() == static_cast<typename TagType::FieldType::TypeInfoType>(coverType))
|
||||
: (coverType == 0);
|
||||
const auto &[fieldId, field] = pair;
|
||||
const auto typeMatches
|
||||
= field.isTypeInfoAssigned() ? (field.typeInfo() == static_cast<typename TagType::FieldType::TypeInfoType>(coverType)) : (coverType == 0);
|
||||
const auto descMatches = !description.has_value() || field.value().description() == description.value();
|
||||
return typeMatches && descMatches;
|
||||
}
|
||||
|
||||
template <class TagType> static void setId3v2CoverValues(TagType *tag, std::vector<std::pair<TagValue, CoverType>> &&values)
|
||||
template <class TagType> static void setId3v2CoverValues(TagType *tag, std::vector<Id3v2Cover> &&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<TagType>, 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<TagType>, 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<TagType>, 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<TagType>, 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<TagValue>();
|
||||
auto convertedValuesWithCoverType = std::vector<std::pair<TagValue, CoverType>>();
|
||||
auto convertedId3v2CoverValues = std::vector<Id3v2Cover>();
|
||||
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<std::string_view>();
|
||||
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<Id3v2Tag *>(tag), std::move(convertedValuesWithCoverType));
|
||||
setId3v2CoverValues(static_cast<Id3v2Tag *>(tag), std::move(convertedId3v2CoverValues));
|
||||
break;
|
||||
case TagType::VorbisComment:
|
||||
setId3v2CoverValues(static_cast<VorbisComment *>(tag), std::move(convertedValuesWithCoverType));
|
||||
setId3v2CoverValues(static_cast<VorbisComment *>(tag), std::move(convertedId3v2CoverValues));
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue