123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- /******************************************************************************
- Copyright (C) 2023 by Lain Bailey <[email protected]>
- Zachary Lund <[email protected]>
- Philippe Groarke <[email protected]>
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
- #include "OBSBasic.hpp"
- #include <components/UIValidation.hpp>
- #include <dialogs/OBSRemux.hpp>
- #include <qt-wrappers.hpp>
- #include <QDesktopServices>
- #include <QFileInfo>
- void OBSBasic::on_actionShow_Recordings_triggered()
- {
- const char *mode = config_get_string(activeConfiguration, "Output", "Mode");
- const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType");
- const char *adv_path = strcmp(type, "Standard")
- ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath")
- : config_get_string(activeConfiguration, "AdvOut", "RecFilePath");
- const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath")
- : adv_path;
- QDesktopServices::openUrl(QUrl::fromLocalFile(path));
- }
- #define RECORDING_START "==== Recording Start ==============================================="
- #define RECORDING_STOP "==== Recording Stop ================================================"
- extern volatile bool replaybuf_active;
- void OBSBasic::AutoRemux(QString input, bool no_show)
- {
- auto config = Config();
- bool autoRemux = config_get_bool(config, "Video", "AutoRemux");
- if (!autoRemux)
- return;
- bool isSimpleMode = false;
- const char *mode = config_get_string(config, "Output", "Mode");
- if (!mode) {
- isSimpleMode = true;
- } else {
- isSimpleMode = strcmp(mode, "Simple") == 0;
- }
- if (!isSimpleMode) {
- const char *recType = config_get_string(config, "AdvOut", "RecType");
- bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0;
- if (ffmpegOutput)
- return;
- }
- if (input.isEmpty())
- return;
- QFileInfo fi(input);
- QString suffix = fi.suffix();
- /* do not remux if lossless */
- if (suffix.compare("avi", Qt::CaseInsensitive) == 0) {
- return;
- }
- QString path = fi.path();
- QString output = input;
- output.resize(output.size() - suffix.size());
- const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput);
- const char *vCodecName = obs_encoder_get_codec(videoEncoder);
- const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2");
- /* Retain original container for fMP4/fMOV */
- if (strncmp(format, "fragmented", 10) == 0) {
- output += "remuxed." + suffix;
- } else if (strcmp(vCodecName, "prores") == 0) {
- output += "mov";
- } else {
- output += "mp4";
- }
- OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true);
- if (!no_show)
- remux->show();
- remux->AutoRemux(input, output);
- }
- void OBSBasic::StartRecording()
- {
- if (outputHandler->RecordingActive())
- return;
- if (disableOutputsRef)
- return;
- if (!OutputPathValid()) {
- OutputPathInvalidMessage();
- return;
- }
- if (!IsFFmpegOutputToURL() && LowDiskSpace()) {
- DiskSpaceMessage();
- return;
- }
- OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING);
- SaveProject();
- outputHandler->StartRecording();
- }
- void OBSBasic::RecordStopping()
- {
- emit RecordingStopping();
- if (sysTrayRecord)
- sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording"));
- recordingStopping = true;
- OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING);
- }
- void OBSBasic::StopRecording()
- {
- SaveProject();
- if (outputHandler->RecordingActive())
- outputHandler->StopRecording(recordingStopping);
- OnDeactivate();
- }
- void OBSBasic::RecordingStart()
- {
- ui->statusbar->RecordingStarted(outputHandler->fileOutput);
- emit RecordingStarted(isRecordingPausable);
- if (sysTrayRecord)
- sysTrayRecord->setText(QTStr("Basic.Main.StopRecording"));
- recordingStopping = false;
- OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED);
- if (!diskFullTimer->isActive())
- diskFullTimer->start(1000);
- OnActivate();
- blog(LOG_INFO, RECORDING_START);
- }
- void OBSBasic::RecordingStop(int code, QString last_error)
- {
- ui->statusbar->RecordingStopped();
- emit RecordingStopped();
- if (sysTrayRecord)
- sysTrayRecord->setText(QTStr("Basic.Main.StartRecording"));
- blog(LOG_INFO, RECORDING_STOP);
- if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) {
- OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported"));
- } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) {
- QString msg = last_error.isEmpty()
- ? QTStr("Output.RecordError.EncodeErrorMsg")
- : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error);
- OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg);
- } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) {
- OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg"));
- } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
- const char *errorDescription;
- DStr errorMessage;
- bool use_last_error = true;
- errorDescription = Str("Output.RecordError.Msg");
- if (use_last_error && !last_error.isEmpty())
- dstr_printf(errorMessage, "%s<br><br>%s", errorDescription, QT_TO_UTF8(last_error));
- else
- dstr_copy(errorMessage, errorDescription);
- OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage));
- } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) {
- SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning);
- } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) {
- SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning);
- } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
- SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning);
- } else if (code == OBS_OUTPUT_SUCCESS) {
- if (outputHandler) {
- std::string path = outputHandler->lastRecordingPath;
- QString str = QTStr("Basic.StatusBar.RecordingSavedTo");
- ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str())));
- }
- }
- OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED);
- if (diskFullTimer->isActive())
- diskFullTimer->stop();
- AutoRemux(outputHandler->lastRecordingPath.c_str());
- OnDeactivate();
- }
- void OBSBasic::RecordingFileChanged(QString lastRecordingPath)
- {
- QString str = QTStr("Basic.StatusBar.RecordingSavedTo");
- ShowStatusBarMessage(str.arg(lastRecordingPath));
- AutoRemux(lastRecordingPath, true);
- }
- void OBSBasic::RecordActionTriggered()
- {
- if (outputHandler->RecordingActive()) {
- bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord");
- if (confirm && isVisible()) {
- QMessageBox::StandardButton button = OBSMessageBox::question(
- this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"),
- QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
- if (button == QMessageBox::No)
- return;
- }
- StopRecording();
- } else {
- if (!UIValidation::NoSourcesConfirmation(this))
- return;
- StartRecording();
- }
- }
- bool OBSBasic::RecordingActive()
- {
- if (!outputHandler)
- return false;
- return outputHandler->RecordingActive();
- }
- void OBSBasic::PauseRecording()
- {
- if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput ||
- os_atomic_load_bool(&recording_paused))
- return;
- obs_output_t *output = outputHandler->fileOutput;
- if (obs_output_pause(output, true)) {
- os_atomic_set_bool(&recording_paused, true);
- emit RecordingPaused();
- ui->statusbar->RecordingPaused();
- TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused);
- if (trayIcon && trayIcon->isVisible()) {
- #ifdef __APPLE__
- QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg");
- trayIconFile.setIsMask(true);
- #else
- QIcon trayIconFile = QIcon(":/res/images/obs_paused.png");
- #endif
- trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile));
- }
- OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED);
- if (os_atomic_load_bool(&replaybuf_active))
- ShowReplayBufferPauseWarning();
- }
- }
- void OBSBasic::UnpauseRecording()
- {
- if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput ||
- !os_atomic_load_bool(&recording_paused))
- return;
- obs_output_t *output = outputHandler->fileOutput;
- if (obs_output_pause(output, false)) {
- os_atomic_set_bool(&recording_paused, false);
- emit RecordingUnpaused();
- ui->statusbar->RecordingUnpaused();
- TaskbarOverlaySetStatus(TaskbarOverlayStatusActive);
- if (trayIcon && trayIcon->isVisible()) {
- #ifdef __APPLE__
- QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg");
- trayIconFile.setIsMask(true);
- #else
- QIcon trayIconFile = QIcon(":/res/images/tray_active.png");
- #endif
- trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile));
- }
- OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED);
- }
- }
- void OBSBasic::RecordPauseToggled()
- {
- if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput)
- return;
- obs_output_t *output = outputHandler->fileOutput;
- bool enable = !obs_output_paused(output);
- if (enable)
- PauseRecording();
- else
- UnpauseRecording();
- }
- void OBSBasic::UpdateIsRecordingPausable()
- {
- const char *mode = config_get_string(activeConfiguration, "Output", "Mode");
- bool adv = astrcmpi(mode, "Advanced") == 0;
- bool shared = true;
- if (adv) {
- const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType");
- if (astrcmpi(recType, "FFmpeg") == 0) {
- shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile");
- } else {
- const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder");
- shared = astrcmpi(recordEncoder, "none") == 0;
- }
- } else {
- const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality");
- shared = strcmp(quality, "Stream") == 0;
- }
- isRecordingPausable = !shared;
- }
- #define MBYTE (1024ULL * 1024ULL)
- #define MBYTES_LEFT_STOP_REC 50ULL
- #define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE)
- void OBSBasic::DiskSpaceMessage()
- {
- blog(LOG_ERROR, "Recording stopped because of low disk space");
- OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg"));
- }
- bool OBSBasic::LowDiskSpace()
- {
- const char *path;
- path = GetCurrentOutputPath();
- if (!path)
- return false;
- uint64_t num_bytes = os_get_free_disk_space(path);
- if (num_bytes < (MAX_BYTES_LEFT))
- return true;
- else
- return false;
- }
- void OBSBasic::CheckDiskSpaceRemaining()
- {
- if (LowDiskSpace()) {
- StopRecording();
- StopReplayBuffer();
- DiskSpaceMessage();
- }
- }
|