Rewrite audio thumbnails to only use FFmpeg's data and optimize memory usage on creation.

Related to #102
parent 3d5c3ceb
Pipeline #28962 passed with stage
in 30 minutes and 40 seconds
...@@ -2517,13 +2517,13 @@ void Bin::slotEditClipCommand(const QString &id, const QMap<QString, QString> &o ...@@ -2517,13 +2517,13 @@ void Bin::slotEditClipCommand(const QString &id, const QMap<QString, QString> &o
m_doc->commandStack()->push(command); m_doc->commandStack()->push(command);
} }
void Bin::reloadClip(const QString &id, bool reloadAudio) void Bin::reloadClip(const QString &id)
{ {
std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id);
if (!clip) { if (!clip) {
return; return;
} }
clip->reloadProducer(false, false, reloadAudio); clip->reloadProducer(false, false);
} }
void Bin::reloadMonitorIfActive(const QString &id) void Bin::reloadMonitorIfActive(const QString &id)
...@@ -3947,11 +3947,11 @@ void Bin::reloadAllProducers(bool reloadThumbs) ...@@ -3947,11 +3947,11 @@ void Bin::reloadAllProducers(bool reloadThumbs)
if (!xml.isNull()) { if (!xml.isNull()) {
clip->setClipStatus(AbstractProjectItem::StatusWaiting); clip->setClipStatus(AbstractProjectItem::StatusWaiting);
pCore->jobManager()->slotDiscardClipJobs(clip->clipId()); pCore->jobManager()->slotDiscardClipJobs(clip->clipId());
clip->discardAudioThumb(false); clip->discardAudioThumb();
// We need to set a temporary id before all outdated producers are replaced; // We need to set a temporary id before all outdated producers are replaced;
int jobId = pCore->jobManager()->startJob<LoadJob>({clip->clipId()}, -1, QString(), xml); int jobId = pCore->jobManager()->startJob<LoadJob>({clip->clipId()}, -1, QString(), xml);
if (reloadThumbs) { if (reloadThumbs) {
ThumbnailCache::get()->invalidateThumbsForClip(clip->clipId(), true); ThumbnailCache::get()->invalidateThumbsForClip(clip->clipId());
} }
pCore->jobManager()->startJob<ThumbJob>({clip->clipId()}, jobId, QString(), -1, true, true); pCore->jobManager()->startJob<ThumbJob>({clip->clipId()}, jobId, QString(), -1, true, true);
if (KdenliveSettings::audiothumbnails()) { if (KdenliveSettings::audiothumbnails()) {
......
...@@ -226,7 +226,7 @@ public: ...@@ -226,7 +226,7 @@ public:
const QString getDocumentProperty(const QString &key); const QString getDocumentProperty(const QString &key);
/** @brief Ask MLT to reload this clip's producer */ /** @brief Ask MLT to reload this clip's producer */
void reloadClip(const QString &id, bool reloadAudio = true); void reloadClip(const QString &id);
/** @brief refresh monitor (if clip changed) */ /** @brief refresh monitor (if clip changed) */
void reloadMonitorIfActive(const QString &id); void reloadMonitorIfActive(const QString &id);
......
...@@ -207,6 +207,50 @@ QString ProjectClip::getXmlProperty(const QDomElement &producer, const QString & ...@@ -207,6 +207,50 @@ QString ProjectClip::getXmlProperty(const QDomElement &producer, const QString &
void ProjectClip::updateAudioThumbnail() void ProjectClip::updateAudioThumbnail()
{ {
emit audioThumbReady(); emit audioThumbReady();
if (m_clipType == ClipType::Audio) {
QImage thumb = ThumbnailCache::get()->getThumbnail(m_binId, 0);
if (thumb.isNull()) {
int iconHeight = QFontInfo(qApp->font()).pixelSize() * 3.5;
QImage img(QSize(iconHeight * pCore->getCurrentDar(), iconHeight), QImage::Format_ARGB32);
img.fill(Qt::darkGray);
QMap <int, QString> streams = audioInfo()->streams();
QMap <int, int> channelsList = audioInfo()->streamChannels();
QPainter painter(&img);
QPen pen = painter.pen();
pen.setColor(Qt::white);
painter.setPen(pen);
int streamCount = 0;
qreal indicesPrPixel = qreal(getFramePlaytime()) / img.width();
double increment = qMax(1., 1. / qAbs(indicesPrPixel));
if (streams.count() > 0) {
double streamHeight = iconHeight / streams.count();
QMapIterator<int, QString> st(streams);
while (st.hasNext()) {
st.next();
int channels = channelsList.value(st.key());
double channelHeight = (double) streamHeight / channels;
const QVector <uint8_t> audioLevels = audioFrameCache(st.key());
for (int channel = 0; channel < channels; channel++) {
double y = (streamHeight * streamCount) + (channel * channelHeight) + channelHeight / 2;
for (int i = 0; i <= img.width(); i++) {
double j = i * increment;
int idx = j * indicesPrPixel;
idx += idx % channels;
idx += channel;
if (idx >= audioLevels.length() || idx < 0) break;
double level = audioLevels.at(idx) * channelHeight / 510.; // divide height by 510 (2*255) to get height
painter.drawLine(i, y - level, i, y + level);
}
}
streamCount++;
}
}
thumb = img;
// Cache thumbnail
ThumbnailCache::get()->storeThumbnail(m_binId, 0, thumb, true);
}
setThumbnail(thumb);
}
if (!KdenliveSettings::audiothumbnails()) { if (!KdenliveSettings::audiothumbnails()) {
return; return;
} }
...@@ -309,7 +353,7 @@ size_t ProjectClip::frameDuration() const ...@@ -309,7 +353,7 @@ size_t ProjectClip::frameDuration() const
return (size_t)getFramePlaytime(); return (size_t)getFramePlaytime();
} }
void ProjectClip::reloadProducer(bool refreshOnly, bool audioStreamChanged, bool reloadAudio) void ProjectClip::reloadProducer(bool refreshOnly, bool isProxy)
{ {
// we find if there are some loading job on that clip // we find if there are some loading job on that clip
int loadjobId = -1; int loadjobId = -1;
...@@ -319,7 +363,7 @@ void ProjectClip::reloadProducer(bool refreshOnly, bool audioStreamChanged, bool ...@@ -319,7 +363,7 @@ void ProjectClip::reloadProducer(bool refreshOnly, bool audioStreamChanged, bool
// In that case, we only want a new thumbnail. // In that case, we only want a new thumbnail.
// We thus set up a thumb job. We must make sure that there is no pending LOADJOB // We thus set up a thumb job. We must make sure that there is no pending LOADJOB
// Clear cache first // Clear cache first
ThumbnailCache::get()->invalidateThumbsForClip(clipId(), false); ThumbnailCache::get()->invalidateThumbsForClip(clipId());
pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::THUMBJOB); pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::THUMBJOB);
m_thumbsProducer.reset(); m_thumbsProducer.reset();
emit pCore->jobManager()->startJob<ThumbJob>({clipId()}, loadjobId, QString(), -1, true, true); emit pCore->jobManager()->startJob<ThumbJob>({clipId()}, loadjobId, QString(), -1, true, true);
...@@ -352,14 +396,11 @@ void ProjectClip::reloadProducer(bool refreshOnly, bool audioStreamChanged, bool ...@@ -352,14 +396,11 @@ void ProjectClip::reloadProducer(bool refreshOnly, bool audioStreamChanged, bool
} }
} }
ThumbnailCache::get()->invalidateThumbsForClip(clipId(), reloadAudio); ThumbnailCache::get()->invalidateThumbsForClip(clipId());
int loadJob = pCore->jobManager()->startJob<LoadJob>({clipId()}, loadjobId, QString(), xml); int loadJob = pCore->jobManager()->startJob<LoadJob>({clipId()}, loadjobId, QString(), xml);
emit pCore->jobManager()->startJob<ThumbJob>({clipId()}, loadJob, QString(), -1, true, true); emit pCore->jobManager()->startJob<ThumbJob>({clipId()}, loadJob, QString(), -1, true, true);
if (audioStreamChanged || hashChanged) { if (!isProxy && hashChanged) {
discardAudioThumb(); discardAudioThumb();
} else {
// refresh bin/monitor mini thumb only
discardAudioThumb(true);
} }
if (KdenliveSettings::audiothumbnails()) { if (KdenliveSettings::audiothumbnails()) {
emit pCore->jobManager()->startJob<AudioThumbJob>({clipId()}, loadjobId, QString()); emit pCore->jobManager()->startJob<AudioThumbJob>({clipId()}, loadjobId, QString());
...@@ -1124,7 +1165,7 @@ void ProjectClip::setProperties(const QMap<QString, QString> &properties, bool r ...@@ -1124,7 +1165,7 @@ void ProjectClip::setProperties(const QMap<QString, QString> &properties, bool r
setProducerProperty(QStringLiteral("_overwriteproxy"), 1); setProducerProperty(QStringLiteral("_overwriteproxy"), 1);
emit pCore->jobManager()->startJob<ProxyJob>({clipId()}, -1, QString()); emit pCore->jobManager()->startJob<ProxyJob>({clipId()}, -1, QString());
} else { } else {
reloadProducer(refreshOnly, audioStreamChanged, audioStreamChanged || (!refreshOnly && !properties.contains(QStringLiteral("kdenlive:proxy")))); reloadProducer(refreshOnly, properties.contains(QStringLiteral("kdenlive:proxy")));
} }
if (refreshOnly) { if (refreshOnly) {
if (auto ptr = m_model.lock()) { if (auto ptr = m_model.lock()) {
...@@ -1292,7 +1333,7 @@ int ProjectClip::audioChannels() const ...@@ -1292,7 +1333,7 @@ int ProjectClip::audioChannels() const
return audioInfo()->channels(); return audioInfo()->channels();
} }
void ProjectClip::discardAudioThumb(bool miniThumbOnly) void ProjectClip::discardAudioThumb()
{ {
if (!m_audioInfo) { if (!m_audioInfo) {
return; return;
...@@ -1300,18 +1341,16 @@ void ProjectClip::discardAudioThumb(bool miniThumbOnly) ...@@ -1300,18 +1341,16 @@ void ProjectClip::discardAudioThumb(bool miniThumbOnly)
pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::AUDIOTHUMBJOB); pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::AUDIOTHUMBJOB);
QString audioThumbPath; QString audioThumbPath;
QList <int> streams = m_audioInfo->streams().keys(); QList <int> streams = m_audioInfo->streams().keys();
if (!miniThumbOnly) { // Delete audio thumbnail data
// Delete audio thumbnail data for (int &st : streams) {
for (int &st : streams) { audioThumbPath = getAudioThumbPath(st);
audioThumbPath = getAudioThumbPath(st); if (!audioThumbPath.isEmpty()) {
if (!audioThumbPath.isEmpty()) { QFile::remove(audioThumbPath);
QFile::remove(audioThumbPath);
}
} }
} }
// Delete mini thumb // Delete thumbnail
for (int &st : streams) { for (int &st : streams) {
audioThumbPath = getAudioThumbPath(st, true); audioThumbPath = getAudioThumbPath(st);
if (!audioThumbPath.isEmpty()) { if (!audioThumbPath.isEmpty()) {
QFile::remove(audioThumbPath); QFile::remove(audioThumbPath);
} }
...@@ -1332,9 +1371,9 @@ int ProjectClip::getAudioStreamFfmpegIndex(int mltStream) ...@@ -1332,9 +1371,9 @@ int ProjectClip::getAudioStreamFfmpegIndex(int mltStream)
return -1; return -1;
} }
const QString ProjectClip::getAudioThumbPath(int stream, bool miniThumb) const QString ProjectClip::getAudioThumbPath(int stream)
{ {
if (audioInfo() == nullptr && !miniThumb) { if (audioInfo() == nullptr) {
return QString(); return QString();
} }
bool ok = false; bool ok = false;
...@@ -1348,10 +1387,6 @@ const QString ProjectClip::getAudioThumbPath(int stream, bool miniThumb) ...@@ -1348,10 +1387,6 @@ const QString ProjectClip::getAudioThumbPath(int stream, bool miniThumb)
} }
QString audioPath = thumbFolder.absoluteFilePath(clipHash); QString audioPath = thumbFolder.absoluteFilePath(clipHash);
audioPath.append(QLatin1Char('_') + QString::number(stream)); audioPath.append(QLatin1Char('_') + QString::number(stream));
if (miniThumb) {
audioPath.append(QStringLiteral(".png"));
return audioPath;
}
int roundedFps = (int)pCore->getCurrentFps(); int roundedFps = (int)pCore->getCurrentFps();
audioPath.append(QStringLiteral("_%1_audio.png").arg(roundedFps)); audioPath.append(QStringLiteral("_%1_audio.png").arg(roundedFps));
return audioPath; return audioPath;
......
...@@ -81,7 +81,7 @@ protected: ...@@ -81,7 +81,7 @@ protected:
public: public:
~ProjectClip() override; ~ProjectClip() override;
void reloadProducer(bool refreshOnly = false, bool audioStreamChanged = false, bool reloadAudio = true); void reloadProducer(bool refreshOnly = false, bool isProxy = false);
/** @brief Returns a unique hash identifier used to store clip thumbnails. */ /** @brief Returns a unique hash identifier used to store clip thumbnails. */
// virtual void hash() = 0; // virtual void hash() = 0;
...@@ -185,9 +185,9 @@ public: ...@@ -185,9 +185,9 @@ public:
/** @brief Returns the list of this clip's subclip's ids. */ /** @brief Returns the list of this clip's subclip's ids. */
QStringList subClipIds() const; QStringList subClipIds() const;
/** @brief Delete cached audio thumb - needs to be recreated */ /** @brief Delete cached audio thumb - needs to be recreated */
void discardAudioThumb(bool miniThumbOnly = false); void discardAudioThumb();
/** @brief Get path for this clip's audio thumbnail */ /** @brief Get path for this clip's audio thumbnail */
const QString getAudioThumbPath(int stream, bool miniThumb = false); const QString getAudioThumbPath(int stream);
/** @brief Returns true if this producer has audio and can be splitted on timeline*/ /** @brief Returns true if this producer has audio and can be splitted on timeline*/
bool isSplittable() const; bool isSplittable() const;
......
...@@ -120,46 +120,13 @@ bool AudioThumbJob::computeWithFFMPEG() ...@@ -120,46 +120,13 @@ bool AudioThumbJob::computeWithFFMPEG()
if (!QFile::exists(filePath)) { if (!QFile::exists(filePath)) {
return false; return false;
} }
m_ffmpegProcess.reset(new QProcess);
QString thumbPath = m_binClip->getAudioThumbPath(m_audioStream, true);
int audioStreamIndex = m_binClip->getAudioStreamFfmpegIndex(m_audioStream);
if (!QFile::exists(thumbPath)) {
// Generate thumbnail used in monitor overlay
QStringList args = {QStringLiteral("-hide_banner"), QStringLiteral("-y"), QStringLiteral("-i"), QUrl::fromLocalFile(filePath).toLocalFile(), QString("-filter_complex")};
if (m_audioStream >= 0) {
args << QString("[a:%1]showwavespic=s=%2x%3:split_channels=1:scale=cbrt:colors=%4|%5").arg(audioStreamIndex).arg(m_thumbSize.width()).arg(m_thumbSize.height()).arg(KdenliveSettings::thumbColor1().name(), KdenliveSettings::thumbColor2().name());
} else {
// Only 1 audio stream in clip
args << QString("[a]showwavespic=s=%2x%3:split_channels=1:scale=cbrt:colors=%4|%5").arg(m_thumbSize.width()).arg(m_thumbSize.height()).arg(KdenliveSettings::thumbColor1().name(), KdenliveSettings::thumbColor2().name());
}
args << QStringLiteral("-frames:v") << QStringLiteral("1");
args << thumbPath;
qDebug()<<"=== FFARGS: "<<args;
connect(m_ffmpegProcess.get(), &QProcess::readyReadStandardOutput, this, &AudioThumbJob::updateFfmpegProgress, Qt::UniqueConnection);
connect(this, &AudioThumbJob::jobCanceled, [&] () {
if (m_ffmpegProcess) {
disconnect(m_ffmpegProcess.get(), &QProcess::readyReadStandardOutput, this, &AudioThumbJob::updateFfmpegProgress);
m_ffmpegProcess->kill();
}
m_done = true;
m_successful = false;
});
m_ffmpegProcess->start(KdenliveSettings::ffmpegpath(), args);
m_ffmpegProcess->waitForFinished(-1);
disconnect(m_ffmpegProcess.get(), &QProcess::readyReadStandardOutput, this, &AudioThumbJob::updateFfmpegProgress);
if (m_ffmpegProcess->exitStatus() != QProcess::CrashExit) {
if (m_dataInCache || !KdenliveSettings::audiothumbnails()) {
m_done = true;
}
}
} else if (!KdenliveSettings::audiothumbnails()) {
m_done = true;
}
if (!KdenliveSettings::audiothumbnails()) { if (!KdenliveSettings::audiothumbnails()) {
// We only wanted the thumb generation // We only wanted the thumb generation
return m_done; return m_done;
} }
if (!QFile::exists(m_cachePath) && !m_dataInCache && !m_done) { int audioStreamIndex = m_binClip->getAudioStreamFfmpegIndex(m_audioStream);
if (!QFile::exists(m_cachePath) && !m_dataInCache) {
// Generate timeline audio thumbnail data // Generate timeline audio thumbnail data
m_audioLevels.clear(); m_audioLevels.clear();
std::vector<std::unique_ptr<QTemporaryFile>> channelFiles; std::vector<std::unique_ptr<QTemporaryFile>> channelFiles;
...@@ -184,9 +151,9 @@ bool AudioThumbJob::computeWithFFMPEG() ...@@ -184,9 +151,9 @@ bool AudioThumbJob::computeWithFFMPEG()
args << QStringLiteral("-filter_complex"); args << QStringLiteral("-filter_complex");
if (m_channels == 1) { if (m_channels == 1) {
if (m_audioStream >= 0) { if (m_audioStream >= 0) {
args << QStringLiteral("[a:%1]aformat=channel_layouts=mono,%2=100").arg(audioStreamIndex).arg(isFFmpeg ? "aresample=async" : "sample_rates"); args << QStringLiteral("[a:%1]aformat=channel_layouts=mono,%2=100").arg(audioStreamIndex).arg(isFFmpeg ? "aresample=1500:async" : "sample_rates");
} else { } else {
args << QStringLiteral("[a]aformat=channel_layouts=mono,%1=100").arg(isFFmpeg ? "aresample=async" : "sample_rates"); args << QStringLiteral("[a]aformat=channel_layouts=mono,%1=100").arg(isFFmpeg ? "aresample=1500:async" : "sample_rates");
} }
/*args << QStringLiteral("-map") << QStringLiteral("0:a%1").arg(m_audioStream > 0 ? ":" + QString::number(audioStreamIndex) : QString())*/ /*args << QStringLiteral("-map") << QStringLiteral("0:a%1").arg(m_audioStream > 0 ? ":" + QString::number(audioStreamIndex) : QString())*/
args << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-frames:v") args << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-frames:v")
...@@ -195,7 +162,7 @@ bool AudioThumbJob::computeWithFFMPEG() ...@@ -195,7 +162,7 @@ bool AudioThumbJob::computeWithFFMPEG()
} else { } else {
QString aformat = QStringLiteral("[a%1]%2=100,channelsplit=channel_layout=%3") QString aformat = QStringLiteral("[a%1]%2=100,channelsplit=channel_layout=%3")
.arg(audioStreamIndex >= 0 ? ":" + QString::number(audioStreamIndex) : QString(), .arg(audioStreamIndex >= 0 ? ":" + QString::number(audioStreamIndex) : QString(),
isFFmpeg ? "aresample=async" : "aformat=sample_rates", isFFmpeg ? "aresample=1500:async" : "aformat=sample_rates",
m_channels > 2 ? "5.1" : "stereo"); m_channels > 2 ? "5.1" : "stereo");
for (int i = 0; i < m_channels; ++i) { for (int i = 0; i < m_channels; ++i) {
aformat.append(QStringLiteral("[0:%1]").arg(i)); aformat.append(QStringLiteral("[0:%1]").arg(i));
...@@ -279,15 +246,16 @@ bool AudioThumbJob::computeWithFFMPEG() ...@@ -279,15 +246,16 @@ bool AudioThumbJob::computeWithFFMPEG()
} }
m_done = true; m_done = true;
return true; return true;
} else {
QString err = m_ffmpegProcess->readAllStandardError();
m_ffmpegProcess.reset();
// m_errorMessage += err;
// m_errorMessage.append(i18n("Failed to create FFmpeg audio thumbnails, we now try to use MLT"));
qWarning() << "Failed to create FFmpeg audio thumbs:\n" << err << "\n---------------------";
} }
} else { } else {
m_done = true; m_done = true;
} }
QString err = m_ffmpegProcess->readAllStandardError();
m_ffmpegProcess.reset();
// m_errorMessage += err;
// m_errorMessage.append(i18n("Failed to create FFmpeg audio thumbnails, we now try to use MLT"));
qWarning() << "Failed to create FFmpeg audio thumbs:\n" << err << "\n---------------------";
return m_done; return m_done;
} }
...@@ -333,20 +301,6 @@ bool AudioThumbJob::startJob() ...@@ -333,20 +301,6 @@ bool AudioThumbJob::startJob()
return false; return false;
} }
m_lengthInFrames = m_prod->get_length(); // Multiply this if we want more than 1 sample per frame m_lengthInFrames = m_prod->get_length(); // Multiply this if we want more than 1 sample per frame
int thumbResolution = 3000;
// Increase audio thumb resolution for longer clips to get a better resolution
if (m_lengthInFrames > 10000) {
// More than 10 minutes at 25fps
if (m_lengthInFrames > 90000) {
// More than 1 hour at 25fps
thumbResolution = 10000;
} else {
thumbResolution = 6000;
}
}
m_thumbSize = QSize(thumbResolution, 1000 / pCore->getCurrentDar());
m_frequency = m_binClip->audioInfo()->samplingRate(); m_frequency = m_binClip->audioInfo()->samplingRate();
m_frequency = m_frequency <= 0 ? 48000 : m_frequency; m_frequency = m_frequency <= 0 ? 48000 : m_frequency;
...@@ -354,12 +308,16 @@ bool AudioThumbJob::startJob() ...@@ -354,12 +308,16 @@ bool AudioThumbJob::startJob()
m_channels = m_channels <= 0 ? 2 : m_channels; m_channels = m_channels <= 0 ? 2 : m_channels;
QMap <int, QString> streams = m_binClip->audioInfo()->streams(); QMap <int, QString> streams = m_binClip->audioInfo()->streams();
QMap <int, int> audioChannels = m_binClip->audioInfo()->streamChannels();
QMapIterator<int, QString> st(streams); QMapIterator<int, QString> st(streams);
m_done = true; m_done = true;
ClipType::ProducerType type = m_binClip->clipType(); ClipType::ProducerType type = m_binClip->clipType();
while (st.hasNext()) { while (st.hasNext()) {
st.next(); st.next();
int stream = st.key(); int stream = st.key();
if (audioChannels.contains(stream)) {
m_channels = audioChannels.value(stream);
}
// Generate one thumb per stream // Generate one thumb per stream
m_audioStream = stream; m_audioStream = stream;
m_cachePath = m_binClip->getAudioThumbPath(stream); m_cachePath = m_binClip->getAudioThumbPath(stream);
...@@ -427,26 +385,13 @@ bool AudioThumbJob::commitResult(Fun &undo, Fun &redo) ...@@ -427,26 +385,13 @@ bool AudioThumbJob::commitResult(Fun &undo, Fun &redo)
if (!m_successful) { if (!m_successful) {
return false; return false;
} }
QImage oldImage;
QImage result;
if (m_binClip->clipType() == ClipType::Audio) {
oldImage = m_binClip->thumbnail(200, 200 / pCore->getCurrentDar()).toImage();
result = ThumbnailCache::get()->getAudioThumbnail(m_clipId).scaled(200, 200 / pCore->getCurrentDar());
}
// note that the image is moved into lambda, it won't be available from this class anymore auto operation = [clip = m_binClip]() {
auto operation = [clip = m_binClip, image = std::move(result)]() {
clip->updateAudioThumbnail(); clip->updateAudioThumbnail();
if (!image.isNull() && clip->clipType() == ClipType::Audio) {
clip->setThumbnail(image);
}
return true; return true;
}; };
auto reverse = [clip = m_binClip, image = std::move(oldImage)]() { auto reverse = [clip = m_binClip]() {
clip->updateAudioThumbnail(); clip->updateAudioThumbnail();
if (!image.isNull() && clip->clipType() == ClipType::Audio) {
clip->setThumbnail(image);
}
return true; return true;
}; };
bool ok = operation(); bool ok = operation();
......
...@@ -64,12 +64,9 @@ protected: ...@@ -64,12 +64,9 @@ protected:
private: private:
std::shared_ptr<ProjectClip> m_binClip; std::shared_ptr<ProjectClip> m_binClip;
std::shared_ptr<Mlt::Producer> m_prod; std::shared_ptr<Mlt::Producer> m_prod;
QString m_miniThumbPath;
QString m_cachePath; QString m_cachePath;
QSize m_thumbSize;
bool m_dataInCache; bool m_dataInCache;
bool m_thumbInCache; bool m_thumbInCache;
bool m_done{false}, m_successful{false}; bool m_done{false}, m_successful{false};
int m_channels, m_frequency, m_lengthInFrames, m_audioStream; int m_channels, m_frequency, m_lengthInFrames, m_audioStream;
QVector <uint8_t>m_audioLevels; QVector <uint8_t>m_audioLevels;
......
...@@ -377,14 +377,14 @@ bool ProxyJob::commitResult(Fun &undo, Fun &redo) ...@@ -377,14 +377,14 @@ bool ProxyJob::commitResult(Fun &undo, Fun &redo)
binClip->setProducerProperty(QStringLiteral("_overwriteproxy"), QString()); binClip->setProducerProperty(QStringLiteral("_overwriteproxy"), QString());
const QString dest = binClip->getProducerProperty(QStringLiteral("kdenlive:proxy")); const QString dest = binClip->getProducerProperty(QStringLiteral("kdenlive:proxy"));
binClip->setProducerProperty(QStringLiteral("resource"), dest); binClip->setProducerProperty(QStringLiteral("resource"), dest);
pCore->bin()->reloadClip(clipId, false); pCore->bin()->reloadClip(clipId);
return true; return true;
}; };
auto reverse = [clipId = m_clipId]() { auto reverse = [clipId = m_clipId]() {
auto binClip = pCore->projectItemModel()->getClipByBinID(clipId); auto binClip = pCore->projectItemModel()->getClipByBinID(clipId);
const QString dest = binClip->getProducerProperty(QStringLiteral("kdenlive:originalurl")); const QString dest = binClip->getProducerProperty(QStringLiteral("kdenlive:originalurl"));
binClip->setProducerProperty(QStringLiteral("resource"), dest); binClip->setProducerProperty(QStringLiteral("resource"), dest);
pCore->bin()->reloadClip(clipId, false); pCore->bin()->reloadClip(clipId);
return true; return true;
}; };
bool ok = operation(); bool ok = operation();
......
...@@ -116,7 +116,12 @@ QMap <int, QString> AudioStreamInfo::streams() const ...@@ -116,7 +116,12 @@ QMap <int, QString> AudioStreamInfo::streams() const
return m_audioStreams; return m_audioStreams;
} }
QList <int> AudioStreamInfo::streamChannels() const QMap <int, int> AudioStreamInfo::streamChannels() const
{
return m_audioChannels;
}
QList <int> AudioStreamInfo::activeStreamChannels() const
{ {
if (m_activeStreams.size() == 1 && m_activeStreams.contains(INT_MAX)) { if (m_activeStreams.size() == 1 && m_activeStreams.contains(INT_MAX)) {
return m_audioChannels.values(); return m_audioChannels.values();
......
...@@ -31,7 +31,9 @@ public: ...@@ -31,7 +31,9 @@ public:
/** @brief returns a list of audio stream index > stream description */ /** @brief returns a list of audio stream index > stream description */
QMap <int, QString> streams() const; QMap <int, QString> streams() const;
/** @brief returns a list of audio stream index > channels per stream */ /** @brief returns a list of audio stream index > channels per stream */
QList <int> streamChannels() const; QMap <int, int> streamChannels() const;
/** @brief returns a list of audio channels per active stream */
QList <int> activeStreamChannels() const;
/** @brief returns a list of enabled audio stream indexes > stream description */ /** @brief returns a list of enabled audio stream indexes > stream description */
QMap <int, QString> activeStreams() const; QMap <int, QString> activeStreams() const;
int bitrate() const; int bitrate() const;
......
...@@ -1049,7 +1049,7 @@ QList <int> ClipController::activeStreamChannels() const ...@@ -1049,7 +1049,7 @@ QList <int> ClipController::activeStreamChannels() const
if (!audioInfo()) { if (!audioInfo()) {
return QList <int>(); return QList <int>();
} }
return audioInfo()->streamChannels(); return audioInfo()->activeStreamChannels();
} }
QMap <int, QString> ClipController::activeStreams() const QMap <int, QString> ClipController::activeStreams() const
......
...@@ -223,7 +223,7 @@ void ThumbnailCache::saveCachedThumbs(QStringList keys) ...@@ -223,7 +223,7 @@ void ThumbnailCache::saveCachedThumbs(QStringList keys)
} }
} }
void ThumbnailCache::invalidateThumbsForClip(const QString &binId, bool reloadAudio) void ThumbnailCache::invalidateThumbsForClip(const QString &binId)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
if (m_storedVolatile.find(binId) != m_storedVolatile.end()) { if (m_storedVolatile.find(binId) != m_storedVolatile.end()) {
...@@ -243,16 +243,7 @@ void ThumbnailCache::invalidateThumbsForClip(const QString &binId, bool reloadAu ...@@ -243,16 +243,7 @@ void ThumbnailCache::invalidateThumbsForClip(const QString &binId, bool reloadAu
if (ok && m_storedOnDisk.find(binId) != m_storedOnDisk.end()) { if (ok && m_storedOnDisk.find(binId) != m_storedOnDisk.end()) {
// Remove persistent cache // Remove persistent cache
for (int pos : m_storedOnDisk.at(binId)) { for (int pos : m_storedOnDisk.at(binId)) {
if (pos < 0) { if (pos >= 0) {
if (reloadAudio) {
auto key = getAudioKey(binId, &ok);
if (ok) {
for (const QString &p : qAsConst(key)) {
QFile::remove(audioThumbFolder.absoluteFilePath(p));
}
}
}
} else {
auto key = getKey(binId, pos, &ok); auto key = getKey(binId, pos, &ok);
if (ok) { if (ok) {
QFile::remove(thumbFolder.absoluteFilePath(key)); QFile::remove(thumbFolder.absoluteFilePath(key));
......
...@@ -71,7 +71,7 @@ public: ...@@ -71,7 +71,7 @@ public:
void storeThumbnail(const QString &binId, int pos, const QImage &img, bool persistent = false); void storeThumbnail(const QString &binId, int pos, const QImage &img, bool persistent = false);
/* @brief Removes all the thumbnails for a given clip */ /* @brief Removes all the thumbnails for a given clip */
void invalidateThumbsForClip(const QString &binId, bool reloadAudio); void invalidateThumbsForClip(const QString &binId);
/* @brief Save all cached thumbs to disk */ /* @brief Save all cached thumbs to disk */
void saveCachedThumbs(QStringList keys); void saveCachedThumbs(QStringList keys);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment