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 }