diff --git a/autotests/taglibextractortest.cpp b/autotests/taglibextractortest.cpp index b364672..2d7ec6c 100644 --- a/autotests/taglibextractortest.cpp +++ b/autotests/taglibextractortest.cpp @@ -1,629 +1,627 @@ /* * TagLibExtractor tests. * * Copyright (C) 2015 Juan Palacios * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "taglibextractortest.h" #include "simpleextractionresult.h" #include "propertyinfo.h" //TODO: use QTESTFINDDATA and remove this #include "indexerextractortestsconfig.h" #include "extractors/taglibextractor.h" #include #include #include Q_DECLARE_METATYPE(KFileMetaData::Property::Property) using namespace KFileMetaData; QString TagLibExtractorTest::testFilePath(const QString& fileName) const { return QLatin1String(INDEXER_TESTS_SAMPLE_FILES_PATH) + QLatin1Char('/') + fileName; } const QStringList TagLibExtractorTest::propertyEnumNames(const QList& keys) const { QStringList result; for (auto key : keys) { result.append(PropertyInfo(key).name()); } return result; } void TagLibExtractorTest::testPropertyTypes() { TagLibExtractor plugin{this}; SimpleExtractionResult resultOpus(testFilePath("test.opus"), "audio/opus"); plugin.extract(&resultOpus); auto testForType = [](SimpleExtractionResult &result, Property::Property prop) { QCOMPARE(result.properties().value(prop).type(), PropertyInfo(prop).valueType()); }; QCOMPARE(resultOpus.types().size(), 1); QCOMPARE(resultOpus.types().constFirst(), Type::Audio); testForType(resultOpus, Property::Title); QEXPECT_FAIL("", "Will be fixed in a following release", Continue); testForType(resultOpus, Property::Artist); testForType(resultOpus, Property::Album); QEXPECT_FAIL("", "Will be fixed in a following release", Continue); testForType(resultOpus, Property::AlbumArtist); QEXPECT_FAIL("", "Will be fixed in a following release", Continue); testForType(resultOpus, Property::Genre); testForType(resultOpus, Property::Comment); testForType(resultOpus, Property::Composer); QEXPECT_FAIL("", "Will be fixed in a following release", Continue); testForType(resultOpus, Property::Lyricist); QEXPECT_FAIL("", "Will be fixed in a following release", Continue); testForType(resultOpus, Property::Conductor); QEXPECT_FAIL("", "Will be fixed in a following release", Continue); testForType(resultOpus, Property::Arranger); testForType(resultOpus, Property::Ensemble); testForType(resultOpus, Property::Location); QEXPECT_FAIL("", "Will be fixed in a following release", Continue); testForType(resultOpus, Property::Performer); testForType(resultOpus, Property::Langauge); testForType(resultOpus, Property::Publisher); testForType(resultOpus, Property::Label); QEXPECT_FAIL("", "Will be fixed in a following release", Continue); testForType(resultOpus, Property::Author); testForType(resultOpus, Property::Copyright); testForType(resultOpus, Property::Compilation); testForType(resultOpus, Property::License); testForType(resultOpus, Property::Opus); - QEXPECT_FAIL("", "Will be fixed in a following release", Continue); testForType(resultOpus, Property::TrackNumber); - QEXPECT_FAIL("", "Will be fixed in a following release", Continue); testForType(resultOpus, Property::ReleaseYear); testForType(resultOpus, Property::Channels); testForType(resultOpus, Property::DiscNumber); testForType(resultOpus, Property::Rating); testForType(resultOpus, Property::ReplayGainAlbumGain); testForType(resultOpus, Property::ReplayGainAlbumPeak); testForType(resultOpus, Property::ReplayGainTrackGain); testForType(resultOpus, Property::ReplayGainTrackPeak); } void TagLibExtractorTest::testCommonData() { QFETCH(QString, fileType); QFETCH(QString, mimeType); QString fileName = QStringLiteral("test.") + fileType; TagLibExtractor plugin{this}; QCOMPARE(plugin.mimetypes().contains(mimeType), true); SimpleExtractionResult result(testFilePath(fileName), mimeType); plugin.extract(&result); QCOMPARE(result.types().size(), 1); QCOMPARE(result.types().constFirst(), Type::Audio); QCOMPARE(result.properties().value(Property::Title), QVariant(QStringLiteral("Title"))); QCOMPARE(result.properties().value(Property::Artist), QVariant(QStringLiteral("Artist"))); QCOMPARE(result.properties().value(Property::Album), QVariant(QStringLiteral("Album"))); QCOMPARE(result.properties().value(Property::Genre), QVariant(QStringLiteral("Genre"))); QCOMPARE(result.properties().value(Property::Comment), QVariant(QStringLiteral("Comment"))); QCOMPARE(result.properties().value(Property::TrackNumber).toInt(), 1); QCOMPARE(result.properties().value(Property::ReleaseYear).toInt(), 2015); } void TagLibExtractorTest::testCommonData_data() { QTest::addColumn("fileType"); QTest::addColumn("mimeType"); QTest::addRow("aiff") << QStringLiteral("aif") << QStringLiteral("audio/x-aiff") ; QTest::addRow("ape") << QStringLiteral("ape") << QStringLiteral("audio/x-ape") ; QTest::addRow("flac") << QStringLiteral("flac") << QStringLiteral("audio/flac") ; QTest::addRow("m4a") << QStringLiteral("m4a") << QStringLiteral("audio/mp4") ; QTest::addRow("mp3") << QStringLiteral("mp3") << QStringLiteral("audio/mpeg3") ; QTest::addRow("mpc") << QStringLiteral("mpc") << QStringLiteral("audio/x-musepack") ; QTest::addRow("ogg") << QStringLiteral("ogg") << QStringLiteral("audio/ogg") ; QTest::addRow("opus") << QStringLiteral("opus") << QStringLiteral("audio/opus") ; QTest::addRow("speex") << QStringLiteral("spx") << QStringLiteral("audio/speex") ; QTest::addRow("wav") << QStringLiteral("wav") << QStringLiteral("audio/wav") ; QTest::addRow("wavpack") << QStringLiteral("wv") << QStringLiteral("audio/x-wavpack") ; QTest::addRow("wma") << QStringLiteral("wma") << QStringLiteral("audio/x-ms-wma") ; } void TagLibExtractorTest::testVorbisComment() { QFETCH(QString, fileType); QFETCH(QString, mimeType); QString fileName = QStringLiteral("test.") + fileType; TagLibExtractor plugin{this}; SimpleExtractionResult result(testFilePath(fileName), mimeType); plugin.extract(&result); QCOMPARE(result.properties().value(Property::AlbumArtist), QVariant(QStringLiteral("Album Artist"))); QCOMPARE(result.properties().value(Property::Composer), QVariant(QStringLiteral("Composer"))); QCOMPARE(result.properties().value(Property::Lyricist), QVariant(QStringLiteral("Lyricist"))); QCOMPARE(result.properties().value(Property::Conductor), QVariant(QStringLiteral("Conductor"))); QCOMPARE(result.properties().value(Property::Arranger), QVariant(QStringLiteral("Arranger"))); QCOMPARE(result.properties().value(Property::Ensemble), QVariant(QStringLiteral("Ensemble"))); QCOMPARE(result.properties().value(Property::Location), QVariant(QStringLiteral("Location"))); QCOMPARE(result.properties().value(Property::Performer), QVariant(QStringLiteral("Performer"))); QCOMPARE(result.properties().value(Property::Language), QVariant(QStringLiteral("Language"))); QCOMPARE(result.properties().value(Property::Publisher), QVariant(QStringLiteral("Publisher"))); QCOMPARE(result.properties().value(Property::Label), QVariant(QStringLiteral("Label"))); QCOMPARE(result.properties().value(Property::Author), QVariant(QStringLiteral("Author"))); QCOMPARE(result.properties().value(Property::Copyright), QVariant(QStringLiteral("Copyright"))); QCOMPARE(result.properties().value(Property::Compilation), QVariant(QStringLiteral("Compilation"))); QCOMPARE(result.properties().value(Property::License), QVariant(QStringLiteral("License"))); QCOMPARE(result.properties().value(Property::Lyrics), QVariant(QStringLiteral("Lyrics"))); QCOMPARE(result.properties().value(Property::Opus).toInt(), 1); QCOMPARE(result.properties().value(Property::Channels).toInt(), 1); QCOMPARE(result.properties().value(Property::DiscNumber).toInt(), 1); QCOMPARE(result.properties().value(Property::Rating).toInt(), 5); QCOMPARE(result.properties().value(Property::ReplayGainAlbumGain), QVariant(-9.90)); QCOMPARE(result.properties().value(Property::ReplayGainAlbumPeak), QVariant(1.512)); QCOMPARE(result.properties().value(Property::ReplayGainTrackGain), QVariant(-10.44)); QCOMPARE(result.properties().value(Property::ReplayGainTrackPeak), QVariant(1.301)); } void TagLibExtractorTest::testVorbisComment_data() { QTest::addColumn("fileType"); QTest::addColumn("mimeType"); QTest::addRow("flac") << QStringLiteral("flac") << QStringLiteral("audio/flac") ; QTest::addRow("ogg") << QStringLiteral("ogg") << QStringLiteral("audio/ogg") ; QTest::addRow("opus") << QStringLiteral("opus") << QStringLiteral("audio/opus") ; QTest::addRow("speex") << QStringLiteral("spx") << QStringLiteral("audio/speex") ; } void TagLibExtractorTest::testId3() { QFETCH(QString, fileType); QFETCH(QString, mimeType); QString fileName = QStringLiteral("test.") + fileType; TagLibExtractor plugin{this}; SimpleExtractionResult result(testFilePath(fileName), mimeType); plugin.extract(&result); QCOMPARE(result.properties().value(Property::AlbumArtist), QVariant(QStringLiteral("Album Artist"))); QCOMPARE(result.properties().value(Property::Composer), QVariant(QStringLiteral("Composer"))); QCOMPARE(result.properties().value(Property::Lyricist), QVariant(QStringLiteral("Lyricist"))); QCOMPARE(result.properties().value(Property::Conductor), QVariant(QStringLiteral("Conductor"))); QCOMPARE(result.properties().value(Property::Publisher), QVariant(QStringLiteral("Publisher"))); QCOMPARE(result.properties().value(Property::Language), QVariant(QStringLiteral("Language"))); QCOMPARE(result.properties().value(Property::Compilation), QVariant(QStringLiteral("Compilation"))); QCOMPARE(result.properties().value(Property::Lyrics), QVariant(QStringLiteral("Lyrics"))); QCOMPARE(result.properties().value(Property::Channels).toInt(), 1); QCOMPARE(result.properties().value(Property::DiscNumber).toInt(), 1); QCOMPARE(result.properties().value(Property::Rating).toInt(), 10); QCOMPARE(result.properties().value(Property::ReplayGainAlbumGain), QVariant(-3.33)); QCOMPARE(result.properties().value(Property::ReplayGainAlbumPeak), QVariant(1.333)); QCOMPARE(result.properties().value(Property::ReplayGainTrackGain), QVariant(3.33)); QCOMPARE(result.properties().value(Property::ReplayGainTrackPeak), QVariant(1.369)); } void TagLibExtractorTest::testId3_data() { QTest::addColumn("fileType"); QTest::addColumn("mimeType"); QTest::addRow("aiff") << QStringLiteral("aif") << QStringLiteral("audio/x-aiff") ; QTest::addRow("mp3") << QStringLiteral("mp3") << QStringLiteral("audio/mpeg") ; QTest::addRow("wav") << QStringLiteral("wav") << QStringLiteral("audio/wav") ; } void TagLibExtractorTest::testApe() { QFETCH(QString, fileType); QFETCH(QString, mimeType); QString fileName = QStringLiteral("test.") + fileType; TagLibExtractor plugin{this}; SimpleExtractionResult result(testFilePath(fileName), mimeType); plugin.extract(&result); QCOMPARE(result.properties().value(Property::AlbumArtist), QVariant(QStringLiteral("Album Artist"))); QCOMPARE(result.properties().value(Property::Composer), QVariant(QStringLiteral("Composer"))); QCOMPARE(result.properties().value(Property::Conductor), QVariant(QStringLiteral("Conductor"))); QCOMPARE(result.properties().value(Property::Arranger), QVariant(QStringLiteral("Arranger"))); QCOMPARE(result.properties().value(Property::Ensemble), QVariant(QStringLiteral("Ensemble"))); QCOMPARE(result.properties().value(Property::Location), QVariant(QStringLiteral("Location"))); QCOMPARE(result.properties().value(Property::Performer), QVariant(QStringLiteral("Performer"))); QCOMPARE(result.properties().value(Property::Language), QVariant(QStringLiteral("Language"))); QCOMPARE(result.properties().value(Property::Publisher), QVariant(QStringLiteral("Publisher"))); QCOMPARE(result.properties().value(Property::Label), QVariant(QStringLiteral("Label"))); QCOMPARE(result.properties().value(Property::Author), QVariant(QStringLiteral("Author"))); QCOMPARE(result.properties().value(Property::Copyright), QVariant(QStringLiteral("Copyright"))); QCOMPARE(result.properties().value(Property::Compilation), QVariant(QStringLiteral("Compilation"))); QCOMPARE(result.properties().value(Property::License), QVariant(QStringLiteral("License"))); QCOMPARE(result.properties().value(Property::Lyrics), QVariant(QStringLiteral("Lyrics"))); QCOMPARE(result.properties().value(Property::Channels).toInt(), 1); QCOMPARE(result.properties().value(Property::DiscNumber).toInt(), 1); QCOMPARE(result.properties().value(Property::Rating).toInt(), 4); QCOMPARE(result.properties().value(Property::ReplayGainAlbumGain), QVariant(-9.44)); QCOMPARE(result.properties().value(Property::ReplayGainAlbumPeak), QVariant(1.099)); QCOMPARE(result.properties().value(Property::ReplayGainTrackGain), QVariant(-5.23)); QCOMPARE(result.properties().value(Property::ReplayGainTrackPeak), QVariant(1.234)); } void TagLibExtractorTest::testApe_data() { QTest::addColumn("fileType"); QTest::addColumn("mimeType"); QTest::addRow("ape") << QStringLiteral("ape") << QStringLiteral("audio/x-ape") ; QTest::addRow("musepack") << QStringLiteral("mpc") << QStringLiteral("audio/x-musepack") ; QTest::addRow("wavpack") << QStringLiteral("wv") << QStringLiteral("audio/x-wavpack") ; } void TagLibExtractorTest::testMp4() { QFETCH(QString, fileType); QFETCH(QString, mimeType); QString fileName = QStringLiteral("test.") + fileType; TagLibExtractor plugin{this}; SimpleExtractionResult resultMp4(testFilePath(fileName), mimeType); plugin.extract(&resultMp4); QCOMPARE(resultMp4.properties().value(Property::AlbumArtist), QVariant(QStringLiteral("Album Artist"))); QCOMPARE(resultMp4.properties().value(Property::Composer), QVariant(QStringLiteral("Composer"))); QCOMPARE(resultMp4.properties().value(Property::Copyright), QVariant(QStringLiteral("Copyright"))); QCOMPARE(resultMp4.properties().value(Property::Lyrics), QVariant(QStringLiteral("Lyrics"))); QCOMPARE(resultMp4.properties().value(Property::Channels).toInt(), 2); QCOMPARE(resultMp4.properties().value(Property::DiscNumber).toInt(), 1); QCOMPARE(resultMp4.properties().value(Property::Rating).toInt(), 8); } void TagLibExtractorTest::testMp4_data() { QTest::addColumn("fileType"); QTest::addColumn("mimeType"); QTest::addRow("mp4") << QStringLiteral("m4a") << QStringLiteral("audio/mp4") ; } void TagLibExtractorTest::testAsf() { QFETCH(QString, fileType); QFETCH(QString, mimeType); QString fileName = QStringLiteral("test.") + fileType; TagLibExtractor plugin{this}; SimpleExtractionResult result(testFilePath(fileName), mimeType); plugin.extract(&result); QCOMPARE(result.properties().value(Property::AlbumArtist), QVariant(QStringLiteral("Album Artist"))); QCOMPARE(result.properties().value(Property::Rating).toInt(), 6); QCOMPARE(result.properties().value(Property::DiscNumber).toInt(), 1); QCOMPARE(result.properties().value(Property::Conductor), QVariant(QStringLiteral("Conductor"))); QCOMPARE(result.properties().value(Property::Composer), QVariant(QStringLiteral("Composer"))); QCOMPARE(result.properties().value(Property::Author), QVariant(QStringLiteral("Author"))); QCOMPARE(result.properties().value(Property::Lyricist), QVariant(QStringLiteral("Lyricist"))); QCOMPARE(result.properties().value(Property::Copyright), QVariant(QStringLiteral("Copyright"))); QCOMPARE(result.properties().value(Property::Publisher), QVariant(QStringLiteral("Publisher"))); } void TagLibExtractorTest::testAsf_data() { QTest::addColumn("fileType"); QTest::addColumn("mimeType"); QTest::addRow("asf") << QStringLiteral("wma") << QStringLiteral("audio/x-ms-wma") ; } void TagLibExtractorTest::testId3Rating_data() { QTest::addColumn("path"); QTest::addColumn("expectedRating"); QTest::addRow("WMP") << QFINDTESTDATA("samplefiles/mp3_rating/testWMP.mp3") << 0 ; QTest::addRow("WMP1") << QFINDTESTDATA("samplefiles/mp3_rating/testWMP1.mp3") << 2 ; QTest::addRow("WMP2") << QFINDTESTDATA("samplefiles/mp3_rating/testWMP2.mp3") << 4 ; QTest::addRow("WMP3") << QFINDTESTDATA("samplefiles/mp3_rating/testWMP3.mp3") << 6 ; QTest::addRow("WMP4") << QFINDTESTDATA("samplefiles/mp3_rating/testWMP4.mp3") << 8 ; QTest::addRow("WMP5") << QFINDTESTDATA("samplefiles/mp3_rating/testWMP5.mp3") << 10 ; QTest::addRow("MM") << QFINDTESTDATA("samplefiles/mp3_rating/testMM.mp3") << 0 ; QTest::addRow("MM1") << QFINDTESTDATA("samplefiles/mp3_rating/testMM1.mp3") << 1 ; QTest::addRow("MM2") << QFINDTESTDATA("samplefiles/mp3_rating/testMM2.mp3") << 2 ; QTest::addRow("MM3") << QFINDTESTDATA("samplefiles/mp3_rating/testMM3.mp3") << 3 ; QTest::addRow("MM4") << QFINDTESTDATA("samplefiles/mp3_rating/testMM4.mp3") << 4 ; QTest::addRow("MM5") << QFINDTESTDATA("samplefiles/mp3_rating/testMM5.mp3") << 5 ; QTest::addRow("MM6") << QFINDTESTDATA("samplefiles/mp3_rating/testMM6.mp3") << 6 ; QTest::addRow("MM7") << QFINDTESTDATA("samplefiles/mp3_rating/testMM7.mp3") << 7 ; QTest::addRow("MM8") << QFINDTESTDATA("samplefiles/mp3_rating/testMM8.mp3") << 8 ; QTest::addRow("MM9") << QFINDTESTDATA("samplefiles/mp3_rating/testMM9.mp3") << 9 ; QTest::addRow("MM10") << QFINDTESTDATA("samplefiles/mp3_rating/testMM10.mp3") << 10 ; } void TagLibExtractorTest::testId3Rating() { QFETCH(QString, path); QFETCH(int, expectedRating); TagLibExtractor plugin{this}; SimpleExtractionResult result(path, "audio/mpeg"); plugin.extract(&result); QCOMPARE(result.properties().value(Property::Rating).toInt(), expectedRating); } void TagLibExtractorTest::testWmaRating() { QFETCH(QString, path); QFETCH(int, expectedRating); TagLibExtractor plugin{this}; SimpleExtractionResult result(path, "audio/x-ms-wma"); plugin.extract(&result); QCOMPARE(result.properties().value(Property::Rating).toInt(), expectedRating); } void TagLibExtractorTest::testWmaRating_data() { QTest::addColumn("path"); QTest::addColumn("expectedRating"); QTest::addRow("WMP0") << QFINDTESTDATA("samplefiles/wma_rating/test0.wma") << 0 ; QTest::addRow("WMP1") << QFINDTESTDATA("samplefiles/wma_rating/test1.wma") << 2 ; QTest::addRow("WMP2") << QFINDTESTDATA("samplefiles/wma_rating/test2.wma") << 4 ; QTest::addRow("WMP3") << QFINDTESTDATA("samplefiles/wma_rating/test3.wma") << 6 ; QTest::addRow("WMP4") << QFINDTESTDATA("samplefiles/wma_rating/test4.wma") << 8 ; QTest::addRow("WMP5") << QFINDTESTDATA("samplefiles/wma_rating/test5.wma") << 10 ; } void TagLibExtractorTest::testNoMetadata_data() { const auto expectedKeys = QList{ Property::BitRate, Property::Channels, Property::Duration, Property::SampleRate, }; QTest::addColumn("path"); QTest::addColumn("mimeType"); QTest::addColumn>("expectedKeys"); QTest::addColumn("failMessage"); QTest::addRow("mp3") << QFINDTESTDATA("samplefiles/no-meta/test.mp3") << QStringLiteral("audio/mpeg") << expectedKeys << QString() ; QTest::addRow("m4a") << QFINDTESTDATA("samplefiles/no-meta/test.m4a") << QStringLiteral("audio/mp4") << expectedKeys << QString() ; QTest::addRow("flac") << QFINDTESTDATA("samplefiles/no-meta/test.flac") << QStringLiteral("audio/flac") << expectedKeys << QString() ; QTest::addRow("opus") << QFINDTESTDATA("samplefiles/no-meta/test.opus") << QStringLiteral("audio/opus") << expectedKeys << QString() ; QTest::addRow("ogg") << QFINDTESTDATA("samplefiles/no-meta/test.ogg") << QStringLiteral("audio/ogg") << expectedKeys << QString() ; QTest::addRow("mpc") << QFINDTESTDATA("samplefiles/no-meta/test.mpc") << QStringLiteral("audio/x-musepack") << expectedKeys << QString() ; } void TagLibExtractorTest::testNoMetadata() { QFETCH(QString, path); QFETCH(QString, mimeType); QFETCH(QList, expectedKeys); QFETCH(QString, failMessage); TagLibExtractor plugin{this}; SimpleExtractionResult extracted(path, mimeType); plugin.extract(&extracted); const auto resultList = extracted.properties(); const auto resultKeys = resultList.uniqueKeys(); const auto excessKeys = resultKeys.toSet() - expectedKeys.toSet(); const auto missingKeys = expectedKeys.toSet() - resultKeys.toSet(); if (!excessKeys.isEmpty()) { const auto propNames = propertyEnumNames(excessKeys.toList()).join(QLatin1String(", ")); if (failMessage.isEmpty()) { const auto message = QStringLiteral("Excess properties: %1").arg(propNames); QWARN(qPrintable(message)); } else { QEXPECT_FAIL("", qPrintable(QStringLiteral("%1: %2").arg(failMessage).arg(propNames)), Continue); } } else if (!missingKeys.isEmpty()) { const auto message = QStringLiteral("Missing properties: %1") .arg(propertyEnumNames(missingKeys.toList()).join(QLatin1String(", "))); QWARN(qPrintable(message)); } QCOMPARE(resultKeys, expectedKeys); if (!failMessage.isEmpty()) { auto excessKeys = resultKeys.toSet() - expectedKeys.toSet(); const auto message = QStringLiteral("%1: %2") .arg(failMessage) .arg(propertyEnumNames(excessKeys.toList()).join(QLatin1String(", "))); QEXPECT_FAIL("", qPrintable(message), Continue); } QCOMPARE(resultKeys, expectedKeys); } QTEST_GUILESS_MAIN(TagLibExtractorTest) diff --git a/src/extractors/taglibextractor.cpp b/src/extractors/taglibextractor.cpp index 5cf556c..7662e4e 100644 --- a/src/extractors/taglibextractor.cpp +++ b/src/extractors/taglibextractor.cpp @@ -1,1104 +1,497 @@ /* Copyright (C) 2012 Vishesh Handa This library 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 2.1 of the License, or (at your option) any later version. This library 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 library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "taglibextractor.h" // Taglib includes -#include +#include +#include #include -#include +#include +#include #include -#include -#include -#include +#include +#include +#include #include -#include -#include #include #include -#include -#include -#include -#include -#include #include #include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include #include #include using namespace KFileMetaData; TagLibExtractor::TagLibExtractor(QObject* parent) : ExtractorPlugin(parent) { } const QStringList supportedMimeTypes = { QStringLiteral("audio/flac"), QStringLiteral("audio/mp4"), QStringLiteral("audio/mpeg"), QStringLiteral("audio/mpeg3"), QStringLiteral("audio/ogg"), QStringLiteral("audio/opus"), QStringLiteral("audio/speex"), QStringLiteral("audio/wav"), QStringLiteral("audio/x-aiff"), QStringLiteral("audio/x-ape"), QStringLiteral("audio/x-mpeg"), QStringLiteral("audio/x-ms-wma"), QStringLiteral("audio/x-musepack"), QStringLiteral("audio/x-opus+ogg"), QStringLiteral("audio/x-speex"), QStringLiteral("audio/x-vorbis+ogg"), QStringLiteral("audio/x-wav"), QStringLiteral("audio/x-wavpack"), }; QStringList TagLibExtractor::mimetypes() const { return supportedMimeTypes; } -void TagLibExtractor::extractId3Tags(TagLib::ID3v2::Tag* Id3Tags, ExtractedData& data) +void extractAudioProperties(TagLib::File* file, ExtractionResult* result) { - if (Id3Tags->isEmpty()) { - return; - } - TagLib::ID3v2::FrameList lstID3v2; - - // Artist. - lstID3v2 = Id3Tags->frameListMap()["TPE1"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.artists.isEmpty()) { - data.artists += ", "; - } - data.artists += frame->toString(); - } - - // Album Artist. - lstID3v2 = Id3Tags->frameListMap()["TPE2"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.albumArtists.isEmpty()) { - data.albumArtists += ", "; - } - data.albumArtists += frame->toString(); - } - - // Composer. - lstID3v2 = Id3Tags->frameListMap()["TCOM"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.composers.isEmpty()) { - data.composers += ", "; - } - data.composers += frame->toString(); - } - - // Lyricist. - lstID3v2 = Id3Tags->frameListMap()["TEXT"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.lyricists.isEmpty()) { - data.lyricists += ", "; - } - data.lyricists += frame->toString(); - } - - // Genre. - lstID3v2 = Id3Tags->frameListMap()["TCON"]; - for (const auto& frame : qAsConst(lstID3v2)) { - data.genres.append(frame->toString()); - } - - // Disc number. - lstID3v2 = Id3Tags->frameListMap()["TPOS"]; - for (const auto& frame : qAsConst(lstID3v2)) { - data.discNumber = frame->toString().toInt(); - } - - // Performer. - lstID3v2 = Id3Tags->frameListMap()["TMCL"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.performer.isEmpty()) { - data.performer += ", "; - } - data.performer += frame->toString(); - } - - // Conductor. - lstID3v2 = Id3Tags->frameListMap()["TPE3"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.conductor.isEmpty()) { - data.conductor += ", "; - } - data.conductor += frame->toString(); - } - - // Publisher. - lstID3v2 = Id3Tags->frameListMap()["TPUB"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.publisher.isEmpty()) { - data.publisher += ", "; - } - data.publisher += frame->toString(); - } - - // Copyright. - lstID3v2 = Id3Tags->frameListMap()["TCOP"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.copyright.isEmpty()) { - data.copyright += ", "; - } - data.copyright += frame->toString(); - } - - // Language. - lstID3v2 = Id3Tags->frameListMap()["TLAN"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.language.isEmpty()) { - data.language += ", "; - } - data.language += frame->toString(); - } - - // Lyrics. - lstID3v2 = Id3Tags->frameListMap()["USLT"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.lyrics.isEmpty()) { - data.lyrics += ", "; - } - data.lyrics += frame->toString(); - } - - // Compilation. - lstID3v2 = Id3Tags->frameListMap()["TCMP"]; - for (const auto& frame : qAsConst(lstID3v2)) { - if (!data.compilation.isEmpty()) { - data.compilation += ", "; - } - data.compilation += frame->toString(); - } - - // Rating. - /* There is no standard regarding ratings. Most of the implementations match - a 5 stars rating to a range of 0-255 for MP3. - Match it to baloo rating with a range of 0 - 10 */ - lstID3v2 = Id3Tags->frameListMap()["POPM"]; - for (const auto& frame : qAsConst(lstID3v2)) { - TagLib::ID3v2::PopularimeterFrame *ratingFrame = static_cast(frame); - int rating = ratingFrame->rating(); - if (rating == 0) { - data.rating = 0; - } else if (rating == 1) { - TagLib::String ratingProvider = ratingFrame->email(); - if (ratingProvider == "no@email" || ratingProvider == "org.kde.kfilemetadata") { - data.rating = 1; - } else { - data.rating = 2; - } - } else if (rating >= 1 && rating <= 255) { - data.rating = static_cast(0.032 * rating + 2); - } - } - - // User Text Frame. - lstID3v2 = Id3Tags->frameListMap()["TXXX"]; - if (!lstID3v2.isEmpty()) { - // look for ReplayGain tags - typedef TagLib::ID3v2::UserTextIdentificationFrame IdFrame; - - auto trackGainFrame = IdFrame::find(Id3Tags, "replaygain_track_gain"); - if (trackGainFrame && !trackGainFrame->fieldList().isEmpty()) { - data.replayGainTrackGain = TStringToQString(trackGainFrame->fieldList().back()); + TagLib::AudioProperties* audioProp = file->audioProperties(); + if (audioProp) { + if (audioProp->length()) { + // What about the xml duration? + result->add(Property::Duration, audioProp->length()); } - auto trackPeakFrame = IdFrame::find(Id3Tags, "replaygain_track_peak"); - if (trackPeakFrame && !trackPeakFrame->fieldList().isEmpty()) { - data.replayGainTrackPeak = TStringToQString(trackPeakFrame->fieldList().back()); + if (audioProp->bitrate()) { + result->add(Property::BitRate, audioProp->bitrate() * 1000); } - auto albumGainFrame = IdFrame::find(Id3Tags, "replaygain_album_gain"); - if (albumGainFrame && !albumGainFrame->fieldList().isEmpty()) { - data.replayGainAlbumGain = TStringToQString(albumGainFrame->fieldList().back()); + if (audioProp->channels()) { + result->add(Property::Channels, audioProp->channels()); } - auto albumPeakFrame = IdFrame::find(Id3Tags, "replaygain_album_peak"); - if (albumPeakFrame && !albumPeakFrame->fieldList().isEmpty()) { - data.replayGainAlbumPeak = TStringToQString(albumPeakFrame->fieldList().back()); + if (audioProp->sampleRate()) { + result->add(Property::SampleRate, audioProp->sampleRate()); } } - //TODO handle TIPL tag } -void TagLibExtractor::extractMp4Tags(TagLib::MP4::Tag* mp4Tags, ExtractedData& data) +void TagLibExtractor::readGenericProperties(const TagLib::PropertyMap &savedProperties, ExtractionResult* result) { - if (mp4Tags->isEmpty()) { + if (savedProperties.isEmpty()) { return; } - TagLib::MP4::ItemListMap allTags = mp4Tags->itemListMap(); - - TagLib::MP4::ItemListMap::Iterator itAlbumArtists = allTags.find("aART"); - if (itAlbumArtists != allTags.end()) { - data.albumArtists = itAlbumArtists->second.toStringList().toString(", "); - } - - TagLib::MP4::ItemListMap::Iterator itDiscNumber = allTags.find("disk"); - if (itDiscNumber != allTags.end()) { - data.discNumber = itDiscNumber->second.toInt(); - } - - TagLib::MP4::ItemListMap::Iterator itCompilation = allTags.find("cpil"); - if (itCompilation != allTags.end()) { - data.compilation = itCompilation->second.toStringList().toString(", "); - } - - TagLib::MP4::ItemListMap::Iterator itCopyright = allTags.find("cprt"); - if (itCopyright != allTags.end()) { - data.copyright = itCopyright->second.toStringList().toString(", "); + if (savedProperties.contains("TITLE")) { + result->add(Property::Title, TStringToQString(savedProperties["TITLE"].toString()).trimmed()); } - - TagLib::String genreAtomName(TagLib::String("©gen", TagLib::String::UTF8).to8Bit(), TagLib::String::Latin1); - TagLib::MP4::ItemListMap::Iterator itGenres = allTags.find(genreAtomName); - if (itGenres != allTags.end()) { - data.genres = itGenres->second.toStringList().toString(", "); + if (savedProperties.contains("ALBUM")) { + result->add(Property::Album, TStringToQString(savedProperties["ALBUM"].toString()).trimmed()); } - - TagLib::String composerAtomName(TagLib::String("©wrt", TagLib::String::UTF8).to8Bit(), TagLib::String::Latin1); - TagLib::MP4::ItemListMap::Iterator itComposers = allTags.find(composerAtomName); - if (itComposers != allTags.end()) { - data.composers = itComposers->second.toStringList().toString(", "); + if (savedProperties.contains("COMMENT")) { + result->add(Property::Comment, TStringToQString(savedProperties["COMMENT"].toString()).trimmed()); } - - /* There is no standard regarding ratings. Mimic MediaMonkey's behavior - with a range of 0 to 100 (stored in steps of 10) and make it compatible - with baloo rating with a range from 0 to 10 */ - TagLib::MP4::ItemListMap::Iterator itRating = allTags.find("rate"); - if (itRating != allTags.end()) { - data.rating = itRating->second.toStringList().toString().toInt() / 10; + if (savedProperties.contains("TRACKNUMBER")) { + result->add(Property::TrackNumber, savedProperties["TRACKNUMBER"].toString().toInt()); } - - TagLib::String lyricsAtomName(TagLib::String("©lyr", TagLib::String::UTF8).to8Bit(), TagLib::String::Latin1); - TagLib::MP4::ItemListMap::Iterator itLyrics = allTags.find(lyricsAtomName); - if (itLyrics != allTags.end()) { - data.lyrics = itLyrics->second.toStringList().toString(", "); + if (savedProperties.contains("DATE")) { + result->add(Property::ReleaseYear, savedProperties["DATE"].toString().toInt()); } -} - -void TagLibExtractor::extractApeTags(TagLib::APE::Tag* apeTags, ExtractedData& data) -{ - if (apeTags->isEmpty()) { - return; - } - TagLib::APE::ItemListMap lstApe = apeTags->itemListMap(); - TagLib::APE::ItemListMap::ConstIterator itApe; - - itApe = lstApe.find("ARTIST"); - if (itApe != lstApe.end()) { - if (!data.artists.isEmpty()) { - data.artists += ", "; - } - data.artists += (*itApe).second.toString(); - } - - itApe = lstApe.find("ALBUMARTIST"); - if (itApe == lstApe.end()) { - itApe = lstApe.find("ALBUM ARTIST"); - } - if (itApe != lstApe.end()) { - if(!data.albumArtists.isEmpty()) { - data.albumArtists += ", "; - } - data.albumArtists += (*itApe).second.toString(); + if (savedProperties.contains("OPUS")) { + result->add(Property::Opus, savedProperties["OPUS"].toString().toInt()); } - - itApe = lstApe.find("COMPOSER"); - if (itApe != lstApe.end()) { - if (!data.composers.isEmpty()) { - data.composers += ", "; - } - data.composers += (*itApe).second.toString(); + if (savedProperties.contains("DISCNUMBER")) { + result->add(Property::DiscNumber, savedProperties["DISCNUMBER"].toString().toInt()); } - - itApe = lstApe.find("LYRICIST"); - if (itApe != lstApe.end()) { - if (!data.lyricists.isEmpty()) { - data.lyricists += ", "; - } - data.lyricists += (*itApe).second.toString(); + if (savedProperties.contains("RATING")) { + /* + * There is no standard regarding ratings. Mimic MediaMonkey's behavior + * with a range of 0 to 100 (stored in steps of 10) and make it compatible + * with baloo rating with a range from 0 to 10 + */ + result->add(Property::Rating, savedProperties["RATING"].toString().toInt() / 10); } - - itApe = lstApe.find("GENRE"); - if (itApe != lstApe.end()) { - data.genres.append((*itApe).second.toString()); + if (savedProperties.contains("LOCATION")) { + result->add(Property::Location, TStringToQString(savedProperties["LOCATION"].toString()).trimmed()); } - - itApe = lstApe.find("LOCATION"); - if (itApe != lstApe.end()) { - if (!data.location.isEmpty()) { - data.location += ", "; - } - data.location += (*itApe).second.toString(); + if (savedProperties.contains("LANGUAGE")) { + result->add(Property::Language, TStringToQString(savedProperties["LANGUAGE"].toString()).trimmed()); } - - itApe = lstApe.find("ARRANGER"); - if (itApe != lstApe.end()) { - if (!data.arranger.isEmpty()) { - data.arranger += ", "; - } - data.arranger += (*itApe).second.toString(); + if (savedProperties.contains("LICENSE")) { + result->add(Property::License, TStringToQString(savedProperties["LICENSE"].toString()).trimmed()); } - - itApe = lstApe.find("PERFORMER"); - if (itApe != lstApe.end()) { - if (!data.performer.isEmpty()) { - data.performer += ", "; - } - data.performer += (*itApe).second.toString(); + if (savedProperties.contains("PUBLISHER")) { + result->add(Property::Publisher, TStringToQString(savedProperties["PUBLISHER"].toString()).trimmed()); } - - itApe = lstApe.find("CONDUCTOR"); - if (itApe != lstApe.end()) { - if (!data.conductor.isEmpty()) { - data.conductor += ", "; - } - data.conductor += (*itApe).second.toString(); + if (savedProperties.contains("COPYRIGHT")) { + result->add(Property::Copyright, TStringToQString(savedProperties["COPYRIGHT"].toString()).trimmed()); } - - itApe = lstApe.find("ENSEMBLE"); - if (itApe != lstApe.end()) { - if (!data.ensemble.isEmpty()) { - data.ensemble += ", "; - } - data.ensemble += (*itApe).second.toString(); + if (savedProperties.contains("LABEL")) { + result->add(Property::Label, TStringToQString(savedProperties["LABEL"].toString()).trimmed()); } - - itApe = lstApe.find("PUBLISHER"); - if (itApe != lstApe.end()) { - if (!data.publisher.isEmpty()) { - data.publisher += ", "; - } - data.publisher += (*itApe).second.toString(); + if (savedProperties.contains("ENSEMBLE")) { + result->add(Property::Ensemble, TStringToQString(savedProperties["ENSEMBLE"].toString()).trimmed()); } - - itApe = lstApe.find("COPYRIGHT"); - if (itApe != lstApe.end()) { - if (!data.copyright.isEmpty()) { - data.copyright += ", "; - } - data.copyright += (*itApe).second.toString(); + if (savedProperties.contains("COMPILATION")) { + result->add(Property::Compilation, TStringToQString(savedProperties["COMPILATION"].toString()).trimmed()); } - - itApe = lstApe.find("LABEL"); - if (itApe != lstApe.end()) { - if (!data.label.isEmpty()) { - data.label += ", "; - } - data.label += (*itApe).second.toString(); + if (savedProperties.contains("LYRICS")) { + result->add(Property::Lyrics, TStringToQString(savedProperties["LYRICS"].toString()).trimmed()); } - - itApe = lstApe.find("AUTHOR"); - if (itApe != lstApe.end()) { - if (!data.author.isEmpty()) { - data.author += ", "; + if (savedProperties.contains("ARTIST")) { + const auto artistString = TStringToQString(savedProperties["ARTIST"].toString(";")).trimmed(); + const auto artists = contactsFromString(artistString); + for (const auto& artist : artists) { + result->add(Property::Artist, artist); } - data.author += (*itApe).second.toString(); } - - itApe = lstApe.find("LICENSE"); - if (itApe != lstApe.end()) { - if (!data.license.isEmpty()) { - data.license += ", "; + if (savedProperties.contains("GENRE")) { + const auto genreString = TStringToQString(savedProperties["GENRE"].toString(";")).trimmed(); + const auto genres = contactsFromString(genreString); + for (const auto& genre : genres) { + result->add(Property::Genre, genre); } - data.license += (*itApe).second.toString(); } - - itApe = lstApe.find("LYRICS"); - if (itApe != lstApe.end()) { - if (!data.lyrics.isEmpty()) { - data.lyrics += ", "; + if (savedProperties.contains("ALBUMARTIST")) { + const auto albumArtistsString = TStringToQString(savedProperties["ALBUMARTIST"].toString(";")).trimmed(); + const auto albumArtists = contactsFromString(albumArtistsString); + for (const auto& res : albumArtists) { + result->add(Property::AlbumArtist, res); } - data.lyrics += (*itApe).second.toString(); } - - itApe = lstApe.find("COMPILATION"); - if (itApe != lstApe.end()) { - if (!data.compilation.isEmpty()) { - data.compilation += ", "; + if (savedProperties.contains("COMPOSER")) { + const auto composersString = TStringToQString(savedProperties["COMPOSER"].toString(";")).trimmed(); + const auto composers = contactsFromString(composersString); + for (const auto& comp : composers) { + result->add(Property::Composer, comp); } - data.compilation += (*itApe).second.toString(); } - - itApe = lstApe.find("LANGUAGE"); - if (itApe != lstApe.end()) { - if (!data.language.isEmpty()) { - data.language += ", "; + if (savedProperties.contains("LYRICIST")) { + const auto lyricistsString = TStringToQString(savedProperties["LYRICIST"].toString(";")).trimmed(); + const auto lyricists = contactsFromString(lyricistsString); + for (const auto& lyr : lyricists) { + result->add(Property::Lyricist, lyr); } - data.language += (*itApe).second.toString(); - } - - - itApe = lstApe.find("DISC"); - if (itApe == lstApe.end()) { - itApe = lstApe.find("DISCNUMBER"); - } - if (itApe != lstApe.end()) { - data.discNumber = (*itApe).second.toString().toInt(); - } - - itApe = lstApe.find("OPUS"); - if (itApe != lstApe.end()) { - data.opus = (*itApe).second.toString().toInt(); - } - - itApe = lstApe.find("RATING"); - if (itApe != lstApe.end()) { - /* There is no standard regarding ratings. There is one implementation - most seem to follow with a range of 0 to 100 (stored in steps of 10). - Make it compatible with baloo rating with a range from 0 to 10 */ - data.rating = (*itApe).second.toString().toInt() / 10; - } - - itApe = lstApe.find("REPLAYGAIN_TRACK_GAIN"); - if (itApe != lstApe.end()) { - data.replayGainTrackGain = TStringToQString((*itApe).second.toString()); - } - - itApe = lstApe.find("REPLAYGAIN_TRACK_PEAK"); - if (itApe != lstApe.end()) { - data.replayGainTrackPeak = TStringToQString((*itApe).second.toString()); - } - - itApe = lstApe.find("REPLAYGAIN_ALBUM_GAIN"); - if (itApe != lstApe.end()) { - data.replayGainAlbumGain = TStringToQString((*itApe).second.toString()); } - - itApe = lstApe.find("REPLAYGAIN_ALBUM_PEAK"); - if (itApe != lstApe.end()) { - data.replayGainAlbumPeak = TStringToQString((*itApe).second.toString()); - } -} - -void TagLibExtractor::extractVorbisTags(TagLib::Ogg::XiphComment* vorbisTags, ExtractedData& data) -{ - if (vorbisTags->isEmpty()) { - return; - } - TagLib::Ogg::FieldListMap lstOgg = vorbisTags->fieldListMap(); - TagLib::Ogg::FieldListMap::ConstIterator itOgg; - - itOgg = lstOgg.find("ARTIST"); - if (itOgg != lstOgg.end()) { - if (!data.artists.isEmpty()) { - data.artists += ", "; + if (savedProperties.contains("CONDUCTOR")) { + const auto conductorString = TStringToQString(savedProperties["CONDUCTOR"].toString(";")).trimmed(); + const auto conductors = contactsFromString(conductorString); + for (const auto& con: conductors) { + result->add(Property::Conductor, con); } - data.artists += (*itOgg).second.toString(", "); - } - - itOgg = lstOgg.find("ALBUMARTIST"); - if (itOgg == lstOgg.end()) { - itOgg = lstOgg.find("ALBUM ARTIST"); } - if (itOgg != lstOgg.end()) { - if (!data.albumArtists.isEmpty()) { - data.albumArtists += ", "; + if (savedProperties.contains("ARRANGER")) { + const auto arrangerString = TStringToQString(savedProperties["ARRANGER"].toString(";")).trimmed(); + const auto arrangers = contactsFromString(arrangerString); + for (const auto& arr: arrangers) { + result->add(Property::Arranger, arr); } - data.albumArtists += (*itOgg).second.toString(", "); } - - itOgg = lstOgg.find("COMPOSER"); - if (itOgg != lstOgg.end()) { - if (!data.composers.isEmpty()) { - data.composers += ", "; + if (savedProperties.contains("PERFORMER")) { + const auto performersString = TStringToQString(savedProperties["PERFORMER"].toString(";")).trimmed(); + const auto performers = contactsFromString(performersString); + for (const auto& per: performers) { + result->add(Property::Performer, per); } - data.composers += (*itOgg).second.toString(", "); } - - itOgg = lstOgg.find("LYRICIST"); - if (itOgg != lstOgg.end()) { - if (!data.lyricists.isEmpty()) { - data.lyricists += ", "; + if (savedProperties.contains("AUTHOR")) { + const auto authorString = TStringToQString(savedProperties["AUTHOR"].toString(";")).trimmed(); + const auto authors = contactsFromString(authorString); + for (const auto& aut: authors) { + result->add(Property::Author, aut); } - data.lyricists += (*itOgg).second.toString(", "); } - itOgg = lstOgg.find("LOCATION"); - if (itOgg != lstOgg.end()) { - if (!data.location.isEmpty()) { - data.location += ", "; + if (savedProperties.contains("REPLAYGAIN_TRACK_GAIN")) { + auto trackGainString = TStringToQString(savedProperties["REPLAYGAIN_TRACK_GAIN"].toString(";")).trimmed(); + // remove " dB" suffix + if (trackGainString.endsWith(QStringLiteral(" dB"), Qt::CaseInsensitive)) { + trackGainString.chop(3); } - data.location += (*itOgg).second.toString(", "); - } - - itOgg = lstOgg.find("ARRANGER"); - if (itOgg != lstOgg.end()) { - if (!data.arranger.isEmpty()) { - data.arranger += ", "; + bool success = false; + double replayGainTrackGain = trackGainString.toDouble(&success); + if (success) { + result->add(Property::ReplayGainTrackGain, replayGainTrackGain); } - data.arranger += (*itOgg).second.toString(", "); } - - itOgg = lstOgg.find("PERFORMER"); - if (itOgg != lstOgg.end()) { - if (!data.performer.isEmpty()) { - data.performer += ", "; + if (savedProperties.contains("REPLAYGAIN_ALBUM_GAIN")) { + auto albumGainString = TStringToQString(savedProperties["REPLAYGAIN_ALBUM_GAIN"].toString(";")).trimmed(); + // remove " dB" suffix + if (albumGainString.endsWith(QStringLiteral(" dB"), Qt::CaseInsensitive)) { + albumGainString.chop(3); } - data.performer += (*itOgg).second.toString(", "); - } - - itOgg = lstOgg.find("CONDUCTOR"); - if (itOgg != lstOgg.end()) { - if (!data.conductor.isEmpty()) { - data.conductor += ", "; + bool success = false; + double replayGainAlbumGain = albumGainString.toDouble(&success); + if (success) { + result->add(Property::ReplayGainAlbumGain, replayGainAlbumGain); } - data.conductor += (*itOgg).second.toString(", "); } - - itOgg = lstOgg.find("ENSEMBLE"); - if (itOgg != lstOgg.end()) { - if (!data.ensemble.isEmpty()) { - data.ensemble += ", "; + if (savedProperties.contains("REPLAYGAIN_TRACK_PEAK")) { + auto trackPeakString = TStringToQString(savedProperties["REPLAYGAIN_TRACK_PEAK"].toString(";")).trimmed(); + bool success = false; + double replayGainTrackPeak = trackPeakString.toDouble(&success); + if (success) { + result->add(Property::ReplayGainTrackPeak, replayGainTrackPeak); } - data.ensemble += (*itOgg).second.toString(", "); } - - itOgg = lstOgg.find("PUBLISHER"); - if (itOgg != lstOgg.end()) { - if (!data.publisher.isEmpty()) { - data.publisher += ", "; + if (savedProperties.contains("REPLAYGAIN_ALBUM_PEAK")) { + auto albumPeakString = TStringToQString(savedProperties["REPLAYGAIN_ALBUM_PEAK"].toString(";")).trimmed(); + bool success = false; + double replayGainAlbumPeak = albumPeakString.toDouble(&success); + if (success) { + result->add(Property::ReplayGainAlbumPeak, replayGainAlbumPeak); } - data.publisher += (*itOgg).second.toString(", "); - } - - itOgg = lstOgg.find("COPYRIGHT"); - if (itOgg != lstOgg.end()) { - if (!data.copyright.isEmpty()) { - data.copyright += ", "; - } - data.copyright += (*itOgg).second.toString(", "); - } - - itOgg = lstOgg.find("LABEL"); - if (itOgg != lstOgg.end()) { - if (!data.label.isEmpty()) { - data.label += ", "; - } - data.label += (*itOgg).second.toString(", "); - } - - itOgg = lstOgg.find("AUTHOR"); - if (itOgg != lstOgg.end()) { - if (!data.author.isEmpty()) { - data.author += ", "; - } - data.author += (*itOgg).second.toString(", "); } +} - itOgg = lstOgg.find("LICENSE"); - if (itOgg != lstOgg.end()) { - if (!data.license.isEmpty()) { - data.license += ", "; - } - data.license += (*itOgg).second.toString(", "); +void TagLibExtractor::extractId3Tags(TagLib::ID3v2::Tag* Id3Tags, ExtractionResult* result) +{ + if (Id3Tags->isEmpty()) { + return; } + TagLib::ID3v2::FrameList lstID3v2; - itOgg = lstOgg.find("LYRICS"); - if (itOgg != lstOgg.end()) { - if (!data.lyrics.isEmpty()) { - data.lyrics += ", "; - } - data.lyrics += (*itOgg).second.toString(", "); + /* + * Publisher. + * Special handling because TagLib::PropertyMap maps "TPUB" to "LABEL" + * Insert manually for Publisher. + */ + lstID3v2 = Id3Tags->frameListMap()["TPUB"]; + if (!lstID3v2.isEmpty()) { + result->add(Property::Publisher, TStringToQString(lstID3v2.front()->toString())); } - itOgg = lstOgg.find("COMPILATION"); - if (itOgg != lstOgg.end()) { - if (!data.compilation.isEmpty()) { - data.compilation += ", "; - } - data.compilation += (*itOgg).second.toString(", "); + // Compilation. + lstID3v2 = Id3Tags->frameListMap()["TCMP"]; + if (!lstID3v2.isEmpty()) { + result->add(Property::Compilation, TStringToQString(lstID3v2.front()->toString())); } - itOgg = lstOgg.find("LANGUAGE"); - if (itOgg != lstOgg.end()) { - if (!data.language.isEmpty()) { - data.language += ", "; + /* + * Rating. + * There is no standard regarding ratings. Most of the implementations match + * a 5 stars rating to a range of 0-255 for MP3. + * Map it to baloo rating with a range of 0 - 10. + */ + lstID3v2 = Id3Tags->frameListMap()["POPM"]; + if (!lstID3v2.isEmpty()) { + TagLib::ID3v2::PopularimeterFrame *ratingFrame = static_cast(lstID3v2.front()); + int rating = ratingFrame->rating(); + if (rating == 0) { + rating = 0; + } else if (rating == 1) { + TagLib::String ratingProvider = ratingFrame->email(); + if (ratingProvider == "no@email" || ratingProvider == "org.kde.kfilemetadata") { + rating = 1; + } else { + rating = 2; + } + } else if (rating >= 1 && rating <= 255) { + rating = static_cast(0.032 * rating + 2); } - data.language += (*itOgg).second.toString(", "); - } - - itOgg = lstOgg.find("GENRE"); - if (itOgg != lstOgg.end()) { - data.genres.append((*itOgg).second); - } - - itOgg = lstOgg.find("DISCNUMBER"); - if (itOgg != lstOgg.end()) { - data.discNumber = (*itOgg).second.toString("").toInt(); - } - - itOgg = lstOgg.find("OPUS"); - if (itOgg != lstOgg.end()) { - data.opus = (*itOgg).second.toString("").toInt(); - } - - itOgg = lstOgg.find("RATING"); - if (itOgg != lstOgg.end()) { - //there is no standard regarding ratings. There is one implementation - //most seem to follow with a range of 0 to 100 (stored in steps of 10). - //make it compatible with baloo rating with a range from 0 to 10 - data.rating = (*itOgg).second.toString("").toInt() / 10; - } - - itOgg = lstOgg.find("REPLAYGAIN_TRACK_GAIN"); - if (itOgg != lstOgg.end()) { - data.replayGainTrackGain = TStringToQString((*itOgg).second.toString("")); - } - - itOgg = lstOgg.find("REPLAYGAIN_TRACK_PEAK"); - if (itOgg != lstOgg.end()) { - data.replayGainTrackPeak = TStringToQString((*itOgg).second.toString("")); + result->add(Property::Rating, rating); } +} - itOgg = lstOgg.find("REPLAYGAIN_ALBUM_GAIN"); - if (itOgg != lstOgg.end()) { - data.replayGainAlbumGain = TStringToQString((*itOgg).second.toString("")); +void TagLibExtractor::extractMp4Tags(TagLib::MP4::Tag* mp4Tags, ExtractionResult* result) +{ + if (mp4Tags->isEmpty()) { + return; } + TagLib::MP4::ItemListMap allTags = mp4Tags->itemListMap(); - itOgg = lstOgg.find("REPLAYGAIN_ALBUM_PEAK"); - if (itOgg != lstOgg.end()) { - data.replayGainAlbumPeak = TStringToQString((*itOgg).second.toString("")); + /* + * There is no standard regarding ratings. Mimic MediaMonkey's behavior + * with a range of 0 to 100 (stored in steps of 10) and make it compatible + * with baloo rating with a range from 0 to 10. + */ + TagLib::MP4::ItemListMap::Iterator itRating = allTags.find("rate"); + if (itRating != allTags.end()) { + result->add(Property::Rating, itRating->second.toStringList().toString().toInt() / 10); } } -void TagLibExtractor::extractAsfTags(TagLib::ASF::Tag* asfTags, ExtractedData& data) +void TagLibExtractor::extractAsfTags(TagLib::ASF::Tag* asfTags, ExtractionResult* result) { if (asfTags->isEmpty()) { return; } - if (!asfTags->copyright().isEmpty()) { - data.copyright = asfTags->copyright(); - } - TagLib::ASF::AttributeList lstASF = asfTags->attribute("WM/SharedUserRating"); if (!lstASF.isEmpty()) { int rating = lstASF.front().toString().toInt(); - //map the rating values of WMP to Baloo rating - //0->0, 1->2, 25->4, 50->6, 75->8, 99->10 + /* + * Map the rating values of WMP to Baloo rating. + * 0->0, 1->2, 25->4, 50->6, 75->8, 99->10 + */ if (rating == 0) { - data.rating = 0; + rating = 0; } else if (rating == 1) { - data.rating = 2; + rating = 2; } else { - data.rating = static_cast(0.09 * rating + 2); + rating = static_cast(0.09 * rating + 2); } + result->add(Property::Rating, rating); } - lstASF = asfTags->attribute("WM/PartOfSet"); + lstASF = asfTags->attribute("Author"); if (!lstASF.isEmpty()) { - data.discNumber = lstASF.front().toString().toInt(); - } - - lstASF = asfTags->attribute("WM/AlbumArtist"); - for (const auto& attribute : qAsConst(lstASF)) { - if (!data.albumArtists.isEmpty()) { - data.albumArtists += ", "; - } - data.albumArtists += attribute.toString(); - } - - lstASF = asfTags->attribute("WM/Composer"); - for (const auto& attribute : qAsConst(lstASF)) { - if (!data.composers.isEmpty()) { - data.composers += ", "; + const auto attribute = lstASF.front(); + const auto authors = contactsFromString(TStringToQString(attribute.toString()).trimmed()); + for (const auto& aut: authors) { + result->add(Property::Author, aut); } - data.composers += attribute.toString(); - } - - lstASF = asfTags->attribute("WM/Conductor"); - for (const auto& attribute : qAsConst(lstASF)) { - if (!data.conductor.isEmpty()) { - data.conductor += ", "; - } - data.conductor += attribute.toString(); } + // Lyricist is called "WRITER" for wma/asf files lstASF = asfTags->attribute("WM/Writer"); - for (const auto& attribute : qAsConst(lstASF)) { - if (!data.lyricists.isEmpty()) { - data.lyricists += ", "; + if (!lstASF.isEmpty()) { + const auto attribute = lstASF.front(); + const auto lyricists = contactsFromString(TStringToQString(attribute.toString()).trimmed()); + for (const auto& lyr : lyricists) { + result->add(Property::Lyricist, lyr); } - data.lyricists += attribute.toString(); } + /* + * TagLib exports "WM/PUBLISHER" as "LABEL" in the PropertyMap, + * add it manually to Publisher. + */ lstASF = asfTags->attribute("WM/Publisher"); - for (const auto& attribute : qAsConst(lstASF)) { - if (!data.publisher.isEmpty()) { - data.publisher += ", "; - } - data.publisher += attribute.toString(); - } - - lstASF = asfTags->attribute("Author"); - for (const auto& attribute : qAsConst(lstASF)) { - if (!data.author.isEmpty()) { - data.author += ", "; - } - data.author += attribute.toString(); + if (!lstASF.isEmpty()) { + const auto attribute = lstASF.front(); + result->add(Property::Publisher, TStringToQString(attribute.toString()).trimmed()); } } void TagLibExtractor::extract(ExtractionResult* result) { const QString fileUrl = result->inputUrl(); const QString mimeType = result->inputMimetype(); // Open the file readonly. Important if we're sandboxed. TagLib::FileStream stream(fileUrl.toUtf8().constData(), true); if (!stream.isOpen()) { qWarning() << "Unable to open file readonly: " << fileUrl; return; } - TagLib::FileRef file(&stream, true); - if (file.isNull()) { - qWarning() << "Unable to open file: " << fileUrl; - return; - } - - TagLib::Tag* tags = file.tag(); - result->addType(Type::Audio); - - ExtractedData data; - if ((mimeType == QLatin1String("audio/mpeg")) || (mimeType == QLatin1String("audio/mpeg3")) || (mimeType == QLatin1String("audio/x-mpeg"))) { - TagLib::MPEG::File mpegFile(&stream, TagLib::ID3v2::FrameFactory::instance(), true); - if (mpegFile.hasID3v2Tag()) { - extractId3Tags(mpegFile.ID3v2Tag(), data); - } - } else if ((mimeType == QLatin1String("audio/x-aiff")) || (mimeType == QLatin1String("audio/wav")) - || (mimeType == QLatin1String("audio/x-wav"))) { - /* For some reason, TagLib::RIFF::AIFF::File and TagLib::RIFF::WAV::File tag() return - * only an invalid pointer. Use the dynamic_cast instead. */ - TagLib::ID3v2::Tag* ID3v2Tag = dynamic_cast(tags); - if (ID3v2Tag) { - extractId3Tags(ID3v2Tag, data); - } - } else if (mimeType == QLatin1String("audio/mp4")) { - TagLib::MP4::File mp4File(&stream, true); - if (mp4File.hasMP4Tag()) { - extractMp4Tags(mp4File.tag(), data); + TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); + if (file.hasID3v2Tag()) { + extractId3Tags(file.ID3v2Tag(), result); + } + } else if (mimeType == QLatin1String("audio/x-aiff")) { + TagLib::RIFF::AIFF::File file(&stream, true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); + if (file.hasID3v2Tag()) { + extractId3Tags(file.tag(), result); + } + } else if ((mimeType == QLatin1String("audio/wav")) || (mimeType == QLatin1String("audio/x-wav"))) { + TagLib::RIFF::WAV::File file(&stream, true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); + if (file.hasID3v2Tag()) { + extractId3Tags(file.tag(), result); } } else if (mimeType == QLatin1String("audio/x-musepack")) { - TagLib::MPC::File mpcFile(&stream, true); - if (mpcFile.hasAPETag()) { - extractApeTags(mpcFile.APETag(), data); - } + TagLib::MPC::File file(&stream, true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); } else if (mimeType == QLatin1String("audio/x-ape")) { - TagLib::APE::File apeFile(&stream, true); - if (apeFile.hasAPETag()) { - extractApeTags(apeFile.APETag(), data); - } + TagLib::APE::File file(&stream, true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); } else if (mimeType == QLatin1String("audio/x-wavpack")) { - TagLib::WavPack::File wavpackFile(&stream, true); - if (wavpackFile.hasAPETag()) { - extractApeTags(wavpackFile.APETag(), data); - } + TagLib::WavPack::File file(&stream, true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); + } else if (mimeType == QLatin1String("audio/mp4")) { + TagLib::MP4::File file(&stream, true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); + extractMp4Tags(file.tag(), result); } else if (mimeType == QLatin1String("audio/flac")) { - TagLib::FLAC::File flacFile(&stream, TagLib::ID3v2::FrameFactory::instance(), true); - if (flacFile.hasXiphComment()) { - extractVorbisTags(flacFile.xiphComment(), data); - } + TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) { - TagLib::Ogg::Vorbis::File oggFile(&stream, true); - if (oggFile.tag()) { - extractVorbisTags(oggFile.tag(), data); - } + TagLib::Ogg::Vorbis::File file(&stream, true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); } else if (mimeType == QLatin1String("audio/opus") || mimeType == QLatin1String("audio/x-opus+ogg")) { - TagLib::Ogg::Opus::File opusFile(&stream, true); - if (opusFile.tag()) { - extractVorbisTags(opusFile.tag(), data); - } + TagLib::Ogg::Opus::File file(&stream, true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); } else if (mimeType == QLatin1String("audio/speex") || mimeType == QLatin1String("audio/x-speex")) { - TagLib::Ogg::Speex::File speexFile(&stream, true); - if (speexFile.tag()) { - extractVorbisTags(speexFile.tag(), data); - } + TagLib::Ogg::Speex::File file(&stream, true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); } else if (mimeType == QLatin1String("audio/x-ms-wma")) { - /* For some reason, TagLib::ASF::File tag() returns only an invalid pointer. - * Use the dynamic_cast instead. */ - TagLib::ASF::Tag* asfTags = dynamic_cast(tags); - if (asfTags) { - extractAsfTags(asfTags, data); - } + TagLib::ASF::File file(&stream, true); + extractAudioProperties(&file, result); + readGenericProperties(file.properties(), result); + extractAsfTags(file.tag(), result); } - if (!tags->isEmpty()) { - QString title = TStringToQString(tags->title()); - if (!title.isEmpty()) { - result->add(Property::Title, title); - } - - QString comment = TStringToQString(tags->comment()); - if (!comment.isEmpty()) { - result->add(Property::Comment, comment); - } - - if (data.genres.isEmpty()) { - data.genres.append(tags->genre()); - } - - for (uint i = 0; i < data.genres.size(); i++) { - QString genre = TStringToQString(data.genres[i]).trimmed(); - if (!genre.isEmpty()) { - // Convert from int - bool ok = false; - int genreNum = genre.toInt(&ok); - if (ok) { - genre = TStringToQString(TagLib::ID3v1::genre(genreNum)); - } - result->add(Property::Genre, genre); - } - } - - const auto artistString = data.artists.isEmpty() - ? TStringToQString(tags->artist()) - : TStringToQString(data.artists).trimmed(); - const auto artists = contactsFromString(artistString); - for (auto& artist : artists) { - result->add(Property::Artist, artist); - } - - const auto composersString = TStringToQString(data.composers).trimmed(); - const auto composers = contactsFromString(composersString); - for (auto& comp : composers) { - result->add(Property::Composer, comp); - } - - const auto lyricistsString = TStringToQString(data.lyricists).trimmed(); - const auto lyricists = contactsFromString(lyricistsString); - for (auto& lyr : lyricists) { - result->add(Property::Lyricist, lyr); - } - - const auto album = TStringToQString(tags->album()); - if (!album.isEmpty()) { - result->add(Property::Album, album); - - const auto albumArtistsString = TStringToQString(data.albumArtists).trimmed(); - const auto albumArtists = contactsFromString(albumArtistsString); - for (auto& res : albumArtists) { - result->add(Property::AlbumArtist, res); - } - } - - if (tags->track()) { - result->add(Property::TrackNumber, tags->track()); - } - - if (tags->year()) { - result->add(Property::ReleaseYear, tags->year()); - } - - QString locationsString = TStringToQString(data.location).trimmed(); - QStringList locations = contactsFromString(locationsString); - foreach(const QString& loc, locations) { - result->add(Property::Location, loc); - } - - QString performersString = TStringToQString(data.performer).trimmed(); - QStringList performers = contactsFromString(performersString); - foreach(const QString& per, performers) { - result->add(Property::Performer, per); - } - - QString ensembleString = TStringToQString(data.ensemble).trimmed(); - QStringList ensembles = contactsFromString(ensembleString); - foreach(const QString& ens, ensembles) { - result->add(Property::Ensemble, ens); - } - - QString arrangerString = TStringToQString(data.arranger).trimmed(); - QStringList arrangers = contactsFromString(arrangerString); - foreach(const QString& arr, arrangers) { - result->add(Property::Arranger, arr); - } - - QString conductorString = TStringToQString(data.conductor).trimmed(); - QStringList conductors = contactsFromString(conductorString); - foreach(const QString& arr, conductors) { - result->add(Property::Conductor, arr); - } - - QString publisherString = TStringToQString(data.publisher).trimmed(); - QStringList publishers = contactsFromString(publisherString); - foreach(const QString& arr, publishers) { - result->add(Property::Publisher, arr); - } - - QString copyrightString = TStringToQString(data.copyright).trimmed(); - QStringList copyrights = contactsFromString(copyrightString); - foreach(const QString& arr, copyrights) { - result->add(Property::Copyright, arr); - } - - QString labelString = TStringToQString(data.label).trimmed(); - QStringList labels = contactsFromString(labelString); - foreach(const QString& arr, labels) { - result->add(Property::Label, arr); - } - - QString authorString = TStringToQString(data.author).trimmed(); - QStringList authors = contactsFromString(authorString); - foreach(const QString& arr, authors) { - result->add(Property::Author, arr); - } - - QString languageString = TStringToQString(data.language).trimmed(); - QStringList languages = contactsFromString(languageString); - foreach(const QString& arr, languages) { - result->add(Property::Language, arr); - } - - QString licenseString = TStringToQString(data.license).trimmed(); - QStringList licenses = contactsFromString(licenseString); - foreach(const QString& arr, licenses) { - result->add(Property::License, arr); - } - - QString compilationString = TStringToQString(data.compilation).trimmed(); - QStringList compilations = contactsFromString(compilationString); - foreach(const QString& arr, compilations) { - result->add(Property::Compilation, arr); - } - - QString lyricsString = TStringToQString(data.lyrics).trimmed(); - if (!lyricsString.isEmpty()) { - result->add(Property::Lyrics, lyricsString); - } - - if (data.opus.isValid()) { - result->add(Property::Opus, data.opus); - } - - if (data.discNumber.isValid()) { - result->add(Property::DiscNumber, data.discNumber); - } - - if (data.rating.isValid()) { - result->add(Property::Rating, data.rating); - } - - if (!data.replayGainAlbumGain.isEmpty()) { - /* remove " dB" suffix */ - if (data.replayGainAlbumGain.endsWith(QStringLiteral(" dB"), Qt::CaseInsensitive)) - { - data.replayGainAlbumGain.chop(3); - } - bool success = false; - double replayGainAlbumGain = data.replayGainAlbumGain.toDouble(&success); - if (success) { - result->add(Property::ReplayGainAlbumGain, replayGainAlbumGain); - } - } - - if (!data.replayGainAlbumPeak.isEmpty()) { - bool success = false; - double replayGainAlbumPeak = data.replayGainAlbumPeak.toDouble(&success); - if (success) { - result->add(Property::ReplayGainAlbumPeak, replayGainAlbumPeak); - } - } - - if (!data.replayGainTrackGain.isEmpty()) { - /* remove " dB" suffix */ - if (data.replayGainTrackGain.endsWith(QStringLiteral(" dB"), Qt::CaseInsensitive)) - { - data.replayGainTrackGain.chop(3); - } - bool success = false; - double replayGainTrackGain = data.replayGainTrackGain.toDouble(&success); - if (success) { - result->add(Property::ReplayGainTrackGain, replayGainTrackGain); - } - } - - if (!data.replayGainTrackPeak.isEmpty()) { - bool success = false; - double replayGainTrackPeak = data.replayGainTrackPeak.toDouble(&success); - if (success) { - result->add(Property::ReplayGainTrackPeak, replayGainTrackPeak); - } - } - } - - TagLib::AudioProperties* audioProp = file.audioProperties(); - if (audioProp) { - if (audioProp->length()) { - // What about the xml duration? - result->add(Property::Duration, audioProp->length()); - } - - if (audioProp->bitrate()) { - result->add(Property::BitRate, audioProp->bitrate() * 1000); - } - - if (audioProp->channels()) { - result->add(Property::Channels, audioProp->channels()); - } - - if (audioProp->sampleRate()) { - result->add(Property::SampleRate, audioProp->sampleRate()); - } - } + result->addType(Type::Audio); } // TAG information (incomplete). // https://xiph.org/vorbis/doc/v-comment.html // https://help.mp3tag.de/main_tags.html // http://id3.org/ // https://www.legroom.net/2009/05/09/ogg-vorbis-and-flac-comment-field-recommendations // https://kodi.wiki/view/Music_tagging#Tags_Kodi_reads // https://wiki.hydrogenaud.io/index.php?title=Tag_Mapping // https://picard.musicbrainz.org/docs/mappings/ // -- FLAC/OGG -- // Artist: ARTIST, PERFORMER // Album artist: ALBUMARTIST // Composer: COMPOSER // Lyricist: LYRICIST // Conductor: CONDUCTOR // Disc number: DISCNUMBER // Total discs: TOTALDISCS, DISCTOTAL // Track number: TRACKNUMBER // Total tracks: TOTALTRACKS, TRACKTOTAL // Genre: GENRE // -- ID3v2 -- // Artist: TPE1 // Album artist: TPE2 // Composer: TCOM // Lyricist: TEXT // Conductor: TPE3 // Disc number[/total dics]: TPOS // Track number[/total tracks]: TRCK // Genre: TCON diff --git a/src/extractors/taglibextractor.h b/src/extractors/taglibextractor.h index 6ce4019..035fed5 100644 --- a/src/extractors/taglibextractor.h +++ b/src/extractors/taglibextractor.h @@ -1,100 +1,66 @@ /* Copyright (C) 2012 Vishesh Handa This library 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 2.1 of the License, or (at your option) any later version. This library 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 library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef TAGLIBEXTRACTOR_H #define TAGLIBEXTRACTOR_H #include "extractorplugin.h" -#include -#include namespace TagLib { namespace ASF { class Tag; } namespace ID3v2 { class Tag; } namespace MP4 { class Tag; } - namespace APE { - class Tag; - } - namespace Ogg { - class XiphComment; - } + class PropertyMap; } namespace KFileMetaData { class TagLibExtractor : public ExtractorPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kf5.kfilemetadata.ExtractorPlugin" FILE "taglibextractor.json") Q_INTERFACES(KFileMetaData::ExtractorPlugin) public: explicit TagLibExtractor(QObject* parent = nullptr); void extract(ExtractionResult* result) override; QStringList mimetypes() const override; private: - struct ExtractedData { - TagLib::String artists; - TagLib::String albumArtists; - TagLib::String composers; - TagLib::String lyricists; - TagLib::String location; - TagLib::String performer; - TagLib::String conductor; - TagLib::String copyright; - TagLib::String ensemble; - TagLib::String arranger; - TagLib::String language; - TagLib::String publisher; - TagLib::String label; - TagLib::String author; - TagLib::String license; - TagLib::String lyrics; - TagLib::String compilation; - TagLib::StringList genres; - QString replayGainAlbumGain; - QString replayGainAlbumPeak; - QString replayGainTrackGain; - QString replayGainTrackPeak; - QVariant discNumber; - QVariant opus; - QVariant rating; - }; - void extractId3Tags(TagLib::ID3v2::Tag* id3Tags, ExtractedData& data); - void extractMp4Tags(TagLib::MP4::Tag* mp4Tags, ExtractedData& data); - void extractApeTags(TagLib::APE::Tag* apeTags, ExtractedData& data); - void extractVorbisTags(TagLib::Ogg::XiphComment* vorbisTags, ExtractedData& data); - void extractAsfTags(TagLib::ASF::Tag* asfTags, ExtractedData& data); + + void extractId3Tags(TagLib::ID3v2::Tag* Id3Tags, ExtractionResult* result); + void extractMp4Tags(TagLib::MP4::Tag* mp4Tags, ExtractionResult* result); + void extractAsfTags(TagLib::ASF::Tag* asfTags, ExtractionResult* result); + void readGenericProperties(const TagLib::PropertyMap &savedProperties, ExtractionResult* result); }; } #endif // TAGLIBEXTRACTOR_H