2013-10-12 20:52:30 +00:00
|
|
|
/***************************************************************************
|
|
|
|
copyright : (C) 2002 - 2008 by Scott Wheeler
|
|
|
|
email : wheeler@kde.org
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
* 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 <tbytevector.h>
|
|
|
|
#include <tdebug.h>
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
#include <flacpicture.h>
|
2013-10-12 20:52:30 +00:00
|
|
|
#include <xiphcomment.h>
|
2021-02-07 01:48:04 +00:00
|
|
|
#include <tpropertymap.h>
|
2013-10-12 20:52:30 +00:00
|
|
|
|
|
|
|
using namespace TagLib;
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
typedef Ogg::FieldListMap::Iterator FieldIterator;
|
|
|
|
typedef Ogg::FieldListMap::ConstIterator FieldConstIterator;
|
|
|
|
|
|
|
|
typedef List<FLAC::Picture *> PictureList;
|
|
|
|
typedef PictureList::Iterator PictureIterator;
|
|
|
|
typedef PictureList::Iterator PictureConstIterator;
|
|
|
|
}
|
|
|
|
|
2013-10-12 20:52:30 +00:00
|
|
|
class Ogg::XiphComment::XiphCommentPrivate
|
|
|
|
{
|
|
|
|
public:
|
2021-02-07 01:48:04 +00:00
|
|
|
XiphCommentPrivate()
|
|
|
|
{
|
|
|
|
pictureList.setAutoDelete(true);
|
|
|
|
}
|
|
|
|
|
2013-10-12 20:52:30 +00:00
|
|
|
FieldListMap fieldListMap;
|
|
|
|
String vendorID;
|
|
|
|
String commentField;
|
2021-02-07 01:48:04 +00:00
|
|
|
PictureList pictureList;
|
2013-10-12 20:52:30 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// public members
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
Ogg::XiphComment::XiphComment() :
|
|
|
|
TagLib::Tag(),
|
|
|
|
d(new XiphCommentPrivate())
|
2013-10-12 20:52:30 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
Ogg::XiphComment::XiphComment(const ByteVector &data) :
|
|
|
|
TagLib::Tag(),
|
|
|
|
d(new XiphCommentPrivate())
|
2013-10-12 20:52:30 +00:00
|
|
|
{
|
|
|
|
parse(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ogg::XiphComment::~XiphComment()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
String Ogg::XiphComment::title() const
|
|
|
|
{
|
|
|
|
if(d->fieldListMap["TITLE"].isEmpty())
|
2021-02-07 01:48:04 +00:00
|
|
|
return String();
|
|
|
|
return d->fieldListMap["TITLE"].toString();
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
2021-10-02 02:18:42 +00:00
|
|
|
String Ogg::XiphComment::albumartist() const
|
|
|
|
{
|
|
|
|
if(!d->fieldListMap["ALBUMARTIST"].isEmpty())
|
|
|
|
return d->fieldListMap["ALBUMARTIST"].toString();
|
|
|
|
if(!d->fieldListMap["ALBUM ARTIST"].isEmpty())
|
|
|
|
return d->fieldListMap["ALBUM ARTIST"].toString();
|
|
|
|
return String();
|
|
|
|
}
|
|
|
|
|
2013-10-12 20:52:30 +00:00
|
|
|
String Ogg::XiphComment::artist() const
|
|
|
|
{
|
|
|
|
if(d->fieldListMap["ARTIST"].isEmpty())
|
2021-02-07 01:48:04 +00:00
|
|
|
return String();
|
|
|
|
return d->fieldListMap["ARTIST"].toString();
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
String Ogg::XiphComment::album() const
|
|
|
|
{
|
|
|
|
if(d->fieldListMap["ALBUM"].isEmpty())
|
2021-02-07 01:48:04 +00:00
|
|
|
return String();
|
|
|
|
return d->fieldListMap["ALBUM"].toString();
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
String Ogg::XiphComment::comment() const
|
|
|
|
{
|
|
|
|
if(!d->fieldListMap["DESCRIPTION"].isEmpty()) {
|
|
|
|
d->commentField = "DESCRIPTION";
|
2021-02-07 01:48:04 +00:00
|
|
|
return d->fieldListMap["DESCRIPTION"].toString();
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(!d->fieldListMap["COMMENT"].isEmpty()) {
|
|
|
|
d->commentField = "COMMENT";
|
2021-02-07 01:48:04 +00:00
|
|
|
return d->fieldListMap["COMMENT"].toString();
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
return String();
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
String Ogg::XiphComment::genre() const
|
|
|
|
{
|
|
|
|
if(d->fieldListMap["GENRE"].isEmpty())
|
2021-02-07 01:48:04 +00:00
|
|
|
return String();
|
|
|
|
return d->fieldListMap["GENRE"].toString();
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
unsigned int Ogg::XiphComment::year() const
|
2013-10-12 20:52:30 +00:00
|
|
|
{
|
|
|
|
if(!d->fieldListMap["DATE"].isEmpty())
|
|
|
|
return d->fieldListMap["DATE"].front().toInt();
|
|
|
|
if(!d->fieldListMap["YEAR"].isEmpty())
|
|
|
|
return d->fieldListMap["YEAR"].front().toInt();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
unsigned int Ogg::XiphComment::track() const
|
2013-10-12 20:52:30 +00:00
|
|
|
{
|
|
|
|
if(!d->fieldListMap["TRACKNUMBER"].isEmpty())
|
|
|
|
return d->fieldListMap["TRACKNUMBER"].front().toInt();
|
|
|
|
if(!d->fieldListMap["TRACKNUM"].isEmpty())
|
|
|
|
return d->fieldListMap["TRACKNUM"].front().toInt();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-02-07 03:50:28 +00:00
|
|
|
float Ogg::XiphComment::rgAlbumGain() const
|
|
|
|
{
|
|
|
|
if(d->fieldListMap["REPLAYGAIN_ALBUM_GAIN"].isEmpty())
|
|
|
|
return 0;
|
|
|
|
return d->fieldListMap["REPLAYGAIN_ALBUM_GAIN"].front().toFloat();
|
|
|
|
}
|
|
|
|
|
|
|
|
float Ogg::XiphComment::rgAlbumPeak() const
|
|
|
|
{
|
|
|
|
if(d->fieldListMap["REPLAYGAIN_ALBUM_PEAK"].isEmpty())
|
|
|
|
return 0;
|
|
|
|
return d->fieldListMap["REPLAYGAIN_ALBUM_PEAK"].front().toFloat();
|
|
|
|
}
|
|
|
|
|
|
|
|
float Ogg::XiphComment::rgTrackGain() const
|
|
|
|
{
|
|
|
|
if(d->fieldListMap["REPLAYGAIN_TRACK_GAIN"].isEmpty())
|
|
|
|
return 0;
|
|
|
|
return d->fieldListMap["REPLAYGAIN_TRACK_GAIN"].front().toFloat();
|
|
|
|
}
|
|
|
|
|
|
|
|
float Ogg::XiphComment::rgTrackPeak() const
|
|
|
|
{
|
|
|
|
if(d->fieldListMap["REPLAYGAIN_TRACK_PEAK"].isEmpty())
|
|
|
|
return 0;
|
|
|
|
return d->fieldListMap["REPLAYGAIN_TRACK_PEAK"].front().toFloat();
|
|
|
|
}
|
|
|
|
|
2013-10-12 20:52:30 +00:00
|
|
|
void Ogg::XiphComment::setTitle(const String &s)
|
|
|
|
{
|
|
|
|
addField("TITLE", s);
|
|
|
|
}
|
|
|
|
|
2021-10-02 02:18:42 +00:00
|
|
|
void Ogg::XiphComment::setAlbumArtist(const String &s)
|
|
|
|
{
|
|
|
|
addField("ALBUMARTIST", s);
|
|
|
|
}
|
|
|
|
|
2013-10-12 20:52:30 +00:00
|
|
|
void Ogg::XiphComment::setArtist(const String &s)
|
|
|
|
{
|
|
|
|
addField("ARTIST", s);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::setAlbum(const String &s)
|
|
|
|
{
|
|
|
|
addField("ALBUM", s);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::setComment(const String &s)
|
|
|
|
{
|
2021-02-07 01:48:04 +00:00
|
|
|
if(d->commentField.isEmpty()) {
|
|
|
|
if(!d->fieldListMap["DESCRIPTION"].isEmpty())
|
|
|
|
d->commentField = "DESCRIPTION";
|
|
|
|
else
|
|
|
|
d->commentField = "COMMENT";
|
|
|
|
}
|
|
|
|
|
|
|
|
addField(d->commentField, s);
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::setGenre(const String &s)
|
|
|
|
{
|
|
|
|
addField("GENRE", s);
|
|
|
|
}
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
void Ogg::XiphComment::setYear(unsigned int i)
|
2013-10-12 20:52:30 +00:00
|
|
|
{
|
2021-02-07 01:48:04 +00:00
|
|
|
removeFields("YEAR");
|
2013-10-12 20:52:30 +00:00
|
|
|
if(i == 0)
|
2021-02-07 01:48:04 +00:00
|
|
|
removeFields("DATE");
|
2013-10-12 20:52:30 +00:00
|
|
|
else
|
|
|
|
addField("DATE", String::number(i));
|
|
|
|
}
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
void Ogg::XiphComment::setTrack(unsigned int i)
|
2013-10-12 20:52:30 +00:00
|
|
|
{
|
2021-02-07 01:48:04 +00:00
|
|
|
removeFields("TRACKNUM");
|
2013-10-12 20:52:30 +00:00
|
|
|
if(i == 0)
|
2021-02-07 01:48:04 +00:00
|
|
|
removeFields("TRACKNUMBER");
|
2013-10-12 20:52:30 +00:00
|
|
|
else
|
|
|
|
addField("TRACKNUMBER", String::number(i));
|
|
|
|
}
|
|
|
|
|
2021-02-07 03:50:28 +00:00
|
|
|
void Ogg::XiphComment::setRGAlbumGain(float f)
|
|
|
|
{
|
|
|
|
if (f == 0)
|
|
|
|
removeField("REPLAYGAIN_ALBUM_GAIN");
|
|
|
|
else
|
|
|
|
addField("REPLAYGAIN_ALBUM_GAIN", String::number(f) + " dB");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::setRGAlbumPeak(float f)
|
|
|
|
{
|
|
|
|
if (f == 0)
|
|
|
|
removeField("REPLAYGAIN_ALBUM_PEAK");
|
|
|
|
else
|
|
|
|
addField("REPLAYGAIN_ALBUM_PEAK", String::number(f));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::setRGTrackGain(float f)
|
|
|
|
{
|
|
|
|
if (f == 0)
|
|
|
|
removeField("REPLAYGAIN_TRACK_GAIN");
|
|
|
|
else
|
|
|
|
addField("REPLAYGAIN_TRACK_GAIN", String::number(f) + " dB");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::setRGTrackPeak(float f)
|
|
|
|
{
|
|
|
|
if (f == 0)
|
|
|
|
removeField("REPLAYGAIN_TRACK_PEAK");
|
|
|
|
else
|
|
|
|
addField("REPLAYGAIN_TRACK_PEAK", String::number(f));
|
|
|
|
}
|
|
|
|
|
2013-10-12 20:52:30 +00:00
|
|
|
bool Ogg::XiphComment::isEmpty() const
|
|
|
|
{
|
2021-02-07 01:48:04 +00:00
|
|
|
for(FieldConstIterator it = d->fieldListMap.begin(); it != d->fieldListMap.end(); ++it) {
|
2013-10-12 20:52:30 +00:00
|
|
|
if(!(*it).second.isEmpty())
|
|
|
|
return false;
|
2021-02-07 01:48:04 +00:00
|
|
|
}
|
2013-10-12 20:52:30 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
unsigned int Ogg::XiphComment::fieldCount() const
|
2013-10-12 20:52:30 +00:00
|
|
|
{
|
2021-02-07 01:48:04 +00:00
|
|
|
unsigned int count = 0;
|
2013-10-12 20:52:30 +00:00
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
for(FieldConstIterator it = d->fieldListMap.begin(); it != d->fieldListMap.end(); ++it)
|
2013-10-12 20:52:30 +00:00
|
|
|
count += (*it).second.size();
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
count += d->pictureList.size();
|
|
|
|
|
2013-10-12 20:52:30 +00:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
const Ogg::FieldListMap &Ogg::XiphComment::fieldListMap() const
|
|
|
|
{
|
|
|
|
return d->fieldListMap;
|
|
|
|
}
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
PropertyMap Ogg::XiphComment::properties() const
|
|
|
|
{
|
|
|
|
return d->fieldListMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
PropertyMap Ogg::XiphComment::setProperties(const PropertyMap &properties)
|
|
|
|
{
|
|
|
|
// check which keys are to be deleted
|
|
|
|
StringList toRemove;
|
|
|
|
for(FieldConstIterator it = d->fieldListMap.begin(); it != d->fieldListMap.end(); ++it)
|
|
|
|
if (!properties.contains(it->first))
|
|
|
|
toRemove.append(it->first);
|
|
|
|
|
|
|
|
for(StringList::ConstIterator it = toRemove.begin(); it != toRemove.end(); ++it)
|
|
|
|
removeFields(*it);
|
|
|
|
|
|
|
|
// now go through keys in \a properties and check that the values match those in the xiph comment
|
|
|
|
PropertyMap invalid;
|
|
|
|
PropertyMap::ConstIterator it = properties.begin();
|
|
|
|
for(; it != properties.end(); ++it)
|
|
|
|
{
|
|
|
|
if(!checkKey(it->first))
|
|
|
|
invalid.insert(it->first, it->second);
|
|
|
|
else if(!d->fieldListMap.contains(it->first) || !(it->second == d->fieldListMap[it->first])) {
|
|
|
|
const StringList &sl = it->second;
|
|
|
|
if(sl.isEmpty())
|
|
|
|
// zero size string list -> remove the tag with all values
|
|
|
|
removeFields(it->first);
|
|
|
|
else {
|
|
|
|
// replace all strings in the list for the tag
|
|
|
|
StringList::ConstIterator valueIterator = sl.begin();
|
|
|
|
addField(it->first, *valueIterator, true);
|
|
|
|
++valueIterator;
|
|
|
|
for(; valueIterator != sl.end(); ++valueIterator)
|
|
|
|
addField(it->first, *valueIterator, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return invalid;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Ogg::XiphComment::checkKey(const String &key)
|
|
|
|
{
|
|
|
|
if(key.size() < 1)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// A key may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded.
|
|
|
|
|
|
|
|
for(String::ConstIterator it = key.begin(); it != key.end(); it++) {
|
|
|
|
if(*it < 0x20 || *it > 0x7D || *it == 0x3D)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-10-12 20:52:30 +00:00
|
|
|
String Ogg::XiphComment::vendorID() const
|
|
|
|
{
|
|
|
|
return d->vendorID;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::addField(const String &key, const String &value, bool replace)
|
|
|
|
{
|
2021-02-07 01:48:04 +00:00
|
|
|
if(!checkKey(key)) {
|
|
|
|
debug("Ogg::XiphComment::addField() - Invalid key. Field not added.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const String upperKey = key.upper();
|
|
|
|
|
2013-10-12 20:52:30 +00:00
|
|
|
if(replace)
|
2021-02-07 01:48:04 +00:00
|
|
|
removeFields(upperKey);
|
2013-10-12 20:52:30 +00:00
|
|
|
|
|
|
|
if(!key.isEmpty() && !value.isEmpty())
|
2021-02-07 01:48:04 +00:00
|
|
|
d->fieldListMap[upperKey].append(value);
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::removeField(const String &key, const String &value)
|
|
|
|
{
|
2021-02-07 01:48:04 +00:00
|
|
|
if(!value.isNull())
|
|
|
|
removeFields(key, value);
|
2013-10-12 20:52:30 +00:00
|
|
|
else
|
2021-02-07 01:48:04 +00:00
|
|
|
removeFields(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::removeFields(const String &key)
|
|
|
|
{
|
|
|
|
d->fieldListMap.erase(key.upper());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::removeFields(const String &key, const String &value)
|
|
|
|
{
|
|
|
|
StringList &fields = d->fieldListMap[key.upper()];
|
|
|
|
for(StringList::Iterator it = fields.begin(); it != fields.end(); ) {
|
|
|
|
if(*it == value)
|
|
|
|
it = fields.erase(it);
|
|
|
|
else
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::removeAllFields()
|
|
|
|
{
|
|
|
|
d->fieldListMap.clear();
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Ogg::XiphComment::contains(const String &key) const
|
|
|
|
{
|
2021-02-07 01:48:04 +00:00
|
|
|
return !d->fieldListMap[key.upper()].isEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::removePicture(FLAC::Picture *picture, bool del)
|
|
|
|
{
|
|
|
|
PictureIterator it = d->pictureList.find(picture);
|
|
|
|
if(it != d->pictureList.end())
|
|
|
|
d->pictureList.erase(it);
|
|
|
|
|
|
|
|
if(del)
|
|
|
|
delete picture;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::removeAllPictures()
|
|
|
|
{
|
|
|
|
d->pictureList.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ogg::XiphComment::addPicture(FLAC::Picture * picture)
|
|
|
|
{
|
|
|
|
d->pictureList.append(picture);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<FLAC::Picture *> Ogg::XiphComment::pictureList()
|
|
|
|
{
|
|
|
|
return d->pictureList;
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ByteVector Ogg::XiphComment::render() const
|
|
|
|
{
|
|
|
|
return render(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
ByteVector Ogg::XiphComment::render(bool addFramingBit) const
|
|
|
|
{
|
|
|
|
ByteVector data;
|
|
|
|
|
|
|
|
// Add the vendor ID length and the vendor ID. It's important to use the
|
|
|
|
// length of the data(String::UTF8) rather than the length of the the string
|
|
|
|
// since this is UTF8 text and there may be more characters in the data than
|
|
|
|
// in the UTF16 string.
|
|
|
|
|
|
|
|
ByteVector vendorData = d->vendorID.data(String::UTF8);
|
|
|
|
|
|
|
|
data.append(ByteVector::fromUInt(vendorData.size(), false));
|
|
|
|
data.append(vendorData);
|
|
|
|
|
|
|
|
// Add the number of fields.
|
|
|
|
|
|
|
|
data.append(ByteVector::fromUInt(fieldCount(), false));
|
|
|
|
|
|
|
|
// Iterate over the the field lists. Our iterator returns a
|
|
|
|
// std::pair<String, StringList> where the first String is the field name and
|
|
|
|
// the StringList is the values associated with that field.
|
|
|
|
|
|
|
|
FieldListMap::ConstIterator it = d->fieldListMap.begin();
|
|
|
|
for(; it != d->fieldListMap.end(); ++it) {
|
|
|
|
|
|
|
|
// And now iterate over the values of the current list.
|
|
|
|
|
|
|
|
String fieldName = (*it).first;
|
|
|
|
StringList values = (*it).second;
|
|
|
|
|
|
|
|
StringList::ConstIterator valuesIt = values.begin();
|
|
|
|
for(; valuesIt != values.end(); ++valuesIt) {
|
|
|
|
ByteVector fieldData = fieldName.data(String::UTF8);
|
|
|
|
fieldData.append('=');
|
|
|
|
fieldData.append((*valuesIt).data(String::UTF8));
|
|
|
|
|
|
|
|
data.append(ByteVector::fromUInt(fieldData.size(), false));
|
|
|
|
data.append(fieldData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
for(PictureConstIterator it = d->pictureList.begin(); it != d->pictureList.end(); ++it) {
|
|
|
|
ByteVector picture = (*it)->render().toBase64();
|
|
|
|
data.append(ByteVector::fromUInt(picture.size() + 23, false));
|
|
|
|
data.append("METADATA_BLOCK_PICTURE=");
|
|
|
|
data.append(picture);
|
|
|
|
}
|
|
|
|
|
2013-10-12 20:52:30 +00:00
|
|
|
// Append the "framing bit".
|
|
|
|
|
|
|
|
if(addFramingBit)
|
|
|
|
data.append(char(1));
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// protected members
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void Ogg::XiphComment::parse(const ByteVector &data)
|
|
|
|
{
|
|
|
|
// The first thing in the comment data is the vendor ID length, followed by a
|
|
|
|
// UTF8 string with the vendor ID.
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
unsigned int pos = 0;
|
2013-10-12 20:52:30 +00:00
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
const unsigned int vendorLength = data.toUInt(0, false);
|
2013-10-12 20:52:30 +00:00
|
|
|
pos += 4;
|
|
|
|
|
|
|
|
d->vendorID = String(data.mid(pos, vendorLength), String::UTF8);
|
|
|
|
pos += vendorLength;
|
|
|
|
|
|
|
|
// Next the number of fields in the comment vector.
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
const unsigned int commentFields = data.toUInt(pos, false);
|
2013-10-12 20:52:30 +00:00
|
|
|
pos += 4;
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
if(commentFields > (data.size() - 8) / 4) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(unsigned int i = 0; i < commentFields; i++) {
|
2013-10-12 20:52:30 +00:00
|
|
|
|
|
|
|
// Each comment field is in the format "KEY=value" in a UTF8 string and has
|
|
|
|
// 4 bytes before the text starts that gives the length.
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
const unsigned int commentLength = data.toUInt(pos, false);
|
2013-10-12 20:52:30 +00:00
|
|
|
pos += 4;
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
const ByteVector entry = data.mid(pos, commentLength);
|
2013-10-12 20:52:30 +00:00
|
|
|
pos += commentLength;
|
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
// Don't go past data end
|
|
|
|
|
|
|
|
if(pos > data.size())
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Check for field separator
|
|
|
|
|
|
|
|
const int sep = entry.find('=');
|
|
|
|
if(sep < 1) {
|
|
|
|
debug("Ogg::XiphComment::parse() - Discarding a field. Separator not found.");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the key
|
|
|
|
|
|
|
|
const String key = String(entry.mid(0, sep), String::UTF8).upper();
|
|
|
|
if(!checkKey(key)) {
|
|
|
|
debug("Ogg::XiphComment::parse() - Discarding a field. Invalid key.");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(key == "METADATA_BLOCK_PICTURE" || key == "COVERART") {
|
|
|
|
|
|
|
|
// Handle Pictures separately
|
|
|
|
|
|
|
|
const ByteVector picturedata = ByteVector::fromBase64(entry.mid(sep + 1));
|
|
|
|
if(picturedata.isEmpty()) {
|
|
|
|
debug("Ogg::XiphComment::parse() - Discarding a field. Invalid base64 data");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(key[0] == L'M') {
|
2013-10-12 20:52:30 +00:00
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
// Decode FLAC Picture
|
2013-10-12 20:52:30 +00:00
|
|
|
|
2021-02-07 01:48:04 +00:00
|
|
|
FLAC::Picture * picture = new FLAC::Picture();
|
|
|
|
if(picture->parse(picturedata)) {
|
|
|
|
d->pictureList.append(picture);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
delete picture;
|
|
|
|
debug("Ogg::XiphComment::parse() - Failed to decode FLAC Picture block");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
|
|
|
// Assume it's some type of image file
|
|
|
|
|
|
|
|
FLAC::Picture * picture = new FLAC::Picture();
|
|
|
|
picture->setData(picturedata);
|
|
|
|
picture->setMimeType("image/");
|
|
|
|
picture->setType(FLAC::Picture::Other);
|
|
|
|
d->pictureList.append(picture);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
|
|
|
// Parse the text
|
|
|
|
|
|
|
|
addField(key, String(entry.mid(sep + 1), String::UTF8), false);
|
|
|
|
}
|
2013-10-12 20:52:30 +00:00
|
|
|
}
|
|
|
|
}
|