1 /*
2     SPDX-FileCopyrightText: 2018 Jean-Baptiste Mardelle <jb@kdenlive.org>
3     This file is part of Kdenlive. See www.kdenlive.org.
4 
5     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
6 */
7 
8 #include "monitorproxy.h"
9 #include "core.h"
10 #include "doc/kthumb.h"
11 #include "glwidget.h"
12 #include "kdenlivesettings.h"
13 #include "monitormanager.h"
14 #include "profiles/profilemodel.hpp"
15 
16 #include <QUuid>
17 
18 #include <mlt++/MltConsumer.h>
19 #include <mlt++/MltFilter.h>
20 #include <mlt++/MltProducer.h>
21 #include <mlt++/MltProfile.h>
22 
MonitorProxy(GLWidget * parent)23 MonitorProxy::MonitorProxy(GLWidget *parent)
24     : QObject(parent)
25     , q(parent)
26     , m_position(-1)
27     , m_zoneIn(0)
28     , m_zoneOut(-1)
29     , m_hasAV(false)
30     , m_speed(0)
31     , m_clipType(0)
32     , m_clipId(-1)
33     , m_seekFinished(true)
34     , m_td(nullptr)
35     , m_boundsCount(0)
36 {
37 }
38 
getPosition() const39 int MonitorProxy::getPosition() const
40 {
41     return m_position;
42 }
43 
resetPosition()44 void MonitorProxy::resetPosition()
45 {
46     m_position = -1;
47 }
48 
rulerHeight() const49 int MonitorProxy::rulerHeight() const
50 {
51     return q->m_rulerHeight;
52 }
53 
setRulerHeight(int addedHeight)54 void MonitorProxy::setRulerHeight(int addedHeight)
55 {
56     q->updateRulerHeight(addedHeight);
57 }
58 
seek(int delta,uint modifiers)59 void MonitorProxy::seek(int delta, uint modifiers)
60 {
61     emit q->mouseSeek(delta, modifiers);
62 }
63 
overlayType() const64 int MonitorProxy::overlayType() const
65 {
66     return (q->m_id == int(Kdenlive::ClipMonitor) ? KdenliveSettings::clipMonitorOverlayGuides() : KdenliveSettings::projectMonitorOverlayGuides());
67 }
68 
setOverlayType(int ix)69 void MonitorProxy::setOverlayType(int ix)
70 {
71     if (q->m_id == int(Kdenlive::ClipMonitor)) {
72         KdenliveSettings::setClipMonitorOverlayGuides(ix);
73     } else {
74         KdenliveSettings::setProjectMonitorOverlayGuides(ix);
75     }
76 }
77 
setPosition(int pos)78 bool MonitorProxy::setPosition(int pos)
79 {
80     return setPositionAdvanced(pos, false);
81 }
82 
setPositionAdvanced(int pos,bool noAudioScrub)83 bool MonitorProxy::setPositionAdvanced(int pos, bool noAudioScrub)
84 {
85     if (m_position == pos) {
86         return true;
87     }
88     m_position = pos;
89     emit requestSeek(pos, noAudioScrub);
90     if (m_seekFinished) {
91         m_seekFinished = false;
92         emit seekFinishedChanged();
93     }
94     emit positionChanged(pos);
95     return false;
96 }
97 
positionFromConsumer(int pos,bool playing)98 void MonitorProxy::positionFromConsumer(int pos, bool playing)
99 {
100     if (playing) {
101         m_position = pos;
102         emit positionChanged(pos);
103         if (!m_seekFinished) {
104             m_seekFinished = true;
105             emit seekFinishedChanged();
106         }
107     } else {
108         if (!m_seekFinished && m_position == pos) {
109             m_seekFinished = true;
110             emit seekFinishedChanged();
111         }
112     }
113 }
114 
setMarker(const QString & comment,const QColor & color)115 void MonitorProxy::setMarker(const QString &comment, const QColor &color)
116 {
117     if (m_markerComment == comment) {
118         return;
119     }
120     m_markerComment = comment;
121     m_markerColor = color;
122     emit markerChanged();
123 }
124 
zoneIn() const125 int MonitorProxy::zoneIn() const
126 {
127     return m_zoneIn;
128 }
129 
zoneOut() const130 int MonitorProxy::zoneOut() const
131 {
132     return m_zoneOut;
133 }
134 
setZoneIn(int pos)135 void MonitorProxy::setZoneIn(int pos)
136 {
137     if (m_zoneIn > 0) {
138         emit removeSnap(m_zoneIn);
139     }
140     m_zoneIn = pos;
141     if (pos > 0) {
142         emit addSnap(pos);
143     }
144     emit zoneChanged();
145     emit saveZone(QPoint(m_zoneIn, m_zoneOut));
146 }
147 
setZoneOut(int pos)148 void MonitorProxy::setZoneOut(int pos)
149 {
150     if (m_zoneOut > 0) {
151         emit removeSnap(m_zoneOut - 1);
152     }
153     m_zoneOut = pos;
154     if (pos > 0) {
155         emit addSnap(m_zoneOut - 1);
156     }
157     emit zoneChanged();
158     emit saveZone(QPoint(m_zoneIn, m_zoneOut));
159 }
160 
startZoneMove()161 void MonitorProxy::startZoneMove()
162 {
163     m_undoZone = QPoint(m_zoneIn, m_zoneOut);
164 }
165 
endZoneMove()166 void MonitorProxy::endZoneMove()
167 {
168     emit saveZoneWithUndo(m_undoZone, QPoint(m_zoneIn, m_zoneOut));
169 }
170 
setZone(int in,int out,bool sendUpdate)171 void MonitorProxy::setZone(int in, int out, bool sendUpdate)
172 {
173     if (m_zoneIn > 0) {
174         emit removeSnap(m_zoneIn);
175     }
176     if (m_zoneOut > 0) {
177         emit removeSnap(m_zoneOut - 1);
178     }
179     m_zoneIn = in;
180     m_zoneOut = out;
181     if (m_zoneIn > 0) {
182         emit addSnap(m_zoneIn);
183     }
184     if (m_zoneOut > 0) {
185         emit addSnap(m_zoneOut - 1);
186     }
187     emit zoneChanged();
188     if (sendUpdate) {
189         emit saveZone(QPoint(m_zoneIn, m_zoneOut));
190     }
191 }
192 
setZone(QPoint zone,bool sendUpdate)193 void MonitorProxy::setZone(QPoint zone, bool sendUpdate)
194 {
195     setZone(zone.x(), zone.y(), sendUpdate);
196 }
197 
resetZone()198 void MonitorProxy::resetZone()
199 {
200     m_zoneIn = 0;
201     m_zoneOut = -1;
202     m_clipBounds = {};
203     m_boundsCount = 0;
204     emit clipBoundsChanged();
205 }
206 
fps() const207 double MonitorProxy::fps() const
208 {
209     return pCore->getCurrentFps();
210 }
211 
zone() const212 QPoint MonitorProxy::zone() const
213 {
214     return {m_zoneIn, m_zoneOut};
215 }
216 
extractFrame(int frame_position,const QString & path,int width,int height,bool useSourceProfile)217 QImage MonitorProxy::extractFrame(int frame_position, const QString &path, int width, int height, bool useSourceProfile)
218 {
219     if (width == -1) {
220         width = pCore->getCurrentProfile()->width();
221         height = pCore->getCurrentProfile()->height();
222     } else if (width % 2 == 1) {
223         width++;
224     }
225     if (!path.isEmpty()) {
226         QScopedPointer<Mlt::Profile> tmpProfile(new Mlt::Profile());
227         QScopedPointer<Mlt::Producer> producer(new Mlt::Producer(*tmpProfile, path.toUtf8().constData()));
228         if (producer && producer->is_valid()) {
229             tmpProfile->from_producer(*producer);
230             width = tmpProfile->width();
231             height = tmpProfile->height();
232             double projectFps = pCore->getCurrentFps();
233             double currentFps = tmpProfile->fps();
234             if (!qFuzzyCompare(projectFps, currentFps)) {
235                 frame_position = int(frame_position * currentFps / projectFps);
236             }
237             QImage img = KThumb::getFrame(producer.data(), frame_position, width, height);
238             return img;
239         }
240     }
241 
242     if ((q->m_producer == nullptr) || !path.isEmpty()) {
243         QImage pix(width, height, QImage::Format_RGB32);
244         pix.fill(Qt::black);
245         return pix;
246     }
247     Mlt::Frame *frame = nullptr;
248     QImage img;
249     if (useSourceProfile) {
250         // Our source clip's resolution is higher than current profile, export at full res
251         QScopedPointer<Mlt::Profile> tmpProfile(new Mlt::Profile());
252         QString service = q->m_producer->get("mlt_service");
253         QScopedPointer<Mlt::Producer> tmpProd(new Mlt::Producer(*tmpProfile, service.toUtf8().constData(), q->m_producer->get("resource")));
254         tmpProfile->from_producer(*tmpProd);
255         width = tmpProfile->width();
256         height = tmpProfile->height();
257         if (tmpProd && tmpProd->is_valid()) {
258             Mlt::Filter scaler(*tmpProfile, "swscale");
259             Mlt::Filter converter(*tmpProfile, "avcolor_space");
260             tmpProd->attach(scaler);
261             tmpProd->attach(converter);
262             // TODO: paste effects
263             // Clip(*tmpProd).addEffects(*q->m_producer);
264             double projectFps = pCore->getCurrentFps();
265             double currentFps = tmpProfile->fps();
266             if (qFuzzyCompare(projectFps, currentFps)) {
267                 tmpProd->seek(q->m_producer->position());
268             } else {
269                 int maxLength = int(q->m_producer->get_length() * currentFps / projectFps);
270                 tmpProd->set("length", maxLength);
271                 tmpProd->set("out", maxLength - 1);
272                 tmpProd->seek(int(q->m_producer->position() * currentFps / projectFps));
273             }
274             frame = tmpProd->get_frame();
275             img = KThumb::getFrame(frame, width, height);
276             delete frame;
277         }
278     } else if (KdenliveSettings::gpu_accel()) {
279         QString service = q->m_producer->get("mlt_service");
280         QScopedPointer<Mlt::Producer> tmpProd(
281             new Mlt::Producer(pCore->getCurrentProfile()->profile(), service.toUtf8().constData(), q->m_producer->get("resource")));
282         Mlt::Filter scaler(pCore->getCurrentProfile()->profile(), "swscale");
283         Mlt::Filter converter(pCore->getCurrentProfile()->profile(), "avcolor_space");
284         tmpProd->attach(scaler);
285         tmpProd->attach(converter);
286         tmpProd->seek(q->m_producer->position());
287         frame = tmpProd->get_frame();
288         img = KThumb::getFrame(frame, width, height);
289         delete frame;
290     } else {
291         frame = q->m_producer->get_frame();
292         img = KThumb::getFrame(frame, width, height);
293         delete frame;
294     }
295     return img;
296 }
297 
activateClipMonitor(bool isClipMonitor)298 void MonitorProxy::activateClipMonitor(bool isClipMonitor)
299 {
300     pCore->monitorManager()->activateMonitor(isClipMonitor ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor);
301 }
302 
toTimecode(int frames) const303 QString MonitorProxy::toTimecode(int frames) const
304 {
305     return KdenliveSettings::frametimecode() ? QString::number(frames) : q->frameToTime(frames);
306 }
307 
setClipProperties(int clipId,ClipType::ProducerType type,bool hasAV,const QString clipName)308 void MonitorProxy::setClipProperties(int clipId, ClipType::ProducerType type, bool hasAV, const QString clipName)
309 {
310     if (clipId != m_clipId) {
311         m_clipId = clipId;
312         emit clipIdChanged();
313     }
314     if (hasAV != m_hasAV) {
315         m_hasAV = hasAV;
316         emit clipHasAVChanged();
317     }
318     if (type != m_clipType) {
319         m_clipType = type;
320         emit clipTypeChanged();
321     }
322     if (clipName == m_clipName) {
323         m_clipName.clear();
324         emit clipNameChanged();
325     }
326     m_clipName = clipName;
327     emit clipNameChanged();
328 }
329 
setAudioThumb(const QList<int> streamIndexes,QList<int> channels)330 void MonitorProxy::setAudioThumb(const QList <int> streamIndexes, QList <int> channels)
331 {
332     m_audioChannels = channels;
333     m_audioStreams = streamIndexes;
334     emit audioThumbChanged();
335 }
336 
setAudioStream(const QString & name)337 void MonitorProxy::setAudioStream(const QString &name)
338 {
339     m_clipStream = name;
340     emit clipStreamChanged();
341 }
342 
343 
profile()344 QPoint MonitorProxy::profile()
345 {
346     QSize s = pCore->getCurrentFrameSize();
347     return QPoint(s.width(), s.height());
348 }
349 
thumbColor1() const350 QColor MonitorProxy::thumbColor1() const
351 {
352     return KdenliveSettings::thumbColor1();
353 }
354 
thumbColor2() const355 QColor MonitorProxy::thumbColor2() const
356 {
357     return KdenliveSettings::thumbColor2();
358 }
359 
audioThumbFormat() const360 bool MonitorProxy::audioThumbFormat() const
361 {
362     return KdenliveSettings::displayallchannels();
363 }
364 
audioThumbNormalize() const365 bool MonitorProxy::audioThumbNormalize() const
366 {
367     return KdenliveSettings::normalizechannels();
368 }
369 
switchAutoKeyframe()370 void MonitorProxy::switchAutoKeyframe()
371 {
372     KdenliveSettings::setAutoKeyframe(!KdenliveSettings::autoKeyframe());
373     emit autoKeyframeChanged();
374 }
375 
autoKeyframe() const376 bool MonitorProxy::autoKeyframe() const
377 {
378     return KdenliveSettings::autoKeyframe();
379 }
380 
timecode() const381 const QString MonitorProxy::timecode() const
382 {
383     if (m_td) {
384         return m_td->displayText();
385     }
386     return QString();
387 }
388 
trimmingTC1() const389 const QString MonitorProxy::trimmingTC1() const
390 {
391     return toTimecode(m_trimmingFrames1);
392 }
393 
trimmingTC2() const394 const QString MonitorProxy::trimmingTC2() const
395 {
396     return toTimecode(m_trimmingFrames2);
397 }
398 
setTimeCode(TimecodeDisplay * td)399 void MonitorProxy::setTimeCode(TimecodeDisplay *td)
400 {
401     m_td = td;
402     connect(m_td, &TimecodeDisplay::timeCodeUpdated, this, &MonitorProxy::timecodeChanged);
403 }
404 
setTrimmingTC1(int frames,bool isRelativ)405 void MonitorProxy::setTrimmingTC1(int frames, bool isRelativ)
406 {
407     if (isRelativ) {
408         m_trimmingFrames1 -= frames;
409     } else {
410         m_trimmingFrames1 = frames;
411     }
412     emit trimmingTC1Changed();
413 }
414 
setTrimmingTC2(int frames,bool isRelativ)415 void MonitorProxy::setTrimmingTC2(int frames, bool isRelativ)
416 {
417     if (isRelativ) {
418         m_trimmingFrames2 -= frames;
419     } else {
420         m_trimmingFrames2 = frames;
421     }
422     emit trimmingTC2Changed();
423 }
424 
setWidgetKeyBinding(const QString & text) const425 void MonitorProxy::setWidgetKeyBinding(const QString &text) const
426 {
427     pCore->setWidgetKeyBinding(text);
428 }
429 
setSpeed(double speed)430 void MonitorProxy::setSpeed(double speed)
431 {
432     if (qAbs(m_speed) > 1. || qAbs(speed) > 1.) {
433         // check if we have or had a speed > 1 or < -1
434         m_speed = speed;
435         emit speedChanged();
436     }
437 }
438 
getUuid() const439 QByteArray MonitorProxy::getUuid() const
440 {
441     return QUuid::createUuid().toByteArray();
442 }
443 
updateClipBounds(QVector<QPoint> bounds)444 void MonitorProxy::updateClipBounds(QVector <QPoint>bounds)
445 {
446     if (bounds.size() == m_boundsCount) {
447         // Enforce refresh, in/out points may have changed
448         m_boundsCount = 0;
449         emit clipBoundsChanged();
450     }
451     m_clipBounds = bounds;
452     m_boundsCount = bounds.size();
453     emit clipBoundsChanged();
454 }
455 
clipBoundary(int ix)456 const QPoint MonitorProxy::clipBoundary(int ix)
457 {
458     return m_clipBounds.at(ix);
459 }
460 
461