From 06c49c1e0581db074650badf2f8addfea49b1b51 Mon Sep 17 00:00:00 2001 From: Shinichi Hanayama Date: Sun, 29 Jun 2025 13:49:12 +0900 Subject: [PATCH 1/6] - Bump --- buildspec.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildspec.json b/buildspec.json index 512e725..a3381d3 100644 --- a/buildspec.json +++ b/buildspec.json @@ -38,7 +38,7 @@ }, "name": "osi-branch-output", "displayName": "Branch Output Plugin", - "version": "1.0.4", + "version": "1.0.5", "author": "OPENSPHERE Inc.", "website": "https://opensphere.co.jp/", "email": "info@opensphere.co.jp", From cf0d44f5ee33398a019fa01cbac5c44f87c7e55e Mon Sep 17 00:00:00 2001 From: Shinichi Hanayama Date: Mon, 28 Jul 2025 14:22:19 +0900 Subject: [PATCH 2/6] - Fix macos CI build error. (cherry picked from commit 72ca4cfbbd1c23e5915ddc3af78f90eca48bc56e) --- .github/scripts/.Brewfile | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/scripts/.Brewfile b/.github/scripts/.Brewfile index 8368e3d..abf230c 100644 --- a/.github/scripts/.Brewfile +++ b/.github/scripts/.Brewfile @@ -1,6 +1,5 @@ brew "ccache" brew "coreutils" -brew "cmake" brew "git" brew "jq" brew "xcbeautify" From 11f6935a650b5433b3e9941738a63e88611ab346 Mon Sep 17 00:00:00 2001 From: Shinichi Hanayama Date: Mon, 28 Jul 2025 16:48:43 +0900 Subject: [PATCH 3/6] - Add hotkey of splitting recording file. --- data/locale/ca-ES.ini | 1 + data/locale/de-DE.ini | 1 + data/locale/en-US.ini | 1 + data/locale/fr-FR.ini | 1 + data/locale/ja-JP.ini | 1 + data/locale/ko-KR.ini | 1 + data/locale/ro-RO.ini | 1 + data/locale/ru-RU.ini | 2 ++ data/locale/uk-UA.ini | 1 + data/locale/zh-CN.ini | 1 + src/plugin-main.cpp | 69 +++++++++++++++++++++++++++++++++++-------- src/plugin-main.hpp | 5 +++- 12 files changed, 72 insertions(+), 13 deletions(-) diff --git a/data/locale/ca-ES.ini b/data/locale/ca-ES.ini index 61fbe83..7c12d8e 100644 --- a/data/locale/ca-ES.ini +++ b/data/locale/ca-ES.ini @@ -123,3 +123,4 @@ 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" diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini index 3cfb5d2..345caea 100644 --- a/data/locale/de-DE.ini +++ b/data/locale/de-DE.ini @@ -123,3 +123,4 @@ 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" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 88c01ed..8757375 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -123,3 +123,4 @@ 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" diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini index d88bf6b..ac682e7 100644 --- a/data/locale/fr-FR.ini +++ b/data/locale/fr-FR.ini @@ -123,3 +123,4 @@ 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" diff --git a/data/locale/ja-JP.ini b/data/locale/ja-JP.ini index 4f32194..b623566 100644 --- a/data/locale/ja-JP.ini +++ b/data/locale/ja-JP.ini @@ -123,3 +123,4 @@ NoSpaceFileName="スペースなしのファイル名を生成する" KeepOutputBaseResolution="ソースの解像度が変更されてもアウトプットを再起動しない" KeepOutputBaseResolutionNote="有効にした場合、ソースの解像度が変わると、映像が見切れたり余白が出来たりする場合があります" AudioSourceNote="マスター音声: OBSのミックス出力(既知の6トラックがあります)
フィルター音声: Branch Outputフィルターに渡されるフィルターパイプラインの音声。
ソース音声: パイプライン内のすべてのフィルターを通過した後のソースのバンドルされた音声。" +SplitRecordingFileHotkey="録画ファイルの分割" diff --git a/data/locale/ko-KR.ini b/data/locale/ko-KR.ini index 7548a3f..2f79cf4 100644 --- a/data/locale/ko-KR.ini +++ b/data/locale/ko-KR.ini @@ -123,3 +123,4 @@ NoSpaceFileName="ス공백 없이 파일 이름 짓기" KeepOutputBaseResolution="소스 해상도가 변경되어도 출력을 다시 시작하지 않음" KeepOutputBaseResolutionNote="활성화된 경우, 소스 해상도가 변경되면 비디오 이미지가 잘리거나 빈 공간이 생길 수 있습니다." AudioSourceNote="마스터 오디오: OBS의 믹스 출력 (알려진 대로 6개의 트랙이 있음)
필터 오디오: Branch Output 필터로 전달되는 필터 파이프라인의 오디오.
소스 오디오: 파이프라인의 모든 필터를 통과한 후의 소스의 번들 오디오." +SplitRecordingFileHotkey="녹화 파일 분할" diff --git a/data/locale/ro-RO.ini b/data/locale/ro-RO.ini index 7d41780..e2de3a9 100644 --- a/data/locale/ro-RO.ini +++ b/data/locale/ro-RO.ini @@ -121,3 +121,4 @@ 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" diff --git a/data/locale/ru-RU.ini b/data/locale/ru-RU.ini index 9094229..0a0e591 100644 --- a/data/locale/ru-RU.ini +++ b/data/locale/ru-RU.ini @@ -123,3 +123,5 @@ NoSpaceFileName="Генерировать имя файла без пробел KeepOutputBaseResolution="Не перезапускать вывод даже при изменении разрешения источника" KeepOutputBaseResolutionNote="При включении этой функции изображение видео может быть обрезано или иметь пустые пространства при изменении разрешения источника." AudioSourceNote="Мастер-аудио: Микшированный выход OBS (известно, что имеет 6 дорожек)
Аудио фильтра: Аудио из конвейера фильтров, которое передается в фильтр Branch Output.
Аудио источника: Объединенное аудио источника после прохождения через все фильтры в конвейере." +SplitRecordingFileHotkey="Разделить файл записи" + diff --git a/data/locale/uk-UA.ini b/data/locale/uk-UA.ini index dbf0f49..8c99d4d 100644 --- a/data/locale/uk-UA.ini +++ b/data/locale/uk-UA.ini @@ -123,3 +123,4 @@ NoSpaceFileName="Генерувати назву файлу без пробіл KeepOutputBaseResolution="Не перезапускати вивід, навіть якщо роздільна здатність джерела змінилася" KeepOutputBaseResolutionNote="Якщо увімкнено, зображення відео може бути обрізане або мати пусті простори при зміні роздільної здатності джерела." AudioSourceNote="Майстер-аудіо: Змішаний вихід OBS (як відомо, має 6 доріжок)
Аудіо фільтра: Аудіо з конвеєра фільтрів, яке передається у фільтр Branch Output.
Аудіо джерела: Об'єднане аудіо джерела після проходження через усі фільтри в конвеєрі." +SplitRecordingFileHotkey="Розділити файл Запису" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index 906f5f7..a503459 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -123,3 +123,4 @@ NoSpaceFileName="生成没有空格的文件名" KeepOutputBaseResolution="即使源分辨率改变也不重启输出" KeepOutputBaseResolutionNote="如果启用,当源分辨率改变时,视频图像可能会被裁剪或出现空白区域。" AudioSourceNote="主音频: OBS的混合输出(众所周知有6个轨道)
滤镜音频: 传递给分支输出滤镜的滤镜管道音频。
源音频: 通过管道中所有滤镜后的源捆绑音频。" +SplitRecordingFileHotkey="分割录制文件" diff --git a/src/plugin-main.cpp b/src/plugin-main.cpp index 345f873..f738513 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,9 @@ void BranchOutputFilter::removeCallback() QMetaObject::invokeMethod(statusDock, "removeFilter", Qt::QueuedConnection, Q_ARG(BranchOutputFilter *, this)); } - if (hotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) { + if (toggleHotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) { // Unregsiter hotkeys - obs_hotkey_pair_unregister(hotkeyPairId); + obs_hotkey_pair_unregister(toggleHotkeyPairId); } obs_log(LOG_INFO, "%s: Filter removed", qUtf8Printable(name)); @@ -1160,6 +1161,30 @@ void BranchOutputFilter::onIntervalTimerTimeout() } } +bool BranchOutputFilter::splitRecording() +{ + if (!recordingActive || !recordingOutput) { + return false; + } + + bool result = false; + + pthread_mutex_lock(&outputMutex); + { + OBSMutexAutoUnlock locked(&outputMutex); + + // 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); + result = calldata_bool(&cd, "split_file_enabled"); + + return result; + } +} + bool BranchOutputFilter::onEnableFilterHotkeyPressed(void *data, obs_hotkey_pair_id, obs_hotkey *, bool pressed) { if (!pressed) { @@ -1190,21 +1215,41 @@ 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..ba827cc 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); @@ -116,6 +117,7 @@ class BranchOutputFilter : public QObject { bool isStreamingEnabled(obs_data_t *settings, size_t index = 0); bool isRecordingEnabled(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 +133,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); From 383926d6c08b4c718f791fdb05447cc7b2e1e885 Mon Sep 17 00:00:00 2001 From: Shinichi Hanayama Date: Mon, 28 Jul 2025 16:50:27 +0900 Subject: [PATCH 4/6] - Fix CI errors. --- src/plugin-main.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugin-main.cpp b/src/plugin-main.cpp index f738513..5c249fb 100644 --- a/src/plugin-main.cpp +++ b/src/plugin-main.cpp @@ -1239,13 +1239,14 @@ void BranchOutputFilter::registerHotkey() 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 + 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); + auto splitDescription = QString(obs_module_text("SplitRecordingFileHotkey")).arg(name); splitRecordingHotkeyId = obs_hotkey_register_source( obs_filter_get_parent(filterSource), qUtf8Printable(splitName), qUtf8Printable(splitDescription), From 16213ba533e11766be6ffbc3a80bb843843c5707 Mon Sep 17 00:00:00 2001 From: Shinichi Hanayama Date: Mon, 28 Jul 2025 20:03:01 +0900 Subject: [PATCH 5/6] - Add "only split manually mode" to splitting recording file feature. --- data/locale/ca-ES.ini | 1 + data/locale/de-DE.ini | 1 + data/locale/en-US.ini | 1 + data/locale/fr-FR.ini | 1 + data/locale/ja-JP.ini | 1 + data/locale/ko-KR.ini | 1 + data/locale/ro-RO.ini | 1 + data/locale/ru-RU.ini | 1 + data/locale/uk-UA.ini | 1 + data/locale/zh-CN.ini | 1 + src/plugin-ui.cpp | 5 ++++- 11 files changed, 14 insertions(+), 1 deletion(-) diff --git a/data/locale/ca-ES.ini b/data/locale/ca-ES.ini index 7c12d8e..4fdbfe5 100644 --- a/data/locale/ca-ES.ini +++ b/data/locale/ca-ES.ini @@ -42,6 +42,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" diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini index 345caea..06bf342 100644 --- a/data/locale/de-DE.ini +++ b/data/locale/de-DE.ini @@ -42,6 +42,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" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 8757375..3b38b62 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -42,6 +42,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" diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini index ac682e7..45055fd 100644 --- a/data/locale/fr-FR.ini +++ b/data/locale/fr-FR.ini @@ -42,6 +42,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" diff --git a/data/locale/ja-JP.ini b/data/locale/ja-JP.ini index b623566..375406f 100644 --- a/data/locale/ja-JP.ini +++ b/data/locale/ja-JP.ini @@ -42,6 +42,7 @@ SplitFile="自動ファイル分割" SplitFile.NoSplit="分割無し" SplitFile.ByTime="時間で分割" SplitFile.BySize="サイズで分割" +SplitFile.Manual="手動で分割" SplitFile.Time="分割時間 (分)" SplitFile.Size="分割サイズ (MB)" MasterTrack="マスター音声" diff --git a/data/locale/ko-KR.ini b/data/locale/ko-KR.ini index 2f79cf4..00abf1c 100644 --- a/data/locale/ko-KR.ini +++ b/data/locale/ko-KR.ini @@ -42,6 +42,7 @@ SplitFile="자동 파일 분할" SplitFile.NoSplit="분할 없음" SplitFile.ByTime="시간별로 분할" SplitFile.BySize="크기별로 분할" +SplitFile.Manual="수동으로만 분할" SplitFile.Time="분할 시간 (분)" SplitFile.Size="분할 크기 (MB)" MasterTrack="마스터 오디오" diff --git a/data/locale/ro-RO.ini b/data/locale/ro-RO.ini index e2de3a9..47571ae 100644 --- a/data/locale/ro-RO.ini +++ b/data/locale/ro-RO.ini @@ -42,6 +42,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" diff --git a/data/locale/ru-RU.ini b/data/locale/ru-RU.ini index 0a0e591..9926b78 100644 --- a/data/locale/ru-RU.ini +++ b/data/locale/ru-RU.ini @@ -42,6 +42,7 @@ SplitFile="Автоматическое разделение файлов" SplitFile.NoSplit="Нет разделения" SplitFile.ByTime="Разделить по времени" SplitFile.BySize="Разделить по размеру" +SplitFile.Manual="Вручную" SplitFile.Time="Время дивизиона (минуты)" SplitFile.Size="Размер дивизиона (MB)" MasterTrack="Мастер-аудио" diff --git a/data/locale/uk-UA.ini b/data/locale/uk-UA.ini index 8c99d4d..5ca38ed 100644 --- a/data/locale/uk-UA.ini +++ b/data/locale/uk-UA.ini @@ -42,6 +42,7 @@ SplitFile="Автоматичне розділення файлів" SplitFile.NoSplit="Без розколу" SplitFile.ByTime="Поділ за часом" SplitFile.BySize="Поділ за розміром" +SplitFile.Manual="Розділяти лише вручну" SplitFile.Time="Час ділення (хвилини)" SplitFile.Size="Розмір поділу (MB)" MasterTrack="Майстер-аудіо" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index a503459..75ef8ac 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -42,6 +42,7 @@ SplitFile="自动分割文件" SplitFile.NoSplit="无分裂" SplitFile.ByTime="按时间分割" SplitFile.BySize="按大小分割" +SplitFile.Manual="仅手动分割" SplitFile.Time="分割时间 (分钟)" SplitFile.Size="分割大小 (MB)" MasterTrack="主音频" diff --git a/src/plugin-ui.cpp b/src/plugin-ui.cpp index b262368..254b68f 100644 --- a/src/plugin-ui.cpp +++ b/src/plugin-ui.cpp @@ -141,8 +141,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); @@ -396,6 +398,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); From 6e499f9bd64d606e513cf5ba50c0ca32f1e8b821 Mon Sep 17 00:00:00 2001 From: Shinichi Hanayama Date: Sun, 3 Aug 2025 00:01:49 +0900 Subject: [PATCH 6/6] - Add split recording buttons to status dock. --- data/locale/ca-ES.ini | 2 + data/locale/de-DE.ini | 3 + data/locale/en-US.ini | 3 + data/locale/fr-FR.ini | 2 + data/locale/ja-JP.ini | 7 +- data/locale/ko-KR.ini | 2 + data/locale/ro-RO.ini | 2 + data/locale/ru-RU.ini | 3 +- data/locale/uk-UA.ini | 2 + data/locale/zh-CN.ini | 2 + src/UI/images/scissors.svg | 1 + src/UI/output-status-dock.cpp | 218 ++++++++++++++++++++++++---------- src/UI/output-status-dock.hpp | 24 +++- src/UI/resources.qrc | 1 + src/plugin-main.cpp | 24 ++-- src/plugin-main.hpp | 1 + 16 files changed, 220 insertions(+), 77 deletions(-) create mode 100644 src/UI/images/scissors.svg diff --git a/data/locale/ca-ES.ini b/data/locale/ca-ES.ini index 4fdbfe5..2eb4b19 100644 --- a/data/locale/ca-ES.ini +++ b/data/locale/ca-ES.ini @@ -125,3 +125,5 @@ KeepOutputBaseResolution="No reinicieu la sortida encara que es canviï la resol 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" diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini index 06bf342..7be0a74 100644 --- a/data/locale/de-DE.ini +++ b/data/locale/de-DE.ini @@ -125,3 +125,6 @@ KeepOutputBaseResolution="Ausgabe nicht neu starten, auch wenn sich die Quellauf 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" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 3b38b62..525b30a 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -125,3 +125,6 @@ KeepOutputBaseResolution="Don't reboot output even if source resolution is chang 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" diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini index 45055fd..7e52fb2 100644 --- a/data/locale/fr-FR.ini +++ b/data/locale/fr-FR.ini @@ -125,3 +125,5 @@ KeepOutputBaseResolution="Ne pas redémarrer la sortie même si la résolution d 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" diff --git a/data/locale/ja-JP.ini b/data/locale/ja-JP.ini index 375406f..60f5735 100644 --- a/data/locale/ja-JP.ini +++ b/data/locale/ja-JP.ini @@ -111,8 +111,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,3 +125,6 @@ KeepOutputBaseResolution="ソースの解像度が変更されてもアウトプ KeepOutputBaseResolutionNote="有効にした場合、ソースの解像度が変わると、映像が見切れたり余白が出来たりする場合があります" AudioSourceNote="マスター音声: OBSのミックス出力(既知の6トラックがあります)
フィルター音声: Branch Outputフィルターに渡されるフィルターパイプラインの音声。
ソース音声: パイプライン内のすべてのフィルターを通過した後のソースのバンドルされた音声。" SplitRecordingFileHotkey="録画ファイルの分割" +SplitRecordingAllHotkey="全ての Branch Output 録画ファイルを分割" +SplitAllRecordings="全録画を分割" +SplitRecording="録画を分割" diff --git a/data/locale/ko-KR.ini b/data/locale/ko-KR.ini index 00abf1c..b763dd8 100644 --- a/data/locale/ko-KR.ini +++ b/data/locale/ko-KR.ini @@ -125,3 +125,5 @@ KeepOutputBaseResolution="소스 해상도가 변경되어도 출력을 다시 KeepOutputBaseResolutionNote="활성화된 경우, 소스 해상도가 변경되면 비디오 이미지가 잘리거나 빈 공간이 생길 수 있습니다." AudioSourceNote="마스터 오디오: OBS의 믹스 출력 (알려진 대로 6개의 트랙이 있음)
필터 오디오: Branch Output 필터로 전달되는 필터 파이프라인의 오디오.
소스 오디오: 파이프라인의 모든 필터를 통과한 후의 소스의 번들 오디오." SplitRecordingFileHotkey="녹화 파일 분할" +SplitRecordingAllHotkey="모든 분기 출력 녹화 파일 분할" +SplitAllRecordings="모든 녹화 분할" diff --git a/data/locale/ro-RO.ini b/data/locale/ro-RO.ini index 47571ae..c85ee86 100644 --- a/data/locale/ro-RO.ini +++ b/data/locale/ro-RO.ini @@ -123,3 +123,5 @@ KeepOutputBaseResolution="Nu reporniți ieșirea chiar dacă rezoluția sursei e 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" diff --git a/data/locale/ru-RU.ini b/data/locale/ru-RU.ini index 9926b78..8bec4cf 100644 --- a/data/locale/ru-RU.ini +++ b/data/locale/ru-RU.ini @@ -125,4 +125,5 @@ KeepOutputBaseResolution="Не перезапускать вывод даже п KeepOutputBaseResolutionNote="При включении этой функции изображение видео может быть обрезано или иметь пустые пространства при изменении разрешения источника." AudioSourceNote="Мастер-аудио: Микшированный выход OBS (известно, что имеет 6 дорожек)
Аудио фильтра: Аудио из конвейера фильтров, которое передается в фильтр Branch Output.
Аудио источника: Объединенное аудио источника после прохождения через все фильтры в конвейере." SplitRecordingFileHotkey="Разделить файл записи" - +SplitRecordingAllHotkey="Разделить все файлы записи Выхода Ветви" +SplitAllRecordings="Разделить все записи" diff --git a/data/locale/uk-UA.ini b/data/locale/uk-UA.ini index 5ca38ed..d577a70 100644 --- a/data/locale/uk-UA.ini +++ b/data/locale/uk-UA.ini @@ -125,3 +125,5 @@ KeepOutputBaseResolution="Не перезапускати вивід, навіт KeepOutputBaseResolutionNote="Якщо увімкнено, зображення відео може бути обрізане або мати пусті простори при зміні роздільної здатності джерела." AudioSourceNote="Майстер-аудіо: Змішаний вихід OBS (як відомо, має 6 доріжок)
Аудіо фільтра: Аудіо з конвеєра фільтрів, яке передається у фільтр Branch Output.
Аудіо джерела: Об'єднане аудіо джерела після проходження через усі фільтри в конвеєрі." SplitRecordingFileHotkey="Розділити файл Запису" +SplitRecordingAllHotkey="Розділити всі файли запису Виходу Гілки" +SplitAllRecordings="Розділити всі записи" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index 75ef8ac..3f07dd4 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -125,3 +125,5 @@ KeepOutputBaseResolution="即使源分辨率改变也不重启输出" KeepOutputBaseResolutionNote="如果启用,当源分辨率改变时,视频图像可能会被裁剪或出现空白区域。" AudioSourceNote="主音频: OBS的混合输出(众所周知有6个轨道)
滤镜音频: 传递给分支输出滤镜的滤镜管道音频。
源音频: 通过管道中所有滤镜后的源捆绑音频。" SplitRecordingFileHotkey="分割录制文件" +SplitRecordingAllHotkey="分割所有分支输出录制文件" +SplitAllRecordings="分割所有录制" 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 5c249fb..193ac2d 100644 --- a/src/plugin-main.cpp +++ b/src/plugin-main.cpp @@ -206,10 +206,13 @@ void BranchOutputFilter::removeCallback() QMetaObject::invokeMethod(statusDock, "removeFilter", Qt::QueuedConnection, Q_ARG(BranchOutputFilter *, this)); } + // Unregsiter hotkeys if (toggleHotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) { - // Unregsiter hotkeys obs_hotkey_pair_unregister(toggleHotkeyPairId); } + if (splitRecordingHotkeyId != OBS_INVALID_HOTKEY_ID) { + obs_hotkey_unregister(splitRecordingHotkeyId); + } obs_log(LOG_INFO, "%s: Filter removed", qUtf8Printable(name)); } @@ -1017,6 +1020,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. @@ -1163,25 +1171,21 @@ void BranchOutputFilter::onIntervalTimerTimeout() bool BranchOutputFilter::splitRecording() { - if (!recordingActive || !recordingOutput) { - return false; - } - - bool result = false; - 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); - result = calldata_bool(&cd, "split_file_enabled"); - - return result; + return calldata_bool(&cd, "split_file_enabled"); } } diff --git a/src/plugin-main.hpp b/src/plugin-main.hpp index ba827cc..b68e27f 100644 --- a/src/plugin-main.hpp +++ b/src/plugin-main.hpp @@ -116,6 +116,7 @@ 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();