diff --git a/src/utils/thumbnailcache.cpp b/src/utils/thumbnailcache.cpp
index 54017e48c..21eb8fb66 100644
--- a/src/utils/thumbnailcache.cpp
+++ b/src/utils/thumbnailcache.cpp
@@ -1,325 +1,325 @@
/***************************************************************************
* 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;
}
void clear()
{
m_data.clear();
m_cache.clear();
m_currentCost = 0;
}
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).first() : 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).first();
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 QList ThumbnailCache::getAudioThumbPath(const QString &binId) const
{
QMutexLocker locker(&m_mutex);
bool ok = false;
auto key = getAudioKey(binId, &ok);
QDir thumbFolder = getDir(true, &ok);
QList pathList;
if (ok) {
for (const QString &p : key) {
if (thumbFolder.exists(p)) {
pathList <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() << ".............\n!!!!!!!! ERROR SAVING THUMB in: "<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, bool reloadAudio)
{
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) {
if (reloadAudio) {
auto key = getAudioKey(binId, &ok);
if (ok) {
for (const QString &p : key) {
QFile::remove(audioThumbFolder.absoluteFilePath(p));
}
}
}
} else {
auto key = getKey(binId, pos, &ok);
if (ok) {
QFile::remove(thumbFolder.absoluteFilePath(key));
}
}
}
m_storedOnDisk.erase(binId);
}
}
void ThumbnailCache::clearCache()
{
QMutexLocker locker(&m_mutex);
m_volatileCache->clear();
m_storedVolatile.clear();
m_storedOnDisk.clear();
}
// static
QString ThumbnailCache::getKey(const QString &binId, int pos, bool *ok)
{
if (binId.isEmpty()) {
*ok = false;
return QString();
}
auto binClip = pCore->projectItemModel()->getClipByBinID(binId);
*ok = binClip != nullptr;
return *ok ? binClip->hash() + QLatin1Char('#') + QString::number(pos) + QStringLiteral(".png") : QString();
}
// static
QStringList ThumbnailCache::getAudioKey(const QString &binId, bool *ok)
{
auto binClip = pCore->projectItemModel()->getClipByBinID(binId);
*ok = binClip != nullptr;
if (ok) {
QString streams = binClip->getProducerProperty(QStringLiteral("kdenlive:active_streams"));
- if (streams.isEmpty()) {
+ if (streams.isEmpty() || streams == QString::number(INT_MAX)) {
// activate all audio streams
QList streamIxes = binClip->audioStreams().keys();
if (streamIxes.size() > 1) {
QStringList streamsList;
for (const int st : streamIxes) {
streamsList << QString("%1_%2.png").arg(binClip->hash()).arg(st);
}
return streamsList;
}
}
- if (streams.size() == 1) {
+ if (streams.size() < 2) {
int audio = binClip->getProducerIntProperty(QStringLiteral("audio_index"));
if (audio > -1) {
return {QString("%1_%2.png").arg(binClip->hash()).arg(audio)};
}
return {binClip->hash() + QStringLiteral(".png")};
}
QStringList streamsList;
QStringList streamIndexes = streams.split(QLatin1Char(';'));
for (const QString st : streamIndexes) {
streamsList << QString("%1_%2.png").arg(binClip->hash()).arg(st);
}
return streamsList;
}
return {};
}
// static
QDir ThumbnailCache::getDir(bool audio, bool *ok)
{
return pCore->currentDoc()->getCacheDir(audio ? CacheAudio : CacheThumbs, ok);
}