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., 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 <tagunion.h>
27 #include <tagutils.h>
28 #include <id3v2tag.h>
29 #include <id3v2header.h>
30 #include <id3v1tag.h>
31 #include <apefooter.h>
32 #include <apetag.h>
33 #include <tdebug.h>
34 
35 #include "mpegfile.h"
36 #include "mpegheader.h"
37 #include "mpegutils.h"
38 #include "tpropertymap.h"
39 
40 using namespace TagLib;
41 
42 namespace
43 {
44   enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 };
45 }
46 
47 class MPEG::File::FilePrivate
48 {
49 public:
FilePrivate(const ID3v2::FrameFactory * frameFactory=ID3v2::FrameFactory::instance ())50   FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
51     ID3v2FrameFactory(frameFactory),
52     ID3v2Location(-1),
53     ID3v2OriginalSize(0),
54     APELocation(-1),
55     APEOriginalSize(0),
56     ID3v1Location(-1),
57     properties(0) {}
58 
~FilePrivate()59   ~FilePrivate()
60   {
61     delete properties;
62   }
63 
64   const ID3v2::FrameFactory *ID3v2FrameFactory;
65 
66   long ID3v2Location;
67   long ID3v2OriginalSize;
68 
69   long APELocation;
70   long APEOriginalSize;
71 
72   long ID3v1Location;
73 
74   TagUnion tag;
75 
76   Properties *properties;
77 };
78 
79 ////////////////////////////////////////////////////////////////////////////////
80 // static members
81 ////////////////////////////////////////////////////////////////////////////////
82 
83 namespace
84 {
85   // Dummy file class to make a stream work with MPEG::Header.
86 
87   class AdapterFile : public TagLib::File
88   {
89   public:
AdapterFile(IOStream * stream)90     AdapterFile(IOStream *stream) : File(stream) {}
91 
tag() const92     Tag *tag() const { return 0; }
audioProperties() const93     AudioProperties *audioProperties() const { return 0; }
save()94     bool save() { return false; }
95   };
96 }
97 
isSupported(IOStream * stream)98 bool MPEG::File::isSupported(IOStream *stream)
99 {
100   if(!stream || !stream->isOpen())
101     return false;
102 
103   // An MPEG file has MPEG frame headers. An ID3v2 tag may precede.
104 
105   // MPEG frame headers are really confusing with irrelevant binary data.
106   // So we check if a frame header is really valid.
107 
108   long headerOffset;
109   const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true, &headerOffset);
110 
111   if(buffer.isEmpty())
112 	  return false;
113 
114   const long originalPosition = stream->tell();
115   AdapterFile file(stream);
116 
117   for(unsigned int i = 0; i < buffer.size() - 1; ++i) {
118     if(isFrameSync(buffer, i)) {
119       const Header header(&file, headerOffset + i, true);
120       if(header.isValid()) {
121         stream->seek(originalPosition);
122         return true;
123       }
124     }
125   }
126 
127   stream->seek(originalPosition);
128   return false;
129 }
130 
131 ////////////////////////////////////////////////////////////////////////////////
132 // public members
133 ////////////////////////////////////////////////////////////////////////////////
134 
File(FileName file,bool readProperties,Properties::ReadStyle)135 MPEG::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
136   TagLib::File(file),
137   d(new FilePrivate())
138 {
139   if(isOpen())
140     read(readProperties);
141 }
142 
File(FileName file,ID3v2::FrameFactory * frameFactory,bool readProperties,Properties::ReadStyle)143 MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
144                  bool readProperties, Properties::ReadStyle) :
145   TagLib::File(file),
146   d(new FilePrivate(frameFactory))
147 {
148   if(isOpen())
149     read(readProperties);
150 }
151 
File(IOStream * stream,ID3v2::FrameFactory * frameFactory,bool readProperties,Properties::ReadStyle)152 MPEG::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
153                  bool readProperties, Properties::ReadStyle) :
154   TagLib::File(stream),
155   d(new FilePrivate(frameFactory))
156 {
157   if(isOpen())
158     read(readProperties);
159 }
160 
~File()161 MPEG::File::~File()
162 {
163   delete d;
164 }
165 
tag() const166 TagLib::Tag *MPEG::File::tag() const
167 {
168   return &d->tag;
169 }
170 
properties() const171 PropertyMap MPEG::File::properties() const
172 {
173   return d->tag.properties();
174 }
175 
removeUnsupportedProperties(const StringList & properties)176 void MPEG::File::removeUnsupportedProperties(const StringList &properties)
177 {
178   d->tag.removeUnsupportedProperties(properties);
179 }
180 
setProperties(const PropertyMap & properties)181 PropertyMap MPEG::File::setProperties(const PropertyMap &properties)
182 {
183   // update ID3v1 tag if it exists, but ignore the return value
184 
185   if(ID3v1Tag())
186     ID3v1Tag()->setProperties(properties);
187 
188   return ID3v2Tag(true)->setProperties(properties);
189 }
190 
audioProperties() const191 MPEG::Properties *MPEG::File::audioProperties() const
192 {
193   return d->properties;
194 }
195 
save()196 bool MPEG::File::save()
197 {
198   return save(AllTags);
199 }
200 
save(int tags)201 bool MPEG::File::save(int tags)
202 {
203   return save(tags, true);
204 }
205 
save(int tags,bool stripOthers)206 bool MPEG::File::save(int tags, bool stripOthers)
207 {
208   return save(tags, stripOthers, 4);
209 }
210 
save(int tags,bool stripOthers,int id3v2Version)211 bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version)
212 {
213   return save(tags, stripOthers, id3v2Version, true);
214 }
215 
save(int tags,bool stripOthers,int id3v2Version,bool duplicateTags)216 bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags)
217 {
218   if(readOnly()) {
219     debug("MPEG::File::save() -- File is read only.");
220     return false;
221   }
222 
223   // Create the tags if we've been asked to.
224 
225   if(duplicateTags) {
226 
227     // Copy the values from the tag that does exist into the new tag,
228     // except if the existing tag is to be stripped.
229 
230     if((tags & ID3v2) && ID3v1Tag() && !(stripOthers && !(tags & ID3v1)))
231       Tag::duplicate(ID3v1Tag(), ID3v2Tag(true), false);
232 
233     if((tags & ID3v1) && d->tag[ID3v2Index] && !(stripOthers && !(tags & ID3v2)))
234       Tag::duplicate(ID3v2Tag(), ID3v1Tag(true), false);
235   }
236 
237   // Remove all the tags not going to be saved.
238 
239   if(stripOthers)
240     strip(~tags, false);
241 
242   if(ID3v2 & tags) {
243 
244     if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
245 
246       // ID3v2 tag is not empty. Update the old one or create a new one.
247 
248       if(d->ID3v2Location < 0)
249         d->ID3v2Location = 0;
250 
251       const ByteVector data = ID3v2Tag()->render(id3v2Version);
252       insert(data, d->ID3v2Location, d->ID3v2OriginalSize);
253 
254       if(d->APELocation >= 0)
255         d->APELocation += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
256 
257       if(d->ID3v1Location >= 0)
258         d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
259 
260       d->ID3v2OriginalSize = data.size();
261     }
262     else {
263 
264       // ID3v2 tag is empty. Remove the old one.
265 
266       strip(ID3v2, false);
267     }
268   }
269 
270   if(ID3v1 & tags) {
271 
272     if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
273 
274       // ID3v1 tag is not empty. Update the old one or create a new one.
275 
276       if(d->ID3v1Location >= 0) {
277         seek(d->ID3v1Location);
278       }
279       else {
280         seek(0, End);
281         d->ID3v1Location = tell();
282       }
283 
284       writeBlock(ID3v1Tag()->render());
285     }
286     else {
287 
288       // ID3v1 tag is empty. Remove the old one.
289 
290       strip(ID3v1, false);
291     }
292   }
293 
294   if(APE & tags) {
295 
296     if(APETag() && !APETag()->isEmpty()) {
297 
298       // APE tag is not empty. Update the old one or create a new one.
299 
300       if(d->APELocation < 0) {
301         if(d->ID3v1Location >= 0)
302           d->APELocation = d->ID3v1Location;
303         else
304           d->APELocation = length();
305       }
306 
307       const ByteVector data = APETag()->render();
308       insert(data, d->APELocation, d->APEOriginalSize);
309 
310       if(d->ID3v1Location >= 0)
311         d->ID3v1Location += (static_cast<long>(data.size()) - d->APEOriginalSize);
312 
313       d->APEOriginalSize = data.size();
314     }
315     else {
316 
317       // APE tag is empty. Remove the old one.
318 
319       strip(APE, false);
320     }
321   }
322 
323   return true;
324 }
325 
ID3v2Tag(bool create)326 ID3v2::Tag *MPEG::File::ID3v2Tag(bool create)
327 {
328   return d->tag.access<ID3v2::Tag>(ID3v2Index, create);
329 }
330 
ID3v1Tag(bool create)331 ID3v1::Tag *MPEG::File::ID3v1Tag(bool create)
332 {
333   return d->tag.access<ID3v1::Tag>(ID3v1Index, create);
334 }
335 
APETag(bool create)336 APE::Tag *MPEG::File::APETag(bool create)
337 {
338   return d->tag.access<APE::Tag>(APEIndex, create);
339 }
340 
strip(int tags)341 bool MPEG::File::strip(int tags)
342 {
343   return strip(tags, true);
344 }
345 
strip(int tags,bool freeMemory)346 bool MPEG::File::strip(int tags, bool freeMemory)
347 {
348   if(readOnly()) {
349     debug("MPEG::File::strip() - Cannot strip tags from a read only file.");
350     return false;
351   }
352 
353   if((tags & ID3v2) && d->ID3v2Location >= 0) {
354     removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
355 
356     if(d->APELocation >= 0)
357       d->APELocation -= d->ID3v2OriginalSize;
358 
359     if(d->ID3v1Location >= 0)
360       d->ID3v1Location -= d->ID3v2OriginalSize;
361 
362     d->ID3v2Location = -1;
363     d->ID3v2OriginalSize = 0;
364 
365     if(freeMemory)
366       d->tag.set(ID3v2Index, 0);
367   }
368 
369   if((tags & ID3v1) && d->ID3v1Location >= 0) {
370     truncate(d->ID3v1Location);
371 
372     d->ID3v1Location = -1;
373 
374     if(freeMemory)
375       d->tag.set(ID3v1Index, 0);
376   }
377 
378   if((tags & APE) && d->APELocation >= 0) {
379     removeBlock(d->APELocation, d->APEOriginalSize);
380 
381     if(d->ID3v1Location >= 0)
382       d->ID3v1Location -= d->APEOriginalSize;
383 
384     d->APELocation = -1;
385     d->APEOriginalSize = 0;
386 
387     if(freeMemory)
388       d->tag.set(APEIndex, 0);
389   }
390 
391   return true;
392 }
393 
setID3v2FrameFactory(const ID3v2::FrameFactory * factory)394 void MPEG::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
395 {
396   d->ID3v2FrameFactory = factory;
397 }
398 
nextFrameOffset(long position)399 long MPEG::File::nextFrameOffset(long position)
400 {
401   ByteVector frameSyncBytes(2, '\0');
402 
403   while(true) {
404     seek(position);
405     const ByteVector buffer = readBlock(bufferSize());
406     if(buffer.isEmpty())
407       return -1;
408 
409     for(unsigned int i = 0; i < buffer.size(); ++i) {
410       frameSyncBytes[0] = frameSyncBytes[1];
411       frameSyncBytes[1] = buffer[i];
412       if(isFrameSync(frameSyncBytes)) {
413         const Header header(this, position + i - 1, true);
414         if(header.isValid())
415           return position + i - 1;
416       }
417     }
418 
419     position += bufferSize();
420   }
421 }
422 
previousFrameOffset(long position)423 long MPEG::File::previousFrameOffset(long position)
424 {
425   ByteVector frameSyncBytes(2, '\0');
426 
427   while(position > 0) {
428     const long bufferLength = std::min<long>(position, bufferSize());
429     position -= bufferLength;
430 
431     seek(position);
432     const ByteVector buffer = readBlock(bufferLength);
433 
434     for(int i = buffer.size() - 1; i >= 0; --i) {
435       frameSyncBytes[1] = frameSyncBytes[0];
436       frameSyncBytes[0] = buffer[i];
437       if(isFrameSync(frameSyncBytes)) {
438         const Header header(this, position + i, true);
439         if(header.isValid())
440           return position + i + header.frameLength();
441       }
442     }
443   }
444 
445   return -1;
446 }
447 
firstFrameOffset()448 long MPEG::File::firstFrameOffset()
449 {
450   long position = 0;
451 
452   if(hasID3v2Tag())
453     position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize();
454 
455   return nextFrameOffset(position);
456 }
457 
lastFrameOffset()458 long MPEG::File::lastFrameOffset()
459 {
460   long position;
461 
462   if(hasAPETag())
463     position = d->APELocation - 1;
464   else if(hasID3v1Tag())
465     position = d->ID3v1Location - 1;
466   else
467     position = length();
468 
469   return previousFrameOffset(position);
470 }
471 
hasID3v1Tag() const472 bool MPEG::File::hasID3v1Tag() const
473 {
474   return (d->ID3v1Location >= 0);
475 }
476 
hasID3v2Tag() const477 bool MPEG::File::hasID3v2Tag() const
478 {
479   return (d->ID3v2Location >= 0);
480 }
481 
hasAPETag() const482 bool MPEG::File::hasAPETag() const
483 {
484   return (d->APELocation >= 0);
485 }
486 
487 ////////////////////////////////////////////////////////////////////////////////
488 // private members
489 ////////////////////////////////////////////////////////////////////////////////
490 
read(bool readProperties)491 void MPEG::File::read(bool readProperties)
492 {
493   // Look for an ID3v2 tag
494 
495   d->ID3v2Location = findID3v2();
496 
497   if(d->ID3v2Location >= 0) {
498     d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
499     d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
500   }
501 
502   // Look for an ID3v1 tag
503 
504   d->ID3v1Location = Utils::findID3v1(this);
505 
506   if(d->ID3v1Location >= 0)
507     d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
508 
509   // Look for an APE tag
510 
511   d->APELocation = Utils::findAPE(this, d->ID3v1Location);
512 
513   if(d->APELocation >= 0) {
514     d->tag.set(APEIndex, new APE::Tag(this, d->APELocation));
515     d->APEOriginalSize = APETag()->footer()->completeTagSize();
516     d->APELocation = d->APELocation + APE::Footer::size() - d->APEOriginalSize;
517   }
518 
519   if(readProperties)
520     d->properties = new Properties(this);
521 
522   // Make sure that we have our default tag types available.
523 
524   ID3v2Tag(true);
525   ID3v1Tag(true);
526 }
527 
findID3v2()528 long MPEG::File::findID3v2()
529 {
530   if(!isValid())
531     return -1;
532 
533   // An ID3v2 tag or MPEG frame is most likely be at the beginning of the file.
534 
535   const ByteVector headerID = ID3v2::Header::fileIdentifier();
536 
537   seek(0);
538   if(readBlock(headerID.size()) == headerID)
539     return 0;
540 
541   const Header firstHeader(this, 0, true);
542   if(firstHeader.isValid())
543     return -1;
544 
545   // Look for an ID3v2 tag until reaching the first valid MPEG frame.
546 
547   ByteVector frameSyncBytes(2, '\0');
548   ByteVector tagHeaderBytes(3, '\0');
549   long position = 0;
550 
551   while(true) {
552     seek(position);
553     const ByteVector buffer = readBlock(bufferSize());
554     if(buffer.isEmpty())
555       return -1;
556 
557     for(unsigned int i = 0; i < buffer.size(); ++i) {
558       frameSyncBytes[0] = frameSyncBytes[1];
559       frameSyncBytes[1] = buffer[i];
560       if(isFrameSync(frameSyncBytes)) {
561         const Header header(this, position + i - 1, true);
562         if(header.isValid())
563           return -1;
564       }
565 
566       tagHeaderBytes[0] = tagHeaderBytes[1];
567       tagHeaderBytes[1] = tagHeaderBytes[2];
568       tagHeaderBytes[2] = buffer[i];
569       if(tagHeaderBytes == headerID)
570         return position + i - 2;
571     }
572 
573     position += bufferSize();
574   }
575 }
576