| Chris@148 | 1 | 
| Chris@148 | 2 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@148 | 3 | 
| Chris@148 | 4 /* | 
| Chris@148 | 5     Sonic Visualiser | 
| Chris@148 | 6     An audio file viewer and annotation editor. | 
| Chris@148 | 7     Centre for Digital Music, Queen Mary, University of London. | 
| Chris@148 | 8     This file copyright 2006 Chris Cannam. | 
| Chris@148 | 9 | 
| Chris@148 | 10     This program is free software; you can redistribute it and/or | 
| Chris@148 | 11     modify it under the terms of the GNU General Public License as | 
| Chris@148 | 12     published by the Free Software Foundation; either version 2 of the | 
| Chris@148 | 13     License, or (at your option) any later version.  See the file | 
| Chris@148 | 14     COPYING included with this distribution for more information. | 
| Chris@148 | 15 */ | 
| Chris@148 | 16 | 
| Chris@148 | 17 #ifdef HAVE_MAD | 
| Chris@148 | 18 | 
| Chris@148 | 19 #include "MP3FileReader.h" | 
| Chris@150 | 20 #include "system/System.h" | 
| Chris@148 | 21 | 
| Chris@148 | 22 #include <sys/types.h> | 
| Chris@148 | 23 #include <sys/stat.h> | 
| Chris@148 | 24 #include <fcntl.h> | 
| Chris@148 | 25 | 
| Chris@148 | 26 #include <iostream> | 
| Chris@148 | 27 | 
| Chris@271 | 28 #ifdef HAVE_ID3TAG | 
| Chris@271 | 29 #include <id3tag.h> | 
| Chris@271 | 30 #endif | 
| Chris@273 | 31 #define DEBUG_ID3TAG 1 | 
| Chris@271 | 32 | 
| Chris@148 | 33 #include <QApplication> | 
| Chris@186 | 34 #include <QFileInfo> | 
| Chris@148 | 35 #include <QProgressDialog> | 
| Chris@148 | 36 | 
| Chris@317 | 37 MP3FileReader::MP3FileReader(FileSource source, DecodeMode decodeMode, | 
| Chris@297 | 38                              CacheMode mode, size_t targetRate) : | 
| Chris@297 | 39     CodedAudioFileReader(mode, targetRate), | 
| Chris@316 | 40     m_source(source), | 
| Chris@316 | 41     m_path(source.getLocalFilename()), | 
| Chris@264 | 42     m_decodeThread(0) | 
| Chris@148 | 43 { | 
| Chris@148 | 44     m_channelCount = 0; | 
| Chris@297 | 45     m_fileRate = 0; | 
| Chris@148 | 46     m_fileSize = 0; | 
| Chris@148 | 47     m_bitrateNum = 0; | 
| Chris@148 | 48     m_bitrateDenom = 0; | 
| Chris@148 | 49     m_cancelled = false; | 
| Chris@265 | 50     m_completion = 0; | 
| Chris@263 | 51     m_done = false; | 
| Chris@263 | 52     m_progress = 0; | 
| Chris@148 | 53 | 
| Chris@148 | 54     struct stat stat; | 
| Chris@316 | 55     if (::stat(m_path.toLocal8Bit().data(), &stat) == -1 || stat.st_size == 0) { | 
| Chris@316 | 56 	m_error = QString("File %1 does not exist.").arg(m_path); | 
| Chris@148 | 57 	return; | 
| Chris@148 | 58     } | 
| Chris@148 | 59 | 
| Chris@148 | 60     m_fileSize = stat.st_size; | 
| Chris@148 | 61 | 
| Chris@229 | 62     int fd = -1; | 
| Chris@316 | 63     if ((fd = ::open(m_path.toLocal8Bit().data(), O_RDONLY | 
| Chris@231 | 64 #ifdef _WIN32 | 
| Chris@231 | 65                      | O_BINARY | 
| Chris@231 | 66 #endif | 
| Chris@231 | 67                      , 0)) < 0) { | 
| Chris@316 | 68 	m_error = QString("Failed to open file %1 for reading.").arg(m_path); | 
| Chris@148 | 69 	return; | 
| Chris@148 | 70     } | 
| Chris@148 | 71 | 
| Chris@263 | 72     m_filebuffer = 0; | 
| Chris@297 | 73     m_samplebuffer = 0; | 
| Chris@297 | 74     m_samplebuffersize = 0; | 
| Chris@148 | 75 | 
| Chris@148 | 76     try { | 
| Chris@263 | 77         m_filebuffer = new unsigned char[m_fileSize]; | 
| Chris@148 | 78     } catch (...) { | 
| Chris@290 | 79         m_error = QString("Out of memory"); | 
| Chris@148 | 80         ::close(fd); | 
| Chris@148 | 81 	return; | 
| Chris@148 | 82     } | 
| Chris@148 | 83 | 
| Chris@229 | 84     ssize_t sz = 0; | 
| Chris@229 | 85     size_t offset = 0; | 
| Chris@229 | 86     while (offset < m_fileSize) { | 
| Chris@263 | 87         sz = ::read(fd, m_filebuffer + offset, m_fileSize - offset); | 
| Chris@229 | 88         if (sz < 0) { | 
| Chris@290 | 89             m_error = QString("Read error for file %1 (after %2 bytes)") | 
| Chris@316 | 90                 .arg(m_path).arg(offset); | 
| Chris@263 | 91             delete[] m_filebuffer; | 
| Chris@229 | 92             ::close(fd); | 
| Chris@229 | 93             return; | 
| Chris@230 | 94         } else if (sz == 0) { | 
| Chris@231 | 95             std::cerr << QString("MP3FileReader::MP3FileReader: Warning: reached EOF after only %1 of %2 bytes") | 
| Chris@231 | 96                 .arg(offset).arg(m_fileSize).toStdString() << std::endl; | 
| Chris@231 | 97             m_fileSize = offset; | 
| Chris@230 | 98             break; | 
| Chris@229 | 99         } | 
| Chris@229 | 100         offset += sz; | 
| Chris@148 | 101     } | 
| Chris@148 | 102 | 
| Chris@148 | 103     ::close(fd); | 
| Chris@148 | 104 | 
| Chris@271 | 105     loadTags(); | 
| Chris@271 | 106 | 
| Chris@263 | 107     if (decodeMode == DecodeAtOnce) { | 
| Chris@263 | 108 | 
| Chris@327 | 109         if (dynamic_cast<QApplication *>(QCoreApplication::instance())) { | 
| Chris@327 | 110             m_progress = new QProgressDialog | 
| Chris@327 | 111                 (QObject::tr("Decoding %1...").arg(QFileInfo(m_path).fileName()), | 
| Chris@327 | 112                  QObject::tr("Stop"), 0, 100); | 
| Chris@327 | 113             m_progress->hide(); | 
| Chris@327 | 114         } | 
| Chris@148 | 115 | 
| Chris@263 | 116         if (!decode(m_filebuffer, m_fileSize)) { | 
| Chris@316 | 117             m_error = QString("Failed to decode file %1.").arg(m_path); | 
| Chris@263 | 118         } | 
| Chris@263 | 119 | 
| Chris@263 | 120         delete[] m_filebuffer; | 
| Chris@263 | 121         m_filebuffer = 0; | 
| Chris@148 | 122 | 
| Chris@263 | 123         if (isDecodeCacheInitialised()) finishDecodeCache(); | 
| Chris@263 | 124 | 
| Chris@148 | 125 	delete m_progress; | 
| Chris@148 | 126 	m_progress = 0; | 
| Chris@263 | 127 | 
| Chris@263 | 128     } else { | 
| Chris@263 | 129 | 
| Chris@263 | 130         m_decodeThread = new DecodeThread(this); | 
| Chris@263 | 131         m_decodeThread->start(); | 
| Chris@263 | 132 | 
| Chris@263 | 133         while (m_channelCount == 0 && !m_done) { | 
| Chris@263 | 134             usleep(10); | 
| Chris@263 | 135         } | 
| Chris@148 | 136     } | 
| Chris@148 | 137 } | 
| Chris@148 | 138 | 
| Chris@148 | 139 MP3FileReader::~MP3FileReader() | 
| Chris@148 | 140 { | 
| Chris@263 | 141     if (m_decodeThread) { | 
| Chris@265 | 142         m_cancelled = true; | 
| Chris@263 | 143         m_decodeThread->wait(); | 
| Chris@263 | 144         delete m_decodeThread; | 
| Chris@263 | 145     } | 
| Chris@148 | 146 } | 
| Chris@148 | 147 | 
| Chris@263 | 148 void | 
| Chris@271 | 149 MP3FileReader::loadTags() | 
| Chris@271 | 150 { | 
| Chris@271 | 151     m_title = ""; | 
| Chris@271 | 152 | 
| Chris@271 | 153 #ifdef HAVE_ID3TAG | 
| Chris@271 | 154 | 
| Chris@290 | 155     id3_file *file = id3_file_open(m_path.toLocal8Bit().data(), | 
| Chris@271 | 156                                    ID3_FILE_MODE_READONLY); | 
| Chris@271 | 157     if (!file) return; | 
| Chris@271 | 158 | 
| Chris@273 | 159     // We can do this a lot more elegantly, but we'll leave that for | 
| Chris@273 | 160     // when we implement support for more than just the one tag! | 
| Chris@273 | 161 | 
| Chris@271 | 162     id3_tag *tag = id3_file_tag(file); | 
| Chris@273 | 163     if (!tag) { | 
| Chris@273 | 164 #ifdef DEBUG_ID3TAG | 
| Chris@273 | 165         std::cerr << "MP3FileReader::loadTags: No ID3 tag found" << std::endl; | 
| Chris@273 | 166 #endif | 
| Chris@273 | 167         id3_file_close(file); | 
| Chris@273 | 168         return; | 
| Chris@271 | 169     } | 
| Chris@271 | 170 | 
| Chris@333 | 171     m_title = loadTag(tag, "TIT2"); // work title | 
| Chris@333 | 172     if (m_title == "") m_title = loadTag(tag, "TIT1"); | 
| Chris@273 | 173 | 
| Chris@333 | 174     m_maker = loadTag(tag, "TPE1"); // "lead artist" | 
| Chris@333 | 175     if (m_maker == "") m_maker = loadTag(tag, "TPE2"); | 
| Chris@273 | 176 | 
| Chris@271 | 177     id3_file_close(file); | 
| Chris@273 | 178 | 
| Chris@273 | 179 #else | 
| Chris@273 | 180 #ifdef DEBUG_ID3TAG | 
| Chris@273 | 181     std::cerr << "MP3FileReader::loadTags: ID3 tag support not compiled in" | 
| Chris@273 | 182               << std::endl; | 
| Chris@271 | 183 #endif | 
| Chris@273 | 184 #endif | 
| Chris@333 | 185 } | 
| Chris@273 | 186 | 
| Chris@333 | 187 QString | 
| Chris@333 | 188 MP3FileReader::loadTag(void *vtag, const char *name) | 
| Chris@333 | 189 { | 
| Chris@333 | 190 #ifdef HAVE_ID3TAG | 
| Chris@333 | 191     id3_tag *tag = (id3_tag *)vtag; | 
| Chris@333 | 192 | 
| Chris@333 | 193     id3_frame *frame = id3_tag_findframe(tag, name, 0); | 
| Chris@333 | 194     if (!frame) { | 
| Chris@333 | 195 #ifdef DEBUG_ID3TAG | 
| Chris@333 | 196         std::cerr << "MP3FileReader::loadTags: No \"" << name << "\" in ID3 tag" << std::endl; | 
| Chris@333 | 197 #endif | 
| Chris@333 | 198         return ""; | 
| Chris@333 | 199     } | 
| Chris@333 | 200 | 
| Chris@333 | 201     if (frame->nfields < 2) { | 
| Chris@333 | 202         std::cerr << "MP3FileReader::loadTags: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << std::endl; | 
| Chris@333 | 203         return ""; | 
| Chris@333 | 204     } | 
| Chris@333 | 205 | 
| Chris@333 | 206     unsigned int nstrings = id3_field_getnstrings(&frame->fields[1]); | 
| Chris@333 | 207     if (nstrings == 0) { | 
| Chris@333 | 208 #ifdef DEBUG_ID3TAG | 
| Chris@333 | 209         std::cerr << "MP3FileReader::loadTags: No data for \"" << name << "\" in ID3 tag" << std::endl; | 
| Chris@333 | 210 #endif | 
| Chris@333 | 211         return ""; | 
| Chris@333 | 212     } | 
| Chris@333 | 213 | 
| Chris@333 | 214     id3_ucs4_t const *ustr = id3_field_getstrings(&frame->fields[1], 0); | 
| Chris@333 | 215     if (!ustr) { | 
| Chris@333 | 216 #ifdef DEBUG_ID3TAG | 
| Chris@333 | 217         std::cerr << "MP3FileReader::loadTags: Invalid or absent data for \"" << name << "\" in ID3 tag" << std::endl; | 
| Chris@333 | 218 #endif | 
| Chris@333 | 219         return ""; | 
| Chris@333 | 220     } | 
| Chris@333 | 221 | 
| Chris@333 | 222     id3_utf8_t *u8str = id3_ucs4_utf8duplicate(ustr); | 
| Chris@333 | 223     if (!u8str) { | 
| Chris@333 | 224         std::cerr << "MP3FileReader::loadTags: ERROR: Internal error: Failed to convert UCS4 to UTF8 in ID3 title" << std::endl; | 
| Chris@333 | 225         return ""; | 
| Chris@333 | 226     } | 
| Chris@333 | 227 | 
| Chris@333 | 228     QString rv = QString::fromUtf8((const char *)u8str); | 
| Chris@333 | 229     free(u8str); | 
| Chris@334 | 230 | 
| Chris@334 | 231 #ifdef DEBUG_ID3TAG | 
| Chris@334 | 232 	std::cerr << "MP3FileReader::loadTags: tag \"" << name << "\" -> \"" | 
| Chris@334 | 233 	<< rv.toStdString() << "\"" << std::endl; | 
| Chris@334 | 234 #endif | 
| Chris@334 | 235 | 
| Chris@334 | 236 | 
| Chris@333 | 237     return rv; | 
| Chris@333 | 238 | 
| Chris@333 | 239 #else | 
| Chris@333 | 240     return ""; | 
| Chris@333 | 241 #endif | 
| Chris@271 | 242 } | 
| Chris@271 | 243 | 
| Chris@271 | 244 void | 
| Chris@263 | 245 MP3FileReader::DecodeThread::run() | 
| Chris@263 | 246 { | 
| Chris@263 | 247     if (!m_reader->decode(m_reader->m_filebuffer, m_reader->m_fileSize)) { | 
| Chris@290 | 248         m_reader->m_error = QString("Failed to decode file %1.").arg(m_reader->m_path); | 
| Chris@263 | 249     } | 
| Chris@263 | 250 | 
| Chris@263 | 251     delete[] m_reader->m_filebuffer; | 
| Chris@263 | 252     m_reader->m_filebuffer = 0; | 
| Chris@297 | 253 | 
| Chris@297 | 254     if (m_reader->m_samplebuffer) { | 
| Chris@297 | 255         for (size_t c = 0; c < m_reader->m_channelCount; ++c) { | 
| Chris@297 | 256             delete[] m_reader->m_samplebuffer[c]; | 
| Chris@297 | 257         } | 
| Chris@297 | 258         delete[] m_reader->m_samplebuffer; | 
| Chris@297 | 259         m_reader->m_samplebuffer = 0; | 
| Chris@297 | 260     } | 
| Chris@297 | 261 | 
| Chris@263 | 262     if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache(); | 
| Chris@263 | 263 | 
| Chris@263 | 264     m_reader->m_done = true; | 
| Chris@265 | 265     m_reader->m_completion = 100; | 
| Chris@297 | 266 | 
| Chris@297 | 267     m_reader->endSerialised(); | 
| Chris@263 | 268 } | 
| Chris@263 | 269 | 
| Chris@148 | 270 bool | 
| Chris@148 | 271 MP3FileReader::decode(void *mm, size_t sz) | 
| Chris@148 | 272 { | 
| Chris@148 | 273     DecoderData data; | 
| Chris@148 | 274     struct mad_decoder decoder; | 
| Chris@148 | 275 | 
| Chris@148 | 276     data.start = (unsigned char const *)mm; | 
| Chris@148 | 277     data.length = (unsigned long)sz; | 
| Chris@148 | 278     data.reader = this; | 
| Chris@148 | 279 | 
| Chris@148 | 280     mad_decoder_init(&decoder, &data, input, 0, 0, output, error, 0); | 
| Chris@148 | 281     mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC); | 
| Chris@148 | 282     mad_decoder_finish(&decoder); | 
| Chris@148 | 283 | 
| Chris@263 | 284     m_done = true; | 
| Chris@148 | 285     return true; | 
| Chris@148 | 286 } | 
| Chris@148 | 287 | 
| Chris@148 | 288 enum mad_flow | 
| Chris@148 | 289 MP3FileReader::input(void *dp, struct mad_stream *stream) | 
| Chris@148 | 290 { | 
| Chris@148 | 291     DecoderData *data = (DecoderData *)dp; | 
| Chris@148 | 292 | 
| Chris@148 | 293     if (!data->length) return MAD_FLOW_STOP; | 
| Chris@148 | 294     mad_stream_buffer(stream, data->start, data->length); | 
| Chris@148 | 295     data->length = 0; | 
| Chris@148 | 296 | 
| Chris@148 | 297     return MAD_FLOW_CONTINUE; | 
| Chris@148 | 298 } | 
| Chris@148 | 299 | 
| Chris@148 | 300 enum mad_flow | 
| Chris@148 | 301 MP3FileReader::output(void *dp, | 
| Chris@148 | 302 		      struct mad_header const *header, | 
| Chris@148 | 303 		      struct mad_pcm *pcm) | 
| Chris@148 | 304 { | 
| Chris@148 | 305     DecoderData *data = (DecoderData *)dp; | 
| Chris@148 | 306     return data->reader->accept(header, pcm); | 
| Chris@148 | 307 } | 
| Chris@148 | 308 | 
| Chris@148 | 309 enum mad_flow | 
| Chris@148 | 310 MP3FileReader::accept(struct mad_header const *header, | 
| Chris@148 | 311 		      struct mad_pcm *pcm) | 
| Chris@148 | 312 { | 
| Chris@148 | 313     int channels = pcm->channels; | 
| Chris@148 | 314     int frames = pcm->length; | 
| Chris@148 | 315 | 
| Chris@148 | 316     if (header) { | 
| Chris@148 | 317         m_bitrateNum += header->bitrate; | 
| Chris@148 | 318         m_bitrateDenom ++; | 
| Chris@148 | 319     } | 
| Chris@148 | 320 | 
| Chris@148 | 321     if (frames < 1) return MAD_FLOW_CONTINUE; | 
| Chris@148 | 322 | 
| Chris@148 | 323     if (m_channelCount == 0) { | 
| Chris@297 | 324 | 
| Chris@297 | 325         m_fileRate = pcm->samplerate; | 
| Chris@148 | 326         m_channelCount = channels; | 
| Chris@297 | 327 | 
| Chris@297 | 328         initialiseDecodeCache(); | 
| Chris@297 | 329 | 
| Chris@297 | 330         if (m_cacheMode == CacheInTemporaryFile) { | 
| Chris@297 | 331             m_completion = 1; | 
| Chris@297 | 332             std::cerr << "MP3FileReader::accept: channel count " << m_channelCount << ", file rate " << m_fileRate << ", about to start serialised section" << std::endl; | 
| Chris@297 | 333             startSerialised("MP3FileReader::Decode"); | 
| Chris@297 | 334         } | 
| Chris@148 | 335     } | 
| Chris@148 | 336 | 
| Chris@148 | 337     if (m_bitrateDenom > 0) { | 
| Chris@148 | 338         double bitrate = m_bitrateNum / m_bitrateDenom; | 
| Chris@148 | 339         double duration = double(m_fileSize * 8) / bitrate; | 
| Chris@148 | 340         double elapsed = double(m_frameCount) / m_sampleRate; | 
| Chris@148 | 341         double percent = ((elapsed * 100.0) / duration); | 
| Chris@265 | 342         int progress = int(percent); | 
| Chris@265 | 343         if (progress < 1) progress = 1; | 
| Chris@265 | 344         if (progress > 99) progress = 99; | 
| Chris@265 | 345         m_completion = progress; | 
| Chris@263 | 346         if (m_progress) { | 
| Chris@263 | 347             if (progress > m_progress->value()) { | 
| Chris@263 | 348                 m_progress->setValue(progress); | 
| Chris@263 | 349                 m_progress->show(); | 
| Chris@263 | 350                 m_progress->raise(); | 
| Chris@263 | 351                 qApp->processEvents(); | 
| Chris@263 | 352                 if (m_progress->wasCanceled()) { | 
| Chris@263 | 353                     m_cancelled = true; | 
| Chris@263 | 354                 } | 
| Chris@148 | 355             } | 
| Chris@148 | 356         } | 
| Chris@148 | 357     } | 
| Chris@148 | 358 | 
| Chris@148 | 359     if (m_cancelled) return MAD_FLOW_STOP; | 
| Chris@148 | 360 | 
| Chris@148 | 361     if (!isDecodeCacheInitialised()) { | 
| Chris@148 | 362         initialiseDecodeCache(); | 
| Chris@148 | 363     } | 
| Chris@148 | 364 | 
| Chris@297 | 365     if (m_samplebuffersize < frames) { | 
| Chris@297 | 366         if (!m_samplebuffer) { | 
| Chris@297 | 367             m_samplebuffer = new float *[channels]; | 
| Chris@297 | 368             for (int c = 0; c < channels; ++c) { | 
| Chris@297 | 369                 m_samplebuffer[c] = 0; | 
| Chris@297 | 370             } | 
| Chris@297 | 371         } | 
| Chris@297 | 372         for (int c = 0; c < channels; ++c) { | 
| Chris@297 | 373             delete[] m_samplebuffer[c]; | 
| Chris@297 | 374             m_samplebuffer[c] = new float[frames]; | 
| Chris@297 | 375         } | 
| Chris@297 | 376         m_samplebuffersize = frames; | 
| Chris@297 | 377     } | 
| Chris@148 | 378 | 
| Chris@297 | 379     int activeChannels = int(sizeof(pcm->samples) / sizeof(pcm->samples[0])); | 
| Chris@297 | 380 | 
| Chris@297 | 381     for (int ch = 0; ch < channels; ++ch) { | 
| Chris@297 | 382 | 
| Chris@297 | 383         for (int i = 0; i < frames; ++i) { | 
| Chris@297 | 384 | 
| Chris@148 | 385 	    mad_fixed_t sample = 0; | 
| Chris@297 | 386 	    if (ch < activeChannels) { | 
| Chris@148 | 387 		sample = pcm->samples[ch][i]; | 
| Chris@148 | 388 	    } | 
| Chris@148 | 389 	    float fsample = float(sample) / float(MAD_F_ONE); | 
| Chris@297 | 390 | 
| Chris@297 | 391             m_samplebuffer[ch][i] = fsample; | 
| Chris@148 | 392 	} | 
| Chris@148 | 393     } | 
| Chris@148 | 394 | 
| Chris@297 | 395     addSamplesToDecodeCache(m_samplebuffer, frames); | 
| Chris@148 | 396 | 
| Chris@148 | 397     return MAD_FLOW_CONTINUE; | 
| Chris@148 | 398 } | 
| Chris@148 | 399 | 
| Chris@148 | 400 enum mad_flow | 
| Chris@148 | 401 MP3FileReader::error(void *dp, | 
| Chris@148 | 402 		     struct mad_stream *stream, | 
| Chris@148 | 403 		     struct mad_frame *) | 
| Chris@148 | 404 { | 
| Chris@148 | 405     DecoderData *data = (DecoderData *)dp; | 
| Chris@148 | 406 | 
| Chris@148 | 407     fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %u\n", | 
| Chris@148 | 408 	    stream->error, mad_stream_errorstr(stream), | 
| Chris@148 | 409 	    stream->this_frame - data->start); | 
| Chris@148 | 410 | 
| Chris@148 | 411     return MAD_FLOW_CONTINUE; | 
| Chris@148 | 412 } | 
| Chris@148 | 413 | 
| Chris@157 | 414 void | 
| Chris@290 | 415 MP3FileReader::getSupportedExtensions(std::set<QString> &extensions) | 
| Chris@157 | 416 { | 
| Chris@157 | 417     extensions.insert("mp3"); | 
| Chris@157 | 418 } | 
| Chris@157 | 419 | 
| Chris@316 | 420 bool | 
| Chris@316 | 421 MP3FileReader::supportsExtension(QString extension) | 
| Chris@316 | 422 { | 
| Chris@316 | 423     std::set<QString> extensions; | 
| Chris@316 | 424     getSupportedExtensions(extensions); | 
| Chris@316 | 425     return (extensions.find(extension.toLower()) != extensions.end()); | 
| Chris@316 | 426 } | 
| Chris@316 | 427 | 
| Chris@316 | 428 bool | 
| Chris@316 | 429 MP3FileReader::supportsContentType(QString type) | 
| Chris@316 | 430 { | 
| Chris@316 | 431     return (type == "audio/mpeg"); | 
| Chris@316 | 432 } | 
| Chris@316 | 433 | 
| Chris@316 | 434 bool | 
| Chris@317 | 435 MP3FileReader::supports(FileSource &source) | 
| Chris@316 | 436 { | 
| Chris@316 | 437     return (supportsExtension(source.getExtension()) || | 
| Chris@316 | 438             supportsContentType(source.getContentType())); | 
| Chris@316 | 439 } | 
| Chris@316 | 440 | 
| Chris@316 | 441 | 
| Chris@148 | 442 #endif |