Allow setting covers of special types with description via CLI
See https://github.com/Martchus/tageditor/issues/64
This commit is contained in:
parent
8258a14089
commit
41fb2069f3
18
README.md
18
README.md
|
@ -270,6 +270,24 @@ Here are some Bash examples which illustrate getting and setting tag information
|
||||||
**Note**: The *+* sign after the field name *track* which indicates that the field value should be increased after
|
**Note**: The *+* sign after the field name *track* which indicates that the field value should be increased after
|
||||||
a file has been processed.
|
a file has been processed.
|
||||||
|
|
||||||
|
* Sets a cover of a special type with a description:
|
||||||
|
```
|
||||||
|
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 `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).
|
||||||
|
- This is only supported by the tag formats ID3v2 and Vorbis Comment. The type and description are ignored
|
||||||
|
when dealing with a different format.
|
||||||
|
|
||||||
## Text encoding / unicode support
|
## Text encoding / unicode support
|
||||||
1. It is possible to set the preferred encoding used *within* the tags via CLI option ``--encoding``
|
1. It is possible to set the preferred encoding used *within* the tags via CLI option ``--encoding``
|
||||||
and in the GUI settings.
|
and in the GUI settings.
|
||||||
|
|
112
cli/helper.cpp
112
cli/helper.cpp
|
@ -28,6 +28,39 @@ using namespace Settings;
|
||||||
|
|
||||||
namespace Cli {
|
namespace Cli {
|
||||||
|
|
||||||
|
const std::vector<std::string_view> &id3v2CoverTypeNames()
|
||||||
|
{
|
||||||
|
static const auto t
|
||||||
|
= std::vector<std::string_view>{ "other"sv, "file-icon"sv, "other-file-icon"sv, "front-cover"sv, "back-cover"sv, "leaflet-page"sv, "media"sv,
|
||||||
|
"lead-performer"sv, "artist"sv, "conductor"sv, "band"sv, "composer"sv, "lyricist"sv, "recording-location"sv, "during-recording"sv,
|
||||||
|
"during-performance"sv, "movie-screen-capture"sv, "bright-colored-fish"sv, "illustration"sv, "artist-logotype"sv, "publisher"sv };
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoverType id3v2CoverType(std::string_view coverName)
|
||||||
|
{
|
||||||
|
static const auto mapping = [] {
|
||||||
|
const auto &names = id3v2CoverTypeNames();
|
||||||
|
auto map = std::map<std::string_view, CoverType>();
|
||||||
|
auto index = CoverType();
|
||||||
|
for (const auto name : names) {
|
||||||
|
map[name] = index++;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}();
|
||||||
|
if (const auto i = mapping.find(coverName); i != mapping.end()) {
|
||||||
|
return i->second;
|
||||||
|
} else {
|
||||||
|
return invalidCoverType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view id3v2CoverName(CoverType coverType)
|
||||||
|
{
|
||||||
|
const auto &names = id3v2CoverTypeNames();
|
||||||
|
return coverType < names.size() ? names[coverType] : "?"sv;
|
||||||
|
}
|
||||||
|
|
||||||
CppUtilities::TimeSpanOutputFormat timeSpanOutputFormat = TimeSpanOutputFormat::WithMeasures;
|
CppUtilities::TimeSpanOutputFormat timeSpanOutputFormat = TimeSpanOutputFormat::WithMeasures;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -208,16 +241,16 @@ void printProperty(const char *propName, ElementPosition elementPosition, const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void printFieldName(const char *fieldName, size_t fieldNameLen)
|
void printFieldName(std::string_view fieldName)
|
||||||
{
|
{
|
||||||
cout << " " << fieldName;
|
cout << " " << fieldName;
|
||||||
// also write padding
|
// also write padding
|
||||||
if (fieldNameLen >= 18) {
|
if (fieldName.size() >= 18) {
|
||||||
// write at least one space
|
// write at least one space
|
||||||
cout << ' ';
|
cout << ' ';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (auto i = fieldNameLen; i < 18; ++i) {
|
for (auto i = fieldName.size(); i < 18; ++i) {
|
||||||
cout << ' ';
|
cout << ' ';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,13 +266,30 @@ void printTagValue(const TagValue &value)
|
||||||
cout << '\n';
|
cout << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class TagType> static void printId3v2CoverValues(TagType *tag)
|
||||||
|
{
|
||||||
|
const auto &fields = tag->fields();
|
||||||
|
const auto id = tag->fieldId(KnownField::Cover);
|
||||||
|
for (auto range = fields.equal_range(id); range.first != range.second; ++range.first) {
|
||||||
|
const auto &field = range.first->second;
|
||||||
|
printFieldName(argsToString("Cover (", id3v2CoverName(static_cast<CoverType>(field.typeInfo())), ")"));
|
||||||
|
printTagValue(field.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void printField(const FieldScope &scope, const Tag *tag, TagType tagType, bool skipEmpty)
|
void printField(const FieldScope &scope, const Tag *tag, TagType tagType, bool skipEmpty)
|
||||||
{
|
{
|
||||||
// write field name
|
const auto fieldName = std::string_view(scope.field.name());
|
||||||
const char *fieldName = scope.field.name();
|
|
||||||
const auto fieldNameLen = strlen(fieldName);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (scope.field.knownFieldForTag(tag, tagType) == KnownField::Cover) {
|
||||||
|
if (tagType == TagType::Id3v2Tag) {
|
||||||
|
printId3v2CoverValues(static_cast<const Id3v2Tag *>(tag));
|
||||||
|
} else {
|
||||||
|
printId3v2CoverValues(static_cast<const VorbisComment *>(tag));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// parse field denotation
|
// parse field denotation
|
||||||
const auto &values = scope.field.values(tag, tagType);
|
const auto &values = scope.field.values(tag, tagType);
|
||||||
|
|
||||||
|
@ -255,20 +305,20 @@ void printField(const FieldScope &scope, const Tag *tag, TagType tagType, bool s
|
||||||
|
|
||||||
// print empty value (if not prevented)
|
// print empty value (if not prevented)
|
||||||
if (values.first.empty()) {
|
if (values.first.empty()) {
|
||||||
printFieldName(fieldName, fieldNameLen);
|
printFieldName(fieldName);
|
||||||
cout << "none\n";
|
cout << "none\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// print values
|
// print values
|
||||||
for (const auto &value : values.first) {
|
for (const auto &value : values.first) {
|
||||||
printFieldName(fieldName, fieldNameLen);
|
printFieldName(fieldName);
|
||||||
printTagValue(*value);
|
printTagValue(*value);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const ConversionException &e) {
|
} catch (const ConversionException &e) {
|
||||||
// handle conversion error which might happen when parsing field denotation
|
// handle conversion error which might happen when parsing field denotation
|
||||||
printFieldName(fieldName, fieldNameLen);
|
printFieldName(fieldName);
|
||||||
cout << "unable to parse - " << e.what() << '\n';
|
cout << "unable to parse - " << e.what() << '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -283,7 +333,7 @@ template <typename ConcreteTag> void printNativeFields(const Tag *tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto fieldId(ConcreteTag::FieldType::fieldIdToString(field.first));
|
const auto fieldId(ConcreteTag::FieldType::fieldIdToString(field.first));
|
||||||
printFieldName(fieldId.data(), fieldId.size());
|
printFieldName(fieldId);
|
||||||
printTagValue(field.second.value());
|
printTagValue(field.second.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -613,7 +663,7 @@ FieldDenotations parseFieldDenotations(const Argument &fieldsArg, bool readOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class ConcreteTag, TagType tagTypeMask = ConcreteTag::tagType>
|
template <class ConcreteTag, TagType tagTypeMask = ConcreteTag::tagType>
|
||||||
std::pair<std::vector<const TagValue *>, bool> valuesForNativeField(std::string_view idString, const Tag *tag, TagType tagType)
|
static std::pair<std::vector<const TagValue *>, bool> valuesForNativeField(std::string_view idString, const Tag *tag, TagType tagType)
|
||||||
{
|
{
|
||||||
auto res = make_pair<std::vector<const TagValue *>, bool>({}, false);
|
auto res = make_pair<std::vector<const TagValue *>, bool>({}, false);
|
||||||
if (!(tagType & tagTypeMask)) {
|
if (!(tagType & tagTypeMask)) {
|
||||||
|
@ -625,7 +675,7 @@ std::pair<std::vector<const TagValue *>, bool> valuesForNativeField(std::string_
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class ConcreteTag, TagType tagTypeMask = ConcreteTag::tagType>
|
template <class ConcreteTag, TagType tagTypeMask = ConcreteTag::tagType>
|
||||||
bool setValuesForNativeField(std::string_view idString, Tag *tag, TagType tagType, const std::vector<TagValue> &values)
|
static bool setValuesForNativeField(std::string_view idString, Tag *tag, TagType tagType, const std::vector<TagValue> &values)
|
||||||
{
|
{
|
||||||
if (!(tagType & tagTypeMask)) {
|
if (!(tagType & tagTypeMask)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -633,20 +683,35 @@ bool setValuesForNativeField(std::string_view idString, Tag *tag, TagType tagTyp
|
||||||
return static_cast<ConcreteTag *>(tag)->setValues(ConcreteTag::FieldType::fieldIdFromString(idString), values);
|
return static_cast<ConcreteTag *>(tag)->setValues(ConcreteTag::FieldType::fieldIdFromString(idString), values);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline FieldId::FieldId(
|
template <class ConcreteTag, TagType tagTypeMask = ConcreteTag::tagType>
|
||||||
std::string_view nativeField, const GetValuesForNativeFieldType &valuesForNativeField, const SetValuesForNativeFieldType &setValuesForNativeField)
|
static KnownField knownFieldForNativeField(std::string_view idString, const Tag *tag, TagType tagType)
|
||||||
|
{
|
||||||
|
if (!(tagType & tagTypeMask)) {
|
||||||
|
return KnownField::Invalid;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return static_cast<const ConcreteTag *>(tag)->knownField(ConcreteTag::FieldType::fieldIdFromString(idString));
|
||||||
|
} catch (const ConversionException &) {
|
||||||
|
return KnownField::Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline FieldId::FieldId(std::string_view nativeField, GetValuesForNativeFieldType &&valuesForNativeField,
|
||||||
|
SetValuesForNativeFieldType &&setValuesForNativeField, KnownFieldForNativeFieldType &&knownFieldForNativeField)
|
||||||
: m_knownField(KnownField::Invalid)
|
: m_knownField(KnownField::Invalid)
|
||||||
, m_nativeField(nativeField)
|
, m_nativeField(nativeField)
|
||||||
, m_valuesForNativeField(valuesForNativeField)
|
, m_valuesForNativeField(std::move(valuesForNativeField))
|
||||||
, m_setValuesForNativeField(setValuesForNativeField)
|
, m_setValuesForNativeField(std::move(setValuesForNativeField))
|
||||||
|
, m_knownFieldForNativeField(std::move(knownFieldForNativeField))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \remarks This wrapper is required because specifying c'tor template args is not possible.
|
/// \remarks This wrapper is required because specifying c'tor template args is not possible.
|
||||||
template <class ConcreteTag, TagType tagTypeMask> FieldId FieldId::fromNativeField(std::string_view nativeFieldId)
|
template <class ConcreteTag, TagType tagTypeMask> FieldId FieldId::fromNativeField(std::string_view nativeFieldId)
|
||||||
{
|
{
|
||||||
return FieldId(nativeFieldId, bind(&valuesForNativeField<ConcreteTag, tagTypeMask>, nativeFieldId, _1, _2),
|
return FieldId(nativeFieldId, std::bind(&valuesForNativeField<ConcreteTag, tagTypeMask>, nativeFieldId, _1, _2),
|
||||||
bind(&setValuesForNativeField<ConcreteTag, tagTypeMask>, nativeFieldId, _1, _2, _3));
|
std::bind(&setValuesForNativeField<ConcreteTag, tagTypeMask>, nativeFieldId, _1, _2, _3),
|
||||||
|
std::bind(&knownFieldForNativeField<ConcreteTag, tagTypeMask>, nativeFieldId, _1, _2));
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldId FieldId::fromTagDenotation(const char *denotation, size_t denotationSize)
|
FieldId FieldId::fromTagDenotation(const char *denotation, size_t denotationSize)
|
||||||
|
@ -700,6 +765,15 @@ bool FieldId::setValues(Tag *tag, TagType tagType, const std::vector<TagValue> &
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KnownField FieldId::knownFieldForTag(const Tag *tag, TagType tagType) const
|
||||||
|
{
|
||||||
|
if (!m_nativeField.empty()) {
|
||||||
|
return m_knownFieldForNativeField(tag, tagType);
|
||||||
|
} else {
|
||||||
|
return m_knownField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string tagName(const Tag *tag)
|
string tagName(const Tag *tag)
|
||||||
{
|
{
|
||||||
stringstream ss;
|
stringstream ss;
|
||||||
|
|
23
cli/helper.h
23
cli/helper.h
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
#include "../application/knownfieldmodel.h"
|
#include "../application/knownfieldmodel.h"
|
||||||
|
|
||||||
|
#include <tagparser/id3/id3v2tag.h>
|
||||||
#include <tagparser/tag.h>
|
#include <tagparser/tag.h>
|
||||||
|
#include <tagparser/vorbis/vorbiscomment.h>
|
||||||
|
|
||||||
#include <c++utilities/application/commandlineutils.h>
|
#include <c++utilities/application/commandlineutils.h>
|
||||||
#include <c++utilities/chrono/datetime.h>
|
#include <c++utilities/chrono/datetime.h>
|
||||||
|
@ -13,6 +15,7 @@
|
||||||
#include <c++utilities/misc/traits.h>
|
#include <c++utilities/misc/traits.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <string_view>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -44,11 +47,18 @@ CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(TagParser, TagParser::TagType)
|
||||||
|
|
||||||
namespace Cli {
|
namespace Cli {
|
||||||
|
|
||||||
|
using CoverType = std::conditional_t<sizeof(typename Id3v2Tag::FieldType::TypeInfoType) >= sizeof(typename VorbisComment::FieldType::TypeInfoType),
|
||||||
|
typename Id3v2Tag::FieldType::TypeInfoType, typename VorbisComment::FieldType::TypeInfoType>;
|
||||||
|
constexpr auto invalidCoverType = std::numeric_limits<CoverType>::max();
|
||||||
|
const std::vector<std::string_view> &id3v2CoverTypeNames();
|
||||||
|
CoverType id3v2CoverType(std::string_view coverName);
|
||||||
|
std::string_view id3v2CoverName(CoverType coverType);
|
||||||
|
|
||||||
class FieldId {
|
class FieldId {
|
||||||
friend struct std::hash<FieldId>;
|
friend struct std::hash<FieldId>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FieldId(KnownField m_knownField = KnownField::Invalid, const char *denotation = nullptr, std::size_t denotationSize = 0);
|
explicit 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 fromTagDenotation(const char *denotation, std::size_t denotationSize);
|
||||||
static FieldId fromTrackDenotation(const char *denotation, std::size_t denotationSize);
|
static FieldId fromTrackDenotation(const char *denotation, std::size_t denotationSize);
|
||||||
bool operator==(const FieldId &other) const;
|
bool operator==(const FieldId &other) const;
|
||||||
|
@ -58,12 +68,14 @@ public:
|
||||||
const std::string &denotation() const;
|
const std::string &denotation() const;
|
||||||
std::pair<std::vector<const TagValue *>, bool> values(const Tag *tag, TagType tagType) const;
|
std::pair<std::vector<const TagValue *>, bool> values(const Tag *tag, TagType tagType) const;
|
||||||
bool setValues(Tag *tag, TagType tagType, const std::vector<TagValue> &values) const;
|
bool setValues(Tag *tag, TagType tagType, const std::vector<TagValue> &values) const;
|
||||||
|
KnownField knownFieldForTag(const Tag *tag, TagType tagType) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using GetValuesForNativeFieldType = std::function<std::pair<std::vector<const TagValue *>, bool>(const Tag *, TagType)>;
|
using GetValuesForNativeFieldType = std::function<std::pair<std::vector<const TagValue *>, bool>(const Tag *, TagType)>;
|
||||||
using SetValuesForNativeFieldType = std::function<bool(Tag *, TagType, const std::vector<TagValue> &)>;
|
using SetValuesForNativeFieldType = std::function<bool(Tag *, TagType, const std::vector<TagValue> &)>;
|
||||||
FieldId(std::string_view nativeField, const GetValuesForNativeFieldType &valuesForNativeField,
|
using KnownFieldForNativeFieldType = std::function<KnownField(const Tag *, TagType)>;
|
||||||
const SetValuesForNativeFieldType &setValuesForNativeField);
|
FieldId(std::string_view nativeField, GetValuesForNativeFieldType &&valuesForNativeField, SetValuesForNativeFieldType &&setValuesForNativeField,
|
||||||
|
KnownFieldForNativeFieldType &&knownFieldForNativeField);
|
||||||
template <class ConcreteTag, TagType tagTypeMask = ConcreteTag::tagType> static FieldId fromNativeField(std::string_view nativeFieldId);
|
template <class ConcreteTag, TagType tagTypeMask = ConcreteTag::tagType> static FieldId fromNativeField(std::string_view nativeFieldId);
|
||||||
|
|
||||||
KnownField m_knownField;
|
KnownField m_knownField;
|
||||||
|
@ -71,6 +83,7 @@ private:
|
||||||
std::string m_nativeField;
|
std::string m_nativeField;
|
||||||
GetValuesForNativeFieldType m_valuesForNativeField;
|
GetValuesForNativeFieldType m_valuesForNativeField;
|
||||||
SetValuesForNativeFieldType m_setValuesForNativeField;
|
SetValuesForNativeFieldType m_setValuesForNativeField;
|
||||||
|
KnownFieldForNativeFieldType m_knownFieldForNativeField;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline FieldId::FieldId(KnownField knownField, const char *denotation, std::size_t denotationSize)
|
inline FieldId::FieldId(KnownField knownField, const char *denotation, std::size_t denotationSize)
|
||||||
|
@ -105,7 +118,7 @@ inline const std::string &FieldId::denotation() const
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FieldScope {
|
struct FieldScope {
|
||||||
FieldScope(KnownField field = KnownField::Invalid, TagType tagType = TagType::Unspecified, TagTarget tagTarget = TagTarget());
|
explicit FieldScope(KnownField field = KnownField::Invalid, TagType tagType = TagType::Unspecified, TagTarget tagTarget = TagTarget());
|
||||||
bool operator==(const FieldScope &other) const;
|
bool operator==(const FieldScope &other) const;
|
||||||
bool isTrack() const;
|
bool isTrack() const;
|
||||||
|
|
||||||
|
@ -150,7 +163,7 @@ inline FieldValue::FieldValue(DenotationType type, unsigned int fileIndex, const
|
||||||
|
|
||||||
class InterruptHandler {
|
class InterruptHandler {
|
||||||
public:
|
public:
|
||||||
InterruptHandler(std::function<void()> handler);
|
explicit InterruptHandler(std::function<void()> handler);
|
||||||
~InterruptHandler();
|
~InterruptHandler();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -18,11 +18,13 @@
|
||||||
#include <tagparser/abstracttrack.h>
|
#include <tagparser/abstracttrack.h>
|
||||||
#include <tagparser/backuphelper.h>
|
#include <tagparser/backuphelper.h>
|
||||||
#include <tagparser/diagnostics.h>
|
#include <tagparser/diagnostics.h>
|
||||||
|
#include <tagparser/id3/id3v2tag.h>
|
||||||
#include <tagparser/localehelper.h>
|
#include <tagparser/localehelper.h>
|
||||||
#include <tagparser/mediafileinfo.h>
|
#include <tagparser/mediafileinfo.h>
|
||||||
#include <tagparser/progressfeedback.h>
|
#include <tagparser/progressfeedback.h>
|
||||||
#include <tagparser/tag.h>
|
#include <tagparser/tag.h>
|
||||||
#include <tagparser/tagvalue.h>
|
#include <tagparser/tagvalue.h>
|
||||||
|
#include <tagparser/vorbis/vorbiscomment.h>
|
||||||
|
|
||||||
#ifdef TAGEDITOR_JSON_EXPORT
|
#ifdef TAGEDITOR_JSON_EXPORT
|
||||||
#include <reflective_rapidjson/json/reflector.h>
|
#include <reflective_rapidjson/json/reflector.h>
|
||||||
|
@ -52,6 +54,7 @@
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace CppUtilities;
|
using namespace CppUtilities;
|
||||||
|
@ -93,6 +96,9 @@ void printFieldNames(const ArgumentOccurrence &)
|
||||||
" - Tag modifier: " TAG_MODIFIER "\n"
|
" - Tag modifier: " TAG_MODIFIER "\n"
|
||||||
" - Track modifier: track=id1,id2,id3,... track=all\n"
|
" - Track modifier: track=id1,id2,id3,... track=all\n"
|
||||||
" - Target modifier:\n " TARGET_MODIFIER "\n"
|
" - Target modifier:\n " TARGET_MODIFIER "\n"
|
||||||
|
"ID3v2 cover types:\n"
|
||||||
|
<< joinStrings<std::remove_reference_t<decltype(id3v2CoverTypeNames())>, std::string>(id3v2CoverTypeNames(), "\n"sv, false, " - "sv, ""sv)
|
||||||
|
<< '\n'
|
||||||
<< flush;
|
<< flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,6 +385,41 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &showUnsupportedAr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class TagType>
|
||||||
|
bool fieldPredicate(CoverType coverType, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class TagType> static void setId3v2CoverValues(TagType *tag, std::vector<std::pair<TagValue, CoverType>> &&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));
|
||||||
|
if (pair != range.second) {
|
||||||
|
// there is already a tag value with the current index/type
|
||||||
|
// -> 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
|
||||||
|
pair->second.setValue(TagValue());
|
||||||
|
}
|
||||||
|
} else if (!tagValue.isEmpty()) {
|
||||||
|
using FieldType = typename TagType::FieldType;
|
||||||
|
auto newField = FieldType(id, tagValue);
|
||||||
|
newField.setTypeInfo(static_cast<typename FieldType::TypeInfoType>(coverType));
|
||||||
|
fields.insert(std::pair(id, std::move(newField)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setTagInfo(const SetTagInfoArgs &args)
|
void setTagInfo(const SetTagInfoArgs &args)
|
||||||
{
|
{
|
||||||
CMD_UTILS_START_CONSOLE;
|
CMD_UTILS_START_CONSOLE;
|
||||||
|
@ -603,7 +644,8 @@ void setTagInfo(const SetTagInfoArgs &args)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// convert the values to TagValue
|
// convert the values to TagValue
|
||||||
vector<TagValue> convertedValues;
|
auto convertedValues = std::vector<TagValue>();
|
||||||
|
auto convertedValuesWithCoverType = std::vector<std::pair<TagValue, CoverType>>();
|
||||||
for (const FieldValue *relevantDenotedValue : fieldDenotation.second.relevantValues) {
|
for (const FieldValue *relevantDenotedValue : fieldDenotation.second.relevantValues) {
|
||||||
// assign an empty TagValue to remove the field if denoted value is empty
|
// assign an empty TagValue to remove the field if denoted value is empty
|
||||||
if (relevantDenotedValue->value.empty()) {
|
if (relevantDenotedValue->value.empty()) {
|
||||||
|
@ -616,32 +658,72 @@ void setTagInfo(const SetTagInfoArgs &args)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// add value from file
|
// add value from file
|
||||||
|
const auto parts = splitStringSimple<std::vector<std::string_view>>(relevantDenotedValue->value, ":", 3);
|
||||||
|
const auto path = parts.empty() ? std::string_view() : parts.front();
|
||||||
try {
|
try {
|
||||||
// assume the file refers to a picture
|
// assume the file refers to a picture
|
||||||
MediaFileInfo coverFileInfo(relevantDenotedValue->value);
|
auto value = TagValue();
|
||||||
Diagnostics coverDiag;
|
if (!path.empty()) {
|
||||||
AbortableProgressFeedback coverProgress; // FIXME: actually use the progress object
|
auto coverFileInfo = MediaFileInfo(path);
|
||||||
coverFileInfo.open(true);
|
auto coverDiag = Diagnostics();
|
||||||
coverFileInfo.parseContainerFormat(coverDiag, coverProgress);
|
auto coverProgress = AbortableProgressFeedback(); // FIXME: actually use the progress object
|
||||||
auto buff = make_unique<char[]>(coverFileInfo.size());
|
coverFileInfo.open(true);
|
||||||
coverFileInfo.stream().seekg(static_cast<streamoff>(coverFileInfo.containerOffset()));
|
coverFileInfo.parseContainerFormat(coverDiag, coverProgress);
|
||||||
coverFileInfo.stream().read(buff.get(), static_cast<streamoff>(coverFileInfo.size()));
|
auto buff = make_unique<char[]>(coverFileInfo.size());
|
||||||
TagValue value(move(buff), coverFileInfo.size(), TagDataType::Picture);
|
coverFileInfo.stream().seekg(static_cast<streamoff>(coverFileInfo.containerOffset()));
|
||||||
value.setMimeType(coverFileInfo.mimeType());
|
coverFileInfo.stream().read(buff.get(), static_cast<streamoff>(coverFileInfo.size()));
|
||||||
convertedValues.emplace_back(move(value));
|
value = TagValue(std::move(buff), coverFileInfo.size(), TagDataType::Picture);
|
||||||
|
value.setMimeType(coverFileInfo.mimeType());
|
||||||
|
}
|
||||||
|
if (parts.size() > 2) {
|
||||||
|
value.setDescription(parts[2], TagTextEncoding::Utf8);
|
||||||
|
}
|
||||||
|
if (parts.size() > 1 && denotedScope.field.knownFieldForTag(tag, tagType) == KnownField::Cover
|
||||||
|
&& (tagType == TagType::Id3v2Tag || tagType == TagType::VorbisComment)) {
|
||||||
|
const auto coverType = id3v2CoverType(parts[1]);
|
||||||
|
if (coverType == invalidCoverType) {
|
||||||
|
diag.emplace_back(DiagLevel::Warning,
|
||||||
|
argsToString("Specified cover type \"", parts[1], "\" is invalid. Ignoring the specified field/value."),
|
||||||
|
context);
|
||||||
|
} else {
|
||||||
|
convertedValuesWithCoverType.emplace_back(std::pair(std::move(value), coverType));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (parts.size() > 1) {
|
||||||
|
diag.emplace_back(DiagLevel::Warning,
|
||||||
|
argsToString("Ignoring cover type \"", parts[1], "\" for ", tag->typeName(),
|
||||||
|
". It is only supported by the cover field and the tag formats ID3v2 and Vorbis Comment."),
|
||||||
|
context);
|
||||||
|
}
|
||||||
|
convertedValues.emplace_back(std::move(value));
|
||||||
|
}
|
||||||
} catch (const TagParser::Failure &) {
|
} catch (const TagParser::Failure &) {
|
||||||
diag.emplace_back(DiagLevel::Critical, "Unable to parse specified cover file.", context);
|
diag.emplace_back(DiagLevel::Critical, "Unable to parse specified cover file.", context);
|
||||||
} catch (const std::ios_base::failure &) {
|
} catch (const std::ios_base::failure &e) {
|
||||||
diag.emplace_back(DiagLevel::Critical, "An IO error occured when parsing the specified cover file.", context);
|
diag.emplace_back(DiagLevel::Critical,
|
||||||
|
argsToString("An IO error occured when parsing the specified cover file: ", e.what()), context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// finally set the values
|
// finally set the values
|
||||||
try {
|
try {
|
||||||
denotedScope.field.setValues(tag, tagType, convertedValues);
|
if (!convertedValues.empty() || convertedValuesWithCoverType.empty()) {
|
||||||
|
denotedScope.field.setValues(tag, tagType, convertedValues);
|
||||||
|
}
|
||||||
} catch (const ConversionException &e) {
|
} catch (const ConversionException &e) {
|
||||||
diag.emplace_back(DiagLevel::Critical,
|
diag.emplace_back(DiagLevel::Critical,
|
||||||
argsToString("Unable to parse denoted field ID \"", denotedScope.field.name(), "\": ", e.what()), context);
|
argsToString("Unable to parse denoted field ID \"", denotedScope.field.name(), "\": ", e.what()), context);
|
||||||
}
|
}
|
||||||
|
if (!convertedValuesWithCoverType.empty()) {
|
||||||
|
switch (tagType) {
|
||||||
|
case TagType::Id3v2Tag:
|
||||||
|
setId3v2CoverValues(static_cast<Id3v2Tag *>(tag), std::move(convertedValuesWithCoverType));
|
||||||
|
break;
|
||||||
|
case TagType::VorbisComment:
|
||||||
|
setId3v2CoverValues(static_cast<VorbisComment *>(tag), std::move(convertedValuesWithCoverType));
|
||||||
|
break;
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue