Improve misc details

This commit is contained in:
Martchus 2016-08-05 01:46:31 +02:00
parent 1e17bf47d3
commit d5c8086230
12 changed files with 179 additions and 67 deletions

View File

@ -29,26 +29,27 @@ public:
FieldMapBasedTag();
virtual const TagValue &value(const typename FieldType::identifierType &id) const;
virtual const TagValue &value(KnownField field) const;
virtual std::list<const TagValue *> values(const typename FieldType::identifierType &id) const;
virtual std::list<const TagValue *> values(KnownField field) const;
const TagValue &value(KnownField field) const;
std::list<const TagValue *> values(const typename FieldType::identifierType &id) const;
std::list<const TagValue *> values(KnownField field) const;
virtual bool setValue(const typename FieldType::identifierType &id, const TagValue &value);
virtual bool setValue(KnownField field, const TagValue &value);
virtual bool setValues(const typename FieldType::identifierType &id, std::initializer_list<TagValue> values);
virtual bool setValues(KnownField field, std::initializer_list<TagValue> values);
virtual bool hasField(KnownField field) const;
bool setValue(KnownField field, const TagValue &value);
bool setValues(const typename FieldType::identifierType &id, std::initializer_list<TagValue> values);
bool setValues(KnownField field, std::initializer_list<TagValue> values);
bool hasField(KnownField field) const;
virtual bool hasField(const typename FieldType::identifierType &id) const;
virtual void removeAllFields();
void removeAllFields();
const std::multimap<typename FieldType::identifierType, FieldType, Compare> &fields() const;
std::multimap<typename FieldType::identifierType, FieldType, Compare> &fields();
virtual unsigned int fieldCount() const;
unsigned int fieldCount() const;
virtual typename FieldType::identifierType fieldId(KnownField value) const = 0;
virtual KnownField knownField(const typename FieldType::identifierType &id) const = 0;
virtual bool supportsField(KnownField field) const;
bool supportsField(KnownField field) const;
using Tag::proposedDataType;
virtual TagDataType proposedDataType(const typename FieldType::identifierType &id) const;
virtual int insertFields(const FieldMapBasedTag<FieldType, Compare> &from, bool overwrite);
virtual unsigned int insertValues(const Tag &from, bool overwrite);
int insertFields(const FieldMapBasedTag<FieldType, Compare> &from, bool overwrite);
unsigned int insertValues(const Tag &from, bool overwrite);
void ensureTextValuesAreProperlyEncoded();
typedef FieldType fieldType;
private:
@ -290,6 +291,14 @@ unsigned int FieldMapBasedTag<FieldType, Compare>::insertValues(const Tag &from,
}
}
template <class FieldType, class Compare>
void FieldMapBasedTag<FieldType, Compare>::ensureTextValuesAreProperlyEncoded()
{
for(auto &field : fields()) {
field.second.value().convertDataEncodingForTag(this);
}
}
}
#endif // FIELDBASEDTAG_H

View File

@ -59,18 +59,18 @@ void Id3v1Tag::parse(std::istream &stream, bool autoSeek)
&& buffer[1] == 0x41
&& buffer[2] == 0x47) {
m_size = 128;
readValue(m_title, 30, buffer, 3);
readValue(m_artist, 30, buffer, 33);
readValue(m_album, 30, buffer, 63);
readValue(m_year, 4, buffer, 93);
readValue(m_title, 30, buffer + 3);
readValue(m_artist, 30, buffer + 33);
readValue(m_album, 30, buffer + 63);
readValue(m_year, 4, buffer + 93);
if(buffer[125] == 0) {
readValue(m_comment, 28, buffer, 97);
readValue(m_comment, 28, buffer + 97);
m_version = "1.1";
} else {
readValue(m_comment, 30, buffer, 97);
readValue(m_comment, 30, buffer + 97);
m_version = "1.0";
}
readValue(m_comment, buffer[125] == 0 ? 28 : 30, buffer, 97);
readValue(m_comment, buffer[125] == 0 ? 28 : 30, buffer + 97);
if(buffer[125] == 0) {
m_trackPos.assignPosition(PositionInSet(*reinterpret_cast<char *>(buffer + 126), 0));
}
@ -123,13 +123,13 @@ void Id3v1Tag::make(ostream &stream)
try {
if(!m_trackPos.isEmpty() && m_trackPos.type() == TagDataType::PositionInSet)
buffer[1] = m_trackPos.toPositionInSet().position();
} catch(ConversionException &) {
} catch(const ConversionException &) {
addNotification(NotificationType::Warning, "Track position field can not be set because given value can not be converted appropriately.", context);
}
// genre
try {
buffer[2] = m_genre.toStandardGenreIndex();
} catch(ConversionException &) {
} catch(const ConversionException &) {
addNotification(NotificationType::Warning, "Genre field can not be set because given value can not be converted appropriately.", context);
}
stream.write(buffer, 3);
@ -254,18 +254,28 @@ bool Id3v1Tag::supportsField(KnownField field) const
}
}
/*!
* \brief Internally used to read values.
*/
void Id3v1Tag::readValue(TagValue &value, size_t length, char *buffer, int offset)
void Id3v1Tag::ensureTextValuesAreProperlyEncoded()
{
char *beg = offset + buffer;
char *end = beg + (length - 1);
while((*end == 0x0 || *end == ' ') && end >= beg) {
m_title.convertDataEncodingForTag(this);
m_artist.convertDataEncodingForTag(this);
m_album.convertDataEncodingForTag(this);
m_year.convertDataEncodingForTag(this);
m_comment.convertDataEncodingForTag(this);
m_trackPos.convertDataEncodingForTag(this);
m_genre.convertDataEncodingForTag(this);
}
/*!
* \brief Internally used to read values with the specified \a maxLength from the specified \a buffer.
*/
void Id3v1Tag::readValue(TagValue &value, size_t maxLength, const char *buffer)
{
const char *end = buffer + maxLength - 1;
while((*end == 0x0 || *end == ' ') && end >= buffer) {
--end;
--length;
--maxLength;
}
value.assignData(beg, length, TagDataType::Text, TagTextEncoding::Latin1);
value.assignData(buffer, maxLength, TagDataType::Text, TagTextEncoding::Latin1);
}
@ -277,7 +287,7 @@ void Id3v1Tag::writeValue(const TagValue &value, size_t length, char *buffer, os
memset(buffer, 0, length);
try {
value.toString().copy(buffer, length);
} catch(ConversionException &) {
} catch(const ConversionException &) {
addNotification(NotificationType::Warning, "Field can not be set because given value can not be converted appropriately.", "making ID3v1 tag field");
}
targetStream.write(buffer, length);

View File

@ -11,23 +11,24 @@ class LIB_EXPORT Id3v1Tag : public Tag
public:
Id3v1Tag();
virtual TagType type() const;
virtual const char *typeName() const;
virtual bool canEncodingBeUsed(TagTextEncoding encoding) const;
virtual const TagValue &value(KnownField value) const;
virtual bool setValue(KnownField field, const TagValue &value);
virtual bool setValueConsideringTypeInfo(KnownField field, const TagValue &value, const std::string &typeInfo);
virtual bool hasField(KnownField field) const;
virtual void removeAllFields();
virtual unsigned int fieldCount() const;
virtual bool supportsField(KnownField field) const;
TagType type() const;
const char *typeName() const;
bool canEncodingBeUsed(TagTextEncoding encoding) const;
const TagValue &value(KnownField value) const;
bool setValue(KnownField field, const TagValue &value);
bool setValueConsideringTypeInfo(KnownField field, const TagValue &value, const std::string &typeInfo);
bool hasField(KnownField field) const;
void removeAllFields();
unsigned int fieldCount() const;
bool supportsField(KnownField field) const;
void ensureTextValuesAreProperlyEncoded();
void parse(std::istream &sourceStream, bool autoSeek);
void parse(std::iostream &sourceStream);
void make(std::ostream &targetStream);
private:
void readValue(TagValue &value, size_t length, char *buffer, int offset);
void readValue(TagValue &value, size_t maxLength, const char *buffer);
void writeValue(const TagValue &value, size_t length, char *buffer, std::ostream &targetStream);
TagValue m_title;

View File

@ -248,7 +248,7 @@ void Id3v2Tag::parse(istream &stream, const uint64 maximalSize)
if(Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
addNotification(NotificationType::Warning, "The text frame " + frame.frameIdString() + " exists more than once.", context);
}
fields().insert(pair<fieldType::identifierType, fieldType>(frame.id(), frame));
fields().insert(make_pair(frame.id(), frame));
}
} catch(const NoDataFoundException &) {
if(frame.hasPaddingReached()) {

View File

@ -492,24 +492,28 @@ void MediaFileInfo::parseEverything()
* \param treatUnknownFilesAsMp3Files Specifies whether unknown file formats should be treated as MP3 (might break the file).
* \param id3v1usage Specifies the usage of ID3v1 when creating tags for MP3 files (has no effect when the file is no MP3 file or not treated as one).
* \param id3v2usage Specifies the usage of ID3v2 when creating tags for MP3 files (has no effect when the file is no MP3 file or not treated as one).
* \param id3InitOnCreate Indicates whether to initialize newly created ID3 tags (according to \a id3v1usage and \a id3v2usage) with the values of the already present ID3 tags.
* \param id3TransferValuesOnRemoval Indicates whether values of removed ID3 tags (according to \a id3v1usage and \a id3v2usage) should be transfered to remaining ID3 tags (no values will be overwritten).
* \param mergeMultipleSuccessiveId3v2Tags Specifies whether multiple successive ID3v2 tags should be merged (see mergeId3v2Tags()).
* \param keepExistingId3v2version Specifies whether the version of existing ID3v2 tags should be adjusted to \a id3v2version (otherwise \a id3v2version is only used when creating a new ID3v2 tag).
* \param id3v2version Specifies the ID3v2 version to be used. Valid values are 2, 3 and 4.
* \param requiredTargets Specifies the required targets. If targets are not supported by the container an informal notification is added.
* \return Returns whether appropriate tags could be created for the file.
* \remarks
* - The ID3 related arguments are only practiced when the file format is MP3 or when the file format
* is unknown and \a treatUnknownFilesAsMp3Files is true.
* - Tags might be removed as well. For example the existing ID3v1 tag of an MP3 file will be removed
* if \a id3v1Usage is set to TagUsage::Never.
* - Tags must have been parsed before invoking this method (otherwise it will just return false).
* - The ID3 related arguments are only practiced when the file format is MP3 or when the file format is unknown and \a treatUnknownFilesAsMp3Files is true.
* - Tags might be removed as well. For example the existing ID3v1 tag of an MP3 file will be removed if \a id3v1Usage is set to TagUsage::Never.
* - The method might do nothing if present tag already match the given specifications.
* - This is only a convenience method. The task could be done by manually using the methods createId3v1Tag(),
* createId3v2Tag(), removeId3v1Tag() ... as well.
* - Some tag information might be discarded. For example when an ID3v2 tag needs to be removed (\a id3v2usage is set to TagUsage::Never)
* and an ID3v1 tag will be created instead not all fields can be transfered.
* - This is only a convenience method. The task could be done by manually using the methods createId3v1Tag(), createId3v2Tag(), removeId3v1Tag() ... as well.
* - Some tag information might be discarded. For example when an ID3v2 tag needs to be removed (\a id3v2usage is set to TagUsage::Never) and an ID3v1 tag will be created instead not all fields can be transfered.
* - TODO: Refactoring required, there are too much params (not sure how to refactor though, since not all of the params are simple flags).
*/
bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagUsage id3v1usage, TagUsage id3v2usage, bool mergeMultipleSuccessiveId3v2Tags, bool keepExistingId3v2version, uint32 id3v2version, const std::vector<TagTarget> &requiredTargets)
bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagUsage id3v1usage, TagUsage id3v2usage, bool id3InitOnCreate, bool id3TransferValuesOnRemoval, bool mergeMultipleSuccessiveId3v2Tags, bool keepExistingId3v2version, byte id3v2MajorVersion, const std::vector<TagTarget> &requiredTargets)
{
// check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
if(tagsParsingStatus() == ParsingStatus::NotParsedYet) {
return false;
}
// check if tags need to be created/adjusted/removed
bool targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
bool targetsSupported = false;
@ -557,12 +561,26 @@ bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagU
// create ID3 tags according to id3v2usage and id3v2usage
if(id3v1usage == TagUsage::Always) {
// always create ID3v1 tag -> ensure there is one
createId3v1Tag();
if(!id3v1Tag()) {
Id3v1Tag *id3v1Tag = createId3v1Tag();
if(id3InitOnCreate) {
for(const auto &id3v2Tag : id3v2Tags()) {
// overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
id3v1Tag->insertValues(*id3v2Tag, true);
// ID3v1 does not support all text encodings which might be used in ID3v2
id3v1Tag->ensureTextValuesAreProperlyEncoded();
}
}
}
}
if(id3v2usage == TagUsage::Always) {
// always create ID3v2 tag -> ensure there is one and set version
if(!hasId3v2Tag()) {
createId3v2Tag()->setVersion(id3v2version, 0);
Id3v2Tag *id3v2Tag = createId3v2Tag();
id3v2Tag->setVersion(id3v2MajorVersion, 0);
if(id3InitOnCreate && id3v1Tag()) {
id3v2Tag->insertValues(*id3v1Tag(), true);
}
}
}
}
@ -573,15 +591,15 @@ bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagU
// remove ID3 tags according to id3v2usage and id3v2usage
if(id3v1usage == TagUsage::Never) {
if(hasId3v1Tag()) {
if(hasId3v2Tag()) {
// transfer tags to ID3v2 tag before removing
// transfer tags to ID3v2 tag before removing
if(id3TransferValuesOnRemoval && hasId3v2Tag()) {
id3v2Tags().front()->insertValues(*id3v1Tag(), false);
}
removeId3v1Tag();
}
}
if(id3v2usage == TagUsage::Never) {
if(hasId3v1Tag()) {
if(id3TransferValuesOnRemoval && hasId3v1Tag()) {
// transfer tags to ID3v1 tag before removing
for(const auto &tag : id3v2Tags()) {
id3v1Tag()->insertValues(*tag, false);
@ -591,7 +609,7 @@ bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagU
} else if(!keepExistingId3v2version) {
// set version of ID3v2 tag according user preferences
for(const auto &tag : id3v2Tags()) {
tag->setVersion(id3v2version, 0);
tag->setVersion(id3v2MajorVersion, 0);
}
}
}
@ -1459,7 +1477,7 @@ void MediaFileInfo::invalidated()
}
/*!
* \brief Internally used to chanings of a MP3 file (or theoretically any file) with ID3 tags.
* \brief Internally used to save chanings of MP3/FLAC files and any other files which might have ID3 tags.
*/
void MediaFileInfo::makeMp3File()
{

View File

@ -119,8 +119,8 @@ public:
// methods to create/remove tags
bool createAppropriateTags(bool treatUnknownFilesAsMp3Files = false, TagUsage id3v1usage = TagUsage::KeepExisting,
TagUsage id3v2usage = TagUsage::Always, bool mergeMultipleSuccessiveId3v2Tags = true,
bool keepExistingId3v2version = true, uint32 id3v2version = 3, const std::vector<TagTarget> &requiredTargets = std::vector<TagTarget>());
TagUsage id3v2usage = TagUsage::Always, bool id3InitOnCreate = false, bool id3TransferValuesOnRemoval = true, bool mergeMultipleSuccessiveId3v2Tags = true,
bool keepExistingId3v2version = true, byte id3v2MajorVersion = 3, const std::vector<TagTarget> &requiredTargets = std::vector<TagTarget>());
bool removeId3v1Tag();
Id3v1Tag *createId3v1Tag();
bool removeId3v2Tag(Id3v2Tag *tag);

View File

@ -110,9 +110,8 @@ StringType PositionInSet::toString() const
if(m_position) {
ss << m_position;
}
ss << "/";
if(m_total) {
ss << m_total;
ss << '/' << m_total;
}
return ss.str();
}

View File

@ -121,6 +121,8 @@ bool Tag::setValues(KnownField field, std::initializer_list<TagValue> values)
* \param from Specifies the Tag the values should be inserted from.
* \param overwrite Indicates whether existing values should be overwritten.
* \return Returns the number of values that have been inserted.
* \remarks The encoding of the inserted text values might not be supported by the tag.
* To fix this, call ensureTextValuesAreProperlyEncoded() after insertion.
*/
unsigned int Tag::insertValues(const Tag &from, bool overwrite)
{
@ -139,6 +141,12 @@ unsigned int Tag::insertValues(const Tag &from, bool overwrite)
return count;
}
/*!
* \fn Tag::ensureTextValuesAreProperlyEncoded()
* \brief Ensures the encoding of all assigned text values is supported by the tag by
* converting the character set if neccessary.
*/
//bool Tag::setParent(Tag *tag)
//{
// if(m_parent != tag) {

1
tag.h
View File

@ -127,6 +127,7 @@ public:
virtual bool supportsDescription(KnownField field) const;
virtual bool supportsMimeType(KnownField field) const;
virtual unsigned int insertValues(const Tag &from, bool overwrite);
virtual void ensureTextValuesAreProperlyEncoded() = 0;
// Tag *parent() const;
// bool setParent(Tag *tag);
// Tag *nestedTag(size_t index) const;

View File

@ -1,4 +1,5 @@
#include "./tagvalue.h"
#include "./tag.h"
#include "./id3/id3genres.h"
@ -326,6 +327,63 @@ pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
}
}
/*!
* \brief Converts the currently assigned text value to the specified \a encoding.
* \throws Throws ConversionUtilities::ConversionException() if the conversion fails.
* \remarks
* - Does nothing if dataEncoding() equals \a encoding.
* - Sets dataEncoding() to the specified \a encoding if the conversion succeeds.
* - Does not do any conversion if the current type() is not TagDataType::Text.
* \sa convertDataEncodingForTag()
*/
void TagValue::convertDataEncoding(TagTextEncoding encoding)
{
if(m_encoding != encoding) {
if(type() == TagDataType::Text) {
StringData encodedData;
switch(encoding) {
case TagTextEncoding::Utf8:
// use pre-defined methods when encoding to UTF-8
switch(dataEncoding()) {
case TagTextEncoding::Latin1:
encodedData = convertLatin1ToUtf8(m_ptr.get(), m_size);
break;
case TagTextEncoding::Utf16LittleEndian:
encodedData = convertUtf16LEToUtf8(m_ptr.get(), m_size);
break;
case TagTextEncoding::Utf16BigEndian:
encodedData = convertUtf16BEToUtf8(m_ptr.get(), m_size);
break;
default:
;
}
break;
default: {
// otherwise, determine input and output parameter to use general covertString method
const auto inputParameter = encodingParameter(dataEncoding());
const auto outputParameter = encodingParameter(encoding);
encodedData = convertString(inputParameter.first, outputParameter.first, m_ptr.get(), m_size, outputParameter.second / inputParameter.second);
}
}
// can't just move the encoded data because it needs to be deleted with free
m_ptr = make_unique<char []>(m_size = encodedData.second);
copy(encodedData.first.get(), encodedData.first.get() + encodedData.second, m_ptr.get());
}
m_encoding = encoding;
}
}
/*!
* \brief Ensures the encoding of the currently assigned text value is supported by the specified \a tag.
* \sa This is a convenience method for convertDataEncoding().
*/
void TagValue::convertDataEncodingForTag(const Tag *tag)
{
if(type() == TagDataType::Text && !tag->canEncodingBeUsed(dataEncoding())) {
convertDataEncoding(tag->proposedTextEncoding());
}
}
/*!
* \brief Converts the value of the current TagValue object to its equivalent
* std::string representation.

View File

@ -14,6 +14,8 @@
namespace Media {
class Tag;
/*!
* \brief Specifies the text encoding.
*/
@ -105,6 +107,8 @@ public:
bool isLabeledAsReadonly() const;
void setReadonly(bool value);
TagTextEncoding dataEncoding() const;
void convertDataEncoding(TagTextEncoding encoding);
void convertDataEncodingForTag(const Tag *tag);
TagTextEncoding descriptionEncoding() const;
static const TagValue &empty();
@ -446,7 +450,7 @@ inline void TagValue::setReadonly(bool value)
/*!
* \brief Returns the data encoding.
* \remarks This method is only useful when a text is assigned.
* \remarks This value is only relevant if type() equals TagDataType::Text.
* \sa assignText()
*/
inline TagTextEncoding TagValue::dataEncoding() const
@ -456,9 +460,8 @@ inline TagTextEncoding TagValue::dataEncoding() const
/*!
* \brief Returns the description encoding.
* \remarks This method is only useful when a description is assigned.
* \sa description()
* \sa setDescription()
* \remarks This value is only relevant if a description is assigned.
* \sa description(), setDescription()
*/
inline TagTextEncoding TagValue::descriptionEncoding() const
{

View File

@ -872,10 +872,15 @@ void OverallTests::checkMp3Testfile1()
case TagType::Id3v2Tag:
CPPUNIT_ASSERT(tag->value(KnownField::Title).dataEncoding() == TagTextEncoding::Utf16LittleEndian);
CPPUNIT_ASSERT(tag->value(KnownField::Title).toWString() == u"Cohesion");
CPPUNIT_ASSERT(tag->value(KnownField::Title).toString(TagTextEncoding::Utf8) == "Cohesion");
CPPUNIT_ASSERT(tag->value(KnownField::Artist).toWString() == u"Minutemen");
CPPUNIT_ASSERT(tag->value(KnownField::Artist).toString(TagTextEncoding::Utf8) == "Minutemen");
CPPUNIT_ASSERT(tag->value(KnownField::Album).toWString() == u"Double Nickels On The Dime");
CPPUNIT_ASSERT(tag->value(KnownField::Album).toString(TagTextEncoding::Utf8) == "Double Nickels On The Dime");
CPPUNIT_ASSERT(tag->value(KnownField::Genre).toWString() == u"Punk Rock");
CPPUNIT_ASSERT(tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8) == "Punk Rock");
CPPUNIT_ASSERT(tag->value(KnownField::Comment).toWString() == u"ExactAudioCopy v0.95b4");
CPPUNIT_ASSERT(tag->value(KnownField::Comment).toString(TagTextEncoding::Utf8) == "ExactAudioCopy v0.95b4");
CPPUNIT_ASSERT(tag->value(KnownField::TrackPosition).toPositionInSet().total() == 43);
CPPUNIT_ASSERT(tag->value(KnownField::Length).toTimeSpan().isNull());
CPPUNIT_ASSERT(tag->value(KnownField::Lyricist).isEmpty());