diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt index ce7382935..43c6f90f7 100644 --- a/obs/CMakeLists.txt +++ b/obs/CMakeLists.txt @@ -97,6 +97,7 @@ set(obs_SOURCES window-basic-preview.cpp window-namedialog.cpp window-log-reply.cpp + window-remux.cpp properties-view.cpp volume-control.cpp qt-wrappers.cpp) @@ -116,6 +117,7 @@ set(obs_HEADERS window-basic-preview.hpp window-namedialog.hpp window-log-reply.hpp + window-remux.hpp properties-view.hpp display-helpers.hpp volume-control.hpp @@ -131,7 +133,8 @@ set(obs_UI forms/OBSBasicSettings.ui forms/OBSBasicSourceSelect.ui forms/OBSBasicInteraction.ui - forms/OBSBasicProperties.ui) + forms/OBSBasicProperties.ui + forms/OBSRemux.ui) set(obs_QRC forms/obs.qrc) diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index b0ec8f67e..ed02bebcc 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -61,6 +61,21 @@ LicenseAgreement.ClickIAgreeToContinue="If you accept the terms of the agreement LicenseAgreement.IAgree="I Agree" LicenseAgreement.Exit="Exit" +# remux dialog +Remux.SourceFile="OBS Recording" +Remux.TargetFile="Target File" +Remux.Remux="Remux" +Remux.RecordingPattern="OBS Recording (*.flv)" +Remux.FinishedTitle="Remuxing finished" +Remux.Finished="Recording remuxed" +Remux.FinishedError="Recording remuxed, but the file may be incomplete" +Remux.SelectRecording="Select OBS Recording …" +Remux.SelectTarget="Select target file …" +Remux.FileExistsTitle="Target file exists" +Remux.FileExists="Target file exists, do you want to replace it?" +Remux.ExitUnfinishedTitle="Remuxing in progress" +Remux.ExitUnfinished="Remuxing is not finished, stopping now may render the target file unusable.\nAre you sure you want to stop remuxing?" + # update dialog UpdateAvailable="New Update Available" UpdateAvailable.Text="Version %1.%2.%3 is now available. Click here to download" @@ -148,6 +163,7 @@ Basic.MainMenu.File="&File" Basic.MainMenu.File.Export="&Export" Basic.MainMenu.File.Import="&Import" Basic.MainMenu.File.ShowRecordings="Show &Recordings" +Basic.MainMenu.File.Remux="&Remux Recordings" Basic.MainMenu.File.Settings="&Settings" Basic.MainMenu.File.Exit="E&xit" diff --git a/obs/forms/OBSBasic.ui b/obs/forms/OBSBasic.ui index 19a529d71..eaf46f422 100644 --- a/obs/forms/OBSBasic.ui +++ b/obs/forms/OBSBasic.ui @@ -457,6 +457,7 @@ + @@ -651,6 +652,11 @@ Basic.MainMenu.File.ShowRecordings + + + Basic.MainMenu.File.Remux + + Basic.MainMenu.File.Settings diff --git a/obs/forms/OBSRemux.ui b/obs/forms/OBSRemux.ui new file mode 100644 index 000000000..a75d640dd --- /dev/null +++ b/obs/forms/OBSRemux.ui @@ -0,0 +1,119 @@ + + + OBSRemux + + + + 0 + 0 + 491 + 124 + + + + Dialog + + + + + 10 + 90 + 351 + 23 + + + + 24 + + + + + + 10 + 10 + 471 + 71 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 6 + + + + + Remux.SourceFile + + + + + + + Remux.TargetFile + + + + + + + + + + 0 + 0 + + + + + + + + Browse + + + + + + + + + + + + 0 + 0 + + + + + + + + Browse + + + + + + + + + + + 370 + 90 + 111 + 23 + + + + Remux.Remux + + + + + + diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 425e7e1a5..90d44c70f 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -38,6 +38,7 @@ #include "window-basic-main.hpp" #include "window-basic-properties.hpp" #include "window-log-reply.hpp" +#include "window-remux.hpp" #include "qt-wrappers.hpp" #include "display-helpers.hpp" #include "volume-control.hpp" @@ -1474,6 +1475,14 @@ void OBSBasic::on_actionShow_Recordings_triggered() QDesktopServices::openUrl(QUrl::fromLocalFile(path)); } +void OBSBasic::on_actionRemux_triggered() +{ + const char *path = config_get_string(basicConfig, + "SimpleOutput", "FilePath"); + OBSRemux remux(path, this); + remux.exec(); +} + void OBSBasic::on_action_Settings_triggered() { OBSBasicSettings settings(this); diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index 79cc2594b..5707e691a 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -233,6 +233,7 @@ private slots: void on_action_Open_triggered(); void on_action_Save_triggered(); void on_actionShow_Recordings_triggered(); + void on_actionRemux_triggered(); void on_action_Settings_triggered(); void on_actionShowLogs_triggered(); void on_actionUploadCurrentLog_triggered(); diff --git a/obs/window-remux.cpp b/obs/window-remux.cpp new file mode 100644 index 000000000..97157efdf --- /dev/null +++ b/obs/window-remux.cpp @@ -0,0 +1,228 @@ +/****************************************************************************** + Copyright (C) 2014 by Ruwen Hahn + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "window-remux.hpp" + +#include "obs-app.hpp" + +#include +#include +#include +#include + +#include "qt-wrappers.hpp" + +#include +#include + +using namespace std; + +OBSRemux::OBSRemux(const char *path, QWidget *parent) + : QDialog (parent), + worker (new RemuxWorker), + ui (new Ui::OBSRemux), + recPath (path) +{ + ui->setupUi(this); + + ui->progressBar->setVisible(false); + ui->remux->setEnabled(false); + ui->targetFile->setEnabled(false); + ui->browseTarget->setEnabled(false); + + ui->progressBar->setMinimum(0); + ui->progressBar->setMaximum(1000); + ui->progressBar->setValue(0); + + connect(ui->browseSource, &QPushButton::clicked, + [&]() { BrowseInput(); }); + connect(ui->browseTarget, &QPushButton::clicked, + [&]() { BrowseOutput(); }); + connect(ui->remux, &QPushButton::clicked, [&]() { Remux(); }); + + connect(ui->sourceFile, &QLineEdit::textChanged, + this, &OBSRemux::inputChanged); + + worker->moveToThread(&remuxer); + + //gcc-4.8 can't use QPointer below + RemuxWorker *worker_ = worker; + connect(worker_, &RemuxWorker::updateProgress, + this, &OBSRemux::updateProgress); + connect(&remuxer, &QThread::finished, worker_, &QObject::deleteLater); + connect(worker_, &RemuxWorker::remuxFinished, + this, &OBSRemux::remuxFinished); + connect(this, &OBSRemux::remux, worker_, &RemuxWorker::remux); +} + +bool OBSRemux::Stop() +{ + if (!worker->job) + return true; + + if (QMessageBox::critical(nullptr, + QTStr("Remux.ExitUnfinishedTitle"), + QTStr("Remux.ExitUnfinished"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No) == + QMessageBox::Yes) { + os_event_signal(worker->stop); + return true; + } + + return false; +} + +OBSRemux::~OBSRemux() +{ + Stop(); + remuxer.quit(); + remuxer.wait(); +} + +void OBSRemux::BrowseInput() +{ + QString path = ui->sourceFile->text(); + if (path.isEmpty()) + path = recPath; + + path = QFileDialog::getOpenFileName(this, + QTStr("Remux.SelectRecording"), path, + QTStr("Remux.RecordingPattern")); + + inputChanged(path); +} + +void OBSRemux::inputChanged(const QString &path) +{ + if (!QFileInfo::exists(path)) { + ui->remux->setEnabled(false); + return; + } + + ui->sourceFile->setText(path); + ui->remux->setEnabled(true); + + QFileInfo fi(path); + QString mp4 = fi.path() + "/" + fi.baseName() + ".mp4"; + ui->targetFile->setText(mp4); + + ui->targetFile->setEnabled(true); + ui->browseTarget->setEnabled(true); +} + +void OBSRemux::BrowseOutput() +{ + QString path(ui->targetFile->text()); + path = QFileDialog::getSaveFileName(this, QTStr("Remux.SelectTarget"), + path, "(*.mp4)"); + + if (path.isEmpty()) + return; + + ui->targetFile->setText(path); +} + +void OBSRemux::Remux() +{ + if (QFileInfo::exists(ui->targetFile->text())) + if (QMessageBox::question(this, QTStr("Remux.FileExistsTitle"), + QTStr("Remux.FileExists"), + QMessageBox::Yes | QMessageBox::No) != + QMessageBox::Yes) + return; + + media_remux_job_t mr_job = nullptr; + if (!media_remux_job_create(&mr_job, QT_TO_UTF8(ui->sourceFile->text()), + QT_TO_UTF8(ui->targetFile->text()))) + return; + + worker->job = job_t(mr_job, media_remux_job_destroy); + worker->lastProgress = 0.f; + + ui->progressBar->setVisible(true); + ui->remux->setEnabled(false); + + if (!remuxer.isRunning()) + remuxer.start(); + emit remux(); +} + +void OBSRemux::closeEvent(QCloseEvent *event) +{ + if (!Stop()) + event->ignore(); + else + QDialog::closeEvent(event); +} + +void OBSRemux::reject() +{ + if (!Stop()) + return; + + QDialog::reject(); +} + +void OBSRemux::updateProgress(float percent) +{ + ui->progressBar->setValue(percent * 10); +} + +void OBSRemux::remuxFinished(bool success) +{ + QMessageBox::information(this, QTStr("Remux.FinishedTitle"), + success ? + QTStr("Remux.Finished") : QTStr("Remux.FinishedError")); + + worker->job.reset(); + ui->progressBar->setVisible(false); + ui->remux->setEnabled(true); +} + +RemuxWorker::RemuxWorker() +{ + os_event_init(&stop, OS_EVENT_TYPE_MANUAL); +} + +RemuxWorker::~RemuxWorker() +{ + os_event_destroy(stop); +} + +void RemuxWorker::UpdateProgress(float percent) +{ + if (abs(lastProgress - percent) < 0.1f) + return; + + emit updateProgress(percent); + lastProgress = percent; +} + +void RemuxWorker::remux() +{ + auto callback = [](void *data, float percent) + { + auto rw = static_cast(data); + rw->UpdateProgress(percent); + return !!os_event_try(rw->stop); + }; + + bool success = media_remux_job_process(job.get(), callback, this); + + emit remuxFinished(os_event_try(stop) && success); +} diff --git a/obs/window-remux.hpp b/obs/window-remux.hpp new file mode 100644 index 000000000..8bda363d9 --- /dev/null +++ b/obs/window-remux.hpp @@ -0,0 +1,86 @@ +/****************************************************************************** + Copyright (C) 2014 by Ruwen Hahn + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include "ui_OBSRemux.h" + +#include +#include + +class RemuxWorker; + +class OBSRemux : public QDialog { + Q_OBJECT + + QThread remuxer; + QPointer worker; + + std::unique_ptr ui; + + const char *recPath; + + void BrowseInput(); + void BrowseOutput(); + void Remux(); + + bool Stop(); + + virtual void closeEvent(QCloseEvent *event) override; + virtual void reject() override; + +public: + explicit OBSRemux(const char *recPath, QWidget *parent = nullptr); + virtual ~OBSRemux() override; + + using job_t = std::shared_ptr; + +private slots: + void inputChanged(const QString &str); + +public slots: + void updateProgress(float percent); + void remuxFinished(bool success); + +signals: + void remux(); +}; + +class RemuxWorker : public QObject { + Q_OBJECT + + OBSRemux::job_t job; + os_event_t *stop; + + float lastProgress; + void UpdateProgress(float percent); + + explicit RemuxWorker(); + virtual ~RemuxWorker(); + +private slots: + void remux(); + +signals: + void updateProgress(float percent); + void remuxFinished(bool success); + + friend class OBSRemux; +};