From 5a375defa8a6d11e1ad83ae96148ce57b33e87df Mon Sep 17 00:00:00 2001 From: derrod Date: Mon, 3 Apr 2023 15:48:55 +0200 Subject: [PATCH] UI: Rework recording format handling --- UI/obs-app.cpp | 20 ++++++++++-- UI/obs-app.hpp | 5 +-- UI/window-basic-main-outputs.cpp | 56 +++++++++++--------------------- UI/window-basic-main-outputs.hpp | 10 +++--- UI/window-basic-main.cpp | 54 ++++++++++++++++++------------ UI/window-basic-settings.cpp | 53 ++++++++++++++++-------------- 6 files changed, 107 insertions(+), 91 deletions(-) diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index d9ef28eac..a67f6bfb4 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -2130,7 +2130,22 @@ string GetFormatString(const char *format, const char *prefix, return f; } -string GetOutputFilename(const char *path, const char *ext, bool noSpace, +string GetFormatExt(const char *container) +{ + string ext = container; + if (ext == "fragmented_mp4") + ext = "mp4"; + else if (ext == "fragmented_mov") + ext = "mov"; + else if (ext == "hls") + ext = "m3u8"; + else if (ext == "mpegts") + ext = "ts"; + + return ext; +} + +string GetOutputFilename(const char *path, const char *container, bool noSpace, bool overwrite, const char *format) { OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); @@ -2157,7 +2172,8 @@ string GetOutputFilename(const char *path, const char *ext, bool noSpace, if (lastChar != '/' && lastChar != '\\') strPath += "/"; - strPath += GenerateSpecifiedFilename(ext, noSpace, format); + string ext = GetFormatExt(container); + strPath += GenerateSpecifiedFilename(ext.c_str(), noSpace, format); ensure_directory_exists(strPath); if (!overwrite) FindBestFilename(strPath, noSpace); diff --git a/UI/obs-app.hpp b/UI/obs-app.hpp index 8e787e241..8ce62968d 100644 --- a/UI/obs-app.hpp +++ b/UI/obs-app.hpp @@ -45,8 +45,9 @@ std::string GenerateSpecifiedFilename(const char *extension, bool noSpace, const char *format); std::string GetFormatString(const char *format, const char *prefix, const char *suffix); -std::string GetOutputFilename(const char *path, const char *ext, bool noSpace, - bool overwrite, const char *format); +std::string GetFormatExt(const char *container); +std::string GetOutputFilename(const char *path, const char *container, + bool noSpace, bool overwrite, const char *format); QObject *CreateShortcutFilter(); struct BaseLexer { diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 06b1c8d84..5151bf77a 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -1297,15 +1297,11 @@ bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer) int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - bool is_fragmented = strcmp(format, "fmp4") == 0 || - strcmp(format, "fmov") == 0; + bool is_fragmented = strncmp(format, "fragmented", 10) == 0; string f; string strPath; - if (is_fragmented) - ++format; - OBSDataAutoRelease settings = obs_data_create(); if (updateReplayBuffer) { f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); @@ -1324,8 +1320,7 @@ bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer) strPath = GetRecordingFilename(path, ffmpegOutput ? "avi" : format, noSpace, overwriteIfExists, - f.c_str(), ffmpegOutput, - is_fragmented); + f.c_str(), ffmpegOutput); obs_data_set_string(settings, ffmpegOutput ? "url" : "path", strPath.c_str()); if (ffmpegOutput) @@ -1782,9 +1777,7 @@ inline void AdvancedOutput::SetupRecording() const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); - bool is_fragmented = strcmp(recFormat, "fmp4") == 0 || - strcmp(recFormat, "fmov") == 0; - + bool is_fragmented = strncmp(recFormat, "fragmented", 10) == 0; bool flv = strcmp(recFormat, "flv") == 0; if (flv) @@ -1843,14 +1836,14 @@ inline void AdvancedOutput::SetupRecording() // Use fragmented MOV/MP4 if user has not already specified custom movflags if (is_fragmented && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_fmp4 = + string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; if (mux) { - mux_fmp4 += " "; - mux_fmp4 += mux; + mux_frag += " "; + mux_frag += mux; } obs_data_set_string(settings, "muxer_settings", - mux_fmp4.c_str()); + mux_frag.c_str()); } else { if (is_fragmented) blog(LOG_WARNING, @@ -2176,7 +2169,6 @@ bool AdvancedOutput::StartRecording() const char *recFormat; const char *filenameFormat; bool noSpace = false; - bool fragmented = false; bool overwriteIfExists = false; bool splitFile; const char *splitFileType; @@ -2214,16 +2206,10 @@ bool AdvancedOutput::StartRecording() splitFile = config_get_bool(main->Config(), "AdvOut", "RecSplitFile"); - // Strip leading "f" in case fragmented format was selected - if (strcmp(recFormat, "fmp4") == 0 || - strcmp(recFormat, "fmov") == 0) { - ++recFormat; - fragmented = true; - } - - string strPath = GetRecordingFilename( - path, recFormat, noSpace, overwriteIfExists, - filenameFormat, ffmpegRecording, fragmented); + string strPath = GetRecordingFilename(path, recFormat, noSpace, + overwriteIfExists, + filenameFormat, + ffmpegRecording); OBSDataAutoRelease settings = obs_data_create(); obs_data_set_string(settings, ffmpegRecording ? "url" : "path", @@ -2322,11 +2308,6 @@ bool AdvancedOutput::StartReplayBuffer() rbTime = config_get_int(main->Config(), "AdvOut", "RecRBTime"); rbSize = config_get_int(main->Config(), "AdvOut", "RecRBSize"); - /* Skip leading f for fragmented formats. */ - if (strcmp(recFormat, "fmp4") == 0 || - strcmp(recFormat, "fmov") == 0) - ++recFormat; - string f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); string strPath = GetOutputFilename( path, recFormat, noSpace, overwriteIfExists, f.c_str()); @@ -2400,21 +2381,22 @@ bool AdvancedOutput::ReplayBufferActive() const /* ------------------------------------------------------------------------ */ -void BasicOutputHandler::SetupAutoRemux(const char *&ext, bool is_fragmented) +void BasicOutputHandler::SetupAutoRemux(const char *&container) { bool autoRemux = config_get_bool(main->Config(), "Video", "AutoRemux"); - if (autoRemux && !is_fragmented && strcmp(ext, "mp4") == 0) - ext = "mkv"; + if (autoRemux && strcmp(container, "mp4") == 0) + container = "mkv"; } std::string BasicOutputHandler::GetRecordingFilename( - const char *path, const char *ext, bool noSpace, bool overwrite, - const char *format, bool ffmpeg, bool is_fragmented) + const char *path, const char *container, bool noSpace, bool overwrite, + const char *format, bool ffmpeg) { if (!ffmpeg) - SetupAutoRemux(ext, is_fragmented); + SetupAutoRemux(container); - string dst = GetOutputFilename(path, ext, noSpace, overwrite, format); + string dst = + GetOutputFilename(path, container, noSpace, overwrite, format); lastRecordingPath = dst; return dst; } diff --git a/UI/window-basic-main-outputs.hpp b/UI/window-basic-main-outputs.hpp index 7273930f7..a67a563e9 100644 --- a/UI/window-basic-main-outputs.hpp +++ b/UI/window-basic-main-outputs.hpp @@ -72,11 +72,11 @@ struct BasicOutputHandler { } protected: - void SetupAutoRemux(const char *&ext, bool is_fragmented); - std::string GetRecordingFilename(const char *path, const char *ext, - bool noSpace, bool overwrite, - const char *format, bool ffmpeg, - bool is_fragmented); + void SetupAutoRemux(const char *&container); + std::string GetRecordingFilename(const char *path, + const char *container, bool noSpace, + bool overwrite, const char *format, + bool ffmpeg); }; BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index dbb8f776c..b3470b39a 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -1374,9 +1374,9 @@ extern void CheckExistingCookieId(); #if OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 #define DEFAULT_CONTAINER "mkv" #elif defined(__APPLE__) -#define DEFAULT_CONTAINER "fmov" +#define DEFAULT_CONTAINER "fragmented_mov" #else -#define DEFAULT_CONTAINER "fmp4" +#define DEFAULT_CONTAINER "fragmented_mp4" #endif bool OBSBasic::InitBasicConfigDefaults() @@ -1474,23 +1474,37 @@ bool OBSBasic::InitBasicConfigDefaults() /* ----------------------------------------------------- */ /* Migrate old container selection (if any) to new key. */ - if (!config_has_user_value(basicConfig, "SimpleOutput", "RecFormat2") && - config_has_user_value(basicConfig, "SimpleOutput", "RecFormat")) { - const char *old_format = config_get_string( - basicConfig, "SimpleOutput", "RecFormat"); - config_set_string(basicConfig, "SimpleOutput", "RecFormat2", - old_format); - changed = true; - } - if (!config_has_user_value(basicConfig, "AdvOut", "RecFormat2") && - config_has_user_value(basicConfig, "AdvOut", "RecFormat")) { - const char *old_format = - config_get_string(basicConfig, "AdvOut", "RecFormat"); - config_set_string(basicConfig, "AdvOut", "RecFormat2", - old_format); - changed = true; - } + auto MigrateFormat = [&](const char *section) { + bool has_old_key = config_has_user_value(basicConfig, section, + "RecFormat"); + bool has_new_key = config_has_user_value(basicConfig, section, + "RecFormat2"); + if (!has_new_key && !has_old_key) + return; + + string old_format = config_get_string( + basicConfig, section, + has_new_key ? "RecFormat2" : "RecFormat"); + string new_format = old_format; + if (old_format == "ts") + new_format = "mpegts"; + else if (old_format == "m3u8") + new_format = "hls"; + else if (old_format == "fmp4") + new_format = "fragmented_mp4"; + else if (old_format == "fmov") + new_format = "fragmented_mov"; + + if (new_format != old_format || !has_new_key) { + config_set_string(basicConfig, section, "RecFormat2", + new_format.c_str()); + changed = true; + } + }; + + MigrateFormat("AdvOut"); + MigrateFormat("SimpleOutput"); /* ----------------------------------------------------- */ @@ -7382,11 +7396,11 @@ void OBSBasic::AutoRemux(QString input, bool no_show) const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); const char *codecName = obs_encoder_get_codec(videoEncoder); - string format = config_get_string( + const char *format = config_get_string( config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); /* Retain original container for fMP4/fMOV */ - if (format == "fmp4" || format == "fmov") { + if (strncmp(format, "fragmented", 10) == 0) { output += "remuxed." + suffix; } else if (strcmp(codecName, "prores") == 0) { output += "mov"; diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 91a37b78b..c13c3847b 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -1165,18 +1165,18 @@ void OBSBasicSettings::LoadFormats() ui->simpleOutRecFormat->addItem(FORMAT_STR("MKV"), "mkv"); ui->simpleOutRecFormat->addItem(FORMAT_STR("MP4"), "mp4"); ui->simpleOutRecFormat->addItem(FORMAT_STR("MOV"), "mov"); - ui->simpleOutRecFormat->addItem(FORMAT_STR("fMP4"), "fmp4"); - ui->simpleOutRecFormat->addItem(FORMAT_STR("fMOV"), "fmov"); - ui->simpleOutRecFormat->addItem(FORMAT_STR("TS"), "ts"); + ui->simpleOutRecFormat->addItem(FORMAT_STR("fMP4"), "fragmented_mp4"); + ui->simpleOutRecFormat->addItem(FORMAT_STR("fMOV"), "fragmented_mov"); + ui->simpleOutRecFormat->addItem(FORMAT_STR("TS"), "mpegts"); ui->advOutRecFormat->addItem(FORMAT_STR("FLV"), "flv"); ui->advOutRecFormat->addItem(FORMAT_STR("MKV"), "mkv"); ui->advOutRecFormat->addItem(FORMAT_STR("MP4"), "mp4"); ui->advOutRecFormat->addItem(FORMAT_STR("MOV"), "mov"); - ui->advOutRecFormat->addItem(FORMAT_STR("fMP4"), "fmp4"); - ui->advOutRecFormat->addItem(FORMAT_STR("fMOV"), "fmov"); - ui->advOutRecFormat->addItem(FORMAT_STR("TS"), "ts"); - ui->advOutRecFormat->addItem(FORMAT_STR("HLS"), "m3u8"); + ui->advOutRecFormat->addItem(FORMAT_STR("fMP4"), "fragmented_mp4"); + ui->advOutRecFormat->addItem(FORMAT_STR("fMOV"), "fragmented_mov"); + ui->advOutRecFormat->addItem(FORMAT_STR("TS"), "mpegts"); + ui->advOutRecFormat->addItem(FORMAT_STR("HLS"), "hls"); #undef FORMAT_STR } @@ -4983,13 +4983,18 @@ static const unordered_set builtin_codecs = { static const unordered_map> codec_compat = { // Technically our muxer supports HEVC and AV1 as well, but nothing else does {"flv", {"h264", "aac"}}, - {"ts", {"h264", "hevc", "aac", "opus"}}, - {"m3u8", + {"mpegts", {"h264", "hevc", "aac", "opus"}}, + {"hls", {"h264", "hevc", "aac"}}, // Also using MPEG-TS, but no Opus support {"mov", {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}}, {"mp4", {"h264", "hevc", "av1", "aac", "opus", "alac", "flac"}}, + {"fragmented_mov", + {"h264", "hevc", "prores", "aac", "alac", "pcm_s16le", "pcm_s24le", + "pcm_f32le"}}, + {"fragmented_mp4", + {"h264", "hevc", "av1", "aac", "opus", "alac", "flac"}}, // MKV supports everything {"mkv", {}}, }; @@ -5007,12 +5012,12 @@ static bool ContainerSupportsCodec(const string &container, const string &codec) return codecs.count(codec) > 0; } -static void DisableIncompatibleCodecs(QComboBox *cbox, const string &format, +static void DisableIncompatibleCodecs(QComboBox *cbox, const QString &format, const QString &streamEncoder) { QString strEncLabel = QTStr("Basic.Settings.Output.Adv.Recording.UseStreamEncoder"); - QString formatUpper = QString::fromStdString(format).toUpper(); + QString formatUpper = format.toUpper(); QString recEncoder = cbox->currentData().toString(); /* Check if selected encoders and output format are compatible, disable incompatible items. */ @@ -5028,11 +5033,14 @@ static void DisableIncompatibleCodecs(QComboBox *cbox, const string &format, encoderId.c_str()); const char *codec = obs_get_encoder_codec(encoderId.c_str()); - bool is_compatible = ContainerSupportsCodec(format, codec); + bool is_compatible = + ContainerSupportsCodec(format.toStdString(), codec); /* Fall back to FFmpeg check if codec not one of the built-in ones. */ - if (!is_compatible && !builtin_codecs.count(codec)) - is_compatible = ff_format_codec_compatible( - codec, format.c_str()); + if (!is_compatible && !builtin_codecs.count(codec)) { + string ext = GetFormatExt(QT_TO_UTF8(format)); + is_compatible = + ff_format_codec_compatible(codec, ext.c_str()); + } QStandardItemModel *model = dynamic_cast(cbox->model()); @@ -5072,21 +5080,16 @@ void OBSBasicSettings::AdvOutRecCheckCodecs() else ui->advOutRecFormat->setToolTip(nullptr); - string format = recFormat.toStdString(); - /* Remove leading "f" for fragmented MP4/MOV */ - if (format == "fmp4" || format == "fmov") - format = format.erase(0, 1); - QString streamEncoder = ui->advOutEncoder->currentData().toString(); - QString streamAudioEncoder = ui->advOutAEncoder->currentData().toString(); /* Disable the signals to prevent AdvOutRecCheckWarnings to be called here. */ ui->advOutRecEncoder->blockSignals(true); ui->advOutRecAEncoder->blockSignals(true); - DisableIncompatibleCodecs(ui->advOutRecEncoder, format, streamEncoder); - DisableIncompatibleCodecs(ui->advOutRecAEncoder, format, + DisableIncompatibleCodecs(ui->advOutRecEncoder, recFormat, + streamEncoder); + DisableIncompatibleCodecs(ui->advOutRecAEncoder, recFormat, streamAudioEncoder); ui->advOutRecEncoder->blockSignals(false); ui->advOutRecAEncoder->blockSignals(false); @@ -5722,8 +5725,8 @@ static void DisableIncompatibleSimpleCodecs(QComboBox *cbox, dynamic_cast(cbox->model()); QStandardItem *item = model->item(idx); - if (ContainerSupportsCodec(QT_TO_UTF8(format), - QT_TO_UTF8(codec))) { + if (ContainerSupportsCodec(format.toStdString(), + codec.toStdString())) { item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } else {