Commit 1350e856 authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

First version of timeline clip time remapping. To test, select time remap from...

First version of timeline clip time remapping. To test, select time remap from timeline context menu and "time remapping" widget to manage keyframes
parent 759ca9bb
Pipeline #69291 canceled with stage
......@@ -722,12 +722,12 @@ int ProjectClip::getRecordTime()
return 0;
}
std::shared_ptr<Mlt::Producer> ProjectClip::getTimelineProducer(int trackId, int clipId, PlaylistState::ClipState state, int audioStream, double speed, bool secondPlaylist)
std::shared_ptr<Mlt::Producer> ProjectClip::getTimelineProducer(int trackId, int clipId, PlaylistState::ClipState state, int audioStream, double speed, bool secondPlaylist, bool timeremap)
{
if (!m_masterProducer) {
return nullptr;
}
if (qFuzzyCompare(speed, 1.0)) {
if (qFuzzyCompare(speed, 1.0) && !timeremap) {
// we are requesting a normal speed producer
bool byPassTrackProducer = false;
if (trackId == -1 && (state != PlaylistState::AudioOnly || audioStream == m_masterProducer->get_int("audio_index"))) {
......@@ -814,7 +814,7 @@ std::shared_ptr<Mlt::Producer> ProjectClip::getTimelineProducer(int trackId, int
if (m_timewarpProducers.count(clipId) > 0) {
// remove in all cases, we add it unconditionally anyways
m_effectStack->removeService(m_timewarpProducers[clipId]);
if (qFuzzyCompare(m_timewarpProducers[clipId]->get_double("warp_speed"), speed)) {
if (qFuzzyCompare(m_timewarpProducers[clipId]->get_double("warp_speed"), speed) || timeremap) {
// the producer we have is good, use it !
warpProducer = m_timewarpProducers[clipId];
qDebug() << "Reusing producer!";
......@@ -827,16 +827,23 @@ std::shared_ptr<Mlt::Producer> ProjectClip::getTimelineProducer(int trackId, int
if (resource.isEmpty() || resource == QLatin1String("<producer>")) {
resource = m_service;
}
QString url = QString("timewarp:%1:%2").arg(QString::fromStdString(std::to_string(speed)), resource);
warpProducer.reset(new Mlt::Producer(*originalProducer()->profile(), url.toUtf8().constData()));
qDebug() << "new producer: " << url;
qDebug() << "warp LENGTH before" << warpProducer->get_length();
int original_length = originalProducer()->get_length();
if (timeremap) {
Mlt::Chain *chain = new Mlt::Chain(*originalProducer()->profile(), resource.toUtf8().constData());
Mlt::Link link("timeremap");
chain->attach(link);
warpProducer.reset(chain);
} else {
QString url = QString("timewarp:%1:%2").arg(QString::fromStdString(std::to_string(speed)), resource);
warpProducer.reset(new Mlt::Producer(*originalProducer()->profile(), url.toUtf8().constData()));
int original_length = originalProducer()->get_length();
warpProducer->set("length", int(original_length / std::abs(speed) + 0.5));
qDebug() << "new producer: " << url;
qDebug() << "warp LENGTH before" << warpProducer->get_length();
}
// this is a workaround to cope with Mlt erroneous rounding
Mlt::Properties original(m_masterProducer->get_properties());
Mlt::Properties cloneProps(warpProducer->get_properties());
cloneProps.pass_list(original, ClipController::getPassPropertiesList(false));
warpProducer->set("length", int(original_length / std::abs(speed) + 0.5));
warpProducer->set("audio_index", audioStream);
}
......@@ -874,6 +881,8 @@ std::pair<std::shared_ptr<Mlt::Producer>, bool> ProjectClip::giveMasterAndGetTim
if (QString::fromUtf8(master->parent().get("mlt_service")) == QLatin1String("timewarp")) {
speed = master->parent().get_double("warp_speed");
timeWarp = true;
} else if (master->parent().type() == mlt_service_chain_type) {
timeWarp = true;
}
if (master->parent().get_int("_loaded") == 1) {
// we already have a clip that shares the same master
......
......@@ -194,7 +194,7 @@ public:
/** @brief This function returns a cut to the master producer associated to the timeline clip with given ID.
Each clip must have a different master producer (see comment of the class)
*/
std::shared_ptr<Mlt::Producer> getTimelineProducer(int trackId, int clipId, PlaylistState::ClipState st, int audioStream = -1, double speed = 1.0, bool secondPlaylist = false);
std::shared_ptr<Mlt::Producer> getTimelineProducer(int trackId, int clipId, PlaylistState::ClipState st, int audioStream = -1, double speed = 1.0, bool secondPlaylist = false, bool timeremap = false);
/** @brief This function should only be used at loading. It takes a producer that was read from mlt, and checks whether the master producer is already in
use. If yes, then we must create a new one, because of the mixing bug. In any case, we return a cut of the master that can be used in the timeline The
......
......@@ -25,6 +25,10 @@
#include "bin/projectclip.h"
#include "project/projectmanager.h"
#include "monitor/monitor.h"
#include "profiles/profilemodel.hpp"
#include "mainwindow.h"
#include "timeline2/view/timelinewidget.h"
#include "timeline2/view/timelinecontroller.h"
#include "macros.hpp"
#include "kdenlive_debug.h"
......@@ -35,7 +39,6 @@
#include <KColorScheme>
#include "klocalizedstring.h"
#include <profiles/profilemodel.hpp>
RemapView::RemapView(QWidget *parent)
: QWidget(parent)
......@@ -47,6 +50,7 @@ RemapView::RemapView(QWidget *parent)
, m_zoomHandle(0,1)
, m_moveKeyframeMode(NoMove)
, m_clip(nullptr)
, m_service(nullptr)
, m_clickPoint(-1)
, m_moveNext(true)
{
......@@ -91,9 +95,31 @@ void RemapView::updateOutPos(int pos)
}
}
void RemapView::setDuration(std::shared_ptr<ProjectClip> clip, int duration)
int RemapView::remapDuration() const
{
int maxDuration = 0;
QMapIterator<int, int> i(m_keyframes);
while (i.hasNext()) {
i.next();
if (i.value() > maxDuration) {
maxDuration = i.value();
}
}
return maxDuration;
}
void RemapView::setBinClipDuration(std::shared_ptr<ProjectClip> clip, int duration)
{
m_clip = clip;
m_service = clip->originalProducer();
m_duration = duration;
m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
}
void RemapView::setDuration(std::shared_ptr<Mlt::Producer> service, int duration)
{
m_clip = nullptr;
m_service = service;
m_duration = duration;
m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1};
}
......@@ -107,10 +133,13 @@ void RemapView::loadKeyframes(const QString &mapData)
} else {
QStringList str = mapData.split(QLatin1Char(';'));
for (auto &s : str) {
int pos = m_clip->originalProducer()->time_to_frames(s.section(QLatin1Char('='), 0, 0).toUtf8().constData());
int pos = m_service->time_to_frames(s.section(QLatin1Char('='), 0, 0).toUtf8().constData());
int val = GenTime(s.section(QLatin1Char('='), 1).toDouble()).frames(pCore->getCurrentFps());
m_keyframes.insert(val, pos);
m_duration = qMax(m_duration, pos);
m_duration = qMax(m_duration, val);
}
emit updateMaxDuration(m_duration);
if (m_keyframes.contains(m_currentKeyframe.first)) {
emit atKeyframe(true);
std::pair<double,double>speeds = getSpeed(m_currentKeyframe);
......@@ -631,16 +660,15 @@ const QString RemapView::getKeyframesData() const
QMapIterator<int, int> i(m_keyframes);
while (i.hasNext()) {
i.next();
result << QString("%1=%2").arg(m_clip->originalProducer()->frames_to_time(i.value(), mlt_time_clock)).arg(GenTime(i.key(), pCore->getCurrentFps()).seconds());
result << QString("%1=%2").arg(m_service->frames_to_time(i.value(), mlt_time_clock)).arg(GenTime(i.key(), pCore->getCurrentFps()).seconds());
}
return result.join(QLatin1Char(';'));
}
void RemapView::reloadProducer()
{
qDebug()<<"==== RELOAD:";
if (!m_clip->clipUrl().endsWith(QLatin1String(".mlt"))) {
qDebug()<<"==== shit is not a playlist clip, aborting";
if (!m_clip || !m_clip->clipUrl().endsWith(QLatin1String(".mlt"))) {
qDebug()<<"==== this is not a playlist clip, aborting";
return;
}
Mlt::Consumer c(pCore->getCurrentProfile()->profile(), "xml", m_clip->clipUrl().toUtf8().constData());
......@@ -897,6 +925,7 @@ void RemapView::paintEvent(QPaintEvent *event)
TimeRemap::TimeRemap(QWidget *parent)
: QWidget(parent)
, m_cid(-1)
{
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
setupUi(this);
......@@ -946,11 +975,86 @@ TimeRemap::TimeRemap(QWidget *parent)
connect(button_next, &QToolButton::clicked, m_view, &RemapView::goNext);
connect(button_prev, &QToolButton::clicked, m_view, &RemapView::goPrev);
connect(move_next, &QCheckBox::toggled, m_view, &RemapView::toggleMoveNext);
connect(m_view, &RemapView::updateMaxDuration, [this](int duration) {
m_out->setRange(m_out->minimum(), duration);
m_in->setRange(m_in->minimum(), duration);
});
setEnabled(false);
}
void TimeRemap::selectedClip(int cid, int splitId)
{
if (cid == -1 && cid == m_cid) {
return;
}
QObject::disconnect( m_seekConnection1 );
QObject::disconnect( m_seekConnection2 );
m_cid = cid;
m_splitId = splitId;
if (cid == -1) {
m_view->setDuration(nullptr, 0);
setEnabled(false);
return;
}
m_remapLink.reset();
bool keyframesLoaded = false;
std::shared_ptr<TimelineItemModel> model = pCore->window()->getCurrentTimeline()->controller()->getModel();
int min = pCore->getItemIn({ObjectType::TimelineClip,cid});
m_lastLength = pCore->getItemDuration({ObjectType::TimelineClip,cid});
int max = min + m_lastLength;
m_startPos = pCore->getItemPosition({ObjectType::TimelineClip,cid});
m_in->setRange(min, max);
m_out->setRange(min, max);
std::shared_ptr<Mlt::Producer> prod = model->getClipProducer(cid);
m_view->setDuration(prod, max - min);
qDebug()<<"===== GOT PRODUCER TYPE: "<<prod->parent().type();
if (prod->parent().type() == mlt_service_chain_type) {
Mlt::Chain fromChain(prod->parent());
int count = fromChain.link_count();
for (int i = 0; i < count; i++) {
QScopedPointer<Mlt::Link> fromLink(fromChain.link(i));
if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) {
if (fromLink->get("mlt_service") == QLatin1String("timeremap")) {
// Found a timeremap effect, read params
m_remapLink = std::make_shared<Mlt::Link>(fromChain.link(i)->get_link());
if (m_splitId > -1) {
std::shared_ptr<Mlt::Producer> prod2 = model->getClipProducer(m_splitId);
if (prod2->parent().type() == mlt_service_chain_type) {
Mlt::Chain fromChain2(prod2->parent());
count = fromChain2.link_count();
for (int j = 0; j < count; j++) {
QScopedPointer<Mlt::Link> fromLink2(fromChain2.link(j));
if (fromLink2 && fromLink2->is_valid() && fromLink2->get("mlt_service")) {
if (fromLink2->get("mlt_service") == QLatin1String("timeremap")) {
m_splitRemap = std::make_shared<Mlt::Link>(fromChain2.link(j)->get_link());
}
}
}
}
}
QString mapData(fromLink->get("map"));
qDebug()<<"==== LOADING KEYFRAMES FROM MAP: "<<mapData;
m_view->loadKeyframes(mapData);
keyframesLoaded = true;
setEnabled(true);
break;
}
}
}
}
m_seekConnection1 = connect(m_view, &RemapView::seekToPos, [this](int pos) {
pCore->getMonitor(Kdenlive::ProjectMonitor)->requestSeek(pos + m_startPos);
});
m_seekConnection2 = connect(pCore->getMonitor(Kdenlive::ProjectMonitor), &Monitor::seekPosition, [this](int pos) {
m_view->slotSetPosition(pos - m_startPos);
});
}
void TimeRemap::setClip(std::shared_ptr<ProjectClip> clip, int in, int out)
{
QObject::disconnect( m_seekConnection1 );
QObject::disconnect( m_seekConnection2 );
m_cid = -1;
if (!clip->statusReady() || clip->clipType() != ClipType::Playlist) {
qDebug()<<"===== CLIP NOT READY; TYPE; "<<clip->clipType();
m_view->setDuration(nullptr, 0);
......@@ -964,7 +1068,8 @@ void TimeRemap::setClip(std::shared_ptr<ProjectClip> clip, int in, int out)
int max = out == -1 ? clip->getFramePlaytime() : out;
m_in->setRange(min, max);
m_out->setRange(min, max);
m_view->setDuration(clip, max - min);
m_startPos = 0;
m_view->setBinClipDuration(clip, max - min);
if (clip->clipType() == ClipType::Playlist) {
Mlt::Service service(clip->originalProducer()->producer()->get_service());
qDebug()<<"==== producer type: "<<service.type();
......@@ -1031,11 +1136,10 @@ void TimeRemap::setClip(std::shared_ptr<ProjectClip> clip, int in, int out)
if (!keyframesLoaded) {
m_view->loadKeyframes(QString());
}
connect(m_view, &RemapView::seekToPos, pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::requestSeek, Qt::UniqueConnection);
connect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekPosition, m_view, &RemapView::slotSetPosition);
m_seekConnection1 = connect(m_view, &RemapView::seekToPos, pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::requestSeek, Qt::UniqueConnection);
m_seekConnection2 = connect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekPosition, m_view, &RemapView::slotSetPosition);
setEnabled(m_remapLink != nullptr);
} else {
disconnect(m_view, &RemapView::seekToPos, pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::requestSeek);
setEnabled(false);
}
}
......@@ -1043,11 +1147,20 @@ void TimeRemap::setClip(std::shared_ptr<ProjectClip> clip, int in, int out)
void TimeRemap::updateKeyframes()
{
QString kfData = m_view->getKeyframesData();
qDebug()<<" ==== RES: "<< kfData;
if (m_remapLink) {
m_remapLink->set("map", kfData.toUtf8().constData());
m_view->timer.start();
qDebug()<<"==== SETTING REMAP LINK!!!!!!!!!!!!!";
if (m_splitRemap) {
m_splitRemap->set("map", kfData.toUtf8().constData());
}
if (m_cid == -1) {
// This is a playlist clip
m_view->timer.start();
} else if (m_lastLength != m_view->remapDuration()) {
// Resize timeline clip
m_lastLength = m_view->remapDuration();
std::shared_ptr<TimelineItemModel> model = pCore->window()->getCurrentTimeline()->controller()->getModel();
model->requestItemResize(m_cid, m_lastLength, true, true, -1, false);
}
}
}
......
......@@ -46,9 +46,11 @@ class RemapView : public QWidget
public:
explicit RemapView(QWidget *parent = nullptr);
void setDuration(std::shared_ptr<ProjectClip> clip, int duration);
void setBinClipDuration(std::shared_ptr<ProjectClip> clip, int duration);
void setDuration(std::shared_ptr<Mlt::Producer> service, int duration);
void loadKeyframes(const QString &mapData);
const QString getKeyframesData() const;
int remapDuration() const;
QTimer timer;
protected:
......@@ -83,6 +85,7 @@ private:
QMap<int, int>m_keyframes;
QMap<int, int>m_keyframesOrigin;
std::shared_ptr<ProjectClip> m_clip;
std::shared_ptr<Mlt::Producer> m_service;
/** @brief The zoom factor (start, end - between 0 and 1) */
QPointF m_zoomHandle;
QPointF m_lastZoomHandle;
......@@ -112,6 +115,7 @@ signals:
/** When the cursor position changes inform parent if we are on a keyframe or not. */
void atKeyframe(bool);
void updateKeyframes();
void updateMaxDuration(int duration);
};
/**
......@@ -126,6 +130,7 @@ class TimeRemap : public QWidget, public Ui::TimeRemap_UI
public:
explicit TimeRemap(QWidget *parent = nullptr);
~TimeRemap() override;
void selectedClip(int cid, int splitId = -1);
void setClip(std::shared_ptr<ProjectClip> clip, int in = -1, int out = -1);
private slots:
......@@ -133,9 +138,16 @@ private slots:
private:
std::shared_ptr<Mlt::Link> m_remapLink;
std::shared_ptr<Mlt::Link> m_splitRemap;
TimecodeDisplay *m_in;
TimecodeDisplay *m_out;
RemapView *m_view;
int m_lastLength;
int m_startPos;
int m_cid;
int m_splitId;
QMetaObject::Connection m_seekConnection1;
QMetaObject::Connection m_seekConnection2;
};
......
......@@ -598,6 +598,7 @@ void MainWindow::init(const QString &mltPath)
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("set_audio_align_ref")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("align_audio")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("edit_item_speed")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("edit_item_remap")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("clip_in_project_tree")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("cut_timeline_clip")));
......@@ -1643,6 +1644,10 @@ void MainWindow::setupActions()
act = addAction(QStringLiteral("edit_item_speed"), i18n("Change Speed"), this, SLOT(slotEditItemSpeed()),
QIcon::fromTheme(QStringLiteral("speedometer")), QKeySequence(), clipActionCategory);
act->setEnabled(false);
act = addAction(QStringLiteral("edit_item_remap"), i18n("Time Remap"), this, SLOT(slotRemapItemTime()),
QIcon::fromTheme(QStringLiteral("speedometer")), QKeySequence(), clipActionCategory);
act->setEnabled(false);
act = addAction(QStringLiteral("clip_in_project_tree"), i18n("Clip in Project Bin"), this, SLOT(slotClipInProjectTree()),
QIcon::fromTheme(QStringLiteral("find-location")), QKeySequence(), clipActionCategory);
......@@ -4247,6 +4252,14 @@ void MainWindow::resetTimelineTracks()
}
}
void MainWindow::slotRemapItemTime()
{
TimelineWidget *current = getCurrentTimeline();
if (current) {
current->controller()->remapItemTime(-1, -1);
}
}
void MainWindow::slotEditItemSpeed()
{
TimelineWidget *current = getCurrentTimeline();
......
......@@ -515,6 +515,7 @@ private slots:
/** @brief Set timeline toolbar icon size. */
void setTimelineToolbarIconSize(QAction *a);
void slotEditItemSpeed();
void slotRemapItemTime();
/** @brief Request adjust of timeline track height */
void resetTimelineTracks();
/** @brief Set keyboard grabbing on current timeline item */
......
......@@ -369,6 +369,12 @@ Mlt::Producer *ClipModel::service() const
return m_producer.get();
}
bool ClipModel::isChain() const
{
READ_LOCK();
return m_producer->parent().type() == mlt_service_chain_type;
}
std::shared_ptr<Mlt::Producer> ClipModel::getProducer()
{
READ_LOCK();
......@@ -467,7 +473,7 @@ bool ClipModel::isAudioOnly() const
return m_currentState == PlaylistState::AudioOnly;
}
void ClipModel::refreshProducerFromBin(int trackId, PlaylistState::ClipState state, int stream, double speed, bool hasPitch, bool secondPlaylist)
void ClipModel::refreshProducerFromBin(int trackId, PlaylistState::ClipState state, int stream, double speed, bool hasPitch, bool secondPlaylist, bool timeremap)
{
// 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.
......@@ -483,7 +489,7 @@ void ClipModel::refreshProducerFromBin(int trackId, PlaylistState::ClipState sta
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(trackId, m_id, state, stream, m_speed, secondPlaylist);
std::shared_ptr<Mlt::Producer> binProducer = binClip->getTimelineProducer(trackId, m_id, state, stream, m_speed, secondPlaylist, timeremap);
m_producer = std::move(binProducer);
m_producer->set_in_and_out(in, out);
if (hasPitch) {
......@@ -513,7 +519,35 @@ void ClipModel::refreshProducerFromBin(int trackId)
hasPitch = m_producer->parent().get_int("warp_pitch") == 1;
}
int stream = m_producer->parent().get_int("audio_index");
refreshProducerFromBin(trackId, m_currentState, stream, 0, hasPitch, m_subPlaylistIndex == 1);
refreshProducerFromBin(trackId, m_currentState, stream, 0, hasPitch, m_subPlaylistIndex == 1, isChain());
}
bool ClipModel::useTimeRemapProducer(Fun &undo, Fun &redo)
{
if (m_endlessResize) {
// no timewarp for endless producers
return false;
}
std::function<bool(void)> local_undo = []() { return true; };
std::function<bool(void)> local_redo = []() { return true; };
int audioStream = getIntProperty(QStringLiteral("audio_index"));
auto operation = useTimeRemapProducer_lambda(true, audioStream);
auto reverse = useTimeRemapProducer_lambda(false, audioStream);
if (operation()) {
UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo);
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
return false;
}
Fun ClipModel::useTimeRemapProducer_lambda(bool enable, int audioStream)
{
QWriteLocker locker(&m_lock);
return [enable, audioStream, this]() {
refreshProducerFromBin(m_currentTrackId, m_currentState, audioStream, 0, false, false, enable);
return true;
};
}
bool ClipModel::useTimewarpProducer(double speed, bool pitchCompensate, bool changeDuration, Fun &undo, Fun &redo)
......
......@@ -85,6 +85,9 @@ public:
/** @brief Returns true if the clip can be converted to an audio clip */
bool canBeAudio() const;
/** @brief Returns true if the producer is embedded in a chain (for use with timeremap) */
bool isChain() const;
/** @brief Returns a comma separated list of effect names */
const QString effectNames() const;
......@@ -187,7 +190,7 @@ protected:
* @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 useTimewarProducer to change the speed
*/
void refreshProducerFromBin(int trackId, PlaylistState::ClipState state, int stream, double speed, bool hasPitch, bool secondPlaylist = false);
void refreshProducerFromBin(int trackId, PlaylistState::ClipState state, int stream, double speed, bool hasPitch, bool secondPlaylist = false, bool timeremap = false);
void refreshProducerFromBin(int trackId);
/** @brief This functions replaces the current producer with a slowmotion one
......@@ -196,6 +199,10 @@ protected:
bool useTimewarpProducer(double speed, bool pitchCompensate, bool changeDuration, Fun &undo, Fun &redo);
/** @brief Lambda that merely changes the speed (in and out are untouched) */
Fun useTimewarpProducer_lambda(double speed, int stream, bool pitchCompensate);
bool useTimeRemapProducer(Fun &undo, Fun &redo);
/** @brief Lambda that merely changes the speed (in and out are untouched) */
Fun useTimeRemapProducer_lambda(bool enable, int audioStream);
/** @brief Returns the marker model associated with this clip */
std::shared_ptr<MarkerListModel> getMarkerModel() const;
......
......@@ -4780,6 +4780,59 @@ bool TimelineModel::requestClipTimeWarp(int clipId, double speed, bool pitchComp
return success;
}
bool TimelineModel::requestClipTimeRemap(int clipId)
{
if (!m_allClips[clipId]->isChain()) {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
int splitId = m_groups->getSplitPartner(clipId);
bool result = true;
if (splitId > -1) {
result = requestClipTimeRemap(splitId, undo, redo);
}
result = result && requestClipTimeRemap(clipId, undo, redo);
if (result) {
PUSH_UNDO(undo, redo, i18n("Enable time remap"));
return true;
} else {
return false;
}
} else return true;
}
std::shared_ptr<Mlt::Producer> TimelineModel::getClipProducer(int clipId)
{
Q_ASSERT(m_allClips.count(clipId) > 0);
return m_allClips[clipId]->getProducer();
}
bool TimelineModel::requestClipTimeRemap(int clipId, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
std::function<bool(void)> local_undo = []() { return true; };
std::function<bool(void)> local_redo = []() { return true; };
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;
int trackId = getClipTrackId(clipId);
if (trackId != -1) {
success = success && getTrackById(trackId)->requestClipDeletion(clipId, true, true, local_undo, local_redo, false, false);
}
if (success) {
qDebug()<<"==== REMAPING CLIP: "<<clipId;
success = m_allClips[clipId]->useTimeRemapProducer(local_undo, local_redo);
}
if (trackId != -1) {
success = success && getTrackById(trackId)->requestClipInsertion(clipId, oldPos, true, true, local_undo, local_redo);
}
if (!success) {
local_undo();
return false;
}
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return success;
}
bool TimelineModel::requestClipTimeWarp(int clipId, double speed, bool pitchCompensate, bool changeDuration)
{
QWriteLocker locker(&m_lock);
......
......@@ -690,6 +690,9 @@ public:
/** @brief Same function as above, but doesn't check for paired audio and accumulate undo/redo
*/
bool requestClipTimeWarp(int clipId, double speed, bool pitchCompensate, bool changeDuration, Fun &undo, Fun &redo);
bool requestClipTimeRemap(int clipId);
bool requestClipTimeRemap(int clipId, Fun &undo, Fun &redo);
std::shared_ptr<Mlt::Producer> getClipProducer(int clipId);
void replugClip(int clipId);
......
......@@ -52,6 +52,7 @@
#include "transitions/transitionsrepository.hpp"
#include "audiomixer/mixermanager.hpp"
#include "ui_import_subtitle_ui.h"
#include "dialogs/timeremap.h"
#include <KColorScheme>
#include <KMessageBox>
......@@ -2193,6 +2194,20 @@ void TimelineController::invalidateZone(int in, int out)
m_timelinePreview->invalidatePreview(in, out == -1 ? m_duration : out);
}
void TimelineController::remapItemTime(int clipId, double speed)
{
if (clipId == -1) {
clipId = getMainSelectedClip();
}
if (clipId == -1 || !m_model->isClip(clipId)) {
pCore->displayMessage(i18n("No item to edit"), ErrorMessage, 500);
return;