1 /***************************************************************************
2 copyright : (C) 2013 by Lukas Krejci
3 email : krejclu6@fel.cvut.cz
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., 51 Franklin Street, Fifth Floor, Boston, MA *
19 * 02110-1301 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 <tbytevectorlist.h>
27 #include <tpropertymap.h>
28 #include <tdebug.h>
29
30 #include "tableofcontentsframe.h"
31
32 using namespace TagLib;
33 using namespace ID3v2;
34
35 class TableOfContentsFrame::TableOfContentsFramePrivate
36 {
37 public:
TableOfContentsFramePrivate()38 TableOfContentsFramePrivate() :
39 tagHeader(0),
40 isTopLevel(false),
41 isOrdered(false)
42 {
43 embeddedFrameList.setAutoDelete(true);
44 }
45
46 const ID3v2::Header *tagHeader;
47 ByteVector elementID;
48 bool isTopLevel;
49 bool isOrdered;
50 ByteVectorList childElements;
51 FrameListMap embeddedFrameListMap;
52 FrameList embeddedFrameList;
53 };
54
55 namespace {
56
57 // These functions are needed to try to aim for backward compatibility with
58 // an API that previously (unreasonably) required null bytes to be appended
59 // at the end of identifiers explicitly by the API user.
60
61 // BIC: remove these
62
strip(ByteVector & b)63 ByteVector &strip(ByteVector &b)
64 {
65 if(b.endsWith('\0'))
66 b.resize(b.size() - 1);
67 return b;
68 }
69
strip(ByteVectorList & l)70 ByteVectorList &strip(ByteVectorList &l)
71 {
72 for(ByteVectorList::Iterator it = l.begin(); it != l.end(); ++it)
73 {
74 strip(*it);
75 }
76 return l;
77 }
78 }
79
80 ////////////////////////////////////////////////////////////////////////////////
81 // public methods
82 ////////////////////////////////////////////////////////////////////////////////
83
TableOfContentsFrame(const ID3v2::Header * tagHeader,const ByteVector & data)84 TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data) :
85 ID3v2::Frame(data),
86 d(new TableOfContentsFramePrivate())
87 {
88 d->tagHeader = tagHeader;
89 setData(data);
90 }
91
TableOfContentsFrame(const ByteVector & elementID,const ByteVectorList & children,const FrameList & embeddedFrames)92 TableOfContentsFrame::TableOfContentsFrame(const ByteVector &elementID,
93 const ByteVectorList &children,
94 const FrameList &embeddedFrames) :
95 ID3v2::Frame("CTOC"),
96 d(new TableOfContentsFramePrivate())
97 {
98 d->elementID = elementID;
99 strip(d->elementID);
100 d->childElements = children;
101
102 for(FrameList::ConstIterator it = embeddedFrames.begin(); it != embeddedFrames.end(); ++it)
103 addEmbeddedFrame(*it);
104 }
105
~TableOfContentsFrame()106 TableOfContentsFrame::~TableOfContentsFrame()
107 {
108 delete d;
109 }
110
elementID() const111 ByteVector TableOfContentsFrame::elementID() const
112 {
113 return d->elementID;
114 }
115
isTopLevel() const116 bool TableOfContentsFrame::isTopLevel() const
117 {
118 return d->isTopLevel;
119 }
120
isOrdered() const121 bool TableOfContentsFrame::isOrdered() const
122 {
123 return d->isOrdered;
124 }
125
entryCount() const126 unsigned int TableOfContentsFrame::entryCount() const
127 {
128 return d->childElements.size();
129 }
130
childElements() const131 ByteVectorList TableOfContentsFrame::childElements() const
132 {
133 return d->childElements;
134 }
135
setElementID(const ByteVector & eID)136 void TableOfContentsFrame::setElementID(const ByteVector &eID)
137 {
138 d->elementID = eID;
139 strip(d->elementID);
140 }
141
setIsTopLevel(const bool & t)142 void TableOfContentsFrame::setIsTopLevel(const bool &t)
143 {
144 d->isTopLevel = t;
145 }
146
setIsOrdered(const bool & o)147 void TableOfContentsFrame::setIsOrdered(const bool &o)
148 {
149 d->isOrdered = o;
150 }
151
setChildElements(const ByteVectorList & l)152 void TableOfContentsFrame::setChildElements(const ByteVectorList &l)
153 {
154 d->childElements = l;
155 strip(d->childElements);
156 }
157
addChildElement(const ByteVector & cE)158 void TableOfContentsFrame::addChildElement(const ByteVector &cE)
159 {
160 d->childElements.append(cE);
161 strip(d->childElements);
162 }
163
removeChildElement(const ByteVector & cE)164 void TableOfContentsFrame::removeChildElement(const ByteVector &cE)
165 {
166 ByteVectorList::Iterator it = d->childElements.find(cE);
167
168 if(it == d->childElements.end())
169 it = d->childElements.find(cE + ByteVector("\0"));
170
171 d->childElements.erase(it);
172 }
173
embeddedFrameListMap() const174 const FrameListMap &TableOfContentsFrame::embeddedFrameListMap() const
175 {
176 return d->embeddedFrameListMap;
177 }
178
embeddedFrameList() const179 const FrameList &TableOfContentsFrame::embeddedFrameList() const
180 {
181 return d->embeddedFrameList;
182 }
183
embeddedFrameList(const ByteVector & frameID) const184 const FrameList &TableOfContentsFrame::embeddedFrameList(const ByteVector &frameID) const
185 {
186 return d->embeddedFrameListMap[frameID];
187 }
188
addEmbeddedFrame(Frame * frame)189 void TableOfContentsFrame::addEmbeddedFrame(Frame *frame)
190 {
191 d->embeddedFrameList.append(frame);
192 d->embeddedFrameListMap[frame->frameID()].append(frame);
193 }
194
removeEmbeddedFrame(Frame * frame,bool del)195 void TableOfContentsFrame::removeEmbeddedFrame(Frame *frame, bool del)
196 {
197 // remove the frame from the frame list
198 FrameList::Iterator it = d->embeddedFrameList.find(frame);
199 d->embeddedFrameList.erase(it);
200
201 // ...and from the frame list map
202 it = d->embeddedFrameListMap[frame->frameID()].find(frame);
203 d->embeddedFrameListMap[frame->frameID()].erase(it);
204
205 // ...and delete as desired
206 if(del)
207 delete frame;
208 }
209
removeEmbeddedFrames(const ByteVector & id)210 void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id)
211 {
212 FrameList l = d->embeddedFrameListMap[id];
213 for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
214 removeEmbeddedFrame(*it, true);
215 }
216
toString() const217 String TableOfContentsFrame::toString() const
218 {
219 String s = String(d->elementID) +
220 ": top level: " + (d->isTopLevel ? "true" : "false") +
221 ", ordered: " + (d->isOrdered ? "true" : "false");
222
223 if(!d->childElements.isEmpty()) {
224 s+= ", chapters: [ " + String(d->childElements.toByteVector(", ")) + " ]";
225 }
226
227 if(!d->embeddedFrameList.isEmpty()) {
228 StringList frameIDs;
229 for(FrameList::ConstIterator it = d->embeddedFrameList.begin();
230 it != d->embeddedFrameList.end(); ++it)
231 frameIDs.append((*it)->frameID());
232 s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]";
233 }
234
235 return s;
236 }
237
asProperties() const238 PropertyMap TableOfContentsFrame::asProperties() const
239 {
240 PropertyMap map;
241
242 map.unsupportedData().append(frameID() + String("/") + d->elementID);
243
244 return map;
245 }
246
findByElementID(const ID3v2::Tag * tag,const ByteVector & eID)247 TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *tag,
248 const ByteVector &eID) // static
249 {
250 ID3v2::FrameList tablesOfContents = tag->frameList("CTOC");
251
252 for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin();
253 it != tablesOfContents.end();
254 ++it)
255 {
256 TableOfContentsFrame *frame = dynamic_cast<TableOfContentsFrame *>(*it);
257 if(frame && frame->elementID() == eID)
258 return frame;
259 }
260
261 return 0;
262 }
263
findTopLevel(const ID3v2::Tag * tag)264 TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const ID3v2::Tag *tag) // static
265 {
266 ID3v2::FrameList tablesOfContents = tag->frameList("CTOC");
267
268 for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin();
269 it != tablesOfContents.end();
270 ++it)
271 {
272 TableOfContentsFrame *frame = dynamic_cast<TableOfContentsFrame *>(*it);
273 if(frame && frame->isTopLevel() == true)
274 return frame;
275 }
276
277 return 0;
278 }
279
parseFields(const ByteVector & data)280 void TableOfContentsFrame::parseFields(const ByteVector &data)
281 {
282 unsigned int size = data.size();
283 if(size < 6) {
284 debug("A CTOC frame must contain at least 6 bytes (1 byte element ID terminated by "
285 "null, 1 byte flags, 1 byte entry count and 1 byte child element ID terminated "
286 "by null.");
287 return;
288 }
289
290 int pos = 0;
291 unsigned int embPos = 0;
292 d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
293 d->isTopLevel = (data.at(pos) & 2) != 0;
294 d->isOrdered = (data.at(pos++) & 1) != 0;
295 unsigned int entryCount = static_cast<unsigned char>(data.at(pos++));
296 for(unsigned int i = 0; i < entryCount; i++) {
297 ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
298 d->childElements.append(childElementID);
299 }
300
301 size -= pos;
302
303 if(size < header()->size())
304 return;
305
306 while(embPos < size - header()->size()) {
307 Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader);
308
309 if(!frame)
310 return;
311
312 // Checks to make sure that frame parsed correctly.
313 if(frame->size() <= 0) {
314 delete frame;
315 return;
316 }
317
318 embPos += frame->size() + header()->size();
319 addEmbeddedFrame(frame);
320 }
321 }
322
renderFields() const323 ByteVector TableOfContentsFrame::renderFields() const
324 {
325 ByteVector data;
326
327 data.append(d->elementID);
328 data.append('\0');
329 char flags = 0;
330 if(d->isTopLevel)
331 flags += 2;
332 if(d->isOrdered)
333 flags += 1;
334 data.append(flags);
335 data.append((char)(entryCount()));
336 ByteVectorList::ConstIterator it = d->childElements.begin();
337 while(it != d->childElements.end()) {
338 data.append(*it);
339 data.append('\0');
340 it++;
341 }
342 FrameList l = d->embeddedFrameList;
343 for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
344 data.append((*it)->render());
345
346 return data;
347 }
348
TableOfContentsFrame(const ID3v2::Header * tagHeader,const ByteVector & data,Header * h)349 TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader,
350 const ByteVector &data, Header *h) :
351 Frame(h),
352 d(new TableOfContentsFramePrivate())
353 {
354 d->tagHeader = tagHeader;
355 parseFields(fieldData(data));
356 }
357