1 /***************************************************************************
2     copyright            : (C) 2002 - 2008 by Scott Wheeler
3     email                : wheeler@kde.org
4 
5     copyright            : (C) 2010 by Alex Novichkov
6     email                : novichko@atnet.ru
7                            (added APE file support)
8  ***************************************************************************/
9 
10 /***************************************************************************
11  *   This library is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU Lesser General Public License version   *
13  *   2.1 as published by the Free Software Foundation.                     *
14  *                                                                         *
15  *   This library is distributed in the hope that it will be useful, but   *
16  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
17  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
18  *   Lesser General Public License for more details.                       *
19  *                                                                         *
20  *   You should have received a copy of the GNU Lesser General Public      *
21  *   License along with this library; if not, write to the Free Software   *
22  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA         *
23  *   02110-1301  USA                                                       *
24  *                                                                         *
25  *   Alternatively, this file is available under the Mozilla Public        *
26  *   License Version 1.1.  You may obtain a copy of the License at         *
27  *   http://www.mozilla.org/MPL/                                           *
28  ***************************************************************************/
29 
30 #include <tfile.h>
31 #include <tfilestream.h>
32 #include <tstring.h>
33 #include <tdebug.h>
34 #include <trefcounter.h>
35 
36 #include "fileref.h"
37 #include "asffile.h"
38 #include "mpegfile.h"
39 #include "vorbisfile.h"
40 #include "flacfile.h"
41 #include "oggflacfile.h"
42 #include "mpcfile.h"
43 #include "mp4file.h"
44 #include "wavpackfile.h"
45 #include "speexfile.h"
46 #include "opusfile.h"
47 #include "trueaudiofile.h"
48 #include "aifffile.h"
49 #include "wavfile.h"
50 #include "apefile.h"
51 #include "modfile.h"
52 #include "s3mfile.h"
53 #include "itfile.h"
54 #include "xmfile.h"
55 #include "dsffile.h"
56 #include "dsdifffile.h"
57 
58 using namespace TagLib;
59 
60 namespace
61 {
62   typedef List<const FileRef::FileTypeResolver *> ResolverList;
63   ResolverList fileTypeResolvers;
64 
65   // Detect the file type by user-defined resolvers.
66 
detectByResolvers(FileName fileName,bool readAudioProperties,AudioProperties::ReadStyle audioPropertiesStyle)67   File *detectByResolvers(FileName fileName, bool readAudioProperties,
68                           AudioProperties::ReadStyle audioPropertiesStyle)
69   {
70     ResolverList::ConstIterator it = fileTypeResolvers.begin();
71     for(; it != fileTypeResolvers.end(); ++it) {
72       File *file = (*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle);
73       if(file)
74         return file;
75     }
76 
77     return 0;
78   }
79 
80   // Detect the file type based on the file extension.
81 
detectByExtension(IOStream * stream,bool readAudioProperties,AudioProperties::ReadStyle audioPropertiesStyle)82   File* detectByExtension(IOStream *stream, bool readAudioProperties,
83                           AudioProperties::ReadStyle audioPropertiesStyle)
84   {
85 #ifdef _WIN32
86     const String s = stream->name().toString();
87 #else
88     const String s(stream->name());
89 #endif
90 
91     String ext;
92     const int pos = s.rfind(".");
93     if(pos != -1)
94       ext = s.substr(pos + 1).upper();
95 
96     // If this list is updated, the method defaultFileExtensions() should also be
97     // updated.  However at some point that list should be created at the same time
98     // that a default file type resolver is created.
99 
100     if(ext.isEmpty())
101       return 0;
102 
103     // .oga can be any audio in the Ogg container. So leave it to content-based detection.
104 
105     if(ext == "MP3")
106       return new MPEG::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
107     if(ext == "OGG")
108       return new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle);
109     if(ext == "FLAC")
110       return new FLAC::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
111     if(ext == "MPC")
112       return new MPC::File(stream, readAudioProperties, audioPropertiesStyle);
113     if(ext == "WV")
114       return new WavPack::File(stream, readAudioProperties, audioPropertiesStyle);
115     if(ext == "SPX")
116       return new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle);
117     if(ext == "OPUS")
118       return new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle);
119     if(ext == "TTA")
120       return new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle);
121     if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V")
122       return new MP4::File(stream, readAudioProperties, audioPropertiesStyle);
123     if(ext == "WMA" || ext == "ASF")
124       return new ASF::File(stream, readAudioProperties, audioPropertiesStyle);
125     if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC")
126       return new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle);
127     if(ext == "WAV")
128       return new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
129     if(ext == "APE")
130       return new APE::File(stream, readAudioProperties, audioPropertiesStyle);
131     // module, nst and wow are possible but uncommon extensions
132     if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
133       return new Mod::File(stream, readAudioProperties, audioPropertiesStyle);
134     if(ext == "S3M")
135       return new S3M::File(stream, readAudioProperties, audioPropertiesStyle);
136     if(ext == "IT")
137       return new IT::File(stream, readAudioProperties, audioPropertiesStyle);
138     if(ext == "XM")
139       return new XM::File(stream, readAudioProperties, audioPropertiesStyle);
140     if(ext == "DFF" || ext == "DSDIFF")
141       return new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
142     if(ext == "DSF")
143       return new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
144 
145     return 0;
146   }
147 
148   // Detect the file type based on the actual content of the stream.
149 
detectByContent(IOStream * stream,bool readAudioProperties,AudioProperties::ReadStyle audioPropertiesStyle)150   File *detectByContent(IOStream *stream, bool readAudioProperties,
151                         AudioProperties::ReadStyle audioPropertiesStyle)
152   {
153     File *file = 0;
154 
155     if(MPEG::File::isSupported(stream))
156       file = new MPEG::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
157     else if(Ogg::Vorbis::File::isSupported(stream))
158       file = new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle);
159     else if(Ogg::FLAC::File::isSupported(stream))
160       file = new Ogg::FLAC::File(stream, readAudioProperties, audioPropertiesStyle);
161     else if(FLAC::File::isSupported(stream))
162       file = new FLAC::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
163     else if(MPC::File::isSupported(stream))
164       file = new MPC::File(stream, readAudioProperties, audioPropertiesStyle);
165     else if(WavPack::File::isSupported(stream))
166       file = new WavPack::File(stream, readAudioProperties, audioPropertiesStyle);
167     else if(Ogg::Speex::File::isSupported(stream))
168       file = new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle);
169     else if(Ogg::Opus::File::isSupported(stream))
170       file = new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle);
171     else if(TrueAudio::File::isSupported(stream))
172       file = new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle);
173     else if(MP4::File::isSupported(stream))
174       file = new MP4::File(stream, readAudioProperties, audioPropertiesStyle);
175     else if(ASF::File::isSupported(stream))
176       file = new ASF::File(stream, readAudioProperties, audioPropertiesStyle);
177     else if(RIFF::AIFF::File::isSupported(stream))
178       file = new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle);
179     else if(RIFF::WAV::File::isSupported(stream))
180       file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
181     else if(APE::File::isSupported(stream))
182       file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
183     else if(DSDIFF::File::isSupported(stream))
184       file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
185     else if(DSF::File::isSupported(stream))
186       file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
187 
188     // isSupported() only does a quick check, so double check the file here.
189 
190     if(file) {
191       if(file->isValid())
192         return file;
193       else
194         delete file;
195     }
196 
197     return 0;
198   }
199 
200   // Internal function that supports FileRef::create().
201   // This looks redundant, but necessary in order not to change the previous
202   // behavior of FileRef::create().
203 
createInternal(FileName fileName,bool readAudioProperties,AudioProperties::ReadStyle audioPropertiesStyle)204   File* createInternal(FileName fileName, bool readAudioProperties,
205                        AudioProperties::ReadStyle audioPropertiesStyle)
206   {
207     File *file = detectByResolvers(fileName, readAudioProperties, audioPropertiesStyle);
208     if(file)
209       return file;
210 
211 #ifdef _WIN32
212     const String s = fileName.toString();
213 #else
214     const String s(fileName);
215 #endif
216 
217     String ext;
218     const int pos = s.rfind(".");
219     if(pos != -1)
220       ext = s.substr(pos + 1).upper();
221 
222     if(ext.isEmpty())
223       return 0;
224 
225     if(ext == "MP3")
226       return new MPEG::File(fileName, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
227     if(ext == "OGG")
228       return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle);
229     if(ext == "OGA") {
230       /* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */
231       File *file = new Ogg::FLAC::File(fileName, readAudioProperties, audioPropertiesStyle);
232       if(file->isValid())
233         return file;
234       delete file;
235       return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle);
236     }
237     if(ext == "FLAC")
238       return new FLAC::File(fileName, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
239     if(ext == "MPC")
240       return new MPC::File(fileName, readAudioProperties, audioPropertiesStyle);
241     if(ext == "WV")
242       return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle);
243     if(ext == "SPX")
244       return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle);
245     if(ext == "OPUS")
246       return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle);
247     if(ext == "TTA")
248       return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle);
249     if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V")
250       return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle);
251     if(ext == "WMA" || ext == "ASF")
252       return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle);
253     if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC")
254       return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle);
255     if(ext == "WAV")
256       return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle);
257     if(ext == "APE")
258       return new APE::File(fileName, readAudioProperties, audioPropertiesStyle);
259     // module, nst and wow are possible but uncommon extensions
260     if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
261       return new Mod::File(fileName, readAudioProperties, audioPropertiesStyle);
262     if(ext == "S3M")
263       return new S3M::File(fileName, readAudioProperties, audioPropertiesStyle);
264     if(ext == "IT")
265       return new IT::File(fileName, readAudioProperties, audioPropertiesStyle);
266     if(ext == "XM")
267       return new XM::File(fileName, readAudioProperties, audioPropertiesStyle);
268     if(ext == "DFF" || ext == "DSDIFF")
269       return new DSDIFF::File(fileName, readAudioProperties, audioPropertiesStyle);
270     if(ext == "DSF")
271       return new DSF::File(fileName, readAudioProperties, audioPropertiesStyle);
272 
273     return 0;
274   }
275 }
276 
277 class FileRef::FileRefPrivate : public RefCounter
278 {
279 public:
FileRefPrivate()280   FileRefPrivate() :
281     RefCounter(),
282     file(0),
283     stream(0) {}
284 
~FileRefPrivate()285   ~FileRefPrivate() {
286     delete file;
287     delete stream;
288   }
289 
290   File     *file;
291   IOStream *stream;
292 };
293 
294 ////////////////////////////////////////////////////////////////////////////////
295 // public members
296 ////////////////////////////////////////////////////////////////////////////////
297 
FileRef()298 FileRef::FileRef() :
299   d(new FileRefPrivate())
300 {
301 }
302 
FileRef(FileName fileName,bool readAudioProperties,AudioProperties::ReadStyle audioPropertiesStyle)303 FileRef::FileRef(FileName fileName, bool readAudioProperties,
304                  AudioProperties::ReadStyle audioPropertiesStyle) :
305   d(new FileRefPrivate())
306 {
307   parse(fileName, readAudioProperties, audioPropertiesStyle);
308 }
309 
FileRef(IOStream * stream,bool readAudioProperties,AudioProperties::ReadStyle audioPropertiesStyle)310 FileRef::FileRef(IOStream* stream, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) :
311   d(new FileRefPrivate())
312 {
313   parse(stream, readAudioProperties, audioPropertiesStyle);
314 }
315 
FileRef(File * file)316 FileRef::FileRef(File *file) :
317   d(new FileRefPrivate())
318 {
319   d->file = file;
320 }
321 
FileRef(const FileRef & ref)322 FileRef::FileRef(const FileRef &ref) :
323   d(ref.d)
324 {
325   d->ref();
326 }
327 
~FileRef()328 FileRef::~FileRef()
329 {
330   if(d->deref())
331     delete d;
332 }
333 
tag() const334 Tag *FileRef::tag() const
335 {
336   if(isNull()) {
337     debug("FileRef::tag() - Called without a valid file.");
338     return 0;
339   }
340   return d->file->tag();
341 }
342 
audioProperties() const343 AudioProperties *FileRef::audioProperties() const
344 {
345   if(isNull()) {
346     debug("FileRef::audioProperties() - Called without a valid file.");
347     return 0;
348   }
349   return d->file->audioProperties();
350 }
351 
file() const352 File *FileRef::file() const
353 {
354   return d->file;
355 }
356 
save()357 bool FileRef::save()
358 {
359   if(isNull()) {
360     debug("FileRef::save() - Called without a valid file.");
361     return false;
362   }
363   return d->file->save();
364 }
365 
addFileTypeResolver(const FileRef::FileTypeResolver * resolver)366 const FileRef::FileTypeResolver *FileRef::addFileTypeResolver(const FileRef::FileTypeResolver *resolver) // static
367 {
368   fileTypeResolvers.prepend(resolver);
369   return resolver;
370 }
371 
defaultFileExtensions()372 StringList FileRef::defaultFileExtensions()
373 {
374   StringList l;
375 
376   l.append("ogg");
377   l.append("flac");
378   l.append("oga");
379   l.append("mp3");
380   l.append("mpc");
381   l.append("wv");
382   l.append("spx");
383   l.append("tta");
384   l.append("m4a");
385   l.append("m4r");
386   l.append("m4b");
387   l.append("m4p");
388   l.append("3g2");
389   l.append("mp4");
390   l.append("m4v");
391   l.append("wma");
392   l.append("asf");
393   l.append("aif");
394   l.append("aiff");
395   l.append("wav");
396   l.append("ape");
397   l.append("mod");
398   l.append("module"); // alias for "mod"
399   l.append("nst"); // alias for "mod"
400   l.append("wow"); // alias for "mod"
401   l.append("s3m");
402   l.append("it");
403   l.append("xm");
404   l.append("dsf");
405   l.append("dff");
406   l.append("dsdiff"); // alias for "dff"
407 
408   return l;
409 }
410 
isNull() const411 bool FileRef::isNull() const
412 {
413   return (!d->file || !d->file->isValid());
414 }
415 
operator =(const FileRef & ref)416 FileRef &FileRef::operator=(const FileRef &ref)
417 {
418   FileRef(ref).swap(*this);
419   return *this;
420 }
421 
swap(FileRef & ref)422 void FileRef::swap(FileRef &ref)
423 {
424   using std::swap;
425 
426   swap(d, ref.d);
427 }
428 
operator ==(const FileRef & ref) const429 bool FileRef::operator==(const FileRef &ref) const
430 {
431   return (ref.d->file == d->file);
432 }
433 
operator !=(const FileRef & ref) const434 bool FileRef::operator!=(const FileRef &ref) const
435 {
436   return (ref.d->file != d->file);
437 }
438 
create(FileName fileName,bool readAudioProperties,AudioProperties::ReadStyle audioPropertiesStyle)439 File *FileRef::create(FileName fileName, bool readAudioProperties,
440                       AudioProperties::ReadStyle audioPropertiesStyle) // static
441 {
442   return createInternal(fileName, readAudioProperties, audioPropertiesStyle);
443 }
444 
445 ////////////////////////////////////////////////////////////////////////////////
446 // private members
447 ////////////////////////////////////////////////////////////////////////////////
448 
parse(FileName fileName,bool readAudioProperties,AudioProperties::ReadStyle audioPropertiesStyle)449 void FileRef::parse(FileName fileName, bool readAudioProperties,
450                     AudioProperties::ReadStyle audioPropertiesStyle)
451 {
452   // Try user-defined resolvers.
453 
454   d->file = detectByResolvers(fileName, readAudioProperties, audioPropertiesStyle);
455   if(d->file)
456     return;
457 
458   // Try to resolve file types based on the file extension.
459 
460   d->stream = new FileStream(fileName);
461   d->file = detectByExtension(d->stream, readAudioProperties, audioPropertiesStyle);
462   if(d->file)
463     return;
464 
465   // At last, try to resolve file types based on the actual content.
466 
467   d->file = detectByContent(d->stream, readAudioProperties, audioPropertiesStyle);
468   if(d->file)
469     return;
470 
471   // Stream have to be closed here if failed to resolve file types.
472 
473   delete d->stream;
474   d->stream = 0;
475 }
476 
parse(IOStream * stream,bool readAudioProperties,AudioProperties::ReadStyle audioPropertiesStyle)477 void FileRef::parse(IOStream *stream, bool readAudioProperties,
478                     AudioProperties::ReadStyle audioPropertiesStyle)
479 {
480   // User-defined resolvers won't work with a stream.
481 
482   // Try to resolve file types based on the file extension.
483 
484   d->file = detectByExtension(stream, readAudioProperties, audioPropertiesStyle);
485   if(d->file)
486     return;
487 
488   // At last, try to resolve file types based on the actual content of the file.
489 
490   d->file = detectByContent(stream, readAudioProperties, audioPropertiesStyle);
491 }
492