diff --git a/src/bin/binplaylist.cpp b/src/bin/binplaylist.cpp
index 877f7bb53..70aed3eb4 100644
--- a/src/bin/binplaylist.cpp
+++ b/src/bin/binplaylist.cpp
@@ -1,201 +1,201 @@
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "binplaylist.hpp"
#include "abstractprojectitem.h"
#include "bin/model/markerlistmodel.hpp"
#include "core.h"
#include "profiles/profilemodel.hpp"
#include "projectclip.h"
#include
QString BinPlaylist::binPlaylistId = QString("main_bin");
BinPlaylist::BinPlaylist()
- : m_binPlaylist(new Mlt::Playlist(pCore->getCurrentProfile()->profile()))
+ : m_binPlaylist(new Mlt::Playlist(*pCore->getProjectProfile()))
{
m_binPlaylist->set("id", binPlaylistId.toUtf8().constData());
}
void BinPlaylist::manageBinItemInsertion(const std::shared_ptr &binElem)
{
QString id = binElem->clipId();
switch (binElem->itemType()) {
case AbstractProjectItem::FolderItem: {
// When a folder is inserted, we have to store its path into the properties
if (binElem->parent()) {
QString propertyName = "kdenlive:folder." + binElem->parent()->clipId() + QLatin1Char('.') + id;
m_binPlaylist->set(propertyName.toUtf8().constData(), binElem->name().toUtf8().constData());
}
break;
}
case AbstractProjectItem::ClipItem: {
Q_ASSERT(m_allClips.count(id) == 0);
auto clip = std::static_pointer_cast(binElem);
if (clip->isValid()) {
m_binPlaylist->append(*clip->originalProducer().get());
} else {
// if clip is not loaded yet, we insert a dummy producer
- Mlt::Producer dummy(pCore->getCurrentProfile()->profile(), "color:blue");
+ Mlt::Producer dummy(*pCore->getProjectProfile(), "color:blue");
dummy.set("kdenlive:id", id.toUtf8().constData());
m_binPlaylist->append(dummy);
}
m_allClips.insert(id);
connect(clip.get(), &ProjectClip::producerChanged, this, &BinPlaylist::changeProducer);
break;
}
default:
break;
}
}
void BinPlaylist::manageBinItemDeletion(AbstractProjectItem *binElem)
{
QString id = binElem->clipId();
switch (binElem->itemType()) {
case AbstractProjectItem::FolderItem: {
// When a folder is removed, we clear the path info
if (!binElem->lastParentId().isEmpty()) {
QString propertyName = "kdenlive:folder." + binElem->lastParentId() + QLatin1Char('.') + binElem->clipId();
m_binPlaylist->set(propertyName.toUtf8().constData(), (char *)nullptr);
}
break;
}
case AbstractProjectItem::ClipItem: {
Q_ASSERT(m_allClips.count(id) > 0);
m_allClips.erase(id);
removeBinClip(id);
disconnect(static_cast(binElem), &ProjectClip::producerChanged, this, &BinPlaylist::changeProducer);
}
default:
break;
}
}
void BinPlaylist::removeBinClip(const QString &id)
{
// we iterate on the clips of the timeline to find the correct one
bool ok = false;
int size = m_binPlaylist->count();
for (int i = 0; !ok && i < size; i++) {
QScopedPointer prod(m_binPlaylist->get_clip(i));
QString prodId(prod->parent().get("kdenlive:id"));
if (prodId == id) {
m_binPlaylist->remove(i);
ok = true;
}
}
Q_ASSERT(ok);
}
void BinPlaylist::changeProducer(const QString &id, const std::shared_ptr &producer)
{
Q_ASSERT(m_allClips.count(id) > 0);
removeBinClip(id);
m_binPlaylist->append(*producer.get());
}
void BinPlaylist::setRetainIn(Mlt::Tractor *modelTractor)
{
QString retain = QStringLiteral("xml_retain %1").arg(binPlaylistId);
modelTractor->set(retain.toUtf8().constData(), m_binPlaylist->get_service(), 0);
}
void BinPlaylist::saveDocumentProperties(const QMap &props, const QMap &metadata,
std::shared_ptr guideModel)
{
Q_UNUSED(guideModel)
// Clear previous properites
Mlt::Properties playlistProps(m_binPlaylist->get_properties());
Mlt::Properties docProperties;
docProperties.pass_values(playlistProps, "kdenlive:docproperties.");
for (int i = 0; i < docProperties.count(); i++) {
QString propName = QStringLiteral("kdenlive:docproperties.%1").arg(docProperties.get_name(i));
playlistProps.set(propName.toUtf8().constData(), (char *)nullptr);
}
// Clear previous metadata
Mlt::Properties docMetadata;
docMetadata.pass_values(playlistProps, "kdenlive:docmetadata.");
for (int i = 0; i < docMetadata.count(); i++) {
QString propName = QStringLiteral("kdenlive:docmetadata.%1").arg(docMetadata.get_name(i));
playlistProps.set(propName.toUtf8().constData(), (char *)nullptr);
}
QMapIterator i(props);
while (i.hasNext()) {
i.next();
playlistProps.set(("kdenlive:docproperties." + i.key()).toUtf8().constData(), i.value().toUtf8().constData());
}
QMapIterator j(metadata);
while (j.hasNext()) {
j.next();
playlistProps.set(("kdenlive:docmetadata." + j.key()).toUtf8().constData(), j.value().toUtf8().constData());
}
}
void BinPlaylist::saveProperty(const QString &name, const QString &value)
{
m_binPlaylist->set(name.toUtf8().constData(), value.toUtf8().constData());
}
QMap BinPlaylist::getProxies(const QString &root)
{
QMap proxies;
int size = m_binPlaylist->count();
for (int i = 0; i < size; i++) {
QScopedPointer prod(m_binPlaylist->get_clip(i));
if (!prod->is_valid() || prod->is_blank()) {
continue;
}
QString proxy = prod->parent().get("kdenlive:proxy");
if (proxy.length() > 2) {
if (QFileInfo(proxy).isRelative()) {
proxy.prepend(root);
}
QString sourceUrl(prod->parent().get("kdenlive:originalurl"));
if (QFileInfo(sourceUrl).isRelative()) {
sourceUrl.prepend(root);
}
proxies.insert(proxy, sourceUrl);
}
}
return proxies;
}
int BinPlaylist::count() const
{
return m_binPlaylist->count();
}
void BinPlaylist::manageBinFolderRename(const std::shared_ptr &binElem)
{
QString id = binElem->clipId();
if (binElem->itemType() != AbstractProjectItem::FolderItem) {
qDebug() << "// ITEM IS NOT A FOLDER; ABORT RENAME";
}
// When a folder is inserted, we have to store its path into the properties
if (binElem->parent()) {
QString propertyName = "kdenlive:folder." + binElem->parent()->clipId() + QLatin1Char('.') + id;
m_binPlaylist->set(propertyName.toUtf8().constData(), binElem->name().toUtf8().constData());
}
}
diff --git a/src/bin/projectclip.cpp b/src/bin/projectclip.cpp
index f8b7e37d5..91327e63f 100644
--- a/src/bin/projectclip.cpp
+++ b/src/bin/projectclip.cpp
@@ -1,1474 +1,1474 @@
/*
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));
}
setTags(getProducerProperty(QStringLiteral("kdenlive:tags")));
AbstractProjectItem::setRating((uint) getProducerIntProperty(QStringLiteral("kdenlive:rating")));
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(const QVector 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, bool reloadAudio)
{
// 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(), false);
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(), reloadAudio);
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;
if (document.documentElement().tagName() == QLatin1String("producer")) {
prod = document.documentElement();
} else {
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;
setTags(getProducerProperty(QStringLiteral("kdenlive:tags")));
AbstractProjectItem::setRating((uint) getProducerIntProperty(QStringLiteral("kdenlive:rating")));
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
if (m_clipType != ClipType::Color && m_clipType != ClipType::Image && m_clipType != ClipType::Text) {
// Color, image and text clips always use master producer in timeline
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::Consumer c(*pCore->getProjectProfile(), "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()));
+ prod.reset(new Mlt::Producer(*pCore->getProjectProfile(), "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:tags"))) {
setTags(properties.value(QStringLiteral("kdenlive:tags")));
if (auto ptr = m_model.lock()) {
std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()),
AbstractProjectItem::DataTag);
}
}
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 {
bool audioStreamChanged = properties.contains(QStringLiteral("audio_index"));
reloadProducer(refreshOnly, audioStreamChanged, audioStreamChanged || (!refreshOnly && !properties.contains(QStringLiteral("kdenlive:proxy"))));
}
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 && !miniThumb) {
return QString();
}
bool ok = false;
QDir thumbFolder = pCore->currentDoc()->getCacheDir(CacheAudio, &ok);
if (!ok) {
return QString();
}
const QString clipHash = hash();
if (clipHash.isEmpty()) {
return QString();
}
QString audioPath = thumbFolder.absoluteFilePath(clipHash);
if (miniThumb) {
audioPath.append(QStringLiteral(".png"));
return audioPath;
}
int audioStream = audioInfo()->ffmpeg_audio_index();
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()));
if (clip->rating() > 0) {
currentZone.insert(QLatin1String("rating"), QJsonValue((int)clip->rating()));
}
if (!clip->tags().isEmpty()) {
currentZone.insert(QLatin1String("tags"), QJsonValue(clip->tags()));
}
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);
}
}
}
void ProjectClip::setRating(uint rating)
{
AbstractProjectItem::setRating(rating);
setProducerProperty(QStringLiteral("kdenlive:rating"), (int) rating);
pCore->currentDoc()->setModified(true);
}
diff --git a/src/core.cpp b/src/core.cpp
index 2b5996053..58d705d59 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -1,838 +1,869 @@
/*
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);
}
+ updatePreviewProfile();
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::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()));
}
void Core::buildLumaThumbs(const QStringList &values)
{
for (auto &entry : values) {
if (MainWindow::m_lumacache.contains(entry)) {
continue;
}
QImage pix(entry);
if (!pix.isNull()) {
MainWindow::m_lumacache.insert(entry, pix.scaled(50, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
}
}
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);
}
+Mlt::Profile *Core::getProjectProfile()
+{
+ if (!m_previewProfile) {
+ m_previewProfile = std::make_unique(m_currentProfile.toStdString().c_str());
+ updatePreviewProfile();
+ }
+ return m_previewProfile.get();
+}
+
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;
+ updatePreviewProfile();
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());
+ m_mainWindow->getCurrentTimeline()->controller()->getModel()->updateProfile(getProjectProfile());
checkProfileValidity();
}
return true;
}
return false;
}
+void Core::updatePreviewProfile()
+{
+ int newWidth = getCurrentProfile()->width() / KdenliveSettings::previewScaling();
+ int newHeight = getCurrentProfile()->height() / KdenliveSettings::previewScaling();
+ if (newWidth % 8 > 0) {
+ newWidth += 8 - newWidth % 8;
+ }
+ if (newHeight % 8 > 0) {
+ newHeight += 8 - newHeight % 8;
+ }
+ m_previewProfile->set_colorspace(getCurrentProfile()->colorspace());
+ m_previewProfile->set_frame_rate(getCurrentProfile()->frame_rate_num(), getCurrentProfile()->frame_rate_den());
+ m_previewProfile->set_width(newWidth);
+ m_previewProfile->set_height(newHeight);
+ m_previewProfile->set_progressive(getCurrentProfile()->progressive());
+ m_previewProfile->set_sample_aspect(getCurrentProfile()->sample_aspect_num(), getCurrentProfile()->sample_aspect_den());
+ m_previewProfile->set_display_aspect(getCurrentProfile()->display_aspect_num(), getCurrentProfile()->display_aspect_den());
+ m_previewProfile->set_explicit(true);
+}
+
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->activateMonitor(Kdenlive::ClipMonitor);
m_monitorManager->refreshClipMonitor();
if (m_monitorManager->projectMonitorVisible() && m_mainWindow->getCurrentTimeline()->controller()->refreshIfVisible(id.second)) {
m_monitorManager->refreshTimer.start();
}
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) {
if (type == ProcessingJobMessage || type == OperationCompletedMessage) {
m_mainWindow->displayProgressMessage(message, type, timeout);
} else {
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_guiConstructed) {
return m_monitorManager->projectMonitor()->position();
}
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);
}
void Core::processInvalidFilter(const QString service, const QString id, const QString message)
{
if (m_guiConstructed) m_mainWindow->assetPanelWarning(service, id, message);
}
void Core::updateProjectTags(QMap tags)
{
// Clear previous tags
for (int i = 1 ; i< 20; i++) {
QString current = currentDoc()->getDocumentProperty(QString("tag%1").arg(i));
if (current.isEmpty()) {
break;
} else {
currentDoc()->setDocumentProperty(QString("tag%1").arg(i), QString());
}
}
QMapIterator j(tags);
int i = 1;
while (j.hasNext()) {
j.next();
currentDoc()->setDocumentProperty(QString("tag%1").arg(i), QString("%1:%2").arg(j.key()).arg(j.value()));
i++;
}
}
diff --git a/src/core.h b/src/core.h
index 27c856276..898eece23 100644
--- a/src/core.h
+++ b/src/core.h
@@ -1,252 +1,257 @@
/*
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.
*/
#ifndef CORE_H
#define CORE_H
#include "definitions.h"
#include "kdenlivecore_export.h"
#include "undohelper.hpp"
#include
#include
#include
#include
#include
class Bin;
class DocUndoStack;
class EffectStackModel;
class JobManager;
class KdenliveDoc;
class LibraryWidget;
class MainWindow;
class MediaCapture;
class MixerManager;
class Monitor;
class MonitorManager;
class ProfileModel;
class ProjectItemModel;
class ProjectManager;
namespace Mlt {
-class Repository;
-class Profile;
+ class Repository;
+ class Profile;
} // namespace Mlt
#define EXIT_RESTART (42)
#define EXIT_CLEAN_RESTART (43)
#define pCore Core::self()
/**
* @class Core
* @brief Singleton that provides access to the different parts of Kdenlive
*
* Needs to be initialize before any widgets are created in MainWindow.
* Plugins should be loaded after the widget setup.
*/
class /*KDENLIVECORE_EXPORT*/ Core : public QObject
{
Q_OBJECT
public:
Core(const Core &) = delete;
Core &operator=(const Core &) = delete;
Core(Core &&) = delete;
Core &operator=(Core &&) = delete;
~Core() override;
/**
* @brief Setup the basics of the application, in particular the connection
* with Mlt
* @param isAppImage do we expect an AppImage (if yes, we use App path to deduce
* other binaries paths (melt, ffmpeg, etc)
* @param MltPath (optional) path to MLT environment
*/
static void build(bool isAppImage, const QString &MltPath = QString());
/**
* @brief Init the GUI part of the app and show the main window
* @param Url (optional) file to open
* If Url is present, it will be opened, otherwise, if openlastproject is
* set, latest project will be opened. If no file is open after trying this,
* a default new file will be created. */
void initGUI(const QUrl &Url);
/** @brief Returns a pointer to the singleton object. */
static std::unique_ptr &self();
/** @brief Delete the global core instance */
static void clean();
/** @brief Returns a pointer to the main window. */
MainWindow *window();
/** @brief Returns a pointer to the project manager. */
ProjectManager *projectManager();
/** @brief Returns a pointer to the current project. */
KdenliveDoc *currentDoc();
/** @brief Returns a pointer to the monitor manager. */
MonitorManager *monitorManager();
/** @brief Returns a pointer to the view of the project bin. */
Bin *bin();
/** @brief Select a clip in the Bin from its id. */
void selectBinClip(const QString &id, int frame = -1, const QPoint &zone = QPoint());
/** @brief Returns a pointer to the model of the project bin. */
std::shared_ptr projectItemModel();
/** @brief Returns a pointer to the job manager. Please do not store it. */
std::shared_ptr jobManager();
/** @brief Returns a pointer to the library. */
LibraryWidget *library();
/** @brief Returns a pointer to the audio mixer. */
MixerManager *mixer();
/** @brief Returns a pointer to MLT's repository */
std::unique_ptr &getMltRepository();
/** @brief Returns a pointer to the current profile */
std::unique_ptr &getCurrentProfile() const;
const QString &getCurrentProfilePath() const;
/** @brief Define the active profile
* @returns true if profile exists, false if not found
*/
bool setCurrentProfile(const QString &profilePath);
/** @brief Returns Sample Aspect Ratio of current profile */
double getCurrentSar() const;
/** @brief Returns Display Aspect Ratio of current profile */
double getCurrentDar() const;
/** @brief Returns frame rate of current profile */
double getCurrentFps() const;
/** @brief Returns the frame size (width x height) of current profile */
QSize getCurrentFrameSize() const;
/** @brief Returns the frame display size (width x height) of current profile */
QSize getCurrentFrameDisplaySize() const;
/** @brief Request project monitor refresh */
void requestMonitorRefresh();
/** @brief Request project monitor refresh if current position is inside range*/
void refreshProjectRange(QSize range);
/** @brief Request project monitor refresh if referenced item is under cursor */
void refreshProjectItem(const ObjectId &id);
/** @brief Returns a reference to a monitor (clip or project monitor) */
Monitor *getMonitor(int id);
/** @brief This function must be called whenever the profile used changes */
void profileChanged();
/** @brief Create and push and undo object based on the corresponding functions
Note that if you class permits and requires it, you should use the macro PUSH_UNDO instead*/
void pushUndo(const Fun &undo, const Fun &redo, const QString &text);
void pushUndo(QUndoCommand *command);
/** @brief display a user info/warning message in statusbar */
void displayMessage(const QString &message, MessageType type, int timeout = -1);
/** @brief Clear asset view if itemId is displayed. */
void clearAssetPanel(int itemId);
/** @brief Returns the effectstack of a given bin clip. */
std::shared_ptr getItemEffectStack(int itemType, int itemId);
int getItemPosition(const ObjectId &id);
int getItemIn(const ObjectId &id);
int getItemTrack(const ObjectId &id);
int getItemDuration(const ObjectId &id);
/** @brief Returns the capabilities of a clip: AudioOnly, VideoOnly or Disabled if both are allowed */
PlaylistState::ClipState getItemState(const ObjectId &id);
/** @brief Get a list of video track names with indexes */
QMap getTrackNames(bool videoOnly);
/** @brief Returns the composition A track (MLT index / Track id) */
QPair getCompositionATrack(int cid) const;
void setCompositionATrack(int cid, int aTrack);
/* @brief Return true if composition's a_track is automatic (no forced track)
*/
bool compositionAutoTrack(int cid) const;
std::shared_ptr undoStack();
double getClipSpeed(int id) const;
/** @brief Mark an item as invalid for timeline preview */
void invalidateItem(ObjectId itemId);
void invalidateRange(QSize range);
void prepareShutdown();
/** the keyframe model changed (effect added, deleted, active effect changed), inform timeline */
void updateItemKeyframes(ObjectId id);
/** A fade for clip id changed, update timeline */
void updateItemModel(ObjectId id, const QString &service);
/** Show / hide keyframes for a timeline clip */
void showClipKeyframes(ObjectId id, bool enable);
Mlt::Profile *thumbProfile();
/** @brief Returns the current project duration */
int projectDuration() const;
/** @brief Returns true if current project has some rendered timeline preview */
bool hasTimelinePreview() const;
/** @brief Returns current timeline cursor position */
int getTimelinePosition() const;
/** @brief Handles audio and video capture **/
void startMediaCapture(int tid, bool, bool);
void stopMediaCapture(int tid, bool, bool);
QStringList getAudioCaptureDevices();
int getMediaCaptureState();
bool isMediaCapturing();
MediaCapture *getAudioDevice();
/** @brief Returns Project Folder name for capture output location */
QString getProjectFolderName();
/** @brief Returns a timeline clip's bin id */
QString getTimelineClipBinId(int cid);
/** @brief Returns a frame duration from a timecode */
int getDurationFromString(const QString &time);
/** @brief An error occured within a filter, inform user */
void processInvalidFilter(const QString service, const QString id, const QString message);
/** @brief Update current project's tags */
void updateProjectTags(QMap tags);
+ Mlt::Profile *getProjectProfile();
+ /** @brief Update MLT's preview profile */
+ void updatePreviewProfile();
private:
explicit Core();
static std::unique_ptr m_self;
/** @brief Makes sure Qt's locale and system locale settings match. */
void initLocale();
MainWindow *m_mainWindow{nullptr};
ProjectManager *m_projectManager{nullptr};
MonitorManager *m_monitorManager{nullptr};
std::shared_ptr m_projectItemModel;
std::shared_ptr m_jobManager;
Bin *m_binWidget{nullptr};
LibraryWidget *m_library{nullptr};
MixerManager *m_mixerWidget{nullptr};
/** @brief Current project's profile path */
QString m_currentProfile;
QString m_profile;
std::unique_ptr m_thumbProfile;
+ /** @brief Mlt profile used in project / monitors */
+ std::unique_ptr m_previewProfile;
bool m_guiConstructed = false;
/** @brief Check that the profile is valid (width is a multiple of 8 and height a multiple of 2 */
void checkProfileValidity();
std::unique_ptr m_capture;
QUrl m_mediaCaptureFile;
-
QMutex m_thumbProfileMutex;
public slots:
void triggerAction(const QString &name);
/** @brief display a user info/warning message in the project bin */
void displayBinMessage(const QString &text, int type, const QList &actions = QList());
void displayBinLogMessage(const QString &text, int type, const QString &logInfo);
/** @brief Create small thumbnails for luma used in compositions */
void buildLumaThumbs(const QStringList &values);
signals:
void coreIsReady();
void updateLibraryPath();
+ void updateMonitorProfile();
/** @brief Call config dialog on a selected page / tab */
void showConfigDialog(int, int);
void finalizeRecording(const QString &captureFile);
void autoScrollChanged();
};
#endif
diff --git a/src/effects/effectsrepository.cpp b/src/effects/effectsrepository.cpp
index 448d441bd..102ea674d 100644
--- a/src/effects/effectsrepository.cpp
+++ b/src/effects/effectsrepository.cpp
@@ -1,344 +1,344 @@
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "effectsrepository.hpp"
#include "core.h"
#include "kdenlivesettings.h"
#include "profiles/profilemodel.hpp"
#include "xml/xml.hpp"
#include
#include
#include
#include
#include
#include
std::unique_ptr EffectsRepository::instance;
std::once_flag EffectsRepository::m_onceFlag;
EffectsRepository::EffectsRepository()
: AbstractAssetsRepository()
{
init();
// Check that our favorite effects are valid
QStringList invalidEffect;
for (const QString &effect : KdenliveSettings::favorite_effects()) {
if (!exists(effect)) {
invalidEffect << effect;
}
}
if (!invalidEffect.isEmpty()) {
pCore->displayMessage(i18n("Some of your favorite effects are invalid and were removed: %1", invalidEffect.join(QLatin1Char(','))), ErrorMessage);
QStringList newFavorites = KdenliveSettings::favorite_effects();
for (const QString &effect : invalidEffect) {
newFavorites.removeAll(effect);
}
KdenliveSettings::setFavorite_effects(newFavorites);
}
}
Mlt::Properties *EffectsRepository::retrieveListFromMlt() const
{
return pCore->getMltRepository()->filters();
}
Mlt::Properties *EffectsRepository::getMetadata(const QString &effectId) const
{
return pCore->getMltRepository()->metadata(filter_type, effectId.toLatin1().data());
}
void EffectsRepository::parseCustomAssetFile(const QString &file_name, std::unordered_map &customAssets) const
{
QFile file(file_name);
QDomDocument doc;
doc.setContent(&file, false);
file.close();
QDomElement base = doc.documentElement();
if (base.tagName() == QLatin1String("effectgroup")) {
QDomNodeList effects = base.elementsByTagName(QStringLiteral("effect"));
if (effects.count() != 1) {
qDebug() << "Error: found unsupported effect group" << base.attribute(QStringLiteral("name"))<<" : "< 0) {
qDebug() << "Warning: duplicate custom definition of effect" << result.id << "found. Only last one will be considered. Duplicate found in"
<< file_name;
}
result.xml = currentEffect;
// Parse type information.
// Video effect by default
result.type = EffectType::Video;
QString type = currentEffect.attribute(QStringLiteral("type"), QString());
if (type == QLatin1String("audio")) {
result.type = EffectType::Audio;
} else if (type == QLatin1String("customVideo")) {
result.type = EffectType::Custom;
} else if (type == QLatin1String("customAudio")) {
result.type = EffectType::CustomAudio;
} else if (type == QLatin1String("hidden")) {
result.type = EffectType::Hidden;
} else if (type == QLatin1String("custom")) {
// Old type effect, update to customVideo / customAudio
const QString effectTag = currentEffect.attribute(QStringLiteral("tag"));
QScopedPointer metadata(getMetadata(effectTag));
if (metadata && metadata->is_valid()) {
Mlt::Properties tags((mlt_properties)metadata->get_data("tags"));
if (QString(tags.get(0)) == QLatin1String("Audio")) {
result.type = EffectType::CustomAudio;
currentEffect.setAttribute(QStringLiteral("type"), QStringLiteral("customAudio"));
} else {
result.type = EffectType::Custom;
currentEffect.setAttribute(QStringLiteral("type"), QStringLiteral("customVideo"));
}
QFile effectFile(file_name);
if (effectFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
effectFile.write(doc.toString().toUtf8());
}
file.close();
}
}
customAssets[result.id] = result;
}
}
std::unique_ptr &EffectsRepository::get()
{
std::call_once(m_onceFlag, [] { instance.reset(new EffectsRepository()); });
return instance;
}
QStringList EffectsRepository::assetDirs() const
{
return QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("effects"), QStandardPaths::LocateDirectory);
}
void EffectsRepository::parseType(QScopedPointer &metadata, Info &res)
{
res.type = EffectType::Video;
Mlt::Properties tags((mlt_properties)metadata->get_data("tags"));
if (QString(tags.get(0)) == QLatin1String("Audio")) {
res.type = EffectType::Audio;
}
}
QString EffectsRepository::assetBlackListPath() const
{
return QStringLiteral(":data/blacklisted_effects.txt");
}
QString EffectsRepository::assetPreferredListPath() const
{
return QStringLiteral(":data/preferred_effects.txt");
}
bool EffectsRepository::isPreferred(const QString &effectId) const
{
return m_preferred_list.contains(effectId);
}
std::unique_ptr EffectsRepository::getEffect(const QString &effectId) const
{
Q_ASSERT(exists(effectId));
QString service_name = m_assets.at(effectId).mltId;
// We create the Mlt element from its name
- auto filter = std::make_unique(pCore->getCurrentProfile()->profile(), service_name.toLatin1().constData(), nullptr);
+ auto filter = std::make_unique(*pCore->getProjectProfile(), service_name.toLatin1().constData(), nullptr);
return filter;
}
bool EffectsRepository::hasInternalEffect(const QString &effectId) const
{
// Retrieve the list of MLT's available assets.
QScopedPointer assets(retrieveListFromMlt());
int max = assets->count();
for (int i = 0; i < max; ++i) {
if (assets->get_name(i) == effectId) {
return true;
}
}
return false;
}
QPair EffectsRepository::reloadCustom(const QString &path)
{
std::unordered_map customAssets;
parseCustomAssetFile(path, customAssets);
QPair result;
// TODO: handle files with several effects
for (const auto &custom : customAssets) {
// Custom assets should override default ones
m_assets[custom.first] = custom.second;
result.first = custom.first;
result.second = custom.second.mltId;
}
return result;
}
QPair EffectsRepository::fixDeprecatedEffects()
{
QString customAssetDir = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("effects"), QStandardPaths::LocateDirectory);
QPair results;
QDir current_dir(customAssetDir);
QStringList filter;
filter << QStringLiteral("*.xml");
QStringList fileList = current_dir.entryList(filter, QDir::Files);
QStringList failed;
for (const auto &file : fileList) {
QString path = current_dir.absoluteFilePath(file);
QPair fixResult = fixCustomAssetFile(path);
if (!fixResult.first.isEmpty()) {
results.first << fixResult.first;
} else if (!fixResult.second.isEmpty()) {
results.second << fixResult.second;
}
}
return results;
}
QPair EffectsRepository::fixCustomAssetFile(const QString &path)
{
QPair results;
QFile file(path);
QDomDocument doc;
doc.setContent(&file, false);
file.close();
QDomElement base = doc.documentElement();
if (base.tagName() == QLatin1String("effectgroup")) {
// Groups not implemented
return results;
}
QDomNodeList effects = doc.elementsByTagName(QStringLiteral("effect"));
int nbr_effect = effects.count();
if (nbr_effect == 0) {
qDebug() << "+++++++++++++\nEffect broken: " << path << "\n+++++++++++";
results.second = path;
return results;
}
bool effectAdjusted = false;
for (int i = 0; i < nbr_effect; ++i) {
QDomNode currentNode = effects.item(i);
if (currentNode.isNull()) {
continue;
}
QDomElement currentEffect = currentNode.toElement();
Info result;
bool ok = parseInfoFromXml(currentEffect, result);
if (!ok) {
continue;
}
if (currentEffect.hasAttribute(QLatin1String("kdenlive_info"))) {
// This is a pre 19.x custom effect, adjust param values
// First backup effect in legacy folder
QDir dir(QFileInfo(path).absoluteDir());
if (!dir.mkpath(QStringLiteral("legacy"))) {
// Cannot create the legacy folder, abort
qDebug()<<" = = = Could not create legacy folder in : "<. *
***************************************************************************/
#include "loadjob.hpp"
#include "bin/projectclip.h"
#include "bin/projectfolder.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "doc/kdenlivedoc.h"
#include "doc/kthumb.h"
#include "kdenlivesettings.h"
#include "klocalizedstring.h"
#include "macros.hpp"
#include "profiles/profilemodel.hpp"
#include "project/dialogs/slideshowclip.h"
#include "monitor/monitor.h"
#include "xml/xml.hpp"
#include
#include
#include
#include
#include
LoadJob::LoadJob(const QString &binId, const QDomElement &xml, const std::function &readyCallBack)
: AbstractClipJob(LOADJOB, binId)
, m_xml(xml)
, m_readyCallBack(readyCallBack)
{
}
const QString LoadJob::getDescription() const
{
return i18n("Loading clip %1", m_clipId);
}
namespace {
ClipType::ProducerType getTypeForService(const QString &id, const QString &path)
{
if (id.isEmpty()) {
QString ext = path.section(QLatin1Char('.'), -1);
if (ext == QLatin1String("mlt") || ext == QLatin1String("kdenlive")) {
return ClipType::Playlist;
}
return ClipType::Unknown;
}
if (id == QLatin1String("color") || id == QLatin1String("colour")) {
return ClipType::Color;
}
if (id == QLatin1String("kdenlivetitle")) {
return ClipType::Text;
}
if (id == QLatin1String("qtext")) {
return ClipType::QText;
}
if (id == QLatin1String("xml") || id == QLatin1String("consumer")) {
return ClipType::Playlist;
}
if (id == QLatin1String("webvfx")) {
return ClipType::WebVfx;
}
return ClipType::Unknown;
}
// Read the properties of the xml and pass them to the producer. Note that some properties like resource are ignored
void processProducerProperties(const std::shared_ptr &prod, const QDomElement &xml)
{
// TODO: there is some duplication with clipcontroller > updateproducer that also copies properties
QString value;
QStringList internalProperties;
internalProperties << QStringLiteral("bypassDuplicate") << QStringLiteral("resource") << QStringLiteral("mlt_service") << QStringLiteral("audio_index")
<< QStringLiteral("video_index") << QStringLiteral("mlt_type") << QStringLiteral("length");
QDomNodeList props;
if (xml.tagName() == QLatin1String("producer")) {
props = xml.childNodes();
} else {
props = xml.firstChildElement(QStringLiteral("producer")).childNodes();
}
for (int i = 0; i < props.count(); ++i) {
if (props.at(i).toElement().tagName() != QStringLiteral("property")) {
continue;
}
QString propertyName = props.at(i).toElement().attribute(QStringLiteral("name"));
if (!internalProperties.contains(propertyName) && !propertyName.startsWith(QLatin1Char('_'))) {
value = props.at(i).firstChild().nodeValue();
if (propertyName.startsWith(QLatin1String("kdenlive-force."))) {
// this is a special forced property, pass it
propertyName.remove(0, 15);
}
prod->set(propertyName.toUtf8().constData(), value.toUtf8().constData());
}
}
}
} // namespace
// static
std::shared_ptr LoadJob::loadResource(QString resource, const QString &type)
{
if (!resource.startsWith(type)) {
resource.prepend(type);
}
- return std::make_shared(pCore->getCurrentProfile()->profile(), nullptr, resource.toUtf8().constData());
+ return std::make_shared(*pCore->getProjectProfile(), nullptr, resource.toUtf8().constData());
}
std::shared_ptr