File indexing completed on 2024-05-12 05:10:08
0001 /*************************************************************************** 0002 Copyright (C) 2004-2009 Robby Stephenson <robby@periapsis.org> 0003 ***************************************************************************/ 0004 0005 /*************************************************************************** 0006 * * 0007 * This program is free software; you can redistribute it and/or * 0008 * modify it under the terms of the GNU General Public License as * 0009 * published by the Free Software Foundation; either version 2 of * 0010 * the License or (at your option) version 3 or any later version * 0011 * accepted by the membership of KDE e.V. (or its successor approved * 0012 * by the membership of KDE e.V.), which shall act as a proxy * 0013 * defined in Section 14 of version 3 of the license. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0022 * * 0023 ***************************************************************************/ 0024 0025 #include <config.h> 0026 0027 #include "audiofileimporter.h" 0028 #include "../collections/musiccollection.h" 0029 #include "../entry.h" 0030 #include "../field.h" 0031 #include "../fieldformat.h" 0032 #include "../images/imagefactory.h" 0033 #include "../utils/string_utils.h" 0034 #include "../utils/tellico_utils.h" 0035 #include "../progressmanager.h" 0036 #include "../tellico_debug.h" 0037 0038 #ifdef HAVE_TAGLIB 0039 #include <fileref.h> 0040 #include <tag.h> 0041 #include <id3v2tag.h> 0042 #include <mpegfile.h> 0043 #include <vorbisfile.h> 0044 #include <vorbisproperties.h> 0045 #include <flacfile.h> 0046 #include <audioproperties.h> 0047 #include <tpropertymap.h> 0048 #endif 0049 0050 #include <KLocalizedString> 0051 0052 #include <QLabel> 0053 #include <QGroupBox> 0054 #include <QCheckBox> 0055 #include <QDir> 0056 #include <QTextStream> 0057 #include <QVBoxLayout> 0058 #include <QApplication> 0059 0060 #ifdef HAVE_TAGLIB 0061 namespace { 0062 bool hasValue(const TagLib::PropertyMap& pmap, const char* key) { 0063 const TagLib::String keyString(key); 0064 return pmap.contains(keyString) && !pmap[keyString].isEmpty(); 0065 } 0066 0067 QString tagValue(const TagLib::PropertyMap& pmap, const char* key) { 0068 const TagLib::String keyString(key); 0069 return (pmap.contains(keyString) && !pmap[keyString].isEmpty()) ? 0070 TStringToQString(pmap[keyString].front()).trimmed() : 0071 QString(); 0072 } 0073 } 0074 #endif 0075 0076 using Tellico::Import::AudioFileImporter; 0077 0078 AudioFileImporter::AudioFileImporter(const QUrl& url_) : Tellico::Import::Importer(url_) 0079 , m_widget(nullptr) 0080 , m_recursive(nullptr) 0081 , m_addFilePath(nullptr) 0082 , m_addBitrate(nullptr) 0083 , m_cancelled(false) 0084 , m_audioOptions(0) { 0085 } 0086 0087 bool AudioFileImporter::canImport(int type) const { 0088 return type == Data::Collection::Album; 0089 } 0090 0091 void AudioFileImporter::setRecursive(bool recursive_) { 0092 if(recursive_) { 0093 m_audioOptions |= Recursive; 0094 } else { 0095 m_audioOptions &= ~Recursive; 0096 } 0097 } 0098 0099 void AudioFileImporter::setAddFilePath(bool addFilePath_) { 0100 if(addFilePath_) { 0101 m_audioOptions |= AddFilePath; 0102 } else { 0103 m_audioOptions &= ~AddFilePath; 0104 } 0105 } 0106 0107 void AudioFileImporter::setAddBitrate(bool addBitrate_) { 0108 if(addBitrate_) { 0109 m_audioOptions |= AddBitrate; 0110 } else { 0111 m_audioOptions &= ~AddBitrate; 0112 } 0113 } 0114 0115 Tellico::Data::CollPtr AudioFileImporter::collection() { 0116 #ifndef HAVE_TAGLIB 0117 return Data::CollPtr(); 0118 #else 0119 0120 if(m_coll) { 0121 return m_coll; 0122 } 0123 0124 if(m_recursive) setRecursive(m_recursive->isChecked()); 0125 if(m_addFilePath) setAddFilePath(m_addFilePath->isChecked()); 0126 if(m_addBitrate) setAddBitrate(m_addBitrate->isChecked()); 0127 0128 ProgressItem& item = ProgressManager::self()->newProgressItem(this, i18n("Scanning audio files..."), true); 0129 item.setTotalSteps(100); 0130 connect(&item, &Tellico::ProgressItem::signalCancelled, this, &Tellico::Import::AudioFileImporter::slotCancel); 0131 ProgressItem::Done done(this); 0132 const bool showProgress = options() & ImportProgress; 0133 0134 // TODO: allow remote audio file importing 0135 QStringList files; 0136 QFileInfo urlInfo(url().toLocalFile()); 0137 if(urlInfo.isDir()) { 0138 // url is a directory 0139 QStringList dirs = QStringList() << url().toLocalFile(); 0140 if(m_audioOptions & Recursive) { 0141 dirs += Tellico::findAllSubDirs(dirs[0]); 0142 } 0143 0144 // grab every file in the dirs list 0145 for(QStringList::ConstIterator it = dirs.constBegin(); !m_cancelled && it != dirs.constEnd(); ++it) { 0146 if((*it).isEmpty()) { 0147 continue; 0148 } 0149 0150 QDir dir(*it); 0151 dir.setFilter(QDir::Files | QDir::Readable | QDir::Hidden); // hidden since I want directory files 0152 const QStringList list = dir.entryList(); 0153 for(QStringList::ConstIterator it2 = list.begin(); it2 != list.end(); ++it2) { 0154 files += dir.absoluteFilePath(*it2); 0155 } 0156 } 0157 } else { 0158 // single file import 0159 // TODO: allow for multiple file list in urls 0160 files += url().toLocalFile(); 0161 } 0162 0163 if(m_cancelled) { 0164 return Data::CollPtr(); 0165 } 0166 0167 // myLog() << "audiofileimporter: total number of files:" << files.count(); 0168 item.setTotalSteps(files.count()); 0169 0170 const QString title = QStringLiteral("title"); 0171 const QString artist = QStringLiteral("artist"); 0172 const QString year = QStringLiteral("year"); 0173 const QString label = QStringLiteral("label"); 0174 const QString genre = QStringLiteral("genre"); 0175 const QString track = QStringLiteral("track"); 0176 const QString comments = QStringLiteral("comments"); 0177 const QString file = QStringLiteral("file"); 0178 0179 m_coll = new Data::MusicCollection(true); 0180 0181 const bool addFile = m_audioOptions & AddFilePath; 0182 const bool addBitrate = m_audioOptions & AddBitrate; 0183 0184 Data::FieldPtr f; 0185 if(addFile) { 0186 f = m_coll->fieldByName(file); 0187 if(!f) { 0188 f = new Data::Field(file, i18n("Files"), Data::Field::Table); 0189 m_coll->addField(f); 0190 } 0191 f->setProperty(QStringLiteral("column1"), i18n("Files")); 0192 if(addBitrate) { 0193 f->setProperty(QStringLiteral("columns"), QStringLiteral("2")); 0194 f->setProperty(QStringLiteral("column2"), i18n("Bitrate")); 0195 } else { 0196 f->setProperty(QStringLiteral("columns"), QStringLiteral("1")); 0197 } 0198 } 0199 0200 QHash<QString, Data::EntryPtr> albumMap; 0201 QHash<QString, QString> directoryAlbumHash; 0202 Data::EntryList entriesToAdd; 0203 0204 QStringList directoryFiles; 0205 const uint stepSize = qMax(1, files.count() / 100); 0206 0207 bool changeTrackTitle = true; 0208 uint j = 0; 0209 for(QStringList::ConstIterator it = files.constBegin(); !m_cancelled && it != files.constEnd(); ++it, ++j) { 0210 TagLib::FileRef f(QFile::encodeName(*it).data()); 0211 if(f.isNull() || !f.tag() || !f.file()) { 0212 if((*it).endsWith(QLatin1String("/.directory"))) { 0213 directoryFiles += *it; 0214 if(showProgress) ProgressManager::self()->setTotalSteps(this, files.count() + directoryFiles.count()); 0215 } 0216 continue; 0217 } 0218 0219 TagLib::PropertyMap pmap = f.file()->properties(); 0220 pmap.removeEmpty(); 0221 TagLib::Tag* tag = f.tag(); 0222 QString album = TStringToQString(tag->album()).trimmed(); 0223 if(album.isEmpty()) { 0224 // can't do anything since tellico entries are by album 0225 myWarning() << "Skipping: no album listed for " << *it; 0226 continue; 0227 } 0228 int disc = discNumber(f); 0229 if(disc > 1 && !m_coll->hasField(QStringLiteral("track%1").arg(disc))) { 0230 Data::FieldPtr f2(new Data::Field(QStringLiteral("track%1").arg(disc), 0231 i18n("Tracks (Disc %1)", disc), 0232 Data::Field::Table)); 0233 f2->setFormatType(FieldFormat::FormatTitle); 0234 f2->setProperty(QStringLiteral("columns"), QStringLiteral("3")); 0235 f2->setProperty(QStringLiteral("column1"), i18n("Title")); 0236 f2->setProperty(QStringLiteral("column2"), i18n("Artist")); 0237 f2->setProperty(QStringLiteral("column3"), i18n("Length")); 0238 m_coll->addField(f2); 0239 if(changeTrackTitle) { 0240 Data::FieldPtr newTrack(new Data::Field(*m_coll->fieldByName(track))); 0241 newTrack->setTitle(i18n("Tracks (Disc %1)", 1)); 0242 m_coll->modifyField(newTrack); 0243 changeTrackTitle = false; 0244 } 0245 } 0246 bool exists = true; 0247 Data::EntryPtr entry; 0248 /* 0249 Let's assume an album already exists (has already been imported) if an 0250 album entry with same Album Title and Album Artist is found; indeed, 0251 multiple albums can have the same title (but from different artists), 0252 but this is very unlikely the same artist release multiple albums with 0253 the same title. Therefore, we propose to make an album entry ID as follows: 0254 "<album title>::<album artist>" if album artist info is available, 0255 "<album title>" if not. 0256 */ 0257 QString albumKey = album.toLower(); 0258 /* 0259 For MP3 files, get the Album Artist from the ID3v2 TPE2 frame. 0260 See http://www.id3.org/id3v2.4.0-frames for a description of this frame. 0261 Although this is not standard in ID3, using a specific frame for album 0262 artist is a solution to the problem of tagging albums that feature 0263 various artists but still have an identified Album Artist, such as 0264 Remix and DJ albums. Example: 0265 Album title: Some Title; Album artist: Some DJ; 0266 Track 1: Some Track Title - Some Artist(s); 0267 Track 2: Some Other Track Title - Some Other Artist(s), etc. 0268 We read the Album Artist from the TPE2 frame to be compatible with 0269 Amarok as the most popular music player by KDE, but also Apple (iTunes), 0270 Microsoft (Windows Media Player) and others which use this frame to 0271 read/write the album artist too. 0272 See Amarok source file src/collectionscanner/CollectionScanner.cpp, 0273 method AttributeHash CollectionScanner::readTags(...). 0274 */ 0275 // TODO: find another way for non-MP3 files 0276 QString albumArtist; 0277 /* As mpeg implementation on TagLib uses a Tag class that's not defined on the headers, 0278 we have to cast the files, not the tags! 0279 */ 0280 TagLib::MPEG::File* mpegFile = dynamic_cast<TagLib::MPEG::File*>(f.file()); 0281 if(mpegFile && mpegFile->ID3v2Tag() && !mpegFile->ID3v2Tag()->frameListMap()["TPE2"].isEmpty()) { 0282 albumArtist = TStringToQString(mpegFile->ID3v2Tag()->frameListMap()["TPE2"].front()->toString()).trimmed(); 0283 } 0284 if(albumArtist.isEmpty()) { 0285 albumArtist = tagValue(pmap, "ALBUMARTIST"); 0286 } 0287 if(albumArtist.isEmpty()) { 0288 albumArtist = tagValue(pmap, "ALBUMARTISTSORT"); 0289 } 0290 if(!albumArtist.isEmpty()) { 0291 albumKey += FieldFormat::columnDelimiterString() + albumArtist.toLower(); 0292 } 0293 0294 entry = albumMap[albumKey]; 0295 if(!entry) { 0296 entry = Data::EntryPtr(new Data::Entry(m_coll)); 0297 albumMap.insert(albumKey, entry); 0298 exists = false; 0299 } 0300 // album entries use the album name as the title 0301 entry->setField(title, album); 0302 QString a = TStringToQString(tag->artist()).trimmed(); 0303 if(a.isEmpty()) { 0304 a = tagValue(pmap, "ArtistSort"); 0305 } 0306 if(a.isEmpty()) { 0307 a = tagValue(pmap, "Artists"); 0308 } 0309 // If no album artist identified, we use track artist as album artist, or "(Various)" if tracks have various artists. 0310 if(!albumArtist.isEmpty()) { 0311 entry->setField(artist, albumArtist); 0312 } else if(!a.isEmpty()) { 0313 if(exists && entry->field(artist).compare(a, Qt::CaseInsensitive) != 0) { 0314 // track artist is different than the album artist 0315 entry->setField(artist, i18n("(Various)")); 0316 } else { 0317 entry->setField(artist, a); 0318 } 0319 } 0320 if(tag->year() > 0) { 0321 entry->setField(year, QString::number(tag->year())); 0322 } else if(hasValue(pmap, "OriginalYear")) { 0323 entry->setField(year, TStringToQString(pmap["OriginalYear"].front())); 0324 } 0325 0326 if(!tag->genre().isEmpty()) { 0327 entry->setField(genre, TStringToQString(tag->genre()).trimmed()); 0328 } 0329 if(hasValue(pmap, "Label")) { 0330 entry->setField(label, TStringToQString(pmap["Label"].front())); 0331 } 0332 if(hasValue(pmap, "Media")) { 0333 const QString media = TStringToQString(pmap["Media"].front()); 0334 if(media == QLatin1String("CD")) { 0335 entry->setField(QLatin1String("medium"), i18n("Compact Disc")); 0336 } else { 0337 entry->setField(QLatin1String("medium"), media); 0338 } 0339 } 0340 0341 QFileInfo fi(*it); 0342 const QString dirName = fi.dir().canonicalPath(); 0343 if(!directoryAlbumHash.contains(dirName)) { 0344 directoryAlbumHash.insert(dirName, albumKey); 0345 } 0346 0347 if(!tag->title().isEmpty()) { 0348 int trackNum = tag->track(); 0349 if(trackNum <= 0) { // try to figure out track number from file name 0350 const QString fileName = fi.baseName(); 0351 QString numString; 0352 int i = 0; 0353 const int len = fileName.length(); 0354 while(i < len && fileName[i].isNumber()) { 0355 i++; 0356 } 0357 if(i == 0) { // does not start with a number 0358 i = len - 1; 0359 while(i >= 0 && fileName[i].isNumber()) { 0360 i--; 0361 } 0362 // file name ends with a number 0363 if(i != len - 1) { 0364 numString = fileName.mid(i + 1); 0365 } 0366 } else { 0367 numString = fileName.mid(0, i); 0368 } 0369 bool ok; 0370 int number = numString.toInt(&ok); 0371 if(ok) { 0372 trackNum = number; 0373 } 0374 } 0375 if(trackNum > 0) { 0376 TagLib::AudioProperties* audioProps = f.audioProperties(); 0377 Q_ASSERT(audioProps); 0378 QString t = TStringToQString(tag->title()).trimmed(); 0379 t += FieldFormat::columnDelimiterString() + a; 0380 int len = audioProps->length(); 0381 if(len == 0) len = audioProps->lengthInSeconds(); 0382 if(len == 0) len = audioProps->lengthInMilliseconds() / 1000; 0383 if(len > 0) { 0384 t += FieldFormat::columnDelimiterString() + Tellico::minutes(len); 0385 } 0386 QString realTrack = disc > 1 ? track + QString::number(disc) : track; 0387 entry->setField(realTrack, insertValue(entry->field(realTrack), t, trackNum)); 0388 if(addFile) { 0389 QString fileValue = *it; 0390 if(addBitrate) { 0391 // for Vorbis, prefer the nominal bitrate (which is bytes/sec, where bitrate() is kb/s) 0392 TagLib::Vorbis::Properties* vorbisProps = dynamic_cast<TagLib::Vorbis::Properties*>(audioProps); 0393 const int bitrate = vorbisProps ? vorbisProps->bitrateNominal()/1000 : audioProps->bitrate(); 0394 fileValue += FieldFormat::columnDelimiterString() + QString::number(bitrate); 0395 } 0396 entry->setField(file, insertValue(entry->field(file), fileValue, trackNum)); 0397 } 0398 } else { 0399 myDebug() << *it << " contains no track number and track number cannot be determined, so the track is not imported."; 0400 } 0401 } else { 0402 myDebug() << *it << " has an empty title, so the track is not imported."; 0403 } 0404 if(!tag->comment().stripWhiteSpace().isEmpty()) { 0405 QString c = entry->field(comments); 0406 if(!c.isEmpty()) { 0407 c += QLatin1String("<br/>"); 0408 } 0409 if(!tag->title().isEmpty()) { 0410 c += QLatin1String("<em>") + TStringToQString(tag->title()).trimmed() + QLatin1String("</em> - "); 0411 } 0412 c += TStringToQString(tag->comment().stripWhiteSpace()); 0413 entry->setField(comments, c); 0414 } 0415 0416 if(!exists) { 0417 entriesToAdd << entry; 0418 } 0419 if(showProgress && j%stepSize == 0) { 0420 ProgressManager::self()->setProgress(this, j); 0421 qApp->processEvents(); 0422 } 0423 0424 /* myDebug() << "-- TAG --"; 0425 myDebug() << "title - \"" << TStringToQString(tag->title()) << "\""; 0426 myDebug() << "artist - \"" << TStringToQString(tag->artist()) << "\""; 0427 myDebug() << "album - \"" << TStringToQString(tag->album()) << "\""; 0428 myDebug() << "year - \"" << tag->year() << "\""; 0429 myDebug() << "comment - \"" << TStringToQString(tag->comment()) << "\""; 0430 myDebug() << "track - \"" << tag->track() << "\""; 0431 myDebug() << "genre - \"" << TStringToQString(tag->genre()) << "\"";*/ 0432 } 0433 0434 if(m_cancelled) { 0435 m_coll = Data::CollPtr(); 0436 return m_coll; 0437 } 0438 // myLog() << "++ Adding" << entriesToAdd.count() << "entries"; 0439 m_coll->addEntries(entriesToAdd); 0440 0441 QTextStream ts; 0442 static const QRegularExpression iconRx(QLatin1String("^Icon\\s*=\\s*(.*?)\\s*$")); 0443 for(QStringList::ConstIterator it = directoryFiles.constBegin(); !m_cancelled && it != directoryFiles.constEnd(); ++it, ++j) { 0444 QFile file(*it); 0445 if(!file.open(QIODevice::ReadOnly)) { 0446 continue; 0447 } 0448 ts.setDevice(&file); 0449 for(QString line = ts.readLine(); !line.isNull(); line = ts.readLine()) { 0450 QRegularExpressionMatch m = iconRx.match(line); 0451 if(!m.hasMatch()) { 0452 continue; 0453 } 0454 QDir thisDir(*it); 0455 thisDir.cdUp(); 0456 QFileInfo fi(thisDir, m.captured(1)); 0457 Data::EntryPtr entry = albumMap.value(directoryAlbumHash.value(thisDir.canonicalPath())); 0458 if(!entry) { 0459 myDebug() << "AudioFileImporter: No entry found for" << thisDir.canonicalPath(); 0460 continue; 0461 } 0462 const QUrl u = QUrl::fromLocalFile(fi.absoluteFilePath()); 0463 const QString id = ImageFactory::addImage(u, true); 0464 if(!id.isEmpty()) { 0465 entry->setField(QStringLiteral("cover"), id); 0466 } 0467 break; 0468 } 0469 0470 if(showProgress && j%stepSize == 0) { 0471 ProgressManager::self()->setProgress(this, j); 0472 qApp->processEvents(); 0473 } 0474 } 0475 0476 if(m_cancelled) { 0477 m_coll = Data::CollPtr(); 0478 } 0479 0480 return m_coll; 0481 #endif 0482 } 0483 0484 QWidget* AudioFileImporter::widget(QWidget* parent_) { 0485 if(m_widget) { 0486 return m_widget; 0487 } 0488 0489 m_widget = new QWidget(parent_); 0490 QVBoxLayout* l = new QVBoxLayout(m_widget); 0491 0492 QGroupBox* gbox = new QGroupBox(i18n("Audio File Options"), m_widget); 0493 QVBoxLayout* vlay = new QVBoxLayout(gbox); 0494 0495 m_recursive = new QCheckBox(i18n("Recursive &folder search"), gbox); 0496 m_recursive->setWhatsThis(i18n("If checked, folders are recursively searched for audio files.")); 0497 // by default, make it checked 0498 m_recursive->setChecked(true); 0499 0500 m_addFilePath = new QCheckBox(i18n("Include file &location"), gbox); 0501 m_addFilePath->setWhatsThis(i18n("If checked, the file names for each track are added to the entries.")); 0502 m_addFilePath->setChecked(false); 0503 connect(m_addFilePath, &QAbstractButton::toggled, this, &AudioFileImporter::slotAddFileToggled); 0504 0505 m_addBitrate = new QCheckBox(i18n("Include &bitrate"), gbox); 0506 m_addBitrate->setWhatsThis(i18n("If checked, the bitrate for each track is added to the entries.")); 0507 m_addBitrate->setChecked(false); 0508 m_addBitrate->setEnabled(false); 0509 0510 vlay->addWidget(m_recursive); 0511 vlay->addWidget(m_addFilePath); 0512 vlay->addWidget(m_addBitrate); 0513 0514 l->addWidget(gbox); 0515 l->addStretch(1); 0516 return m_widget; 0517 } 0518 0519 // pos_ is NOT zero-indexed! 0520 QString AudioFileImporter::insertValue(const QString& str_, const QString& value_, int pos_) { 0521 QStringList list = FieldFormat::splitTable(str_); 0522 for(int i = list.count(); i < pos_; ++i) { 0523 list.append(QString()); 0524 } 0525 if(!list.at(pos_-1).isEmpty()) { 0526 myDebug() << "overwriting track " << pos_; 0527 myDebug() << "*** Old value: " << list[pos_-1]; 0528 myDebug() << "*** New value: " << value_; 0529 } 0530 list[pos_-1] = value_; 0531 return list.join(FieldFormat::rowDelimiterString()); 0532 } 0533 0534 void AudioFileImporter::slotCancel() { 0535 m_cancelled = true; 0536 } 0537 0538 void AudioFileImporter::slotAddFileToggled(bool on_) { 0539 m_addBitrate->setEnabled(on_); 0540 if(!on_) { 0541 m_addBitrate->setChecked(false); 0542 } 0543 } 0544 0545 int AudioFileImporter::discNumber(const TagLib::FileRef& ref_) const { 0546 // default to 1 unless otherwise 0547 int num = 1; 0548 #ifdef HAVE_TAGLIB 0549 QString disc; 0550 if(TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(ref_.file())) { 0551 if(file->ID3v2Tag() && !file->ID3v2Tag()->frameListMap()["TPOS"].isEmpty()) { 0552 disc = TStringToQString(file->ID3v2Tag()->frameListMap()["TPOS"].front()->toString()).trimmed(); 0553 } 0554 } else if(TagLib::Ogg::Vorbis::File* file = dynamic_cast<TagLib::Ogg::Vorbis::File*>(ref_.file())) { 0555 if(file->tag() && !file->tag()->fieldListMap()["DISCNUMBER"].isEmpty()) { 0556 disc = TStringToQString(file->tag()->fieldListMap()["DISCNUMBER"].front()).trimmed(); 0557 } 0558 } else if(TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(ref_.file())) { 0559 if(file->xiphComment() && !file->xiphComment()->fieldListMap()["DISCNUMBER"].isEmpty()) { 0560 disc = TStringToQString(file->xiphComment()->fieldListMap()["DISCNUMBER"].front()).trimmed(); 0561 } 0562 } 0563 0564 if(!disc.isEmpty()) { 0565 int pos = disc.indexOf(QLatin1Char('/')); 0566 int n; 0567 bool ok; 0568 if(pos == -1) { 0569 n = disc.toInt(&ok); 0570 } else { 0571 n = disc.leftRef(pos).toInt(&ok); 0572 } 0573 if(ok && n > 0) { 0574 num = n; 0575 } 0576 } 0577 #else 0578 Q_UNUSED(ref_); 0579 #endif 0580 return num; 0581 }