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.
This commit is contained in:
Norihiro Kamae 2021-10-03 19:06:46 +09:00
parent ce92f441b5
commit 0e81c66f6e
8 changed files with 222 additions and 3 deletions

View file

@ -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"

View file

@ -2408,6 +2408,85 @@
<item row="6" column="1">
<widget class="QLineEdit" name="advOutMuxCustom"/>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="advOutSplitFile">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Basic.Settings.Output.EnableSplitFile</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QComboBox" name="advOutSplitFileType">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>Basic.Settings.Output.SplitFile.TypeTime</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.Settings.Output.SplitFile.TypeSize</string>
</property>
</item>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="advOutSplitFileTimeLabel">
<property name="text">
<string>Basic.Settings.Output.SplitFile.Time</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QSpinBox" name="advOutSplitFileTime">
<property name="suffix">
<string> s</string>
</property>
<property name="minimum">
<number>5</number>
</property>
<property name="maximum">
<number>21600</number>
</property>
<property name="value">
<number>900</number>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="advOutSplitFileSizeLabel">
<property name="text">
<string>Basic.Settings.Output.SplitFile.Size</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QSpinBox" name="advOutSplitFileSize">
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>20</number>
</property>
<property name="maximum">
<number>8192</number>
</property>
<property name="value">
<number>2048</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QStackedWidget" name="advRecTrackWidget">
<property name="sizePolicy">
@ -5675,6 +5754,10 @@
<tabstop>advOutRecUseRescale</tabstop>
<tabstop>advOutRecRescale</tabstop>
<tabstop>advOutMuxCustom</tabstop>
<tabstop>advOutSplitFile</tabstop>
<tabstop>advOutSplitFileType</tabstop>
<tabstop>advOutSplitFileTime</tabstop>
<tabstop>advOutSplitFileSize</tabstop>
<tabstop>advOutFFType</tabstop>
<tabstop>advOutFFRecPath</tabstop>
<tabstop>advOutFFPathBrowse</tabstop>
@ -6198,5 +6281,21 @@
</hint>
</hints>
</connection>
<connection>
<sender>advOutSplitFile</sender>
<signal>toggled(bool)</signal>
<receiver>advOutSplitFileType</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>327</x>
<y>355</y>
</hint>
<hint type="destinationlabel">
<x>701</x>
<y>355</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -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<BasicOutputHandler *>(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<BasicOutputHandler *>(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);
}

View file

@ -32,6 +32,7 @@ struct BasicOutputHandler {
OBSSignal streamDelayStarting;
OBSSignal streamStopping;
OBSSignal recordStopping;
OBSSignal recordFileChanged;
OBSSignal replayBufferStopping;
OBSSignal replayBufferSaved;

View file

@ -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 = []() {

View file

@ -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);

View file

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

View file

@ -381,6 +381,7 @@ private slots:
void UpdateAutomaticReplayBufferCheckboxes();
void AdvOutSplitFileChanged();
void AdvOutRecCheckWarnings();
void SimpleRecordingQualityChanged();