Adjust values()/setValues() to handle multiple values per text frame
See https://github.com/Martchus/tagparser/issues/10
This commit is contained in:
parent
af4b43ff79
commit
e75a8d25c5
|
@ -35,7 +35,6 @@ public:
|
|||
using FieldType = typename FieldMapBasedTagTraits<ImplementationType>::FieldType;
|
||||
using IdentifierType = typename FieldMapBasedTagTraits<ImplementationType>::FieldType::IdentifierType;
|
||||
using Compare = typename FieldMapBasedTagTraits<ImplementationType>::Compare;
|
||||
using FieldMapBasedTagBase = FieldMapBasedTag<ImplementationType>;
|
||||
|
||||
FieldMapBasedTag();
|
||||
|
||||
|
@ -66,8 +65,12 @@ public:
|
|||
void ensureTextValuesAreProperlyEncoded();
|
||||
|
||||
protected:
|
||||
using CRTPBase = FieldMapBasedTag<ImplementationType>;
|
||||
|
||||
const TagValue &internallyGetValue(const IdentifierType &id) const;
|
||||
std::vector<const TagValue *> internallyGetValues(const IdentifierType &id) const;
|
||||
bool internallySetValue(const IdentifierType &id, const TagValue &value);
|
||||
bool internallySetValues(const IdentifierType &id, const std::vector<TagValue> &values);
|
||||
bool internallyHasField(const IdentifierType &id) const;
|
||||
// no default implementation: IdentifierType internallyGetFieldId(KnownField field) const;
|
||||
// no default implementation: KnownField internallyGetKnownField(const IdentifierType &id) const;
|
||||
|
@ -123,6 +126,23 @@ template <class ImplementationType> const TagValue &FieldMapBasedTag<Implementat
|
|||
return i != m_fields.end() ? i->second.value() : TagValue::empty();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Default implementation for values().
|
||||
* \remarks Shadow in subclass to provide custom implementation.
|
||||
*/
|
||||
template <class ImplementationType>
|
||||
std::vector<const TagValue *> FieldMapBasedTag<ImplementationType>::internallyGetValues(const IdentifierType &id) const
|
||||
{
|
||||
auto range = m_fields.equal_range(id);
|
||||
std::vector<const TagValue *> values;
|
||||
for (auto i = range.first; i != range.second; ++i) {
|
||||
if (!i->second.value().isEmpty()) {
|
||||
values.push_back(&i->second.value());
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the value of the field with the specified \a id.
|
||||
* \sa Tag::value()
|
||||
|
@ -143,19 +163,12 @@ template <class ImplementationType> inline const TagValue &FieldMapBasedTag<Impl
|
|||
*/
|
||||
template <class ImplementationType> inline std::vector<const TagValue *> FieldMapBasedTag<ImplementationType>::values(const IdentifierType &id) const
|
||||
{
|
||||
auto range = m_fields.equal_range(id);
|
||||
std::vector<const TagValue *> values;
|
||||
for (auto i = range.first; i != range.second; ++i) {
|
||||
if (!i->second.value().isEmpty()) {
|
||||
values.push_back(&i->second.value());
|
||||
}
|
||||
}
|
||||
return values;
|
||||
return static_cast<const ImplementationType *>(this)->internallyGetValues(id);
|
||||
}
|
||||
|
||||
template <class ImplementationType> inline std::vector<const TagValue *> FieldMapBasedTag<ImplementationType>::values(KnownField field) const
|
||||
{
|
||||
return values(fieldId(field));
|
||||
return static_cast<const ImplementationType *>(this)->values(fieldId(field));
|
||||
}
|
||||
|
||||
template <class ImplementationType> inline bool FieldMapBasedTag<ImplementationType>::setValue(KnownField field, const TagValue &value)
|
||||
|
@ -181,22 +194,11 @@ template <class ImplementationType> bool FieldMapBasedTag<ImplementationType>::i
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Assigns the given \a value to the field with the specified \a id.
|
||||
* \sa Tag::setValue()
|
||||
*/
|
||||
template <class ImplementationType> bool FieldMapBasedTag<ImplementationType>::setValue(const IdentifierType &id, const TagParser::TagValue &value)
|
||||
{
|
||||
return static_cast<ImplementationType *>(this)->internallySetValue(id, value);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Assigns the given \a values to the field with the specified \a id.
|
||||
* \remarks There might me more than one value assigned to an \a id. Whereas setValue() only alters the first value, this
|
||||
* method will replace all currently assigned values with the specified \a values.
|
||||
* \sa Tag::setValues()
|
||||
* \brief Default implementation for setValues().
|
||||
* \remarks Shadow in subclass to provide custom implementation.
|
||||
*/
|
||||
template <class ImplementationType>
|
||||
bool FieldMapBasedTag<ImplementationType>::setValues(const IdentifierType &id, const std::vector<TagValue> &values)
|
||||
bool FieldMapBasedTag<ImplementationType>::internallySetValues(const FieldMapBasedTag::IdentifierType &id, const std::vector<TagValue> &values)
|
||||
{
|
||||
auto valuesIterator = values.cbegin();
|
||||
auto range = m_fields.equal_range(id);
|
||||
|
@ -219,6 +221,27 @@ bool FieldMapBasedTag<ImplementationType>::setValues(const IdentifierType &id, c
|
|||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Assigns the given \a value to the field with the specified \a id.
|
||||
* \sa Tag::setValue()
|
||||
*/
|
||||
template <class ImplementationType> bool FieldMapBasedTag<ImplementationType>::setValue(const IdentifierType &id, const TagParser::TagValue &value)
|
||||
{
|
||||
return static_cast<ImplementationType *>(this)->internallySetValue(id, value);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Assigns the given \a values to the field with the specified \a id.
|
||||
* \remarks There might me more than one value assigned to an \a id. Whereas setValue() only alters the first value, this
|
||||
* method will replace all currently assigned values with the specified \a values.
|
||||
* \sa Tag::setValues()
|
||||
*/
|
||||
template <class ImplementationType>
|
||||
bool FieldMapBasedTag<ImplementationType>::setValues(const IdentifierType &id, const std::vector<TagValue> &values)
|
||||
{
|
||||
return static_cast<ImplementationType *>(this)->internallySetValues(id, values);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Assigns the given \a values to the field with the specified \a id.
|
||||
* \remarks There might me more than one value assigned to a \a field. Whereas setValue() only alters the first value, this
|
||||
|
|
|
@ -20,6 +20,68 @@ namespace TagParser {
|
|||
* \brief Implementation of TagParser::Tag for ID3v2 tags.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \brief Works like the default implementation but adds additional values as well.
|
||||
*/
|
||||
std::vector<const TagValue *> Id3v2Tag::internallyGetValues(const IdentifierType &id) const
|
||||
{
|
||||
auto range = fields().equal_range(id);
|
||||
std::vector<const TagValue *> values;
|
||||
for (auto i = range.first; i != range.second; ++i) {
|
||||
const auto &frame(i->second);
|
||||
if (!frame.value().isEmpty()) {
|
||||
values.push_back(&frame.value());
|
||||
}
|
||||
for (const auto &value : frame.additionalValues()) {
|
||||
values.push_back(&value);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Uses default implementation for non-text frames and applies special handling to text frames.
|
||||
*
|
||||
* - Ensure text frames are unique
|
||||
* - Allow to store multiple values inside the same text frame.
|
||||
*/
|
||||
bool Id3v2Tag::internallySetValues(const IdentifierType &id, const std::vector<TagValue> &values)
|
||||
{
|
||||
// use default implementation for non-text frames
|
||||
if (!Id3v2FrameIds::isTextFrame(id)) {
|
||||
return CRTPBase::internallySetValues(id, values);
|
||||
}
|
||||
|
||||
// find existing text frame
|
||||
auto range = fields().equal_range(id);
|
||||
auto frameIterator = range.first;
|
||||
|
||||
// use existing frame or insert new text frame
|
||||
if (frameIterator != range.second) {
|
||||
++range.first;
|
||||
} else {
|
||||
frameIterator = fields().insert(make_pair(id, Id3v2Frame()));
|
||||
}
|
||||
|
||||
// add primary value to frame
|
||||
auto &frame(frameIterator->second);
|
||||
auto valuesIterator = values.cbegin();
|
||||
if (valuesIterator != values.cend()) {
|
||||
frame.setValue(*valuesIterator);
|
||||
++valuesIterator;
|
||||
} else {
|
||||
frame.value().clearDataAndMetadata();
|
||||
}
|
||||
// add additional values to frame
|
||||
frame.additionalValues() = vector<TagValue>(valuesIterator, values.cend());
|
||||
|
||||
// remove remaining existing values (there are more existing values than specified ones)
|
||||
for (; range.first != range.second; ++range.first) {
|
||||
range.first->second.setValue(TagValue());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Id3v2Tag::IdentifierType Id3v2Tag::internallyGetFieldId(KnownField field) const
|
||||
{
|
||||
using namespace Id3v2FrameIds;
|
||||
|
|
|
@ -92,6 +92,8 @@ protected:
|
|||
IdentifierType internallyGetFieldId(KnownField field) const;
|
||||
KnownField internallyGetKnownField(const IdentifierType &id) const;
|
||||
TagDataType internallyGetProposedDataType(const uint32 &id) const;
|
||||
std::vector<const TagValue *> internallyGetValues(const IdentifierType &id) const;
|
||||
bool internallySetValues(const IdentifierType &id, const std::vector<TagValue> &values);
|
||||
|
||||
private:
|
||||
byte m_majorVersion;
|
||||
|
|
|
@ -121,7 +121,8 @@ void OverallTests::checkMp3Testfile2()
|
|||
CPPUNIT_ASSERT_EQUAL("Infinite (Original Mix)"s, tag->value(KnownField::Title).toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("B-Front"s, tag->value(KnownField::Artist).toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Infinite"s, tag->value(KnownField::Album).toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Hardstyle"s, tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL(m_tagStatus == TagStatus::TestMetaDataPresent ? "Test"s : "Hardstyle"s,
|
||||
tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Lavf57.83.100"s, tag->value(KnownField::EncoderSettings).toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Roughstate"s, tag->value(KnownField::RecordLabel).toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("2017"s, tag->value(KnownField::RecordDate).toString(TagTextEncoding::Utf8));
|
||||
|
@ -129,29 +130,57 @@ void OverallTests::checkMp3Testfile2()
|
|||
CPPUNIT_ASSERT(tag->value(KnownField::Length).toTimeSpan().isNull());
|
||||
CPPUNIT_ASSERT(tag->value(KnownField::Lyricist).isEmpty());
|
||||
|
||||
// check multiple values
|
||||
// check additional text frame values
|
||||
const auto &fields = id3v2Tag->fields();
|
||||
auto genreFields = fields.equal_range(Id3v2FrameIds::lGenre);
|
||||
CPPUNIT_ASSERT_MESSAGE("genre field present"s, genreFields.first != genreFields.second);
|
||||
const auto &genreField = genreFields.first->second;
|
||||
const auto &additionalValues = genreField.additionalValues();
|
||||
CPPUNIT_ASSERT_EQUAL("Hardstyle"s, tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL(3_st, additionalValues.size());
|
||||
CPPUNIT_ASSERT_EQUAL("Test"s, additionalValues[0].toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Example"s, additionalValues[1].toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Hard Dance"s, additionalValues[2].toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Hardstyle"s, tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
|
||||
if (m_tagStatus == TagStatus::TestMetaDataPresent) {
|
||||
CPPUNIT_ASSERT_EQUAL("Test"s, tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, additionalValues.size());
|
||||
CPPUNIT_ASSERT_EQUAL("Example"s, additionalValues[0].toString(TagTextEncoding::Utf8));
|
||||
} else {
|
||||
CPPUNIT_ASSERT_EQUAL("Hardstyle"s, tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL(3_st, additionalValues.size());
|
||||
CPPUNIT_ASSERT_EQUAL("Test"s, additionalValues[0].toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Example"s, additionalValues[1].toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Hard Dance"s, additionalValues[2].toString(TagTextEncoding::Utf8));
|
||||
}
|
||||
CPPUNIT_ASSERT_MESSAGE("exactly one genre field present"s, ++genreFields.first == genreFields.second);
|
||||
|
||||
// check whether additional text frame values are returned correctly by values()
|
||||
const auto artists = id3v2Tag->values(KnownField::Artist);
|
||||
CPPUNIT_ASSERT_EQUAL(m_tagStatus == TagStatus::TestMetaDataPresent ? 3_st : 2_st, artists.size());
|
||||
CPPUNIT_ASSERT_EQUAL("B-Front"s, artists[0]->toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Second Artist Example"s, artists[1]->toString(TagTextEncoding::Utf8));
|
||||
if (m_tagStatus == TagStatus::TestMetaDataPresent) {
|
||||
CPPUNIT_ASSERT_EQUAL("3rd Artist Example"s, artists[2]->toString(TagTextEncoding::Utf8));
|
||||
}
|
||||
|
||||
const auto genres = id3v2Tag->values(KnownField::Genre);
|
||||
if (m_tagStatus == TagStatus::TestMetaDataPresent) {
|
||||
CPPUNIT_ASSERT_EQUAL(2_st, genres.size());
|
||||
CPPUNIT_ASSERT_EQUAL("Test"s, genres[0]->toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Example"s, genres[1]->toString(TagTextEncoding::Utf8));
|
||||
} else {
|
||||
CPPUNIT_ASSERT_EQUAL(4_st, genres.size());
|
||||
CPPUNIT_ASSERT_EQUAL("Hardstyle"s, genres[0]->toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Test"s, genres[1]->toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Example"s, genres[2]->toString(TagTextEncoding::Utf8));
|
||||
CPPUNIT_ASSERT_EQUAL("Hard Dance"s, genres[3]->toString(TagTextEncoding::Utf8));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TagStatus::Removed:
|
||||
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
|
||||
CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
|
||||
}
|
||||
|
||||
if (expectId3v24) {
|
||||
if (expectId3v24 || m_tagStatus == TagStatus::Removed) {
|
||||
CPPUNIT_ASSERT(m_diag.level() <= DiagLevel::Information);
|
||||
return;
|
||||
}
|
||||
|
||||
CPPUNIT_ASSERT(m_diag.level() <= DiagLevel::Warning);
|
||||
int warningCount = 0;
|
||||
for (const auto &msg : m_diag) {
|
||||
|
@ -162,8 +191,8 @@ void OverallTests::checkMp3Testfile2()
|
|||
TESTUTILS_ASSERT_LIKE("context", "(parsing|making) (TPE1|TCON)( frame)?", msg.context());
|
||||
TESTUTILS_ASSERT_LIKE("message",
|
||||
"Multiple strings (found|assigned) .*"
|
||||
"Additional (value \"Second Artist Example\" is|"
|
||||
"values \"Test\", \"Example\" and \"Hard Dance\" are) "
|
||||
"Additional (values \"Second Artist Example\" and \"3rd Artist Example\" are|"
|
||||
"value \"Example\" is) "
|
||||
"supposed to be ignored.",
|
||||
msg.message());
|
||||
}
|
||||
|
@ -306,7 +335,12 @@ void OverallTests::setMp3TestMetaData2()
|
|||
using namespace Mp3TestFlags;
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, m_fileInfo.id3v2Tags().size());
|
||||
m_fileInfo.id3v2Tags().front()->setVersion((m_mode & UseId3v24) ? 4 : 3, 0);
|
||||
auto &id3v2Tag(m_fileInfo.id3v2Tags().front());
|
||||
id3v2Tag->setVersion((m_mode & UseId3v24) ? 4 : 3, 0);
|
||||
const auto artists = id3v2Tag->values(KnownField::Artist);
|
||||
CPPUNIT_ASSERT_EQUAL(2_st, artists.size());
|
||||
id3v2Tag->setValues(KnownField::Artist, { *artists[0], *artists[1], TagValue("3rd Artist Example") });
|
||||
id3v2Tag->setValues(KnownField::Genre, { TagValue("Test"), TagValue("Example") });
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
Loading…
Reference in New Issue