From 044dbf5f23fbbd0af0e3b354dfad1eacc1241173 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 5 Oct 2021 21:34:19 +0200 Subject: [PATCH] Implement loading chapters from mpeg files --- CMakeLists.txt | 1 + README.md | 1 + src/CMakeLists.txt | 2 +- src/models/chaptermodel.cpp | 62 ++++++++++++++++++++++++++++++++++--- src/models/chaptermodel.h | 11 +++++++ src/qml/EntryPage.qml | 1 + src/qml/PlayerControls.qml | 1 + 7 files changed, 74 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e1799a51..d7f6efa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ ecm_setup_version(${PROJECT_VERSION} find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Quick Test Gui QuickControls2 Sql Multimedia) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS CoreAddons Syndication Config I18n) +find_package(Taglib REQUIRED) if (ANDROID) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Svg) diff --git a/README.md b/README.md index 20b1c98f..84fb4f96 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Note: When using versions of kasts built from git-master, it's possible that the - KConfig - Kirigami - Syndication + - TagLib ## Linux diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e7ade1e0..0a2861d0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -122,7 +122,7 @@ add_executable(kasts ${SRCS}) kconfig_add_kcfg_files(kasts settingsmanager.kcfgc GENERATE_MOC) target_include_directories(kasts PRIVATE ${CMAKE_BINARY_DIR}) -target_link_libraries(kasts PRIVATE Qt::Core Qt::Qml Qt::Quick Qt::QuickControls2 Qt::Sql Qt::Multimedia KF5::Syndication KF5::CoreAddons KF5::ConfigGui KF5::I18n) +target_link_libraries(kasts PRIVATE Qt::Core Qt::Qml Qt::Quick Qt::QuickControls2 Qt::Sql Qt::Multimedia KF5::Syndication KF5::CoreAddons KF5::ConfigGui KF5::I18n Taglib::Taglib) if(ANDROID) target_link_libraries(kasts PRIVATE diff --git a/src/models/chaptermodel.cpp b/src/models/chaptermodel.cpp index c1bfbed5..7b9bcfad 100644 --- a/src/models/chaptermodel.cpp +++ b/src/models/chaptermodel.cpp @@ -7,9 +7,12 @@ #include "models/chaptermodel.h" #include +#include #include #include +#include + #include "database.h" ChapterModel::ChapterModel(QObject *parent) @@ -69,15 +72,24 @@ QString ChapterModel::enclosureId() const void ChapterModel::setEnclosureId(QString newEnclosureId) { m_enclosureId = newEnclosureId; - loadFromDatabase(); + load(); Q_EMIT enclosureIdChanged(); } -void ChapterModel::loadFromDatabase() +void ChapterModel::load() { beginResetModel(); m_chapters = {}; + loadFromDatabase(); + if (m_chapters.isEmpty()) { + loadChaptersFromFile(); + } + endResetModel(); +} + +void ChapterModel::loadFromDatabase() +{ QSqlQuery query; query.prepare(QStringLiteral("SELECT * FROM Chapters WHERE id=:id")); query.bindValue(QStringLiteral(":id"), enclosureId()); @@ -90,6 +102,48 @@ void ChapterModel::loadFromDatabase() chapter.start = query.value(QStringLiteral("start")).toInt(); m_chapters << chapter; } - - endResetModel(); +} + +void ChapterModel::loadMPEGChapters(TagLib::MPEG::File &f) +{ + if (!f.hasID3v2Tag()) { + return; + } + for (const auto &frame : f.ID3v2Tag()->frameListMap()["CHAP"]) { + auto chapterFrame = dynamic_cast(frame); + + ChapterEntry chapter{}; + chapter.title = QString::fromStdString(chapterFrame->embeddedFrameListMap()["TIT2"].front()->toString().to8Bit(true)); + chapter.link = QString(); + chapter.image = QString(); + chapter.start = chapterFrame->startTime() / 1000; + m_chapters << chapter; + } + std::sort(m_chapters.begin(), m_chapters.end(), [](const ChapterEntry &a, const ChapterEntry &b) { + return a.start < b.start; + }); +} + +void ChapterModel::loadChaptersFromFile() +{ + if (m_enclosurePath.isEmpty()) { + return; + } + const auto mime = QMimeDatabase().mimeTypeForFile(m_enclosurePath).name(); + if (mime == QStringLiteral("audio/mpeg")) { + TagLib::MPEG::File f(m_enclosurePath.toLatin1().data()); + loadMPEGChapters(f); + } // TODO else... +} + +void ChapterModel::setEnclosurePath(const QString &enclosurePath) +{ + m_enclosurePath = enclosurePath; + Q_EMIT enclosureIdChanged(); + load(); +} + +QString ChapterModel::enclosurePath() const +{ + return m_enclosurePath; } diff --git a/src/models/chaptermodel.h b/src/models/chaptermodel.h index fab1d681..0b8a6a62 100644 --- a/src/models/chaptermodel.h +++ b/src/models/chaptermodel.h @@ -9,6 +9,8 @@ #include #include +#include + struct ChapterEntry { QString title; QString link; @@ -21,6 +23,7 @@ class ChapterModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(QString enclosureId READ enclosureId WRITE setEnclosureId NOTIFY enclosureIdChanged) + Q_PROPERTY(QString enclosurePath READ enclosurePath WRITE setEnclosurePath NOTIFY enclosurePathChanged) public: enum RoleNames { @@ -40,13 +43,21 @@ public: void setEnclosureId(QString newEnclosureId); QString enclosureId() const; + void setEnclosurePath(const QString &enclosurePath); + QString enclosurePath() const; + Q_SIGNALS: void enclosureIdChanged(); + void enclosurePathChanged(); private: + void load(); void loadFromDatabase(); + void loadChaptersFromFile(); + void loadMPEGChapters(TagLib::MPEG::File &f); QString m_enclosureId; QVector m_chapters; KFormat m_kformat; + QString m_enclosurePath; }; diff --git a/src/qml/EntryPage.qml b/src/qml/EntryPage.qml index 2d1894b2..4b56abce 100644 --- a/src/qml/EntryPage.qml +++ b/src/qml/EntryPage.qml @@ -97,6 +97,7 @@ Kirigami.ScrollablePage { Layout.bottomMargin: Kirigami.Units.gridUnit model: ChapterModel { enclosureId: entry.id + enclosurePath: entry.enclosure.path } delegate: ChapterListDelegate { entry: page.entry diff --git a/src/qml/PlayerControls.qml b/src/qml/PlayerControls.qml index 2316a31c..9b8d38db 100644 --- a/src/qml/PlayerControls.qml +++ b/src/qml/PlayerControls.qml @@ -123,6 +123,7 @@ Kirigami.Page { id: chapterList model: ChapterModel { enclosureId: AudioManager.entry.id + enclosurePath: AudioManager.entry.enclosure.path } clip: true visible: chapterList.count !== 0