diff --git a/autotests/filescannertest.cpp b/autotests/filescannertest.cpp index dff1e496..43feaa60 100644 --- a/autotests/filescannertest.cpp +++ b/autotests/filescannertest.cpp @@ -1,119 +1,119 @@ /* * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "musicaudiotrack.h" #include "filescanner.h" #include "config-upnp-qt.h" #include #include #include #include #include #include #include class FileScannerTest: public QObject { Q_OBJECT public: QString createTrackUrl(QString subpath) { return QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/cover_art") + subpath; } QList mTestTracksForDirectory = { - createTrackUrl(QStringLiteral("/artist1/album1/not_existing.ogg")), - createTrackUrl(QStringLiteral("/artist1/album2/not_existing.ogg")), - createTrackUrl(QStringLiteral("/artist1/album3/not_existing.ogg")), - createTrackUrl(QStringLiteral("/artist2/album1/not_existing.ogg")), - createTrackUrl(QStringLiteral("/artist2/album2/not_existing.ogg")), - createTrackUrl(QStringLiteral("/artist2/album3/not_existing.ogg")), - createTrackUrl(QStringLiteral("/artist3/album1/not_existing.ogg")), - createTrackUrl(QStringLiteral("/artist3/album2/not_existing.ogg")), - createTrackUrl(QStringLiteral("/artist3/album3/not_existing.ogg")), + createTrackUrl(QStringLiteral("/artist1/album1/not_existing.ogg")), + createTrackUrl(QStringLiteral("/artist1/album2/not_existing.ogg")), + createTrackUrl(QStringLiteral("/artist1/album3/not_existing.ogg")), + createTrackUrl(QStringLiteral("/artist2/album1/not_existing.ogg")), + createTrackUrl(QStringLiteral("/artist2/album2/not_existing.ogg")), + createTrackUrl(QStringLiteral("/artist2/album3/not_existing.ogg")), + createTrackUrl(QStringLiteral("/artist3/album1/not_existing.ogg")), + createTrackUrl(QStringLiteral("/artist3/album2/not_existing.ogg")), + createTrackUrl(QStringLiteral("/artist3/album3/not_existing.ogg")), }; QList mTestTracksForMetaData = { - createTrackUrl(QStringLiteral("/artist4/test.ogg")), - createTrackUrl(QStringLiteral("/artist4/test.flac")), - createTrackUrl(QStringLiteral("/artist4/test.mp3")), + createTrackUrl(QStringLiteral("/artist4/test.ogg")), + createTrackUrl(QStringLiteral("/artist4/test.flac")), + createTrackUrl(QStringLiteral("/artist4/test.mp3")), }; private Q_SLOTS: void initTestCase() { } void testFindCoverInDirectory() { FileScanner fileScanner; QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(0)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(1)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(2)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(3)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(4)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(5)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(6)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(7)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(8)).isEmpty()); } void loadCoverFromMetaData() { FileScanner fileScanner; QVERIFY(fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(0))); QVERIFY(fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(1))); QVERIFY(fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(2))); } void benchmarkCoverInDirectory() { FileScanner fileScanner; QBENCHMARK { fileScanner.searchForCoverFile(mTestTracksForDirectory.at(0)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(1)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(2)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(3)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(4)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(5)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(6)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(7)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(8)); } } void benchmarkCoverFromMetadata() { FileScanner fileScanner; QBENCHMARK { fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(0)); fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(1)); fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(2)); } } }; QTEST_GUILESS_MAIN(FileScannerTest) #include "filescannertest.moc" diff --git a/src/filescanner.cpp b/src/filescanner.cpp index a650d2df..d4311567 100644 --- a/src/filescanner.cpp +++ b/src/filescanner.cpp @@ -1,305 +1,317 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "filescanner.h" #include "config-upnp-qt.h" #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND #include #include #include #include #include #include #if defined KF5Baloo_FOUND && KF5Baloo_FOUND #include #endif #endif #include #include #include class FileScannerPrivate { public: - + static const QStringList constSearchStrings; #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND KFileMetaData::ExtractorCollection mAllExtractors; KFileMetaData::PropertyMap mAllProperties; QString checkForMultipleEntries(KFileMetaData::Property::Property property); KFileMetaData::EmbeddedImageData mImageScanner; #endif }; +const QStringList FileScannerPrivate::constSearchStrings = { + QStringLiteral("*[Cc]over*.jpg"), + QStringLiteral("*[Cc]over*.png"), + QStringLiteral("*[Ff]older*.jpg"), + QStringLiteral("*[Ff]older*.png"), + QStringLiteral("*[Ff]ront*.jpg"), + QStringLiteral("*[Ff]ront*.png") +}; + FileScanner::FileScanner() : d(std::make_unique()) { } FileScanner::~FileScanner() = default; MusicAudioTrack FileScanner::scanOneFile(const QUrl &scanFile, const QMimeDatabase &mimeDatabase) { #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND MusicAudioTrack newTrack; auto localFileName = scanFile.toLocalFile(); QFileInfo scanFileInfo(localFileName); newTrack.setFileModificationTime(scanFileInfo.fileTime(QFile::FileModificationTime)); newTrack.setResourceURI(scanFile); const auto &fileMimeType = mimeDatabase.mimeTypeForFile(localFileName); if (!fileMimeType.name().startsWith(QStringLiteral("audio/"))) { return newTrack; } QString mimetype = fileMimeType.name(); QList exList = d->mAllExtractors.fetchExtractors(mimetype); if (exList.isEmpty()) { return newTrack; } KFileMetaData::Extractor* ex = exList.first(); KFileMetaData::SimpleExtractionResult result(localFileName, mimetype, KFileMetaData::ExtractionResult::ExtractMetaData); ex->extract(&result); d->mAllProperties = result.properties(); scanProperties(localFileName, newTrack); return newTrack; #else Q_UNUSED(scanFile) Q_UNUSED(mimeDatabase) return {}; #endif } void FileScanner::scanProperties(const Baloo::File &match, MusicAudioTrack &trackData) { #if defined KF5Baloo_FOUND && KF5Baloo_FOUND d->mAllProperties = match.properties(); scanProperties(match.path(), trackData); #else Q_UNUSED(match) Q_UNUSED(trackData) #endif } void FileScanner::scanProperties(const QString &localFileName, MusicAudioTrack &trackData) { #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND auto artistString = d->checkForMultipleEntries(KFileMetaData::Property::Artist); auto albumArtistString = d->checkForMultipleEntries(KFileMetaData::Property::AlbumArtist); auto genreString = d->checkForMultipleEntries(KFileMetaData::Property::Genre); auto composerString = d->checkForMultipleEntries(KFileMetaData::Property::Composer); auto lyricistString = d->checkForMultipleEntries(KFileMetaData::Property::Lyricist); auto titleProperty = d->mAllProperties.find(KFileMetaData::Property::Title); auto durationProperty = d->mAllProperties.find(KFileMetaData::Property::Duration); auto albumProperty = d->mAllProperties.find(KFileMetaData::Property::Album); auto trackNumberProperty = d->mAllProperties.find(KFileMetaData::Property::TrackNumber); auto discNumberProperty = d->mAllProperties.find(KFileMetaData::Property::DiscNumber); auto yearProperty = d->mAllProperties.find(KFileMetaData::Property::ReleaseYear); auto lyricsProperty = d->mAllProperties.find(KFileMetaData::Property::Lyrics); auto channelsProperty = d->mAllProperties.find(KFileMetaData::Property::Channels); auto bitRateProperty = d->mAllProperties.find(KFileMetaData::Property::BitRate); auto sampleRateProperty = d->mAllProperties.find(KFileMetaData::Property::SampleRate); auto commentProperty = d->mAllProperties.find(KFileMetaData::Property::Comment); auto ratingProperty = d->mAllProperties.find(KFileMetaData::Property::Rating); #if !defined Q_OS_ANDROID auto fileData = KFileMetaData::UserMetaData(localFileName); #endif if (!artistString.isEmpty()) { trackData.setArtist(artistString); } if (!albumArtistString.isEmpty()) { trackData.setAlbumArtist(albumArtistString); } if (!genreString.isEmpty()) { trackData.setGenre(genreString); } if (!composerString.isEmpty()) { trackData.setComposer(composerString); } if (!lyricistString.isEmpty()) { trackData.setLyricist(lyricistString); } if (albumProperty != d->mAllProperties.end()) { trackData.setAlbumName(albumProperty->toString()); } if (durationProperty != d->mAllProperties.end()) { trackData.setDuration(QTime::fromMSecsSinceStartOfDay(int(1000 * durationProperty->toDouble()))); } if (titleProperty != d->mAllProperties.end()) { trackData.setTitle(titleProperty->toString()); } if (trackNumberProperty != d->mAllProperties.end()) { trackData.setTrackNumber(trackNumberProperty->toInt()); } if (discNumberProperty != d->mAllProperties.end()) { trackData.setDiscNumber(discNumberProperty->toInt()); } if (yearProperty != d->mAllProperties.end()) { trackData.setYear(yearProperty->toInt()); } if (channelsProperty != d->mAllProperties.end()) { trackData.setChannels(channelsProperty->toInt()); } if (bitRateProperty != d->mAllProperties.end()) { trackData.setBitRate(bitRateProperty->toInt()); } if (sampleRateProperty != d->mAllProperties.end()) { trackData.setSampleRate(sampleRateProperty->toInt()); } if (lyricsProperty != d->mAllProperties.end()) { trackData.setLyrics(lyricsProperty->toString()); } if (trackData.artist().isEmpty()) { trackData.setArtist(trackData.albumArtist()); } #if !defined Q_OS_ANDROID && !defined Q_OS_WIN QString comment = fileData.userComment(); if (!comment.isEmpty()) { trackData.setComment(comment); } else if (commentProperty != d->mAllProperties.end()) { trackData.setComment(commentProperty->toString()); } int rating = fileData.rating(); if (rating > 0) { trackData.setRating(rating); } else if (ratingProperty != d->mAllProperties.end()) { trackData.setRating(ratingProperty->toInt()); } else { trackData.setRating(0); } #else if (ratingProperty != d->mAllProperties.end()) { trackData.setRating(ratingProperty->toInt()); } else { trackData.setRating(0); } if (commentProperty != d->mAllProperties.end()) { trackData.setComment(commentProperty->toString()); } #endif if (!trackData.duration().isValid()) { return; } trackData.setValid(true); #else Q_UNUSED(localFileName) Q_UNUSED(trackData) #endif } #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND QString FileScannerPrivate::checkForMultipleEntries(KFileMetaData::Property::Property property) { if (mAllProperties.count(property) > 1) { auto propertyList = mAllProperties.values(property); return QLocale().createSeparatedList(QVariant(propertyList).toStringList()); } else { auto variantResult = mAllProperties.find(property); if (variantResult != mAllProperties.end()) { auto value = variantResult.value(); if (value.type() == QVariant::List || value.type() == QVariant::StringList) { return QLocale().createSeparatedList(value.toStringList()); } else { return value.toString(); } } else { return QString(); } } } #endif QUrl FileScanner::searchForCoverFile(const QString &localFileName) { QFileInfo trackFilePath(localFileName); QDir trackFileDir = trackFilePath.absoluteDir(); - QString dirNamePattern = QStringLiteral("*") + trackFileDir.dirName() + QStringLiteral("*"); - QStringList filters; - filters << QStringLiteral("*[Cc]over*.jpg") << QStringLiteral("*[Cc]over*.png") - << QStringLiteral("*[Ff]older*.jpg") << QStringLiteral("*[Ff]older*.png") - << QStringLiteral("*[Ff]ront*.jpg") << QStringLiteral("*[Ff]ront*.png") - << dirNamePattern + QStringLiteral(".jpg") << dirNamePattern + QStringLiteral(".png") - << dirNamePattern.toLower() + QStringLiteral(".jpg") << dirNamePattern.toLower() + QStringLiteral(".png"); - dirNamePattern.remove(QLatin1Char(' ')); - filters << dirNamePattern + QStringLiteral(".jpg") << dirNamePattern + QStringLiteral(".png") - << dirNamePattern.toLower() + QStringLiteral(".jpg") << dirNamePattern.toLower() + QStringLiteral(".png"); - trackFileDir.setNameFilters(filters); + trackFileDir.setFilter(QDir::Files); + trackFileDir.setNameFilters(d->constSearchStrings); QFileInfoList coverFiles = trackFileDir.entryInfoList(); + if (coverFiles.isEmpty()) { + QString dirNamePattern = QStringLiteral("*") + trackFileDir.dirName() + QStringLiteral("*"); + QString dirNameNoSpaces = dirNamePattern.remove(QLatin1Char(' ')); + QStringList filters = { + dirNamePattern + QStringLiteral(".jpg"), + dirNamePattern + QStringLiteral(".png"), + + dirNameNoSpaces + QStringLiteral(".jpg"), + dirNameNoSpaces + QStringLiteral(".png") + }; + trackFileDir.setNameFilters(filters); + coverFiles = trackFileDir.entryInfoList(); + } if (coverFiles.isEmpty()) { return QUrl(); - } else { - return QUrl::fromLocalFile(coverFiles.at(0).absoluteFilePath()); } + return QUrl::fromLocalFile(coverFiles.first().absoluteFilePath()); } bool FileScanner::checkEmbeddedCoverImage(const QString &localFileName) { #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND auto imageData = d->mImageScanner.imageData(localFileName); if (imageData.contains(KFileMetaData::EmbeddedImageData::FrontCover)) { if (!imageData[KFileMetaData::EmbeddedImageData::FrontCover].isEmpty()) { return true; } } #endif return false; }