1 /***************************************************************************
2     copyright            : (C) 2003-2004 by Allan Sandfeld Jensen
3     email                : kde@carewolf.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., 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 <tbytevector.h>
27 #include <tstring.h>
28 #include <tlist.h>
29 #include <tdebug.h>
30 #include <tagunion.h>
31 #include <tpropertymap.h>
32 #include <tagutils.h>
33 
34 #include <id3v2header.h>
35 #include <id3v2tag.h>
36 #include <id3v1tag.h>
37 #include <xiphcomment.h>
38 
39 #include "flacpicture.h"
40 #include "flacfile.h"
41 #include "flacmetadatablock.h"
42 #include "flacunknownmetadatablock.h"
43 
44 using namespace TagLib;
45 
46 namespace
47 {
48   typedef List<FLAC::MetadataBlock *> BlockList;
49   typedef BlockList::Iterator BlockIterator;
50   typedef BlockList::Iterator BlockConstIterator;
51 
52   enum { FlacXiphIndex = 0, FlacID3v2Index = 1, FlacID3v1Index = 2 };
53 
54   const long MinPaddingLength = 4096;
55   const long MaxPaddingLegnth = 1024 * 1024;
56 
57   const char LastBlockFlag = '\x80';
58 }
59 
60 class FLAC::File::FilePrivate
61 {
62 public:
FilePrivate(const ID3v2::FrameFactory * frameFactory=ID3v2::FrameFactory::instance ())63   FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
64     ID3v2FrameFactory(frameFactory),
65     ID3v2Location(-1),
66     ID3v2OriginalSize(0),
67     ID3v1Location(-1),
68     properties(0),
69     flacStart(0),
70     streamStart(0),
71     scanned(false)
72   {
73     blocks.setAutoDelete(true);
74   }
75 
~FilePrivate()76   ~FilePrivate()
77   {
78     delete properties;
79   }
80 
81   const ID3v2::FrameFactory *ID3v2FrameFactory;
82   long ID3v2Location;
83   long ID3v2OriginalSize;
84 
85   long ID3v1Location;
86 
87   TagUnion tag;
88 
89   Properties *properties;
90   ByteVector xiphCommentData;
91   BlockList blocks;
92 
93   long flacStart;
94   long streamStart;
95   bool scanned;
96 };
97 
98 ////////////////////////////////////////////////////////////////////////////////
99 // static members
100 ////////////////////////////////////////////////////////////////////////////////
101 
isSupported(IOStream * stream)102 bool FLAC::File::isSupported(IOStream *stream)
103 {
104   // A FLAC file has an ID "fLaC" somewhere. An ID3v2 tag may precede.
105 
106   const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true);
107   return (buffer.find("fLaC") >= 0);
108 }
109 
110 ////////////////////////////////////////////////////////////////////////////////
111 // public members
112 ////////////////////////////////////////////////////////////////////////////////
113 
File(FileName file,bool readProperties,Properties::ReadStyle)114 FLAC::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
115   TagLib::File(file),
116   d(new FilePrivate())
117 {
118   if(isOpen())
119     read(readProperties);
120 }
121 
File(FileName file,ID3v2::FrameFactory * frameFactory,bool readProperties,Properties::ReadStyle)122 FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
123                  bool readProperties, Properties::ReadStyle) :
124   TagLib::File(file),
125   d(new FilePrivate(frameFactory))
126 {
127   if(isOpen())
128     read(readProperties);
129 }
130 
File(IOStream * stream,ID3v2::FrameFactory * frameFactory,bool readProperties,Properties::ReadStyle)131 FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
132                  bool readProperties, Properties::ReadStyle) :
133   TagLib::File(stream),
134   d(new FilePrivate(frameFactory))
135 {
136   if(isOpen())
137     read(readProperties);
138 }
139 
~File()140 FLAC::File::~File()
141 {
142   delete d;
143 }
144 
tag() const145 TagLib::Tag *FLAC::File::tag() const
146 {
147   return &d->tag;
148 }
149 
properties() const150 PropertyMap FLAC::File::properties() const
151 {
152   return d->tag.properties();
153 }
154 
removeUnsupportedProperties(const StringList & unsupported)155 void FLAC::File::removeUnsupportedProperties(const StringList &unsupported)
156 {
157   d->tag.removeUnsupportedProperties(unsupported);
158 }
159 
setProperties(const PropertyMap & properties)160 PropertyMap FLAC::File::setProperties(const PropertyMap &properties)
161 {
162   return xiphComment(true)->setProperties(properties);
163 }
164 
audioProperties() const165 FLAC::Properties *FLAC::File::audioProperties() const
166 {
167   return d->properties;
168 }
169 
save()170 bool FLAC::File::save()
171 {
172   if(readOnly()) {
173     debug("FLAC::File::save() - Cannot save to a read only file.");
174     return false;
175   }
176 
177   if(!isValid()) {
178     debug("FLAC::File::save() -- Trying to save invalid file.");
179     return false;
180   }
181 
182   // Create new vorbis comments
183   if(!hasXiphComment())
184     Tag::duplicate(&d->tag, xiphComment(true), false);
185 
186   d->xiphCommentData = xiphComment()->render(false);
187 
188   // Replace metadata blocks
189 
190   for(BlockIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
191     if((*it)->code() == MetadataBlock::VorbisComment) {
192       // Set the new Vorbis Comment block
193       delete *it;
194       d->blocks.erase(it);
195       break;
196     }
197   }
198 
199   d->blocks.append(new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData));
200 
201   // Render data for the metadata blocks
202 
203   ByteVector data;
204   for(BlockConstIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
205     ByteVector blockData = (*it)->render();
206     ByteVector blockHeader = ByteVector::fromUInt(blockData.size());
207     blockHeader[0] = (*it)->code();
208     data.append(blockHeader);
209     data.append(blockData);
210   }
211 
212   // Compute the amount of padding, and append that to data.
213 
214   long originalLength = d->streamStart - d->flacStart;
215   long paddingLength = originalLength - data.size() - 4;
216 
217   if(paddingLength <= 0) {
218     paddingLength = MinPaddingLength;
219   }
220   else {
221     // Padding won't increase beyond 1% of the file size or 1MB.
222 
223     long threshold = length() / 100;
224     threshold = std::max(threshold, MinPaddingLength);
225     threshold = std::min(threshold, MaxPaddingLegnth);
226 
227     if(paddingLength > threshold)
228       paddingLength = MinPaddingLength;
229   }
230 
231   ByteVector paddingHeader = ByteVector::fromUInt(paddingLength);
232   paddingHeader[0] = static_cast<char>(MetadataBlock::Padding | LastBlockFlag);
233   data.append(paddingHeader);
234   data.resize(static_cast<unsigned int>(data.size() + paddingLength));
235 
236   // Write the data to the file
237 
238   insert(data, d->flacStart, originalLength);
239 
240   d->streamStart += (static_cast<long>(data.size()) - originalLength);
241 
242   if(d->ID3v1Location >= 0)
243     d->ID3v1Location += (static_cast<long>(data.size()) - originalLength);
244 
245   // Update ID3 tags
246 
247   if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
248 
249     // ID3v2 tag is not empty. Update the old one or create a new one.
250 
251     if(d->ID3v2Location < 0)
252       d->ID3v2Location = 0;
253 
254     data = ID3v2Tag()->render();
255     insert(data, d->ID3v2Location, d->ID3v2OriginalSize);
256 
257     d->flacStart   += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
258     d->streamStart += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
259 
260     if(d->ID3v1Location >= 0)
261       d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
262 
263     d->ID3v2OriginalSize = data.size();
264   }
265   else {
266 
267     // ID3v2 tag is empty. Remove the old one.
268 
269     if(d->ID3v2Location >= 0) {
270       removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
271 
272       d->flacStart   -= d->ID3v2OriginalSize;
273       d->streamStart -= d->ID3v2OriginalSize;
274 
275       if(d->ID3v1Location >= 0)
276         d->ID3v1Location -= d->ID3v2OriginalSize;
277 
278       d->ID3v2Location = -1;
279       d->ID3v2OriginalSize = 0;
280     }
281   }
282 
283   if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
284 
285     // ID3v1 tag is not empty. Update the old one or create a new one.
286 
287     if(d->ID3v1Location >= 0) {
288       seek(d->ID3v1Location);
289     }
290     else {
291       seek(0, End);
292       d->ID3v1Location = tell();
293     }
294 
295     writeBlock(ID3v1Tag()->render());
296   }
297   else {
298 
299     // ID3v1 tag is empty. Remove the old one.
300 
301     if(d->ID3v1Location >= 0) {
302       truncate(d->ID3v1Location);
303       d->ID3v1Location = -1;
304     }
305   }
306 
307   return true;
308 }
309 
ID3v2Tag(bool create)310 ID3v2::Tag *FLAC::File::ID3v2Tag(bool create)
311 {
312   return d->tag.access<ID3v2::Tag>(FlacID3v2Index, create);
313 }
314 
ID3v1Tag(bool create)315 ID3v1::Tag *FLAC::File::ID3v1Tag(bool create)
316 {
317   return d->tag.access<ID3v1::Tag>(FlacID3v1Index, create);
318 }
319 
xiphComment(bool create)320 Ogg::XiphComment *FLAC::File::xiphComment(bool create)
321 {
322   return d->tag.access<Ogg::XiphComment>(FlacXiphIndex, create);
323 }
324 
setID3v2FrameFactory(const ID3v2::FrameFactory * factory)325 void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
326 {
327   d->ID3v2FrameFactory = factory;
328 }
329 
streamInfoData()330 ByteVector FLAC::File::streamInfoData()
331 {
332   debug("FLAC::File::streamInfoData() -- This function is obsolete. Returning an empty ByteVector.");
333   return ByteVector();
334 }
335 
streamLength()336 long FLAC::File::streamLength()
337 {
338   debug("FLAC::File::streamLength() -- This function is obsolete. Returning zero.");
339   return 0;
340 }
341 
pictureList()342 List<FLAC::Picture *> FLAC::File::pictureList()
343 {
344   List<Picture *> pictures;
345   for(BlockConstIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
346     Picture *picture = dynamic_cast<Picture *>(*it);
347     if(picture) {
348       pictures.append(picture);
349     }
350   }
351   return pictures;
352 }
353 
addPicture(Picture * picture)354 void FLAC::File::addPicture(Picture *picture)
355 {
356   d->blocks.append(picture);
357 }
358 
removePicture(Picture * picture,bool del)359 void FLAC::File::removePicture(Picture *picture, bool del)
360 {
361   BlockIterator it = d->blocks.find(picture);
362   if(it != d->blocks.end())
363     d->blocks.erase(it);
364 
365   if(del)
366     delete picture;
367 }
368 
removePictures()369 void FLAC::File::removePictures()
370 {
371   for(BlockIterator it = d->blocks.begin(); it != d->blocks.end(); ) {
372     if(dynamic_cast<Picture *>(*it)) {
373       delete *it;
374       it = d->blocks.erase(it);
375     }
376     else {
377       ++it;
378     }
379   }
380 }
381 
strip(int tags)382 void FLAC::File::strip(int tags)
383 {
384   if(tags & ID3v1)
385     d->tag.set(FlacID3v1Index, 0);
386 
387   if(tags & ID3v2)
388     d->tag.set(FlacID3v2Index, 0);
389 
390   if(tags & XiphComment) {
391     xiphComment()->removeAllFields();
392     xiphComment()->removeAllPictures();
393   }
394 }
395 
hasXiphComment() const396 bool FLAC::File::hasXiphComment() const
397 {
398   return !d->xiphCommentData.isEmpty();
399 }
400 
hasID3v1Tag() const401 bool FLAC::File::hasID3v1Tag() const
402 {
403   return (d->ID3v1Location >= 0);
404 }
405 
hasID3v2Tag() const406 bool FLAC::File::hasID3v2Tag() const
407 {
408   return (d->ID3v2Location >= 0);
409 }
410 
411 ////////////////////////////////////////////////////////////////////////////////
412 // private members
413 ////////////////////////////////////////////////////////////////////////////////
414 
read(bool readProperties)415 void FLAC::File::read(bool readProperties)
416 {
417   // Look for an ID3v2 tag
418 
419   d->ID3v2Location = Utils::findID3v2(this);
420 
421   if(d->ID3v2Location >= 0) {
422     d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
423     d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
424   }
425 
426   // Look for an ID3v1 tag
427 
428   d->ID3v1Location = Utils::findID3v1(this);
429 
430   if(d->ID3v1Location >= 0)
431     d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
432 
433   // Look for FLAC metadata, including vorbis comments
434 
435   scan();
436 
437   if(!isValid())
438     return;
439 
440   if(!d->xiphCommentData.isEmpty())
441     d->tag.set(FlacXiphIndex, new Ogg::XiphComment(d->xiphCommentData));
442   else
443     d->tag.set(FlacXiphIndex, new Ogg::XiphComment());
444 
445   if(readProperties) {
446 
447     // First block should be the stream_info metadata
448 
449     const ByteVector infoData = d->blocks.front()->render();
450 
451     long streamLength;
452 
453     if(d->ID3v1Location >= 0)
454       streamLength = d->ID3v1Location - d->streamStart;
455     else
456       streamLength = length() - d->streamStart;
457 
458     d->properties = new Properties(infoData, streamLength);
459   }
460 }
461 
scan()462 void FLAC::File::scan()
463 {
464   // Scan the metadata pages
465 
466   if(d->scanned)
467     return;
468 
469   if(!isValid())
470     return;
471 
472   long nextBlockOffset;
473 
474   if(d->ID3v2Location >= 0)
475     nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
476   else
477     nextBlockOffset = find("fLaC");
478 
479   if(nextBlockOffset < 0) {
480     debug("FLAC::File::scan() -- FLAC stream not found");
481     setValid(false);
482     return;
483   }
484 
485   nextBlockOffset += 4;
486   d->flacStart = nextBlockOffset;
487 
488   while(true) {
489 
490     seek(nextBlockOffset);
491     const ByteVector header = readBlock(4);
492 
493     // Header format (from spec):
494     // <1> Last-metadata-block flag
495     // <7> BLOCK_TYPE
496     //    0 : STREAMINFO
497     //    1 : PADDING
498     //    ..
499     //    4 : VORBIS_COMMENT
500     //    ..
501     //    6 : PICTURE
502     //    ..
503     // <24> Length of metadata to follow
504 
505     const char blockType = header[0] & ~LastBlockFlag;
506     const bool isLastBlock = (header[0] & LastBlockFlag) != 0;
507     const unsigned int blockLength = header.toUInt(1U, 3U);
508 
509     // First block should be the stream_info metadata
510 
511     if(d->blocks.isEmpty() && blockType != MetadataBlock::StreamInfo) {
512       debug("FLAC::File::scan() -- First block should be the stream_info metadata");
513       setValid(false);
514       return;
515     }
516 
517     if(blockLength == 0
518       && blockType != MetadataBlock::Padding && blockType != MetadataBlock::SeekTable)
519     {
520       debug("FLAC::File::scan() -- Zero-sized metadata block found");
521       setValid(false);
522       return;
523     }
524 
525     const ByteVector data = readBlock(blockLength);
526     if(data.size() != blockLength) {
527       debug("FLAC::File::scan() -- Failed to read a metadata block");
528       setValid(false);
529       return;
530     }
531 
532     MetadataBlock *block = 0;
533 
534     // Found the vorbis-comment
535     if(blockType == MetadataBlock::VorbisComment) {
536       if(d->xiphCommentData.isEmpty()) {
537         d->xiphCommentData = data;
538         block = new UnknownMetadataBlock(MetadataBlock::VorbisComment, data);
539       }
540       else {
541         debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, discarding");
542       }
543     }
544     else if(blockType == MetadataBlock::Picture) {
545       FLAC::Picture *picture = new FLAC::Picture();
546       if(picture->parse(data)) {
547         block = picture;
548       }
549       else {
550         debug("FLAC::File::scan() -- invalid picture found, discarding");
551         delete picture;
552       }
553     }
554     else if(blockType == MetadataBlock::Padding) {
555       // Skip all padding blocks.
556     }
557     else {
558       block = new UnknownMetadataBlock(blockType, data);
559     }
560 
561     if(block)
562       d->blocks.append(block);
563 
564     nextBlockOffset += blockLength + 4;
565 
566     if(isLastBlock)
567       break;
568   }
569 
570   // End of metadata, now comes the datastream
571 
572   d->streamStart = nextBlockOffset;
573 
574   d->scanned = true;
575 }
576