Refactor MediaFileInfo::createAppropriateTags()

This commit is contained in:
Martchus 2018-03-11 18:57:22 +01:00
parent dabd18102e
commit b47705ff81
6 changed files with 180 additions and 117 deletions

View File

@ -64,6 +64,7 @@ set(HEADER_FILES
opus/opusidentificationheader.h
positioninset.h
progressfeedback.h
settings.h
signature.h
size.h
tag.h

View File

@ -2,6 +2,7 @@
#define TAG_PARSER_ABSTRACTCONTAINER_H
#include "./exceptions.h"
#include "./settings.h"
#include "./tagtarget.h"
#include <c++utilities/chrono/datetime.h>
@ -25,12 +26,6 @@ class AbstractAttachment;
class Diagnostics;
class AbortableProgressFeedback;
enum class ElementPosition {
BeforeData, /**< the element is positioned before the actual data */
AfterData, /**< the element is positioned after the actual data */
Keep /**< the element is placed where it was before */
};
class TAG_PARSER_EXPORT AbstractContainer {
public:
virtual ~AbstractContainer();

View File

@ -489,16 +489,7 @@ void MediaFileInfo::parseEverything(Diagnostics &diag)
}
/*!
* \brief Ensures appropriate tags are created according the given specifications.
* \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 id3v2MajorVersion 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.
* \brief Ensures appropriate tags are created according the given \a settings.
* \return Returns whether appropriate tags could be created for the file.
* \remarks
* - Tags must have been parsed before invoking this method (otherwise it will just return false).
@ -506,20 +497,20 @@ void MediaFileInfo::parseEverything(Diagnostics &diag)
* - 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(s) 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.
* \todo Refactoring required, there are too much params (not sure how to refactor though, since not all of the params are simple flags).
* - Some tag information might be discarded. For example when an ID3v2 tag needs to be removed (TagSettings::id3v2usage is set to TagUsage::Never) and an ID3v1 tag will be created instead not all fields can be transfered.
*/
bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagUsage id3v1usage, TagUsage id3v2usage, bool id3InitOnCreate,
bool id3TransferValuesOnRemoval, bool mergeMultipleSuccessiveId3v2Tags, bool keepExistingId3v2version, byte id3v2MajorVersion,
const std::vector<TagTarget> &requiredTargets)
bool MediaFileInfo::createAppropriateTags(const TagCreationSettings &settings)
{
// 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;
const auto requiredTargets(settings.requiredTargets);
const auto flags(settings.flags);
const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
auto targetsSupported = false;
if (areTagsSupported() && m_container) {
// container object takes care of tag management
if (targetsRequired) {
@ -529,11 +520,9 @@ bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagU
targetsSupported = m_container->tag(0)->supportsTarget();
} else {
// try to create a new tag and check whether targets are supported
auto *tag = m_container->createTag();
if (tag) {
if ((targetsSupported = tag->supportsTarget())) {
tag->setTarget(requiredTargets.front());
}
auto *const tag = m_container->createTag();
if (tag && (targetsSupported = tag->supportsTarget())) {
tag->setTarget(requiredTargets.front());
}
}
if (targetsSupported) {
@ -545,82 +534,74 @@ bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagU
// no targets are required -> just ensure that at least one tag is present
m_container->createTag();
}
} else {
// no container object present
if (m_containerFormat == ContainerFormat::Flac) {
// creation of Vorbis comment is possible
static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
} else {
// creation of ID3 tag is possible
if (!hasAnyTag() && !treatUnknownFilesAsMp3Files) {
switch (containerFormat()) {
case ContainerFormat::Adts:
case ContainerFormat::MpegAudioFrames:
case ContainerFormat::WavPack:
break;
default:
return false;
}
}
// create ID3 tags according to id3v2usage and id3v2usage
if (id3v1usage == TagUsage::Always) {
// always create ID3v1 tag -> ensure there is one
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()) {
Id3v2Tag *id3v2Tag = createId3v2Tag();
id3v2Tag->setVersion(id3v2MajorVersion, 0);
if (id3InitOnCreate && id3v1Tag()) {
id3v2Tag->insertValues(*id3v1Tag(), true);
}
}
}
}
return true;
}
if (mergeMultipleSuccessiveId3v2Tags) {
mergeId3v2Tags();
}
// remove ID3 tags according to id3v1usage and id3v2usage
if (id3v1usage == TagUsage::Never) {
if (hasId3v1Tag()) {
// transfer tags to ID3v2 tag before removing
if (id3TransferValuesOnRemoval && hasId3v2Tag()) {
id3v2Tags().front()->insertValues(*id3v1Tag(), false);
}
removeId3v1Tag();
// no container object present
switch (m_containerFormat) {
case ContainerFormat::Flac:
static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
break;
default:
// create ID3 tag(s)
if (!hasAnyTag() && !(flags & TagCreationFlags::TreatUnknownFilesAsMp3Files)) {
switch (containerFormat()) {
case ContainerFormat::Adts:
case ContainerFormat::MpegAudioFrames:
case ContainerFormat::WavPack:
break;
default:
return false;
}
}
if (id3v2usage == TagUsage::Never) {
if (id3TransferValuesOnRemoval && hasId3v1Tag()) {
// transfer tags to ID3v1 tag before removing
for (const auto &tag : id3v2Tags()) {
id3v1Tag()->insertValues(*tag, false);
// create ID3 tags according to id3v2usage and id3v2usage
// always create ID3v1 tag -> ensure there is one
if (settings.id3v1usage == TagUsage::Always && !id3v1Tag()) {
auto *const id3v1Tag = createId3v1Tag();
if (flags & TagCreationFlags::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();
}
}
removeAllId3v2Tags();
} else if (!keepExistingId3v2version) {
// set version of ID3v2 tag according user preferences
for (const auto &tag : id3v2Tags()) {
tag->setVersion(id3v2MajorVersion, 0);
}
if (settings.id3v2usage == TagUsage::Always && !hasId3v2Tag()) {
// always create ID3v2 tag -> ensure there is one and set version
auto *const id3v2Tag = createId3v2Tag();
id3v2Tag->setVersion(settings.id3v2MajorVersion, 0);
if ((flags & TagCreationFlags::Id3InitOnCreate) && id3v1Tag()) {
id3v2Tag->insertValues(*id3v1Tag(), true);
}
}
}
// FIXME
//if(targetsRequired && !targetsSupported) {
// diag.emplace_back(DiagLevel::Information, "The container/tags do not support targets. The specified targets are ignored.", "creating tags");
//}
if (flags & TagCreationFlags::MergeMultipleSuccessiveId3v2Tags) {
mergeId3v2Tags();
}
// remove ID3 tags according to settings
if (settings.id3v1usage == TagUsage::Never && hasId3v1Tag()) {
// transfer tags to ID3v2 tag before removing
if ((flags & TagCreationFlags::Id3TransferValuesOnRemoval) && hasId3v2Tag()) {
id3v2Tags().front()->insertValues(*id3v1Tag(), false);
}
removeId3v1Tag();
}
if (settings.id3v2usage == TagUsage::Never) {
if ((flags & TagCreationFlags::Id3TransferValuesOnRemoval) && hasId3v1Tag()) {
// transfer tags to ID3v1 tag before removing
for (const auto &tag : id3v2Tags()) {
id3v1Tag()->insertValues(*tag, false);
}
}
removeAllId3v2Tags();
} else if (!(flags & TagCreationFlags::KeepExistingId3v2Version)) {
// set version of ID3v2 tag according user preferences
for (const auto &tag : id3v2Tags()) {
tag->setVersion(settings.id3v2MajorVersion, 0);
}
}
return true;
}
@ -1307,11 +1288,11 @@ void MediaFileInfo::mergeId3v2Tags()
*/
bool MediaFileInfo::id3v1ToId3v2()
{
if (tagsParsingStatus() != ParsingStatus::NotParsedYet && areTagsSupported() && hasId3v1Tag()) {
return createAppropriateTags(false, TagUsage::Never, TagUsage::Always, true, true, 3);
} else {
if (tagsParsingStatus() == ParsingStatus::NotParsedYet || !areTagsSupported() || !hasId3v1Tag()) {
return false;
}
return createAppropriateTags(TagCreationSettings{
{}, TagCreationFlags::MergeMultipleSuccessiveId3v2Tags | TagCreationFlags::KeepExistingId3v2Version, TagUsage::Never, TagUsage::Always, 3 });
}
/*!
@ -1326,11 +1307,11 @@ bool MediaFileInfo::id3v1ToId3v2()
*/
bool MediaFileInfo::id3v2ToId3v1()
{
if (tagsParsingStatus() != ParsingStatus::NotParsedYet && areTagsSupported() && hasId3v2Tag()) {
return createAppropriateTags(false, TagUsage::Always, TagUsage::Never, true, true, 3);
} else {
if (tagsParsingStatus() == ParsingStatus::NotParsedYet || !areTagsSupported() || !hasId3v2Tag()) {
return false;
}
return createAppropriateTags(TagCreationSettings{
{}, TagCreationFlags::MergeMultipleSuccessiveId3v2Tags | TagCreationFlags::KeepExistingId3v2Version, TagUsage::Always, TagUsage::Never, 3 });
}
/*!

View File

@ -3,6 +3,7 @@
#include "./abstractcontainer.h"
#include "./basicfileinfo.h"
#include "./settings.h"
#include "./signature.h"
#include <memory>
@ -29,15 +30,6 @@ class AbortableProgressFeedback;
enum class MediaType;
DECLARE_ENUM_CLASS(TagType, unsigned int);
/*!
* \brief The TagUsage enum specifies the usage of a certain tag type.
*/
enum class TagUsage {
Always, /**< a tag of the type is always used; a new tag is created if none exists yet */
KeepExisting, /**< existing tags of the type are kept and updated but no new tag is created */
Never /**< tags of the type are never used; a possibly existing tag of the type is removed */
};
/*!
* \brief The ParsingStatus enum specifies whether a certain part of the file (tracks, tags, ...) has
* been parsed yet and if what the parsing result is.
@ -112,10 +104,7 @@ public:
bool areTagsSupported() const;
// methods to create/remove tags
bool createAppropriateTags(bool treatUnknownFilesAsMp3Files = false, TagUsage id3v1usage = TagUsage::KeepExisting,
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 createAppropriateTags(const TagCreationSettings &settings = TagCreationSettings());
bool removeId3v1Tag();
Id3v1Tag *createId3v1Tag();
bool removeId3v2Tag(Id3v2Tag *tag);

98
settings.h Normal file
View File

@ -0,0 +1,98 @@
#ifndef TAGPARSER_SETTINGS_H
#define TAGPARSER_SETTINGS_H
#include "./tagtarget.h"
#include <type_traits>
namespace TagParser {
enum class ElementPosition {
BeforeData, /**< the element is positioned before the actual data */
AfterData, /**< the element is positioned after the actual data */
Keep, /**< the element is placed where it was before */
};
/*!
* \brief The TagUsage enum specifies the usage of a certain tag type.
*/
enum class TagUsage {
Always, /**< a tag of the type is always used; a new tag is created if none exists yet */
KeepExisting, /**< existing tags of the type are kept and updated but no new tag is created */
Never, /**< tags of the type are never used; a possibly existing tag of the type is removed */
};
/*!
* \brief The Flags enum contains options to control the tag creation via MediaFileInfo::createAppropriateTags().
*/
enum class TagCreationFlags : uint64 {
None = 0, /**< no flags present */
TreatUnknownFilesAsMp3Files = 1 << 0, /**< treat unknown file formats as MP3 (might make those files unusable) */
Id3InitOnCreate = 1 << 1, /**< initialize newly created ID3 tags with the values of the already present ID3 tags */
Id3TransferValuesOnRemoval = 1 << 2, /**< transfer values of removed ID3 tags to remaining ID3 tags (no values will be overwritten) */
MergeMultipleSuccessiveId3v2Tags = 1 << 3, /**< merge multiple successive ID3v2 tags (see MediaFileInfo::mergeId3v2Tags()) */
KeepExistingId3v2Version
= 1 << 4, /**< keep version of existing ID3v2 tags so TagSettings::id3v2version is only used when creating a *new* ID3v2 tag */
};
constexpr TagCreationFlags operator|(TagCreationFlags lhs, TagCreationFlags rhs)
{
return static_cast<TagCreationFlags>(
static_cast<std::underlying_type<TagCreationFlags>::type>(lhs) | static_cast<std::underlying_type<TagCreationFlags>::type>(rhs));
}
constexpr bool operator&(TagCreationFlags lhs, TagCreationFlags rhs)
{
return static_cast<std::underlying_type<TagCreationFlags>::type>(lhs) & static_cast<std::underlying_type<TagCreationFlags>::type>(rhs);
}
constexpr TagCreationFlags &operator|=(TagCreationFlags &lhs, TagCreationFlags rhs)
{
return lhs = static_cast<TagCreationFlags>(
static_cast<std::underlying_type<TagCreationFlags>::type>(lhs) | static_cast<std::underlying_type<TagCreationFlags>::type>(rhs));
}
constexpr TagCreationFlags &operator+=(TagCreationFlags &lhs, TagCreationFlags rhs)
{
return lhs = static_cast<TagCreationFlags>(
static_cast<std::underlying_type<TagCreationFlags>::type>(lhs) | static_cast<std::underlying_type<TagCreationFlags>::type>(rhs));
}
constexpr TagCreationFlags &operator-=(TagCreationFlags &lhs, TagCreationFlags rhs)
{
return lhs = static_cast<TagCreationFlags>(
static_cast<std::underlying_type<TagCreationFlags>::type>(lhs) & (~static_cast<std::underlying_type<TagCreationFlags>::type>(rhs)));
}
/*!
* \brief The TagSettings struct contains settings which can be passed to MediaFileInfo::createAppropriateTags().
*/
struct TagCreationSettings {
/// \brief Specifies the required targets. If targets are not supported by the container an informal notification is added.
std::vector<TagTarget> requiredTargets = std::vector<TagTarget>();
/// \brief Specifies options to control the tag creation. See TagSettings::Flags.
TagCreationFlags flags = TagCreationFlags::Id3TransferValuesOnRemoval | TagCreationFlags::MergeMultipleSuccessiveId3v2Tags
| TagCreationFlags::KeepExistingId3v2Version;
/// \brief 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).
TagUsage id3v1usage = TagUsage::KeepExisting;
/// \brief 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).
TagUsage id3v2usage = TagUsage::Always;
/// \brief Specifies the ID3v2 version to be used in case an ID3v2 tag present or will be created. Valid values are 2, 3 and 4.
byte id3v2MajorVersion = 3;
constexpr TagCreationSettings &setFlag(TagCreationFlags flag, bool enabled);
};
constexpr TagCreationSettings &TagCreationSettings::setFlag(TagCreationFlags flag, bool enabled)
{
if (enabled) {
flags |= flag;
} else {
flags -= flag;
}
return *this;
}
} // namespace TagParser
#endif // TAGPARSER_SETTINGS_H

View File

@ -31,7 +31,7 @@ enum class TagTextEncoding : unsigned int {
* \brief Returns the size of one character for the specified \a encoding in bytes.
* \remarks For variable-width encoding the minimum size is returned.
*/
inline int characterSize(TagTextEncoding encoding)
constexpr int characterSize(TagTextEncoding encoding)
{
switch (encoding) {
case TagTextEncoding::Latin1:
@ -132,7 +132,6 @@ public:
static void ensureHostByteOrder(std::u16string &u16str, TagTextEncoding currentEncoding);
private:
std::unique_ptr<char[]> m_ptr;
std::size_t m_size;
std::string m_desc;