Added TagLib (with AUTORS and COPYING files)
[someplayer] / src / taglib / ogg / xiphcomment.cpp
1 /***************************************************************************
2     copyright            : (C) 2002 - 2008 by Scott Wheeler
3     email                : wheeler@kde.org
4  ***************************************************************************/
5
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.                     *
10  *                                                                         *
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.                       *
15  *                                                                         *
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  *
19  *   USA                                                                   *
20  *                                                                         *
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  ***************************************************************************/
25
26 #include <tbytevector.h>
27 #include <tdebug.h>
28
29 #include <xiphcomment.h>
30
31 using namespace TagLib;
32
33 class Ogg::XiphComment::XiphCommentPrivate
34 {
35 public:
36   FieldListMap fieldListMap;
37   String vendorID;
38   String commentField;
39 };
40
41 ////////////////////////////////////////////////////////////////////////////////
42 // public members
43 ////////////////////////////////////////////////////////////////////////////////
44
45 Ogg::XiphComment::XiphComment() : TagLib::Tag()
46 {
47   d = new XiphCommentPrivate;
48 }
49
50 Ogg::XiphComment::XiphComment(const ByteVector &data) : TagLib::Tag()
51 {
52   d = new XiphCommentPrivate;
53   parse(data);
54 }
55
56 Ogg::XiphComment::~XiphComment()
57 {
58   delete d;
59 }
60
61 String Ogg::XiphComment::title() const
62 {
63   if(d->fieldListMap["TITLE"].isEmpty())
64     return String::null;
65   return d->fieldListMap["TITLE"].front();
66 }
67
68 String Ogg::XiphComment::artist() const
69 {
70   if(d->fieldListMap["ARTIST"].isEmpty())
71     return String::null;
72   return d->fieldListMap["ARTIST"].front();
73 }
74
75 String Ogg::XiphComment::album() const
76 {
77   if(d->fieldListMap["ALBUM"].isEmpty())
78     return String::null;
79   return d->fieldListMap["ALBUM"].front();
80 }
81
82 String Ogg::XiphComment::comment() const
83 {
84   if(!d->fieldListMap["DESCRIPTION"].isEmpty()) {
85     d->commentField = "DESCRIPTION";
86     return d->fieldListMap["DESCRIPTION"].front();
87   }
88
89   if(!d->fieldListMap["COMMENT"].isEmpty()) {
90     d->commentField = "COMMENT";
91     return d->fieldListMap["COMMENT"].front();
92   }
93
94   return String::null;
95 }
96
97 String Ogg::XiphComment::genre() const
98 {
99   if(d->fieldListMap["GENRE"].isEmpty())
100     return String::null;
101   return d->fieldListMap["GENRE"].front();
102 }
103
104 TagLib::uint Ogg::XiphComment::year() const
105 {
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();
110   return 0;
111 }
112
113 TagLib::uint Ogg::XiphComment::track() const
114 {
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();
119   return 0;
120 }
121
122 void Ogg::XiphComment::setTitle(const String &s)
123 {
124   addField("TITLE", s);
125 }
126
127 void Ogg::XiphComment::setArtist(const String &s)
128 {
129   addField("ARTIST", s);
130 }
131
132 void Ogg::XiphComment::setAlbum(const String &s)
133 {
134   addField("ALBUM", s);
135 }
136
137 void Ogg::XiphComment::setComment(const String &s)
138 {
139   addField(d->commentField.isEmpty() ? "DESCRIPTION" : d->commentField, s);
140 }
141
142 void Ogg::XiphComment::setGenre(const String &s)
143 {
144   addField("GENRE", s);
145 }
146
147 void Ogg::XiphComment::setYear(uint i)
148 {
149   removeField("YEAR");
150   if(i == 0)
151     removeField("DATE");
152   else
153     addField("DATE", String::number(i));
154 }
155
156 void Ogg::XiphComment::setTrack(uint i)
157 {
158   removeField("TRACKNUM");
159   if(i == 0)
160     removeField("TRACKNUMBER");
161   else
162     addField("TRACKNUMBER", String::number(i));
163 }
164
165 bool Ogg::XiphComment::isEmpty() const
166 {
167   FieldListMap::ConstIterator it = d->fieldListMap.begin();
168   for(; it != d->fieldListMap.end(); ++it)
169     if(!(*it).second.isEmpty())
170       return false;
171
172   return true;
173 }
174
175 TagLib::uint Ogg::XiphComment::fieldCount() const
176 {
177   uint count = 0;
178
179   FieldListMap::ConstIterator it = d->fieldListMap.begin();
180   for(; it != d->fieldListMap.end(); ++it)
181     count += (*it).second.size();
182
183   return count;
184 }
185
186 const Ogg::FieldListMap &Ogg::XiphComment::fieldListMap() const
187 {
188   return d->fieldListMap;
189 }
190
191 String Ogg::XiphComment::vendorID() const
192 {
193   return d->vendorID;
194 }
195
196 void Ogg::XiphComment::addField(const String &key, const String &value, bool replace)
197 {
198   if(replace)
199     removeField(key.upper());
200
201   if(!key.isEmpty() && !value.isEmpty())
202     d->fieldListMap[key.upper()].append(value);
203 }
204
205 void Ogg::XiphComment::removeField(const String &key, const String &value)
206 {
207   if(!value.isNull()) {
208     StringList::Iterator it = d->fieldListMap[key].begin();
209     while(it != d->fieldListMap[key].end()) {
210       if(value == *it)
211         it = d->fieldListMap[key].erase(it);
212       else
213         it++;
214     }
215   }
216   else
217     d->fieldListMap.erase(key);
218 }
219
220 bool Ogg::XiphComment::contains(const String &key) const
221 {
222   return d->fieldListMap.contains(key) && !d->fieldListMap[key].isEmpty();
223 }
224
225 ByteVector Ogg::XiphComment::render() const
226 {
227   return render(true);
228 }
229
230 ByteVector Ogg::XiphComment::render(bool addFramingBit) const
231 {
232   ByteVector data;
233
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.
238
239   ByteVector vendorData = d->vendorID.data(String::UTF8);
240
241   data.append(ByteVector::fromUInt(vendorData.size(), false));
242   data.append(vendorData);
243
244   // Add the number of fields.
245
246   data.append(ByteVector::fromUInt(fieldCount(), false));
247
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.
251
252   FieldListMap::ConstIterator it = d->fieldListMap.begin();
253   for(; it != d->fieldListMap.end(); ++it) {
254
255     // And now iterate over the values of the current list.
256
257     String fieldName = (*it).first;
258     StringList values = (*it).second;
259
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));
265
266       data.append(ByteVector::fromUInt(fieldData.size(), false));
267       data.append(fieldData);
268     }
269   }
270
271   // Append the "framing bit".
272
273   if(addFramingBit)
274     data.append(char(1));
275
276   return data;
277 }
278
279 ////////////////////////////////////////////////////////////////////////////////
280 // protected members
281 ////////////////////////////////////////////////////////////////////////////////
282
283 void Ogg::XiphComment::parse(const ByteVector &data)
284 {
285   // The first thing in the comment data is the vendor ID length, followed by a
286   // UTF8 string with the vendor ID.
287
288   int pos = 0;
289
290   int vendorLength = data.mid(0, 4).toUInt(false);
291   pos += 4;
292
293   d->vendorID = String(data.mid(pos, vendorLength), String::UTF8);
294   pos += vendorLength;
295
296   // Next the number of fields in the comment vector.
297
298   int commentFields = data.mid(pos, 4).toUInt(false);
299   pos += 4;
300
301   for(int i = 0; i < commentFields; i++) {
302
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.
305
306     int commentLength = data.mid(pos, 4).toUInt(false);
307     pos += 4;
308
309     String comment = String(data.mid(pos, commentLength), String::UTF8);
310     pos += commentLength;
311
312     int commentSeparatorPosition = comment.find("=");
313
314     String key = comment.substr(0, commentSeparatorPosition);
315     String value = comment.substr(commentSeparatorPosition + 1);
316
317     addField(key, value, false);
318   }
319 }