Added TagLib (with AUTORS and COPYING files)
[someplayer] / src / taglib / flac / flacfile.cpp
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., 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 <tstring.h>
28 #include <tlist.h>
29 #include <tdebug.h>
30 #include <tagunion.h>
31
32 #include <id3v2header.h>
33 #include <id3v2tag.h>
34 #include <id3v1tag.h>
35 #include <xiphcomment.h>
36
37 #include "flacfile.h"
38
39 using namespace TagLib;
40
41 namespace
42 {
43   enum { XiphIndex = 0, ID3v2Index = 1, ID3v1Index = 2 };
44   enum { StreamInfo = 0, Padding, Application, SeekTable, VorbisComment, CueSheet };
45   enum { MinPaddingLength = 4096 };
46 }
47
48 class FLAC::File::FilePrivate
49 {
50 public:
51   FilePrivate() :
52     ID3v2FrameFactory(ID3v2::FrameFactory::instance()),
53     ID3v2Location(-1),
54     ID3v2OriginalSize(0),
55     ID3v1Location(-1),
56     properties(0),
57     flacStart(0),
58     streamStart(0),
59     streamLength(0),
60     scanned(false),
61     hasXiphComment(false),
62     hasID3v2(false),
63     hasID3v1(false) {}
64
65   ~FilePrivate()
66   {
67     delete properties;
68   }
69
70   const ID3v2::FrameFactory *ID3v2FrameFactory;
71   long ID3v2Location;
72   uint ID3v2OriginalSize;
73
74   long ID3v1Location;
75
76   TagUnion tag;
77
78   Properties *properties;
79   ByteVector streamInfoData;
80   ByteVector xiphCommentData;
81
82   long flacStart;
83   long streamStart;
84   long streamLength;
85   bool scanned;
86
87   bool hasXiphComment;
88   bool hasID3v2;
89   bool hasID3v1;
90 };
91
92 ////////////////////////////////////////////////////////////////////////////////
93 // public members
94 ////////////////////////////////////////////////////////////////////////////////
95
96 FLAC::File::File(FileName file, bool readProperties,
97                  Properties::ReadStyle propertiesStyle) :
98   TagLib::File(file)
99 {
100   d = new FilePrivate;
101   read(readProperties, propertiesStyle);
102 }
103
104 FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
105                  bool readProperties, Properties::ReadStyle propertiesStyle) :
106   TagLib::File(file)
107 {
108   d = new FilePrivate;
109   d->ID3v2FrameFactory = frameFactory;
110   read(readProperties, propertiesStyle);
111 }
112
113 FLAC::File::~File()
114 {
115   delete d;
116 }
117
118 TagLib::Tag *FLAC::File::tag() const
119 {
120   return &d->tag;
121 }
122
123 FLAC::Properties *FLAC::File::audioProperties() const
124 {
125   return d->properties;
126 }
127
128
129 bool FLAC::File::save()
130 {
131   if(readOnly()) {
132     debug("FLAC::File::save() - Cannot save to a read only file.");
133     return false;
134   }
135
136   // Create new vorbis comments
137
138   Tag::duplicate(&d->tag, xiphComment(true), true);
139
140   d->xiphCommentData = xiphComment()->render(false);
141
142   // A Xiph comment portion of the data stream starts with a 4-byte descriptor.
143   // The first byte indicates the frame type.  The last three bytes are used
144   // to give the length of the data segment.  Here we start
145
146   ByteVector data = ByteVector::fromUInt(d->xiphCommentData.size());
147
148   data[0] = char(VorbisComment);
149   data.append(d->xiphCommentData);
150
151
152    // If file already have comment => find and update it
153    // if not => insert one
154
155    // TODO: Search for padding and use that
156
157   if(d->hasXiphComment) {
158
159     long nextBlockOffset = d->flacStart;
160     bool isLastBlock = false;
161
162     while(!isLastBlock) {
163       seek(nextBlockOffset);
164
165       ByteVector header = readBlock(4);
166       char blockType = header[0] & 0x7f;
167       isLastBlock = (header[0] & 0x80) != 0;
168       uint blockLength = header.mid(1, 3).toUInt();
169
170       if(blockType == VorbisComment) {
171
172         long paddingBreak = 0;
173
174         if(!isLastBlock) {
175           paddingBreak = findPaddingBreak(nextBlockOffset + blockLength + 4,
176                                           nextBlockOffset + d->xiphCommentData.size() + 8,
177                                           &isLastBlock);
178         }
179
180         uint paddingLength = 0;
181
182          if(paddingBreak) {
183
184            // There is space for comment and padding blocks without rewriting the
185            // whole file.  Note: This cannot overflow.
186
187            paddingLength = paddingBreak - (nextBlockOffset + d->xiphCommentData.size() + 8);
188          }
189          else {
190
191            // Not enough space, so we will have to rewrite the whole file
192            // following this block
193
194            paddingLength = d->xiphCommentData.size();
195
196            if(paddingLength < MinPaddingLength)
197              paddingLength = MinPaddingLength;
198
199            paddingBreak = nextBlockOffset + blockLength + 4;
200          }
201
202          ByteVector padding = ByteVector::fromUInt(paddingLength);
203
204          padding[0] = 1;
205
206          if(isLastBlock)
207            padding[0] |= 0x80;
208
209          padding.resize(paddingLength + 4);
210          ByteVector pair(data);
211          pair.append(padding);
212          insert(pair, nextBlockOffset, paddingBreak - nextBlockOffset);
213          break;
214       }
215
216       nextBlockOffset += blockLength + 4;
217     }
218   }
219   else {
220
221     const long firstBlockOffset = d->flacStart;
222     seek(firstBlockOffset);
223
224     ByteVector header = readBlock(4);
225     bool isLastBlock = (header[0] & 0x80) != 0;
226     uint blockLength = header.mid(1, 3).toUInt();
227
228     if(isLastBlock) {
229
230       // If the first block was previously also the last block, then we want to
231       // mark it as no longer being the first block (the writeBlock() call) and
232       // then set the data for the block that we're about to write to mark our
233       // new block as the last block.
234
235       seek(firstBlockOffset);
236       writeBlock(static_cast<char>(header[0] & 0x7F));
237       data[0] |= 0x80;
238     }
239
240     insert(data, firstBlockOffset + blockLength + 4, 0);
241     d->hasXiphComment = true;
242   }
243
244   // Update ID3 tags
245
246   if(ID3v2Tag()) {
247     if(d->hasID3v2) {
248       if(d->ID3v2Location < d->flacStart)
249         debug("FLAC::File::save() -- This can't be right -- an ID3v2 tag after the "
250               "start of the FLAC bytestream?  Not writing the ID3v2 tag.");
251       else
252         insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize);
253     }
254     else
255       insert(ID3v2Tag()->render(), 0, 0);
256   }
257
258   if(ID3v1Tag()) {
259     seek(-128, End);
260     writeBlock(ID3v1Tag()->render());
261   }
262
263   return true;
264 }
265
266 ID3v2::Tag *FLAC::File::ID3v2Tag(bool create)
267 {
268   if(!create || d->tag[ID3v2Index])
269     return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]);
270
271   d->tag.set(ID3v2Index, new ID3v2::Tag);
272   return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]);
273 }
274
275 ID3v1::Tag *FLAC::File::ID3v1Tag(bool create)
276 {
277   return d->tag.access<ID3v1::Tag>(ID3v1Index, create);
278 }
279
280 Ogg::XiphComment *FLAC::File::xiphComment(bool create)
281 {
282   return d->tag.access<Ogg::XiphComment>(XiphIndex, create);
283 }
284
285 void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
286 {
287   d->ID3v2FrameFactory = factory;
288 }
289
290
291 ////////////////////////////////////////////////////////////////////////////////
292 // private members
293 ////////////////////////////////////////////////////////////////////////////////
294
295 void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
296 {
297   // Look for an ID3v2 tag
298
299   d->ID3v2Location = findID3v2();
300
301   if(d->ID3v2Location >= 0) {
302
303     d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
304
305     d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
306
307     if(ID3v2Tag()->header()->tagSize() <= 0)
308       d->tag.set(ID3v2Index, 0);
309     else
310       d->hasID3v2 = true;
311   }
312
313   // Look for an ID3v1 tag
314
315   d->ID3v1Location = findID3v1();
316
317   if(d->ID3v1Location >= 0) {
318     d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
319     d->hasID3v1 = true;
320   }
321
322   // Look for FLAC metadata, including vorbis comments
323
324   scan();
325
326   if(!isValid())
327     return;
328
329   if(d->hasXiphComment)
330     d->tag.set(XiphIndex, new Ogg::XiphComment(xiphCommentData()));
331   else
332     d->tag.set(XiphIndex, new Ogg::XiphComment);
333
334   if(readProperties)
335     d->properties = new Properties(streamInfoData(), streamLength(), propertiesStyle);
336 }
337
338 ByteVector FLAC::File::streamInfoData()
339 {
340   return isValid() ? d->streamInfoData : ByteVector();
341 }
342
343 ByteVector FLAC::File::xiphCommentData() const
344 {
345   return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector();
346 }
347
348 long FLAC::File::streamLength()
349 {
350   return d->streamLength;
351 }
352
353 void FLAC::File::scan()
354 {
355   // Scan the metadata pages
356
357   if(d->scanned)
358     return;
359
360   if(!isValid())
361     return;
362
363   long nextBlockOffset;
364
365   if(d->hasID3v2)
366     nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
367   else
368     nextBlockOffset = find("fLaC");
369
370   if(nextBlockOffset < 0) {
371     debug("FLAC::File::scan() -- FLAC stream not found");
372     setValid(false);
373     return;
374   }
375
376   nextBlockOffset += 4;
377   d->flacStart = nextBlockOffset;
378
379   seek(nextBlockOffset);
380
381   ByteVector header = readBlock(4);
382
383   // Header format (from spec):
384   // <1> Last-metadata-block flag
385   // <7> BLOCK_TYPE
386   //    0 : STREAMINFO
387   //    1 : PADDING
388   //    ..
389   //    4 : VORBIS_COMMENT
390   //    ..
391   // <24> Length of metadata to follow
392
393   char blockType = header[0] & 0x7f;
394   bool isLastBlock = (header[0] & 0x80) != 0;
395   uint length = header.mid(1, 3).toUInt();
396
397   // First block should be the stream_info metadata
398
399   if(blockType != StreamInfo) {
400     debug("FLAC::File::scan() -- invalid FLAC stream");
401     setValid(false);
402     return;
403   }
404
405   d->streamInfoData = readBlock(length);
406   nextBlockOffset += length + 4;
407
408   // Search through the remaining metadata
409   while(!isLastBlock) {
410
411     header = readBlock(4);
412     blockType = header[0] & 0x7f;
413     isLastBlock = (header[0] & 0x80) != 0;
414     length = header.mid(1, 3).toUInt();
415
416     // Found the vorbis-comment
417     if(blockType == VorbisComment) {
418       if(!d->hasXiphComment) {
419         d->xiphCommentData = readBlock(length);
420         d->hasXiphComment = true;
421       }
422       else {
423         debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one");
424       }
425     }
426
427     nextBlockOffset += length + 4;
428
429     if(nextBlockOffset >= File::length()) {
430       debug("FLAC::File::scan() -- FLAC stream corrupted");
431       setValid(false);
432       return;
433     }
434     seek(nextBlockOffset);
435   }
436
437   // End of metadata, now comes the datastream
438
439   d->streamStart = nextBlockOffset;
440   d->streamLength = File::length() - d->streamStart;
441
442   if(d->hasID3v1)
443     d->streamLength -= 128;
444
445   d->scanned = true;
446 }
447
448 long FLAC::File::findID3v1()
449 {
450   if(!isValid())
451     return -1;
452
453   seek(-128, End);
454   long p = tell();
455
456   if(readBlock(3) == ID3v1::Tag::fileIdentifier())
457     return p;
458
459   return -1;
460 }
461
462 long FLAC::File::findID3v2()
463 {
464   if(!isValid())
465     return -1;
466
467   seek(0);
468
469   if(readBlock(3) == ID3v2::Header::fileIdentifier())
470     return 0;
471
472   return -1;
473 }
474
475 long FLAC::File::findPaddingBreak(long nextBlockOffset, long targetOffset, bool *isLast)
476 {
477   // Starting from nextBlockOffset, step over padding blocks to find the
478   // address of a block which is after targetOffset. Return zero if
479   // a non-padding block occurs before that point.
480
481   while(true) {
482     seek(nextBlockOffset);
483
484     ByteVector header = readBlock(4);
485     char blockType = header[0] & 0x7f;
486     bool isLastBlock = header[0] & 0x80;
487     uint length = header.mid(1, 3).toUInt();
488
489     if(blockType != Padding)
490       break;
491
492     nextBlockOffset += 4 + length;
493
494     if(nextBlockOffset >= targetOffset) {
495       *isLast = isLastBlock;
496       return nextBlockOffset;
497     }
498
499     if(isLastBlock)
500       break;
501   }
502
503   return 0;
504 }