Jelajahi Sumber

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.
Norihiro Kamae 4 tahun lalu
induk
melakukan
0e81c66f6e

+ 5 - 0
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"

+ 99 - 0
UI/forms/OBSBasicSettings.ui

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

+ 49 - 0
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<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);
 	}
 

+ 1 - 0
UI/window-basic-main-outputs.hpp

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

+ 15 - 2
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 = []() {

+ 2 - 1
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);

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

+ 1 - 0
UI/window-basic-settings.hpp

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