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 f599b769 authored by Nicolas Carion's avatar Nicolas Carion

Update Catch+FakeIt, simplify logic for timewarping and start testing timewarp...

Update Catch+FakeIt, simplify logic for timewarping and start testing timewarp (not working due to MLT weirdness)
parent a97fa5d8
......@@ -149,6 +149,7 @@ if (BUILD_TESTS)
src
)
ADD_EXECUTABLE(runTests ${Tests_SRCS})
set_property(TARGET runTests PROPERTY CXX_STANDARD 14)
target_link_libraries(runTests kdenliveLib)
ADD_TEST(runTests runTests -d yes)
else()
......
......@@ -430,6 +430,7 @@ void ProjectClip::createDisabledMasterProducer()
}
std::shared_ptr<Mlt::Producer> ProjectClip::getTimelineProducer(int clipId, PlaylistState::ClipState state, double speed)
{
qDebug() << "producer request"<<clipId<<speed;
if (qFuzzyCompare(speed, 1.0)) {
// we are requesting a normal speed producer
// We can first cleen the speed producers we have for the current id
......@@ -476,6 +477,7 @@ std::shared_ptr<Mlt::Producer> ProjectClip::getTimelineProducer(int clipId, Play
if (qFuzzyCompare(m_timewarpProducers[clipId]->get_double("warp_speed"), speed)) {
// the producer we have is good, use it !
warpProducer = m_timewarpProducers[clipId];
qDebug() << "Reusing producer!";
} else {
m_timewarpProducers.erase(clipId);
}
......@@ -484,8 +486,10 @@ std::shared_ptr<Mlt::Producer> ProjectClip::getTimelineProducer(int clipId, Play
QLocale locale;
QString resource = QString("timewarp:%1:%2").arg(locale.toString(speed)).arg(originalProducer()->get("resource"));
warpProducer.reset(new Mlt::Producer(*originalProducer()->profile(), resource.toUtf8().constData()));
qDebug() << "new producer!";
}
qDebug() << "warp LENGTH" << warpProducer->get_length();
warpProducer->set("set.test_audio", 1);
warpProducer->set("set.test_image", 1);
if (state == PlaylistState::AudioOnly) {
......
......@@ -126,8 +126,8 @@ 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 << "total length" <<
// m_producer->get_length() << "current length" << getPlaytime();
qDebug() << "RESIZE CLIP" << m_id << "target size=" << size << "right=" << right << "endless=" << m_endlessResize << "total length"
<< m_producer->get_length() << "current length" << getPlaytime();
if (!m_endlessResize && (size <= 0 || size > m_producer->get_length())) {
return false;
}
......@@ -142,13 +142,14 @@ bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool l
bool ClipModel::requestResize(int old_in, int old_out, int oldDuration, int delta, 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 << "total length" <<
// m_producer->get_length() << "current length" << getPlaytime();
qDebug() << "requestResize" << old_in << old_out << oldDuration << delta << right << m_producer->get_length();
// check if there is enough space on the chosen side
if (!right && old_in + delta < 0 && !m_endlessResize) {
qDebug() << "fail 1";
return false;
}
if (!m_endlessResize && right && old_out - delta >= m_producer->get_length()) {
qDebug() << "fail 2";
return false;
}
int in = old_in;
......@@ -339,11 +340,18 @@ bool ClipModel::isAudioOnly() const
return m_currentState == PlaylistState::AudioOnly;
}
void ClipModel::refreshProducerFromBin(PlaylistState::ClipState state)
void ClipModel::refreshProducerFromBin(PlaylistState::ClipState state, double speed)
{
QWriteLocker locker(&m_lock);
int in = getIn();
int out = getOut();
qDebug() << "refresh " << speed << m_speed << in << out;
if (!qFuzzyCompare(speed, m_speed) && speed != 0.) {
in = in * m_speed / speed;
out = in + getPlaytime() - 1;
m_speed = speed;
qDebug() << "changing speed" << in << out << m_speed;
}
std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
std::shared_ptr<Mlt::Producer> binProducer = binClip->getTimelineProducer(m_id, state, m_speed);
m_producer = std::move(binProducer);
......@@ -362,79 +370,43 @@ void ClipModel::refreshProducerFromBin()
bool ClipModel::useTimewarpProducer(double speed, int extraSpace, Fun &undo, Fun &redo)
{
if (m_endlessResize || qFuzzyCompare(speed, m_speed)) {
if (m_endlessResize) {
// no timewarp for endless producers
return false;
}
if (qFuzzyCompare(speed, m_speed)) {
// nothing to do
return true;
}
std::function<bool(void)> local_undo = []() { return true; };
std::function<bool(void)> local_redo = []() { return true; };
double previousSpeed = getSpeed();
int old_in = getIn();
int old_out = getOut();
int oldDuration = old_out - old_in;
int newIn = old_in * previousSpeed / speed;
auto operation = useTimewarpProducer_lambda(speed);
Fun newInOperation = [this, newIn]() {
int duration = m_producer->get_playtime() - 1;
m_producer->set_in_and_out(newIn, newIn + duration);
return true;
};
Fun oldInOperation = [this, old_in]() {
int duration = m_producer->get_playtime() - 1;
m_producer->set_in_and_out(old_in, old_in + duration);
return true;
};
int oldDuration = getPlaytime();
int newDuration = int(double(oldDuration) * previousSpeed / speed);
if (extraSpace > 0 && (newDuration >= extraSpace)) {
newDuration = extraSpace;
}
auto operation = useTimewarpProducer_lambda(speed);
auto reverse = useTimewarpProducer_lambda(previousSpeed);
bool res = false;
// We have 2 different cases here to have a working undo. If the speed is slowed (<1), duration will be increased
// So the new out point might be outside current clip. So we perform the producer change, then resize
if (speed < previousSpeed) {
// Update to timewarp producer, duration is longer.
res = operation();
if (res) {
UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo);
// Adjust length
res = requestResize(old_in, old_out, oldDuration, oldDuration - newDuration, true, local_undo, local_redo, true);
if (res) {
// Set the new in point
newInOperation();
UPDATE_UNDO_REDO(newInOperation, oldInOperation, local_undo, local_redo);
}
}
} else {
// Speed is increased (>1), duration will be shortened
// So we first resize to the new duration, then change the producer
// Resize to shortened length
// Switch to timewarp producer
res = requestResize(old_in, old_out, oldDuration, oldDuration - newDuration, true, local_undo, local_redo, true);
if (res) {
UPDATE_UNDO_REDO(newInOperation, oldInOperation, local_undo, local_redo);
UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo);
// Update in point
newInOperation();
res = operation();
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;
}
if (!res) {
return false;
}
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
return false;
}
Fun ClipModel::useTimewarpProducer_lambda(double speed)
{
QWriteLocker locker(&m_lock);
return [speed, this]() {
m_speed = speed;
refreshProducerFromBin(m_currentState);
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});
ptr->notifyChange(ix, ix, TimelineModel::SpeedRole);
}
return true;
};
......
......@@ -153,8 +153,11 @@ protected:
*/
void setTimelineEffectsEnabled(bool enabled);
/* @brief This functions should be called when the producer of the binClip changes, to allow refresh */
void refreshProducerFromBin(PlaylistState::ClipState state);
/* @brief This functions should be called when the producer of the binClip changes, to allow refresh
* @param state corresponds to the state of the clip we want (audio or video)
* @param speed corresponds to the speed we need. Leave to 0 to keep current speed. Warning: this function doesn't notify the model. Unless you know what you are doing, better use uteTimewarProducer to change the speed
*/
void refreshProducerFromBin(PlaylistState::ClipState state, double speed = 0);
void refreshProducerFromBin();
/* @brief This functions replaces the current producer with a slowmotion one
......
......@@ -488,6 +488,11 @@ void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelInd
emit dataChanged(topleft, bottomright, roles);
}
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"<<i<<j<<k;
......
......@@ -87,6 +87,7 @@ public:
const QString getTrackFullName(int tid) const;
void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, bool start, bool duration, bool updateThumb) override;
void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, const QVector<int> &roles) override;
void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, int role) override;
void buildTrackCompositing();
const QString groupsData();
bool loadGroups(const QString &groupsData);
......
......@@ -2194,7 +2194,7 @@ void TimelineModel::requestClipUpdate(int clipId, const QVector<int> &roles)
notifyChange(modelIndex, modelIndex, roles);
}
bool TimelineModel::requestClipTimeWarp(int clipId, int trackId, int blankSpace, double speed, Fun &undo, Fun &redo)
bool TimelineModel::requestClipTimeWarp(int clipId, int blankSpace, double speed, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
if (qFuzzyCompare(speed, m_allClips[clipId]->getSpeed())) {
......@@ -2205,12 +2205,15 @@ bool TimelineModel::requestClipTimeWarp(int clipId, int trackId, int blankSpace,
int oldPos = getClipPosition(clipId);
// in order to make the producer change effective, we need to unplant / replant the clip in int track
bool success = true;
success = getTrackById(trackId)->requestClipDeletion(clipId, true, true, local_undo, local_redo);
int trackId = getClipTrackId(clipId);
if (trackId != -1) {
success = success && getTrackById(trackId)->requestClipDeletion(clipId, true, true, local_undo, local_redo);
}
if (success) {
success = m_allClips[clipId]->useTimewarpProducer(speed, blankSpace, local_undo, local_redo);
}
if (success) {
success = getTrackById(trackId)->requestClipInsertion(clipId, oldPos, true, true, local_undo, local_redo);
if (trackId != -1) {
success = success && getTrackById(trackId)->requestClipInsertion(clipId, oldPos, true, true, local_undo, local_redo);
}
if (!success) {
local_undo();
......@@ -2241,10 +2244,10 @@ bool TimelineModel::changeItemSpeed(int clipId, double speed)
blankSpace = qMin(blankSpace, partnerSpace);
}
}
result = requestClipTimeWarp(splitId, split_trackId, blankSpace, speed / 100.0, undo, redo);
result = requestClipTimeWarp(splitId, blankSpace, speed / 100.0, undo, redo);
}
if (result) {
result = requestClipTimeWarp(clipId, trackId, blankSpace, speed / 100.0, undo, redo);
result = requestClipTimeWarp(clipId, blankSpace, speed / 100.0, undo, redo);
} else {
pCore->displayMessage(i18n("Change speed failed"), ErrorMessage);
undo();
......
......@@ -527,7 +527,7 @@ public:
std::shared_ptr<EffectStackModel> getTrackEffectStackModel(int trackId);
/** @brief Add slowmotion effect to clip in timeline. */
bool requestClipTimeWarp(int clipId, int trackId, int blankSpace, double speed, Fun &undo, Fun &redo);
bool requestClipTimeWarp(int clipId, int blankSpace, double speed, Fun &undo, Fun &redo);
bool changeItemSpeed(int clipId, double speed);
void replugClip(int clipId);
/** @brief Refresh the tractor profile in case a change was requested. */
......@@ -678,6 +678,7 @@ protected:
virtual void _endInsertRows() = 0;
virtual void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, bool start, bool duration, bool updateThumb) = 0;
virtual void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, const QVector<int> &roles) = 0;
virtual void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, int role) = 0;
virtual QModelIndex makeClipIndexFromID(int) const = 0;
virtual QModelIndex makeCompositionIndexFromID(int) const = 0;
virtual QModelIndex makeTrackIndexFromID(int) const = 0;
......
############################
# Tests
############################
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -fexceptions")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -fexceptions")
string (REPLACE "-std=gnu++11" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
SET(Tests_SRCS
......@@ -15,6 +16,7 @@ SET(Tests_SRCS
tests/regressions.cpp
tests/snaptest.cpp
tests/test_utils.cpp
tests/timewarptest.cpp
tests/treetest.cpp
PARENT_SCOPE
)
......
This diff is collapsed.
This diff is collapsed.
......@@ -890,6 +890,7 @@ TEST_CASE("Undo and Redo", "[ClipModel]")
timeline->m_allClips[cid1]->m_endlessResize = false;
timeline->m_allClips[cid2]->m_endlessResize = false;
int length = 20;
int nclips = timeline->m_allClips.size();
......@@ -1432,6 +1433,7 @@ TEST_CASE("Undo and Redo", "[ClipModel]")
undoStack->redo();
state4();
}
}
TEST_CASE("Snapping", "[Snapping]")
......
File added
......@@ -20,19 +20,14 @@ QString createProducer(Mlt::Profile &prof, std::string color, std::shared_ptr<Pr
return binId;
}
QString createProducerWithSound(Mlt::Profile &prof, std::shared_ptr<ProjectItemModel> binModel, int length, bool limited)
QString createProducerWithSound(Mlt::Profile &prof, std::shared_ptr<ProjectItemModel> binModel)
{
std::shared_ptr<Mlt::Producer> producer = std::make_shared<Mlt::Producer>(prof, "blipflash:");
producer->set("length", length);
producer->set("out", length - 1);
std::shared_ptr<Mlt::Producer> producer = std::make_shared<Mlt::Producer>(prof, QFileInfo("../tests/small.mkv").absoluteFilePath().toStdString().c_str());
REQUIRE(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; };
REQUIRE(binModel->addItem(binClip, binModel->getRootFolder()->clipId(), undo, redo));
......
......@@ -45,7 +45,8 @@ using namespace fakeit;
Spy(Method(mock, _endInsertRows)); \
Spy(Method(mock, _endRemoveRows)); \
Spy(OverloadedMethod(mock, notifyChange, void(const QModelIndex &, const QModelIndex &, bool, bool, bool))); \
Spy(OverloadedMethod(mock, notifyChange, void(const QModelIndex &, const QModelIndex &, const QVector<int> &)));
Spy(OverloadedMethod(mock, notifyChange, void(const QModelIndex &, const QModelIndex &, const QVector<int> &))); \
Spy(OverloadedMethod(mock, notifyChange, void(const QModelIndex &, const QModelIndex &, int)));
#define NO_OTHERS() \
VerifyNoOtherInvocations(Method(timMock, _beginRemoveRows)); \
......@@ -73,6 +74,12 @@ using namespace fakeit;
Verify(OverloadedMethod(timMock, notifyChange, void(const QModelIndex &, const QModelIndex &, bool, bool, bool))).Exactly(times); \
NO_OTHERS();
#define CHECK_UPDATE(role) \
Verify(OverloadedMethod(timMock, notifyChange, void(const QModelIndex &, const QModelIndex &, int)) \
.Matching([](const QModelIndex &, const QModelIndex &, int c) { return c == role; })) \
.Exactly(1); \
NO_OTHERS();
QString createProducer(Mlt::Profile &prof, std::string color, std::shared_ptr<ProjectItemModel> binModel, int length = 20, bool limited = true);
QString createProducerWithSound(Mlt::Profile &prof, std::shared_ptr<ProjectItemModel> binModel, int length = 20, bool limited = true);
QString createProducerWithSound(Mlt::Profile &prof, std::shared_ptr<ProjectItemModel> binModel);
#include "test_utils.hpp"
using namespace fakeit;
Mlt::Profile profile_timewarp;
TEST_CASE("Test of timewarping", "[Timewarp]")
{
auto binModel = pCore->projectItemModel();
binModel->clean();
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
std::shared_ptr<MarkerListModel> guideModel = std::make_shared<MarkerListModel>(undoStack);
// Here we do some trickery to enable testing.
// We mock the project class so that the undoStack function returns our undoStack
Mock<ProjectManager> pmMock;
When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
ProjectManager &mocked = pmMock.get();
pCore->m_projectManager = &mocked;
// We also mock timeline object to spy few functions and mock others
TimelineItemModel tim(new Mlt::Profile(), undoStack);
Mock<TimelineItemModel> timMock(tim);
TimelineItemModel &tt = timMock.get();
auto timeline = std::shared_ptr<TimelineItemModel>(&timMock.get(), [](...) {});
TimelineItemModel::finishConstruct(timeline, guideModel);
RESET(timMock);
QString binId = createProducer(profile_timewarp, "red", binModel);
QString binId2 = createProducer(profile_timewarp, "blue", binModel);
QString binId3 = createProducerWithSound(profile_timewarp, binModel);
int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly);
int tid1 = TrackModel::construct(timeline);
int tid2 = TrackModel::construct(timeline);
int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
int cid3 = ClipModel::construct(timeline, binId3, -1, PlaylistState::VideoOnly);
timeline->m_allClips[cid1]->m_endlessResize = false;
timeline->m_allClips[cid2]->m_endlessResize = false;
timeline->m_allClips[cid3]->m_endlessResize = false;
SECTION("Timewarping orphan clip") {
int originalDuration = timeline->getClipPlaytime(cid3);
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getClipTrackId(cid3) == -1);
REQUIRE(timeline->getClipSpeed(cid3) == 1.);
std::function<bool(void)> undo = []() { return true; };
std::function<bool(void)> redo = []() { return true; };
REQUIRE(timeline->requestClipTimeWarp(cid3, 100000, 0.1, undo, redo));
CHECK_UPDATE(TimelineModel::SpeedRole);
REQUIRE(timeline->getClipSpeed(cid3) == 0.1);
INFO(timeline->m_allClips[cid3]->getIn());
INFO(timeline->m_allClips[cid3]->getOut());
REQUIRE(timeline->getClipPlaytime(cid3) == originalDuration / 0.1);
undo();
CHECK_UPDATE(TimelineModel::SpeedRole);
REQUIRE(timeline->getClipSpeed(cid3) == 1.);
REQUIRE(timeline->getClipPlaytime(cid3) == originalDuration);
redo();
CHECK_UPDATE(TimelineModel::SpeedRole);
REQUIRE(timeline->getClipSpeed(cid3) == 0.1);
REQUIRE(timeline->getClipPlaytime(cid3) == originalDuration / 0.1);
std::function<bool(void)> undo2 = []() { return true; };
std::function<bool(void)> redo2 = []() { return true; };
REQUIRE(timeline->requestClipTimeWarp(cid3, 100000, 1.2, undo2, redo2));
CHECK_UPDATE(TimelineModel::SpeedRole);
REQUIRE(timeline->getClipSpeed(cid3) == 1.2);
REQUIRE(timeline->getClipPlaytime(cid3) == originalDuration / 1.2);
undo2();
CHECK_UPDATE(TimelineModel::SpeedRole);
REQUIRE(timeline->getClipSpeed(cid3) == 1.);
REQUIRE(timeline->getClipPlaytime(cid3) == originalDuration);
// Finally, we test that setting a very high speed isn't possible.
// Specifically, it must be impossible to make the clip shorter than one frame
int curLength = timeline->getClipPlaytime(cid3);
// This is the limit, should work
REQUIRE(timeline->requestClipTimeWarp(cid3, 100000, curLength, undo2, redo2));
CHECK_UPDATE(TimelineModel::SpeedRole);
REQUIRE(timeline->getClipSpeed(cid3) == curLength);
REQUIRE(timeline->getClipPlaytime(cid3) == 1);
REQUIRE_FALSE(true);
}
}
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