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