Add UI for remuxing recordings via FFmpeg
This commit is contained in:
parent
4247a7b81e
commit
c9ee436e1c
@ -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)
|
||||
|
@ -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. <a href='%4'>Click here to download</a>"
|
||||
@ -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"
|
||||
|
||||
|
@ -457,6 +457,7 @@
|
||||
<addaction name="action_Open"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionShow_Recordings"/>
|
||||
<addaction name="actionRemux"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Settings"/>
|
||||
<addaction name="separator"/>
|
||||
@ -651,6 +652,11 @@
|
||||
<string>Basic.MainMenu.File.ShowRecordings</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemux">
|
||||
<property name="text">
|
||||
<string>Basic.MainMenu.File.Remux</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Settings">
|
||||
<property name="text">
|
||||
<string>Basic.MainMenu.File.Settings</string>
|
||||
|
119
obs/forms/OBSRemux.ui
Normal file
119
obs/forms/OBSRemux.ui
Normal file
@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OBSRemux</class>
|
||||
<widget class="QDialog" name="OBSRemux">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>491</width>
|
||||
<height>124</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>90</y>
|
||||
<width>351</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>24</number>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="formLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>471</width>
|
||||
<height>71</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Remux.SourceFile</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Remux.TargetFile</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="sourceFile">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browseSource">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="targetFile">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browseTarget">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="remux">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>370</x>
|
||||
<y>90</y>
|
||||
<width>111</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remux.Remux</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -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);
|
||||
|
@ -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();
|
||||
|
228
obs/window-remux.cpp
Normal file
228
obs/window-remux.cpp
Normal file
@ -0,0 +1,228 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include "window-remux.hpp"
|
||||
|
||||
#include "obs-app.hpp"
|
||||
|
||||
#include <QCloseEvent>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "qt-wrappers.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
||||
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<RemuxWorker> 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<RemuxWorker*>(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);
|
||||
}
|
86
obs/window-remux.hpp
Normal file
86
obs/window-remux.hpp
Normal file
@ -0,0 +1,86 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPointer>
|
||||
#include <QThread>
|
||||
#include <memory>
|
||||
#include "ui_OBSRemux.h"
|
||||
|
||||
#include <media-io/media-remux.h>
|
||||
#include <util/threading.h>
|
||||
|
||||
class RemuxWorker;
|
||||
|
||||
class OBSRemux : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
QThread remuxer;
|
||||
QPointer<RemuxWorker> worker;
|
||||
|
||||
std::unique_ptr<Ui::OBSRemux> 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<struct media_remux_job>;
|
||||
|
||||
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;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user