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