1 /* Webcamoid, webcam capture application.
2  * Copyright (C) 2016  Gonzalo Exequiel Pedone
3  *
4  * Webcamoid is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * Webcamoid is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with Webcamoid. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Web-Site: http://webcamoid.github.io/
18  */
19 
20 #include <QMutex>
21 #include <QSettings>
22 #include <QQuickItem>
23 #include <QQmlContext>
24 #include <QQmlProperty>
25 #include <QQmlApplicationEngine>
26 #include <akcaps.h>
27 #include <akpacket.h>
28 
29 #include "videoeffects.h"
30 
31 class VideoEffectsPrivate
32 {
33     public:
34         QQmlApplicationEngine *m_engine {nullptr};
35         QStringList m_availableEffects;
36         AkElement::ElementState m_state {AkElement::ElementStateNull};
37         bool m_advancedMode {false};
38         QList<AkElementPtr> m_effects;
39         QStringList m_effectsId;
40         AkElementPtr m_videoMux;
41         QMutex m_mutex;
42 };
43 
VideoEffects(QQmlApplicationEngine * engine,QObject * parent)44 VideoEffects::VideoEffects(QQmlApplicationEngine *engine, QObject *parent):
45     QObject(parent)
46 {
47     this->d = new VideoEffectsPrivate;
48     this->setQmlEngine(engine);
49     this->d->m_videoMux = AkElement::create("Multiplex");
50 
51     if (this->d->m_videoMux) {
52         this->d->m_videoMux->setProperty("caps", QVariant::fromValue(AkCaps("video/x-raw")));
53         this->d->m_videoMux->setProperty("outputIndex", 0);
54 
55         QObject::connect(this->d->m_videoMux.data(),
56                          SIGNAL(oStream(const AkPacket &)),
57                          this,
58                          SIGNAL(oStream(const AkPacket &)),
59                          Qt::DirectConnection);
60     }
61 
62     this->d->m_availableEffects = AkElement::listPlugins("VideoFilter");
63 
64     std::sort(this->d->m_availableEffects.begin(),
65               this->d->m_availableEffects.end(),
66               [this] (const QString &pluginId1, const QString &pluginId2) {
67         auto desc1 = this->effectDescription(pluginId1);
68         auto desc2 = this->effectDescription(pluginId2);
69 
70         return desc1 < desc2;
71     });
72 
73     QObject::connect(this,
74                      &VideoEffects::effectsChanged,
75                      this,
76                      &VideoEffects::saveEffects);
77     QObject::connect(this,
78                      &VideoEffects::advancedModeChanged,
79                      this,
80                      &VideoEffects::advancedModeUpdated);
81     QObject::connect(this,
82                      &VideoEffects::advancedModeChanged,
83                      this,
84                      &VideoEffects::saveAdvancedMode);
85 
86     this->loadProperties();
87 }
88 
~VideoEffects()89 VideoEffects::~VideoEffects()
90 {
91     this->saveProperties();
92     this->setState(AkElement::ElementStateNull);
93     delete this->d;
94 }
95 
availableEffects() const96 QStringList VideoEffects::availableEffects() const
97 {
98     auto effects = this->d->m_availableEffects;
99 
100     if (this->d->m_advancedMode)
101         for (auto &effect: this->d->m_effects) {
102             int i = effects.indexOf(effect->pluginId());
103 
104             if (i < 0 || effect->property("preview").toBool())
105                 continue;
106 
107             effects.removeAt(i);
108         }
109 
110     return effects;
111 }
112 
effects() const113 QStringList VideoEffects::effects() const
114 {
115     return this->d->m_effectsId;
116 }
117 
effectInfo(const QString & effectId) const118 QVariantMap VideoEffects::effectInfo(const QString &effectId) const
119 {
120     return AkElement::pluginInfo(effectId);
121 }
122 
effectDescription(const QString & effectId) const123 QString VideoEffects::effectDescription(const QString &effectId) const
124 {
125     if (effectId.isEmpty())
126         return QString();
127 
128     auto info = AkElement::pluginInfo(effectId);
129     auto metaData = info["MetaData"].toMap();
130 
131     return metaData["description"].toString();
132 }
133 
state() const134 AkElement::ElementState VideoEffects::state() const
135 {
136     return this->d->m_state;
137 }
138 
advancedMode() const139 bool VideoEffects::advancedMode() const
140 {
141     return this->d->m_advancedMode;
142 }
143 
embedControls(const QString & where,int effectIndex,const QString & name) const144 bool VideoEffects::embedControls(const QString &where,
145                                  int effectIndex,
146                                  const QString &name) const
147 {
148     auto effect = this->d->m_effects.value(effectIndex);
149 
150     if (!effect)
151         return false;
152 
153     auto interface = effect->controlInterface(this->d->m_engine,
154                                               effect->pluginId());
155 
156     if (!interface)
157         return false;
158 
159     if (!name.isEmpty())
160         interface->setObjectName(name);
161 
162     for (auto &obj: this->d->m_engine->rootObjects()) {
163         // First, find where to embed the UI.
164         auto item = obj->findChild<QQuickItem *>(where);
165 
166         if (!item)
167             continue;
168 
169         // Create an item with the plugin context.
170         auto interfaceItem = qobject_cast<QQuickItem *>(interface);
171 
172         // Finally, embed the plugin item UI in the desired place.
173         interfaceItem->setParentItem(item);
174 
175         return true;
176     }
177 
178     return false;
179 }
180 
removeInterface(const QString & where) const181 void VideoEffects::removeInterface(const QString &where) const
182 {
183     if (!this->d->m_engine)
184         return;
185 
186     for (auto &obj: this->d->m_engine->rootObjects()) {
187         auto item = obj->findChild<QQuickItem *>(where);
188 
189         if (!item)
190             continue;
191 
192         QList<decltype(item)> childItems = item->childItems();
193 
194         for (auto &child: childItems) {
195             child->setParentItem(nullptr);
196             child->setParent(nullptr);
197 
198             delete child;
199         }
200     }
201 }
202 
setEffects(const QStringList & effects,bool emitSignal)203 void VideoEffects::setEffects(const QStringList &effects, bool emitSignal)
204 {
205     if (this->d->m_effectsId == effects)
206         return;
207 
208     auto state = this->state();
209 
210     if (state != AkElement::ElementStateNull)
211         this->setState(AkElement::ElementStatePaused);
212 
213     this->d->m_mutex.lock();
214 
215     if (!this->d->m_effects.isEmpty()) {
216         if (this->d->m_videoMux)
217             this->d->m_effects.last()->unlink(this->d->m_videoMux);
218 
219         this->d->m_effects.clear();
220         this->d->m_effectsId.clear();
221     }
222 
223     QStringList curEffects;
224     AkElementPtr prevEffect;
225 
226     for (const QString &effectId: effects)
227         if (auto effect = AkElement::create(effectId)) {
228             this->d->m_effects << effect;
229             this->d->m_effectsId << effectId;
230             curEffects << effectId;
231 
232             if (prevEffect)
233                 prevEffect->link(effect, Qt::DirectConnection);
234             else
235                 prevEffect = effect;
236         }
237 
238     if (!this->d->m_effects.isEmpty() && this->d->m_videoMux)
239         this->d->m_effects.last()->link(this->d->m_videoMux, Qt::DirectConnection);
240 
241     this->d->m_mutex.unlock();
242     this->setState(state);
243 
244     if (emitSignal)
245         emit this->effectsChanged(curEffects);
246 }
247 
setState(AkElement::ElementState state)248 void VideoEffects::setState(AkElement::ElementState state)
249 {
250     if (this->d->m_state == state)
251         return;
252 
253     this->d->m_mutex.lock();
254 
255     if (state == AkElement::ElementStatePlaying)
256         for (auto it = this->d->m_effects.rbegin();
257              it != this->d->m_effects.rend();
258              it++)
259             (*it)->setState(state);
260     else
261         for (auto &effect: this->d->m_effects)
262             effect->setState(state);
263 
264     this->d->m_state = state;
265 
266     this->d->m_mutex.unlock();
267 
268     emit this->stateChanged(state);
269 }
270 
setAdvancedMode(bool advancedMode)271 void VideoEffects::setAdvancedMode(bool advancedMode)
272 {
273     if (this->d->m_advancedMode == advancedMode)
274         return;
275 
276     this->d->m_advancedMode = advancedMode;
277     emit this->advancedModeChanged(advancedMode);
278 }
279 
resetEffects()280 void VideoEffects::resetEffects()
281 {
282     this->setEffects({});
283 }
284 
resetState()285 void VideoEffects::resetState()
286 {
287     this->setState(AkElement::ElementStateNull);
288 }
289 
resetAdvancedMode()290 void VideoEffects::resetAdvancedMode()
291 {
292     this->setAdvancedMode(false);
293 }
294 
appendEffect(const QString & effectId,bool preview)295 AkElementPtr VideoEffects::appendEffect(const QString &effectId, bool preview)
296 {
297     auto effect = AkElement::create(effectId);
298 
299     if (!effect)
300         return AkElementPtr();
301 
302     if (preview)
303         effect->setProperty("preview", preview);
304 
305     auto state = this->state();
306 
307     if (state != AkElement::ElementStateNull)
308         this->setState(AkElement::ElementStatePaused);
309 
310     this->d->m_mutex.lock();
311 
312     if (!this->d->m_effects.isEmpty()) {
313         auto prevEffect = this->d->m_effects.last();
314 
315         if (this->d->m_videoMux)
316             prevEffect->unlink(this->d->m_videoMux);
317 
318         prevEffect->link(effect, Qt::DirectConnection);
319     }
320 
321     if (this->d->m_videoMux)
322         effect->link(this->d->m_videoMux, Qt::DirectConnection);
323 
324     this->d->m_effects << effect;
325 
326     if (!preview)
327         this->d->m_effectsId << effectId;
328 
329     this->d->m_mutex.unlock();
330 
331     this->setState(state);
332 
333     if (!preview)
334         emit this->effectsChanged(this->d->m_effectsId);
335 
336     return effect;
337 }
338 
showPreview(const QString & effectId)339 void VideoEffects::showPreview(const QString &effectId)
340 {
341     this->removeAllPreviews();
342     this->appendEffect(effectId, true);
343 }
344 
setAsPreview(int index,bool preview)345 void VideoEffects::setAsPreview(int index, bool preview)
346 {
347     auto effect = this->d->m_effects.value(index);
348 
349     if (!effect)
350         return;
351 
352     effect->setProperty("preview", preview? true: QVariant());
353 
354     if (preview) {
355         if (index < this->d->m_effectsId.size())
356             this->d->m_effectsId.removeAt(index);
357     } else {
358         if (index >= this->d->m_effectsId.size())
359             this->d->m_effectsId << effect->pluginId();
360     }
361 
362     emit this->effectsChanged(this->d->m_effectsId);
363 }
364 
moveEffect(int from,int to)365 void VideoEffects::moveEffect(int from, int to)
366 {
367     if (from == to
368         || from < 0
369         || from >= this->d->m_effects.size()
370         || to < 0
371         || to > this->d->m_effects.size())
372         return;
373 
374     auto state = this->state();
375 
376     if (state != AkElement::ElementStateNull)
377         this->setState(AkElement::ElementStatePaused);
378 
379     this->d->m_mutex.lock();
380 
381     // Diconnect effect from list.
382     auto effect = this->d->m_effects.value(from);
383     auto prev = this->d->m_effects.value(from - 1);
384     auto next = this->d->m_effects.value(from + 1);
385 
386     if (!next)
387         next = this->d->m_videoMux;
388 
389     if (prev) {
390         prev->unlink(effect);
391         prev->link(next, Qt::DirectConnection);
392     }
393 
394     effect->unlink(next);
395 
396     // Reconnect effect.
397     prev = this->d->m_effects.value(to - 1);
398     next = this->d->m_effects.value(to);
399 
400     if (!next)
401         next = this->d->m_videoMux;
402 
403     if (prev) {
404         prev->unlink(next);
405         prev->link(effect, Qt::DirectConnection);
406     }
407 
408     effect->link(next, Qt::DirectConnection);
409 
410     // Move the effect in the list.
411     this->d->m_effects.move(from, to);
412     this->d->m_effectsId.move(from, to);
413 
414     this->d->m_mutex.unlock();
415 
416     this->setState(state);
417     emit this->effectsChanged(this->d->m_effectsId);
418 }
419 
removeEffect(int index)420 void VideoEffects::removeEffect(int index)
421 {
422     auto state = this->state();
423 
424     if (state != AkElement::ElementStateNull)
425         this->setState(AkElement::ElementStatePaused);
426 
427     this->d->m_mutex.lock();
428 
429     auto effect = this->d->m_effects.value(index);
430 
431     if (effect) {
432         auto next = this->d->m_effects.value(index + 1);
433 
434         if (!next)
435             next = this->d->m_videoMux;
436 
437         effect->unlink(next);
438 
439         auto prev = this->d->m_effects.value(index - 1);
440 
441         if (prev) {
442             prev->unlink(effect);
443             prev->link(next, Qt::DirectConnection);
444         }
445 
446         this->d->m_effects.removeAt(index);
447         this->d->m_effectsId.removeAt(index);
448     }
449 
450     this->d->m_mutex.unlock();
451 
452     this->setState(state);
453 
454     if (effect)
455         emit this->effectsChanged(this->d->m_effectsId);
456 }
457 
removeAllPreviews()458 void VideoEffects::removeAllPreviews()
459 {
460     bool hasPreview = false;
461 
462     for (AkElementPtr &effect: this->d->m_effects)
463         if (effect->property("preview").toBool()) {
464             hasPreview = true;
465 
466             break;
467         }
468 
469     if (!hasPreview)
470         return;
471 
472     auto state = this->state();
473 
474     if (state != AkElement::ElementStateNull)
475         this->setState(AkElement::ElementStatePaused);
476 
477     this->d->m_mutex.lock();
478 
479     for (int i = 0; i < this->d->m_effects.size(); i++) {
480         auto effect = this->d->m_effects[i];
481 
482         if (effect->property("preview").toBool()) {
483             auto prev = this->d->m_effects.value(i - 1);
484             auto next = this->d->m_effects.value(i + 1);
485 
486             if (!next)
487                 next = this->d->m_videoMux;
488 
489             if (prev) {
490                 prev->unlink(effect);
491                 prev->link(next, Qt::DirectConnection);
492             }
493 
494             effect->unlink(next);
495             this->d->m_effects.removeAt(i);
496             i--;
497         }
498     }
499 
500     this->d->m_mutex.unlock();
501     this->setState(state);
502 }
503 
updateEffects()504 void VideoEffects::updateEffects()
505 {
506     QStringList availableEffects = AkElement::listPlugins("VideoFilter");
507 
508     if (this->d->m_availableEffects != availableEffects) {
509         this->d->m_availableEffects = availableEffects;
510         emit this->availableEffectsChanged(availableEffects);
511         QStringList effects;
512 
513         for (auto &effectId: this->d->m_effectsId)
514             if (availableEffects.contains(effectId))
515                 effects << effectId;
516 
517         this->setEffects(effects);
518     }
519 }
520 
iStream(const AkPacket & packet)521 AkPacket VideoEffects::iStream(const AkPacket &packet)
522 {
523     this->d->m_mutex.lock();
524 
525     if (this->d->m_state == AkElement::ElementStatePlaying) {
526         if (this->d->m_effects.isEmpty()) {
527             if (this->d->m_videoMux)
528                 (*this->d->m_videoMux)(packet);
529         } else
530             (*this->d->m_effects.first())(packet);
531     }
532 
533     this->d->m_mutex.unlock();
534 
535     return AkPacket();
536 }
537 
setQmlEngine(QQmlApplicationEngine * engine)538 void VideoEffects::setQmlEngine(QQmlApplicationEngine *engine)
539 {
540     if (this->d->m_engine == engine)
541         return;
542 
543     this->d->m_engine = engine;
544 
545     if (engine)
546         engine->rootContext()->setContextProperty("VideoEffects", this);
547 }
548 
advancedModeUpdated(bool advancedMode)549 void VideoEffects::advancedModeUpdated(bool advancedMode)
550 {
551     if (advancedMode || this->d->m_effects.isEmpty())
552         return;
553 
554     auto effect = this->d->m_effects.last();
555     effect->setProperty("preview", QVariant());
556 
557     auto state = this->state();
558 
559     if (state != AkElement::ElementStateNull)
560         this->setState(AkElement::ElementStatePaused);
561 
562     this->d->m_mutex.lock();
563     this->d->m_effects = {effect};
564     this->d->m_effectsId = QStringList {effect->pluginId()};
565     this->d->m_mutex.unlock();
566 
567     this->setState(state);
568     emit this->effectsChanged(this->d->m_effectsId);
569 }
570 
loadProperties()571 void VideoEffects::loadProperties()
572 {
573     QSettings config;
574 
575     config.beginGroup("VideoEffects");
576     this->setAdvancedMode(config.value("advancedMode").toBool());
577 
578     int size = config.beginReadArray("effects");
579     QStringList effects;
580 
581     for (int i = 0; i < size; i++) {
582         config.setArrayIndex(i);
583         effects << config.value("effect").toString();
584     }
585 
586     config.endArray();
587     config.endGroup();
588 
589     this->setEffects(effects, false);
590 
591     for (auto &effect: this->d->m_effects) {
592         config.beginGroup("VideoEffects_" + effect->pluginId());
593 
594         for (auto &key: config.allKeys())
595             effect->setProperty(key.toStdString().c_str(), config.value(key));
596 
597         config.endGroup();
598     }
599 }
600 
saveEffects(const QStringList & effects)601 void VideoEffects::saveEffects(const QStringList &effects)
602 {
603     Q_UNUSED(effects)
604 
605     QSettings config;
606 
607     config.beginGroup("VideoEffects");
608     config.beginWriteArray("effects");
609 
610     int i = 0;
611 
612     for (auto &effect: this->d->m_effects)
613         if (!effect->property("preview").toBool()) {
614             config.setArrayIndex(i);
615             config.setValue("effect", effect->pluginId());
616             i++;
617         }
618 
619     config.endArray();
620     config.endGroup();
621 
622     for (auto &effect: this->d->m_effects) {
623         config.beginGroup("VideoEffects_" + effect->pluginId());
624 
625         for (int property = 0;
626              property < effect->metaObject()->propertyCount();
627              property++) {
628             auto metaProperty = effect->metaObject()->property(property);
629 
630             if (metaProperty.isWritable()) {
631                 auto propertyName = metaProperty.name();
632                 config.setValue(propertyName, effect->property(propertyName));
633             }
634         }
635 
636         config.endGroup();
637     }
638 }
639 
saveAdvancedMode(bool advancedMode)640 void VideoEffects::saveAdvancedMode(bool advancedMode)
641 {
642     QSettings config;
643 
644     config.beginGroup("VideoEffects");
645     config.setValue("advancedMode", advancedMode);
646     config.endGroup();
647 }
648 
saveProperties()649 void VideoEffects::saveProperties()
650 {
651     QSettings config;
652 
653     config.beginGroup("VideoEffects");
654     config.setValue("advancedMode", this->advancedMode());
655 
656     config.beginWriteArray("effects");
657 
658     int i = 0;
659 
660     for (auto &effect: this->d->m_effects)
661         if (!effect->property("preview").toBool()) {
662             config.setArrayIndex(i);
663             config.setValue("effect", effect->pluginId());
664             i++;
665         }
666 
667     config.endArray();
668     config.endGroup();
669 
670     for (auto &effect: this->d->m_effects) {
671         config.beginGroup("VideoEffects_" + effect->pluginId());
672 
673         for (int property = 0;
674              property < effect->metaObject()->propertyCount();
675              property++) {
676             auto metaProperty = effect->metaObject()->property(property);
677 
678             if (metaProperty.isWritable()) {
679                 auto propertyName = metaProperty.name();
680                 config.setValue(propertyName, effect->property(propertyName));
681             }
682         }
683 
684         config.endGroup();
685     }
686 }
687 
688 #include "moc_videoeffects.cpp"
689