Improve misc details
This commit is contained in:
parent
1e17bf47d3
commit
d5c8086230
|
@ -29,26 +29,27 @@ public:
|
||||||
FieldMapBasedTag();
|
FieldMapBasedTag();
|
||||||
|
|
||||||
virtual const TagValue &value(const typename FieldType::identifierType &id) const;
|
virtual const TagValue &value(const typename FieldType::identifierType &id) const;
|
||||||
virtual const TagValue &value(KnownField field) const;
|
const TagValue &value(KnownField field) const;
|
||||||
virtual std::list<const TagValue *> values(const typename FieldType::identifierType &id) const;
|
std::list<const TagValue *> values(const typename FieldType::identifierType &id) const;
|
||||||
virtual std::list<const TagValue *> values(KnownField field) const;
|
std::list<const TagValue *> values(KnownField field) const;
|
||||||
virtual bool setValue(const typename FieldType::identifierType &id, const TagValue &value);
|
virtual bool setValue(const typename FieldType::identifierType &id, const TagValue &value);
|
||||||
virtual bool setValue(KnownField field, const TagValue &value);
|
bool setValue(KnownField field, const TagValue &value);
|
||||||
virtual bool setValues(const typename FieldType::identifierType &id, std::initializer_list<TagValue> values);
|
bool setValues(const typename FieldType::identifierType &id, std::initializer_list<TagValue> values);
|
||||||
virtual bool setValues(KnownField field, std::initializer_list<TagValue> values);
|
bool setValues(KnownField field, std::initializer_list<TagValue> values);
|
||||||
virtual bool hasField(KnownField field) const;
|
bool hasField(KnownField field) const;
|
||||||
virtual bool hasField(const typename FieldType::identifierType &id) 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;
|
const std::multimap<typename FieldType::identifierType, FieldType, Compare> &fields() const;
|
||||||
std::multimap<typename FieldType::identifierType, FieldType, Compare> &fields();
|
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 typename FieldType::identifierType fieldId(KnownField value) const = 0;
|
||||||
virtual KnownField knownField(const typename FieldType::identifierType &id) 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;
|
using Tag::proposedDataType;
|
||||||
virtual TagDataType proposedDataType(const typename FieldType::identifierType &id) const;
|
virtual TagDataType proposedDataType(const typename FieldType::identifierType &id) const;
|
||||||
virtual int insertFields(const FieldMapBasedTag<FieldType, Compare> &from, bool overwrite);
|
int insertFields(const FieldMapBasedTag<FieldType, Compare> &from, bool overwrite);
|
||||||
virtual unsigned int insertValues(const Tag &from, bool overwrite);
|
unsigned int insertValues(const Tag &from, bool overwrite);
|
||||||
|
void ensureTextValuesAreProperlyEncoded();
|
||||||
typedef FieldType fieldType;
|
typedef FieldType fieldType;
|
||||||
|
|
||||||
private:
|
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
|
#endif // FIELDBASEDTAG_H
|
||||||
|
|
|
@ -59,18 +59,18 @@ void Id3v1Tag::parse(std::istream &stream, bool autoSeek)
|
||||||
&& buffer[1] == 0x41
|
&& buffer[1] == 0x41
|
||||||
&& buffer[2] == 0x47) {
|
&& buffer[2] == 0x47) {
|
||||||
m_size = 128;
|
m_size = 128;
|
||||||
readValue(m_title, 30, buffer, 3);
|
readValue(m_title, 30, buffer + 3);
|
||||||
readValue(m_artist, 30, buffer, 33);
|
readValue(m_artist, 30, buffer + 33);
|
||||||
readValue(m_album, 30, buffer, 63);
|
readValue(m_album, 30, buffer + 63);
|
||||||
readValue(m_year, 4, buffer, 93);
|
readValue(m_year, 4, buffer + 93);
|
||||||
if(buffer[125] == 0) {
|
if(buffer[125] == 0) {
|
||||||
readValue(m_comment, 28, buffer, 97);
|
readValue(m_comment, 28, buffer + 97);
|
||||||
m_version = "1.1";
|
m_version = "1.1";
|
||||||
} else {
|
} else {
|
||||||
readValue(m_comment, 30, buffer, 97);
|
readValue(m_comment, 30, buffer + 97);
|
||||||
m_version = "1.0";
|
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) {
|
if(buffer[125] == 0) {
|
||||||
m_trackPos.assignPosition(PositionInSet(*reinterpret_cast<char *>(buffer + 126), 0));
|
m_trackPos.assignPosition(PositionInSet(*reinterpret_cast<char *>(buffer + 126), 0));
|
||||||
}
|
}
|
||||||
|
@ -123,13 +123,13 @@ void Id3v1Tag::make(ostream &stream)
|
||||||
try {
|
try {
|
||||||
if(!m_trackPos.isEmpty() && m_trackPos.type() == TagDataType::PositionInSet)
|
if(!m_trackPos.isEmpty() && m_trackPos.type() == TagDataType::PositionInSet)
|
||||||
buffer[1] = m_trackPos.toPositionInSet().position();
|
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);
|
addNotification(NotificationType::Warning, "Track position field can not be set because given value can not be converted appropriately.", context);
|
||||||
}
|
}
|
||||||
// genre
|
// genre
|
||||||
try {
|
try {
|
||||||
buffer[2] = m_genre.toStandardGenreIndex();
|
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);
|
addNotification(NotificationType::Warning, "Genre field can not be set because given value can not be converted appropriately.", context);
|
||||||
}
|
}
|
||||||
stream.write(buffer, 3);
|
stream.write(buffer, 3);
|
||||||
|
@ -254,18 +254,28 @@ bool Id3v1Tag::supportsField(KnownField field) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
void Id3v1Tag::ensureTextValuesAreProperlyEncoded()
|
||||||
* \brief Internally used to read values.
|
|
||||||
*/
|
|
||||||
void Id3v1Tag::readValue(TagValue &value, size_t length, char *buffer, int offset)
|
|
||||||
{
|
{
|
||||||
char *beg = offset + buffer;
|
m_title.convertDataEncodingForTag(this);
|
||||||
char *end = beg + (length - 1);
|
m_artist.convertDataEncodingForTag(this);
|
||||||
while((*end == 0x0 || *end == ' ') && end >= beg) {
|
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;
|
--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);
|
memset(buffer, 0, length);
|
||||||
try {
|
try {
|
||||||
value.toString().copy(buffer, length);
|
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");
|
addNotification(NotificationType::Warning, "Field can not be set because given value can not be converted appropriately.", "making ID3v1 tag field");
|
||||||
}
|
}
|
||||||
targetStream.write(buffer, length);
|
targetStream.write(buffer, length);
|
||||||
|
|
|
@ -11,23 +11,24 @@ class LIB_EXPORT Id3v1Tag : public Tag
|
||||||
public:
|
public:
|
||||||
Id3v1Tag();
|
Id3v1Tag();
|
||||||
|
|
||||||
virtual TagType type() const;
|
TagType type() const;
|
||||||
virtual const char *typeName() const;
|
const char *typeName() const;
|
||||||
virtual bool canEncodingBeUsed(TagTextEncoding encoding) const;
|
bool canEncodingBeUsed(TagTextEncoding encoding) const;
|
||||||
virtual const TagValue &value(KnownField value) const;
|
const TagValue &value(KnownField value) const;
|
||||||
virtual bool setValue(KnownField field, const TagValue &value);
|
bool setValue(KnownField field, const TagValue &value);
|
||||||
virtual bool setValueConsideringTypeInfo(KnownField field, const TagValue &value, const std::string &typeInfo);
|
bool setValueConsideringTypeInfo(KnownField field, const TagValue &value, const std::string &typeInfo);
|
||||||
virtual bool hasField(KnownField field) const;
|
bool hasField(KnownField field) const;
|
||||||
virtual void removeAllFields();
|
void removeAllFields();
|
||||||
virtual unsigned int fieldCount() const;
|
unsigned int fieldCount() const;
|
||||||
virtual bool supportsField(KnownField field) const;
|
bool supportsField(KnownField field) const;
|
||||||
|
void ensureTextValuesAreProperlyEncoded();
|
||||||
|
|
||||||
void parse(std::istream &sourceStream, bool autoSeek);
|
void parse(std::istream &sourceStream, bool autoSeek);
|
||||||
void parse(std::iostream &sourceStream);
|
void parse(std::iostream &sourceStream);
|
||||||
void make(std::ostream &targetStream);
|
void make(std::ostream &targetStream);
|
||||||
|
|
||||||
private:
|
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);
|
void writeValue(const TagValue &value, size_t length, char *buffer, std::ostream &targetStream);
|
||||||
|
|
||||||
TagValue m_title;
|
TagValue m_title;
|
||||||
|
|
|
@ -248,7 +248,7 @@ void Id3v2Tag::parse(istream &stream, const uint64 maximalSize)
|
||||||
if(Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
|
if(Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
|
||||||
addNotification(NotificationType::Warning, "The text frame " + frame.frameIdString() + " exists more than once.", context);
|
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 &) {
|
} catch(const NoDataFoundException &) {
|
||||||
if(frame.hasPaddingReached()) {
|
if(frame.hasPaddingReached()) {
|
||||||
|
|
|
@ -492,24 +492,28 @@ void MediaFileInfo::parseEverything()
|
||||||
* \param treatUnknownFilesAsMp3Files Specifies whether unknown file formats should be treated as MP3 (might break the file).
|
* \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 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 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 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 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 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.
|
* \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.
|
* \return Returns whether appropriate tags could be created for the file.
|
||||||
* \remarks
|
* \remarks
|
||||||
* - The ID3 related arguments are only practiced when the file format is MP3 or when the file format
|
* - Tags must have been parsed before invoking this method (otherwise it will just return false).
|
||||||
* is unknown and \a treatUnknownFilesAsMp3Files is true.
|
* - 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
|
* - 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.
|
||||||
* if \a id3v1Usage is set to TagUsage::Never.
|
|
||||||
* - The method might do nothing if present tag already match the given specifications.
|
* - 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(),
|
* - This is only a convenience method. The task could be done by manually using the methods createId3v1Tag(), createId3v2Tag(), removeId3v1Tag() ... as well.
|
||||||
* 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.
|
||||||
* - Some tag information might be discarded. For example when an ID3v2 tag needs to be removed (\a id3v2usage is set to TagUsage::Never)
|
* - TODO: Refactoring required, there are too much params (not sure how to refactor though, since not all of the params are simple flags).
|
||||||
* and an ID3v1 tag will be created instead not all fields can be transfered.
|
|
||||||
*/
|
*/
|
||||||
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
|
// check if tags need to be created/adjusted/removed
|
||||||
bool targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
|
bool targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
|
||||||
bool targetsSupported = false;
|
bool targetsSupported = false;
|
||||||
|
@ -557,12 +561,26 @@ bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagU
|
||||||
// create ID3 tags according to id3v2usage and id3v2usage
|
// create ID3 tags according to id3v2usage and id3v2usage
|
||||||
if(id3v1usage == TagUsage::Always) {
|
if(id3v1usage == TagUsage::Always) {
|
||||||
// always create ID3v1 tag -> ensure there is one
|
// 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) {
|
if(id3v2usage == TagUsage::Always) {
|
||||||
// always create ID3v2 tag -> ensure there is one and set version
|
// always create ID3v2 tag -> ensure there is one and set version
|
||||||
if(!hasId3v2Tag()) {
|
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
|
// remove ID3 tags according to id3v2usage and id3v2usage
|
||||||
if(id3v1usage == TagUsage::Never) {
|
if(id3v1usage == TagUsage::Never) {
|
||||||
if(hasId3v1Tag()) {
|
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);
|
id3v2Tags().front()->insertValues(*id3v1Tag(), false);
|
||||||
}
|
}
|
||||||
removeId3v1Tag();
|
removeId3v1Tag();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(id3v2usage == TagUsage::Never) {
|
if(id3v2usage == TagUsage::Never) {
|
||||||
if(hasId3v1Tag()) {
|
if(id3TransferValuesOnRemoval && hasId3v1Tag()) {
|
||||||
// transfer tags to ID3v1 tag before removing
|
// transfer tags to ID3v1 tag before removing
|
||||||
for(const auto &tag : id3v2Tags()) {
|
for(const auto &tag : id3v2Tags()) {
|
||||||
id3v1Tag()->insertValues(*tag, false);
|
id3v1Tag()->insertValues(*tag, false);
|
||||||
|
@ -591,7 +609,7 @@ bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagU
|
||||||
} else if(!keepExistingId3v2version) {
|
} else if(!keepExistingId3v2version) {
|
||||||
// set version of ID3v2 tag according user preferences
|
// set version of ID3v2 tag according user preferences
|
||||||
for(const auto &tag : id3v2Tags()) {
|
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()
|
void MediaFileInfo::makeMp3File()
|
||||||
{
|
{
|
||||||
|
|
|
@ -119,8 +119,8 @@ public:
|
||||||
|
|
||||||
// methods to create/remove tags
|
// methods to create/remove tags
|
||||||
bool createAppropriateTags(bool treatUnknownFilesAsMp3Files = false, TagUsage id3v1usage = TagUsage::KeepExisting,
|
bool createAppropriateTags(bool treatUnknownFilesAsMp3Files = false, TagUsage id3v1usage = TagUsage::KeepExisting,
|
||||||
TagUsage id3v2usage = TagUsage::Always, bool mergeMultipleSuccessiveId3v2Tags = true,
|
TagUsage id3v2usage = TagUsage::Always, bool id3InitOnCreate = false, bool id3TransferValuesOnRemoval = true, bool mergeMultipleSuccessiveId3v2Tags = true,
|
||||||
bool keepExistingId3v2version = true, uint32 id3v2version = 3, const std::vector<TagTarget> &requiredTargets = std::vector<TagTarget>());
|
bool keepExistingId3v2version = true, byte id3v2MajorVersion = 3, const std::vector<TagTarget> &requiredTargets = std::vector<TagTarget>());
|
||||||
bool removeId3v1Tag();
|
bool removeId3v1Tag();
|
||||||
Id3v1Tag *createId3v1Tag();
|
Id3v1Tag *createId3v1Tag();
|
||||||
bool removeId3v2Tag(Id3v2Tag *tag);
|
bool removeId3v2Tag(Id3v2Tag *tag);
|
||||||
|
|
|
@ -110,9 +110,8 @@ StringType PositionInSet::toString() const
|
||||||
if(m_position) {
|
if(m_position) {
|
||||||
ss << m_position;
|
ss << m_position;
|
||||||
}
|
}
|
||||||
ss << "/";
|
|
||||||
if(m_total) {
|
if(m_total) {
|
||||||
ss << m_total;
|
ss << '/' << m_total;
|
||||||
}
|
}
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
8
tag.cpp
8
tag.cpp
|
@ -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 from Specifies the Tag the values should be inserted from.
|
||||||
* \param overwrite Indicates whether existing values should be overwritten.
|
* \param overwrite Indicates whether existing values should be overwritten.
|
||||||
* \return Returns the number of values that have been inserted.
|
* \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)
|
unsigned int Tag::insertValues(const Tag &from, bool overwrite)
|
||||||
{
|
{
|
||||||
|
@ -139,6 +141,12 @@ unsigned int Tag::insertValues(const Tag &from, bool overwrite)
|
||||||
return count;
|
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)
|
//bool Tag::setParent(Tag *tag)
|
||||||
//{
|
//{
|
||||||
// if(m_parent != tag) {
|
// if(m_parent != tag) {
|
||||||
|
|
1
tag.h
1
tag.h
|
@ -127,6 +127,7 @@ public:
|
||||||
virtual bool supportsDescription(KnownField field) const;
|
virtual bool supportsDescription(KnownField field) const;
|
||||||
virtual bool supportsMimeType(KnownField field) const;
|
virtual bool supportsMimeType(KnownField field) const;
|
||||||
virtual unsigned int insertValues(const Tag &from, bool overwrite);
|
virtual unsigned int insertValues(const Tag &from, bool overwrite);
|
||||||
|
virtual void ensureTextValuesAreProperlyEncoded() = 0;
|
||||||
// Tag *parent() const;
|
// Tag *parent() const;
|
||||||
// bool setParent(Tag *tag);
|
// bool setParent(Tag *tag);
|
||||||
// Tag *nestedTag(size_t index) const;
|
// Tag *nestedTag(size_t index) const;
|
||||||
|
|
58
tagvalue.cpp
58
tagvalue.cpp
|
@ -1,4 +1,5 @@
|
||||||
#include "./tagvalue.h"
|
#include "./tagvalue.h"
|
||||||
|
#include "./tag.h"
|
||||||
|
|
||||||
#include "./id3/id3genres.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
|
* \brief Converts the value of the current TagValue object to its equivalent
|
||||||
* std::string representation.
|
* std::string representation.
|
||||||
|
|
11
tagvalue.h
11
tagvalue.h
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
|
|
||||||
|
class Tag;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Specifies the text encoding.
|
* \brief Specifies the text encoding.
|
||||||
*/
|
*/
|
||||||
|
@ -105,6 +107,8 @@ public:
|
||||||
bool isLabeledAsReadonly() const;
|
bool isLabeledAsReadonly() const;
|
||||||
void setReadonly(bool value);
|
void setReadonly(bool value);
|
||||||
TagTextEncoding dataEncoding() const;
|
TagTextEncoding dataEncoding() const;
|
||||||
|
void convertDataEncoding(TagTextEncoding encoding);
|
||||||
|
void convertDataEncodingForTag(const Tag *tag);
|
||||||
TagTextEncoding descriptionEncoding() const;
|
TagTextEncoding descriptionEncoding() const;
|
||||||
static const TagValue &empty();
|
static const TagValue &empty();
|
||||||
|
|
||||||
|
@ -446,7 +450,7 @@ inline void TagValue::setReadonly(bool value)
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns the data encoding.
|
* \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()
|
* \sa assignText()
|
||||||
*/
|
*/
|
||||||
inline TagTextEncoding TagValue::dataEncoding() const
|
inline TagTextEncoding TagValue::dataEncoding() const
|
||||||
|
@ -456,9 +460,8 @@ inline TagTextEncoding TagValue::dataEncoding() const
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns the description encoding.
|
* \brief Returns the description encoding.
|
||||||
* \remarks This method is only useful when a description is assigned.
|
* \remarks This value is only relevant if a description is assigned.
|
||||||
* \sa description()
|
* \sa description(), setDescription()
|
||||||
* \sa setDescription()
|
|
||||||
*/
|
*/
|
||||||
inline TagTextEncoding TagValue::descriptionEncoding() const
|
inline TagTextEncoding TagValue::descriptionEncoding() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -872,10 +872,15 @@ void OverallTests::checkMp3Testfile1()
|
||||||
case TagType::Id3v2Tag:
|
case TagType::Id3v2Tag:
|
||||||
CPPUNIT_ASSERT(tag->value(KnownField::Title).dataEncoding() == TagTextEncoding::Utf16LittleEndian);
|
CPPUNIT_ASSERT(tag->value(KnownField::Title).dataEncoding() == TagTextEncoding::Utf16LittleEndian);
|
||||||
CPPUNIT_ASSERT(tag->value(KnownField::Title).toWString() == u"Cohesion");
|
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).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).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).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).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::TrackPosition).toPositionInSet().total() == 43);
|
||||||
CPPUNIT_ASSERT(tag->value(KnownField::Length).toTimeSpan().isNull());
|
CPPUNIT_ASSERT(tag->value(KnownField::Length).toTimeSpan().isNull());
|
||||||
CPPUNIT_ASSERT(tag->value(KnownField::Lyricist).isEmpty());
|
CPPUNIT_ASSERT(tag->value(KnownField::Lyricist).isEmpty());
|
||||||
|
|
Loading…
Reference in New Issue