diff --git a/src/audiomixer/mixermanager.cpp b/src/audiomixer/mixermanager.cpp
index 8d8ff4c22..9370c0985 100644
--- a/src/audiomixer/mixermanager.cpp
+++ b/src/audiomixer/mixermanager.cpp
@@ -1,219 +1,224 @@
/***************************************************************************
* Copyright (C) 2019 by Jean-Baptiste Mardelle *
* 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 "mixermanager.hpp"
#include "mixerwidget.hpp"
#include "timeline2/model/timelineitemmodel.hpp"
+#include "kdenlivesettings.h"
#include "mlt++/MltService.h"
#include "mlt++/MltTractor.h"
#include
#include
#include
#include
#include
const double log_factor = 1.0 / log10(1.0 / 127);
static inline double levelToDB(double level)
{
if (level <= 0) {
return -100;
}
return 100 * (1.0 - log10(level) * log_factor);
}
MixerManager::MixerManager(QWidget *parent)
: QWidget(parent)
, m_masterMixer(nullptr)
, m_connectedWidgets(false)
, m_expandedWidth(-1)
{
m_masterBox = new QHBoxLayout;
m_channelsBox = new QScrollArea(this);
m_box = new QHBoxLayout;
m_box->setSpacing(0);
auto *channelsBoxContainer = new QWidget;
channelsBoxContainer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_channelsBox->setWidget(channelsBoxContainer);
m_channelsBox->setWidgetResizable(true);
m_channelsBox->setFrameShape(QFrame::NoFrame);
m_box->addWidget(m_channelsBox);
m_channelsLayout = new QHBoxLayout;
m_channelsLayout->setContentsMargins(0, 0, 0, 0);
m_channelsLayout->setSpacing(0);
channelsBoxContainer->setLayout(m_channelsLayout);
m_channelsLayout->addStretch(10);
m_line = new QFrame(this);
m_line->setFrameShape(QFrame::VLine);
m_line->setFrameShadow(QFrame::Sunken);
m_box->addWidget(m_line);
m_box->addLayout(m_masterBox);
setLayout(m_box);
}
void MixerManager::registerTrack(int tid, std::shared_ptr service, const QString &trackTag)
{
if (m_mixers.count(tid) > 0) {
// Track already registered
return;
}
std::shared_ptr mixer(new MixerWidget(tid, service, trackTag, this));
connect(mixer.get(), &MixerWidget::muteTrack, [&](int id, bool mute) {
m_model->setTrackProperty(id, "hide", mute ? QStringLiteral("1") : QStringLiteral("3"));
});
if (m_connectedWidgets) {
mixer->connectMixer(true);
}
connect(this, &MixerManager::updateLevels, mixer.get(), &MixerWidget::updateAudioLevel);
connect(mixer.get(), &MixerWidget::toggleSolo, [&](int trid, bool solo) {
if (!solo) {
// unmute
for (int id : m_soloMuted) {
if (m_mixers.count(id) > 0) {
m_model->setTrackProperty(id, "hide", QStringLiteral("1"));
}
}
m_soloMuted.clear();
} else {
if (!m_soloMuted.isEmpty()) {
// Another track was solo, discard first
for (int id : m_soloMuted) {
if (m_mixers.count(id) > 0) {
m_model->setTrackProperty(id, "hide", QStringLiteral("1"));
}
}
m_soloMuted.clear();
}
for (auto item : m_mixers) {
if (item.first != trid && !item.second->isMute()) {
m_model->setTrackProperty(item.first, "hide", QStringLiteral("3"));
m_soloMuted << item.first;
item.second->unSolo();
}
}
}
});
m_mixers[tid] = mixer;
m_channelsLayout->insertWidget(0, mixer.get());
}
void MixerManager::deregisterTrack(int tid)
{
Q_ASSERT(m_mixers.count(tid) > 0);
m_mixers.erase(tid);
}
void MixerManager::resetAudioValues()
{
qDebug()<<"======\n\nRESTTING AUDIO VALUES\n\n------------------_";
for (auto item : m_mixers) {
item.second.get()->clear();
}
if (m_masterMixer) {
m_masterMixer->clear();
}
}
void MixerManager::cleanup()
{
for (auto item : m_mixers) {
m_channelsLayout->removeWidget(item.second.get());
}
m_mixers.clear();
if (m_masterMixer) {
m_masterMixer->clear(true);
}
}
void MixerManager::setModel(std::shared_ptr model)
{
// Insert master mixer
m_model = model;
connect(m_model.get(), &TimelineItemModel::dataChanged, [&](const QModelIndex &topLeft, const QModelIndex &, const QVector &roles) {
if (roles.contains(TimelineModel::IsDisabledRole)) {
int id = (int) topLeft.internalId();
if (m_mixers.count(id) > 0) {
m_mixers[id]->setMute(m_model->data(topLeft, TimelineModel::IsDisabledRole).toBool());
} else {
qDebug()<<"=== MODEL DATA CHANGED: MUTE DONE TRACK NOT FOUND!!!";
}
}
});
Mlt::Tractor *service = model->tractor();
if (m_masterMixer != nullptr) {
// delete previous master mixer
m_masterBox->removeWidget(m_masterMixer.get());
}
m_masterMixer.reset(new MixerWidget(-1, service, i18n("Master"), this));
connect(m_masterMixer.get(), &MixerWidget::muteTrack, [&](int /*id*/, bool mute) {
m_model->tractor()->set("hide", mute ? 3 : 1);
});
if (m_connectedWidgets) {
m_masterMixer->connectMixer(true);
}
connect(this, &MixerManager::updateLevels, m_masterMixer.get(), &MixerWidget::updateAudioLevel);
m_masterBox->addWidget(m_masterMixer.get());
+ collapseMixers();
}
void MixerManager::recordStateChanged(int tid, bool recording)
{
if (m_mixers.count(tid) > 0) {
m_mixers[tid]->setRecordState(recording);
}
}
-void MixerManager::connectMixer(bool doConnect)
+void MixerManager::connectMixer(bool doConnect, bool channelsOnly)
{
m_connectedWidgets = doConnect;
for (auto item : m_mixers) {
item.second->connectMixer(m_connectedWidgets);
}
- if (m_masterMixer != nullptr) {
+ if (!channelsOnly && m_masterMixer != nullptr) {
m_masterMixer->connectMixer(m_connectedWidgets);
}
}
-void MixerManager::collapseMixers(bool collapse)
+void MixerManager::collapseMixers()
{
- if (collapse) {
+ if (KdenliveSettings::mixerCollapse()) {
m_expandedWidth = width();
- m_channelsBox->setMaximumWidth(0);
+ m_channelsBox->setFixedWidth(0);
m_line->setMaximumWidth(0);
+ connectMixer(false, true);
setFixedWidth(m_masterMixer->width() + 2 * m_box->contentsMargins().left());
} else {
m_line->setMaximumWidth(QWIDGETSIZE_MAX);
m_channelsBox->setMaximumWidth(QWIDGETSIZE_MAX);
+ m_channelsBox->setMinimumWidth(m_masterMixer->width() + 2 * m_box->contentsMargins().left());
setMaximumWidth(QWIDGETSIZE_MAX);
if (m_expandedWidth > 0) {
setFixedWidth(m_expandedWidth);
}
+ connectMixer(true, true);
QTimer::singleShot(500, this, &MixerManager::resetSizePolicy);
}
}
void MixerManager::resetSizePolicy()
{
setMaximumWidth(QWIDGETSIZE_MAX);
setMinimumWidth(0);
}
diff --git a/src/audiomixer/mixermanager.hpp b/src/audiomixer/mixermanager.hpp
index a402b28c3..5d5cf6012 100644
--- a/src/audiomixer/mixermanager.hpp
+++ b/src/audiomixer/mixermanager.hpp
@@ -1,88 +1,88 @@
/***************************************************************************
* Copyright (C) 2019 by Jean-Baptiste Mardelle *
* 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 . *
***************************************************************************/
#ifndef MIXERMANGER_H
#define MIXERMANGER_H
#include "definitions.h"
#include
#include
#include
namespace Mlt {
class Tractor;
}
class MixerWidget;
class QHBoxLayout;
class TimelineItemModel;
class QScrollArea;
class QFrame;
class MixerManager : public QWidget
{
Q_OBJECT
public:
MixerManager(QWidget *parent);
/** @brief Shows the parameters of the given transition model */
void registerTrack(int tid, std::shared_ptr service, const QString &trackTag);
void deregisterTrack(int tid);
void setModel(std::shared_ptr model);
void cleanup();
/** @brief Connect the mixer widgets to the correspondant filters */
- void connectMixer(bool doConnect);
- void collapseMixers(bool collapse);
+ void connectMixer(bool doConnect, bool channelsOnly = false);
+ void collapseMixers();
public slots:
void resetAudioValues();
void recordStateChanged(int tid, bool recording);
private slots:
void resetSizePolicy();
signals:
void updateLevels(int);
void recordAudio(int tid);
void purgeCache();
protected:
std::unordered_map> m_mixers;
std::shared_ptr m_masterMixer;
private:
std::shared_ptr m_masterService;
std::shared_ptr m_model;
QHBoxLayout *m_box;
QHBoxLayout *m_masterBox;
QHBoxLayout *m_channelsLayout;
QScrollArea *m_channelsBox;
QFrame *m_line;
int m_lastFrame;
bool m_connectedWidgets;
int m_expandedWidth;
QVector m_soloMuted;
};
#endif
diff --git a/src/audiomixer/mixerwidget.cpp b/src/audiomixer/mixerwidget.cpp
index 8b4fe8daa..ff45ee34c 100644
--- a/src/audiomixer/mixerwidget.cpp
+++ b/src/audiomixer/mixerwidget.cpp
@@ -1,479 +1,480 @@
/***************************************************************************
* Copyright (C) 2019 by Jean-Baptiste Mardelle *
* 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 "mixerwidget.hpp"
#include "mlt++/MltFilter.h"
#include "mlt++/MltTractor.h"
#include "mlt++/MltEvent.h"
#include "mlt++/MltProfile.h"
#include "core.h"
#include "kdenlivesettings.h"
#include "mixermanager.hpp"
#include "audiolevelwidget.hpp"
#include "capture/mediacapture.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static inline double IEC_Scale(double dB)
{
dB = log10(dB) * 20.0;
double fScale = 1.0f;
if (dB < -70.0f)
fScale = 0.0f;
else if (dB < -60.0f)
fScale = (dB + 70.0f) * 0.0025f;
else if (dB < -50.0f)
fScale = (dB + 60.0f) * 0.005f + 0.025f;
else if (dB < -40.0)
fScale = (dB + 50.0f) * 0.0075f + 0.075f;
else if (dB < -30.0f)
fScale = (dB + 40.0f) * 0.015f + 0.15f;
else if (dB < -20.0f)
fScale = (dB + 30.0f) * 0.02f + 0.3f;
else if (dB < -0.001f || dB > 0.001f) /* if (dB < 0.0f) */
fScale = (dB + 20.0f) * 0.025f + 0.5f;
return fScale;
}
static inline int fromDB(double level)
{
int value = 60;
if (level > 0.) {
// increase volume
value = 100 - ((pow(10, 1. - level/24) - 1) / .225);
} else if (level < 0.) {
value = (10 - pow(10, 1. - level/-50)) / -0.11395 + 59;
}
return value;
}
void MixerWidget::property_changed( mlt_service , MixerWidget *widget, char *name )
{
if (widget && !strcmp(name, "_position")) {
mlt_properties filter_props = MLT_FILTER_PROPERTIES( widget->m_monitorFilter->get_filter());
int pos = mlt_properties_get_int(filter_props, "_position");
if (!widget->m_levels.contains(pos)) {
widget->m_levels[pos] = {IEC_Scale(mlt_properties_get_double(filter_props, "_audio_level.0")), IEC_Scale(mlt_properties_get_double(filter_props, "_audio_level.1"))};
if (widget->m_levels.size() > widget->m_maxLevels) {
widget->m_levels.erase(widget->m_levels.begin());
}
}
}
}
MixerWidget::MixerWidget(int tid, std::shared_ptr service, const QString &trackTag, MixerManager *parent)
: QWidget(parent)
, m_manager(parent)
, m_tid(tid)
, m_levelFilter(nullptr)
, m_monitorFilter(nullptr)
, m_balanceFilter(nullptr)
, m_maxLevels(qMax(30, (int)(service->get_fps() * 1.5)))
, m_solo(nullptr)
, m_record(nullptr)
, m_collapse(nullptr)
, m_lastVolume(0)
, m_listener(nullptr)
, m_recording(false)
{
buildUI(service.get(), trackTag);
}
MixerWidget::MixerWidget(int tid, Mlt::Tractor *service, const QString &trackTag, MixerManager *parent)
: QWidget(parent)
, m_manager(parent)
, m_tid(tid)
, m_levelFilter(nullptr)
, m_monitorFilter(nullptr)
, m_balanceFilter(nullptr)
, m_maxLevels(qMax(30, (int)(service->get_fps() * 1.5)))
, m_solo(nullptr)
, m_record(nullptr)
, m_collapse(nullptr)
, m_lastVolume(0)
, m_listener(nullptr)
, m_recording(false)
{
buildUI(service, trackTag);
}
MixerWidget::~MixerWidget()
{
if (m_listener) {
delete m_listener;
}
}
void MixerWidget::buildUI(Mlt::Tractor *service, const QString &trackTag)
{
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
// Build audio meter widget
m_audioMeterWidget.reset(new AudioLevelWidget(width(), this));
// intialize for stereo display
m_audioMeterWidget->setAudioValues({-100, -100});
// Build volume widget
m_volumeSlider = new QSlider(Qt::Vertical, this);
m_volumeSlider->setRange(0, 100);
m_volumeSlider->setValue(60);
m_volumeSlider->setToolTip(i18n("Volume"));
m_volumeSpin = new QDoubleSpinBox(this);
m_volumeSpin->setRange(-50, 24);
m_volumeSpin->setSuffix(i18n("dB"));
m_volumeSpin->setFrame(false);
connect(m_volumeSpin, static_cast(&QDoubleSpinBox::valueChanged), [&](double val) {
m_volumeSlider->setValue(fromDB(val));
});
m_balanceDial = new QDial(this);
m_balanceDial->setRange(-50, 50);
m_balanceDial->setValue(0);
m_balanceSpin = new QSpinBox(this);
m_balanceSpin->setRange(-50, 50);
m_balanceSpin->setValue(0);
m_balanceSpin->setFrame(false);
m_balanceSpin->setToolTip(i18n("Balance"));
// Check if we already have build-in filters for this tractor
int max = service->filter_count();
for (int i = 0; i < max; i++) {
std::shared_ptr fl(service->filter(i));
if (!fl->is_valid()) {
continue;
}
const QString filterService = fl->get("mlt_service");
if (filterService == QLatin1String("audiolevel")) {
m_monitorFilter = fl;
} else if (filterService == QLatin1String("volume")) {
m_levelFilter = fl;
int volume = m_levelFilter->get_int("level");
m_volumeSpin->setValue(volume);
m_volumeSlider->setValue(fromDB(volume));
} else if (filterService == QLatin1String("panner")) {
m_balanceFilter = fl;
int val = m_balanceFilter->get_double("start") * 100 - 50;
m_balanceSpin->setValue(val);
m_balanceDial->setValue(val);
}
}
// Build default filters if not found
if (m_levelFilter == nullptr) {
m_levelFilter.reset(new Mlt::Filter(service->get_profile(), "volume"));
if (m_levelFilter->is_valid()) {
m_levelFilter->set("internal_added", 237);
m_levelFilter->set("disable", 1);
service->attach(*m_levelFilter.get());
}
}
if (m_balanceFilter == nullptr) {
m_balanceFilter.reset(new Mlt::Filter(service->get_profile(), "panner"));
if (m_balanceFilter->is_valid()) {
m_balanceFilter->set("internal_added", 237);
m_balanceFilter->set("start", 0.5);
m_balanceFilter->set("disable", 1);
service->attach(*m_balanceFilter.get());
}
}
// Monitoring should be appended last so that other effects are reflected in audio monitor
if (m_monitorFilter == nullptr) {
m_monitorFilter.reset(new Mlt::Filter(service->get_profile(), "audiolevel"));
if (m_monitorFilter->is_valid()) {
m_monitorFilter->set("iec_scale", 0);
service->attach(*m_monitorFilter.get());
}
}
m_trackLabel = new QLabel(trackTag, this);
m_trackLabel->setAutoFillBackground(true);
m_trackLabel->setAlignment(Qt::AlignHCenter);
m_trackLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
m_muteAction = new KDualAction(i18n("Mute track"), i18n("Unmute track"), this);
m_muteAction->setActiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-hide-audio")));
m_muteAction->setInactiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-show-audio")));
connect(m_balanceDial, &QDial::valueChanged, m_balanceSpin, &QSpinBox::setValue);
connect(m_muteAction, &KDualAction::activeChangedByUser, [&](bool active) {
if (m_tid == -1) {
// Muting master, special case
if (m_levelFilter) {
if (active) {
m_lastVolume = m_levelFilter->get_int("level");
m_levelFilter->set("level", -1000);
} else {
m_levelFilter->set("level", m_lastVolume);
}
}
} else {
emit muteTrack(m_tid, !active);
clear(true);
}
updateLabel();
});
QToolButton *mute = new QToolButton(this);
mute->setDefaultAction(m_muteAction);
mute->setAutoRaise(true);
// Setup default width
QFontMetrics fm(font());
setFixedWidth(3 * mute->sizeHint().width());
if (m_tid > -1) {
// No solo / rec button on master
m_solo = new QToolButton(this);
m_solo->setCheckable(true);
m_solo->setIcon(QIcon::fromTheme("headphones"));
m_solo->setToolTip(i18n("Solo mode"));
m_solo->setAutoRaise(true);
connect(m_solo, &QToolButton::toggled, [&](bool toggled) {
emit toggleSolo(m_tid, toggled);
updateLabel();
});
m_record = new QToolButton(this);
m_record->setIcon(QIcon::fromTheme("media-record"));
m_record->setToolTip(i18n("Record"));
m_record->setCheckable(true);
m_record->setAutoRaise(true);
connect(m_record, &QToolButton::clicked, [&]() {
m_manager->recordAudio(m_tid);
});
} else {
m_collapse = new QToolButton(this);
m_collapse->setIcon(QIcon::fromTheme("arrow-left"));
m_collapse->setToolTip(i18n("Show Channels"));
m_collapse->setCheckable(true);
m_collapse->setAutoRaise(true);
connect(m_collapse, &QToolButton::clicked, [&]() {
- m_manager->collapseMixers(m_collapse->isChecked());
+ KdenliveSettings::setMixerCollapse(m_collapse->isChecked());
+ m_manager->collapseMixers();
});
}
connect(m_volumeSlider, &QSlider::valueChanged, [&](int value) {
QSignalBlocker bk(m_volumeSpin);
if (m_recording) {
m_volumeSpin->setValue(value);
KdenliveSettings::setAudiocapturevolume(value);
//TODO update capture volume
} else if (m_levelFilter != nullptr) {
double dbValue = 0;
if (value > 60) {
// increase volume
dbValue = 24 * (1 - log10((100 - value)*0.225 + 1));
} else if (value < 60) {
dbValue = -50 * (1 - log10(10 - (value - 59)*(-0.11395)));
}
m_volumeSpin->setValue(dbValue);
m_levelFilter->set("level", dbValue);
m_levelFilter->set("disable", value == 60 ? 1 : 0);
m_levels.clear();
m_manager->purgeCache();
}
});
connect(m_balanceSpin, static_cast(&QSpinBox::valueChanged), [&](int value) {
QSignalBlocker bk(m_balanceDial);
m_balanceDial->setValue(value);
if (m_balanceFilter != nullptr) {
m_balanceFilter->set("start", (value + 50) / 100.);
m_balanceFilter->set("disable", value == 0 ? 1 : 0);
m_levels.clear();
m_manager->purgeCache();
}
});
QVBoxLayout *lay = new QVBoxLayout;
setContentsMargins(0, 0, 0, 0);
lay->setMargin(0);
lay->addWidget(m_trackLabel);
QHBoxLayout *buttonslay = new QHBoxLayout;
buttonslay->setSpacing(0);
buttonslay->setContentsMargins(0, 0, 0, 0);
if (m_collapse) {
buttonslay->addWidget(m_collapse);
}
buttonslay->addWidget(mute);
if (m_solo) {
buttonslay->addWidget(m_solo);
}
if (m_record) {
buttonslay->addWidget(m_record);
}
lay->addLayout(buttonslay);
lay->addWidget(m_balanceDial);
lay->addWidget(m_balanceSpin);
QHBoxLayout *hlay = new QHBoxLayout;
hlay->addWidget(m_audioMeterWidget.get());
hlay->addWidget(m_volumeSlider);
lay->addLayout(hlay);
lay->addWidget(m_volumeSpin);
lay->setStretch(4, 10);
setLayout(lay);
if (service->get_int("hide") > 1) {
setMute(true);
}
}
void MixerWidget::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::RightButton) {
QWidget *child = childAt(event->pos());
if (child == m_balanceDial) {
m_balanceSpin->setValue(0);
} else if (child == m_volumeSlider) {
m_volumeSlider->setValue(60);
}
} else {
QWidget::mousePressEvent(event);
}
}
void MixerWidget::setMute(bool mute)
{
m_muteAction->setActive(mute);
m_volumeSlider->setEnabled(!mute);
m_volumeSpin->setEnabled(!mute);
m_audioMeterWidget->setEnabled(!mute);
m_balanceSpin->setEnabled(!mute);
m_balanceDial->setEnabled(!mute);
updateLabel();
}
void MixerWidget::updateLabel()
{
if (m_recording) {
QPalette pal = m_trackLabel->palette();
pal.setColor(QPalette::Window, Qt::red);
m_trackLabel->setPalette(pal);
} else if (m_muteAction->isActive()) {
QPalette pal = m_trackLabel->palette();
pal.setColor(QPalette::Window, QColor("#ff8c00"));
m_trackLabel->setPalette(pal);
} else if (m_solo && m_solo->isChecked()) {
QPalette pal = m_trackLabel->palette();
pal.setColor(QPalette::Window, Qt::darkGreen);
m_trackLabel->setPalette(pal);
} else {
QPalette pal = palette();
m_trackLabel->setPalette(pal);
}
}
void MixerWidget::updateAudioLevel(int pos)
{
QMutexLocker lk(&m_storeMutex);
if (m_levels.contains(pos)) {
m_audioMeterWidget->setAudioValues({m_levels.value(pos).first, m_levels.value(pos).second});
//m_levels.remove(pos);
} else {
m_audioMeterWidget->setAudioValues({-100, -100});
}
}
void MixerWidget::clear(bool reset)
{
if (reset) {
m_audioMeterWidget->setAudioValues({-100, -100});
}
QMutexLocker lk(&m_storeMutex);
m_levels.clear();
}
bool MixerWidget::isMute() const
{
return m_muteAction->isActive();
}
void MixerWidget::unSolo()
{
if (m_solo) {
QSignalBlocker bl(m_solo);
m_solo->setChecked(false);
}
}
void MixerWidget::gotRecLevels(QVectorlevels)
{
switch (levels.size()) {
case 0:
m_audioMeterWidget->setAudioValues({-100, -100});
break;
case 1:
m_audioMeterWidget->setAudioValues({IEC_Scale(levels[0]), -100});
break;
default:
m_audioMeterWidget->setAudioValues({IEC_Scale(levels[0]), IEC_Scale(levels[1])});
break;
}
}
void MixerWidget::setRecordState(bool recording)
{
m_recording = recording;
m_record->setChecked(m_recording);
QSignalBlocker bk(m_volumeSpin);
QSignalBlocker bk2(m_volumeSlider);
if (m_recording) {
connect(pCore->getAudioDevice(), &MediaCapture::audioLevels, this, &MixerWidget::gotRecLevels);
m_balanceDial->setEnabled(false);
m_balanceSpin->setEnabled(false);
m_volumeSpin->setRange(0, 100);
m_volumeSpin->setSuffix(QStringLiteral("%"));
m_volumeSpin->setValue(KdenliveSettings::audiocapturevolume());
m_volumeSlider->setValue(KdenliveSettings::audiocapturevolume());
} else {
m_balanceDial->setEnabled(true);
m_balanceSpin->setEnabled(true);
int level = m_levelFilter->get_int("level");
disconnect(pCore->getAudioDevice(), &MediaCapture::audioLevels, this, &MixerWidget::gotRecLevels);
m_volumeSpin->setRange(-100, 60);
m_volumeSpin->setSuffix(i18n("dB"));
m_volumeSpin->setValue(level);
m_volumeSlider->setValue(fromDB(level));
}
updateLabel();
}
void MixerWidget::connectMixer(bool doConnect)
{
if (doConnect) {
if (m_listener == nullptr) {
m_listener = m_monitorFilter->listen("property-changed", this, (mlt_listener)property_changed);
}
} else {
delete m_listener;
m_listener = nullptr;
}
}
diff --git a/src/kdenlivesettings.kcfg b/src/kdenlivesettings.kcfg
index 681e8c511..4b540acf0 100644
--- a/src/kdenlivesettings.kcfg
+++ b/src/kdenlivesettings.kcfg
@@ -1,1019 +1,1024 @@
0
4
false
true
1
true
true
false
00:00:05:00
00:00:05:00
00:00:00:01
00:00:03:00
false
false
false
false
false
00:00:05:00
00:00:01:00
true
0
2
2
false
false
false
false
1000
2000
800
0
0
true
false
false
false
140
1
25
false
true
true
true
false
false
true
false
0
1
true
true
true
false
sdl2_audio
0
sdl2_audio
0
#999999
100
true
1
false
0
1
2
1
/tmp/
false
true
$HOME
true
default:
100
2
48000
0
0
/dev/video0
2
default
0
true
false
0
0
0
false
0
0
1280
720
15.0
true
false
false
0
0
capture
false
3
false
true
0
true
0
25
true
false
false
false
true
true
true
false
false
false
false
0x15
0x05
0
0
false
0x07
true
true
+
+
+ false
+
+
false
true
#000000
true
320
240
true
false
false
false
true
5
3
false
false
false
10
0
false
false
true
false
false
true
true
true
0
onefield
nearest
volume,lift_gamma_gain,qtblend
wipe,qtblend
0
false
false
true
false
2
3
#ff0000
0
diff --git a/src/monitor/glwidget.cpp b/src/monitor/glwidget.cpp
index 34bd9e7f2..3b3484490 100644
--- a/src/monitor/glwidget.cpp
+++ b/src/monitor/glwidget.cpp
@@ -1,2020 +1,2021 @@
/*
* Copyright (c) 2011-2016 Meltytech, LLC
* Original author: Dan Dennedy
* Modified for Kdenlive: Jean-Baptiste Mardelle
*
* GL shader based on BSD licensed code from Peter Bengtsson:
* http://www.fourcc.org/source/YUV420P-OpenGL-GLSLang.c
*
* 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 3 of the License, or
* (at your option) any later version.
*
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include "core.h"
#include "glwidget.h"
#include "kdenlivesettings.h"
#include "monitorproxy.h"
#include "profiles/profilemodel.hpp"
#include "qml/qmlaudiothumb.h"
#include "timeline2/view/qml/timelineitems.h"
#include
#ifndef GL_UNPACK_ROW_LENGTH
#ifdef GL_UNPACK_ROW_LENGTH_EXT
#define GL_UNPACK_ROW_LENGTH GL_UNPACK_ROW_LENGTH_EXT
#else
#error GL_UNPACK_ROW_LENGTH undefined
#endif
#endif
#ifdef QT_NO_DEBUG
#define check_error(fn) \
{ \
}
#else
#define check_error(fn) \
{ \
uint err = fn->glGetError(); \
if (err != GL_NO_ERROR) { \
qCCritical(KDENLIVE_LOG) << "GL error" << hex << err << dec << "at" << __FILE__ << ":" << __LINE__; \
} \
}
#endif
#ifndef GL_TIMEOUT_IGNORED
#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull
#endif
using namespace Mlt;
GLWidget::GLWidget(int id, QObject *parent)
: QQuickView((QWindow *)parent)
, sendFrameForAnalysis(false)
, m_glslManager(nullptr)
, m_consumer(nullptr)
, m_producer(nullptr)
, m_id(id)
, m_rulerHeight(QFontMetrics(QApplication::font()).lineSpacing() * 0.7)
, m_shader(nullptr)
, m_initSem(0)
, m_analyseSem(1)
, m_isInitialized(false)
, m_threadStartEvent(nullptr)
, m_threadStopEvent(nullptr)
, m_threadCreateEvent(nullptr)
, m_threadJoinEvent(nullptr)
, m_displayEvent(nullptr)
, m_frameRenderer(nullptr)
, m_projectionLocation(0)
, m_modelViewLocation(0)
, m_vertexLocation(0)
, m_texCoordLocation(0)
, m_colorspaceLocation(0)
, m_zoom(1.0f)
, m_sendFrame(false)
, m_isZoneMode(false)
, m_isLoopMode(false)
, m_offset(QPoint(0, 0))
, m_audioWaveDisplayed(false)
, m_fbo(nullptr)
, m_shareContext(nullptr)
, m_openGLSync(false)
, m_ClientWaitSync(nullptr)
{
KDeclarative::KDeclarative kdeclarative;
kdeclarative.setDeclarativeEngine(engine());
#if KDECLARATIVE_VERSION >= QT_VERSION_CHECK(5, 45, 0)
kdeclarative.setupEngine(engine());
kdeclarative.setupContext();
#else
kdeclarative.setupBindings();
#endif
m_texture[0] = m_texture[1] = m_texture[2] = 0;
qRegisterMetaType("Mlt::Frame");
qRegisterMetaType("SharedFrame");
qmlRegisterType("AudioThumb", 1, 0, "QmlAudioThumb");
setPersistentOpenGLContext(true);
setPersistentSceneGraph(true);
setClearBeforeRendering(false);
setResizeMode(QQuickView::SizeRootObjectToView);
m_offscreenSurface.setFormat(QWindow::format());
m_offscreenSurface.create();
m_refreshTimer.setSingleShot(true);
m_refreshTimer.setInterval(50);
m_blackClip.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), "color:black"));
m_blackClip->set("kdenlive:id", "black");
m_blackClip->set("out", 3);
connect(&m_refreshTimer, &QTimer::timeout, this, &GLWidget::refresh);
m_producer = m_blackClip;
rootContext()->setContextProperty("markersModel", 0);
if (!initGPUAccel()) {
disableGPUAccel();
}
connect(this, &QQuickWindow::sceneGraphInitialized, this, &GLWidget::initializeGL, Qt::DirectConnection);
connect(this, &QQuickWindow::beforeRendering, this, &GLWidget::paintGL, Qt::DirectConnection);
+ connect(this, &GLWidget::buildAudioThumb, this, &GLWidget::setAudioThumb);
+
registerTimelineItems();
m_proxy = new MonitorProxy(this);
connect(m_proxy, &MonitorProxy::seekRequestChanged, this, &GLWidget::requestSeek);
rootContext()->setContextProperty("controller", m_proxy);
}
GLWidget::~GLWidget()
{
// C & D
delete m_glslManager;
delete m_threadStartEvent;
delete m_threadStopEvent;
delete m_threadCreateEvent;
delete m_threadJoinEvent;
delete m_displayEvent;
if (m_frameRenderer) {
if (m_frameRenderer->isRunning()) {
QMetaObject::invokeMethod(m_frameRenderer, "cleanup");
m_frameRenderer->quit();
m_frameRenderer->wait();
m_frameRenderer->deleteLater();
} else {
delete m_frameRenderer;
}
}
m_blackClip.reset();
delete m_shareContext;
delete m_shader;
// delete pCore->getCurrentProfile();
}
void GLWidget::updateAudioForAnalysis()
{
if (m_frameRenderer) {
m_frameRenderer->sendAudioForAnalysis = KdenliveSettings::monitor_audio();
}
}
void GLWidget::initializeGL()
{
if (m_isInitialized || !isVisible() || (openglContext() == nullptr)) return;
openglContext()->makeCurrent(&m_offscreenSurface);
initializeOpenGLFunctions();
qCDebug(KDENLIVE_LOG) << "OpenGL vendor: " << QString::fromUtf8((const char *)glGetString(GL_VENDOR));
qCDebug(KDENLIVE_LOG) << "OpenGL renderer: " << QString::fromUtf8((const char *)glGetString(GL_RENDERER));
qCDebug(KDENLIVE_LOG) << "OpenGL Threaded: " << openglContext()->supportsThreadedOpenGL();
qCDebug(KDENLIVE_LOG) << "OpenGL ARG_SYNC: " << openglContext()->hasExtension("GL_ARB_sync");
qCDebug(KDENLIVE_LOG) << "OpenGL OpenGLES: " << openglContext()->isOpenGLES();
// C & D
if (onlyGLESGPUAccel()) {
disableGPUAccel();
}
createShader();
m_openGLSync = initGPUAccelSync();
// C & D
if (m_glslManager) {
// Create a context sharing with this context for the RenderThread context.
// This is needed because openglContext() is active in another thread
// at the time that RenderThread is created.
// See this Qt bug for more info: https://bugreports.qt.io/browse/QTBUG-44677
// TODO: QTBUG-44677 is closed. still applicable?
m_shareContext = new QOpenGLContext;
m_shareContext->setFormat(openglContext()->format());
m_shareContext->setShareContext(openglContext());
m_shareContext->create();
}
m_frameRenderer = new FrameRenderer(openglContext(), &m_offscreenSurface, m_ClientWaitSync);
m_frameRenderer->sendAudioForAnalysis = KdenliveSettings::monitor_audio();
openglContext()->makeCurrent(this);
// openglContext()->blockSignals(false);
connect(m_frameRenderer, &FrameRenderer::frameDisplayed, this, &GLWidget::frameDisplayed, Qt::QueuedConnection);
connect(m_frameRenderer, &FrameRenderer::textureReady, this, &GLWidget::updateTexture, Qt::DirectConnection);
connect(m_frameRenderer, &FrameRenderer::frameDisplayed, this, &GLWidget::onFrameDisplayed, Qt::QueuedConnection);
connect(m_frameRenderer, &FrameRenderer::audioSamplesSignal, this, &GLWidget::audioSamplesSignal, Qt::QueuedConnection);
m_initSem.release();
m_isInitialized = true;
reconfigure();
}
void GLWidget::resizeGL(int width, int height)
{
int x, y, w, h;
height -= m_rulerHeight;
double this_aspect = (double)width / height;
double video_aspect = pCore->getCurrentProfile()->dar();
// Special case optimization to negate odd effect of sample aspect ratio
// not corresponding exactly with image resolution.
if ((int)(this_aspect * 1000) == (int)(video_aspect * 1000)) {
w = width;
h = height;
}
// Use OpenGL to normalise sample aspect ratio
else if (height * video_aspect > width) {
w = width;
h = width / video_aspect;
} else {
w = height * video_aspect;
h = height;
}
x = (width - w) / 2;
y = (height - h) / 2;
m_rect.setRect(x, y, w, h);
double scalex = (double)m_rect.width() / pCore->getCurrentProfile()->width() * m_zoom;
double scaley = (double)m_rect.width() /
((double)pCore->getCurrentProfile()->height() * pCore->getCurrentProfile()->dar() / pCore->getCurrentProfile()->width()) /
pCore->getCurrentProfile()->width() * m_zoom;
QPoint center = m_rect.center();
QQuickItem *rootQml = rootObject();
if (rootQml) {
rootQml->setProperty("center", center);
rootQml->setProperty("scalex", scalex);
rootQml->setProperty("scaley", scaley);
if (rootQml->objectName() == QLatin1String("rootsplit")) {
// Adjust splitter pos
rootQml->setProperty("splitterPos", x + (rootQml->property("realpercent").toDouble() * w));
}
}
emit rectChanged();
}
void GLWidget::resizeEvent(QResizeEvent *event)
{
resizeGL(event->size().width(), event->size().height());
QQuickView::resizeEvent(event);
}
void GLWidget::createGPUAccelFragmentProg()
{
m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, "uniform sampler2D tex;"
"varying highp vec2 coordinates;"
"void main(void) {"
" gl_FragColor = texture2D(tex, coordinates);"
"}");
m_shader->link();
m_textureLocation[0] = m_shader->uniformLocation("tex");
}
void GLWidget::createShader()
{
m_shader = new QOpenGLShaderProgram;
m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, "uniform highp mat4 projection;"
"uniform highp mat4 modelView;"
"attribute highp vec4 vertex;"
"attribute highp vec2 texCoord;"
"varying highp vec2 coordinates;"
"void main(void) {"
" gl_Position = projection * modelView * vertex;"
" coordinates = texCoord;"
"}");
// C & D
if (m_glslManager) {
createGPUAccelFragmentProg();
} else {
// A & B
createYUVTextureProjectFragmentProg();
}
m_projectionLocation = m_shader->uniformLocation("projection");
m_modelViewLocation = m_shader->uniformLocation("modelView");
m_vertexLocation = m_shader->attributeLocation("vertex");
m_texCoordLocation = m_shader->attributeLocation("texCoord");
}
void GLWidget::createYUVTextureProjectFragmentProg()
{
m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment,
"uniform sampler2D Ytex, Utex, Vtex;"
"uniform lowp int colorspace;"
"varying highp vec2 coordinates;"
"void main(void) {"
" mediump vec3 texel;"
" texel.r = texture2D(Ytex, coordinates).r - 0.0625;" // Y
" texel.g = texture2D(Utex, coordinates).r - 0.5;" // U
" texel.b = texture2D(Vtex, coordinates).r - 0.5;" // V
" mediump mat3 coefficients;"
" if (colorspace == 601) {"
" coefficients = mat3("
" 1.1643, 1.1643, 1.1643," // column 1
" 0.0, -0.39173, 2.017," // column 2
" 1.5958, -0.8129, 0.0);" // column 3
" } else {" // ITU-R 709
" coefficients = mat3("
" 1.1643, 1.1643, 1.1643," // column 1
" 0.0, -0.213, 2.112," // column 2
" 1.793, -0.533, 0.0);" // column 3
" }"
" gl_FragColor = vec4(coefficients * texel, 1.0);"
"}");
m_shader->link();
m_textureLocation[0] = m_shader->uniformLocation("Ytex");
m_textureLocation[1] = m_shader->uniformLocation("Utex");
m_textureLocation[2] = m_shader->uniformLocation("Vtex");
m_colorspaceLocation = m_shader->uniformLocation("colorspace");
}
static void uploadTextures(QOpenGLContext *context, const SharedFrame &frame, GLuint texture[])
{
int width = frame.get_image_width();
int height = frame.get_image_height();
const uint8_t *image = frame.get_image();
QOpenGLFunctions *f = context->functions();
// The planes of pixel data may not be a multiple of the default 4 bytes.
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Upload each plane of YUV to a texture.
if (texture[0] != 0u) {
f->glDeleteTextures(3, texture);
}
check_error(f);
f->glGenTextures(3, texture);
check_error(f);
f->glBindTexture(GL_TEXTURE_2D, texture[0]);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image);
check_error(f);
f->glBindTexture(GL_TEXTURE_2D, texture[1]);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image + width * height);
check_error(f);
f->glBindTexture(GL_TEXTURE_2D, texture[2]);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image + width * height + width / 2 * height / 2);
check_error(f);
}
void GLWidget::clear()
{
stopGlsl();
update();
}
void GLWidget::releaseAnalyse()
{
m_analyseSem.release();
}
bool GLWidget::acquireSharedFrameTextures()
{
// A
if ((m_glslManager == nullptr) && !openglContext()->supportsThreadedOpenGL()) {
QMutexLocker locker(&m_contextSharedAccess);
if (!m_sharedFrame.is_valid()) {
return false;
}
uploadTextures(openglContext(), m_sharedFrame, m_texture);
} else if (m_glslManager) {
// C & D
m_contextSharedAccess.lock();
if (m_sharedFrame.is_valid()) {
m_texture[0] = *((const GLuint *)m_sharedFrame.get_image());
}
}
if (!m_texture[0]) {
// C & D
if (m_glslManager) m_contextSharedAccess.unlock();
return false;
}
return true;
}
void GLWidget::bindShaderProgram()
{
m_shader->bind();
// C & D
if (m_glslManager) {
m_shader->setUniformValue(m_textureLocation[0], 0);
} else {
// A & B
m_shader->setUniformValue(m_textureLocation[0], 0);
m_shader->setUniformValue(m_textureLocation[1], 1);
m_shader->setUniformValue(m_textureLocation[2], 2);
m_shader->setUniformValue(m_colorspaceLocation, pCore->getCurrentProfile()->colorspace());
}
}
void GLWidget::releaseSharedFrameTextures()
{
// C & D
if (m_glslManager) {
glFinish();
m_contextSharedAccess.unlock();
}
}
bool GLWidget::initGPUAccel()
{
if (!KdenliveSettings::gpu_accel()) return false;
m_glslManager = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "glsl.manager");
return m_glslManager->is_valid();
}
// C & D
// TODO: insure safe, idempotent on all pipelines.
void GLWidget::disableGPUAccel()
{
delete m_glslManager;
m_glslManager = nullptr;
KdenliveSettings::setGpu_accel(false);
// Need to destroy MLT global reference to prevent filters from trying to use GPU.
mlt_properties_set_data(mlt_global_properties(), "glslManager", nullptr, 0, nullptr, nullptr);
emit gpuNotSupported();
}
bool GLWidget::onlyGLESGPUAccel() const
{
return (m_glslManager != nullptr) && openglContext()->isOpenGLES();
}
#if defined(Q_OS_WIN)
bool GLWidget::initGPUAccelSync()
{
// no-op
// TODO: getProcAddress is not working on Windows?
return false;
}
#else
bool GLWidget::initGPUAccelSync()
{
if (!KdenliveSettings::gpu_accel()) return false;
if (m_glslManager == nullptr) return false;
if (!openglContext()->hasExtension("GL_ARB_sync")) return false;
m_ClientWaitSync = (ClientWaitSync_fp)openglContext()->getProcAddress("glClientWaitSync");
if (m_ClientWaitSync) {
return true;
} else {
qCDebug(KDENLIVE_LOG) << " / / // NO GL SYNC, ERROR";
// fallback on A || B
// TODO: fallback on A || B || C?
disableGPUAccel();
return false;
}
}
#endif
void GLWidget::paintGL()
{
QOpenGLFunctions *f = openglContext()->functions();
int width = this->width() * devicePixelRatio();
int height = this->height() * devicePixelRatio();
f->glDisable(GL_BLEND);
f->glDisable(GL_DEPTH_TEST);
f->glDepthMask(GL_FALSE);
f->glViewport(0, (m_rulerHeight * devicePixelRatio() * 0.5 + 0.5), width, height);
check_error(f);
QColor color(KdenliveSettings::window_background());
f->glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
f->glClear(GL_COLOR_BUFFER_BIT);
check_error(f);
if (!acquireSharedFrameTextures()) return;
// Bind textures.
for (uint i = 0; i < 3; ++i) {
if (m_texture[i] != 0u) {
f->glActiveTexture(GL_TEXTURE0 + i);
f->glBindTexture(GL_TEXTURE_2D, m_texture[i]);
check_error(f);
}
}
bindShaderProgram();
check_error(f);
// Setup an orthographic projection.
QMatrix4x4 projection;
projection.scale(2.0f / (float)width, 2.0f / (float)height);
m_shader->setUniformValue(m_projectionLocation, projection);
check_error(f);
// Set model view.
QMatrix4x4 modelView;
if (!qFuzzyCompare(m_zoom, 1.0f)) {
if ((offset().x() != 0) || (offset().y() != 0)) modelView.translate(-offset().x() * devicePixelRatio(), offset().y() * devicePixelRatio());
modelView.scale(zoom(), zoom());
}
m_shader->setUniformValue(m_modelViewLocation, modelView);
check_error(f);
// Provide vertices of triangle strip.
QVector vertices;
width = m_rect.width() * devicePixelRatio();
height = m_rect.height() * devicePixelRatio();
vertices << QVector2D(float(-width) / 2.0f, float(-height) / 2.0f);
vertices << QVector2D(float(-width) / 2.0f, float(height) / 2.0f);
vertices << QVector2D(float(width) / 2.0f, float(-height) / 2.0f);
vertices << QVector2D(float(width) / 2.0f, float(height) / 2.0f);
m_shader->enableAttributeArray(m_vertexLocation);
check_error(f);
m_shader->setAttributeArray(m_vertexLocation, vertices.constData());
check_error(f);
// Provide texture coordinates.
QVector texCoord;
texCoord << QVector2D(0.0f, 1.0f);
texCoord << QVector2D(0.0f, 0.0f);
texCoord << QVector2D(1.0f, 1.0f);
texCoord << QVector2D(1.0f, 0.0f);
m_shader->enableAttributeArray(m_texCoordLocation);
check_error(f);
m_shader->setAttributeArray(m_texCoordLocation, texCoord.constData());
check_error(f);
// Render
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size());
check_error(f);
if (m_sendFrame && m_analyseSem.tryAcquire(1)) {
// Render RGB frame for analysis
int fullWidth = pCore->getCurrentProfile()->width();
int fullHeight = pCore->getCurrentProfile()->height();
if ((m_fbo == nullptr) || m_fbo->size() != QSize(fullWidth, fullHeight)) {
delete m_fbo;
QOpenGLFramebufferObjectFormat fmt;
fmt.setSamples(1);
fmt.setInternalTextureFormat(GL_RGB); // GL_RGBA32F); // which one is the fastest ?
m_fbo = new QOpenGLFramebufferObject(fullWidth, fullHeight, fmt); // GL_TEXTURE_2D);
}
m_fbo->bind();
glViewport(0, 0, fullWidth, fullHeight);
QMatrix4x4 projection2;
projection2.scale(2.0f / (float)width, 2.0f / (float)height);
m_shader->setUniformValue(m_projectionLocation, projection2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size());
check_error(f);
m_fbo->release();
emit analyseFrame(m_fbo->toImage());
m_sendFrame = false;
}
// Cleanup
m_shader->disableAttributeArray(m_vertexLocation);
m_shader->disableAttributeArray(m_texCoordLocation);
m_shader->release();
for (uint i = 0; i < 3; ++i) {
if (m_texture[i] != 0u) {
f->glActiveTexture(GL_TEXTURE0 + i);
f->glBindTexture(GL_TEXTURE_2D, 0);
check_error(f);
}
}
glActiveTexture(GL_TEXTURE0);
check_error(f);
releaseSharedFrameTextures();
check_error(f);
}
void GLWidget::slotZoom(bool zoomIn)
{
if (zoomIn) {
if (qFuzzyCompare(m_zoom, 1.0f)) {
setZoom(2.0f);
} else if (qFuzzyCompare(m_zoom, 2.0f)) {
setZoom(3.0f);
} else if (m_zoom < 1.0f) {
setZoom(m_zoom * 2);
}
} else {
if (qFuzzyCompare(m_zoom, 3.0f)) {
setZoom(2.0);
} else if (qFuzzyCompare(m_zoom, 2.0f)) {
setZoom(1.0);
} else if (m_zoom > 0.2) {
setZoom(m_zoom / 2);
}
}
}
void GLWidget::wheelEvent(QWheelEvent *event)
{
if (((event->modifiers() & Qt::ControlModifier) != 0u) && ((event->modifiers() & Qt::ShiftModifier) != 0u)) {
slotZoom(event->delta() > 0);
return;
}
emit mouseSeek(event->delta(), (uint)event->modifiers());
event->accept();
}
void GLWidget::requestSeek()
{
if (!m_producer) {
return;
}
if (m_proxy->seeking()) {
m_producer->seek(m_proxy->seekPosition());
if (!qFuzzyIsNull(m_producer->get_speed())) {
m_consumer->purge();
}
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("refresh", 1);
}
}
void GLWidget::seek(int pos)
{
if (!m_proxy->seeking()) {
m_proxy->setSeekPosition(pos);
m_producer->seek(pos);
if (m_consumer->is_stopped()) {
m_consumer->start();
} else {
m_consumer->purge();
m_consumer->set("refresh", 1);
}
} else {
m_proxy->setSeekPosition(pos);
}
}
void GLWidget::requestRefresh()
{
if (m_proxy->seeking()) {
return;
}
if (m_producer && qFuzzyIsNull(m_producer->get_speed())) {
m_refreshTimer.start();
}
}
QString GLWidget::frameToTime(int frames) const
{
return m_consumer ? m_consumer->frames_to_time(frames, mlt_time_smpte_df) : QStringLiteral("-");
}
void GLWidget::refresh()
{
m_refreshTimer.stop();
if (m_proxy->seeking()) {
return;
}
QMutexLocker locker(&m_mltMutex);
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("refresh", 1);
}
bool GLWidget::checkFrameNumber(int pos, int offset)
{
emit consumerPosition(pos);
if (!m_proxy->setPosition(pos)) {
emit seekPosition(m_proxy->seekOrCurrentPosition());
}
const double speed = m_producer->get_speed();
if (m_proxy->seeking()) {
m_producer->set_speed(0);
m_producer->seek(m_proxy->seekPosition());
if (qFuzzyIsNull(speed)) {
m_consumer->set("refresh", 1);
} else {
m_producer->set_speed(speed);
}
return true;
}
int maxPos = m_producer->get_int("out");
if (m_isLoopMode || m_isZoneMode) {
if (qFuzzyIsNull(speed) && pos >= maxPos) {
m_consumer->purge();
if (!m_isLoopMode) {
return false;
}
m_producer->seek(m_proxy->zoneIn());
m_producer->set_speed(1.0);
m_consumer->set("refresh", 1);
return true;
}
return true;
} else if (!qFuzzyIsNull(speed)) {
maxPos -= offset;
if (pos >= (maxPos - 1) && speed > 0.) {
// Playing past last clip, pause
m_producer->set_speed(0);
m_consumer->set("refresh", 0);
m_consumer->purge();
m_producer->seek(qMax(0, maxPos));
return false;
} else if (pos <= 0 && speed < 0.) {
// rewinding reached 0, pause
m_producer->set_speed(0);
m_consumer->set("refresh", 0);
m_consumer->purge();
m_producer->seek(0);
return false;
}
}
return true;
}
void GLWidget::mousePressEvent(QMouseEvent *event)
{
if ((rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") && !(event->modifiers() & Qt::ControlModifier) &&
!(event->buttons() & Qt::MiddleButton)) {
event->ignore();
QQuickView::mousePressEvent(event);
return;
}
if ((event->button() & Qt::LeftButton) != 0u) {
if ((event->modifiers() & Qt::ControlModifier) != 0u) {
// Pan view
m_panStart = event->pos();
setCursor(Qt::ClosedHandCursor);
} else {
m_dragStart = event->pos();
}
} else if ((event->button() & Qt::RightButton) != 0u) {
emit showContextMenu(event->globalPos());
} else if ((event->button() & Qt::MiddleButton) != 0u) {
m_panStart = event->pos();
setCursor(Qt::ClosedHandCursor);
}
event->accept();
QQuickView::mousePressEvent(event);
}
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
if ((rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") && !(event->modifiers() & Qt::ControlModifier) &&
!(event->buttons() & Qt::MiddleButton)) {
event->ignore();
QQuickView::mouseMoveEvent(event);
return;
}
/* if (event->modifiers() == Qt::ShiftModifier && m_producer) {
emit seekTo(m_producer->get_length() * event->x() / width());
return;
}*/
QQuickView::mouseMoveEvent(event);
if (!m_panStart.isNull()) {
emit panView(m_panStart - event->pos());
m_panStart = event->pos();
event->accept();
QQuickView::mouseMoveEvent(event);
return;
}
if (!(event->buttons() & Qt::LeftButton)) {
QQuickView::mouseMoveEvent(event);
return;
}
if (!event->isAccepted() && !m_dragStart.isNull() && (event->pos() - m_dragStart).manhattanLength() >= QApplication::startDragDistance()) {
m_dragStart = QPoint();
emit startDrag();
}
}
void GLWidget::keyPressEvent(QKeyEvent *event)
{
QQuickView::keyPressEvent(event);
if (!event->isAccepted()) {
emit passKeyEvent(event);
}
}
void GLWidget::createThread(RenderThread **thread, thread_function_t function, void *data)
{
#ifdef Q_OS_WIN
// On Windows, MLT event consumer-thread-create is fired from the Qt main thread.
while (!m_isInitialized) {
qApp->processEvents();
}
#else
if (!m_isInitialized) {
m_initSem.acquire();
}
#endif
(*thread) = new RenderThread(function, data, m_shareContext, &m_offscreenSurface);
(*thread)->start();
}
static void onThreadCreate(mlt_properties owner, GLWidget *self, RenderThread **thread, int *priority, thread_function_t function, void *data)
{
Q_UNUSED(owner)
Q_UNUSED(priority)
// self->clearFrameRenderer();
self->createThread(thread, function, data);
self->lockMonitor();
}
static void onThreadJoin(mlt_properties owner, GLWidget *self, RenderThread *thread)
{
Q_UNUSED(owner)
if (thread) {
thread->quit();
thread->wait();
delete thread;
// self->clearFrameRenderer();
self->releaseMonitor();
}
}
void GLWidget::startGlsl()
{
// C & D
if (m_glslManager) {
// clearFrameRenderer();
m_glslManager->fire_event("init glsl");
if (m_glslManager->get_int("glsl_supported") == 0) {
disableGPUAccel();
} else {
emit started();
}
}
}
static void onThreadStarted(mlt_properties owner, GLWidget *self)
{
Q_UNUSED(owner)
self->startGlsl();
}
void GLWidget::releaseMonitor()
{
emit lockMonitor(false);
}
void GLWidget::lockMonitor()
{
emit lockMonitor(true);
}
void GLWidget::stopGlsl()
{
if (m_consumer) {
m_consumer->purge();
}
// C & D
// TODO This is commented out for now because it is causing crashes.
// Technically, this should be the correct thing to do, but it appears
// some changes have created regression (see shotcut)
// with respect to restarting the consumer in GPU mode.
// m_glslManager->fire_event("close glsl");
m_texture[0] = 0;
}
static void onThreadStopped(mlt_properties owner, GLWidget *self)
{
Q_UNUSED(owner)
self->stopGlsl();
}
void GLWidget::slotSwitchAudioOverlay(bool enable)
{
KdenliveSettings::setDisplayAudioOverlay(enable);
if (m_audioWaveDisplayed && !enable) {
if (m_producer && m_producer->get_int("video_index") != -1) {
// We have a video producer, disable filter
removeAudioOverlay();
}
}
if (enable && !m_audioWaveDisplayed && m_producer) {
createAudioOverlay(m_producer->get_int("video_index") == -1);
}
}
int GLWidget::setProducer(const std::shared_ptr &producer, bool isActive, int position)
{
int error = 0;
QString currentId;
int consumerPosition = 0;
currentId = m_producer->parent().get("kdenlive:id");
if (producer) {
m_producer = producer;
} else {
if (currentId == QLatin1String("black")) {
return 0;
}
if (m_audioWaveDisplayed) {
removeAudioOverlay();
}
m_producer = m_blackClip;
// Reset markersModel
rootContext()->setContextProperty("markersModel", 0);
}
// redundant check. postcondition of above is m_producer != null
if (m_producer) {
m_producer->set_speed(0);
if (m_consumer) {
consumerPosition = m_consumer->position();
m_consumer->stop();
if (!m_consumer->is_stopped()) {
m_consumer->stop();
}
}
error = reconfigure();
if (error == 0) {
// The profile display aspect ratio may have changed.
resizeGL(width(), height());
}
} else {
return error;
}
if (!m_consumer) {
return error;
}
consumerPosition = m_consumer->position();
if (m_producer->get_int("video_index") == -1) {
// This is an audio only clip, attach visualization filter. Currently, the filter crashes MLT when Movit accel is used
if (!m_audioWaveDisplayed) {
createAudioOverlay(true);
} else if (m_consumer) {
if (KdenliveSettings::gpu_accel()) {
removeAudioOverlay();
} else {
adjustAudioOverlay(true);
}
}
} else if (m_audioWaveDisplayed && (m_consumer != nullptr)) {
// This is not an audio clip, hide wave
if (KdenliveSettings::displayAudioOverlay()) {
adjustAudioOverlay(m_producer->get_int("video_index") == -1);
} else {
removeAudioOverlay();
}
} else if (KdenliveSettings::displayAudioOverlay()) {
createAudioOverlay(false);
}
if (position == -1 && m_producer->parent().get("kdenlive:id") == currentId) {
position = consumerPosition;
}
if (isActive) {
startConsumer();
}
m_proxy->requestSeekPosition(position > 0 ? position : m_producer->position());
return error;
}
int GLWidget::droppedFrames() const
{
return (m_consumer ? m_consumer->get_int("drop_count") : 0);
}
void GLWidget::resetDrops()
{
if (m_consumer) {
m_consumer->set("drop_count", 0);
}
}
void GLWidget::createAudioOverlay(bool isAudio)
{
if (!m_consumer) {
return;
}
if (isAudio && KdenliveSettings::gpu_accel()) {
// Audiowaveform filter crashes on Movit + audio clips)
return;
}
Mlt::Filter f(pCore->getCurrentProfile()->profile(), "audiowaveform");
if (f.is_valid()) {
// f.set("show_channel", 1);
f.set("color.1", "0xffff0099");
f.set("fill", 1);
if (isAudio) {
// Fill screen
f.set("rect", "0,0,100%,100%");
} else {
// Overlay on lower part of the screen
f.set("rect", "0,80%,100%,20%");
}
m_consumer->attach(f);
m_audioWaveDisplayed = true;
}
}
void GLWidget::removeAudioOverlay()
{
Mlt::Service sourceService(m_consumer->get_service());
// move all effects to the correct producer
int ct = 0;
Mlt::Filter *filter = sourceService.filter(ct);
while (filter != nullptr) {
QString srv = filter->get("mlt_service");
if (srv == QLatin1String("audiowaveform")) {
sourceService.detach(*filter);
delete filter;
break;
} else {
ct++;
}
filter = sourceService.filter(ct);
}
m_audioWaveDisplayed = false;
}
void GLWidget::adjustAudioOverlay(bool isAudio)
{
Mlt::Service sourceService(m_consumer->get_service());
// move all effects to the correct producer
int ct = 0;
Mlt::Filter *filter = sourceService.filter(ct);
while (filter != nullptr) {
QString srv = filter->get("mlt_service");
if (srv == QLatin1String("audiowaveform")) {
if (isAudio) {
filter->set("rect", "0,0,100%,100%");
} else {
filter->set("rect", "0,80%,100%,20%");
}
break;
} else {
ct++;
}
filter = sourceService.filter(ct);
}
}
void GLWidget::stopCapture()
{
if (strcmp(m_consumer->get("mlt_service"), "multi") == 0) {
m_consumer->set("refresh", 0);
m_consumer->purge();
m_consumer->stop();
}
}
int GLWidget::reconfigureMulti(const QString ¶ms, const QString &path, Mlt::Profile *profile)
{
Q_UNUSED(params);
Q_UNUSED(path);
Q_UNUSED(profile);
// TODO Fix or delete
/*
QString serviceName = property("mlt_service").toString();
if ((m_consumer == nullptr) || !m_consumer->is_valid() || strcmp(m_consumer->get("mlt_service"), "multi") != 0) {
if (m_consumer) {
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
}
m_consumer.reset(new Mlt::FilteredConsumer(*profile, "multi"));
delete m_threadStartEvent;
m_threadStartEvent = nullptr;
delete m_threadStopEvent;
m_threadStopEvent = nullptr;
delete m_threadCreateEvent;
delete m_threadJoinEvent;
if (m_consumer) {
m_threadCreateEvent = m_consumer->listen("consumer-thread-create", this, (mlt_listener)onThreadCreate);
m_threadJoinEvent = m_consumer->listen("consumer-thread-join", this, (mlt_listener)onThreadJoin);
}
}
if (m_consumer->is_valid()) {
// build sub consumers
// m_consumer->set("mlt_image_format", "yuv422");
reloadProfile();
int volume = KdenliveSettings::volume();
m_consumer->set("0", serviceName.toUtf8().constData());
m_consumer->set("0.mlt_image_format", "yuv422");
m_consumer->set("0.terminate_on_pause", 0);
// m_consumer->set("0.preview_off", 1);
m_consumer->set("0.real_time", 0);
m_consumer->set("0.volume", (double)volume / 100);
if (serviceName.startsWith(QLatin1String("sdl_audio"))) {
#ifdef Q_OS_WIN
m_consumer->set("0.audio_buffer", 2048);
#else
m_consumer->set("0.audio_buffer", 512);
#endif
QString audioDevice = KdenliveSettings::audiodevicename();
if (!audioDevice.isEmpty()) {
m_consumer->set("audio_device", audioDevice.toUtf8().constData());
}
QString audioDriver = KdenliveSettings::audiodrivername();
if (!audioDriver.isEmpty()) {
m_consumer->set("audio_driver", audioDriver.toUtf8().constData());
}
}
m_consumer->set("1", "avformat");
m_consumer->set("1.target", path.toUtf8().constData());
// m_consumer->set("1.real_time", -KdenliveSettings::mltthreads());
m_consumer->set("terminate_on_pause", 0);
m_consumer->set("1.terminate_on_pause", 0);
// m_consumer->set("1.terminate_on_pause", 0);// was commented out. restoring it fixes mantis#3415 - FFmpeg recording freezes
QStringList paramList = params.split(' ', QString::SkipEmptyParts);
for (int i = 0; i < paramList.count(); ++i) {
QString key = "1." + paramList.at(i).section(QLatin1Char('='), 0, 0);
QString value = paramList.at(i).section(QLatin1Char('='), 1, 1);
if (value == QLatin1String("%threads")) {
value = QString::number(QThread::idealThreadCount());
}
m_consumer->set(key.toUtf8().constData(), value.toUtf8().constData());
}
// Connect the producer to the consumer - tell it to "run" later
delete m_displayEvent;
// C & D
if (m_glslManager) {
// D
if (m_openGLSync) {
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_frame_show);
} else {
// C
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_nosync_frame_show);
}
} else {
// A & B
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_frame_show);
}
m_consumer->connect(*m_producer.get());
m_consumer->start();
return 0;
}
*/
return -1;
}
int GLWidget::reconfigure(bool reload)
{
int error = 0;
// use SDL for audio, OpenGL for video
QString serviceName = property("mlt_service").toString();
if (reload) {
m_blackClip.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), "color:black"));
m_blackClip->set("kdenlive:id", "black");
reloadProfile();
return error;
}
if ((m_consumer == nullptr) || !m_consumer->is_valid() || strcmp(m_consumer->get("mlt_service"), "multi") == 0) {
if (m_consumer) {
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
}
QString audioBackend = (KdenliveSettings::external_display()) ? QString("decklink:%1").arg(KdenliveSettings::blackmagic_output_device())
: KdenliveSettings::audiobackend();
if (m_consumer == nullptr || serviceName.isEmpty() || serviceName != audioBackend) {
m_consumer.reset(new Mlt::FilteredConsumer(pCore->getCurrentProfile()->profile(), audioBackend.toLatin1().constData()));
if (m_consumer->is_valid()) {
serviceName = audioBackend;
setProperty("mlt_service", serviceName);
if (KdenliveSettings::external_display()) {
m_consumer->set("terminate_on_pause", 0);
}
} else {
// Warning, audio backend unavailable on system
m_consumer.reset();
QStringList backends = {"sdl2_audio", "sdl_audio", "rtaudio"};
for (const QString &bk : backends) {
if (bk == audioBackend) {
// Already tested
continue;
}
m_consumer.reset(new Mlt::FilteredConsumer(pCore->getCurrentProfile()->profile(), bk.toLatin1().constData()));
if (m_consumer->is_valid()) {
if (audioBackend == KdenliveSettings::sdlAudioBackend()) {
// switch sdl audio backend
KdenliveSettings::setSdlAudioBackend(bk);
}
qDebug() << "++++++++\nSwitching audio backend to: " << bk << "\n++++++++++";
KdenliveSettings::setAudiobackend(bk);
serviceName = bk;
setProperty("mlt_service", serviceName);
break;
} else {
m_consumer.reset();
}
}
if (!m_consumer) {
qWarning() << "WARNING, NO AUDIO BACKEND FOUND";
return -1;
}
}
}
delete m_threadStartEvent;
m_threadStartEvent = nullptr;
delete m_threadStopEvent;
m_threadStopEvent = nullptr;
delete m_threadCreateEvent;
delete m_threadJoinEvent;
if (m_consumer) {
m_threadCreateEvent = m_consumer->listen("consumer-thread-create", this, (mlt_listener)onThreadCreate);
m_threadJoinEvent = m_consumer->listen("consumer-thread-join", this, (mlt_listener)onThreadJoin);
}
}
if (m_consumer->is_valid()) {
// Connect the producer to the consumer - tell it to "run" later
if (m_producer) {
m_consumer->connect(*m_producer.get());
// m_producer->set_speed(0.0);
}
int dropFrames = realTime();
if (!KdenliveSettings::monitor_dropframes()) {
dropFrames = -dropFrames;
}
m_consumer->set("real_time", dropFrames);
// C & D
if (m_glslManager) {
if (!m_threadStartEvent) {
m_threadStartEvent = m_consumer->listen("consumer-thread-started", this, (mlt_listener)onThreadStarted);
}
if (!m_threadStopEvent) {
m_threadStopEvent = m_consumer->listen("consumer-thread-stopped", this, (mlt_listener)onThreadStopped);
}
if (!serviceName.startsWith(QLatin1String("decklink"))) {
m_consumer->set("mlt_image_format", "glsl");
}
} else {
// A & B
m_consumer->set("mlt_image_format", "yuv422");
}
delete m_displayEvent;
// C & D
if (m_glslManager) {
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_frame_show);
} else {
// A & B
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_frame_show);
}
int volume = KdenliveSettings::volume();
if (serviceName.startsWith(QLatin1String("sdl_audio"))) {
QString audioDevice = KdenliveSettings::audiodevicename();
if (!audioDevice.isEmpty()) {
m_consumer->set("audio_device", audioDevice.toUtf8().constData());
}
QString audioDriver = KdenliveSettings::audiodrivername();
if (!audioDriver.isEmpty()) {
m_consumer->set("audio_driver", audioDriver.toUtf8().constData());
}
}
/*if (!pCore->getCurrentProfile()->progressive())
m_consumer->set("progressive", property("progressive").toBool());*/
m_consumer->set("volume", volume / 100.0);
// m_consumer->set("progressive", 1);
m_consumer->set("rescale", KdenliveSettings::mltinterpolation().toUtf8().constData());
m_consumer->set("deinterlace_method", KdenliveSettings::mltdeinterlacer().toUtf8().constData());
/*
#ifdef Q_OS_WIN
m_consumer->set("audio_buffer", 2048);
#else
m_consumer->set("audio_buffer", 512);
#endif
*/
int fps = qRound(pCore->getCurrentProfile()->profile().fps());
m_consumer->set("buffer", qMax(25, fps));
m_consumer->set("prefill", qMax(1, fps / 25));
m_consumer->set("drop_max", fps / 4);
m_consumer->set("scrub_audio", 1);
if (KdenliveSettings::monitor_gamma() == 0) {
m_consumer->set("color_trc", "iec61966_2_1");
} else {
m_consumer->set("color_trc", "bt709");
}
} else {
// Cleanup on error
error = 2;
}
return error;
}
float GLWidget::zoom() const
{
return m_zoom;
}
float GLWidget::scale() const
{
return (double)m_rect.width() / pCore->getCurrentProfile()->width() * m_zoom;
}
void GLWidget::reloadProfile()
{
// The profile display aspect ratio may have changed.
if (m_consumer) {
// Make sure to delete and rebuild consumer to match profile
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
reconfigure();
}
resizeGL(width(), height());
refreshSceneLayout();
}
QSize GLWidget::profileSize() const
{
return {pCore->getCurrentProfile()->width(), pCore->getCurrentProfile()->height()};
}
QRect GLWidget::displayRect() const
{
return m_rect;
}
QPoint GLWidget::offset() const
{
return {m_offset.x() - ((int)((float)pCore->getCurrentProfile()->width() * m_zoom) - width()) / 2,
m_offset.y() - ((int)((float)pCore->getCurrentProfile()->height() * m_zoom) - height()) / 2};
}
void GLWidget::setZoom(float zoom)
{
double zoomRatio = zoom / m_zoom;
m_zoom = zoom;
emit zoomChanged();
if (rootObject()) {
rootObject()->setProperty("zoom", m_zoom);
double scalex = rootObject()->property("scalex").toDouble() * zoomRatio;
rootObject()->setProperty("scalex", scalex);
double scaley = rootObject()->property("scaley").toDouble() * zoomRatio;
rootObject()->setProperty("scaley", scaley);
}
update();
}
void GLWidget::onFrameDisplayed(const SharedFrame &frame)
{
m_contextSharedAccess.lock();
m_sharedFrame = frame;
m_sendFrame = sendFrameForAnalysis;
m_contextSharedAccess.unlock();
update();
}
void GLWidget::mouseReleaseEvent(QMouseEvent *event)
{
QQuickView::mouseReleaseEvent(event);
if (m_dragStart.isNull() && m_panStart.isNull() && (rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") &&
!(event->modifiers() & Qt::ControlModifier)) {
event->ignore();
return;
}
if (!m_dragStart.isNull() && m_panStart.isNull() && ((event->button() & Qt::LeftButton) != 0u) && !event->isAccepted()) {
emit monitorPlay();
}
m_dragStart = QPoint();
m_panStart = QPoint();
setCursor(Qt::ArrowCursor);
}
void GLWidget::purgeCache()
{
if (m_consumer) {
m_consumer->purge();
m_producer->seek(m_proxy->position() + 1);
}
}
void GLWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
QQuickView::mouseDoubleClickEvent(event);
if (event->isAccepted()) {
return;
}
if ((rootObject() == nullptr) || rootObject()->objectName() != QLatin1String("rooteffectscene")) {
emit switchFullScreen();
}
event->accept();
}
void GLWidget::setOffsetX(int x, int max)
{
m_offset.setX(x);
emit offsetChanged();
if (rootObject()) {
rootObject()->setProperty("offsetx", m_zoom > 1.0f ? x - max / 2.0 - 10 : 0);
}
update();
}
void GLWidget::setOffsetY(int y, int max)
{
m_offset.setY(y);
if (rootObject()) {
rootObject()->setProperty("offsety", m_zoom > 1.0f ? y - max / 2.0 - 10 : 0);
}
update();
}
int GLWidget::realTime() const
{
// C & D
if (m_glslManager) {
return 1;
}
return KdenliveSettings::mltthreads();
}
std::shared_ptr GLWidget::consumer()
{
return m_consumer;
}
void GLWidget::updateGamma()
{
reconfigure();
}
void GLWidget::resetConsumer(bool fullReset)
{
if (fullReset && m_consumer) {
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
}
reconfigure();
}
const QString GLWidget::sceneList(const QString &root, const QString &fullPath)
{
QString playlist;
qCDebug(KDENLIVE_LOG) << " * * *Setting document xml root: " << root;
Mlt::Consumer xmlConsumer(pCore->getCurrentProfile()->profile(), "xml", fullPath.isEmpty() ? "kdenlive_playlist" : fullPath.toUtf8().constData());
if (!root.isEmpty()) {
xmlConsumer.set("root", root.toUtf8().constData());
}
if (!xmlConsumer.is_valid()) {
return QString();
}
m_producer->optimise();
xmlConsumer.set("terminate_on_pause", 1);
xmlConsumer.set("store", "kdenlive");
xmlConsumer.set("time_format", "clock");
// Disabling meta creates cleaner files, but then we don't have access to metadata on the fly (meta channels, etc)
// And we must use "avformat" instead of "avformat-novalidate" on project loading which causes a big delay on project opening
// xmlConsumer.set("no_meta", 1);
Mlt::Producer prod(m_producer->get_producer());
if (!prod.is_valid()) {
return QString();
}
xmlConsumer.connect(prod);
xmlConsumer.run();
playlist = fullPath.isEmpty() ? QString::fromUtf8(xmlConsumer.get("kdenlive_playlist")) : fullPath;
return playlist;
}
void GLWidget::updateTexture(GLuint yName, GLuint uName, GLuint vName)
{
m_texture[0] = yName;
m_texture[1] = uName;
m_texture[2] = vName;
m_sendFrame = sendFrameForAnalysis;
// update();
}
// MLT consumer-frame-show event handler
void GLWidget::on_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr)
{
Mlt::Frame frame(frame_ptr);
//qDebug()<<"== SHOWING FRAME: "<(self);
int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
QMetaObject::invokeMethod(widget->m_frameRenderer, "showFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
}
}
}
void GLWidget::on_gl_nosync_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr)
{
Mlt::Frame frame(frame_ptr);
if (frame.get_int("rendered") != 0) {
auto *widget = static_cast(self);
int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
QMetaObject::invokeMethod(widget->m_frameRenderer, "showGLNoSyncFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
}
}
}
void GLWidget::on_gl_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr)
{
Mlt::Frame frame(frame_ptr);
qDebug()<<"== SHOWING GL FRAME: "<(self);
int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
QMetaObject::invokeMethod(widget->m_frameRenderer, "showGLFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
}
}
}
RenderThread::RenderThread(thread_function_t function, void *data, QOpenGLContext *context, QSurface *surface)
: QThread(nullptr)
, m_function(function)
, m_data(data)
, m_context(nullptr)
, m_surface(surface)
{
if (context) {
m_context = new QOpenGLContext;
m_context->setFormat(context->format());
m_context->setShareContext(context);
m_context->create();
m_context->moveToThread(this);
}
}
RenderThread::~RenderThread()
{
// would otherwise leak if RenderThread is allocated with a context but not run.
// safe post-run
delete m_context;
}
// TODO: missing some exception handling?
void RenderThread::run()
{
if (m_context) {
m_context->makeCurrent(m_surface);
}
m_function(m_data);
if (m_context) {
m_context->doneCurrent();
delete m_context;
m_context = nullptr;
}
}
FrameRenderer::FrameRenderer(QOpenGLContext *shareContext, QSurface *surface, GLWidget::ClientWaitSync_fp clientWaitSync)
: QThread(nullptr)
, m_semaphore(3)
, m_context(nullptr)
, m_surface(surface)
, m_ClientWaitSync(clientWaitSync)
, m_gl32(nullptr)
, sendAudioForAnalysis(false)
{
Q_ASSERT(shareContext);
m_renderTexture[0] = m_renderTexture[1] = m_renderTexture[2] = 0;
m_displayTexture[0] = m_displayTexture[1] = m_displayTexture[2] = 0;
// B & C & D
if (KdenliveSettings::gpu_accel() || shareContext->supportsThreadedOpenGL()) {
m_context = new QOpenGLContext;
m_context->setFormat(shareContext->format());
m_context->setShareContext(shareContext);
m_context->create();
m_context->moveToThread(this);
}
setObjectName(QStringLiteral("FrameRenderer"));
moveToThread(this);
start();
}
FrameRenderer::~FrameRenderer()
{
delete m_context;
delete m_gl32;
}
void FrameRenderer::showFrame(Mlt::Frame frame)
{
int width = 0;
int height = 0;
mlt_image_format format = mlt_image_yuv420p;
frame.get_image(format, width, height);
// Save this frame for future use and to keep a reference to the GL Texture.
m_displayFrame = SharedFrame(frame);
if ((m_context != nullptr) && m_context->isValid()) {
m_context->makeCurrent(m_surface);
// Upload each plane of YUV to a texture.
QOpenGLFunctions *f = m_context->functions();
uploadTextures(m_context, m_displayFrame, m_renderTexture);
f->glBindTexture(GL_TEXTURE_2D, 0);
check_error(f);
f->glFinish();
for (int i = 0; i < 3; ++i) {
std::swap(m_renderTexture[i], m_displayTexture[i]);
}
emit textureReady(m_displayTexture[0], m_displayTexture[1], m_displayTexture[2]);
m_context->doneCurrent();
}
// The frame is now done being modified and can be shared with the rest
// of the application.
emit frameDisplayed(m_displayFrame);
m_semaphore.release();
}
void FrameRenderer::showGLFrame(Mlt::Frame frame)
{
if ((m_context != nullptr) && m_context->isValid()) {
int width = 0;
int height = 0;
frame.set("movit.convert.use_texture", 1);
mlt_image_format format = mlt_image_glsl_texture;
frame.get_image(format, width, height);
m_context->makeCurrent(m_surface);
pipelineSyncToFrame(frame);
m_context->functions()->glFinish();
m_context->doneCurrent();
// Save this frame for future use and to keep a reference to the GL Texture.
m_displayFrame = SharedFrame(frame);
}
// The frame is now done being modified and can be shared with the rest
// of the application.
emit frameDisplayed(m_displayFrame);
m_semaphore.release();
}
void FrameRenderer::showGLNoSyncFrame(Mlt::Frame frame)
{
if ((m_context != nullptr) && m_context->isValid()) {
int width = 0;
int height = 0;
frame.set("movit.convert.use_texture", 1);
mlt_image_format format = mlt_image_glsl_texture;
frame.get_image(format, width, height);
m_context->makeCurrent(m_surface);
m_context->functions()->glFinish();
m_context->doneCurrent();
// Save this frame for future use and to keep a reference to the GL Texture.
m_displayFrame = SharedFrame(frame);
}
// The frame is now done being modified and can be shared with the rest
// of the application.
emit frameDisplayed(m_displayFrame);
m_semaphore.release();
}
void FrameRenderer::cleanup()
{
if ((m_renderTexture[0] != 0u) && (m_renderTexture[1] != 0u) && (m_renderTexture[2] != 0u)) {
m_context->makeCurrent(m_surface);
m_context->functions()->glDeleteTextures(3, m_renderTexture);
if ((m_displayTexture[0] != 0u) && (m_displayTexture[1] != 0u) && (m_displayTexture[2] != 0u)) {
m_context->functions()->glDeleteTextures(3, m_displayTexture);
}
m_context->doneCurrent();
m_renderTexture[0] = m_renderTexture[1] = m_renderTexture[2] = 0;
m_displayTexture[0] = m_displayTexture[1] = m_displayTexture[2] = 0;
}
}
// D
void FrameRenderer::pipelineSyncToFrame(Mlt::Frame &frame)
{
auto sync = (GLsync)frame.get_data("movit.convert.fence");
if (!sync) return;
#ifdef Q_OS_WIN
// On Windows, use QOpenGLFunctions_3_2_Core instead of getProcAddress.
// TODO: move to initialization of m_ClientWaitSync
if (!m_gl32) {
m_gl32 = m_context->versionFunctions();
if (m_gl32) {
m_gl32->initializeOpenGLFunctions();
}
}
if (m_gl32) {
m_gl32->glClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
check_error(m_context->functions());
}
#else
if (m_ClientWaitSync) {
m_ClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
check_error(m_context->functions());
}
#endif // Q_OS_WIN
}
void GLWidget::setAudioThumb(int channels, const QList &audioCache)
{
if (!rootObject()) return;
auto *audioThumbDisplay = rootObject()->findChild(QStringLiteral("audiothumb"));
if (!audioThumbDisplay) return;
QImage img(width(), height() / 2, QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::transparent);
if (!audioCache.isEmpty() && channels > 0) {
int audioLevelCount = audioCache.count() - 1;
// simplified audio
QPainter painter(&img);
QRectF mappedRect(0, 0, img.width(), img.height());
int channelHeight = mappedRect.height();
double value;
double scale = (double)width() / (audioLevelCount / channels);
if (scale < 1) {
painter.setPen(QColor(80, 80, 150, 200));
for (int i = 0; i < img.width(); i++) {
int framePos = i / scale;
value = audioCache.at(qMin(framePos * channels, audioLevelCount)) / 256;
for (int channel = 1; channel < channels; channel++) {
value = qMax(value, audioCache.at(qMin(framePos * channels + channel, audioLevelCount)) / 256);
}
painter.drawLine(i, mappedRect.bottom() - (value * channelHeight), i, mappedRect.bottom());
}
} else {
QPainterPath positiveChannelPath;
positiveChannelPath.moveTo(0, mappedRect.bottom());
for (int i = 0; i < audioLevelCount / channels; i++) {
value = audioCache.at(qMin(i * channels, audioLevelCount)) / 256;
for (int channel = 1; channel < channels; channel++) {
value = qMax(value, audioCache.at(qMin(i * channels + channel, audioLevelCount)) / 256);
}
positiveChannelPath.lineTo(i * scale, mappedRect.bottom() - (value * channelHeight));
}
positiveChannelPath.lineTo(mappedRect.right(), mappedRect.bottom());
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(QColor(80, 80, 150, 200)));
painter.drawPath(positiveChannelPath);
}
painter.end();
}
-
audioThumbDisplay->setImage(img);
}
void GLWidget::refreshSceneLayout()
{
if (!rootObject()) {
return;
}
rootObject()->setProperty("profile", QPoint(pCore->getCurrentProfile()->width(), pCore->getCurrentProfile()->height()));
rootObject()->setProperty("scalex", (double)m_rect.width() / pCore->getCurrentProfile()->width() * m_zoom);
rootObject()->setProperty("scaley",
(double)m_rect.width() /
(((double)pCore->getCurrentProfile()->height() * pCore->getCurrentProfile()->dar() / pCore->getCurrentProfile()->width())) /
pCore->getCurrentProfile()->width() * m_zoom);
}
void GLWidget::switchPlay(bool play, double speed)
{
m_proxy->setSeekPosition(-1);
if (!m_producer || !m_consumer) {
return;
}
if (m_isZoneMode) {
resetZoneMode();
}
if (play) {
if (m_id == Kdenlive::ClipMonitor && m_consumer->position() == m_producer->get_out()) {
m_producer->seek(0);
}
m_producer->set_speed(speed);
m_consumer->start();
m_consumer->set("refresh", 1);
} else {
emit paused();
m_producer->set_speed(0);
m_producer->seek(m_consumer->position() + 1);
m_consumer->purge();
m_consumer->start();
}
}
bool GLWidget::playZone(bool loop)
{
if (!m_producer || m_proxy->zoneOut() <= m_proxy->zoneIn()) {
pCore->displayMessage(i18n("Select a zone to play"), InformationMessage, 500);
return false;
}
m_proxy->setSeekPosition(-1);
m_producer->seek(m_proxy->zoneIn());
m_producer->set_speed(0);
m_consumer->purge();
m_producer->set("out", m_proxy->zoneOut());
m_producer->set_speed(1.0);
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("refresh", 1);
m_isZoneMode = true;
m_isLoopMode = loop;
return true;
}
bool GLWidget::loopClip()
{
if (!m_producer || m_proxy->zoneOut() <= m_proxy->zoneIn()) {
pCore->displayMessage(i18n("Select a zone to play"), InformationMessage, 500);
return false;
}
m_proxy->setSeekPosition(-1);
m_producer->seek(0);
m_producer->set_speed(0);
m_consumer->purge();
m_producer->set("out", m_producer->get_playtime());
m_producer->set_speed(1.0);
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("refresh", 1);
m_isZoneMode = true;
m_isLoopMode = true;
return true;
}
void GLWidget::resetZoneMode()
{
if (!m_isZoneMode && !m_isLoopMode) {
return;
}
m_producer->set("out", m_producer->get_length());
m_isZoneMode = false;
m_isLoopMode = false;
}
MonitorProxy *GLWidget::getControllerProxy()
{
return m_proxy;
}
int GLWidget::getCurrentPos() const
{
return m_proxy->seeking() ? m_proxy->seekPosition() : m_consumer->position();
}
void GLWidget::setRulerInfo(int duration, const std::shared_ptr &model)
{
rootObject()->setProperty("duration", duration);
if (model != nullptr) {
// we are resetting marker/snap model, reset zone
rootContext()->setContextProperty("markersModel", model.get());
}
}
void GLWidget::startConsumer()
{
if (m_consumer == nullptr) {
return;
}
if (m_consumer->is_stopped() && m_consumer->start() == -1) {
// ARGH CONSUMER BROKEN!!!!
KMessageBox::error(
qApp->activeWindow(),
i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or your driver settings, please fix it."));
if (m_displayEvent) {
delete m_displayEvent;
}
m_displayEvent = nullptr;
m_consumer.reset();
return;
}
m_consumer->set("refresh", 1);
}
void GLWidget::stop()
{
m_refreshTimer.stop();
m_proxy->setSeekPosition(-1);
// why this lock?
QMutexLocker locker(&m_mltMutex);
if (m_producer) {
if (m_isZoneMode) {
resetZoneMode();
}
m_producer->set_speed(0.0);
}
if (m_consumer) {
m_consumer->purge();
if (!m_consumer->is_stopped()) {
m_consumer->stop();
}
}
}
double GLWidget::playSpeed() const
{
if (m_producer) {
return m_producer->get_speed();
}
return 0.0;
}
void GLWidget::setDropFrames(bool drop)
{
// why this lock?
QMutexLocker locker(&m_mltMutex);
if (m_consumer) {
int dropFrames = realTime();
if (!drop) {
dropFrames = -dropFrames;
}
m_consumer->stop();
m_consumer->set("real_time", dropFrames);
if (m_consumer->start() == -1) {
qCWarning(KDENLIVE_LOG) << "ERROR, Cannot start monitor";
}
}
}
int GLWidget::volume() const
{
if ((!m_consumer) || (!m_producer)) {
return -1;
}
if (m_consumer->get("mlt_service") == QStringLiteral("multi")) {
return ((int)100 * m_consumer->get_double("0.volume"));
}
return ((int)100 * m_consumer->get_double("volume"));
}
void GLWidget::setVolume(double volume)
{
if (m_consumer) {
if (m_consumer->get("mlt_service") == QStringLiteral("multi")) {
m_consumer->set("0.volume", volume);
} else {
m_consumer->set("volume", volume);
}
}
}
int GLWidget::duration() const
{
if (!m_producer) {
return 0;
}
return m_producer->get_playtime();
}
void GLWidget::setConsumerProperty(const QString &name, const QString &value)
{
QMutexLocker locker(&m_mltMutex);
if (m_consumer) {
m_consumer->set(name.toUtf8().constData(), value.toUtf8().constData());
if (m_consumer->start() == -1) {
qCWarning(KDENLIVE_LOG) << "ERROR, Cannot start monitor";
}
}
}
diff --git a/src/monitor/glwidget.h b/src/monitor/glwidget.h
index 39ab29160..326911a12 100644
--- a/src/monitor/glwidget.h
+++ b/src/monitor/glwidget.h
@@ -1,343 +1,344 @@
/*
* Copyright (c) 2011-2014 Meltytech, LLC
* Author: Dan Dennedy
*
* 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 3 of the License, or
* (at your option) any later version.
*
* 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 .
*/
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "bin/model/markerlistmodel.hpp"
#include "definitions.h"
#include "kdenlivesettings.h"
#include "scopes/sharedframe.h"
class QOpenGLFunctions_3_2_Core;
namespace Mlt {
class Filter;
class Producer;
class Consumer;
class Profile;
} // namespace Mlt
class RenderThread;
class FrameRenderer;
class MonitorProxy;
using thread_function_t = void *(*)(void *);
/* QQuickView that renders an .
*
* Creates an MLT consumer and renders a GL view from the consumer. This pipeline is one of:
*
* A. YUV gl texture w/o GPU filter acceleration
* B. YUV gl texture multithreaded w/o GPU filter acceleration
* C. RGB gl texture multithreaded w/ GPU filter acceleration and no sync
* D. RGB gl texture multithreaded w/ GPU filter acceleration and sync
*/
class GLWidget : public QQuickView, protected QOpenGLFunctions
{
Q_OBJECT
Q_PROPERTY(QRect rect READ rect NOTIFY rectChanged)
Q_PROPERTY(float zoom READ zoom NOTIFY zoomChanged)
Q_PROPERTY(QPoint offset READ offset NOTIFY offsetChanged)
public:
friend class MonitorController;
friend class Monitor;
friend class MonitorProxy;
using ClientWaitSync_fp = GLenum (*)(GLsync, GLbitfield, GLuint64);
GLWidget(int id, QObject *parent = nullptr);
~GLWidget() override;
int requestedSeekPosition;
void createThread(RenderThread **thread, thread_function_t function, void *data);
void startGlsl();
void stopGlsl();
void clear();
// TODO: currently unused
int reconfigureMulti(const QString ¶ms, const QString &path, Mlt::Profile *profile);
void stopCapture();
int reconfigure(bool reload = false);
/** @brief Get the current MLT producer playlist.
* @return A string describing the playlist */
const QString sceneList(const QString &root, const QString &fullPath = QString());
int displayWidth() const { return m_rect.width(); }
void updateAudioForAnalysis();
int displayHeight() const { return m_rect.height(); }
QObject *videoWidget() { return this; }
Mlt::Filter *glslManager() const { return m_glslManager; }
QRect rect() const { return m_rect; }
QRect effectRect() const { return m_effectRect; }
float zoom() const;
float scale() const;
QPoint offset() const;
std::shared_ptr consumer();
Mlt::Producer *producer();
QSize profileSize() const;
QRect displayRect() const;
/** @brief set to true if we want to emit a QImage of the frame for analysis */
bool sendFrameForAnalysis;
void updateGamma();
/** @brief delete and rebuild consumer, for example when external display is switched */
void resetConsumer(bool fullReset);
void reloadProfile();
void lockMonitor();
void releaseMonitor();
int realTime() const;
void setAudioThumb(int channels = 0, const QList &audioCache = QList());
int droppedFrames() const;
void resetDrops();
bool checkFrameNumber(int pos, int offset);
/** @brief Return current timeline position */
int getCurrentPos() const;
/** @brief Requests a monitor refresh */
void requestRefresh();
void setRulerInfo(int duration, const std::shared_ptr &model = nullptr);
MonitorProxy *getControllerProxy();
bool playZone(bool loop = false);
bool loopClip();
void startConsumer();
void stop();
int rulerHeight() const;
/** @brief return current play producer's playing speed */
double playSpeed() const;
/** @brief Turn drop frame feature on/off */
void setDropFrames(bool drop);
/** @brief Returns current audio volume */
int volume() const;
/** @brief Set audio volume on consumer */
void setVolume(double volume);
/** @brief Returns current producer's duration in frames */
int duration() const;
/** @brief Set a property on the MLT consumer */
void setConsumerProperty(const QString &name, const QString &value);
/** @brief Clear consumer cache */
void purgeCache();
protected:
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
/** @brief Update producer, should ONLY be called from monitor */
int setProducer(const std::shared_ptr &producer, bool isActive, int position = -1);
QString frameToTime(int frames) const;
public slots:
void seek(int pos);
void requestSeek();
void setZoom(float zoom);
void setOffsetX(int x, int max);
void setOffsetY(int y, int max);
void slotSwitchAudioOverlay(bool enable);
void slotZoom(bool zoomIn);
void initializeGL();
void releaseAnalyse();
void switchPlay(bool play, double speed = 1.0);
signals:
void frameDisplayed(const SharedFrame &frame);
void frameRendered(int pos);
void dragStarted();
void seekTo(int x);
void gpuNotSupported();
void started();
void paused();
void playing();
void rectChanged();
void zoomChanged();
void offsetChanged();
void monitorPlay();
void switchFullScreen(bool minimizeOnly = false);
void mouseSeek(int eventDelta, uint modifiers);
void startDrag();
void analyseFrame(const QImage &);
void audioSamplesSignal(const audioShortVector &, int, int, int);
void showContextMenu(const QPoint &);
void lockMonitor(bool);
void passKeyEvent(QKeyEvent *);
void panView(const QPoint &diff);
void seekPosition(int);
void consumerPosition(int);
void activateMonitor();
+ void buildAudioThumb(int channels = 0, const QList &audioCache = QList());
protected:
Mlt::Filter *m_glslManager;
// TODO: MTL has lock/unlock of individual nodes. Use those.
// keeping this for refactoring ease.
QMutex m_mltMutex;
std::shared_ptr m_consumer;
std::shared_ptr m_producer;
int m_id;
int m_rulerHeight;
private:
QRect m_rect;
QRect m_effectRect;
GLuint m_texture[3];
QOpenGLShaderProgram *m_shader;
QPoint m_panStart;
QPoint m_dragStart;
QSemaphore m_initSem;
QSemaphore m_analyseSem;
bool m_isInitialized;
Mlt::Event *m_threadStartEvent;
Mlt::Event *m_threadStopEvent;
Mlt::Event *m_threadCreateEvent;
Mlt::Event *m_threadJoinEvent;
Mlt::Event *m_displayEvent;
Mlt::Event *m_renderEvent;
FrameRenderer *m_frameRenderer;
int m_projectionLocation;
int m_modelViewLocation;
int m_vertexLocation;
int m_texCoordLocation;
int m_colorspaceLocation;
int m_textureLocation[3];
QTimer m_refreshTimer;
float m_zoom;
bool m_sendFrame;
bool m_isZoneMode;
bool m_isLoopMode;
QPoint m_offset;
bool m_audioWaveDisplayed;
MonitorProxy *m_proxy;
std::shared_ptr m_blackClip;
static void on_frame_show(mlt_consumer, void *self, mlt_frame frame);
static void on_frame_render(mlt_consumer, GLWidget *widget, mlt_frame frame);
static void on_gl_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr);
static void on_gl_nosync_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr);
void createAudioOverlay(bool isAudio);
void removeAudioOverlay();
void adjustAudioOverlay(bool isAudio);
QOpenGLFramebufferObject *m_fbo;
void refreshSceneLayout();
void resetZoneMode();
/* OpenGL context management. Interfaces to MLT according to the configured render pipeline.
*/
private slots:
void resizeGL(int width, int height);
void updateTexture(GLuint yName, GLuint uName, GLuint vName);
void paintGL();
void onFrameDisplayed(const SharedFrame &frame);
void refresh();
protected:
QMutex m_contextSharedAccess;
QOffscreenSurface m_offscreenSurface;
SharedFrame m_sharedFrame;
QOpenGLContext *m_shareContext;
bool acquireSharedFrameTextures();
void bindShaderProgram();
void createGPUAccelFragmentProg();
void createShader();
void createYUVTextureProjectFragmentProg();
void disableGPUAccel();
void releaseSharedFrameTextures();
// pipeline A - YUV gl texture w/o GPU filter acceleration
// pipeline B - YUV gl texture multithreaded w/o GPU filter acceleration
// pipeline C - RGB gl texture multithreaded w/ GPU filter acceleration and no sync
// pipeline D - RGB gl texture multithreaded w/ GPU filter acceleration and sync
bool m_openGLSync;
bool initGPUAccelSync();
// pipeline C & D
bool initGPUAccel();
bool onlyGLESGPUAccel() const;
// pipeline A & B & C & D
// not null iff D
ClientWaitSync_fp m_ClientWaitSync;
protected:
void resizeEvent(QResizeEvent *event) override;
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
void keyPressEvent(QKeyEvent *event) override;
};
class RenderThread : public QThread
{
Q_OBJECT
public:
RenderThread(thread_function_t function, void *data, QOpenGLContext *context, QSurface *surface);
~RenderThread() override;
protected:
void run() override;
private:
thread_function_t m_function;
void *m_data;
QOpenGLContext *m_context;
QSurface *m_surface;
};
class FrameRenderer : public QThread
{
Q_OBJECT
public:
explicit FrameRenderer(QOpenGLContext *shareContext, QSurface *surface, GLWidget::ClientWaitSync_fp clientWaitSync);
~FrameRenderer() override;
QSemaphore *semaphore() { return &m_semaphore; }
QOpenGLContext *context() const { return m_context; }
Q_INVOKABLE void showFrame(Mlt::Frame frame);
Q_INVOKABLE void showGLFrame(Mlt::Frame frame);
Q_INVOKABLE void showGLNoSyncFrame(Mlt::Frame frame);
public slots:
void cleanup();
signals:
void textureReady(GLuint yName, GLuint uName = 0, GLuint vName = 0);
void frameDisplayed(const SharedFrame &frame);
void audioSamplesSignal(const audioShortVector &, int, int, int);
private:
QSemaphore m_semaphore;
SharedFrame m_displayFrame;
QOpenGLContext *m_context;
QSurface *m_surface;
GLWidget::ClientWaitSync_fp m_ClientWaitSync;
void pipelineSyncToFrame(Mlt::Frame &);
public:
GLuint m_renderTexture[3];
GLuint m_displayTexture[3];
QOpenGLFunctions_3_2_Core *m_gl32;
bool sendAudioForAnalysis;
};
#endif
diff --git a/src/monitor/monitor.cpp b/src/monitor/monitor.cpp
index b377568a5..ee80bf992 100644
--- a/src/monitor/monitor.cpp
+++ b/src/monitor/monitor.cpp
@@ -1,2126 +1,2126 @@
/***************************************************************************
* Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) any later version. *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "monitor.h"
#include "bin/bin.h"
#include "bin/projectclip.h"
#include "core.h"
#include "dialogs/profilesdialog.h"
#include "doc/kdenlivedoc.h"
#include "doc/kthumb.h"
#include "glwidget.h"
#include "kdenlivesettings.h"
#include "lib/audio/audioStreamInfo.h"
#include "mainwindow.h"
#include "mltcontroller/clipcontroller.h"
#include "monitorproxy.h"
#include "profiles/profilemodel.hpp"
#include "project/projectmanager.h"
#include "qmlmanager.h"
#include "recmanager.h"
#include "jobs/jobmanager.h"
#include "jobs/cutclipjob.h"
#include "scopes/monitoraudiolevel.h"
#include "timeline2/model/snapmodel.hpp"
#include "transitions/transitionsrepository.hpp"
#include "klocalizedstring.h"
#include
#include
#include
#include
#include
#include
#include
#include "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SEEK_INACTIVE (-1)
QuickEventEater::QuickEventEater(QObject *parent)
: QObject(parent)
{
}
bool QuickEventEater::eventFilter(QObject *obj, QEvent *event)
{
switch (event->type()) {
case QEvent::DragEnter: {
auto *ev = reinterpret_cast(event);
if (ev->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) {
ev->acceptProposedAction();
return true;
}
break;
}
case QEvent::DragMove: {
auto *ev = reinterpret_cast(event);
if (ev->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) {
ev->acceptProposedAction();
return true;
}
break;
}
case QEvent::Drop: {
auto *ev = static_cast(event);
if (ev) {
QStringList effectData;
effectData << QString::fromUtf8(ev->mimeData()->data(QStringLiteral("kdenlive/effect")));
QStringList source = QString::fromUtf8(ev->mimeData()->data(QStringLiteral("kdenlive/effectsource"))).split(QLatin1Char('-'));
effectData << source;
emit addEffect(effectData);
ev->accept();
return true;
}
break;
}
default:
break;
}
return QObject::eventFilter(obj, event);
}
QuickMonitorEventEater::QuickMonitorEventEater(QWidget *parent)
: QObject(parent)
{
}
bool QuickMonitorEventEater::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
auto *ev = static_cast(event);
if (ev) {
emit doKeyPressEvent(ev);
return true;
}
}
return QObject::eventFilter(obj, event);
}
Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *parent)
: AbstractMonitor(id, manager, parent)
, m_controller(nullptr)
, m_glMonitor(nullptr)
, m_snaps(new SnapModel())
, m_splitEffect(nullptr)
, m_splitProducer(nullptr)
, m_dragStarted(false)
, m_recManager(nullptr)
, m_loopClipAction(nullptr)
, m_sceneVisibilityAction(nullptr)
, m_contextMenu(nullptr)
, m_markerMenu(nullptr)
, m_loopClipTransition(true)
, m_editMarker(nullptr)
, m_forceSizeFactor(0)
, m_lastMonitorSceneType(MonitorSceneDefault)
{
auto *layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
// Create container widget
m_glWidget = new QWidget;
auto *glayout = new QGridLayout(m_glWidget);
glayout->setSpacing(0);
glayout->setContentsMargins(0, 0, 0, 0);
// Create QML OpenGL widget
m_glMonitor = new GLWidget((int)id);
connect(m_glMonitor, &GLWidget::passKeyEvent, this, &Monitor::doKeyPressEvent);
connect(m_glMonitor, &GLWidget::panView, this, &Monitor::panView);
connect(m_glMonitor, &GLWidget::seekPosition, this, &Monitor::seekPosition, Qt::DirectConnection);
connect(m_glMonitor, &GLWidget::consumerPosition, this, &Monitor::slotSeekPosition, Qt::DirectConnection);
connect(m_glMonitor, &GLWidget::activateMonitor, this, &AbstractMonitor::slotActivateMonitor, Qt::DirectConnection);
m_videoWidget = QWidget::createWindowContainer(qobject_cast(m_glMonitor));
m_videoWidget->setAcceptDrops(true);
auto *leventEater = new QuickEventEater(this);
m_videoWidget->installEventFilter(leventEater);
connect(leventEater, &QuickEventEater::addEffect, this, &Monitor::slotAddEffect);
m_qmlManager = new QmlManager(m_glMonitor);
connect(m_qmlManager, &QmlManager::effectChanged, this, &Monitor::effectChanged);
connect(m_qmlManager, &QmlManager::effectPointsChanged, this, &Monitor::effectPointsChanged);
auto *monitorEventEater = new QuickMonitorEventEater(this);
m_videoWidget->installEventFilter(monitorEventEater);
connect(monitorEventEater, &QuickMonitorEventEater::doKeyPressEvent, this, &Monitor::doKeyPressEvent);
glayout->addWidget(m_videoWidget, 0, 0);
m_verticalScroll = new QScrollBar(Qt::Vertical);
glayout->addWidget(m_verticalScroll, 0, 1);
m_verticalScroll->hide();
m_horizontalScroll = new QScrollBar(Qt::Horizontal);
glayout->addWidget(m_horizontalScroll, 1, 0);
m_horizontalScroll->hide();
connect(m_horizontalScroll, &QAbstractSlider::valueChanged, this, &Monitor::setOffsetX);
connect(m_verticalScroll, &QAbstractSlider::valueChanged, this, &Monitor::setOffsetY);
connect(m_glMonitor, &GLWidget::frameDisplayed, this, &Monitor::onFrameDisplayed);
connect(m_glMonitor, &GLWidget::mouseSeek, this, &Monitor::slotMouseSeek);
connect(m_glMonitor, &GLWidget::monitorPlay, this, &Monitor::slotPlay);
connect(m_glMonitor, &GLWidget::startDrag, this, &Monitor::slotStartDrag);
connect(m_glMonitor, &GLWidget::switchFullScreen, this, &Monitor::slotSwitchFullScreen);
connect(m_glMonitor, &GLWidget::zoomChanged, this, &Monitor::setZoom);
connect(m_glMonitor, SIGNAL(lockMonitor(bool)), this, SLOT(slotLockMonitor(bool)), Qt::DirectConnection);
connect(m_glMonitor, &GLWidget::showContextMenu, this, &Monitor::slotShowMenu);
connect(m_glMonitor, &GLWidget::gpuNotSupported, this, &Monitor::gpuError);
m_glWidget->setMinimumSize(QSize(320, 180));
layout->addWidget(m_glWidget, 10);
layout->addStretch();
// Tool bar buttons
m_toolbar = new QToolBar(this);
QWidget *sp1 = new QWidget(this);
sp1->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
m_toolbar->addWidget(sp1);
if (id == Kdenlive::ClipMonitor) {
// Add options for recording
m_recManager = new RecManager(this);
connect(m_recManager, &RecManager::warningMessage, this, &Monitor::warningMessage);
connect(m_recManager, &RecManager::addClipToProject, this, &Monitor::addClipToProject);
m_toolbar->addAction(manager->getAction(QStringLiteral("insert_project_tree")));
m_toolbar->setToolTip(i18n("Insert Zone to Project Bin"));
m_toolbar->addSeparator();
} else if (id == Kdenlive::ProjectMonitor) {
connect(m_glMonitor, &GLWidget::paused, m_monitorManager, &MonitorManager::cleanMixer);
}
if (id != Kdenlive::DvdMonitor) {
QAction *markIn = new QAction(QIcon::fromTheme(QStringLiteral("zone-in")), i18n("Set Zone In"), this);
QAction *markOut = new QAction(QIcon::fromTheme(QStringLiteral("zone-out")), i18n("Set Zone Out"), this);
m_toolbar->addAction(markIn);
m_toolbar->addAction(markOut);
connect(markIn, &QAction::triggered, [&, manager]() {
m_monitorManager->activateMonitor(m_id);
manager->getAction(QStringLiteral("mark_in"))->trigger();
});
connect(markOut, &QAction::triggered, [&, manager]() {
m_monitorManager->activateMonitor(m_id);
manager->getAction(QStringLiteral("mark_out"))->trigger();
});
}
m_toolbar->addAction(manager->getAction(QStringLiteral("monitor_seek_backward")));
auto *playButton = new QToolButton(m_toolbar);
m_playMenu = new QMenu(i18n("Play..."), this);
QAction *originalPlayAction = static_cast(manager->getAction(QStringLiteral("monitor_play")));
m_playAction = new KDualAction(i18n("Play"), i18n("Pause"), this);
m_playAction->setInactiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
m_playAction->setActiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
QString strippedTooltip = m_playAction->toolTip().remove(QRegExp(QStringLiteral("\\s\\(.*\\)")));
// append shortcut if it exists for action
if (originalPlayAction->shortcut() == QKeySequence(0)) {
m_playAction->setToolTip(strippedTooltip);
} else {
m_playAction->setToolTip(strippedTooltip + QStringLiteral(" (") + originalPlayAction->shortcut().toString() + QLatin1Char(')'));
}
m_playMenu->addAction(m_playAction);
connect(m_playAction, &QAction::triggered, this, &Monitor::slotSwitchPlay);
playButton->setMenu(m_playMenu);
playButton->setPopupMode(QToolButton::MenuButtonPopup);
m_toolbar->addWidget(playButton);
m_toolbar->addAction(manager->getAction(QStringLiteral("monitor_seek_forward")));
playButton->setDefaultAction(m_playAction);
m_configMenu = new QMenu(i18n("Misc..."), this);
if (id != Kdenlive::DvdMonitor) {
if (id == Kdenlive::ClipMonitor) {
m_markerMenu = new QMenu(i18n("Go to marker..."), this);
} else {
m_markerMenu = new QMenu(i18n("Go to guide..."), this);
}
m_markerMenu->setEnabled(false);
m_configMenu->addMenu(m_markerMenu);
connect(m_markerMenu, &QMenu::triggered, this, &Monitor::slotGoToMarker);
m_forceSize = new KSelectAction(QIcon::fromTheme(QStringLiteral("transform-scale")), i18n("Force Monitor Size"), this);
QAction *fullAction = m_forceSize->addAction(QIcon(), i18n("Force 100%"));
fullAction->setData(100);
QAction *halfAction = m_forceSize->addAction(QIcon(), i18n("Force 50%"));
halfAction->setData(50);
QAction *freeAction = m_forceSize->addAction(QIcon(), i18n("Free Resize"));
freeAction->setData(0);
m_configMenu->addAction(m_forceSize);
m_forceSize->setCurrentAction(freeAction);
connect(m_forceSize, static_cast(&KSelectAction::triggered), this, &Monitor::slotForceSize);
}
// Create Volume slider popup
m_audioSlider = new QSlider(Qt::Vertical);
m_audioSlider->setRange(0, 100);
m_audioSlider->setValue(KdenliveSettings::volume());
connect(m_audioSlider, &QSlider::valueChanged, this, &Monitor::slotSetVolume);
auto *widgetslider = new QWidgetAction(this);
widgetslider->setText(i18n("Audio volume"));
widgetslider->setDefaultWidget(m_audioSlider);
auto *menu = new QMenu(i18n("Volume"), this);
menu->setIcon(QIcon::fromTheme(QStringLiteral("audio-volume-medium")));
menu->addAction(widgetslider);
m_configMenu->addMenu(menu);
/*QIcon icon;
if (KdenliveSettings::volume() == 0) {
icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted"));
} else {
icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
}
m_audioButton->setIcon(icon);*/
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setLayout(layout);
setMinimumHeight(200);
connect(this, &Monitor::scopesClear, m_glMonitor, &GLWidget::releaseAnalyse, Qt::DirectConnection);
connect(m_glMonitor, &GLWidget::analyseFrame, this, &Monitor::frameUpdated);
connect(m_glMonitor, &GLWidget::audioSamplesSignal, this, &Monitor::audioSamplesSignal);
if (id != Kdenlive::ClipMonitor) {
// TODO: reimplement
// connect(render, &Render::durationChanged, this, &Monitor::durationChanged);
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::updateTimelineClipZone);
} else {
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::updateClipZone);
}
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::triggerAction, pCore.get(), &Core::triggerAction);
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekNextKeyframe, this, &Monitor::seekToNextKeyframe);
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekPreviousKeyframe, this, &Monitor::seekToPreviousKeyframe);
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::addRemoveKeyframe, this, &Monitor::addRemoveKeyframe);
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekToKeyframe, this, &Monitor::slotSeekToKeyFrame);
m_sceneVisibilityAction = new QAction(QIcon::fromTheme(QStringLiteral("transform-crop")), i18n("Show/Hide edit mode"), this);
m_sceneVisibilityAction->setCheckable(true);
m_sceneVisibilityAction->setChecked(KdenliveSettings::showOnMonitorScene());
connect(m_sceneVisibilityAction, &QAction::triggered, this, &Monitor::slotEnableEffectScene);
m_toolbar->addAction(m_sceneVisibilityAction);
m_toolbar->addSeparator();
m_timePos = new TimecodeDisplay(m_monitorManager->timecode(), this);
m_toolbar->addWidget(m_timePos);
auto *configButton = new QToolButton(m_toolbar);
configButton->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu")));
configButton->setToolTip(i18n("Options"));
configButton->setMenu(m_configMenu);
configButton->setPopupMode(QToolButton::InstantPopup);
m_toolbar->addWidget(configButton);
/*QWidget *spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
m_toolbar->addWidget(spacer);*/
m_toolbar->addSeparator();
int tm = 0;
int bm = 0;
m_toolbar->getContentsMargins(nullptr, &tm, nullptr, &bm);
m_audioMeterWidget = new MonitorAudioLevel(m_toolbar->height() - tm - bm, this);
m_toolbar->addWidget(m_audioMeterWidget);
if (!m_audioMeterWidget->isValid) {
KdenliveSettings::setMonitoraudio(0x01);
m_audioMeterWidget->setVisibility(false);
} else {
m_audioMeterWidget->setVisibility((KdenliveSettings::monitoraudio() & m_id) != 0);
}
connect(m_timePos, SIGNAL(timeCodeEditingFinished()), this, SLOT(slotSeek()));
layout->addWidget(m_toolbar);
if (m_recManager) {
layout->addWidget(m_recManager->toolbar());
}
// Load monitor overlay qml
loadQmlScene(MonitorSceneDefault);
// Info message widget
m_infoMessage = new KMessageWidget(this);
layout->addWidget(m_infoMessage);
m_infoMessage->hide();
}
Monitor::~Monitor()
{
delete m_audioMeterWidget;
delete m_glMonitor;
delete m_videoWidget;
delete m_glWidget;
delete m_timePos;
}
void Monitor::setOffsetX(int x)
{
m_glMonitor->setOffsetX(x, m_horizontalScroll->maximum());
}
void Monitor::setOffsetY(int y)
{
m_glMonitor->setOffsetY(y, m_verticalScroll->maximum());
}
void Monitor::slotGetCurrentImage(bool request)
{
m_glMonitor->sendFrameForAnalysis = request;
m_monitorManager->activateMonitor(m_id);
refreshMonitorIfActive();
if (request) {
// Update analysis state
QTimer::singleShot(500, m_monitorManager, &MonitorManager::checkScopes);
} else {
m_glMonitor->releaseAnalyse();
}
}
void Monitor::slotAddEffect(const QStringList &effect)
{
if (m_id == Kdenlive::ClipMonitor) {
if (m_controller) {
emit addMasterEffect(m_controller->AbstractProjectItem::clipId(), effect);
}
} else {
emit addEffect(effect);
}
}
void Monitor::refreshIcons()
{
QList allMenus = this->findChildren();
for (int i = 0; i < allMenus.count(); i++) {
QAction *m = allMenus.at(i);
QIcon ic = m->icon();
if (ic.isNull() || ic.name().isEmpty()) {
continue;
}
QIcon newIcon = QIcon::fromTheme(ic.name());
m->setIcon(newIcon);
}
QList allButtons = this->findChildren();
for (int i = 0; i < allButtons.count(); i++) {
KDualAction *m = allButtons.at(i);
QIcon ic = m->activeIcon();
if (ic.isNull() || ic.name().isEmpty()) {
continue;
}
QIcon newIcon = QIcon::fromTheme(ic.name());
m->setActiveIcon(newIcon);
ic = m->inactiveIcon();
if (ic.isNull() || ic.name().isEmpty()) {
continue;
}
newIcon = QIcon::fromTheme(ic.name());
m->setInactiveIcon(newIcon);
}
}
QAction *Monitor::recAction()
{
if (m_recManager) {
return m_recManager->recAction();
}
return nullptr;
}
void Monitor::slotLockMonitor(bool lock)
{
m_monitorManager->lockMonitor(m_id, lock);
}
void Monitor::setupMenu(QMenu *goMenu, QMenu *overlayMenu, QAction *playZone, QAction *loopZone, QMenu *markerMenu, QAction *loopClip)
{
delete m_contextMenu;
m_contextMenu = new QMenu(this);
m_contextMenu->addMenu(m_playMenu);
if (goMenu) {
m_contextMenu->addMenu(goMenu);
}
if (markerMenu) {
m_contextMenu->addMenu(markerMenu);
QList list = markerMenu->actions();
for (int i = 0; i < list.count(); ++i) {
if (list.at(i)->data().toString() == QLatin1String("edit_marker")) {
m_editMarker = list.at(i);
break;
}
}
}
m_playMenu->addAction(playZone);
m_playMenu->addAction(loopZone);
if (loopClip) {
m_loopClipAction = loopClip;
m_playMenu->addAction(loopClip);
}
// TODO: add save zone to timeline monitor when fixed
m_contextMenu->addMenu(m_markerMenu);
if (m_id == Kdenlive::ClipMonitor) {
m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save zone"), this, SLOT(slotSaveZone()));
QAction *extractZone =
m_configMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Extract Zone"), this, SLOT(slotExtractCurrentZone()));
m_contextMenu->addAction(extractZone);
}
m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame")));
m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame_to_project")));
if (m_id == Kdenlive::ProjectMonitor) {
m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("monitor_multitrack")));
} else if (m_id == Kdenlive::ClipMonitor) {
QAction *setThumbFrame =
m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Set current image as thumbnail"), this, SLOT(slotSetThumbFrame()));
m_configMenu->addAction(setThumbFrame);
}
if (overlayMenu) {
m_contextMenu->addMenu(overlayMenu);
}
QAction *overlayAudio = m_contextMenu->addAction(QIcon(), i18n("Overlay audio waveform"));
overlayAudio->setCheckable(true);
connect(overlayAudio, &QAction::toggled, m_glMonitor, &GLWidget::slotSwitchAudioOverlay);
overlayAudio->setChecked(KdenliveSettings::displayAudioOverlay());
m_configMenu->addAction(overlayAudio);
QAction *switchAudioMonitor = m_configMenu->addAction(i18n("Show Audio Levels"), this, SLOT(slotSwitchAudioMonitor()));
switchAudioMonitor->setCheckable(true);
switchAudioMonitor->setChecked((KdenliveSettings::monitoraudio() & m_id) != 0);
// For some reason, the frame in QAbstracSpinBox (base class of TimeCodeDisplay) needs to be displayed once, then hidden
// or it will never appear (supposed to appear on hover).
m_timePos->setFrame(false);
}
void Monitor::slotGoToMarker(QAction *action)
{
int pos = action->data().toInt();
slotSeek(pos);
}
void Monitor::slotForceSize(QAction *a)
{
int resizeType = a->data().toInt();
int profileWidth = 320;
int profileHeight = 200;
if (resizeType > 0) {
// calculate size
QRect r = QApplication::primaryScreen()->geometry();
profileHeight = m_glMonitor->profileSize().height() * resizeType / 100;
profileWidth = pCore->getCurrentProfile()->dar() * profileHeight;
if (profileWidth > r.width() * 0.8 || profileHeight > r.height() * 0.7) {
// reset action to free resize
const QList list = m_forceSize->actions();
for (QAction *ac : list) {
if (ac->data().toInt() == m_forceSizeFactor) {
m_forceSize->setCurrentAction(ac);
break;
}
}
warningMessage(i18n("Your screen resolution is not sufficient for this action"));
return;
}
}
switch (resizeType) {
case 100:
case 50:
// resize full size
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_videoWidget->setMinimumSize(profileWidth, profileHeight);
m_videoWidget->setMaximumSize(profileWidth, profileHeight);
setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height() + m_glMonitor->getControllerProxy()->rulerHeight()));
break;
default:
// Free resize
m_videoWidget->setMinimumSize(profileWidth, profileHeight);
m_videoWidget->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height() + m_glMonitor->getControllerProxy()->rulerHeight()));
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
break;
}
m_forceSizeFactor = resizeType;
updateGeometry();
}
QString Monitor::getTimecodeFromFrames(int pos)
{
return m_monitorManager->timecode().getTimecodeFromFrames(pos);
}
double Monitor::fps() const
{
return m_monitorManager->timecode().fps();
}
Timecode Monitor::timecode() const
{
return m_monitorManager->timecode();
}
void Monitor::updateMarkers()
{
if (m_controller) {
m_markerMenu->clear();
QList markers = m_controller->getMarkerModel()->getAllMarkers();
if (!markers.isEmpty()) {
for (int i = 0; i < markers.count(); ++i) {
int pos = (int)markers.at(i).time().frames(m_monitorManager->timecode().fps());
QString position = m_monitorManager->timecode().getTimecode(markers.at(i).time()) + QLatin1Char(' ') + markers.at(i).comment();
QAction *go = m_markerMenu->addAction(position);
go->setData(pos);
}
}
m_markerMenu->setEnabled(!m_markerMenu->isEmpty());
}
}
void Monitor::setGuides(const QMap &guides)
{
// TODO: load guides model
m_markerMenu->clear();
QMapIterator i(guides);
QList guidesList;
while (i.hasNext()) {
i.next();
CommentedTime timeGuide(GenTime(i.key()), i.value());
guidesList << timeGuide;
int pos = (int)timeGuide.time().frames(m_monitorManager->timecode().fps());
QString position = m_monitorManager->timecode().getTimecode(timeGuide.time()) + QLatin1Char(' ') + timeGuide.comment();
QAction *go = m_markerMenu->addAction(position);
go->setData(pos);
}
// m_ruler->setMarkers(guidesList);
m_markerMenu->setEnabled(!m_markerMenu->isEmpty());
checkOverlay();
}
void Monitor::slotSeekToPreviousSnap()
{
if (m_controller) {
m_glMonitor->seek(getSnapForPos(true).frames(m_monitorManager->timecode().fps()));
}
}
void Monitor::slotSeekToNextSnap()
{
if (m_controller) {
m_glMonitor->seek(getSnapForPos(false).frames(m_monitorManager->timecode().fps()));
}
}
int Monitor::position()
{
return m_glMonitor->getCurrentPos();
}
GenTime Monitor::getSnapForPos(bool previous)
{
int frame = previous ? m_snaps->getPreviousPoint(m_glMonitor->getCurrentPos()) : m_snaps->getNextPoint(m_glMonitor->getCurrentPos());
return {frame, pCore->getCurrentFps()};
}
void Monitor::slotLoadClipZone(const QPoint &zone)
{
m_glMonitor->getControllerProxy()->setZone(zone.x(), zone.y());
checkOverlay();
}
void Monitor::slotSetZoneStart()
{
m_glMonitor->getControllerProxy()->setZoneIn(m_glMonitor->getCurrentPos());
if (m_controller) {
m_controller->setZone(m_glMonitor->getControllerProxy()->zone());
} else {
// timeline
emit timelineZoneChanged();
}
checkOverlay();
}
void Monitor::slotSetZoneEnd(bool discardLastFrame)
{
Q_UNUSED(discardLastFrame);
int pos = m_glMonitor->getCurrentPos();
if (m_controller) {
if (pos < (int)m_controller->frameDuration() - 1) {
pos++;
}
} else
pos++;
m_glMonitor->getControllerProxy()->setZoneOut(pos);
if (m_controller) {
m_controller->setZone(m_glMonitor->getControllerProxy()->zone());
}
checkOverlay();
}
// virtual
void Monitor::mousePressEvent(QMouseEvent *event)
{
m_monitorManager->activateMonitor(m_id);
if ((event->button() & Qt::RightButton) == 0u) {
if (m_glWidget->geometry().contains(event->pos())) {
m_DragStartPosition = event->pos();
event->accept();
}
} else if (m_contextMenu) {
slotActivateMonitor();
m_contextMenu->popup(event->globalPos());
event->accept();
}
QWidget::mousePressEvent(event);
}
void Monitor::slotShowMenu(const QPoint pos)
{
slotActivateMonitor();
if (m_contextMenu) {
if (m_markerMenu) {
// Fill guide menu
m_markerMenu->clear();
std::shared_ptr model;
if (m_id == Kdenlive::ClipMonitor && m_controller) {
model = m_controller->getMarkerModel();
} else if (m_id == Kdenlive::ProjectMonitor && pCore->currentDoc()) {
model = pCore->currentDoc()->getGuideModel();
}
if (model) {
QList markersList = model->getAllMarkers();
for (CommentedTime mkr : markersList) {
QAction *a = m_markerMenu->addAction(mkr.comment());
a->setData(mkr.time().frames(pCore->getCurrentFps()));
}
}
m_markerMenu->setEnabled(!m_markerMenu->isEmpty());
}
m_contextMenu->popup(pos);
}
}
void Monitor::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event)
if (m_glMonitor->zoom() > 0.0f) {
float horizontal = float(m_horizontalScroll->value()) / float(m_horizontalScroll->maximum());
float vertical = float(m_verticalScroll->value()) / float(m_verticalScroll->maximum());
adjustScrollBars(horizontal, vertical);
} else {
m_horizontalScroll->hide();
m_verticalScroll->hide();
}
}
void Monitor::adjustScrollBars(float horizontal, float vertical)
{
if (m_glMonitor->zoom() > 1.0f) {
m_horizontalScroll->setPageStep(m_glWidget->width());
m_horizontalScroll->setMaximum((int)((float)m_glMonitor->profileSize().width() * m_glMonitor->zoom()) - m_horizontalScroll->pageStep());
m_horizontalScroll->setValue(qRound(horizontal * float(m_horizontalScroll->maximum())));
emit m_horizontalScroll->valueChanged(m_horizontalScroll->value());
m_horizontalScroll->show();
} else {
int max = (int)((float)m_glMonitor->profileSize().width() * m_glMonitor->zoom()) - m_glWidget->width();
emit m_horizontalScroll->valueChanged(qRound(0.5 * max));
m_horizontalScroll->hide();
}
if (m_glMonitor->zoom() > 1.0f) {
m_verticalScroll->setPageStep(m_glWidget->height());
m_verticalScroll->setMaximum((int)((float)m_glMonitor->profileSize().height() * m_glMonitor->zoom()) - m_verticalScroll->pageStep());
m_verticalScroll->setValue((int)((float)m_verticalScroll->maximum() * vertical));
emit m_verticalScroll->valueChanged(m_verticalScroll->value());
m_verticalScroll->show();
} else {
int max = (int)((float)m_glMonitor->profileSize().height() * m_glMonitor->zoom()) - m_glWidget->height();
emit m_verticalScroll->valueChanged(qRound(0.5 * max));
m_verticalScroll->hide();
}
}
void Monitor::setZoom()
{
if (qFuzzyCompare(m_glMonitor->zoom(), 1.0f)) {
m_horizontalScroll->hide();
m_verticalScroll->hide();
m_glMonitor->setOffsetX(m_horizontalScroll->value(), m_horizontalScroll->maximum());
m_glMonitor->setOffsetY(m_verticalScroll->value(), m_verticalScroll->maximum());
} else {
adjustScrollBars(0.5f, 0.5f);
}
}
void Monitor::slotSwitchFullScreen(bool minimizeOnly)
{
// TODO: disable screensaver?
pause();
if (!m_videoWidget->isFullScreen() && !minimizeOnly) {
// Move monitor widget to the second screen (one screen for Kdenlive, the other one for the Monitor widget)
if (qApp->screens().count() > 1) {
for (auto screen : qApp->screens()) {
if (screen != qApp->screenAt(this->parentWidget()->mapToGlobal(QPoint()))) {
QRect rect = screen->availableGeometry();
m_videoWidget->setParent(nullptr);
m_videoWidget->move(this->parentWidget()->mapFromGlobal(rect.topLeft()));
break;
}
}
} else {
m_videoWidget->setParent(qApp->desktop()->screen(0));
}
m_qmlManager->enableAudioThumbs(false);
m_videoWidget->showFullScreen();
} else {
m_videoWidget->setParent(m_glWidget);
//m_videoWidget->move(this->pos());
m_videoWidget->showNormal();
m_qmlManager->enableAudioThumbs(true);
auto *lay = (QGridLayout *)m_glWidget->layout();
lay->addWidget(m_videoWidget, 0, 0);
}
}
// virtual
void Monitor::mouseReleaseEvent(QMouseEvent *event)
{
if (m_dragStarted) {
event->ignore();
return;
}
if (event->button() != Qt::RightButton) {
if (m_glMonitor->geometry().contains(event->pos())) {
if (isActive()) {
slotPlay();
} else {
slotActivateMonitor();
}
} // else event->ignore(); //QWidget::mouseReleaseEvent(event);
}
m_dragStarted = false;
event->accept();
QWidget::mouseReleaseEvent(event);
}
void Monitor::slotStartDrag()
{
if (m_id == Kdenlive::ProjectMonitor || m_controller == nullptr) {
// dragging is only allowed for clip monitor
return;
}
auto *drag = new QDrag(this);
auto *mimeData = new QMimeData;
// Get drag state
QQuickItem *root = m_glMonitor->rootObject();
int dragType = 0;
if (root) {
dragType = root->property("dragType").toInt();
root->setProperty("dragType", 0);
}
QByteArray prodData;
QPoint p = m_glMonitor->getControllerProxy()->zone();
if (p.x() == -1 || p.y() == -1) {
prodData = m_controller->AbstractProjectItem::clipId().toUtf8();
} else {
QStringList list;
list.append(m_controller->AbstractProjectItem::clipId());
list.append(QString::number(p.x()));
list.append(QString::number(p.y() - 1));
prodData.append(list.join(QLatin1Char('/')).toUtf8());
}
switch (dragType) {
case 1:
// Audio only drag
prodData.prepend('A');
break;
case 2:
// Audio only drag
prodData.prepend('V');
break;
default:
break;
}
mimeData->setData(QStringLiteral("kdenlive/producerslist"), prodData);
drag->setMimeData(mimeData);
/*QPixmap pix = m_currentClip->thumbnail();
drag->setPixmap(pix);
drag->setHotSpot(QPoint(0, 50));*/
drag->exec(Qt::MoveAction);
}
void Monitor::enterEvent(QEvent *event)
{
m_qmlManager->enableAudioThumbs(true);
QWidget::enterEvent(event);
}
void Monitor::leaveEvent(QEvent *event)
{
m_qmlManager->enableAudioThumbs(false);
QWidget::leaveEvent(event);
}
// virtual
void Monitor::mouseMoveEvent(QMouseEvent *event)
{
if (m_dragStarted || m_controller == nullptr) {
return;
}
if ((event->pos() - m_DragStartPosition).manhattanLength() < QApplication::startDragDistance()) {
return;
}
{
auto *drag = new QDrag(this);
auto *mimeData = new QMimeData;
m_dragStarted = true;
QStringList list;
list.append(m_controller->AbstractProjectItem::clipId());
QPoint p = m_glMonitor->getControllerProxy()->zone();
list.append(QString::number(p.x()));
list.append(QString::number(p.y()));
QByteArray clipData;
clipData.append(list.join(QLatin1Char(';')).toUtf8());
mimeData->setData(QStringLiteral("kdenlive/clip"), clipData);
drag->setMimeData(mimeData);
drag->exec(Qt::MoveAction);
}
event->accept();
}
/*void Monitor::dragMoveEvent(QDragMoveEvent * event) {
event->setDropAction(Qt::IgnoreAction);
event->setDropAction(Qt::MoveAction);
if (event->mimeData()->hasText()) {
event->acceptProposedAction();
}
}
Qt::DropActions Monitor::supportedDropActions() const {
// returns what actions are supported when dropping
return Qt::MoveAction;
}*/
QStringList Monitor::mimeTypes() const
{
QStringList qstrList;
// list of accepted MIME types for drop
qstrList.append(QStringLiteral("kdenlive/clip"));
return qstrList;
}
// virtual
void Monitor::wheelEvent(QWheelEvent *event)
{
slotMouseSeek(event->delta(), event->modifiers());
event->accept();
}
void Monitor::mouseDoubleClickEvent(QMouseEvent *event)
{
slotSwitchFullScreen();
event->accept();
}
void Monitor::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
slotSwitchFullScreen();
event->accept();
return;
}
if (m_videoWidget->isFullScreen()) {
event->ignore();
emit passKeyPress(event);
return;
}
QWidget::keyPressEvent(event);
}
void Monitor::slotMouseSeek(int eventDelta, uint modifiers)
{
if ((modifiers & Qt::ControlModifier) != 0u) {
int delta = m_monitorManager->timecode().fps();
if (eventDelta > 0) {
delta = 0 - delta;
}
m_glMonitor->seek(m_glMonitor->getCurrentPos() - delta);
} else if ((modifiers & Qt::AltModifier) != 0u) {
if (eventDelta >= 0) {
emit seekToPreviousSnap();
} else {
emit seekToNextSnap();
}
} else {
if (eventDelta >= 0) {
slotRewindOneFrame();
} else {
slotForwardOneFrame();
}
}
}
void Monitor::slotSetThumbFrame()
{
if (m_controller == nullptr) {
return;
}
m_controller->setProducerProperty(QStringLiteral("kdenlive:thumbnailFrame"), m_glMonitor->getCurrentPos());
emit refreshClipThumbnail(m_controller->AbstractProjectItem::clipId());
}
void Monitor::slotExtractCurrentZone()
{
if (m_controller == nullptr) {
return;
}
GenTime inPoint(getZoneStart(), pCore->getCurrentFps());
GenTime outPoint(getZoneEnd(), pCore->getCurrentFps());
pCore->jobManager()->startJob({m_controller->clipId()}, -1, QString(), inPoint, outPoint);
}
std::shared_ptr Monitor::currentController() const
{
return m_controller;
}
void Monitor::slotExtractCurrentFrame(QString frameName, bool addToProject)
{
if (QFileInfo(frameName).fileName().isEmpty()) {
// convenience: when extracting an image to be added to the project,
// suggest a suitable image file name. In the project monitor, this
// suggestion bases on the project file name; in the clip monitor,
// the suggestion bases on the clip file name currently shown.
// Finally, the frame number is added to this suggestion, prefixed
// with "-f", so we get something like clip-f#.png.
QString suggestedImageName =
QFileInfo(currentController() ? currentController()->clipName()
: pCore->currentDoc()->url().isValid() ? pCore->currentDoc()->url().fileName() : i18n("untitled"))
.completeBaseName() +
QStringLiteral("-f") + QString::number(m_glMonitor->getCurrentPos()).rightJustified(6, QLatin1Char('0')) + QStringLiteral(".png");
frameName = QFileInfo(frameName, suggestedImageName).fileName();
}
QString framesFolder = KRecentDirs::dir(QStringLiteral(":KdenliveFramesFolder"));
if (framesFolder.isEmpty()) {
framesFolder = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
}
QScopedPointer dlg(new QDialog(this));
QScopedPointer fileWidget(new KFileWidget(QUrl::fromLocalFile(framesFolder), dlg.data()));
dlg->setWindowTitle(addToProject ? i18n("Save Image") : i18n("Save Image to Project"));
auto *layout = new QVBoxLayout;
layout->addWidget(fileWidget.data());
QCheckBox *b = nullptr;
if (m_id == Kdenlive::ClipMonitor) {
b = new QCheckBox(i18n("Export image using source resolution"), dlg.data());
b->setChecked(KdenliveSettings::exportframe_usingsourceres());
fileWidget->setCustomWidget(b);
}
fileWidget->setConfirmOverwrite(true);
fileWidget->okButton()->show();
fileWidget->cancelButton()->show();
QObject::connect(fileWidget->okButton(), &QPushButton::clicked, fileWidget.data(), &KFileWidget::slotOk);
QObject::connect(fileWidget.data(), &KFileWidget::accepted, fileWidget.data(), &KFileWidget::accept);
QObject::connect(fileWidget.data(), &KFileWidget::accepted, dlg.data(), &QDialog::accept);
QObject::connect(fileWidget->cancelButton(), &QPushButton::clicked, dlg.data(), &QDialog::reject);
dlg->setLayout(layout);
fileWidget->setMimeFilter(QStringList() << QStringLiteral("image/png"));
fileWidget->setMode(KFile::File | KFile::LocalOnly);
fileWidget->setOperationMode(KFileWidget::Saving);
QUrl relativeUrl;
relativeUrl.setPath(frameName);
#if KIO_VERSION >= QT_VERSION_CHECK(5, 33, 0)
fileWidget->setSelectedUrl(relativeUrl);
#else
fileWidget->setSelection(relativeUrl.toString());
#endif
KSharedConfig::Ptr conf = KSharedConfig::openConfig();
QWindow *handle = dlg->windowHandle();
if ((handle != nullptr) && conf->hasGroup("FileDialogSize")) {
KWindowConfig::restoreWindowSize(handle, conf->group("FileDialogSize"));
dlg->resize(handle->size());
}
if (dlg->exec() == QDialog::Accepted) {
QString selectedFile = fileWidget->selectedFile();
if (!selectedFile.isEmpty()) {
// Create Qimage with frame
QImage frame;
// check if we are using a proxy
if ((m_controller != nullptr) && !m_controller->getProducerProperty(QStringLiteral("kdenlive:proxy")).isEmpty() &&
m_controller->getProducerProperty(QStringLiteral("kdenlive:proxy")) != QLatin1String("-")) {
// using proxy, use original clip url to get frame
frame = m_glMonitor->getControllerProxy()->extractFrame(m_glMonitor->getCurrentPos(),
m_controller->getProducerProperty(QStringLiteral("kdenlive:originalurl")), -1, -1,
b != nullptr ? b->isChecked() : false);
} else {
frame = m_glMonitor->getControllerProxy()->extractFrame(m_glMonitor->getCurrentPos(), QString(), -1, -1, b != nullptr ? b->isChecked() : false);
}
frame.save(selectedFile);
if (b != nullptr) {
KdenliveSettings::setExportframe_usingsourceres(b->isChecked());
}
KRecentDirs::add(QStringLiteral(":KdenliveFramesFolder"), QUrl::fromLocalFile(selectedFile).adjusted(QUrl::RemoveFilename).toLocalFile());
if (addToProject) {
QString folderInfo = pCore->bin()->getCurrentFolder();
pCore->bin()->droppedUrls(QList {QUrl::fromLocalFile(selectedFile)}, folderInfo);
}
}
}
}
void Monitor::setTimePos(const QString &pos)
{
m_timePos->setValue(pos);
slotSeek();
}
void Monitor::slotSeek()
{
slotSeek(m_timePos->getValue());
}
void Monitor::slotSeek(int pos)
{
slotActivateMonitor();
m_glMonitor->seek(pos);
m_monitorManager->cleanMixer();
}
void Monitor::checkOverlay(int pos)
{
if (m_qmlManager->sceneType() != MonitorSceneDefault) {
// we are not in main view, ignore
return;
}
QString overlayText;
if (pos == -1) {
pos = m_timePos->getValue();
}
std::shared_ptr model(nullptr);
if (m_id == Kdenlive::ClipMonitor) {
if (m_controller) {
model = m_controller->getMarkerModel();
}
} else if (m_id == Kdenlive::ProjectMonitor && pCore->currentDoc()) {
model = pCore->currentDoc()->getGuideModel();
}
if (model) {
bool found = false;
CommentedTime marker = model->getMarker(GenTime(pos, m_monitorManager->timecode().fps()), &found);
if (found) {
overlayText = marker.comment();
}
}
m_glMonitor->getControllerProxy()->setMarkerComment(overlayText);
}
int Monitor::getZoneStart()
{
return m_glMonitor->getControllerProxy()->zoneIn();
}
int Monitor::getZoneEnd()
{
return m_glMonitor->getControllerProxy()->zoneOut();
}
void Monitor::slotZoneStart()
{
slotActivateMonitor();
m_glMonitor->getControllerProxy()->pauseAndSeek(m_glMonitor->getControllerProxy()->zoneIn());
}
void Monitor::slotZoneEnd()
{
slotActivateMonitor();
m_glMonitor->getControllerProxy()->pauseAndSeek(m_glMonitor->getControllerProxy()->zoneOut() - 1);
}
void Monitor::slotRewind(double speed)
{
slotActivateMonitor();
if (qFuzzyIsNull(speed)) {
double currentspeed = m_glMonitor->playSpeed();
if (currentspeed > -1) {
speed = -1;
} else {
speed = currentspeed * 1.5;
}
}
m_glMonitor->switchPlay(true, speed);
m_playAction->setActive(true);
}
void Monitor::slotForward(double speed)
{
slotActivateMonitor();
if (qFuzzyIsNull(speed)) {
double currentspeed = m_glMonitor->playSpeed();
if (currentspeed < 1) {
speed = 1;
} else {
speed = currentspeed * 1.2;
}
}
m_glMonitor->switchPlay(true, speed);
m_playAction->setActive(true);
}
void Monitor::slotRewindOneFrame(int diff)
{
slotActivateMonitor();
m_glMonitor->seek(m_glMonitor->getCurrentPos() - diff);
}
void Monitor::slotForwardOneFrame(int diff)
{
slotActivateMonitor();
m_glMonitor->seek(m_glMonitor->getCurrentPos() + diff);
}
void Monitor::adjustRulerSize(int length, const std::shared_ptr &markerModel)
{
if (m_controller != nullptr) {
m_glMonitor->setRulerInfo(length);
} else {
m_glMonitor->setRulerInfo(length, markerModel);
}
m_timePos->setRange(0, length);
if (markerModel) {
connect(markerModel.get(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector &)), this, SLOT(checkOverlay()));
connect(markerModel.get(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(checkOverlay()));
connect(markerModel.get(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(checkOverlay()));
}
}
void Monitor::stop()
{
m_playAction->setActive(false);
m_glMonitor->stop();
}
void Monitor::mute(bool mute, bool updateIconOnly)
{
// TODO: we should set the "audio_off" property to 1 to mute the consumer instead of changing volume
QIcon icon;
if (mute || KdenliveSettings::volume() == 0) {
icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted"));
} else {
icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
}
if (!updateIconOnly) {
m_glMonitor->setVolume(mute ? 0 : (double)KdenliveSettings::volume() / 100.0);
}
}
void Monitor::start()
{
if (!isVisible() || !isActive()) {
return;
}
m_glMonitor->startConsumer();
}
void Monitor::slotRefreshMonitor(bool visible)
{
if (visible) {
if (slotActivateMonitor()) {
start();
}
}
}
void Monitor::refreshMonitorIfActive(bool directUpdate)
{
if (isActive()) {
if (directUpdate) {
m_glMonitor->refresh();
} else {
m_glMonitor->requestRefresh();
}
}
}
void Monitor::pause()
{
if (!m_playAction->isActive()) {
return;
}
slotActivateMonitor();
m_glMonitor->switchPlay(false);
m_playAction->setActive(false);
}
void Monitor::switchPlay(bool play)
{
m_playAction->setActive(play);
m_glMonitor->switchPlay(play);
}
void Monitor::slotSwitchPlay()
{
slotActivateMonitor();
m_glMonitor->switchPlay(m_playAction->isActive());
}
void Monitor::slotPlay()
{
m_playAction->trigger();
}
void Monitor::resetPlayOrLoopZone(const QString &binId)
{
if (activeClipId() == binId) {
m_glMonitor->resetZoneMode();
}
}
void Monitor::slotPlayZone()
{
slotActivateMonitor();
bool ok = m_glMonitor->playZone();
if (ok) {
m_playAction->setActive(true);
}
}
void Monitor::slotLoopZone()
{
slotActivateMonitor();
bool ok = m_glMonitor->playZone(true);
if (ok) {
m_playAction->setActive(true);
}
}
void Monitor::slotLoopClip()
{
slotActivateMonitor();
bool ok = m_glMonitor->loopClip();
if (ok) {
m_playAction->setActive(true);
}
}
void Monitor::updateClipProducer(const std::shared_ptr &prod)
{
if (m_glMonitor->setProducer(prod, isActive(), -1)) {
prod->set_speed(1.0);
}
}
void Monitor::updateClipProducer(const QString &playlist)
{
Q_UNUSED(playlist)
// TODO
// Mlt::Producer *prod = new Mlt::Producer(*m_glMonitor->profile(), playlist.toUtf8().constData());
// m_glMonitor->setProducer(prod, isActive(), render->seekFramePosition());
m_glMonitor->switchPlay(true);
}
void Monitor::slotOpenClip(const std::shared_ptr &controller, int in, int out)
{
if (m_controller) {
m_glMonitor->resetZoneMode();
disconnect(m_controller->getMarkerModel().get(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector &)), this,
SLOT(checkOverlay()));
disconnect(m_controller->getMarkerModel().get(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(checkOverlay()));
disconnect(m_controller->getMarkerModel().get(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(checkOverlay()));
}
m_controller = controller;
m_snaps.reset(new SnapModel());
m_glMonitor->getControllerProxy()->resetZone();
if (controller) {
connect(m_controller->getMarkerModel().get(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector &)), this,
SLOT(checkOverlay()));
connect(m_controller->getMarkerModel().get(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(checkOverlay()));
connect(m_controller->getMarkerModel().get(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(checkOverlay()));
if (m_recManager->toolbar()->isVisible()) {
// we are in record mode, don't display clip
return;
}
m_glMonitor->setRulerInfo((int)m_controller->frameDuration(), controller->getMarkerModel());
loadQmlScene(MonitorSceneDefault);
m_timePos->setRange(0, (int)m_controller->frameDuration());
updateMarkers();
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::addSnap, this, &Monitor::addSnapPoint, Qt::DirectConnection);
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::removeSnap, this, &Monitor::removeSnapPoint, Qt::DirectConnection);
if (out == -1) {
m_glMonitor->getControllerProxy()->setZone(m_controller->zone(), false);
qDebug() << m_controller->zone();
} else {
m_glMonitor->getControllerProxy()->setZone(in, out, false);
}
m_snaps->addPoint((int)m_controller->frameDuration());
// Loading new clip / zone, stop if playing
if (m_playAction->isActive()) {
m_playAction->setActive(false);
}
m_glMonitor->setProducer(m_controller->originalProducer(), isActive(), in);
m_audioMeterWidget->audioChannels = controller->audioInfo() ? controller->audioInfo()->channels() : 0;
m_glMonitor->setAudioThumb(controller->audioChannels(), controller->audioFrameCache);
m_controller->getMarkerModel()->registerSnapModel(m_snaps);
m_glMonitor->getControllerProxy()->setClipProperties(controller->clipType(), controller->hasAudioAndVideo(), controller->clipName());
// hasEffects = controller->hasEffects();
} else {
loadQmlScene(MonitorSceneDefault);
m_glMonitor->setProducer(nullptr, isActive());
m_glMonitor->setAudioThumb();
m_audioMeterWidget->audioChannels = 0;
m_glMonitor->getControllerProxy()->setClipProperties(ClipType::Unknown, false, QString());
}
if (slotActivateMonitor()) {
start();
}
checkOverlay();
}
const QString Monitor::activeClipId()
{
if (m_controller) {
return m_controller->clipId();
}
return QString();
}
void Monitor::slotOpenDvdFile(const QString &file)
{
// TODO
Q_UNUSED(file)
m_glMonitor->initializeGL();
// render->loadUrl(file);
}
void Monitor::slotSaveZone()
{
// TODO? or deprecate
// render->saveZone(pCore->currentDoc()->projectDataFolder(), m_ruler->zone());
}
void Monitor::setCustomProfile(const QString &profile, const Timecode &tc)
{
// TODO or deprecate
Q_UNUSED(profile)
m_timePos->updateTimeCode(tc);
if (/* DISABLES CODE */ (true)) {
return;
}
slotActivateMonitor();
// render->prepareProfileReset(tc.fps());
// TODO: this is a temporary profile for DVD preview, it should not alter project profile
// pCore->setCurrentProfile(profile);
m_glMonitor->reloadProfile();
}
void Monitor::resetProfile()
{
m_timePos->updateTimeCode(m_monitorManager->timecode());
m_glMonitor->reloadProfile();
m_glMonitor->rootObject()->setProperty("framesize", QRect(0, 0, m_glMonitor->profileSize().width(), m_glMonitor->profileSize().height()));
double fps = m_monitorManager->timecode().fps();
// Update drop frame info
m_qmlManager->setProperty(QStringLiteral("dropped"), false);
m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(fps, 'g', 2));
}
void Monitor::resetConsumer(bool fullReset)
{
m_glMonitor->resetConsumer(fullReset);
}
const QString Monitor::sceneList(const QString &root, const QString &fullPath)
{
return m_glMonitor->sceneList(root, fullPath);
}
void Monitor::updateClipZone()
{
if (m_controller == nullptr) {
return;
}
m_controller->setZone(m_glMonitor->getControllerProxy()->zone());
}
void Monitor::updateTimelineClipZone()
{
emit zoneUpdated(m_glMonitor->getControllerProxy()->zone());
}
void Monitor::switchDropFrames(bool drop)
{
m_glMonitor->setDropFrames(drop);
}
void Monitor::switchMonitorInfo(int code)
{
int currentOverlay;
if (m_id == Kdenlive::ClipMonitor) {
currentOverlay = KdenliveSettings::displayClipMonitorInfo();
currentOverlay ^= code;
KdenliveSettings::setDisplayClipMonitorInfo(currentOverlay);
} else {
currentOverlay = KdenliveSettings::displayProjectMonitorInfo();
currentOverlay ^= code;
KdenliveSettings::setDisplayProjectMonitorInfo(currentOverlay);
}
updateQmlDisplay(currentOverlay);
}
void Monitor::updateMonitorGamma()
{
if (isActive()) {
stop();
m_glMonitor->updateGamma();
start();
} else {
m_glMonitor->updateGamma();
}
}
void Monitor::slotEditMarker()
{
if (m_editMarker) {
m_editMarker->trigger();
}
}
void Monitor::updateTimecodeFormat()
{
m_timePos->slotUpdateTimeCodeFormat();
m_glMonitor->rootObject()->setProperty("timecode", m_timePos->displayText());
}
QPoint Monitor::getZoneInfo() const
{
if (m_controller == nullptr) {
return {};
}
return m_controller->zone();
}
void Monitor::slotEnableEffectScene(bool enable)
{
KdenliveSettings::setShowOnMonitorScene(enable);
MonitorSceneType sceneType = enable ? m_lastMonitorSceneType : MonitorSceneDefault;
slotShowEffectScene(sceneType, true);
if (enable) {
emit updateScene();
}
}
void Monitor::slotShowEffectScene(MonitorSceneType sceneType, bool temporary)
{
if (sceneType == MonitorSceneNone) {
// We just want to revert to normal scene
if (m_qmlManager->sceneType() == MonitorSceneSplit || m_qmlManager->sceneType() == MonitorSceneDefault) {
// Ok, nothing to do
return;
}
sceneType = MonitorSceneDefault;
}
if (!temporary) {
m_lastMonitorSceneType = sceneType;
}
loadQmlScene(sceneType);
}
void Monitor::slotSeekToKeyFrame()
{
if (m_qmlManager->sceneType() == MonitorSceneGeometry) {
// Adjust splitter pos
int kfr = m_glMonitor->rootObject()->property("requestedKeyFrame").toInt();
emit seekToKeyframe(kfr);
}
}
void Monitor::setUpEffectGeometry(const QRect &r, const QVariantList &list, const QVariantList &types)
{
QQuickItem *root = m_glMonitor->rootObject();
if (!root) {
return;
}
if (!list.isEmpty()) {
root->setProperty("centerPointsTypes", types);
root->setProperty("centerPoints", list);
}
if (!r.isEmpty()) {
root->setProperty("framesize", r);
}
}
void Monitor::setEffectSceneProperty(const QString &name, const QVariant &value)
{
QQuickItem *root = m_glMonitor->rootObject();
if (!root) {
return;
}
root->setProperty(name.toUtf8().constData(), value);
}
QRect Monitor::effectRect() const
{
QQuickItem *root = m_glMonitor->rootObject();
if (!root) {
return {};
}
return root->property("framesize").toRect();
}
QVariantList Monitor::effectPolygon() const
{
QQuickItem *root = m_glMonitor->rootObject();
if (!root) {
return QVariantList();
}
return root->property("centerPoints").toList();
}
QVariantList Monitor::effectRoto() const
{
QQuickItem *root = m_glMonitor->rootObject();
if (!root) {
return QVariantList();
}
QVariantList points = root->property("centerPoints").toList();
QVariantList controlPoints = root->property("centerPointsTypes").toList();
// rotoscoping effect needs a list of
QVariantList mix;
mix.reserve(points.count() * 3);
for (int i = 0; i < points.count(); i++) {
mix << controlPoints.at(2 * i);
mix << points.at(i);
mix << controlPoints.at(2 * i + 1);
}
return mix;
}
void Monitor::setEffectKeyframe(bool enable)
{
QQuickItem *root = m_glMonitor->rootObject();
if (root) {
root->setProperty("iskeyframe", enable);
}
}
bool Monitor::effectSceneDisplayed(MonitorSceneType effectType)
{
return m_qmlManager->sceneType() == effectType;
}
void Monitor::slotSetVolume(int volume)
{
KdenliveSettings::setVolume(volume);
QIcon icon;
double renderVolume = m_glMonitor->volume();
m_glMonitor->setVolume((double)volume / 100.0);
if (renderVolume > 0 && volume > 0) {
return;
}
/*if (volume == 0) {
icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted"));
} else {
icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
}*/
}
void Monitor::sendFrameForAnalysis(bool analyse)
{
m_glMonitor->sendFrameForAnalysis = analyse;
}
void Monitor::updateAudioForAnalysis()
{
m_glMonitor->updateAudioForAnalysis();
}
void Monitor::onFrameDisplayed(const SharedFrame &frame)
{
m_monitorManager->frameDisplayed(frame);
if (!m_glMonitor->checkFrameNumber(frame.get_position(), m_id == Kdenlive::ClipMonitor ? 0 : TimelineModel::seekDuration)) {
m_playAction->setActive(false);
}
checkDrops(m_glMonitor->droppedFrames());
}
void Monitor::checkDrops(int dropped)
{
if (m_droppedTimer.isValid()) {
if (m_droppedTimer.hasExpired(1000)) {
m_droppedTimer.invalidate();
double fps = m_monitorManager->timecode().fps();
if (dropped == 0) {
// No dropped frames since last check
m_qmlManager->setProperty(QStringLiteral("dropped"), false);
m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(fps, 'g', 2));
} else {
m_glMonitor->resetDrops();
fps -= dropped;
m_qmlManager->setProperty(QStringLiteral("dropped"), true);
m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(fps, 'g', 2));
m_droppedTimer.start();
}
}
} else if (dropped > 0) {
// Start m_dropTimer
m_glMonitor->resetDrops();
m_droppedTimer.start();
}
}
void Monitor::reloadProducer(const QString &id)
{
if (!m_controller) {
return;
}
if (m_controller->AbstractProjectItem::clipId() == id) {
slotOpenClip(m_controller);
}
}
QString Monitor::getMarkerThumb(GenTime pos)
{
if (!m_controller) {
return QString();
}
if (!m_controller->getClipHash().isEmpty()) {
bool ok = false;
QDir dir = pCore->currentDoc()->getCacheDir(CacheThumbs, &ok);
if (ok) {
QString url = dir.absoluteFilePath(m_controller->getClipHash() + QLatin1Char('#') +
QString::number((int)pos.frames(m_monitorManager->timecode().fps())) + QStringLiteral(".png"));
if (QFile::exists(url)) {
return url;
}
}
}
return QString();
}
void Monitor::setPalette(const QPalette &p)
{
QWidget::setPalette(p);
QList allButtons = this->findChildren();
for (int i = 0; i < allButtons.count(); i++) {
QToolButton *m = allButtons.at(i);
QIcon ic = m->icon();
if (ic.isNull() || ic.name().isEmpty()) {
continue;
}
QIcon newIcon = QIcon::fromTheme(ic.name());
m->setIcon(newIcon);
}
QQuickItem *root = m_glMonitor->rootObject();
if (root) {
QMetaObject::invokeMethod(root, "updatePalette");
}
m_audioMeterWidget->refreshPixmap();
}
void Monitor::gpuError()
{
qCWarning(KDENLIVE_LOG) << " + + + + Error initializing Movit GLSL manager";
warningMessage(i18n("Cannot initialize Movit's GLSL manager, please disable Movit"), -1);
}
void Monitor::warningMessage(const QString &text, int timeout, const QList &actions)
{
m_infoMessage->setMessageType(KMessageWidget::Warning);
m_infoMessage->setText(text);
for (QAction *action : actions) {
m_infoMessage->addAction(action);
}
m_infoMessage->setCloseButtonVisible(true);
m_infoMessage->animatedShow();
if (timeout > 0) {
QTimer::singleShot(timeout, m_infoMessage, &KMessageWidget::animatedHide);
}
}
void Monitor::activateSplit()
{
loadQmlScene(MonitorSceneSplit);
if (isActive()) {
m_glMonitor->requestRefresh();
} else if (slotActivateMonitor()) {
start();
}
}
void Monitor::slotSwitchCompare(bool enable)
{
if (m_id == Kdenlive::ProjectMonitor) {
if (enable) {
if (m_qmlManager->sceneType() == MonitorSceneSplit) {
// Split scene is already active
return;
}
m_splitEffect.reset(new Mlt::Filter(pCore->getCurrentProfile()->profile(), "frei0r.alphagrad"));
if ((m_splitEffect != nullptr) && m_splitEffect->is_valid()) {
m_splitEffect->set("0", 0.5); // 0 is the Clip left parameter
m_splitEffect->set("1", 0); // 1 is gradient width
m_splitEffect->set("2", -0.747); // 2 is tilt
} else {
// frei0r.scal0tilt is not available
warningMessage(i18n("The alphagrad filter is required for that feature, please install frei0r and restart Kdenlive"));
return;
}
emit createSplitOverlay(m_splitEffect);
return;
}
// Delete temp track
emit removeSplitOverlay();
m_splitEffect.reset();
loadQmlScene(MonitorSceneDefault);
if (isActive()) {
m_glMonitor->requestRefresh();
} else if (slotActivateMonitor()) {
start();
}
return;
}
if (m_controller == nullptr || !m_controller->hasEffects()) {
// disable split effect
if (m_controller) {
pCore->displayMessage(i18n("Clip has no effects"), InformationMessage);
} else {
pCore->displayMessage(i18n("Select a clip in project bin to compare effect"), InformationMessage);
}
return;
}
if (enable) {
if (m_qmlManager->sceneType() == MonitorSceneSplit) {
// Split scene is already active
qDebug() << " . . . .. ALREADY ACTIVE";
return;
}
buildSplitEffect(m_controller->masterProducer());
} else if (m_splitEffect) {
// TODO
m_glMonitor->setProducer(m_controller->originalProducer(), isActive(), position());
m_splitEffect.reset();
m_splitProducer.reset();
loadQmlScene(MonitorSceneDefault);
}
slotActivateMonitor();
}
void Monitor::buildSplitEffect(Mlt::Producer *original)
{
m_splitEffect.reset(new Mlt::Filter(pCore->getCurrentProfile()->profile(), "frei0r.alphagrad"));
if ((m_splitEffect != nullptr) && m_splitEffect->is_valid()) {
m_splitEffect->set("0", 0.5); // 0 is the Clip left parameter
m_splitEffect->set("1", 0); // 1 is gradient width
m_splitEffect->set("2", -0.747); // 2 is tilt
} else {
// frei0r.scal0tilt is not available
pCore->displayMessage(i18n("The alphagrad filter is required for that feature, please install frei0r and restart Kdenlive"), ErrorMessage);
return;
}
QString splitTransition = TransitionsRepository::get()->getCompositingTransition();
Mlt::Transition t(pCore->getCurrentProfile()->profile(), splitTransition.toUtf8().constData());
if (!t.is_valid()) {
m_splitEffect.reset();
pCore->displayMessage(i18n("The cairoblend transition is required for that feature, please install frei0r and restart Kdenlive"), ErrorMessage);
return;
}
Mlt::Tractor trac(pCore->getCurrentProfile()->profile());
std::shared_ptr clone = ProjectClip::cloneProducer(std::make_shared(original));
// Delete all effects
int ct = 0;
Mlt::Filter *filter = clone->filter(ct);
while (filter != nullptr) {
QString ix = QString::fromLatin1(filter->get("kdenlive_id"));
if (!ix.isEmpty()) {
if (clone->detach(*filter) == 0) {
} else {
ct++;
}
} else {
ct++;
}
delete filter;
filter = clone->filter(ct);
}
trac.set_track(*original, 0);
trac.set_track(*clone.get(), 1);
clone.get()->attach(*m_splitEffect.get());
t.set("always_active", 1);
trac.plant_transition(t, 0, 1);
delete original;
m_splitProducer = std::make_shared(trac.get_producer());
m_glMonitor->setProducer(m_splitProducer, isActive(), position());
m_glMonitor->setRulerInfo((int)m_controller->frameDuration(), m_controller->getMarkerModel());
loadQmlScene(MonitorSceneSplit);
}
QSize Monitor::profileSize() const
{
return m_glMonitor->profileSize();
}
void Monitor::loadQmlScene(MonitorSceneType type)
{
if (m_id == Kdenlive::DvdMonitor || type == m_qmlManager->sceneType()) {
return;
}
bool sceneWithEdit = type == MonitorSceneGeometry || type == MonitorSceneCorners || type == MonitorSceneRoto;
if ((m_sceneVisibilityAction != nullptr) && !m_sceneVisibilityAction->isChecked() && sceneWithEdit) {
// User doesn't want effect scenes
pCore->displayMessage(i18n("Enable edit mode in monitor to edit effect"), InformationMessage, 500);
type = MonitorSceneDefault;
}
double ratio = (double)m_glMonitor->profileSize().width() / (int)(m_glMonitor->profileSize().height() * pCore->getCurrentProfile()->dar() + 0.5);
m_qmlManager->setScene(m_id, type, m_glMonitor->profileSize(), ratio, m_glMonitor->displayRect(), m_glMonitor->zoom(), m_timePos->maximum());
QQuickItem *root = m_glMonitor->rootObject();
switch (type) {
case MonitorSceneSplit:
QObject::connect(root, SIGNAL(qmlMoveSplit()), this, SLOT(slotAdjustEffectCompare()), Qt::UniqueConnection);
break;
case MonitorSceneGeometry:
case MonitorSceneCorners:
case MonitorSceneRoto:
break;
case MonitorSceneRipple:
QObject::connect(root, SIGNAL(doAcceptRipple(bool)), this, SIGNAL(acceptRipple(bool)), Qt::UniqueConnection);
QObject::connect(root, SIGNAL(switchTrimMode(int)), this, SIGNAL(switchTrimMode(int)), Qt::UniqueConnection);
break;
case MonitorSceneDefault:
QObject::connect(root, SIGNAL(editCurrentMarker()), this, SLOT(slotEditInlineMarker()), Qt::UniqueConnection);
m_qmlManager->setProperty(QStringLiteral("timecode"), m_timePos->displayText());
if (m_id == Kdenlive::ClipMonitor) {
updateQmlDisplay(KdenliveSettings::displayClipMonitorInfo());
} else if (m_id == Kdenlive::ProjectMonitor) {
updateQmlDisplay(KdenliveSettings::displayProjectMonitorInfo());
}
break;
default:
break;
}
m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(m_monitorManager->timecode().fps(), 'g', 2));
}
void Monitor::setQmlProperty(const QString &name, const QVariant &value)
{
m_qmlManager->setProperty(name, value);
}
void Monitor::slotAdjustEffectCompare()
{
QRect r = m_glMonitor->rect();
double percent = 0.5;
if (m_qmlManager->sceneType() == MonitorSceneSplit) {
// Adjust splitter pos
QQuickItem *root = m_glMonitor->rootObject();
percent = 0.5 - ((root->property("splitterPos").toInt() - r.left() - r.width() / 2.0) / (double)r.width() / 2.0) / 0.75;
// Store real frame percentage for resize events
root->setProperty("realpercent", percent);
}
if (m_splitEffect) {
m_splitEffect->set("0", percent);
}
m_glMonitor->refresh();
}
void Monitor::slotSwitchRec(bool enable)
{
if (!m_recManager) {
return;
}
if (enable) {
m_toolbar->setVisible(false);
m_recManager->toolbar()->setVisible(true);
} else if (m_recManager->toolbar()->isVisible()) {
m_recManager->stop();
m_toolbar->setVisible(true);
emit refreshCurrentClip();
}
}
void Monitor::doKeyPressEvent(QKeyEvent *ev)
{
keyPressEvent(ev);
}
void Monitor::slotEditInlineMarker()
{
QQuickItem *root = m_glMonitor->rootObject();
if (root) {
std::shared_ptr model;
if (m_controller) {
// We are editing a clip marker
model = m_controller->getMarkerModel();
} else {
model = pCore->currentDoc()->getGuideModel();
}
QString newComment = root->property("markerText").toString();
bool found = false;
CommentedTime oldMarker = model->getMarker(m_timePos->gentime(), &found);
if (!found || newComment == oldMarker.comment()) {
// No change
return;
}
oldMarker.setComment(newComment);
model->addMarker(oldMarker.time(), oldMarker.comment(), oldMarker.markerType());
}
}
void Monitor::prepareAudioThumb(int channels, const QList &audioCache)
{
- m_glMonitor->setAudioThumb(channels, audioCache);
+ m_glMonitor->buildAudioThumb(channels, audioCache);
}
void Monitor::slotSwitchAudioMonitor()
{
if (!m_audioMeterWidget->isValid) {
KdenliveSettings::setMonitoraudio(0x01);
m_audioMeterWidget->setVisibility(false);
return;
}
int currentOverlay = KdenliveSettings::monitoraudio();
currentOverlay ^= m_id;
KdenliveSettings::setMonitoraudio(currentOverlay);
if ((KdenliveSettings::monitoraudio() & m_id) != 0) {
// We want to enable this audio monitor, so make monitor active
slotActivateMonitor();
}
displayAudioMonitor(isActive());
}
void Monitor::displayAudioMonitor(bool isActive)
{
bool enable = isActive && ((KdenliveSettings::monitoraudio() & m_id) != 0);
if (enable) {
connect(m_monitorManager, &MonitorManager::frameDisplayed, m_audioMeterWidget, &ScopeWidget::onNewFrame, Qt::UniqueConnection);
} else {
disconnect(m_monitorManager, &MonitorManager::frameDisplayed, m_audioMeterWidget, &ScopeWidget::onNewFrame);
}
m_audioMeterWidget->setVisibility((KdenliveSettings::monitoraudio() & m_id) != 0);
}
void Monitor::updateQmlDisplay(int currentOverlay)
{
m_glMonitor->rootObject()->setVisible((currentOverlay & 0x01) != 0);
m_glMonitor->rootObject()->setProperty("showMarkers", currentOverlay & 0x04);
m_glMonitor->rootObject()->setProperty("showFps", currentOverlay & 0x20);
m_glMonitor->rootObject()->setProperty("showTimecode", currentOverlay & 0x02);
m_glMonitor->rootObject()->setProperty("showAudiothumb", currentOverlay & 0x10);
}
void Monitor::clearDisplay()
{
m_glMonitor->clear();
}
void Monitor::panView(QPoint diff)
{
// Only pan if scrollbars are visible
if (m_horizontalScroll->isVisible()) {
m_horizontalScroll->setValue(m_horizontalScroll->value() + diff.x());
}
if (m_verticalScroll->isVisible()) {
m_verticalScroll->setValue(m_verticalScroll->value() + diff.y());
}
}
void Monitor::requestSeek(int pos)
{
m_glMonitor->seek(pos);
m_monitorManager->cleanMixer();
}
void Monitor::setProducer(std::shared_ptr producer, int pos)
{
m_glMonitor->setProducer(std::move(producer), isActive(), pos);
}
void Monitor::reconfigure()
{
m_glMonitor->reconfigure();
}
void Monitor::slotSeekPosition(int pos)
{
m_timePos->setValue(pos);
checkOverlay();
}
void Monitor::slotStart()
{
slotActivateMonitor();
m_glMonitor->switchPlay(false);
m_glMonitor->seek(0);
}
void Monitor::slotEnd()
{
slotActivateMonitor();
m_glMonitor->switchPlay(false);
if (m_id == Kdenlive::ClipMonitor) {
m_glMonitor->seek(m_glMonitor->duration());
} else {
m_glMonitor->seek(pCore->projectDuration() - 1);
}
}
void Monitor::addSnapPoint(int pos)
{
m_snaps->addPoint(pos);
}
void Monitor::removeSnapPoint(int pos)
{
m_snaps->removePoint(pos);
}
void Monitor::slotSetScreen(int screenIndex)
{
emit screenChanged(screenIndex);
}
void Monitor::slotZoomIn()
{
m_glMonitor->slotZoom(true);
}
void Monitor::slotZoomOut()
{
m_glMonitor->slotZoom(false);
}
void Monitor::setConsumerProperty(const QString &name, const QString &value)
{
m_glMonitor->setConsumerProperty(name, value);
}
void Monitor::purgeCache()
{
m_glMonitor->purgeCache();
}
diff --git a/src/monitor/monitorproxy.cpp b/src/monitor/monitorproxy.cpp
index 124778491..114aedc62 100644
--- a/src/monitor/monitorproxy.cpp
+++ b/src/monitor/monitorproxy.cpp
@@ -1,308 +1,311 @@
/***************************************************************************
* 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 (m_hasAV != hasAV) {
+ if (hasAV != m_hasAV) {
m_hasAV = hasAV;
emit clipHasAVChanged();
}
- if (clipName == clipName) {
+ if (clipName == m_clipName) {
m_clipName.clear();
emit clipNameChanged();
}
m_clipName = clipName;
emit clipNameChanged();
- m_clipType = type;
- emit clipTypeChanged();
+ if (type != m_clipType) {
+ m_clipType = type;
+ emit clipTypeChanged();
+ }
}