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 }