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