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
This commit is contained in:
parent
086e3f4a09
commit
50961861c7
@ -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:"
|
||||
|
@ -2953,6 +2953,32 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_6">
|
||||
<property name="title">
|
||||
<string>Basic.Settings.Output.Adv.Recording</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_17">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Basic.Settings.Output.Adv.Recording.Filename</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="filenameFormatting"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="overwriteIfExists">
|
||||
<property name="text">
|
||||
<string>Basic.Settings.Output.Adv.Recording.OverwriteIfExists</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <graphics/math-defs.h>
|
||||
#include <initializer_list>
|
||||
#include <sstream>
|
||||
#include <QCompleter>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QCloseEvent>
|
||||
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user