diff --git a/src/timeline2/model/clipmodel.cpp b/src/timeline2/model/clipmodel.cpp index e02fb6795..473b20b9a 100644 --- a/src/timeline2/model/clipmodel.cpp +++ b/src/timeline2/model/clipmodel.cpp @@ -1,483 +1,486 @@ /*************************************************************************** * 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) : 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_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) { std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(binClipId); std::shared_ptr cutProducer = binClip->timelineProducer(state); return construct(parent, binClipId, cutProducer, id); } int ClipModel::construct(const std::shared_ptr &parent, const QString &binClipId, std::shared_ptr producer, int id) { std::shared_ptr clip(new ClipModel(parent, producer, binClipId, id)); id = clip->m_id; parent->registerClip(clip); clip->m_effectStack->loadEffects(); 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 "<get_playtime(); 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) { - READ_LOCK(); + QWriteLocker locker(&m_lock); m_effectStack->setEffectStackEnabled(enabled); } bool ClipModel::addEffect(const QString &effectId) { - READ_LOCK(); + QWriteLocker locker(&m_lock); m_effectStack->appendEffect(effectId); return true; } bool ClipModel::copyEffect(std::shared_ptr stackModel, int rowId) { - READ_LOCK(); + QWriteLocker locker(&m_lock); m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId)); return true; } bool ClipModel::importEffects(std::shared_ptr stackModel) { - READ_LOCK(); + QWriteLocker locker(&m_lock); m_effectStack->importEffects(stackModel); return true; } bool ClipModel::removeFade(bool fromStart) { - READ_LOCK(); + 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) { - READ_LOCK(); + QWriteLocker locker(&m_lock); qDebug()<<".... ADJUSTING FADE LENGTH: "<adjustFadeLength(duration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"), hasAudio(), !isAudioOnly()); }; if (operation() && originalDuration > 0) { Fun reverse = [this, originalDuration, effectName]() { return m_effectStack->adjustFadeLength(originalDuration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"), hasAudio(), !isAudioOnly()); }; - PUSH_LAMBDA(operation, redo); - PUSH_LAMBDA(reverse, undo); + UPDATE_UNDO_REDO(operation, reverse, undo, redo); } return true; } bool ClipModel::hasAudio() const { READ_LOCK(); QString service = getProperty("mlt_service"); if (service == QLatin1String("xml")) { // Playlist clip, assume audio return true; } qDebug() << "checking audio:" << service << ((service.contains(QStringLiteral("avformat")) || service == QLatin1String("timewarp")) && (getIntProperty(QStringLiteral("audio_index")) > -1)); if ((service.contains(QStringLiteral("avformat")) || service == QLatin1String("timewarp")) && (getIntProperty(QStringLiteral("audio_index")) > -1)) { return true; } std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); return binClip->hasAudio(); } bool ClipModel::isAudioOnly() const { READ_LOCK(); QString service = getProperty("mlt_service"); return service.contains(QStringLiteral("avformat")) && (getIntProperty(QStringLiteral("video_index")) == -1); } 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; } - QWriteLocker locker(&m_lock); int in = getIn(); int out = getOut(); std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); std::shared_ptr binProducer = binClip->timelineProducer(state, m_currentTrackId); m_producer = std::move(binProducer); m_producer->set_in_and_out(in, out); // m_producer.reset(binProducer->cut(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(); } bool ClipModel::useTimewarpProducer(double speed, int extraSpace, Fun &undo, Fun &redo) { double previousSpeed = getSpeed(); auto operation = useTimewarpProducer_lambda(speed, extraSpace); if (operation()) { auto reverse = useTimewarpProducer_lambda(previousSpeed, extraSpace); UPDATE_UNDO_REDO(operation, reverse, undo, redo); return true; } return false; } Fun ClipModel::useTimewarpProducer_lambda(double speed, int extraSpace) { 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 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; } 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); if (!getProperty("mlt_service").startsWith(QStringLiteral("avformat"))) { return false; } std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); std::shared_ptr binProducer = binClip->timelineProducer(state, m_currentTrackId); int in = getIn(); int out = getOut(); m_producer = std::move(binProducer); m_producer->set_in_and_out(in, out); m_producer->set("kdenlive:id", m_binClipId.toUtf8().constData()); m_producer->set("_kdenlive_cid", m_id); // replant effect stack in updated service m_effectStack->resetService(m_producer); return true; } PlaylistState::ClipState ClipModel::clipState() const { + READ_LOCK(); 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"); }