Fix extensibility of ID3v2 FrameFactory

Because the main extension point of FrameFactory was using a protected
Frame subclass, it was not really possible to implement a custom frame
factory. Existing Frame subclasses also show that access to the frame
header might be needed when implementing a Frame subclass.
This commit is contained in:
Urs Fleisch
2023-11-18 07:14:32 +01:00
parent 59166f6757
commit 3d67b139e4
9 changed files with 643 additions and 247 deletions

View File

@@ -135,6 +135,21 @@ namespace
std::pair("DJ-MIX", "DJMIXER"),
std::pair("MIX", "MIXER"),
};
constexpr std::array txxxFrameTranslation {
std::pair("MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID"),
std::pair("MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID"),
std::pair("MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ_ALBUMARTISTID"),
std::pair("MUSICBRAINZ ALBUM RELEASE COUNTRY", "RELEASECOUNTRY"),
std::pair("MUSICBRAINZ ALBUM STATUS", "RELEASESTATUS"),
std::pair("MUSICBRAINZ ALBUM TYPE", "RELEASETYPE"),
std::pair("MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ_RELEASEGROUPID"),
std::pair("MUSICBRAINZ RELEASE TRACK ID", "MUSICBRAINZ_RELEASETRACKID"),
std::pair("MUSICBRAINZ WORK ID", "MUSICBRAINZ_WORKID"),
std::pair("ACOUSTID ID", "ACOUSTID_ID"),
std::pair("ACOUSTID FINGERPRINT", "ACOUSTID_FINGERPRINT"),
std::pair("MUSICIP PUID", "MUSICIP_PUID"),
};
} // namespace
const KeyConversionMap &TextIdentificationFrame::involvedPeopleMap() // static
@@ -432,6 +447,26 @@ UserTextIdentificationFrame *UserTextIdentificationFrame::find(
return nullptr;
}
String UserTextIdentificationFrame::txxxToKey(const String &description)
{
const String d = description.upper();
for(const auto &[o, t] : txxxFrameTranslation) {
if(d == o)
return t;
}
return d;
}
String UserTextIdentificationFrame::keyToTXXX(const String &s)
{
const String key = s.upper();
for(const auto &[o, t] : txxxFrameTranslation) {
if(key == t)
return o;
}
return s;
}
////////////////////////////////////////////////////////////////////////////////
// UserTextIdentificationFrame private members
////////////////////////////////////////////////////////////////////////////////

View File

@@ -301,6 +301,16 @@ namespace TagLib {
*/
static UserTextIdentificationFrame *find(Tag *tag, const String &description);
/*!
* Returns an appropriate TXXX frame description for the given free-form tag key.
*/
static String keyToTXXX(const String &);
/*!
* Returns a free-form tag name for the given ID3 frame description.
*/
static String txxxToKey(const String &);
private:
UserTextIdentificationFrame(const ByteVector &data, Header *h);
UserTextIdentificationFrame(const TextIdentificationFrame &);

View File

@@ -97,56 +97,6 @@ unsigned int Frame::headerSize()
return d->header->size();
}
Frame *Frame::createTextualFrame(const String &key, const StringList &values) //static
{
// check if the key is contained in the key<=>frameID mapping
ByteVector frameID = keyToFrameID(key);
if(!frameID.isEmpty()) {
// Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number), GRP1 (Grouping) are in fact text frames.
if(frameID[0] == 'T' || frameID == "WFED" || frameID == "MVNM" || frameID == "MVIN" || frameID == "GRP1"){ // text frame
auto frame = new TextIdentificationFrame(frameID, String::UTF8);
frame->setText(values);
return frame;
} if((frameID[0] == 'W') && (values.size() == 1)){ // URL frame (not WXXX); support only one value
auto frame = new UrlLinkFrame(frameID);
frame->setUrl(values.front());
return frame;
} if(frameID == "PCST") {
return new PodcastFrame();
}
}
if(key == "MUSICBRAINZ_TRACKID" && values.size() == 1) {
auto frame = new UniqueFileIdentifierFrame("http://musicbrainz.org", values.front().data(String::UTF8));
return frame;
}
// now we check if it's one of the "special" cases:
// -LYRICS: depending on the number of values, use USLT or TXXX (with description=LYRICS)
if((key == "LYRICS" || key.startsWith(lyricsPrefix)) && values.size() == 1){
auto frame = new UnsynchronizedLyricsFrame(String::UTF8);
frame->setDescription(key == "LYRICS" ? key : key.substr(lyricsPrefix.size()));
frame->setText(values.front());
return frame;
}
// -URL: depending on the number of values, use WXXX or TXXX (with description=URL)
if((key == "URL" || key.startsWith(urlPrefix)) && values.size() == 1){
auto frame = new UserUrlLinkFrame(String::UTF8);
frame->setDescription(key == "URL" ? key : key.substr(urlPrefix.size()));
frame->setUrl(values.front());
return frame;
}
// -COMMENT: depending on the number of values, use COMM or TXXX (with description=COMMENT)
if((key == "COMMENT" || key.startsWith(commentPrefix)) && values.size() == 1){
auto frame = new CommentsFrame(String::UTF8);
if (key != "COMMENT"){
frame->setDescription(key.substr(commentPrefix.size()));
}
frame->setText(values.front());
return frame;
}
// if none of the above cases apply, we use a TXXX frame with the key as description
return new UserTextIdentificationFrame(keyToTXXX(key), values, String::UTF8);
}
Frame::~Frame() = default;
ByteVector Frame::frameID() const
@@ -181,6 +131,125 @@ ByteVector Frame::render() const
return headerData + fieldData;
}
Frame::Header *Frame::header() const
{
return d->header;
}
namespace
{
constexpr std::array frameTranslation {
// Text information frames
std::pair("TALB", "ALBUM"),
std::pair("TBPM", "BPM"),
std::pair("TCOM", "COMPOSER"),
std::pair("TCON", "GENRE"),
std::pair("TCOP", "COPYRIGHT"),
std::pair("TDEN", "ENCODINGTIME"),
std::pair("TDLY", "PLAYLISTDELAY"),
std::pair("TDOR", "ORIGINALDATE"),
std::pair("TDRC", "DATE"),
// std::pair("TRDA", "DATE"), // id3 v2.3, replaced by TDRC in v2.4
// std::pair("TDAT", "DATE"), // id3 v2.3, replaced by TDRC in v2.4
// std::pair("TYER", "DATE"), // id3 v2.3, replaced by TDRC in v2.4
// std::pair("TIME", "DATE"), // id3 v2.3, replaced by TDRC in v2.4
std::pair("TDRL", "RELEASEDATE"),
std::pair("TDTG", "TAGGINGDATE"),
std::pair("TENC", "ENCODEDBY"),
std::pair("TEXT", "LYRICIST"),
std::pair("TFLT", "FILETYPE"),
// std::pair("TIPL", "INVOLVEDPEOPLE"), handled separately
std::pair("TIT1", "WORK"), // 'Work' in iTunes
std::pair("TIT2", "TITLE"),
std::pair("TIT3", "SUBTITLE"),
std::pair("TKEY", "INITIALKEY"),
std::pair("TLAN", "LANGUAGE"),
std::pair("TLEN", "LENGTH"),
// std::pair("TMCL", "MUSICIANCREDITS"), handled separately
std::pair("TMED", "MEDIA"),
std::pair("TMOO", "MOOD"),
std::pair("TOAL", "ORIGINALALBUM"),
std::pair("TOFN", "ORIGINALFILENAME"),
std::pair("TOLY", "ORIGINALLYRICIST"),
std::pair("TOPE", "ORIGINALARTIST"),
std::pair("TOWN", "OWNER"),
std::pair("TPE1", "ARTIST"),
std::pair("TPE2", "ALBUMARTIST"), // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST'
std::pair("TPE3", "CONDUCTOR"),
std::pair("TPE4", "REMIXER"), // could also be ARRANGER
std::pair("TPOS", "DISCNUMBER"),
std::pair("TPRO", "PRODUCEDNOTICE"),
std::pair("TPUB", "LABEL"),
std::pair("TRCK", "TRACKNUMBER"),
std::pair("TRSN", "RADIOSTATION"),
std::pair("TRSO", "RADIOSTATIONOWNER"),
std::pair("TSOA", "ALBUMSORT"),
std::pair("TSOC", "COMPOSERSORT"),
std::pair("TSOP", "ARTISTSORT"),
std::pair("TSOT", "TITLESORT"),
std::pair("TSO2", "ALBUMARTISTSORT"), // non-standard, used by iTunes
std::pair("TSRC", "ISRC"),
std::pair("TSSE", "ENCODING"),
std::pair("TSST", "DISCSUBTITLE"),
// URL frames
std::pair("WCOP", "COPYRIGHTURL"),
std::pair("WOAF", "FILEWEBPAGE"),
std::pair("WOAR", "ARTISTWEBPAGE"),
std::pair("WOAS", "AUDIOSOURCEWEBPAGE"),
std::pair("WORS", "RADIOSTATIONWEBPAGE"),
std::pair("WPAY", "PAYMENTWEBPAGE"),
std::pair("WPUB", "PUBLISHERWEBPAGE"),
// std::pair("WXXX", "URL"), handled specially
// Other frames
std::pair("COMM", "COMMENT"),
// std::pair("USLT", "LYRICS"), handled specially
// Apple iTunes proprietary frames
std::pair("PCST", "PODCAST"),
std::pair("TCAT", "PODCASTCATEGORY"),
std::pair("TDES", "PODCASTDESC"),
std::pair("TGID", "PODCASTID"),
std::pair("WFED", "PODCASTURL"),
std::pair("MVNM", "MOVEMENTNAME"),
std::pair("MVIN", "MOVEMENTNUMBER"),
std::pair("GRP1", "GROUPING"),
std::pair("TCMP", "COMPILATION"),
};
// list of deprecated frames and their successors
constexpr std::array deprecatedFrames {
std::pair("TRDA", "TDRC"), // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3)
std::pair("TDAT", "TDRC"), // 2.3 -> 2.4
std::pair("TYER", "TDRC"), // 2.3 -> 2.4
std::pair("TIME", "TDRC"), // 2.3 -> 2.4
};
} // namespace
String Frame::frameIDToKey(const ByteVector &id)
{
ByteVector id24 = id;
for(const auto &[o, t] : deprecatedFrames) {
if(id24 == o) {
id24 = t;
break;
}
}
for(const auto &[o, t] : frameTranslation) {
if(id24 == o)
return t;
}
return String();
}
ByteVector Frame::keyToFrameID(const String &s)
{
const String key = s.upper();
for(const auto &[o, t] : frameTranslation) {
if(key == t)
return o;
}
return ByteVector();
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
@@ -197,11 +266,6 @@ Frame::Frame(Header *h) :
d->header = h;
}
Frame::Header *Frame::header() const
{
return d->header;
}
void Frame::setHeader(Header *h, bool deleteCurrent)
{
if(deleteCurrent)
@@ -296,155 +360,6 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc
return String::Latin1;
}
namespace
{
constexpr std::array frameTranslation {
// Text information frames
std::pair("TALB", "ALBUM"),
std::pair("TBPM", "BPM"),
std::pair("TCOM", "COMPOSER"),
std::pair("TCON", "GENRE"),
std::pair("TCOP", "COPYRIGHT"),
std::pair("TDEN", "ENCODINGTIME"),
std::pair("TDLY", "PLAYLISTDELAY"),
std::pair("TDOR", "ORIGINALDATE"),
std::pair("TDRC", "DATE"),
// std::pair("TRDA", "DATE"), // id3 v2.3, replaced by TDRC in v2.4
// std::pair("TDAT", "DATE"), // id3 v2.3, replaced by TDRC in v2.4
// std::pair("TYER", "DATE"), // id3 v2.3, replaced by TDRC in v2.4
// std::pair("TIME", "DATE"), // id3 v2.3, replaced by TDRC in v2.4
std::pair("TDRL", "RELEASEDATE"),
std::pair("TDTG", "TAGGINGDATE"),
std::pair("TENC", "ENCODEDBY"),
std::pair("TEXT", "LYRICIST"),
std::pair("TFLT", "FILETYPE"),
// std::pair("TIPL", "INVOLVEDPEOPLE"), handled separately
std::pair("TIT1", "WORK"), // 'Work' in iTunes
std::pair("TIT2", "TITLE"),
std::pair("TIT3", "SUBTITLE"),
std::pair("TKEY", "INITIALKEY"),
std::pair("TLAN", "LANGUAGE"),
std::pair("TLEN", "LENGTH"),
// std::pair("TMCL", "MUSICIANCREDITS"), handled separately
std::pair("TMED", "MEDIA"),
std::pair("TMOO", "MOOD"),
std::pair("TOAL", "ORIGINALALBUM"),
std::pair("TOFN", "ORIGINALFILENAME"),
std::pair("TOLY", "ORIGINALLYRICIST"),
std::pair("TOPE", "ORIGINALARTIST"),
std::pair("TOWN", "OWNER"),
std::pair("TPE1", "ARTIST"),
std::pair("TPE2", "ALBUMARTIST"), // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST'
std::pair("TPE3", "CONDUCTOR"),
std::pair("TPE4", "REMIXER"), // could also be ARRANGER
std::pair("TPOS", "DISCNUMBER"),
std::pair("TPRO", "PRODUCEDNOTICE"),
std::pair("TPUB", "LABEL"),
std::pair("TRCK", "TRACKNUMBER"),
std::pair("TRSN", "RADIOSTATION"),
std::pair("TRSO", "RADIOSTATIONOWNER"),
std::pair("TSOA", "ALBUMSORT"),
std::pair("TSOC", "COMPOSERSORT"),
std::pair("TSOP", "ARTISTSORT"),
std::pair("TSOT", "TITLESORT"),
std::pair("TSO2", "ALBUMARTISTSORT"), // non-standard, used by iTunes
std::pair("TSRC", "ISRC"),
std::pair("TSSE", "ENCODING"),
std::pair("TSST", "DISCSUBTITLE"),
// URL frames
std::pair("WCOP", "COPYRIGHTURL"),
std::pair("WOAF", "FILEWEBPAGE"),
std::pair("WOAR", "ARTISTWEBPAGE"),
std::pair("WOAS", "AUDIOSOURCEWEBPAGE"),
std::pair("WORS", "RADIOSTATIONWEBPAGE"),
std::pair("WPAY", "PAYMENTWEBPAGE"),
std::pair("WPUB", "PUBLISHERWEBPAGE"),
// std::pair("WXXX", "URL"), handled specially
// Other frames
std::pair("COMM", "COMMENT"),
// std::pair("USLT", "LYRICS"), handled specially
// Apple iTunes proprietary frames
std::pair("PCST", "PODCAST"),
std::pair("TCAT", "PODCASTCATEGORY"),
std::pair("TDES", "PODCASTDESC"),
std::pair("TGID", "PODCASTID"),
std::pair("WFED", "PODCASTURL"),
std::pair("MVNM", "MOVEMENTNAME"),
std::pair("MVIN", "MOVEMENTNUMBER"),
std::pair("GRP1", "GROUPING"),
std::pair("TCMP", "COMPILATION"),
};
constexpr std::array txxxFrameTranslation {
std::pair("MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID"),
std::pair("MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID"),
std::pair("MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ_ALBUMARTISTID"),
std::pair("MUSICBRAINZ ALBUM RELEASE COUNTRY", "RELEASECOUNTRY"),
std::pair("MUSICBRAINZ ALBUM STATUS", "RELEASESTATUS"),
std::pair("MUSICBRAINZ ALBUM TYPE", "RELEASETYPE"),
std::pair("MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ_RELEASEGROUPID"),
std::pair("MUSICBRAINZ RELEASE TRACK ID", "MUSICBRAINZ_RELEASETRACKID"),
std::pair("MUSICBRAINZ WORK ID", "MUSICBRAINZ_WORKID"),
std::pair("ACOUSTID ID", "ACOUSTID_ID"),
std::pair("ACOUSTID FINGERPRINT", "ACOUSTID_FINGERPRINT"),
std::pair("MUSICIP PUID", "MUSICIP_PUID"),
};
// list of deprecated frames and their successors
constexpr std::array deprecatedFrames {
std::pair("TRDA", "TDRC"), // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3)
std::pair("TDAT", "TDRC"), // 2.3 -> 2.4
std::pair("TYER", "TDRC"), // 2.3 -> 2.4
std::pair("TIME", "TDRC"), // 2.3 -> 2.4
};
} // namespace
String Frame::frameIDToKey(const ByteVector &id)
{
ByteVector id24 = id;
for(const auto &[o, t] : deprecatedFrames) {
if(id24 == o) {
id24 = t;
break;
}
}
for(const auto &[o, t] : frameTranslation) {
if(id24 == o)
return t;
}
return String();
}
ByteVector Frame::keyToFrameID(const String &s)
{
const String key = s.upper();
for(const auto &[o, t] : frameTranslation) {
if(key == t)
return o;
}
return ByteVector();
}
String Frame::txxxToKey(const String &description)
{
const String d = description.upper();
for(const auto &[o, t] : txxxFrameTranslation) {
if(d == o)
return t;
}
return d;
}
String Frame::keyToTXXX(const String &s)
{
const String key = s.upper();
for(const auto &[o, t] : txxxFrameTranslation) {
if(key == t)
return o;
}
return s;
}
PropertyMap Frame::asProperties() const
{
if(dynamic_cast< const UnknownFrame *>(this)) {

View File

@@ -54,18 +54,9 @@ namespace TagLib {
class TAGLIB_EXPORT Frame
{
friend class Tag;
friend class FrameFactory;
friend class TableOfContentsFrame;
friend class ChapterFrame;
public:
/*!
* Creates a textual frame which corresponds to a single key in the PropertyMap
* interface. These are all (User)TextIdentificationFrames except TIPL and TMCL,
* all (User)URLLinkFrames, CommentsFrames, and UnsynchronizedLyricsFrame.
*/
static Frame *createTextualFrame(const String &key, const StringList &values);
class Header;
/*!
* Destroys this Frame instance.
@@ -122,12 +113,29 @@ namespace TagLib {
*/
ByteVector render() const;
/*!
* Returns a pointer to the frame header.
*/
Header *header() const;
/*!
* Returns the text delimiter that is used between fields for the string
* type \a t.
*/
static ByteVector textDelimiter(String::Type t);
/*!
* Returns an appropriate ID3 frame ID for the given free-form tag key. This method
* will return an empty ByteVector if no specialized translation is found.
*/
static ByteVector keyToFrameID(const String &);
/*!
* Returns a free-form tag name for the given ID3 frame ID. Note that this does not work
* for general frame IDs such as TXXX or WXXX; in such a case an empty string is returned.
*/
static String frameIDToKey(const ByteVector &);
/*!
* The string with which an instrument name is prefixed to build a key in a PropertyMap;
* used to translate PropertyMaps to TMCL frames. In the current implementation, this
@@ -151,8 +159,6 @@ namespace TagLib {
static const String urlPrefix;
protected:
class Header;
/*!
* Constructs an ID3v2 frame using \a data to read the header information.
* All other processing of \a data should be handled in a subclass.
@@ -170,11 +176,6 @@ namespace TagLib {
*/
Frame(Header *h);
/*!
* Returns a pointer to the frame header.
*/
Header *header() const;
/*!
* Sets the header to \a h. If \a deleteCurrent is true, this will free
* the memory of the current header.
@@ -236,28 +237,6 @@ namespace TagLib {
*/
virtual PropertyMap asProperties() const;
/*!
* Returns an appropriate ID3 frame ID for the given free-form tag key. This method
* will return an empty ByteVector if no specialized translation is found.
*/
static ByteVector keyToFrameID(const String &);
/*!
* Returns a free-form tag name for the given ID3 frame ID. Note that this does not work
* for general frame IDs such as TXXX or WXXX; in such a case an empty string is returned.
*/
static String frameIDToKey(const ByteVector &);
/*!
* Returns an appropriate TXXX frame description for the given free-form tag key.
*/
static String keyToTXXX(const String &);
/*!
* Returns a free-form tag name for the given ID3 frame description.
*/
static String txxxToKey(const String &);
/*!
* This helper function splits the PropertyMap \a original into three ProperytMaps
* \a singleFrameProperties, \a tiplProperties, and \a tmclProperties, such that:

View File

@@ -370,6 +370,11 @@ void FrameFactory::setDefaultTextEncoding(String::Type encoding)
d->defaultEncoding = encoding;
}
bool FrameFactory::isUsingDefaultTextEncoding() const
{
return d->useDefaultEncoding;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
@@ -538,3 +543,54 @@ bool FrameFactory::updateFrame(Frame::Header *header) const
return true;
}
Frame *FrameFactory::createFrameForProperty(const String &key, const StringList &values) const
{
// check if the key is contained in the key<=>frameID mapping
ByteVector frameID = Frame::keyToFrameID(key);
if(!frameID.isEmpty()) {
// Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number), GRP1 (Grouping) are in fact text frames.
if(frameID[0] == 'T' || frameID == "WFED" || frameID == "MVNM" || frameID == "MVIN" || frameID == "GRP1"){ // text frame
auto frame = new TextIdentificationFrame(frameID, String::UTF8);
frame->setText(values);
return frame;
} if((frameID[0] == 'W') && (values.size() == 1)){ // URL frame (not WXXX); support only one value
auto frame = new UrlLinkFrame(frameID);
frame->setUrl(values.front());
return frame;
} if(frameID == "PCST") {
return new PodcastFrame();
}
}
if(key == "MUSICBRAINZ_TRACKID" && values.size() == 1) {
auto frame = new UniqueFileIdentifierFrame("http://musicbrainz.org", values.front().data(String::UTF8));
return frame;
}
// now we check if it's one of the "special" cases:
// -LYRICS: depending on the number of values, use USLT or TXXX (with description=LYRICS)
if((key == "LYRICS" || key.startsWith(Frame::lyricsPrefix)) && values.size() == 1){
auto frame = new UnsynchronizedLyricsFrame(String::UTF8);
frame->setDescription(key == "LYRICS" ? key : key.substr(Frame::lyricsPrefix.size()));
frame->setText(values.front());
return frame;
}
// -URL: depending on the number of values, use WXXX or TXXX (with description=URL)
if((key == "URL" || key.startsWith(Frame::urlPrefix)) && values.size() == 1){
auto frame = new UserUrlLinkFrame(String::UTF8);
frame->setDescription(key == "URL" ? key : key.substr(Frame::urlPrefix.size()));
frame->setUrl(values.front());
return frame;
}
// -COMMENT: depending on the number of values, use COMM or TXXX (with description=COMMENT)
if((key == "COMMENT" || key.startsWith(Frame::commentPrefix)) && values.size() == 1){
auto frame = new CommentsFrame(String::UTF8);
if (key != "COMMENT"){
frame->setDescription(key.substr(Frame::commentPrefix.size()));
}
frame->setText(values.front());
return frame;
}
// if none of the above cases apply, we use a TXXX frame with the key as description
return new UserTextIdentificationFrame(
UserTextIdentificationFrame::keyToTXXX(key), values, String::UTF8);
}

View File

@@ -50,10 +50,11 @@ namespace TagLib {
* factory to be the default factory in ID3v2::Tag constructor you can
* implement behavior that will allow for new ID3v2::Frame subclasses (also
* provided by you) to be used.
* See \c testFrameFactory() in \e tests/test_mpeg.cpp for an example.
*
* This implements both <i>abstract factory</i> and <i>singleton</i> patterns
* of which more information is available on the web and in software design
* textbooks (Notably <i>Design Patters</i>).
* textbooks (Notably <i>Design Patterns</i>).
*
* \note You do not need to use this factory to create new frames to add to
* an ID3v2::Tag. You can instantiate frame subclasses directly (with new)
@@ -76,6 +77,14 @@ namespace TagLib {
*/
virtual Frame *createFrame(const ByteVector &data, const Header *tagHeader) const;
/*!
* Creates a textual frame which corresponds to a single key in the
* PropertyMap interface. TIPL and TMCL do not belong to this category
* and are thus handled explicitly in the Frame class.
*/
virtual Frame *createFrameForProperty(
const String &key, const StringList &values) const;
/*!
* After a tag has been read, this tries to rebuild some of them
* information, most notably the recording date, from frames that
@@ -105,6 +114,18 @@ namespace TagLib {
*/
void setDefaultTextEncoding(String::Type encoding);
/*!
* Returns true if defaultTextEncoding() is used.
* The default text encoding is used when setDefaultTextEncoding() has
* been called. In this case, reimplementations of FrameFactory should
* use defaultTextEncoding() on the frames (having a text encoding field)
* they create.
*
* \see defaultTextEncoding()
* \see setDefaultTextEncoding()
*/
bool isUsingDefaultTextEncoding() const;
protected:
/*!
* Constructs a frame factory. Because this is a singleton this method is

View File

@@ -443,13 +443,13 @@ PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps)
// now create remaining frames:
// start with the involved people list (TIPL)
if(!tiplProperties.isEmpty())
addFrame(TextIdentificationFrame::createTIPLFrame(tiplProperties));
addFrame(TextIdentificationFrame::createTIPLFrame(tiplProperties));
// proceed with the musician credit list (TMCL)
if(!tmclProperties.isEmpty())
addFrame(TextIdentificationFrame::createTMCLFrame(tmclProperties));
addFrame(TextIdentificationFrame::createTMCLFrame(tmclProperties));
// now create the "one key per frame" frames
for(const auto &[tag, frames] : std::as_const(properties))
addFrame(Frame::createTextualFrame(tag, frames));
addFrame(d->factory->createFrameForProperty(tag, frames));
return PropertyMap(); // ID3 implements the complete PropertyMap interface, so an empty map is returned
}