Commit a612d886 authored by Urs Fleisch's avatar Urs Fleisch
Browse files

new frame types Grouping, Lyrics; new method Frame::merge()

parent 7be4bf66
......@@ -80,9 +80,11 @@ const char* Frame::getNameFromType(Type type)
I18N_NOOP("Copyright"), // FT_Copyright,
I18N_NOOP("Disc Number"), // FT_Disc,
I18N_NOOP("Encoded-by"), // FT_EncodedBy,
I18N_NOOP("Grouping"), // FT_Grouping,
I18N_NOOP("ISRC"), // FT_Isrc,
I18N_NOOP("Language"), // FT_Language,
I18N_NOOP("Lyricist"), // FT_Lyricist,
I18N_NOOP("Lyrics"), // FT_Lyrics,
I18N_NOOP("Media"), // FT_Media,
I18N_NOOP("Original Album"), // FT_OriginalAlbum,
I18N_NOOP("Original Artist"), // FT_OriginalArtist,
......@@ -267,6 +269,34 @@ void FrameCollection::removeDisabledFrames(const FrameFilter& flt)
}
}
/**
* Copy frames which are empty or inactive from other frames.
* This can be used to merge two frame collections.
*
* @param frames other frames
*/
void FrameCollection::merge(const FrameCollection& frames)
{
for (const_iterator otherIt = frames.begin();
otherIt != frames.end();
++otherIt) {
iterator it = find(*otherIt);
if (it != end()) {
QString value(otherIt->getValue());
Frame& frameFound = const_cast<Frame&>(*it);
if (frameFound.getValue().isEmpty() && !value.isEmpty()) {
frameFound.setValue(value);
frameFound.setValueChanged();
}
} else {
Frame frame(*otherIt);
frame.setIndex(-1);
frame.setValueChanged(true);
insert(frame);
}
}
}
/**
* Find a frame by name.
*
......
......@@ -61,9 +61,11 @@ public:
FT_Copyright,
FT_Disc,
FT_EncodedBy,
FT_Grouping,
FT_Isrc,
FT_Language,
FT_Lyricist,
FT_Lyrics,
FT_Media,
FT_OriginalAlbum,
FT_OriginalArtist,
......@@ -350,7 +352,12 @@ public:
void enable(Frame::Type type, const QString& name = QString(), bool en = true);
private:
enum { FTM_AllFrames = (1 << (Frame::FT_LastFrame + 1)) - 1 };
class not_used { int num_frame_types_check[
Frame::FT_LastFrame == 31
? 1 : -1 ]; };
enum { FTM_AllFrames = 0xffffffff };
// if FTM_AllFrames is not 31, use
// enum { FTM_AllFrames = (1 << (Frame::FT_LastFrame + 1)) - 1 };
unsigned long m_enabledFrames;
std::set<QString> m_disabledOtherFrames;
};
......@@ -396,6 +403,14 @@ public:
*/
void removeDisabledFrames(const FrameFilter& flt);
/**
* Copy frames which are empty or inactive from other frames.
* This can be used to merge two frame collections.
*
* @param frames other frames
*/
void merge(const FrameCollection& frames);
/**
* Find a frame by name.
*
......
......@@ -83,8 +83,8 @@ ImportConfig::ImportConfig(const QString& grp) :
"%{author}([^\\r\\n\\t]*)\\t%{bpm}([^\\r\\n\\t]*)\\t"
"%{composer}([^\\r\\n\\t]*)\\t%{conductor}([^\\r\\n\\t]*)\\t"
"%{copyright}([^\\r\\n\\t]*)\\t%{disc number}([^\\r\\n\\t]*)\\t"
"%{encoded-by}([^\\r\\n\\t]*)\\t%{isrc}([^\\r\\n\\t]*)\\t"
"%{language}([^\\r\\n\\t]*)\\t%{lyricist}([^\\r\\n\\t]*)\\t"
"%{encoded-by}([^\\r\\n\\t]*)\\t%{grouping}([^\\r\\n\\t]*)\\t%{isrc}([^\\r\\n\\t]*)\\t"
"%{language}([^\\r\\n\\t]*)\\t%{lyricist}([^\\r\\n\\t]*)\\t%{lyrics}([^\\r\\n\\t]*)\\t"
"%{media}([^\\r\\n\\t]*)\\t%{original album}([^\\r\\n\\t]*)\\t"
"%{original artist}([^\\r\\n\\t]*)\\t%{original date}([^\\r\\n\\t]*)\\t"
"%{part}([^\\r\\n\\t]*)\\t%{performer}([^\\r\\n\\t]*)\\t"
......@@ -103,8 +103,8 @@ ImportConfig::ImportConfig(const QString& grp) :
"\"?%{author}([^\\r\\n\\t\"]*)\"?\\t\"?%{bpm}([^\\r\\n\\t\"]*)\"?\\t"
"\"?%{composer}([^\\r\\n\\t\"]*)\"?\\t\"?%{conductor}([^\\r\\n\\t\"]*)\"?\\t"
"\"?%{copyright}([^\\r\\n\\t\"]*)\"?\\t\"?%{disc number}([^\\r\\n\\t\"]*)\"?\\t"
"\"?%{encoded-by}([^\\r\\n\\t\"]*)\"?\\t\"?%{isrc}([^\\r\\n\\t\"]*)\"?\\t"
"\"?%{language}([^\\r\\n\\t\"]*)\"?\\t\"?%{lyricist}([^\\r\\n\\t\"]*)\"?\\t"
"\"?%{encoded-by}([^\\r\\n\\t\"]*)\"?\\t\"?%{grouping}([^\\r\\n\\t\"]*)\"?\\t\"?%{isrc}([^\\r\\n\\t\"]*)\"?\\t"
"\"?%{language}([^\\r\\n\\t\"]*)\"?\\t\"?%{lyricist}([^\\r\\n\\t\"]*)\"?\\t\"?%{lyrics}([^\\r\\n\\t\"]*)\"?\\t"
"\"?%{media}([^\\r\\n\\t\"]*)\"?\\t\"?%{original album}([^\\r\\n\\t\"]*)\"?\\t"
"\"?%{original artist}([^\\r\\n\\t\"]*)\"?\\t\"?%{original date}([^\\r\\n\\t\"]*)\"?\\t"
"\"?%{part}([^\\r\\n\\t\"]*)\"?\\t\"?%{performer}([^\\r\\n\\t\"]*)\"?\\t"
......@@ -149,16 +149,16 @@ ImportConfig::ImportConfig(const QString& grp) :
m_exportFormatHeaders.append(
"Track\\tTitle\\tArtist\\tAlbum\\tDate\\tGenre\\tComment\\tDuration\\t"
"Album Artist\\tArranger\\tAuthor\\tBPM\\tComposer\\t"
"Conductor\\tCopyright\\tDisc Number\\tEncoded-by\\tISRC\\t"
"Language\\tLyricist\\tMedia\\tOriginal Album\\t"
"Conductor\\tCopyright\\tDisc Number\\tEncoded-by\\tGrouping\\tISRC\\t"
"Language\\tLyricist\\tLyrics\\tMedia\\tOriginal Album\\t"
"Original Artist\\tOriginal Date\\tPart\\tPerformer\\t"
"Publisher\\tRemixer\\tSubtitle\\tWebsite");
m_exportFormatTracks.append(
"%{track}\\t%{title}\\t%{artist}\\t%{album}\\t%{year}\\t%{genre}\\t%{comment}\\t"
"%{duration}.00\\t"
"%{album artist}\\t%{arranger}\\t%{author}\\t%{bpm}\\t%{composer}\\t"
"%{conductor}\\t%{copyright}\\t%{disc number}\\t%{encoded-by}\\t%{isrc}\\t"
"%{language}\\t%{lyricist}\\t%{media}\\t%{original album}\\t"
"%{conductor}\\t%{copyright}\\t%{disc number}\\t%{encoded-by}\\t%{grouping}\\t%{isrc}\\t"
"%{language}\\t%{lyricist}\\t%{lyrics}\\t%{media}\\t%{original album}\\t"
"%{original artist}\\t%{original date}\\t%{part}\\t%{performer}\\t"
"%{publisher}\\t%{remixer}\\t%{subtitle}\\t%{website}");
m_exportFormatTrailers.append("");
......@@ -169,7 +169,7 @@ ImportConfig::ImportConfig(const QString& grp) :
"\"Genre\"\\t\"Comment\"\\t\"Duration\"\\t"
"\"Album Artist\"\\t\"Arranger\"\\t\"Author\"\\t\"BPM\"\\t"
"\"Composer\"\\t\"Conductor\"\\t\"Copyright\"\\t\"Disc Number\"\\t"
"\"Encoded-by\"\\t\"ISRC\"\\t\"Language\"\\t\"Lyricist\"\\t"
"\"Encoded-by\"\\t\"Grouping\"\\t\"ISRC\"\\t\"Language\"\\t\"Lyricist\"\\t\"Lyrics\"\\t"
"\"Media\"\\t\"Original Album\"\\t\"Original Artist\"\\t"
"\"Original Date\"\\t\"Part\"\\t\"Performer\"\\t\"Publisher\"\\t"
"\"Remixer\"\\t\"Subtitle\"\\t\"Website\"");
......@@ -178,7 +178,7 @@ ImportConfig::ImportConfig(const QString& grp) :
"\"%{genre}\"\\t\"%{comment}\"\\t\"%{duration}.00\"\\t"
"\"%{album artist}\"\\t\"%{arranger}\"\\t\"%{author}\"\\t\"%{bpm}\"\\t"
"\"%{composer}\"\\t\"%{conductor}\"\\t\"%{copyright}\"\\t\"%{disc number}\"\\t"
"\"%{encoded-by}\"\\t\"%{isrc}\"\\t\"%{language}\"\\t\"%{lyricist}\"\\t"
"\"%{encoded-by}\"\\t\"%{grouping}\"\\t\"%{isrc}\"\\t\"%{language}\"\\t\"%{lyricist}\"\\t\"%{lyrics}\"\\t"
"\"%{media}\"\\t\"%{original album}\"\\t\"%{original artist}\"\\t"
"\"%{original date}\"\\t\"%{part}\"\\t\"%{performer}\"\\t\"%{publisher}\"\\t"
"\"%{remixer}\"\\t\"%{subtitle}\"\\t\"%{website}\"");
......
......@@ -91,13 +91,39 @@ static const struct {
{ "cpil", Frame::FT_Other },
{ "tmpo", Frame::FT_Bpm },
#if MPEG4IP_MAJOR_MINOR_VERSION >= 0x0105
{ "\251grp", Frame::FT_Other },
{ "\251grp", Frame::FT_Grouping },
#endif
#if MPEG4IP_MAJOR_MINOR_VERSION >= 0x0106
{ "aART", Frame::FT_AlbumArtist },
{ "pgap", Frame::FT_Other },
#endif
{ "covr", Frame::FT_Picture }
},
freeFormNameTypes[] = {
#if !(MPEG4IP_MAJOR_MINOR_VERSION >= 0x0105)
{ "GROUPING", Frame::FT_Grouping },
#endif
#if !(MPEG4IP_MAJOR_MINOR_VERSION >= 0x0106)
{ "ALBUMARTIST", Frame::FT_AlbumArtist },
#endif
{ "ARRANGER", Frame::FT_Arranger },
{ "AUTHOR", Frame::FT_Author },
{ "CONDUCTOR", Frame::FT_Conductor },
{ "COPYRIGHT", Frame::FT_Copyright },
{ "ISRC", Frame::FT_Isrc },
{ "LANGUAGE", Frame::FT_Language },
{ "LYRICIST", Frame::FT_Lyricist },
{ "LYRICS", Frame::FT_Lyrics },
{ "SOURCEMEDIA", Frame::FT_Media },
{ "ORIGINALALBUM", Frame::FT_OriginalAlbum },
{ "ORIGINALARTIST", Frame::FT_OriginalArtist },
{ "ORIGINALDATE", Frame::FT_OriginalDate },
{ "PART", Frame::FT_Part },
{ "PERFORMER", Frame::FT_Performer },
{ "PUBLISHER", Frame::FT_Publisher },
{ "REMIXER", Frame::FT_Remixer },
{ "SUBTITLE", Frame::FT_Subtitle },
{ "WEBSITE", Frame::FT_Website }
};
/**
......@@ -117,6 +143,9 @@ static QString getNameForType(Frame::Type type)
typeNameMap.insert(nameTypes[i].type, nameTypes[i].name);
}
}
for (unsigned i = 0; i < sizeof(freeFormNameTypes) / sizeof(freeFormNameTypes[0]); ++i) {
typeNameMap.insert(freeFormNameTypes[i].type, freeFormNameTypes[i].name);
}
}
if (type != Frame::FT_Other) {
QMap<Frame::Type, QString>::const_iterator it = typeNameMap.find(type);
......@@ -153,7 +182,21 @@ static Frame::Type getTypeForName(const QString& name,
return *it;
}
}
return onlyPredefined ? Frame::FT_UnknownFrame : Frame::FT_Other;
if (!onlyPredefined) {
static QMap<QString, Frame::Type> freeFormNameTypeMap;
if (freeFormNameTypeMap.empty()) {
// first time initialization
for (unsigned i = 0; i < sizeof(freeFormNameTypes) / sizeof(freeFormNameTypes[0]); ++i) {
freeFormNameTypeMap.insert(freeFormNameTypes[i].name, freeFormNameTypes[i].type);
}
}
QMap<QString, Frame::Type>::const_iterator it = freeFormNameTypeMap.find(name);
if (it != freeFormNameTypeMap.end()) {
return *it;
}
return Frame::FT_Other;
}
return Frame::FT_UnknownFrame;
}
#ifndef HAVE_MP4V2_MP4GETMETADATABYINDEX_CHARPP_ARG
......@@ -1002,6 +1045,9 @@ QStringList M4aFile::getFrameIds() const
Frame::FT_Composer,
Frame::FT_Disc,
Frame::FT_EncodedBy,
#if MPEG4IP_MAJOR_MINOR_VERSION >= 0x0105
Frame::FT_Grouping,
#endif
Frame::FT_Picture
};
......@@ -1010,9 +1056,6 @@ QStringList M4aFile::getFrameIds() const
lst.append(QCM_translate(Frame::getNameFromType(types[i])));
}
lst << "cpil";
#if MPEG4IP_MAJOR_MINOR_VERSION >= 0x0105
lst << "\251grp";
#endif
#if MPEG4IP_MAJOR_MINOR_VERSION >= 0x0106
lst << "pgap";
#endif
......
......@@ -1283,7 +1283,7 @@ static const struct TypeStrOfId {
{ Frame::FT_Lyricist, I18N_NOOP("TEXT - Lyricist/Text writer") }, /* TEXT */
{ Frame::FT_Other, I18N_NOOP("TFLT - File type") }, /* TFLT */
{ Frame::FT_Other, I18N_NOOP("TIME - Time") }, /* TIME */
{ Frame::FT_Other, I18N_NOOP("TIT1 - Content group description") }, /* TIT1 */
{ Frame::FT_Grouping, I18N_NOOP("TIT1 - Content group description") }, /* TIT1 */
{ Frame::FT_Title, I18N_NOOP("TIT2 - Title/songname/content description") }, /* TIT2 */
{ Frame::FT_Subtitle, I18N_NOOP("TIT3 - Subtitle/Description refinement") }, /* TIT3 */
{ Frame::FT_Other, I18N_NOOP("TKEY - Initial key") }, /* TKEY */
......@@ -1320,7 +1320,7 @@ static const struct TypeStrOfId {
{ Frame::FT_Date, I18N_NOOP("TYER - Year") }, /* TYER */
{ Frame::FT_Other, I18N_NOOP("UFID - Unique file identifier") }, /* UFID */
{ Frame::FT_Other, I18N_NOOP("USER - Terms of use") }, /* USER */
{ Frame::FT_Other, I18N_NOOP("USLT - Unsynchronized lyric/text transcription") }, /* USLT */
{ Frame::FT_Lyrics, I18N_NOOP("USLT - Unsynchronized lyric/text transcription") }, /* USLT */
{ Frame::FT_Other, I18N_NOOP("WCOM - Commercial information") }, /* WCOM */
{ Frame::FT_Other, I18N_NOOP("WCOP - Copyright/Legal information") }, /* WCOP */
{ Frame::FT_Other, I18N_NOOP("WOAF - Official audio file webpage") }, /* WOAF */
......
......@@ -254,9 +254,11 @@ static const char* getVorbisNameFromType(Frame::Type type)
"COPYRIGHT", // FT_Copyright,
"DISCNUMBER", // FT_Disc,
"ENCODED-BY", // FT_EncodedBy,
"GROUPING", // FT_Grouping,
"ISRC", // FT_Isrc,
"LANGUAGE", // FT_Language,
"LYRICIST", // FT_Lyricist,
"LYRICS", // FT_Lyrics,
"SOURCEMEDIA", // FT_Media,
"ORIGINALALBUM", // FT_OriginalAlbum,
"ORIGINALARTIST", // FT_OriginalArtist,
......
......@@ -1401,7 +1401,7 @@ static const struct TypeStrOfId {
{ Frame::FT_Lyricist, I18N_NOOP("TEXT - Lyricist/Text writer"), true },
{ Frame::FT_Other, I18N_NOOP("TFLT - File type"), true },
{ Frame::FT_Arranger, I18N_NOOP("TIPL - Involved people list"), true },
{ Frame::FT_Other, I18N_NOOP("TIT1 - Content group description"), true },
{ Frame::FT_Grouping, I18N_NOOP("TIT1 - Content group description"), true },
{ Frame::FT_Title, I18N_NOOP("TIT2 - Title/songname/content description"), true },
{ Frame::FT_Subtitle, I18N_NOOP("TIT3 - Subtitle/Description refinement"), true },
{ Frame::FT_Other, I18N_NOOP("TKEY - Initial key"), true },
......@@ -1434,7 +1434,7 @@ static const struct TypeStrOfId {
{ Frame::FT_Other, I18N_NOOP("TXXX - User defined text information"), true },
{ Frame::FT_Other, I18N_NOOP("UFID - Unique file identifier"), true },
{ Frame::FT_Other, I18N_NOOP("USER - Terms of use"), false },
{ Frame::FT_Other, I18N_NOOP("USLT - Unsynchronized lyric/text transcription"), true },
{ Frame::FT_Lyrics, I18N_NOOP("USLT - Unsynchronized lyric/text transcription"), true },
{ Frame::FT_Other, I18N_NOOP("WCOM - Commercial information"), true },
{ Frame::FT_Other, I18N_NOOP("WCOP - Copyright/Legal information"), true },
{ Frame::FT_Other, I18N_NOOP("WOAF - Official audio file webpage"), true },
......@@ -2306,82 +2306,6 @@ void TagLibFile::setId3v2Frame(
}
}
/**
* Set a frame in the tags 2.
*
* @param frame frame to set
*
* @return true if ok.
*/
bool TagLibFile::setFrameV2(const Frame& frame)
{
// If the frame has an index, change that specific frame
int index = frame.getIndex();
if (index != -1 && m_tagV2) {
TagLib::ID3v2::Tag* id3v2Tag;
TagLib::Ogg::XiphComment* oggTag;
TagLib::APE::Tag* apeTag;
if ((id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tagV2)) != 0) {
const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
if (index < static_cast<int>(frameList.size())) {
// This is a hack. The frameList should not be modified directly.
// However when removing the old frame and adding a new frame,
// the indices of all frames get invalid.
setId3v2Frame(frameList[index], frame);
markTag2Changed();
return true;
}
} else if ((oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tagV2)) != 0) {
TagLib::String key = QSTRING_TO_TSTRING(frame.getName(true));
TagLib::String value = QSTRING_TO_TSTRING(frame.getValue());
#if TAGLIB_VERSION <= 0x010400
// Remove all fields with that key, because TagLib <= 1.4 crashes
// using an invalidated iterator after calling erase().
oggTag->addField(key, value, true);
#else
const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
if (fieldListMap.contains(key) && fieldListMap[key].size() > 1) {
int i = 0;
TagLib::String oldValue(TagLib::String::null);
for (TagLib::Ogg::FieldListMap::ConstIterator it = fieldListMap.begin();
it != fieldListMap.end();
++it) {
TagLib::StringList stringList = (*it).second;
for (TagLib::StringList::ConstIterator slit = stringList.begin();
slit != stringList.end();
++slit) {
if (i++ == index) {
oldValue = *slit;
break;
}
}
}
oggTag->removeField(key, oldValue);
oggTag->addField(key, value, false);
} else {
oggTag->addField(key, value, true);
}
#endif
if (frame.getType() == Frame::FT_Track) {
int numTracks = getTotalNumberOfTracksIfEnabled();
if (numTracks > 0) {
oggTag->addField("TRACKTOTAL", TagLib::String::number(numTracks), true);
}
}
markTag2Changed();
return true;
} else if ((apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tagV2)) != 0) {
apeTag->addValue(QSTRING_TO_TSTRING(frame.getName(true)),
QSTRING_TO_TSTRING(frame.getValue()));
markTag2Changed();
return true;
}
}
// Try the superclass method
return TaggedFile::setFrameV2(frame);
}
/**
* Get name of frame from type.
*
......@@ -2409,9 +2333,11 @@ static const char* getVorbisNameFromType(Frame::Type type)
"COPYRIGHT", // FT_Copyright,
"DISCNUMBER", // FT_Disc,
"ENCODED-BY", // FT_EncodedBy,
"GROUPING", // FT_Grouping,
"ISRC", // FT_Isrc,
"LANGUAGE", // FT_Language,
"LYRICIST", // FT_Lyricist,
"LYRICS", // FT_Lyrics,
"SOURCEMEDIA", // FT_Media,
"ORIGINALALBUM", // FT_OriginalAlbum,
"ORIGINALARTIST", // FT_OriginalArtist,
......@@ -2519,6 +2445,82 @@ static QString getApeName(const Frame& frame)
}
}
/**
* Set a frame in the tags 2.
*
* @param frame frame to set
*
* @return true if ok.
*/
bool TagLibFile::setFrameV2(const Frame& frame)
{
// If the frame has an index, change that specific frame
int index = frame.getIndex();
if (index != -1 && m_tagV2) {
TagLib::ID3v2::Tag* id3v2Tag;
TagLib::Ogg::XiphComment* oggTag;
TagLib::APE::Tag* apeTag;
if ((id3v2Tag = dynamic_cast<TagLib::ID3v2::Tag*>(m_tagV2)) != 0) {
const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameList();
if (index < static_cast<int>(frameList.size())) {
// This is a hack. The frameList should not be modified directly.
// However when removing the old frame and adding a new frame,
// the indices of all frames get invalid.
setId3v2Frame(frameList[index], frame);
markTag2Changed();
return true;
}
} else if ((oggTag = dynamic_cast<TagLib::Ogg::XiphComment*>(m_tagV2)) != 0) {
TagLib::String key = QSTRING_TO_TSTRING(getVorbisName(frame));
TagLib::String value = QSTRING_TO_TSTRING(frame.getValue());
#if TAGLIB_VERSION <= 0x010400
// Remove all fields with that key, because TagLib <= 1.4 crashes
// using an invalidated iterator after calling erase().
oggTag->addField(key, value, true);
#else
const TagLib::Ogg::FieldListMap& fieldListMap = oggTag->fieldListMap();
if (fieldListMap.contains(key) && fieldListMap[key].size() > 1) {
int i = 0;
TagLib::String oldValue(TagLib::String::null);
for (TagLib::Ogg::FieldListMap::ConstIterator it = fieldListMap.begin();
it != fieldListMap.end();
++it) {
TagLib::StringList stringList = (*it).second;
for (TagLib::StringList::ConstIterator slit = stringList.begin();
slit != stringList.end();
++slit) {
if (i++ == index) {
oldValue = *slit;
break;
}
}
}
oggTag->removeField(key, oldValue);
oggTag->addField(key, value, false);
} else {
oggTag->addField(key, value, true);
}
#endif
if (frame.getType() == Frame::FT_Track) {
int numTracks = getTotalNumberOfTracksIfEnabled();
if (numTracks > 0) {
oggTag->addField("TRACKTOTAL", TagLib::String::number(numTracks), true);
}
}
markTag2Changed();
return true;
} else if ((apeTag = dynamic_cast<TagLib::APE::Tag*>(m_tagV2)) != 0) {
apeTag->addValue(QSTRING_TO_TSTRING(getApeName(frame)),
QSTRING_TO_TSTRING(frame.getValue()));
markTag2Changed();
return true;
}
}
// Try the superclass method
return TaggedFile::setFrameV2(frame);
}
/**
* Check if an ID3v2.4.0 frame ID is valid.
*
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment