diff --git a/fuzzer/fuzzing.cpp b/fuzzer/fuzzing.cpp
index 672568eb8..5b3a2e000 100644
--- a/fuzzer/fuzzing.cpp
+++ b/fuzzer/fuzzing.cpp
@@ -1,509 +1,509 @@
/***************************************************************************
* Copyright (C) 2019 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 "fuzzing.hpp"
#include "bin/model/markerlistmodel.hpp"
#include "doc/docundostack.hpp"
#include "fakeit_standalone.hpp"
#include "logger.hpp"
#include
#include
#include
#include
#include
#define private public
#define protected public
#include "assets/keyframes/model/keyframemodel.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "bin/clipcreator.hpp"
#include "bin/projectclip.h"
#include "bin/projectfolder.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "effects/effectsrepository.hpp"
#include "effects/effectstack/model/effectitemmodel.hpp"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "mltconnection.h"
#include "project/projectmanager.h"
#include "timeline2/model/clipmodel.hpp"
#include "timeline2/model/compositionmodel.hpp"
#include "timeline2/model/groupsmodel.hpp"
#include "timeline2/model/timelinefunctions.hpp"
#include "timeline2/model/timelineitemmodel.hpp"
#include "timeline2/model/timelinemodel.hpp"
#include "timeline2/model/trackmodel.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wfloat-equal"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wpedantic"
#include
#pragma GCC diagnostic pop
using namespace fakeit;
namespace {
QString createProducer(Mlt::Profile &prof, std::string color, std::shared_ptr binModel, int length, bool limited)
{
Logger::log_create_producer("test_producer", {color, binModel, length, limited});
std::shared_ptr producer = std::make_shared(prof, "color", color.c_str());
producer->set("length", length);
producer->set("out", length - 1);
Q_ASSERT(producer->is_valid());
QString binId = QString::number(binModel->getFreeClipId());
auto binClip = ProjectClip::construct(binId, QIcon(), binModel, producer);
if (limited) {
binClip->forceLimitedDuration();
}
Fun undo = []() { return true; };
Fun redo = []() { return true; };
Q_ASSERT(binModel->addItem(binClip, binModel->getRootFolder()->clipId(), undo, redo));
return binId;
}
QString createProducerWithSound(Mlt::Profile &prof, std::shared_ptr binModel)
{
Logger::log_create_producer("test_producer_sound", {binModel});
// std::shared_ptr producer = std::make_shared(prof,
// QFileInfo("../tests/small.mkv").absoluteFilePath().toStdString().c_str());
// In case the test system does not have avformat support, we can switch to the integrated blipflash producer
std::shared_ptr producer = std::make_shared(prof, "blipflash");
producer->set_in_and_out(0, 1);
producer->set("kdenlive:duration", 2);
Q_ASSERT(producer->is_valid());
QString binId = QString::number(binModel->getFreeClipId());
auto binClip = ProjectClip::construct(binId, QIcon(), binModel, producer);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
Q_ASSERT(binModel->addItem(binClip, binModel->getRootFolder()->clipId(), undo, redo));
return binId;
}
inline int modulo(int a, int b)
{
const int result = a % b;
return result >= 0 ? result : result + b;
}
namespace {
bool isIthParamARef(const rttr::method &method, size_t i)
{
QString sig = QString::fromStdString(method.get_signature().to_string());
int deb = sig.indexOf("(");
int end = sig.lastIndexOf(")");
sig = sig.mid(deb + 1, deb - end - 1);
QStringList args = sig.split(QStringLiteral(","));
return args[(int)i].contains("&") && !args[(int)i].contains("const &");
}
} // namespace
} // namespace
void fuzz(const std::string &input)
{
Logger::init();
Logger::clear();
std::stringstream ss;
ss << input;
Mlt::Profile profile;
auto binModel = pCore->projectItemModel();
binModel->clean();
std::shared_ptr undoStack = std::make_shared(nullptr);
std::shared_ptr guideModel = std::make_shared(undoStack);
TimelineModel::next_id = 0;
Mock pmMock;
When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
ProjectManager &mocked = pmMock.get();
pCore->m_projectManager = &mocked;
std::vector> all_timelines;
std::unordered_map, std::vector> all_clips, all_tracks, all_compositions, all_groups;
auto update_elems = [&]() {
all_clips.clear();
all_tracks.clear();
all_compositions.clear();
for (const auto &timeline : all_timelines) {
all_clips[timeline] = {};
all_tracks[timeline] = {};
all_compositions[timeline] = {};
all_groups[timeline] = {};
auto &clips = all_clips[timeline];
clips.clear();
for (const auto &c : timeline->m_allClips) {
clips.push_back(c.first);
}
std::sort(clips.begin(), clips.end());
auto &compositions = all_compositions[timeline];
compositions.clear();
for (const auto &c : timeline->m_allCompositions) {
compositions.push_back(c.first);
}
std::sort(compositions.begin(), compositions.end());
auto &tracks = all_tracks[timeline];
tracks.clear();
for (const auto &c : timeline->m_iteratorTable) {
tracks.push_back(c.first);
}
std::sort(tracks.begin(), tracks.end());
auto &groups = all_groups[timeline];
groups.clear();
for (int c : timeline->m_allGroups) {
groups.push_back(c);
}
std::sort(groups.begin(), groups.end());
}
};
auto get_timeline = [&]() -> std::shared_ptr {
int id = 0;
ss >> id;
if (all_timelines.size() == 0) return nullptr;
id = modulo(id, (int)all_timelines.size());
return all_timelines[size_t(id)];
};
auto get_group = [&](std::shared_ptr timeline) {
int id = 0;
ss >> id;
if (!timeline) return -1;
if (timeline->isGroup(id)) return id;
if (all_timelines.size() == 0) return -1;
if (all_groups.count(timeline) == 0) return -1;
if (all_groups[timeline].size() == 0) return -1;
id = modulo(id, (int)all_groups[timeline].size());
return all_groups[timeline][id];
};
auto get_clip = [&](std::shared_ptr timeline) {
int id = 0;
ss >> id;
if (!timeline) return -1;
if (timeline->isClip(id)) return id;
if (all_timelines.size() == 0) return -1;
if (all_clips.count(timeline) == 0) return -1;
if (all_clips[timeline].size() == 0) return -1;
id = modulo(id, (int)all_clips[timeline].size());
return all_clips[timeline][id];
};
auto get_compo = [&](std::shared_ptr timeline) {
int id = 0;
ss >> id;
if (!timeline) return -1;
if (timeline->isComposition(id)) return id;
if (all_timelines.size() == 0) return -1;
if (all_compositions.count(timeline) == 0) return -1;
if (all_compositions[timeline].size() == 0) return -1;
id = modulo(id, (int)all_compositions[timeline].size());
return all_compositions[timeline][id];
};
auto get_item = [&](std::shared_ptr timeline) {
int id = 0;
ss >> id;
if (!timeline) return -1;
if (timeline->isClip(id)) return id;
if (timeline->isComposition(id)) return id;
if (all_timelines.size() == 0) return -1;
int clip_count = 0;
if (all_clips.count(timeline) > 0) {
clip_count = all_clips[timeline].size();
}
int compo_count = 0;
if (all_compositions.count(timeline) > 0) {
compo_count = all_compositions[timeline].size();
}
if (clip_count + compo_count == 0) return -1;
id = modulo(id, clip_count + compo_count);
if (id < clip_count) {
return all_clips[timeline][id];
}
return all_compositions[timeline][id - clip_count];
};
auto get_track = [&](std::shared_ptr timeline) {
int id = 0;
ss >> id;
if (!timeline) return -1;
if (timeline->isTrack(id)) return id;
if (all_timelines.size() == 0) return -1;
if (all_tracks.count(timeline) == 0) return -1;
if (all_tracks[timeline].size() == 0) return -1;
id = modulo(id, (int)all_tracks[timeline].size());
return all_tracks[timeline][id];
};
std::string c;
while (ss >> c) {
if (c == "u") {
std::cout << "UNDOING" << std::endl;
undoStack->undo();
} else if (c == "r") {
std::cout << "REDOING" << std::endl;
undoStack->redo();
} else if (Logger::back_translation_table.count(c) > 0) {
// std::cout << "found=" << c;
c = Logger::back_translation_table[c];
- // std::cout << " tranlated=" << c << std::endl;
+ // std::cout << " translated=" << c << std::endl;
if (c == "constr_TimelineModel") {
all_timelines.emplace_back(TimelineItemModel::construct(&profile, guideModel, undoStack));
} else if (c == "constr_ClipModel") {
auto timeline = get_timeline();
int id = 0, state_id;
double speed = 1;
PlaylistState::ClipState state = PlaylistState::VideoOnly;
std::string binId;
ss >> binId >> id >> state_id >> speed;
QString binClip = QString::fromStdString(binId);
bool valid = true;
if (!pCore->projectItemModel()->hasClip(binClip)) {
if (pCore->projectItemModel()->getAllClipIds().size() == 0) {
valid = false;
} else {
binClip = pCore->projectItemModel()->getAllClipIds()[0];
}
}
state = static_cast(state_id);
if (timeline && valid) {
ClipModel::construct(timeline, binClip, -1, state, speed);
}
} else if (c == "constr_TrackModel") {
auto timeline = get_timeline();
int id, pos = 0;
std::string name;
bool audio = false;
ss >> id >> pos >> name >> audio;
if (name == "$$") {
name = "";
}
if (pos < -1) pos = 0;
pos = std::min((int)all_tracks[timeline].size(), pos);
if (timeline) {
TrackModel::construct(timeline, -1, pos, QString::fromStdString(name), audio);
}
} else if (c == "constr_test_producer") {
std::string color;
int length = 0;
bool limited = false;
ss >> color >> length >> limited;
createProducer(profile, color, binModel, length, limited);
} else if (c == "constr_test_producer_sound") {
createProducerWithSound(profile, binModel);
} else {
// std::cout << "executing " << c << std::endl;
rttr::type target_type = rttr::type::get();
bool found = false;
for (const std::string &t : {"TimelineModel", "TimelineFunctions"}) {
rttr::type current_type = rttr::type::get_by_name(t);
// std::cout << "type " << t << " has methods count=" << current_type.get_methods().size() << std::endl;
if (current_type.get_method(c).is_valid()) {
found = true;
target_type = current_type;
break;
}
}
if (found) {
// std::cout << "found!" << std::endl;
bool valid = true;
rttr::method target_method = target_type.get_method(c);
std::vector arguments;
rttr::variant ptr;
if (target_type == rttr::type::get()) {
if (all_timelines.size() == 0) {
valid = false;
}
ptr = get_timeline();
}
int i = -1;
for (const auto &p : target_method.get_parameter_infos()) {
++i;
std::string arg_name = p.get_name().to_string();
// std::cout << arg_name << std::endl;
if (arg_name == "compoId") {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
int compoId = get_compo(tim);
valid = valid && (compoId >= 0);
// std::cout << "got compo" << compoId << std::endl;
arguments.emplace_back(compoId);
} else if (arg_name == "clipId") {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
int clipId = get_clip(tim);
valid = valid && (clipId >= 0);
arguments.emplace_back(clipId);
// std::cout << "got clipId" << clipId << std::endl;
} else if (arg_name == "trackId") {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
int trackId = get_track(tim);
valid = valid && (trackId >= 0);
arguments.emplace_back(trackId);
// std::cout << "got trackId" << trackId << std::endl;
} else if (arg_name == "itemId") {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
int itemId = get_item(tim);
valid = valid && (itemId >= 0);
arguments.emplace_back(itemId);
// std::cout << "got itemId" << itemId << std::endl;
} else if (arg_name == "groupId") {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
int groupId = get_group(tim);
valid = valid && (groupId >= 0);
arguments.emplace_back(groupId);
// std::cout << "got clipId" << clipId << std::endl;
} else if (arg_name == "logUndo") {
bool a = false;
ss >> a;
// we enforce undo logging
a = true;
arguments.emplace_back(a);
} else if (arg_name == "itemIds") {
int count = 0;
ss >> count;
// std::cout << "got ids. going to read count=" << count << std::endl;
if (count > 0) {
std::shared_ptr tim =
(ptr.can_convert>() ? ptr.convert>() : nullptr);
std::unordered_set ids;
for (int i = 0; i < count; ++i) {
int itemId = get_item(tim);
// std::cout << "\t read" << itemId << std::endl;
valid = valid && (itemId >= 0);
ids.insert(itemId);
}
arguments.emplace_back(ids);
} else {
valid = false;
}
} else if (!isIthParamARef(target_method, i)) {
rttr::type arg_type = p.get_type();
if (arg_type == rttr::type::get()) {
int a = 0;
ss >> a;
// std::cout << "read int " << a << std::endl;
arguments.emplace_back(a);
} else if (arg_type == rttr::type::get()) {
size_t a = 0;
ss >> a;
arguments.emplace_back(a);
} else if (arg_type == rttr::type::get()) {
double a = 0;
ss >> a;
arguments.emplace_back(a);
} else if (arg_type == rttr::type::get()) {
float a = 0;
ss >> a;
arguments.emplace_back(a);
} else if (arg_type == rttr::type::get()) {
bool a = false;
ss >> a;
// std::cout << "read bool " << a << std::endl;
arguments.emplace_back(a);
} else if (arg_type == rttr::type::get()) {
std::string str = "";
ss >> str;
// std::cout << "read str " << str << std::endl;
if (str == "$$") {
str = "";
}
arguments.emplace_back(QString::fromStdString(str));
} else if (arg_type == rttr::type::get>()) {
auto timeline = get_timeline();
if (timeline) {
// std::cout << "got timeline" << std::endl;
auto timeline2 = std::dynamic_pointer_cast(timeline);
arguments.emplace_back(timeline2);
ptr = timeline;
} else {
// std::cout << "didn't get timeline" << std::endl;
valid = false;
}
} else if (arg_type.is_enumeration()) {
int a = 0;
ss >> a;
rttr::variant var_a = a;
var_a.convert((const rttr::type &)arg_type);
// std::cout << "read enum " << arg_type.get_enumeration().value_to_name(var_a).to_string() << std::endl;
arguments.push_back(var_a);
} else {
std::cout << "ERROR: unsupported arg type " << arg_type.get_name().to_string() << std::endl;
assert(false);
}
} else {
if (p.get_type() == rttr::type::get()) {
arguments.emplace_back(-1);
} else {
assert(false);
}
}
}
if (valid) {
std::cout << "VALID!!! " << target_method.get_name().to_string() << std::endl;
std::vector args;
args.reserve(arguments.size());
for (auto &a : arguments) {
args.emplace_back(a);
// std::cout << "argument=" << a.get_type().get_name().to_string() << std::endl;
}
for (const auto &p : target_method.get_parameter_infos()) {
// std::cout << "expected=" << p.get_type().get_name().to_string() << std::endl;
}
rttr::variant res = target_method.invoke_variadic(ptr, args);
if (res.is_valid()) {
std::cout << "SUCCESS!!!" << std::endl;
} else {
std::cout << "!!!FAILLLLLL!!!" << std::endl;
}
}
}
}
}
update_elems();
for (const auto &t : all_timelines) {
assert(t->checkConsistency());
}
}
undoStack->clear();
all_clips.clear();
all_tracks.clear();
all_compositions.clear();
all_groups.clear();
for (auto &all_timeline : all_timelines) {
all_timeline.reset();
}
pCore->m_projectManager = nullptr;
Core::m_self.reset();
MltConnection::m_self.reset();
std::cout << "---------------------------------------------------------------------------------------------------------------------------------------------"
"---------------"
<< std::endl;
}
diff --git a/renderer/kdenlive_render.cpp b/renderer/kdenlive_render.cpp
index cf541e4f0..2dc0f5cdd 100644
--- a/renderer/kdenlive_render.cpp
+++ b/renderer/kdenlive_render.cpp
@@ -1,165 +1,165 @@
/***************************************************************************
* 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 "framework/mlt_version.h"
#include "mlt++/Mlt.h"
#include "renderjob.h"
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStringList args = app.arguments();
QStringList preargs;
QString locale;
if (args.count() >= 4) {
// Remove program name
args.removeFirst();
// renderer path (melt)
QString render = args.at(0);
args.removeFirst();
// Source playlist path
QString playlist = args.at(0);
args.removeFirst();
// target - where to save result
QString target = args.at(0);
args.removeFirst();
int pid = 0;
// pid to send back progress
if (args.count() > 0 && args.at(0).startsWith(QLatin1String("-pid:"))) {
pid = args.at(0).section(QLatin1Char(':'), 1).toInt();
args.removeFirst();
}
// Do we want a split render
if (args.count() > 0 && args.at(0) == QLatin1String("-split")) {
args.removeFirst();
// chunks to render
QStringList chunks = args.at(0).split(QLatin1Char(','), QString::SkipEmptyParts);
args.removeFirst();
// chunk size in frames
int chunkSize = args.at(0).toInt();
args.removeFirst();
// chunk size in frames
QString profilePath = args.at(0);
args.removeFirst();
// rendered file extension
QString extension = args.at(0);
args.removeFirst();
// avformat consumer params
QStringList consumerParams = args.at(0).split(QLatin1Char(' '), QString::SkipEmptyParts);
args.removeFirst();
QDir baseFolder(target);
Mlt::Factory::init();
Mlt::Profile profile(profilePath.toUtf8().constData());
profile.set_explicit(1);
Mlt::Producer prod(profile, nullptr, playlist.toUtf8().constData());
if (!prod.is_valid()) {
fprintf(stderr, "INVALID playlist: %s \n", playlist.toUtf8().constData());
return 1;
}
const char *localename = prod.get_lcnumeric();
QLocale::setDefault(QLocale(localename));
for (const QString &frame : chunks) {
fprintf(stderr, "START:%d \n", frame.toInt());
QString fileName = QStringLiteral("%1.%2").arg(frame).arg(extension);
if (baseFolder.exists(fileName)) {
// Don't overwrite an existing file
fprintf(stderr, "DONE:%d \n", frame.toInt());
continue;
}
QScopedPointer playlst(prod.cut(frame.toInt(), frame.toInt() + chunkSize));
QScopedPointer cons(
new Mlt::Consumer(profile, QString("avformat:%1").arg(baseFolder.absoluteFilePath(fileName)).toUtf8().constData()));
for (const QString ¶m : consumerParams) {
if (param.contains(QLatin1Char('='))) {
cons->set(param.section(QLatin1Char('='), 0, 0).toUtf8().constData(), param.section(QLatin1Char('='), 1).toUtf8().constData());
}
}
if (!cons->is_valid()) {
fprintf(stderr, " = = = INVALID CONSUMER\n\n");
return 1;
}
cons->set("terminate_on_pause", 1);
cons->connect(*playlst);
playlst.reset();
cons->run();
cons->stop();
cons->purge();
fprintf(stderr, "DONE:%d \n", frame.toInt());
}
// Mlt::Factory::close();
fprintf(stderr, "+ + + RENDERING FINSHED + + + \n");
return 0;
}
int in = -1;
int out = -1;
- // older MLT version, does not support embeded consumer in/out in xml, and current
+ // older MLT version, does not support embedded consumer in/out in xml, and current
// MLT (6.16) does not pass it onto the multi / movit consumer, so read it manually and enforce
QFile f(playlist);
QDomDocument doc;
doc.setContent(&f, false);
f.close();
QDomElement consumer = doc.documentElement().firstChildElement(QStringLiteral("consumer"));
if (!consumer.isNull()) {
if (LIBMLT_VERSION_INT < 397568) {
// Previous MLT versions did not correctly pass in and out
in = consumer.attribute("in").toInt();
out = consumer.attribute("out").toInt();
}
if (consumer.hasAttribute(QLatin1String("s")) || consumer.hasAttribute(QLatin1String("r"))) {
- // Workaround MLT embeded consumer resize (MLT issue #453)
+ // Workaround MLT embedded consumer resize (MLT issue #453)
playlist.prepend(QStringLiteral("xml:"));
playlist.append(QStringLiteral("?multi=1"));
}
}
auto *rJob = new RenderJob(render, playlist, target, pid, in, out, qApp);
rJob->start();
QObject::connect(rJob, &RenderJob::renderingFinished, [&, rJob]() {
rJob->deleteLater();
app.quit();
});
return app.exec();
} else {
fprintf(stderr,
"Kdenlive video renderer for MLT.\nUsage: "
"kdenlive_render [-erase] [-kuiserver] [-locale:LOCALE] [in=pos] [out=pos] [render] [profile] [rendermodule] [player] [src] [dest] [[arg1] "
"[arg2] ...]\n"
" -erase: if that parameter is present, src file will be erased at the end\n"
" -kuiserver: if that parameter is present, use KDE job tracker\n"
" -locale:LOCALE : set a locale for rendering. For example, -locale:fr_FR.UTF-8 will use a french locale (comma as numeric separator)\n"
" in=pos: start rendering at frame pos\n"
" out=pos: end rendering at frame pos\n"
" render: path to MLT melt renderer\n"
" profile: the MLT video profile\n"
" rendermodule: the MLT consumer used for rendering, usually it is avformat\n"
" player: path to video player to play when rendering is over, use '-' to disable playing\n"
" src: source file (usually MLT XML)\n"
" dest: destination file\n"
" args: space separated libavformat arguments\n");
return 1;
}
}
diff --git a/src/bin/projectitemmodel.cpp b/src/bin/projectitemmodel.cpp
index 420646106..d97be49b9 100644
--- a/src/bin/projectitemmodel.cpp
+++ b/src/bin/projectitemmodel.cpp
@@ -1,1040 +1,1040 @@
/*
Copyright (C) 2012 Till Theato
Copyright (C) 2014 Jean-Baptiste Mardelle
Copyright (C) 2017 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 "projectitemmodel.h"
#include "abstractprojectitem.h"
#include "binplaylist.hpp"
#include "core.h"
#include "doc/kdenlivedoc.h"
#include "filewatcher.hpp"
#include "jobs/audiothumbjob.hpp"
#include "jobs/jobmanager.h"
#include "jobs/loadjob.hpp"
#include "jobs/thumbjob.hpp"
#include "jobs/cachejob.hpp"
#include "kdenlivesettings.h"
#include "macros.hpp"
#include "profiles/profilemodel.hpp"
#include "project/projectmanager.h"
#include "projectclip.h"
#include "projectfolder.h"
#include "projectsubclip.h"
#include "xml/xml.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
ProjectItemModel::ProjectItemModel(QObject *parent)
: AbstractTreeModel(parent)
, m_lock(QReadWriteLock::Recursive)
, m_binPlaylist(new BinPlaylist())
, m_fileWatcher(new FileWatcher())
, m_nextId(1)
, m_blankThumb()
, m_dragType(PlaylistState::Disabled)
{
QPixmap pix(QSize(160, 90));
pix.fill(Qt::lightGray);
m_blankThumb.addPixmap(pix);
connect(m_fileWatcher.get(), &FileWatcher::binClipModified, this, &ProjectItemModel::reloadClip);
connect(m_fileWatcher.get(), &FileWatcher::binClipWaiting, this, &ProjectItemModel::setClipWaiting);
connect(m_fileWatcher.get(), &FileWatcher::binClipMissing, this, &ProjectItemModel::setClipInvalid);
}
std::shared_ptr ProjectItemModel::construct(QObject *parent)
{
std::shared_ptr self(new ProjectItemModel(parent));
self->rootItem = ProjectFolder::construct(self);
return self;
}
ProjectItemModel::~ProjectItemModel() = default;
int ProjectItemModel::mapToColumn(int column) const
{
switch (column) {
case 0:
return AbstractProjectItem::DataName;
break;
case 1:
return AbstractProjectItem::DataDate;
break;
case 2:
return AbstractProjectItem::DataDescription;
break;
default:
return AbstractProjectItem::DataName;
}
}
QVariant ProjectItemModel::data(const QModelIndex &index, int role) const
{
READ_LOCK();
if (!index.isValid()) {
return QVariant();
}
if (role == Qt::DisplayRole || role == Qt::EditRole) {
std::shared_ptr item = getBinItemByIndex(index);
auto type = static_cast(mapToColumn(index.column()));
QVariant ret = item->getData(type);
return ret;
}
if (role == Qt::DecorationRole) {
if (index.column() != 0) {
return QVariant();
}
// Data has to be returned as icon to allow the view to scale it
std::shared_ptr item = getBinItemByIndex(index);
QVariant thumb = item->getData(AbstractProjectItem::DataThumbnail);
QIcon icon;
if (thumb.canConvert()) {
icon = thumb.value();
} else {
qDebug() << "ERROR: invalid icon found";
}
return icon;
}
std::shared_ptr item = getBinItemByIndex(index);
return item->getData(static_cast(role));
}
bool ProjectItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
QWriteLocker locker(&m_lock);
std::shared_ptr item = getBinItemByIndex(index);
if (item->rename(value.toString(), index.column())) {
emit dataChanged(index, index, {role});
return true;
}
// Item name was not changed
return false;
}
Qt::ItemFlags ProjectItemModel::flags(const QModelIndex &index) const
{
/*return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;*/
READ_LOCK();
if (!index.isValid()) {
return Qt::ItemIsDropEnabled;
}
std::shared_ptr item = getBinItemByIndex(index);
AbstractProjectItem::PROJECTITEMTYPE type = item->itemType();
switch (type) {
case AbstractProjectItem::FolderItem:
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;
break;
case AbstractProjectItem::ClipItem:
if (!item->statusReady()) {
return Qt::ItemIsSelectable;
}
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;
break;
case AbstractProjectItem::SubClipItem:
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
break;
case AbstractProjectItem::FolderUpItem:
return Qt::ItemIsEnabled;
break;
default:
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
}
// cppcheck-suppress unusedFunction
bool ProjectItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_UNUSED(row)
Q_UNUSED(column)
QWriteLocker locker(&m_lock);
if (action == Qt::IgnoreAction) {
return true;
}
if (data->hasUrls()) {
emit itemDropped(data->urls(), parent);
return true;
}
if (data->hasFormat(QStringLiteral("kdenlive/producerslist"))) {
// Dropping an Bin item
const QStringList ids = QString(data->data(QStringLiteral("kdenlive/producerslist"))).split(QLatin1Char(';'));
if (ids.constFirst().contains(QLatin1Char('/'))) {
// subclip zone
QStringList clipData = ids.constFirst().split(QLatin1Char('/'));
if (clipData.length() >= 3) {
QString id;
return requestAddBinSubClip(id, clipData.at(1).toInt(), clipData.at(2).toInt(), QString(), clipData.at(0));
} else {
// error, malformed clip zone, abort
return false;
}
} else {
emit itemDropped(ids, parent);
}
return true;
}
if (data->hasFormat(QStringLiteral("kdenlive/effect"))) {
// Dropping effect on a Bin item
QStringList effectData;
effectData << QString::fromUtf8(data->data(QStringLiteral("kdenlive/effect")));
QStringList source = QString::fromUtf8(data->data(QStringLiteral("kdenlive/effectsource"))).split(QLatin1Char('-'));
effectData << source;
emit effectDropped(effectData, parent);
return true;
}
if (data->hasFormat(QStringLiteral("kdenlive/clip"))) {
const QStringList list = QString(data->data(QStringLiteral("kdenlive/clip"))).split(QLatin1Char(';'));
QString id;
return requestAddBinSubClip(id, list.at(1).toInt(), list.at(2).toInt(), QString(), list.at(0));
}
return false;
}
QVariant ProjectItemModel::headerData(int section, Qt::Orientation orientation, int role) const
{
READ_LOCK();
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
QVariant columnName;
switch (section) {
case 0:
columnName = i18n("Name");
break;
case 1:
columnName = i18n("Date");
break;
case 2:
columnName = i18n("Description");
break;
default:
columnName = i18n("Unknown");
break;
}
return columnName;
}
return QAbstractItemModel::headerData(section, orientation, role);
}
int ProjectItemModel::columnCount(const QModelIndex &parent) const
{
READ_LOCK();
if (parent.isValid()) {
return getBinItemByIndex(parent)->supportedDataCount();
}
return std::static_pointer_cast(rootItem)->supportedDataCount();
}
// cppcheck-suppress unusedFunction
Qt::DropActions ProjectItemModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
QStringList ProjectItemModel::mimeTypes() const
{
QStringList types;
types << QStringLiteral("kdenlive/producerslist") << QStringLiteral("text/uri-list") << QStringLiteral("kdenlive/clip")
<< QStringLiteral("kdenlive/effect");
return types;
}
QMimeData *ProjectItemModel::mimeData(const QModelIndexList &indices) const
{
READ_LOCK();
// Mime data is a list of id's separated by ';'.
// Clip ids are represented like: 2 (where 2 is the clip's id)
// Clip zone ids are represented like: 2/10/200 (where 2 is the clip's id, 10 and 200 are in and out points)
// Folder ids are represented like: #2 (where 2 is the folder's id)
auto *mimeData = new QMimeData();
QStringList list;
size_t duration = 0;
for (int i = 0; i < indices.count(); i++) {
QModelIndex ix = indices.at(i);
if (!ix.isValid() || ix.column() != 0) {
continue;
}
std::shared_ptr item = getBinItemByIndex(ix);
AbstractProjectItem::PROJECTITEMTYPE type = item->itemType();
if (type == AbstractProjectItem::ClipItem) {
ClipType::ProducerType cType = item->clipType();
QString dragId = item->clipId();
if ((cType == ClipType::AV || cType == ClipType::Playlist)) {
switch (m_dragType) {
case PlaylistState::AudioOnly:
dragId.prepend(QLatin1Char('A'));
break;
case PlaylistState::VideoOnly:
dragId.prepend(QLatin1Char('V'));
break;
default:
break;
}
}
list << dragId;
duration += (std::static_pointer_cast(item))->frameDuration();
} else if (type == AbstractProjectItem::SubClipItem) {
QPoint p = item->zone();
list << std::static_pointer_cast(item)->getMasterClip()->clipId() + QLatin1Char('/') + QString::number(p.x()) + QLatin1Char('/') +
QString::number(p.y());
} else if (type == AbstractProjectItem::FolderItem) {
list << "#" + item->clipId();
}
}
if (!list.isEmpty()) {
QByteArray data;
data.append(list.join(QLatin1Char(';')).toUtf8());
mimeData->setData(QStringLiteral("kdenlive/producerslist"), data);
mimeData->setText(QString::number(duration));
}
return mimeData;
}
void ProjectItemModel::onItemUpdated(const std::shared_ptr &item, int role)
{
QWriteLocker locker(&m_lock);
auto tItem = std::static_pointer_cast(item);
auto ptr = tItem->parentItem().lock();
if (ptr) {
auto index = getIndexFromItem(tItem);
emit dataChanged(index, index, {role});
}
}
void ProjectItemModel::onItemUpdated(const QString &binId, int role)
{
QWriteLocker locker(&m_lock);
std::shared_ptr item = getItemByBinId(binId);
if (item) {
onItemUpdated(item, role);
}
}
std::shared_ptr ProjectItemModel::getClipByBinID(const QString &binId)
{
READ_LOCK();
if (binId.contains(QLatin1Char('_'))) {
return getClipByBinID(binId.section(QLatin1Char('_'), 0, 0));
}
for (const auto &clip : m_allItems) {
auto c = std::static_pointer_cast(clip.second.lock());
if (c->itemType() == AbstractProjectItem::ClipItem && c->clipId() == binId) {
return std::static_pointer_cast(c);
}
}
return nullptr;
}
const QList ProjectItemModel::getAudioLevelsByBinID(const QString &binId)
{
READ_LOCK();
if (binId.contains(QLatin1Char('_'))) {
return getAudioLevelsByBinID(binId.section(QLatin1Char('_'), 0, 0));
}
for (const auto &clip : m_allItems) {
auto c = std::static_pointer_cast(clip.second.lock());
if (c->itemType() == AbstractProjectItem::ClipItem && c->clipId() == binId) {
return std::static_pointer_cast(c)->audioFrameCache;
}
}
return QList();
}
bool ProjectItemModel::hasClip(const QString &binId)
{
READ_LOCK();
return getClipByBinID(binId) != nullptr;
}
std::shared_ptr ProjectItemModel::getFolderByBinId(const QString &binId)
{
READ_LOCK();
for (const auto &clip : m_allItems) {
auto c = std::static_pointer_cast(clip.second.lock());
if (c->itemType() == AbstractProjectItem::FolderItem && c->clipId() == binId) {
return std::static_pointer_cast(c);
}
}
return nullptr;
}
const QString ProjectItemModel::getFolderIdByName(const QString &folderName)
{
READ_LOCK();
for (const auto &clip : m_allItems) {
auto c = std::static_pointer_cast(clip.second.lock());
if (c->itemType() == AbstractProjectItem::FolderItem && c->name() == folderName) {
return c->clipId();
}
}
return QString();
}
std::shared_ptr ProjectItemModel::getItemByBinId(const QString &binId)
{
READ_LOCK();
for (const auto &clip : m_allItems) {
auto c = std::static_pointer_cast(clip.second.lock());
if (c->clipId() == binId) {
return c;
}
}
return nullptr;
}
void ProjectItemModel::setBinEffectsEnabled(bool enabled)
{
QWriteLocker locker(&m_lock);
return std::static_pointer_cast(rootItem)->setBinEffectsEnabled(enabled);
}
QStringList ProjectItemModel::getEnclosingFolderInfo(const QModelIndex &index) const
{
READ_LOCK();
QStringList noInfo;
noInfo << QString::number(-1);
noInfo << QString();
if (!index.isValid()) {
return noInfo;
}
std::shared_ptr currentItem = getBinItemByIndex(index);
auto folder = currentItem->getEnclosingFolder(true);
if ((folder == nullptr) || folder == rootItem) {
return noInfo;
}
QStringList folderInfo;
folderInfo << currentItem->clipId();
folderInfo << currentItem->name();
return folderInfo;
}
void ProjectItemModel::clean()
{
QWriteLocker locker(&m_lock);
std::vector> toDelete;
toDelete.reserve((size_t)rootItem->childCount());
for (int i = 0; i < rootItem->childCount(); ++i) {
toDelete.push_back(std::static_pointer_cast(rootItem->child(i)));
}
Fun undo = []() { return true; };
Fun redo = []() { return true; };
for (const auto &child : toDelete) {
requestBinClipDeletion(child, undo, redo);
}
Q_ASSERT(rootItem->childCount() == 0);
m_nextId = 1;
m_fileWatcher->clear();
}
std::shared_ptr ProjectItemModel::getRootFolder() const
{
READ_LOCK();
return std::static_pointer_cast(rootItem);
}
void ProjectItemModel::loadSubClips(const QString &id, const QString &clipData)
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
loadSubClips(id, clipData, undo, redo);
}
void ProjectItemModel::loadSubClips(const QString &id, const QString &dataMap, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
std::shared_ptr clip = getClipByBinID(id);
if (!clip) {
qDebug()<<" = = = = = CLIP NOT LOADED";
return;
}
auto json = QJsonDocument::fromJson(dataMap.toUtf8());
if (!json.isArray()) {
qDebug() << "Error loading zones : Json file should be an array";
return;
}
int maxFrame = clip->duration().frames(pCore->getCurrentFps()) - 1;
auto list = json.array();
for (const auto &entry : list) {
if (!entry.isObject()) {
qDebug() << "Warning : Skipping invalid marker data";
continue;
}
auto entryObj = entry.toObject();
if (!entryObj.contains(QLatin1String("name"))) {
qDebug() << "Warning : Skipping invalid zone(does not contain name)";
continue;
}
int in = entryObj[QLatin1String("in")].toInt();
int out = entryObj[QLatin1String("out")].toInt();
QString name = entryObj[QLatin1String("name")].toString(i18n("Zone"));
if (in >= out) {
qDebug() << "Warning : Invalid zone: "< 0) {
out = qMin(out, maxFrame);
}
QString subId;
requestAddBinSubClip(subId, in, out, name, id, undo, redo);
}
}
std::shared_ptr ProjectItemModel::getBinItemByIndex(const QModelIndex &index) const
{
READ_LOCK();
return std::static_pointer_cast(getItemById((int)index.internalId()));
}
bool ProjectItemModel::requestBinClipDeletion(const std::shared_ptr &clip, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(clip);
if (!clip) return false;
int parentId = -1;
QString binId;
if (auto ptr = clip->parent()) {
parentId = ptr->getId();
binId = ptr->clipId();
}
bool isSubClip = clip->itemType() == AbstractProjectItem::SubClipItem;
clip->selfSoftDelete(undo, redo);
int id = clip->getId();
Fun operation = removeItem_lambda(id);
Fun reverse = addItem_lambda(clip, parentId);
bool res = operation();
if (res) {
if (isSubClip) {
Fun update_doc = [this, binId]() {
std::shared_ptr parentItem = getItemByBinId(binId);
if (parentItem && parentItem->itemType() == AbstractProjectItem::ClipItem) {
auto clipItem = std::static_pointer_cast(parentItem);
clipItem->updateZones();
}
return true;
};
update_doc();
PUSH_LAMBDA(update_doc, operation);
PUSH_LAMBDA(update_doc, reverse);
}
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return res;
}
void ProjectItemModel::registerItem(const std::shared_ptr &item)
{
QWriteLocker locker(&m_lock);
auto clip = std::static_pointer_cast(item);
m_binPlaylist->manageBinItemInsertion(clip);
AbstractTreeModel::registerItem(item);
if (clip->itemType() == AbstractProjectItem::ClipItem) {
auto clipItem = std::static_pointer_cast(clip);
updateWatcher(clipItem);
}
}
void ProjectItemModel::deregisterItem(int id, TreeItem *item)
{
QWriteLocker locker(&m_lock);
auto clip = static_cast(item);
m_binPlaylist->manageBinItemDeletion(clip);
// TODO : here, we should suspend jobs belonging to the item we delete. They can be restarted if the item is reinserted by undo
AbstractTreeModel::deregisterItem(id, item);
if (clip->itemType() == AbstractProjectItem::ClipItem) {
auto clipItem = static_cast(clip);
m_fileWatcher->removeFile(clipItem->clipId());
}
}
int ProjectItemModel::getFreeFolderId()
{
while (!isIdFree(QString::number(++m_nextId))) {
};
return m_nextId;
}
int ProjectItemModel::getFreeClipId()
{
while (!isIdFree(QString::number(++m_nextId))) {
};
return m_nextId;
}
bool ProjectItemModel::addItem(const std::shared_ptr &item, const QString &parentId, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
std::shared_ptr parentItem = getItemByBinId(parentId);
if (!parentItem) {
qCDebug(KDENLIVE_LOG) << " / / ERROR IN PARENT FOLDER";
return false;
}
if (item->itemType() == AbstractProjectItem::ClipItem && parentItem->itemType() != AbstractProjectItem::FolderItem) {
qCDebug(KDENLIVE_LOG) << " / / ERROR when inserting clip: a clip should be inserted in a folder";
return false;
}
if (item->itemType() == AbstractProjectItem::SubClipItem && parentItem->itemType() != AbstractProjectItem::ClipItem) {
qCDebug(KDENLIVE_LOG) << " / / ERROR when inserting subclip: a subclip should be inserted in a clip";
return false;
}
Fun operation = addItem_lambda(item, parentItem->getId());
int itemId = item->getId();
Fun reverse = removeItem_lambda(itemId);
bool res = operation();
Q_ASSERT(item->isInModel());
if (res) {
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return res;
}
bool ProjectItemModel::requestAddFolder(QString &id, const QString &name, const QString &parentId, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
if (!id.isEmpty() && !isIdFree(id)) {
id = QString();
}
if (id.isEmpty()) {
id = QString::number(getFreeFolderId());
}
std::shared_ptr new_folder = ProjectFolder::construct(id, name, std::static_pointer_cast(shared_from_this()));
return addItem(new_folder, parentId, undo, redo);
}
bool ProjectItemModel::requestAddBinClip(QString &id, const QDomElement &description, const QString &parentId, Fun &undo, Fun &redo,
const std::function &readyCallBack)
{
qDebug() << "/////////// requestAddBinClip" << parentId;
QWriteLocker locker(&m_lock);
if (id.isEmpty()) {
id =
Xml::getTagContentByAttribute(description, QStringLiteral("property"), QStringLiteral("name"), QStringLiteral("kdenlive:id"), QStringLiteral("-1"));
if (id == QStringLiteral("-1") || !isIdFree(id)) {
id = QString::number(getFreeClipId());
}
}
Q_ASSERT(!id.isEmpty() && isIdFree(id));
qDebug() << "/////////// found id" << id;
std::shared_ptr new_clip =
ProjectClip::construct(id, description, m_blankThumb, std::static_pointer_cast(shared_from_this()));
qDebug() << "/////////// constructed ";
bool res = addItem(new_clip, parentId, undo, redo);
qDebug() << "/////////// added " << res;
if (res) {
int loadJob = pCore->jobManager()->startJob({id}, -1, QString(), description, std::bind(readyCallBack, id));
int thumbJob = pCore->jobManager()->startJob({id}, loadJob, QString(), 150, 0, true);
pCore->jobManager()->startJob({id}, thumbJob, QString(), 150);
pCore->jobManager()->startJob({id}, loadJob, QString());
}
return res;
}
bool ProjectItemModel::requestAddBinClip(QString &id, const QDomElement &description, const QString &parentId, const QString &undoText)
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = requestAddBinClip(id, description, parentId, undo, redo);
if (res) {
pCore->pushUndo(undo, redo, undoText.isEmpty() ? i18n("Add bin clip") : undoText);
}
return res;
}
bool ProjectItemModel::requestAddBinClip(QString &id, const std::shared_ptr &producer, const QString &parentId, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
if (id.isEmpty()) {
id = QString::number(producer->get_int("kdenlive:id"));
if (!isIdFree(id)) {
id = QString::number(getFreeClipId());
}
}
Q_ASSERT(!id.isEmpty() && isIdFree(id));
std::shared_ptr new_clip = ProjectClip::construct(id, m_blankThumb, std::static_pointer_cast(shared_from_this()), producer);
bool res = addItem(new_clip, parentId, undo, redo);
if (res) {
new_clip->importEffects(producer);
if (new_clip->sourceExists()) {
int blocking = pCore->jobManager()->getBlockingJobId(id, AbstractClipJob::LOADJOB);
pCore->jobManager()->startJob({id}, blocking, QString(), 150, -1, true);
pCore->jobManager()->startJob({id}, blocking, QString());
}
}
return res;
}
bool ProjectItemModel::requestAddBinSubClip(QString &id, int in, int out, const QString &zoneName, const QString &parentId, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
if (id.isEmpty()) {
id = QString::number(getFreeClipId());
}
Q_ASSERT(!id.isEmpty() && isIdFree(id));
QString subId = parentId;
if (subId.startsWith(QLatin1Char('A')) || subId.startsWith(QLatin1Char('V'))) {
subId.remove(0, 1);
}
auto clip = getClipByBinID(subId);
Q_ASSERT(clip->itemType() == AbstractProjectItem::ClipItem);
auto tc = pCore->currentDoc()->timecode().getDisplayTimecodeFromFrames(in, KdenliveSettings::frametimecode());
std::shared_ptr new_clip =
ProjectSubClip::construct(id, clip, std::static_pointer_cast(shared_from_this()), in, out, tc, zoneName);
bool res = addItem(new_clip, subId, undo, redo);
if (res) {
int parentJob = pCore->jobManager()->getBlockingJobId(subId, AbstractClipJob::LOADJOB);
pCore->jobManager()->startJob({id}, parentJob, QString(), 150, -1, true);
}
return res;
}
bool ProjectItemModel::requestAddBinSubClip(QString &id, int in, int out, const QString &zoneName, const QString &parentId)
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = requestAddBinSubClip(id, in, out, zoneName, parentId, undo, redo);
if (res) {
Fun update_doc = [this, parentId]() {
std::shared_ptr parentItem = getItemByBinId(parentId);
if (parentItem && parentItem->itemType() == AbstractProjectItem::ClipItem) {
auto clipItem = std::static_pointer_cast(parentItem);
clipItem->updateZones();
}
return true;
};
update_doc();
PUSH_LAMBDA(update_doc, undo);
PUSH_LAMBDA(update_doc, redo);
pCore->pushUndo(undo, redo, i18n("Add a sub clip"));
}
return res;
}
Fun ProjectItemModel::requestRenameFolder_lambda(const std::shared_ptr &folder, const QString &newName)
{
int id = folder->getId();
return [this, id, newName]() {
auto currentFolder = std::static_pointer_cast(m_allItems[id].lock());
if (!currentFolder) {
return false;
}
currentFolder->setName(newName);
m_binPlaylist->manageBinFolderRename(currentFolder);
auto index = getIndexFromItem(currentFolder);
emit dataChanged(index, index, {AbstractProjectItem::DataName});
return true;
};
}
bool ProjectItemModel::requestRenameFolder(const std::shared_ptr &folder, const QString &name, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
QString oldName = folder->name();
auto operation = requestRenameFolder_lambda(folder, name);
if (operation()) {
auto reverse = requestRenameFolder_lambda(folder, oldName);
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
return true;
}
return false;
}
bool ProjectItemModel::requestRenameFolder(std::shared_ptr folder, const QString &name)
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = requestRenameFolder(std::move(folder), name, undo, redo);
if (res) {
pCore->pushUndo(undo, redo, i18n("Rename Folder"));
}
return res;
}
bool ProjectItemModel::requestCleanup()
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = true;
std::vector> to_delete;
// Iterate to find clips that are not in timeline
for (const auto &clip : m_allItems) {
auto c = std::static_pointer_cast(clip.second.lock());
if (c->itemType() == AbstractProjectItem::ClipItem && !c->isIncludedInTimeline()) {
to_delete.push_back(c);
}
}
// it is important to execute deletion in a separate loop, because otherwise
// the iterators of m_allItems get messed up
for (const auto &c : to_delete) {
res = requestBinClipDeletion(c, undo, redo);
if (!res) {
bool undone = undo();
Q_ASSERT(undone);
return false;
}
}
pCore->pushUndo(undo, redo, i18n("Clean Project"));
return true;
}
std::vector ProjectItemModel::getAllClipIds() const
{
READ_LOCK();
std::vector result;
for (const auto &clip : m_allItems) {
auto c = std::static_pointer_cast(clip.second.lock());
if (c->itemType() == AbstractProjectItem::ClipItem) {
result.push_back(c->clipId());
}
}
return result;
}
QStringList ProjectItemModel::getClipByUrl(const QFileInfo &url) const
{
READ_LOCK();
QStringList result;
for (const auto &clip : m_allItems) {
auto c = std::static_pointer_cast(clip.second.lock());
if (c->itemType() == AbstractProjectItem::ClipItem) {
if (QFileInfo(std::static_pointer_cast(c)->clipUrl()) == url) {
result << c->clipId();
}
}
}
return result;
}
bool ProjectItemModel::loadFolders(Mlt::Properties &folders)
{
QWriteLocker locker(&m_lock);
// At this point, we expect the folders properties to have a name of the form "x.y" where x is the id of the parent folder and y the id of the child.
// Note that for root folder, x = -1
// The value of the property is the name of the child folder
std::unordered_map> downLinks; // key are parents, value are children
std::unordered_map upLinks; // key are children, value are parent
std::unordered_map newIds; // we store the correspondence to the new ids
std::unordered_map folderNames;
newIds[-1] = getRootFolder()->clipId();
if (folders.count() == 0) return true;
for (int i = 0; i < folders.count(); i++) {
QString folderName = folders.get(i);
QString id = folders.get_name(i);
int parentId = id.section(QLatin1Char('.'), 0, 0).toInt();
int folderId = id.section(QLatin1Char('.'), 1, 1).toInt();
downLinks[parentId].push_back(folderId);
upLinks[folderId] = parentId;
folderNames[folderId] = folderName;
qDebug() << "Found folder " << folderId << "name = " << folderName << "parent=" << parentId;
}
- // In case there are some non-existant parent, we fall back to root
+ // In case there are some non-existent parent, we fall back to root
for (const auto &f : downLinks) {
if (upLinks.count(f.first) == 0) {
upLinks[f.first] = -1;
}
if (f.first != -1 && downLinks.count(upLinks[f.first]) == 0) {
qDebug() << "Warning: parent folder " << upLinks[f.first] << "for folder" << f.first << "is invalid. Folder will be placed in topmost directory.";
upLinks[f.first] = -1;
}
}
// We now do a BFS to construct the folders in order
Q_ASSERT(downLinks.count(-1) > 0);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
std::queue queue;
std::unordered_set seen;
queue.push(-1);
while (!queue.empty()) {
int current = queue.front();
seen.insert(current);
queue.pop();
if (current != -1) {
QString id = QString::number(current);
bool res = requestAddFolder(id, folderNames[current], newIds[upLinks[current]], undo, redo);
if (!res) {
bool undone = undo();
Q_ASSERT(undone);
return false;
}
newIds[current] = id;
}
for (int c : downLinks[current]) {
queue.push(c);
}
}
return true;
}
bool ProjectItemModel::isIdFree(const QString &id) const
{
READ_LOCK();
for (const auto &clip : m_allItems) {
auto c = std::static_pointer_cast(clip.second.lock());
if (c->clipId() == id) {
return false;
}
}
return true;
}
void ProjectItemModel::loadBinPlaylist(Mlt::Tractor *documentTractor, Mlt::Tractor *modelTractor, std::unordered_map &binIdCorresp, QProgressDialog *progressDialog)
{
QWriteLocker locker(&m_lock);
clean();
Mlt::Properties retainList((mlt_properties)documentTractor->get_data("xml_retain"));
qDebug() << "Loading bin playlist...";
if (retainList.is_valid()) {
qDebug() << "retain is valid";
Mlt::Playlist playlist((mlt_playlist)retainList.get_data(BinPlaylist::binPlaylistId.toUtf8().constData()));
if (playlist.is_valid() && playlist.type() == playlist_type) {
qDebug() << "playlist is valid";
// Load bin clips
qDebug() << "init bin";
// Load folders
Mlt::Properties folderProperties;
Mlt::Properties playlistProps(playlist.get_properties());
folderProperties.pass_values(playlistProps, "kdenlive:folder.");
loadFolders(folderProperties);
// Read notes
QString notes = playlistProps.get("kdenlive:documentnotes");
pCore->projectManager()->setDocumentNotes(notes);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
qDebug() << "Found " << playlist.count() << "clips";
int max = playlist.count();
if (progressDialog) {
progressDialog->setMaximum(progressDialog->maximum() + max);
}
for (int i = 0; i < max; i++) {
if (progressDialog) {
progressDialog->setValue(i);
}
QScopedPointer prod(playlist.get_clip(i));
std::shared_ptr producer(new Mlt::Producer(prod->parent()));
qDebug() << "dealing with bin clip" << i;
if (producer->is_blank() || !producer->is_valid()) {
qDebug() << "producer is not valid or blank";
continue;
}
QString id = qstrdup(producer->get("kdenlive:id"));
QString parentId = qstrdup(producer->get("kdenlive:folderid"));
if (parentId.isEmpty()) {
parentId = QStringLiteral("-1");
}
qDebug() << "clip id" << id;
QString newId = isIdFree(id) ? id : QString::number(getFreeClipId());
producer->set("_kdenlive_processed", 1);
requestAddBinClip(newId, producer, parentId, undo, redo);
binIdCorresp[id] = newId;
qDebug() << "Loaded clip " << id << "under id" << newId;
}
}
}
m_binPlaylist->setRetainIn(modelTractor);
}
/** @brief Save document properties in MLT's bin playlist */
void ProjectItemModel::saveDocumentProperties(const QMap &props, const QMap &metadata,
std::shared_ptr guideModel)
{
m_binPlaylist->saveDocumentProperties(props, metadata, std::move(guideModel));
}
void ProjectItemModel::saveProperty(const QString &name, const QString &value)
{
m_binPlaylist->saveProperty(name, value);
}
QMap ProjectItemModel::getProxies(const QString &root)
{
READ_LOCK();
return m_binPlaylist->getProxies(root);
}
void ProjectItemModel::reloadClip(const QString &binId)
{
QWriteLocker locker(&m_lock);
std::shared_ptr clip = getClipByBinID(binId);
if (clip) {
clip->reloadProducer();
}
}
void ProjectItemModel::setClipWaiting(const QString &binId)
{
QWriteLocker locker(&m_lock);
std::shared_ptr clip = getClipByBinID(binId);
if (clip) {
clip->setClipStatus(AbstractProjectItem::StatusWaiting);
}
}
void ProjectItemModel::setClipInvalid(const QString &binId)
{
QWriteLocker locker(&m_lock);
std::shared_ptr clip = getClipByBinID(binId);
if (clip) {
clip->setClipStatus(AbstractProjectItem::StatusMissing);
// TODO: set producer as blank invalid
}
}
void ProjectItemModel::updateWatcher(const std::shared_ptr &clipItem)
{
QWriteLocker locker(&m_lock);
if (clipItem->clipType() == ClipType::AV || clipItem->clipType() == ClipType::Audio || clipItem->clipType() == ClipType::Image ||
clipItem->clipType() == ClipType::Video || clipItem->clipType() == ClipType::Playlist || clipItem->clipType() == ClipType::TextTemplate) {
m_fileWatcher->removeFile(clipItem->clipId());
m_fileWatcher->addFile(clipItem->clipId(), clipItem->clipUrl());
}
}
void ProjectItemModel::setDragType(PlaylistState::ClipState type)
{
QWriteLocker locker(&m_lock);
m_dragType = type;
}
int ProjectItemModel::clipsCount() const
{
READ_LOCK();
return m_binPlaylist->count();
}
diff --git a/src/dialogs/renderwidget.cpp b/src/dialogs/renderwidget.cpp
index 9f898e853..704f6be32 100644
--- a/src/dialogs/renderwidget.cpp
+++ b/src/dialogs/renderwidget.cpp
@@ -1,3121 +1,3121 @@
/***************************************************************************
* 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 "bin/bin.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 "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.running_jobs->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_view.running_jobs, &QTreeWidget::customContextMenuRequested, this, &RenderWidget::prepareMenu);
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); });
if (KdenliveSettings::gpu_accel()) {
// Disable parallel rendering for movit
m_view.parallel_process->setEnabled(false);
}
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(std::weak_ptr guidesModel)
{
m_guidesModel = std::move(guidesModel);
reloadGuides();
if (auto ptr = m_guidesModel.lock()) {
connect(ptr.get(), &MarkerListModel::modelChanged, this, &RenderWidget::reloadGuides);
}
}
void RenderWidget::reloadGuides()
{
double projectDuration = GenTime(pCore->projectDuration() - TimelineModel::seekDuration - 2, pCore->getCurrentFps()).ms() / 1000;
QVariant startData = m_view.guide_start->currentData();
QVariant endData = m_view.guide_end->currentData();
m_view.guide_start->clear();
m_view.guide_end->clear();
if (auto ptr = m_guidesModel.lock()) {
QList markers = ptr->getAllMarkers();
double fps = pCore->getCurrentProfile()->fps();
m_view.render_guide->setEnabled(!markers.isEmpty());
if (!markers.isEmpty()) {
m_view.guide_start->addItem(i18n("Beginning"), "0");
m_view.create_chapter->setEnabled(true);
for (auto marker : markers) {
GenTime pos = marker.time();
const QString guidePos = Timecode::getStringTimecode(pos.frames(fps), fps);
m_view.guide_start->addItem(marker.comment() + QLatin1Char('/') + guidePos, pos.seconds());
m_view.guide_end->addItem(marker.comment() + QLatin1Char('/') + guidePos, pos.seconds());
}
m_view.guide_end->addItem(i18n("End"), QString::number(projectDuration));
if (!startData.isNull()) {
int ix = qMax(0, m_view.guide_start->findData(startData));
m_view.guide_start->setCurrentIndex(ix);
}
if (!endData.isNull()) {
int ix = qMax(m_view.guide_start->currentIndex() + 1, m_view.guide_end->findData(endData));
m_view.guide_end->setCurrentIndex(ix);
}
}
} else {
m_view.render_guide->setEnabled(false);
m_view.create_chapter->setEnabled(false);
}
}
/**
* 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 (pCore->projectDuration() < 2) {
- // Empty project, dont attempt to render
+ // Empty project, don't attempt to render
return;
}
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();
// 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"), m_view.out_file->url().toLocalFile());
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);
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");
} else {
renderName = renderName.section(QLatin1Char('.'), 0, -2);
renderName.append(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(), i.value());
++i;
}
}
// Adjust encoding speed
if (m_view.speed->isEnabled() && m_view.formats->currentItem()) {
QStringList speeds = m_view.formats->currentItem()->data(0, SpeedsRole).toStringList();
if (m_view.speed->value() < speeds.count()) {
QString speedValue = speeds.at(m_view.speed->value());
if (speedValue.contains(QLatin1Char('='))) {
consumer.setAttribute(speedValue.section(QLatin1Char('='), 0, 0), speedValue.section(QLatin1Char('='), 1));
}
}
}
// 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();
}
if (renderArgs.contains(QLatin1String("pix_fmt=argb"))
|| renderArgs.contains(QLatin1String("pix_fmt=abgr"))
|| renderArgs.contains(QLatin1String("pix_fmt=bgra"))
|| renderArgs.contains(QLatin1String("pix_fmt=gbra"))
|| renderArgs.contains(QLatin1String("pix_fmt=rgba"))
|| renderArgs.contains(QLatin1String("pix_fmt=yuva"))
|| renderArgs.contains(QLatin1String("pix_fmt=ya" ))
|| renderArgs.contains(QLatin1String("pix_fmt=ayuv"))) {
auto prods = doc.elementsByTagName(QStringLiteral("producer"));
for (int i = 0; i < prods.count(); ++i) {
auto prod = prods.at(i).toElement();
if (prod.attribute(QStringLiteral("id")) == QStringLiteral("black_track")) {
auto props = prod.elementsByTagName(QStringLiteral("property"));
for (int j = 0; j < props.count(); ++j) {
auto prop = props.at(i).toElement();
if (prop.attribute(QStringLiteral("name")) == QStringLiteral("resource")) {
prop.firstChild().setNodeValue(QStringLiteral("transparent"));
break;
}
}
break;
}
}
}
// disable audio if requested
if (!exportAudio) {
consumer.setAttribute(QStringLiteral("an"), 1);
}
int threadCount = QThread::idealThreadCount();
if (threadCount < 2 || !m_view.parallel_process->isChecked() || !m_view.parallel_process->isEnabled()) {
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::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("internet-services")));
}
}
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