Skip to content

Commit

Permalink
Allow setting covers of special types with description via CLI
Browse files Browse the repository at this point in the history
See #64
  • Loading branch information
Martchus committed Apr 27, 2021
1 parent 8258a14 commit d9619dd
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 39 deletions.
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -270,6 +270,23 @@ 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
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 `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
1. It is possible to set the preferred encoding used *within* the tags via CLI option ``--encoding``
and in the GUI settings.
Expand Down
112 changes: 93 additions & 19 deletions cli/helper.cpp
Expand Up @@ -28,6 +28,39 @@ using namespace Settings;

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;

/*!
Expand Down Expand Up @@ -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;
// also write padding
if (fieldNameLen >= 18) {
if (fieldName.size() >= 18) {
// write at least one space
cout << ' ';
return;
}
for (auto i = fieldNameLen; i < 18; ++i) {
for (auto i = fieldName.size(); i < 18; ++i) {
cout << ' ';
}
}
Expand All @@ -233,13 +266,30 @@ void printTagValue(const TagValue &value)
cout << '\n';
}

void printField(const FieldScope &scope, const Tag *tag, TagType tagType, bool skipEmpty)
template <class TagType> static void printId3v2CoverValues(TagType *tag)
{
// write field name
const char *fieldName = scope.field.name();
const auto fieldNameLen = strlen(fieldName);
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)
{
const auto fieldName = std::string_view(scope.field.name());
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
const auto &values = scope.field.values(tag, tagType);

Expand All @@ -255,20 +305,20 @@ void printField(const FieldScope &scope, const Tag *tag, TagType tagType, bool s

// print empty value (if not prevented)
if (values.first.empty()) {
printFieldName(fieldName, fieldNameLen);
printFieldName(fieldName);
cout << "none\n";
return;
}

// print values
for (const auto &value : values.first) {
printFieldName(fieldName, fieldNameLen);
printFieldName(fieldName);
printTagValue(*value);
}

} catch (const ConversionException &e) {
// handle conversion error which might happen when parsing field denotation
printFieldName(fieldName, fieldNameLen);
printFieldName(fieldName);
cout << "unable to parse - " << e.what() << '\n';
}
}
Expand All @@ -283,7 +333,7 @@ template <typename ConcreteTag> void printNativeFields(const Tag *tag)
}

const auto fieldId(ConcreteTag::FieldType::fieldIdToString(field.first));
printFieldName(fieldId.data(), fieldId.size());
printFieldName(fieldId);
printTagValue(field.second.value());
}
}
Expand Down Expand Up @@ -613,7 +663,7 @@ FieldDenotations parseFieldDenotations(const Argument &fieldsArg, bool readOnly)
}

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);
if (!(tagType & tagTypeMask)) {
Expand All @@ -625,28 +675,43 @@ std::pair<std::vector<const TagValue *>, bool> valuesForNativeField(std::string_
}

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)) {
return false;
}
return static_cast<ConcreteTag *>(tag)->setValues(ConcreteTag::FieldType::fieldIdFromString(idString), values);
}

inline FieldId::FieldId(
std::string_view nativeField, const GetValuesForNativeFieldType &valuesForNativeField, const SetValuesForNativeFieldType &setValuesForNativeField)
template <class ConcreteTag, TagType tagTypeMask = ConcreteTag::tagType>
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_nativeField(nativeField)
, m_valuesForNativeField(valuesForNativeField)
, m_setValuesForNativeField(setValuesForNativeField)
, m_valuesForNativeField(std::move(valuesForNativeField))
, m_setValuesForNativeField(std::move(setValuesForNativeField))
, m_knownFieldForNativeField(std::move(knownFieldForNativeField))
{
}

/// \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)
{
return FieldId(nativeFieldId, bind(&valuesForNativeField<ConcreteTag, tagTypeMask>, nativeFieldId, _1, _2),
bind(&setValuesForNativeField<ConcreteTag, tagTypeMask>, nativeFieldId, _1, _2, _3));
return FieldId(nativeFieldId, std::bind(&valuesForNativeField<ConcreteTag, tagTypeMask>, nativeFieldId, _1, _2),
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)
Expand Down Expand Up @@ -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)
{
stringstream ss;
Expand Down
23 changes: 18 additions & 5 deletions cli/helper.h
Expand Up @@ -3,7 +3,9 @@

#include "../application/knownfieldmodel.h"

#include <tagparser/id3/id3v2tag.h>
#include <tagparser/tag.h>
#include <tagparser/vorbis/vorbiscomment.h>

#include <c++utilities/application/commandlineutils.h>
#include <c++utilities/chrono/datetime.h>
Expand All @@ -13,6 +15,7 @@
#include <c++utilities/misc/traits.h>

#include <functional>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
Expand Down Expand Up @@ -44,11 +47,18 @@ CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(TagParser, TagParser::TagType)

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 {
friend struct std::hash<FieldId>;

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 fromTrackDenotation(const char *denotation, std::size_t denotationSize);
bool operator==(const FieldId &other) const;
Expand All @@ -58,19 +68,22 @@ class FieldId {
const std::string &denotation() 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;
KnownField knownFieldForTag(const Tag *tag, TagType tagType) const;

private:
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> &)>;
FieldId(std::string_view nativeField, const GetValuesForNativeFieldType &valuesForNativeField,
const SetValuesForNativeFieldType &setValuesForNativeField);
using KnownFieldForNativeFieldType = std::function<KnownField(const Tag *, TagType)>;
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);

KnownField m_knownField;
std::string m_denotation;
std::string m_nativeField;
GetValuesForNativeFieldType m_valuesForNativeField;
SetValuesForNativeFieldType m_setValuesForNativeField;
KnownFieldForNativeFieldType m_knownFieldForNativeField;
};

inline FieldId::FieldId(KnownField knownField, const char *denotation, std::size_t denotationSize)
Expand Down Expand Up @@ -105,7 +118,7 @@ inline const std::string &FieldId::denotation() const
}

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 isTrack() const;

Expand Down Expand Up @@ -150,7 +163,7 @@ inline FieldValue::FieldValue(DenotationType type, unsigned int fileIndex, const

class InterruptHandler {
public:
InterruptHandler(std::function<void()> handler);
explicit InterruptHandler(std::function<void()> handler);
~InterruptHandler();

private:
Expand Down

0 comments on commit d9619dd

Please sign in to comment.