1 /***************************************************************************
2 copyright : (C) 2002 - 2008 by Scott Wheeler
3 email : wheeler@kde.org
4 ***************************************************************************/
6 /***************************************************************************
7 * This library is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU Lesser General Public License version *
9 * 2.1 as published by the Free Software Foundation. *
11 * This library is distributed in the hope that it will be useful, but *
12 * WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14 * Lesser General Public License for more details. *
16 * You should have received a copy of the GNU Lesser General Public *
17 * License along with this library; if not, write to the Free Software *
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
21 * Alternatively, this file is available under the Mozilla Public *
22 * License Version 1.1. You may obtain a copy of the License at *
23 * http://www.mozilla.org/MPL/ *
24 ***************************************************************************/
26 #include <tbytevector.h>
29 #include <xiphcomment.h>
31 using namespace TagLib;
33 class Ogg::XiphComment::XiphCommentPrivate
36 FieldListMap fieldListMap;
41 ////////////////////////////////////////////////////////////////////////////////
43 ////////////////////////////////////////////////////////////////////////////////
45 Ogg::XiphComment::XiphComment() : TagLib::Tag()
47 d = new XiphCommentPrivate;
50 Ogg::XiphComment::XiphComment(const ByteVector &data) : TagLib::Tag()
52 d = new XiphCommentPrivate;
56 Ogg::XiphComment::~XiphComment()
61 String Ogg::XiphComment::title() const
63 if(d->fieldListMap["TITLE"].isEmpty())
65 return d->fieldListMap["TITLE"].front();
68 String Ogg::XiphComment::artist() const
70 if(d->fieldListMap["ARTIST"].isEmpty())
72 return d->fieldListMap["ARTIST"].front();
75 String Ogg::XiphComment::album() const
77 if(d->fieldListMap["ALBUM"].isEmpty())
79 return d->fieldListMap["ALBUM"].front();
82 String Ogg::XiphComment::comment() const
84 if(!d->fieldListMap["DESCRIPTION"].isEmpty()) {
85 d->commentField = "DESCRIPTION";
86 return d->fieldListMap["DESCRIPTION"].front();
89 if(!d->fieldListMap["COMMENT"].isEmpty()) {
90 d->commentField = "COMMENT";
91 return d->fieldListMap["COMMENT"].front();
97 String Ogg::XiphComment::genre() const
99 if(d->fieldListMap["GENRE"].isEmpty())
101 return d->fieldListMap["GENRE"].front();
104 TagLib::uint Ogg::XiphComment::year() const
106 if(!d->fieldListMap["DATE"].isEmpty())
107 return d->fieldListMap["DATE"].front().toInt();
108 if(!d->fieldListMap["YEAR"].isEmpty())
109 return d->fieldListMap["YEAR"].front().toInt();
113 TagLib::uint Ogg::XiphComment::track() const
115 if(!d->fieldListMap["TRACKNUMBER"].isEmpty())
116 return d->fieldListMap["TRACKNUMBER"].front().toInt();
117 if(!d->fieldListMap["TRACKNUM"].isEmpty())
118 return d->fieldListMap["TRACKNUM"].front().toInt();
122 void Ogg::XiphComment::setTitle(const String &s)
124 addField("TITLE", s);
127 void Ogg::XiphComment::setArtist(const String &s)
129 addField("ARTIST", s);
132 void Ogg::XiphComment::setAlbum(const String &s)
134 addField("ALBUM", s);
137 void Ogg::XiphComment::setComment(const String &s)
139 addField(d->commentField.isEmpty() ? "DESCRIPTION" : d->commentField, s);
142 void Ogg::XiphComment::setGenre(const String &s)
144 addField("GENRE", s);
147 void Ogg::XiphComment::setYear(uint i)
153 addField("DATE", String::number(i));
156 void Ogg::XiphComment::setTrack(uint i)
158 removeField("TRACKNUM");
160 removeField("TRACKNUMBER");
162 addField("TRACKNUMBER", String::number(i));
165 bool Ogg::XiphComment::isEmpty() const
167 FieldListMap::ConstIterator it = d->fieldListMap.begin();
168 for(; it != d->fieldListMap.end(); ++it)
169 if(!(*it).second.isEmpty())
175 TagLib::uint Ogg::XiphComment::fieldCount() const
179 FieldListMap::ConstIterator it = d->fieldListMap.begin();
180 for(; it != d->fieldListMap.end(); ++it)
181 count += (*it).second.size();
186 const Ogg::FieldListMap &Ogg::XiphComment::fieldListMap() const
188 return d->fieldListMap;
191 String Ogg::XiphComment::vendorID() const
196 void Ogg::XiphComment::addField(const String &key, const String &value, bool replace)
199 removeField(key.upper());
201 if(!key.isEmpty() && !value.isEmpty())
202 d->fieldListMap[key.upper()].append(value);
205 void Ogg::XiphComment::removeField(const String &key, const String &value)
207 if(!value.isNull()) {
208 StringList::Iterator it = d->fieldListMap[key].begin();
209 while(it != d->fieldListMap[key].end()) {
211 it = d->fieldListMap[key].erase(it);
217 d->fieldListMap.erase(key);
220 bool Ogg::XiphComment::contains(const String &key) const
222 return d->fieldListMap.contains(key) && !d->fieldListMap[key].isEmpty();
225 ByteVector Ogg::XiphComment::render() const
230 ByteVector Ogg::XiphComment::render(bool addFramingBit) const
234 // Add the vendor ID length and the vendor ID. It's important to use the
235 // length of the data(String::UTF8) rather than the length of the the string
236 // since this is UTF8 text and there may be more characters in the data than
237 // in the UTF16 string.
239 ByteVector vendorData = d->vendorID.data(String::UTF8);
241 data.append(ByteVector::fromUInt(vendorData.size(), false));
242 data.append(vendorData);
244 // Add the number of fields.
246 data.append(ByteVector::fromUInt(fieldCount(), false));
248 // Iterate over the the field lists. Our iterator returns a
249 // std::pair<String, StringList> where the first String is the field name and
250 // the StringList is the values associated with that field.
252 FieldListMap::ConstIterator it = d->fieldListMap.begin();
253 for(; it != d->fieldListMap.end(); ++it) {
255 // And now iterate over the values of the current list.
257 String fieldName = (*it).first;
258 StringList values = (*it).second;
260 StringList::ConstIterator valuesIt = values.begin();
261 for(; valuesIt != values.end(); ++valuesIt) {
262 ByteVector fieldData = fieldName.data(String::UTF8);
263 fieldData.append('=');
264 fieldData.append((*valuesIt).data(String::UTF8));
266 data.append(ByteVector::fromUInt(fieldData.size(), false));
267 data.append(fieldData);
271 // Append the "framing bit".
274 data.append(char(1));
279 ////////////////////////////////////////////////////////////////////////////////
281 ////////////////////////////////////////////////////////////////////////////////
283 void Ogg::XiphComment::parse(const ByteVector &data)
285 // The first thing in the comment data is the vendor ID length, followed by a
286 // UTF8 string with the vendor ID.
290 int vendorLength = data.mid(0, 4).toUInt(false);
293 d->vendorID = String(data.mid(pos, vendorLength), String::UTF8);
296 // Next the number of fields in the comment vector.
298 int commentFields = data.mid(pos, 4).toUInt(false);
301 for(int i = 0; i < commentFields; i++) {
303 // Each comment field is in the format "KEY=value" in a UTF8 string and has
304 // 4 bytes before the text starts that gives the length.
306 int commentLength = data.mid(pos, 4).toUInt(false);
309 String comment = String(data.mid(pos, commentLength), String::UTF8);
310 pos += commentLength;
312 int commentSeparatorPosition = comment.find("=");
314 String key = comment.substr(0, commentSeparatorPosition);
315 String value = comment.substr(commentSeparatorPosition + 1);
317 addField(key, value, false);