diff --git a/src/monitor/monitorproxy.cpp b/src/monitor/monitorproxy.cpp index 0df165a78..973919c4a 100644 --- a/src/monitor/monitorproxy.cpp +++ b/src/monitor/monitorproxy.cpp @@ -1,317 +1,317 @@ /*************************************************************************** * Copyright (C) 2018 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "monitorproxy.h" #include "core.h" #include "doc/kthumb.h" #include "glwidget.h" #include "kdenlivesettings.h" #include "monitormanager.h" #include "profiles/profilemodel.hpp" #include #include #include #include #define SEEK_INACTIVE (-1) MonitorProxy::MonitorProxy(GLWidget *parent) : QObject(parent) , q(parent) , m_position(0) , m_seekPosition(-1) , m_zoneIn(0) , m_zoneOut(-1) , m_hasAV(false) , m_clipType(0) { } int MonitorProxy::seekPosition() const { return m_seekPosition; } bool MonitorProxy::seeking() const { return m_seekPosition != SEEK_INACTIVE; } int MonitorProxy::position() const { return m_position; } int MonitorProxy::rulerHeight() const { return q->m_rulerHeight; } int MonitorProxy::overlayType() const { return (q->m_id == (int)Kdenlive::ClipMonitor ? KdenliveSettings::clipMonitorOverlayGuides() : KdenliveSettings::projectMonitorOverlayGuides()); } void MonitorProxy::setOverlayType(int ix) { if (q->m_id == (int)Kdenlive::ClipMonitor) { KdenliveSettings::setClipMonitorOverlayGuides(ix); } else { KdenliveSettings::setProjectMonitorOverlayGuides(ix); } } QString MonitorProxy::markerComment() const { return m_markerComment; } void MonitorProxy::requestSeekPosition(int pos) { q->activateMonitor(); m_seekPosition = pos; emit seekPositionChanged(); emit seekRequestChanged(); } int MonitorProxy::seekOrCurrentPosition() const { return m_seekPosition == SEEK_INACTIVE ? m_position : m_seekPosition; } bool MonitorProxy::setPosition(int pos) { if (m_seekPosition == pos) { m_position = pos; m_seekPosition = SEEK_INACTIVE; emit seekPositionChanged(); } else if (m_position == pos) { return true; } else { m_position = pos; } emit positionChanged(); return false; } void MonitorProxy::setMarkerComment(const QString &comment) { if (m_markerComment == comment) { return; } m_markerComment = comment; emit markerCommentChanged(); } void MonitorProxy::setSeekPosition(int pos) { m_seekPosition = pos; emit seekPositionChanged(); } void MonitorProxy::pauseAndSeek(int pos) { q->switchPlay(false); requestSeekPosition(pos); } int MonitorProxy::zoneIn() const { return m_zoneIn; } int MonitorProxy::zoneOut() const { return m_zoneOut; } void MonitorProxy::setZoneIn(int pos) { if (m_zoneIn > 0) { emit removeSnap(m_zoneIn); } m_zoneIn = pos; if (pos > 0) { emit addSnap(pos); } emit zoneChanged(); emit saveZone(); } void MonitorProxy::setZoneOut(int pos) { if (m_zoneOut > 0) { emit removeSnap(m_zoneOut - 1); } m_zoneOut = pos; if (pos > 0) { emit addSnap(pos - 1); } emit zoneChanged(); emit saveZone(); } void MonitorProxy::setZone(int in, int out, bool sendUpdate) { if (m_zoneIn > 0) { emit removeSnap(m_zoneIn); } if (m_zoneOut > 0) { emit removeSnap(m_zoneOut - 1); } m_zoneIn = in; m_zoneOut = out; if (m_zoneIn > 0) { emit addSnap(m_zoneIn); } if (m_zoneOut > 0) { emit addSnap(m_zoneOut - 1); } emit zoneChanged(); if (sendUpdate) { emit saveZone(); } } void MonitorProxy::setZone(QPoint zone, bool sendUpdate) { setZone(zone.x(), zone.y(), sendUpdate); } void MonitorProxy::resetZone() { m_zoneIn = 0; m_zoneOut = -1; } double MonitorProxy::fps() const { return pCore->getCurrentFps(); } QPoint MonitorProxy::zone() const { return {m_zoneIn, m_zoneOut}; } QImage MonitorProxy::extractFrame(int frame_position, const QString &path, int width, int height, bool useSourceProfile) { if (width == -1) { width = pCore->getCurrentProfile()->width(); height = pCore->getCurrentProfile()->height(); } else if (width % 2 == 1) { width++; } if (!path.isEmpty()) { QScopedPointer producer(new Mlt::Producer(pCore->getCurrentProfile()->profile(), path.toUtf8().constData())); if (producer && producer->is_valid()) { QImage img = KThumb::getFrame(producer.data(), frame_position, width, height); return img; } } if ((q->m_producer == nullptr) || !path.isEmpty()) { QImage pix(width, height, QImage::Format_RGB32); pix.fill(Qt::black); return pix; } Mlt::Frame *frame = nullptr; QImage img; if (useSourceProfile) { // Our source clip's resolution is higher than current profile, export at full res QScopedPointer tmpProfile(new Mlt::Profile()); QString service = q->m_producer->get("mlt_service"); QScopedPointer tmpProd(new Mlt::Producer(*tmpProfile, service.toUtf8().constData(), q->m_producer->get("resource"))); tmpProfile->from_producer(*tmpProd); width = tmpProfile->width(); height = tmpProfile->height(); if (tmpProd && tmpProd->is_valid()) { Mlt::Filter scaler(*tmpProfile, "swscale"); Mlt::Filter converter(*tmpProfile, "avcolor_space"); tmpProd->attach(scaler); tmpProd->attach(converter); // TODO: paste effects // Clip(*tmpProd).addEffects(*q->m_producer); double projectFps = pCore->getCurrentFps(); double currentFps = tmpProfile->fps(); if (qFuzzyCompare(projectFps, currentFps)) { tmpProd->seek(q->m_producer->position()); } else { tmpProd->seek(q->m_producer->position() * currentFps / projectFps); } frame = tmpProd->get_frame(); img = KThumb::getFrame(frame, width, height); delete frame; } } else if (KdenliveSettings::gpu_accel()) { QString service = q->m_producer->get("mlt_service"); QScopedPointer tmpProd( new Mlt::Producer(pCore->getCurrentProfile()->profile(), service.toUtf8().constData(), q->m_producer->get("resource"))); Mlt::Filter scaler(pCore->getCurrentProfile()->profile(), "swscale"); Mlt::Filter converter(pCore->getCurrentProfile()->profile(), "avcolor_space"); tmpProd->attach(scaler); tmpProd->attach(converter); tmpProd->seek(q->m_producer->position()); frame = tmpProd->get_frame(); img = KThumb::getFrame(frame, width, height); delete frame; } else { frame = q->m_producer->get_frame(); img = KThumb::getFrame(frame, width, height); delete frame; } return img; } void MonitorProxy::activateClipMonitor(bool isClipMonitor) { pCore->monitorManager()->activateMonitor(isClipMonitor ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor); } QString MonitorProxy::toTimecode(int frames) const { return KdenliveSettings::frametimecode() ? QString::number(frames) : q->frameToTime(frames); } void MonitorProxy::setClipProperties(ClipType::ProducerType type, bool hasAV, const QString clipName) { if (hasAV != m_hasAV) { m_hasAV = hasAV; emit clipHasAVChanged(); } if (clipName == m_clipName) { m_clipName.clear(); emit clipNameChanged(); } m_clipName = clipName; emit clipNameChanged(); if (type != m_clipType) { m_clipType = type; emit clipTypeChanged(); } } -void MonitorProxy::setAudioThumb(const QString thumbPath) +void MonitorProxy::setAudioThumb(const QUrl thumbPath) { m_audioThumb = thumbPath; emit audioThumbChanged(); } diff --git a/src/monitor/monitorproxy.h b/src/monitor/monitorproxy.h index f3ca9b6ec..e5497ef05 100644 --- a/src/monitor/monitorproxy.h +++ b/src/monitor/monitorproxy.h @@ -1,130 +1,131 @@ /*************************************************************************** * Copyright (C) 2018 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ /** @brief This class is a wrapper around the monitor / glwidget and handles communication * with the qml overlay through its properties. */ #ifndef MONITORPROXY_H #define MONITORPROXY_H #include "definitions.h" #include +#include #include class GLWidget; class MonitorProxy : public QObject { Q_OBJECT // Q_PROPERTY(int consumerPosition READ consumerPosition NOTIFY consumerPositionChanged) Q_PROPERTY(int position READ position NOTIFY positionChanged) Q_PROPERTY(int seekPosition READ seekPosition WRITE setSeekPosition NOTIFY seekPositionChanged) Q_PROPERTY(int zoneIn READ zoneIn WRITE setZoneIn NOTIFY zoneChanged) Q_PROPERTY(int zoneOut READ zoneOut WRITE setZoneOut NOTIFY zoneChanged) Q_PROPERTY(int rulerHeight READ rulerHeight NOTIFY rulerHeightChanged) Q_PROPERTY(QString markerComment READ markerComment NOTIFY markerCommentChanged) - Q_PROPERTY(QString audioThumb MEMBER m_audioThumb NOTIFY audioThumbChanged) + Q_PROPERTY(QUrl audioThumb MEMBER m_audioThumb NOTIFY audioThumbChanged) Q_PROPERTY(int overlayType READ overlayType WRITE setOverlayType NOTIFY overlayTypeChanged) /** @brief: Returns true if current clip in monitor has Audio and Video * */ Q_PROPERTY(bool clipHasAV MEMBER m_hasAV NOTIFY clipHasAVChanged) /** @brief: Contains the name of clip currently displayed in monitor * */ Q_PROPERTY(QString clipName MEMBER m_clipName NOTIFY clipNameChanged) /** @brief: Contains the name of clip currently displayed in monitor * */ Q_PROPERTY(int clipType MEMBER m_clipType NOTIFY clipTypeChanged) public: MonitorProxy(GLWidget *parent); int seekPosition() const; /** brief: Returns true if we are still in a seek operation * */ bool seeking() const; int position() const; int rulerHeight() const; int overlayType() const; void setOverlayType(int ix); QString markerComment() const; Q_INVOKABLE void requestSeekPosition(int pos); /** brief: Returns seek position or consumer position when not seeking * */ int seekOrCurrentPosition() const; /** brief: update position and end seeking if we reached the requested seek position. * returns true if the position was unchanged, false otherwise * */ bool setPosition(int pos); void setMarkerComment(const QString &comment); void setSeekPosition(int pos); void pauseAndSeek(int pos); int zoneIn() const; int zoneOut() const; void setZoneIn(int pos); void setZoneOut(int pos); Q_INVOKABLE void setZone(int in, int out, bool sendUpdate = true); /** brief: Activate clip monitor if param is true, project monitor otherwise * */ Q_INVOKABLE void activateClipMonitor(bool isClipMonitor); void setZone(QPoint zone, bool sendUpdate = true); void resetZone(); QPoint zone() const; QImage extractFrame(int frame_position, const QString &path = QString(), int width = -1, int height = -1, bool useSourceProfile = false); Q_INVOKABLE QString toTimecode(int frames) const; Q_INVOKABLE double fps() const; void setClipProperties(ClipType::ProducerType type, bool hasAV, const QString clipName); - void setAudioThumb(const QString thumbPath = QString()); + void setAudioThumb(const QUrl thumbPath = QUrl()); signals: void positionChanged(); void seekPositionChanged(); void seekRequestChanged(); void zoneChanged(); void saveZone(); void markerCommentChanged(); void rulerHeightChanged(); void addSnap(int); void removeSnap(int); void triggerAction(const QString &name); void overlayTypeChanged(); void seekNextKeyframe(); void seekPreviousKeyframe(); void addRemoveKeyframe(); void seekToKeyframe(); void clipHasAVChanged(); void clipNameChanged(); void clipTypeChanged(); void audioThumbChanged(); private: GLWidget *q; int m_position; int m_seekPosition; int m_zoneIn; int m_zoneOut; bool m_hasAV; - QString m_audioThumb; + QUrl m_audioThumb; QString m_markerComment; QString m_clipName; int m_clipType; }; #endif diff --git a/src/utils/thumbnailcache.cpp b/src/utils/thumbnailcache.cpp index 2f34375d1..857ba56dc 100644 --- a/src/utils/thumbnailcache.cpp +++ b/src/utils/thumbnailcache.cpp @@ -1,271 +1,271 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "thumbnailcache.hpp" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "core.h" #include "doc/kdenlivedoc.h" #include #include #include std::unique_ptr ThumbnailCache::instance; std::once_flag ThumbnailCache::m_onceFlag; class ThumbnailCache::Cache_t { public: Cache_t(int maxCost) : m_maxCost(maxCost) { } bool contains(const QString &key) const { return m_cache.count(key) > 0; } void remove(const QString &key) { if (!contains(key)) { return; } auto it = m_cache.at(key); m_currentCost -= (*it).second.second; m_data.erase(it); m_cache.erase(key); } void insert(const QString &key, const QImage &img, int cost) { if (cost > m_maxCost) { return; } m_data.push_front({key, {img, cost}}); auto it = m_data.begin(); m_cache[key] = it; m_currentCost += cost; while (m_currentCost > m_maxCost) { remove(m_data.back().first); } } QImage get(const QString &key) { if (!contains(key)) { return QImage(); } // when a get operation occurs, we put the corresponding list item in front to remember last access std::pair> data; auto it = m_cache.at(key); std::swap(data, (*it)); // take data out without copy QImage result = data.second.first; // a copy occurs here m_data.erase(it); // delete old iterator m_cache[key] = m_data.emplace(m_data.begin(), std::move(data)); // reinsert without copy and store iterator return result; } protected: int m_maxCost; int m_currentCost{0}; std::list>> m_data; // the data is stored as (key,(image, cost)) std::unordered_map m_cache; }; ThumbnailCache::ThumbnailCache() : m_volatileCache(new Cache_t(10000000)) { } std::unique_ptr &ThumbnailCache::get() { std::call_once(m_onceFlag, [] { instance.reset(new ThumbnailCache()); }); return instance; } bool ThumbnailCache::hasThumbnail(const QString &binId, int pos, bool volatileOnly) const { QMutexLocker locker(&m_mutex); bool ok = false; auto key = pos < 0 ? getAudioKey(binId, &ok) : getKey(binId, pos, &ok); if (ok && m_volatileCache->contains(key)) { return true; } if (!ok || volatileOnly) { return false; } QDir thumbFolder = getDir(pos < 0, &ok); return ok && thumbFolder.exists(key); } QImage ThumbnailCache::getAudioThumbnail(const QString &binId, bool volatileOnly) const { QMutexLocker locker(&m_mutex); bool ok = false; auto key = getAudioKey(binId, &ok); if (ok && m_volatileCache->contains(key)) { return m_volatileCache->get(key); } if (!ok || volatileOnly) { return QImage(); } QDir thumbFolder = getDir(true, &ok); if (ok && thumbFolder.exists(key)) { m_storedOnDisk[binId].push_back(-1); return QImage(thumbFolder.absoluteFilePath(key)); } return QImage(); } -const QString ThumbnailCache::getAudioThumbPath(const QString &binId) const +const QUrl ThumbnailCache::getAudioThumbPath(const QString &binId) const { QMutexLocker locker(&m_mutex); bool ok = false; auto key = getAudioKey(binId, &ok); QDir thumbFolder = getDir(true, &ok); if (ok && thumbFolder.exists(key)) { - return QStringLiteral("file://") + thumbFolder.absoluteFilePath(key); + return QUrl::fromLocalFile(thumbFolder.absoluteFilePath(key)); } - return QString(); + return QUrl(); } QImage ThumbnailCache::getThumbnail(const QString &binId, int pos, bool volatileOnly) const { QMutexLocker locker(&m_mutex); bool ok = false; auto key = getKey(binId, pos, &ok); if (ok && m_volatileCache->contains(key)) { return m_volatileCache->get(key); } if (!ok || volatileOnly) { return QImage(); } QDir thumbFolder = getDir(false, &ok); if (ok && thumbFolder.exists(key)) { m_storedOnDisk[binId].push_back(pos); return QImage(thumbFolder.absoluteFilePath(key)); } return QImage(); } void ThumbnailCache::storeThumbnail(const QString &binId, int pos, const QImage &img, bool persistent) { QMutexLocker locker(&m_mutex); bool ok = false; const QString key = getKey(binId, pos, &ok); if (!ok) { return; } if (persistent) { QDir thumbFolder = getDir(false, &ok); if (ok) { if (!img.save(thumbFolder.absoluteFilePath(key))) { qDebug() << ".............\nAAAAAAAAAAAARGH ERROR SAVING THUMB"; } m_storedOnDisk[binId].push_back(pos); // if volatile cache also contains this entry, update it if (m_volatileCache->contains(key)) { m_volatileCache->remove(key); } else { m_storedVolatile[binId].push_back(pos); } m_volatileCache->insert(key, img, (int)img.sizeInBytes()); } } else { m_volatileCache->insert(key, img, (int)img.sizeInBytes()); m_storedVolatile[binId].push_back(pos); } } void ThumbnailCache::saveCachedThumbs(QStringList keys) { bool ok; QDir thumbFolder = getDir(false, &ok); if (!ok) { return; } for (const QString &key : keys) { if (!thumbFolder.exists(key) && m_volatileCache->contains(key)) { QImage img = m_volatileCache->get(key); if (!img.save(thumbFolder.absoluteFilePath(key))) { qDebug() << "// Error writing thumbnails to " << thumbFolder.absolutePath(); break; } } } } void ThumbnailCache::invalidateThumbsForClip(const QString &binId) { QMutexLocker locker(&m_mutex); if (m_storedVolatile.find(binId) != m_storedVolatile.end()) { bool ok = false; for (int pos : m_storedVolatile.at(binId)) { auto key = getKey(binId, pos, &ok); if (ok) { m_volatileCache->remove(key); } } m_storedVolatile.erase(binId); } bool ok = false; // Video thumbs QDir thumbFolder = getDir(false, &ok); QDir audioThumbFolder = getDir(true, &ok); if (ok && m_storedOnDisk.find(binId) != m_storedOnDisk.end()) { // Remove persistent cache for (int pos : m_storedOnDisk.at(binId)) { if (pos < 0) { auto key = getAudioKey(binId, &ok); if (ok) { QFile::remove(audioThumbFolder.absoluteFilePath(key)); } } else { auto key = getKey(binId, pos, &ok); if (ok) { QFile::remove(thumbFolder.absoluteFilePath(key)); } } } m_storedOnDisk.erase(binId); } } // static QString ThumbnailCache::getKey(const QString &binId, int pos, bool *ok) { auto binClip = pCore->projectItemModel()->getClipByBinID(binId); *ok = binClip != nullptr; return *ok ? binClip->hash() + QLatin1Char('#') + QString::number(pos) + QStringLiteral(".png") : QString(); } // static QString ThumbnailCache::getAudioKey(const QString &binId, bool *ok) { auto binClip = pCore->projectItemModel()->getClipByBinID(binId); *ok = binClip != nullptr; return *ok ? binClip->hash() + QStringLiteral(".png") : QString(); } // static QDir ThumbnailCache::getDir(bool audio, bool *ok) { return pCore->currentDoc()->getCacheDir(audio ? CacheAudio : CacheThumbs, ok); } diff --git a/src/utils/thumbnailcache.hpp b/src/utils/thumbnailcache.hpp index c60e0af5f..76b379140 100644 --- a/src/utils/thumbnailcache.hpp +++ b/src/utils/thumbnailcache.hpp @@ -1,100 +1,101 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #pragma once #include "definitions.h" #include +#include #include #include #include #include #include #include /** @brief This class class is an interface to the caches that store thumbnails. In Kdenlive, we use two such caches, a persistent that is stored on disk to allow thumbnails to be reused when reopening. The other one is a volatile LRU cache that lives in memory. Note that for the volatile cache uses a custom implementation. QCache is not suitable since it operates on pointers and since the object is removed from the cache when accessed. KImageCache is not suitable since it lacks a way to remove objects from the cache. * Note that this class is a Singleton */ class ThumbnailCache { public: // Returns the instance of the Singleton static std::unique_ptr &get(); /* @brief Check whether a given thumbnail is in the cache @param binId is the id of the queried clip @param pos is the position where we query @param volatileOnly if true, we only check the volatile cache (no disk access) */ bool hasThumbnail(const QString &binId, int pos, bool volatileOnly = false) const; /* @brief Get a given thumbnail from the cache @param binId is the id of the queried clip @param pos is the position where we query @param volatileOnly if true, we only check the volatile cache (no disk access) */ QImage getThumbnail(const QString &binId, int pos, bool volatileOnly = false) const; QImage getAudioThumbnail(const QString &binId, bool volatileOnly = false) const; - const QString getAudioThumbPath(const QString &binId) const; + const QUrl getAudioThumbPath(const QString &binId) const; /* @brief Get a given thumbnail from the cache @param binId is the id of the queried clip @param pos is the position where we query @param persistent if true, we store the image in the persistent cache, which generates a disk access */ void storeThumbnail(const QString &binId, int pos, const QImage &img, bool persistent = false); /* @brief Removes all the thumbnails for a given clip */ void invalidateThumbsForClip(const QString &binId); /* @brief Save all cached thumbs to disk */ void saveCachedThumbs(QStringList keys); protected: // Constructor is protected because class is a Singleton ThumbnailCache(); // Return the key associated to a thumbnail static QString getKey(const QString &binId, int pos, bool *ok); static QString getAudioKey(const QString &binId, bool *ok); // Return the dir where the persistent cache lives static QDir getDir(bool audio, bool *ok); static std::unique_ptr instance; static std::once_flag m_onceFlag; // flag to create the repository only once; class Cache_t; std::unique_ptr m_volatileCache; mutable QMutex m_mutex; // the following maps keeps track of the positions that we store for each clip in volatile caches. // Note that we don't track deletions due to items dropped from the cache. So the maps can contain more items that are currently stored. std::unordered_map> m_storedVolatile; mutable std::unordered_map> m_storedOnDisk; };