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