1
0
mirror of https://gitlab.com/kelteseth/ScreenPlay.git synced 2024-11-07 03:22:33 +01:00

Add mp4 import

This commit is contained in:
Elias Steurer 2021-09-26 18:52:51 +02:00
parent 6b653bdfdc
commit 2b5b7f0aad
12 changed files with 722 additions and 72 deletions

View File

@ -94,5 +94,8 @@
<file>qml/Workshop/SteamWorkshop.qml</file>
<file>qml/Workshop/Forum.qml</file>
<file>qml/Workshop/SteamWorkshopStartPage.qml</file>
<file>qml/Create/Wizards/Importh264/Importh264.qml</file>
<file>qml/Create/Wizards/Importh264/Importh264Convert.qml</file>
<file>qml/Create/Wizards/Importh264/Importh264Init.qml</file>
</qresource>
</RCC>

View File

@ -25,69 +25,6 @@ Rectangle {
color: Material.background
ListView {
/*
ListElement {
headline: qsTr("QML Particle Wallpaper")
source: ""
category: "Example Wallpaper"
}
ListElement {
headline: qsTr("Countdown Clock Wallpaper")
source: ""
category: "Example Wallpaper"
}
ListElement {
headline: qsTr("QML Water Shader Wallpaper")
source: ""
category: "Example Wallpaper"
}
ListElement {
headline: qsTr("QML Shadertoy Shader Wallpaper")
source: ""
category: "Example Wallpaper"
}
ListElement {
headline: qsTr("QML Lightning Shader Wallpaper")
source: ""
category: "Example Wallpaper"
}
ListElement {
headline: qsTr("Clock Widget")
source: "qrc:/qml/Create/Wizards/CreateEmptyWidget/CreateEmptyWidget.qml"
category: "Example Widget"
}
ListElement {
headline: qsTr("CPU Widget")
source: "qrc:/qml/Create/Wizards/CreateEmptyWidget/CreateEmptyWidget.qml"
category: "Example Widget"
}
ListElement {
headline: qsTr("Storage Widget")
source: "qrc:/qml/Create/Wizards/CreateEmptyWidget/CreateEmptyWidget.qml"
category: "Example Widget"
}
ListElement {
headline: qsTr("RAM Widget")
source: "qrc:/qml/Create/Wizards/CreateEmptyWidget/CreateEmptyWidget.qml"
category: "Example Widget"
}
ListElement {
headline: qsTr("XKCD Widget")
source: "qrc:/qml/Create/Wizards/CreateEmptyWidget/CreateEmptyWidget.qml"
category: "Example Widget"
}
*/
id: listView
objectName: "wizardsListView"
@ -130,7 +67,14 @@ Rectangle {
}
ListElement {
headline: qsTr("Video Import (.webm)")
headline: qsTr("Video Import h264 (.mp4)")
source: "qrc:/qml/Create/Wizards/Importh264/Importh264.qml"
category: "Video Wallpaper"
objectName: ""
}
ListElement {
headline: qsTr("Video Import VP8 & VP9 (.webm)")
source: "qrc:/qml/Create/Wizards/ImportWebm/ImportWebm.qml"
category: "Video Wallpaper"
objectName: ""

View File

@ -16,7 +16,7 @@ Item {
Common.Headline {
id: headline
text: qsTr("Free Tools to create wallpaper")
text: qsTr("Free tools to help you to create wallpaper")
anchors {
top: parent.top
@ -33,7 +33,7 @@ Item {
color: Material.primaryTextColor
font.pointSize: 12
font.family: ScreenPlay.settings.font
text: qsTr("Below you can find tools to create wallaper beyond the tools that ScreenPlay provides for you!")
text: qsTr("Below you can find tools to create wallaper, beyond the tools that ScreenPlay provides for you!")
anchors {
top: headline.bottom

View File

@ -202,7 +202,6 @@ Item {
id: txtConvertNumber
color: "white"
text: qsTr("")
font.pointSize: 21
font.family: ScreenPlay.settings.font

View File

@ -197,7 +197,6 @@ Item {
id: txtConvertNumber
color: "white"
text: qsTr("")
font.pointSize: 21
font.family: ScreenPlay.settings.font

View File

@ -0,0 +1,41 @@
import QtQuick
import Qt5Compat.GraphicalEffects
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Layouts
import ScreenPlay 1.0
import ScreenPlay.Create 1.0
Item {
id: root
signal wizardStarted()
signal wizardExited()
signal next()
SwipeView {
id: swipeView
anchors.fill: parent
interactive: false
clip: true
Importh264Init {
onNext: (filePath) =>{
root.wizardStarted();
swipeView.currentIndex = 1;
createWallpaperVideoImportConvert.filePath = filePath;
ScreenPlay.util.setNavigationActive(false);
ScreenPlay.create.importH264(filePath);
}
}
Importh264Convert {
id: createWallpaperVideoImportConvert
onExit: root.wizardExited()
}
}
}

View File

@ -0,0 +1,396 @@
import QtQuick
import Qt5Compat.GraphicalEffects
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Layouts
import ScreenPlay 1.0
import ScreenPlay.Create 1.0
import ScreenPlay.Enums.ImportVideoState 1.0
import "../../../Common" as Common
Item {
id: root
property bool conversionFinishedSuccessful: false
property bool canSave: false
property string filePath
signal exit()
signal save()
function basename(str) {
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;
else
btnSave.enabled = false;
}
onCanSaveChanged: root.checkCanSave()
onFilePathChanged: {
textFieldName.text = basename(filePath);
}
Connections {
function onCreateWallpaperStateChanged(state) {
switch (state) {
case ImportVideoState.AnalyseVideo:
txtConvert.text = qsTr("AnalyseVideo...");
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;
btnExit.enabled = false;
root.checkCanSave();
break;
}
}
function onProgressChanged(progress) {
var percentage = Math.floor(progress * 100);
if (percentage > 100 || progress > 0.95)
percentage = 100;
if (percentage === NaN)
print(progress, percentage);
txtConvertNumber.text = percentage + "%";
}
target: ScreenPlay.create
}
Common.Headline {
id: txtHeadline
text: qsTr("Import a video to a wallpaper")
anchors {
top: parent.top
left: parent.left
margins: 40
bottomMargin: 0
}
}
Item {
id: wrapperLeft
width: parent.width * 0.66
anchors {
left: parent.left
top: txtHeadline.bottom
margins: 30
bottom: parent.bottom
}
Rectangle {
id: imgWrapper
color: Material.color(Material.Grey)
anchors {
top: parent.top
right: parent.right
rightMargin: 20
bottom: previewSelector.top
bottomMargin: 20
left: parent.left
}
Image {
id: imgPreview
fillMode: Image.PreserveAspectCrop
asynchronous: true
visible: false
anchors.fill: parent
}
AnimatedImage {
id: gifPreview
fillMode: Image.PreserveAspectCrop
asynchronous: true
playing: true
visible: false
anchors.fill: parent
}
LinearGradient {
id: shadow
cached: true
anchors.fill: parent
start: Qt.point(0, height)
end: Qt.point(0, 0)
gradient: Gradient {
GradientStop {
id: gradientStop0
position: 0
color: "#DD000000"
}
GradientStop {
id: gradientStop1
position: 1
color: "#00000000"
}
}
}
BusyIndicator {
id: busyIndicator
anchors.centerIn: parent
running: true
}
Text {
id: txtConvertNumber
color: "white"
font.pointSize: 21
font.family: ScreenPlay.settings.font
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 40
}
}
Text {
id: txtConvert
color: Material.secondaryTextColor
text: qsTr("Generating preview video...")
font.pointSize: 14
font.family: ScreenPlay.settings.font
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 20
}
}
}
Common.ImageSelector {
id: previewSelector
height: 80
anchors {
right: parent.right
rightMargin: 20
left: parent.left
bottom: parent.bottom
}
}
}
Item {
id: wrapperRight
width: parent.width * 0.33
anchors {
top: txtHeadline.bottom
topMargin: 30
bottom: parent.bottom
right: parent.right
}
ColumnLayout {
id: column
spacing: 0
anchors {
right: parent.right
left: parent.left
margins: 30
top: parent.top
topMargin: 0
bottom: column1.top
bottomMargin: 50
}
Common.TextField {
id: textFieldName
placeholderText: qsTr("Name (required!)")
width: parent.width
Layout.fillWidth: true
onTextChanged: {
if (textFieldName.text.length >= 3)
canSave = true;
else
canSave = false;
}
}
Common.TextField {
id: textFieldDescription
placeholderText: qsTr("Description")
width: parent.width
Layout.fillWidth: true
}
Common.TextField {
id: textFieldYoutubeURL
placeholderText: qsTr("Youtube URL")
width: parent.width
Layout.fillWidth: true
}
Common.TagSelector {
id: textFieldTags
width: parent.width
Layout.fillWidth: true
}
}
Row {
id: column1
height: 80
width: childrenRect.width
spacing: 10
anchors {
right: parent.right
rightMargin: 30
bottomMargin: -10
bottom: parent.bottom
}
Button {
id: btnExit
text: qsTr("Abort")
Material.background: Material.Red
Material.foreground: "white"
font.family: ScreenPlay.settings.font
onClicked: {
root.exit();
ScreenPlay.create.cancel();
}
}
Button {
id: btnSave
objectName: "btnSave"
text: qsTr("Save")
enabled: false
Material.background: Material.accent
Material.foreground: "white"
font.family: ScreenPlay.settings.font
onClicked: {
if (conversionFinishedSuccessful) {
btnSave.enabled = false;
ScreenPlay.create.saveWallpaper(textFieldName.text, textFieldDescription.text, root.filePath, previewSelector.imageSource, textFieldYoutubeURL.text, Create.H264, textFieldTags.getTags());
savePopup.open();
}
}
}
}
}
Popup {
id: savePopup
modal: true
focus: true
width: 250
anchors.centerIn: parent
height: 200
onOpened: timerSave.start()
BusyIndicator {
anchors.centerIn: parent
running: true
}
Text {
text: qsTr("Save Wallpaper...")
color: Material.primaryTextColor
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 30
font.family: ScreenPlay.settings.font
}
Timer {
id: timerSave
interval: 1000 + Math.random() * 1000
onTriggered: {
savePopup.close();
ScreenPlay.util.setNavigationActive(true);
root.exit();
}
}
}
}

View File

@ -0,0 +1,162 @@
import QtQuick
import Qt5Compat.GraphicalEffects
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Layouts
import QtQuick.Dialogs
import ScreenPlay 1.0
import ScreenPlay.Create 1.0
import "../../../Common" as Common
import "../../"
Item {
id: root
signal next(var filePath)
ColumnLayout {
id: wrapper
spacing: 40
anchors {
top: parent.top
bottom: btnOpenDocs.top
left: parent.left
right: parent.right
margins: 20
}
Common.Headline {
Layout.fillWidth: true
text: qsTr("Import a .mp4 video")
}
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 40
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 40
Text {
id: txtDescription
text: qsTr("ScreenPlay V0.15 and up can play *.mp4 (also more known as h264). This can improove performance on older systems.")
color: Material.primaryTextColor
Layout.fillWidth: true
font.pointSize: 13
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
font.family: ScreenPlay.settings.font
}
DropArea {
id: dropArea
Layout.fillHeight: true
Layout.fillWidth: true
onExited: {
bg.color = Qt.darker(Material.backgroundColor)
}
onEntered: {
bg.color = Qt.darker(Qt.darker(
Material.backgroundColor))
drag.accept(Qt.LinkAction)
}
onDropped: {
let file = ScreenPlay.util.toLocal(drop.urls[0])
bg.color = Qt.darker(Qt.darker(
Material.backgroundColor))
if (file.endsWith(".mp4"))
root.next(drop.urls[0])
else
txtFile.text = qsTr(
"Invalid file type. Must be valid h264 (*.mp4)!")
}
Rectangle {
id: bg
anchors.fill: parent
radius: 3
color: Qt.darker(Material.backgroundColor)
}
Image {
id: bgPattern
anchors.fill: parent
fillMode: Image.Tile
opacity: 0.2
source: "qrc:/assets/images/noisy-texture-3.png"
}
Text {
id: txtFile
text: qsTr("Drop a *.mp4 file here or use 'Select file' below.")
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
color: Material.primaryTextColor
font.pointSize: 13
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
font.family: ScreenPlay.settings.font
anchors {
fill: parent
margins: 40
}
}
}
}
}
}
Button {
id: btnOpenDocs
text: qsTr("Open Documentation")
Material.background: Material.LightGreen
Material.foreground: "white"
icon.source: "qrc:/assets/icons/icon_document.svg"
icon.color: "white"
icon.width: 16
icon.height: 16
font.family: ScreenPlay.settings.font
onClicked: Qt.openUrlExternally(
"https://kelteseth.gitlab.io/ScreenPlayDocs/wallpaper/wallpaper/#performance")
anchors {
bottom: parent.bottom
left: parent.left
margins: 20
}
}
Button {
text: qsTr("Select file")
highlighted: true
font.family: ScreenPlay.settings.font
onClicked: {
fileDialogImportVideo.open()
}
FileDialog {
id: fileDialogImportVideo
nameFilters: ["Video files (*.mp4)"]
onAccepted: {
root.next(fileDialogImportVideo.currentFile)
}
}
anchors {
right: parent.right
bottom: parent.bottom
margins: 20
}
}
}

View File

@ -169,6 +169,91 @@ void Create::createWallpaperStart(QString videoPath, Create::VideoCodec codec, c
m_createImportFutureWatcher.setFuture(m_createImportFuture);
}
void Create::importH264(QString videoPath)
{
reset();
videoPath = ScreenPlayUtil::toLocal(videoPath);
const QDir installedDir = ScreenPlayUtil::toLocal(m_globalVariables->localStoragePath().toString());
// Create a temp dir so we can later alter it to the workshop id
const QDateTime date = QDateTime::currentDateTime();
const auto folderName = date.toString("ddMMyyyyhhmmss");
setWorkingDir(installedDir.path() + "/" + folderName);
if (!installedDir.mkdir(folderName)) {
qInfo() << "Unable to create folder with name: " << folderName << " at: " << installedDir;
emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CreateTmpFolderError);
emit abortCreateWallpaper();
return;
}
m_createImportFuture = QtConcurrent::run(QThreadPool::globalInstance(), [videoPath, this]() {
CreateImportVideo import(videoPath, workingDir(), 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;
}
// Skip preview convert for webm
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;
}
}
emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::Finished);
return;
});
QObject::connect(&m_createImportFutureWatcher, &QFutureWatcherBase::finished, this, [this]() {
if (m_interrupt)
abortAndCleanup();
});
m_createImportFutureWatcher.setFuture(m_createImportFuture);
}
/*!
\brief When converting of the wallpaper steps where successful.
*/
@ -207,7 +292,7 @@ void Create::saveWallpaper(
}
QFileInfo filePathFile(filePath);
if (filePath.endsWith(".webm")) {
if (filePath.endsWith(".webm") || filePath.endsWith(".mp4")) {
if (!QFile::copy(filePath, m_workingDir + "/" + filePathFile.fileName())) {
qDebug() << "Could not copy" << filePath << " to " << m_workingDir + "/" + filePathFile.fileName();
emit createWallpaperStateChanged(ImportVideoState::ImportVideoState::CopyFilesError);
@ -221,8 +306,8 @@ void Create::saveWallpaper(
obj.insert("description", description);
obj.insert("title", title);
obj.insert("youtube", youtube);
obj.insert("videoCodec", codec == Create::VideoCodec::VP8 ? "vp8" : "vp9");
obj.insert("file", filePathFile.completeBaseName() + ".webm");
obj.insert("videoCodec", QVariant::fromValue<VideoCodec>(codec).toString());
obj.insert("file", filePathFile.completeBaseName() + (codec == VideoCodec::H264 ? ".mp4" : ".webm"));
obj.insert("previewGIF", "preview.gif");
obj.insert("previewWEBM", "preview.webm");
obj.insert("preview", previewImageFile.exists() ? previewImageFile.fileName() : "preview.jpg");

View File

@ -78,7 +78,8 @@ public:
enum class VideoCodec {
VP8,
VP9,
AV1
AV1,
H264
};
Q_ENUM(VideoCodec)
@ -101,6 +102,8 @@ public slots:
void createWallpaperStart(QString videoPath, Create::VideoCodec codec = Create::VideoCodec::VP9, const int quality = 50);
void importH264(QString videoPath);
void saveWallpaper(const QString title,
const QString description,
QString filePath,

View File

@ -38,6 +38,21 @@ CreateImportVideo::CreateImportVideo(
m_videoPath = videoPath;
m_exportPath = exportPath;
m_codec = codec;
setupFFMPEG();
}
CreateImportVideo::CreateImportVideo(const QString& videoPath, const QString& exportPath, std::atomic<bool>& interrupt)
: QObject(nullptr)
, m_quality(0)
, m_interrupt(interrupt)
{
m_videoPath = videoPath;
m_exportPath = exportPath;
setupFFMPEG();
}
void CreateImportVideo::setupFFMPEG()
{
m_ffprobeExecutable = QApplication::applicationDirPath() + "/ffprobe" + ScreenPlayUtil::executableBinEnding();
m_ffmpegExecutable = QApplication::applicationDirPath() + "/ffmpeg" + ScreenPlayUtil::executableBinEnding();

View File

@ -60,6 +60,7 @@ class CreateImportVideo : public QObject {
public:
explicit CreateImportVideo(const QString& videoPath, const QString& exportPath, const QString& codec, const int quality, std::atomic<bool>& interrupt);
explicit CreateImportVideo(const QString& videoPath, const QString& exportPath, std::atomic<bool>& interrupt);
float progress() const { return m_progress; }
@ -124,7 +125,9 @@ private:
bool analyzeWebmReadFrames(const QJsonObject& obj);
bool analyzeVideo(const QJsonObject& obj);
void setupFFMPEG();
private:
QString m_ffprobeExecutable;
QString m_ffmpegExecutable;
std::unique_ptr<QProcess> m_process;