diff --git a/CMakeLists.txt b/CMakeLists.txt
index b36307204..ca98f9fc2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,179 +1,179 @@
project(Kdenlive)
# An odd patch version number means development version, while an even one means
# stable release. An additional number can be used for bugfix-only releases.
# KDE Application Version, managed by release script
set(KDE_APPLICATIONS_VERSION_MAJOR "19")
set(KDE_APPLICATIONS_VERSION_MINOR "03")
set(KDE_APPLICATIONS_VERSION_MICRO "70")
set(KDENLIVE_VERSION ${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO})
cmake_minimum_required(VERSION 3.0)
if(POLICY CMP0063)
cmake_policy(SET CMP0063 NEW)
endif()
if(POLICY CMP0053)
cmake_policy(SET CMP0053 NEW)
endif()
if(BUILD_FUZZING)
set(CMAKE_CXX_FLAGS "${KDENLIVE_CXX_FLAGS} -fsanitize=fuzzer-no-link,address")
endif()
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-warning-option")
endif()
# To be switched on when releasing.
option(RELEASE_BUILD "Remove Git revision from program version" ON)
option(BUILD_TESTING "Build tests" ON)
option(BUILD_FUZZING "Build fuzzing target" OFF)
# Minimum versions of main dependencies.
set(MLT_MIN_MAJOR_VERSION 6)
set(MLT_MIN_MINOR_VERSION 12)
set(MLT_MIN_PATCH_VERSION 0)
set(MLT_MIN_VERSION ${MLT_MIN_MAJOR_VERSION}.${MLT_MIN_MINOR_VERSION}.${MLT_MIN_PATCH_VERSION})
# KDE Frameworks
find_package(ECM 5.18.0 REQUIRED CONFIG)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(FeatureSummary)
include(ECMInstallIcons)
include(GenerateExportHeader)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(ECMOptionalAddSubdirectory)
include(ECMMarkNonGuiExecutable)
include(ECMAddAppIcon)
include(ECMQtDeclareLoggingCategory)
include(ECMEnableSanitizers)
add_definitions(-DTRANSLATION_DOMAIN=\"kdenlive\")
find_package(KF5 REQUIRED COMPONENTS Archive Bookmarks CoreAddons Config ConfigWidgets
DBusAddons KIO WidgetsAddons NotifyConfig NewStuff XmlGui Notifications GuiAddons TextWidgets IconThemes Declarative Solid
OPTIONAL_COMPONENTS DocTools FileMetaData Crash Purpose)
# Qt
set(QT_MIN_VERSION 5.7.0)
find_package(Qt5 REQUIRED COMPONENTS Core DBus Widgets Svg Quick Concurrent QuickWidgets Multimedia)
find_package(Qt5 OPTIONAL_COMPONENTS WebKitWidgets QUIET)
add_definitions(-DQT_NO_CAST_TO_ASCII -DQT_NO_URL_CAST_FROM_STRING)
set(DEFAULT_CXX_FLAGS "${DEFAULT_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")
# MLT
find_package(MLT ${MLT_MIN_VERSION} REQUIRED)
set_package_properties(MLT PROPERTIES DESCRIPTION "Multimedia framework"
URL "https://mltframework.org"
PURPOSE "Required to do video processing")
message(STATUS "Found MLT++: ${MLTPP_LIBRARIES}")
# Windows
include(CheckIncludeFiles)
check_include_files(malloc.h HAVE_MALLOC_H)
check_include_files(pthread.h HAVE_PTHREAD_H)
if(WIN32)
find_package(DrMinGW)
set(MLT_PREFIX "..")
else()
set(MLT_PREFIX ${MLT_ROOT_DIR})
endif()
# Optional deps status
find_package(KF5 5.23.0 OPTIONAL_COMPONENTS XmlGui QUIET)
if(KF5XmlGui_FOUND)
message(STATUS "Found KF5 >= 5.23.0 enabling icon coloring")
else()
message(STATUS "KF5 < 5.23.0 Disable icon coloring")
set(KF5_ICON_COMPATIBILITY TRUE)
endif()
if(KF5FileMetaData_FOUND)
message(STATUS "Found KF5 FileMetadata to extract file metadata")
set(KF5_FILEMETADATA TRUE)
else()
message(STATUS "KF5 FileMetadata not found, file metadata will not be available")
endif()
if(KF5Purpose_FOUND)
message(STATUS "Found KF5 Purpose, filesharing enabled")
set(KF5_PURPOSE TRUE)
else()
message(STATUS "KF5 Purpose not found, filesharing disabled")
endif()
if(KF5DocTools_FOUND)
add_subdirectory(doc)
kdoctools_install(po)
endif()
# Get current version.
set(KDENLIVE_VERSION_STRING "${KDENLIVE_VERSION}")
if(NOT RELEASE_BUILD AND EXISTS ${CMAKE_SOURCE_DIR}/.git)
# Probably a Git workspace; determine the revision.
find_package(Git QUIET)
if(GIT_FOUND)
exec_program(${GIT_EXECUTABLE} ${CMAKE_SOURCE_DIR}
ARGS "log -n 1 --pretty=format:\"%h\""
OUTPUT_VARIABLE KDENLIVE_GIT_REVISION)
message(STATUS "Kdenlive Git revision: ${KDENLIVE_GIT_REVISION}")
set(KDENLIVE_VERSION_STRING "${KDENLIVE_VERSION} (rev. ${KDENLIVE_GIT_REVISION})")
else()
message(STATUS "Kdenlive Git revision could not be determined")
endif()
endif()
find_package(RTTR 0.9.6 QUIET)
if(NOT RTTR_FOUND)
- message("RTTR not found on system, will download source and build it")
+ message(STATUS "RTTR not found on system, will download source and build it")
include(rttr.CMakeLists.txt)
endif()
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
set(FFMPEG_SUFFIX "" CACHE STRING "FFmpeg custom suffix")
configure_file(config-kdenlive.h.cmake config-kdenlive.h @ONLY)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wno-suggest-override")
# Sources
add_subdirectory(src)
add_subdirectory(renderer)
add_subdirectory(thumbnailer)
add_subdirectory(data)
ki18n_install(po)
include(GNUInstallDirs)
install(FILES AUTHORS COPYING README.md DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(FILES kdenlive.categories DESTINATION ${KDE_INSTALL_CONFDIR})
############################
# Tests
############################
if(BUILD_TESTING)
message(STATUS "Building tests")
add_subdirectory(tests)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fexceptions")
include_directories(
${CMAKE_BINARY_DIR}
${CMAKE_BINARY_DIR}/src
${MLT_INCLUDE_DIR}
${MLTPP_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/lib/external
${CMAKE_CURRENT_SOURCE_DIR}/lib
src)
add_executable(runTests ${Tests_SRCS})
set_property(TARGET runTests PROPERTY CXX_STANDARD 14)
target_link_libraries(runTests kdenliveLib)
add_test(runTests runTests -d yes)
endif()
if(BUILD_FUZZING)
message(STATUS "Building fuzzing")
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDENLIVE_CXX_FLAGS} -fsanitize=fuzzer-no-link,address")
add_subdirectory(fuzzer)
endif()
diff --git a/src/assets/keyframes/model/keyframemodellist.cpp b/src/assets/keyframes/model/keyframemodellist.cpp
index b73e275ad..2f9aba05e 100644
--- a/src/assets/keyframes/model/keyframemodellist.cpp
+++ b/src/assets/keyframes/model/keyframemodellist.cpp
@@ -1,447 +1,446 @@
/***************************************************************************
* 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 "keyframemodellist.hpp"
#include "assets/model/assetcommand.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "core.h"
#include "doc/docundostack.hpp"
#include "keyframemodel.hpp"
#include "klocalizedstring.h"
#include "macros.hpp"
#include
#include
#include
KeyframeModelList::KeyframeModelList(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack)
: m_model(std::move(model))
, m_undoStack(std::move(undo_stack))
, m_lock(QReadWriteLock::Recursive)
{
qDebug() << "Construct keyframemodellist. Checking model:" << m_model.expired();
addParameter(index);
connect(m_parameters.begin()->second.get(), &KeyframeModel::modelChanged, this, &KeyframeModelList::modelChanged);
}
ObjectId KeyframeModelList::getOwnerId() const
{
if (auto ptr = m_model.lock()) {
return ptr->getOwnerId();
}
return {};
}
void KeyframeModelList::addParameter(const QModelIndex &index)
{
std::shared_ptr parameter(new KeyframeModel(m_model, index, m_undoStack));
m_parameters.insert({index, std::move(parameter)});
}
bool KeyframeModelList::applyOperation(const std::function, Fun &, Fun &)> &op, const QString &undoString)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = true;
for (const auto ¶m : m_parameters) {
res = op(param.second, undo, redo);
if (!res) {
bool undone = undo();
Q_ASSERT(undone);
return res;
}
}
if (res && !undoString.isEmpty()) {
PUSH_UNDO(undo, redo, undoString);
}
return res;
}
bool KeyframeModelList::addKeyframe(GenTime pos, KeyframeType type)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
bool update = (m_parameters.begin()->second->hasKeyframe(pos) > 0);
auto op = [pos, type](std::shared_ptr param, Fun &undo, Fun &redo) {
QVariant value = param->getInterpolatedValue(pos);
return param->addKeyframe(pos, type, value, true, undo, redo);
};
return applyOperation(op, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
}
bool KeyframeModelList::addKeyframe(int frame, double val)
{
QWriteLocker locker(&m_lock);
GenTime pos(frame, pCore->getCurrentFps());
Q_ASSERT(m_parameters.size() > 0);
bool update = (m_parameters.begin()->second->hasKeyframe(pos) > 0);
bool isRectParam = false;
if (m_inTimelineIndex.isValid()) {
if (auto ptr = m_model.lock()) {
auto tp = ptr->data(m_inTimelineIndex, AssetParameterModel::TypeRole).value();
if (tp == ParamType::AnimatedRect) {
isRectParam = true;
}
}
}
auto op = [this, pos, val, isRectParam](std::shared_ptr param, Fun &undo, Fun &redo) {
QVariant value;
if (m_inTimelineIndex.isValid()) {
if (m_parameters.at(m_inTimelineIndex) == param) {
if (isRectParam) {
value = param->getInterpolatedValue(pos);
value = param->updateInterpolated(value, val);
} else {
value = param->getNormalizedValue(val);
}
} else {
value = param->getInterpolatedValue(pos);
}
} else if (m_parameters.begin()->second == param) {
value = param->getNormalizedValue(val);
} else {
value = param->getInterpolatedValue(pos);
}
return param->addKeyframe(pos, (KeyframeType)KdenliveSettings::defaultkeyframeinterp(), value, true, undo, redo);
};
return applyOperation(op, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
}
bool KeyframeModelList::removeKeyframe(GenTime pos)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
auto op = [pos](std::shared_ptr param, Fun &undo, Fun &redo) { return param->removeKeyframe(pos, undo, redo); };
return applyOperation(op, i18n("Delete keyframe"));
}
bool KeyframeModelList::removeAllKeyframes()
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
auto op = [](std::shared_ptr param, Fun &undo, Fun &redo) { return param->removeAllKeyframes(undo, redo); };
return applyOperation(op, i18n("Delete all keyframes"));
}
bool KeyframeModelList::removeNextKeyframes(GenTime pos)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
auto op = [pos](std::shared_ptr param, Fun &undo, Fun &redo) { return param->removeNextKeyframes(pos, undo, redo); };
return applyOperation(op, i18n("Delete keyframes"));
}
bool KeyframeModelList::moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
auto op = [oldPos, pos](std::shared_ptr param, Fun &undo, Fun &redo) { return param->moveKeyframe(oldPos, pos, QVariant(), undo, redo); };
return applyOperation(op, logUndo ? i18n("Move keyframe") : QString());
}
bool KeyframeModelList::updateKeyframe(GenTime oldPos, GenTime pos, const QVariant &normalizedVal, bool logUndo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
bool isRectParam = false;
if (m_inTimelineIndex.isValid()) {
if (auto ptr = m_model.lock()) {
auto tp = ptr->data(m_inTimelineIndex, AssetParameterModel::TypeRole).value();
if (tp == ParamType::AnimatedRect) {
isRectParam = true;
}
}
}
auto op = [this, oldPos, pos, normalizedVal, isRectParam](std::shared_ptr param, Fun &undo, Fun &redo) {
QVariant value;
if (m_inTimelineIndex.isValid()) {
if (m_parameters.at(m_inTimelineIndex) == param) {
if (isRectParam) {
if (normalizedVal.isValid()) {
value = param->getInterpolatedValue(oldPos);
value = param->updateInterpolated(value, normalizedVal.toDouble());
}
} else {
value = normalizedVal;
}
}
} else if (m_parameters.begin()->second == param) {
value = normalizedVal;
}
return param->moveKeyframe(oldPos, pos, value, undo, redo);
};
return applyOperation(op, logUndo ? i18n("Move keyframe") : QString());
}
bool KeyframeModelList::updateKeyframe(GenTime pos, const QVariant &value, const QPersistentModelIndex &index)
{
if (singleKeyframe()) {
bool ok = false;
Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok);
pos = kf.first;
}
if (auto ptr = m_model.lock()) {
auto *command = new AssetKeyframeCommand(ptr, index, value, pos);
pCore->pushUndo(command);
}
return true;
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.count(index) > 0);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
if (singleKeyframe()) {
bool ok = false;
Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok);
pos = kf.first;
}
bool res = m_parameters.at(index)->updateKeyframe(pos, value, undo, redo);
if (res) {
PUSH_UNDO(undo, redo, i18n("Update keyframe"));
}
return res;
}
bool KeyframeModelList::updateKeyframeType(GenTime pos, int type, const QPersistentModelIndex &index)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.count(index) > 0);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
if (singleKeyframe()) {
bool ok = false;
Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok);
pos = kf.first;
}
// Update kf type in all parameters
bool res = true;
for (const auto ¶m : m_parameters) {
res = res && param.second->updateKeyframeType(pos, type, undo, redo);
}
if (res) {
PUSH_UNDO(undo, redo, i18n("Update keyframe"));
}
return res;
}
KeyframeType KeyframeModelList::keyframeType(GenTime pos) const
{
QWriteLocker locker(&m_lock);
if (singleKeyframe()) {
bool ok = false;
Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok);
return kf.second;
}
bool ok = false;
Keyframe kf = m_parameters.begin()->second->getKeyframe(pos, &ok);
return kf.second;
}
Keyframe KeyframeModelList::getKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->getKeyframe(pos, ok);
}
bool KeyframeModelList::singleKeyframe() const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->singleKeyframe();
}
bool KeyframeModelList::isEmpty() const
{
READ_LOCK();
return (m_parameters.size() == 0 || m_parameters.begin()->second->rowCount() == 0);
}
Keyframe KeyframeModelList::getNextKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->getNextKeyframe(pos, ok);
}
Keyframe KeyframeModelList::getPrevKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->getPrevKeyframe(pos, ok);
}
Keyframe KeyframeModelList::getClosestKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->getClosestKeyframe(pos, ok);
}
bool KeyframeModelList::hasKeyframe(int frame) const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->hasKeyframe(frame);
}
void KeyframeModelList::refresh()
{
QWriteLocker locker(&m_lock);
for (const auto ¶m : m_parameters) {
param.second->refresh();
}
}
void KeyframeModelList::reset()
{
QWriteLocker locker(&m_lock);
for (const auto ¶m : m_parameters) {
param.second->reset();
}
}
QVariant KeyframeModelList::getInterpolatedValue(int pos, const QPersistentModelIndex &index) const
{
READ_LOCK();
Q_ASSERT(m_parameters.count(index) > 0);
return m_parameters.at(index)->getInterpolatedValue(pos);
}
KeyframeModel *KeyframeModelList::getKeyModel()
{
if (m_inTimelineIndex.isValid()) {
return m_parameters.at(m_inTimelineIndex).get();
}
if (auto ptr = m_model.lock()) {
for (const auto ¶m : m_parameters) {
if (ptr->data(param.first, AssetParameterModel::ShowInTimelineRole) == true) {
m_inTimelineIndex = param.first;
return param.second.get();
}
}
}
return nullptr;
}
KeyframeModel *KeyframeModelList::getKeyModel(const QPersistentModelIndex &index)
{
if (m_parameters.size() > 0) {
return m_parameters.at(index).get();
}
return nullptr;
}
void KeyframeModelList::resizeKeyframes(int oldIn, int oldOut, int in, int out, int offset, bool adjustFromEnd, Fun &undo, Fun &redo)
{
bool ok;
bool ok2;
QList positions;
if (!adjustFromEnd) {
if (offset != 0) {
// this is an endless resize clip
GenTime old_in(oldIn, pCore->getCurrentFps());
Keyframe kf = getKeyframe(old_in, &ok);
- KeyframeType type = kf.second;
GenTime new_in(in + offset, pCore->getCurrentFps());
getKeyframe(new_in, &ok2);
positions = m_parameters.begin()->second->getKeyframePos();
std::sort(positions.begin(), positions.end());
for (const auto ¶m : m_parameters) {
if (offset > 0) {
QVariant value = param.second->getInterpolatedValue(new_in);
param.second->updateKeyframe(old_in, value, undo, redo);
}
for (auto frame : positions) {
if (new_in > GenTime()) {
if (frame > new_in) {
param.second->moveKeyframe(frame, frame - new_in, QVariant(), undo, redo);
continue;
}
} else if (frame > GenTime()) {
param.second->moveKeyframe(frame, frame - new_in, QVariant(), undo, redo);
continue;
}
if (frame != GenTime()) {
param.second->removeKeyframe(frame, undo, redo);
}
}
}
} else {
GenTime old_in(oldIn, pCore->getCurrentFps());
GenTime new_in(in, pCore->getCurrentFps());
Keyframe kf = getKeyframe(old_in, &ok);
KeyframeType type = kf.second;
getKeyframe(new_in, &ok2);
// Check keyframes after last position
if (ok && !ok2 && oldIn != 0) {
positions << old_in;
} else if (in == 0 && oldIn != 0 && ok && ok2) {
// We moved start to 0. As the 0 keyframe is always here, simply remove old position
for (const auto ¶m : m_parameters) {
param.second->removeKeyframe(old_in, undo, redo);
}
}
// qDebug()<<"/// \n\nKEYS TO DELETE: "<getInterpolatedValue(new_in);
param.second->addKeyframe(new_in, type, value, true, undo, redo);
for (auto frame : positions) {
param.second->removeKeyframe(frame, undo, redo);
}
}
}
}
} else {
GenTime old_out(oldOut, pCore->getCurrentFps());
GenTime new_out(out, pCore->getCurrentFps());
Keyframe kf = getKeyframe(old_out, &ok);
KeyframeType type = kf.second;
getKeyframe(new_out, &ok2);
// Check keyframes after last position
bool ok3;
Keyframe toDel = getNextKeyframe(new_out, &ok3);
if (ok && !ok2) {
positions << old_out;
}
if (toDel.first == GenTime()) {
// No keyframes
return;
}
while (ok3) {
if (!positions.contains(toDel.first)) {
positions << toDel.first;
}
toDel = getNextKeyframe(toDel.first, &ok3);
}
if ((ok || positions.size() > 0) && !ok2) {
for (const auto ¶m : m_parameters) {
QVariant value = param.second->getInterpolatedValue(new_out);
param.second->addKeyframe(new_out, type, value, true, undo, redo);
for (auto frame : positions) {
param.second->removeKeyframe(frame, undo, redo);
}
}
}
}
}
diff --git a/src/assets/view/assetparameterview.cpp b/src/assets/view/assetparameterview.cpp
index d2d507788..679c246f9 100644
--- a/src/assets/view/assetparameterview.cpp
+++ b/src/assets/view/assetparameterview.cpp
@@ -1,337 +1,337 @@
/***************************************************************************
* 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 "assetparameterview.hpp"
#include "assets/model/assetcommand.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "assets/view/widgets/abstractparamwidget.hpp"
#include "assets/view/widgets/keyframewidget.hpp"
#include "core.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
AssetParameterView::AssetParameterView(QWidget *parent)
: QWidget(parent)
{
m_lay = new QVBoxLayout(this);
m_lay->setContentsMargins(0, 0, 0, 2);
m_lay->setSpacing(0);
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
// Presets Combo
m_presetMenu = new QMenu(this);
}
void AssetParameterView::setModel(const std::shared_ptr &model, QSize frameSize, bool addSpacer)
{
unsetModel();
QMutexLocker lock(&m_lock);
m_model = model;
const QString paramTag = model->getAssetId();
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(paramTag));
connect(this, &AssetParameterView::updatePresets, [this, presetFile](const QString &presetName) {
m_presetMenu->clear();
m_presetGroup.reset(new QActionGroup(this));
m_presetGroup->setExclusive(true);
m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reset Effect"), this, SLOT(resetValues()));
// Save preset
m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save-as-template")), i18n("Save preset"), this, SLOT(slotSavePreset()));
m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save-as-template")), i18n("Update current preset"), this, SLOT(slotUpdatePreset()));
m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete preset"), this, SLOT(slotDeletePreset()));
m_presetMenu->addSeparator();
QStringList presets = m_model->getPresetList(presetFile);
for (const QString &pName : presets) {
QAction *ac = m_presetMenu->addAction(pName, this, SLOT(slotLoadPreset()));
m_presetGroup->addAction(ac);
ac->setData(pName);
ac->setCheckable(true);
if (pName == presetName) {
ac->setChecked(true);
}
}
});
emit updatePresets();
connect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh);
if (paramTag.endsWith(QStringLiteral("lift_gamma_gain"))) {
// Special case, the colorwheel widget manages several parameters
QModelIndex index = model->index(0, 0);
auto w = AbstractParamWidget::construct(model, index, frameSize, this);
connect(w, &AbstractParamWidget::valueChanged, this, &AssetParameterView::commitChanges);
m_lay->addWidget(w);
m_widgets.push_back(w);
} else {
for (int i = 0; i < model->rowCount(); ++i) {
QModelIndex index = model->index(i, 0);
auto type = model->data(index, AssetParameterModel::TypeRole).value();
if (m_mainKeyframeWidget &&
(type == ParamType::Geometry || type == ParamType::Animated || type == ParamType::RestrictedAnim || type == ParamType::KeyframeParam)) {
// Keyframe widget can have some extra params that shouldn't build a new widget
qDebug() << "// FOUND ADDED PARAM";
m_mainKeyframeWidget->addParameter(index);
} else {
auto w = AbstractParamWidget::construct(model, index, frameSize, this);
connect(this, &AssetParameterView::initKeyframeView, w, &AbstractParamWidget::slotInitMonitor);
if (type == ParamType::KeyframeParam || type == ParamType::AnimatedRect || type == ParamType::Roto_spline) {
m_mainKeyframeWidget = static_cast(w);
}
connect(w, &AbstractParamWidget::valueChanged, this, &AssetParameterView::commitChanges);
connect(w, &AbstractParamWidget::seekToPos, this, &AssetParameterView::seekToPos);
m_lay->addWidget(w);
m_widgets.push_back(w);
}
}
}
if (addSpacer) {
m_lay->addStretch();
}
}
QVector> AssetParameterView::getDefaultValues() const
{
QLocale locale;
QVector> values;
for (int i = 0; i < m_model->rowCount(); ++i) {
QModelIndex index = m_model->index(i, 0);
QString name = m_model->data(index, AssetParameterModel::NameRole).toString();
auto type = m_model->data(index, AssetParameterModel::TypeRole).value();
QVariant defaultValue = m_model->data(index, AssetParameterModel::DefaultRole);
if (type == ParamType::KeyframeParam || type == ParamType::AnimatedRect) {
QString val = type == ParamType::KeyframeParam ? locale.toString(defaultValue.toDouble()) : defaultValue.toString();
if (!val.contains(QLatin1Char('='))) {
val.prepend(QStringLiteral("%1=").arg(m_model->data(index, AssetParameterModel::ParentInRole).toInt()));
defaultValue = QVariant(val);
}
}
values.append({name, defaultValue});
}
return values;
}
void AssetParameterView::resetValues()
{
const QVector> values = getDefaultValues();
auto *command = new AssetUpdateCommand(m_model, values);
pCore->pushUndo(command);
/*if (m_mainKeyframeWidget) {
m_mainKeyframeWidget->resetKeyframes();
}*/
}
void AssetParameterView::commitChanges(const QModelIndex &index, const QString &value, bool storeUndo)
{
// Warning: please note that some widgets (for example keyframes) do NOT send the valueChanged signal and do modifications on their own
auto *command = new AssetCommand(m_model, index, value);
if (storeUndo) {
pCore->pushUndo(command);
} else {
command->redo();
delete command;
}
}
void AssetParameterView::unsetModel()
{
QMutexLocker lock(&m_lock);
if (m_model) {
// if a model is already there, we have to disconnect signals first
disconnect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh);
}
m_mainKeyframeWidget = nullptr;
// clear layout
m_widgets.clear();
QLayoutItem *child;
while ((child = m_lay->takeAt(0)) != nullptr) {
if (child->layout()) {
QLayoutItem *subchild;
while ((subchild = child->layout()->takeAt(0)) != nullptr) {
delete subchild->widget();
delete subchild->spacerItem();
}
}
delete child->widget();
delete child->spacerItem();
}
// Release ownership of smart pointer
m_model.reset();
}
void AssetParameterView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles)
{
QMutexLocker lock(&m_lock);
if (m_widgets.size() == 0) {
// no visible param for this asset, abort
return;
}
Q_UNUSED(roles);
// We are expecting indexes that are children of the root index, which is "invalid"
Q_ASSERT(!topLeft.parent().isValid());
// We make sure the range is valid
if (m_mainKeyframeWidget) {
m_mainKeyframeWidget->slotRefresh();
} else {
auto type = m_model->data(m_model->index(topLeft.row(), 0), AssetParameterModel::TypeRole).value();
if (type == ParamType::ColorWheel) {
// Some special widgets, like colorwheel handle multiple params so we can have cases where param index row is greater than the number of widgets.
// Should be better managed
m_widgets[0]->slotRefresh();
return;
}
- size_t max;
+ int max;
if (!bottomRight.isValid()) {
- max = m_widgets.size() - 1;
+ max = (int)m_widgets.size() - 1;
} else {
- max = (size_t)bottomRight.row();
+ max = bottomRight.row();
}
Q_ASSERT(max < (int)m_widgets.size());
for (auto i = (size_t)topLeft.row(); i <= max; ++i) {
m_widgets[i]->slotRefresh();
}
}
}
int AssetParameterView::contentHeight() const
{
return m_lay->minimumSize().height();
}
MonitorSceneType AssetParameterView::needsMonitorEffectScene() const
{
if (m_mainKeyframeWidget) {
return m_mainKeyframeWidget->requiredScene();
}
for (int i = 0; i < m_model->rowCount(); ++i) {
QModelIndex index = m_model->index(i, 0);
auto type = m_model->data(index, AssetParameterModel::TypeRole).value();
if (type == ParamType::Geometry) {
return MonitorSceneGeometry;
}
}
return MonitorSceneDefault;
}
/*void AssetParameterView::initKeyframeView()
{
if (m_mainKeyframeWidget) {
m_mainKeyframeWidget->initMonitor();
} else {
for (int i = 0; i < m_model->rowCount(); ++i) {
QModelIndex index = m_model->index(i, 0);
auto type = m_model->data(index, AssetParameterModel::TypeRole).value();
if (type == ParamType::Geometry) {
return MonitorSceneGeometry;
}
}
}
}*/
void AssetParameterView::slotRefresh()
{
refresh(m_model->index(0, 0), m_model->index(m_model->rowCount() - 1, 0), {});
}
bool AssetParameterView::keyframesAllowed() const
{
return m_mainKeyframeWidget != nullptr;
}
bool AssetParameterView::modelHideKeyframes() const
{
return m_mainKeyframeWidget != nullptr && !m_mainKeyframeWidget->keyframesVisible();
}
void AssetParameterView::toggleKeyframes(bool enable)
{
if (m_mainKeyframeWidget) {
m_mainKeyframeWidget->showKeyframes(enable);
}
}
void AssetParameterView::slotDeletePreset()
{
QAction *ac = m_presetGroup->checkedAction();
if (!ac) {
return;
}
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
if (!dir.exists()) {
dir.mkpath(QStringLiteral("."));
}
const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId()));
m_model->deletePreset(presetFile, ac->data().toString());
emit updatePresets();
}
void AssetParameterView::slotUpdatePreset()
{
QAction *ac = m_presetGroup->checkedAction();
if (!ac) {
return;
}
slotSavePreset(ac->data().toString());
}
void AssetParameterView::slotSavePreset(QString presetName)
{
if (presetName.isEmpty()) {
bool ok;
presetName = QInputDialog::getText(this, i18n("Enter preset name"), i18n("Enter the name of this preset"), QLineEdit::Normal, QString(), &ok);
if (!ok) return;
}
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
if (!dir.exists()) {
dir.mkpath(QStringLiteral("."));
}
const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId()));
m_model->savePreset(presetFile, presetName);
emit updatePresets(presetName);
}
void AssetParameterView::slotLoadPreset()
{
auto *action = qobject_cast(sender());
if (!action) {
return;
}
const QString presetName = action->data().toString();
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId()));
const QVector> params = m_model->loadPreset(presetFile, presetName);
auto *command = new AssetUpdateCommand(m_model, params);
pCore->pushUndo(command);
}
QMenu *AssetParameterView::presetMenu()
{
return m_presetMenu;
}
diff --git a/src/dialogs/renderwidget.cpp b/src/dialogs/renderwidget.cpp
index 7b59054ed..c5d18fe1a 100644
--- a/src/dialogs/renderwidget.cpp
+++ b/src/dialogs/renderwidget.cpp
@@ -1,3405 +1,3409 @@
/***************************************************************************
* Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "renderwidget.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "dialogs/profilesdialog.h"
#include "doc/kdenlivedoc.h"
#include "kdenlivesettings.h"
#include "monitor/monitor.h"
#include "profiles/profilemodel.hpp"
#include "profiles/profilerepository.hpp"
#include "project/projectmanager.h"
#include "timecode.h"
#include "ui_saveprofile_ui.h"
#include "xml/xml.hpp"
#include "klocalizedstring.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef KF5_USE_PURPOSE
#include
#include
#endif
#include
#ifdef Q_OS_MAC
#include
#endif
// Render profiles roles
enum {
GroupRole = Qt::UserRole,
ExtensionRole,
StandardRole,
RenderRole,
ParamsRole,
EditableRole,
ExtraRole,
BitratesRole,
DefaultBitrateRole,
AudioBitratesRole,
DefaultAudioBitrateRole,
SpeedsRole,
FieldRole,
ErrorRole
};
// Render job roles
const int ParametersRole = Qt::UserRole + 1;
const int TimeRole = Qt::UserRole + 2;
const int ProgressRole = Qt::UserRole + 3;
const int ExtraInfoRole = Qt::UserRole + 5;
// Running job status
enum JOBSTATUS { WAITINGJOB = 0, STARTINGJOB, RUNNINGJOB, FINISHEDJOB, FAILEDJOB, ABORTEDJOB };
static QStringList acodecsList;
static QStringList vcodecsList;
static QStringList supportedFormats;
RenderJobItem::RenderJobItem(QTreeWidget *parent, const QStringList &strings, int type)
: QTreeWidgetItem(parent, strings, type)
, m_status(-1)
{
setSizeHint(1, QSize(parent->columnWidth(1), parent->fontMetrics().height() * 3));
setStatus(WAITINGJOB);
}
void RenderJobItem::setStatus(int status)
{
if (m_status == status) {
return;
}
m_status = status;
switch (status) {
case WAITINGJOB:
setIcon(0, QIcon::fromTheme(QStringLiteral("media-playback-pause")));
setData(1, Qt::UserRole, i18n("Waiting..."));
break;
case FINISHEDJOB:
setData(1, Qt::UserRole, i18n("Rendering finished"));
setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-ok")));
setData(1, ProgressRole, 100);
break;
case FAILEDJOB:
setData(1, Qt::UserRole, i18n("Rendering crashed"));
setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-close")));
setData(1, ProgressRole, 100);
break;
case ABORTEDJOB:
setData(1, Qt::UserRole, i18n("Rendering aborted"));
setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-cancel")));
setData(1, ProgressRole, 100);
default:
break;
}
}
int RenderJobItem::status() const
{
return m_status;
}
void RenderJobItem::setMetadata(const QString &data)
{
m_data = data;
}
const QString RenderJobItem::metadata() const
{
return m_data;
}
RenderWidget::RenderWidget(bool enableProxy, QWidget *parent)
: QDialog(parent)
, m_blockProcessing(false)
{
m_view.setupUi(this);
int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
QSize iconSize(size, size);
setWindowTitle(i18n("Rendering"));
m_view.buttonDelete->setIconSize(iconSize);
m_view.buttonEdit->setIconSize(iconSize);
m_view.buttonSave->setIconSize(iconSize);
m_view.buttonFavorite->setIconSize(iconSize);
m_view.buttonDownload->setIconSize(iconSize);
m_view.buttonDelete->setIcon(QIcon::fromTheme(QStringLiteral("trash-empty")));
m_view.buttonDelete->setToolTip(i18n("Delete profile"));
m_view.buttonDelete->setEnabled(false);
m_view.buttonEdit->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
m_view.buttonEdit->setToolTip(i18n("Edit profile"));
m_view.buttonEdit->setEnabled(false);
m_view.buttonSave->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
m_view.buttonSave->setToolTip(i18n("Create new profile"));
m_view.hide_log->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
m_view.buttonFavorite->setIcon(QIcon::fromTheme(QStringLiteral("favorite")));
m_view.buttonFavorite->setToolTip(i18n("Copy profile to favorites"));
m_view.buttonDownload->setIcon(QIcon::fromTheme(QStringLiteral("edit-download")));
m_view.buttonDownload->setToolTip(i18n("Download New Render Profiles..."));
m_view.out_file->button()->setToolTip(i18n("Select output destination"));
m_view.advanced_params->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
m_view.optionsGroup->setVisible(m_view.options->isChecked());
connect(m_view.options, &QAbstractButton::toggled, m_view.optionsGroup, &QWidget::setVisible);
m_view.videoLabel->setVisible(m_view.options->isChecked());
connect(m_view.options, &QAbstractButton::toggled, m_view.videoLabel, &QWidget::setVisible);
m_view.video->setVisible(m_view.options->isChecked());
connect(m_view.options, &QAbstractButton::toggled, m_view.video, &QWidget::setVisible);
m_view.audioLabel->setVisible(m_view.options->isChecked());
connect(m_view.options, &QAbstractButton::toggled, m_view.audioLabel, &QWidget::setVisible);
m_view.audio->setVisible(m_view.options->isChecked());
connect(m_view.options, &QAbstractButton::toggled, m_view.audio, &QWidget::setVisible);
connect(m_view.quality, &QAbstractSlider::valueChanged, this, &RenderWidget::adjustAVQualities);
connect(m_view.video, static_cast(&QSpinBox::valueChanged), this, &RenderWidget::adjustQuality);
connect(m_view.speed, &QAbstractSlider::valueChanged, this, &RenderWidget::adjustSpeed);
m_view.buttonRender->setEnabled(false);
m_view.buttonGenerateScript->setEnabled(false);
setRescaleEnabled(false);
m_view.guides_box->setVisible(false);
m_view.open_dvd->setVisible(false);
m_view.create_chapter->setVisible(false);
m_view.open_browser->setVisible(false);
m_view.error_box->setVisible(false);
m_view.tc_type->setEnabled(false);
m_view.checkTwoPass->setEnabled(false);
m_view.proxy_render->setHidden(!enableProxy);
connect(m_view.proxy_render, &QCheckBox::toggled, this, &RenderWidget::slotProxyWarn);
KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window);
QColor bg = scheme.background(KColorScheme::NegativeBackground).color();
m_view.errorBox->setStyleSheet(
QStringLiteral("QGroupBox { background-color: rgb(%1, %2, %3); border-radius: 5px;}; ").arg(bg.red()).arg(bg.green()).arg(bg.blue()));
int height = QFontInfo(font()).pixelSize();
m_view.errorIcon->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(height, height));
m_view.errorBox->setHidden(true);
m_infoMessage = new KMessageWidget;
m_view.info->addWidget(m_infoMessage);
m_infoMessage->setCloseButtonVisible(false);
m_infoMessage->hide();
m_jobInfoMessage = new KMessageWidget;
m_view.jobInfo->addWidget(m_jobInfoMessage);
m_jobInfoMessage->setCloseButtonVisible(false);
m_jobInfoMessage->hide();
m_view.encoder_threads->setMinimum(0);
m_view.encoder_threads->setMaximum(QThread::idealThreadCount());
m_view.encoder_threads->setToolTip(i18n("Encoding threads (0 is automatic)"));
m_view.encoder_threads->setValue(KdenliveSettings::encodethreads());
connect(m_view.encoder_threads, static_cast(&QSpinBox::valueChanged), this, &RenderWidget::slotUpdateEncodeThreads);
m_view.rescale_keep->setChecked(KdenliveSettings::rescalekeepratio());
connect(m_view.rescale_width, static_cast(&QSpinBox::valueChanged), this, &RenderWidget::slotUpdateRescaleWidth);
connect(m_view.rescale_height, static_cast(&QSpinBox::valueChanged), this, &RenderWidget::slotUpdateRescaleHeight);
m_view.rescale_keep->setIcon(QIcon::fromTheme(QStringLiteral("edit-link")));
m_view.rescale_keep->setToolTip(i18n("Preserve aspect ratio"));
connect(m_view.rescale_keep, &QAbstractButton::clicked, this, &RenderWidget::slotSwitchAspectRatio);
connect(m_view.buttonRender, SIGNAL(clicked()), this, SLOT(slotPrepareExport()));
connect(m_view.buttonGenerateScript, &QAbstractButton::clicked, this, &RenderWidget::slotGenerateScript);
m_view.abort_job->setEnabled(false);
m_view.start_script->setEnabled(false);
m_view.delete_script->setEnabled(false);
connect(m_view.export_audio, &QCheckBox::stateChanged, this, &RenderWidget::slotUpdateAudioLabel);
m_view.export_audio->setCheckState(Qt::PartiallyChecked);
checkCodecs();
parseProfiles();
parseScriptFiles();
m_view.running_jobs->setUniformRowHeights(false);
m_view.scripts_list->setUniformRowHeights(false);
connect(m_view.start_script, &QAbstractButton::clicked, this, &RenderWidget::slotStartScript);
connect(m_view.delete_script, &QAbstractButton::clicked, this, &RenderWidget::slotDeleteScript);
connect(m_view.scripts_list, &QTreeWidget::itemSelectionChanged, this, &RenderWidget::slotCheckScript);
connect(m_view.running_jobs, &QTreeWidget::itemSelectionChanged, this, &RenderWidget::slotCheckJob);
connect(m_view.running_jobs, &QTreeWidget::itemDoubleClicked, this, &RenderWidget::slotPlayRendering);
connect(m_view.buttonSave, &QAbstractButton::clicked, this, &RenderWidget::slotSaveProfile);
connect(m_view.buttonEdit, &QAbstractButton::clicked, this, &RenderWidget::slotEditProfile);
connect(m_view.buttonDelete, &QAbstractButton::clicked, this, &RenderWidget::slotDeleteProfile);
connect(m_view.buttonFavorite, &QAbstractButton::clicked, this, &RenderWidget::slotCopyToFavorites);
connect(m_view.buttonDownload, &QAbstractButton::clicked, this, &RenderWidget::slotDownloadNewRenderProfiles);
connect(m_view.abort_job, &QAbstractButton::clicked, this, &RenderWidget::slotAbortCurrentJob);
connect(m_view.start_job, &QAbstractButton::clicked, this, &RenderWidget::slotStartCurrentJob);
connect(m_view.clean_up, &QAbstractButton::clicked, this, &RenderWidget::slotCLeanUpJobs);
connect(m_view.hide_log, &QAbstractButton::clicked, this, &RenderWidget::slotHideLog);
connect(m_view.buttonClose, &QAbstractButton::clicked, this, &QWidget::hide);
connect(m_view.buttonClose2, &QAbstractButton::clicked, this, &QWidget::hide);
connect(m_view.buttonClose3, &QAbstractButton::clicked, this, &QWidget::hide);
connect(m_view.rescale, &QAbstractButton::toggled, this, &RenderWidget::setRescaleEnabled);
connect(m_view.out_file, &KUrlRequester::textChanged, this, static_cast(&RenderWidget::slotUpdateButtons));
connect(m_view.out_file, &KUrlRequester::urlSelected, this, static_cast(&RenderWidget::slotUpdateButtons));
connect(m_view.formats, &QTreeWidget::currentItemChanged, this, &RenderWidget::refreshParams);
connect(m_view.formats, &QTreeWidget::itemDoubleClicked, this, &RenderWidget::slotEditItem);
connect(m_view.render_guide, &QAbstractButton::clicked, this, &RenderWidget::slotUpdateGuideBox);
connect(m_view.render_zone, &QAbstractButton::clicked, this, &RenderWidget::slotUpdateGuideBox);
connect(m_view.render_full, &QAbstractButton::clicked, this, &RenderWidget::slotUpdateGuideBox);
connect(m_view.guide_end, static_cast(&KComboBox::activated), this, &RenderWidget::slotCheckStartGuidePosition);
connect(m_view.guide_start, static_cast(&KComboBox::activated), this, &RenderWidget::slotCheckEndGuidePosition);
connect(m_view.tc_overlay, &QAbstractButton::toggled, m_view.tc_type, &QWidget::setEnabled);
// m_view.splitter->setStretchFactor(1, 5);
// m_view.splitter->setStretchFactor(0, 2);
m_view.out_file->setMode(KFile::File);
#if KIO_VERSION >= QT_VERSION_CHECK(5, 33, 0)
m_view.out_file->setAcceptMode(QFileDialog::AcceptSave);
#elif !defined(KIOWIDGETS_DEPRECATED)
m_view.out_file->fileDialog()->setAcceptMode(QFileDialog::AcceptSave);
#endif
m_view.out_file->setFocusPolicy(Qt::ClickFocus);
m_jobsDelegate = new RenderViewDelegate(this);
m_view.running_jobs->setHeaderLabels(QStringList() << QString() << i18n("File"));
m_view.running_jobs->setItemDelegate(m_jobsDelegate);
QHeaderView *header = m_view.running_jobs->header();
header->setSectionResizeMode(0, QHeaderView::Fixed);
header->resizeSection(0, size + 4);
header->setSectionResizeMode(1, QHeaderView::Interactive);
m_view.scripts_list->setHeaderLabels(QStringList() << QString() << i18n("Stored Playlists"));
m_scriptsDelegate = new RenderViewDelegate(this);
m_view.scripts_list->setItemDelegate(m_scriptsDelegate);
header = m_view.scripts_list->header();
header->setSectionResizeMode(0, QHeaderView::Fixed);
header->resizeSection(0, size + 4);
// Find path for Kdenlive renderer
#ifdef Q_OS_WIN
m_renderer = QCoreApplication::applicationDirPath() + QStringLiteral("/kdenlive_render.exe");
#else
m_renderer = QCoreApplication::applicationDirPath() + QStringLiteral("/kdenlive_render");
#endif
if (!QFile::exists(m_renderer)) {
m_renderer = QStandardPaths::findExecutable(QStringLiteral("kdenlive_render"));
if (m_renderer.isEmpty()) {
m_renderer = QStringLiteral("kdenlive_render");
}
}
QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
if ((interface == nullptr) ||
(!interface->isServiceRegistered(QStringLiteral("org.kde.ksmserver")) && !interface->isServiceRegistered(QStringLiteral("org.gnome.SessionManager")))) {
m_view.shutdown->setEnabled(false);
}
#ifdef KF5_USE_PURPOSE
m_shareMenu = new Purpose::Menu();
m_view.shareButton->setMenu(m_shareMenu);
m_view.shareButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share")));
connect(m_shareMenu, &Purpose::Menu::finished, this, &RenderWidget::slotShareActionFinished);
#else
m_view.shareButton->setEnabled(false);
#endif
m_view.parallel_process->setChecked(KdenliveSettings::parallelrender());
connect(m_view.parallel_process, &QCheckBox::stateChanged, [](int state) { KdenliveSettings::setParallelrender(state == Qt::Checked); });
m_view.field_order->setEnabled(false);
connect(m_view.scanning_list, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { m_view.field_order->setEnabled(index == 2); });
refreshView();
focusFirstVisibleItem();
adjustSize();
}
void RenderWidget::slotShareActionFinished(const QJsonObject &output, int error, const QString &message)
{
#ifdef KF5_USE_PURPOSE
m_jobInfoMessage->hide();
if (error) {
KMessageBox::error(this, i18n("There was a problem sharing the document: %1", message), i18n("Share"));
} else {
const QString url = output["url"].toString();
if (url.isEmpty()) {
m_jobInfoMessage->setMessageType(KMessageWidget::Positive);
m_jobInfoMessage->setText(i18n("Document shared successfully"));
m_jobInfoMessage->show();
} else {
KMessageBox::information(this, i18n("You can find the shared document at: %1", url), i18n("Share"), QString(),
KMessageBox::Notify | KMessageBox::AllowLink);
}
}
+#else
+ Q_UNUSED(output);
+ Q_UNUSED(error);
+ Q_UNUSED(message);
#endif
}
QSize RenderWidget::sizeHint() const
{
// Make sure the widget has minimum size on opening
return {200, 200};
}
RenderWidget::~RenderWidget()
{
m_view.running_jobs->blockSignals(true);
m_view.scripts_list->blockSignals(true);
m_view.running_jobs->clear();
m_view.scripts_list->clear();
delete m_jobsDelegate;
delete m_scriptsDelegate;
delete m_infoMessage;
delete m_jobInfoMessage;
}
void RenderWidget::slotEditItem(QTreeWidgetItem *item)
{
if (item->parent() == nullptr) {
// This is a top level item - group - don't edit
return;
}
const QString edit = item->data(0, EditableRole).toString();
if (edit.isEmpty() || !edit.endsWith(QLatin1String("customprofiles.xml"))) {
slotSaveProfile();
} else {
slotEditProfile();
}
}
void RenderWidget::showInfoPanel()
{
if (m_view.advanced_params->isVisible()) {
m_view.advanced_params->setVisible(false);
KdenliveSettings::setShowrenderparams(false);
} else {
m_view.advanced_params->setVisible(true);
KdenliveSettings::setShowrenderparams(true);
}
}
void RenderWidget::updateDocumentPath()
{
if (m_view.out_file->url().isEmpty()) {
return;
}
const QString fileName = m_view.out_file->url().fileName();
m_view.out_file->setUrl(QUrl::fromLocalFile(QDir(pCore->currentDoc()->projectDataFolder()).absoluteFilePath(fileName)));
parseScriptFiles();
}
void RenderWidget::slotUpdateGuideBox()
{
m_view.guides_box->setVisible(m_view.render_guide->isChecked());
}
void RenderWidget::slotCheckStartGuidePosition()
{
if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex()) {
m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex());
}
}
void RenderWidget::slotCheckEndGuidePosition()
{
if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex()) {
m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex());
}
}
void RenderWidget::setGuides(const QList &guidesList, double duration)
{
m_view.guide_start->clear();
m_view.guide_end->clear();
if (!guidesList.isEmpty()) {
m_view.guide_start->addItem(i18n("Beginning"), "0");
m_view.render_guide->setEnabled(true);
m_view.create_chapter->setEnabled(true);
} else {
m_view.render_guide->setEnabled(false);
m_view.create_chapter->setEnabled(false);
}
double fps = pCore->getCurrentProfile()->fps();
for (int i = 0; i < guidesList.count(); i++) {
const CommentedTime &c = guidesList.at(i);
GenTime pos = c.time();
const QString guidePos = Timecode::getStringTimecode(pos.frames(fps), fps);
m_view.guide_start->addItem(c.comment() + QLatin1Char('/') + guidePos, pos.seconds());
m_view.guide_end->addItem(c.comment() + QLatin1Char('/') + guidePos, pos.seconds());
}
if (!guidesList.isEmpty()) {
m_view.guide_end->addItem(i18n("End"), QString::number(duration));
}
}
/**
* Will be called when the user selects an output file via the file dialog.
* File extension will be added automatically.
*/
void RenderWidget::slotUpdateButtons(const QUrl &url)
{
if (m_view.out_file->url().isEmpty()) {
m_view.buttonGenerateScript->setEnabled(false);
m_view.buttonRender->setEnabled(false);
} else {
updateButtons(); // This also checks whether the selected format is available
}
if (url.isValid()) {
QTreeWidgetItem *item = m_view.formats->currentItem();
if ((item == nullptr) || (item->parent() == nullptr)) { // categories have no parent
m_view.buttonRender->setEnabled(false);
m_view.buttonGenerateScript->setEnabled(false);
return;
}
const QString extension = item->data(0, ExtensionRole).toString();
m_view.out_file->setUrl(filenameWithExtension(url, extension));
}
}
/**
* Will be called when the user changes the output file path in the text line.
* File extension must NOT be added, would make editing impossible!
*/
void RenderWidget::slotUpdateButtons()
{
if (m_view.out_file->url().isEmpty()) {
m_view.buttonRender->setEnabled(false);
m_view.buttonGenerateScript->setEnabled(false);
} else {
updateButtons(); // This also checks whether the selected format is available
}
}
void RenderWidget::slotSaveProfile()
{
Ui::SaveProfile_UI ui;
QPointer d = new QDialog(this);
ui.setupUi(d);
QString customGroup;
QStringList arguments = m_view.advanced_params->toPlainText().split(' ', QString::SkipEmptyParts);
if (!arguments.isEmpty()) {
ui.parameters->setText(arguments.join(QLatin1Char(' ')));
}
ui.profile_name->setFocus();
QTreeWidgetItem *item = m_view.formats->currentItem();
if ((item != nullptr) && (item->parent() != nullptr)) { // not a category
// Duplicate current item settings
customGroup = item->parent()->text(0);
ui.extension->setText(item->data(0, ExtensionRole).toString());
if (ui.parameters->toPlainText().contains(QStringLiteral("%bitrate")) || ui.parameters->toPlainText().contains(QStringLiteral("%quality"))) {
if (ui.parameters->toPlainText().contains(QStringLiteral("%quality"))) {
ui.vbitrates_label->setText(i18n("Qualities"));
ui.default_vbitrate_label->setText(i18n("Default quality"));
} else {
ui.vbitrates_label->setText(i18n("Bitrates"));
ui.default_vbitrate_label->setText(i18n("Default bitrate"));
}
if (item->data(0, BitratesRole).canConvert(QVariant::StringList) && (item->data(0, BitratesRole).toStringList().count() != 0)) {
QStringList bitrates = item->data(0, BitratesRole).toStringList();
ui.vbitrates_list->setText(bitrates.join(QLatin1Char(',')));
if (item->data(0, DefaultBitrateRole).canConvert(QVariant::String)) {
ui.default_vbitrate->setValue(item->data(0, DefaultBitrateRole).toInt());
}
}
} else {
ui.vbitrates->setHidden(true);
}
if (ui.parameters->toPlainText().contains(QStringLiteral("%audiobitrate")) || ui.parameters->toPlainText().contains(QStringLiteral("%audioquality"))) {
if (ui.parameters->toPlainText().contains(QStringLiteral("%audioquality"))) {
ui.abitrates_label->setText(i18n("Qualities"));
ui.default_abitrate_label->setText(i18n("Default quality"));
} else {
ui.abitrates_label->setText(i18n("Bitrates"));
ui.default_abitrate_label->setText(i18n("Default bitrate"));
}
if ((item != nullptr) && item->data(0, AudioBitratesRole).canConvert(QVariant::StringList) &&
(item->data(0, AudioBitratesRole).toStringList().count() != 0)) {
QStringList bitrates = item->data(0, AudioBitratesRole).toStringList();
ui.abitrates_list->setText(bitrates.join(QLatin1Char(',')));
if (item->data(0, DefaultAudioBitrateRole).canConvert(QVariant::String)) {
ui.default_abitrate->setValue(item->data(0, DefaultAudioBitrateRole).toInt());
}
}
} else {
ui.abitrates->setHidden(true);
}
if (item->data(0, SpeedsRole).canConvert(QVariant::StringList) && (item->data(0, SpeedsRole).toStringList().count() != 0)) {
QStringList speeds = item->data(0, SpeedsRole).toStringList();
ui.speeds_list->setText(speeds.join('\n'));
}
}
if (customGroup.isEmpty()) {
customGroup = i18nc("Group Name", "Custom");
}
ui.group_name->setText(customGroup);
if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) {
QString newProfileName = ui.profile_name->text().simplified();
QString newGroupName = ui.group_name->text().simplified();
if (newGroupName.isEmpty()) {
newGroupName = i18nc("Group Name", "Custom");
}
QDomDocument doc;
QDomElement profileElement = doc.createElement(QStringLiteral("profile"));
profileElement.setAttribute(QStringLiteral("name"), newProfileName);
profileElement.setAttribute(QStringLiteral("category"), newGroupName);
profileElement.setAttribute(QStringLiteral("extension"), ui.extension->text().simplified());
QString args = ui.parameters->toPlainText().simplified();
profileElement.setAttribute(QStringLiteral("args"), args);
if (args.contains(QStringLiteral("%bitrate"))) {
// profile has a variable bitrate
profileElement.setAttribute(QStringLiteral("defaultbitrate"), QString::number(ui.default_vbitrate->value()));
profileElement.setAttribute(QStringLiteral("bitrates"), ui.vbitrates_list->text());
} else if (args.contains(QStringLiteral("%quality"))) {
profileElement.setAttribute(QStringLiteral("defaultquality"), QString::number(ui.default_vbitrate->value()));
profileElement.setAttribute(QStringLiteral("qualities"), ui.vbitrates_list->text());
}
if (args.contains(QStringLiteral("%audiobitrate"))) {
// profile has a variable bitrate
profileElement.setAttribute(QStringLiteral("defaultaudiobitrate"), QString::number(ui.default_abitrate->value()));
profileElement.setAttribute(QStringLiteral("audiobitrates"), ui.abitrates_list->text());
} else if (args.contains(QStringLiteral("%audioquality"))) {
// profile has a variable bitrate
profileElement.setAttribute(QStringLiteral("defaultaudioquality"), QString::number(ui.default_abitrate->value()));
profileElement.setAttribute(QStringLiteral("audioqualities"), ui.abitrates_list->text());
}
QString speeds_list_str = ui.speeds_list->toPlainText();
if (!speeds_list_str.isEmpty()) {
profileElement.setAttribute(QStringLiteral("speeds"), speeds_list_str.replace('\n', ';').simplified());
}
doc.appendChild(profileElement);
saveProfile(doc.documentElement());
parseProfiles();
}
delete d;
}
bool RenderWidget::saveProfile(QDomElement newprofile)
{
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/export/"));
if (!dir.exists()) {
dir.mkpath(QStringLiteral("."));
}
QDomDocument doc;
QFile file(dir.absoluteFilePath(QStringLiteral("customprofiles.xml")));
doc.setContent(&file, false);
file.close();
QDomElement documentElement;
QDomElement profiles = doc.documentElement();
if (profiles.isNull() || profiles.tagName() != QLatin1String("profiles")) {
doc.clear();
profiles = doc.createElement(QStringLiteral("profiles"));
profiles.setAttribute(QStringLiteral("version"), 1);
doc.appendChild(profiles);
}
int version = profiles.attribute(QStringLiteral("version"), nullptr).toInt();
if (version < 1) {
doc.clear();
profiles = doc.createElement(QStringLiteral("profiles"));
profiles.setAttribute(QStringLiteral("version"), 1);
doc.appendChild(profiles);
}
QDomNodeList profilelist = doc.elementsByTagName(QStringLiteral("profile"));
QString newProfileName = newprofile.attribute(QStringLiteral("name"));
// Check existing profiles
QStringList existingProfileNames;
int i = 0;
while (!profilelist.item(i).isNull()) {
documentElement = profilelist.item(i).toElement();
QString profileName = documentElement.attribute(QStringLiteral("name"));
existingProfileNames << profileName;
i++;
}
// Check if a profile with that same name already exists
bool ok;
while (existingProfileNames.contains(newProfileName)) {
QString updatedProfileName = QInputDialog::getText(this, i18n("Profile already exists"),
i18n("This profile name already exists. Change the name if you do not want to overwrite it."),
QLineEdit::Normal, newProfileName, &ok);
if (!ok) {
return false;
}
if (updatedProfileName == newProfileName) {
// remove previous profile
profiles.removeChild(profilelist.item(existingProfileNames.indexOf(newProfileName)));
break;
} else {
newProfileName = updatedProfileName;
newprofile.setAttribute(QStringLiteral("name"), newProfileName);
}
}
profiles.appendChild(newprofile);
// QCString save = doc.toString().utf8();
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
KMessageBox::sorry(this, i18n("Unable to write to file %1", dir.absoluteFilePath("customprofiles.xml")));
return false;
}
QTextStream out(&file);
out << doc.toString();
if (file.error() != QFile::NoError) {
KMessageBox::error(this, i18n("Cannot write to file %1", dir.absoluteFilePath("customprofiles.xml")));
file.close();
return false;
}
file.close();
return true;
}
void RenderWidget::slotCopyToFavorites()
{
QTreeWidgetItem *item = m_view.formats->currentItem();
if ((item == nullptr) || (item->parent() == nullptr)) {
return;
}
QString params = item->data(0, ParamsRole).toString();
QString extension = item->data(0, ExtensionRole).toString();
QString currentProfile = item->text(0);
QDomDocument doc;
QDomElement profileElement = doc.createElement(QStringLiteral("profile"));
profileElement.setAttribute(QStringLiteral("name"), currentProfile);
profileElement.setAttribute(QStringLiteral("category"), i18nc("Category Name", "Custom"));
profileElement.setAttribute(QStringLiteral("destinationid"), QStringLiteral("favorites"));
profileElement.setAttribute(QStringLiteral("extension"), extension);
profileElement.setAttribute(QStringLiteral("args"), params);
if (params.contains(QStringLiteral("%bitrate"))) {
// profile has a variable bitrate
profileElement.setAttribute(QStringLiteral("defaultbitrate"), item->data(0, DefaultBitrateRole).toString());
profileElement.setAttribute(QStringLiteral("bitrates"), item->data(0, BitratesRole).toStringList().join(QLatin1Char(',')));
} else if (params.contains(QStringLiteral("%quality"))) {
profileElement.setAttribute(QStringLiteral("defaultquality"), item->data(0, DefaultBitrateRole).toString());
profileElement.setAttribute(QStringLiteral("qualities"), item->data(0, BitratesRole).toStringList().join(QLatin1Char(',')));
}
if (params.contains(QStringLiteral("%audiobitrate"))) {
// profile has a variable bitrate
profileElement.setAttribute(QStringLiteral("defaultaudiobitrate"), item->data(0, DefaultAudioBitrateRole).toString());
profileElement.setAttribute(QStringLiteral("audiobitrates"), item->data(0, AudioBitratesRole).toStringList().join(QLatin1Char(',')));
} else if (params.contains(QStringLiteral("%audioquality"))) {
// profile has a variable bitrate
profileElement.setAttribute(QStringLiteral("defaultaudioquality"), item->data(0, DefaultAudioBitrateRole).toString());
profileElement.setAttribute(QStringLiteral("audioqualities"), item->data(0, AudioBitratesRole).toStringList().join(QLatin1Char(',')));
}
if (item->data(0, SpeedsRole).canConvert(QVariant::StringList) && (item->data(0, SpeedsRole).toStringList().count() != 0)) {
// profile has a variable speed
profileElement.setAttribute(QStringLiteral("speeds"), item->data(0, SpeedsRole).toStringList().join(QLatin1Char(';')));
}
doc.appendChild(profileElement);
if (saveProfile(doc.documentElement())) {
parseProfiles(profileElement.attribute(QStringLiteral("name")));
}
}
void RenderWidget::slotDownloadNewRenderProfiles()
{
if (getNewStuff(QStringLiteral(":data/kdenlive_renderprofiles.knsrc")) > 0) {
reloadProfiles();
}
}
int RenderWidget::getNewStuff(const QString &configFile)
{
KNS3::Entry::List entries;
QPointer dialog = new KNS3::DownloadDialog(configFile);
if (dialog->exec() != 0) {
entries = dialog->changedEntries();
}
for (const KNS3::Entry &entry : entries) {
if (entry.status() == KNS3::Entry::Installed) {
qCDebug(KDENLIVE_LOG) << "// Installed files: " << entry.installedFiles();
}
}
delete dialog;
return entries.size();
}
void RenderWidget::slotEditProfile()
{
QTreeWidgetItem *item = m_view.formats->currentItem();
if ((item == nullptr) || (item->parent() == nullptr)) {
return;
}
QString params = item->data(0, ParamsRole).toString();
Ui::SaveProfile_UI ui;
QPointer d = new QDialog(this);
ui.setupUi(d);
QString customGroup = item->parent()->text(0);
if (customGroup.isEmpty()) {
customGroup = i18nc("Group Name", "Custom");
}
ui.group_name->setText(customGroup);
ui.profile_name->setText(item->text(0));
ui.extension->setText(item->data(0, ExtensionRole).toString());
ui.parameters->setText(params);
ui.profile_name->setFocus();
if (params.contains(QStringLiteral("%bitrate")) || ui.parameters->toPlainText().contains(QStringLiteral("%quality"))) {
if (params.contains(QStringLiteral("%quality"))) {
ui.vbitrates_label->setText(i18n("Qualities"));
ui.default_vbitrate_label->setText(i18n("Default quality"));
} else {
ui.vbitrates_label->setText(i18n("Bitrates"));
ui.default_vbitrate_label->setText(i18n("Default bitrate"));
}
if (item->data(0, BitratesRole).canConvert(QVariant::StringList) && (item->data(0, BitratesRole).toStringList().count() != 0)) {
QStringList bitrates = item->data(0, BitratesRole).toStringList();
ui.vbitrates_list->setText(bitrates.join(QLatin1Char(',')));
if (item->data(0, DefaultBitrateRole).canConvert(QVariant::String)) {
ui.default_vbitrate->setValue(item->data(0, DefaultBitrateRole).toInt());
}
}
} else {
ui.vbitrates->setHidden(true);
}
if (params.contains(QStringLiteral("%audiobitrate")) || ui.parameters->toPlainText().contains(QStringLiteral("%audioquality"))) {
if (params.contains(QStringLiteral("%audioquality"))) {
ui.abitrates_label->setText(i18n("Qualities"));
ui.default_abitrate_label->setText(i18n("Default quality"));
} else {
ui.abitrates_label->setText(i18n("Bitrates"));
ui.default_abitrate_label->setText(i18n("Default bitrate"));
}
if (item->data(0, AudioBitratesRole).canConvert(QVariant::StringList) && (item->data(0, AudioBitratesRole).toStringList().count() != 0)) {
QStringList bitrates = item->data(0, AudioBitratesRole).toStringList();
ui.abitrates_list->setText(bitrates.join(QLatin1Char(',')));
if (item->data(0, DefaultAudioBitrateRole).canConvert(QVariant::String)) {
ui.default_abitrate->setValue(item->data(0, DefaultAudioBitrateRole).toInt());
}
}
} else {
ui.abitrates->setHidden(true);
}
if (item->data(0, SpeedsRole).canConvert(QVariant::StringList) && (item->data(0, SpeedsRole).toStringList().count() != 0)) {
QStringList speeds = item->data(0, SpeedsRole).toStringList();
ui.speeds_list->setText(speeds.join('\n'));
}
d->setWindowTitle(i18n("Edit Profile"));
if (d->exec() == QDialog::Accepted) {
slotDeleteProfile(true);
QString exportFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/export/customprofiles.xml");
QDomDocument doc;
QFile file(exportFile);
doc.setContent(&file, false);
file.close();
QDomElement documentElement;
QDomElement profiles = doc.documentElement();
if (profiles.isNull() || profiles.tagName() != QLatin1String("profiles")) {
doc.clear();
profiles = doc.createElement(QStringLiteral("profiles"));
profiles.setAttribute(QStringLiteral("version"), 1);
doc.appendChild(profiles);
}
int version = profiles.attribute(QStringLiteral("version"), nullptr).toInt();
if (version < 1) {
doc.clear();
profiles = doc.createElement(QStringLiteral("profiles"));
profiles.setAttribute(QStringLiteral("version"), 1);
doc.appendChild(profiles);
}
QString newProfileName = ui.profile_name->text().simplified();
QString newGroupName = ui.group_name->text().simplified();
if (newGroupName.isEmpty()) {
newGroupName = i18nc("Group Name", "Custom");
}
QDomNodeList profilelist = doc.elementsByTagName(QStringLiteral("profile"));
int i = 0;
while (!profilelist.item(i).isNull()) {
// make sure a profile with same name doesn't exist
documentElement = profilelist.item(i).toElement();
QString profileName = documentElement.attribute(QStringLiteral("name"));
if (profileName == newProfileName) {
// a profile with that same name already exists
bool ok;
newProfileName = QInputDialog::getText(this, i18n("Profile already exists"),
i18n("This profile name already exists. Change the name if you do not want to overwrite it."),
QLineEdit::Normal, newProfileName, &ok);
if (!ok) {
return;
}
if (profileName == newProfileName) {
profiles.removeChild(profilelist.item(i));
break;
}
}
++i;
}
QDomElement profileElement = doc.createElement(QStringLiteral("profile"));
profileElement.setAttribute(QStringLiteral("name"), newProfileName);
profileElement.setAttribute(QStringLiteral("category"), newGroupName);
profileElement.setAttribute(QStringLiteral("extension"), ui.extension->text().simplified());
QString args = ui.parameters->toPlainText().simplified();
profileElement.setAttribute(QStringLiteral("args"), args);
if (args.contains(QStringLiteral("%bitrate"))) {
// profile has a variable bitrate
profileElement.setAttribute(QStringLiteral("defaultbitrate"), QString::number(ui.default_vbitrate->value()));
profileElement.setAttribute(QStringLiteral("bitrates"), ui.vbitrates_list->text());
} else if (args.contains(QStringLiteral("%quality"))) {
profileElement.setAttribute(QStringLiteral("defaultquality"), QString::number(ui.default_vbitrate->value()));
profileElement.setAttribute(QStringLiteral("qualities"), ui.vbitrates_list->text());
}
if (args.contains(QStringLiteral("%audiobitrate"))) {
// profile has a variable bitrate
profileElement.setAttribute(QStringLiteral("defaultaudiobitrate"), QString::number(ui.default_abitrate->value()));
profileElement.setAttribute(QStringLiteral("audiobitrates"), ui.abitrates_list->text());
} else if (args.contains(QStringLiteral("%audioquality"))) {
profileElement.setAttribute(QStringLiteral("defaultaudioquality"), QString::number(ui.default_abitrate->value()));
profileElement.setAttribute(QStringLiteral("audioqualities"), ui.abitrates_list->text());
}
QString speeds_list_str = ui.speeds_list->toPlainText();
if (!speeds_list_str.isEmpty()) {
// profile has a variable speed
profileElement.setAttribute(QStringLiteral("speeds"), speeds_list_str.replace('\n', ';').simplified());
}
profiles.appendChild(profileElement);
// QCString save = doc.toString().utf8();
delete d;
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
return;
}
QTextStream out(&file);
out << doc.toString();
if (file.error() != QFile::NoError) {
KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
file.close();
return;
}
file.close();
parseProfiles();
} else {
delete d;
}
}
void RenderWidget::slotDeleteProfile(bool dontRefresh)
{
// TODO: delete a profile installed by KNewStuff the easy way
/*
QString edit = m_view.formats->currentItem()->data(EditableRole).toString();
if (!edit.endsWith(QLatin1String("customprofiles.xml"))) {
// This is a KNewStuff installed file, process through KNS
KNS::Engine engine(0);
if (engine.init("kdenlive_render.knsrc")) {
KNS::Entry::List entries;
}
return;
}*/
QTreeWidgetItem *item = m_view.formats->currentItem();
if ((item == nullptr) || (item->parent() == nullptr)) {
return;
}
QString currentProfile = item->text(0);
QString exportFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/export/customprofiles.xml");
QDomDocument doc;
QFile file(exportFile);
doc.setContent(&file, false);
file.close();
QDomElement documentElement;
QDomNodeList profiles = doc.elementsByTagName(QStringLiteral("profile"));
int i = 0;
QString profileName;
while (!profiles.item(i).isNull()) {
documentElement = profiles.item(i).toElement();
profileName = documentElement.attribute(QStringLiteral("name"));
if (profileName == currentProfile) {
doc.documentElement().removeChild(profiles.item(i));
break;
}
++i;
}
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
return;
}
QTextStream out(&file);
out << doc.toString();
if (file.error() != QFile::NoError) {
KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
file.close();
return;
}
file.close();
if (dontRefresh) {
return;
}
parseProfiles();
focusFirstVisibleItem();
}
void RenderWidget::updateButtons()
{
if ((m_view.formats->currentItem() == nullptr) || m_view.formats->currentItem()->isHidden()) {
m_view.buttonSave->setEnabled(false);
m_view.buttonDelete->setEnabled(false);
m_view.buttonEdit->setEnabled(false);
m_view.buttonRender->setEnabled(false);
m_view.buttonGenerateScript->setEnabled(false);
} else {
m_view.buttonSave->setEnabled(true);
m_view.buttonRender->setEnabled(m_view.formats->currentItem()->data(0, ErrorRole).isNull());
m_view.buttonGenerateScript->setEnabled(m_view.formats->currentItem()->data(0, ErrorRole).isNull());
QString edit = m_view.formats->currentItem()->data(0, EditableRole).toString();
if (edit.isEmpty() || !edit.endsWith(QLatin1String("customprofiles.xml"))) {
m_view.buttonDelete->setEnabled(false);
m_view.buttonEdit->setEnabled(false);
} else {
m_view.buttonDelete->setEnabled(true);
m_view.buttonEdit->setEnabled(true);
}
}
}
void RenderWidget::focusFirstVisibleItem(const QString &profile)
{
QTreeWidgetItem *item = nullptr;
if (!profile.isEmpty()) {
QList items = m_view.formats->findItems(profile, Qt::MatchExactly | Qt::MatchRecursive);
if (!items.isEmpty()) {
item = items.constFirst();
}
}
if (!item) {
// searched profile not found in any category, select 1st available profile
for (int i = 0; i < m_view.formats->topLevelItemCount(); ++i) {
item = m_view.formats->topLevelItem(i);
if (item->childCount() > 0) {
item = item->child(0);
break;
}
}
}
if (item) {
m_view.formats->setCurrentItem(item);
item->parent()->setExpanded(true);
refreshParams();
}
updateButtons();
}
void RenderWidget::slotPrepareExport(bool delayedRendering, const QString &scriptPath)
{
Q_UNUSED(scriptPath);
if (!QFile::exists(KdenliveSettings::rendererpath())) {
KMessageBox::sorry(this, i18n("Cannot find the melt program required for rendering (part of Mlt)"));
return;
}
if (QFile::exists(m_view.out_file->url().toLocalFile())) {
if (KMessageBox::warningYesNo(this, i18n("Output file already exists. Do you want to overwrite it?")) != KMessageBox::Yes) {
return;
}
}
QString chapterFile;
if (m_view.create_chapter->isChecked()) {
chapterFile = m_view.out_file->url().toLocalFile() + QStringLiteral(".dvdchapter");
}
// mantisbt 1051
QDir dir(m_view.out_file->url().adjusted(QUrl::RemoveFilename).toLocalFile());
if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) {
KMessageBox::sorry(this, i18n("The directory %1, could not be created.\nPlease make sure you have the required permissions.",
m_view.out_file->url().adjusted(QUrl::RemoveFilename).toLocalFile()));
return;
}
prepareRendering(delayedRendering, chapterFile);
}
void RenderWidget::prepareRendering(bool delayedRendering, const QString &chapterFile)
{
KdenliveDoc *project = pCore->currentDoc();
QString playlistPath;
QString mltSuffix(QStringLiteral(".mlt"));
QList playlistPaths;
QList trackNames;
QString renderName;
if (delayedRendering) {
bool ok;
renderName = QFileInfo(pCore->currentDoc()->url().toLocalFile()).fileName();
if (renderName.isEmpty()) {
renderName = i18n("export") + QStringLiteral(".mlt");
}
QDir projectFolder(pCore->currentDoc()->projectDataFolder());
projectFolder.mkpath(QStringLiteral("kdenlive-renderqueue"));
projectFolder.cd(QStringLiteral("kdenlive-renderqueue"));
if (projectFolder.exists(renderName)) {
int ix = 1;
while (projectFolder.exists(renderName)) {
if (renderName.contains(QLatin1Char('-'))) {
renderName = renderName.section(QLatin1Char('-'), 0, -2);
} else {
renderName = renderName.section(QLatin1Char('.'), 0, -2);
}
renderName.append(QString("-%1.mlt").arg(ix));
ix++;
}
}
renderName = renderName.section(QLatin1Char('.'), 0, -2);
renderName = QInputDialog::getText(this, i18n("Delayed rendering"), i18n("Select a name for this rendering."), QLineEdit::Normal, renderName, &ok);
if (!ok) {
return;
}
if (!renderName.endsWith(QStringLiteral(".mlt"))) {
renderName.append(QStringLiteral(".mlt"));
}
if (projectFolder.exists(renderName)) {
if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", renderName)) == KMessageBox::No) {
return;
}
}
playlistPath = projectFolder.absoluteFilePath(renderName);
} else {
QTemporaryFile tmp(QDir::tempPath() + "/kdenlive-XXXXXX.mlt");
if (!tmp.open()) {
// Something went wrong
return;
}
tmp.close();
playlistPath = tmp.fileName();
}
int in = 0;
int out;
Monitor *pMon = pCore->getMonitor(Kdenlive::ProjectMonitor);
bool zoneOnly = m_view.render_zone->isChecked();
if (zoneOnly) {
in = pMon->getZoneStart();
out = pMon->getZoneEnd() - 1;
} else {
out = pCore->projectDuration() - 2;
}
QString playlistContent = pCore->projectManager()->projectSceneList(project->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile());
if (!chapterFile.isEmpty()) {
QDomDocument doc;
QDomElement chapters = doc.createElement(QStringLiteral("chapters"));
chapters.setAttribute(QStringLiteral("fps"), pCore->getCurrentFps());
doc.appendChild(chapters);
const QList guidesList = project->getGuideModel()->getAllMarkers();
for (int i = 0; i < guidesList.count(); i++) {
const CommentedTime &c = guidesList.at(i);
int time = c.time().frames(pCore->getCurrentFps());
if (time >= in && time < out) {
if (zoneOnly) {
time = time - in;
}
}
QDomElement chapter = doc.createElement(QStringLiteral("chapter"));
chapters.appendChild(chapter);
chapter.setAttribute(QStringLiteral("title"), c.comment());
chapter.setAttribute(QStringLiteral("time"), time);
}
if (!chapters.childNodes().isEmpty()) {
if (!project->getGuideModel()->hasMarker(out)) {
// Always insert a guide in pos 0
QDomElement chapter = doc.createElement(QStringLiteral("chapter"));
chapters.insertBefore(chapter, QDomNode());
chapter.setAttribute(QStringLiteral("title"), i18nc("the first in a list of chapters", "Start"));
chapter.setAttribute(QStringLiteral("time"), QStringLiteral("0"));
}
// save chapters file
QFile file(chapterFile);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qCWarning(KDENLIVE_LOG) << "////// ERROR writing DVD CHAPTER file: " << chapterFile;
} else {
file.write(doc.toString().toUtf8());
if (file.error() != QFile::NoError) {
qCWarning(KDENLIVE_LOG) << "////// ERROR writing DVD CHAPTER file: " << chapterFile;
}
file.close();
}
}
}
// Set playlist audio volume to 100%
QDomDocument doc;
doc.setContent(playlistContent);
QDomElement tractor = doc.documentElement().firstChildElement(QStringLiteral("tractor"));
if (!tractor.isNull()) {
QDomNodeList props = tractor.elementsByTagName(QStringLiteral("property"));
for (int i = 0; i < props.count(); ++i) {
if (props.at(i).toElement().attribute(QStringLiteral("name")) == QLatin1String("meta.volume")) {
props.at(i).firstChild().setNodeValue(QStringLiteral("1"));
break;
}
}
}
// Add autoclose to playlists.
QDomNodeList playlists = doc.elementsByTagName(QStringLiteral("playlist"));
for (int i = 0; i < playlists.length(); ++i) {
playlists.item(i).toElement().setAttribute(QStringLiteral("autoclose"), 1);
}
// Do we want proxy rendering
if (project->useProxy() && !proxyRendering()) {
QString root = doc.documentElement().attribute(QStringLiteral("root"));
if (!root.isEmpty() && !root.endsWith(QLatin1Char('/'))) {
root.append(QLatin1Char('/'));
}
// replace proxy clips with originals
QMap proxies = pCore->projectItemModel()->getProxies(pCore->currentDoc()->documentRoot());
QDomNodeList producers = doc.elementsByTagName(QStringLiteral("producer"));
QString producerResource;
QString producerService;
QString suffix;
QString prefix;
for (int n = 0; n < producers.length(); ++n) {
QDomElement e = producers.item(n).toElement();
producerResource = Xml::getXmlProperty(e, QStringLiteral("resource"));
producerService = Xml::getXmlProperty(e, QStringLiteral("mlt_service"));
if (producerResource.isEmpty() || producerService == QLatin1String("color")) {
continue;
}
if (producerService == QLatin1String("timewarp")) {
// slowmotion producer
prefix = producerResource.section(QLatin1Char(':'), 0, 0) + QLatin1Char(':');
producerResource = producerResource.section(QLatin1Char(':'), 1);
} else {
prefix.clear();
}
if (producerService == QLatin1String("framebuffer")) {
// slowmotion producer
suffix = QLatin1Char('?') + producerResource.section(QLatin1Char('?'), 1);
producerResource = producerResource.section(QLatin1Char('?'), 0, 0);
} else {
suffix.clear();
}
if (!producerResource.isEmpty()) {
if (QFileInfo(producerResource).isRelative()) {
producerResource.prepend(root);
}
if (proxies.contains(producerResource)) {
QString replacementResource = proxies.value(producerResource);
Xml::setXmlProperty(e, QStringLiteral("resource"), prefix + replacementResource + suffix);
if (producerService == QLatin1String("timewarp")) {
Xml::setXmlProperty(e, QStringLiteral("warp_resource"), replacementResource);
}
// We need to delete the "aspect_ratio" property because proxy clips
// sometimes have different ratio than original clips
Xml::removeXmlProperty(e, QStringLiteral("aspect_ratio"));
Xml::removeMetaProperties(e);
}
}
}
}
generateRenderFiles(doc, playlistPath, in, out, delayedRendering);
}
void RenderWidget::generateRenderFiles(QDomDocument doc, const QString &playlistPath, int in, int out, bool delayedRendering)
{
QDomDocument clone;
KdenliveDoc *project = pCore->currentDoc();
int passes = m_view.checkTwoPass->isChecked() ? 2 : 1;
QString renderArgs = m_view.advanced_params->toPlainText().simplified();
QDomElement consumer = doc.createElement(QStringLiteral("consumer"));
QDomNodeList profiles = doc.elementsByTagName(QStringLiteral("profile"));
if (profiles.isEmpty()) {
doc.documentElement().insertAfter(consumer, doc.documentElement());
} else {
doc.documentElement().insertAfter(consumer, profiles.at(profiles.length() - 1));
}
// Check for fps change
double forcedfps = 0;
if (renderArgs.startsWith(QLatin1String("r="))) {
QString sub = renderArgs.section(QLatin1Char(' '), 0, 0).toLower();
sub = sub.section(QLatin1Char('='), 1, 1);
forcedfps = sub.toDouble();
} else if (renderArgs.contains(QStringLiteral(" r="))) {
QString sub = renderArgs.section(QStringLiteral(" r="), 1, 1);
sub = sub.section(QLatin1Char(' '), 0, 0).toLower();
forcedfps = sub.toDouble();
} else if (renderArgs.contains(QStringLiteral("mlt_profile="))) {
QString sub = renderArgs.section(QStringLiteral("mlt_profile="), 1, 1);
sub = sub.section(QLatin1Char(' '), 0, 0).toLower();
forcedfps = ProfileRepository::get()->getProfile(sub)->fps();
}
bool resizeProfile = false;
std::unique_ptr &profile = pCore->getCurrentProfile();
if (renderArgs.contains(QLatin1String("%dv_standard"))) {
QString dvstd;
if (fmod((double)profile->frame_rate_num() / profile->frame_rate_den(), 30.01) > 27) {
dvstd = QStringLiteral("ntsc");
if (!(profile->frame_rate_num() == 30000 && profile->frame_rate_den() == 1001)) {
forcedfps = 30000.0 / 1001;
}
if (!(profile->width() == 720 && profile->height() == 480)) {
resizeProfile = true;
}
} else {
dvstd = QStringLiteral("pal");
if (!(profile->frame_rate_num() == 25 && profile->frame_rate_den() == 1)) {
forcedfps = 25;
}
if (!(profile->width() == 720 && profile->height() == 576)) {
resizeProfile = true;
}
}
if ((double)profile->display_aspect_num() / profile->display_aspect_den() > 1.5) {
dvstd += QLatin1String("_wide");
}
renderArgs.replace(QLatin1String("%dv_standard"), dvstd);
}
QStringList args = renderArgs.split(QLatin1Char(' '));
for (auto ¶m : args) {
if (param.contains(QLatin1Char('='))) {
QString paramValue = param.section(QLatin1Char('='), 1);
if (paramValue.startsWith(QLatin1Char('%'))) {
if (paramValue.startsWith(QStringLiteral("%bitrate")) || paramValue == QStringLiteral("%quality")) {
if (paramValue.contains("+'k'"))
paramValue = QString::number(m_view.video->value()) + 'k';
else
paramValue = QString::number(m_view.video->value());
}
if (paramValue.startsWith(QStringLiteral("%audiobitrate")) || paramValue == QStringLiteral("%audioquality")) {
if (paramValue.contains("+'k'"))
paramValue = QString::number(m_view.audio->value()) + 'k';
else
paramValue = QString::number(m_view.audio->value());
}
if (paramValue == QStringLiteral("%dar"))
paramValue = '@' + QString::number(profile->display_aspect_num()) + QLatin1Char('/') + QString::number(profile->display_aspect_den());
if (paramValue == QStringLiteral("%passes")) paramValue = QString::number(static_cast(m_view.checkTwoPass->isChecked()) + 1);
}
consumer.setAttribute(param.section(QLatin1Char('='), 0, 0), paramValue);
}
}
// Check for movit
if (KdenliveSettings::gpu_accel()) {
consumer.setAttribute(QStringLiteral("glsl."), 1);
}
// in/out points
if (m_view.render_guide->isChecked()) {
double fps = profile->fps();
double guideStart = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble();
double guideEnd = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble();
consumer.setAttribute(QStringLiteral("in"), (int)GenTime(guideStart).frames(fps));
consumer.setAttribute(QStringLiteral("out"), (int)GenTime(guideEnd).frames(fps));
} else {
consumer.setAttribute(QStringLiteral("in"), in);
consumer.setAttribute(QStringLiteral("out"), out);
}
// Check if the rendering profile is different from project profile,
// in which case we need to use the producer_comsumer from MLT
QString subsize;
if (renderArgs.startsWith(QLatin1String("s="))) {
subsize = renderArgs.section(QLatin1Char(' '), 0, 0).toLower();
subsize = subsize.section(QLatin1Char('='), 1, 1);
} else if (renderArgs.contains(QStringLiteral(" s="))) {
subsize = renderArgs.section(QStringLiteral(" s="), 1, 1);
subsize = subsize.section(QLatin1Char(' '), 0, 0).toLower();
} else if (m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
subsize = QStringLiteral("%1x%2").arg(m_view.rescale_width->value()).arg(m_view.rescale_height->value());
}
if (!subsize.isEmpty()) {
consumer.setAttribute(QStringLiteral("s"), subsize);
}
// Check if we need to embed the playlist into the producer consumer
// That is required if PAR != 1
if (profile->sample_aspect_num() != profile->sample_aspect_den() && subsize.isEmpty()) {
resizeProfile = true;
}
// Project metadata
if (m_view.export_meta->isChecked()) {
QMap metadata = project->metadata();
QMap::const_iterator i = metadata.constBegin();
while (i != metadata.constEnd()) {
consumer.setAttribute(i.key(), QString(QUrl::toPercentEncoding(i.value())));
++i;
}
}
// Adjust scanning
switch (m_view.scanning_list->currentIndex()) {
case 1:
consumer.setAttribute(QStringLiteral("progressive"), 1);
break;
case 2:
// Interlaced rendering
consumer.setAttribute(QStringLiteral("progressive"), 0);
// Adjust field order
consumer.setAttribute(QStringLiteral("top_field_first"), m_view.field_order->currentIndex());
break;
default:
// leave as is
break;
}
// check if audio export is selected
bool exportAudio;
if (automaticAudioExport()) {
// TODO check if projact contains audio
// exportAudio = pCore->projectManager()->currentTimeline()->checkProjectAudio();
exportAudio = true;
} else {
exportAudio = selectedAudioExport();
}
// disable audio if requested
if (!exportAudio) {
consumer.setAttribute(QStringLiteral("an"), 1);
}
int threadCount = QThread::idealThreadCount();
if (threadCount > 2 && m_view.parallel_process->isChecked()) {
threadCount = qMin(threadCount - 1, 4);
} else {
threadCount = 1;
}
// Set the thread counts
if (!renderArgs.contains(QStringLiteral("threads="))) {
consumer.setAttribute(QStringLiteral("threads"), KdenliveSettings::encodethreads());
}
consumer.setAttribute(QStringLiteral("real_time"), -threadCount);
// check which audio tracks have to be exported
/*if (stemExport) {
// TODO refac
//TODO port to new timeline model
Timeline *ct = pCore->projectManager()->currentTimeline();
int allTracksCount = ct->tracksCount();
// reset tracks count (tracks to be rendered)
tracksCount = 0;
// begin with track 1 (track zero is a hidden black track)
for (int i = 1; i < allTracksCount; i++) {
Track *track = ct->track(i);
// add only tracks to render list that are not muted and have audio
if ((track != nullptr) && !track->info().isMute && track->hasAudio()) {
QDomDocument docCopy = doc.cloneNode(true).toDocument();
QString trackName = track->info().trackName;
// save track name
trackNames << trackName;
qCDebug(KDENLIVE_LOG) << "Track-Name: " << trackName;
// create stem export doc content
QDomNodeList tracks = docCopy.elementsByTagName(QStringLiteral("track"));
for (int j = 0; j < allTracksCount; j++) {
if (j != i) {
// mute other tracks
tracks.at(j).toElement().setAttribute(QStringLiteral("hide"), QStringLiteral("both"));
}
}
docList << docCopy;
tracksCount++;
}
}
}*/
if (m_view.checkTwoPass->isChecked()) {
// We will generate 2 files, one for each pass.
clone = doc.cloneNode(true).toDocument();
}
QStringList playlists;
QString renderedFile = m_view.out_file->url().toLocalFile();
for (int i = 0; i < passes; i++) {
// Append consumer settings
QDomDocument final = i > 0 ? clone : doc;
QDomNodeList cons = final.elementsByTagName(QStringLiteral("consumer"));
QDomElement myConsumer = cons.at(0).toElement();
QString mytarget = renderedFile;
QString playlistName = playlistPath;
myConsumer.setAttribute(QStringLiteral("mlt_service"), QStringLiteral("avformat"));
if (passes == 2 && i == 1) {
playlistName = playlistName.section(QLatin1Char('.'), 0, -2) + QString("-pass%1.").arg(i + 1) + playlistName.section(QLatin1Char('.'), -1);
}
playlists << playlistName;
myConsumer.setAttribute(QStringLiteral("target"), mytarget);
// Prepare rendering args
int pass = passes == 2 ? i + 1 : 0;
if (renderArgs.contains(QStringLiteral("libx265"))) {
if (pass == 1 || pass == 2) {
QString x265params = myConsumer.attribute("x265-params");
x265params = QString("pass=%1:stats=%2:%3").arg(pass).arg(mytarget.replace(":", "\\:") + "_2pass.log").arg(x265params);
myConsumer.setAttribute("x265-params", x265params);
}
} else {
if (pass == 1 || pass == 2) {
myConsumer.setAttribute("pass", pass);
myConsumer.setAttribute("passlogfile", mytarget + "_2pass.log");
}
if (pass == 1) {
myConsumer.setAttribute("fastfirstpass", 1);
myConsumer.removeAttribute("acodec");
myConsumer.setAttribute("an", 1);
} else {
myConsumer.removeAttribute("fastfirstpass");
}
}
QFile file(playlistName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
pCore->displayMessage(i18n("Cannot write to file %1", playlistName), ErrorMessage);
return;
}
file.write(final.toString().toUtf8());
if (file.error() != QFile::NoError) {
pCore->displayMessage(i18n("Cannot write to file %1", playlistName), ErrorMessage);
file.close();
return;
}
file.close();
}
// Create job
RenderJobItem *renderItem = nullptr;
QList existing = m_view.running_jobs->findItems(renderedFile, Qt::MatchExactly, 1);
if (!existing.isEmpty()) {
renderItem = static_cast(existing.at(0));
if (renderItem->status() == RUNNINGJOB || renderItem->status() == WAITINGJOB || renderItem->status() == STARTINGJOB) {
KMessageBox::information(
this, i18n("There is already a job writing file:
%1
Abort the job if you want to overwrite it...", renderedFile),
i18n("Already running"));
return;
}
if (delayedRendering || playlists.size() > 1) {
delete renderItem;
renderItem = nullptr;
} else {
renderItem->setData(1, ProgressRole, 0);
renderItem->setStatus(WAITINGJOB);
renderItem->setIcon(0, QIcon::fromTheme(QStringLiteral("media-playback-pause")));
renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
QStringList argsJob = {KdenliveSettings::rendererpath(), playlistPath, renderedFile,
QStringLiteral("-pid:%1").arg(QCoreApplication::applicationPid())};
renderItem->setData(1, ParametersRole, argsJob);
renderItem->setData(1, TimeRole, QDateTime::currentDateTime());
if (!exportAudio) {
renderItem->setData(1, ExtraInfoRole, i18n("Video without audio track"));
} else {
renderItem->setData(1, ExtraInfoRole, QString());
}
m_view.running_jobs->setCurrentItem(renderItem);
m_view.tabWidget->setCurrentIndex(1);
checkRenderStatus();
return;
}
}
if (delayedRendering) {
parseScriptFiles();
return;
}
QList jobList;
for (const QString &pl : playlists) {
renderItem = new RenderJobItem(m_view.running_jobs, QStringList() << QString() << renderedFile);
renderItem->setData(1, TimeRole, QDateTime::currentDateTime());
QStringList argsJob = {KdenliveSettings::rendererpath(), pl, renderedFile, QStringLiteral("-pid:%1").arg(QCoreApplication::applicationPid())};
renderItem->setData(1, ParametersRole, argsJob);
qDebug() << "* CREATED JOB WITH ARGS: " << argsJob;
if (!exportAudio) {
renderItem->setData(1, ExtraInfoRole, i18n("Video without audio track"));
} else {
renderItem->setData(1, ExtraInfoRole, QString());
}
jobList << renderItem;
}
m_view.running_jobs->setCurrentItem(jobList.at(0));
m_view.tabWidget->setCurrentIndex(1);
// check render status
checkRenderStatus();
// create full playlistPaths
/*for (int i = 0; i < tracksCount; i++) {
QString plPath(playlistPath);
// add track number to path name
if (stemExport) {
plPath = plPath + QLatin1Char('_') + QString(trackNames.at(i)).replace(QLatin1Char(' '), QLatin1Char('_'));
}
// add mlt suffix
if (!plPath.endsWith(mltSuffix)) {
plPath += mltSuffix;
}
playlistPaths << plPath;
qCDebug(KDENLIVE_LOG) << "playlistPath: " << plPath << endl;
// Do save scenelist
QFile file(plPath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
pCore->displayMessage(i18n("Cannot write to file %1", plPath), ErrorMessage);
return;
}
file.write(docList.at(i).toString().toUtf8());
if (file.error() != QFile::NoError) {
pCore->displayMessage(i18n("Cannot write to file %1", plPath), ErrorMessage);
file.close();
return;
}
file.close();
}*/
// slotExport(delayedRendering, in, out, project->metadata(), playlistPaths, trackNames, renderName, exportAudio);
}
void RenderWidget::slotExport(bool scriptExport, int zoneIn, int zoneOut, const QMap &metadata, const QList &playlistPaths,
const QList &trackNames, const QString &scriptPath, bool exportAudio)
{
// DEPRECATED
QTreeWidgetItem *item = m_view.formats->currentItem();
if (!item) {
return;
}
QString destBase = m_view.out_file->url().toLocalFile().trimmed();
if (destBase.isEmpty()) {
return;
}
// script file
QFile file(scriptPath);
int stemCount = playlistPaths.count();
bool stemExport = (!trackNames.isEmpty());
for (int stemIdx = 0; stemIdx < stemCount; stemIdx++) {
QString dest(destBase);
// on stem export append track name to each filename
if (stemExport) {
QFileInfo dfi(dest);
QStringList filePath;
// construct the full file path
filePath << dfi.absolutePath() << QDir::separator() << dfi.completeBaseName() + QLatin1Char('_')
<< QString(trackNames.at(stemIdx)).replace(QLatin1Char(' '), QLatin1Char('_')) << QStringLiteral(".") << dfi.suffix();
dest = filePath.join(QString());
}
// Check whether target file has an extension.
// If not, ask whether extension should be added or not.
QString extension = item->data(0, ExtensionRole).toString();
if (!dest.endsWith(extension, Qt::CaseInsensitive)) {
if (KMessageBox::questionYesNo(this, i18n("File has no extension. Add extension (%1)?", extension)) == KMessageBox::Yes) {
dest.append('.' + extension);
}
}
// Checks for image sequence
QStringList imageSequences;
imageSequences << QStringLiteral("jpg") << QStringLiteral("png") << QStringLiteral("bmp") << QStringLiteral("dpx") << QStringLiteral("ppm")
<< QStringLiteral("tga") << QStringLiteral("tif");
if (imageSequences.contains(extension)) {
// format string for counter?
if (!QRegExp(QStringLiteral(".*%[0-9]*d.*")).exactMatch(dest)) {
dest = dest.section(QLatin1Char('.'), 0, -2) + QStringLiteral("_%05d.") + extension;
}
}
if (QFile::exists(dest)) {
if (KMessageBox::warningYesNo(this, i18n("Output file already exists. Do you want to overwrite it?")) != KMessageBox::Yes) {
for (const QString &playlistFilePath : playlistPaths) {
QFile playlistFile(playlistFilePath);
if (playlistFile.exists()) {
playlistFile.remove();
}
}
return;
}
}
// Generate script file
QStringList overlayargs;
if (m_view.tc_overlay->isChecked()) {
QString filterFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("metadata.properties"));
overlayargs << QStringLiteral("meta.attr.timecode=1")
<< "meta.attr.timecode.markup=#" + QString(m_view.tc_type->currentIndex() != 0 ? "frame" : "timecode");
overlayargs << QStringLiteral("-attach") << QStringLiteral("data_feed:attr_check") << QStringLiteral("-attach");
overlayargs << "data_show:" + filterFile << QStringLiteral("_loader=1") << QStringLiteral("dynamic=1");
}
QStringList render_process_args;
if (!scriptExport) {
render_process_args << QStringLiteral("-erase");
}
#ifndef Q_OS_WIN
if (KdenliveSettings::usekuiserver()) {
render_process_args << QStringLiteral("-kuiserver");
}
// get process id
render_process_args << QStringLiteral("-pid:%1").arg(QCoreApplication::applicationPid());
#endif
// Set locale for render process if required
if (QLocale().decimalPoint() != QLocale::system().decimalPoint()) {
;
#ifndef Q_OS_MAC
const QString currentLocale = setlocale(LC_NUMERIC, nullptr);
#else
const QString currentLocale = setlocale(LC_NUMERIC_MASK, nullptr);
#endif
render_process_args << QStringLiteral("-locale:%1").arg(currentLocale);
}
QString renderArgs = m_view.advanced_params->toPlainText().simplified();
QString std = renderArgs;
// Check for fps change
double forcedfps = 0;
if (std.startsWith(QLatin1String("r="))) {
QString sub = std.section(QLatin1Char(' '), 0, 0).toLower();
sub = sub.section(QLatin1Char('='), 1, 1);
forcedfps = sub.toDouble();
} else if (std.contains(QStringLiteral(" r="))) {
QString sub = std.section(QStringLiteral(" r="), 1, 1);
sub = sub.section(QLatin1Char(' '), 0, 0).toLower();
forcedfps = sub.toDouble();
} else if (std.contains(QStringLiteral("mlt_profile="))) {
QString sub = std.section(QStringLiteral("mlt_profile="), 1, 1);
sub = sub.section(QLatin1Char(' '), 0, 0).toLower();
forcedfps = ProfileRepository::get()->getProfile(sub)->fps();
}
bool resizeProfile = false;
std::unique_ptr &profile = pCore->getCurrentProfile();
if (renderArgs.contains(QLatin1String("%dv_standard"))) {
QString dvstd;
if (fmod((double)profile->frame_rate_num() / profile->frame_rate_den(), 30.01) > 27) {
dvstd = QStringLiteral("ntsc");
if (!(profile->frame_rate_num() == 30000 && profile->frame_rate_den() == 1001)) {
forcedfps = 30000.0 / 1001;
}
if (!(profile->width() == 720 && profile->height() == 480)) {
resizeProfile = true;
}
} else {
dvstd = QStringLiteral("pal");
if (!(profile->frame_rate_num() == 25 && profile->frame_rate_den() == 1)) {
forcedfps = 25;
}
if (!(profile->width() == 720 && profile->height() == 576)) {
resizeProfile = true;
}
}
if ((double)profile->display_aspect_num() / profile->display_aspect_den() > 1.5) {
dvstd += QLatin1String("_wide");
}
renderArgs.replace(QLatin1String("%dv_standard"), dvstd);
}
// If there is an fps change, we need to use the producer consumer AND update the in/out points
if (forcedfps > 0 && qAbs((int)100 * forcedfps - ((int)100 * profile->frame_rate_num() / profile->frame_rate_den())) > 2) {
resizeProfile = true;
double ratio = profile->frame_rate_num() / profile->frame_rate_den() / forcedfps;
if (ratio > 0) {
zoneIn /= ratio;
zoneOut /= ratio;
}
}
if (m_view.render_guide->isChecked()) {
double fps = profile->fps();
double guideStart = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble();
double guideEnd = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble();
render_process_args << "in=" + QString::number((int)GenTime(guideStart).frames(fps))
<< "out=" + QString::number((int)GenTime(guideEnd).frames(fps));
} else {
render_process_args << "in=" + QString::number(zoneIn) << "out=" + QString::number(zoneOut);
}
if (!overlayargs.isEmpty()) {
render_process_args << "preargs=" + overlayargs.join(QLatin1Char(' '));
}
render_process_args << profile->path() << item->data(0, RenderRole).toString();
if (!scriptExport && m_view.play_after->isChecked()) {
QMimeDatabase db;
QMimeType mime = db.mimeTypeForFile(dest);
KService::Ptr serv = KMimeTypeTrader::self()->preferredService(mime.name());
if (serv) {
KIO::DesktopExecParser parser(*serv, QList() << QUrl::fromLocalFile(QUrl::toPercentEncoding(dest)));
render_process_args << parser.resultingArguments().join(QLatin1Char(' '));
} else {
// no service found to play MIME type
// TODO: inform user
// errorMessage(PlaybackError, i18n("No service found to play %1", mime.name()));
render_process_args << QStringLiteral("-");
}
} else {
render_process_args << QStringLiteral("-");
}
if (m_view.speed->isEnabled()) {
renderArgs.append(QChar(' ') + item->data(0, SpeedsRole).toStringList().at(m_view.speed->value()));
}
// Project metadata
if (m_view.export_meta->isChecked()) {
QMap::const_iterator i = metadata.constBegin();
while (i != metadata.constEnd()) {
renderArgs.append(QStringLiteral(" %1=%2").arg(i.key(), QString(QUrl::toPercentEncoding(i.value()))));
++i;
}
}
// Adjust frame scale
int width;
int height;
if (m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
width = m_view.rescale_width->value();
height = m_view.rescale_height->value();
} else {
width = profile->width();
height = profile->height();
}
// Adjust scanning
if (m_view.scanning_list->currentIndex() == 1) {
renderArgs.append(QStringLiteral(" progressive=1"));
} else if (m_view.scanning_list->currentIndex() == 2) {
renderArgs.append(QStringLiteral(" progressive=0"));
}
// disable audio if requested
if (!exportAudio) {
renderArgs.append(QStringLiteral(" an=1 "));
}
int threadCount = QThread::idealThreadCount();
if (threadCount > 2 && m_view.parallel_process->isChecked()) {
threadCount = qMin(threadCount - 1, 4);
} else {
threadCount = 1;
}
// Set the thread counts
if (!renderArgs.contains(QStringLiteral("threads="))) {
renderArgs.append(QStringLiteral(" threads=%1").arg(KdenliveSettings::encodethreads()));
}
renderArgs.append(QStringLiteral(" real_time=-%1").arg(threadCount));
// Check if the rendering profile is different from project profile,
// in which case we need to use the producer_consumer from MLT
QString subsize;
if (std.startsWith(QLatin1String("s="))) {
subsize = std.section(QLatin1Char(' '), 0, 0).toLower();
subsize = subsize.section(QLatin1Char('='), 1, 1);
} else if (std.contains(QStringLiteral(" s="))) {
subsize = std.section(QStringLiteral(" s="), 1, 1);
subsize = subsize.section(QLatin1Char(' '), 0, 0).toLower();
} else if (m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
subsize = QStringLiteral(" s=%1x%2").arg(width).arg(height);
// Add current size parameter
renderArgs.append(subsize);
}
// Check if we need to embed the playlist into the producer consumer
// That is required if PAR != 1
if (profile->sample_aspect_num() != profile->sample_aspect_den() && subsize.isEmpty()) {
resizeProfile = true;
}
QStringList paramsList = renderArgs.split(' ', QString::SkipEmptyParts);
for (int i = 0; i < paramsList.count(); ++i) {
QString paramName = paramsList.at(i).section(QLatin1Char('='), 0, -2);
QString paramValue = paramsList.at(i).section(QLatin1Char('='), -1);
// If the profiles do not match we need to use the consumer tag
if (paramName == QLatin1String("mlt_profile") && paramValue != profile->path()) {
resizeProfile = true;
}
// evaluate expression
if (paramValue.startsWith(QLatin1Char('%'))) {
if (paramValue.startsWith(QStringLiteral("%bitrate")) || paramValue == QStringLiteral("%quality")) {
if (paramValue.contains("+'k'"))
paramValue = QString::number(m_view.video->value()) + 'k';
else
paramValue = QString::number(m_view.video->value());
}
if (paramValue.startsWith(QStringLiteral("%audiobitrate")) || paramValue == QStringLiteral("%audioquality")) {
if (paramValue.contains("+'k'"))
paramValue = QString::number(m_view.audio->value()) + 'k';
else
paramValue = QString::number(m_view.audio->value());
}
if (paramValue == QStringLiteral("%dar"))
paramValue = '@' + QString::number(profile->display_aspect_num()) + QLatin1Char('/') + QString::number(profile->display_aspect_den());
if (paramValue == QStringLiteral("%passes")) paramValue = QString::number(static_cast(m_view.checkTwoPass->isChecked()) + 1);
paramsList[i] = paramName + QLatin1Char('=') + paramValue;
}
}
/*if (resizeProfile && !KdenliveSettings::gpu_accel()) {
render_process_args << "consumer:" + (scriptExport ? ScriptGetVar("SOURCE_" + QString::number(stemIdx))
: QUrl::fromLocalFile(playlistPaths.at(stemIdx)).toEncoded());
} else {
render_process_args << (scriptExport ? ScriptGetVar("SOURCE_" + QString::number(stemIdx))
: QUrl::fromLocalFile(playlistPaths.at(stemIdx)).toEncoded());
}
render_process_args << (scriptExport ? ScriptGetVar("TARGET_" + QString::number(stemIdx)) : QUrl::fromLocalFile(dest).toEncoded());*/
if (KdenliveSettings::gpu_accel()) {
render_process_args << QStringLiteral("glsl.=1");
}
render_process_args << paramsList;
if (scriptExport) {
QTextStream outStream(&file);
QString stemIdxStr(QString::number(stemIdx));
/*outStream << ScriptSetVar("SOURCE_" + stemIdxStr, QUrl::fromLocalFile(playlistPaths.at(stemIdx)).toEncoded()) << '\n';
outStream << ScriptSetVar("TARGET_" + stemIdxStr, QUrl::fromLocalFile(dest).toEncoded()) << '\n';
outStream << ScriptSetVar("PARAMETERS_" + stemIdxStr, render_process_args.join(QLatin1Char(' '))) << '\n';
outStream << ScriptGetVar("RENDERER") + " " + ScriptGetVar("PARAMETERS_" + stemIdxStr) << "\n";*/
if (stemIdx == (stemCount - 1)) {
if (file.error() != QFile::NoError) {
KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath));
file.close();
return;
}
file.close();
QFile::setPermissions(scriptPath, file.permissions() | QFile::ExeUser);
QTimer::singleShot(400, this, &RenderWidget::parseScriptFiles);
m_view.tabWidget->setCurrentIndex(2);
return;
}
continue;
}
// Save rendering profile to document
QMap renderProps;
renderProps.insert(QStringLiteral("rendercategory"), m_view.formats->currentItem()->parent()->text(0));
renderProps.insert(QStringLiteral("renderprofile"), m_view.formats->currentItem()->text(0));
renderProps.insert(QStringLiteral("renderurl"), destBase);
renderProps.insert(QStringLiteral("renderzone"), QString::number(static_cast(m_view.render_zone->isChecked())));
renderProps.insert(QStringLiteral("renderguide"), QString::number(static_cast(m_view.render_guide->isChecked())));
renderProps.insert(QStringLiteral("renderstartguide"), QString::number(m_view.guide_start->currentIndex()));
renderProps.insert(QStringLiteral("renderendguide"), QString::number(m_view.guide_end->currentIndex()));
renderProps.insert(QStringLiteral("renderscanning"), QString::number(m_view.scanning_list->currentIndex()));
renderProps.insert(QStringLiteral("renderfield"), QString::number(m_view.field_order->currentIndex()));
int export_audio = 0;
if (m_view.export_audio->checkState() == Qt::Checked) {
export_audio = 2;
} else if (m_view.export_audio->checkState() == Qt::Unchecked) {
export_audio = 1;
}
renderProps.insert(QStringLiteral("renderexportaudio"), QString::number(export_audio));
renderProps.insert(QStringLiteral("renderrescale"), QString::number(static_cast(m_view.rescale->isChecked())));
renderProps.insert(QStringLiteral("renderrescalewidth"), QString::number(m_view.rescale_width->value()));
renderProps.insert(QStringLiteral("renderrescaleheight"), QString::number(m_view.rescale_height->value()));
renderProps.insert(QStringLiteral("rendertcoverlay"), QString::number(static_cast(m_view.tc_overlay->isChecked())));
renderProps.insert(QStringLiteral("rendertctype"), QString::number(m_view.tc_type->currentIndex()));
renderProps.insert(QStringLiteral("renderratio"), QString::number(static_cast(m_view.rescale_keep->isChecked())));
renderProps.insert(QStringLiteral("renderplay"), QString::number(static_cast(m_view.play_after->isChecked())));
renderProps.insert(QStringLiteral("rendertwopass"), QString::number(static_cast(m_view.checkTwoPass->isChecked())));
renderProps.insert(QStringLiteral("renderquality"), QString::number(m_view.video->value()));
renderProps.insert(QStringLiteral("renderaudioquality"), QString::number(m_view.audio->value()));
renderProps.insert(QStringLiteral("renderspeed"), QString::number(m_view.speed->value()));
emit selectedRenderProfile(renderProps);
// insert item in running jobs list
RenderJobItem *renderItem = nullptr;
QList existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
if (!existing.isEmpty()) {
renderItem = static_cast(existing.at(0));
if (renderItem->status() == RUNNINGJOB || renderItem->status() == WAITINGJOB || renderItem->status() == STARTINGJOB) {
KMessageBox::information(this,
i18n("There is already a job writing file:
%1
Abort the job if you want to overwrite it...", dest),
i18n("Already running"));
return;
}
/*if (renderItem->type() != DirectRenderType) {
delete renderItem;
renderItem = nullptr;
} else {
renderItem->setData(1, ProgressRole, 0);
renderItem->setStatus(WAITINGJOB);
renderItem->setIcon(0, QIcon::fromTheme(QStringLiteral("media-playback-pause")));
renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
renderItem->setData(1, ParametersRole, dest);
}*/
}
if (!renderItem) {
renderItem = new RenderJobItem(m_view.running_jobs, QStringList() << QString() << dest);
}
renderItem->setData(1, TimeRole, QDateTime::currentDateTime());
// Set rendering type
/*if (group == QLatin1String("dvd")) {
if (m_view.open_dvd->isChecked()) {
renderItem->setData(0, Qt::UserRole, group);
if (renderArgs.contains(QStringLiteral("mlt_profile="))) {
//TODO: probably not valid anymore (no more MLT profiles in args)
// rendering profile contains an MLT profile, so pass it to the running jog item, useful for dvd
QString prof = renderArgs.section(QStringLiteral("mlt_profile="), 1, 1);
prof = prof.section(QLatin1Char(' '), 0, 0);
qCDebug(KDENLIVE_LOG) << "// render profile: " << prof;
renderItem->setMetadata(prof);
}
}
} else {
if (group == QLatin1String("websites") && m_view.open_browser->isChecked()) {
renderItem->setData(0, Qt::UserRole, group);
// pass the url
QString url = m_view.formats->currentItem()->data(ExtraRole).toString();
renderItem->setMetadata(url);
}
}*/
renderItem->setData(1, ParametersRole, render_process_args);
if (!exportAudio) {
renderItem->setData(1, ExtraInfoRole, i18n("Video without audio track"));
} else {
renderItem->setData(1, ExtraInfoRole, QString());
}
m_view.running_jobs->setCurrentItem(renderItem);
m_view.tabWidget->setCurrentIndex(1);
// check render status
checkRenderStatus();
} // end loop
}
void RenderWidget::checkRenderStatus()
{
// check if we have a job waiting to render
if (m_blockProcessing) {
return;
}
auto *item = static_cast(m_view.running_jobs->topLevelItem(0));
// Make sure no other rendering is running
while (item != nullptr) {
if (item->status() == RUNNINGJOB) {
return;
}
item = static_cast(m_view.running_jobs->itemBelow(item));
}
item = static_cast(m_view.running_jobs->topLevelItem(0));
bool waitingJob = false;
// Find first waiting job
while (item != nullptr) {
if (item->status() == WAITINGJOB) {
item->setData(1, TimeRole, QDateTime::currentDateTime());
waitingJob = true;
startRendering(item);
// Check for 2 pass encoding
QStringList jobData = item->data(1, ParametersRole).toStringList();
if (jobData.size() > 2 && jobData.at(1).endsWith(QStringLiteral("-pass2.mlt"))) {
// Find and remove 1st pass job
QTreeWidgetItem *above = m_view.running_jobs->itemAbove(item);
QString firstPassName = jobData.at(1).section(QLatin1Char('-'), 0, -2) + QStringLiteral(".mlt");
while (above) {
QStringList aboveData = above->data(1, ParametersRole).toStringList();
qDebug() << "// GOT JOB: " << aboveData.at(1);
if (aboveData.size() > 2 && aboveData.at(1) == firstPassName) {
delete above;
break;
}
above = m_view.running_jobs->itemAbove(above);
}
}
item->setStatus(STARTINGJOB);
break;
}
item = static_cast(m_view.running_jobs->itemBelow(item));
}
if (!waitingJob && m_view.shutdown->isChecked()) {
emit shutdown();
}
}
void RenderWidget::startRendering(RenderJobItem *item)
{
auto rendererArgs = item->data(1, ParametersRole).toStringList();
qDebug() << "starting kdenlive_render process using: " << m_renderer;
if (!QProcess::startDetached(m_renderer, rendererArgs)) {
item->setStatus(FAILEDJOB);
} else {
KNotification::event(QStringLiteral("RenderStarted"), i18n("Rendering %1 started", item->text(1)), QPixmap(), this);
}
}
int RenderWidget::waitingJobsCount() const
{
int count = 0;
auto *item = static_cast(m_view.running_jobs->topLevelItem(0));
while (item != nullptr) {
if (item->status() == WAITINGJOB || item->status() == STARTINGJOB) {
count++;
}
item = static_cast(m_view.running_jobs->itemBelow(item));
}
return count;
}
void RenderWidget::adjustViewToProfile()
{
m_view.scanning_list->setCurrentIndex(0);
m_view.rescale_width->setValue(KdenliveSettings::defaultrescalewidth());
if (!m_view.rescale_keep->isChecked()) {
m_view.rescale_height->blockSignals(true);
m_view.rescale_height->setValue(KdenliveSettings::defaultrescaleheight());
m_view.rescale_height->blockSignals(false);
}
refreshView();
}
void RenderWidget::refreshView()
{
m_view.formats->blockSignals(true);
QIcon brokenIcon = QIcon::fromTheme(QStringLiteral("dialog-close"));
QIcon warningIcon = QIcon::fromTheme(QStringLiteral("dialog-warning"));
KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window);
const QColor disabled = scheme.foreground(KColorScheme::InactiveText).color();
const QColor disabledbg = scheme.background(KColorScheme::NegativeBackground).color();
// We borrow a reference to the profile's pointer to query it more easily
std::unique_ptr &profile = pCore->getCurrentProfile();
double project_framerate = (double)profile->frame_rate_num() / profile->frame_rate_den();
for (int i = 0; i < m_view.formats->topLevelItemCount(); ++i) {
QTreeWidgetItem *group = m_view.formats->topLevelItem(i);
for (int j = 0; j < group->childCount(); ++j) {
QTreeWidgetItem *item = group->child(j);
QString std = item->data(0, StandardRole).toString();
if (std.isEmpty() ||
(std.contains(QStringLiteral("PAL"), Qt::CaseInsensitive) && profile->frame_rate_num() == 25 && profile->frame_rate_den() == 1) ||
(std.contains(QStringLiteral("NTSC"), Qt::CaseInsensitive) && profile->frame_rate_num() == 30000 && profile->frame_rate_den() == 1001)) {
// Standard OK
} else {
item->setData(0, ErrorRole, i18n("Standard (%1) not compatible with project profile (%2)", std, project_framerate));
item->setIcon(0, brokenIcon);
item->setForeground(0, disabled);
continue;
}
QString params = item->data(0, ParamsRole).toString();
// Make sure the selected profile uses the same frame rate as project profile
if (params.contains(QStringLiteral("mlt_profile="))) {
QString profile_str = params.section(QStringLiteral("mlt_profile="), 1, 1).section(QLatin1Char(' '), 0, 0);
std::unique_ptr &target_profile = ProfileRepository::get()->getProfile(profile_str);
if (target_profile->frame_rate_den() > 0) {
double profile_rate = (double)target_profile->frame_rate_num() / target_profile->frame_rate_den();
if ((int)(1000.0 * profile_rate) != (int)(1000.0 * project_framerate)) {
item->setData(0, ErrorRole, i18n("Frame rate (%1) not compatible with project profile (%2)", profile_rate, project_framerate));
item->setIcon(0, brokenIcon);
item->setForeground(0, disabled);
continue;
}
}
}
// Make sure the selected profile uses an installed avformat codec / format
if (!supportedFormats.isEmpty()) {
QString format;
if (params.startsWith(QLatin1String("f="))) {
format = params.section(QStringLiteral("f="), 1, 1);
} else if (params.contains(QStringLiteral(" f="))) {
format = params.section(QStringLiteral(" f="), 1, 1);
}
if (!format.isEmpty()) {
format = format.section(QLatin1Char(' '), 0, 0).toLower();
if (!supportedFormats.contains(format)) {
item->setData(0, ErrorRole, i18n("Unsupported video format: %1", format));
item->setIcon(0, brokenIcon);
item->setForeground(0, disabled);
continue;
}
}
}
if (!acodecsList.isEmpty()) {
QString format;
if (params.startsWith(QLatin1String("acodec="))) {
format = params.section(QStringLiteral("acodec="), 1, 1);
} else if (params.contains(QStringLiteral(" acodec="))) {
format = params.section(QStringLiteral(" acodec="), 1, 1);
}
if (!format.isEmpty()) {
format = format.section(QLatin1Char(' '), 0, 0).toLower();
if (!acodecsList.contains(format)) {
item->setData(0, ErrorRole, i18n("Unsupported audio codec: %1", format));
item->setIcon(0, brokenIcon);
item->setForeground(0, disabled);
item->setBackground(0, disabledbg);
}
}
}
if (!vcodecsList.isEmpty()) {
QString format;
if (params.startsWith(QLatin1String("vcodec="))) {
format = params.section(QStringLiteral("vcodec="), 1, 1);
} else if (params.contains(QStringLiteral(" vcodec="))) {
format = params.section(QStringLiteral(" vcodec="), 1, 1);
}
if (!format.isEmpty()) {
format = format.section(QLatin1Char(' '), 0, 0).toLower();
if (!vcodecsList.contains(format)) {
item->setData(0, ErrorRole, i18n("Unsupported video codec: %1", format));
item->setIcon(0, brokenIcon);
item->setForeground(0, disabled);
continue;
}
}
}
if (params.contains(QStringLiteral(" profile=")) || params.startsWith(QLatin1String("profile="))) {
// changed in MLT commit d8a3a5c9190646aae72048f71a39ee7446a3bd45
// (http://www.mltframework.org/gitweb/mlt.git?p=mltframework.org/mlt.git;a=commit;h=d8a3a5c9190646aae72048f71a39ee7446a3bd45)
item->setData(0, ErrorRole,
i18n("This render profile uses a 'profile' parameter.
Unless you know what you are doing you will probably "
"have to change it to 'mlt_profile'."));
item->setIcon(0, warningIcon);
continue;
}
}
}
focusFirstVisibleItem();
m_view.formats->blockSignals(false);
refreshParams();
}
QUrl RenderWidget::filenameWithExtension(QUrl url, const QString &extension)
{
if (!url.isValid()) {
url = QUrl::fromLocalFile(pCore->currentDoc()->projectDataFolder() + QDir::separator());
}
QString directory = url.adjusted(QUrl::RemoveFilename).toLocalFile();
QString filename = url.fileName();
QString ext;
if (extension.at(0) == '.') {
ext = extension;
} else {
ext = '.' + extension;
}
if (filename.isEmpty()) {
filename = i18n("untitled");
}
int pos = filename.lastIndexOf('.');
if (pos == 0) {
filename.append(ext);
} else {
if (!filename.endsWith(ext, Qt::CaseInsensitive)) {
filename = filename.left(pos) + ext;
}
}
return QUrl::fromLocalFile(directory + filename);
}
void RenderWidget::refreshParams()
{
// Format not available (e.g. codec not installed); Disable start button
QTreeWidgetItem *item = m_view.formats->currentItem();
if ((item == nullptr) || item->parent() == nullptr) {
// This is a category item, not a real profile
m_view.buttonBox->setEnabled(false);
} else {
m_view.buttonBox->setEnabled(true);
}
QString extension;
if (item) {
extension = item->data(0, ExtensionRole).toString();
}
if ((item == nullptr) || item->isHidden() || extension.isEmpty()) {
if (!item) {
errorMessage(ProfileError, i18n("No matching profile"));
} else if (!item->parent()) // category
;
else if (extension.isEmpty()) {
errorMessage(ProfileError, i18n("Invalid profile"));
}
m_view.advanced_params->clear();
m_view.buttonRender->setEnabled(false);
m_view.buttonGenerateScript->setEnabled(false);
return;
}
QString params = item->data(0, ParamsRole).toString();
errorMessage(ProfileError, item->data(0, ErrorRole).toString());
m_view.advanced_params->setPlainText(params);
if (params.contains(QStringLiteral(" s=")) || params.startsWith(QLatin1String("s=")) || params.contains(QLatin1String("%dv_standard"))) {
// profile has a fixed size, do not allow resize
m_view.rescale->setEnabled(false);
setRescaleEnabled(false);
} else {
m_view.rescale->setEnabled(true);
setRescaleEnabled(m_view.rescale->isChecked());
}
QUrl url = filenameWithExtension(m_view.out_file->url(), extension);
m_view.out_file->setUrl(url);
// if (!url.isEmpty()) {
// QString path = url.path();
// int pos = path.lastIndexOf('.') + 1;
// if (pos == 0) path.append('.' + extension);
// else path = path.left(pos) + extension;
// m_view.out_file->setUrl(QUrl(path));
// } else {
// m_view.out_file->setUrl(QUrl(QDir::homePath() + QStringLiteral("/untitled.") + extension));
// }
m_view.out_file->setFilter("*." + extension);
QString edit = item->data(0, EditableRole).toString();
if (edit.isEmpty() || !edit.endsWith(QLatin1String("customprofiles.xml"))) {
m_view.buttonDelete->setEnabled(false);
m_view.buttonEdit->setEnabled(false);
} else {
m_view.buttonDelete->setEnabled(true);
m_view.buttonEdit->setEnabled(true);
}
// video quality control
m_view.video->blockSignals(true);
bool quality = false;
if ((params.contains(QStringLiteral("%quality")) || params.contains(QStringLiteral("%bitrate"))) &&
item->data(0, BitratesRole).canConvert(QVariant::StringList)) {
// bitrates or quantizers list
QStringList qs = item->data(0, BitratesRole).toStringList();
if (qs.count() > 1) {
quality = true;
int qmax = qs.constFirst().toInt();
int qmin = qs.last().toInt();
if (qmax < qmin) {
// always show best quality on right
m_view.video->setRange(qmax, qmin);
m_view.video->setProperty("decreasing", true);
} else {
m_view.video->setRange(qmin, qmax);
m_view.video->setProperty("decreasing", false);
}
}
}
m_view.video->setEnabled(quality);
m_view.quality->setEnabled(quality);
m_view.qualityLabel->setEnabled(quality);
m_view.video->blockSignals(false);
// audio quality control
quality = false;
m_view.audio->blockSignals(true);
if ((params.contains(QStringLiteral("%audioquality")) || params.contains(QStringLiteral("%audiobitrate"))) &&
item->data(0, AudioBitratesRole).canConvert(QVariant::StringList)) {
// bitrates or quantizers list
QStringList qs = item->data(0, AudioBitratesRole).toStringList();
if (qs.count() > 1) {
quality = true;
int qmax = qs.constFirst().toInt();
int qmin = qs.last().toInt();
if (qmax < qmin) {
m_view.audio->setRange(qmax, qmin);
m_view.audio->setProperty("decreasing", true);
} else {
m_view.audio->setRange(qmin, qmax);
m_view.audio->setProperty("decreasing", false);
}
if (params.contains(QStringLiteral("%audiobitrate"))) {
m_view.audio->setSingleStep(32); // 32kbps step
} else {
m_view.audio->setSingleStep(1);
}
}
}
m_view.audio->setEnabled(quality);
m_view.audio->blockSignals(false);
if (m_view.quality->isEnabled()) {
adjustAVQualities(m_view.quality->value());
}
if (item->data(0, SpeedsRole).canConvert(QVariant::StringList) && (item->data(0, SpeedsRole).toStringList().count() != 0)) {
int speed = item->data(0, SpeedsRole).toStringList().count() - 1;
m_view.speed->setEnabled(true);
m_view.speed->setMaximum(speed);
m_view.speed->setValue(speed * 3 / 4); // default to intermediate speed
} else {
m_view.speed->setEnabled(false);
}
if (!item->data(0, FieldRole).isNull()) {
m_view.field_order->setCurrentIndex(item->data(0, FieldRole).toInt());
}
adjustSpeed(m_view.speed->value());
bool passes = params.contains(QStringLiteral("passes"));
m_view.checkTwoPass->setEnabled(passes);
m_view.checkTwoPass->setChecked(passes && params.contains(QStringLiteral("passes=2")));
m_view.encoder_threads->setEnabled(!params.contains(QStringLiteral("threads=")));
m_view.buttonRender->setEnabled(m_view.formats->currentItem()->data(0, ErrorRole).isNull());
m_view.buttonGenerateScript->setEnabled(m_view.formats->currentItem()->data(0, ErrorRole).isNull());
}
void RenderWidget::reloadProfiles()
{
parseProfiles();
}
void RenderWidget::parseProfiles(const QString &selectedProfile)
{
m_view.formats->clear();
// Parse our xml profile
QString exportFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("export/profiles.xml"));
parseFile(exportFile, false);
// Parse some MLT's profiles
parseMltPresets();
QString exportFolder = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/export/");
QDir directory(exportFolder);
QStringList filter;
filter << QStringLiteral("*.xml");
QStringList fileList = directory.entryList(filter, QDir::Files);
// We should parse customprofiles.xml in last position, so that user profiles
// can also override profiles installed by KNewStuff
fileList.removeAll(QStringLiteral("customprofiles.xml"));
for (const QString &filename : fileList) {
parseFile(directory.absoluteFilePath(filename), true);
}
if (QFile::exists(exportFolder + QStringLiteral("customprofiles.xml"))) {
parseFile(exportFolder + QStringLiteral("customprofiles.xml"), true);
}
focusFirstVisibleItem(selectedProfile);
}
void RenderWidget::parseMltPresets()
{
QDir root(KdenliveSettings::mltpath());
if (!root.cd(QStringLiteral("../presets/consumer/avformat"))) {
// Cannot find MLT's presets directory
qCWarning(KDENLIVE_LOG) << " / / / WARNING, cannot find MLT's preset folder";
return;
}
if (root.cd(QStringLiteral("lossless"))) {
QString groupName = i18n("Lossless/HQ");
QList foundGroup = m_view.formats->findItems(groupName, Qt::MatchExactly);
QTreeWidgetItem *groupItem;
if (!foundGroup.isEmpty()) {
groupItem = foundGroup.takeFirst();
} else {
groupItem = new QTreeWidgetItem(QStringList(groupName));
m_view.formats->addTopLevelItem(groupItem);
groupItem->setExpanded(true);
}
const QStringList profiles = root.entryList(QDir::Files, QDir::Name);
for (const QString &prof : profiles) {
KConfig config(root.absoluteFilePath(prof), KConfig::SimpleConfig);
KConfigGroup group = config.group(QByteArray());
QString vcodec = group.readEntry("vcodec");
QString acodec = group.readEntry("acodec");
QString extension = group.readEntry("meta.preset.extension");
QString note = group.readEntry("meta.preset.note");
QString profileName = prof;
if (!vcodec.isEmpty() || !acodec.isEmpty()) {
profileName.append(" (");
if (!vcodec.isEmpty()) {
profileName.append(vcodec);
if (!acodec.isEmpty()) {
profileName.append("+" + acodec);
}
} else if (!acodec.isEmpty()) {
profileName.append(acodec);
}
profileName.append(QLatin1Char(')'));
}
QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(profileName));
item->setData(0, ExtensionRole, extension);
item->setData(0, RenderRole, "avformat");
item->setData(0, ParamsRole, QString("properties=lossless/" + prof));
if (!note.isEmpty()) {
item->setToolTip(0, note);
}
groupItem->addChild(item);
}
}
if (root.cd(QStringLiteral("../stills"))) {
QString groupName = i18nc("Category Name", "Images sequence");
QList foundGroup = m_view.formats->findItems(groupName, Qt::MatchExactly);
QTreeWidgetItem *groupItem;
if (!foundGroup.isEmpty()) {
groupItem = foundGroup.takeFirst();
} else {
groupItem = new QTreeWidgetItem(QStringList(groupName));
m_view.formats->addTopLevelItem(groupItem);
groupItem->setExpanded(true);
}
QStringList profiles = root.entryList(QDir::Files, QDir::Name);
for (const QString &prof : profiles) {
QTreeWidgetItem *item = loadFromMltPreset(groupName, root.absoluteFilePath(prof), prof);
if (!item) {
continue;
}
item->setData(0, ParamsRole, QString("properties=stills/" + prof));
groupItem->addChild(item);
}
// Add GIF as image sequence
root.cdUp();
QTreeWidgetItem *item = loadFromMltPreset(groupName, root.absoluteFilePath(QStringLiteral("GIF")), QStringLiteral("GIF"));
if (item) {
item->setData(0, ParamsRole, QStringLiteral("properties=GIF"));
groupItem->addChild(item);
}
}
}
QTreeWidgetItem *RenderWidget::loadFromMltPreset(const QString &groupName, const QString &path, const QString &profileName)
{
KConfig config(path, KConfig::SimpleConfig);
KConfigGroup group = config.group(QByteArray());
QString extension = group.readEntry("meta.preset.extension");
QString note = group.readEntry("meta.preset.note");
if (extension.isEmpty()) {
return nullptr;
}
QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(profileName));
item->setData(0, GroupRole, groupName);
item->setData(0, ExtensionRole, extension);
item->setData(0, RenderRole, "avformat");
if (!note.isEmpty()) {
item->setToolTip(0, note);
}
return item;
}
void RenderWidget::parseFile(const QString &exportFile, bool editable)
{
QDomDocument doc;
QFile file(exportFile);
doc.setContent(&file, false);
file.close();
QDomElement documentElement;
QDomElement profileElement;
QString extension;
QDomNodeList groups = doc.elementsByTagName(QStringLiteral("group"));
QTreeWidgetItem *item = nullptr;
bool replaceVorbisCodec = false;
if (acodecsList.contains(QStringLiteral("libvorbis"))) {
replaceVorbisCodec = true;
}
bool replaceLibfaacCodec = false;
if (acodecsList.contains(QStringLiteral("libfaac"))) {
replaceLibfaacCodec = true;
}
if (editable || groups.isEmpty()) {
QDomElement profiles = doc.documentElement();
if (editable && profiles.attribute(QStringLiteral("version"), nullptr).toInt() < 1) {
// this is an old profile version, update it
QDomDocument newdoc;
QDomElement newprofiles = newdoc.createElement(QStringLiteral("profiles"));
newprofiles.setAttribute(QStringLiteral("version"), 1);
newdoc.appendChild(newprofiles);
QDomNodeList profilelist = doc.elementsByTagName(QStringLiteral("profile"));
for (int i = 0; i < profilelist.count(); ++i) {
QString category = i18nc("Category Name", "Custom");
QString ext;
QDomNode parent = profilelist.at(i).parentNode();
if (!parent.isNull()) {
QDomElement parentNode = parent.toElement();
if (parentNode.hasAttribute(QStringLiteral("name"))) {
category = parentNode.attribute(QStringLiteral("name"));
}
ext = parentNode.attribute(QStringLiteral("extension"));
}
if (!profilelist.at(i).toElement().hasAttribute(QStringLiteral("category"))) {
profilelist.at(i).toElement().setAttribute(QStringLiteral("category"), category);
}
if (!ext.isEmpty()) {
profilelist.at(i).toElement().setAttribute(QStringLiteral("extension"), ext);
}
QDomNode n = profilelist.at(i).cloneNode();
newprofiles.appendChild(newdoc.importNode(n, true));
}
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
return;
}
QTextStream out(&file);
out << newdoc.toString();
file.close();
parseFile(exportFile, editable);
return;
}
QDomNode node = doc.elementsByTagName(QStringLiteral("profile")).at(0);
if (node.isNull()) {
return;
}
int count = 1;
while (!node.isNull()) {
QDomElement profile = node.toElement();
QString profileName = profile.attribute(QStringLiteral("name"));
QString standard = profile.attribute(QStringLiteral("standard"));
QTextDocument docConvert;
docConvert.setHtml(profile.attribute(QStringLiteral("args")));
QString params = docConvert.toPlainText().simplified();
if (replaceVorbisCodec && params.contains(QStringLiteral("acodec=vorbis"))) {
// replace vorbis with libvorbis
params = params.replace(QLatin1String("=vorbis"), QLatin1String("=libvorbis"));
}
if (replaceLibfaacCodec && params.contains(QStringLiteral("acodec=aac"))) {
// replace libfaac with aac
params = params.replace(QLatin1String("aac"), QLatin1String("libfaac"));
}
QString prof_extension = profile.attribute(QStringLiteral("extension"));
if (!prof_extension.isEmpty()) {
extension = prof_extension;
}
QString groupName = profile.attribute(QStringLiteral("category"), i18nc("Category Name", "Custom"));
QList foundGroup = m_view.formats->findItems(groupName, Qt::MatchExactly);
QTreeWidgetItem *groupItem;
if (!foundGroup.isEmpty()) {
groupItem = foundGroup.takeFirst();
} else {
groupItem = new QTreeWidgetItem(QStringList(groupName));
if (editable) {
m_view.formats->insertTopLevelItem(0, groupItem);
} else {
m_view.formats->addTopLevelItem(groupItem);
groupItem->setExpanded(true);
}
}
// Check if item with same name already exists and replace it,
// allowing to override default profiles
QTreeWidgetItem *childitem = nullptr;
for (int j = 0; j < groupItem->childCount(); ++j) {
if (groupItem->child(j)->text(0) == profileName) {
childitem = groupItem->child(j);
break;
}
}
if (!childitem) {
childitem = new QTreeWidgetItem(QStringList(profileName));
}
childitem->setData(0, GroupRole, groupName);
childitem->setData(0, ExtensionRole, extension);
childitem->setData(0, RenderRole, "avformat");
childitem->setData(0, StandardRole, standard);
childitem->setData(0, ParamsRole, params);
if (params.contains(QLatin1String("%quality"))) {
childitem->setData(0, BitratesRole, profile.attribute(QStringLiteral("qualities")).split(QLatin1Char(','), QString::SkipEmptyParts));
} else if (params.contains(QLatin1String("%bitrate"))) {
childitem->setData(0, BitratesRole, profile.attribute(QStringLiteral("bitrates")).split(QLatin1Char(','), QString::SkipEmptyParts));
}
if (params.contains(QLatin1String("%audioquality"))) {
childitem->setData(0, AudioBitratesRole, profile.attribute(QStringLiteral("audioqualities")).split(QLatin1Char(','), QString::SkipEmptyParts));
} else if (params.contains(QLatin1String("%audiobitrate"))) {
childitem->setData(0, AudioBitratesRole, profile.attribute(QStringLiteral("audiobitrates")).split(QLatin1Char(','), QString::SkipEmptyParts));
}
if (profile.hasAttribute(QStringLiteral("speeds"))) {
childitem->setData(0, SpeedsRole, profile.attribute(QStringLiteral("speeds")).split(QLatin1Char(';'), QString::SkipEmptyParts));
}
if (profile.hasAttribute(QStringLiteral("url"))) {
childitem->setData(0, ExtraRole, profile.attribute(QStringLiteral("url")));
}
if (profile.hasAttribute(QStringLiteral("top_field_first"))) {
childitem->setData(0, FieldRole, profile.attribute(QStringLiteral("top_field_first")));
}
if (editable) {
childitem->setData(0, EditableRole, exportFile);
if (exportFile.endsWith(QLatin1String("customprofiles.xml"))) {
childitem->setIcon(0, QIcon::fromTheme(QStringLiteral("favorite")));
} else {
childitem->setIcon(0, QIcon::fromTheme(QStringLiteral("applications-internet")));
}
}
groupItem->addChild(childitem);
node = doc.elementsByTagName(QStringLiteral("profile")).at(count);
count++;
}
return;
}
int i = 0;
QString groupName;
QString profileName;
QString prof_extension;
QString renderer;
QString params;
QString standard;
while (!groups.item(i).isNull()) {
documentElement = groups.item(i).toElement();
QDomNode gname = documentElement.elementsByTagName(QStringLiteral("groupname")).at(0);
groupName = documentElement.attribute(QStringLiteral("name"), i18nc("Attribute Name", "Custom"));
extension = documentElement.attribute(QStringLiteral("extension"), QString());
renderer = documentElement.attribute(QStringLiteral("renderer"), QString());
QList foundGroup = m_view.formats->findItems(groupName, Qt::MatchExactly);
QTreeWidgetItem *groupItem;
if (!foundGroup.isEmpty()) {
groupItem = foundGroup.takeFirst();
} else {
groupItem = new QTreeWidgetItem(QStringList(groupName));
m_view.formats->addTopLevelItem(groupItem);
groupItem->setExpanded(true);
}
QDomNode n = groups.item(i).firstChild();
while (!n.isNull()) {
if (n.toElement().tagName() != QLatin1String("profile")) {
n = n.nextSibling();
continue;
}
profileElement = n.toElement();
profileName = profileElement.attribute(QStringLiteral("name"));
standard = profileElement.attribute(QStringLiteral("standard"));
params = profileElement.attribute(QStringLiteral("args")).simplified();
if (replaceVorbisCodec && params.contains(QStringLiteral("acodec=vorbis"))) {
// replace vorbis with libvorbis
params = params.replace(QLatin1String("=vorbis"), QLatin1String("=libvorbis"));
}
if (replaceLibfaacCodec && params.contains(QStringLiteral("acodec=aac"))) {
// replace libfaac with aac
params = params.replace(QLatin1String("aac"), QLatin1String("libfaac"));
}
prof_extension = profileElement.attribute(QStringLiteral("extension"));
if (!prof_extension.isEmpty()) {
extension = prof_extension;
}
item = new QTreeWidgetItem(QStringList(profileName));
item->setData(0, GroupRole, groupName);
item->setData(0, ExtensionRole, extension);
item->setData(0, RenderRole, renderer);
item->setData(0, StandardRole, standard);
item->setData(0, ParamsRole, params);
if (params.contains(QLatin1String("%quality"))) {
item->setData(0, BitratesRole, profileElement.attribute(QStringLiteral("qualities")).split(QLatin1Char(','), QString::SkipEmptyParts));
} else if (params.contains(QLatin1String("%bitrate"))) {
item->setData(0, BitratesRole, profileElement.attribute(QStringLiteral("bitrates")).split(QLatin1Char(','), QString::SkipEmptyParts));
}
if (params.contains(QLatin1String("%audioquality"))) {
item->setData(0, AudioBitratesRole,
profileElement.attribute(QStringLiteral("audioqualities")).split(QLatin1Char(','), QString::SkipEmptyParts));
} else if (params.contains(QLatin1String("%audiobitrate"))) {
item->setData(0, AudioBitratesRole, profileElement.attribute(QStringLiteral("audiobitrates")).split(QLatin1Char(','), QString::SkipEmptyParts));
}
if (profileElement.hasAttribute(QStringLiteral("speeds"))) {
item->setData(0, SpeedsRole, profileElement.attribute(QStringLiteral("speeds")).split(QLatin1Char(';'), QString::SkipEmptyParts));
}
if (profileElement.hasAttribute(QStringLiteral("url"))) {
item->setData(0, ExtraRole, profileElement.attribute(QStringLiteral("url")));
}
groupItem->addChild(item);
n = n.nextSibling();
}
++i;
}
}
void RenderWidget::setRenderJob(const QString &dest, int progress)
{
RenderJobItem *item;
QList existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
if (!existing.isEmpty()) {
item = static_cast(existing.at(0));
} else {
item = new RenderJobItem(m_view.running_jobs, QStringList() << QString() << dest);
if (progress == 0) {
item->setStatus(WAITINGJOB);
}
}
item->setData(1, ProgressRole, progress);
item->setStatus(RUNNINGJOB);
if (progress == 0) {
item->setIcon(0, QIcon::fromTheme(QStringLiteral("media-record")));
item->setData(1, TimeRole, QDateTime::currentDateTime());
slotCheckJob();
} else {
QDateTime startTime = item->data(1, TimeRole).toDateTime();
qint64 elapsedTime = startTime.secsTo(QDateTime::currentDateTime());
qint64 remaining = elapsedTime * (100 - progress) / progress;
int days = static_cast(remaining / 86400);
int remainingSecs = static_cast(remaining % 86400);
QTime when = QTime(0, 0, 0, 0);
when = when.addSecs(remainingSecs);
QString est = (days > 0) ? i18np("%1 day ", "%1 days ", days) : QString();
est.append(when.toString(QStringLiteral("hh:mm:ss")));
QString t = i18n("Remaining time %1", est);
item->setData(1, Qt::UserRole, t);
}
}
void RenderWidget::setRenderStatus(const QString &dest, int status, const QString &error)
{
RenderJobItem *item;
QList existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
if (!existing.isEmpty()) {
item = static_cast(existing.at(0));
} else {
item = new RenderJobItem(m_view.running_jobs, QStringList() << QString() << dest);
}
if (!item) {
return;
}
if (status == -1) {
// Job finished successfully
item->setStatus(FINISHEDJOB);
QDateTime startTime = item->data(1, TimeRole).toDateTime();
qint64 elapsedTime = startTime.secsTo(QDateTime::currentDateTime());
int days = static_cast(elapsedTime / 86400);
int secs = static_cast(elapsedTime % 86400);
QTime when = QTime(0, 0, 0, 0);
when = when.addSecs(secs);
QString est = (days > 0) ? i18np("%1 day ", "%1 days ", days) : QString();
est.append(when.toString(QStringLiteral("hh:mm:ss")));
QString t = i18n("Rendering finished in %1", est);
item->setData(1, Qt::UserRole, t);
#ifdef KF5_USE_PURPOSE
m_shareMenu->model()->setInputData(QJsonObject{{QStringLiteral("mimeType"), QMimeDatabase().mimeTypeForFile(item->text(1)).name()},
{QStringLiteral("urls"), QJsonArray({item->text(1)})}});
m_shareMenu->model()->setPluginType(QStringLiteral("Export"));
m_shareMenu->reload();
#endif
QString notif = i18n("Rendering of %1 finished in %2", item->text(1), est);
KNotification *notify = new KNotification(QStringLiteral("RenderFinished"));
notify->setText(notif);
#if KNOTIFICATIONS_VERSION >= QT_VERSION_CHECK(5, 29, 0)
notify->setUrls({QUrl::fromLocalFile(dest)});
#endif
notify->sendEvent();
QString itemGroup = item->data(0, Qt::UserRole).toString();
if (itemGroup == QLatin1String("dvd")) {
emit openDvdWizard(item->text(1));
} else if (itemGroup == QLatin1String("websites")) {
QString url = item->metadata();
if (!url.isEmpty()) {
new KRun(QUrl::fromLocalFile(url), this);
}
}
} else if (status == -2) {
// Rendering crashed
item->setStatus(FAILEDJOB);
m_view.error_log->append(i18n("Rendering of %1 crashed
", dest));
m_view.error_log->append(error);
m_view.error_log->append(QStringLiteral("
"));
m_view.error_box->setVisible(true);
} else if (status == -3) {
// User aborted job
item->setStatus(ABORTEDJOB);
} else {
delete item;
}
slotCheckJob();
checkRenderStatus();
}
void RenderWidget::slotAbortCurrentJob()
{
auto *current = static_cast(m_view.running_jobs->currentItem());
if (current) {
if (current->status() == RUNNINGJOB) {
emit abortProcess(current->text(1));
} else {
delete current;
slotCheckJob();
checkRenderStatus();
}
}
}
void RenderWidget::slotStartCurrentJob()
{
auto *current = static_cast(m_view.running_jobs->currentItem());
if ((current != nullptr) && current->status() == WAITINGJOB) {
startRendering(current);
}
m_view.start_job->setEnabled(false);
}
void RenderWidget::slotCheckJob()
{
bool activate = false;
auto *current = static_cast(m_view.running_jobs->currentItem());
if (current) {
if (current->status() == RUNNINGJOB || current->status() == STARTINGJOB) {
m_view.abort_job->setText(i18n("Abort Job"));
m_view.start_job->setEnabled(false);
} else {
m_view.abort_job->setText(i18n("Remove Job"));
m_view.start_job->setEnabled(current->status() == WAITINGJOB);
}
activate = true;
#ifdef KF5_USE_PURPOSE
if (current->status() == FINISHEDJOB) {
m_shareMenu->model()->setInputData(QJsonObject{{QStringLiteral("mimeType"), QMimeDatabase().mimeTypeForFile(current->text(1)).name()},
{QStringLiteral("urls"), QJsonArray({current->text(1)})}});
m_shareMenu->model()->setPluginType(QStringLiteral("Export"));
m_shareMenu->reload();
m_view.shareButton->setEnabled(true);
} else {
m_view.shareButton->setEnabled(false);
}
#endif
}
m_view.abort_job->setEnabled(activate);
/*
for (int i = 0; i < m_view.running_jobs->topLevelItemCount(); ++i) {
current = static_cast(m_view.running_jobs->topLevelItem(i));
if (current == static_cast (m_view.running_jobs->currentItem())) {
current->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 3));
} else current->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
}*/
}
void RenderWidget::slotCLeanUpJobs()
{
int ix = 0;
auto *current = static_cast(m_view.running_jobs->topLevelItem(ix));
while (current != nullptr) {
if (current->status() == FINISHEDJOB || current->status() == ABORTEDJOB) {
delete current;
} else {
ix++;
}
current = static_cast(m_view.running_jobs->topLevelItem(ix));
}
slotCheckJob();
}
void RenderWidget::parseScriptFiles()
{
QStringList scriptsFilter;
scriptsFilter << QStringLiteral("*.mlt");
m_view.scripts_list->clear();
QTreeWidgetItem *item;
// List the project scripts
QDir projectFolder(pCore->currentDoc()->projectDataFolder());
projectFolder.mkpath(QStringLiteral("kdenlive-renderqueue"));
projectFolder.cd(QStringLiteral("kdenlive-renderqueue"));
QStringList scriptFiles = projectFolder.entryList(scriptsFilter, QDir::Files);
for (int i = 0; i < scriptFiles.size(); ++i) {
QUrl scriptpath = QUrl::fromLocalFile(projectFolder.absoluteFilePath(scriptFiles.at(i)));
QFile f(scriptpath.toLocalFile());
QDomDocument doc;
doc.setContent(&f, false);
f.close();
QDomElement consumer = doc.documentElement().firstChildElement(QStringLiteral("consumer"));
if (consumer.isNull()) {
continue;
}
QString target = consumer.attribute(QStringLiteral("target"));
if (target.isEmpty()) {
continue;
}
item = new QTreeWidgetItem(m_view.scripts_list, QStringList() << QString() << scriptpath.fileName());
auto icon = QFileIconProvider().icon(QFileInfo(f));
item->setIcon(0, icon.isNull() ? QIcon::fromTheme(QStringLiteral("application-x-executable-script")) : icon);
item->setSizeHint(0, QSize(m_view.scripts_list->columnWidth(0), fontMetrics().height() * 2));
item->setData(1, Qt::UserRole, QUrl(QUrl::fromEncoded(target.toUtf8())).url(QUrl::PreferLocalFile));
item->setData(1, Qt::UserRole + 1, scriptpath.toLocalFile());
}
QTreeWidgetItem *script = m_view.scripts_list->topLevelItem(0);
if (script) {
m_view.scripts_list->setCurrentItem(script);
script->setSelected(true);
}
}
void RenderWidget::slotCheckScript()
{
QTreeWidgetItem *current = m_view.scripts_list->currentItem();
if (current == nullptr) {
return;
}
m_view.start_script->setEnabled(current->data(0, Qt::UserRole).toString().isEmpty());
m_view.delete_script->setEnabled(true);
for (int i = 0; i < m_view.scripts_list->topLevelItemCount(); ++i) {
current = m_view.scripts_list->topLevelItem(i);
if (current == m_view.scripts_list->currentItem()) {
current->setSizeHint(1, QSize(m_view.scripts_list->columnWidth(1), fontMetrics().height() * 3));
} else {
current->setSizeHint(1, QSize(m_view.scripts_list->columnWidth(1), fontMetrics().height() * 2));
}
}
}
void RenderWidget::slotStartScript()
{
auto *item = static_cast(m_view.scripts_list->currentItem());
if (item) {
QString destination = item->data(1, Qt::UserRole).toString();
if (QFile::exists(destination)) {
if (KMessageBox::warningYesNo(this, i18n("Output file already exists. Do you want to overwrite it?")) != KMessageBox::Yes) {
return;
}
}
QString path = item->data(1, Qt::UserRole + 1).toString();
// Insert new job in queue
RenderJobItem *renderItem = nullptr;
QList existing = m_view.running_jobs->findItems(destination, Qt::MatchExactly, 1);
if (!existing.isEmpty()) {
renderItem = static_cast(existing.at(0));
if (renderItem->status() == RUNNINGJOB || renderItem->status() == WAITINGJOB || renderItem->status() == STARTINGJOB) {
KMessageBox::information(
this, i18n("There is already a job writing file:
%1
Abort the job if you want to overwrite it...", destination),
i18n("Already running"));
return;
}
delete renderItem;
renderItem = nullptr;
}
if (!renderItem) {
renderItem = new RenderJobItem(m_view.running_jobs, QStringList() << QString() << destination);
}
renderItem->setData(1, ProgressRole, 0);
renderItem->setStatus(WAITINGJOB);
renderItem->setIcon(0, QIcon::fromTheme(QStringLiteral("media-playback-pause")));
renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
renderItem->setData(1, TimeRole, QDateTime::currentDateTime());
QStringList argsJob = {KdenliveSettings::rendererpath(), path, destination, QStringLiteral("-pid:%1").arg(QCoreApplication::applicationPid())};
renderItem->setData(1, ParametersRole, argsJob);
checkRenderStatus();
m_view.tabWidget->setCurrentIndex(1);
}
}
void RenderWidget::slotDeleteScript()
{
QTreeWidgetItem *item = m_view.scripts_list->currentItem();
if (item) {
QString path = item->data(1, Qt::UserRole + 1).toString();
bool success = true;
success &= static_cast(QFile::remove(path));
if (!success) {
qCWarning(KDENLIVE_LOG) << "// Error removing script or playlist: " << path << ", " << path << ".mlt";
}
parseScriptFiles();
}
}
void RenderWidget::slotGenerateScript()
{
slotPrepareExport(true);
}
void RenderWidget::slotHideLog()
{
m_view.error_box->setVisible(false);
}
void RenderWidget::setRenderProfile(const QMap &props)
{
m_view.scanning_list->setCurrentIndex(props.value(QStringLiteral("renderscanning")).toInt());
m_view.field_order->setCurrentIndex(props.value(QStringLiteral("renderfield")).toInt());
int exportAudio = props.value(QStringLiteral("renderexportaudio")).toInt();
switch (exportAudio) {
case 1:
m_view.export_audio->setCheckState(Qt::Unchecked);
break;
case 2:
m_view.export_audio->setCheckState(Qt::Checked);
break;
default:
m_view.export_audio->setCheckState(Qt::PartiallyChecked);
}
if (props.contains(QStringLiteral("renderrescale"))) {
m_view.rescale->setChecked(props.value(QStringLiteral("renderrescale")).toInt() != 0);
}
if (props.contains(QStringLiteral("renderrescalewidth"))) {
m_view.rescale_width->setValue(props.value(QStringLiteral("renderrescalewidth")).toInt());
}
if (props.contains(QStringLiteral("renderrescaleheight"))) {
m_view.rescale_height->setValue(props.value(QStringLiteral("renderrescaleheight")).toInt());
}
if (props.contains(QStringLiteral("rendertcoverlay"))) {
m_view.tc_overlay->setChecked(props.value(QStringLiteral("rendertcoverlay")).toInt() != 0);
}
if (props.contains(QStringLiteral("rendertctype"))) {
m_view.tc_type->setCurrentIndex(props.value(QStringLiteral("rendertctype")).toInt());
}
if (props.contains(QStringLiteral("renderratio"))) {
m_view.rescale_keep->setChecked(props.value(QStringLiteral("renderratio")).toInt() != 0);
}
if (props.contains(QStringLiteral("renderplay"))) {
m_view.play_after->setChecked(props.value(QStringLiteral("renderplay")).toInt() != 0);
}
if (props.contains(QStringLiteral("rendertwopass"))) {
m_view.checkTwoPass->setChecked(props.value(QStringLiteral("rendertwopass")).toInt() != 0);
}
if (props.value(QStringLiteral("renderzone")) == QLatin1String("1")) {
m_view.render_zone->setChecked(true);
} else if (props.value(QStringLiteral("renderguide")) == QLatin1String("1")) {
m_view.render_guide->setChecked(true);
m_view.guide_start->setCurrentIndex(props.value(QStringLiteral("renderstartguide")).toInt());
m_view.guide_end->setCurrentIndex(props.value(QStringLiteral("renderendguide")).toInt());
} else {
m_view.render_full->setChecked(true);
}
slotUpdateGuideBox();
QString url = props.value(QStringLiteral("renderurl"));
if (!url.isEmpty()) {
m_view.out_file->setUrl(QUrl::fromLocalFile(url));
}
if (props.contains(QStringLiteral("renderprofile")) || props.contains(QStringLiteral("rendercategory"))) {
focusFirstVisibleItem(props.value(QStringLiteral("renderprofile")));
}
if (props.contains(QStringLiteral("renderquality"))) {
m_view.video->setValue(props.value(QStringLiteral("renderquality")).toInt());
} else if (props.contains(QStringLiteral("renderbitrate"))) {
m_view.video->setValue(props.value(QStringLiteral("renderbitrate")).toInt());
} else {
m_view.quality->setValue(m_view.quality->maximum() * 3 / 4);
}
if (props.contains(QStringLiteral("renderaudioquality"))) {
m_view.audio->setValue(props.value(QStringLiteral("renderaudioquality")).toInt());
}
if (props.contains(QStringLiteral("renderaudiobitrate"))) {
m_view.audio->setValue(props.value(QStringLiteral("renderaudiobitrate")).toInt());
}
if (props.contains(QStringLiteral("renderspeed"))) {
m_view.speed->setValue(props.value(QStringLiteral("renderspeed")).toInt());
}
}
bool RenderWidget::startWaitingRenderJobs()
{
m_blockProcessing = true;
#ifdef Q_OS_WIN
const QLatin1String ScriptFormat(".bat");
#else
const QLatin1String ScriptFormat(".sh");
#endif
QTemporaryFile tmp(QDir::tempPath() + QStringLiteral("/kdenlive-XXXXXX") + ScriptFormat);
if (!tmp.open()) {
// Something went wrong
return false;
}
tmp.close();
QString autoscriptFile = tmp.fileName();
QFile file(autoscriptFile);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qCWarning(KDENLIVE_LOG) << "////// ERROR writing to file: " << autoscriptFile;
KMessageBox::error(nullptr, i18n("Cannot write to file %1", autoscriptFile));
return false;
}
QTextStream outStream(&file);
#ifndef Q_OS_WIN
outStream << "#! /bin/sh" << '\n' << '\n';
#endif
auto *item = static_cast(m_view.running_jobs->topLevelItem(0));
while (item != nullptr) {
if (item->status() == WAITINGJOB) {
// Add render process for item
const QString params = item->data(1, ParametersRole).toStringList().join(QLatin1Char(' '));
outStream << '\"' << m_renderer << "\" " << params << '\n';
}
item = static_cast(m_view.running_jobs->itemBelow(item));
}
// erase itself when rendering is finished
#ifndef Q_OS_WIN
outStream << "rm \"" << autoscriptFile << "\"\n";
#else
outStream << "del \"" << autoscriptFile << "\"\n";
#endif
if (file.error() != QFile::NoError) {
KMessageBox::error(nullptr, i18n("Cannot write to file %1", autoscriptFile));
file.close();
m_blockProcessing = false;
return false;
}
file.close();
QFile::setPermissions(autoscriptFile, file.permissions() | QFile::ExeUser);
QProcess::startDetached(autoscriptFile, QStringList());
return true;
}
void RenderWidget::slotPlayRendering(QTreeWidgetItem *item, int)
{
auto *renderItem = static_cast(item);
if (renderItem->status() != FINISHEDJOB) {
return;
}
new KRun(QUrl::fromLocalFile(item->text(1)), this);
}
void RenderWidget::errorMessage(RenderError type, const QString &message)
{
QString fullMessage;
m_errorMessages.insert(type, message);
QMapIterator i(m_errorMessages);
while (i.hasNext()) {
i.next();
if (!i.value().isEmpty()) {
if (!fullMessage.isEmpty()) {
fullMessage.append(QLatin1Char('\n'));
}
fullMessage.append(i.value());
}
}
if (!fullMessage.isEmpty()) {
m_infoMessage->setMessageType(KMessageWidget::Warning);
m_infoMessage->setText(fullMessage);
m_infoMessage->show();
} else {
m_infoMessage->hide();
}
}
void RenderWidget::slotUpdateEncodeThreads(int val)
{
KdenliveSettings::setEncodethreads(val);
}
void RenderWidget::slotUpdateRescaleWidth(int val)
{
KdenliveSettings::setDefaultrescalewidth(val);
if (!m_view.rescale_keep->isChecked()) {
return;
}
m_view.rescale_height->blockSignals(true);
std::unique_ptr &profile = pCore->getCurrentProfile();
m_view.rescale_height->setValue(val * profile->height() / profile->width());
KdenliveSettings::setDefaultrescaleheight(m_view.rescale_height->value());
m_view.rescale_height->blockSignals(false);
}
void RenderWidget::slotUpdateRescaleHeight(int val)
{
KdenliveSettings::setDefaultrescaleheight(val);
if (!m_view.rescale_keep->isChecked()) {
return;
}
m_view.rescale_width->blockSignals(true);
std::unique_ptr &profile = pCore->getCurrentProfile();
m_view.rescale_width->setValue(val * profile->width() / profile->height());
KdenliveSettings::setDefaultrescaleheight(m_view.rescale_width->value());
m_view.rescale_width->blockSignals(false);
}
void RenderWidget::slotSwitchAspectRatio()
{
KdenliveSettings::setRescalekeepratio(m_view.rescale_keep->isChecked());
if (m_view.rescale_keep->isChecked()) {
slotUpdateRescaleWidth(m_view.rescale_width->value());
}
}
void RenderWidget::slotUpdateAudioLabel(int ix)
{
if (ix == Qt::PartiallyChecked) {
m_view.export_audio->setText(i18n("Export audio (automatic)"));
} else {
m_view.export_audio->setText(i18n("Export audio"));
}
m_view.stemAudioExport->setEnabled(ix != Qt::Unchecked);
}
bool RenderWidget::automaticAudioExport() const
{
return (m_view.export_audio->checkState() == Qt::PartiallyChecked);
}
bool RenderWidget::selectedAudioExport() const
{
return (m_view.export_audio->checkState() != Qt::Unchecked);
}
void RenderWidget::updateProxyConfig(bool enable)
{
m_view.proxy_render->setHidden(!enable);
}
bool RenderWidget::proxyRendering()
{
return m_view.proxy_render->isChecked();
}
bool RenderWidget::isStemAudioExportEnabled() const
{
return (m_view.stemAudioExport->isChecked() && m_view.stemAudioExport->isVisible() && m_view.stemAudioExport->isEnabled());
}
void RenderWidget::setRescaleEnabled(bool enable)
{
for (int i = 0; i < m_view.rescale_box->layout()->count(); ++i) {
if (m_view.rescale_box->itemAt(i)->widget()) {
m_view.rescale_box->itemAt(i)->widget()->setEnabled(enable);
}
}
}
void RenderWidget::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
switch (m_view.tabWidget->currentIndex()) {
case 1:
if (m_view.start_job->isEnabled()) {
slotStartCurrentJob();
}
break;
case 2:
if (m_view.start_script->isEnabled()) {
slotStartScript();
}
break;
default:
if (m_view.buttonRender->isEnabled()) {
slotPrepareExport();
}
break;
}
} else {
QDialog::keyPressEvent(e);
}
}
void RenderWidget::adjustAVQualities(int quality)
{
// calculate video/audio quality indexes from the general quality cursor
// taking into account decreasing/increasing video/audio quality parameter
double q = (double)quality / m_view.quality->maximum();
int dq = q * (m_view.video->maximum() - m_view.video->minimum());
// prevent video spinbox to update quality cursor (loop)
m_view.video->blockSignals(true);
m_view.video->setValue(m_view.video->property("decreasing").toBool() ? m_view.video->maximum() - dq : m_view.video->minimum() + dq);
m_view.video->blockSignals(false);
dq = q * (m_view.audio->maximum() - m_view.audio->minimum());
dq -= dq % m_view.audio->singleStep(); // keep a 32 pitch for bitrates
m_view.audio->setValue(m_view.audio->property("decreasing").toBool() ? m_view.audio->maximum() - dq : m_view.audio->minimum() + dq);
}
void RenderWidget::adjustQuality(int videoQuality)
{
int dq = videoQuality * m_view.quality->maximum() / (m_view.video->maximum() - m_view.video->minimum());
m_view.quality->blockSignals(true);
m_view.quality->setValue(m_view.video->property("decreasing").toBool() ? m_view.video->maximum() - dq : m_view.video->minimum() + dq);
m_view.quality->blockSignals(false);
}
void RenderWidget::adjustSpeed(int speedIndex)
{
if (m_view.formats->currentItem()) {
QStringList speeds = m_view.formats->currentItem()->data(0, SpeedsRole).toStringList();
if (speedIndex < speeds.count()) {
m_view.speed->setToolTip(i18n("Codec speed parameters:\n%1", speeds.at(speedIndex)));
}
}
}
void RenderWidget::checkCodecs()
{
Mlt::Profile p;
auto *consumer = new Mlt::Consumer(p, "avformat");
if (consumer) {
consumer->set("vcodec", "list");
consumer->set("acodec", "list");
consumer->set("f", "list");
consumer->start();
vcodecsList.clear();
Mlt::Properties vcodecs((mlt_properties)consumer->get_data("vcodec"));
vcodecsList.reserve(vcodecs.count());
for (int i = 0; i < vcodecs.count(); ++i) {
vcodecsList << QString(vcodecs.get(i));
}
acodecsList.clear();
Mlt::Properties acodecs((mlt_properties)consumer->get_data("acodec"));
acodecsList.reserve(acodecs.count());
for (int i = 0; i < acodecs.count(); ++i) {
acodecsList << QString(acodecs.get(i));
}
supportedFormats.clear();
Mlt::Properties formats((mlt_properties)consumer->get_data("f"));
supportedFormats.reserve(formats.count());
for (int i = 0; i < formats.count(); ++i) {
supportedFormats << QString(formats.get(i));
}
delete consumer;
}
}
void RenderWidget::slotProxyWarn(bool enableProxy)
{
errorMessage(ProxyWarning, enableProxy ? i18n("Rendering using low quality proxy") : QString());
}
diff --git a/src/monitor/glwidget.cpp b/src/monitor/glwidget.cpp
index 70bbbd6ce..a0024795d 100644
--- a/src/monitor/glwidget.cpp
+++ b/src/monitor/glwidget.cpp
@@ -1,1979 +1,1982 @@
/*
* Copyright (c) 2011-2016 Meltytech, LLC
* Original author: Dan Dennedy
* Modified for Kdenlive: Jean-Baptiste Mardelle
*
* GL shader based on BSD licensed code from Peter Bengtsson:
* http://www.fourcc.org/source/YUV420P-OpenGL-GLSLang.c
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "core.h"
#include "glwidget.h"
#include "kdenlivesettings.h"
#include "monitorproxy.h"
#include "profiles/profilemodel.hpp"
#include "qml/qmlaudiothumb.h"
#include "timeline2/view/qml/timelineitems.h"
#include
#ifndef GL_UNPACK_ROW_LENGTH
#ifdef GL_UNPACK_ROW_LENGTH_EXT
#define GL_UNPACK_ROW_LENGTH GL_UNPACK_ROW_LENGTH_EXT
#else
#error GL_UNPACK_ROW_LENGTH undefined
#endif
#endif
#ifdef QT_NO_DEBUG
#define check_error(fn) \
{ \
}
#else
#define check_error(fn) \
{ \
uint err = fn->glGetError(); \
if (err != GL_NO_ERROR) { \
qCCritical(KDENLIVE_LOG) << "GL error" << hex << err << dec << "at" << __FILE__ << ":" << __LINE__; \
} \
}
#endif
#ifndef GL_TIMEOUT_IGNORED
#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull
#endif
using namespace Mlt;
GLWidget::GLWidget(int id, QObject *parent)
: QQuickView((QWindow *)parent)
, sendFrameForAnalysis(false)
, m_glslManager(nullptr)
, m_consumer(nullptr)
, m_producer(nullptr)
, m_id(id)
, m_rulerHeight(QFontMetrics(QApplication::font()).lineSpacing() * 0.7)
, m_shader(nullptr)
, m_initSem(0)
, m_analyseSem(1)
, m_isInitialized(false)
, m_threadStartEvent(nullptr)
, m_threadStopEvent(nullptr)
, m_threadCreateEvent(nullptr)
, m_threadJoinEvent(nullptr)
, m_displayEvent(nullptr)
, m_frameRenderer(nullptr)
, m_projectionLocation(0)
, m_modelViewLocation(0)
, m_vertexLocation(0)
, m_texCoordLocation(0)
, m_colorspaceLocation(0)
, m_zoom(1.0f)
, m_sendFrame(false)
, m_isZoneMode(false)
, m_isLoopMode(false)
, m_offset(QPoint(0, 0))
, m_audioWaveDisplayed(false)
, m_fbo(nullptr)
, m_shareContext(nullptr)
, m_openGLSync(false)
, m_ClientWaitSync(nullptr)
{
KDeclarative::KDeclarative kdeclarative;
kdeclarative.setDeclarativeEngine(engine());
#if KDECLARATIVE_VERSION >= QT_VERSION_CHECK(5, 45, 0)
kdeclarative.setupEngine(engine());
kdeclarative.setupContext();
#else
kdeclarative.setupBindings();
#endif
m_texture[0] = m_texture[1] = m_texture[2] = 0;
qRegisterMetaType("Mlt::Frame");
qRegisterMetaType("SharedFrame");
qmlRegisterType("AudioThumb", 1, 0, "QmlAudioThumb");
setPersistentOpenGLContext(true);
setPersistentSceneGraph(true);
setClearBeforeRendering(false);
setResizeMode(QQuickView::SizeRootObjectToView);
m_offscreenSurface.setFormat(QWindow::format());
m_offscreenSurface.create();
m_refreshTimer.setSingleShot(true);
m_refreshTimer.setInterval(50);
m_blackClip.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), "color:black"));
m_blackClip->set("kdenlive:id", "black");
m_blackClip->set("out", 3);
connect(&m_refreshTimer, &QTimer::timeout, this, &GLWidget::refresh);
m_producer = m_blackClip;
if (!initGPUAccel()) {
disableGPUAccel();
}
connect(this, &QQuickWindow::sceneGraphInitialized, this, &GLWidget::initializeGL, Qt::DirectConnection);
connect(this, &QQuickWindow::beforeRendering, this, &GLWidget::paintGL, Qt::DirectConnection);
registerTimelineItems();
m_proxy = new MonitorProxy(this);
connect(m_proxy, &MonitorProxy::seekRequestChanged, this, &GLWidget::requestSeek);
rootContext()->setContextProperty("controller", m_proxy);
}
GLWidget::~GLWidget()
{
// C & D
delete m_glslManager;
delete m_threadStartEvent;
delete m_threadStopEvent;
delete m_threadCreateEvent;
delete m_threadJoinEvent;
delete m_displayEvent;
if (m_frameRenderer) {
if (m_frameRenderer->isRunning()) {
QMetaObject::invokeMethod(m_frameRenderer, "cleanup");
m_frameRenderer->quit();
m_frameRenderer->wait();
m_frameRenderer->deleteLater();
} else {
delete m_frameRenderer;
}
}
m_blackClip.reset();
delete m_shareContext;
delete m_shader;
// delete pCore->getCurrentProfile();
}
void GLWidget::updateAudioForAnalysis()
{
if (m_frameRenderer) {
m_frameRenderer->sendAudioForAnalysis = KdenliveSettings::monitor_audio();
}
}
void GLWidget::initializeGL()
{
if (m_isInitialized || !isVisible() || (openglContext() == nullptr)) return;
openglContext()->makeCurrent(&m_offscreenSurface);
initializeOpenGLFunctions();
qCDebug(KDENLIVE_LOG) << "OpenGL vendor: " << QString::fromUtf8((const char *)glGetString(GL_VENDOR));
qCDebug(KDENLIVE_LOG) << "OpenGL renderer: " << QString::fromUtf8((const char *)glGetString(GL_RENDERER));
qCDebug(KDENLIVE_LOG) << "OpenGL Threaded: " << openglContext()->supportsThreadedOpenGL();
qCDebug(KDENLIVE_LOG) << "OpenGL ARG_SYNC: " << openglContext()->hasExtension("GL_ARB_sync");
qCDebug(KDENLIVE_LOG) << "OpenGL OpenGLES: " << openglContext()->isOpenGLES();
// C & D
if (onlyGLESGPUAccel()) {
disableGPUAccel();
}
createShader();
m_openGLSync = initGPUAccelSync();
// C & D
if (m_glslManager) {
// Create a context sharing with this context for the RenderThread context.
// This is needed because openglContext() is active in another thread
// at the time that RenderThread is created.
// See this Qt bug for more info: https://bugreports.qt.io/browse/QTBUG-44677
// TODO: QTBUG-44677 is closed. still applicable?
m_shareContext = new QOpenGLContext;
m_shareContext->setFormat(openglContext()->format());
m_shareContext->setShareContext(openglContext());
m_shareContext->create();
}
m_frameRenderer = new FrameRenderer(openglContext(), &m_offscreenSurface, m_ClientWaitSync);
m_frameRenderer->sendAudioForAnalysis = KdenliveSettings::monitor_audio();
openglContext()->makeCurrent(this);
// openglContext()->blockSignals(false);
connect(m_frameRenderer, &FrameRenderer::frameDisplayed, this, &GLWidget::frameDisplayed, Qt::QueuedConnection);
connect(m_frameRenderer, &FrameRenderer::textureReady, this, &GLWidget::updateTexture, Qt::DirectConnection);
connect(m_frameRenderer, &FrameRenderer::frameDisplayed, this, &GLWidget::onFrameDisplayed, Qt::QueuedConnection);
connect(m_frameRenderer, &FrameRenderer::audioSamplesSignal, this, &GLWidget::audioSamplesSignal, Qt::QueuedConnection);
m_initSem.release();
m_isInitialized = true;
reconfigure();
}
void GLWidget::resizeGL(int width, int height)
{
int x, y, w, h;
height -= m_rulerHeight;
double this_aspect = (double)width / height;
double video_aspect = pCore->getCurrentProfile()->dar();
// Special case optimization to negate odd effect of sample aspect ratio
// not corresponding exactly with image resolution.
if ((int)(this_aspect * 1000) == (int)(video_aspect * 1000)) {
w = width;
h = height;
}
// Use OpenGL to normalise sample aspect ratio
else if (height * video_aspect > width) {
w = width;
h = width / video_aspect;
} else {
w = height * video_aspect;
h = height;
}
x = (width - w) / 2;
y = (height - h) / 2;
m_rect.setRect(x, y, w, h);
double scalex = (double)m_rect.width() / pCore->getCurrentProfile()->width() * m_zoom;
double scaley = (double)m_rect.width() /
((double)pCore->getCurrentProfile()->height() * pCore->getCurrentProfile()->dar() / pCore->getCurrentProfile()->width()) /
pCore->getCurrentProfile()->width() * m_zoom;
QPoint center = m_rect.center();
QQuickItem *rootQml = rootObject();
if (rootQml) {
rootQml->setProperty("center", center);
rootQml->setProperty("scalex", scalex);
rootQml->setProperty("scaley", scaley);
if (rootQml->objectName() == QLatin1String("rootsplit")) {
// Adjust splitter pos
rootQml->setProperty("splitterPos", x + (rootQml->property("realpercent").toDouble() * w));
}
}
emit rectChanged();
}
void GLWidget::resizeEvent(QResizeEvent *event)
{
resizeGL(event->size().width(), event->size().height());
QQuickView::resizeEvent(event);
}
void GLWidget::createGPUAccelFragmentProg()
{
m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, "uniform sampler2D tex;"
"varying highp vec2 coordinates;"
"void main(void) {"
" gl_FragColor = texture2D(tex, coordinates);"
"}");
m_shader->link();
m_textureLocation[0] = m_shader->uniformLocation("tex");
}
void GLWidget::createShader()
{
m_shader = new QOpenGLShaderProgram;
m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, "uniform highp mat4 projection;"
"uniform highp mat4 modelView;"
"attribute highp vec4 vertex;"
"attribute highp vec2 texCoord;"
"varying highp vec2 coordinates;"
"void main(void) {"
" gl_Position = projection * modelView * vertex;"
" coordinates = texCoord;"
"}");
// C & D
if (m_glslManager) {
createGPUAccelFragmentProg();
} else {
// A & B
createYUVTextureProjectFragmentProg();
}
m_projectionLocation = m_shader->uniformLocation("projection");
m_modelViewLocation = m_shader->uniformLocation("modelView");
m_vertexLocation = m_shader->attributeLocation("vertex");
m_texCoordLocation = m_shader->attributeLocation("texCoord");
}
void GLWidget::createYUVTextureProjectFragmentProg()
{
m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment,
"uniform sampler2D Ytex, Utex, Vtex;"
"uniform lowp int colorspace;"
"varying highp vec2 coordinates;"
"void main(void) {"
" mediump vec3 texel;"
" texel.r = texture2D(Ytex, coordinates).r - 0.0625;" // Y
" texel.g = texture2D(Utex, coordinates).r - 0.5;" // U
" texel.b = texture2D(Vtex, coordinates).r - 0.5;" // V
" mediump mat3 coefficients;"
" if (colorspace == 601) {"
" coefficients = mat3("
" 1.1643, 1.1643, 1.1643," // column 1
" 0.0, -0.39173, 2.017," // column 2
" 1.5958, -0.8129, 0.0);" // column 3
" } else {" // ITU-R 709
" coefficients = mat3("
" 1.1643, 1.1643, 1.1643," // column 1
" 0.0, -0.213, 2.112," // column 2
" 1.793, -0.533, 0.0);" // column 3
" }"
" gl_FragColor = vec4(coefficients * texel, 1.0);"
"}");
m_shader->link();
m_textureLocation[0] = m_shader->uniformLocation("Ytex");
m_textureLocation[1] = m_shader->uniformLocation("Utex");
m_textureLocation[2] = m_shader->uniformLocation("Vtex");
m_colorspaceLocation = m_shader->uniformLocation("colorspace");
}
static void uploadTextures(QOpenGLContext *context, const SharedFrame &frame, GLuint texture[])
{
int width = frame.get_image_width();
int height = frame.get_image_height();
const uint8_t *image = frame.get_image();
QOpenGLFunctions *f = context->functions();
// The planes of pixel data may not be a multiple of the default 4 bytes.
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Upload each plane of YUV to a texture.
if (texture[0] != 0u) {
f->glDeleteTextures(3, texture);
}
check_error(f);
f->glGenTextures(3, texture);
check_error(f);
f->glBindTexture(GL_TEXTURE_2D, texture[0]);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image);
check_error(f);
f->glBindTexture(GL_TEXTURE_2D, texture[1]);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image + width * height);
check_error(f);
f->glBindTexture(GL_TEXTURE_2D, texture[2]);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image + width * height + width / 2 * height / 2);
check_error(f);
}
void GLWidget::clear()
{
stopGlsl();
update();
}
void GLWidget::releaseAnalyse()
{
m_analyseSem.release();
}
bool GLWidget::acquireSharedFrameTextures()
{
// A
if ((m_glslManager == nullptr) && !openglContext()->supportsThreadedOpenGL()) {
QMutexLocker locker(&m_contextSharedAccess);
if (!m_sharedFrame.is_valid()) {
return false;
}
uploadTextures(openglContext(), m_sharedFrame, m_texture);
} else if (m_glslManager) {
// C & D
m_contextSharedAccess.lock();
if (m_sharedFrame.is_valid()) {
m_texture[0] = *((const GLuint *)m_sharedFrame.get_image());
}
}
if (!m_texture[0]) {
// C & D
if (m_glslManager) m_contextSharedAccess.unlock();
return false;
}
return true;
}
void GLWidget::bindShaderProgram()
{
m_shader->bind();
// C & D
if (m_glslManager) {
m_shader->setUniformValue(m_textureLocation[0], 0);
} else {
// A & B
m_shader->setUniformValue(m_textureLocation[0], 0);
m_shader->setUniformValue(m_textureLocation[1], 1);
m_shader->setUniformValue(m_textureLocation[2], 2);
m_shader->setUniformValue(m_colorspaceLocation, pCore->getCurrentProfile()->colorspace());
}
}
void GLWidget::releaseSharedFrameTextures()
{
// C & D
if (m_glslManager) {
glFinish();
m_contextSharedAccess.unlock();
}
}
bool GLWidget::initGPUAccel()
{
if (!KdenliveSettings::gpu_accel()) return false;
m_glslManager = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "glsl.manager");
return m_glslManager->is_valid();
}
// C & D
// TODO: insure safe, idempotent on all pipelines.
void GLWidget::disableGPUAccel()
{
delete m_glslManager;
m_glslManager = nullptr;
KdenliveSettings::setGpu_accel(false);
// Need to destroy MLT global reference to prevent filters from trying to use GPU.
mlt_properties_set_data(mlt_global_properties(), "glslManager", nullptr, 0, nullptr, nullptr);
emit gpuNotSupported();
}
bool GLWidget::onlyGLESGPUAccel() const
{
return (m_glslManager != nullptr) && openglContext()->isOpenGLES();
}
#if defined(Q_OS_WIN)
bool GLWidget::initGPUAccelSync()
{
// no-op
// TODO: getProcAddress is not working on Windows?
return false;
}
#else
bool GLWidget::initGPUAccelSync()
{
if (!KdenliveSettings::gpu_accel()) return false;
if (m_glslManager == nullptr) return false;
if (!openglContext()->hasExtension("GL_ARB_sync")) return false;
m_ClientWaitSync = (ClientWaitSync_fp)openglContext()->getProcAddress("glClientWaitSync");
if (m_ClientWaitSync) {
return true;
} else {
qCDebug(KDENLIVE_LOG) << " / / // NO GL SYNC, ERROR";
// fallback on A || B
// TODO: fallback on A || B || C?
disableGPUAccel();
return false;
}
}
#endif
void GLWidget::paintGL()
{
QOpenGLFunctions *f = openglContext()->functions();
int width = this->width() * devicePixelRatio();
int height = this->height() * devicePixelRatio();
f->glDisable(GL_BLEND);
f->glDisable(GL_DEPTH_TEST);
f->glDepthMask(GL_FALSE);
f->glViewport(0, (m_rulerHeight * devicePixelRatio() * 0.5 + 0.5), width, height);
check_error(f);
QColor color(KdenliveSettings::window_background());
f->glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
f->glClear(GL_COLOR_BUFFER_BIT);
check_error(f);
if (!acquireSharedFrameTextures()) return;
// Bind textures.
for (uint i = 0; i < 3; ++i) {
if (m_texture[i] != 0u) {
f->glActiveTexture(GL_TEXTURE0 + i);
f->glBindTexture(GL_TEXTURE_2D, m_texture[i]);
check_error(f);
}
}
bindShaderProgram();
check_error(f);
// Setup an orthographic projection.
QMatrix4x4 projection;
projection.scale(2.0f / (float)width, 2.0f / (float)height);
m_shader->setUniformValue(m_projectionLocation, projection);
check_error(f);
// Set model view.
QMatrix4x4 modelView;
if (!qFuzzyCompare(m_zoom, 1.0f)) {
if ((offset().x() != 0) || (offset().y() != 0)) modelView.translate(-offset().x() * devicePixelRatio(), offset().y() * devicePixelRatio());
modelView.scale(zoom(), zoom());
}
m_shader->setUniformValue(m_modelViewLocation, modelView);
check_error(f);
// Provide vertices of triangle strip.
QVector vertices;
width = m_rect.width() * devicePixelRatio();
height = m_rect.height() * devicePixelRatio();
vertices << QVector2D(float(-width) / 2.0f, float(-height) / 2.0f);
vertices << QVector2D(float(-width) / 2.0f, float(height) / 2.0f);
vertices << QVector2D(float(width) / 2.0f, float(-height) / 2.0f);
vertices << QVector2D(float(width) / 2.0f, float(height) / 2.0f);
m_shader->enableAttributeArray(m_vertexLocation);
check_error(f);
m_shader->setAttributeArray(m_vertexLocation, vertices.constData());
check_error(f);
// Provide texture coordinates.
QVector texCoord;
texCoord << QVector2D(0.0f, 1.0f);
texCoord << QVector2D(0.0f, 0.0f);
texCoord << QVector2D(1.0f, 1.0f);
texCoord << QVector2D(1.0f, 0.0f);
m_shader->enableAttributeArray(m_texCoordLocation);
check_error(f);
m_shader->setAttributeArray(m_texCoordLocation, texCoord.constData());
check_error(f);
// Render
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size());
check_error(f);
if (m_sendFrame && m_analyseSem.tryAcquire(1)) {
// Render RGB frame for analysis
int fullWidth = pCore->getCurrentProfile()->width();
int fullHeight = pCore->getCurrentProfile()->height();
if ((m_fbo == nullptr) || m_fbo->size() != QSize(fullWidth, fullHeight)) {
delete m_fbo;
QOpenGLFramebufferObjectFormat fmt;
fmt.setSamples(1);
fmt.setInternalTextureFormat(GL_RGB); // GL_RGBA32F); // which one is the fastest ?
m_fbo = new QOpenGLFramebufferObject(fullWidth, fullHeight, fmt); // GL_TEXTURE_2D);
}
m_fbo->bind();
glViewport(0, 0, fullWidth, fullHeight);
QMatrix4x4 projection2;
projection2.scale(2.0f / (float)width, 2.0f / (float)height);
m_shader->setUniformValue(m_projectionLocation, projection2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size());
check_error(f);
m_fbo->release();
emit analyseFrame(m_fbo->toImage());
m_sendFrame = false;
}
// Cleanup
m_shader->disableAttributeArray(m_vertexLocation);
m_shader->disableAttributeArray(m_texCoordLocation);
m_shader->release();
for (uint i = 0; i < 3; ++i) {
if (m_texture[i] != 0u) {
f->glActiveTexture(GL_TEXTURE0 + i);
f->glBindTexture(GL_TEXTURE_2D, 0);
check_error(f);
}
}
glActiveTexture(GL_TEXTURE0);
check_error(f);
releaseSharedFrameTextures();
check_error(f);
}
void GLWidget::slotZoom(bool zoomIn)
{
if (zoomIn) {
if (qFuzzyCompare(m_zoom, 1.0f)) {
setZoom(2.0f);
} else if (qFuzzyCompare(m_zoom, 2.0f)) {
setZoom(3.0f);
} else if (m_zoom < 1.0f) {
setZoom(m_zoom * 2);
}
} else {
if (qFuzzyCompare(m_zoom, 3.0f)) {
setZoom(2.0);
} else if (qFuzzyCompare(m_zoom, 2.0f)) {
setZoom(1.0);
} else if (m_zoom > 0.2) {
setZoom(m_zoom / 2);
}
}
}
void GLWidget::wheelEvent(QWheelEvent *event)
{
if (((event->modifiers() & Qt::ControlModifier) != 0u) && ((event->modifiers() & Qt::ShiftModifier) != 0u)) {
slotZoom(event->delta() > 0);
return;
}
emit mouseSeek(event->delta(), (uint)event->modifiers());
event->accept();
}
void GLWidget::requestSeek()
{
if (!m_producer) {
return;
}
if (m_proxy->seeking()) {
m_producer->seek(m_proxy->seekPosition());
if (!qFuzzyIsNull(m_producer->get_speed())) {
m_consumer->purge();
}
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("refresh", 1);
}
}
void GLWidget::seek(int pos)
{
if (!m_proxy->seeking()) {
m_proxy->setSeekPosition(pos);
m_producer->seek(pos);
if (m_consumer->is_stopped()) {
m_consumer->start();
} else {
m_consumer->purge();
m_consumer->set("refresh", 1);
}
} else {
m_proxy->setSeekPosition(pos);
}
}
void GLWidget::requestRefresh()
{
if (m_proxy->seeking()) {
return;
}
if (m_producer && qFuzzyIsNull(m_producer->get_speed())) {
m_refreshTimer.start();
}
}
QString GLWidget::frameToTime(int frames) const
{
return m_consumer ? m_consumer->frames_to_time(frames, mlt_time_smpte_df) : QStringLiteral("-");
}
void GLWidget::refresh()
{
m_refreshTimer.stop();
if (m_proxy->seeking()) {
return;
}
QMutexLocker locker(&m_mltMutex);
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("refresh", 1);
}
bool GLWidget::checkFrameNumber(int pos, int offset)
{
emit consumerPosition(pos);
if (!m_proxy->setPosition(pos)) {
emit seekPosition(m_proxy->seekOrCurrentPosition());
}
const double speed = m_producer->get_speed();
if (m_proxy->seeking()) {
m_producer->set_speed(0);
m_producer->seek(m_proxy->seekPosition());
if (qFuzzyIsNull(speed)) {
m_consumer->set("refresh", 1);
} else {
m_producer->set_speed(speed);
}
} else if (qFuzzyIsNull(speed)) {
if (m_isLoopMode) {
if (pos >= m_producer->get_int("out") - offset) {
m_consumer->purge();
m_producer->seek(m_proxy->zoneIn());
m_producer->set_speed(1.0);
m_consumer->set("refresh", 1);
}
return true;
} else {
if (pos >= m_producer->get_int("out") - offset) {
return false;
}
return true;
}
} else if (speed < 0. && pos <= 0) {
m_producer->set_speed(0);
return false;
}
return true;
}
void GLWidget::mousePressEvent(QMouseEvent *event)
{
if ((rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") && !(event->modifiers() & Qt::ControlModifier) &&
!(event->buttons() & Qt::MiddleButton)) {
event->ignore();
QQuickView::mousePressEvent(event);
return;
}
if ((event->button() & Qt::LeftButton) != 0u) {
if ((event->modifiers() & Qt::ControlModifier) != 0u) {
// Pan view
m_panStart = event->pos();
setCursor(Qt::ClosedHandCursor);
} else {
m_dragStart = event->pos();
}
} else if ((event->button() & Qt::RightButton) != 0u) {
emit showContextMenu(event->globalPos());
} else if ((event->button() & Qt::MiddleButton) != 0u) {
m_panStart = event->pos();
setCursor(Qt::ClosedHandCursor);
}
event->accept();
QQuickView::mousePressEvent(event);
}
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
if ((rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") && !(event->modifiers() & Qt::ControlModifier) &&
!(event->buttons() & Qt::MiddleButton)) {
event->ignore();
QQuickView::mouseMoveEvent(event);
return;
}
/* if (event->modifiers() == Qt::ShiftModifier && m_producer) {
emit seekTo(m_producer->get_length() * event->x() / width());
return;
}*/
QQuickView::mouseMoveEvent(event);
if (!m_panStart.isNull()) {
emit panView(m_panStart - event->pos());
m_panStart = event->pos();
event->accept();
QQuickView::mouseMoveEvent(event);
return;
}
if (!(event->buttons() & Qt::LeftButton)) {
QQuickView::mouseMoveEvent(event);
return;
}
if (!event->isAccepted() && !m_dragStart.isNull() && (event->pos() - m_dragStart).manhattanLength() >= QApplication::startDragDistance()) {
m_dragStart = QPoint();
emit startDrag();
}
}
void GLWidget::keyPressEvent(QKeyEvent *event)
{
QQuickView::keyPressEvent(event);
if (!event->isAccepted()) {
emit passKeyEvent(event);
}
}
void GLWidget::createThread(RenderThread **thread, thread_function_t function, void *data)
{
#ifdef Q_OS_WIN
// On Windows, MLT event consumer-thread-create is fired from the Qt main thread.
while (!m_isInitialized) {
qApp->processEvents();
}
#else
if (!m_isInitialized) {
m_initSem.acquire();
}
#endif
(*thread) = new RenderThread(function, data, m_shareContext, &m_offscreenSurface);
(*thread)->start();
}
static void onThreadCreate(mlt_properties owner, GLWidget *self, RenderThread **thread, int *priority, thread_function_t function, void *data)
{
Q_UNUSED(owner)
Q_UNUSED(priority)
// self->clearFrameRenderer();
self->createThread(thread, function, data);
self->lockMonitor();
}
static void onThreadJoin(mlt_properties owner, GLWidget *self, RenderThread *thread)
{
Q_UNUSED(owner)
if (thread) {
thread->quit();
thread->wait();
delete thread;
// self->clearFrameRenderer();
self->releaseMonitor();
}
}
void GLWidget::startGlsl()
{
// C & D
if (m_glslManager) {
// clearFrameRenderer();
m_glslManager->fire_event("init glsl");
if (m_glslManager->get_int("glsl_supported") == 0) {
disableGPUAccel();
} else {
emit started();
}
}
}
static void onThreadStarted(mlt_properties owner, GLWidget *self)
{
Q_UNUSED(owner)
self->startGlsl();
}
void GLWidget::releaseMonitor()
{
emit lockMonitor(false);
}
void GLWidget::lockMonitor()
{
emit lockMonitor(true);
}
void GLWidget::stopGlsl()
{
if (m_consumer) {
m_consumer->purge();
}
// C & D
// TODO This is commented out for now because it is causing crashes.
// Technically, this should be the correct thing to do, but it appears
// some changes have created regression (see shotcut)
// with respect to restarting the consumer in GPU mode.
// m_glslManager->fire_event("close glsl");
m_texture[0] = 0;
}
static void onThreadStopped(mlt_properties owner, GLWidget *self)
{
Q_UNUSED(owner)
self->stopGlsl();
}
void GLWidget::slotSwitchAudioOverlay(bool enable)
{
KdenliveSettings::setDisplayAudioOverlay(enable);
if (m_audioWaveDisplayed && !enable) {
if (m_producer && m_producer->get_int("video_index") != -1) {
// We have a video producer, disable filter
removeAudioOverlay();
}
}
if (enable && !m_audioWaveDisplayed && m_producer) {
createAudioOverlay(m_producer->get_int("video_index") == -1);
}
}
int GLWidget::setProducer(const std::shared_ptr &producer, bool isActive, int position)
{
int error = 0;
QString currentId;
int consumerPosition = 0;
currentId = m_producer->parent().get("kdenlive:id");
if (producer) {
m_producer = producer;
} else {
if (currentId == QLatin1String("black")) {
return 0;
}
if (m_audioWaveDisplayed) {
removeAudioOverlay();
}
m_producer = m_blackClip;
}
// redundant check. postcondition of above is m_producer != null
if (m_producer) {
m_producer->set_speed(0);
if (m_consumer) {
consumerPosition = m_consumer->position();
m_consumer->stop();
if (!m_consumer->is_stopped()) {
m_consumer->stop();
}
}
error = reconfigure();
if (error == 0) {
// The profile display aspect ratio may have changed.
resizeGL(width(), height());
}
} else {
return error;
}
if (!m_consumer) {
return error;
}
consumerPosition = m_consumer->position();
if (m_producer->get_int("video_index") == -1) {
// This is an audio only clip, attach visualization filter. Currently, the filter crashes MLT when Movit accel is used
if (!m_audioWaveDisplayed) {
createAudioOverlay(true);
} else if (m_consumer) {
if (KdenliveSettings::gpu_accel()) {
removeAudioOverlay();
} else {
adjustAudioOverlay(true);
}
}
} else if (m_audioWaveDisplayed && (m_consumer != nullptr)) {
// This is not an audio clip, hide wave
if (KdenliveSettings::displayAudioOverlay()) {
adjustAudioOverlay(m_producer->get_int("video_index") == -1);
} else {
removeAudioOverlay();
}
} else if (KdenliveSettings::displayAudioOverlay()) {
createAudioOverlay(false);
}
if (position == -1 && m_producer->parent().get("kdenlive:id") == currentId) {
position = consumerPosition;
}
if (isActive) {
startConsumer();
}
m_proxy->requestSeekPosition(position > 0 ? position : m_producer->position());
return error;
}
int GLWidget::droppedFrames() const
{
return (m_consumer ? m_consumer->get_int("drop_count") : 0);
}
void GLWidget::resetDrops()
{
if (m_consumer) {
m_consumer->set("drop_count", 0);
}
}
void GLWidget::createAudioOverlay(bool isAudio)
{
if (!m_consumer) {
return;
}
if (isAudio && KdenliveSettings::gpu_accel()) {
// Audiowaveform filter crashes on Movit + audio clips)
return;
}
Mlt::Filter f(pCore->getCurrentProfile()->profile(), "audiowaveform");
if (f.is_valid()) {
// f.set("show_channel", 1);
f.set("color.1", "0xffff0099");
f.set("fill", 1);
if (isAudio) {
// Fill screen
f.set("rect", "0,0,100%,100%");
} else {
// Overlay on lower part of the screen
f.set("rect", "0,80%,100%,20%");
}
m_consumer->attach(f);
m_audioWaveDisplayed = true;
}
}
void GLWidget::removeAudioOverlay()
{
Mlt::Service sourceService(m_consumer->get_service());
// move all effects to the correct producer
int ct = 0;
Mlt::Filter *filter = sourceService.filter(ct);
while (filter != nullptr) {
QString srv = filter->get("mlt_service");
if (srv == QLatin1String("audiowaveform")) {
sourceService.detach(*filter);
delete filter;
break;
} else {
ct++;
}
filter = sourceService.filter(ct);
}
m_audioWaveDisplayed = false;
}
void GLWidget::adjustAudioOverlay(bool isAudio)
{
Mlt::Service sourceService(m_consumer->get_service());
// move all effects to the correct producer
int ct = 0;
Mlt::Filter *filter = sourceService.filter(ct);
while (filter != nullptr) {
QString srv = filter->get("mlt_service");
if (srv == QLatin1String("audiowaveform")) {
if (isAudio) {
filter->set("rect", "0,0,100%,100%");
} else {
filter->set("rect", "0,80%,100%,20%");
}
break;
} else {
ct++;
}
filter = sourceService.filter(ct);
}
}
void GLWidget::stopCapture()
{
if (strcmp(m_consumer->get("mlt_service"), "multi") == 0) {
m_consumer->set("refresh", 0);
m_consumer->purge();
m_consumer->stop();
}
}
int GLWidget::reconfigureMulti(const QString ¶ms, const QString &path, Mlt::Profile *profile)
{
+ Q_UNUSED(params);
+ Q_UNUSED(path);
+ Q_UNUSED(profile);
// TODO Fix or delete
/*
QString serviceName = property("mlt_service").toString();
if ((m_consumer == nullptr) || !m_consumer->is_valid() || strcmp(m_consumer->get("mlt_service"), "multi") != 0) {
if (m_consumer) {
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
}
m_consumer.reset(new Mlt::FilteredConsumer(*profile, "multi"));
delete m_threadStartEvent;
m_threadStartEvent = nullptr;
delete m_threadStopEvent;
m_threadStopEvent = nullptr;
delete m_threadCreateEvent;
delete m_threadJoinEvent;
if (m_consumer) {
m_threadCreateEvent = m_consumer->listen("consumer-thread-create", this, (mlt_listener)onThreadCreate);
m_threadJoinEvent = m_consumer->listen("consumer-thread-join", this, (mlt_listener)onThreadJoin);
}
}
if (m_consumer->is_valid()) {
// build sub consumers
// m_consumer->set("mlt_image_format", "yuv422");
reloadProfile();
int volume = KdenliveSettings::volume();
m_consumer->set("0", serviceName.toUtf8().constData());
m_consumer->set("0.mlt_image_format", "yuv422");
m_consumer->set("0.terminate_on_pause", 0);
// m_consumer->set("0.preview_off", 1);
m_consumer->set("0.real_time", 0);
m_consumer->set("0.volume", (double)volume / 100);
if (serviceName.startsWith(QLatin1String("sdl_audio"))) {
#ifdef Q_OS_WIN
m_consumer->set("0.audio_buffer", 2048);
#else
m_consumer->set("0.audio_buffer", 512);
#endif
QString audioDevice = KdenliveSettings::audiodevicename();
if (!audioDevice.isEmpty()) {
m_consumer->set("audio_device", audioDevice.toUtf8().constData());
}
QString audioDriver = KdenliveSettings::audiodrivername();
if (!audioDriver.isEmpty()) {
m_consumer->set("audio_driver", audioDriver.toUtf8().constData());
}
}
m_consumer->set("1", "avformat");
m_consumer->set("1.target", path.toUtf8().constData());
// m_consumer->set("1.real_time", -KdenliveSettings::mltthreads());
m_consumer->set("terminate_on_pause", 0);
m_consumer->set("1.terminate_on_pause", 0);
// m_consumer->set("1.terminate_on_pause", 0);// was commented out. restoring it fixes mantis#3415 - FFmpeg recording freezes
QStringList paramList = params.split(' ', QString::SkipEmptyParts);
for (int i = 0; i < paramList.count(); ++i) {
QString key = "1." + paramList.at(i).section(QLatin1Char('='), 0, 0);
QString value = paramList.at(i).section(QLatin1Char('='), 1, 1);
if (value == QLatin1String("%threads")) {
value = QString::number(QThread::idealThreadCount());
}
m_consumer->set(key.toUtf8().constData(), value.toUtf8().constData());
}
// Connect the producer to the consumer - tell it to "run" later
delete m_displayEvent;
// C & D
if (m_glslManager) {
// D
if (m_openGLSync) {
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_frame_show);
} else {
// C
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_nosync_frame_show);
}
} else {
// A & B
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_frame_show);
}
m_consumer->connect(*m_producer.get());
m_consumer->start();
return 0;
}
*/
return -1;
}
int GLWidget::reconfigure(bool reload)
{
int error = 0;
// use SDL for audio, OpenGL for video
QString serviceName = property("mlt_service").toString();
if (reload) {
reloadProfile();
m_blackClip.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), "color:black"));
m_blackClip->set("kdenlive:id", "black");
}
if ((m_consumer == nullptr) || !m_consumer->is_valid() || strcmp(m_consumer->get("mlt_service"), "multi") == 0) {
if (m_consumer) {
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
}
QString audioBackend = (KdenliveSettings::external_display()) ? QString("decklink:%1").arg(KdenliveSettings::blackmagic_output_device())
: KdenliveSettings::audiobackend();
if (serviceName.isEmpty() || serviceName != audioBackend) {
m_consumer.reset(new Mlt::FilteredConsumer(pCore->getCurrentProfile()->profile(), audioBackend.toLatin1().constData()));
if (m_consumer->is_valid()) {
serviceName = audioBackend;
setProperty("mlt_service", serviceName);
if (KdenliveSettings::external_display()) {
m_consumer->set("terminate_on_pause", 0);
}
} else {
// Warning, audio backend unavailable on system
m_consumer.reset();
QStringList backends = {"sdl2_audio", "sdl_audio", "rtaudio"};
for (const QString &bk : backends) {
if (bk == audioBackend) {
// Already tested
continue;
}
m_consumer.reset(new Mlt::FilteredConsumer(pCore->getCurrentProfile()->profile(), bk.toLatin1().constData()));
if (m_consumer->is_valid()) {
if (audioBackend == KdenliveSettings::sdlAudioBackend()) {
// switch sdl audio backend
KdenliveSettings::setSdlAudioBackend(bk);
}
qDebug() << "++++++++\nSwitching audio backend to: " << bk << "\n++++++++++";
KdenliveSettings::setAudiobackend(bk);
serviceName = bk;
setProperty("mlt_service", serviceName);
break;
} else {
m_consumer.reset();
}
}
if (!m_consumer) {
qWarning() << "WARNING, NO AUDIO BACKEND FOUND";
return -1;
}
}
}
delete m_threadStartEvent;
m_threadStartEvent = nullptr;
delete m_threadStopEvent;
m_threadStopEvent = nullptr;
delete m_threadCreateEvent;
delete m_threadJoinEvent;
if (m_consumer) {
m_threadCreateEvent = m_consumer->listen("consumer-thread-create", this, (mlt_listener)onThreadCreate);
m_threadJoinEvent = m_consumer->listen("consumer-thread-join", this, (mlt_listener)onThreadJoin);
}
}
if (m_consumer->is_valid()) {
// Connect the producer to the consumer - tell it to "run" later
if (m_producer) {
m_consumer->connect(*m_producer.get());
// m_producer->set_speed(0.0);
}
int dropFrames = realTime();
if (!KdenliveSettings::monitor_dropframes()) {
dropFrames = -dropFrames;
}
m_consumer->set("real_time", dropFrames);
// C & D
if (m_glslManager) {
if (!m_threadStartEvent) {
m_threadStartEvent = m_consumer->listen("consumer-thread-started", this, (mlt_listener)onThreadStarted);
}
if (!m_threadStopEvent) {
m_threadStopEvent = m_consumer->listen("consumer-thread-stopped", this, (mlt_listener)onThreadStopped);
}
if (!serviceName.startsWith(QLatin1String("decklink"))) {
m_consumer->set("mlt_image_format", "glsl");
}
} else {
// A & B
m_consumer->set("mlt_image_format", "yuv422");
}
delete m_displayEvent;
// C & D
if (m_glslManager) {
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_frame_show);
} else {
// A & B
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_frame_show);
}
int volume = KdenliveSettings::volume();
if (serviceName.startsWith(QLatin1String("sdl_audio"))) {
QString audioDevice = KdenliveSettings::audiodevicename();
if (!audioDevice.isEmpty()) {
m_consumer->set("audio_device", audioDevice.toUtf8().constData());
}
QString audioDriver = KdenliveSettings::audiodrivername();
if (!audioDriver.isEmpty()) {
m_consumer->set("audio_driver", audioDriver.toUtf8().constData());
}
}
/*if (!pCore->getCurrentProfile()->progressive())
m_consumer->set("progressive", property("progressive").toBool());*/
m_consumer->set("volume", volume / 100.0);
// m_consumer->set("progressive", 1);
m_consumer->set("rescale", KdenliveSettings::mltinterpolation().toUtf8().constData());
m_consumer->set("deinterlace_method", KdenliveSettings::mltdeinterlacer().toUtf8().constData());
/*
#ifdef Q_OS_WIN
m_consumer->set("audio_buffer", 2048);
#else
m_consumer->set("audio_buffer", 512);
#endif
*/
m_consumer->set("buffer", 25);
m_consumer->set("prefill", 1);
m_consumer->set("scrub_audio", 1);
if (KdenliveSettings::monitor_gamma() == 0) {
m_consumer->set("color_trc", "iec61966_2_1");
} else {
m_consumer->set("color_trc", "bt709");
}
} else {
// Cleanup on error
error = 2;
}
return error;
}
float GLWidget::zoom() const
{
return m_zoom;
}
float GLWidget::scale() const
{
return (double)m_rect.width() / pCore->getCurrentProfile()->width() * m_zoom;
}
void GLWidget::reloadProfile()
{
// The profile display aspect ratio may have changed.
resizeGL(width(), height());
refreshSceneLayout();
}
QSize GLWidget::profileSize() const
{
return {pCore->getCurrentProfile()->width(), pCore->getCurrentProfile()->height()};
}
QRect GLWidget::displayRect() const
{
return m_rect;
}
QPoint GLWidget::offset() const
{
return {m_offset.x() - ((int)((float)pCore->getCurrentProfile()->width() * m_zoom) - width()) / 2,
m_offset.y() - ((int)((float)pCore->getCurrentProfile()->height() * m_zoom) - height()) / 2};
}
void GLWidget::setZoom(float zoom)
{
double zoomRatio = zoom / m_zoom;
m_zoom = zoom;
emit zoomChanged();
if (rootObject()) {
rootObject()->setProperty("zoom", m_zoom);
double scalex = rootObject()->property("scalex").toDouble() * zoomRatio;
rootObject()->setProperty("scalex", scalex);
double scaley = rootObject()->property("scaley").toDouble() * zoomRatio;
rootObject()->setProperty("scaley", scaley);
}
update();
}
void GLWidget::onFrameDisplayed(const SharedFrame &frame)
{
m_contextSharedAccess.lock();
m_sharedFrame = frame;
m_sendFrame = sendFrameForAnalysis;
m_contextSharedAccess.unlock();
update();
}
void GLWidget::mouseReleaseEvent(QMouseEvent *event)
{
QQuickView::mouseReleaseEvent(event);
if (m_dragStart.isNull() && m_panStart.isNull() && (rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") &&
!(event->modifiers() & Qt::ControlModifier)) {
event->ignore();
return;
}
if (!m_dragStart.isNull() && m_panStart.isNull() && ((event->button() & Qt::LeftButton) != 0u) && !event->isAccepted()) {
emit monitorPlay();
}
m_dragStart = QPoint();
m_panStart = QPoint();
setCursor(Qt::ArrowCursor);
}
void GLWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
QQuickView::mouseDoubleClickEvent(event);
if (event->isAccepted()) {
return;
}
if ((rootObject() == nullptr) || rootObject()->objectName() != QLatin1String("rooteffectscene")) {
emit switchFullScreen();
}
event->accept();
}
void GLWidget::setOffsetX(int x, int max)
{
m_offset.setX(x);
emit offsetChanged();
if (rootObject()) {
rootObject()->setProperty("offsetx", m_zoom > 1.0f ? x - max / 2.0 - 10 : 0);
}
update();
}
void GLWidget::setOffsetY(int y, int max)
{
m_offset.setY(y);
if (rootObject()) {
rootObject()->setProperty("offsety", m_zoom > 1.0f ? y - max / 2.0 - 10 : 0);
}
update();
}
int GLWidget::realTime() const
{
// C & D
if (m_glslManager) {
return 1;
}
return KdenliveSettings::mltthreads();
}
std::shared_ptr GLWidget::consumer()
{
return m_consumer;
}
void GLWidget::updateGamma()
{
reconfigure();
}
void GLWidget::resetConsumer(bool fullReset)
{
if (fullReset && m_consumer) {
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
}
reconfigure();
}
const QString GLWidget::sceneList(const QString &root, const QString &fullPath)
{
QString playlist;
qCDebug(KDENLIVE_LOG) << " * * *Setting document xml root: " << root;
Mlt::Consumer xmlConsumer(pCore->getCurrentProfile()->profile(), "xml", fullPath.isEmpty() ? "kdenlive_playlist" : fullPath.toUtf8().constData());
if (!root.isEmpty()) {
xmlConsumer.set("root", root.toUtf8().constData());
}
if (!xmlConsumer.is_valid()) {
return QString();
}
m_producer->optimise();
xmlConsumer.set("terminate_on_pause", 1);
xmlConsumer.set("store", "kdenlive");
xmlConsumer.set("time_format", "clock");
// Disabling meta creates cleaner files, but then we don't have access to metadata on the fly (meta channels, etc)
// And we must use "avformat" instead of "avformat-novalidate" on project loading which causes a big delay on project opening
// xmlConsumer.set("no_meta", 1);
Mlt::Producer prod(m_producer->get_producer());
if (!prod.is_valid()) {
return QString();
}
xmlConsumer.connect(prod);
xmlConsumer.run();
playlist = fullPath.isEmpty() ? QString::fromUtf8(xmlConsumer.get("kdenlive_playlist")) : fullPath;
return playlist;
}
void GLWidget::updateTexture(GLuint yName, GLuint uName, GLuint vName)
{
m_texture[0] = yName;
m_texture[1] = uName;
m_texture[2] = vName;
m_sendFrame = sendFrameForAnalysis;
// update();
}
// MLT consumer-frame-show event handler
void GLWidget::on_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr)
{
Mlt::Frame frame(frame_ptr);
if (frame.get_int("rendered") != 0) {
auto *widget = static_cast(self);
int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
QMetaObject::invokeMethod(widget->m_frameRenderer, "showFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
}
}
}
void GLWidget::on_gl_nosync_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr)
{
Mlt::Frame frame(frame_ptr);
if (frame.get_int("rendered") != 0) {
auto *widget = static_cast(self);
int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
QMetaObject::invokeMethod(widget->m_frameRenderer, "showGLNoSyncFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
}
}
}
void GLWidget::on_gl_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr)
{
Mlt::Frame frame(frame_ptr);
if (frame.get_int("rendered") != 0) {
auto *widget = static_cast(self);
int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
QMetaObject::invokeMethod(widget->m_frameRenderer, "showGLFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
}
}
}
RenderThread::RenderThread(thread_function_t function, void *data, QOpenGLContext *context, QSurface *surface)
: QThread(nullptr)
, m_function(function)
, m_data(data)
, m_context(nullptr)
, m_surface(surface)
{
if (context) {
m_context = new QOpenGLContext;
m_context->setFormat(context->format());
m_context->setShareContext(context);
m_context->create();
m_context->moveToThread(this);
}
}
RenderThread::~RenderThread()
{
// would otherwise leak if RenderThread is allocated with a context but not run.
// safe post-run
delete m_context;
}
// TODO: missing some exception handling?
void RenderThread::run()
{
if (m_context) {
m_context->makeCurrent(m_surface);
}
m_function(m_data);
if (m_context) {
m_context->doneCurrent();
delete m_context;
m_context = nullptr;
}
}
FrameRenderer::FrameRenderer(QOpenGLContext *shareContext, QSurface *surface, GLWidget::ClientWaitSync_fp clientWaitSync)
: QThread(nullptr)
, m_semaphore(3)
, m_context(nullptr)
, m_surface(surface)
, m_ClientWaitSync(clientWaitSync)
, m_gl32(nullptr)
, sendAudioForAnalysis(false)
{
Q_ASSERT(shareContext);
m_renderTexture[0] = m_renderTexture[1] = m_renderTexture[2] = 0;
m_displayTexture[0] = m_displayTexture[1] = m_displayTexture[2] = 0;
// B & C & D
if (KdenliveSettings::gpu_accel() || shareContext->supportsThreadedOpenGL()) {
m_context = new QOpenGLContext;
m_context->setFormat(shareContext->format());
m_context->setShareContext(shareContext);
m_context->create();
m_context->moveToThread(this);
}
setObjectName(QStringLiteral("FrameRenderer"));
moveToThread(this);
start();
}
FrameRenderer::~FrameRenderer()
{
delete m_context;
delete m_gl32;
}
void FrameRenderer::showFrame(Mlt::Frame frame)
{
int width = 0;
int height = 0;
mlt_image_format format = mlt_image_yuv420p;
frame.get_image(format, width, height);
// Save this frame for future use and to keep a reference to the GL Texture.
m_displayFrame = SharedFrame(frame);
if ((m_context != nullptr) && m_context->isValid()) {
m_context->makeCurrent(m_surface);
// Upload each plane of YUV to a texture.
QOpenGLFunctions *f = m_context->functions();
uploadTextures(m_context, m_displayFrame, m_renderTexture);
f->glBindTexture(GL_TEXTURE_2D, 0);
check_error(f);
f->glFinish();
for (int i = 0; i < 3; ++i) {
std::swap(m_renderTexture[i], m_displayTexture[i]);
}
emit textureReady(m_displayTexture[0], m_displayTexture[1], m_displayTexture[2]);
m_context->doneCurrent();
}
// The frame is now done being modified and can be shared with the rest
// of the application.
emit frameDisplayed(m_displayFrame);
m_semaphore.release();
}
void FrameRenderer::showGLFrame(Mlt::Frame frame)
{
if ((m_context != nullptr) && m_context->isValid()) {
int width = 0;
int height = 0;
frame.set("movit.convert.use_texture", 1);
mlt_image_format format = mlt_image_glsl_texture;
frame.get_image(format, width, height);
m_context->makeCurrent(m_surface);
pipelineSyncToFrame(frame);
m_context->functions()->glFinish();
m_context->doneCurrent();
// Save this frame for future use and to keep a reference to the GL Texture.
m_displayFrame = SharedFrame(frame);
}
// The frame is now done being modified and can be shared with the rest
// of the application.
emit frameDisplayed(m_displayFrame);
m_semaphore.release();
}
void FrameRenderer::showGLNoSyncFrame(Mlt::Frame frame)
{
if ((m_context != nullptr) && m_context->isValid()) {
int width = 0;
int height = 0;
frame.set("movit.convert.use_texture", 1);
mlt_image_format format = mlt_image_glsl_texture;
frame.get_image(format, width, height);
m_context->makeCurrent(m_surface);
m_context->functions()->glFinish();
m_context->doneCurrent();
// Save this frame for future use and to keep a reference to the GL Texture.
m_displayFrame = SharedFrame(frame);
}
// The frame is now done being modified and can be shared with the rest
// of the application.
emit frameDisplayed(m_displayFrame);
m_semaphore.release();
}
void FrameRenderer::cleanup()
{
if ((m_renderTexture[0] != 0u) && (m_renderTexture[1] != 0u) && (m_renderTexture[2] != 0u)) {
m_context->makeCurrent(m_surface);
m_context->functions()->glDeleteTextures(3, m_renderTexture);
if ((m_displayTexture[0] != 0u) && (m_displayTexture[1] != 0u) && (m_displayTexture[2] != 0u)) {
m_context->functions()->glDeleteTextures(3, m_displayTexture);
}
m_context->doneCurrent();
m_renderTexture[0] = m_renderTexture[1] = m_renderTexture[2] = 0;
m_displayTexture[0] = m_displayTexture[1] = m_displayTexture[2] = 0;
}
}
// D
void FrameRenderer::pipelineSyncToFrame(Mlt::Frame &frame)
{
auto sync = (GLsync)frame.get_data("movit.convert.fence");
if (!sync) return;
#ifdef Q_OS_WIN
// On Windows, use QOpenGLFunctions_3_2_Core instead of getProcAddress.
// TODO: move to initialization of m_ClientWaitSync
if (!m_gl32) {
m_gl32 = m_context->versionFunctions();
if (m_gl32) {
m_gl32->initializeOpenGLFunctions();
}
}
if (m_gl32) {
m_gl32->glClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
check_error(m_context->functions());
}
#else
if (m_ClientWaitSync) {
m_ClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
check_error(m_context->functions());
}
#endif // Q_OS_WIN
}
void GLWidget::setAudioThumb(int channels, const QVariantList &audioCache)
{
if (!rootObject()) return;
auto *audioThumbDisplay = rootObject()->findChild(QStringLiteral("audiothumb"));
if (!audioThumbDisplay) return;
QImage img(width(), height() / 6, QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::transparent);
if (!audioCache.isEmpty() && channels > 0) {
int audioLevelCount = audioCache.count() - 1;
// simplified audio
QPainter painter(&img);
QRectF mappedRect(0, 0, img.width(), img.height());
int channelHeight = mappedRect.height();
double value;
double scale = (double)width() / (audioLevelCount / channels);
if (scale < 1) {
painter.setPen(QColor(80, 80, 150, 200));
for (int i = 0; i < img.width(); i++) {
int framePos = i / scale;
value = audioCache.at(qMin(framePos * channels, audioLevelCount)).toDouble() / 256;
for (int channel = 1; channel < channels; channel++) {
value = qMax(value, audioCache.at(qMin(framePos * channels + channel, audioLevelCount)).toDouble() / 256);
}
painter.drawLine(i, mappedRect.bottom() - (value * channelHeight), i, mappedRect.bottom());
}
} else {
QPainterPath positiveChannelPath;
positiveChannelPath.moveTo(0, mappedRect.bottom());
for (int i = 0; i < audioLevelCount / channels; i++) {
value = audioCache.at(qMin(i * channels, audioLevelCount)).toDouble() / 256;
for (int channel = 1; channel < channels; channel++) {
value = qMax(value, audioCache.at(qMin(i * channels + channel, audioLevelCount)).toDouble() / 256);
}
positiveChannelPath.lineTo(i * scale, mappedRect.bottom() - (value * channelHeight));
}
positiveChannelPath.lineTo(mappedRect.right(), mappedRect.bottom());
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(QColor(80, 80, 150, 200)));
painter.drawPath(positiveChannelPath);
}
painter.end();
}
audioThumbDisplay->setImage(img);
}
void GLWidget::refreshSceneLayout()
{
if (!rootObject()) {
return;
}
rootObject()->setProperty("profile", QPoint(pCore->getCurrentProfile()->width(), pCore->getCurrentProfile()->height()));
rootObject()->setProperty("scalex", (double)m_rect.width() / pCore->getCurrentProfile()->width() * m_zoom);
rootObject()->setProperty("scaley",
(double)m_rect.width() /
(((double)pCore->getCurrentProfile()->height() * pCore->getCurrentProfile()->dar() / pCore->getCurrentProfile()->width())) /
pCore->getCurrentProfile()->width() * m_zoom);
}
void GLWidget::switchPlay(bool play, double speed)
{
m_proxy->setSeekPosition(-1);
if (!m_producer || !m_consumer) {
return;
}
if (m_isZoneMode) {
resetZoneMode();
}
if (play) {
if (m_id == Kdenlive::ClipMonitor && m_consumer->position() == m_producer->get_out()) {
m_producer->seek(0);
}
m_producer->set_speed(speed);
m_consumer->start();
m_consumer->set("refresh", 1);
} else {
m_producer->set_speed(0);
m_producer->seek(m_consumer->position() + 1);
m_consumer->purge();
m_consumer->start();
}
}
bool GLWidget::playZone(bool loop)
{
if (!m_producer || m_proxy->zoneOut() <= m_proxy->zoneIn()) {
pCore->displayMessage(i18n("Select a zone to play"), InformationMessage, 500);
return false;
}
m_proxy->setSeekPosition(-1);
m_producer->seek(m_proxy->zoneIn());
m_producer->set_speed(0);
m_consumer->purge();
m_producer->set("out", m_proxy->zoneOut());
m_producer->set_speed(1.0);
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("refresh", 1);
m_isZoneMode = true;
m_isLoopMode = loop;
return true;
}
bool GLWidget::loopClip()
{
if (!m_producer || m_proxy->zoneOut() <= m_proxy->zoneIn()) {
pCore->displayMessage(i18n("Select a zone to play"), InformationMessage, 500);
return false;
}
m_proxy->setSeekPosition(-1);
m_producer->seek(0);
m_producer->set_speed(0);
m_consumer->purge();
m_producer->set("out", m_producer->get_playtime());
m_producer->set_speed(1.0);
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("refresh", 1);
m_isZoneMode = true;
m_isLoopMode = true;
return true;
}
void GLWidget::resetZoneMode()
{
if (!m_isZoneMode && !m_isLoopMode) {
return;
}
m_producer->set("out", m_producer->get_length());
m_isZoneMode = false;
m_isLoopMode = false;
}
MonitorProxy *GLWidget::getControllerProxy()
{
return m_proxy;
}
int GLWidget::getCurrentPos() const
{
return m_proxy->seeking() ? m_proxy->seekPosition() : m_consumer->position();
}
void GLWidget::setRulerInfo(int duration, const std::shared_ptr &model)
{
rootObject()->setProperty("duration", duration);
if (model != nullptr) {
// we are resetting marker/snap model, reset zone
rootContext()->setContextProperty("markersModel", model.get());
}
}
void GLWidget::startConsumer()
{
if (m_consumer == nullptr) {
return;
}
if (m_consumer->is_stopped() && m_consumer->start() == -1) {
// ARGH CONSUMER BROKEN!!!!
KMessageBox::error(
qApp->activeWindow(),
i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or your driver settings, please fix it."));
if (m_displayEvent) {
delete m_displayEvent;
}
m_displayEvent = nullptr;
m_consumer.reset();
return;
}
m_consumer->set("refresh", 1);
}
void GLWidget::stop()
{
m_refreshTimer.stop();
m_proxy->setSeekPosition(-1);
// why this lock?
QMutexLocker locker(&m_mltMutex);
if (m_producer) {
if (m_isZoneMode) {
resetZoneMode();
}
m_producer->set_speed(0.0);
}
if (m_consumer) {
m_consumer->purge();
if (!m_consumer->is_stopped()) {
m_consumer->stop();
}
}
}
double GLWidget::playSpeed() const
{
if (m_producer) {
return m_producer->get_speed();
}
return 0.0;
}
void GLWidget::setDropFrames(bool drop)
{
// why this lock?
QMutexLocker locker(&m_mltMutex);
if (m_consumer) {
int dropFrames = realTime();
if (!drop) {
dropFrames = -dropFrames;
}
m_consumer->stop();
m_consumer->set("real_time", dropFrames);
if (m_consumer->start() == -1) {
qCWarning(KDENLIVE_LOG) << "ERROR, Cannot start monitor";
}
}
}
int GLWidget::volume() const
{
if ((!m_consumer) || (!m_producer)) {
return -1;
}
if (m_consumer->get("mlt_service") == QStringLiteral("multi")) {
return ((int)100 * m_consumer->get_double("0.volume"));
}
return ((int)100 * m_consumer->get_double("volume"));
}
void GLWidget::setVolume(double volume)
{
if (m_consumer) {
if (m_consumer->get("mlt_service") == QStringLiteral("multi")) {
m_consumer->set("0.volume", volume);
} else {
m_consumer->set("volume", volume);
}
}
}
int GLWidget::duration() const
{
if (!m_producer) {
return 0;
}
return m_producer->get_playtime();
}
void GLWidget::setConsumerProperty(const QString &name, const QString &value)
{
QMutexLocker locker(&m_mltMutex);
if (m_consumer) {
m_consumer->set(name.toUtf8().constData(), value.toUtf8().constData());
if (m_consumer->start() == -1) {
qCWarning(KDENLIVE_LOG) << "ERROR, Cannot start monitor";
}
}
}