File indexing completed on 2024-04-14 04:46:48

0001 /*
0002     SPDX-FileCopyrightText: 2018 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003     This file is part of Kdenlive. See www.kdenlive.org.
0004 
0005     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "monitorproxy.h"
0009 #include "bin/bin.h"
0010 #include "core.h"
0011 #include "doc/kthumb.h"
0012 #include "kdenlivesettings.h"
0013 #include "monitormanager.h"
0014 #include "profiles/profilemodel.hpp"
0015 
0016 #include <QUuid>
0017 
0018 #include <mlt++/MltConsumer.h>
0019 #include <mlt++/MltFilter.h>
0020 #include <mlt++/MltProducer.h>
0021 #include <mlt++/MltProfile.h>
0022 
0023 MonitorProxy::MonitorProxy(VideoWidget *parent)
0024     : QObject(parent)
0025     , q(parent)
0026     , m_position(-1)
0027     , m_zoneIn(0)
0028     , m_zoneOut(-1)
0029     , m_hasAV(false)
0030     , m_speed(0)
0031     , m_clipType(0)
0032     , m_clipId(-1)
0033     , m_seekFinished(true)
0034     , m_td(nullptr)
0035     , m_trimmingFrames1(0)
0036     , m_trimmingFrames2(0)
0037     , m_boundsCount(0)
0038 {
0039     if (q->m_id == int(Kdenlive::ClipMonitor)) {
0040         connect(pCore->bin(), &Bin::clipNameChanged, this, &MonitorProxy::updateClipName);
0041     }
0042 }
0043 
0044 int MonitorProxy::getPosition() const
0045 {
0046     return m_position;
0047 }
0048 
0049 void MonitorProxy::updateClipName(int id, const QString newName)
0050 {
0051     for (int i = 0; i < m_lastClipsIds.size(); i++) {
0052         if (m_lastClipsIds.at(i).first == id) {
0053             m_lastClipsIds.remove(i);
0054             m_lastClipsIds.insert(i, {id, newName});
0055             m_lastClips.clear();
0056             for (int j = 0; j < 4 && j < m_lastClipsIds.size(); j++) {
0057                 m_lastClips << m_lastClipsIds.at(j).second;
0058             }
0059             m_clipName = newName;
0060             Q_EMIT clipNameChanged();
0061             Q_EMIT lastClipsChanged();
0062             break;
0063         }
0064     }
0065 }
0066 
0067 void MonitorProxy::resetPosition()
0068 {
0069     m_position = -1;
0070 }
0071 
0072 int MonitorProxy::rulerHeight() const
0073 {
0074     return q->m_rulerHeight;
0075 }
0076 
0077 void MonitorProxy::setRulerHeight(int addedHeight)
0078 {
0079     q->updateRulerHeight(addedHeight);
0080 }
0081 
0082 void MonitorProxy::seek(int delta, uint modifiers)
0083 {
0084     Q_EMIT q->mouseSeek(delta, modifiers);
0085 }
0086 
0087 int MonitorProxy::overlayType() const
0088 {
0089     return (q->m_id == int(Kdenlive::ClipMonitor) ? KdenliveSettings::clipMonitorOverlayGuides() : KdenliveSettings::projectMonitorOverlayGuides());
0090 }
0091 
0092 void MonitorProxy::setOverlayType(int ix)
0093 {
0094     if (q->m_id == int(Kdenlive::ClipMonitor)) {
0095         KdenliveSettings::setClipMonitorOverlayGuides(ix);
0096     } else {
0097         KdenliveSettings::setProjectMonitorOverlayGuides(ix);
0098     }
0099 }
0100 
0101 bool MonitorProxy::setPosition(int pos)
0102 {
0103     return setPositionAdvanced(pos, false);
0104 }
0105 
0106 bool MonitorProxy::setPositionAdvanced(int pos, bool noAudioScrub)
0107 {
0108     if (m_position == pos) {
0109         return true;
0110     }
0111     m_position = pos;
0112     Q_EMIT requestSeek(pos, noAudioScrub);
0113     if (m_seekFinished) {
0114         m_seekFinished = false;
0115         Q_EMIT seekFinishedChanged();
0116     }
0117     Q_EMIT positionChanged(pos);
0118     return false;
0119 }
0120 
0121 void MonitorProxy::positionFromConsumer(int pos, bool playing)
0122 {
0123     if (playing) {
0124         m_position = pos;
0125         Q_EMIT positionChanged(pos);
0126         if (!m_seekFinished) {
0127             m_seekFinished = true;
0128             Q_EMIT seekFinishedChanged();
0129         }
0130     } else {
0131         if (!m_seekFinished && m_position == pos) {
0132             m_seekFinished = true;
0133             Q_EMIT seekFinishedChanged();
0134         }
0135     }
0136 }
0137 
0138 void MonitorProxy::setMarker(const QString &comment, const QColor &color)
0139 {
0140     if (m_markerComment == comment && color == m_markerColor) {
0141         return;
0142     }
0143     m_markerComment = comment;
0144     m_markerColor = color;
0145     Q_EMIT markerChanged();
0146 }
0147 
0148 int MonitorProxy::zoneIn() const
0149 {
0150     return m_zoneIn;
0151 }
0152 
0153 int MonitorProxy::zoneOut() const
0154 {
0155     return m_zoneOut;
0156 }
0157 
0158 void MonitorProxy::setZoneIn(int pos)
0159 {
0160     if (m_zoneIn > 0) {
0161         Q_EMIT removeSnap(m_zoneIn);
0162     }
0163     m_zoneIn = pos;
0164     if (pos > 0) {
0165         Q_EMIT addSnap(pos);
0166     }
0167     Q_EMIT zoneChanged();
0168     Q_EMIT saveZone(QPoint(m_zoneIn, m_zoneOut));
0169 }
0170 
0171 void MonitorProxy::setZoneOut(int pos)
0172 {
0173     if (m_zoneOut > 0) {
0174         Q_EMIT removeSnap(m_zoneOut);
0175     }
0176     m_zoneOut = pos;
0177     if (pos > 0) {
0178         Q_EMIT addSnap(m_zoneOut);
0179     }
0180     Q_EMIT zoneChanged();
0181     Q_EMIT saveZone(QPoint(m_zoneIn, m_zoneOut));
0182 }
0183 
0184 void MonitorProxy::startZoneMove()
0185 {
0186     m_undoZone = QPoint(m_zoneIn, m_zoneOut);
0187 }
0188 
0189 void MonitorProxy::endZoneMove()
0190 {
0191     Q_EMIT saveZoneWithUndo(m_undoZone, QPoint(m_zoneIn, m_zoneOut));
0192 }
0193 
0194 void MonitorProxy::setZone(int in, int out, bool sendUpdate)
0195 {
0196     if (m_zoneIn > 0) {
0197         Q_EMIT removeSnap(m_zoneIn);
0198     }
0199     if (m_zoneOut > 0) {
0200         Q_EMIT removeSnap(m_zoneOut);
0201     }
0202     m_zoneIn = in;
0203     m_zoneOut = out;
0204     if (m_zoneIn > 0) {
0205         Q_EMIT addSnap(m_zoneIn);
0206     }
0207     if (m_zoneOut > 0) {
0208         Q_EMIT addSnap(m_zoneOut);
0209     }
0210     Q_EMIT zoneChanged();
0211     if (sendUpdate) {
0212         Q_EMIT saveZone(QPoint(m_zoneIn, m_zoneOut));
0213     }
0214 }
0215 
0216 void MonitorProxy::setZone(QPoint zone, bool sendUpdate)
0217 {
0218     setZone(zone.x(), zone.y(), sendUpdate);
0219 }
0220 
0221 void MonitorProxy::resetZone()
0222 {
0223     m_zoneIn = 0;
0224     m_zoneOut = -1;
0225     m_clipBounds = {};
0226     m_boundsCount = 0;
0227     Q_EMIT clipBoundsChanged();
0228 }
0229 
0230 double MonitorProxy::fps() const
0231 {
0232     return pCore->getCurrentFps();
0233 }
0234 
0235 QPoint MonitorProxy::zone() const
0236 {
0237     return {m_zoneIn, m_zoneOut};
0238 }
0239 
0240 void MonitorProxy::extractFrameToFile(int frame_position, const QStringList &pathInfo, bool addToProject, bool useSourceProfile)
0241 {
0242     const QString path = pathInfo.at(0);
0243     const QString destPath = pathInfo.at(1);
0244     const QString folderInfo = pathInfo.at(2);
0245     QSize finalSize = pCore->getCurrentFrameDisplaySize();
0246     QSize size = pCore->getCurrentFrameSize();
0247     int height = size.height();
0248     int width = size.width();
0249     if (path.isEmpty()) {
0250         // Use current monitor producer to extract frame
0251         Mlt::Frame *frame = q->m_producer->get_frame();
0252         QImage img = KThumb::getFrame(frame, width, height, finalSize.width());
0253         delete frame;
0254         img.save(destPath);
0255         if (addToProject) {
0256             QMetaObject::invokeMethod(pCore->bin(), "droppedUrls", Q_ARG(const QList<QUrl> &, {QUrl::fromLocalFile(destPath)}),
0257                                       Q_ARG(const QString &, folderInfo));
0258         }
0259         return;
0260     }
0261     QScopedPointer<Mlt::Producer> producer;
0262     QScopedPointer<Mlt::Profile> tmpProfile;
0263     if (useSourceProfile) {
0264         tmpProfile.reset(new Mlt::Profile());
0265         producer.reset(new Mlt::Producer(*tmpProfile, path.toUtf8().constData()));
0266     } else {
0267         producer.reset(new Mlt::Producer(pCore->getProjectProfile(), path.toUtf8().constData()));
0268     }
0269     if (producer && producer->is_valid()) {
0270         if (useSourceProfile) {
0271             tmpProfile->from_producer(*producer);
0272             width = tmpProfile->width();
0273             height = tmpProfile->height();
0274             if (tmpProfile->sar() != 1.) {
0275                 finalSize.setWidth(qRound(height * tmpProfile->dar()));
0276             } else {
0277                 finalSize.setWidth(0);
0278             }
0279             double projectFps = pCore->getCurrentFps();
0280             double currentFps = tmpProfile->fps();
0281             if (!qFuzzyCompare(projectFps, currentFps)) {
0282                 frame_position = int(frame_position * currentFps / projectFps);
0283             }
0284         }
0285         QImage img = KThumb::getFrame(producer.data(), frame_position, width, height, finalSize.width());
0286         img.save(destPath);
0287         if (addToProject) {
0288             QMetaObject::invokeMethod(pCore->bin(), "droppedUrls", Q_ARG(const QList<QUrl> &, {QUrl::fromLocalFile(destPath)}),
0289                                       Q_ARG(const QString &, folderInfo));
0290         }
0291     } else {
0292         qDebug() << "::: INVALID PRODUCER: " << path;
0293     }
0294     if (QDir::temp().exists(path)) {
0295         // This was a temporary playlist file, remove
0296         QFile::remove(path);
0297     }
0298 }
0299 
0300 QImage MonitorProxy::extractFrame(const QString &path, int width, int height, bool useSourceProfile)
0301 {
0302     if (width == -1) {
0303         width = pCore->getCurrentProfile()->width();
0304         height = pCore->getCurrentProfile()->height();
0305     } else if (width % 2 == 1) {
0306         width++;
0307     }
0308 
0309     if ((q->m_producer == nullptr) || !path.isEmpty()) {
0310         QImage pix(width, height, QImage::Format_RGB32);
0311         pix.fill(Qt::black);
0312         return pix;
0313     }
0314     Mlt::Frame *frame = nullptr;
0315     QImage img;
0316     if (useSourceProfile) {
0317         // Our source clip's resolution is higher than current profile, export at full res
0318         QScopedPointer<Mlt::Profile> tmpProfile(new Mlt::Profile());
0319         QString service = q->m_producer->get("mlt_service");
0320         QScopedPointer<Mlt::Producer> tmpProd(new Mlt::Producer(*tmpProfile, service.toUtf8().constData(), q->m_producer->get("resource")));
0321         tmpProfile->from_producer(*tmpProd);
0322         width = tmpProfile->width();
0323         height = tmpProfile->height();
0324         if (tmpProd && tmpProd->is_valid()) {
0325             Mlt::Filter scaler(*tmpProfile, "swscale");
0326             Mlt::Filter converter(*tmpProfile, "avcolor_space");
0327             tmpProd->attach(scaler);
0328             tmpProd->attach(converter);
0329             // TODO: paste effects
0330             // Clip(*tmpProd).addEffects(*q->m_producer);
0331             double projectFps = pCore->getCurrentFps();
0332             double currentFps = tmpProfile->fps();
0333             if (qFuzzyCompare(projectFps, currentFps)) {
0334                 tmpProd->seek(q->m_producer->position());
0335             } else {
0336                 int maxLength = int(q->m_producer->get_length() * currentFps / projectFps);
0337                 tmpProd->set("length", maxLength);
0338                 tmpProd->set("out", maxLength - 1);
0339                 tmpProd->seek(int(q->m_producer->position() * currentFps / projectFps));
0340             }
0341             frame = tmpProd->get_frame();
0342             img = KThumb::getFrame(frame, width, height);
0343             delete frame;
0344         }
0345     } else if (KdenliveSettings::gpu_accel()) {
0346         QString service = q->m_producer->get("mlt_service");
0347         QScopedPointer<Mlt::Producer> tmpProd(new Mlt::Producer(pCore->getProjectProfile(), service.toUtf8().constData(), q->m_producer->get("resource")));
0348         Mlt::Filter scaler(pCore->getProjectProfile(), "swscale");
0349         Mlt::Filter converter(pCore->getProjectProfile(), "avcolor_space");
0350         tmpProd->attach(scaler);
0351         tmpProd->attach(converter);
0352         tmpProd->seek(q->m_producer->position());
0353         frame = tmpProd->get_frame();
0354         img = KThumb::getFrame(frame, width, height);
0355         delete frame;
0356     } else {
0357         frame = q->m_producer->get_frame();
0358         img = KThumb::getFrame(frame, width, height);
0359         delete frame;
0360     }
0361     return img;
0362 }
0363 
0364 void MonitorProxy::activateClipMonitor(bool isClipMonitor)
0365 {
0366     pCore->monitorManager()->activateMonitor(isClipMonitor ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor);
0367 }
0368 
0369 QString MonitorProxy::toTimecode(int frames) const
0370 {
0371     return KdenliveSettings::frametimecode() ? QString::number(frames) : q->frameToTime(frames);
0372 }
0373 
0374 void MonitorProxy::selectClip(int ix)
0375 {
0376     if (ix < m_lastClipsIds.size()) {
0377         int cid = m_lastClipsIds.at(ix).first;
0378         pCore->bin()->selectClipById(QString::number(cid));
0379     }
0380 }
0381 
0382 void MonitorProxy::setClipProperties(int clipId, ClipType::ProducerType type, bool hasAV, const QString &clipName)
0383 {
0384     if (clipId != m_clipId) {
0385         m_clipId = clipId;
0386         Q_EMIT clipIdChanged();
0387     }
0388     if (hasAV != m_hasAV) {
0389         m_hasAV = hasAV;
0390         Q_EMIT clipHasAVChanged();
0391     }
0392     if (type != m_clipType) {
0393         m_clipType = type;
0394         Q_EMIT clipTypeChanged();
0395     }
0396     if (!clipName.isEmpty()) {
0397         std::pair<int, QString> id = {clipId, clipName};
0398         for (int i = 0; i < m_lastClipsIds.size(); i++) {
0399             if (m_lastClipsIds.at(i).first == clipId) {
0400                 m_lastClipsIds.remove(i);
0401                 break;
0402             }
0403         }
0404         m_lastClipsIds.prepend(id);
0405         while (m_lastClipsIds.size() > 4) {
0406             m_lastClipsIds.removeLast();
0407         }
0408         m_lastClips.clear();
0409         for (int i = 0; i < 4 && i < m_lastClipsIds.size(); i++) {
0410             m_lastClips << m_lastClipsIds.at(i).second;
0411         }
0412         Q_EMIT lastClipsChanged();
0413     }
0414     if (clipName == m_clipName) {
0415         m_clipName.clear();
0416         Q_EMIT clipNameChanged();
0417     }
0418     m_clipName = clipName;
0419     Q_EMIT clipNameChanged();
0420 }
0421 
0422 void MonitorProxy::clipDeleted(int cid)
0423 {
0424     for (int i = 0; i < m_lastClipsIds.size(); i++) {
0425         if (m_lastClipsIds.at(i).first == cid) {
0426             m_lastClipsIds.remove(i);
0427             m_lastClips.clear();
0428             for (int j = 0; j < 4 && j < m_lastClipsIds.size(); j++) {
0429                 m_lastClips << m_lastClipsIds.at(j).second;
0430             }
0431             Q_EMIT lastClipsChanged();
0432             break;
0433         }
0434     }
0435 }
0436 
0437 void MonitorProxy::documentClosed()
0438 {
0439     m_lastClipsIds.clear();
0440     m_lastClips.clear();
0441     m_clipName.clear();
0442     Q_EMIT lastClipsChanged();
0443     Q_EMIT clipNameChanged();
0444 }
0445 
0446 void MonitorProxy::setAudioThumb(const QList<int> &streamIndexes, const QList<int> &channels)
0447 {
0448     m_audioChannels = channels;
0449     m_audioStreams = streamIndexes;
0450     Q_EMIT audioThumbChanged();
0451 }
0452 
0453 void MonitorProxy::setAudioStream(const QString &name)
0454 {
0455     m_clipStream = name;
0456     Q_EMIT clipStreamChanged();
0457 }
0458 
0459 QPoint MonitorProxy::profile()
0460 {
0461     QSize s = pCore->getCurrentFrameSize();
0462     return QPoint(s.width(), s.height());
0463 }
0464 
0465 QColor MonitorProxy::thumbColor1() const
0466 {
0467     return KdenliveSettings::thumbColor1();
0468 }
0469 
0470 QColor MonitorProxy::thumbColor2() const
0471 {
0472     return KdenliveSettings::thumbColor2();
0473 }
0474 
0475 QColor MonitorProxy::overlayColor() const
0476 {
0477     return KdenliveSettings::overlayColor();
0478 }
0479 
0480 bool MonitorProxy::audioThumbFormat() const
0481 {
0482     return KdenliveSettings::displayallchannels();
0483 }
0484 
0485 bool MonitorProxy::audioThumbNormalize() const
0486 {
0487     return KdenliveSettings::normalizechannels();
0488 }
0489 
0490 void MonitorProxy::switchAutoKeyframe()
0491 {
0492     KdenliveSettings::setAutoKeyframe(!KdenliveSettings::autoKeyframe());
0493     Q_EMIT autoKeyframeChanged();
0494 }
0495 
0496 bool MonitorProxy::autoKeyframe() const
0497 {
0498     return KdenliveSettings::autoKeyframe();
0499 }
0500 
0501 const QString MonitorProxy::timecode() const
0502 {
0503     if (m_td) {
0504         return m_td->displayText();
0505     }
0506     return QString();
0507 }
0508 
0509 const QString MonitorProxy::trimmingTC1() const
0510 {
0511     return toTimecode(m_trimmingFrames1);
0512 }
0513 
0514 const QString MonitorProxy::trimmingTC2() const
0515 {
0516     return toTimecode(m_trimmingFrames2);
0517 }
0518 
0519 void MonitorProxy::setTimeCode(TimecodeDisplay *td)
0520 {
0521     m_td = td;
0522     connect(m_td, &TimecodeDisplay::timeCodeUpdated, this, &MonitorProxy::timecodeChanged);
0523 }
0524 
0525 void MonitorProxy::setTrimmingTC1(int frames, bool isRelativ)
0526 {
0527     if (isRelativ) {
0528         m_trimmingFrames1 -= frames;
0529     } else {
0530         m_trimmingFrames1 = frames;
0531     }
0532     Q_EMIT trimmingTC1Changed();
0533 }
0534 
0535 void MonitorProxy::setTrimmingTC2(int frames, bool isRelativ)
0536 {
0537     if (isRelativ) {
0538         m_trimmingFrames2 -= frames;
0539     } else {
0540         m_trimmingFrames2 = frames;
0541     }
0542     Q_EMIT trimmingTC2Changed();
0543 }
0544 
0545 void MonitorProxy::setWidgetKeyBinding(const QString &text) const
0546 {
0547     pCore->setWidgetKeyBinding(text);
0548 }
0549 
0550 void MonitorProxy::setSpeed(double speed)
0551 {
0552     if (qAbs(m_speed) > 1. || qAbs(speed) > 1.) {
0553         // check if we have or had a speed > 1 or < -1
0554         m_speed = speed;
0555         Q_EMIT speedChanged();
0556     }
0557 }
0558 
0559 QByteArray MonitorProxy::getUuid() const
0560 {
0561     return QUuid::createUuid().toByteArray();
0562 }
0563 
0564 void MonitorProxy::updateClipBounds(const QVector<QPoint> &bounds)
0565 {
0566     if (bounds.size() == m_boundsCount) {
0567         // Enforce refresh, in/out points may have changed
0568         m_boundsCount = 0;
0569         Q_EMIT clipBoundsChanged();
0570     }
0571     m_clipBounds = bounds;
0572     m_boundsCount = bounds.size();
0573     Q_EMIT clipBoundsChanged();
0574 }
0575 
0576 const QPoint MonitorProxy::clipBoundary(int ix)
0577 {
0578     return m_clipBounds.at(ix);
0579 }
0580 
0581 bool MonitorProxy::seekOnDrop() const
0582 {
0583     return KdenliveSettings::seekonaddeffect();
0584 }
0585 
0586 void MonitorProxy::addEffect(const QString &effectData, const QString &effectSource)
0587 {
0588     QStringList effectInfo = effectSource.split(QLatin1Char(','));
0589     effectInfo.prepend(effectData);
0590     if (m_clipId > -1) {
0591         QMetaObject::invokeMethod(pCore->bin(), "slotAddEffect", Qt::QueuedConnection, Q_ARG(std::vector<QString>, {QString::number(m_clipId)}),
0592                                   Q_ARG(QStringList, effectInfo));
0593     } else {
0594         // Dropped in project monitor
0595         QMetaObject::invokeMethod(this, "addTimelineEffect", Qt::QueuedConnection, Q_ARG(QStringList, effectInfo));
0596     }
0597 }
0598 
0599 void MonitorProxy::setJobsProgress(const ObjectId &owner, const QStringList &jobNames, const QList<int> &jobProgress, const QStringList &jobUuids)
0600 {
0601     if (owner.itemId != m_clipId) {
0602         // Not interested
0603         return;
0604     }
0605     if (m_runningJobs != jobNames) {
0606         m_runningJobs = jobNames;
0607         Q_EMIT runningJobsChanged();
0608     }
0609     m_jobsProgress = jobProgress;
0610     m_jobsUuids = jobUuids;
0611     Q_EMIT jobsProgressChanged();
0612 }
0613 
0614 void MonitorProxy::terminateJob(const QString &uuid)
0615 {
0616     pCore->taskManager.discardJob(ObjectId(KdenliveObjectType::BinClip, m_clipId, QUuid()), QUuid(uuid));
0617 }