diff --git a/src/audiomixer/mixermanager.cpp b/src/audiomixer/mixermanager.cpp
index 9370c0985..65ddc457f 100644
--- a/src/audiomixer/mixermanager.cpp
+++ b/src/audiomixer/mixermanager.cpp
@@ -1,224 +1,215 @@
/***************************************************************************
* 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(this, &MixerManager::clearMixers, mixer.get(), &MixerWidget::clear);
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);
+ connect(this, &MixerManager::clearMixers, m_masterMixer.get(), &MixerWidget::clear);
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, bool channelsOnly)
{
m_connectedWidgets = doConnect;
for (auto item : m_mixers) {
item.second->connectMixer(m_connectedWidgets);
}
if (!channelsOnly && m_masterMixer != nullptr) {
m_masterMixer->connectMixer(m_connectedWidgets);
}
}
void MixerManager::collapseMixers()
{
if (KdenliveSettings::mixerCollapse()) {
m_expandedWidth = width();
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 5d5cf6012..c062dd49c 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, 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();
+ void clearMixers();
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/bin/projectclip.cpp b/src/bin/projectclip.cpp
index e736095a3..bd1e37f02 100644
--- a/src/bin/projectclip.cpp
+++ b/src/bin/projectclip.cpp
@@ -1,1437 +1,1439 @@
/*
Copyright (C) 2012 Till Theato
Copyright (C) 2014 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 "projectclip.h"
#include "bin.h"
#include "core.h"
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
#include "doc/kthumb.h"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "jobs/audiothumbjob.hpp"
#include "jobs/jobmanager.h"
#include "jobs/loadjob.hpp"
#include "jobs/thumbjob.hpp"
#include "jobs/cachejob.hpp"
#include "kdenlivesettings.h"
#include "lib/audio/audioStreamInfo.h"
#include "mltcontroller/clipcontroller.h"
#include "mltcontroller/clippropertiescontroller.h"
#include "model/markerlistmodel.hpp"
#include "profiles/profilemodel.hpp"
#include "project/projectcommands.h"
#include "project/projectmanager.h"
#include "projectfolder.h"
#include "projectitemmodel.h"
#include "projectsubclip.h"
#include "timecode.h"
#include "timeline2/model/snapmodel.hpp"
#include "utils/thumbnailcache.hpp"
#include "xml/xml.hpp"
#include
#include
#include
#include "kdenlive_debug.h"
#include "logger.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wfloat-equal"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wpedantic"
#include
#pragma GCC diagnostic pop
RTTR_REGISTRATION
{
using namespace rttr;
registration::class_("ProjectClip");
}
ProjectClip::ProjectClip(const QString &id, const QIcon &thumb, const std::shared_ptr &model, std::shared_ptr producer)
: AbstractProjectItem(AbstractProjectItem::ClipItem, id, model)
, ClipController(id, std::move(producer))
, m_thumbsProducer(nullptr)
{
m_markerModel = std::make_shared(id, pCore->projectManager()->undoStack());
m_clipStatus = StatusReady;
m_name = clipName();
m_duration = getStringDuration();
m_inPoint = 0;
m_outPoint = 0;
m_date = date;
m_description = ClipController::description();
if (m_clipType == ClipType::Audio) {
m_thumbnail = QIcon::fromTheme(QStringLiteral("audio-x-generic"));
} else {
m_thumbnail = thumb;
}
// Make sure we have a hash for this clip
hash();
connect(m_markerModel.get(), &MarkerListModel::modelChanged, [&]() { setProducerProperty(QStringLiteral("kdenlive:markers"), m_markerModel->toJson()); });
QString markers = getProducerProperty(QStringLiteral("kdenlive:markers"));
if (!markers.isEmpty()) {
QMetaObject::invokeMethod(m_markerModel.get(), "importFromJson", Qt::QueuedConnection, Q_ARG(const QString &, markers), Q_ARG(bool, true),
Q_ARG(bool, false));
}
connectEffectStack();
}
// static
std::shared_ptr ProjectClip::construct(const QString &id, const QIcon &thumb, const std::shared_ptr &model,
const std::shared_ptr &producer)
{
std::shared_ptr self(new ProjectClip(id, thumb, model, producer));
baseFinishConstruct(self);
QMetaObject::invokeMethod(model.get(), "loadSubClips", Qt::QueuedConnection, Q_ARG(const QString&, id), Q_ARG(const QString&, self->getProducerProperty(QStringLiteral("kdenlive:clipzones"))));
return self;
}
void ProjectClip::importEffects(const std::shared_ptr &producer)
{
m_effectStack->importEffects(producer, PlaylistState::Disabled, true);
}
ProjectClip::ProjectClip(const QString &id, const QDomElement &description, const QIcon &thumb, const std::shared_ptr &model)
: AbstractProjectItem(AbstractProjectItem::ClipItem, id, model)
, ClipController(id)
, m_thumbsProducer(nullptr)
{
m_clipStatus = StatusWaiting;
m_thumbnail = thumb;
m_markerModel = std::make_shared(m_binId, pCore->projectManager()->undoStack());
if (description.hasAttribute(QStringLiteral("type"))) {
m_clipType = (ClipType::ProducerType)description.attribute(QStringLiteral("type")).toInt();
if (m_clipType == ClipType::Audio) {
m_thumbnail = QIcon::fromTheme(QStringLiteral("audio-x-generic"));
}
}
m_temporaryUrl = getXmlProperty(description, QStringLiteral("resource"));
QString clipName = getXmlProperty(description, QStringLiteral("kdenlive:clipname"));
if (!clipName.isEmpty()) {
m_name = clipName;
} else if (!m_temporaryUrl.isEmpty()) {
m_name = QFileInfo(m_temporaryUrl).fileName();
} else {
m_name = i18n("Untitled");
}
connect(m_markerModel.get(), &MarkerListModel::modelChanged, [&]() { setProducerProperty(QStringLiteral("kdenlive:markers"), m_markerModel->toJson()); });
}
std::shared_ptr ProjectClip::construct(const QString &id, const QDomElement &description, const QIcon &thumb,
std::shared_ptr model)
{
std::shared_ptr self(new ProjectClip(id, description, thumb, std::move(model)));
baseFinishConstruct(self);
return self;
}
ProjectClip::~ProjectClip()
{
// controller is deleted in bincontroller
m_thumbMutex.lock();
m_requestedThumbs.clear();
m_thumbMutex.unlock();
m_thumbThread.waitForFinished();
audioFrameCache.clear();
}
void ProjectClip::connectEffectStack()
{
connect(m_effectStack.get(), &EffectStackModel::dataChanged, [&]() {
if (auto ptr = m_model.lock()) {
std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()),
AbstractProjectItem::IconOverlay);
}
});
}
QString ProjectClip::getToolTip() const
{
return url();
}
QString ProjectClip::getXmlProperty(const QDomElement &producer, const QString &propertyName, const QString &defaultValue)
{
QString value = defaultValue;
QDomNodeList props = producer.elementsByTagName(QStringLiteral("property"));
for (int i = 0; i < props.count(); ++i) {
if (props.at(i).toElement().attribute(QStringLiteral("name")) == propertyName) {
value = props.at(i).firstChild().nodeValue();
break;
}
}
return value;
}
void ProjectClip::updateAudioThumbnail(QList audioLevels)
{
audioFrameCache = audioLevels;
m_audioThumbCreated = true;
}
bool ProjectClip::audioThumbCreated() const
{
return (m_audioThumbCreated);
}
ClipType::ProducerType ProjectClip::clipType() const
{
return m_clipType;
}
bool ProjectClip::hasParent(const QString &id) const
{
std::shared_ptr par = parent();
while (par) {
if (par->clipId() == id) {
return true;
}
par = par->parent();
}
return false;
}
std::shared_ptr ProjectClip::clip(const QString &id)
{
if (id == m_binId) {
return std::static_pointer_cast(shared_from_this());
}
return std::shared_ptr();
}
std::shared_ptr ProjectClip::folder(const QString &id)
{
Q_UNUSED(id)
return std::shared_ptr();
}
std::shared_ptr ProjectClip::getSubClip(int in, int out)
{
for (int i = 0; i < childCount(); ++i) {
std::shared_ptr clip = std::static_pointer_cast(child(i))->subClip(in, out);
if (clip) {
return clip;
}
}
return std::shared_ptr();
}
QStringList ProjectClip::subClipIds() const
{
QStringList subIds;
for (int i = 0; i < childCount(); ++i) {
std::shared_ptr clip = std::static_pointer_cast(child(i));
if (clip) {
subIds << clip->clipId();
}
}
return subIds;
}
std::shared_ptr ProjectClip::clipAt(int ix)
{
if (ix == row()) {
return std::static_pointer_cast(shared_from_this());
}
return std::shared_ptr();
}
/*bool ProjectClip::isValid() const
{
return m_controller->isValid();
}*/
bool ProjectClip::hasUrl() const
{
if ((m_clipType != ClipType::Color) && (m_clipType != ClipType::Unknown)) {
return (!clipUrl().isEmpty());
}
return false;
}
const QString ProjectClip::url() const
{
return clipUrl();
}
GenTime ProjectClip::duration() const
{
return getPlaytime();
}
size_t ProjectClip::frameDuration() const
{
GenTime d = duration();
return (size_t)d.frames(pCore->getCurrentFps());
}
void ProjectClip::reloadProducer(bool refreshOnly, bool audioStreamChanged)
{
// we find if there are some loading job on that clip
int loadjobId = -1;
pCore->jobManager()->hasPendingJob(clipId(), AbstractClipJob::LOADJOB, &loadjobId);
QMutexLocker lock(&m_thumbMutex);
if (refreshOnly) {
// In that case, we only want a new thumbnail.
// We thus set up a thumb job. We must make sure that there is no pending LOADJOB
// Clear cache first
ThumbnailCache::get()->invalidateThumbsForClip(clipId());
pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::THUMBJOB);
m_thumbsProducer.reset();
pCore->jobManager()->startJob({clipId()}, loadjobId, QString(), 150, -1, true, true);
} else {
// If another load job is running?
if (loadjobId > -1) {
pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::LOADJOB);
}
if (QFile::exists(m_path) && !hasProxy()) {
clearBackupProperties();
}
QDomDocument doc;
QDomElement xml = toXml(doc);
if (!xml.isNull()) {
pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::THUMBJOB);
m_thumbsProducer.reset();
ClipType::ProducerType type = clipType();
if (type != ClipType::Color && type != ClipType::Image && type != ClipType::SlideShow) {
xml.removeAttribute("out");
}
ThumbnailCache::get()->invalidateThumbsForClip(clipId());
int loadJob = pCore->jobManager()->startJob({clipId()}, loadjobId, QString(), xml);
pCore->jobManager()->startJob({clipId()}, loadJob, QString(), 150, -1, true, true);
if (audioStreamChanged) {
discardAudioThumb();
pCore->jobManager()->startJob({clipId()}, loadjobId, QString());
}
}
}
}
QDomElement ProjectClip::toXml(QDomDocument &document, bool includeMeta, bool includeProfile)
{
getProducerXML(document, includeMeta, includeProfile);
QDomElement prod = document.documentElement().firstChildElement(QStringLiteral("producer"));
if (m_clipType != ClipType::Unknown) {
prod.setAttribute(QStringLiteral("type"), (int)m_clipType);
}
return prod;
}
void ProjectClip::setThumbnail(const QImage &img)
{
QPixmap thumb = roundedPixmap(QPixmap::fromImage(img));
if (hasProxy() && !thumb.isNull()) {
// Overlay proxy icon
QPainter p(&thumb);
QColor c(220, 220, 10, 200);
QRect r(0, 0, thumb.height() / 2.5, thumb.height() / 2.5);
p.fillRect(r, c);
QFont font = p.font();
font.setPixelSize(r.height());
font.setBold(true);
p.setFont(font);
p.setPen(Qt::black);
p.drawText(r, Qt::AlignCenter, i18nc("The first letter of Proxy, used as abbreviation", "P"));
}
m_thumbnail = QIcon(thumb);
if (auto ptr = m_model.lock()) {
std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()),
AbstractProjectItem::DataThumbnail);
}
}
bool ProjectClip::hasAudioAndVideo() const
{
return hasAudio() && hasVideo() && m_masterProducer->get_int("set.test_image") == 0 && m_masterProducer->get_int("set.test_audio") == 0;
}
bool ProjectClip::isCompatible(PlaylistState::ClipState state) const
{
switch (state) {
case PlaylistState::AudioOnly:
return hasAudio() && (m_masterProducer->get_int("set.test_audio") == 0);
case PlaylistState::VideoOnly:
return hasVideo() && (m_masterProducer->get_int("set.test_image") == 0);
default:
return true;
}
}
QPixmap ProjectClip::thumbnail(int width, int height)
{
return m_thumbnail.pixmap(width, height);
}
bool ProjectClip::setProducer(std::shared_ptr producer, bool replaceProducer)
{
Q_UNUSED(replaceProducer)
qDebug() << "################### ProjectClip::setproducer";
QMutexLocker locker(&m_producerMutex);
updateProducer(producer);
m_thumbsProducer.reset();
connectEffectStack();
// Update info
if (m_name.isEmpty()) {
m_name = clipName();
}
m_date = date;
m_description = ClipController::description();
m_temporaryUrl.clear();
if (m_clipType == ClipType::Audio) {
m_thumbnail = QIcon::fromTheme(QStringLiteral("audio-x-generic"));
} else if (m_clipType == ClipType::Image) {
if (producer->get_int("meta.media.width") < 8 || producer->get_int("meta.media.height") < 8) {
KMessageBox::information(QApplication::activeWindow(),
i18n("Image dimension smaller than 8 pixels.\nThis is not correctly supported by our video framework."));
}
}
m_duration = getStringDuration();
m_clipStatus = StatusReady;
if (!hasProxy()) {
if (auto ptr = m_model.lock()) emit std::static_pointer_cast(ptr)->refreshPanel(m_binId);
}
if (auto ptr = m_model.lock()) {
std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()),
AbstractProjectItem::DataDuration);
std::static_pointer_cast(ptr)->updateWatcher(std::static_pointer_cast(shared_from_this()));
}
// Make sure we have a hash for this clip
getFileHash();
// set parent again (some info need to be stored in producer)
updateParent(parentItem().lock());
if (pCore->currentDoc()->getDocumentProperty(QStringLiteral("enableproxy")).toInt() == 1) {
QList> clipList;
// automatic proxy generation enabled
if (m_clipType == ClipType::Image && pCore->currentDoc()->getDocumentProperty(QStringLiteral("generateimageproxy")).toInt() == 1) {
if (getProducerIntProperty(QStringLiteral("meta.media.width")) >= KdenliveSettings::proxyimageminsize() &&
getProducerProperty(QStringLiteral("kdenlive:proxy")) == QStringLiteral()) {
clipList << std::static_pointer_cast(shared_from_this());
}
} else if (pCore->currentDoc()->getDocumentProperty(QStringLiteral("generateproxy")).toInt() == 1 &&
(m_clipType == ClipType::AV || m_clipType == ClipType::Video) && getProducerProperty(QStringLiteral("kdenlive:proxy")) == QStringLiteral()) {
bool skipProducer = false;
if (pCore->currentDoc()->getDocumentProperty(QStringLiteral("enableexternalproxy")).toInt() == 1) {
QStringList externalParams = pCore->currentDoc()->getDocumentProperty(QStringLiteral("externalproxyparams")).split(QLatin1Char(';'));
// We have a camcorder profile, check if we have opened a proxy clip
if (externalParams.count() >= 6) {
QFileInfo info(m_path);
QDir dir = info.absoluteDir();
dir.cd(externalParams.at(3));
QString fileName = info.fileName();
if (!externalParams.at(2).isEmpty()) {
fileName.chop(externalParams.at(2).size());
}
fileName.append(externalParams.at(5));
if (dir.exists(fileName)) {
setProducerProperty(QStringLiteral("kdenlive:proxy"), m_path);
m_path = dir.absoluteFilePath(fileName);
setProducerProperty(QStringLiteral("kdenlive:originalurl"), m_path);
getFileHash();
skipProducer = true;
}
}
}
if (!skipProducer && getProducerIntProperty(QStringLiteral("meta.media.width")) >= KdenliveSettings::proxyminsize()) {
clipList << std::static_pointer_cast(shared_from_this());
}
}
if (!clipList.isEmpty()) {
pCore->currentDoc()->slotProxyCurrentItem(true, clipList, false);
}
}
pCore->bin()->reloadMonitorIfActive(clipId());
for (auto &p : m_audioProducers) {
m_effectStack->removeService(p.second);
}
for (auto &p : m_videoProducers) {
m_effectStack->removeService(p.second);
}
for (auto &p : m_timewarpProducers) {
m_effectStack->removeService(p.second);
}
// Release audio producers
m_audioProducers.clear();
m_videoProducers.clear();
m_timewarpProducers.clear();
emit refreshPropertiesPanel();
replaceInTimeline();
return true;
}
std::shared_ptr ProjectClip::thumbProducer()
{
if (m_thumbsProducer) {
return m_thumbsProducer;
}
if (clipType() == ClipType::Unknown) {
return nullptr;
}
QMutexLocker lock(&m_thumbMutex);
std::shared_ptr prod = originalProducer();
if (!prod->is_valid()) {
return nullptr;
}
if (KdenliveSettings::gpu_accel()) {
// TODO: when the original producer changes, we must reload this thumb producer
m_thumbsProducer = softClone(ClipController::getPassPropertiesList());
Mlt::Filter converter(*prod->profile(), "avcolor_space");
m_thumbsProducer->attach(converter);
} else {
QString mltService = m_masterProducer->get("mlt_service");
const QString mltResource = m_masterProducer->get("resource");
if (mltService == QLatin1String("avformat")) {
mltService = QStringLiteral("avformat-novalidate");
}
m_thumbsProducer.reset(new Mlt::Producer(*pCore->thumbProfile(), mltService.toUtf8().constData(), mltResource.toUtf8().constData()));
if (m_thumbsProducer->is_valid()) {
Mlt::Properties original(m_masterProducer->get_properties());
Mlt::Properties cloneProps(m_thumbsProducer->get_properties());
cloneProps.pass_list(original, ClipController::getPassPropertiesList());
Mlt::Filter scaler(*pCore->thumbProfile(), "swscale");
Mlt::Filter padder(*pCore->thumbProfile(), "resize");
Mlt::Filter converter(*pCore->thumbProfile(), "avcolor_space");
m_thumbsProducer->set("audio_index", -1);
// Required to make get_playtime() return > 1
m_thumbsProducer->set("out", m_thumbsProducer->get_length() -1);
m_thumbsProducer->attach(scaler);
m_thumbsProducer->attach(padder);
m_thumbsProducer->attach(converter);
}
}
return m_thumbsProducer;
}
void ProjectClip::createDisabledMasterProducer()
{
if (!m_disabledProducer) {
m_disabledProducer = cloneProducer();
m_disabledProducer->set("set.test_audio", 1);
m_disabledProducer->set("set.test_image", 1);
m_effectStack->addService(m_disabledProducer);
}
}
std::shared_ptr ProjectClip::getTimelineProducer(int trackId, int clipId, PlaylistState::ClipState state, double speed)
{
if (!m_masterProducer) {
return nullptr;
}
if (qFuzzyCompare(speed, 1.0)) {
// we are requesting a normal speed producer
if (trackId == -1 ||
(state == PlaylistState::VideoOnly && (m_clipType == ClipType::Color || m_clipType == ClipType::Image || m_clipType == ClipType::Text))) {
// Temporary copy, return clone of master
int duration = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
return std::shared_ptr(m_masterProducer->cut(-1, duration > 0 ? duration : -1));
}
if (m_timewarpProducers.count(clipId) > 0) {
m_effectStack->removeService(m_timewarpProducers[clipId]);
m_timewarpProducers.erase(clipId);
}
if (state == PlaylistState::AudioOnly) {
// We need to get an audio producer, if none exists
if (m_audioProducers.count(trackId) == 0) {
m_audioProducers[trackId] = cloneProducer(true);
m_audioProducers[trackId]->set("set.test_audio", 0);
m_audioProducers[trackId]->set("set.test_image", 1);
m_effectStack->addService(m_audioProducers[trackId]);
}
return std::shared_ptr(m_audioProducers[trackId]->cut());
}
if (m_audioProducers.count(trackId) > 0) {
m_effectStack->removeService(m_audioProducers[trackId]);
m_audioProducers.erase(trackId);
}
if (state == PlaylistState::VideoOnly) {
// we return the video producer
// We need to get an audio producer, if none exists
if (m_videoProducers.count(trackId) == 0) {
m_videoProducers[trackId] = cloneProducer(true);
m_videoProducers[trackId]->set("set.test_audio", 1);
m_videoProducers[trackId]->set("set.test_image", 0);
m_effectStack->addService(m_videoProducers[trackId]);
}
int duration = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
return std::shared_ptr(m_videoProducers[trackId]->cut(-1, duration > 0 ? duration : -1));
}
if (m_videoProducers.count(trackId) > 0) {
m_effectStack->removeService(m_videoProducers[trackId]);
m_videoProducers.erase(trackId);
}
Q_ASSERT(state == PlaylistState::Disabled);
createDisabledMasterProducer();
int duration = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
return std::shared_ptr(m_disabledProducer->cut(-1, duration > 0 ? duration : -1));
}
// For timewarp clips, we keep one separate producer for each clip.
std::shared_ptr warpProducer;
if (m_timewarpProducers.count(clipId) > 0) {
// remove in all cases, we add it unconditionally anyways
m_effectStack->removeService(m_timewarpProducers[clipId]);
if (qFuzzyCompare(m_timewarpProducers[clipId]->get_double("warp_speed"), speed)) {
// the producer we have is good, use it !
warpProducer = m_timewarpProducers[clipId];
qDebug() << "Reusing producer!";
} else {
m_timewarpProducers.erase(clipId);
}
}
if (!warpProducer) {
QString resource(originalProducer()->get("resource"));
if (resource.isEmpty() || resource == QLatin1String("")) {
resource = m_service;
}
QString url = QString("timewarp:%1:%2").arg(QString::fromStdString(std::to_string(speed))).arg(resource);
warpProducer.reset(new Mlt::Producer(*originalProducer()->profile(), url.toUtf8().constData()));
qDebug() << "new producer: " << url;
qDebug() << "warp LENGTH before" << warpProducer->get_length();
int original_length = originalProducer()->get_length();
// this is a workaround to cope with Mlt erroneous rounding
Mlt::Properties original(m_masterProducer->get_properties());
Mlt::Properties cloneProps(warpProducer->get_properties());
cloneProps.pass_list(original, ClipController::getPassPropertiesList(false));
warpProducer->set("length", (int) (original_length / std::abs(speed) + 0.5));
}
qDebug() << "warp LENGTH" << warpProducer->get_length();
warpProducer->set("set.test_audio", 1);
warpProducer->set("set.test_image", 1);
if (state == PlaylistState::AudioOnly) {
warpProducer->set("set.test_audio", 0);
}
if (state == PlaylistState::VideoOnly) {
warpProducer->set("set.test_image", 0);
}
m_timewarpProducers[clipId] = warpProducer;
m_effectStack->addService(m_timewarpProducers[clipId]);
return std::shared_ptr(warpProducer->cut());
}
std::pair, bool> ProjectClip::giveMasterAndGetTimelineProducer(int clipId, std::shared_ptr master,
PlaylistState::ClipState state)
{
int in = master->get_in();
int out = master->get_out();
if (master->parent().is_valid()) {
// in that case, we have a cut
// check whether it's a timewarp
double speed = 1.0;
bool timeWarp = false;
if (QString::fromUtf8(master->parent().get("mlt_service")) == QLatin1String("timewarp")) {
speed = master->parent().get_double("warp_speed");
timeWarp = true;
}
if (master->parent().get_int("_loaded") == 1) {
// we already have a clip that shares the same master
if (state != PlaylistState::Disabled || timeWarp) {
// In that case, we must create copies
std::shared_ptr prod(getTimelineProducer(-1, clipId, state, speed)->cut(in, out));
return {prod, false};
}
if (state == PlaylistState::Disabled) {
if (!m_disabledProducer) {
qDebug() << "Warning: weird, we found a disabled clip whose master is already loaded but we don't have any yet";
createDisabledMasterProducer();
}
return {std::shared_ptr(m_disabledProducer->cut(in, out)), false};
}
// We have a good id, this clip can be used
return {master, true};
} else {
master->parent().set("_loaded", 1);
if (timeWarp) {
m_timewarpProducers[clipId] = std::make_shared(&master->parent());
m_effectStack->loadService(m_timewarpProducers[clipId]);
return {master, true};
}
if (state == PlaylistState::AudioOnly) {
m_audioProducers[clipId] = std::make_shared(&master->parent());
m_effectStack->loadService(m_audioProducers[clipId]);
return {master, true};
}
if (state == PlaylistState::VideoOnly) {
// good, we found a master video producer, and we didn't have any
m_videoProducers[clipId] = std::make_shared(&master->parent());
m_effectStack->loadService(m_videoProducers[clipId]);
return {master, true};
}
if (state == PlaylistState::Disabled) {
if (!m_disabledProducer) {
createDisabledMasterProducer();
}
return {std::make_shared(m_disabledProducer->cut(master->get_in(), master->get_out())), true};
}
qDebug() << "Warning: weird, we found a clip whose master is not loaded but we already have a master";
Q_ASSERT(false);
}
} else if (master->is_valid()) {
// in that case, we have a master
qDebug() << "Warning: weird, we received a master clip in lieue of a cut";
double speed = 1.0;
if (QString::fromUtf8(master->parent().get("mlt_service")) == QLatin1String("timewarp")) {
speed = master->get_double("warp_speed");
}
return {getTimelineProducer(-1, clipId, state, speed), false};
}
// we have a problem
return {std::shared_ptr(ClipController::mediaUnavailable->cut()), false};
}
/*
std::shared_ptr ProjectClip::timelineProducer(PlaylistState::ClipState state, int track)
{
if (!m_service.startsWith(QLatin1String("avformat"))) {
std::shared_ptr prod(originalProducer()->cut());
int length = getProducerIntProperty(QStringLiteral("kdenlive:duration"));
if (length > 0) {
prod->set_in_and_out(0, length);
}
return prod;
}
if (state == PlaylistState::VideoOnly) {
if (m_timelineProducers.count(0) > 0) {
return std::shared_ptr(m_timelineProducers.find(0)->second->cut());
}
std::shared_ptr videoProd = cloneProducer();
videoProd->set("audio_index", -1);
m_timelineProducers[0] = videoProd;
return std::shared_ptr(videoProd->cut());
}
if (state == PlaylistState::AudioOnly) {
if (m_timelineProducers.count(-track) > 0) {
return std::shared_ptr(m_timelineProducers.find(-track)->second->cut());
}
std::shared_ptr audioProd = cloneProducer();
audioProd->set("video_index", -1);
m_timelineProducers[-track] = audioProd;
return std::shared_ptr(audioProd->cut());
}
if (m_timelineProducers.count(track) > 0) {
return std::shared_ptr(m_timelineProducers.find(track)->second->cut());
}
std::shared_ptr normalProd = cloneProducer();
m_timelineProducers[track] = normalProd;
return std::shared_ptr(normalProd->cut());
}*/
std::shared_ptr ProjectClip::cloneProducer(bool removeEffects)
{
Mlt::Consumer c(pCore->getCurrentProfile()->profile(), "xml", "string");
Mlt::Service s(m_masterProducer->get_service());
int ignore = s.get_int("ignore_points");
if (ignore) {
s.set("ignore_points", 0);
}
c.connect(s);
c.set("time_format", "frames");
c.set("no_meta", 1);
c.set("no_root", 1);
c.set("no_profile", 1);
c.set("root", "/");
c.set("store", "kdenlive");
c.run();
if (ignore) {
s.set("ignore_points", ignore);
}
const QByteArray clipXml = c.get("string");
std::shared_ptr prod;
prod.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), "xml-string", clipXml.constData()));
if (strcmp(prod->get("mlt_service"), "avformat") == 0) {
prod->set("mlt_service", "avformat-novalidate");
+ prod->set("mute_on_pause", 0);
}
// we pass some properties that wouldn't be passed because of the novalidate
const char *prefix = "meta.";
const size_t prefix_len = strlen(prefix);
for (int i = 0; i < m_masterProducer->count(); ++i) {
char *current = m_masterProducer->get_name(i);
if (strlen(current) >= prefix_len && strncmp(current, prefix, prefix_len) == 0) {
prod->set(current, m_masterProducer->get(i));
}
}
if (removeEffects) {
int ct = 0;
Mlt::Filter *filter = prod->filter(ct);
while (filter) {
qDebug() << "// EFFECT " << ct << " : " << filter->get("mlt_service");
QString ix = QString::fromLatin1(filter->get("kdenlive_id"));
if (!ix.isEmpty()) {
qDebug() << "/ + + DELTING";
if (prod->detach(*filter) == 0) {
} else {
ct++;
}
} else {
ct++;
}
delete filter;
filter = prod->filter(ct);
}
}
prod->set("id", (char *)nullptr);
return prod;
}
std::shared_ptr ProjectClip::cloneProducer(const std::shared_ptr &producer)
{
Mlt::Consumer c(*producer->profile(), "xml", "string");
Mlt::Service s(producer->get_service());
int ignore = s.get_int("ignore_points");
if (ignore) {
s.set("ignore_points", 0);
}
c.connect(s);
c.set("time_format", "frames");
c.set("no_meta", 1);
c.set("no_root", 1);
c.set("no_profile", 1);
c.set("root", "/");
c.set("store", "kdenlive");
c.start();
if (ignore) {
s.set("ignore_points", ignore);
}
const QByteArray clipXml = c.get("string");
std::shared_ptr prod(new Mlt::Producer(*producer->profile(), "xml-string", clipXml.constData()));
if (strcmp(prod->get("mlt_service"), "avformat") == 0) {
prod->set("mlt_service", "avformat-novalidate");
+ prod->set("mute_on_pause", 0);
}
return prod;
}
std::shared_ptr ProjectClip::softClone(const char *list)
{
QString service = QString::fromLatin1(m_masterProducer->get("mlt_service"));
QString resource = QString::fromLatin1(m_masterProducer->get("resource"));
std::shared_ptr clone(new Mlt::Producer(*m_masterProducer->profile(), service.toUtf8().constData(), resource.toUtf8().constData()));
Mlt::Properties original(m_masterProducer->get_properties());
Mlt::Properties cloneProps(clone->get_properties());
cloneProps.pass_list(original, list);
return clone;
}
bool ProjectClip::isReady() const
{
return m_clipStatus == StatusReady;
}
QPoint ProjectClip::zone() const
{
return ClipController::zone();
}
const QString ProjectClip::hash()
{
QString clipHash = getProducerProperty(QStringLiteral("kdenlive:file_hash"));
if (!clipHash.isEmpty()) {
return clipHash;
}
return getFileHash();
}
const QString ProjectClip::getFileHash()
{
QByteArray fileData;
QByteArray fileHash;
switch (m_clipType) {
case ClipType::SlideShow:
fileData = clipUrl().toUtf8();
fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
break;
case ClipType::Text:
case ClipType::TextTemplate:
fileData = getProducerProperty(QStringLiteral("xmldata")).toUtf8();
fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
break;
case ClipType::QText:
fileData = getProducerProperty(QStringLiteral("text")).toUtf8();
fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
break;
case ClipType::Color:
fileData = getProducerProperty(QStringLiteral("resource")).toUtf8();
fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
break;
default:
QFile file(clipUrl());
if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file
/*
* 1 MB = 1 second per 450 files (or faster)
* 10 MB = 9 seconds per 450 files (or faster)
*/
if (file.size() > 2000000) {
fileData = file.read(1000000);
if (file.seek(file.size() - 1000000)) {
fileData.append(file.readAll());
}
} else {
fileData = file.readAll();
}
file.close();
ClipController::setProducerProperty(QStringLiteral("kdenlive:file_size"), QString::number(file.size()));
fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
}
break;
}
if (fileHash.isEmpty()) {
qDebug() << "// WARNING EMPTY CLIP HASH: ";
return QString();
}
QString result = fileHash.toHex();
ClipController::setProducerProperty(QStringLiteral("kdenlive:file_hash"), result);
return result;
}
double ProjectClip::getOriginalFps() const
{
return originalFps();
}
bool ProjectClip::hasProxy() const
{
QString proxy = getProducerProperty(QStringLiteral("kdenlive:proxy"));
return proxy.size() > 2;
}
void ProjectClip::setProperties(const QMap &properties, bool refreshPanel)
{
qDebug() << "// SETTING CLIP PROPERTIES: " << properties;
QMapIterator i(properties);
QMap passProperties;
bool refreshAnalysis = false;
bool reload = false;
bool refreshOnly = true;
if (properties.contains(QStringLiteral("templatetext"))) {
m_description = properties.value(QStringLiteral("templatetext"));
if (auto ptr = m_model.lock())
std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()),
AbstractProjectItem::ClipStatus);
refreshPanel = true;
}
// Some properties also need to be passed to track producers
QStringList timelineProperties{
QStringLiteral("force_aspect_ratio"), QStringLiteral("set.force_full_luma"), QStringLiteral("full_luma"), QStringLiteral("threads"),
QStringLiteral("force_colorspace"), QStringLiteral("force_tff"), QStringLiteral("force_progressive"), QStringLiteral("video_delay")
};
QStringList forceReloadProperties{QStringLiteral("autorotate"), QStringLiteral("templatetext"), QStringLiteral("resource"),
QStringLiteral("force_fps"), QStringLiteral("set.test_image"), QStringLiteral("set.test_audio"),
QStringLiteral("audio_index"), QStringLiteral("video_index")};
QStringList keys{QStringLiteral("luma_duration"), QStringLiteral("luma_file"), QStringLiteral("fade"), QStringLiteral("ttl"),
QStringLiteral("softness"), QStringLiteral("crop"), QStringLiteral("animation")};
QVector updateRoles;
while (i.hasNext()) {
i.next();
setProducerProperty(i.key(), i.value());
if (m_clipType == ClipType::SlideShow && keys.contains(i.key())) {
reload = true;
refreshOnly = false;
}
if (i.key().startsWith(QLatin1String("kdenlive:clipanalysis"))) {
refreshAnalysis = true;
}
if (timelineProperties.contains(i.key())) {
passProperties.insert(i.key(), i.value());
}
}
if (properties.contains(QStringLiteral("kdenlive:proxy"))) {
QString value = properties.value(QStringLiteral("kdenlive:proxy"));
// If value is "-", that means user manually disabled proxy on this clip
if (value.isEmpty() || value == QLatin1String("-")) {
// reset proxy
int id;
if (pCore->jobManager()->hasPendingJob(clipId(), AbstractClipJob::PROXYJOB, &id)) {
// The proxy clip is being created, abort
pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::PROXYJOB);
} else {
reload = true;
refreshOnly = false;
}
} else {
// A proxy was requested, make sure to keep original url
setProducerProperty(QStringLiteral("kdenlive:originalurl"), url());
backupOriginalProperties();
pCore->jobManager()->startJob({clipId()}, -1, QString());
}
} else if (!reload) {
const QList propKeys = properties.keys();
for (const QString &k : propKeys) {
if (forceReloadProperties.contains(k)) {
if (m_clipType != ClipType::Color) {
reload = true;
refreshOnly = false;
} else {
// Clip resource changed, update thumbnail
reload = true;
refreshPanel = true;
updateRoles << TimelineModel::ResourceRole;
}
break;
}
}
}
if (!reload && (properties.contains(QStringLiteral("xmldata")) || !passProperties.isEmpty())) {
reload = true;
}
if (refreshAnalysis) {
emit refreshAnalysisPanel();
}
if (properties.contains(QStringLiteral("length")) || properties.contains(QStringLiteral("kdenlive:duration"))) {
m_duration = getStringDuration();
if (auto ptr = m_model.lock())
std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()),
AbstractProjectItem::DataDuration);
refreshOnly = false;
reload = true;
}
if (properties.contains(QStringLiteral("kdenlive:clipname"))) {
m_name = properties.value(QStringLiteral("kdenlive:clipname"));
refreshPanel = true;
if (auto ptr = m_model.lock()) {
std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()),
AbstractProjectItem::DataName);
}
// update timeline clips
updateTimelineClips(QVector() << TimelineModel::NameRole);
}
if (refreshPanel) {
// Some of the clip properties have changed through a command, update properties panel
emit refreshPropertiesPanel();
}
if (reload) {
// producer has changed, refresh monitor and thumbnail
if (hasProxy()) {
pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::PROXYJOB);
setProducerProperty(QStringLiteral("_overwriteproxy"), 1);
pCore->jobManager()->startJob({clipId()}, -1, QString());
} else {
reloadProducer(refreshOnly, properties.contains(QStringLiteral("audio_index")));
}
if (refreshOnly) {
if (auto ptr = m_model.lock()) {
emit std::static_pointer_cast(ptr)->refreshClip(m_binId);
}
}
if (!updateRoles.isEmpty()) {
updateTimelineClips(updateRoles);
}
}
if (!passProperties.isEmpty() && (!reload || refreshOnly)) {
for (auto &p : m_audioProducers) {
QMapIterator pr(passProperties);
while (pr.hasNext()) {
pr.next();
p.second->set(pr.key().toUtf8().constData(), pr.value().toUtf8().constData());
}
}
for (auto &p : m_videoProducers) {
QMapIterator pr(passProperties);
while (pr.hasNext()) {
pr.next();
p.second->set(pr.key().toUtf8().constData(), pr.value().toUtf8().constData());
}
}
for (auto &p : m_timewarpProducers) {
QMapIterator pr(passProperties);
while (pr.hasNext()) {
pr.next();
p.second->set(pr.key().toUtf8().constData(), pr.value().toUtf8().constData());
}
}
}
}
ClipPropertiesController *ProjectClip::buildProperties(QWidget *parent)
{
auto ptr = m_model.lock();
Q_ASSERT(ptr);
auto *panel = new ClipPropertiesController(static_cast(this), parent);
connect(this, &ProjectClip::refreshPropertiesPanel, panel, &ClipPropertiesController::slotReloadProperties);
connect(this, &ProjectClip::refreshAnalysisPanel, panel, &ClipPropertiesController::slotFillAnalysisData);
connect(panel, &ClipPropertiesController::requestProxy, [this](bool doProxy) {
QList> clipList{std::static_pointer_cast(shared_from_this())};
pCore->currentDoc()->slotProxyCurrentItem(doProxy, clipList);
});
connect(panel, &ClipPropertiesController::deleteProxy, this, &ProjectClip::deleteProxy);
return panel;
}
void ProjectClip::deleteProxy()
{
// Disable proxy file
QString proxy = getProducerProperty(QStringLiteral("kdenlive:proxy"));
QList> clipList{std::static_pointer_cast(shared_from_this())};
pCore->currentDoc()->slotProxyCurrentItem(false, clipList);
// Delete
bool ok;
QDir dir = pCore->currentDoc()->getCacheDir(CacheProxy, &ok);
if (ok && proxy.length() > 2) {
proxy = QFileInfo(proxy).fileName();
if (dir.exists(proxy)) {
dir.remove(proxy);
}
}
}
void ProjectClip::updateParent(std::shared_ptr parent)
{
if (parent) {
auto item = std::static_pointer_cast(parent);
ClipController::setProducerProperty(QStringLiteral("kdenlive:folderid"), item->clipId());
}
AbstractProjectItem::updateParent(parent);
}
bool ProjectClip::matches(const QString &condition)
{
// TODO
Q_UNUSED(condition)
return true;
}
bool ProjectClip::rename(const QString &name, int column)
{
QMap newProperites;
QMap oldProperites;
bool edited = false;
switch (column) {
case 0:
if (m_name == name) {
return false;
}
// Rename clip
oldProperites.insert(QStringLiteral("kdenlive:clipname"), m_name);
newProperites.insert(QStringLiteral("kdenlive:clipname"), name);
m_name = name;
edited = true;
break;
case 2:
if (m_description == name) {
return false;
}
// Rename clip
if (m_clipType == ClipType::TextTemplate) {
oldProperites.insert(QStringLiteral("templatetext"), m_description);
newProperites.insert(QStringLiteral("templatetext"), name);
} else {
oldProperites.insert(QStringLiteral("kdenlive:description"), m_description);
newProperites.insert(QStringLiteral("kdenlive:description"), name);
}
m_description = name;
edited = true;
break;
}
if (edited) {
pCore->bin()->slotEditClipCommand(m_binId, oldProperites, newProperites);
}
return edited;
}
QVariant ProjectClip::getData(DataType type) const
{
switch (type) {
case AbstractProjectItem::IconOverlay:
return m_effectStack && m_effectStack->rowCount() > 0 ? QVariant("kdenlive-track_has_effect") : QVariant();
default:
return AbstractProjectItem::getData(type);
}
}
int ProjectClip::audioChannels() const
{
if (!audioInfo()) {
return 0;
}
return audioInfo()->channels();
}
void ProjectClip::discardAudioThumb()
{
QString audioThumbPath = getAudioThumbPath();
if (!audioThumbPath.isEmpty()) {
QFile::remove(audioThumbPath);
}
audioFrameCache.clear();
qCDebug(KDENLIVE_LOG) << "//////////////////// DISCARD AUIIO THUMBNS";
m_audioThumbCreated = false;
refreshAudioInfo();
pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::AUDIOTHUMBJOB);
}
const QString ProjectClip::getAudioThumbPath(bool miniThumb)
{
if (audioInfo() == nullptr) {
return QString();
}
int audioStream = audioInfo()->ffmpeg_audio_index();
QString clipHash = hash();
if (clipHash.isEmpty()) {
return QString();
}
bool ok = false;
QDir thumbFolder = pCore->currentDoc()->getCacheDir(CacheAudio, &ok);
if (!ok) {
return QString();
}
QString audioPath = thumbFolder.absoluteFilePath(clipHash);
if (miniThumb) {
audioPath.append(QStringLiteral(".png"));
return audioPath;
}
if (audioStream > 0) {
audioPath.append(QLatin1Char('_') + QString::number(audioInfo()->audio_index()));
}
int roundedFps = (int)pCore->getCurrentFps();
audioPath.append(QStringLiteral("_%1_audio.png").arg(roundedFps));
return audioPath;
}
QStringList ProjectClip::updatedAnalysisData(const QString &name, const QString &data, int offset)
{
if (data.isEmpty()) {
// Remove data
return QStringList() << QString("kdenlive:clipanalysis." + name) << QString();
// m_controller->resetProperty("kdenlive:clipanalysis." + name);
}
QString current = getProducerProperty("kdenlive:clipanalysis." + name);
if (!current.isEmpty()) {
if (KMessageBox::questionYesNo(QApplication::activeWindow(), i18n("Clip already contains analysis data %1", name), QString(), KGuiItem(i18n("Merge")),
KGuiItem(i18n("Add"))) == KMessageBox::Yes) {
// Merge data
auto &profile = pCore->getCurrentProfile();
Mlt::Geometry geometry(current.toUtf8().data(), duration().frames(profile->fps()), profile->width(), profile->height());
Mlt::Geometry newGeometry(data.toUtf8().data(), duration().frames(profile->fps()), profile->width(), profile->height());
Mlt::GeometryItem item;
int pos = 0;
while (newGeometry.next_key(&item, pos) == 0) {
pos = item.frame();
item.frame(pos + offset);
pos++;
geometry.insert(item);
}
return QStringList() << QString("kdenlive:clipanalysis." + name) << geometry.serialise();
// m_controller->setProperty("kdenlive:clipanalysis." + name, geometry.serialise());
}
// Add data with another name
int i = 1;
QString previous = getProducerProperty("kdenlive:clipanalysis." + name + QString::number(i));
while (!previous.isEmpty()) {
++i;
previous = getProducerProperty("kdenlive:clipanalysis." + name + QString::number(i));
}
return QStringList() << QString("kdenlive:clipanalysis." + name + QString::number(i)) << geometryWithOffset(data, offset);
// m_controller->setProperty("kdenlive:clipanalysis." + name + QLatin1Char(' ') + QString::number(i), geometryWithOffset(data, offset));
}
return QStringList() << QString("kdenlive:clipanalysis." + name) << geometryWithOffset(data, offset);
// m_controller->setProperty("kdenlive:clipanalysis." + name, geometryWithOffset(data, offset));
}
QMap ProjectClip::analysisData(bool withPrefix)
{
return getPropertiesFromPrefix(QStringLiteral("kdenlive:clipanalysis."), withPrefix);
}
const QString ProjectClip::geometryWithOffset(const QString &data, int offset)
{
if (offset == 0) {
return data;
}
auto &profile = pCore->getCurrentProfile();
Mlt::Geometry geometry(data.toUtf8().data(), duration().frames(profile->fps()), profile->width(), profile->height());
Mlt::Geometry newgeometry(nullptr, duration().frames(profile->fps()), profile->width(), profile->height());
Mlt::GeometryItem item;
int pos = 0;
while (geometry.next_key(&item, pos) == 0) {
pos = item.frame();
item.frame(pos + offset);
pos++;
newgeometry.insert(item);
}
return newgeometry.serialise();
}
bool ProjectClip::isSplittable() const
{
return (m_clipType == ClipType::AV || m_clipType == ClipType::Playlist);
}
void ProjectClip::setBinEffectsEnabled(bool enabled)
{
ClipController::setBinEffectsEnabled(enabled);
}
void ProjectClip::registerService(std::weak_ptr timeline, int clipId, const std::shared_ptr &service, bool forceRegister)
{
if (!service->is_cut() || forceRegister) {
int hasAudio = service->get_int("set.test_audio") == 0;
int hasVideo = service->get_int("set.test_image") == 0;
if (hasVideo && m_videoProducers.count(clipId) == 0) {
// This is an undo producer, register it!
m_videoProducers[clipId] = service;
m_effectStack->addService(m_videoProducers[clipId]);
} else if (hasAudio && m_audioProducers.count(clipId) == 0) {
// This is an undo producer, register it!
m_audioProducers[clipId] = service;
m_effectStack->addService(m_audioProducers[clipId]);
}
}
registerTimelineClip(std::move(timeline), clipId);
}
void ProjectClip::registerTimelineClip(std::weak_ptr timeline, int clipId)
{
Q_ASSERT(m_registeredClips.count(clipId) == 0);
Q_ASSERT(!timeline.expired());
m_registeredClips[clipId] = std::move(timeline);
setRefCount((uint)m_registeredClips.size());
}
void ProjectClip::deregisterTimelineClip(int clipId)
{
qDebug() << " ** * DEREGISTERING TIMELINE CLIP: " << clipId;
Q_ASSERT(m_registeredClips.count(clipId) > 0);
m_registeredClips.erase(clipId);
if (m_videoProducers.count(clipId) > 0) {
m_effectStack->removeService(m_videoProducers[clipId]);
m_videoProducers.erase(clipId);
}
if (m_audioProducers.count(clipId) > 0) {
m_effectStack->removeService(m_audioProducers[clipId]);
m_audioProducers.erase(clipId);
}
setRefCount((uint)m_registeredClips.size());
}
QList ProjectClip::timelineInstances() const
{
QList ids;
for (const auto &m_registeredClip : m_registeredClips) {
ids.push_back(m_registeredClip.first);
}
return ids;
}
bool ProjectClip::selfSoftDelete(Fun &undo, Fun &redo)
{
auto toDelete = m_registeredClips; // we cannot use m_registeredClips directly, because it will be modified during loop
for (const auto &clip : toDelete) {
if (m_registeredClips.count(clip.first) == 0) {
// clip already deleted, was probably grouped with another one
continue;
}
if (auto timeline = clip.second.lock()) {
timeline->requestItemDeletion(clip.first, undo, redo);
} else {
qDebug() << "Error while deleting clip: timeline unavailable";
Q_ASSERT(false);
return false;
}
}
return AbstractProjectItem::selfSoftDelete(undo, redo);
}
bool ProjectClip::isIncludedInTimeline()
{
return m_registeredClips.size() > 0;
}
void ProjectClip::replaceInTimeline()
{
for (const auto &clip : m_registeredClips) {
if (auto timeline = clip.second.lock()) {
timeline->requestClipReload(clip.first);
} else {
qDebug() << "Error while reloading clip: timeline unavailable";
Q_ASSERT(false);
}
}
}
void ProjectClip::updateTimelineClips(const QVector &roles)
{
for (const auto &clip : m_registeredClips) {
if (auto timeline = clip.second.lock()) {
timeline->requestClipUpdate(clip.first, roles);
} else {
qDebug() << "Error while reloading clip thumb: timeline unavailable";
Q_ASSERT(false);
return;
}
}
}
void ProjectClip::updateZones()
{
int zonesCount = childCount();
if (zonesCount == 0) {
resetProducerProperty(QStringLiteral("kdenlive:clipzones"));
return;
}
QJsonArray list;
for (int i = 0; i < zonesCount; ++i) {
std::shared_ptr clip = std::static_pointer_cast(child(i));
if (clip) {
QJsonObject currentZone;
currentZone.insert(QLatin1String("name"), QJsonValue(clip->name()));
QPoint zone = clip->zone();
currentZone.insert(QLatin1String("in"), QJsonValue(zone.x()));
currentZone.insert(QLatin1String("out"), QJsonValue(zone.y()));
list.push_back(currentZone);
}
}
QJsonDocument json(list);
setProducerProperty(QStringLiteral("kdenlive:clipzones"), QString(json.toJson()));
}
void ProjectClip::getThumbFromPercent(int percent)
{
// extract a maximum of 50 frames for bin preview
percent += percent%2;
int duration = getFramePlaytime();
int framePos = duration * percent / 100;
if (ThumbnailCache::get()->hasThumbnail(m_binId, framePos)) {
setThumbnail(ThumbnailCache::get()->getThumbnail(m_binId, framePos));
} else {
// Generate percent thumbs
int id;
if (pCore->jobManager()->hasPendingJob(m_binId, AbstractClipJob::CACHEJOB, &id)) {
} else {
pCore->jobManager()->startJob({m_binId}, -1, QString(), 150, 50);
}
}
}
diff --git a/src/core.cpp b/src/core.cpp
index 38867b71c..26c5f4941 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -1,792 +1,792 @@
/*
Copyright (C) 2014 Till Theato
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 3 of the License, or
(at your option) any later version.
*/
#include "core.h"
#include "bin/bin.h"
#include "bin/projectitemmodel.h"
#include "capture/mediacapture.h"
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
#include "jobs/jobmanager.h"
#include "kdenlive_debug.h"
#include "kdenlivesettings.h"
#include "library/librarywidget.h"
#include "audiomixer/mixermanager.hpp"
#include "mainwindow.h"
#include "mltconnection.h"
#include "mltcontroller/clipcontroller.h"
#include "monitor/monitormanager.h"
#include "profiles/profilemodel.hpp"
#include "profiles/profilerepository.hpp"
#include "project/projectmanager.h"
#include "timeline2/model/timelineitemmodel.hpp"
#include "timeline2/view/timelinecontroller.h"
#include "timeline2/view/timelinewidget.h"
#include
#include
#include
#include
#include
#include
#ifdef Q_OS_MAC
#include
#endif
std::unique_ptr Core::m_self;
Core::Core()
: m_thumbProfile(nullptr)
, m_capture(new MediaCapture(this))
{
}
void Core::prepareShutdown()
{
m_guiConstructed = false;
QThreadPool::globalInstance()->clear();
}
Core::~Core()
{
qDebug() << "deleting core";
if (m_monitorManager) {
delete m_monitorManager;
}
// delete m_binWidget;
if (m_projectManager) {
delete m_projectManager;
}
ClipController::mediaUnavailable.reset();
}
void Core::build(bool isAppImage, const QString &MltPath)
{
if (m_self) {
return;
}
m_self.reset(new Core());
m_self->initLocale();
qRegisterMetaType("audioShortVector");
qRegisterMetaType>("QVector");
qRegisterMetaType("MessageType");
qRegisterMetaType("stringMap");
qRegisterMetaType("audioByteArray");
qRegisterMetaType>("QList");
qRegisterMetaType>("std::shared_ptr");
qRegisterMetaType>();
qRegisterMetaType("QDomElement");
qRegisterMetaType("requestClipInfo");
if (isAppImage) {
QString appPath = qApp->applicationDirPath();
KdenliveSettings::setFfmpegpath(QDir::cleanPath(appPath + QStringLiteral("/ffmpeg")));
KdenliveSettings::setFfplaypath(QDir::cleanPath(appPath + QStringLiteral("/ffplay")));
KdenliveSettings::setFfprobepath(QDir::cleanPath(appPath + QStringLiteral("/ffprobe")));
KdenliveSettings::setRendererpath(QDir::cleanPath(appPath + QStringLiteral("/melt")));
MltConnection::construct(QDir::cleanPath(appPath + QStringLiteral("/../share/mlt/profiles")));
} else {
// Open connection with Mlt
MltConnection::construct(MltPath);
}
// load the profile from disk
ProfileRepository::get()->refresh();
// load default profile
m_self->m_profile = KdenliveSettings::default_profile();
if (m_self->m_profile.isEmpty()) {
m_self->m_profile = ProjectManager::getDefaultProjectFormat();
KdenliveSettings::setDefault_profile(m_self->m_profile);
}
// Init producer shown for unavailable media
// TODO make it a more proper image, it currently causes a crash on exit
ClipController::mediaUnavailable = std::make_shared(ProfileRepository::get()->getProfile(m_self->m_profile)->profile(), "color:blue");
ClipController::mediaUnavailable->set("length", 99999999);
m_self->m_projectItemModel = ProjectItemModel::construct();
// Job manager must be created before bin to correctly connect
m_self->m_jobManager.reset(new JobManager(m_self.get()));
}
void Core::initGUI(const QUrl &Url)
{
m_guiConstructed = true;
m_profile = KdenliveSettings::default_profile();
m_currentProfile = m_profile;
profileChanged();
m_mainWindow = new MainWindow();
connect(this, &Core::showConfigDialog, m_mainWindow, &MainWindow::slotPreferences);
// load default profile and ask user to select one if not found.
if (m_profile.isEmpty()) {
m_profile = ProjectManager::getDefaultProjectFormat();
profileChanged();
KdenliveSettings::setDefault_profile(m_profile);
}
if (!ProfileRepository::get()->profileExists(m_profile)) {
KMessageBox::sorry(m_mainWindow, i18n("The default profile of Kdenlive is not set or invalid, press OK to set it to a correct value."));
// TODO this simple widget should be improved and probably use profileWidget
// we get the list of profiles
QVector> all_profiles = ProfileRepository::get()->getAllProfiles();
QStringList all_descriptions;
for (const auto &profile : all_profiles) {
all_descriptions << profile.first;
}
// ask the user
bool ok;
QString item = QInputDialog::getItem(m_mainWindow, i18n("Select Default Profile"), i18n("Profile:"), all_descriptions, 0, false, &ok);
if (ok) {
ok = false;
for (const auto &profile : all_profiles) {
if (profile.first == item) {
m_profile = profile.second;
ok = true;
}
}
}
if (!ok) {
KMessageBox::error(
m_mainWindow,
i18n("The given profile is invalid. We default to the profile \"dv_pal\", but you can change this from Kdenlive's settings panel"));
m_profile = QStringLiteral("dv_pal");
}
KdenliveSettings::setDefault_profile(m_profile);
profileChanged();
}
m_projectManager = new ProjectManager(this);
m_binWidget = new Bin(m_projectItemModel, m_mainWindow);
m_library = new LibraryWidget(m_projectManager, m_mainWindow);
m_mixerWidget = new MixerManager(m_mainWindow);
connect(m_library, SIGNAL(addProjectClips(QList)), m_binWidget, SLOT(droppedUrls(QList)));
connect(this, &Core::updateLibraryPath, m_library, &LibraryWidget::slotUpdateLibraryPath);
connect(m_capture.get(), &MediaCapture::recordStateChanged, m_mixerWidget, &MixerManager::recordStateChanged);
m_monitorManager = new MonitorManager(this);
- connect(m_monitorManager, &MonitorManager::cleanMixer, m_mixerWidget, &MixerManager::resetAudioValues);
+ connect(m_monitorManager, &MonitorManager::cleanMixer, m_mixerWidget, &MixerManager::clearMixers);
// Producer queue, creating MLT::Producers on request
/*
m_producerQueue = new ProducerQueue(m_binController);
connect(m_producerQueue, &ProducerQueue::gotFileProperties, m_binWidget, &Bin::slotProducerReady);
connect(m_producerQueue, &ProducerQueue::replyGetImage, m_binWidget, &Bin::slotThumbnailReady);
connect(m_producerQueue, &ProducerQueue::requestProxy,
[this](const QString &id){ m_binWidget->startJob(id, AbstractClipJob::PROXYJOB);});
connect(m_producerQueue, &ProducerQueue::removeInvalidClip, m_binWidget, &Bin::slotRemoveInvalidClip, Qt::DirectConnection);
connect(m_producerQueue, SIGNAL(addClip(QString, QMap)), m_binWidget, SLOT(slotAddUrl(QString, QMap)));
connect(m_binController.get(), SIGNAL(createThumb(QDomElement, QString, int)), m_producerQueue, SLOT(getFileProperties(QDomElement, QString, int)));
connect(m_binWidget, &Bin::producerReady, m_producerQueue, &ProducerQueue::slotProcessingDone, Qt::DirectConnection);
// TODO
connect(m_producerQueue, SIGNAL(removeInvalidProxy(QString,bool)), m_binWidget, SLOT(slotRemoveInvalidProxy(QString,bool)));*/
m_mainWindow->init();
projectManager()->init(Url, QString());
if (qApp->isSessionRestored()) {
// NOTE: we are restoring only one window, because Kdenlive only uses one MainWindow
m_mainWindow->restore(1, false);
}
QMetaObject::invokeMethod(pCore->projectManager(), "slotLoadOnOpen", Qt::QueuedConnection);
m_mainWindow->show();
QThreadPool::globalInstance()->setMaxThreadCount(qMin(4, QThreadPool::globalInstance()->maxThreadCount()));
}
std::unique_ptr &Core::self()
{
if (!m_self) {
qDebug() << "Error : Core has not been created";
}
return m_self;
}
MainWindow *Core::window()
{
return m_mainWindow;
}
ProjectManager *Core::projectManager()
{
return m_projectManager;
}
MonitorManager *Core::monitorManager()
{
return m_monitorManager;
}
Monitor *Core::getMonitor(int id)
{
if (id == Kdenlive::ClipMonitor) {
return m_monitorManager->clipMonitor();
}
return m_monitorManager->projectMonitor();
}
Bin *Core::bin()
{
return m_binWidget;
}
void Core::selectBinClip(const QString &clipId, int frame, const QPoint &zone)
{
m_binWidget->selectClipById(clipId, frame, zone);
}
std::shared_ptr Core::jobManager()
{
return m_jobManager;
}
LibraryWidget *Core::library()
{
return m_library;
}
MixerManager *Core::mixer()
{
return m_mixerWidget;
}
void Core::initLocale()
{
QLocale systemLocale = QLocale();
#ifndef Q_OS_MAC
setlocale(LC_NUMERIC, nullptr);
#else
setlocale(LC_NUMERIC_MASK, nullptr);
#endif
// localeconv()->decimal_point does not give reliable results on Windows
#ifndef Q_OS_WIN
char *separator = localeconv()->decimal_point;
if (QString::fromUtf8(separator) != QChar(systemLocale.decimalPoint())) {
// qCDebug(KDENLIVE_LOG)<<"------\n!!! system locale is not similar to Qt's locale... be prepared for bugs!!!\n------";
// HACK: There is a locale conflict, so set locale to C
// Make sure to override exported values or it won't work
qputenv("LANG", "C");
#ifndef Q_OS_MAC
setlocale(LC_NUMERIC, "C");
#else
setlocale(LC_NUMERIC_MASK, "C");
#endif
systemLocale = QLocale::c();
}
#endif
systemLocale.setNumberOptions(QLocale::OmitGroupSeparator);
QLocale::setDefault(systemLocale);
}
std::unique_ptr &Core::getMltRepository()
{
return MltConnection::self()->getMltRepository();
}
std::unique_ptr &Core::getCurrentProfile() const
{
return ProfileRepository::get()->getProfile(m_currentProfile);
}
const QString &Core::getCurrentProfilePath() const
{
return m_currentProfile;
}
bool Core::setCurrentProfile(const QString &profilePath)
{
if (m_currentProfile == profilePath) {
// no change required
return true;
}
if (ProfileRepository::get()->profileExists(profilePath)) {
m_currentProfile = profilePath;
m_thumbProfile.reset();
// inform render widget
profileChanged();
m_mainWindow->updateRenderWidgetProfile();
if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
m_mainWindow->getCurrentTimeline()->controller()->getModel()->updateProfile(&getCurrentProfile()->profile());
checkProfileValidity();
}
return true;
}
return false;
}
void Core::checkProfileValidity()
{
int offset = (getCurrentProfile()->profile().width() % 8) + (getCurrentProfile()->profile().height() % 2);
if (offset > 0) {
// Profile is broken, warn user
if (m_binWidget) {
m_binWidget->displayBinMessage(i18n("Your project profile is invalid, rendering might fail."), KMessageWidget::Warning);
}
}
}
double Core::getCurrentSar() const
{
return getCurrentProfile()->sar();
}
double Core::getCurrentDar() const
{
return getCurrentProfile()->dar();
}
double Core::getCurrentFps() const
{
return getCurrentProfile()->fps();
}
QSize Core::getCurrentFrameDisplaySize() const
{
return {(int)(getCurrentProfile()->height() * getCurrentDar() + 0.5), getCurrentProfile()->height()};
}
QSize Core::getCurrentFrameSize() const
{
return {getCurrentProfile()->width(), getCurrentProfile()->height()};
}
void Core::requestMonitorRefresh()
{
if (!m_guiConstructed) return;
m_monitorManager->refreshProjectMonitor();
}
void Core::refreshProjectRange(QSize range)
{
if (!m_guiConstructed) return;
m_monitorManager->refreshProjectRange(range);
}
int Core::getItemPosition(const ObjectId &id)
{
if (!m_guiConstructed) return 0;
switch (id.first) {
case ObjectType::TimelineClip:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPosition(id.second);
}
break;
case ObjectType::TimelineComposition:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPosition(id.second);
}
break;
case ObjectType::BinClip:
case ObjectType::TimelineTrack:
case ObjectType::Master:
return 0;
break;
default:
qDebug() << "ERROR: unhandled object type";
}
return 0;
}
int Core::getItemIn(const ObjectId &id)
{
if (!m_guiConstructed || !m_mainWindow->getCurrentTimeline() || !m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
qDebug() << "/ / // QUERYING ITEM IN BUT GUI NOT BUILD!!";
return 0;
}
switch (id.first) {
case ObjectType::TimelineClip:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipIn(id.second);
} else {
qDebug()<<"// ERROR QUERYING NON CLIP PROPERTIES\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!";
}
break;
case ObjectType::TimelineComposition:
case ObjectType::BinClip:
case ObjectType::TimelineTrack:
case ObjectType::Master:
return 0;
break;
default:
qDebug() << "ERROR: unhandled object type";
}
return 0;
}
PlaylistState::ClipState Core::getItemState(const ObjectId &id)
{
if (!m_guiConstructed) return PlaylistState::Disabled;
switch (id.first) {
case ObjectType::TimelineClip:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipState(id.second);
}
break;
case ObjectType::TimelineComposition:
return PlaylistState::VideoOnly;
break;
case ObjectType::BinClip:
return m_binWidget->getClipState(id.second);
break;
case ObjectType::TimelineTrack:
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->isAudioTrack(id.second) ? PlaylistState::AudioOnly : PlaylistState::VideoOnly;
case ObjectType::Master:
return PlaylistState::Disabled;
break;
default:
qDebug() << "ERROR: unhandled object type";
break;
}
return PlaylistState::Disabled;
}
int Core::getItemDuration(const ObjectId &id)
{
if (!m_guiConstructed) return 0;
switch (id.first) {
case ObjectType::TimelineClip:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPlaytime(id.second);
}
break;
case ObjectType::TimelineComposition:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPlaytime(id.second);
}
break;
case ObjectType::BinClip:
return (int)m_binWidget->getClipDuration(id.second);
break;
case ObjectType::TimelineTrack:
case ObjectType::Master:
return m_mainWindow->getCurrentTimeline()->controller()->duration();
default:
qDebug() << "ERROR: unhandled object type";
}
return 0;
}
int Core::getItemTrack(const ObjectId &id)
{
if (!m_guiConstructed) return 0;
switch (id.first) {
case ObjectType::TimelineClip:
case ObjectType::TimelineComposition:
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getItemTrackId(id.second);
break;
default:
qDebug() << "ERROR: unhandled object type";
}
return 0;
}
void Core::refreshProjectItem(const ObjectId &id)
{
if (!m_guiConstructed || m_mainWindow->getCurrentTimeline()->loading) return;
switch (id.first) {
case ObjectType::TimelineClip:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
}
break;
case ObjectType::TimelineComposition:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
}
break;
case ObjectType::TimelineTrack:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isTrack(id.second)) {
requestMonitorRefresh();
}
break;
case ObjectType::BinClip:
m_monitorManager->refreshClipMonitor();
break;
case ObjectType::Master:
requestMonitorRefresh();
break;
default:
qDebug() << "ERROR: unhandled object type";
}
}
bool Core::hasTimelinePreview() const
{
if (!m_guiConstructed) {
return false;
}
return m_mainWindow->getCurrentTimeline()->controller()->renderedChunks().size() > 0;
}
KdenliveDoc *Core::currentDoc()
{
return m_projectManager->current();
}
int Core::projectDuration() const
{
if (!m_guiConstructed) {
return 0;
}
return m_mainWindow->getCurrentTimeline()->controller()->duration();
}
void Core::profileChanged()
{
GenTime::setFps(getCurrentFps());
}
void Core::pushUndo(const Fun &undo, const Fun &redo, const QString &text)
{
undoStack()->push(new FunctionalUndoCommand(undo, redo, text));
}
void Core::pushUndo(QUndoCommand *command)
{
undoStack()->push(command);
}
void Core::displayMessage(const QString &message, MessageType type, int timeout)
{
if (m_mainWindow) {
m_mainWindow->displayMessage(message, type, timeout);
} else {
qDebug() << message;
}
}
void Core::displayBinMessage(const QString &text, int type, const QList &actions)
{
m_binWidget->doDisplayMessage(text, (KMessageWidget::MessageType)type, actions);
}
void Core::displayBinLogMessage(const QString &text, int type, const QString &logInfo)
{
m_binWidget->doDisplayMessage(text, (KMessageWidget::MessageType)type, logInfo);
}
void Core::clearAssetPanel(int itemId)
{
if (m_guiConstructed) m_mainWindow->clearAssetPanel(itemId);
}
std::shared_ptr Core::getItemEffectStack(int itemType, int itemId)
{
if (!m_guiConstructed) return nullptr;
switch (itemType) {
case (int)ObjectType::TimelineClip:
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipEffectStack(itemId);
case (int)ObjectType::TimelineTrack:
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getTrackEffectStackModel(itemId);
break;
case (int)ObjectType::BinClip:
return m_binWidget->getClipEffectStack(itemId);
case (int)ObjectType::Master:
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getMasterEffectStackModel();
default:
return nullptr;
}
}
std::shared_ptr Core::undoStack()
{
return projectManager()->undoStack();
}
QMap Core::getTrackNames(bool videoOnly)
{
if (!m_guiConstructed) return QMap();
return m_mainWindow->getCurrentTimeline()->controller()->getTrackNames(videoOnly);
}
QPair Core::getCompositionATrack(int cid) const
{
if (!m_guiConstructed) return {};
return m_mainWindow->getCurrentTimeline()->controller()->getCompositionATrack(cid);
}
bool Core::compositionAutoTrack(int cid) const
{
return m_mainWindow->getCurrentTimeline()->controller()->compositionAutoTrack(cid);
}
void Core::setCompositionATrack(int cid, int aTrack)
{
if (!m_guiConstructed) return;
m_mainWindow->getCurrentTimeline()->controller()->setCompositionATrack(cid, aTrack);
}
std::shared_ptr Core::projectItemModel()
{
return m_projectItemModel;
}
void Core::invalidateRange(QSize range)
{
if (!m_mainWindow || m_mainWindow->getCurrentTimeline()->loading) return;
m_mainWindow->getCurrentTimeline()->controller()->invalidateZone(range.width(), range.height());
}
void Core::invalidateItem(ObjectId itemId)
{
if (!m_mainWindow || !m_mainWindow->getCurrentTimeline() || m_mainWindow->getCurrentTimeline()->loading) return;
switch (itemId.first) {
case ObjectType::TimelineClip:
case ObjectType::TimelineComposition:
m_mainWindow->getCurrentTimeline()->controller()->invalidateItem(itemId.second);
break;
case ObjectType::TimelineTrack:
m_mainWindow->getCurrentTimeline()->controller()->invalidateTrack(itemId.second);
break;
case ObjectType::BinClip:
m_binWidget->invalidateClip(QString::number(itemId.second));
break;
case ObjectType::Master:
m_mainWindow->getCurrentTimeline()->controller()->invalidateZone(0, -1);
break;
default:
// compositions should not have effects
break;
}
}
double Core::getClipSpeed(int id) const
{
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipSpeed(id);
}
void Core::updateItemKeyframes(ObjectId id)
{
if (id.first == ObjectType::TimelineClip && m_mainWindow) {
m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {TimelineModel::KeyframesRole});
}
}
void Core::updateItemModel(ObjectId id, const QString &service)
{
if (m_mainWindow && id.first == ObjectType::TimelineClip && !m_mainWindow->getCurrentTimeline()->loading && service.startsWith(QLatin1String("fade"))) {
bool startFade = service == QLatin1String("fadein") || service == QLatin1String("fade_from_black");
m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {startFade ? TimelineModel::FadeInRole : TimelineModel::FadeOutRole});
}
}
void Core::showClipKeyframes(ObjectId id, bool enable)
{
if (id.first == ObjectType::TimelineClip) {
m_mainWindow->getCurrentTimeline()->controller()->showClipKeyframes(id.second, enable);
} else if (id.first == ObjectType::TimelineComposition) {
m_mainWindow->getCurrentTimeline()->controller()->showCompositionKeyframes(id.second, enable);
}
}
Mlt::Profile *Core::thumbProfile()
{
QMutexLocker lck(&m_thumbProfileMutex);
if (!m_thumbProfile) {
m_thumbProfile = std::make_unique(m_currentProfile.toStdString().c_str());
m_thumbProfile->set_height(144);
int width = 144 * m_thumbProfile->dar() + 0.5;
if (width % 8 > 0) {
width += 8 - width % 8;
}
m_thumbProfile->set_width(width);
}
return m_thumbProfile.get();
}
int Core::getTimelinePosition() const
{
if (m_mainWindow && m_guiConstructed) {
return m_mainWindow->getCurrentTimeline()->controller()->timelinePosition();
}
return 0;
}
void Core::triggerAction(const QString &name)
{
QAction *action = m_mainWindow->actionCollection()->action(name);
if (action) {
action->trigger();
}
}
void Core::clean()
{
m_self.reset();
}
void Core::startMediaCapture(int tid, bool checkAudio, bool checkVideo)
{
if (checkAudio && checkVideo) {
m_capture->recordVideo(tid, true);
} else if (checkAudio) {
m_capture->recordAudio(tid, true);
}
m_mediaCaptureFile = m_capture->getCaptureOutputLocation();
}
void Core::stopMediaCapture(int tid, bool checkAudio, bool checkVideo)
{
if (checkAudio && checkVideo) {
m_capture->recordVideo(tid, false);
} else if (checkAudio) {
m_capture->recordAudio(tid, false);
}
}
QStringList Core::getAudioCaptureDevices()
{
return m_capture->getAudioCaptureDevices();
}
int Core::getMediaCaptureState()
{
return m_capture->getState();
}
bool Core::isMediaCapturing()
{
return m_capture->isRecording();
}
MediaCapture *Core::getAudioDevice()
{
return m_capture.get();
}
QString Core::getProjectFolderName()
{
if (currentDoc()) {
return currentDoc()->projectDataFolder() + QDir::separator();
}
return QString();
}
QString Core::getTimelineClipBinId(int cid)
{
if (!m_guiConstructed) {
return QString();
}
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(cid)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipBinId(cid);
}
return QString();
}
int Core::getDurationFromString(const QString &time)
{
if (!m_guiConstructed) {
return 0;
}
const QString duration = currentDoc()->timecode().reformatSeparators(time);
return currentDoc()->timecode().getFrameCount(duration);
}
diff --git a/src/main.cpp b/src/main.cpp
index 8dcf1af62..c0dd8f329 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,256 +1,267 @@
/***************************************************************************
* Copyright (C) 2007 by Marco Gittler (g.marco@freenet.de) *
* Copyright (C) 2008 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 "core.h"
#include "dialogs/splash.hpp"
#include "logger.hpp"
#include
#include
#include "kxmlgui_version.h"
#include
#include
#ifdef USE_DRMINGW
#include
#elif defined(KF5_USE_CRASH)
#include
#endif
#include
#include
#include "definitions.h"
#include "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //new
#include
+#ifdef Q_OS_WIN
+extern "C"
+{
+ // Inform the driver we could make use of the discrete gpu
+ __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
+ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
+}
+#endif
+
+
int main(int argc, char *argv[])
{
#ifdef USE_DRMINGW
ExcHndlInit();
#endif
// Force QDomDocument to use a deterministic XML attribute order
qSetGlobalQHashSeed(0);
Logger::init();
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+ //TODO: is it a good option ?
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true);
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
QCoreApplication::setAttribute(Qt::AA_X11InitThreads);
#elif defined(Q_OS_WIN)
KSharedConfigPtr configWin = KSharedConfig::openConfig("kdenliverc");
KConfigGroup grp1(configWin, "misc");
if (grp1.exists()) {
int glMode = grp1.readEntry("opengl_backend", 0);
if (glMode > 0) {
QCoreApplication::setAttribute((Qt::ApplicationAttribute)glMode, true);
}
}
#endif
QApplication app(argc, argv);
app.setApplicationName(QStringLiteral("kdenlive"));
app.setOrganizationDomain(QStringLiteral("kde.org"));
app.setWindowIcon(QIcon(QStringLiteral(":/pics/kdenlive.png")));
KLocalizedString::setApplicationDomain("kdenlive");
#ifdef Q_OS_WIN
qputenv("KDE_FORK_SLAVES", "1");
QString path = qApp->applicationDirPath() + QLatin1Char(';') + qgetenv("PATH");
qputenv("PATH", path.toUtf8().constData());
const QStringList themes {"/icons/breeze/breeze-icons.rcc", "/icons/breeze-dark/breeze-icons-dark.rcc"};
for(const QString theme : themes ) {
const QString themePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, theme);
if (!themePath.isEmpty()) {
const QString iconSubdir = theme.left(theme.lastIndexOf('/'));
if (QResource::registerResource(themePath, iconSubdir)) {
if (QFileInfo::exists(QLatin1Char(':') + iconSubdir + QStringLiteral("/index.theme"))) {
qDebug() << "Loaded icon theme:" << theme;
} else {
qWarning() << "No index.theme found in" << theme;
QResource::unregisterResource(themePath, iconSubdir);
}
} else {
qWarning() << "Invalid rcc file" << theme;
}
}
}
#endif
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup grp(config, "unmanaged");
if (!grp.exists()) {
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
if (env.contains(QStringLiteral("XDG_CURRENT_DESKTOP")) && env.value(QStringLiteral("XDG_CURRENT_DESKTOP")).toLower() == QLatin1String("kde")) {
qCDebug(KDENLIVE_LOG) << "KDE Desktop detected, using system icons";
} else {
// We are not on a KDE desktop, force breeze icon theme
// Check if breeze theme is available
QStringList iconThemes = KIconTheme::list();
if (iconThemes.contains(QStringLiteral("breeze"))) {
grp.writeEntry("force_breeze", true);
grp.writeEntry("use_dark_breeze", true);
qCDebug(KDENLIVE_LOG) << "Non KDE Desktop detected, forcing Breeze icon theme";
}
}
// Set breeze dark as default on first opening
KConfigGroup cg(config, "UiSettings");
cg.writeEntry("ColorScheme", "Breeze Dark");
}
// Init DBus services
KDBusService programDBusService;
bool forceBreeze = grp.readEntry("force_breeze", QVariant(false)).toBool();
if (forceBreeze) {
bool darkBreeze = grp.readEntry("use_dark_breeze", QVariant(false)).toBool();
QIcon::setThemeName(darkBreeze ? QStringLiteral("breeze-dark") : QStringLiteral("breeze"));
}
// Create KAboutData
KAboutData aboutData(QByteArray("kdenlive"), i18n("Kdenlive"), KDENLIVE_VERSION, i18n("An open source video editor."), KAboutLicense::GPL,
i18n("Copyright © 2007–2019 Kdenlive authors"), i18n("Please report bugs to http://bugs.kde.org"),
QStringLiteral("https://kdenlive.org"));
aboutData.addAuthor(i18n("Jean-Baptiste Mardelle"), i18n("MLT and KDE SC 4 / KF5 port, main developer and maintainer"), QStringLiteral("jb@kdenlive.org"));
aboutData.addAuthor(i18n("Nicolas Carion"), i18n("Code re-architecture & timeline rewrite"), QStringLiteral("french.ebook.lover@gmail.com"));
aboutData.addAuthor(i18n("Vincent Pinon"), i18n("KF5 port, Windows cross-build, bugs fixing"), QStringLiteral("vpinon@kde.org"));
aboutData.addAuthor(i18n("Laurent Montel"), i18n("Bugs fixing, clean up code, optimization etc."), QStringLiteral("montel@kde.org"));
aboutData.addAuthor(i18n("Till Theato"), i18n("Bug fixing, etc."), QStringLiteral("root@ttill.de"));
aboutData.addAuthor(i18n("Simon A. Eugster"), i18n("Color scopes, bug fixing, etc."), QStringLiteral("simon.eu@gmail.com"));
aboutData.addAuthor(i18n("Marco Gittler"), i18n("MLT transitions and effects, timeline, audio thumbs"), QStringLiteral("g.marco@freenet.de"));
aboutData.addAuthor(i18n("Dan Dennedy"), i18n("Bug fixing, etc."), QStringLiteral("dan@dennedy.org"));
aboutData.addAuthor(i18n("Alberto Villa"), i18n("Bug fixing, logo, etc."), QStringLiteral("avilla@FreeBSD.org"));
aboutData.addAuthor(i18n("Jean-Michel Poure"), i18n("Rendering profiles customization"), QStringLiteral("jm@poure.com"));
aboutData.addAuthor(i18n("Ray Lehtiniemi"), i18n("Bug fixing, etc."), QStringLiteral("rayl@mail.com"));
aboutData.addAuthor(i18n("Steve Guilford"), i18n("Bug fixing, etc."), QStringLiteral("s.guilford@dbplugins.com"));
aboutData.addAuthor(i18n("Jason Wood"), i18n("Original KDE 3 version author (not active anymore)"), QStringLiteral("jasonwood@blueyonder.co.uk"));
aboutData.addCredit(i18n("Nara Oliveira and Farid Abdelnour | Estúdio Gunga"), i18n("Kdenlive 16.08 icon"));
aboutData.setTranslator(i18n("NAME OF TRANSLATORS"), i18n("EMAIL OF TRANSLATORS"));
aboutData.setOrganizationDomain(QByteArray("kde.org"));
aboutData.setOtherText(
i18n("Using:\nMLT version %1\nFFmpeg libraries", mlt_version_get_string()));
aboutData.setDesktopFileName(QStringLiteral("org.kde.kdenlive"));
// Register about data
KAboutData::setApplicationData(aboutData);
// Add rcc stored icons to the search path so that we always find our icons
KIconLoader *loader = KIconLoader::global();
loader->reconfigure("kdenlive", QStringList() << QStringLiteral(":/pics"));
// Set app stuff from about data
app.setApplicationDisplayName(aboutData.displayName());
app.setOrganizationDomain(aboutData.organizationDomain());
app.setApplicationVersion(aboutData.version());
app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
// Create command line parser with options
QCommandLineParser parser;
aboutData.setupCommandLine(&parser);
parser.setApplicationDescription(aboutData.shortDescription());
parser.addVersionOption();
parser.addHelpOption();
parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("config"), i18n("Set a custom config file name"), QStringLiteral("config")));
parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("mlt-path"), i18n("Set the path for MLT environment"), QStringLiteral("mlt-path")));
parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("mlt-log"), i18n("MLT log level"), QStringLiteral("verbose/debug")));
parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("i"), i18n("Comma separated list of clips to add"), QStringLiteral("clips")));
parser.addPositionalArgument(QStringLiteral("file"), i18n("Document to open"));
// Parse command line
parser.process(app);
aboutData.processCommandLine(&parser);
#ifdef USE_DRMINGW
ExcHndlInit();
#elif defined(KF5_USE_CRASH)
KCrash::initialize();
#endif
//auto splash = new Splash(&app);
//splash->show();
//qApp->processEvents();
qmlRegisterUncreatableMetaObject(PlaylistState::staticMetaObject, // static meta object
"com.enums", // import statement
1, 0, // major and minor version of the import
"ClipState", // name in QML
"Error: only enums");
qmlRegisterUncreatableMetaObject(ClipType::staticMetaObject, // static meta object
"com.enums", // import statement
1, 0, // major and minor version of the import
"ProducerType", // name in QML
"Error: only enums");
if (parser.value(QStringLiteral("mlt-log")) == QStringLiteral("verbose")) {
mlt_log_set_level(MLT_LOG_VERBOSE);
} else if (parser.value(QStringLiteral("mlt-log")) == QStringLiteral("debug")) {
mlt_log_set_level(MLT_LOG_DEBUG);
}
QUrl url;
if (parser.positionalArguments().count() != 0) {
url = QUrl::fromLocalFile(parser.positionalArguments().at(0));
// Make sure we get an absolute URL so that we can autosave correctly
QString currentPath = QDir::currentPath();
QUrl startup = QUrl::fromLocalFile(currentPath.endsWith(QDir::separator()) ? currentPath : currentPath + QDir::separator());
url = startup.resolved(url);
}
Core::build(!parser.value(QStringLiteral("config")).isEmpty(), parser.value(QStringLiteral("mlt-path")));
pCore->initGUI(url);
//delete splash;
//splash->endSplash();
//qApp->processEvents();
int result = app.exec();
Core::clean();
if (result == EXIT_RESTART || result == EXIT_CLEAN_RESTART) {
qCDebug(KDENLIVE_LOG) << "restarting app";
if (result == EXIT_CLEAN_RESTART) {
// Delete config file
KSharedConfigPtr config = KSharedConfig::openConfig();
if (config->name().contains(QLatin1String("kdenlive"))) {
// Make sure we delete our config file
QFile f(QStandardPaths::locate(QStandardPaths::ConfigLocation, config->name(), QStandardPaths::LocateFile));
if (f.exists()) {
qDebug()<<" = = = =\nGOT Deleted file: "<start(app.applicationFilePath(), progArgs);
restart->waitForReadyRead();
restart->waitForFinished(1000);
result = EXIT_SUCCESS;
}
return result;
}
diff --git a/src/mltcontroller/clipcontroller.cpp b/src/mltcontroller/clipcontroller.cpp
index 81060bb7f..0042b2c93 100644
--- a/src/mltcontroller/clipcontroller.cpp
+++ b/src/mltcontroller/clipcontroller.cpp
@@ -1,1055 +1,1056 @@
/*
Copyright (C) 2012 Till Theato
Copyright (C) 2014 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 "clipcontroller.h"
#include "bin/model/markerlistmodel.hpp"
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "kdenlivesettings.h"
#include "lib/audio/audioStreamInfo.h"
#include "profiles/profilemodel.hpp"
#include "core.h"
#include "kdenlive_debug.h"
#include
#include
#include
std::shared_ptr ClipController::mediaUnavailable;
ClipController::ClipController(const QString &clipId, const std::shared_ptr &producer)
: selectedEffectIndex(1)
, m_audioThumbCreated(false)
, m_masterProducer(producer)
, m_properties(producer ? new Mlt::Properties(producer->get_properties()) : nullptr)
, m_usesProxy(false)
, m_audioInfo(nullptr)
, m_videoIndex(0)
, m_clipType(ClipType::Unknown)
, m_hasLimitedDuration(true)
, m_effectStack(producer ? EffectStackModel::construct(producer, {ObjectType::BinClip, clipId.toInt()}, pCore->undoStack()) : nullptr)
, m_hasAudio(false)
, m_hasVideo(false)
, m_producerLock(QReadWriteLock::Recursive)
, m_controllerBinId(clipId)
{
if (m_masterProducer && !m_masterProducer->is_valid()) {
qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
return;
}
if (m_properties) {
setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
m_service = m_properties->get("mlt_service");
QString proxy = m_properties->get("kdenlive:proxy");
QString path = m_properties->get("resource");
if (proxy.length() > 2) {
if (QFileInfo(path).isRelative()) {
path.prepend(pCore->currentDoc()->documentRoot());
m_properties->set("resource", path.toUtf8().constData());
}
// This is a proxy producer, read original url from kdenlive property
path = m_properties->get("kdenlive:originalurl");
if (QFileInfo(path).isRelative()) {
path.prepend(pCore->currentDoc()->documentRoot());
}
m_usesProxy = true;
} else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative() &&
path != QLatin1String("")) {
path.prepend(pCore->currentDoc()->documentRoot());
m_properties->set("resource", path.toUtf8().constData());
}
m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath();
getInfoForProducer();
checkAudioVideo();
} else {
m_producerLock.lockForWrite();
}
}
ClipController::~ClipController()
{
delete m_properties;
m_masterProducer.reset();
}
const QString ClipController::binId() const
{
return m_controllerBinId;
}
const std::unique_ptr &ClipController::audioInfo() const
{
return m_audioInfo;
}
void ClipController::addMasterProducer(const std::shared_ptr &producer)
{
qDebug() << "################### ClipController::addmasterproducer";
QString documentRoot = pCore->currentDoc()->documentRoot();
m_masterProducer = producer;
m_properties = new Mlt::Properties(m_masterProducer->get_properties());
m_producerLock.unlock();
// Pass temporary properties
QMapIterator i(m_tempProps);
while (i.hasNext()) {
i.next();
switch(i.value().type()) {
case QVariant::Int:
setProducerProperty(i.key(), i.value().toInt());
break;
case QVariant::Double:
setProducerProperty(i.key(), i.value().toDouble());
break;
default:
setProducerProperty(i.key(), i.value().toString());
break;
}
}
m_tempProps.clear();
int id = m_controllerBinId.toInt();
m_effectStack = EffectStackModel::construct(producer, {ObjectType::BinClip, id}, pCore->undoStack());
if (!m_masterProducer->is_valid()) {
m_masterProducer = ClipController::mediaUnavailable;
qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
} else {
checkAudioVideo();
QString proxy = m_properties->get("kdenlive:proxy");
m_service = m_properties->get("mlt_service");
QString path = m_properties->get("resource");
m_usesProxy = false;
if (proxy.length() > 2) {
// This is a proxy producer, read original url from kdenlive property
path = m_properties->get("kdenlive:originalurl");
if (QFileInfo(path).isRelative()) {
path.prepend(documentRoot);
}
m_usesProxy = true;
} else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative()) {
path.prepend(documentRoot);
}
m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath();
getInfoForProducer();
emitProducerChanged(m_controllerBinId, producer);
setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
}
connectEffectStack();
}
namespace {
QString producerXml(const std::shared_ptr &producer, bool includeMeta, bool includeProfile)
{
Mlt::Consumer c(*producer->profile(), "xml", "string");
Mlt::Service s(producer->get_service());
if (!s.is_valid()) {
return QString();
}
int ignore = s.get_int("ignore_points");
if (ignore != 0) {
s.set("ignore_points", 0);
}
c.set("time_format", "frames");
if (!includeMeta) {
c.set("no_meta", 1);
}
if (!includeProfile) {
c.set("no_profile", 1);
}
c.set("store", "kdenlive");
c.set("no_root", 1);
c.set("root", "/");
c.connect(s);
c.start();
if (ignore != 0) {
s.set("ignore_points", ignore);
}
return QString::fromUtf8(c.get("string"));
}
} // namespace
void ClipController::getProducerXML(QDomDocument &document, bool includeMeta, bool includeProfile)
{
// TODO refac this is a probable duplicate with Clip::xml
if (m_masterProducer) {
QString xml = producerXml(m_masterProducer, includeMeta, includeProfile);
document.setContent(xml);
} else {
qCDebug(KDENLIVE_LOG) << " + + ++ NO MASTER PROD";
}
}
void ClipController::getInfoForProducer()
{
QReadLocker lock(&m_producerLock);
date = QFileInfo(m_path).lastModified();
m_videoIndex = -1;
int audioIndex = -1;
// special case: playlist with a proxy clip have to be detected separately
if (m_usesProxy && m_path.endsWith(QStringLiteral(".mlt"))) {
m_clipType = ClipType::Playlist;
} else if (m_service == QLatin1String("avformat") || m_service == QLatin1String("avformat-novalidate")) {
audioIndex = getProducerIntProperty(QStringLiteral("audio_index"));
m_videoIndex = getProducerIntProperty(QStringLiteral("video_index"));
if (m_videoIndex == -1) {
m_clipType = ClipType::Audio;
} else {
if (audioIndex == -1) {
m_clipType = ClipType::Video;
} else {
m_clipType = ClipType::AV;
}
if (m_service == QLatin1String("avformat")) {
m_properties->set("mlt_service", "avformat-novalidate");
+ m_properties->set("mute_on_pause", 0);
}
}
} else if (m_service == QLatin1String("qimage") || m_service == QLatin1String("pixbuf")) {
if (m_path.contains(QLatin1Char('%')) || m_path.contains(QStringLiteral("/.all."))) {
m_clipType = ClipType::SlideShow;
m_hasLimitedDuration = true;
} else {
m_clipType = ClipType::Image;
m_hasLimitedDuration = false;
}
} else if (m_service == QLatin1String("colour") || m_service == QLatin1String("color")) {
m_clipType = ClipType::Color;
m_hasLimitedDuration = false;
} else if (m_service == QLatin1String("kdenlivetitle")) {
if (!m_path.isEmpty()) {
m_clipType = ClipType::TextTemplate;
} else {
m_clipType = ClipType::Text;
}
m_hasLimitedDuration = false;
} else if (m_service == QLatin1String("xml") || m_service == QLatin1String("consumer")) {
m_clipType = ClipType::Playlist;
} else if (m_service == QLatin1String("webvfx")) {
m_clipType = ClipType::WebVfx;
} else if (m_service == QLatin1String("qtext")) {
m_clipType = ClipType::QText;
} else if (m_service == QLatin1String("blipflash")) {
// Mostly used for testing
m_clipType = ClipType::AV;
m_hasLimitedDuration = true;
} else {
m_clipType = ClipType::Unknown;
}
if (audioIndex > -1 || m_clipType == ClipType::Playlist) {
m_audioInfo = std::make_unique(m_masterProducer, audioIndex);
}
if (!m_hasLimitedDuration) {
int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
if (playtime <= 0) {
// Fix clips having missing kdenlive:duration
m_masterProducer->set("kdenlive:duration", m_masterProducer->frames_to_time(m_masterProducer->get_playtime(), mlt_time_clock));
m_masterProducer->set("out", m_masterProducer->frames_to_time(m_masterProducer->get_length() - 1, mlt_time_clock));
}
}
}
bool ClipController::hasLimitedDuration() const
{
return m_hasLimitedDuration;
}
void ClipController::forceLimitedDuration()
{
m_hasLimitedDuration = true;
}
std::shared_ptr ClipController::originalProducer()
{
QReadLocker lock(&m_producerLock);
return m_masterProducer;
}
Mlt::Producer *ClipController::masterProducer()
{
return new Mlt::Producer(*m_masterProducer);
}
bool ClipController::isValid()
{
if (m_masterProducer == nullptr) {
return false;
}
return m_masterProducer->is_valid();
}
// static
const char *ClipController::getPassPropertiesList(bool passLength)
{
if (!passLength) {
return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_"
"colorspace,set.force_full_luma,file_hash,autorotate,xmldata,video_index,audio_index,set.test_image,set.test_audio";
}
return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_"
"colorspace,set.force_full_luma,templatetext,file_hash,autorotate,xmldata,length,video_index,audio_index,set.test_image,set.test_audio";
}
QMap ClipController::getPropertiesFromPrefix(const QString &prefix, bool withPrefix)
{
QReadLocker lock(&m_producerLock);
Mlt::Properties subProperties;
subProperties.pass_values(*m_properties, prefix.toUtf8().constData());
QMap subclipsData;
for (int i = 0; i < subProperties.count(); i++) {
subclipsData.insert(withPrefix ? QString(prefix + subProperties.get_name(i)) : subProperties.get_name(i), subProperties.get(i));
}
return subclipsData;
}
void ClipController::updateProducer(const std::shared_ptr &producer)
{
qDebug() << "################### ClipController::updateProducer";
// TODO replace all track producers
if (!m_properties) {
// producer has not been initialized
return addMasterProducer(producer);
}
m_producerLock.lockForWrite();
Mlt::Properties passProperties;
// Keep track of necessary properties
QString proxy = producer->get("kdenlive:proxy");
if (proxy.length() > 2) {
// This is a proxy producer, read original url from kdenlive property
m_usesProxy = true;
} else {
m_usesProxy = false;
}
// When resetting profile, duration can change so we invalidate it to 0 in that case
int length = m_properties->get_int("length");
const char *passList = getPassPropertiesList(m_usesProxy && length > 0);
// This is necessary as some properties like set.test_audio are reset on producer creation
passProperties.pass_list(*m_properties, passList);
delete m_properties;
*m_masterProducer = producer.get();
m_properties = new Mlt::Properties(m_masterProducer->get_properties());
m_producerLock.unlock();
checkAudioVideo();
// Pass properties from previous producer
m_properties->pass_list(passProperties, passList);
if (!m_masterProducer->is_valid()) {
qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
} else {
m_effectStack->resetService(m_masterProducer);
emitProducerChanged(m_controllerBinId, producer);
// URL and name should not be updated otherwise when proxying a clip we cannot find back the original url
/*m_url = QUrl::fromLocalFile(m_masterProducer->get("resource"));
if (m_url.isValid()) {
m_name = m_url.fileName();
}
*/
}
qDebug() << "// replace finished: " << binId() << " : " << m_masterProducer->get("resource");
}
const QString ClipController::getStringDuration()
{
QReadLocker lock(&m_producerLock);
if (m_masterProducer) {
int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
if (playtime > 0) {
return QString(m_properties->frames_to_time(playtime, mlt_time_smpte_df));
}
return m_properties->frames_to_time(m_masterProducer->get_length(), mlt_time_smpte_df);
}
return i18n("Unknown");
}
int ClipController::getProducerDuration() const
{
QReadLocker lock(&m_producerLock);
if (m_masterProducer) {
int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
if (playtime <= 0) {
return playtime = m_masterProducer->get_length();
}
return playtime;
}
return -1;
}
char *ClipController::framesToTime(int frames) const
{
QReadLocker lock(&m_producerLock);
if (m_masterProducer) {
return m_masterProducer->frames_to_time(frames, mlt_time_clock);
}
return nullptr;
}
GenTime ClipController::getPlaytime() const
{
QReadLocker lock(&m_producerLock);
if (!m_masterProducer || !m_masterProducer->is_valid()) {
return GenTime();
}
double fps = pCore->getCurrentFps();
if (!m_hasLimitedDuration) {
int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
return GenTime(playtime == 0 ? m_masterProducer->get_playtime() : playtime, fps);
}
return {m_masterProducer->get_playtime(), fps};
}
int ClipController::getFramePlaytime() const
{
QReadLocker lock(&m_producerLock);
if (!m_masterProducer || !m_masterProducer->is_valid()) {
return 0;
}
if (!m_hasLimitedDuration) {
int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
return playtime == 0 ? m_masterProducer->get_playtime() : playtime;
}
return m_masterProducer->get_playtime();
}
QString ClipController::getProducerProperty(const QString &name) const
{
QReadLocker lock(&m_producerLock);
if (m_properties == nullptr) {
return QString();
}
if (m_usesProxy && name.startsWith(QLatin1String("meta."))) {
QString correctedName = QStringLiteral("kdenlive:") + name;
return m_properties->get(correctedName.toUtf8().constData());
}
return QString(m_properties->get(name.toUtf8().constData()));
}
int ClipController::getProducerIntProperty(const QString &name) const
{
QReadLocker lock(&m_producerLock);
if (!m_properties) {
return 0;
}
if (m_usesProxy && name.startsWith(QLatin1String("meta."))) {
QString correctedName = QStringLiteral("kdenlive:") + name;
return m_properties->get_int(correctedName.toUtf8().constData());
}
return m_properties->get_int(name.toUtf8().constData());
}
qint64 ClipController::getProducerInt64Property(const QString &name) const
{
QReadLocker lock(&m_producerLock);
if (!m_properties) {
return 0;
}
return m_properties->get_int64(name.toUtf8().constData());
}
double ClipController::getProducerDoubleProperty(const QString &name) const
{
QReadLocker lock(&m_producerLock);
if (!m_properties) {
return 0;
}
return m_properties->get_double(name.toUtf8().constData());
}
QColor ClipController::getProducerColorProperty(const QString &name) const
{
QReadLocker lock(&m_producerLock);
if (!m_properties) {
return {};
}
mlt_color color = m_properties->get_color(name.toUtf8().constData());
return QColor::fromRgb(color.r, color.g, color.b);
}
QMap ClipController::currentProperties(const QMap &props)
{
QMap currentProps;
QMap::const_iterator i = props.constBegin();
while (i != props.constEnd()) {
currentProps.insert(i.key(), getProducerProperty(i.key()));
++i;
}
return currentProps;
}
double ClipController::originalFps() const
{
QReadLocker lock(&m_producerLock);
if (!m_properties) {
return 0;
}
QString propertyName = QStringLiteral("meta.media.%1.stream.frame_rate").arg(m_videoIndex);
return m_properties->get_double(propertyName.toUtf8().constData());
}
QString ClipController::videoCodecProperty(const QString &property) const
{
QReadLocker lock(&m_producerLock);
if (!m_properties) {
return QString();
}
QString propertyName = QStringLiteral("meta.media.%1.codec.%2").arg(m_videoIndex).arg(property);
return m_properties->get(propertyName.toUtf8().constData());
}
const QString ClipController::codec(bool audioCodec) const
{
QReadLocker lock(&m_producerLock);
if ((m_properties == nullptr) || (m_clipType != ClipType::AV && m_clipType != ClipType::Video && m_clipType != ClipType::Audio)) {
return QString();
}
QString propertyName = QStringLiteral("meta.media.%1.codec.name").arg(audioCodec ? m_properties->get_int("audio_index") : m_videoIndex);
return m_properties->get(propertyName.toUtf8().constData());
}
const QString ClipController::clipUrl() const
{
return m_path;
}
bool ClipController::sourceExists() const
{
if (m_clipType == ClipType::Color || m_clipType == ClipType::Text) {
return true;
}
if (m_clipType == ClipType::SlideShow) {
//TODO
return true;
}
return QFile::exists(m_path);
}
QString ClipController::clipName() const
{
QString name = getProducerProperty(QStringLiteral("kdenlive:clipname"));
if (!name.isEmpty()) {
return name;
}
return QFileInfo(m_path).fileName();
}
QString ClipController::description() const
{
if (m_clipType == ClipType::TextTemplate) {
QString name = getProducerProperty(QStringLiteral("templatetext"));
return name;
}
QString name = getProducerProperty(QStringLiteral("kdenlive:description"));
if (!name.isEmpty()) {
return name;
}
return getProducerProperty(QStringLiteral("meta.attr.comment.markup"));
}
QString ClipController::serviceName() const
{
return m_service;
}
void ClipController::setProducerProperty(const QString &name, int value)
{
if (!m_masterProducer) {
m_tempProps.insert(name, value);
return;
}
QWriteLocker lock(&m_producerLock);
m_masterProducer->parent().set(name.toUtf8().constData(), value);
}
void ClipController::setProducerProperty(const QString &name, double value)
{
if (!m_masterProducer) {
m_tempProps.insert(name, value);
return;
}
QWriteLocker lock(&m_producerLock);
m_masterProducer->parent().set(name.toUtf8().constData(), value);
}
void ClipController::setProducerProperty(const QString &name, const QString &value)
{
if (!m_masterProducer) {
m_tempProps.insert(name, value);
return;
}
QWriteLocker lock(&m_producerLock);
if (value.isEmpty()) {
m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr);
} else {
m_masterProducer->parent().set(name.toUtf8().constData(), value.toUtf8().constData());
}
}
void ClipController::resetProducerProperty(const QString &name)
{
if (!m_masterProducer) {
m_tempProps.insert(name, QString());
return;
}
QWriteLocker lock(&m_producerLock);
m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr);
}
ClipType::ProducerType ClipController::clipType() const
{
return m_clipType;
}
const QSize ClipController::getFrameSize() const
{
QReadLocker lock(&m_producerLock);
if (m_masterProducer == nullptr) {
return QSize();
}
int width = m_masterProducer->get_int("meta.media.width");
if (width == 0) {
width = m_masterProducer->get_int("width");
}
int height = m_masterProducer->get_int("meta.media.height");
if (height == 0) {
height = m_masterProducer->get_int("height");
}
return QSize(width, height);
}
bool ClipController::hasAudio() const
{
return m_hasAudio;
}
void ClipController::checkAudioVideo()
{
QReadLocker lock(&m_producerLock);
m_masterProducer->seek(0);
if (m_masterProducer->get_int("_placeholder") == 1 || m_masterProducer->get("text") == QLatin1String("INVALID")) {
// This is a placeholder file, try to guess from its properties
QString orig_service = m_masterProducer->get("kdenlive:orig_service");
if (orig_service.startsWith(QStringLiteral("avformat")) || (m_masterProducer->get_int("audio_index") + m_masterProducer->get_int("video_index") > 0)) {
m_hasAudio = m_masterProducer->get_int("audio_index") >= 0;
m_hasVideo = m_masterProducer->get_int("video_index") >= 0;
} else {
// Assume image or text producer
m_hasAudio = false;
m_hasVideo = true;
}
return;
}
QScopedPointer frame(m_masterProducer->get_frame());
if (frame->is_valid()) {
// test_audio returns 1 if there is NO audio (strange but true at the time this code is written)
m_hasAudio = frame->get_int("test_audio") == 0;
m_hasVideo = frame->get_int("test_image") == 0;
} else {
qDebug()<<"* * * *ERROR INVALID FRAME On test";
}
}
bool ClipController::hasVideo() const
{
return m_hasVideo;
}
PlaylistState::ClipState ClipController::defaultState() const
{
if (hasVideo()) {
return PlaylistState::VideoOnly;
}
if (hasAudio()) {
return PlaylistState::AudioOnly;
}
return PlaylistState::Disabled;
}
QPixmap ClipController::pixmap(int framePosition, int width, int height)
{
// TODO refac this should use the new thumb infrastructure
QReadLocker lock(&m_producerLock);
m_masterProducer->seek(framePosition);
Mlt::Frame *frame = m_masterProducer->get_frame();
if (frame == nullptr || !frame->is_valid()) {
QPixmap p(width, height);
p.fill(QColor(Qt::red).rgb());
return p;
}
frame->set("rescale.interp", "bilinear");
frame->set("deinterlace_method", "onefield");
frame->set("top_field_first", -1);
if (width == 0) {
width = m_masterProducer->get_int("meta.media.width");
if (width == 0) {
width = m_masterProducer->get_int("width");
}
}
if (height == 0) {
height = m_masterProducer->get_int("meta.media.height");
if (height == 0) {
height = m_masterProducer->get_int("height");
}
}
// int ow = frameWidth;
// int oh = height;
mlt_image_format format = mlt_image_rgb24a;
width += width % 2;
height += height % 2;
const uchar *imagedata = frame->get_image(format, width, height);
QImage image(imagedata, width, height, QImage::Format_RGBA8888);
QPixmap pixmap;
pixmap.convertFromImage(image);
delete frame;
return pixmap;
}
void ClipController::setZone(const QPoint &zone)
{
setProducerProperty(QStringLiteral("kdenlive:zone_in"), zone.x());
setProducerProperty(QStringLiteral("kdenlive:zone_out"), zone.y());
}
QPoint ClipController::zone() const
{
int in = getProducerIntProperty(QStringLiteral("kdenlive:zone_in"));
int max = getFramePlaytime() - 1;
int out = qMin(getProducerIntProperty(QStringLiteral("kdenlive:zone_out")), max);
if (out <= in) {
out = max;
}
QPoint zone(in, out);
return zone;
}
const QString ClipController::getClipHash() const
{
return getProducerProperty(QStringLiteral("kdenlive:file_hash"));
}
Mlt::Properties &ClipController::properties()
{
QReadLocker lock(&m_producerLock);
return *m_properties;
}
void ClipController::backupOriginalProperties()
{
QReadLocker lock(&m_producerLock);
if (m_properties->get_int("kdenlive:original.backup") == 1) {
return;
}
int propsCount = m_properties->count();
// store original props
QStringList doNotPass {QStringLiteral("kdenlive:proxy"),QStringLiteral("kdenlive:originalurl"),QStringLiteral("kdenlive:clipname")};
for (int j = 0; j < propsCount; j++) {
QString propName = m_properties->get_name(j);
if (doNotPass.contains(propName)) {
continue;
}
if (!propName.startsWith(QLatin1Char('_'))) {
propName.prepend(QStringLiteral("kdenlive:original."));
m_properties->set(propName.toUtf8().constData(), m_properties->get(j));
}
}
m_properties->set("kdenlive:original.backup", 1);
}
void ClipController::clearBackupProperties()
{
QReadLocker lock(&m_producerLock);
if (m_properties->get_int("kdenlive:original.backup") == 0) {
return;
}
int propsCount = m_properties->count();
// clear original props
QStringList passProps;
for (int j = 0; j < propsCount; j++) {
QString propName = m_properties->get_name(j);
if (propName.startsWith(QLatin1String("kdenlive:original."))) {
passProps << propName;
}
}
for (const QString &p : passProps) {
m_properties->set(p.toUtf8().constData(), (char *)nullptr);
}
m_properties->set("kdenlive:original.backup", (char *)nullptr);
}
void ClipController::mirrorOriginalProperties(Mlt::Properties &props)
{
QReadLocker lock(&m_producerLock);
if (m_usesProxy && QFileInfo(m_properties->get("resource")).fileName() == QFileInfo(m_properties->get("kdenlive:proxy")).fileName()) {
// This is a proxy, we need to use the real source properties
if (m_properties->get_int("kdenlive:original.backup") == 0) {
// We have a proxy clip, load original source producer
std::shared_ptr prod = std::make_shared(pCore->getCurrentProfile()->profile(), nullptr, m_path.toUtf8().constData());
// Get frame to make sure we retrieve all original props
std::shared_ptr fr(prod->get_frame());
if (!prod->is_valid()) {
return;
}
int width = 0;
int height = 0;
mlt_image_format format = mlt_image_none;
fr->get_image(format, width, height);
Mlt::Properties sourceProps(prod->get_properties());
props.inherit(sourceProps);
int propsCount = sourceProps.count();
// store original props
QStringList doNotPass {QStringLiteral("kdenlive:proxy"),QStringLiteral("kdenlive:originalurl"),QStringLiteral("kdenlive:clipname")};
for (int i = 0; i < propsCount; i++) {
QString propName = sourceProps.get_name(i);
if (doNotPass.contains(propName)) {
continue;
}
if (!propName.startsWith(QLatin1Char('_'))) {
propName.prepend(QStringLiteral("kdenlive:original."));
m_properties->set(propName.toUtf8().constData(), sourceProps.get(i));
}
}
m_properties->set("kdenlive:original.backup", 1);
}
// Properties were fetched in the past, reuse
Mlt::Properties sourceProps;
sourceProps.pass_values(*m_properties, "kdenlive:original.");
props.inherit(sourceProps);
} else {
if (m_clipType == ClipType::AV || m_clipType == ClipType::Video || m_clipType == ClipType::Audio) {
// Make sure that a frame / image was fetched to initialize all meta properties
QString progressive = m_properties->get("meta.media.progressive");
if (progressive.isEmpty()) {
// Fetch a frame to initialize required properties
QScopedPointer tmpProd(nullptr);
if (KdenliveSettings::gpu_accel()) {
QString service = m_masterProducer->get("mlt_service");
tmpProd.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), service.toUtf8().constData(), m_masterProducer->get("resource")));
}
std::shared_ptr fr(tmpProd ? tmpProd->get_frame() : m_masterProducer->get_frame());
mlt_image_format format = mlt_image_none;
int width = 0;
int height = 0;
fr->get_image(format, width, height);
}
}
props.inherit(*m_properties);
}
}
void ClipController::addEffect(QDomElement &xml)
{
Q_UNUSED(xml)
// TODO refac: this must be rewritten
/*
QMutexLocker lock(&m_effectMutex);
Mlt::Service service = m_masterProducer->parent();
ItemInfo info;
info.cropStart = GenTime();
info.cropDuration = getPlaytime();
EffectsList eff = effectList();
EffectsController::initEffect(info, eff, getProducerProperty(QStringLiteral("kdenlive:proxy")), xml);
// Add effect to list and setup a kdenlive_ix value
int kdenlive_ix = 0;
for (int i = 0; i < service.filter_count(); ++i) {
QScopedPointer effect(service.filter(i));
int ix = effect->get_int("kdenlive_ix");
if (ix > kdenlive_ix) {
kdenlive_ix = ix;
}
}
kdenlive_ix++;
xml.setAttribute(QStringLiteral("kdenlive_ix"), kdenlive_ix);
EffectsParameterList params = EffectsController::getEffectArgs(xml);
EffectManager effect(service);
effect.addEffect(params, getPlaytime().frames(pCore->getCurrentFps()));
if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId);
*/
}
void ClipController::removeEffect(int effectIndex, bool delayRefresh)
{
Q_UNUSED(effectIndex) Q_UNUSED(delayRefresh)
// TODO refac: this must be rewritten
/*
QMutexLocker lock(&m_effectMutex);
Mlt::Service service(m_masterProducer->parent());
EffectManager effect(service);
effect.removeEffect(effectIndex, true);
if (!delayRefresh) {
if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId);
}
*/
}
void ClipController::moveEffect(int oldPos, int newPos)
{
Q_UNUSED(oldPos)
Q_UNUSED(newPos)
// TODO refac: this must be rewritten
/*
QMutexLocker lock(&m_effectMutex);
Mlt::Service service(m_masterProducer->parent());
EffectManager effect(service);
effect.moveEffect(oldPos, newPos);
*/
}
int ClipController::effectsCount()
{
int count = 0;
QReadLocker lock(&m_producerLock);
Mlt::Service service(m_masterProducer->parent());
for (int ix = 0; ix < service.filter_count(); ++ix) {
QScopedPointer effect(service.filter(ix));
QString id = effect->get("kdenlive_id");
if (!id.isEmpty()) {
count++;
}
}
return count;
}
void ClipController::changeEffectState(const QList &indexes, bool disable)
{
Q_UNUSED(indexes)
Q_UNUSED(disable)
// TODO refac : this must be rewritten
/*
Mlt::Service service = m_masterProducer->parent();
for (int i = 0; i < service.filter_count(); ++i) {
QScopedPointer effect(service.filter(i));
if ((effect != nullptr) && effect->is_valid() && indexes.contains(effect->get_int("kdenlive_ix"))) {
effect->set("disable", (int)disable);
}
}
if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId);
*/
}
void ClipController::updateEffect(const QDomElement &e, int ix)
{
Q_UNUSED(e)
Q_UNUSED(ix)
// TODO refac : this must be rewritten
/*
QString tag = e.attribute(QStringLiteral("id"));
if (tag == QLatin1String("autotrack_rectangle") || tag.startsWith(QLatin1String("ladspa")) || tag == QLatin1String("sox")) {
// this filters cannot be edited, remove and re-add it
removeEffect(ix, true);
QDomElement clone = e.cloneNode().toElement();
addEffect(clone);
return;
}
EffectsParameterList params = EffectsController::getEffectArgs(e);
Mlt::Service service = m_masterProducer->parent();
for (int i = 0; i < service.filter_count(); ++i) {
QScopedPointer effect(service.filter(i));
if (!effect || !effect->is_valid() || effect->get_int("kdenlive_ix") != ix) {
continue;
}
service.lock();
QString prefix;
QString ser = effect->get("mlt_service");
if (ser == QLatin1String("region")) {
prefix = QStringLiteral("filter0.");
}
for (int j = 0; j < params.count(); ++j) {
effect->set((prefix + params.at(j).name()).toUtf8().constData(), params.at(j).value().toUtf8().constData());
// qCDebug(KDENLIVE_LOG)<updateTrackProducer(m_controllerBinId);
// slotRefreshTracks();
*/
}
bool ClipController::hasEffects() const
{
return m_effectStack->rowCount() > 0;
}
void ClipController::setBinEffectsEnabled(bool enabled)
{
m_effectStack->setEffectStackEnabled(enabled);
}
void ClipController::saveZone(QPoint zone, const QDir &dir)
{
QString path = QString(clipName() + QLatin1Char('_') + QString::number(zone.x()) + QStringLiteral(".mlt"));
if (dir.exists(path)) {
// TODO ask for overwrite
}
Mlt::Consumer xmlConsumer(pCore->getCurrentProfile()->profile(), ("xml:" + dir.absoluteFilePath(path)).toUtf8().constData());
xmlConsumer.set("terminate_on_pause", 1);
QReadLocker lock(&m_producerLock);
Mlt::Producer prod(m_masterProducer->get_producer());
Mlt::Producer *prod2 = prod.cut(zone.x(), zone.y());
Mlt::Playlist list(pCore->getCurrentProfile()->profile());
list.insert_at(0, *prod2, 0);
// list.set("title", desc.toUtf8().constData());
xmlConsumer.connect(list);
xmlConsumer.run();
delete prod2;
}
std::shared_ptr ClipController::getEffectStack() const
{
return m_effectStack;
}
bool ClipController::addEffect(const QString &effectId)
{
return m_effectStack->appendEffect(effectId, true);
}
bool ClipController::copyEffect(const std::shared_ptr &stackModel, int rowId)
{
m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId),
!m_hasAudio ? PlaylistState::VideoOnly : !m_hasVideo ? PlaylistState::AudioOnly : PlaylistState::Disabled);
return true;
}
std::shared_ptr ClipController::getMarkerModel() const
{
return m_markerModel;
}
void ClipController::refreshAudioInfo()
{
if (m_audioInfo && m_masterProducer) {
QReadLocker lock(&m_producerLock);
m_audioInfo->setAudioIndex(m_masterProducer, m_properties->get_int("audio_index"));
}
}
QList ClipController::audioStreams() const
{
if (m_audioInfo) {
return m_audioInfo->streams();
}
return {};
}
int ClipController::audioStreamsCount() const
{
if (m_audioInfo) {
return m_audioInfo->streams().count();
}
return 0;
}
diff --git a/src/monitor/glwidget.cpp b/src/monitor/glwidget.cpp
index f4e2f3d31..446276ddf 100644
--- a/src/monitor/glwidget.cpp
+++ b/src/monitor/glwidget.cpp
@@ -1,1888 +1,1884 @@
/*
* 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