diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp
index 409cbbdd..9ead4d62 100644
--- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp
+++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp
@@ -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
////////////////////////////////////////////////////////////////////////////////
diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.h b/taglib/mpeg/id3v2/frames/textidentificationframe.h
index 9b53fb83..e6c8755d 100644
--- a/taglib/mpeg/id3v2/frames/textidentificationframe.h
+++ b/taglib/mpeg/id3v2/frames/textidentificationframe.h
@@ -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 &);
diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp
index 6c37a6a6..04b45711 100644
--- a/taglib/mpeg/id3v2/id3v2frame.cpp
+++ b/taglib/mpeg/id3v2/id3v2frame.cpp
@@ -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)) {
diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h
index 0f090453..41743ff6 100644
--- a/taglib/mpeg/id3v2/id3v2frame.h
+++ b/taglib/mpeg/id3v2/id3v2frame.h
@@ -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:
diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp
index 5f692804..66f39509 100644
--- a/taglib/mpeg/id3v2/id3v2framefactory.cpp
+++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp
@@ -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);
+}
diff --git a/taglib/mpeg/id3v2/id3v2framefactory.h b/taglib/mpeg/id3v2/id3v2framefactory.h
index 17bdc2a1..f41f60a6 100644
--- a/taglib/mpeg/id3v2/id3v2framefactory.h
+++ b/taglib/mpeg/id3v2/id3v2framefactory.h
@@ -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 abstract factory and singleton patterns
* of which more information is available on the web and in software design
- * textbooks (Notably Design Patters).
+ * textbooks (Notably Design Patterns).
*
* \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
diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp
index 689970ec..9fc10b4b 100644
--- a/taglib/mpeg/id3v2/id3v2tag.cpp
+++ b/taglib/mpeg/id3v2/id3v2tag.cpp
@@ -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
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index fbd6eb83..25e05979 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -47,6 +47,7 @@ SET(test_runner_SRCS
test_fileref.cpp
test_id3v1.cpp
test_id3v2.cpp
+ test_id3v2framefactory.cpp
test_xiphcomment.cpp
test_aiff.cpp
test_riff.cpp
diff --git a/tests/test_id3v2framefactory.cpp b/tests/test_id3v2framefactory.cpp
new file mode 100644
index 00000000..bdc4cfcb
--- /dev/null
+++ b/tests/test_id3v2framefactory.cpp
@@ -0,0 +1,379 @@
+/***************************************************************************
+ copyright : (C) 2023 by Urs Fleisch
+ email : ufleisch@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * This library is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU Lesser General Public License version *
+ * 2.1 as published by the Free Software Foundation. *
+ * *
+ * This library is distributed in the hope that it will be useful, but *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+ * Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with this library; if not, write to the Free Software *
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
+ * 02110-1301 USA *
+ * *
+ * Alternatively, this file is available under the Mozilla Public *
+ * License Version 1.1. You may obtain a copy of the License at *
+ * http://www.mozilla.org/MPL/ *
+ ***************************************************************************/
+
+#include
+#include
+
+#include "tbytevector.h"
+#include "tpropertymap.h"
+#include "mpegfile.h"
+#include "flacfile.h"
+#include "trueaudiofile.h"
+#include "wavfile.h"
+#include "aifffile.h"
+#include "dsffile.h"
+#include "dsdifffile.h"
+#include "id3v2tag.h"
+#include "id3v2frame.h"
+#include "id3v2framefactory.h"
+#include
+#include "utils.h"
+
+using namespace std;
+using namespace TagLib;
+
+namespace
+{
+
+ class CustomFrameFactory;
+
+ // Just a silly example of a custom frame holding a number.
+ class CustomFrame : public ID3v2::Frame
+ {
+ friend class CustomFrameFactory;
+ public:
+ explicit CustomFrame(unsigned int value = 0)
+ : Frame("CUST"), m_value(value) {}
+ CustomFrame(const CustomFrame &) = delete;
+ CustomFrame &operator=(const CustomFrame &) = delete;
+ ~CustomFrame() override = default;
+ String toString() const override { return String::number(m_value); }
+ PropertyMap asProperties() const override {
+ return SimplePropertyMap{{"CUSTOM", StringList(String::number(m_value))}};
+ }
+ unsigned int value() const { return m_value; }
+
+ protected:
+ void parseFields(const ByteVector &data) override {
+ m_value = data.toUInt();
+ }
+ ByteVector renderFields() const override {
+ return ByteVector::fromUInt(m_value);
+ }
+
+ private:
+ CustomFrame(const ByteVector &data, Header *h) : Frame(h) {
+ parseFields(fieldData(data));
+ }
+ unsigned int m_value;
+ };
+
+ // Example for frame factory with support for CustomFrame.
+ class CustomFrameFactory : public ID3v2::FrameFactory {
+ public:
+ ID3v2::Frame *createFrameForProperty(
+ const String &key, const StringList &values) const override {
+ if(key == "CUSTOM") {
+ return new CustomFrame(!values.isEmpty() ? values.front().toInt() : 0);
+ }
+ return ID3v2::FrameFactory::createFrameForProperty(key, values);
+ }
+
+ protected:
+ ID3v2::Frame *createFrame(const ByteVector &data, ID3v2::Frame::Header *header,
+ const ID3v2::Header *tagHeader) const override {
+ if(header->frameID() == "CUST") {
+ return new CustomFrame(data, header);
+ }
+ return ID3v2::FrameFactory::createFrame(data, header, tagHeader);
+ }
+ };
+
+} // namespace
+
+class TestId3v2FrameFactory : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(TestId3v2FrameFactory);
+ CPPUNIT_TEST(testMPEG);
+ CPPUNIT_TEST(testFLAC);
+ CPPUNIT_TEST(testTrueAudio);
+ CPPUNIT_TEST(testWAV);
+ CPPUNIT_TEST(testAIFF);
+ CPPUNIT_TEST(testDSF);
+ CPPUNIT_TEST(testDSDIFF);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+
+ void testGenericFrameFactory(
+ const char *fileName,
+ function createFileWithDefaultFactory,
+ function createFileWithFactory,
+ function hasID3v2Tag,
+ function getID3v2Tag,
+ function stripAllTags)
+ {
+ CustomFrameFactory factory;
+
+ {
+ auto f = std::unique_ptr(createFileWithDefaultFactory(fileName));
+ CPPUNIT_ASSERT(f->isValid());
+ ID3v2::Tag *tag = getID3v2Tag(*f);
+ const ID3v2::FrameList frames = tag->frameList();
+ for(const auto &frame : frames) {
+ tag->removeFrame(frame, false);
+ }
+ tag->setArtist("An artist");
+ tag->setTitle("A title");
+ f->save();
+ }
+ {
+ auto f = std::unique_ptr(createFileWithDefaultFactory(fileName));
+ CPPUNIT_ASSERT(f->isValid());
+ CPPUNIT_ASSERT(hasID3v2Tag(*f));
+ ID3v2::Tag *tag = getID3v2Tag(*f);
+ tag->addFrame(new CustomFrame(1234567890));
+ f->save();
+ }
+ {
+ auto f = std::unique_ptr(createFileWithDefaultFactory(fileName));
+ CPPUNIT_ASSERT(f->isValid());
+ CPPUNIT_ASSERT(hasID3v2Tag(*f));
+ ID3v2::Tag *tag = getID3v2Tag(*f);
+ const auto &frames = tag->frameList("CUST");
+ CPPUNIT_ASSERT(!frames.isEmpty());
+ // Without a specialized FrameFactory, you can add custom frames,
+ // but your cannot parse them.
+ CPPUNIT_ASSERT(!dynamic_cast(frames.front()));
+ }
+ {
+ auto f = std::unique_ptr(createFileWithFactory(fileName, &factory));
+ CPPUNIT_ASSERT(f->isValid());
+ CPPUNIT_ASSERT(hasID3v2Tag(*f));
+ ID3v2::Tag *tag = getID3v2Tag(*f);
+ const auto &frames = tag->frameList("CUST");
+ CPPUNIT_ASSERT(!frames.isEmpty());
+ auto frame = dynamic_cast(frames.front());
+ CPPUNIT_ASSERT(frame);
+ CPPUNIT_ASSERT_EQUAL(1234567890U, frame->value());
+ PropertyMap properties = tag->properties();
+ CPPUNIT_ASSERT_EQUAL(StringList("1234567890"),
+ properties.value("CUSTOM"));
+ CPPUNIT_ASSERT_EQUAL(StringList("An artist"),
+ properties.value("ARTIST"));
+ CPPUNIT_ASSERT_EQUAL(StringList("A title"),
+ properties.value("TITLE"));
+ stripAllTags(*f);
+ }
+ {
+ auto f = std::unique_ptr(createFileWithFactory(fileName, &factory));
+ CPPUNIT_ASSERT(f->isValid());
+ CPPUNIT_ASSERT(!hasID3v2Tag(*f));
+ ID3v2::Tag *tag = getID3v2Tag(*f);
+ PropertyMap properties = tag->properties();
+ CPPUNIT_ASSERT(properties.isEmpty());
+ properties.insert("CUSTOM", StringList("305419896"));
+ tag->setProperties(properties);
+ f->save();
+ }
+ {
+ auto f = std::unique_ptr(createFileWithFactory(fileName, &factory));
+ CPPUNIT_ASSERT(f->isValid());
+ CPPUNIT_ASSERT(hasID3v2Tag(*f));
+ ID3v2::Tag *tag = getID3v2Tag(*f);
+ PropertyMap properties = tag->properties();
+ CPPUNIT_ASSERT_EQUAL(StringList("305419896"), properties.value("CUSTOM"));
+ const auto &frames = tag->frameList("CUST");
+ CPPUNIT_ASSERT(!frames.isEmpty());
+ auto frame = dynamic_cast(frames.front());
+ CPPUNIT_ASSERT(frame);
+ CPPUNIT_ASSERT_EQUAL(0x12345678U, frame->value());
+ }
+ }
+
+ void testMPEG()
+ {
+ ScopedFileCopy copy("lame_cbr", ".mp3");
+ testGenericFrameFactory(
+ copy.fileName().c_str(),
+ [](const char *fileName) {
+ return new MPEG::File(fileName);
+ },
+ [](const char *fileName, ID3v2::FrameFactory *factory) {
+ return new MPEG::File(fileName, factory);
+ },
+ [](const File &f) {
+ return static_cast(f).hasID3v2Tag();
+ },
+ [](File &f) {
+ return static_cast(f).ID3v2Tag(true);
+ },
+ [](File &f) {
+ return static_cast(f).strip();
+ }
+ );
+ }
+
+ void testFLAC()
+ {
+ ScopedFileCopy copy("no-tags", ".flac");
+ testGenericFrameFactory(
+ copy.fileName().c_str(),
+ [](const char *fileName) {
+ return new FLAC::File(fileName);
+ },
+ [](const char *fileName, ID3v2::FrameFactory *factory) {
+ return new FLAC::File(fileName, factory);
+ },
+ [](const File &f) {
+ return static_cast(f).hasID3v2Tag();
+ },
+ [](File &f) {
+ return static_cast(f).ID3v2Tag(true);
+ },
+ [](File &f) {
+ static_cast(f).strip();
+ return f.save();
+ }
+ );
+ }
+
+ void testTrueAudio()
+ {
+ ScopedFileCopy copy("empty", ".tta");
+ testGenericFrameFactory(
+ copy.fileName().c_str(),
+ [](const char *fileName) {
+ return new TrueAudio::File(fileName);
+ },
+ [](const char *fileName, ID3v2::FrameFactory *factory) {
+ return new TrueAudio::File(fileName, factory);
+ },
+ [](const File &f) {
+ return static_cast(f).hasID3v2Tag();
+ },
+ [](File &f) {
+ return static_cast(f).ID3v2Tag(true);
+ },
+ [](File &f) {
+ static_cast(f).strip();
+ return f.save();
+ }
+ );
+ }
+
+ void testWAV()
+ {
+ ScopedFileCopy copy("empty", ".wav");
+ testGenericFrameFactory(
+ copy.fileName().c_str(),
+ [](const char *fileName) {
+ return new RIFF::WAV::File(fileName);
+ },
+ [](const char *fileName, ID3v2::FrameFactory *factory) {
+ return new RIFF::WAV::File(
+ fileName, true, RIFF::WAV::Properties::Average, factory);
+ },
+ [](const File &f) {
+ return static_cast(f).hasID3v2Tag();
+ },
+ [](File &f) {
+ return static_cast(f).tag();
+ },
+ [](File &f) {
+ static_cast(f).strip();
+ return true;
+ }
+ );
+ }
+
+ void testAIFF()
+ {
+ ScopedFileCopy copy("empty", ".aiff");
+ testGenericFrameFactory(
+ copy.fileName().c_str(),
+ [](const char *fileName) {
+ return new RIFF::AIFF::File(fileName);
+ },
+ [](const char *fileName, ID3v2::FrameFactory *factory) {
+ return new RIFF::AIFF::File(
+ fileName, true, RIFF::AIFF::Properties::Average, factory);
+ },
+ [](const File &f) {
+ return static_cast(f).hasID3v2Tag();
+ },
+ [](File &f) {
+ return static_cast(f).tag();
+ },
+ [](File &f) {
+ f.setProperties({});
+ return f.save();
+ }
+ );
+ }
+
+ void testDSF()
+ {
+ ScopedFileCopy copy("empty10ms", ".dsf");
+ testGenericFrameFactory(
+ copy.fileName().c_str(),
+ [](const char *fileName) {
+ return new DSF::File(fileName);
+ },
+ [](const char *fileName, ID3v2::FrameFactory *factory) {
+ return new DSF::File(
+ fileName, true, DSF::Properties::Average, factory);
+ },
+ [](const File &f) {
+ return !f.tag()->isEmpty();
+ },
+ [](File &f) {
+ return static_cast(f).tag();
+ },
+ [](File &f) {
+ f.setProperties({});
+ return f.save();
+ }
+ );
+ }
+
+ void testDSDIFF()
+ {
+ ScopedFileCopy copy("empty10ms", ".dff");
+ testGenericFrameFactory(
+ copy.fileName().c_str(),
+ [](const char *fileName) {
+ return new DSDIFF::File(fileName);
+ },
+ [](const char *fileName, ID3v2::FrameFactory *factory) {
+ return new DSDIFF::File(
+ fileName, true, DSDIFF::Properties::Average, factory);
+ },
+ [](const File &f) {
+ return static_cast(f).hasID3v2Tag();
+ },
+ [](File &f) {
+ return static_cast(f).ID3v2Tag(true);
+ },
+ [](File &f) {
+ static_cast(f).strip();
+ return true;
+ }
+ );
+ }
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestId3v2FrameFactory);