From dced066c126812397c1270811cf3898facedab47 Mon Sep 17 00:00:00 2001 From: Warchamp7 Date: Tue, 15 Jul 2025 14:52:56 -0400 Subject: [PATCH] frontend: Improve UX around recording paths --- frontend/OBSApp.cpp | 6 ----- frontend/data/locale/en-US.ini | 7 ++++-- frontend/forms/OBSBasicSettings.ui | 6 +++++ frontend/utility/ScreenshotObj.cpp | 24 +++++++++++++------ frontend/utility/ScreenshotObj.hpp | 1 + frontend/widgets/OBSBasic.hpp | 1 + frontend/widgets/OBSBasic_OutputHandler.cpp | 26 ++++++++++++++++++++- frontend/widgets/OBSBasic_Recording.cpp | 7 ++++-- frontend/widgets/OBSBasic_ReplayBuffer.cpp | 2 +- 9 files changed, 61 insertions(+), 19 deletions(-) diff --git a/frontend/OBSApp.cpp b/frontend/OBSApp.cpp index de10127b54b44e..f0727d969745c1 100644 --- a/frontend/OBSApp.cpp +++ b/frontend/OBSApp.cpp @@ -1390,15 +1390,9 @@ string GetFormatExt(const char *container) string GetOutputFilename(const char *path, const char *container, bool noSpace, bool overwrite, const char *format) { - OBSBasic *main = OBSBasic::Get(); - os_dir_t *dir = path && path[0] ? os_opendir(path) : nullptr; if (!dir) { - if (main->isVisible()) - OBSMessageBox::warning(main, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); - else - main->SysTrayNotify(QTStr("Output.BadPath.Text"), QSystemTrayIcon::Warning); return ""; } diff --git a/frontend/data/locale/en-US.ini b/frontend/data/locale/en-US.ini index da7c09be7a7cc2..f304bce76a1ba9 100644 --- a/frontend/data/locale/en-US.ini +++ b/frontend/data/locale/en-US.ini @@ -439,10 +439,12 @@ Output.RecordError.EncodeErrorMsg="An encoder error occurred while recording." Output.RecordError.EncodeErrorMsg.LastError="An encoder error occurred while recording:

%1" # output recording messages -Output.BadPath.Title="Bad File Path" +Output.Error.Title="Output Error" +Output.BadPath.Title="Invalid Path" # "Recording Path" should match Basic.Settings.Output.Simple.SavePath -Output.BadPath.Text="The configured Recording Path could not be opened. Please check your Recording Path under Settings → Output → Recording." +Output.PathError.Text="The configured Recording Path could not be opened. The folder specified in Settings → Output → Recording does not exist." +Output.BadPath.Text="Recording Path '%1' does not exist.\n\nWould you like to create it?" # broadcast setup messages Output.NoBroadcast.Title="No Broadcast Configured" @@ -674,6 +676,7 @@ Basic.StatusBar.DelayStartingStoppingIn="Delay (stopping in %1 sec, starting in Basic.StatusBar.RecordingSavedTo="Recording saved to '%1'" Basic.StatusBar.ReplayBufferSavedTo="Replay buffer saved to '%1'" Basic.StatusBar.ScreenshotSavedTo="Screenshot saved to '%1'" +Basic.StatusBar.ScreenshotError="Error while saving screenshot" Basic.StatusBar.AutoRemuxedTo="Recording auto remuxed to '%1'" # filters window diff --git a/frontend/forms/OBSBasicSettings.ui b/frontend/forms/OBSBasicSettings.ui index 1d59d537b7dde5..69c3fd7e033d5c 100644 --- a/frontend/forms/OBSBasicSettings.ui +++ b/frontend/forms/OBSBasicSettings.ui @@ -2615,6 +2615,9 @@ true + + true + @@ -3622,6 +3625,9 @@ 0 + + true + diff --git a/frontend/utility/ScreenshotObj.cpp b/frontend/utility/ScreenshotObj.cpp index 0fff65d6f526d1..4a0ae48abd55cb 100644 --- a/frontend/utility/ScreenshotObj.cpp +++ b/frontend/utility/ScreenshotObj.cpp @@ -51,12 +51,16 @@ ScreenshotObj::~ScreenshotObj() if (cx && cy) { OBSBasic *main = OBSBasic::Get(); - main->ShowStatusBarMessage( - QTStr("Basic.StatusBar.ScreenshotSavedTo").arg(QT_UTF8(path.c_str()))); + if (success) { + main->ShowStatusBarMessage( + QTStr("Basic.StatusBar.ScreenshotSavedTo").arg(QT_UTF8(path.c_str()))); - main->lastScreenshot = path; + main->lastScreenshot = path; - main->OnEvent(OBS_FRONTEND_EVENT_SCREENSHOT_TAKEN); + main->OnEvent(OBS_FRONTEND_EVENT_SCREENSHOT_TAKEN); + } else { + main->ShowStatusBarMessage(QTStr("Basic.StatusBar.ScreenshotError")); + } } } } @@ -262,9 +266,15 @@ static HRESULT SaveJxr(LPCWSTR path, uint8_t *pixels, uint32_t cx, uint32_t cy) void ScreenshotObj::MuxAndFinish() { - if (half_bytes.empty()) { - image.save(QT_UTF8(path.c_str())); - blog(LOG_INFO, "Saved screenshot to '%s'", path.c_str()); + success = false; + + if (half_bytes.empty() && !path.empty()) { + success = image.save(QT_UTF8(path.c_str())); + if (success) { + blog(LOG_INFO, "Saved screenshot to '%s'", path.c_str()); + } else { + blog(LOG_INFO, "Error while saving screenshot"); + } } else { #ifdef _WIN32 wchar_t *path_w = nullptr; diff --git a/frontend/utility/ScreenshotObj.hpp b/frontend/utility/ScreenshotObj.hpp index 01a4fefd8b131c..96190b8d2a3c1e 100644 --- a/frontend/utility/ScreenshotObj.hpp +++ b/frontend/utility/ScreenshotObj.hpp @@ -44,6 +44,7 @@ class ScreenshotObj : public QObject { uint32_t cx; uint32_t cy; std::thread th; + bool success = false; int stage = 0; diff --git a/frontend/widgets/OBSBasic.hpp b/frontend/widgets/OBSBasic.hpp index 48e9960fa2b091..902455eb86c374 100644 --- a/frontend/widgets/OBSBasic.hpp +++ b/frontend/widgets/OBSBasic.hpp @@ -728,6 +728,7 @@ private slots: bool IsFFmpegOutputToURL() const; bool OutputPathValid(); + bool promptCreateOutputPath(); void OutputPathInvalidMessage(); // TODO: Unimplemented, remove. diff --git a/frontend/widgets/OBSBasic_OutputHandler.cpp b/frontend/widgets/OBSBasic_OutputHandler.cpp index 4eada19423f73b..f77b5382352966 100644 --- a/frontend/widgets/OBSBasic_OutputHandler.cpp +++ b/frontend/widgets/OBSBasic_OutputHandler.cpp @@ -114,7 +114,7 @@ void OBSBasic::OutputPathInvalidMessage() { blog(LOG_ERROR, "Recording stopped because of bad output path"); - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); + OBSMessageBox::critical(this, QTStr("Output.Error.Title"), QTStr("Output.PathError.Text")); } bool OBSBasic::IsFFmpegOutputToURL() const @@ -140,3 +140,27 @@ bool OBSBasic::OutputPathValid() const char *path = GetCurrentOutputPath(); return path && *path && QDir(path).exists(); } + +bool OBSBasic::promptCreateOutputPath() +{ + const char *path = GetCurrentOutputPath(); + + if (!path || !*path) { + return false; + } + + auto result = OBSMessageBox::question(this, QTStr("Output.BadPath.Title"), + QTStr("Output.BadPath.Text").arg(path), + QMessageBox::Yes | QMessageBox::No); + + if (result == QMessageBox::No) { + return false; + } + + auto makeDirectory = os_mkdir(path); + if (makeDirectory == MKDIR_ERROR) { + OBSMessageBox::critical(this, QTStr("Error"), QTStr("Failed to create directory '%1'").arg(path)); + } + + return makeDirectory != MKDIR_ERROR; +} diff --git a/frontend/widgets/OBSBasic_Recording.cpp b/frontend/widgets/OBSBasic_Recording.cpp index 83f0304887d917..21a4fd68d67054 100644 --- a/frontend/widgets/OBSBasic_Recording.cpp +++ b/frontend/widgets/OBSBasic_Recording.cpp @@ -36,7 +36,10 @@ void OBSBasic::on_actionShow_Recordings_triggered() : 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)); + bool success = QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + if (!success) { + OutputPathInvalidMessage(); + } } #define RECORDING_START "==== Recording Start ===============================================" @@ -113,7 +116,7 @@ void OBSBasic::StartRecording() if (disableOutputsRef) return; - if (!OutputPathValid()) { + if (!OutputPathValid() && !promptCreateOutputPath()) { OutputPathInvalidMessage(); return; } diff --git a/frontend/widgets/OBSBasic_ReplayBuffer.cpp b/frontend/widgets/OBSBasic_ReplayBuffer.cpp index 8d95f2210677ba..a930eb7adc76db 100644 --- a/frontend/widgets/OBSBasic_ReplayBuffer.cpp +++ b/frontend/widgets/OBSBasic_ReplayBuffer.cpp @@ -76,7 +76,7 @@ void OBSBasic::StartReplayBuffer() if (!UIValidation::NoSourcesConfirmation(this)) return; - if (!OutputPathValid()) { + if (!OutputPathValid() && !promptCreateOutputPath()) { OutputPathInvalidMessage(); return; }