Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 3a5cc9d8 authored by Nicolas Carion's avatar Nicolas Carion

[Timeline2][Model] Implement clip resize, improve tests and fix various bugs

parent 2eaab760
......@@ -20,6 +20,7 @@
***************************************************************************/
#include "clipmodel.hpp"
#include "timelinemodel.hpp"
#include "trackmodel.hpp"
#include <mlt++/MltProducer.h>
#include <QDebug>
......@@ -44,7 +45,6 @@ int ClipModel::construct(std::weak_ptr<TimelineModel> parent, std::shared_ptr<Ml
Q_ASSERT(false);
}
prod.reset(prod->cut());
return id;
}
......@@ -79,6 +79,44 @@ bool ClipModel::isValid()
return m_producer->is_valid();
}
bool ClipModel::slotRequestResize(int size, bool right, bool dry)
{
if (!dry && !slotRequestResize(size, right, true)) {
return false;
}
if (size < 0 || size > m_producer->get_length()) {
return false;
}
int delta = getPlaytime() - size;
int in = m_producer->get_in();
int out = m_producer->get_out();
//check if there is enough space on the chosen side
if ((!right && in + delta < 0) || (right && out - delta >= m_producer->get_length())) {
return false;
}
if (right) {
out -= delta;
} else {
in += delta;
}
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
bool ok = ptr->getTrackById(m_currentTrackId)->requestClipResize(m_id, in, out, right, dry);
if (!ok) {
return false;
}
} else {
qDebug() << "Error : Moving clip failed because parent timeline is not available anymore";
Q_ASSERT(false);
}
}
if (!dry) {
m_producer.reset(m_producer->cut(in, out));
}
return true;
}
int ClipModel::getPlaytime()
{
return m_producer->get_playtime();
......
......@@ -79,11 +79,16 @@ public:
bool isValid();
public:
/* This is called whenever a resize of the clip is issued
/* @brief Performs a resize of the given clip.
Returns true if the operation succeeded, and otherwise nothing is modified
This is called whenever a resize of the clip is issued
If the resize is accepted, it should send back a signal to the QML interface.
If a snap point is withing reach, the operation will be coerced to use it.
If a snap point is within reach, the operation will be coerced to use it.
@param size is the new size of the clip
@param right is true if we change the right side of the clip, false otherwise
@param dry If this parameter is true, no action is actually executed, but we return true if it would be possible to do it.
*/
void slotRequestResize(int newSize);
bool slotRequestResize(int size, bool right, bool dry = false);
/* This is called whenever a move of the clip is issued
If the move is accepted, it should send back a signal to the QML interface.
......
......@@ -117,6 +117,7 @@ bool TrackModel::requestClipDeletion(int cid, bool dry)
int clip_position = m_allClips[cid]->getPosition();
int target_clip = m_playlist.get_clip_index_at(clip_position);
Q_ASSERT(target_clip < m_playlist.count());
Q_ASSERT(!m_playlist.is_blank(target_clip));
auto prod = m_playlist.replace_with_blank(target_clip);
if (prod != nullptr) {
m_playlist.consolidate_blanks();
......@@ -126,8 +127,120 @@ bool TrackModel::requestClipDeletion(int cid, bool dry)
return false;
}
bool TrackModel::requestClipResize(int cid, int in, int out, bool right, bool dry)
{
//Find index of clip
Q_ASSERT(m_allClips.count(cid) > 0);
int clip_position = m_allClips[cid]->getPosition();
int target_clip = m_playlist.get_clip_index_at(clip_position);
Q_ASSERT(target_clip < m_playlist.count());
int size = out - in + 1;
int delta = m_allClips[cid]->getPlaytime() - size;
if (delta == 0) {
return true;
}
if (delta > 0) {
if (dry) {
return true;
}
int blank_index = right ? (target_clip + 1) : target_clip;
// insert blank to space that is going to be empty
// The second is parameter is delta - 1 because this function expects an out time, which is basically size - 1
m_playlist.insert_blank(blank_index, delta - 1);
if (!right) {
m_allClips[cid]->setPosition(clip_position + delta);
//Because we inserted blank before, the index of our clip has increased
target_clip++;
}
int err = m_playlist.resize_clip(target_clip, in, out);
//make sure to do this after, to avoid messing the indexes
m_playlist.consolidate_blanks();
return err == 0;
} else {
int blank = -1;
if (right) {
if (target_clip == m_playlist.count() - 1) {
//clip is last, it can always be extended
int err = m_playlist.resize_clip(target_clip, in, out);
return err == 0;
}
blank = target_clip + 1;
} else {
if (target_clip == 0) {
//clip is first, it can never be extended on the left
return false;
}
blank = target_clip - 1;
}
if (m_playlist.is_blank(blank)) {
int blank_length = m_playlist.clip_length(blank);
if (blank_length >= m_allClips[cid]->getPlaytime()) {
if (dry) {
return true;
}
int err = 0;
if (blank_length + delta == 0) {
err = m_playlist.remove(blank);
if (!right) {
target_clip--;
}
} else {
err = m_playlist.resize_clip(blank, 0, blank_length + delta - 1);
}
if (err == 0) {
err = m_playlist.resize_clip(target_clip, in, out);
}
if (!right && err == 0) {
m_allClips[cid]->setPosition(m_playlist.clip_start(target_clip));
}
return err == 0;
}
}
}
return false;
}
int TrackModel::getId() const
{
return m_id;
}
bool TrackModel::checkConsistency()
{
std::vector<std::pair<int, int> > clips; //clips stored by (position, id)
for (const auto& c : m_allClips) {
if (c.second)
clips.push_back({c.second->getPosition(), c.first});
}
std::sort(clips.begin(), clips.end());
size_t current_clip = 0;
for(int i = 0; i < m_playlist.get_playtime(); i++) {
auto clip = m_allClips[clips[current_clip].second];
int index = m_playlist.get_clip_index_at(i);
if (current_clip < clips.size() && i >= clips[current_clip].first) {
if (i >= clips[current_clip].first + clip->getPlaytime()) {
current_clip++;
i--;
continue;
} else {
Mlt::Producer prod(m_playlist.get_clip(index));
if (m_playlist.is_blank(index)) {
qDebug() << "ERROR: Found blank when clip was required at position " << i;
qDebug() << "Blank size" << m_playlist.clip_length(index);
return false;
}
if (!prod.same_clip(*clip)) {
qDebug() << "ERROR: Wrong clip at position " << i;
return false;
}
}
} else {
if (!m_playlist.is_blank_at(i)) {
qDebug() << "ERROR: Found clip when blank was required at position " << i;
return false;
}
}
}
return true;
}
......@@ -37,6 +37,7 @@ public:
TrackModel() = delete;
friend class TimelineModel;
friend class ClipModel;
private:
/* This constructor is private, call the static construct instead */
TrackModel(std::weak_ptr<TimelineModel> parent);
......@@ -69,8 +70,6 @@ public:
*/
bool requestClipDeletion(int cid, bool dry = false);
/* Perform a resize operation on a clip. Returns true if the operation succeeded*/
bool requestClipResize(QSharedPointer<ClipModel> caller, int newSize);
/* Perform a move operation on a clip. Returns true if the operation succeeded*/
bool requestClipMove(QSharedPointer<ClipModel> caller, int newPosition);
......@@ -83,9 +82,21 @@ public:
operator Mlt::Producer&(){ return m_playlist;}
protected:
/* @brief Performs a resize of the given clip.
Returns true if the operation succeeded, and otherwise nothing is modified
This method is protected because it shouldn't be called directly. Call the function in the clip instead.
@param cid is the id of the clip
@param in is the new starting on the clip
@param out is the new ending on the clip
@param right is true if we change the right side of the clip, false otherwise
@param dry If this parameter is true, no action is actually executed, but we return true if it would be possible to do it.
*/
bool requestClipResize(int cid, int in, int out, bool right, bool dry = false);
/*@brief Returns the (unique) construction id of the track*/
int getId() const;
/*@brief This is an helper function that test frame level consistancy with the MLT structures */
bool checkConsistency();
public slots:
/*Delete the current track and all its associated clips */
void slotDelete();
......
......@@ -2,6 +2,7 @@
#include <memory>
#include <random>
#define private public
#define protected public
#include "timeline2/model/trackmodel.hpp"
#include "timeline2/model/timelinemodel.hpp"
#include "timeline2/model/clipmodel.hpp"
......@@ -83,6 +84,8 @@ TEST_CASE("Clip manipulation", "[ClipModel]")
int cid2 = ClipModel::construct(timeline, producer);
SECTION("Insert a clip in a track and change track") {
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
......@@ -92,6 +95,8 @@ TEST_CASE("Clip manipulation", "[ClipModel]")
int pos = 10;
//dry
REQUIRE(timeline->requestClipChangeTrack(cid1, tid1, pos,true));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 0);
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
......@@ -100,6 +105,8 @@ TEST_CASE("Clip manipulation", "[ClipModel]")
//real insert
REQUIRE(timeline->requestClipChangeTrack(cid1, tid1, pos));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == pos);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
......@@ -108,12 +115,16 @@ TEST_CASE("Clip manipulation", "[ClipModel]")
pos = 1;
//dry
REQUIRE(timeline->requestClipChangeTrack(cid1, tid2, pos,true));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 10);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
//real
REQUIRE(timeline->requestClipChangeTrack(cid1, tid2, pos));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid2);
REQUIRE(timeline->getClipPosition(cid1) == pos);
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
......@@ -123,6 +134,8 @@ TEST_CASE("Clip manipulation", "[ClipModel]")
// Check conflicts
int pos2 = producer->get_playtime();
REQUIRE(timeline->requestClipChangeTrack(cid2, tid1, pos2));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid2) == pos2);
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
......@@ -130,6 +143,8 @@ TEST_CASE("Clip manipulation", "[ClipModel]")
REQUIRE_FALSE(timeline->requestClipChangeTrack(cid1, tid1, pos2 + 2, true));
REQUIRE_FALSE(timeline->requestClipChangeTrack(cid1, tid1, pos2 + 2));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid2);
......@@ -139,6 +154,8 @@ TEST_CASE("Clip manipulation", "[ClipModel]")
REQUIRE_FALSE(timeline->requestClipChangeTrack(cid1, tid1, pos2 - 2, true));
REQUIRE_FALSE(timeline->requestClipChangeTrack(cid1, tid1, pos2 - 2));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->getClipTrackId(cid1) == tid2);
......@@ -147,6 +164,8 @@ TEST_CASE("Clip manipulation", "[ClipModel]")
REQUIRE(timeline->getClipPosition(cid2) == pos2);
REQUIRE(timeline->requestClipChangeTrack(cid1, tid1, 0));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
......@@ -154,6 +173,98 @@ TEST_CASE("Clip manipulation", "[ClipModel]")
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid2) == pos2);
}
int length = producer->get_playtime();
SECTION("Insert consecutive clips") {
REQUIRE(timeline->requestClipChangeTrack(cid1, tid1, 0));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->requestClipChangeTrack(cid2, tid1, length));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid2) == length);
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
}
SECTION("Resize orphan clip"){
REQUIRE(timeline->m_allClips[cid2]->slotRequestResize(5, true, true));
REQUIRE(timeline->m_allClips[cid2]->getPlaytime() == length);
REQUIRE(timeline->m_allClips[cid2]->slotRequestResize(5, true));
REQUIRE(producer->get_playtime() == length);
REQUIRE(timeline->m_allClips[cid2]->getPlaytime() == 5);
REQUIRE_FALSE(timeline->m_allClips[cid2]->slotRequestResize(10, false));
REQUIRE_FALSE(timeline->m_allClips[cid2]->slotRequestResize(length + 1, true));
REQUIRE(timeline->m_allClips[cid2]->getPlaytime() == 5);
REQUIRE_FALSE(timeline->m_allClips[cid2]->slotRequestResize(length + 1, true, true));
REQUIRE(timeline->m_allClips[cid2]->getPlaytime() == 5);
REQUIRE(timeline->m_allClips[cid2]->slotRequestResize(2, false));
REQUIRE(timeline->m_allClips[cid2]->getPlaytime() == 2);
REQUIRE_FALSE(timeline->m_allClips[cid2]->slotRequestResize(length, true));
REQUIRE(timeline->m_allClips[cid2]->getPlaytime() == 2);
CAPTURE(timeline->m_allClips[cid2]->m_producer->get_in());
REQUIRE_FALSE(timeline->m_allClips[cid2]->slotRequestResize(length - 2, true));
REQUIRE(timeline->m_allClips[cid2]->slotRequestResize(length - 3, true));
REQUIRE(timeline->m_allClips[cid2]->getPlaytime() == length - 3);
}
SECTION("Resize inserted clips"){
REQUIRE(timeline->requestClipChangeTrack(cid1, tid1, 0));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->m_allClips[cid1]->slotRequestResize(5, true));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->m_allClips[cid1]->getPlaytime() == 5);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->requestClipChangeTrack(cid2, tid1, 5));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(producer->get_playtime() == length);
REQUIRE_FALSE(timeline->m_allClips[cid1]->slotRequestResize(6, true));
REQUIRE_FALSE(timeline->m_allClips[cid1]->slotRequestResize(6, false));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->m_allClips[cid2]->slotRequestResize(length - 5, false));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getClipPosition(cid2) == 10);
REQUIRE(timeline->m_allClips[cid1]->slotRequestResize(10, true));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
}
SECTION("Change track of resized clips"){
REQUIRE(timeline->requestClipChangeTrack(cid2, tid1, 5));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 1);
REQUIRE(timeline->requestClipChangeTrack(cid1, tid2, 10));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid2) == 1);
REQUIRE(timeline->m_allClips[cid1]->slotRequestResize(5, false));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->requestClipChangeTrack(cid1, tid1, 0));
REQUIRE(timeline->getTrackById(tid1)->checkConsistency());
REQUIRE(timeline->getTrackById(tid2)->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getTrackClipsCount(tid2) == 0);
}
}
TEST_CASE("Check id unicity", "[ClipModel]")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment