From 0e81c66f6e5a71e3d9c6e771c981ebf5c9f0b802 Mon Sep 17 00:00:00 2001 From: Norihiro Kamae Date: Sun, 3 Oct 2021 19:06:46 +0900 Subject: [PATCH] UI: Add automatic file splitting This commit implements a new feature to split recordings in split files in Advanced output mode. These basic settings are implemented. - Enable/disable the feature. Default is disabled. - Select a type of the limit, time or size. - Specifies the limit in seconds or MiB. --- UI/data/locale/en-US.ini | 5 ++ UI/forms/OBSBasicSettings.ui | 99 ++++++++++++++++++++++++++++++++ UI/window-basic-main-outputs.cpp | 49 ++++++++++++++++ UI/window-basic-main-outputs.hpp | 1 + UI/window-basic-main.cpp | 17 +++++- UI/window-basic-main.hpp | 3 +- UI/window-basic-settings.cpp | 50 ++++++++++++++++ UI/window-basic-settings.hpp | 1 + 8 files changed, 222 insertions(+), 3 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 536504507..b0698b6cd 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -941,6 +941,11 @@ Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings="Audio Encoder Settings (if an Basic.Settings.Output.Adv.FFmpeg.MuxerSettings="Muxer Settings (if any)" Basic.Settings.Output.Adv.FFmpeg.GOPSize="Keyframe interval (frames)" Basic.Settings.Output.Adv.FFmpeg.IgnoreCodecCompat="Show all codecs (even if potentially incompatible)" +Basic.Settings.Output.EnableSplitFile="Automatic File Splitting" +Basic.Settings.Output.SplitFile.TypeTime="Split by Time" +Basic.Settings.Output.SplitFile.TypeSize="Split by Size" +Basic.Settings.Output.SplitFile.Time="Split Time" +Basic.Settings.Output.SplitFile.Size="Split Size" # Screenshot Screenshot="Screenshot Output" diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index 4b562a174..1eee870d7 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -2408,6 +2408,85 @@ + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Basic.Settings.Output.EnableSplitFile + + + + + + + false + + + + Basic.Settings.Output.SplitFile.TypeTime + + + + + Basic.Settings.Output.SplitFile.TypeSize + + + + + + + + Basic.Settings.Output.SplitFile.Time + + + + + + + s + + + 5 + + + 21600 + + + 900 + + + + + + + Basic.Settings.Output.SplitFile.Size + + + + + + + MB + + + 20 + + + 8192 + + + 2048 + + + @@ -5675,6 +5754,10 @@ advOutRecUseRescale advOutRecRescale advOutMuxCustom + advOutSplitFile + advOutSplitFileType + advOutSplitFileTime + advOutSplitFileSize advOutFFType advOutFFRecPath advOutFFPathBrowse @@ -6198,5 +6281,21 @@ + + advOutSplitFile + toggled(bool) + advOutSplitFileType + setEnabled(bool) + + + 327 + 355 + + + 701 + 355 + + + diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index e8eb6f7c4..bd346b949 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -110,6 +110,20 @@ static void OBSRecordStopping(void *data, calldata_t *params) UNUSED_PARAMETER(params); } +static void OBSRecordFileChanged(void *data, calldata_t *params) +{ + BasicOutputHandler *output = static_cast(data); + const char *next_file = calldata_string(params, "next_file"); + + QString arg_last_file = + QString::fromUtf8(output->lastRecordingPath.c_str()); + + QMetaObject::invokeMethod(output->main, "RecordingFileChanged", + Q_ARG(QString, arg_last_file)); + + output->lastRecordingPath = next_file; +} + static void OBSStartReplayBuffer(void *data, calldata_t *params) { BasicOutputHandler *output = static_cast(data); @@ -1311,6 +1325,8 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) OBSStopRecording, this); recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); + recordFileChanged.Connect(obs_output_get_signal_handler(fileOutput), + "file_changed", OBSRecordFileChanged, this); } void AdvancedOutput::UpdateStreamSettings() @@ -1834,6 +1850,10 @@ bool AdvancedOutput::StartRecording() const char *filenameFormat; bool noSpace = false; bool overwriteIfExists = false; + bool splitFile; + const char *splitFileType; + int splitFileTime; + int splitFileSize; if (!useStreamEncoder) { if (!ffmpegOutput) { @@ -1863,6 +1883,8 @@ bool AdvancedOutput::StartRecording() ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); + splitFile = config_get_bool(main->Config(), "AdvOut", + "RecSplitFile"); string strPath = GetRecordingFilename(path, recFormat, noSpace, overwriteIfExists, @@ -1873,6 +1895,33 @@ bool AdvancedOutput::StartRecording() obs_data_set_string(settings, ffmpegRecording ? "url" : "path", strPath.c_str()); + if (splitFile) { + splitFileType = config_get_string( + main->Config(), "AdvOut", "RecSplitFileType"); + splitFileTime = + (astrcmpi(splitFileType, "Time") == 0) + ? config_get_int(main->Config(), + "AdvOut", + "RecSplitFileTime") + : 0; + splitFileSize = + (astrcmpi(splitFileType, "Size") == 0) + ? config_get_int(main->Config(), + "AdvOut", + "RecSplitFileSize") + : 0; + obs_data_set_string(settings, "directory", path); + obs_data_set_string(settings, "format", filenameFormat); + obs_data_set_string(settings, "extension", recFormat); + obs_data_set_bool(settings, "allow_spaces", !noSpace); + obs_data_set_bool(settings, "allow_overwrite", + overwriteIfExists); + obs_data_set_int(settings, "max_time_sec", + splitFileTime); + obs_data_set_int(settings, "max_size_mb", + splitFileSize); + } + obs_output_update(fileOutput, settings); } diff --git a/UI/window-basic-main-outputs.hpp b/UI/window-basic-main-outputs.hpp index 242b26434..c94fcfbf3 100644 --- a/UI/window-basic-main-outputs.hpp +++ b/UI/window-basic-main-outputs.hpp @@ -32,6 +32,7 @@ struct BasicOutputHandler { OBSSignal streamDelayStarting; OBSSignal streamStopping; OBSSignal recordStopping; + OBSSignal recordFileChanged; OBSSignal replayBufferStopping; OBSSignal replayBufferSaved; diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 0517ae255..a006c38fd 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -1414,6 +1414,10 @@ bool OBSBasic::InitBasicConfigDefaults() config_set_default_uint(basicConfig, "AdvOut", "Track5Bitrate", 160); config_set_default_uint(basicConfig, "AdvOut", "Track6Bitrate", 160); + config_set_default_uint(basicConfig, "AdvOut", "RecSplitFileTime", 900); + config_set_default_uint(basicConfig, "AdvOut", "RecSplitFileSize", + 2048); + config_set_default_bool(basicConfig, "AdvOut", "RecRB", false); config_set_default_uint(basicConfig, "AdvOut", "RecRBTime", 20); config_set_default_int(basicConfig, "AdvOut", "RecRBSize", 512); @@ -6947,7 +6951,7 @@ void OBSBasic::StreamingStop(int code, QString last_error) SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); } -void OBSBasic::AutoRemux(QString input) +void OBSBasic::AutoRemux(QString input, bool no_show) { bool autoRemux = config_get_bool(Config(), "Video", "AutoRemux"); @@ -6979,7 +6983,8 @@ void OBSBasic::AutoRemux(QString input) output += "mp4"; OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - remux->show(); + if (!no_show) + remux->show(); remux->AutoRemux(input, output); } @@ -7132,6 +7137,14 @@ void OBSBasic::RecordingStop(int code, QString last_error) UpdatePause(false); } +void OBSBasic::RecordingFileChanged(QString lastRecordingPath) +{ + QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); + ShowStatusBarMessage(str.arg(lastRecordingPath)); + + AutoRemux(lastRecordingPath, true); +} + void OBSBasic::ShowReplayBufferPauseWarning() { auto msgBox = []() { diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 9b5b2db2d..2f984d021 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -630,6 +630,7 @@ public slots: void RecordingStart(); void RecordStopping(); void RecordingStop(int code, QString last_error); + void RecordingFileChanged(QString lastRecordingPath); void ShowReplayBufferPauseWarning(); void StartReplayBuffer(); @@ -797,7 +798,7 @@ private: static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed); - void AutoRemux(QString input); + void AutoRemux(QString input, bool no_show = false); void UpdatePause(bool activate = true); void UpdateReplayBuffer(bool activate = true); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 2a34e6283..523c1c599 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -457,6 +457,10 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->advOutRecUseRescale, CHECK_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutRecRescale, CBEDIT_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutMuxCustom, EDIT_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->advOutSplitFile, CHECK_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->advOutSplitFileType, COMBO_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->advOutSplitFileTime, SCROLL_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->advOutSplitFileSize, SCROLL_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutRecTrack1, CHECK_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutRecTrack2, CHECK_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutRecTrack3, CHECK_CHANGED, OUTPUTS_CHANGED); @@ -769,6 +773,10 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) this, SLOT(SimpleReplayBufferChanged())); connect(ui->simpleRBSecMax, SIGNAL(valueChanged(int)), this, SLOT(SimpleReplayBufferChanged())); + connect(ui->advOutSplitFile, SIGNAL(stateChanged(int)), this, + SLOT(AdvOutSplitFileChanged())); + connect(ui->advOutSplitFileType, SIGNAL(currentIndexChanged(int)), this, + SLOT(AdvOutSplitFileChanged())); connect(ui->advReplayBuf, SIGNAL(toggled(bool)), this, SLOT(AdvReplayBufferChanged())); connect(ui->advOutRecTrack1, SIGNAL(toggled(bool)), this, @@ -895,6 +903,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) ui->buttonBox->button(QDialogButtonBox::Cancel)->setIcon(QIcon()); SimpleRecordingQualityChanged(); + AdvOutSplitFileChanged(); UpdateAutomaticReplayBufferCheckboxes(); @@ -1901,6 +1910,14 @@ void OBSBasicSettings::LoadAdvOutputRecordingSettings() config_get_string(main->Config(), "AdvOut", "RecMuxerCustom"); int tracks = config_get_int(main->Config(), "AdvOut", "RecTracks"); int flvTrack = config_get_int(main->Config(), "AdvOut", "FLVTrack"); + bool splitFile = + config_get_bool(main->Config(), "AdvOut", "RecSplitFile"); + const char *splitFileType = + config_get_string(main->Config(), "AdvOut", "RecSplitFileType"); + int splitFileTime = + config_get_int(main->Config(), "AdvOut", "RecSplitFileTime"); + int splitFileSize = + config_get_int(main->Config(), "AdvOut", "RecSplitFileSize"); int typeIndex = (astrcmpi(type, "FFmpeg") == 0) ? 1 : 0; ui->advOutRecType->setCurrentIndex(typeIndex); @@ -1920,6 +1937,12 @@ void OBSBasicSettings::LoadAdvOutputRecordingSettings() ui->advOutRecTrack5->setChecked(tracks & (1 << 4)); ui->advOutRecTrack6->setChecked(tracks & (1 << 5)); + idx = (astrcmpi(splitFileType, "Size") == 0) ? 1 : 0; + ui->advOutSplitFile->setChecked(splitFile); + ui->advOutSplitFileType->setCurrentIndex(idx); + ui->advOutSplitFileTime->setValue(splitFileTime); + ui->advOutSplitFileSize->setValue(splitFileSize); + switch (flvTrack) { case 1: ui->flvTrack1->setChecked(true); @@ -3378,6 +3401,14 @@ static inline const char *RecTypeFromIdx(int idx) return "Standard"; } +static inline const char *SplitFileTypeFromIdx(int idx) +{ + if (idx == 1) + return "Size"; + else + return "Time"; +} + static void WriteJsonData(OBSPropertiesView *view, const char *path) { char full_path[512]; @@ -3513,6 +3544,12 @@ void OBSBasicSettings::SaveOutputSettings() SaveCheckBox(ui->advOutRecUseRescale, "AdvOut", "RecRescale"); SaveCombo(ui->advOutRecRescale, "AdvOut", "RecRescaleRes"); SaveEdit(ui->advOutMuxCustom, "AdvOut", "RecMuxerCustom"); + SaveCheckBox(ui->advOutSplitFile, "AdvOut", "RecSplitFile"); + config_set_string( + main->Config(), "AdvOut", "RecSplitFileType", + SplitFileTypeFromIdx(ui->advOutSplitFileType->currentIndex())); + SaveSpinBox(ui->advOutSplitFileTime, "AdvOut", "RecSplitFileTime"); + SaveSpinBox(ui->advOutSplitFileSize, "AdvOut", "RecSplitFileSize"); config_set_int( main->Config(), "AdvOut", "RecTracks", @@ -4414,6 +4451,19 @@ void OBSBasicSettings::AdvancedChanged() } } +void OBSBasicSettings::AdvOutSplitFileChanged() +{ + bool splitFile = ui->advOutSplitFile->isChecked(); + int splitFileType = splitFile ? ui->advOutSplitFileType->currentIndex() + : -1; + + ui->advOutSplitFileType->setEnabled(splitFile); + ui->advOutSplitFileTimeLabel->setVisible(splitFileType == 0); + ui->advOutSplitFileTime->setVisible(splitFileType == 0); + ui->advOutSplitFileSizeLabel->setVisible(splitFileType == 1); + ui->advOutSplitFileSize->setVisible(splitFileType == 1); +} + void OBSBasicSettings::AdvOutRecCheckWarnings() { auto Checked = [](QCheckBox *box) { return box->isChecked() ? 1 : 0; }; diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index b27c9e143..73610d9ce 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -381,6 +381,7 @@ private slots: void UpdateAutomaticReplayBufferCheckboxes(); + void AdvOutSplitFileChanged(); void AdvOutRecCheckWarnings(); void SimpleRecordingQualityChanged();