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;
+};