From 122d2c786981b0bed68899b64322efb173698764 Mon Sep 17 00:00:00 2001 From: Elias Steurer Date: Sat, 28 Aug 2021 17:54:19 +0200 Subject: [PATCH] Rewrite video convert to fix threading issues Add unit tests for video import --- ScreenPlay/CMakeLists.txt | 1 + ScreenPlay/main.qml | 2 +- ScreenPlay/qml/Create/Sidebar.qml | 7 +- .../ImportVideoAndConvert/CreateWallpaper.qml | 4 +- .../CreateWallpaperInit.qml | 2 + .../CreateWallpaperVideoImportConvert.qml | 149 ++++++------- .../Wizards/ImportWebm/ImportWebmConvert.qml | 30 +-- ScreenPlay/qml/Create/Wizards/WizardPage.qml | 2 +- ScreenPlay/qml/Navigation/NavigationItem.qml | 1 + ScreenPlay/src/create.cpp | 203 ++++++++++++------ ScreenPlay/src/create.h | 30 +-- ScreenPlay/src/createimportstates.h | 52 +++++ ScreenPlay/src/createimportvideo.cpp | 171 +++++---------- ScreenPlay/src/createimportvideo.h | 51 +---- ScreenPlay/tests/tst_main.cpp | 124 ++++++++++- ScreenPlay/translations/ScreenPlay_de.ts | 34 +-- ScreenPlay/translations/ScreenPlay_en.ts | 34 +-- ScreenPlay/translations/ScreenPlay_es.ts | 34 +-- ScreenPlay/translations/ScreenPlay_fr.ts | 34 +-- ScreenPlay/translations/ScreenPlay_ko.ts | 34 +-- ScreenPlay/translations/ScreenPlay_pt_br.ts | 34 +-- ScreenPlay/translations/ScreenPlay_ru.ts | 34 +-- ScreenPlay/translations/ScreenPlay_vi.ts | 34 +-- ScreenPlay/translations/ScreenPlay_zh_cn.qm | Bin 144 -> 34276 bytes ScreenPlay/translations/ScreenPlay_zh_cn.ts | 37 ++-- 25 files changed, 627 insertions(+), 511 deletions(-) create mode 100644 ScreenPlay/src/createimportstates.h diff --git a/ScreenPlay/CMakeLists.txt b/ScreenPlay/CMakeLists.txt index 1342cace..d2e568dc 100644 --- a/ScreenPlay/CMakeLists.txt +++ b/ScreenPlay/CMakeLists.txt @@ -35,6 +35,7 @@ set(headers app.h src/globalvariables.h src/createimportvideo.h + src/createimportstates.h src/installedlistmodel.h src/monitorlistmodel.h src/screenplaywallpaper.h diff --git a/ScreenPlay/main.qml b/ScreenPlay/main.qml index e47ac1ca..48f6ab2b 100644 --- a/ScreenPlay/main.qml +++ b/ScreenPlay/main.qml @@ -105,7 +105,7 @@ ApplicationWindow { StackView { id: stackView - + objectName: "stackView" property int duration: 300 anchors { diff --git a/ScreenPlay/qml/Create/Sidebar.qml b/ScreenPlay/qml/Create/Sidebar.qml index 1cd64fb1..0314e52d 100644 --- a/ScreenPlay/qml/Create/Sidebar.qml +++ b/ScreenPlay/qml/Create/Sidebar.qml @@ -11,6 +11,7 @@ import ScreenPlay.QMLUtilities 1.0 Rectangle { id: root + objectName: "createSidebar" property bool expanded: false property alias listView: listView @@ -87,6 +88,7 @@ Rectangle { */ id: listView + objectName: "wizardsListView" anchors.fill: parent anchors.margins: 20 @@ -119,9 +121,10 @@ Rectangle { } ListElement { - headline: "Video import and convert (all types)" + headline: qsTr("Video import and convert (all types)") source: "qrc:/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaper.qml" category: "Video Wallpaper" + objectName: "videoImportConvert" } ListElement { @@ -193,7 +196,7 @@ Rectangle { delegate: Button { id: listItem - + objectName: model.objectName width: listView.width - 40 height: 45 highlighted: ListView.isCurrentItem diff --git a/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaper.qml b/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaper.qml index 42a25037..bc459ab3 100644 --- a/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaper.qml +++ b/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaper.qml @@ -21,12 +21,14 @@ Item { clip: true CreateWallpaperInit { - onNext: { + onNext: startConvert(filePath,codec); + function startConvert(filePath,codec){ root.wizardStarted(); swipeView.currentIndex = 1; createWallpaperVideoImportConvert.codec = codec; createWallpaperVideoImportConvert.filePath = filePath; ScreenPlay.create.createWallpaperStart(filePath, codec, quality); + } } diff --git a/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaperInit.qml b/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaperInit.qml index 00dda2ca..5e9fc533 100644 --- a/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaperInit.qml +++ b/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaperInit.qml @@ -10,6 +10,7 @@ import "../../../Common" as Common Item { id: root + objectName: "createWallpaperInit" property int quality: sliderQuality.slider.value @@ -127,6 +128,7 @@ Item { } Button { + objectName: "createWallpaperInitFileSelectButton" text: qsTr("Select file") highlighted: true font.family: ScreenPlay.settings.font diff --git a/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaperVideoImportConvert.qml b/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaperVideoImportConvert.qml index 63a0701d..9717fdd0 100644 --- a/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaperVideoImportConvert.qml +++ b/ScreenPlay/qml/Create/Wizards/ImportVideoAndConvert/CreateWallpaperVideoImportConvert.qml @@ -5,6 +5,7 @@ import QtQuick.Controls.Material 2.3 import QtQuick.Layouts 1.12 import ScreenPlay 1.0 import ScreenPlay.Create 1.0 +import ScreenPlay.Enums.ImportVideoState 1.0 import "../../../Common" as Common Item { @@ -15,86 +16,87 @@ Item { property var codec: Create.VP8 property string filePath - signal abort() - signal save() + signal abort + signal save function cleanup() { - ScreenPlay.create.abortAndCleanup(); + ScreenPlay.create.cancel() } function basename(str) { - let filenameWithExtentions = (str.slice(str.lastIndexOf("/") + 1)); - let filename = filenameWithExtentions.split('.').slice(0, -1).join('.'); - return filename; + let filenameWithExtentions = (str.slice(str.lastIndexOf("/") + 1)) + let filename = filenameWithExtentions.split('.').slice(0, -1).join('.') + return filename } function checkCanSave() { if (canSave && conversionFinishedSuccessful) - btnSave.enabled = true; + btnSave.enabled = true else - btnSave.enabled = false; + btnSave.enabled = false } onCanSaveChanged: root.checkCanSave() onFilePathChanged: { - textFieldName.text = basename(filePath); + textFieldName.text = basename(filePath) } Connections { function onCreateWallpaperStateChanged(state) { switch (state) { - case CreateImportVideo.ConvertingPreviewImage: - txtConvert.text = qsTr("Generating preview image..."); - break; - case CreateImportVideo.ConvertingPreviewThumbnailImage: - txtConvert.text = qsTr("Generating preview thumbnail image..."); - break; - case CreateImportVideo.ConvertingPreviewImageFinished: - imgPreview.source = "file:///" + ScreenPlay.create.workingDir + "/preview.jpg"; - imgPreview.visible = true; - break; - case CreateImportVideo.ConvertingPreviewVideo: - txtConvert.text = qsTr("Generating 5 second preview video..."); - break; - case CreateImportVideo.ConvertingPreviewGif: - txtConvert.text = qsTr("Generating preview gif..."); - break; - case CreateImportVideo.ConvertingPreviewGifFinished: - gifPreview.source = "file:///" + ScreenPlay.create.workingDir + "/preview.gif"; - imgPreview.visible = false; - gifPreview.visible = true; - gifPreview.playing = true; - break; - case CreateImportVideo.ConvertingAudio: - txtConvert.text = qsTr("Converting Audio..."); - break; - case CreateImportVideo.ConvertingVideo: - txtConvert.text = qsTr("Converting Video... This can take some time!"); - break; - case CreateImportVideo.ConvertingVideoError: - txtConvert.text = qsTr("Converting Video ERROR!"); - break; - case CreateImportVideo.AnalyseVideoError: - txtConvert.text = qsTr("Analyse Video ERROR!"); - break; - case CreateImportVideo.Finished: - txtConvert.text = ""; - conversionFinishedSuccessful = true; - busyIndicator.running = false; - root.checkCanSave(); - break; + case ImportVideoState.ConvertingPreviewImage: + txtConvert.text = qsTr("Generating preview image...") + break + case ImportVideoState.ConvertingPreviewThumbnailImage: + txtConvert.text = qsTr("Generating preview thumbnail image...") + break + case ImportVideoState.ConvertingPreviewImageFinished: + imgPreview.source = "file:///" + ScreenPlay.create.workingDir + "/preview.jpg" + imgPreview.visible = true + break + case ImportVideoState.ConvertingPreviewVideo: + txtConvert.text = qsTr("Generating 5 second preview video...") + break + case ImportVideoState.ConvertingPreviewGif: + txtConvert.text = qsTr("Generating preview gif...") + break + case ImportVideoState.ConvertingPreviewGifFinished: + gifPreview.source = "file:///" + ScreenPlay.create.workingDir + "/preview.gif" + imgPreview.visible = false + gifPreview.visible = true + gifPreview.playing = true + break + case ImportVideoState.ConvertingAudio: + txtConvert.text = qsTr("Converting Audio...") + break + case ImportVideoState.ConvertingVideo: + txtConvert.text = qsTr( + "Converting Video... This can take some time!") + break + case ImportVideoState.ConvertingVideoError: + txtConvert.text = qsTr("Converting Video ERROR!") + break + case ImportVideoState.AnalyseVideoError: + txtConvert.text = qsTr("Analyse Video ERROR!") + break + case ImportVideoState.Finished: + txtConvert.text = "" + conversionFinishedSuccessful = true + busyIndicator.running = false + root.checkCanSave() + break } } function onProgressChanged(progress) { - var percentage = Math.floor(progress * 100); + var percentage = Math.floor(progress * 100) if (percentage > 100 || progress > 0.95) - percentage = 100; + percentage = 100 if (percentage === NaN) - print(progress, percentage); + print(progress, percentage) - txtConvertNumber.text = percentage + "%"; + txtConvertNumber.text = percentage + "%" } target: ScreenPlay.create @@ -116,7 +118,6 @@ Item { margins: 40 bottomMargin: 0 } - } Item { @@ -186,9 +187,7 @@ Item { position: 1 color: "#00000000" } - } - } BusyIndicator { @@ -211,7 +210,6 @@ Item { bottom: parent.bottom bottomMargin: 40 } - } Text { @@ -227,9 +225,7 @@ Item { bottom: parent.bottom bottomMargin: 20 } - } - } Common.ImageSelector { @@ -243,9 +239,7 @@ Item { left: parent.left bottom: parent.bottom } - } - } Item { @@ -283,9 +277,9 @@ Item { Layout.fillWidth: true onTextChanged: { if (textFieldName.text.length >= 3) - canSave = true; + canSave = true else - canSave = false; + canSave = false } } @@ -311,7 +305,6 @@ Item { width: parent.width Layout.fillWidth: true } - } Row { @@ -336,14 +329,14 @@ Item { Material.foreground: "white" font.family: ScreenPlay.settings.font onClicked: { - root.abort(); - ScreenPlay.create.abortAndCleanup(); + root.abort() + ScreenPlay.create.cancel() } } Button { id: btnSave - + objectName: "btnSave" text: qsTr("Save") enabled: false Material.background: Material.accent @@ -351,16 +344,18 @@ Item { font.family: ScreenPlay.settings.font onClicked: { if (conversionFinishedSuccessful) { - btnSave.enabled = false; - ScreenPlay.create.saveWallpaper(textFieldName.text, textFieldDescription.text, root.filePath, previewSelector.imageSource, textFieldYoutubeURL.text, codec, textFieldTags.getTags()); - savePopup.open(); - ScreenPlay.installedListModel.reset(); + btnSave.enabled = false + ScreenPlay.create.saveWallpaper( + textFieldName.text, + textFieldDescription.text, root.filePath, + previewSelector.imageSource, + textFieldYoutubeURL.text, codec, + textFieldTags.getTags()) + savePopup.open() } } } - } - } Popup { @@ -392,12 +387,10 @@ Item { interval: 1000 + Math.random() * 1000 onTriggered: { - savePopup.close(); - ScreenPlay.util.setNavigationActive(true); - ScreenPlay.util.setNavigation("Installed"); + savePopup.close() + ScreenPlay.util.setNavigationActive(true) + ScreenPlay.util.setNavigation("Installed") } } - } - } diff --git a/ScreenPlay/qml/Create/Wizards/ImportWebm/ImportWebmConvert.qml b/ScreenPlay/qml/Create/Wizards/ImportWebm/ImportWebmConvert.qml index 0d15579b..049e040d 100644 --- a/ScreenPlay/qml/Create/Wizards/ImportWebm/ImportWebmConvert.qml +++ b/ScreenPlay/qml/Create/Wizards/ImportWebm/ImportWebmConvert.qml @@ -5,6 +5,7 @@ import QtQuick.Controls.Material 2.3 import QtQuick.Layouts 1.12 import ScreenPlay 1.0 import ScreenPlay.Create 1.0 +import ScreenPlay.Enums.ImportVideoState 1.0 import "../../../Common" as Common Item { @@ -38,44 +39,44 @@ Item { Connections { function onCreateWallpaperStateChanged(state) { switch (state) { - case CreateImportVideo.AnalyseVideo: + case ImportVideoState.AnalyseVideo: txtConvert.text = qsTr("AnalyseVideo..."); break; - case CreateImportVideo.ConvertingPreviewImage: + case ImportVideoState.ConvertingPreviewImage: txtConvert.text = qsTr("Generating preview image..."); break; - case CreateImportVideo.ConvertingPreviewThumbnailImage: + case ImportVideoState.ConvertingPreviewThumbnailImage: txtConvert.text = qsTr("Generating preview thumbnail image..."); break; - case CreateImportVideo.ConvertingPreviewImageFinished: + case ImportVideoState.ConvertingPreviewImageFinished: imgPreview.source = "file:///" + ScreenPlay.create.workingDir + "/preview.jpg"; imgPreview.visible = true; break; - case CreateImportVideo.ConvertingPreviewVideo: + case ImportVideoState.ConvertingPreviewVideo: txtConvert.text = qsTr("Generating 5 second preview video..."); break; - case CreateImportVideo.ConvertingPreviewGif: + case ImportVideoState.ConvertingPreviewGif: txtConvert.text = qsTr("Generating preview gif..."); break; - case CreateImportVideo.ConvertingPreviewGifFinished: + case ImportVideoState.ConvertingPreviewGifFinished: gifPreview.source = "file:///" + ScreenPlay.create.workingDir + "/preview.gif"; imgPreview.visible = false; gifPreview.visible = true; gifPreview.playing = true; break; - case CreateImportVideo.ConvertingAudio: + case ImportVideoState.ConvertingAudio: txtConvert.text = qsTr("Converting Audio..."); break; - case CreateImportVideo.ConvertingVideo: + case ImportVideoState.ConvertingVideo: txtConvert.text = qsTr("Converting Video... This can take some time!"); break; - case CreateImportVideo.ConvertingVideoError: + case ImportVideoState.ConvertingVideoError: txtConvert.text = qsTr("Converting Video ERROR!"); break; - case CreateImportVideo.AnalyseVideoError: + case ImportVideoState.AnalyseVideoError: txtConvert.text = qsTr("Analyse Video ERROR!"); break; - case CreateImportVideo.Finished: + case ImportVideoState.Finished: txtConvert.text = ""; conversionFinishedSuccessful = true; busyIndicator.running = false; @@ -330,13 +331,13 @@ Item { font.family: ScreenPlay.settings.font onClicked: { root.exit(); - ScreenPlay.create.abortAndCleanup(); + ScreenPlay.create.cancel(); } } Button { id: btnSave - + objectName: "btnSave" text: qsTr("Save") enabled: false Material.background: Material.accent @@ -347,7 +348,6 @@ Item { btnSave.enabled = false; ScreenPlay.create.saveWallpaper(textFieldName.text, textFieldDescription.text, root.filePath, previewSelector.imageSource, textFieldYoutubeURL.text, Create.VP9, textFieldTags.getTags()); savePopup.open(); - ScreenPlay.installedListModel.reset(); } } } diff --git a/ScreenPlay/qml/Create/Wizards/WizardPage.qml b/ScreenPlay/qml/Create/Wizards/WizardPage.qml index c1439d80..94cfa4ac 100644 --- a/ScreenPlay/qml/Create/Wizards/WizardPage.qml +++ b/ScreenPlay/qml/Create/Wizards/WizardPage.qml @@ -65,7 +65,7 @@ FocusScope { Button { id: btnSave - + objectName: "btnSave" text: qsTr("Save") enabled: root.ready Material.background: Material.accent diff --git a/ScreenPlay/qml/Navigation/NavigationItem.qml b/ScreenPlay/qml/Navigation/NavigationItem.qml index 69aa3ace..c79af723 100644 --- a/ScreenPlay/qml/Navigation/NavigationItem.qml +++ b/ScreenPlay/qml/Navigation/NavigationItem.qml @@ -5,6 +5,7 @@ import ScreenPlay 1.0 Item { id: navigationItem + objectName: txt.text property string iconSource: "qrc:/assets/icons/icon_installed.svg" property alias name: txt.text diff --git a/ScreenPlay/src/create.cpp b/ScreenPlay/src/create.cpp index df9bc39a..261666a5 100644 --- a/ScreenPlay/src/create.cpp +++ b/ScreenPlay/src/create.cpp @@ -10,23 +10,16 @@ namespace ScreenPlay { As for this writing (April 2019) it is solely used to import webm wallpaper and create the gif/web 5 second previews. - - */ /*! Constructor. */ -Create::Create(const std::shared_ptr& globalVariables, QObject* parent) - : QObject(parent) +Create::Create(const std::shared_ptr& globalVariables) + : QObject(nullptr) , m_globalVariables(globalVariables) - { - qRegisterMetaType("CreateImportVideo::ImportVideoState"); - qRegisterMetaType("Create::VideoCodec"); - qmlRegisterUncreatableType("ScreenPlay.Create", 1, 0, "CreateImportVideo", "Error only for enums"); - qmlRegisterUncreatableType("ScreenPlay.Create", 1, 0, "VideoCodec", "Error only for enums"); - qmlRegisterType("ScreenPlay.Create", 1, 0, "Create"); + init(); } /*! @@ -35,19 +28,36 @@ Create::Create(const std::shared_ptr& globalVariables, QObject* Create::Create() : QObject(nullptr) { - qRegisterMetaType("CreateImportVideo::ImportVideoState"); - qRegisterMetaType("Create::VideoCodec"); - qmlRegisterUncreatableType("ScreenPlay.Create", 1, 0, "CreateImportVideo", "Error only for enums"); - qmlRegisterUncreatableType("ScreenPlay.Create", 1, 0, "VideoCodec", "Error only for enums"); - qmlRegisterType("ScreenPlay.Create", 1, 0, "Create"); + init(); } +void Create::init() +{ + qRegisterMetaType("Create::VideoCodec"); + qmlRegisterUncreatableType("ScreenPlay.Create", 1, 0, "VideoCodec", "Error only for enums"); + qmlRegisterType("ScreenPlay.Create", 1, 0, "Create"); + + qRegisterMetaType("ImportVideoState::ImportVideoState"); + qmlRegisterUncreatableMetaObject(ScreenPlay::ImportVideoState::staticMetaObject, + "ScreenPlay.Enums.ImportVideoState", + 1, 0, + "ImportVideoState", + "Error: only enums"); +} + +void Create::reset() +{ + clearFfmpegOutput(); + m_interrupt = false; + setProgress(0.); + setWorkingDir({}); +} /*! \brief Starts the process. */ void Create::createWallpaperStart(QString videoPath, Create::VideoCodec codec, const int quality) { - clearFfmpegOutput(); + reset(); videoPath = ScreenPlayUtil::toLocal(videoPath); const QDir dir(m_globalVariables->localStoragePath().toLocalFile()); @@ -56,10 +66,11 @@ void Create::createWallpaperStart(QString videoPath, Create::VideoCodec codec, c const auto folderName = QString("_tmp_" + QTime::currentTime().toString()).replace(":", ""); if (!dir.mkdir(folderName)) { - emit createWallpaperStateChanged(CreateImportVideo::ImportVideoState::CreateTmpFolderError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CreateTmpFolderError); emit abortCreateWallpaper(); return; } + setWorkingDir(dir.path() + "/" + folderName); QString target_codec; @@ -75,34 +86,100 @@ void Create::createWallpaperStart(QString videoPath, Create::VideoCodec codec, c break; } - m_createImportVideoThread = std::make_unique(); - m_createImportVideo = std::make_unique(videoPath, workingDir(), target_codec, quality); - connect(m_createImportVideo.get(), &CreateImportVideo::processOutput, this, [this](QString text) { - appendFfmpegOutput(text + "\n"); + m_createImportFuture = QtConcurrent::run(QThreadPool::globalInstance(), [videoPath, target_codec, quality, this]() { + CreateImportVideo import(videoPath, workingDir(), target_codec, quality, m_interrupt); + QObject::connect(&import, &CreateImportVideo::createWallpaperStateChanged, this, &Create::createWallpaperStateChanged, Qt::ConnectionType::QueuedConnection); + QObject::connect(&import, &CreateImportVideo::abortAndCleanup, this, &Create::abortAndCleanup, Qt::ConnectionType::QueuedConnection); + QObject::connect( + &import, &CreateImportVideo::processOutput, this, [this](const QString text) { + appendFfmpegOutput(text + "\n"); + }, + Qt::ConnectionType::QueuedConnection); + + if (!import.createWallpaperInfo() || m_interrupt) { + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::Failed); + emit import.abortAndCleanup(); + return; + } + + qInfo() << "createWallpaperImageThumbnailPreview()"; + if (!import.createWallpaperImageThumbnailPreview() || m_interrupt) { + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::Failed); + emit import.abortAndCleanup(); + return; + } + + qInfo() << "createWallpaperImagePreview()"; + if (!import.createWallpaperImagePreview() || m_interrupt) { + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::Failed); + emit import.abortAndCleanup(); + return; + } + + qInfo() << "createWallpaperVideoPreview()"; + if (!import.createWallpaperVideoPreview() || m_interrupt) { + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::Failed); + emit import.abortAndCleanup(); + + return; + } + + qInfo() << "createWallpaperGifPreview()"; + if (!import.createWallpaperGifPreview() || m_interrupt) { + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::Failed); + emit import.abortAndCleanup(); + return; + } + + // If the video has no audio we can skip the extraction + if (!import.m_skipAudio) { + qInfo() << "extractWallpaperAudio()"; + if (!import.extractWallpaperAudio() || m_interrupt) { + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::Failed); + emit import.abortAndCleanup(); + return; + } + } + + // Skip convert for webm + if (import.m_isWebm) { + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::Finished); + return; + } + + qInfo() << "createWallpaperVideo()"; + if (!import.createWallpaperVideo() || m_interrupt) { + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::Failed); + emit import.abortAndCleanup(); + return; + } + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::Finished); }); - connect(m_createImportVideo.get(), &CreateImportVideo::createWallpaperStateChanged, this, &Create::createWallpaperStateChanged); - connect(m_createImportVideo.get(), &CreateImportVideo::progressChanged, this, &Create::setProgress); - connect(m_createImportVideoThread.get(), &QThread::started, m_createImportVideo.get(), &CreateImportVideo::process); - connect(m_createImportVideo.get(), &CreateImportVideo::abortAndCleanup, this, &Create::abortAndCleanup); + QObject::connect(&m_createImportFutureWatcher, &QFutureWatcherBase::finished, this, [this]() { + if (m_interrupt) + abortAndCleanup(); + }); - connect(m_createImportVideo.get(), &CreateImportVideo::finished, m_createImportVideoThread.get(), &QThread::quit); - connect(m_createImportVideo.get(), &CreateImportVideo::finished, m_createImportVideo.get(), &QObject::deleteLater); - connect(m_createImportVideoThread.get(), &QThread::finished, m_createImportVideoThread.get(), &QObject::deleteLater); - - m_createImportVideo->moveToThread(m_createImportVideoThread.get()); - m_createImportVideoThread->start(); + m_createImportFutureWatcher.setFuture(m_createImportFuture); } /*! \brief When converting of the wallpaper steps where successful. */ -void Create::saveWallpaper(QString title, QString description, QString filePath, QString previewImagePath, QString youtube, Create::VideoCodec codec, QVector tags) +void Create::saveWallpaper( + const QString title, + const QString description, + QString filePath, + QString previewImagePath, + const QString youtube, + const Create::VideoCodec codec, + const QVector tags) { filePath = ScreenPlayUtil::toLocal(filePath); previewImagePath = ScreenPlayUtil::toLocal(previewImagePath); - emit createWallpaperStateChanged(CreateImportVideo::ImportVideoState::CopyFiles); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CopyFiles); // Case when the selected users preview image has the same name as // our default "preview.jpg" name. QFile::copy does no override exsisting files @@ -111,7 +188,7 @@ void Create::saveWallpaper(QString title, QString description, QString filePath, if (userSelectedPreviewImage.fileName() == "preview.jpg") { if (!userSelectedPreviewImage.remove()) { qDebug() << "Could remove" << previewImagePath; - emit createWallpaperStateChanged(CreateImportVideo::ImportVideoState::CopyFilesError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CopyFilesError); } } @@ -119,7 +196,7 @@ void Create::saveWallpaper(QString title, QString description, QString filePath, if (previewImageFile.exists()) { if (!QFile::copy(previewImagePath, m_workingDir + "/" + previewImageFile.fileName())) { qDebug() << "Could not copy" << previewImagePath << " to " << m_workingDir + "/" + previewImageFile.fileName(); - emit createWallpaperStateChanged(CreateImportVideo::ImportVideoState::CopyFilesError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CopyFilesError); return; } } @@ -128,12 +205,12 @@ void Create::saveWallpaper(QString title, QString description, QString filePath, if (filePath.endsWith(".webm")) { if (!QFile::copy(filePath, m_workingDir + "/" + filePathFile.fileName())) { qDebug() << "Could not copy" << filePath << " to " << m_workingDir + "/" + filePathFile.fileName(); - emit createWallpaperStateChanged(CreateImportVideo::ImportVideoState::CopyFilesError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CopyFilesError); return; } } - emit createWallpaperStateChanged(CreateImportVideo::ImportVideoState::CopyFilesFinished); - emit createWallpaperStateChanged(CreateImportVideo::ImportVideoState::CreateProjectFile); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CopyFilesFinished); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CreateProjectFile); QJsonObject obj; obj.insert("description", description); @@ -155,14 +232,20 @@ void Create::saveWallpaper(QString title, QString description, QString filePath, } if (!Util::writeSettings(std::move(obj), m_workingDir + "/project.json")) { - emit createWallpaperStateChanged(CreateImportVideo::ImportVideoState::CreateProjectFileError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CreateProjectFileError); return; } - emit createWallpaperStateChanged(CreateImportVideo::ImportVideoState::CreateProjectFileFinished); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CreateProjectFileFinished); +} - m_createImportVideoThread->quit(); - m_createImportVideoThread->wait(); +/*! + \brief This method is called from qml. +*/ +void Create::cancel() +{ + qInfo() << "cancel()"; + m_interrupt = true; } /*! @@ -170,35 +253,15 @@ void Create::saveWallpaper(QString title, QString description, QString filePath, */ void Create::abortAndCleanup() { - qWarning() << "Abort and Cleanup!"; - - if (m_createImportVideo == nullptr || m_createImportVideoThread == nullptr) { - qDebug() << "Invalid thread pointer. Cancel abort!"; - return; - } - - // Save to export path before aborting to be able to cleanup the tmp folder - QString tmpExportPath = m_createImportVideo->m_exportPath; - - connect(m_createImportVideoThread.get(), &QThread::finished, this, [=]() { - QDir exportPath(tmpExportPath); - qWarning() << "Abort and Cleanup!" << exportPath; - if (exportPath.exists()) { - if (!exportPath.removeRecursively()) { - emit createWallpaperStateChanged(CreateImportVideo::ImportVideoState::AbortCleanupError); - qWarning() << "Could not delete temp exportPath: " << exportPath; - } else { - qDebug() << "cleanup " << tmpExportPath; - } - } else { - qDebug() << "Could not cleanup and delete: " << exportPath; + QDir exportPath(m_workingDir); + if (exportPath.exists()) { + if (!exportPath.removeRecursively()) { + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::AbortCleanupError); + qWarning() << "Could not delete temp exportPath: " << exportPath; } - m_createImportVideo.reset(); - m_createImportVideoThread.reset(); - }); - - m_createImportVideoThread->quit(); - m_createImportVideoThread->wait(); + } else { + qWarning() << "Could not cleanup video import. Export path does not exist: " << exportPath; + } } } diff --git a/ScreenPlay/src/create.h b/ScreenPlay/src/create.h index 60cbb5e5..c8fc1d46 100644 --- a/ScreenPlay/src/create.h +++ b/ScreenPlay/src/create.h @@ -71,9 +71,7 @@ class Create : public QObject { Q_PROPERTY(QString ffmpegOutput READ ffmpegOutput WRITE appendFfmpegOutput NOTIFY ffmpegOutputChanged) public: - explicit Create( - const std::shared_ptr& globalVariables, - QObject* parent = nullptr); + explicit Create(const std::shared_ptr& globalVariables); Create(); @@ -84,14 +82,13 @@ public: }; Q_ENUM(VideoCodec) + float progress() const { return m_progress; } - QString workingDir() const { return m_workingDir; } - QString ffmpegOutput() const { return m_ffmpegOutput; } signals: - void createWallpaperStateChanged(CreateImportVideo::ImportVideoState state); + void createWallpaperStateChanged(ImportVideoState::ImportVideoState state); void progressChanged(float progress); void abortCreateWallpaper(); void workingDirChanged(QString workingDir); @@ -100,17 +97,17 @@ signals: void htmlWallpaperCreatedSuccessful(QString path); public slots: + void cancel(); void createWallpaperStart(QString videoPath, Create::VideoCodec codec, const int quality = 50); - void saveWallpaper( - QString title, - QString description, + void saveWallpaper(const QString title, + const QString description, QString filePath, QString previewImagePath, - QString youtube, - ScreenPlay::Create::VideoCodec codec, - QVector tags); + const QString youtube, + const ScreenPlay::Create::VideoCodec codec, + const QVector tags); void abortAndCleanup(); @@ -145,13 +142,18 @@ public slots: } private: - std::unique_ptr m_createImportVideo; - std::unique_ptr m_createImportVideoThread; + void init(); + void reset(); +private: const std::shared_ptr m_globalVariables; float m_progress { 0.0F }; QString m_workingDir; QString m_ffmpegOutput; + + std::atomic m_interrupt; + QFuture m_createImportFuture; + QFutureWatcher m_createImportFutureWatcher; }; } diff --git a/ScreenPlay/src/createimportstates.h b/ScreenPlay/src/createimportstates.h new file mode 100644 index 00000000..0676956a --- /dev/null +++ b/ScreenPlay/src/createimportstates.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +namespace ScreenPlay { +/*! + \namespace ScreenPlay::ImportVideoState + \inmodule ScreenPlay + \brief Global enum for ImportVideoState. +*/ +namespace ImportVideoState { + Q_NAMESPACE + + enum class ImportVideoState { + Idle, + Started, + AnalyseVideo, + AnalyseVideoFinished, + AnalyseVideoError, + AnalyseVideoHasNoVideoStreamError, + ConvertingPreviewVideo, + ConvertingPreviewVideoFinished, + ConvertingPreviewVideoError, + ConvertingPreviewGif, + ConvertingPreviewGifFinished, //10 + ConvertingPreviewGifError, + ConvertingPreviewImage, + ConvertingPreviewImageFinished, + ConvertingPreviewImageError, + ConvertingPreviewImageThumbnail, + ConvertingPreviewImageThumbnailFinished, + ConvertingPreviewImageThumbnailError, + ConvertingAudio, + ConvertingAudioFinished, + ConvertingAudioError, //20 + ConvertingVideo, + ConvertingVideoFinished, + ConvertingVideoError, + CopyFiles, + CopyFilesFinished, + CopyFilesError, + CreateProjectFile, + CreateProjectFileFinished, + CreateProjectFileError, + AbortCleanupError, //30 + CreateTmpFolderError, + Finished, + Failed, + }; + Q_ENUM_NS(ImportVideoState) +} +} diff --git a/ScreenPlay/src/createimportvideo.cpp b/ScreenPlay/src/createimportvideo.cpp index cf931057..cc1b5d0d 100644 --- a/ScreenPlay/src/createimportvideo.cpp +++ b/ScreenPlay/src/createimportvideo.cpp @@ -17,21 +17,23 @@ namespace ScreenPlay { /*! \brief This constructor is only needed for calling qRegisterMetaType on CreateImportVideo to register the enums. \code - qRegisterMetaType("CreateImportVideo::ImportVideoState"); + qRegisterMetaType("ImportVideoState::ImportVideoState"); \endcode */ -CreateImportVideo::CreateImportVideo(QObject* parent) - : QObject(parent) -{ -} /*! \brief Creates a CreateImportVideo object to be used in a different thread. A \a videoPath and a \a exportPath are needed for convertion. */ -CreateImportVideo::CreateImportVideo(const QString& videoPath, const QString& exportPath, const QString& codec, const int quality, QObject* parent) - : QObject(parent) +CreateImportVideo::CreateImportVideo( + const QString& videoPath, + const QString& exportPath, + const QString& codec, + const int quality, + std::atomic& interrupt) + : QObject(nullptr) , m_quality(quality) + , m_interrupt(interrupt) { m_videoPath = videoPath; m_exportPath = exportPath; @@ -48,90 +50,6 @@ CreateImportVideo::CreateImportVideo(const QString& videoPath, const QString& ex } } -/*! - \brief Processes the multiple steps of creating a wallpaper. - \list 1 - \li createWallpaperInfo() - \li createWallpaperImagePreview() - \li createWallpaperVideoPreview() - \li createWallpaperGifPreview() - \li createWallpaperVideo() - skiped if already a webm - \li extractWallpaperAudio() - skiped if no audio - \li emit createWallpaperStateChanged(ImportVideoState::Finished); - \endlist -*/ -void CreateImportVideo::process() -{ - - qInfo() << "createWallpaperInfo()" << m_videoPath << m_exportPath << m_codec << m_ffmpegExecutable << m_ffprobeExecutable; - if (!createWallpaperInfo() || QThread::currentThread()->isInterruptionRequested()) { - emit abortAndCleanup(); - return; - } - - qInfo() << "createWallpaperImageThumbnailPreview()"; - if (!createWallpaperImageThumbnailPreview() || QThread::currentThread()->isInterruptionRequested()) { - emit abortAndCleanup(); - return; - } - - qInfo() << "createWallpaperImagePreview()"; - if (!createWallpaperImagePreview() || QThread::currentThread()->isInterruptionRequested()) { - emit abortAndCleanup(); - return; - } - - qInfo() << "createWallpaperVideoPreview()"; - if (!createWallpaperVideoPreview() || QThread::currentThread()->isInterruptionRequested()) { - emit abortAndCleanup(); - return; - } - - qInfo() << "createWallpaperGifPreview()"; - if (!createWallpaperGifPreview() || QThread::currentThread()->isInterruptionRequested()) { - emit abortAndCleanup(); - return; - } - - // If the video has no audio we can skip the extraction - if (!m_skipAudio) { - qInfo() << "extractWallpaperAudio()"; - if (!extractWallpaperAudio() || QThread::currentThread()->isInterruptionRequested()) { - emit abortAndCleanup(); - return; - } - } - - if (m_isWebm) { - emit createWallpaperStateChanged(ImportVideoState::Finished); - return; - } - - qInfo() << "createWallpaperVideo()"; - if (!createWallpaperVideo() || QThread::currentThread()->isInterruptionRequested()) { - emit abortAndCleanup(); - return; - } - - emit createWallpaperStateChanged(ImportVideoState::Finished); -} - -void CreateImportVideo::processGif() -{ - qInfo() << "createWallpaperImageThumbnailPreview()"; - if (!createWallpaperImageThumbnailPreview() || QThread::currentThread()->isInterruptionRequested()) { - emit abortAndCleanup(); - return; - } - - qInfo() << "createWallpaperImagePreview()"; - if (!createWallpaperImagePreview() || QThread::currentThread()->isInterruptionRequested()) { - emit abortAndCleanup(); - return; - } - emit createWallpaperStateChanged(ImportVideoState::Finished); -} - /*! \brief Starts ffprobe and tries to parse the resulting json. If the video is a container that not contains the video length like webm or mkv @@ -173,12 +91,12 @@ bool CreateImportVideo::createWallpaperInfo() emit processOutput("ffprobe " + ScreenPlayUtil::toString(args)); - emit createWallpaperStateChanged(ImportVideoState::AnalyseVideo); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::AnalyseVideo); const QString ffmpegOut = waitForFinished(args, QProcess::SeparateChannels, Executable::FFPROBE); qInfo() << ffmpegOut; - emit createWallpaperStateChanged(ImportVideoState::AnalyseVideoFinished); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::AnalyseVideoFinished); auto obj = ScreenPlayUtil::parseQByteArrayToQJsonObject(QByteArray::fromStdString(ffmpegOut.toStdString())); @@ -188,14 +106,14 @@ bool CreateImportVideo::createWallpaperInfo() emit processOutput(ffmpegOut); emit processOutput("Error parsing FFPROBE json output"); - emit createWallpaperStateChanged(ImportVideoState::AnalyseVideoError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::AnalyseVideoError); return false; } if (obj->empty()) { qWarning() << "Error! File could not be parsed."; emit processOutput("Error! File could not be parsed."); - emit createWallpaperStateChanged(ImportVideoState::AnalyseVideoError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::AnalyseVideoError); return false; } @@ -214,7 +132,7 @@ bool CreateImportVideo::createWallpaperInfo() bool CreateImportVideo::analyzeWebmReadFrames(const QJsonObject& obj) { - emit createWallpaperStateChanged(ImportVideoState::AnalyseVideo); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::AnalyseVideo); // Number of frames is a string for some reason... if (!obj.value("streams").isArray()) { @@ -283,7 +201,7 @@ bool CreateImportVideo::analyzeVideo(const QJsonObject& obj) if (!hasVideoStream) { qDebug() << "Error! File has no video Stream!"; emit processOutput("Error! File has no video Stream!"); - emit createWallpaperStateChanged(ImportVideoState::AnalyseVideoHasNoVideoStreamError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::AnalyseVideoHasNoVideoStreamError); return false; } @@ -299,7 +217,7 @@ bool CreateImportVideo::analyzeVideo(const QJsonObject& obj) emit processOutput("Error parsing number of frames. Is this really a valid video File?"); QJsonDocument tmpVideoStreamDoc(videoStream); emit processOutput(tmpVideoStreamDoc.toJson()); - emit createWallpaperStateChanged(ImportVideoState::AnalyseVideoError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::AnalyseVideoError); return false; } @@ -312,7 +230,7 @@ bool CreateImportVideo::analyzeVideo(const QJsonObject& obj) if (!okParseDuration) { qDebug() << "Error parsing video length. Is this really a valid video File?"; emit processOutput("Error parsing video length. Is this really a valid video File?"); - emit createWallpaperStateChanged(ImportVideoState::AnalyseVideoError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::AnalyseVideoError); return false; } @@ -373,7 +291,7 @@ bool CreateImportVideo::analyzeVideo(const QJsonObject& obj) bool CreateImportVideo::createWallpaperVideoPreview() { - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewVideo); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewVideo); QStringList args; args.append("-y"); @@ -397,13 +315,13 @@ bool CreateImportVideo::createWallpaperVideoPreview() const QString ffmpegOut = waitForFinished(args); const QFile previewVideo(m_exportPath + "/preview.webm"); if (!previewVideo.exists() || !(previewVideo.size() > 0)) { - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewVideoError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewVideoError); return false; } emit processOutput(ffmpegOut); - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewVideoFinished); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewVideoFinished); return true; } @@ -425,7 +343,7 @@ bool CreateImportVideo::createWallpaperVideoPreview() bool CreateImportVideo::createWallpaperGifPreview() { - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewGif); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewGif); QStringList args; args.append("-y"); @@ -442,13 +360,13 @@ bool CreateImportVideo::createWallpaperGifPreview() if (!ffmpegOut.isEmpty()) { const QFile previewGif(m_exportPath + "/preview.gif"); if (!previewGif.exists() || !(previewGif.size() > 0)) { - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewGifError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewGifError); return false; } } emit processOutput(ffmpegOut); - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewGifFinished); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewGifFinished); return true; } @@ -464,7 +382,7 @@ bool CreateImportVideo::createWallpaperGifPreview() bool CreateImportVideo::createWallpaperImageThumbnailPreview() { - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewImageThumbnail); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewImageThumbnail); QStringList args; args.clear(); @@ -500,13 +418,13 @@ bool CreateImportVideo::createWallpaperImageThumbnailPreview() if (!ffmpegOut.isEmpty()) { const QFile previewImg(m_exportPath + "/previewThumbnail.jpg"); if (!previewImg.exists() || !(previewImg.size() > 0)) { - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewImageThumbnailError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewImageThumbnailError); return false; } } emit processOutput(ffmpegOut); - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewImageThumbnailFinished); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewImageThumbnailFinished); return true; } @@ -517,7 +435,7 @@ bool CreateImportVideo::createWallpaperImageThumbnailPreview() bool CreateImportVideo::createWallpaperImagePreview() { - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewImage); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewImage); QStringList args; args.clear(); @@ -544,13 +462,13 @@ bool CreateImportVideo::createWallpaperImagePreview() if (!ffmpegOut.isEmpty()) { const QFile previewImg(m_exportPath + "/preview.jpg"); if (!previewImg.exists() || !(previewImg.size() > 0)) { - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewImageError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewImageError); return false; } } emit processOutput(ffmpegOut); - emit createWallpaperStateChanged(ImportVideoState::ConvertingPreviewImageFinished); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingPreviewImageFinished); return true; } @@ -576,13 +494,13 @@ bool CreateImportVideo::createWallpaperImagePreview() */ bool CreateImportVideo::createWallpaperVideo() { - emit createWallpaperStateChanged(ImportVideoState::ConvertingVideo); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingVideo); connect(m_process.get(), &QProcess::readyReadStandardOutput, this, [&]() { QString tmpOut = m_process->readAllStandardOutput(); qInfo() << tmpOut; if (tmpOut.contains("Conversion failed!")) { - emit createWallpaperStateChanged(ImportVideoState::ConvertingVideoError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingVideoError); } const auto tmpList = tmpOut.split(QRegExp("\\s+"), Qt::SplitBehaviorFlags::SkipEmptyParts); @@ -683,11 +601,11 @@ bool CreateImportVideo::createWallpaperVideo() QFile video(convertedFileAbsolutePath); if (!video.exists() || !(video.size() > 0)) { qDebug() << convertedFileAbsolutePath << ffmpegOutput << video.exists() << video.size(); - emit createWallpaperStateChanged(ImportVideoState::ConvertingVideoError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingVideoError); return false; } - emit createWallpaperStateChanged(ImportVideoState::ConvertingVideoFinished); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingVideoFinished); return true; } @@ -710,7 +628,7 @@ bool CreateImportVideo::createWallpaperVideo() bool CreateImportVideo::extractWallpaperAudio() { - emit createWallpaperStateChanged(ImportVideoState::ConvertingAudio); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingAudio); QStringList args; args.append("-y"); @@ -731,13 +649,13 @@ bool CreateImportVideo::extractWallpaperAudio() if (!previewImg.exists() || !(previewImg.size() > 0)) { qDebug() << args; qDebug() << tmpErrImg; - emit createWallpaperStateChanged(ImportVideoState::ConvertingAudioError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingAudioError); return false; } } emit processOutput(tmpErrImg); - emit createWallpaperStateChanged(ImportVideoState::ConvertingAudioFinished); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::ConvertingAudioFinished); return true; } @@ -755,12 +673,20 @@ QString CreateImportVideo::waitForFinished( m_process = std::make_unique(); QObject::connect(m_process.get(), &QProcess::errorOccurred, [=](QProcess::ProcessError error) { qDebug() << "error enum val = " << error << m_process->errorString(); - emit createWallpaperStateChanged(ImportVideoState::AnalyseVideoError); + emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::AnalyseVideoError); m_process->terminate(); if (!m_process->waitForFinished(1000)) { m_process->kill(); } }); + + QObject::connect(m_process.get(), QOverload::of(&QProcess::finished), + [=](int exitCode, QProcess::ExitStatus exitStatus) { + qInfo() << "Process finished with exit code: " << exitCode; + if (exitCode != 0) + qWarning() << "Process finished with exit code: " << exitCode << " exitStatus:" << exitStatus; + }); + if (executable == Executable::FFMPEG) { m_process->setProgram(m_ffmpegExecutable); } else { @@ -786,7 +712,7 @@ QString CreateImportVideo::waitForFinished( while (!m_process->waitForFinished(10)) //Wake up every 10ms and check if we must exit { - if (QThread::currentThread()->isInterruptionRequested()) { + if (m_interrupt) { qInfo() << "Interrupt thread"; m_process->terminate(); if (!m_process->waitForFinished(1000)) { @@ -802,7 +728,10 @@ QString CreateImportVideo::waitForFinished( } else { processOutput = m_process->readAll(); } - qInfo() << "ProcessOutput:" << processOutput; + + if (!processOutput.isEmpty()) + qInfo() << "ProcessOutput:" << processOutput; + m_process->close(); return processOutput; diff --git a/ScreenPlay/src/createimportvideo.h b/ScreenPlay/src/createimportvideo.h index ca4a3546..9da6bca9 100644 --- a/ScreenPlay/src/createimportvideo.h +++ b/ScreenPlay/src/createimportvideo.h @@ -47,9 +47,9 @@ #include #include #include -#include #include +#include "createimportstates.h" #include "util.h" namespace ScreenPlay { @@ -59,46 +59,7 @@ class CreateImportVideo : public QObject { Q_PROPERTY(float progress READ progress WRITE setProgress NOTIFY progressChanged) public: - CreateImportVideo() { } - CreateImportVideo(QObject* parent = nullptr); - explicit CreateImportVideo(const QString& videoPath, const QString& exportPath, const QString& codec, const int quality, QObject* parent = nullptr); - - enum class ImportVideoState { - Idle, - Started, - AnalyseVideo, - AnalyseVideoFinished, - AnalyseVideoError, - AnalyseVideoHasNoVideoStreamError, - ConvertingPreviewVideo, - ConvertingPreviewVideoFinished, - ConvertingPreviewVideoError, - ConvertingPreviewGif, - ConvertingPreviewGifFinished, //10 - ConvertingPreviewGifError, - ConvertingPreviewImage, - ConvertingPreviewImageFinished, - ConvertingPreviewImageError, - ConvertingPreviewImageThumbnail, - ConvertingPreviewImageThumbnailFinished, - ConvertingPreviewImageThumbnailError, - ConvertingAudio, - ConvertingAudioFinished, - ConvertingAudioError, //20 - ConvertingVideo, - ConvertingVideoFinished, - ConvertingVideoError, - CopyFiles, - CopyFilesFinished, - CopyFilesError, - CreateProjectFile, - CreateProjectFileFinished, - CreateProjectFileError, - AbortCleanupError, //30 - CreateTmpFolderError, - Finished, - }; - Q_ENUM(ImportVideoState) + explicit CreateImportVideo(const QString& videoPath, const QString& exportPath, const QString& codec, const int quality, std::atomic& interrupt); float progress() const { return m_progress; } @@ -128,16 +89,13 @@ public: }; signals: - void createWallpaperStateChanged(CreateImportVideo::ImportVideoState state); + void createWallpaperStateChanged(ImportVideoState::ImportVideoState state); void processOutput(QString text); void finished(); void abortAndCleanup(); void progressChanged(float progress); public slots: - void process(); - void processGif(); - bool createWallpaperInfo(); bool createWallpaperVideoPreview(); bool createWallpaperGifPreview(); @@ -170,6 +128,7 @@ private: QString m_ffprobeExecutable; QString m_ffmpegExecutable; std::unique_ptr m_process; + std::atomic& m_interrupt; }; } -Q_DECLARE_METATYPE(ScreenPlay::CreateImportVideo::ImportVideoState) +Q_DECLARE_METATYPE(ScreenPlay::ImportVideoState::ImportVideoState) diff --git a/ScreenPlay/tests/tst_main.cpp b/ScreenPlay/tests/tst_main.cpp index 52e1cc61..e5adfc12 100644 --- a/ScreenPlay/tests/tst_main.cpp +++ b/ScreenPlay/tests/tst_main.cpp @@ -33,6 +33,7 @@ ****************************************************************************/ #include "app.h" +#include "create.h" #include #include #include @@ -49,25 +50,128 @@ class ScreenPlayTest : public QObject { Q_OBJECT private slots: - void main_test(); + void initTestCase() + { + Q_INIT_RESOURCE(ScreenPlayQML); + Q_INIT_RESOURCE(ScreenPlayAssets); + + app.init(); + m_window = qobject_cast(app.mainWindowEngine()->rootObjects().first()); + QVERIFY(m_window); + QVERIFY(QTest::qWaitForWindowExposed(m_window)); + + QTest::qWait(1000); + } + void import_convert_video(); private: QQuickWindow* m_window = nullptr; + ScreenPlay::App app; }; -void ScreenPlayTest::main_test() +/*! + * For some reason a direct findChild does not work for item + * delegates. + * https://stackoverflow.com/questions/36767512/how-to-access-qml-listview-delegate-items-from-c + * + */ +QQuickItem* findItemDelegate(QQuickItem* listView) { - Q_INIT_RESOURCE(ScreenPlayQML); - Q_INIT_RESOURCE(ScreenPlayAssets); + if (!listView->property("contentItem").isValid()) + return {}; -// QtWebEngine::initialize(); -// QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -// QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + auto contentItem = listView->property("contentItem").value(); + auto contentItemChildren = contentItem->childItems(); + QQuickItem* videoImportConvertButton {}; + for (auto childItem : contentItemChildren) { + if (childItem->objectName() == "videoImportConvert") + return childItem; + } + return {}; +} - ScreenPlay::App app; - app.init(); +void clickItem(QQuickItem* item) +{ + QQuickWindow* itemWindow = item->window(); + QVERIFY(itemWindow); + auto centre = item->mapToScene(QPoint(item->width() / 2, item->height() / 2)).toPoint(); + qInfo() << "click_:" << centre; + QTest::mouseClick(itemWindow, Qt::LeftButton, Qt::NoModifier, centre); +} - QTest::qWait(10000); +void ScreenPlayTest::import_convert_video() +{ + + using namespace ScreenPlay; + auto* createTab = m_window->findChild("Create"); + QVERIFY(createTab); + clickItem(createTab); + QTest::qWait(300); + auto* stackView = m_window->findChild("stackView"); + QVERIFY(stackView); + QVERIFY(stackView->property("currentItem").isValid()); + auto* createView = qvariant_cast(stackView->property("currentItem")); + QVERIFY(createView); + QTest::qWait(300); + auto* wizardsListView = m_window->findChild("wizardsListView"); + QVERIFY(wizardsListView); + QQuickItem* videoImportConvertButton = findItemDelegate(wizardsListView); + QVERIFY(videoImportConvertButton); + clickItem(videoImportConvertButton); + QTest::qWait(300); + + auto* createWallpaperInit = m_window->findChild("createWallpaperInit"); + QVERIFY(createWallpaperInit); + + QVERIFY(QMetaObject::invokeMethod(createWallpaperInit, + QString("startConvert").toLatin1().constData(), + Qt::ConnectionType::AutoConnection, + Q_ARG(QVariant, "file:///D:/Video Loop/bbb.mp4"), + Q_ARG(QVariant, 1))); // VideoCodec::VP9 + + QTest::qWait(1000); + // Wait for Create::createWallpaperStart + { + ImportVideoState::ImportVideoState status = ImportVideoState::ImportVideoState::Idle; + QObject::connect(app.create(), &Create::createWallpaperStateChanged, this, [&status](ImportVideoState::ImportVideoState state) { + status = state; + }); + + while (true) { + QSignalSpy videoConvertFinishSpy(app.create(), &Create::createWallpaperStateChanged); + if (status == ImportVideoState::ImportVideoState::Finished || status == ImportVideoState::ImportVideoState::Failed) { + QVERIFY(status == ImportVideoState::ImportVideoState::Finished); + QTest::qWait(1000); // Wait for the ui to process the event + break; + } + videoConvertFinishSpy.wait(); + } + } + + QTest::qWait(1000); + auto* btnSave = m_window->findChild("btnSave"); + QVERIFY(btnSave); + clickItem(btnSave); + + // Wait for Create::saveWallpaper + { + ImportVideoState::ImportVideoState status = ImportVideoState::ImportVideoState::Idle; + QObject::connect(app.create(), &Create::createWallpaperStateChanged, this, [&status](ImportVideoState::ImportVideoState state) { + status = state; + }); + + while (true) { + QSignalSpy videoConvertFinishSpy(app.create(), &Create::createWallpaperStateChanged); + if (status == ImportVideoState::ImportVideoState::CreateProjectFileFinished || status == ImportVideoState::ImportVideoState::CreateProjectFileError || status == ImportVideoState::ImportVideoState::CopyFilesError) { + QVERIFY(status == ImportVideoState::ImportVideoState::CreateProjectFileFinished); + QTest::qWait(1000); // Wait for the ui to process the event + break; + } + videoConvertFinishSpy.wait(); + } + } + + QTest::qWait(1000); } QTEST_MAIN(ScreenPlayTest) diff --git a/ScreenPlay/translations/ScreenPlay_de.ts b/ScreenPlay/translations/ScreenPlay_de.ts index d5f69e2a..d7786177 100644 --- a/ScreenPlay/translations/ScreenPlay_de.ts +++ b/ScreenPlay/translations/ScreenPlay_de.ts @@ -129,12 +129,12 @@ CreateWallpaperInit - + Import any video type Importiere jedes Video - + Depending on your PC configuration it is better to convert your wallpaper to a specific video codec. If both have bad performance you can also try a QML wallpaper! Supported video formats are: *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv @@ -142,22 +142,22 @@ *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv - + Set your preffered video codec: Bevorzugte Video-Kodierung Festlegen - + Quality slider. Lower value means better quality. Qualitäts-Regler. Niedriger wert heißt niedrige Qualität - + Open Documentation Öffne Documentation - + Select file Datei auswählen @@ -263,7 +263,7 @@ Speichern - + Save Wallpaper... Speicher Wallpaper... @@ -560,7 +560,7 @@ Speichern - + Save Wallpaper... Speicher Wallpaper... @@ -1299,47 +1299,47 @@ Bitte Konfiguriere deine Wallpaper noch erneut Abonniere - + Tools Overview Werkzeugeübersicht - + Video import and convert (all types) - + Video Import (.webm) Importiere Video (.webm) - + GIF Wallpaper - + QML Wallpaper - + HTML5 Wallpaper - + Website Wallpaper - + QML Widget - + HTML Widget diff --git a/ScreenPlay/translations/ScreenPlay_en.ts b/ScreenPlay/translations/ScreenPlay_en.ts index dfe910a7..930e9048 100644 --- a/ScreenPlay/translations/ScreenPlay_en.ts +++ b/ScreenPlay/translations/ScreenPlay_en.ts @@ -113,34 +113,34 @@ CreateWallpaperInit - + Import any video type - + Depending on your PC configuration it is better to convert your wallpaper to a specific video codec. If both have bad performance you can also try a QML wallpaper! Supported video formats are: *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv - + Set your preffered video codec: - + Quality slider. Lower value means better quality. - + Open Documentation - + Select file @@ -246,7 +246,7 @@ - + Save Wallpaper... @@ -543,7 +543,7 @@ - + Save Wallpaper... @@ -1181,47 +1181,47 @@ Sidebar - + Tools Overview - + Video import and convert (all types) - + Video Import (.webm) - + GIF Wallpaper - + QML Wallpaper - + HTML5 Wallpaper - + Website Wallpaper - + QML Widget - + HTML Widget diff --git a/ScreenPlay/translations/ScreenPlay_es.ts b/ScreenPlay/translations/ScreenPlay_es.ts index 0943fd72..efca6d35 100644 --- a/ScreenPlay/translations/ScreenPlay_es.ts +++ b/ScreenPlay/translations/ScreenPlay_es.ts @@ -129,34 +129,34 @@ CreateWallpaperInit - + Import any video type - + Depending on your PC configuration it is better to convert your wallpaper to a specific video codec. If both have bad performance you can also try a QML wallpaper! Supported video formats are: *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv - + Set your preffered video codec: - + Quality slider. Lower value means better quality. - + Open Documentation - + Select file @@ -262,7 +262,7 @@ - + Save Wallpaper... @@ -559,7 +559,7 @@ - + Save Wallpaper... @@ -1197,47 +1197,47 @@ Sidebar - + Tools Overview - + Video import and convert (all types) - + Video Import (.webm) - + GIF Wallpaper - + QML Wallpaper - + HTML5 Wallpaper - + Website Wallpaper - + QML Widget - + HTML Widget diff --git a/ScreenPlay/translations/ScreenPlay_fr.ts b/ScreenPlay/translations/ScreenPlay_fr.ts index d3563a86..65620758 100644 --- a/ScreenPlay/translations/ScreenPlay_fr.ts +++ b/ScreenPlay/translations/ScreenPlay_fr.ts @@ -113,34 +113,34 @@ CreateWallpaperInit - + Import any video type - + Depending on your PC configuration it is better to convert your wallpaper to a specific video codec. If both have bad performance you can also try a QML wallpaper! Supported video formats are: *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv - + Set your preffered video codec: - + Quality slider. Lower value means better quality. - + Open Documentation - + Select file @@ -246,7 +246,7 @@ - + Save Wallpaper... @@ -543,7 +543,7 @@ - + Save Wallpaper... @@ -1181,47 +1181,47 @@ Sidebar - + Tools Overview - + Video import and convert (all types) - + Video Import (.webm) - + GIF Wallpaper - + QML Wallpaper - + HTML5 Wallpaper - + Website Wallpaper - + QML Widget - + HTML Widget diff --git a/ScreenPlay/translations/ScreenPlay_ko.ts b/ScreenPlay/translations/ScreenPlay_ko.ts index 90f6d8f7..c0503dd2 100644 --- a/ScreenPlay/translations/ScreenPlay_ko.ts +++ b/ScreenPlay/translations/ScreenPlay_ko.ts @@ -129,34 +129,34 @@ CreateWallpaperInit - + Import any video type - + Depending on your PC configuration it is better to convert your wallpaper to a specific video codec. If both have bad performance you can also try a QML wallpaper! Supported video formats are: *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv - + Set your preffered video codec: - + Quality slider. Lower value means better quality. - + Open Documentation - + Select file @@ -262,7 +262,7 @@ - + Save Wallpaper... @@ -559,7 +559,7 @@ - + Save Wallpaper... @@ -1197,47 +1197,47 @@ Sidebar - + Tools Overview - + Video import and convert (all types) - + Video Import (.webm) - + GIF Wallpaper - + QML Wallpaper - + HTML5 Wallpaper - + Website Wallpaper - + QML Widget - + HTML Widget diff --git a/ScreenPlay/translations/ScreenPlay_pt_br.ts b/ScreenPlay/translations/ScreenPlay_pt_br.ts index 5ae7f77a..0c91a101 100644 --- a/ScreenPlay/translations/ScreenPlay_pt_br.ts +++ b/ScreenPlay/translations/ScreenPlay_pt_br.ts @@ -129,34 +129,34 @@ CreateWallpaperInit - + Import any video type - + Depending on your PC configuration it is better to convert your wallpaper to a specific video codec. If both have bad performance you can also try a QML wallpaper! Supported video formats are: *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv - + Set your preffered video codec: - + Quality slider. Lower value means better quality. - + Open Documentation - + Select file @@ -262,7 +262,7 @@ - + Save Wallpaper... @@ -559,7 +559,7 @@ - + Save Wallpaper... @@ -1197,47 +1197,47 @@ Sidebar - + Tools Overview - + Video import and convert (all types) - + Video Import (.webm) - + GIF Wallpaper - + QML Wallpaper - + HTML5 Wallpaper - + Website Wallpaper - + QML Widget - + HTML Widget diff --git a/ScreenPlay/translations/ScreenPlay_ru.ts b/ScreenPlay/translations/ScreenPlay_ru.ts index 03a31008..742aa98f 100644 --- a/ScreenPlay/translations/ScreenPlay_ru.ts +++ b/ScreenPlay/translations/ScreenPlay_ru.ts @@ -129,34 +129,34 @@ CreateWallpaperInit - + Import any video type - + Depending on your PC configuration it is better to convert your wallpaper to a specific video codec. If both have bad performance you can also try a QML wallpaper! Supported video formats are: *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv - + Set your preffered video codec: - + Quality slider. Lower value means better quality. - + Open Documentation - + Select file @@ -262,7 +262,7 @@ - + Save Wallpaper... @@ -559,7 +559,7 @@ - + Save Wallpaper... @@ -1197,47 +1197,47 @@ Sidebar - + Tools Overview - + Video import and convert (all types) - + Video Import (.webm) - + GIF Wallpaper - + QML Wallpaper - + HTML5 Wallpaper - + Website Wallpaper - + QML Widget - + HTML Widget diff --git a/ScreenPlay/translations/ScreenPlay_vi.ts b/ScreenPlay/translations/ScreenPlay_vi.ts index 37b16f21..1f8eefa7 100644 --- a/ScreenPlay/translations/ScreenPlay_vi.ts +++ b/ScreenPlay/translations/ScreenPlay_vi.ts @@ -113,34 +113,34 @@ CreateWallpaperInit - + Import any video type - + Depending on your PC configuration it is better to convert your wallpaper to a specific video codec. If both have bad performance you can also try a QML wallpaper! Supported video formats are: *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv - + Set your preffered video codec: - + Quality slider. Lower value means better quality. - + Open Documentation - + Select file @@ -246,7 +246,7 @@ - + Save Wallpaper... @@ -543,7 +543,7 @@ - + Save Wallpaper... @@ -1181,47 +1181,47 @@ Sidebar - + Tools Overview - + Video import and convert (all types) - + Video Import (.webm) - + GIF Wallpaper - + QML Wallpaper - + HTML5 Wallpaper - + Website Wallpaper - + QML Widget - + HTML Widget diff --git a/ScreenPlay/translations/ScreenPlay_zh_cn.qm b/ScreenPlay/translations/ScreenPlay_zh_cn.qm index a1da966addd745e3a762f9d709b5f1b4353f2dba..5a629558bfc93d373f5074f43fe16bd49a62bb65 100644 GIT binary patch literal 34276 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 Ob!+PA7hV({ CreateWallpaperInit - + Import any video type 导入任何视频类型 - + Depending on your PC configuration it is better to convert your wallpaper to a specific video codec. If both have bad performance you can also try a QML wallpaper! Supported video formats are: *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv @@ -127,22 +127,22 @@ *.mp4 *.mpg *.mp2 *.mpeg *.ogv *.avi *.wmv *.m4v *.3gp *.flv - + Set your preffered video codec: 设置您偏好的视频编码格式: - + Quality slider. Lower value means better quality. 质量滑条,更小的值意味着更好的质量。 - + Open Documentation 打开文档 - + Select file 选择文件 @@ -248,7 +248,7 @@ 保存 - + Save Wallpaper... 保存壁纸... @@ -545,7 +545,7 @@ 保存 - + Save Wallpaper... 保存壁纸... @@ -1279,42 +1279,47 @@ 订阅 - + Tools Overview 工具概览 - + + Video import and convert (all types) + + + + Video Import (.webm) 视频导入 (.webm) - + GIF Wallpaper GIF 壁纸 - + QML Wallpaper QML 壁纸 - + HTML5 Wallpaper HTML5 壁纸 - + Website Wallpaper 网页壁纸 - + QML Widget QML 部件 - + HTML Widget HTML 部件