mirror of
https://github.com/taglib/taglib.git
synced 2025-05-27 21:20:26 -04:00
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:
parent
59166f6757
commit
3d67b139e4
@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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 &);
|
||||
|
@ -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)) {
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
379
tests/test_id3v2framefactory.cpp
Normal file
379
tests/test_id3v2framefactory.cpp
Normal file
@ -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 <functional>
|
||||
#include <memory>
|
||||
|
||||
#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 <cppunit/extensions/HelperMacros.h>
|
||||
#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<File *(const char *)> createFileWithDefaultFactory,
|
||||
function<File *(const char *, ID3v2::FrameFactory *factory)> createFileWithFactory,
|
||||
function<bool(const File &)> hasID3v2Tag,
|
||||
function<ID3v2::Tag *(File &)> getID3v2Tag,
|
||||
function<bool(File &)> stripAllTags)
|
||||
{
|
||||
CustomFrameFactory factory;
|
||||
|
||||
{
|
||||
auto f = std::unique_ptr<File>(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<File>(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<File>(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<CustomFrame *>(frames.front()));
|
||||
}
|
||||
{
|
||||
auto f = std::unique_ptr<File>(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<CustomFrame *>(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<File>(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<File>(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<CustomFrame *>(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<const MPEG::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<MPEG::File &>(f).ID3v2Tag(true);
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<MPEG::File &>(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<const FLAC::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<FLAC::File &>(f).ID3v2Tag(true);
|
||||
},
|
||||
[](File &f) {
|
||||
static_cast<FLAC::File &>(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<const TrueAudio::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<TrueAudio::File &>(f).ID3v2Tag(true);
|
||||
},
|
||||
[](File &f) {
|
||||
static_cast<TrueAudio::File &>(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<const RIFF::WAV::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<RIFF::WAV::File &>(f).tag();
|
||||
},
|
||||
[](File &f) {
|
||||
static_cast<RIFF::WAV::File &>(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<const RIFF::AIFF::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<RIFF::AIFF::File &>(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<DSF::File &>(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<const DSDIFF::File &>(f).hasID3v2Tag();
|
||||
},
|
||||
[](File &f) {
|
||||
return static_cast<DSDIFF::File &>(f).ID3v2Tag(true);
|
||||
},
|
||||
[](File &f) {
|
||||
static_cast<DSDIFF::File &>(f).strip();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestId3v2FrameFactory);
|
Loading…
Reference in New Issue
Block a user