diff --git a/data/locale/ca-ES.ini b/data/locale/ca-ES.ini
index c79ef03..f7f3b70 100644
--- a/data/locale/ca-ES.ini
+++ b/data/locale/ca-ES.ini
@@ -44,6 +44,7 @@ SplitFile="Divisió automàtica de fitxers"
SplitFile.NoSplit="Sense divisió"
SplitFile.ByTime="Dividit pel temps"
SplitFile.BySize="Dividit per mida"
+SplitFile.Manual="Només divisió manual"
SplitFile.Time="Temps de divisió (Minuts)"
SplitFile.Size="Mida de la divisió (MB)"
MasterTrack="Àudio mestre"
@@ -125,5 +126,7 @@ NoSpaceFileName="Genera noms de fitxer sense espai"
KeepOutputBaseResolution="No reinicieu la sortida encara que es canviï la resolució de la font"
KeepOutputBaseResolutionNote="Si s'activa, la imatge de vídeo pot quedar retallada o tenir espais en blanc quan es canvia la resolució de la font."
AudioSourceNote="Àudio mestre: Sortida mixta d'OBS (té 6 pistes conegudes)
Àudio de font: Àudio del conducte de filtre que es passa al filtre Branch Output.
Àudio de font: Àudio agrupat de la font després de passar per tots els filtres del conducte."
+SplitRecordingFileHotkey="Divideix el fitxer enregistrat"
+SplitRecordingAllHotkey="Divideix tots els fitxers d'enregistrament de Sortida de Branca"
+SplitAllRecordings="Divideix tots els enregistraments"
CustomMuxerSettings="Configuració del conversor personalitzada"
-
diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini
index c890b10..9ee28b2 100644
--- a/data/locale/de-DE.ini
+++ b/data/locale/de-DE.ini
@@ -44,6 +44,7 @@ SplitFile="Automatische Dateiaufteilung"
SplitFile.NoSplit="Keine Aufteilung"
SplitFile.ByTime="Nach Zeit aufteilen"
SplitFile.BySize="Nach Größe aufteilen"
+SplitFile.Manual="Nur manuell teilen"
SplitFile.Time="Teilungszeit (Minuten)"
SplitFile.Size="Teilungsgröße (MB)"
MasterTrack="Master-Audio"
@@ -125,4 +126,9 @@ NoSpaceFileName="Dateinamen ohne Leerzeichen generieren"
KeepOutputBaseResolution="Ausgabe nicht neu starten, auch wenn sich die Quellauflösung ändert"
KeepOutputBaseResolutionNote="Wenn aktiviert, kann das Videobild beschnitten werden oder leere Bereiche aufweisen, wenn sich die Quellauflösung ändert."
AudioSourceNote="Master-Audio: OBS-Mischausgang (hat bekanntlich 6 Spuren)
Filter-Audio: Audio der Filterpipeline, das an den Branch Output-Filter übergeben wird.
Quell-Audio: Gebündeltes Audio der Quelle, nachdem es alle Filter in der Pipeline durchlaufen hat."
+SplitRecordingFileHotkey="Aufnehmende Datei teilen"
+SplitRecordingAllHotkey="Alle Aufnahmedateien des Branch-Ausgangs teilen"
+SplitRecordingAllHotkey="Alle Zweigausgang-Aufnahmedateien aufteilen"
+SplitAllRecordings="Alle Aufnahmen teilen"
CustomMuxerSettings="CBenutzerdefinierte Muxereinstellungen"
+
diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini
index 0a3abb9..50a805d 100644
--- a/data/locale/en-US.ini
+++ b/data/locale/en-US.ini
@@ -44,6 +44,7 @@ SplitFile="Automatic File Splitting"
SplitFile.NoSplit="No split"
SplitFile.ByTime="Split by Time"
SplitFile.BySize="Split by Size"
+SplitFile.Manual="Only split manually"
SplitFile.Time="Split Time (Minutes)"
SplitFile.Size="Split Size (MB)"
MasterTrack="Master Audio"
@@ -125,4 +126,8 @@ NoSpaceFileName="Generate File Name without Space"
KeepOutputBaseResolution="Don't reboot output even if source resolution is changed"
KeepOutputBaseResolutionNote="If enabled, the video image may be cropped or have blank spaces when the source resolution is changed."
AudioSourceNote="Master Audio: OBS's mix output (It has 6 tracks as known)
Filter Audio: Filter pipeline's audio which is passed to Branch Output filter.
Source Audio: Source's bundled audio which after passed through the all filters in pipeline."
+SplitRecordingFileHotkey="Split Recording File"
+SplitRecordingAllHotkey="Split all Branch Output recording files"
+SplitAllRecordings="Split All Recordings"
+SplitRecording="Split Recording"
CustomMuxerSettings="Custom Muxer Settings"
diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini
index 9f57faa..1a20268 100644
--- a/data/locale/fr-FR.ini
+++ b/data/locale/fr-FR.ini
@@ -44,6 +44,7 @@ SplitFile="Fractionnement automatique des fichiers"
SplitFile.NoSplit="Pas de division"
SplitFile.ByTime="Divisé par le temps"
SplitFile.BySize="Divisé par taille"
+SplitFile.Manual="Ne fractionner que manuellement"
SplitFile.Time="Temps de division (minutes)"
SplitFile.Size="Taille de la division (MB)"
MasterTrack="Audio principal"
@@ -125,4 +126,7 @@ NoSpaceFileName="Générer le nom du fichier sans espace"
KeepOutputBaseResolution="Ne pas redémarrer la sortie même si la résolution de la source est modifiée"
KeepOutputBaseResolutionNote="Si activé, l'image vidéo peut être recadrée ou présenter des espaces vides lorsque la résolution de la source est modifiée."
AudioSourceNote="Audio principal: Sortie mixée d'OBS (elle possède 6 pistes comme connu)
Audio du filtre: Audio du pipeline de filtrage qui est transmis au filtre Branch Output.
Audio de la source: Audio groupé de la source après avoir traversé tous les filtres du pipeline."
+SplitRecordingFileHotkey="Fractionner le fichier cible"
+SplitRecordingAllHotkey="Diviser tous les fichiers d'enregistrement de Sortie de Branche"
+SplitAllRecordings="Diviser tous les enregistrements"
CustomMuxerSettings="Paramètres personnalisés du muxer"
diff --git a/data/locale/ja-JP.ini b/data/locale/ja-JP.ini
index d6077d4..add252e 100644
--- a/data/locale/ja-JP.ini
+++ b/data/locale/ja-JP.ini
@@ -44,6 +44,7 @@ SplitFile="自動ファイル分割"
SplitFile.NoSplit="分割無し"
SplitFile.ByTime="時間で分割"
SplitFile.BySize="サイズで分割"
+SplitFile.Manual="手動で分割"
SplitFile.Time="分割時間 (分)"
SplitFile.Size="分割サイズ (MB)"
MasterTrack="マスター音声"
@@ -112,8 +113,8 @@ DownscaleFilter.Bilinear="バイリニア (最速だが、スケーリングす
DownscaleFilter.Bicubic="バイキュービック (先鋭化スケーリング、16サンプル)"
DownscaleFilter.Lanczos="ランチョス (先鋭化スケーリング、36サンプル)"
DownscaleFilter.Area="エリア (加重合計、4/6/9サンプル)"
-EnableAllHotkey="全ての Branch Outputs を有効化"
-DisableAllHotkey="全ての Branch Outputs を無効化"
+EnableAllHotkey="全ての Branch Output を有効化"
+DisableAllHotkey="全ての Branch Output を無効化"
EnableHotkey="'%1' を有効化"
DisableHotkey="'%1' を無効化"
ServiceCount="配信数"
@@ -125,4 +126,8 @@ NoSpaceFileName="スペースなしのファイル名を生成する"
KeepOutputBaseResolution="ソースの解像度が変更されてもアウトプットを再起動しない"
KeepOutputBaseResolutionNote="有効にした場合、ソースの解像度が変わると、映像が見切れたり余白が出来たりする場合があります"
AudioSourceNote="マスター音声: OBSのミックス出力(既知の6トラックがあります)
フィルター音声: Branch Outputフィルターに渡されるフィルターパイプラインの音声。
ソース音声: パイプライン内のすべてのフィルターを通過した後のソースのバンドルされた音声。"
+SplitRecordingFileHotkey="録画ファイルの分割"
+SplitRecordingAllHotkey="全ての Branch Output 録画ファイルを分割"
+SplitAllRecordings="全録画を分割"
+SplitRecording="録画を分割"
CustomMuxerSettings="カスタムマルチプレクサ設定"
diff --git a/data/locale/ko-KR.ini b/data/locale/ko-KR.ini
index 0c24552..c75cba2 100644
--- a/data/locale/ko-KR.ini
+++ b/data/locale/ko-KR.ini
@@ -44,6 +44,7 @@ SplitFile="자동 파일 분할"
SplitFile.NoSplit="분할 없음"
SplitFile.ByTime="시간별로 분할"
SplitFile.BySize="크기별로 분할"
+SplitFile.Manual="수동으로만 분할"
SplitFile.Time="분할 시간 (분)"
SplitFile.Size="분할 크기 (MB)"
MasterTrack="마스터 오디오"
@@ -125,4 +126,7 @@ NoSpaceFileName="ス공백 없이 파일 이름 짓기"
KeepOutputBaseResolution="소스 해상도가 변경되어도 출력을 다시 시작하지 않음"
KeepOutputBaseResolutionNote="활성화된 경우, 소스 해상도가 변경되면 비디오 이미지가 잘리거나 빈 공간이 생길 수 있습니다."
AudioSourceNote="마스터 오디오: OBS의 믹스 출력 (알려진 대로 6개의 트랙이 있음)
필터 오디오: Branch Output 필터로 전달되는 필터 파이프라인의 오디오.
소스 오디오: 파이프라인의 모든 필터를 통과한 후의 소스의 번들 오디오."
+SplitRecordingFileHotkey="녹화 파일 분할"
+SplitRecordingAllHotkey="모든 분기 출력 녹화 파일 분할"
+SplitAllRecordings="모든 녹화 분할"
CustomMuxerSettings="사용자 Muxer 설정"
diff --git a/data/locale/ro-RO.ini b/data/locale/ro-RO.ini
index 9cc7970..388635d 100644
--- a/data/locale/ro-RO.ini
+++ b/data/locale/ro-RO.ini
@@ -44,6 +44,7 @@ SplitFile="Divizarea automată a fișierelor"
SplitFile.NoSplit="Nicio scindare"
SplitFile.ByTime="Împărțit după timp"
SplitFile.BySize="Împărțit după dimensiune"
+SplitFile.Manual="Separă numai manual"
SplitFile.Time="Timp de împărțire (minut)"
SplitFile.Size="Dimensiune împărțită (MB)"
MasterTrack="Audio master"
@@ -123,4 +124,7 @@ NoSpaceFileName="Generează nume de fișiere fără spațiu"
KeepOutputBaseResolution="Nu reporniți ieșirea chiar dacă rezoluția sursei este modificată"
KeepOutputBaseResolutionNote="Dacă este activat, imaginea video poate fi decupată sau poate avea spații goale atunci când rezoluția sursei este modificată."
AudioSourceNote="Audio master: Ieșirea mixată a OBS (are 6 piste după cum se știe)
Audio filtru: Audioul conductei de filtre care este transmis către filtrul Branch Output.
Audio sursă: Audioul grupat al sursei după ce a trecut prin toate filtrele din conductă."
+SplitRecordingFileHotkey="Separă fișierul înregistrării"
+SplitRecordingAllHotkey="Împarte toate fișierele de înregistrare Ieșire de Ramură"
+SplitAllRecordings="Împarte toate înregistrările"
CustomMuxerSettings="Setări personalizate pentru muxer"
diff --git a/data/locale/ru-RU.ini b/data/locale/ru-RU.ini
index 57af3fe..df13527 100644
--- a/data/locale/ru-RU.ini
+++ b/data/locale/ru-RU.ini
@@ -44,6 +44,7 @@ SplitFile="Автоматическое разделение файлов"
SplitFile.NoSplit="Нет разделения"
SplitFile.ByTime="Разделить по времени"
SplitFile.BySize="Разделить по размеру"
+SplitFile.Manual="Вручную"
SplitFile.Time="Время дивизиона (минуты)"
SplitFile.Size="Размер дивизиона (MB)"
MasterTrack="Мастер-аудио"
@@ -125,4 +126,7 @@ NoSpaceFileName="Генерировать имя файла без пробел
KeepOutputBaseResolution="Не перезапускать вывод даже при изменении разрешения источника"
KeepOutputBaseResolutionNote="При включении этой функции изображение видео может быть обрезано или иметь пустые пространства при изменении разрешения источника."
AudioSourceNote="Мастер-аудио: Микшированный выход OBS (известно, что имеет 6 дорожек)
Аудио фильтра: Аудио из конвейера фильтров, которое передается в фильтр Branch Output.
Аудио источника: Объединенное аудио источника после прохождения через все фильтры в конвейере."
+SplitRecordingFileHotkey="Разделить файл записи"
+SplitRecordingAllHotkey="Разделить все файлы записи Выхода Ветви"
+SplitAllRecordings="Разделить все записи"
CustomMuxerSettings="Свои настройки паковщика"
diff --git a/data/locale/uk-UA.ini b/data/locale/uk-UA.ini
index 3507dc9..26c860b 100644
--- a/data/locale/uk-UA.ini
+++ b/data/locale/uk-UA.ini
@@ -44,6 +44,7 @@ SplitFile="Автоматичне розділення файлів"
SplitFile.NoSplit="Без розколу"
SplitFile.ByTime="Поділ за часом"
SplitFile.BySize="Поділ за розміром"
+SplitFile.Manual="Розділяти лише вручну"
SplitFile.Time="Час ділення (хвилини)"
SplitFile.Size="Розмір поділу (MB)"
MasterTrack="Майстер-аудіо"
@@ -125,4 +126,7 @@ NoSpaceFileName="Генерувати назву файлу без пробіл
KeepOutputBaseResolution="Не перезапускати вивід, навіть якщо роздільна здатність джерела змінилася"
KeepOutputBaseResolutionNote="Якщо увімкнено, зображення відео може бути обрізане або мати пусті простори при зміні роздільної здатності джерела."
AudioSourceNote="Майстер-аудіо: Змішаний вихід OBS (як відомо, має 6 доріжок)
Аудіо фільтра: Аудіо з конвеєра фільтрів, яке передається у фільтр Branch Output.
Аудіо джерела: Об'єднане аудіо джерела після проходження через усі фільтри в конвеєрі."
+SplitRecordingFileHotkey="Розділити файл Запису"
+SplitRecordingAllHotkey="Розділити всі файли запису Виходу Гілки"
+SplitAllRecordings="Розділити всі записи"
CustomMuxerSettings="Власні налаштування мультиплексора"
diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini
index 1e8f2ba..954ba91 100644
--- a/data/locale/zh-CN.ini
+++ b/data/locale/zh-CN.ini
@@ -44,6 +44,7 @@ SplitFile="自动分割文件"
SplitFile.NoSplit="无分裂"
SplitFile.ByTime="按时间分割"
SplitFile.BySize="按大小分割"
+SplitFile.Manual="仅手动分割"
SplitFile.Time="分割时间 (分钟)"
SplitFile.Size="分割大小 (MB)"
MasterTrack="主音频"
@@ -125,4 +126,7 @@ NoSpaceFileName="生成没有空格的文件名"
KeepOutputBaseResolution="即使源分辨率改变也不重启输出"
KeepOutputBaseResolutionNote="如果启用,当源分辨率改变时,视频图像可能会被裁剪或出现空白区域。"
AudioSourceNote="主音频: OBS的混合输出(众所周知有6个轨道)
滤镜音频: 传递给分支输出滤镜的滤镜管道音频。
源音频: 通过管道中所有滤镜后的源捆绑音频。"
+SplitRecordingFileHotkey="分割录制文件"
+SplitRecordingAllHotkey="分割所有分支输出录制文件"
+SplitAllRecordings="分割所有录制"
CustomMuxerSettings="自定义混流器设置"
diff --git a/src/UI/images/scissors.svg b/src/UI/images/scissors.svg
new file mode 100644
index 0000000..e79f670
--- /dev/null
+++ b/src/UI/images/scissors.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/UI/output-status-dock.cpp b/src/UI/output-status-dock.cpp
index f2a33ac..010dde2 100644
--- a/src/UI/output-status-dock.cpp
+++ b/src/UI/output-status-dock.cpp
@@ -81,10 +81,16 @@ BranchOutputStatusDock::BranchOutputStatusDock(QWidget *parent) : QFrame(parent)
// Tool buttons
enableAllButton = new QPushButton(QTStr("EnableAll"), this);
- connect(enableAllButton, &QPushButton::clicked, [this]() { setEabnleAll(true); });
+ connect(enableAllButton, &QPushButton::clicked, this, [this]() { setEabnleAll(true); });
disableAllButton = new QPushButton(QTStr("DisableAll"), this);
- connect(disableAllButton, &QPushButton::clicked, [this]() { setEabnleAll(false); });
+ connect(disableAllButton, &QPushButton::clicked, this, [this]() { setEabnleAll(false); });
+
+ splitRecordingAllButton = new QPushButton(QTStr("SplitAllRecordings"), this);
+ connect(
+ splitRecordingAllButton, &QPushButton::clicked, this, &BranchOutputStatusDock::onSplitRecordingAllButtonClicked
+ );
+ splitRecordingAllButton->setVisible(false);
interlockLabel = new QLabel(QTStr("Interlock"), this);
interlockComboBox = new QComboBox(this);
@@ -97,6 +103,7 @@ BranchOutputStatusDock::BranchOutputStatusDock(QWidget *parent) : QFrame(parent)
auto buttonsContainerLayout = new QHBoxLayout();
buttonsContainerLayout->addWidget(enableAllButton);
buttonsContainerLayout->addWidget(disableAllButton);
+ buttonsContainerLayout->addWidget(splitRecordingAllButton);
buttonsContainerLayout->addStretch();
buttonsContainerLayout->addWidget(interlockLabel);
buttonsContainerLayout->addWidget(interlockComboBox);
@@ -113,10 +120,15 @@ BranchOutputStatusDock::BranchOutputStatusDock(QWidget *parent) : QFrame(parent)
disableAllHotkey = obs_hotkey_register_frontend(
"DisableAllBranchOutputsHotkey", obs_module_text("DisableAllHotkey"), onDisableAllHotkeyPressed, this
);
+ splitRecordingAllHotkey = obs_hotkey_register_frontend(
+ "SplitRecordingAllBranchOutputsHotkey", obs_module_text("SplitRecordingAllHotkey"),
+ onSplitRecordingAllHotkeyPressed, this
+ );
loadSettings();
loadHotkey(enableAllHotkey, "EnableAllBranchOutputsHotkey");
loadHotkey(disableAllHotkey, "DisableAllBranchOutputsHotkey");
+ loadHotkey(splitRecordingAllHotkey, "SplitRecordingAllBranchOutputsHotkey");
obs_log(LOG_DEBUG, "BranchOutputStatusDock created");
}
@@ -128,6 +140,7 @@ BranchOutputStatusDock::~BranchOutputStatusDock()
// Unregister hotkeys
obs_hotkey_unregister(enableAllHotkey);
obs_hotkey_unregister(disableAllHotkey);
+ obs_hotkey_unregister(splitRecordingAllHotkey);
obs_log(LOG_DEBUG, "BranchOutputStatusDock destroyed");
}
@@ -189,64 +202,8 @@ void BranchOutputStatusDock::addRow(
BranchOutputFilter *filter, size_t streamingIndex, RowOutputType outputType, size_t groupIndex
)
{
- auto parent = obs_filter_get_parent(filter->filterSource);
auto row = (int)outputTableRows.size();
-
- auto otr = new OutputTableRow(this);
-
- otr->filter = filter;
- otr->filterCell = new FilterCell(filter->name, filter->filterSource, this);
- otr->parentCell = new ParentCell(obs_source_get_name(parent), parent, this);
- otr->status = new StatusCell(QTStr("Status.Inactive"), this);
-
- switch (outputType) {
- case ROW_OUTPUT_STREAMING:
- otr->outputName = new QLabel(QTStr("Streaming%1").arg(streamingIndex + 1), this);
- otr->status->setIcon(QPixmap(":/branch-output/images/streaming.svg").scaled(16, 16));
- break;
- case ROW_OUTPUT_RECORDING:
- otr->outputName = new RecordingOutputCell(QTStr("Recording"), filter->filterSource, this);
- otr->status->setIcon(QPixmap(":/branch-output/images/recording.svg").scaled(16, 16));
- break;
- default:
- otr->outputName = new QLabel(QTStr("None"), this);
- otr->status->setIcon(QPixmap());
- }
-
- otr->streamingIndex = streamingIndex;
- otr->outputType = outputType;
- otr->groupIndex = groupIndex;
- otr->droppedFrames = new QLabel("", this);
- otr->megabytesSent = new QLabel("", this);
- otr->bitrate = new QLabel("", this);
-
- auto col = 0;
- outputTable->setRowCount(row + 1);
- outputTable->setCellWidget(row, col++, otr->filterCell);
- outputTable->setCellWidget(row, col++, otr->parentCell);
- outputTable->setCellWidget(row, col++, otr->outputName);
- outputTable->setCellWidget(row, col++, otr->status);
- outputTable->setCellWidget(row, col++, otr->droppedFrames);
- outputTable->setCellWidget(row, col++, otr->megabytesSent);
- outputTable->setCellWidget(row, col++, otr->bitrate);
-
- outputTable->setRowHeight(row, 32);
-
- // Setup reset button
- auto resetButtonContainer = new QWidget(this);
- auto resetButtonContainerLayout = new QHBoxLayout();
- resetButtonContainerLayout->setContentsMargins(0, 0, 0, 0);
- resetButtonContainer->setLayout(resetButtonContainerLayout);
-
- auto resetButton = new QPushButton(QTStr("Reset"), this);
- connect(resetButton, &QPushButton::clicked, [this, row]() { outputTableRows[row]->reset(); });
- resetButton->setProperty("toolButton", true); // Until OBS 30
- resetButton->setProperty("class", "btn-tool"); // Since OBS 31
- resetButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
-
- resetButtonContainerLayout->addWidget(resetButton);
- outputTable->setCellWidget(row, col, resetButtonContainer);
-
+ auto otr = new OutputTableRow(row, filter, streamingIndex, outputType, groupIndex, this);
outputTableRows.push_back(otr);
}
@@ -300,6 +257,19 @@ void BranchOutputStatusDock::update()
}
row->update();
}
+
+ applySplitRecordingAllButtonVisible();
+}
+
+void BranchOutputStatusDock::applySplitRecordingAllButtonVisible()
+{
+ foreach (auto row, outputTableRows) {
+ if (row->status->isSPlitRecordingButtonShow()) {
+ splitRecordingAllButton->setVisible(true);
+ return;
+ }
+ }
+ splitRecordingAllButton->setVisible(false);
}
void BranchOutputStatusDock::showEvent(QShowEvent *)
@@ -322,6 +292,15 @@ void BranchOutputStatusDock::setEabnleAll(bool enabled)
}
}
+void BranchOutputStatusDock::splitRecordingAll()
+{
+ foreach (auto row, outputTableRows) {
+ if (row->outputType == ROW_OUTPUT_RECORDING) {
+ row->filter->splitRecording();
+ }
+ }
+}
+
void BranchOutputStatusDock::onEanbleAllHotkeyPressed(void *data, obs_hotkey_id, obs_hotkey *, bool pressed)
{
auto dock = static_cast(data);
@@ -338,11 +317,99 @@ void BranchOutputStatusDock::onDisableAllHotkeyPressed(void *data, obs_hotkey_id
}
}
+void BranchOutputStatusDock::onSplitRecordingAllHotkeyPressed(void *data, obs_hotkey_id, obs_hotkey *, bool pressed)
+{
+ auto dock = static_cast(data);
+ if (pressed) {
+ dock->splitRecordingAll();
+ }
+}
+
+void BranchOutputStatusDock::onSplitRecordingAllButtonClicked()
+{
+ splitRecordingAll();
+
+ auto interlockType = interlockComboBox->currentData().toInt();
+ if (interlockType == BranchOutputFilter::INTERLOCK_TYPE_RECORDING ||
+ interlockType == BranchOutputFilter::INTERLOCK_TYPE_STREAMING_RECORDING) {
+ // Split OBS's recording too when interlock is set to recording or streaming+recording
+ obs_frontend_recording_split_file();
+ }
+}
+
//--- OutputTableRow class ---//
-OutputTableRow::OutputTableRow(QObject *parent) : QObject(parent) {}
+OutputTableRow::OutputTableRow(
+ int row, BranchOutputFilter *_filter, size_t _streamingIndex, RowOutputType _outputType, size_t _groupIndex,
+ BranchOutputStatusDock *parent
+)
+ : QObject(parent),
+ filter(_filter),
+ streamingIndex(_streamingIndex),
+ outputType(_outputType),
+ groupIndex(_groupIndex)
+{
+ auto source = obs_filter_get_parent(filter->filterSource);
+
+ filterCell = new FilterCell(filter->name, filter->filterSource, parent);
+ parentCell = new ParentCell(obs_source_get_name(source), source, parent);
+ status = new StatusCell(QTStr("Status.Inactive"), parent);
+
+ OBSDataAutoRelease settings = obs_source_get_settings(filter->filterSource);
+
+ switch (outputType) {
+ case ROW_OUTPUT_STREAMING:
+ outputName = new QLabel(QTStr("Streaming%1").arg(streamingIndex + 1), parent);
+ status->setIcon(QPixmap(":/branch-output/images/streaming.svg").scaled(16, 16));
+ break;
+ case ROW_OUTPUT_RECORDING:
+ outputName = new RecordingOutputCell(QTStr("Recording"), filter->filterSource, parent);
+ status->setIcon(QPixmap(":/branch-output/images/recording.svg").scaled(16, 16));
+ isSplitRecordingEnabled = filter->isRecordingSplitEnabled(settings);
+ break;
+ default:
+ outputName = new QLabel(QTStr("None"), parent);
+ status->setIcon(QPixmap());
+ }
+
+ droppedFrames = new QLabel("", parent);
+ megabytesSent = new QLabel("", parent);
+ bitrate = new QLabel("", parent);
+
+ auto col = 0;
+ parent->outputTable->setRowCount(row + 1);
+ parent->outputTable->setCellWidget(row, col++, filterCell);
+ parent->outputTable->setCellWidget(row, col++, parentCell);
+ parent->outputTable->setCellWidget(row, col++, outputName);
+ parent->outputTable->setCellWidget(row, col++, status);
+ parent->outputTable->setCellWidget(row, col++, droppedFrames);
+ parent->outputTable->setCellWidget(row, col++, megabytesSent);
+ parent->outputTable->setCellWidget(row, col++, bitrate);
+
+ parent->outputTable->setRowHeight(row, 32);
-OutputTableRow::~OutputTableRow() {}
+ // Setup reset button
+ auto buttonsContainer = new QWidget(parent);
+ auto buttonsContainerLayout = new QHBoxLayout();
+ buttonsContainerLayout->setContentsMargins(0, 0, 0, 0);
+ buttonsContainer->setLayout(buttonsContainerLayout);
+
+ auto resetButton = new QPushButton(QTStr("Reset"), parent);
+ connect(resetButton, &QPushButton::clicked, this, [parent, row]() { parent->outputTableRows[row]->reset(); });
+ resetButton->setProperty("toolButton", true); // Until OBS 30
+ resetButton->setProperty("class", "btn-tool"); // Since OBS 31
+ resetButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+
+ buttonsContainerLayout->addWidget(resetButton);
+ parent->outputTable->setCellWidget(row, col, buttonsContainer);
+
+ connect(status, &StatusCell::splitRecordingButtonClicked, this, [this]() { splitRecording(); });
+}
+
+OutputTableRow::~OutputTableRow()
+{
+ disconnect(this);
+}
// Imitate UI/window-basic-stats.cpp
void OutputTableRow::update()
@@ -378,16 +445,20 @@ void OutputTableRow::update()
status->setText(QTStr("Status.Reconnecting"));
status->setTheme("error", "text-danger");
status->setIconShow(false);
+ status->setSplitRecordingButtonShow(false);
} else {
switch (outputType) {
case ROW_OUTPUT_STREAMING:
status->setText(QTStr("Status.Streaming"));
+ status->setSplitRecordingButtonShow(false);
break;
case ROW_OUTPUT_RECORDING:
status->setText(QTStr("Status.Recording"));
+ status->setSplitRecordingButtonShow(isSplitRecordingEnabled);
break;
default:
status->setText(QTStr("Status.Inactive"));
+ status->setSplitRecordingButtonShow(false);
}
status->setTheme("good", "text-success");
@@ -397,6 +468,7 @@ void OutputTableRow::update()
status->setText(QTStr("Status.Inactive"));
status->setTheme("", "");
status->setIconShow(false);
+ status->setSplitRecordingButtonShow(false);
droppedFrames->setText("");
megabytesSent->setText("");
bitrate->setText("");
@@ -498,6 +570,15 @@ void OutputTableRow::reset()
bitrate->setText(QString("0 kb/s"));
}
+void OutputTableRow::splitRecording()
+{
+ if (outputType != ROW_OUTPUT_RECORDING) {
+ return;
+ }
+
+ filter->splitRecording();
+}
+
//--- FilterCell class ---//
FilterCell::FilterCell(const QString &text, obs_source_t *source, QWidget *parent) : QWidget(parent)
@@ -511,7 +592,7 @@ FilterCell::FilterCell(const QString &text, obs_source_t *source, QWidget *paren
visibilityCheckbox->setChecked(obs_source_enabled(source));
visibilityCheckbox->setCursor(Qt::PointingHandCursor);
- connect(visibilityCheckbox, &QCheckBox::clicked, [source](bool visible) {
+ connect(visibilityCheckbox, &QCheckBox::clicked, this, [source](bool visible) {
obs_source_set_enabled(source, visible);
});
@@ -635,15 +716,26 @@ StatusCell::StatusCell(const QString &text, QWidget *parent) : QWidget(parent)
{
icon = new QLabel(this);
statusText = new QLabel(text, this);
+ splitRecordingButton = new QToolButton(this);
icon->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
icon->setVisible(false);
+ splitRecordingButton->setIcon(QIcon(":/branch-output/images/scissors.svg"));
+ splitRecordingButton->setVisible(false);
+
+ connect(splitRecordingButton, &QToolButton::clicked, this, [this]() { emit splitRecordingButtonClicked(); });
+
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(icon);
layout->addWidget(statusText);
+ layout->addWidget(splitRecordingButton);
+ layout->addSpacing(5);
setLayout(layout);
}
-StatusCell::~StatusCell() {}
+StatusCell::~StatusCell()
+{
+ disconnect(this);
+}
diff --git a/src/UI/output-status-dock.hpp b/src/UI/output-status-dock.hpp
index 80a086f..96a0e80 100644
--- a/src/UI/output-status-dock.hpp
+++ b/src/UI/output-status-dock.hpp
@@ -28,6 +28,7 @@ with this program. If not, see
#include
#include
#include
+#include
#include "../utils.hpp"
@@ -95,6 +96,10 @@ class StatusCell : public QWidget {
QLabel *icon;
QLabel *statusText;
+ QToolButton *splitRecordingButton;
+
+signals:
+ void splitRecordingButtonClicked();
public:
explicit StatusCell(const QString &text, QWidget *parent = (QWidget *)nullptr);
@@ -104,6 +109,8 @@ class StatusCell : public QWidget {
inline void setIcon(const QPixmap &pixmap) { icon->setPixmap(pixmap); };
inline void setIconShow(bool show) { icon->setVisible(show); };
inline void setTheme(const QString &id, const QString &classes) { setThemeID(statusText, id, classes); };
+ inline void setSplitRecordingButtonShow(bool show) { splitRecordingButton->setVisible(show); };
+ inline bool isSPlitRecordingButtonShow() const { return splitRecordingButton->isVisible(); };
};
enum RowOutputType {
@@ -115,23 +122,32 @@ enum RowOutputType {
class BranchOutputStatusDock : public QFrame {
Q_OBJECT
+ friend class OutputTableRow;
+
QTimer timer;
QTableWidget *outputTable = nullptr;
QList outputTableRows;
QPushButton *enableAllButton = nullptr;
QPushButton *disableAllButton = nullptr;
+ QPushButton *splitRecordingAllButton = nullptr;
QLabel *interlockLabel = nullptr;
QComboBox *interlockComboBox = nullptr;
OBSSignal sourceAddedSignal;
obs_hotkey_id enableAllHotkey;
obs_hotkey_id disableAllHotkey;
+ obs_hotkey_id splitRecordingAllHotkey;
void update();
+ void applySplitRecordingAllButtonVisible();
void saveSettings();
void loadSettings();
static void onEanbleAllHotkeyPressed(void *data, obs_hotkey_id id, obs_hotkey *hotkey, bool pressed);
static void onDisableAllHotkeyPressed(void *data, obs_hotkey_id id, obs_hotkey *hotkey, bool pressed);
+ static void onSplitRecordingAllHotkeyPressed(void *data, obs_hotkey_id id, obs_hotkey *hotkey, bool pressed);
+
+private slots:
+ void onSplitRecordingAllButtonClicked();
protected:
virtual void showEvent(QShowEvent *event) override;
@@ -146,6 +162,7 @@ public slots:
void addFilter(BranchOutputFilter *filter);
void removeFilter(BranchOutputFilter *filter);
void setEabnleAll(bool enabled);
+ void splitRecordingAll();
inline int getInterlockType() const { return interlockComboBox->currentData().toInt(); };
};
@@ -160,6 +177,7 @@ class OutputTableRow : public QObject {
ParentCell *parentCell;
StatusCell *status;
RowOutputType outputType;
+ bool isSplitRecordingEnabled = false;
size_t streamingIndex;
size_t groupIndex;
QLabel *droppedFrames;
@@ -175,10 +193,14 @@ class OutputTableRow : public QObject {
void update();
void reset();
+ void splitRecording();
long double kbps = 0.0l;
public:
- explicit OutputTableRow(QObject *parent = (QObject *)nullptr);
+ explicit OutputTableRow(
+ int row, BranchOutputFilter *filter, size_t streamingIndex, RowOutputType outputType, size_t groupIndex,
+ BranchOutputStatusDock *parent = nullptr
+ );
~OutputTableRow();
};
diff --git a/src/UI/resources.qrc b/src/UI/resources.qrc
index 5290700..4d09653 100644
--- a/src/UI/resources.qrc
+++ b/src/UI/resources.qrc
@@ -2,5 +2,6 @@
images/recording.svg
images/streaming.svg
+ images/scissors.svg
diff --git a/src/plugin-main.cpp b/src/plugin-main.cpp
index f4f3dec..daa3343 100644
--- a/src/plugin-main.cpp
+++ b/src/plugin-main.cpp
@@ -59,7 +59,8 @@ BranchOutputFilter::BranchOutputFilter(obs_data_t *settings, obs_source_t *sourc
view(nullptr),
width(0),
height(0),
- hotkeyPairId(OBS_INVALID_HOTKEY_PAIR_ID)
+ toggleHotkeyPairId(OBS_INVALID_HOTKEY_PAIR_ID),
+ splitRecordingHotkeyId(OBS_INVALID_HOTKEY_ID)
{
// DO NOT use obs_filter_get_parent() in this function (It'll return nullptr)
obs_log(LOG_DEBUG, "%s: BranchOutputFilter creating", qUtf8Printable(name));
@@ -205,9 +206,12 @@ void BranchOutputFilter::removeCallback()
QMetaObject::invokeMethod(statusDock, "removeFilter", Qt::QueuedConnection, Q_ARG(BranchOutputFilter *, this));
}
- if (hotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) {
- // Unregsiter hotkeys
- obs_hotkey_pair_unregister(hotkeyPairId);
+ // Unregsiter hotkeys
+ if (toggleHotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) {
+ obs_hotkey_pair_unregister(toggleHotkeyPairId);
+ }
+ if (splitRecordingHotkeyId != OBS_INVALID_HOTKEY_ID) {
+ obs_hotkey_unregister(splitRecordingHotkeyId);
}
obs_log(LOG_INFO, "%s: Filter removed", qUtf8Printable(name));
@@ -1035,6 +1039,11 @@ bool BranchOutputFilter::isRecordingEnabled(obs_data_t *settings)
return obs_data_get_bool(settings, "stream_recording");
}
+bool BranchOutputFilter::isRecordingSplitEnabled(obs_data_t *settings)
+{
+ return !!strlen(obs_data_get_string(settings, "split_file"));
+}
+
// Controlling output status here.
// Start / Stop should only heppen in this function as possible because rapid manipulation caused crash easily.
// NOTE: Becareful this function is called so offen.
@@ -1179,6 +1188,26 @@ void BranchOutputFilter::onIntervalTimerTimeout()
}
}
+bool BranchOutputFilter::splitRecording()
+{
+ pthread_mutex_lock(&outputMutex);
+ {
+ OBSMutexAutoUnlock locked(&outputMutex);
+
+ if (!recordingActive || !recordingOutput) {
+ return false;
+ }
+
+ // Immitate obs_frontend_recording_split_file()
+ proc_handler_t *ph = obs_output_get_proc_handler(recordingOutput);
+ uint8_t stack[128];
+ calldata cd;
+ calldata_init_fixed(&cd, stack, sizeof(stack));
+ proc_handler_call(ph, "split_file", &cd);
+ return calldata_bool(&cd, "split_file_enabled");
+ }
+}
+
bool BranchOutputFilter::onEnableFilterHotkeyPressed(void *data, obs_hotkey_pair_id, obs_hotkey *, bool pressed)
{
if (!pressed) {
@@ -1209,21 +1238,42 @@ bool BranchOutputFilter::onDisableFilterHotkeyPressed(void *data, obs_hotkey_pai
return true;
}
+void BranchOutputFilter::onSplitRecordingFileHotkeyPressed(void *data, obs_hotkey_id, obs_hotkey *, bool pressed)
+{
+ if (!pressed) {
+ return;
+ }
+
+ BranchOutputFilter *filter = static_cast(data);
+ filter->splitRecording();
+}
+
void BranchOutputFilter::registerHotkey()
{
- if (hotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) {
+ if (toggleHotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) {
// Unregsiter previous
- obs_hotkey_pair_unregister(hotkeyPairId);
+ obs_hotkey_pair_unregister(toggleHotkeyPairId);
}
- auto name0 = QString("EnableFilter.%1").arg(obs_source_get_uuid(filterSource));
- auto description0 = QString(obs_module_text("EnableHotkey")).arg(name);
- auto name1 = QString("DisableFilter.%1").arg(obs_source_get_uuid(filterSource));
- auto description1 = QString(obs_module_text("DisableHotkey")).arg(name);
+ // Register enable/disable hotkeys
+ auto toggleName0 = QString("EnableFilter.%1").arg(obs_source_get_uuid(filterSource));
+ auto toggleDescription0 = QString(obs_module_text("EnableHotkey")).arg(name);
+ auto toggleName1 = QString("DisableFilter.%1").arg(obs_source_get_uuid(filterSource));
+ auto toggleDescription1 = QString(obs_module_text("DisableHotkey")).arg(name);
+
+ toggleHotkeyPairId = obs_hotkey_pair_register_source(
+ obs_filter_get_parent(filterSource), qUtf8Printable(toggleName0), qUtf8Printable(toggleDescription0),
+ qUtf8Printable(toggleName1), qUtf8Printable(toggleDescription1), onEnableFilterHotkeyPressed,
+ onDisableFilterHotkeyPressed, this, this
+ );
+
+ // Register split recording hotkey
+ auto splitName = QString("SplitRecordingFile.%1").arg(obs_source_get_uuid(filterSource));
+ auto splitDescription = QString(obs_module_text("SplitRecordingFileHotkey")).arg(name);
- hotkeyPairId = obs_hotkey_pair_register_source(
- obs_filter_get_parent(filterSource), qUtf8Printable(name0), qUtf8Printable(description0), qUtf8Printable(name1),
- qUtf8Printable(description1), onEnableFilterHotkeyPressed, onDisableFilterHotkeyPressed, this, this
+ splitRecordingHotkeyId = obs_hotkey_register_source(
+ obs_filter_get_parent(filterSource), qUtf8Printable(splitName), qUtf8Printable(splitDescription),
+ onSplitRecordingFileHotkeyPressed, this
);
}
diff --git a/src/plugin-main.hpp b/src/plugin-main.hpp
index 0298e97..b68e27f 100644
--- a/src/plugin-main.hpp
+++ b/src/plugin-main.hpp
@@ -93,7 +93,8 @@ class BranchOutputFilter : public QObject {
BranchOutputStreamingContext streamings[MAX_SERVICES];
// Hotkey context
- obs_hotkey_pair_id hotkeyPairId;
+ obs_hotkey_pair_id toggleHotkeyPairId;
+ obs_hotkey_id splitRecordingHotkeyId;
OBSSignal filterRenamedSignal;
void startOutput(obs_data_t *settings);
@@ -115,7 +116,9 @@ class BranchOutputFilter : public QObject {
int countActiveStreamings();
bool isStreamingEnabled(obs_data_t *settings, size_t index = 0);
bool isRecordingEnabled(obs_data_t *settings);
+ bool isRecordingSplitEnabled(obs_data_t *settings);
void registerHotkey();
+ bool splitRecording();
// Implemented in plugin-ui.cpp
void addApplyButton(obs_properties_t *props, const char *propName = "apply");
@@ -131,6 +134,7 @@ class BranchOutputFilter : public QObject {
// Callbacks from obs core
static bool onEnableFilterHotkeyPressed(void *data, obs_hotkey_pair_id id, obs_hotkey *hotkey, bool pressed);
static bool onDisableFilterHotkeyPressed(void *data, obs_hotkey_pair_id id, obs_hotkey *hotkey, bool pressed);
+ static void onSplitRecordingFileHotkeyPressed(void *data, obs_hotkey_id id, obs_hotkey *hotkey, bool pressed);
void addCallback(obs_source_t *source);
void updateCallback(obs_data_t *settings);
diff --git a/src/plugin-ui.cpp b/src/plugin-ui.cpp
index fe70e02..5bc2dd6 100644
--- a/src/plugin-ui.cpp
+++ b/src/plugin-ui.cpp
@@ -144,8 +144,10 @@ void BranchOutputFilter::getDefaults(obs_data_t *defaults)
if (recSplitFile && strcmp(recSplitFileType, "Manual")) {
if (!strcmp(recSplitFileType, "Size")) {
splitFileValue = "by_size";
- } else {
+ } else if (!strcmp(recSplitFileType, "Time")) {
splitFileValue = "by_time";
+ } else {
+ splitFileValue = "manual";
}
}
obs_data_set_default_string(defaults, "split_file", splitFileValue);
@@ -403,6 +405,7 @@ void BranchOutputFilter::addStreamGroup(obs_properties_t *props)
obs_property_list_add_string(splitFileList, obs_module_text("SplitFile.NoSplit"), "");
obs_property_list_add_string(splitFileList, obs_module_text("SplitFile.ByTime"), "by_time");
obs_property_list_add_string(splitFileList, obs_module_text("SplitFile.BySize"), "by_size");
+ obs_property_list_add_string(splitFileList, obs_module_text("SplitFile.Manual"), "manual");
obs_property_set_modified_callback2(splitFileList, streamRecordingChangeHandler, nullptr);