diff --git a/fuzzer/fuzzing.cpp b/fuzzer/fuzzing.cpp
index 72eb01d63..e9b48ea68 100644
--- a/fuzzer/fuzzing.cpp
+++ b/fuzzer/fuzzing.cpp
@@ -1,458 +1,493 @@
/***************************************************************************
* 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 (Logger::back_translation_table.count(c) > 0) {
- // std::cout << "found=" << c;
+ std::cout << "found=" << c;
c = Logger::back_translation_table[c];
// std::cout << " tranlated=" << 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;
+ state = static_cast(state_id);
+ if (timeline) {
+ ClipModel::construct(timeline, QString::fromStdString(binId), -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;
+ std::cout << "executing " << c << std::endl;
rttr::type target_type = rttr::type::get();
bool found = false;
- for (const std::string &t : {"TimelineModel"}) {
+ 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;
+ 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;
+ 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;
+ 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;
+ 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!!!" << std::endl;
+ std::cout << "VALID!!! " << target_method.get_name().to_string() << std::endl;
std::vector args;
args.reserve(arguments.size());
- for (const auto &a : arguments) {
+ for (auto &a : arguments) {
args.emplace_back(a);
- // std::cout<<"argument="<checkConsistency());
}
}
}
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/src/definitions.cpp b/src/definitions.cpp
index f46f52edb..b970b2249 100644
--- a/src/definitions.cpp
+++ b/src/definitions.cpp
@@ -1,200 +1,205 @@
/***************************************************************************
* Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "definitions.h"
#include
#include
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wfloat-equal"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wpedantic"
#include
#pragma GCC diagnostic pop
RTTR_REGISTRATION
{
using namespace rttr;
// clang-format off
registration::enumeration("GroupType")(
value("Normal", GroupType::Normal),
value("Selection", GroupType::Selection),
value("AVSplit", GroupType::AVSplit),
value("Leaf", GroupType::Leaf)
);
+ registration::enumeration("PlaylistState")(
+ value("VideoOnly", PlaylistState::VideoOnly),
+ value("AudioOnly", PlaylistState::AudioOnly),
+ value("Disabled", PlaylistState::Disabled)
+ );
// clang-format on
}
QDebug operator<<(QDebug qd, const ItemInfo &info)
{
qd << "ItemInfo " << &info;
qd << "\tTrack" << info.track;
qd << "\tStart pos: " << info.startPos.toString();
qd << "\tEnd pos: " << info.endPos.toString();
qd << "\tCrop start: " << info.cropStart.toString();
qd << "\tCrop duration: " << info.cropDuration.toString();
return qd.maybeSpace();
}
CommentedTime::CommentedTime()
: m_time(GenTime(0))
{
}
CommentedTime::CommentedTime(const GenTime &time, QString comment, int markerType)
: m_time(time)
, m_comment(std::move(comment))
, m_type(markerType)
{
}
CommentedTime::CommentedTime(const QString &hash, const GenTime &time)
: m_time(time)
, m_comment(hash.section(QLatin1Char(':'), 1))
, m_type(hash.section(QLatin1Char(':'), 0, 0).toInt())
{
}
QString CommentedTime::comment() const
{
return (m_comment.isEmpty() ? i18n("Marker") : m_comment);
}
GenTime CommentedTime::time() const
{
return m_time;
}
void CommentedTime::setComment(const QString &comm)
{
m_comment = comm;
}
void CommentedTime::setMarkerType(int newtype)
{
m_type = newtype;
}
QString CommentedTime::hash() const
{
return QString::number(m_type) + QLatin1Char(':') + (m_comment.isEmpty() ? i18n("Marker") : m_comment);
}
int CommentedTime::markerType() const
{
return m_type;
}
QColor CommentedTime::markerColor(int type)
{
switch (type) {
case 0:
return Qt::red;
break;
case 1:
return Qt::blue;
break;
case 2:
return Qt::green;
break;
case 3:
return Qt::yellow;
break;
default:
return Qt::cyan;
break;
}
}
bool CommentedTime::operator>(const CommentedTime &op) const
{
return m_time > op.time();
}
bool CommentedTime::operator<(const CommentedTime &op) const
{
return m_time < op.time();
}
bool CommentedTime::operator>=(const CommentedTime &op) const
{
return m_time >= op.time();
}
bool CommentedTime::operator<=(const CommentedTime &op) const
{
return m_time <= op.time();
}
bool CommentedTime::operator==(const CommentedTime &op) const
{
return m_time == op.time();
}
bool CommentedTime::operator!=(const CommentedTime &op) const
{
return m_time != op.time();
}
const QString groupTypeToStr(GroupType t)
{
switch (t) {
case GroupType::Normal:
return QStringLiteral("Normal");
case GroupType::Selection:
return QStringLiteral("Selection");
case GroupType::AVSplit:
return QStringLiteral("AVSplit");
case GroupType::Leaf:
return QStringLiteral("Leaf");
}
Q_ASSERT(false);
return QString();
}
GroupType groupTypeFromStr(const QString &s)
{
std::vector types{GroupType::Selection, GroupType::Normal, GroupType::AVSplit, GroupType::Leaf};
for (const auto &t : types) {
if (s == groupTypeToStr(t)) {
return t;
}
}
Q_ASSERT(false);
return GroupType::Normal;
}
std::pair stateToBool(PlaylistState::ClipState state)
{
return {state == PlaylistState::VideoOnly, state == PlaylistState::AudioOnly};
}
PlaylistState::ClipState stateFromBool(std::pair av)
{
assert(!av.first || !av.second);
if (av.first) {
return PlaylistState::VideoOnly;
} else if (av.second) {
return PlaylistState::AudioOnly;
} else {
return PlaylistState::Disabled;
}
}
diff --git a/src/logger.cpp b/src/logger.cpp
index 8e6ece04d..f34ef9c3a 100644
--- a/src/logger.cpp
+++ b/src/logger.cpp
@@ -1,387 +1,422 @@
/***************************************************************************
* 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 "logger.hpp"
#include "bin/projectitemmodel.h"
#include "timeline2/model/timelinefunctions.hpp"
+#include "timeline2/model/timelineitemmodel.hpp"
#include "timeline2/model/timelinemodel.hpp"
#include
#include
#include
#include
#include
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wfloat-equal"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wpedantic"
#include
#pragma GCC diagnostic pop
thread_local bool Logger::is_executing = false;
std::mutex Logger::mut;
std::vector Logger::operations;
std::vector Logger::invoks;
std::unordered_map> Logger::constr;
std::unordered_map Logger::translation_table;
std::unordered_map Logger::back_translation_table;
int Logger::dump_count = 0;
thread_local size_t Logger::result_awaiting = INT_MAX;
void Logger::init()
{
std::string cur_ind = "a";
auto incr_ind = [&](auto &&self, size_t i = 0) {
if (i >= cur_ind.size()) {
cur_ind += "a";
return;
}
if (cur_ind[i] == 'z') {
cur_ind[i] = 'A';
} else if (cur_ind[i] == 'Z') {
cur_ind[i] = 'a';
self(self, i + 1);
} else {
cur_ind[i]++;
}
};
- for (const auto &o : {"TimelineModel", "TrackModel", "test_producer", "test_producer_sound"}) {
+ for (const auto &o : {"TimelineModel", "TrackModel", "test_producer", "test_producer_sound", "ClipModel"}) {
translation_table[std::string("constr_") + o] = cur_ind;
incr_ind(incr_ind);
}
for (const auto &m : rttr::type::get().get_methods()) {
translation_table[m.get_name().to_string()] = cur_ind;
incr_ind(incr_ind);
}
for (const auto &m : rttr::type::get().get_methods()) {
translation_table[m.get_name().to_string()] = cur_ind;
incr_ind(incr_ind);
}
for (const auto &i : translation_table) {
back_translation_table[i.second] = i.first;
}
}
bool Logger::start_logging()
{
std::unique_lock lk(mut);
if (is_executing) {
return false;
}
is_executing = true;
return true;
}
void Logger::stop_logging()
{
std::unique_lock lk(mut);
is_executing = false;
}
std::string Logger::get_ptr_name(const rttr::variant &ptr)
{
if (ptr.can_convert()) {
return "timeline_" + std::to_string(get_id_from_ptr(ptr.convert()));
} else if (ptr.can_convert()) {
return "binModel";
+ } else if (ptr.can_convert()) {
+ return "timeline_" + std::to_string(get_id_from_ptr(static_cast(ptr.convert())));
} else {
std::cout << "Error: unhandled ptr type " << ptr.get_type().get_name().to_string() << std::endl;
}
return "unknown";
}
void Logger::log_res(rttr::variant result)
{
std::unique_lock lk(mut);
Q_ASSERT(result_awaiting < invoks.size());
invoks[result_awaiting].res = std::move(result);
}
void Logger::log_create_producer(const std::string &type, std::vector args)
{
std::unique_lock lk(mut);
for (auto &a : args) {
// this will rewove shared/weak/unique ptrs
if (a.get_type().is_wrapper()) {
a = a.extract_wrapped_value();
}
const std::string class_name = a.get_type().get_name().to_string();
}
constr[type].push_back({type, std::move(args)});
operations.emplace_back(ConstrId{type, constr[type].size() - 1});
}
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 &");
}
std::string quoted(const std::string &input)
{
#if __cpp_lib_quoted_string_io
std::stringstream ss;
ss << std::quoted(input);
return ss.str();
#else
// very incomplete implem
return "\"" + input + "\"";
#endif
}
} // namespace
void Logger::print_trace()
{
dump_count++;
auto process_args = [&](const std::vector &args, const std::unordered_set &refs = {}) {
std::stringstream ss;
bool deb = true;
size_t i = 0;
for (const auto &a : args) {
if (deb) {
deb = false;
i = 0;
} else {
ss << ", ";
++i;
}
if (refs.count(i) > 0) {
ss << "dummy_" << i;
} else if (a.get_type() == rttr::type::get()) {
ss << a.convert();
+ } else if (a.get_type() == rttr::type::get()) {
+ ss << a.convert();
+ } else if (a.get_type() == rttr::type::get()) {
+ ss << a.convert();
+ } else if (a.get_type() == rttr::type::get()) {
+ ss << a.convert();
} else if (a.get_type() == rttr::type::get()) {
ss << (a.convert() ? "true" : "false");
} else if (a.get_type().is_enumeration()) {
auto e = a.get_type().get_enumeration();
ss << e.get_name().to_string() << "::" << a.convert();
} else if (a.can_convert()) {
ss << quoted(a.convert().toStdString());
} else if (a.can_convert()) {
ss << quoted(a.convert());
} else if (a.can_convert>()) {
auto set = a.convert>();
ss << "{";
bool beg = true;
for (int s : set) {
if (beg)
beg = false;
else
ss << ", ";
ss << s;
}
ss << "}";
} else if (a.get_type().is_pointer()) {
ss << get_ptr_name(a);
} else {
std::cout << "Error: unhandled arg type " << a.get_type().get_name().to_string() << std::endl;
}
}
return ss.str();
};
auto process_args_fuzz = [&](const std::vector &args, const std::unordered_set &refs = {}) {
std::stringstream ss;
bool deb = true;
size_t i = 0;
for (const auto &a : args) {
if (deb) {
deb = false;
i = 0;
} else {
ss << " ";
++i;
}
if (refs.count(i) > 0) {
continue;
} else if (a.get_type() == rttr::type::get()) {
ss << a.convert();
+ } else if (a.get_type() == rttr::type::get()) {
+ ss << a.convert();
+ } else if (a.get_type() == rttr::type::get()) {
+ ss << a.convert();
+ } else if (a.get_type() == rttr::type::get()) {
+ ss << a.convert();
} else if (a.get_type() == rttr::type::get()) {
ss << (a.convert() ? "1" : "0");
} else if (a.get_type().is_enumeration()) {
ss << a.convert();
} else if (a.can_convert()) {
std::string out = a.convert().toStdString();
if (out.empty()) {
out = "$$";
}
ss << out;
} else if (a.can_convert()) {
std::string out = a.convert();
if (out.empty()) {
out = "$$";
}
ss << out;
} else if (a.can_convert>()) {
auto set = a.convert>();
ss << set.size() << " ";
bool beg = true;
for (int s : set) {
if (beg)
beg = false;
else
ss << " ";
ss << s;
}
} else if (a.get_type().is_pointer()) {
if (a.can_convert()) {
ss << get_id_from_ptr(a.convert());
+ } else if (a.can_convert()) {
+ ss << get_id_from_ptr(static_cast(a.convert()));
} else if (a.can_convert()) {
// only one binModel, we skip the parameter since it's unambiguous
} else {
std::cout << "Error: unhandled ptr type " << a.get_type().get_name().to_string() << std::endl;
}
} else {
std::cout << "Error: unhandled arg type " << a.get_type().get_name().to_string() << std::endl;
}
}
return ss.str();
};
std::ofstream fuzz_file;
fuzz_file.open("fuzz_case_" + std::to_string(dump_count) + ".txt");
std::ofstream test_file;
test_file.open("test_case_" + std::to_string(dump_count) + ".cpp");
test_file << "TEST_CASE(\"Regression\") {" << std::endl;
test_file << "auto binModel = pCore->projectItemModel();" << std::endl;
test_file << "binModel->clean();" << std::endl;
test_file << "std::shared_ptr undoStack = std::make_shared(nullptr);" << std::endl;
test_file << "std::shared_ptr guideModel = std::make_shared(undoStack);" << std::endl;
test_file << "TimelineModel::next_id = 0;" << std::endl;
test_file << "{" << std::endl;
test_file << "Mock pmMock;" << std::endl;
test_file << "When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);" << std::endl;
test_file << "ProjectManager &mocked = pmMock.get();" << std::endl;
test_file << "pCore->m_projectManager = &mocked;" << std::endl;
size_t nbrConstructedTimelines = 0;
auto check_consistancy = [&]() {
for (size_t i = 0; i < nbrConstructedTimelines; ++i) {
test_file << "REQUIRE(timeline_" << i << "->checkConsistency());" << std::endl;
}
};
for (const auto &o : operations) {
if (o.can_convert()) {
InvokId id = o.convert();
Invok &invok = invoks[id.id];
std::unordered_set refs;
+ bool is_static = false;
rttr::method m = invok.ptr.get_type().get_method(invok.method);
+ if (!m.is_valid()) {
+ is_static = true;
+ m = rttr::type::get_by_name("TimelineFunctions").get_method(invok.method);
+ }
+ if (!m.is_valid()) {
+ std::cout << "ERROR: unknown method " << invok.method << std::endl;
+ continue;
+ }
test_file << "{" << std::endl;
for (const auto &a : m.get_parameter_infos()) {
if (isIthParamARef(m, a.get_index())) {
refs.insert(a.get_index());
test_file << a.get_type().get_name().to_string() << " dummy_" << std::to_string(a.get_index()) << ";" << std::endl;
}
}
if (m.get_return_type() != rttr::type::get()) {
test_file << m.get_return_type().get_name().to_string() << " res = ";
}
- test_file << get_ptr_name(invok.ptr) << "->" << invok.method << "(" << process_args(invok.args, refs) << ");" << std::endl;
+ if (is_static) {
+ test_file << "TimelineFunctions::" << invok.method << "(" << get_ptr_name(invok.ptr) << ", " << process_args(invok.args, refs) << ");"
+ << std::endl;
+ } else {
+ test_file << get_ptr_name(invok.ptr) << "->" << invok.method << "(" << process_args(invok.args, refs) << ");" << std::endl;
+ }
if (m.get_return_type() != rttr::type::get() && invok.res.is_valid()) {
test_file << "REQUIRE( res == " << invok.res.to_string() << ");" << std::endl;
}
test_file << "}" << std::endl;
std::string invok_name = invok.method;
if (translation_table.count(invok_name) > 0) {
auto args = invok.args;
- if (rttr::type::get().get_method(invok_name).is_valid()) {
+ if (rttr::type::get().get_method(invok_name).is_valid() ||
+ rttr::type::get().get_method(invok_name).is_valid()) {
args.insert(args.begin(), invok.ptr);
// adding an arg just messed up the references
std::unordered_set new_refs;
for (const size_t &r : refs) {
new_refs.insert(r + 1);
}
std::swap(refs, new_refs);
}
fuzz_file << translation_table[invok_name] << " " << process_args_fuzz(args, refs) << std::endl;
} else {
std::cout << "ERROR: unknown method " << invok_name << std::endl;
}
} else if (o.can_convert()) {
ConstrId id = o.convert();
std::string constr_name = std::string("constr_") + id.type;
if (translation_table.count(constr_name) > 0) {
fuzz_file << translation_table[constr_name] << " " << process_args_fuzz(constr[id.type][id.id].second) << std::endl;
} else {
std::cout << "ERROR: unknown constructor " << constr_name << std::endl;
}
if (id.type == "TimelineModel") {
test_file << "TimelineItemModel tim_" << id.id << "(®_profile, undoStack);" << std::endl;
test_file << "Mock timMock_" << id.id << "(tim_" << id.id << ");" << std::endl;
test_file << "auto timeline_" << id.id << " = std::shared_ptr(&timMock_" << id.id << ".get(), [](...) {});" << std::endl;
test_file << "TimelineItemModel::finishConstruct(timeline_" << id.id << ", guideModel);" << std::endl;
test_file << "Fake(Method(timMock_" << id.id << ", adjustAssetRange));" << std::endl;
nbrConstructedTimelines++;
} else if (id.type == "TrackModel") {
std::string params = process_args(constr[id.type][id.id].second);
test_file << "TrackModel::construct(" << params << ");" << std::endl;
+ } else if (id.type == "ClipModel") {
+ std::string params = process_args(constr[id.type][id.id].second);
+ test_file << "ClipModel::construct(" << params << ");" << std::endl;
} else if (id.type == "test_producer") {
std::string params = process_args(constr[id.type][id.id].second);
test_file << "createProducer(reg_profile, " << params << ");" << std::endl;
} else if (id.type == "test_producer_sound") {
std::string params = process_args(constr[id.type][id.id].second);
test_file << "createProducerWithSound(reg_profile, " << params << ");" << std::endl;
} else {
std::cout << "Error: unknown constructor " << id.type << std::endl;
}
} else {
std::cout << "Error: unknown operation" << std::endl;
}
check_consistancy();
test_file << "undoStack->undo();" << std::endl;
check_consistancy();
test_file << "undoStack->redo();" << std::endl;
check_consistancy();
}
test_file << "}" << std::endl;
test_file << "pCore->m_projectManager = nullptr;" << std::endl;
test_file << "}" << std::endl;
}
void Logger::clear()
{
is_executing = false;
invoks.clear();
operations.clear();
constr.clear();
}
LogGuard::LogGuard()
{
m_hasGuard = Logger::start_logging();
}
LogGuard::~LogGuard()
{
if (m_hasGuard) {
Logger::stop_logging();
}
}
bool LogGuard::hasGuard() const
{
return m_hasGuard;
}
diff --git a/src/timeline2/model/clipmodel.cpp b/src/timeline2/model/clipmodel.cpp
index b175a02b4..fa85b10a7 100644
--- a/src/timeline2/model/clipmodel.cpp
+++ b/src/timeline2/model/clipmodel.cpp
@@ -1,696 +1,698 @@
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "clipmodel.hpp"
#include "bin/projectclip.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "effects/effectstack/model/effectstackmodel.hpp"
+#include "logger.hpp"
#include "macros.hpp"
#include "timelinemodel.hpp"
#include "trackmodel.hpp"
#include
#include
#include
#include
ClipModel::ClipModel(const std::shared_ptr &parent, std::shared_ptr prod, const QString &binClipId, int id,
PlaylistState::ClipState state, double speed)
: MoveableItem(parent, id)
, m_producer(std::move(prod))
, m_effectStack(EffectStackModel::construct(m_producer, {ObjectType::TimelineClip, m_id}, parent->m_undoStack))
, m_binClipId(binClipId)
, forceThumbReload(false)
, m_currentState(state)
, m_speed(speed)
, m_fakeTrack(-1)
{
m_producer->set("kdenlive:id", binClipId.toUtf8().constData());
m_producer->set("_kdenlive_cid", m_id);
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
m_canBeVideo = binClip->hasVideo();
m_canBeAudio = binClip->hasAudio();
m_clipType = binClip->clipType();
if (binClip) {
m_endlessResize = !binClip->hasLimitedDuration();
} else {
m_endlessResize = false;
}
QObject::connect(m_effectStack.get(), &EffectStackModel::dataChanged, [&](const QModelIndex &, const QModelIndex &, QVector roles) {
qDebug() << "// GOT CLIP STACK DATA CHANGE: " << roles;
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
qDebug() << "// GOT CLIP STACK DATA CHANGE DONE: " << ix << " = " << roles;
ptr->dataChanged(ix, ix, roles);
}
}
});
}
int ClipModel::construct(const std::shared_ptr &parent, const QString &binClipId, int id, PlaylistState::ClipState state, double speed)
{
id = (id == -1 ? TimelineModel::getNextId() : id);
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(binClipId);
// We refine the state according to what the clip can actually produce
std::pair videoAudio = stateToBool(state);
videoAudio.first = videoAudio.first && binClip->hasVideo();
videoAudio.second = videoAudio.second && binClip->hasAudio();
state = stateFromBool(videoAudio);
std::shared_ptr cutProducer = binClip->getTimelineProducer(-1, id, state, speed);
std::shared_ptr clip(new ClipModel(parent, cutProducer, binClipId, id, state, speed));
+ TRACE_CONSTR(clip.get(), parent, binClipId, id, state, speed);
clip->setClipState_lambda(state)();
parent->registerClip(clip);
return id;
}
int ClipModel::construct(const std::shared_ptr &parent, const QString &binClipId, const std::shared_ptr &producer,
PlaylistState::ClipState state)
{
// we hand the producer to the bin clip, and in return we get a cut to a good master producer
// We might not be able to use directly the producer that we receive as an argument, because it cannot share the same master producer with any other
// clipModel (due to a mlt limitation, see ProjectClip doc)
int id = TimelineModel::getNextId();
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(binClipId);
// We refine the state according to what the clip can actually produce
std::pair videoAudio = stateToBool(state);
videoAudio.first = videoAudio.first && binClip->hasVideo();
videoAudio.second = videoAudio.second && binClip->hasAudio();
state = stateFromBool(videoAudio);
double speed = 1.0;
if (QString::fromUtf8(producer->parent().get("mlt_service")) == QLatin1String("timewarp")) {
speed = producer->parent().get_double("warp_speed");
}
auto result = binClip->giveMasterAndGetTimelineProducer(id, producer, state);
std::shared_ptr clip(new ClipModel(parent, result.first, binClipId, id, state, speed));
clip->setClipState_lambda(state)();
clip->m_effectStack->importEffects(producer, state, result.second);
parent->registerClip(clip);
return id;
}
void ClipModel::registerClipToBin(std::shared_ptr service, bool registerProducer)
{
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
if (!binClip) {
qDebug() << "Error : Bin clip for id: " << m_binClipId << " NOT AVAILABLE!!!";
}
qDebug() << "REGISTRATION " << m_id << "ptr count" << m_parent.use_count();
binClip->registerService(m_parent, m_id, std::move(service), registerProducer);
}
void ClipModel::deregisterClipToBin()
{
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
binClip->deregisterTimelineClip(m_id);
}
ClipModel::~ClipModel() = default;
bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo)
{
QWriteLocker locker(&m_lock);
// qDebug() << "RESIZE CLIP" << m_id << "target size=" << size << "right=" << right << "endless=" << m_endlessResize << "length" <<
// m_producer->get_length();
if (!m_endlessResize && (size <= 0 || size > m_producer->get_length())) {
return false;
}
int delta = getPlaytime() - size;
if (delta == 0) {
return true;
}
int in = m_producer->get_in();
int out = m_producer->get_out();
int old_in = in, old_out = out;
// check if there is enough space on the chosen side
if (!right && in + delta < 0 && !m_endlessResize) {
return false;
}
if (!m_endlessResize && right && (out - delta >= m_producer->get_length())) {
return false;
}
if (right) {
out -= delta;
} else {
in += delta;
}
// qDebug() << "Resize facts delta =" << delta << "old in" << old_in << "old_out" << old_out << "in" << in << "out" << out;
std::function track_operation = []() { return true; };
std::function track_reverse = []() { return true; };
int outPoint = out;
int inPoint = in;
int offset = 0;
if (m_endlessResize) {
offset = inPoint;
outPoint = out - in;
inPoint = 0;
}
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
track_operation = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, inPoint, outPoint, right);
} else {
qDebug() << "Error : Moving clip failed because parent timeline is not available anymore";
Q_ASSERT(false);
}
} else {
// Ensure producer is long enough
if (m_endlessResize && outPoint > m_producer->parent().get_length()) {
m_producer->set("length", outPoint + 1);
}
}
Fun operation = [this, inPoint, outPoint, track_operation]() {
if (track_operation()) {
m_producer->set_in_and_out(inPoint, outPoint);
return true;
}
return false;
};
if (operation()) {
// Now, we are in the state in which the timeline should be when we try to revert current action. So we can build the reverse action from here
if (m_currentTrackId != -1) {
QVector roles{TimelineModel::DurationRole};
if (!right) {
roles.push_back(TimelineModel::StartRole);
roles.push_back(TimelineModel::InPointRole);
} else {
roles.push_back(TimelineModel::OutPointRole);
}
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
// TODO: integrate in undo
ptr->dataChanged(ix, ix, roles);
track_reverse = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, old_in, old_out, right);
}
}
Fun reverse = [this, old_in, old_out, track_reverse]() {
if (track_reverse()) {
m_producer->set_in_and_out(old_in, old_out);
return true;
}
return false;
};
qDebug() << "----------\n-----------\n// ADJUSTING EFFECT LENGTH, LOGUNDO " << logUndo << ", " << old_in << "/" << inPoint << ", "
<< m_producer->get_playtime();
if (logUndo) {
adjustEffectLength(right, old_in, inPoint, old_out - old_in, m_producer->get_playtime(), offset, reverse, operation, logUndo);
}
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
return true;
}
return false;
}
const QString ClipModel::getProperty(const QString &name) const
{
READ_LOCK();
if (service()->parent().is_valid()) {
return QString::fromUtf8(service()->parent().get(name.toUtf8().constData()));
}
return QString::fromUtf8(service()->get(name.toUtf8().constData()));
}
int ClipModel::getIntProperty(const QString &name) const
{
READ_LOCK();
if (service()->parent().is_valid()) {
return service()->parent().get_int(name.toUtf8().constData());
}
return service()->get_int(name.toUtf8().constData());
}
QSize ClipModel::getFrameSize() const
{
READ_LOCK();
if (service()->parent().is_valid()) {
return QSize(service()->parent().get_int("meta.media.width"), service()->parent().get_int("meta.media.height"));
}
return {service()->get_int("meta.media.width"), service()->get_int("meta.media.height")};
}
double ClipModel::getDoubleProperty(const QString &name) const
{
READ_LOCK();
if (service()->parent().is_valid()) {
return service()->parent().get_double(name.toUtf8().constData());
}
return service()->get_double(name.toUtf8().constData());
}
Mlt::Producer *ClipModel::service() const
{
READ_LOCK();
return m_producer.get();
}
std::shared_ptr ClipModel::getProducer()
{
READ_LOCK();
return m_producer;
}
int ClipModel::getPlaytime() const
{
READ_LOCK();
return m_producer->get_playtime();
}
void ClipModel::setTimelineEffectsEnabled(bool enabled)
{
QWriteLocker locker(&m_lock);
m_effectStack->setEffectStackEnabled(enabled);
}
bool ClipModel::addEffect(const QString &effectId)
{
QWriteLocker locker(&m_lock);
if (EffectsRepository::get()->getType(effectId) == EffectType::Audio) {
if (m_currentState == PlaylistState::VideoOnly) {
return false;
}
} else if (m_currentState == PlaylistState::AudioOnly) {
return false;
}
m_effectStack->appendEffect(effectId);
return true;
}
bool ClipModel::copyEffect(const std::shared_ptr &stackModel, int rowId)
{
QWriteLocker locker(&m_lock);
m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId), m_currentState);
return true;
}
bool ClipModel::importEffects(std::shared_ptr stackModel)
{
QWriteLocker locker(&m_lock);
m_effectStack->importEffects(std::move(stackModel), m_currentState);
return true;
}
bool ClipModel::importEffects(std::weak_ptr service)
{
QWriteLocker locker(&m_lock);
m_effectStack->importEffects(std::move(service), m_currentState);
return true;
}
bool ClipModel::removeFade(bool fromStart)
{
QWriteLocker locker(&m_lock);
m_effectStack->removeFade(fromStart);
return true;
}
bool ClipModel::adjustEffectLength(bool adjustFromEnd, int oldIn, int newIn, int oldDuration, int duration, int offset, Fun &undo, Fun &redo, bool logUndo)
{
QWriteLocker locker(&m_lock);
return m_effectStack->adjustStackLength(adjustFromEnd, oldIn, oldDuration, newIn, duration, offset, undo, redo, logUndo);
}
bool ClipModel::adjustEffectLength(const QString &effectName, int duration, int originalDuration, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
qDebug() << ".... ADJUSTING FADE LENGTH: " << duration << " / " << effectName;
Fun operation = [this, duration, effectName, originalDuration]() {
return m_effectStack->adjustFadeLength(duration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"), audioEnabled(),
!isAudioOnly(), originalDuration > 0);
};
if (operation() && originalDuration > 0) {
Fun reverse = [this, originalDuration, effectName]() {
return m_effectStack->adjustFadeLength(originalDuration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"),
audioEnabled(), !isAudioOnly(), true);
};
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return true;
}
bool ClipModel::audioEnabled() const
{
READ_LOCK();
return stateToBool(m_currentState).second;
}
bool ClipModel::isAudioOnly() const
{
READ_LOCK();
return m_currentState == PlaylistState::AudioOnly;
}
void ClipModel::refreshProducerFromBin(PlaylistState::ClipState state, double speed)
{
// We require that the producer is not in the track when we refresh the producer, because otherwise the modification will not be propagated. Remove the clip
// first, refresh, and then replant.
QWriteLocker locker(&m_lock);
int in = getIn();
int out = getOut();
qDebug() << "refresh " << speed << m_speed << in << out;
if (!qFuzzyCompare(speed, m_speed) && !qFuzzyCompare(speed, 0.)) {
in = in * std::abs(m_speed / speed);
out = in + getPlaytime() - 1;
// prevent going out of the clip's range
out = std::min(out, int(double(m_producer->get_length()) * std::abs(m_speed / speed)) - 1);
m_speed = speed;
qDebug() << "changing speed" << in << out << m_speed;
}
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
std::shared_ptr binProducer = binClip->getTimelineProducer(m_currentTrackId, m_id, state, m_speed);
m_producer = std::move(binProducer);
m_producer->set_in_and_out(in, out);
// replant effect stack in updated service
m_effectStack->resetService(m_producer);
m_producer->set("kdenlive:id", binClip->clipId().toUtf8().constData());
m_producer->set("_kdenlive_cid", m_id);
m_endlessResize = !binClip->hasLimitedDuration();
}
void ClipModel::refreshProducerFromBin()
{
refreshProducerFromBin(m_currentState);
}
bool ClipModel::useTimewarpProducer(double speed, Fun &undo, Fun &redo)
{
if (m_endlessResize) {
// no timewarp for endless producers
return false;
}
if (qFuzzyCompare(speed, m_speed)) {
// nothing to do
return true;
}
std::function local_undo = []() { return true; };
std::function local_redo = []() { return true; };
double previousSpeed = getSpeed();
int oldDuration = getPlaytime();
int newDuration = int(double(oldDuration) * std::abs(previousSpeed / speed));
int oldOut = getOut();
int oldIn = getIn();
auto operation = useTimewarpProducer_lambda(speed);
auto reverse = useTimewarpProducer_lambda(previousSpeed);
if (oldOut >= newDuration) {
// in that case, we are going to shrink the clip when changing the producer. We must undo that when reloading the old producer
reverse = [reverse, oldIn, oldOut, this]() {
bool res = reverse();
if (res) {
setInOut(oldIn, oldOut);
}
return res;
};
}
if (operation()) {
UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo);
bool res = requestResize(newDuration, true, local_undo, local_redo, true);
if (!res) {
local_undo();
return false;
}
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
qDebug() << "tw: operation fail";
return false;
}
Fun ClipModel::useTimewarpProducer_lambda(double speed)
{
QWriteLocker locker(&m_lock);
return [speed, this]() {
qDebug() << "timeWarp producer" << speed;
refreshProducerFromBin(m_currentState, speed);
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
ptr->notifyChange(ix, ix, TimelineModel::SpeedRole);
}
return true;
};
}
QVariant ClipModel::getAudioWaveform()
{
READ_LOCK();
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
if (binClip) {
return QVariant::fromValue(binClip->audioFrameCache);
}
return QVariant();
}
const QString &ClipModel::binId() const
{
return m_binClipId;
}
std::shared_ptr ClipModel::getMarkerModel() const
{
READ_LOCK();
return pCore->projectItemModel()->getClipByBinID(m_binClipId)->getMarkerModel();
}
int ClipModel::audioChannels() const
{
READ_LOCK();
return pCore->projectItemModel()->getClipByBinID(m_binClipId)->audioChannels();
}
int ClipModel::fadeIn() const
{
return m_effectStack->getFadePosition(true);
}
int ClipModel::fadeOut() const
{
return m_effectStack->getFadePosition(false);
}
double ClipModel::getSpeed() const
{
return m_speed;
}
KeyframeModel *ClipModel::getKeyframeModel()
{
return m_effectStack->getEffectKeyframeModel();
}
bool ClipModel::showKeyframes() const
{
READ_LOCK();
return !service()->get_int("kdenlive:hide_keyframes");
}
void ClipModel::setShowKeyframes(bool show)
{
QWriteLocker locker(&m_lock);
service()->set("kdenlive:hide_keyframes", (int)!show);
}
void ClipModel::setCurrentTrackId(int tid, bool finalMove)
{
if (tid == m_currentTrackId) {
return;
}
MoveableItem::setCurrentTrackId(tid, finalMove);
if (finalMove && tid != -1) {
refreshProducerFromBin(m_currentState);
}
}
Fun ClipModel::setClipState_lambda(PlaylistState::ClipState state)
{
QWriteLocker locker(&m_lock);
return [this, state]() {
if (auto ptr = m_parent.lock()) {
switch (state) {
case PlaylistState::Disabled:
m_producer->set("set.test_audio", 1);
m_producer->set("set.test_image", 1);
break;
case PlaylistState::VideoOnly:
m_producer->set("set.test_image", 0);
break;
case PlaylistState::AudioOnly:
m_producer->set("set.test_audio", 0);
break;
default:
// error
break;
}
m_currentState = state;
if (m_currentTrackId != -1 && ptr->isClip(m_id)) { // if this is false, the clip is being created. Don't update model in that case
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
ptr->dataChanged(ix, ix, {TimelineModel::StatusRole});
}
return true;
}
return false;
};
}
bool ClipModel::setClipState(PlaylistState::ClipState state, Fun &undo, Fun &redo)
{
if (state == PlaylistState::VideoOnly && !canBeVideo()) {
return false;
}
if (state == PlaylistState::AudioOnly && !canBeAudio()) {
return false;
}
if (state == m_currentState) {
return true;
}
auto old_state = m_currentState;
auto operation = setClipState_lambda(state);
if (operation()) {
auto reverse = setClipState_lambda(old_state);
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
return true;
}
return false;
}
PlaylistState::ClipState ClipModel::clipState() const
{
READ_LOCK();
return m_currentState;
}
ClipType::ProducerType ClipModel::clipType() const
{
READ_LOCK();
return m_clipType;
}
void ClipModel::passTimelineProperties(const std::shared_ptr &other)
{
READ_LOCK();
Mlt::Properties source(m_producer->get_properties());
Mlt::Properties dest(other->service()->get_properties());
dest.pass_list(source, "kdenlive:hide_keyframes,kdenlive:activeeffect");
}
bool ClipModel::canBeVideo() const
{
return m_canBeVideo;
}
bool ClipModel::canBeAudio() const
{
return m_canBeAudio;
}
const QString ClipModel::effectNames() const
{
READ_LOCK();
return m_effectStack->effectNames();
}
int ClipModel::getFakeTrackId() const
{
return m_fakeTrack;
}
void ClipModel::setFakeTrackId(int fid)
{
m_fakeTrack = fid;
}
int ClipModel::getFakePosition() const
{
return m_fakePosition;
}
void ClipModel::setFakePosition(int fid)
{
m_fakePosition = fid;
}
QDomElement ClipModel::toXml(QDomDocument &document)
{
QDomElement container = document.createElement(QStringLiteral("clip"));
container.setAttribute(QStringLiteral("binid"), m_binClipId);
container.setAttribute(QStringLiteral("id"), m_id);
container.setAttribute(QStringLiteral("in"), getIn());
container.setAttribute(QStringLiteral("out"), getOut());
container.setAttribute(QStringLiteral("position"), getPosition());
container.setAttribute(QStringLiteral("state"), (int)m_currentState);
if (auto ptr = m_parent.lock()) {
int trackId = ptr->getTrackPosition(m_currentTrackId);
container.setAttribute(QStringLiteral("track"), trackId);
if (ptr->isAudioTrack(getCurrentTrackId())) {
container.setAttribute(QStringLiteral("audioTrack"), 1);
int mirrorId = ptr->getMirrorVideoTrackId(m_currentTrackId);
if (mirrorId > -1) {
mirrorId = ptr->getTrackPosition(mirrorId);
}
container.setAttribute(QStringLiteral("mirrorTrack"), mirrorId);
}
}
container.setAttribute(QStringLiteral("speed"), m_speed);
container.appendChild(m_effectStack->toXml(document));
return container;
}
bool ClipModel::checkConsistency()
{
if (!m_effectStack->checkConsistency()) {
qDebug() << "Consistency check failed for effecstack";
return false;
}
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
auto instances = binClip->timelineInstances();
bool found = false;
for (const auto &i : instances) {
if (i == m_id) {
found = true;
break;
}
}
if (!found) {
qDebug() << "ERROR: binClip doesn't acknowledge timeline clip existence";
return false;
}
if (m_currentState == PlaylistState::VideoOnly && !m_canBeVideo) {
qDebug() << "ERROR: clip is in video state but doesn't have video";
return false;
}
if (m_currentState == PlaylistState::AudioOnly && !m_canBeAudio) {
qDebug() << "ERROR: clip is in video state but doesn't have video";
return false;
}
// TODO: check speed
return true;
}
int ClipModel::getSubPlaylistIndex() const
{
return m_subPlaylistIndex;
}
void ClipModel::setSubPlaylistIndex(int index)
{
m_subPlaylistIndex = index;
}
diff --git a/src/timeline2/model/timelineitemmodel.cpp b/src/timeline2/model/timelineitemmodel.cpp
index c5db02c90..a1d95dae8 100644
--- a/src/timeline2/model/timelineitemmodel.cpp
+++ b/src/timeline2/model/timelineitemmodel.cpp
@@ -1,600 +1,614 @@
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "timelineitemmodel.hpp"
#include "assets/keyframes/model/keyframemodel.hpp"
#include "bin/model/markerlistmodel.hpp"
#include "clipmodel.hpp"
#include "compositionmodel.hpp"
#include "core.h"
#include "doc/docundostack.hpp"
#include "groupsmodel.hpp"
#include "kdenlivesettings.h"
#include "macros.hpp"
#include "trackmodel.hpp"
#include "transitions/transitionsrepository.hpp"
#include
#include
#include
#include
#include
#include
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+#pragma GCC diagnostic ignored "-Wshadow"
+#pragma GCC diagnostic ignored "-Wpedantic"
+#include
+#pragma GCC diagnostic pop
+RTTR_REGISTRATION
+{
+ using namespace rttr;
+ registration::class_("TimelineItemModel");
+}
+
TimelineItemModel::TimelineItemModel(Mlt::Profile *profile, std::weak_ptr undo_stack)
: TimelineModel(profile, std::move(undo_stack))
{
}
void TimelineItemModel::finishConstruct(const std::shared_ptr &ptr, const std::shared_ptr &guideModel)
{
ptr->weak_this_ = ptr;
ptr->m_groups = std::make_unique(ptr);
guideModel->registerSnapModel(ptr->m_snaps);
}
std::shared_ptr TimelineItemModel::construct(Mlt::Profile *profile, std::shared_ptr guideModel,
std::weak_ptr undo_stack)
{
std::shared_ptr ptr(new TimelineItemModel(profile, std::move(undo_stack)));
finishConstruct(ptr, std::move(guideModel));
return ptr;
}
TimelineItemModel::~TimelineItemModel() = default;
QModelIndex TimelineItemModel::index(int row, int column, const QModelIndex &parent) const
{
READ_LOCK();
QModelIndex result;
if (parent.isValid()) {
auto trackId = int(parent.internalId());
Q_ASSERT(isTrack(trackId));
int clipId = getTrackById_const(trackId)->getClipByRow(row);
if (clipId != -1) {
result = createIndex(row, 0, quintptr(clipId));
} else if (row < getTrackClipsCount(trackId) + getTrackCompositionsCount(trackId)) {
int compoId = getTrackById_const(trackId)->getCompositionByRow(row);
if (compoId != -1) {
result = createIndex(row, 0, quintptr(compoId));
}
} else {
// Invalid index requested
Q_ASSERT(false);
}
} else if (row < getTracksCount() && row >= 0) {
// Get sort order
// row = getTracksCount() - 1 - row;
auto it = m_allTracks.cbegin();
std::advance(it, row);
int trackId = (*it)->getId();
result = createIndex(row, column, quintptr(trackId));
}
return result;
}
/*QModelIndex TimelineItemModel::makeIndex(int trackIndex, int clipIndex) const
{
return index(clipIndex, 0, index(trackIndex));
}*/
QModelIndex TimelineItemModel::makeClipIndexFromID(int clipId) const
{
Q_ASSERT(m_allClips.count(clipId) > 0);
int trackId = m_allClips.at(clipId)->getCurrentTrackId();
if (trackId == -1) {
// Clip is not inserted in a track
qDebug() << "/// WARNING; INVALID CLIP INDEX REQUESTED\n________________";
return {};
}
int row = getTrackById_const(trackId)->getRowfromClip(clipId);
return index(row, 0, makeTrackIndexFromID(trackId));
}
QModelIndex TimelineItemModel::makeCompositionIndexFromID(int compoId) const
{
Q_ASSERT(m_allCompositions.count(compoId) > 0);
int trackId = m_allCompositions.at(compoId)->getCurrentTrackId();
return index(getTrackById_const(trackId)->getRowfromComposition(compoId), 0, makeTrackIndexFromID(trackId));
}
QModelIndex TimelineItemModel::makeTrackIndexFromID(int trackId) const
{
// we retrieve iterator
Q_ASSERT(m_iteratorTable.count(trackId) > 0);
auto it = m_iteratorTable.at(trackId);
int ind = (int)std::distance(m_allTracks.begin(), it);
// Get sort order
// ind = getTracksCount() - 1 - ind;
return index(ind);
}
QModelIndex TimelineItemModel::parent(const QModelIndex &index) const
{
READ_LOCK();
// qDebug() << "TimelineItemModel::parent"<< index;
if (index == QModelIndex()) {
return index;
}
const int id = static_cast(index.internalId());
if (!index.isValid() || isTrack(id)) {
return QModelIndex();
}
if (isClip(id)) {
const int trackId = getClipTrackId(id);
return makeTrackIndexFromID(trackId);
}
if (isComposition(id)) {
const int trackId = getCompositionTrackId(id);
return makeTrackIndexFromID(trackId);
}
return {};
}
int TimelineItemModel::rowCount(const QModelIndex &parent) const
{
READ_LOCK();
if (parent.isValid()) {
const int id = (int)parent.internalId();
if (!isTrack(id)) {
// clips don't have children
// if it is not a track, it is something invalid
return 0;
}
return getTrackClipsCount(id) + getTrackCompositionsCount(id);
}
return getTracksCount();
}
int TimelineItemModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 1;
}
QHash TimelineItemModel::roleNames() const
{
QHash roles;
roles[NameRole] = "name";
roles[ResourceRole] = "resource";
roles[ServiceRole] = "mlt_service";
roles[BinIdRole] = "binId";
roles[TrackIdRole] = "trackId";
roles[FakeTrackIdRole] = "fakeTrackId";
roles[FakePositionRole] = "fakePosition";
roles[StartRole] = "start";
roles[DurationRole] = "duration";
roles[MarkersRole] = "markers";
roles[KeyframesRole] = "keyframeModel";
roles[ShowKeyframesRole] = "showKeyframes";
roles[StatusRole] = "clipStatus";
roles[TypeRole] = "clipType";
roles[InPointRole] = "in";
roles[OutPointRole] = "out";
roles[FramerateRole] = "fps";
roles[GroupedRole] = "grouped";
roles[IsDisabledRole] = "disabled";
roles[IsAudioRole] = "audio";
roles[AudioLevelsRole] = "audioLevels";
roles[AudioChannelsRole] = "audioChannels";
roles[IsCompositeRole] = "composite";
roles[IsLockedRole] = "locked";
roles[FadeInRole] = "fadeIn";
roles[FadeOutRole] = "fadeOut";
roles[FileHashRole] = "hash";
roles[SpeedRole] = "speed";
roles[HeightRole] = "trackHeight";
roles[TrackTagRole] = "trackTag";
roles[ItemIdRole] = "item";
roles[ItemATrack] = "a_track";
roles[HasAudio] = "hasAudio";
roles[CanBeAudioRole] = "canBeAudio";
roles[CanBeVideoRole] = "canBeVideo";
roles[ReloadThumbRole] = "reloadThumb";
roles[ThumbsFormatRole] = "thumbsFormat";
roles[AudioRecordRole] = "audioRecord";
roles[TrackActiveRole] = "trackActive";
roles[EffectNamesRole] = "effectNames";
roles[EffectsEnabledRole] = "isStackEnabled";
roles[GrabbedRole] = "isGrabbed";
return roles;
}
QVariant TimelineItemModel::data(const QModelIndex &index, int role) const
{
READ_LOCK();
if (!m_tractor || !index.isValid()) {
// qDebug() << "DATA abort. Index validity="< clip = m_allClips.at(id);
// Get data for a clip
switch (role) {
// TODO
case NameRole:
case Qt::DisplayRole: {
QString result = clip->getProperty("kdenlive:clipname");
if (result.isEmpty()) {
result = clip->getProperty("kdenlive:originalurl");
if (result.isEmpty()) {
result = clip->getProperty("resource");
}
if (!result.isEmpty()) {
result = QFileInfo(result).fileName();
} else {
result = clip->getProperty("mlt_service");
}
}
return result;
}
case ResourceRole: {
QString result = clip->getProperty("resource");
if (result == QLatin1String("")) {
result = clip->getProperty("mlt_service");
}
return result;
}
case FakeTrackIdRole:
return clip->getFakeTrackId();
case FakePositionRole:
return clip->getFakePosition();
case BinIdRole:
return clip->binId();
case TrackIdRole:
return clip->getCurrentTrackId();
case ServiceRole:
return clip->getProperty("mlt_service");
break;
case AudioLevelsRole:
// Dumb property to trigger audio thumbs reload
return true;
case AudioChannelsRole:
return clip->audioChannels();
case HasAudio:
return clip->audioEnabled();
case IsAudioRole:
return clip->isAudioOnly();
case CanBeAudioRole:
return clip->canBeAudio();
case CanBeVideoRole:
return clip->canBeVideo();
case MarkersRole: {
return QVariant::fromValue(clip->getMarkerModel().get());
}
case KeyframesRole: {
return QVariant::fromValue(clip->getKeyframeModel());
}
case StatusRole:
return QVariant::fromValue(clip->clipState());
case TypeRole:
return QVariant::fromValue(clip->clipType());
case StartRole:
return clip->getPosition();
case DurationRole:
return clip->getPlaytime();
case GroupedRole:
return m_groups->isInGroup(id);
case EffectNamesRole:
return clip->effectNames();
case InPointRole:
return clip->getIn();
case OutPointRole:
return clip->getOut();
case ShowKeyframesRole:
return clip->showKeyframes();
case FadeInRole:
return clip->fadeIn();
case FadeOutRole:
return clip->fadeOut();
case ReloadThumbRole:
return clip->forceThumbReload;
case SpeedRole:
return clip->getSpeed();
case GrabbedRole:
return clip->isGrabbed();
default:
break;
}
} else if (isTrack(id)) {
// qDebug() << "DATA REQUESTED FOR TRACK "<< id;
switch (role) {
case NameRole:
case Qt::DisplayRole: {
return getTrackById_const(id)->getProperty("kdenlive:track_name").toString();
}
case TypeRole:
return QVariant::fromValue(ClipType::ProducerType::Track);
case DurationRole:
// qDebug() << "DATA yielding duration" << m_tractor->get_playtime();
return getTrackById_const(id)->trackDuration();
case IsDisabledRole:
// qDebug() << "DATA yielding mute" << 0;
return getTrackById_const(id)->isAudioTrack() ? getTrackById_const(id)->isMute() : getTrackById_const(id)->isHidden();
case IsAudioRole:
return getTrackById_const(id)->isAudioTrack();
case TrackTagRole:
return getTrackTagById(id);
case IsLockedRole:
return getTrackById_const(id)->getProperty("kdenlive:locked_track").toInt() == 1;
case HeightRole: {
int collapsed = getTrackById_const(id)->getProperty("kdenlive:collapsed").toInt();
if (collapsed > 0) {
return collapsed;
}
int height = getTrackById_const(id)->getProperty("kdenlive:trackheight").toInt();
// qDebug() << "DATA yielding height" << height;
return (height > 0 ? height : 60);
}
case ThumbsFormatRole:
return getTrackById_const(id)->getProperty("kdenlive:thumbs_format").toInt();
case IsCompositeRole: {
case AudioRecordRole:
return getTrackById_const(id)->getProperty("kdenlive:audio_rec").toInt();
}
case TrackActiveRole: {
return getTrackById_const(id)->isTimelineActive();
}
case EffectNamesRole: {
return getTrackById_const(id)->effectNames();
}
case EffectsEnabledRole: {
return getTrackById_const(id)->stackEnabled();
}
default:
break;
}
} else if (isComposition(id)) {
std::shared_ptr compo = m_allCompositions.at(id);
switch (role) {
case NameRole:
case Qt::DisplayRole:
case ResourceRole:
case ServiceRole:
return compo->displayName();
break;
case TypeRole:
return QVariant::fromValue(ClipType::ProducerType::Composition);
case StartRole:
return compo->getPosition();
case TrackIdRole:
return compo->getCurrentTrackId();
case DurationRole:
return compo->getPlaytime();
case GroupedRole:
return m_groups->isInGroup(id);
case InPointRole:
return 0;
case OutPointRole:
return 100;
case BinIdRole:
return 5;
case KeyframesRole: {
return QVariant::fromValue(compo->getEffectKeyframeModel());
}
case ShowKeyframesRole:
return compo->showKeyframes();
case ItemATrack:
return compo->getForcedTrack();
case MarkersRole: {
QVariantList markersList;
return markersList;
}
case GrabbedRole:
return compo->isGrabbed();
default:
break;
}
} else {
qDebug() << "UNKNOWN DATA requested " << index << roleNames()[role];
}
return QVariant();
}
void TimelineItemModel::setTrackProperty(int trackId, const QString &name, const QString &value)
{
std::shared_ptr track = getTrackById(trackId);
track->setProperty(name, value);
QVector roles;
if (name == QLatin1String("kdenlive:track_name")) {
roles.push_back(NameRole);
} else if (name == QLatin1String("kdenlive:locked_track")) {
roles.push_back(IsLockedRole);
} else if (name == QLatin1String("hide")) {
roles.push_back(IsDisabledRole);
if (!track->isAudioTrack()) {
pCore->requestMonitorRefresh();
}
} else if (name == QLatin1String("kdenlive:timeline_active")) {
roles.push_back(TrackActiveRole);
} else if (name == QLatin1String("kdenlive:thumbs_format")) {
roles.push_back(ThumbsFormatRole);
} else if (name == QLatin1String("kdenlive:audio_rec")) {
roles.push_back(AudioRecordRole);
}
if (!roles.isEmpty()) {
QModelIndex ix = makeTrackIndexFromID(trackId);
emit dataChanged(ix, ix, roles);
}
}
void TimelineItemModel::setTrackStackEnabled(int tid, bool enable)
{
std::shared_ptr track = getTrackById(tid);
track->setEffectStackEnabled(enable);
QModelIndex ix = makeTrackIndexFromID(tid);
emit dataChanged(ix, ix, {TimelineModel::EffectsEnabledRole});
}
void TimelineItemModel::importTrackEffects(int tid, std::weak_ptr service)
{
std::shared_ptr track = getTrackById(tid);
track->importEffects(std::move(service));
}
QVariant TimelineItemModel::getTrackProperty(int tid, const QString &name) const
{
return getTrackById_const(tid)->getProperty(name);
}
int TimelineItemModel::getFirstVideoTrackIndex() const
{
int trackId = -1;
auto it = m_allTracks.cbegin();
while (it != m_allTracks.cend()) {
trackId = (*it)->getId();
if (!(*it)->isAudioTrack()) {
break;
}
++it;
}
return trackId;
}
const QString TimelineItemModel::getTrackFullName(int tid) const
{
QString tag = getTrackTagById(tid);
QString trackName = getTrackById_const(tid)->getProperty(QStringLiteral("kdenlive:track_name")).toString();
return trackName.isEmpty() ? tag : tag + QStringLiteral(" - ") + trackName;
}
const QString TimelineItemModel::groupsData()
{
return m_groups->toJson();
}
bool TimelineItemModel::loadGroups(const QString &groupsData)
{
return m_groups->fromJson(groupsData);
}
void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, bool start, bool duration, bool updateThumb)
{
QVector roles;
if (start) {
roles.push_back(TimelineModel::StartRole);
if (updateThumb) {
roles.push_back(TimelineModel::InPointRole);
}
}
if (duration) {
roles.push_back(TimelineModel::DurationRole);
if (updateThumb) {
roles.push_back(TimelineModel::OutPointRole);
}
}
emit dataChanged(topleft, bottomright, roles);
}
void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, const QVector &roles)
{
emit dataChanged(topleft, bottomright, roles);
}
void TimelineItemModel::buildTrackCompositing(bool rebuild)
{
auto it = m_allTracks.cbegin();
QScopedPointer field(m_tractor->field());
field->lock();
// Make sure all previous track compositing is removed
if (rebuild) {
QScopedPointer service(new Mlt::Service(field->get_service()));
while ((service != nullptr) && service->is_valid()) {
if (service->type() == transition_type) {
Mlt::Transition t((mlt_transition)service->get_service());
QString serviceName = t.get("mlt_service");
if (t.get_int("internal_added") == 237) {
// remove all compositing transitions
field->disconnect_service(t);
}
}
service.reset(service->producer());
}
}
QString composite = TransitionsRepository::get()->getCompositingTransition();
while (it != m_allTracks.cend()) {
int trackId = getTrackMltIndex((*it)->getId());
if (!composite.isEmpty() && !(*it)->isAudioTrack()) {
// video track, add composition
std::unique_ptr transition = TransitionsRepository::get()->getTransition(composite);
transition->set("internal_added", 237);
transition->set("always_active", 1);
field->plant_transition(*transition, 0, trackId);
transition->set_tracks(0, trackId);
} else if ((*it)->isAudioTrack()) {
// audio mix
std::unique_ptr transition = TransitionsRepository::get()->getTransition(QStringLiteral("mix"));
transition->set("internal_added", 237);
transition->set("always_active", 1);
transition->set("sum", 1);
field->plant_transition(*transition, 0, trackId);
transition->set_tracks(0, trackId);
}
++it;
}
field->unlock();
if (composite.isEmpty()) {
pCore->displayMessage(i18n("Could not setup track compositing, check your install"), MessageType::ErrorMessage);
}
}
void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, int role)
{
emit dataChanged(topleft, bottomright, {role});
}
void TimelineItemModel::_beginRemoveRows(const QModelIndex &i, int j, int k)
{
// qDebug()<<"FORWARDING beginRemoveRows"<