1 /* Webcamoid, webcam capture application.
2 * Copyright (C) 2015 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 <QSize>
21 #include <QMutex>
22 #include <QQmlContext>
23 #include <QQuickItem>
24 #include <QQmlProperty>
25 #include <QQmlApplicationEngine>
26 #include <QSystemTrayIcon>
27 #include <QSettings>
28 #include <QFileInfo>
29 #include <QDateTime>
30 #include <QStandardPaths>
31 #include <QFileDialog>
32 #include <QApplication>
33 #include <ak.h>
34 #include <akcaps.h>
35 #include <akaudiocaps.h>
36 #include <akvideocaps.h>
37
38 #include "mediatools.h"
39 #include "videodisplay.h"
40 #include "iconsprovider.h"
41 #include "pluginconfigs.h"
42 #include "mediasource.h"
43 #include "audiolayer.h"
44 #include "videoeffects.h"
45 #include "recording.h"
46 #include "updates.h"
47 #include "clioptions.h"
48
49 #define COMMONS_PROJECT_URL "https://webcamoid.github.io/"
50 #define COMMONS_PROJECT_LICENSE_URL "https://raw.githubusercontent.com/webcamoid/webcamoid/master/COPYING"
51 #define COMMONS_PROJECT_DOWNLOADS_URL "https://webcamoid.github.io/#downloads"
52 #define COMMONS_PROJECT_ISSUES_URL "https://github.com/webcamoid/webcamoid/issues"
53 #define COMMONS_COPYRIGHT_NOTICE "Copyright (C) 2011-2018 Gonzalo Exequiel Pedone"
54
55 class MediaToolsPrivate
56 {
57 public:
58 QQmlApplicationEngine *m_engine {nullptr};
59 PluginConfigsPtr m_pluginConfigs;
60 MediaSourcePtr m_mediaSource;
61 AudioLayerPtr m_audioLayer;
62 VideoEffectsPtr m_videoEffects;
63 RecordingPtr m_recording;
64 UpdatesPtr m_updates;
65 AkElementPtr m_virtualCamera;
66 QSystemTrayIcon *m_trayIcon {nullptr};
67 CliOptions m_cliOptions;
68 int m_windowWidth {0};
69 int m_windowHeight {0};
70 bool m_enableVirtualCamera {false};
71
72 bool embedInterface(QQmlApplicationEngine *engine,
73 QObject *ctrlInterface,
74 const QString &where) const;
75 };
76
MediaTools(QObject * parent)77 MediaTools::MediaTools(QObject *parent):
78 QObject(parent)
79 {
80 this->d = new MediaToolsPrivate;
81
82 // Initialize environment.
83 this->d->m_trayIcon = new QSystemTrayIcon(QApplication::windowIcon(), this);
84 this->d->m_engine = new QQmlApplicationEngine();
85 this->d->m_engine->addImageProvider(QLatin1String("icons"), new IconsProvider);
86 this->d->m_engine->addImportPath(":/Ak/share/qml");
87 Ak::setQmlEngine(this->d->m_engine);
88 this->d->m_pluginConfigs =
89 PluginConfigsPtr(new PluginConfigs(this->d->m_cliOptions,
90 this->d->m_engine));
91 this->d->m_mediaSource = MediaSourcePtr(new MediaSource(this->d->m_engine));
92 this->d->m_audioLayer = AudioLayerPtr(new AudioLayer(this->d->m_engine));
93 this->d->m_videoEffects = VideoEffectsPtr(new VideoEffects(this->d->m_engine));
94 this->d->m_recording = RecordingPtr(new Recording(this->d->m_engine));
95 this->d->m_updates = UpdatesPtr(new Updates(this->d->m_engine));
96 this->d->m_virtualCamera = AkElement::create("VirtualCamera");
97
98 if (this->d->m_virtualCamera) {
99 AkElement::link(this->d->m_videoEffects.data(),
100 this->d->m_virtualCamera.data(),
101 Qt::DirectConnection);
102 QObject::connect(this->d->m_virtualCamera.data(),
103 SIGNAL(stateChanged(AkElement::ElementState)),
104 this,
105 SIGNAL(virtualCameraStateChanged(AkElement::ElementState)));
106 }
107
108 AkElement::link(this->d->m_mediaSource.data(),
109 this->d->m_videoEffects.data(),
110 Qt::DirectConnection);
111 AkElement::link(this->d->m_mediaSource.data(),
112 this->d->m_audioLayer.data(),
113 Qt::DirectConnection);
114 AkElement::link(this->d->m_videoEffects.data(),
115 this->d->m_recording.data(),
116 Qt::DirectConnection);
117 AkElement::link(this->d->m_audioLayer.data(),
118 this->d->m_recording.data(),
119 Qt::DirectConnection);
120 QObject::connect(this->d->m_mediaSource.data(),
121 &MediaSource::error,
122 this,
123 &MediaTools::error);
124 QObject::connect(this->d->m_mediaSource.data(),
125 &MediaSource::stateChanged,
126 this->d->m_videoEffects.data(),
127 &VideoEffects::setState);
128 QObject::connect(this->d->m_mediaSource.data(),
129 &MediaSource::stateChanged,
130 this->d->m_audioLayer.data(),
131 &AudioLayer::setOutputState);
132 QObject::connect(this->d->m_recording.data(),
133 &Recording::stateChanged,
134 this->d->m_audioLayer.data(),
135 &AudioLayer::setInputState);
136 QObject::connect(this->d->m_mediaSource.data(),
137 &MediaSource::audioCapsChanged,
138 this->d->m_audioLayer.data(),
139 &AudioLayer::setInputCaps);
140 QObject::connect(this->d->m_mediaSource.data(),
141 &MediaSource::streamChanged,
142 this->d->m_audioLayer.data(),
143 [this] (const QString &stream)
144 {
145 this->d->m_audioLayer->setInputDescription(this->d->m_mediaSource->description(stream));
146 });
147 QObject::connect(this->d->m_mediaSource.data(),
148 &MediaSource::streamChanged,
149 this,
150 &MediaTools::updateVCamState);
151 QObject::connect(this->d->m_mediaSource.data(),
152 &MediaSource::videoCapsChanged,
153 this,
154 &MediaTools::updateVCamCaps);
155 QObject::connect(this,
156 &MediaTools::enableVirtualCameraChanged,
157 this,
158 &MediaTools::updateVCamState);
159 QObject::connect(this->d->m_pluginConfigs.data(),
160 &PluginConfigs::pluginsChanged,
161 this->d->m_videoEffects.data(),
162 &VideoEffects::updateEffects);
163 QObject::connect(this->d->m_audioLayer.data(),
164 &AudioLayer::outputCapsChanged,
165 this->d->m_recording.data(),
166 &Recording::setAudioCaps);
167 QObject::connect(this->d->m_mediaSource.data(),
168 &MediaSource::videoCapsChanged,
169 this->d->m_recording.data(),
170 &Recording::setVideoCaps);
171 QObject::connect(qApp,
172 &QCoreApplication::aboutToQuit,
173 this->d->m_mediaSource.data(),
174 [this] () {
175 this->d->m_mediaSource->setState(AkElement::ElementStateNull);
176 });
177
178 this->loadConfigs();
179 this->updateVCamCaps(this->d->m_mediaSource->videoCaps());
180 this->d->m_recording->setVideoCaps(this->d->m_mediaSource->videoCaps());
181 this->d->m_recording->setAudioCaps(this->d->m_audioLayer->outputCaps());
182 this->d->m_audioLayer->setInputCaps(this->d->m_mediaSource->audioCaps());
183 this->d->m_audioLayer->setInputDescription(this->d->m_mediaSource->description(this->d->m_mediaSource->stream()));
184 }
185
~MediaTools()186 MediaTools::~MediaTools()
187 {
188 this->saveConfigs();
189 delete this->d->m_engine;
190 delete this->d;
191 }
192
windowWidth() const193 int MediaTools::windowWidth() const
194 {
195 return this->d->m_windowWidth;
196 }
197
windowHeight() const198 int MediaTools::windowHeight() const
199 {
200 return this->d->m_windowHeight;
201 }
202
enableVirtualCamera() const203 bool MediaTools::enableVirtualCamera() const
204 {
205 return this->d->m_enableVirtualCamera;
206 }
207
virtualCameraState() const208 AkElement::ElementState MediaTools::virtualCameraState() const
209 {
210 if (this->d->m_virtualCamera)
211 return this->d->m_virtualCamera->state();
212
213 return AkElement::ElementStateNull;
214 }
215
applicationName() const216 QString MediaTools::applicationName() const
217 {
218 return QCoreApplication::applicationName();
219 }
220
applicationVersion() const221 QString MediaTools::applicationVersion() const
222 {
223 #ifdef DAILY_BUILD
224 return QString(tr("Daily Build"));
225 #else
226 return QCoreApplication::applicationVersion();
227 #endif
228 }
229
qtVersion() const230 QString MediaTools::qtVersion() const
231 {
232 return QT_VERSION_STR;
233 }
234
copyrightNotice() const235 QString MediaTools::copyrightNotice() const
236 {
237 return COMMONS_COPYRIGHT_NOTICE;
238 }
239
projectUrl() const240 QString MediaTools::projectUrl() const
241 {
242 return COMMONS_PROJECT_URL;
243 }
244
projectLicenseUrl() const245 QString MediaTools::projectLicenseUrl() const
246 {
247 return COMMONS_PROJECT_LICENSE_URL;
248 }
249
projectDownloadsUrl() const250 QString MediaTools::projectDownloadsUrl() const
251 {
252 return COMMONS_PROJECT_DOWNLOADS_URL;
253 }
254
projectIssuesUrl() const255 QString MediaTools::projectIssuesUrl() const
256 {
257 return COMMONS_PROJECT_ISSUES_URL;
258 }
259
fileNameFromUri(const QString & uri) const260 QString MediaTools::fileNameFromUri(const QString &uri) const
261 {
262 return QFileInfo(uri).baseName();
263 }
264
matches(const QString & pattern,const QStringList & strings) const265 bool MediaTools::matches(const QString &pattern,
266 const QStringList &strings) const
267 {
268 if (pattern.isEmpty())
269 return true;
270
271 for (const QString &str: strings)
272 if (str.contains(QRegExp(pattern,
273 Qt::CaseInsensitive,
274 QRegExp::Wildcard)))
275 return true;
276
277 return false;
278 }
279
currentTime() const280 QString MediaTools::currentTime() const
281 {
282 return QDateTime::currentDateTime().toString("yyyy-MM-dd hh-mm-ss");
283 }
284
standardLocations(const QString & type) const285 QStringList MediaTools::standardLocations(const QString &type) const
286 {
287 static const QMap<QString, QStandardPaths::StandardLocation> stdPaths = {
288 {"movies" , QStandardPaths::MoviesLocation },
289 {"pictures", QStandardPaths::PicturesLocation},
290 };
291
292 if (stdPaths.contains(type))
293 return QStandardPaths::standardLocations(stdPaths[type]);
294
295 return QStringList();
296 }
297
saveFileDialog(const QString & caption,const QString & fileName,const QString & directory,const QString & suffix,const QString & filters) const298 QString MediaTools::saveFileDialog(const QString &caption,
299 const QString &fileName,
300 const QString &directory,
301 const QString &suffix,
302 const QString &filters) const
303 {
304 QFileDialog saveFileDialog(nullptr,
305 caption,
306 fileName,
307 filters);
308
309 saveFileDialog.setModal(true);
310 saveFileDialog.setDefaultSuffix(suffix);
311 saveFileDialog.setDirectory(directory);
312 saveFileDialog.setFileMode(QFileDialog::AnyFile);
313 saveFileDialog.setAcceptMode(QFileDialog::AcceptSave);
314
315 if (saveFileDialog.exec() == QDialog::Accepted)
316 return saveFileDialog.selectedFiles().value(0);
317
318 return QString();
319 }
320
readFile(const QString & fileName) const321 QString MediaTools::readFile(const QString &fileName) const
322 {
323 QFile file(fileName);
324 file.open(QIODevice::ReadOnly | QIODevice::Text);
325 QString data = file.readAll();
326 file.close();
327
328 return data;
329 }
330
urlToLocalFile(const QUrl & url) const331 QString MediaTools::urlToLocalFile(const QUrl &url) const
332 {
333 return url.toLocalFile();
334 }
335
embedVirtualCameraControls(const QString & where,const QString & name)336 bool MediaTools::embedVirtualCameraControls(const QString &where,
337 const QString &name)
338 {
339 if (!this->d->m_virtualCamera)
340 return false;
341
342 auto ctrlInterface = this->d->m_virtualCamera->controlInterface(this->d->m_engine, "");
343
344 if (!ctrlInterface)
345 return false;
346
347 if (!name.isEmpty())
348 ctrlInterface->setObjectName(name);
349
350 return this->d->embedInterface(this->d->m_engine, ctrlInterface, where);
351 }
352
removeInterface(const QString & where,QQmlApplicationEngine * engine)353 void MediaTools::removeInterface(const QString &where,
354 QQmlApplicationEngine *engine)
355 {
356 if (!engine)
357 engine = this->d->m_engine;
358
359 if (!engine)
360 return;
361
362 for (auto &obj: engine->rootObjects()) {
363 auto item = obj->findChild<QQuickItem *>(where);
364
365 if (!item)
366 continue;
367
368 auto childItems = item->childItems();
369
370 for (QQuickItem *child: childItems) {
371 child->setParentItem(nullptr);
372 child->setParent(nullptr);
373
374 delete child;
375 }
376 }
377 }
378
convertToAbsolute(const QString & path)379 QString MediaTools::convertToAbsolute(const QString &path)
380 {
381 if (!QDir::isRelativePath(path))
382 return QDir::cleanPath(path);
383
384 static const QDir applicationDir(QCoreApplication::applicationDirPath());
385 QString absPath = applicationDir.absoluteFilePath(path);
386
387 return QDir::cleanPath(absPath).replace('/', QDir::separator());
388 }
389
embedInterface(QQmlApplicationEngine * engine,QObject * ctrlInterface,const QString & where) const390 bool MediaToolsPrivate::embedInterface(QQmlApplicationEngine *engine,
391 QObject *ctrlInterface,
392 const QString &where) const
393 {
394 if (!engine || !ctrlInterface)
395 return false;
396
397 for (auto &obj: engine->rootObjects()) {
398 // First, find where to embed the UI.
399 auto item = obj->findChild<QQuickItem *>(where);
400
401 if (!item)
402 continue;
403
404 // Create an item with the plugin context.
405 auto interfaceItem = qobject_cast<QQuickItem *>(ctrlInterface);
406
407 // Finally, embed the plugin item UI in the desired place.
408 interfaceItem->setParentItem(item);
409
410 return true;
411 }
412
413 return false;
414 }
415
setWindowWidth(int windowWidth)416 void MediaTools::setWindowWidth(int windowWidth)
417 {
418 if (this->d->m_windowWidth == windowWidth)
419 return;
420
421 this->d->m_windowWidth = windowWidth;
422 emit this->windowWidthChanged(windowWidth);
423 }
424
setWindowHeight(int windowHeight)425 void MediaTools::setWindowHeight(int windowHeight)
426 {
427 if (this->d->m_windowHeight == windowHeight)
428 return;
429
430 this->d->m_windowHeight = windowHeight;
431 emit this->windowHeightChanged(windowHeight);
432 }
433
setEnableVirtualCamera(bool enableVirtualCamera)434 void MediaTools::setEnableVirtualCamera(bool enableVirtualCamera)
435 {
436 if (this->d->m_enableVirtualCamera == enableVirtualCamera)
437 return;
438
439 this->d->m_enableVirtualCamera = enableVirtualCamera;
440 emit this->enableVirtualCameraChanged(enableVirtualCamera);
441 }
442
setVirtualCameraState(AkElement::ElementState virtualCameraState)443 void MediaTools::setVirtualCameraState(AkElement::ElementState virtualCameraState)
444 {
445 if (this->d->m_virtualCamera) {
446 auto state = virtualCameraState;
447 auto vcamStream = this->d->m_virtualCamera->property("media").toString();
448
449 if (this->d->m_enableVirtualCamera
450 && virtualCameraState == AkElement::ElementStatePlaying
451 && this->d->m_mediaSource->state() == AkElement::ElementStatePlaying
452 && this->d->m_mediaSource->stream() == vcamStream) {
453 // Prevents self blocking by pausing the virtual camera.
454 state = AkElement::ElementStatePaused;
455 }
456
457 this->d->m_virtualCamera->setState(state);
458 }
459 }
460
resetWindowWidth()461 void MediaTools::resetWindowWidth()
462 {
463 this->setWindowWidth(0);
464 }
465
resetWindowHeight()466 void MediaTools::resetWindowHeight()
467 {
468 this->setWindowHeight(0);
469 }
470
resetEnableVirtualCamera()471 void MediaTools::resetEnableVirtualCamera()
472 {
473 this->setEnableVirtualCamera(false);
474 }
475
resetVirtualCameraState()476 void MediaTools::resetVirtualCameraState()
477 {
478 this->setVirtualCameraState(AkElement::ElementStateNull);
479 }
480
loadConfigs()481 void MediaTools::loadConfigs()
482 {
483 QSettings config;
484
485 config.beginGroup("GeneralConfigs");
486 QSize windowSize = config.value("windowSize", QSize(1024, 600)).toSize();
487 this->d->m_windowWidth = windowSize.width();
488 this->d->m_windowHeight = windowSize.height();
489 config.endGroup();
490
491 config.beginGroup("VirtualCamera");
492 this->setEnableVirtualCamera(config.value("enable", false).toBool());
493 config.endGroup();
494
495 auto optPaths =
496 this->d->m_cliOptions.value(this->d->m_cliOptions.vcamPathOpt()).split(';');
497
498 QStringList driverPaths;
499
500 for (auto path: optPaths) {
501 path = this->convertToAbsolute(path);
502
503 if (QFileInfo::exists(path))
504 driverPaths << path;
505 }
506
507 if (this->d->m_virtualCamera)
508 QMetaObject::invokeMethod(this->d->m_virtualCamera.data(),
509 "addDriverPaths",
510 Q_ARG(QStringList, driverPaths));
511 }
512
saveConfigs()513 void MediaTools::saveConfigs()
514 {
515 QSettings config;
516
517 config.beginGroup("GeneralConfigs");
518 config.setValue("windowSize", QSize(this->d->m_windowWidth,
519 this->d->m_windowHeight));
520 config.endGroup();
521
522 config.beginGroup("VirtualCamera");
523 config.setValue("enable", this->enableVirtualCamera());
524 config.endGroup();
525 }
526
show()527 void MediaTools::show()
528 {
529 // @uri Webcamoid
530 qmlRegisterType<VideoDisplay>("Webcamoid", 1, 0, "VideoDisplay");
531 this->d->m_engine->rootContext()->setContextProperty("Webcamoid", this);
532
533 // Map tray icon to QML
534 this->d->m_engine->rootContext()->setContextProperty("trayIcon", this->d->m_trayIcon);
535
536 // Map tray icon enums to QML
537 this->d->m_engine->rootContext()->setContextProperty("TrayIcon_NoIcon", QSystemTrayIcon::NoIcon);
538 this->d->m_engine->rootContext()->setContextProperty("TrayIcon_Information", QSystemTrayIcon::Information);
539 this->d->m_engine->rootContext()->setContextProperty("TrayIcon_Warning", QSystemTrayIcon::Warning);
540 this->d->m_engine->rootContext()->setContextProperty("TrayIcon_Critical", QSystemTrayIcon::Critical);
541
542 this->d->m_engine->load(QUrl(QStringLiteral("qrc:/Webcamoid/share/qml/main.qml")));
543
544 for (auto &obj: this->d->m_engine->rootObjects()) {
545 // First, find where to enbed the UI.
546 auto videoDisplay = obj->findChild<VideoDisplay *>("videoDisplay");
547
548 if (!videoDisplay)
549 continue;
550
551 AkElement::link(this->d->m_videoEffects.data(),
552 videoDisplay,
553 Qt::DirectConnection);
554 break;
555 }
556
557 emit this->interfaceLoaded();
558 }
559
updateVCamCaps(const AkCaps & videoCaps)560 void MediaTools::updateVCamCaps(const AkCaps &videoCaps)
561 {
562 if (!this->d->m_virtualCamera)
563 return;
564
565 QMetaObject::invokeMethod(this->d->m_virtualCamera.data(),
566 "clearStreams");
567 QMetaObject::invokeMethod(this->d->m_virtualCamera.data(),
568 "addStream",
569 Q_ARG(int, 0),
570 Q_ARG(AkCaps, videoCaps));
571 }
572
updateVCamState()573 void MediaTools::updateVCamState()
574 {
575 if (!this->d->m_virtualCamera)
576 return;
577
578 if (this->d->m_enableVirtualCamera) {
579 if (this->d->m_mediaSource->state() == AkElement::ElementStatePlaying) {
580 auto vcamStream = this->d->m_virtualCamera->property("media").toString();
581
582 // Prevents self blocking by pausing the virtual camera.
583 auto state = this->d->m_mediaSource->stream() == vcamStream?
584 AkElement::ElementStatePaused:
585 AkElement::ElementStatePlaying;
586 this->d->m_virtualCamera->setState(state);
587 }
588 } else
589 this->d->m_virtualCamera->setState(AkElement::ElementStateNull);
590 }
591
592 #include "moc_mediatools.cpp"
593