diff --git a/fuzzer/fuzzing.cpp b/fuzzer/fuzzing.cpp
index bc791ed95..239eadee4 100644
--- a/fuzzer/fuzzing.cpp
+++ b/fuzzer/fuzzing.cpp
@@ -1,419 +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 "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();
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;
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] = {};
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 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_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;
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_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"}) {
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) {
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.push_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.push_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.push_back(rttr::variant(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.push_back(itemId);
// std::cout << "got itemId" << itemId << std::endl;
} else if (arg_name == "ids") {
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.push_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.push_back(a);
} else if (arg_type == rttr::type::get()) {
bool a = false;
ss >> a;
// std::cout << "read bool " << a << std::endl;
arguments.push_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.push_back(QString::fromStdString(str));
} 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 {
assert(false);
}
} else {
if (p.get_type() == rttr::type::get()) {
arguments.push_back(-1);
} else {
assert(false);
}
}
}
if (valid) {
// std::cout << "VALID!!!" << std::endl;
std::vector args;
for (const auto &a : arguments) {
args.emplace_back(a);
// std::cout<<"argument="<checkConsistency());
}
}
}
all_clips.clear();
all_tracks.clear();
all_compositions.clear();
for (size_t i = 0; i < all_timelines.size(); ++i) {
all_timelines[i].reset();
}
pCore->m_projectManager = nullptr;
Core::m_self.reset();
MltConnection::m_self.reset();
+ std::cout << "---------------------------------------------------------------------------------------------------------------------------------------------"
+ "---------------"
+ << std::endl;
}
diff --git a/src/profiles/profilerepository.cpp b/src/profiles/profilerepository.cpp
index b8475681b..8a68e0e92 100644
--- a/src/profiles/profilerepository.cpp
+++ b/src/profiles/profilerepository.cpp
@@ -1,221 +1,221 @@
/***************************************************************************
* 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 "profilerepository.hpp"
#include "kdenlive_debug.h"
#include "kdenlivesettings.h"
#include "profilemodel.hpp"
#include
#include
#include
#include
#include
#include
#include
std::unique_ptr ProfileRepository::instance;
std::once_flag ProfileRepository::m_onceFlag;
std::vector> ProfileRepository::colorProfiles{
{601, QStringLiteral("ITU-R 601")}, {709, QStringLiteral("ITU-R 709")}, {240, QStringLiteral("SMPTE240M")}};
ProfileRepository::ProfileRepository()
{
refresh();
}
std::unique_ptr &ProfileRepository::get()
{
std::call_once(m_onceFlag, [] { instance.reset(new ProfileRepository()); });
return instance;
}
void ProfileRepository::refresh(bool fullRefresh)
{
QWriteLocker locker(&m_mutex);
if (fullRefresh) {
// Reset all profiles
m_profiles.clear();
}
// Helper function to check a profile and print debug info
auto check_profile = [&](std::unique_ptr &profile, const QString &file) {
if (m_profiles.count(file) > 0) {
return false;
}
if (!profile->is_valid()) {
qCWarning(KDENLIVE_LOG) << "//// WARNING: invalid profile found: " << file << ". Ignoring.";
return false;
}
return true;
};
// list MLT profiles.
QDir mltDir(KdenliveSettings::mltpath());
QStringList profilesFiles = mltDir.entryList(QDir::Files);
// list Custom Profiles
QStringList customProfilesDir = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("profiles/"), QStandardPaths::LocateDirectory);
for (const auto &dir : customProfilesDir) {
QStringList files = QDir(dir).entryList(QDir::Files);
for (const auto &file : files) {
profilesFiles << QDir(dir).absoluteFilePath(file);
}
}
// Iterate through files
for (const auto &file : profilesFiles) {
std::unique_ptr profile(new ProfileModel(file));
if (check_profile(profile, file)) {
m_profiles.insert(std::make_pair(file, std::move(profile)));
}
}
}
QVector> ProfileRepository::getAllProfiles() const
{
QReadLocker locker(&m_mutex);
QVector> list;
for (const auto &profile : m_profiles) {
list.push_back({profile.second->description(), profile.first});
}
std::sort(list.begin(), list.end());
return list;
}
std::unique_ptr &ProfileRepository::getProfile(const QString &path)
{
QReadLocker locker(&m_mutex);
if (m_profiles.count(path) == 0) {
- qCWarning(KDENLIVE_LOG) << "//// WARNING: profile not found: " << path << ". Returning default profile instead.";
+ // qCWarning(KDENLIVE_LOG) << "//// WARNING: profile not found: " << path << ". Returning default profile instead.";
QString default_profile = KdenliveSettings::default_profile();
if (m_profiles.count(default_profile) == 0) {
qCWarning(KDENLIVE_LOG) << "//// WARNING: default profile not found: " << default_profile << ". Returning random profile instead.";
return (*(m_profiles.begin())).second;
}
return m_profiles.at(default_profile);
}
return m_profiles.at(path);
}
bool ProfileRepository::profileExists(const QString &path) const
{
QReadLocker locker(&m_mutex);
return m_profiles.count(path) != 0;
}
// static
QString ProfileRepository::getColorspaceDescription(int colorspace)
{
// TODO: should the descriptions be translated?
for (const auto &cs : colorProfiles) {
if (cs.first == colorspace) return cs.second;
}
return i18n("Unknown");
}
// static
int ProfileRepository::getColorspaceFromDescription(const QString &description)
{
for (const auto &cs : colorProfiles) {
if (cs.second == description) return cs.first;
}
return 0;
}
QVector ProfileRepository::getAllFps() const
{
QReadLocker locker(&m_mutex);
QVector res;
for (const auto &ptr : m_profiles) {
res.push_back(ptr.second->fps());
}
std::sort(res.begin(), res.end());
res.erase(std::unique(res.begin(), res.end()), res.end());
return res;
}
QString ProfileRepository::findMatchingProfile(ProfileInfo *profile) const
{
QReadLocker locker(&m_mutex);
for (const auto &ptr : m_profiles) {
if (*ptr.second.get() == *profile) {
return ptr.first;
}
}
return QString();
}
const QString ProfileRepository::saveProfile(ProfileInfo *profile, QString profilePath)
{
if (profilePath.isEmpty()) {
int i = 0;
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/"));
if (!dir.exists()) {
dir.mkpath(QStringLiteral("."));
}
QString customName = QStringLiteral("customprofile");
profilePath = dir.absoluteFilePath(customName + QString::number(i));
while (QFile::exists(profilePath)) {
++i;
profilePath = dir.absoluteFilePath(customName + QString::number(i));
}
}
QFile file(profilePath);
if (!file.open(QIODevice::WriteOnly)) {
KMessageBox::sorry(nullptr, i18n("Cannot open file %1", profilePath));
return QString();
}
QTextStream out(&file);
out << "description=" << profile->description() << '\n'
<< "frame_rate_num=" << profile->frame_rate_num() << '\n'
<< "frame_rate_den=" << profile->frame_rate_den() << '\n'
<< "width=" << profile->width() << '\n'
<< "height=" << profile->height() << '\n'
<< "progressive=" << static_cast(profile->progressive()) << '\n'
<< "sample_aspect_num=" << profile->sample_aspect_num() << '\n'
<< "sample_aspect_den=" << profile->sample_aspect_den() << '\n'
<< "display_aspect_num=" << profile->display_aspect_num() << '\n'
<< "display_aspect_den=" << profile->display_aspect_den() << '\n'
<< "colorspace=" << profile->colorspace() << '\n';
if (file.error() != QFile::NoError) {
KMessageBox::error(nullptr, i18n("Cannot write to file %1", profilePath));
profilePath.clear();
}
file.close();
refresh(false);
return profilePath;
}
bool ProfileRepository::deleteProfile(const QString &path)
{
bool success = false;
if (path.contains(QLatin1Char('/'))) {
success = QFile::remove(path);
}
if (!success) {
qCDebug(KDENLIVE_LOG) << "//// Cannot delete profile " << path << ", does not seem to be custom one";
}
refresh(true);
return success;
}
diff --git a/src/timeline2/model/clipmodel.cpp b/src/timeline2/model/clipmodel.cpp
index 2481e14de..99638816c 100644
--- a/src/timeline2/model/clipmodel.cpp
+++ b/src/timeline2/model/clipmodel.cpp
@@ -1,667 +1,669 @@
/***************************************************************************
* 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 "macros.hpp"
#include "timelinemodel.hpp"
#include "trackmodel.hpp"
#include
#include
#include
#include
ClipModel::ClipModel(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(id, state, speed);
std::shared_ptr clip(new ClipModel(parent, cutProducer, binClipId, id, state, speed));
clip->setClipState_lambda(state)();
parent->registerClip(clip);
return id;
}
int ClipModel::construct(const std::shared_ptr &parent, const QString &binClipId, 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, service, registerProducer);
}
void ClipModel::deregisterClipToBin()
{
std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
binClip->deregisterTimelineClip(m_id);
pCore->removeFromSelection(m_id);
}
ClipModel::~ClipModel() {}
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 QSize(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(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(stackModel, m_currentState);
return true;
}
bool ClipModel::importEffects(std::weak_ptr service)
{
QWriteLocker locker(&m_lock);
m_effectStack->importEffects(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.
Q_ASSERT(m_currentTrackId == -1);
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 * 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()) * 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_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) * 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);
}
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(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());
if (auto ptr = m_parent.lock()) {
int trackId = ptr->getTrackPosition(getCurrentTrackId());
container.setAttribute(QStringLiteral("track"), trackId);
}
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;
}