diff --git a/src/timeline2/model/clipmodel.cpp b/src/timeline2/model/clipmodel.cpp index a39f6f49a..e0a160ba8 100644 --- a/src/timeline2/model/clipmodel.cpp +++ b/src/timeline2/model/clipmodel.cpp @@ -1,510 +1,469 @@ /*************************************************************************** * 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 // this can be deleted #include "bin/model/markerlistmodel.hpp" #include "gentime.h" #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_producer->set("kdenlive:id", binClipId.toUtf8().constData()); m_producer->set("_kdenlive_cid", m_id); std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); if (binClip) { m_endlessResize = !binClip->hasLimitedDuration(); } else { m_endlessResize = false; } } int ClipModel::construct(const std::shared_ptr &parent, const QString &binClipId, int id, PlaylistState::ClipState state) { 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, 1.); std::shared_ptr clip(new ClipModel(parent, cutProducer, binClipId, id, state)); clip->setClipState(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 = (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); double speed = 1.0; if (QString::fromUtf8(producer->get("mlt_service")) == QLatin1String("timewarp")) { speed = producer->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->m_effectStack->importEffects(producer, result.second); clip->setClipState(state); parent->registerClip(clip); return id; } void ClipModel::registerClipToBin() { 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->registerTimelineClip(m_parent, m_id); } void ClipModel::deregisterClipToBin() { std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); binClip->deregisterTimelineClip(m_id); } ClipModel::~ClipModel() {} bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo) { qDebug() << "++++++++++ PERFORMAING CLIP RESIZE===="; 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(); if (!m_endlessResize && (size <= 0 || size > m_producer->get_length())) { return false; } int oldDuration = getPlaytime(); int delta = oldDuration - 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; if (m_endlessResize) { 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); } } 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 auto ptr = m_parent.lock(); if (m_currentTrackId != -1 && ptr) { 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() << "// ADJUSTING EFFECT LENGTH, LOGUNDO " << logUndo << ", " << old_in << "/" << inPoint << ", " << m_producer->get_playtime(); - if (logUndo) adjustEffectLength(right, old_in, inPoint, oldDuration, m_producer->get_playtime(), reverse, operation, logUndo); + if (logUndo) { + adjustEffectLength(right, old_in, inPoint, oldDuration, m_producer->get_playtime(), 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(); } 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); 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)); return true; } bool ClipModel::importEffects(std::shared_ptr stackModel) { QWriteLocker locker(&m_lock); m_effectStack->importEffects(stackModel); return true; } bool ClipModel::importEffects(std::weak_ptr service) { QWriteLocker locker(&m_lock); m_effectStack->importEffects(service); 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, Fun &undo, Fun &redo, bool logUndo) { QWriteLocker locker(&m_lock); return m_effectStack->adjustStackLength(adjustFromEnd, oldIn, oldDuration, newIn, duration, 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]() { return m_effectStack->adjustFadeLength(duration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"), audioEnabled(), !isAudioOnly()); }; if (operation() && originalDuration > 0) { Fun reverse = [this, originalDuration, effectName]() { return m_effectStack->adjustFadeLength(originalDuration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"), audioEnabled(), !isAudioOnly()); }; 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) { QWriteLocker locker(&m_lock); - if (getProperty("mlt_service") == QLatin1String("timewarp")) { - // slowmotion producer, keep it - int space = -1; - if (m_currentTrackId != -1) { - if (auto ptr = m_parent.lock()) { - space = ptr->getTrackById(m_currentTrackId)->getBlankSizeNearClip(m_id, true); - } else { - qDebug() << "Error : Moving clip failed because parent timeline is not available anymore"; - Q_ASSERT(false); - } - } - std::function local_undo = []() { return true; }; - std::function local_redo = []() { return true; }; - useTimewarpProducer(m_producer->get_double("warp_speed"), space, local_undo, local_redo); - return; - } int in = getIn(); int out = getOut(); std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); - std::shared_ptr binProducer = binClip->getTimelineProducer(m_id, state); + 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->AbstractProjectItem::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, int extraSpace, Fun &undo, Fun &redo) { + if (m_endlessResize) { + // no timewarp for endless producers + return false; + } + std::function local_undo = []() { return true; }; + std::function local_redo = []() { return true; }; double previousSpeed = getSpeed(); - auto operation = useTimewarpProducer_lambda(speed, extraSpace); + int new_in = int(double(getIn()) * previousSpeed / speed); + int new_out = int(double(getOut()) * previousSpeed / speed); + auto operation = useTimewarpProducer_lambda(speed); if (operation()) { - auto reverse = useTimewarpProducer_lambda(previousSpeed, extraSpace); - UPDATE_UNDO_REDO(operation, reverse, undo, redo); + auto reverse = useTimewarpProducer_lambda(previousSpeed); + UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo); + bool res = requestResize(new_out - new_in + 1, true, local_undo, local_redo, true); + if (!res) { + bool undone = local_undo(); + Q_ASSERT(undone); + return false; + } + UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } return false; } -Fun ClipModel::useTimewarpProducer_lambda(double speed, int extraSpace) +Fun ClipModel::useTimewarpProducer_lambda(double speed) { - Q_UNUSED(extraSpace) - QWriteLocker locker(&m_lock); - // TODO: disable timewarp on color clips - int in = getIn(); - int out = getOut(); - int warp_in; - int warp_out; - if (getProperty("mlt_service") == QLatin1String("timewarp")) { - // slowmotion producer, get current speed - warp_in = m_producer->get_int("warp_in"); - warp_out = m_producer->get_int("warp_out"); - } else { - // store original in/out - warp_in = in; - warp_out = out; - } - in = warp_in / speed; - out = qMin((int)(warp_out / speed), extraSpace); - std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); - std::shared_ptr originalProducer = binClip->originalProducer(); - bool limitedDuration = binClip->hasLimitedDuration(); - - return [originalProducer, speed, in, out, warp_in, warp_out, limitedDuration, this]() { - if (qFuzzyCompare(speed, 1.0)) { - m_producer.reset(originalProducer->cut(in, out)); - } else { - QLocale locale; - QString resource = QString("timewarp:%1:%2").arg(locale.toString(speed)).arg(originalProducer->get("resource")); - std::shared_ptr warpProducer(new Mlt::Producer(*m_producer->profile(), resource.toUtf8().constData())); - // Make sure we use a cut so that the source producer in/out are not modified - m_producer.reset(warpProducer->cut(0, warpProducer->get_length())); - setInOut(in, out); - } - // replant effect stack in updated service - m_effectStack->resetService(m_producer); - m_producer->set("kdenlive:id", m_binClipId.toUtf8().constData()); - m_producer->set("_kdenlive_cid", m_id); - m_producer->set("warp_in", warp_in); - m_producer->set("warp_out", warp_out); - m_endlessResize = !limitedDuration; + return [speed, this]() { + m_speed = speed; + refreshProducerFromBin(m_currentState); return true; }; - return []() { return false; }; } 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::fadeIn() const { return m_effectStack->getFadePosition(true); } int ClipModel::fadeOut() const { return m_effectStack->getFadePosition(false); } double ClipModel::getSpeed() const { - if (getProperty("mlt_service") == QLatin1String("timewarp")) { - // slowmotion producer, get current speed - return m_producer->parent().get_double("warp_speed"); - } - return 1.0; + 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); } bool ClipModel::setClipState(PlaylistState::ClipState state) { QWriteLocker locker(&m_lock); refreshProducerFromBin(state); m_currentState = state; return true; } PlaylistState::ClipState ClipModel::clipState() const { READ_LOCK(); return m_currentState; /* if (service()->parent().get_int("audio_index") == -1) { if (service()->parent().get_int("video_index") == -1) { return PlaylistState::Disabled; } else { return PlaylistState::VideoOnly; } } else if (service()->parent().get_int("video_index") == -1) { return PlaylistState::AudioOnly; } return PlaylistState::Original; */ } 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"); } diff --git a/src/timeline2/model/clipmodel.hpp b/src/timeline2/model/clipmodel.hpp index efbb3825b..ca24b57cb 100644 --- a/src/timeline2/model/clipmodel.hpp +++ b/src/timeline2/model/clipmodel.hpp @@ -1,169 +1,173 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef CLIPMODEL_H #define CLIPMODEL_H #include "moveableItem.hpp" #include "undohelper.hpp" #include #include namespace Mlt { class Producer; } class EffectStackModel; class MarkerListModel; class ProjectClip; class TimelineModel; class TrackModel; class KeyframeModel; /* @brief This class represents a Clip object, as viewed by the backend. In general, the Gui associated with it will send modification queries (such as resize or move), and this class authorize them or not depending on the validity of the modifications */ class ClipModel : public MoveableItem { ClipModel() = delete; protected: /* This constructor is not meant to be called, call the static construct instead */ ClipModel(std::shared_ptr parent, std::shared_ptr prod, const QString &binClipId, int id, PlaylistState::ClipState state, double speed = 1.); public: ~ClipModel(); /* @brief Creates a clip, which references itself to the parent timeline Returns the (unique) id of the created clip @param parent is a pointer to the timeline @param binClip is the id of the bin clip associated @param id Requested id of the clip. Automatic if -1 */ static int construct(const std::shared_ptr &parent, const QString &binClipId, int id, PlaylistState::ClipState state); /* @brief Creates a clip, which references itself to the parent timeline Returns the (unique) id of the created clip This variants assumes a producer is already known, which should typically happen only at loading time. Note that there is no guarantee that this producer is actually going to be used. It might be discarded. */ static int construct(const std::shared_ptr &parent, const QString &binClipId, std::shared_ptr producer, PlaylistState::ClipState state); /* @brief returns a property of the clip, or from it's parent if it's a cut */ const QString getProperty(const QString &name) const override; int getIntProperty(const QString &name) const; double getDoubleProperty(const QString &name) const; QSize getFrameSize() const; Q_INVOKABLE bool showKeyframes() const; Q_INVOKABLE void setShowKeyframes(bool show); /** @brief Returns the timeline clip status (video / audio only) */ PlaylistState::ClipState clipState() const; /** @brief Sets the timeline clip status (video / audio only) */ bool setClipState(PlaylistState::ClipState state); /* @brief returns the length of the item on the timeline */ int getPlaytime() const override; /** @brief Returns audio cache data from bin clip to display audio thumbs */ QVariant getAudioWaveform(); /** @brief Returns the bin clip's id */ const QString &binId() const; void registerClipToBin(); void deregisterClipToBin(); bool addEffect(const QString &effectId); bool copyEffect(std::shared_ptr stackModel, int rowId); /* @brief Import effects from a different stackModel */ bool importEffects(std::shared_ptr stackModel); /* @brief Import effects from a service that contains some (another clip?) */ bool importEffects(std::weak_ptr service); bool removeFade(bool fromStart); /** @brief Adjust effects duration. Should be called after each resize / cut operation */ bool adjustEffectLength(bool adjustFromEnd, int oldIn, int newIn, int oldDuration, int duration, Fun &undo, Fun &redo, bool logUndo); bool adjustEffectLength(const QString &effectName, int duration, int originalDuration, Fun &undo, Fun &redo); - void passTimelineProperties(std::shared_ptr other); + void passTimelineProperties(std::shared_ptr other); KeyframeModel *getKeyframeModel(); int fadeIn() const; int fadeOut() const; friend class TrackModel; friend class TimelineModel; friend class TimelineItemModel; friend class TimelineController; friend struct TimelineFunctions; protected: Mlt::Producer *service() const override; /* @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 timeline instead. 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 undo Lambda function containing the current undo stack. Will be updated with current operation @param redo Lambda function containing the current redo queue. Will be updated with current operation */ bool requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo = true) override; /* @brief This function change the global (timeline-wise) enabled state of the effects - */ + */ 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); void refreshProducerFromBin(); - /* @brief This functions replaces the current producer with a slowmotion one */ + + /* @brief This functions replaces the current producer with a slowmotion one + It also resizes the producer so that set of frames contained in the clip is the same + */ bool useTimewarpProducer(double speed, int extraSpace, Fun &undo, Fun &redo); - Fun useTimewarpProducer_lambda(double speed, int extraSpace); + // @brief Lambda that merely changes the speed (in and out are untouched) + Fun useTimewarpProducer_lambda(double speed); /** @brief Returns the marker model associated with this clip */ std::shared_ptr getMarkerModel() const; bool audioEnabled() const; bool isAudioOnly() const; double getSpeed() const; protected: std::shared_ptr m_producer; std::shared_ptr m_effectStack; QString m_binClipId; // This is the Id of the bin clip this clip corresponds to. bool m_endlessResize; // Whether this clip can be freely resized bool forceThumbReload; // Used to trigger a forced thumb reload, when producer changes PlaylistState::ClipState m_currentState; double m_speed = -1; // Speed of the clip }; #endif