From 50961861c74795383a3e1b85a8d076480df6b263 Mon Sep 17 00:00:00 2001 From: bl Date: Fri, 25 Mar 2016 02:43:38 -0700 Subject: [PATCH] UI: Add file formatting options for recording Allows the user to specify custom formatting for their recording file names with many formatting options, viewed via tooltip. The options have been added to the advanced settings section. Closes jp9000/obs-studio#507 --- obs/data/locale/en-US.ini | 8 +++++ obs/forms/OBSBasicSettings.ui | 26 +++++++++++++++ obs/window-basic-main-outputs.cpp | 55 ++++++++++++++++++++++++++++--- obs/window-basic-main.cpp | 3 ++ obs/window-basic-settings.cpp | 37 +++++++++++++++++++++ obs/window-basic-settings.hpp | 1 + 6 files changed, 125 insertions(+), 5 deletions(-) diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index 99c965038..88a2de719 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -404,6 +404,8 @@ Basic.Settings.Output.Adv.Recording.Type="Type" Basic.Settings.Output.Adv.Recording.Type.Standard="Standard" Basic.Settings.Output.Adv.Recording.Type.FFmpegOutput="Custom Output (FFmpeg)" Basic.Settings.Output.Adv.Recording.UseStreamEncoder="(Use stream encoder)" +Basic.Settings.Output.Adv.Recording.Filename="Filename Formatting" +Basic.Settings.Output.Adv.Recording.OverwriteIfExists="Overwrite if file exists" Basic.Settings.Output.Adv.FFmpeg.Type="FFmpeg Output Type" Basic.Settings.Output.Adv.FFmpeg.Type.URL="Output to URL" Basic.Settings.Output.Adv.FFmpeg.Type.RecordToFile="Output to File" @@ -424,6 +426,12 @@ Basic.Settings.Output.Adv.FFmpeg.AEncoder="Audio Encoder" Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings="Audio Encoder Settings (if any)" Basic.Settings.Output.Adv.FFmpeg.MuxerSettings="Muxer Settings (if any)" +# basic mode 'output' settings - advanced section - recording subsection - completer +FilenameFormatting.completer="%yyyy-%mm-%dd %hh-%mm-%ss\n%yy-%mm-%dd %hh-%mm-%ss\n%Y-%m-%d %H-%M-%S\n%y-%m-%d %H-%M-%S\n%a %Y-%m-%d %H-%M-%S\n%A %Y-%m-%d %H-%M-%S\n%Y-%b-%d %H-%M-%S\n%Y-%B-%d %H-%M-%S\n%Y-%m-%d %I-%M-%S-%p\n%Y-%m-%d %H-%M-%S-%z\n%Y-%m-%d %H-%M-%S-%Z" + +# basic mode 'output' settings - advanced section - recording subsection - TT +FilenameFormatting.TT="%yyyy Year, four digits\n%yy Year, last two digits (00-99)\n%mm Month as a decimal number (01-12)\n%dd Day of the month, zero-padded (01-31)\n%hh Hour in 24h format (00-23)\n%mm Minute (00-59)\n%ss Second (00-61)\n%% A % sign\n%a Abbreviated weekday name\n%A Full weekday name\n%b Abbreviated month name\n%B Full month name\n%d Day of the month, zero-padded (01-31)\n%H Hour in 24h format (00-23)\n%I Hour in 12h format (01-12)\n%m Month as a decimal number (01-12)\n%M Minute (00-59)\n%p AM or PM designation\n%S Second (00-61)\n%y Year, last two digits (00-99)\n%Y Year\n%z ISO 8601 offset from UTC or timezone\n name or abbreviation\n%Z Timezone name or abbreviation\n" + # basic mode 'video' settings Basic.Settings.Video="Video" Basic.Settings.Video.Adapter="Video Adapter:" diff --git a/obs/forms/OBSBasicSettings.ui b/obs/forms/OBSBasicSettings.ui index 4b8919c12..fae897a91 100644 --- a/obs/forms/OBSBasicSettings.ui +++ b/obs/forms/OBSBasicSettings.ui @@ -2953,6 +2953,32 @@ + + + + Basic.Settings.Output.Adv.Recording + + + + + + Basic.Settings.Output.Adv.Recording.Filename + + + + + + + + + + Basic.Settings.Output.Adv.Recording.OverwriteIfExists + + + + + + diff --git a/obs/window-basic-main-outputs.cpp b/obs/window-basic-main-outputs.cpp index 7149df493..e8711dde4 100644 --- a/obs/window-basic-main-outputs.cpp +++ b/obs/window-basic-main-outputs.cpp @@ -75,6 +75,36 @@ static void OBSStopRecording(void *data, calldata_t *params) UNUSED_PARAMETER(params); } +static void FindBestFilename(string &strPath, bool noSpace) +{ + int num = 2; + + if (!os_file_exists(strPath.c_str())) + return; + + const char *ext = strrchr(strPath.c_str(), '.'); + if (!ext) + return; + + int extStart = int(ext - strPath.c_str()); + for (;;) { + string testPath = strPath; + string numStr; + + numStr = noSpace ? "_" : " ("; + numStr += to_string(num++); + if (!noSpace) + numStr += ")"; + + testPath.insert(extStart, numStr); + + if (!os_file_exists(testPath.c_str())) { + strPath = testPath; + break; + } + } +} + /* ------------------------------------------------------------------------ */ static bool CreateAACEncoder(OBSEncoder &res, string &id, int bitrate, @@ -431,6 +461,10 @@ bool SimpleOutput::StartRecording() "MuxerCustom"); bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); + const char *filenameFormat = config_get_string(main->Config(), "Output", + "FilenameFormatting"); + bool overwriteIfExists = config_get_bool(main->Config(), "Output", + "OverwriteIfExists"); os_dir_t *dir = path ? os_opendir(path) : nullptr; @@ -450,8 +484,10 @@ bool SimpleOutput::StartRecording() if (lastChar != '/' && lastChar != '\\') strPath += "/"; - strPath += GenerateTimeDateFilename(ffmpegOutput ? "avi" : format, - noSpace); + strPath += GenerateSpecifiedFilename(ffmpegOutput ? "avi" : format, + noSpace, filenameFormat); + if (!overwriteIfExists) + FindBestFilename(strPath, noSpace); SetupOutputs(); @@ -932,8 +968,10 @@ bool AdvancedOutput::StartStreaming(obs_service_t *service) bool AdvancedOutput::StartRecording() { const char *path; - const char *format; + const char *recFormat; + const char *filenameFormat; bool noSpace = false; + bool overwriteIfExists = false; if (!useStreamEncoder) { if (!ffmpegOutput) { @@ -951,8 +989,12 @@ bool AdvancedOutput::StartRecording() if (!ffmpegOutput || ffmpegRecording) { path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - format = config_get_string(main->Config(), "AdvOut", + recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat"); + filenameFormat = config_get_string(main->Config(), "Output", + "FilenameFormatting"); + overwriteIfExists = config_get_bool(main->Config(), "Output", + "OverwriteIfExists"); noSpace = config_get_bool(main->Config(), "AdvOut", ffmpegRecording ? "FFFileNameWithoutSpace" : @@ -976,7 +1018,10 @@ bool AdvancedOutput::StartRecording() if (lastChar != '/' && lastChar != '\\') strPath += "/"; - strPath += GenerateTimeDateFilename(format, noSpace); + strPath += GenerateSpecifiedFilename(recFormat, noSpace, + filenameFormat); + if (!overwriteIfExists) + FindBestFilename(strPath, noSpace); obs_data_t *settings = obs_data_create(); obs_data_set_string(settings, diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 23cc3f4a7..eaba4c3ea 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -749,6 +749,9 @@ bool OBSBasic::InitBasicConfigDefaults() config_set_default_uint (basicConfig, "Video", "BaseCX", cx); config_set_default_uint (basicConfig, "Video", "BaseCY", cy); + config_set_default_string(basicConfig, "Output", "FilenameFormatting", + "%yyyy-%mm-%dd %hh-%mm-%ss"); + config_set_default_bool (basicConfig, "Output", "DelayEnable", false); config_set_default_uint (basicConfig, "Output", "DelaySec", 20); config_set_default_bool (basicConfig, "Output", "DelayPreserve", true); diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index b80306a0a..86d459330 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -347,6 +348,8 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->colorRange, COMBO_CHANGED, ADV_CHANGED); HookWidget(ui->disableOSXVSync, CHECK_CHANGED, ADV_CHANGED); HookWidget(ui->resetOSXVSync, CHECK_CHANGED, ADV_CHANGED); + HookWidget(ui->filenameFormatting, EDIT_CHANGED, ADV_CHANGED); + HookWidget(ui->overwriteIfExists, CHECK_CHANGED, ADV_CHANGED); HookWidget(ui->streamDelayEnable, CHECK_CHANGED, ADV_CHANGED); HookWidget(ui->streamDelaySec, SCROLL_CHANGED, ADV_CHANGED); HookWidget(ui->streamDelayPreserve, CHECK_CHANGED, ADV_CHANGED); @@ -1128,6 +1131,14 @@ void OBSBasicSettings::LoadAdvOutputStreamingSettings() ui->advOutRescale->setEnabled(rescale); ui->advOutRescale->setCurrentText(rescaleRes); + QStringList specList = QTStr("FilenameFormatting.completer").split( + QRegularExpression("\n")); + QCompleter *specCompleter = new QCompleter(specList); + specCompleter->setCaseSensitivity(Qt::CaseSensitive); + specCompleter->setFilterMode(Qt::MatchContains); + ui->filenameFormatting->setCompleter(specCompleter); + ui->filenameFormatting->setToolTip(QTStr("FilenameFormatting.TT")); + switch (trackIndex) { case 1: ui->advOutTrack1->setChecked(true); break; case 2: ui->advOutTrack2->setChecked(true); break; @@ -1664,11 +1675,18 @@ void OBSBasicSettings::LoadAdvancedSettings() "RetryDelay"); int maxRetries = config_get_int(main->Config(), "Output", "MaxRetries"); + const char *filename = config_get_string(main->Config(), "Output", + "FilenameFormatting"); + bool overwriteIfExists = config_get_bool(main->Config(), "Output", + "OverwriteIfExists"); loading = true; LoadRendererList(); + ui->filenameFormatting->setText(filename); + ui->overwriteIfExists->setChecked(overwriteIfExists); + ui->reconnectEnable->setChecked(reconnect); ui->reconnectRetryDelay->setValue(retryDelay); ui->reconnectMaxRetries->setValue(maxRetries); @@ -2110,6 +2128,8 @@ void OBSBasicSettings::SaveAdvancedSettings() SaveCombo(ui->colorFormat, "Video", "ColorFormat"); SaveCombo(ui->colorSpace, "Video", "ColorSpace"); SaveComboData(ui->colorRange, "Video", "ColorRange"); + SaveEdit(ui->filenameFormatting, "Output", "FilenameFormatting"); + SaveCheckBox(ui->overwriteIfExists, "Output", "OverwriteIfExists"); SaveCheckBox(ui->streamDelayEnable, "Output", "DelayEnable"); SaveSpinBox(ui->streamDelaySec, "Output", "DelaySec"); SaveCheckBox(ui->streamDelayPreserve, "Output", "DelayPreserve"); @@ -2690,6 +2710,23 @@ void OBSBasicSettings::RecalcOutputResPixels(const char *resText) } } + +void OBSBasicSettings::on_filenameFormatting_textEdited(const QString &text) +{ +#ifdef __APPLE__ + size_t invalidLocation = + text.toStdString().find_first_of(":/\\"); +#elif _WIN32 + size_t invalidLocation = + text.toStdString().find_first_of("<>:\"/\\|?*"); +#else + size_t invalidLocation = text.toStdString().find_first_of("/"); +#endif + + if (invalidLocation != string::npos) + ui->filenameFormatting->backspace(); +} + void OBSBasicSettings::on_outputResolution_editTextChanged(const QString &text) { if (!loading) diff --git a/obs/window-basic-settings.hpp b/obs/window-basic-settings.hpp index ded66eedd..00be30837 100644 --- a/obs/window-basic-settings.hpp +++ b/obs/window-basic-settings.hpp @@ -253,6 +253,7 @@ private slots: void on_colorFormat_currentIndexChanged(const QString &text); + void on_filenameFormatting_textEdited(const QString &text); void on_outputResolution_editTextChanged(const QString &text); void on_baseResolution_editTextChanged(const QString &text);