Compare commits

...

8 Commits

Author SHA1 Message Date
Michael Fabian 'Xaymar' Dirks 8b97c2b23d templates: Fix the remaining uncommitted changes 2023-05-20 20:52:40 +02:00
Michael Fabian 'Xaymar' Dirks 9df2f01963 templates: Pascal uses <> instead of != 2023-05-20 20:34:25 +02:00
Michael Fabian 'Xaymar' Dirks ffb7a6c5d7 code: Add GoPro CineForm to FFmpeg Encoders 2023-05-20 19:54:46 +02:00
Michael Fabian 'Xaymar' Dirks f66fabc5d4 templates: Move to 'usercf' instead of 'userpf'
Local (per-user) add-ons to software should reside in "C:\Users\Username\AppData\Local\Programs\Common\", similar to System (all-users) add-ons which reside in "C:\Program Files\Common Files\".

Fixes #1049
2023-05-20 19:54:15 +02:00
Michael Fabian 'Xaymar' Dirks 38d87f6fcf code: Don't crash if there is no encoder instance 2023-05-20 19:54:05 +02:00
Michael Fabian 'Xaymar' Dirks 3e13126f89 code: Remove audio encoder registration from FFmpeg Encoders 2023-05-20 19:25:46 +02:00
Michael Fabian 'Xaymar' Dirks 9d0233a740 code: Create mutexes to prevent Windows (un)installer from continuing
Might fix the problem where people uninstall StreamFX while they still have OBS Studio open with StreamFX loaded. InnoSetup appears to ignore this in /VERYSILENT, so this is an additional guard against that.
2023-05-20 19:24:06 +02:00
Michael Fabian 'Xaymar' Dirks 07182d2f89 templates: Exit-early if the user aborts the removal of an older version 2023-05-20 19:24:06 +02:00
9 changed files with 219 additions and 60 deletions

View File

@ -293,6 +293,7 @@ set(${PREFIX}ENABLE_ENCODER_FFMPEG_AMF ${FEATURE_DEPRECATED} CACHE BOOL "Enable
set(${PREFIX}ENABLE_ENCODER_FFMPEG_NVENC ${FEATURE_STABLE} CACHE BOOL "Enable NVENC Encoder in FFmpeg.")
set(${PREFIX}ENABLE_ENCODER_FFMPEG_PRORES ${FEATURE_STABLE} CACHE BOOL "Enable ProRes Encoder in FFmpeg.")
set(${PREFIX}ENABLE_ENCODER_FFMPEG_DNXHR ${FEATURE_STABLE} CACHE BOOL "Enable DNXHR Encoder in FFmpeg.")
set(${PREFIX}ENABLE_ENCODER_FFMPEG_CFHD ${FEATURE_STABLE} CACHE BOOL "Enable CineForm Encoder in FFmpeg.")
## Filters
set(${PREFIX}ENABLE_FILTER_AUTOFRAMING ${FEATURE_EXPERIMENTAL} CACHE BOOL "Enable Auto-Framing Filter")
@ -476,6 +477,9 @@ function(feature_encoder_ffmpeg RESOLVE)
# DNxHR
is_feature_enabled(ENCODER_FFMPEG_DNXHR T_CHECK)
# CineForm
is_feature_enabled(ENCODER_FFMPEG_CFHD T_CHECK)
endif()
elseif(T_CHECK)
set(REQUIRE_FFMPEG ON PARENT_SCOPE)
@ -1202,6 +1206,18 @@ if(T_CHECK)
ENABLE_ENCODER_FFMPEG_DNXHR
)
endif()
# CineForm HD
is_feature_enabled(ENCODER_FFMPEG_CFHD T_CHECK)
if(T_CHECK)
list(APPEND PROJECT_PRIVATE_SOURCE
"source/encoders/ffmpeg/cfhd.hpp"
"source/encoders/ffmpeg/cfhd.cpp"
)
list(APPEND PROJECT_DEFINITIONS
ENABLE_ENCODER_FFMPEG_CFHD
)
endif()
endif()
# Filter/Auto-Framing

View File

@ -235,6 +235,22 @@ Encoder.FFmpeg.NVENC.Other.NonReferencePFrames="Non-reference P-Frames"
Encoder.FFmpeg.NVENC.Other.ReferenceFrames="Reference Frames"
Encoder.FFmpeg.NVENC.Other.LowDelayKeyFrameScale="Low Delay Key-Frame Scale"
# Encoder/FFmpeg/CFHD
Encoder.FFmpeg.CineForm.Quality="Quality"
Encoder.FFmpeg.CineForm.Quality.low="Low"
Encoder.FFmpeg.CineForm.Quality.low+="Low+"
Encoder.FFmpeg.CineForm.Quality.medium="Medium"
Encoder.FFmpeg.CineForm.Quality.medium+="Medium+"
Encoder.FFmpeg.CineForm.Quality.high="High"
Encoder.FFmpeg.CineForm.Quality.high+="High+"
Encoder.FFmpeg.CineForm.Quality.film1="Film 1"
Encoder.FFmpeg.CineForm.Quality.film1+="Film 1+"
Encoder.FFmpeg.CineForm.Quality.film1.5="Film 1.5"
Encoder.FFmpeg.CineForm.Quality.film2="Film 2"
Encoder.FFmpeg.CineForm.Quality.film2+="Film 2+"
Encoder.FFmpeg.CineForm.Quality.film3="Film 3"
Encoder.FFmpeg.CineForm.Quality.film3+="Film 3+"
# Blur
Blur.Type.Box="Box"
Blur.Type.BoxLinear="Box Linear"

View File

@ -404,46 +404,44 @@ bool ffmpeg_instance::encode_video(uint32_t handle, int64_t pts, uint64_t lock_k
void ffmpeg_instance::initialize_sw(obs_data_t* settings)
{
if (_codec->type == AVMEDIA_TYPE_VIDEO) {
// Initialize Video Encoding
auto voi = video_output_get_info(obs_encoder_video(_self));
// Initialize Video Encoding
auto voi = video_output_get_info(obs_encoder_video(_self));
// Figure out a suitable pixel format to convert to if necessary.
AVPixelFormat pix_fmt_source = ::streamfx::ffmpeg::tools::obs_videoformat_to_avpixelformat(voi->format);
AVPixelFormat pix_fmt_target = AV_PIX_FMT_NONE;
{
if (_codec->pix_fmts) {
pix_fmt_target = ::streamfx::ffmpeg::tools::get_least_lossy_format(_codec->pix_fmts, pix_fmt_source);
} else { // If there are no supported formats, just pass in the current one.
pix_fmt_target = pix_fmt_source;
}
if (_handler) // Allow Handler to override the automatic color format for sanity reasons.
_handler->override_colorformat(this->_factory, this, settings, pix_fmt_target);
// Figure out a suitable pixel format to convert to if necessary.
AVPixelFormat pix_fmt_source = ::streamfx::ffmpeg::tools::obs_videoformat_to_avpixelformat(voi->format);
AVPixelFormat pix_fmt_target = AV_PIX_FMT_NONE;
{
if (_codec->pix_fmts) {
pix_fmt_target = ::streamfx::ffmpeg::tools::get_least_lossy_format(_codec->pix_fmts, pix_fmt_source);
} else { // If there are no supported formats, just pass in the current one.
pix_fmt_target = pix_fmt_source;
}
// Setup from OBS information.
::streamfx::ffmpeg::tools::context_setup_from_obs(voi, _context);
if (_handler) // Allow Handler to override the automatic color format for sanity reasons.
_handler->override_colorformat(this->_factory, this, settings, pix_fmt_target);
}
// Override with other information.
_context->width = static_cast<int>(obs_encoder_get_width(_self));
_context->height = static_cast<int>(obs_encoder_get_height(_self));
_context->pix_fmt = pix_fmt_target;
// Setup from OBS information.
::streamfx::ffmpeg::tools::context_setup_from_obs(voi, _context);
_scaler.set_source_size(static_cast<uint32_t>(_context->width), static_cast<uint32_t>(_context->height));
_scaler.set_source_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
_scaler.set_source_format(pix_fmt_source);
// Override with other information.
_context->width = static_cast<int>(obs_encoder_get_width(_self));
_context->height = static_cast<int>(obs_encoder_get_height(_self));
_context->pix_fmt = pix_fmt_target;
_scaler.set_target_size(static_cast<uint32_t>(_context->width), static_cast<uint32_t>(_context->height));
_scaler.set_target_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
_scaler.set_target_format(pix_fmt_target);
_scaler.set_source_size(static_cast<uint32_t>(_context->width), static_cast<uint32_t>(_context->height));
_scaler.set_source_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
_scaler.set_source_format(pix_fmt_source);
// Create Scaler
if (!_scaler.initialize(SWS_SINC | SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP | SWS_ACCURATE_RND | SWS_BITEXACT)) {
std::stringstream sstr;
sstr << "Initializing scaler failed for conversion from '" << ::streamfx::ffmpeg::tools::get_pixel_format_name(_scaler.get_source_format()) << "' to '" << ::streamfx::ffmpeg::tools::get_pixel_format_name(_scaler.get_target_format()) << "' with color space '" << ::streamfx::ffmpeg::tools::get_color_space_name(_scaler.get_source_colorspace()) << "' and " << (_scaler.is_source_full_range() ? "full" : "partial") << " range.";
throw std::runtime_error(sstr.str());
}
_scaler.set_target_size(static_cast<uint32_t>(_context->width), static_cast<uint32_t>(_context->height));
_scaler.set_target_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
_scaler.set_target_format(pix_fmt_target);
// Create Scaler
if (!_scaler.initialize(SWS_SINC | SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP | SWS_ACCURATE_RND | SWS_BITEXACT)) {
std::stringstream sstr;
sstr << "Initializing scaler failed for conversion from '" << ::streamfx::ffmpeg::tools::get_pixel_format_name(_scaler.get_source_format()) << "' to '" << ::streamfx::ffmpeg::tools::get_pixel_format_name(_scaler.get_target_format()) << "' with color space '" << ::streamfx::ffmpeg::tools::get_color_space_name(_scaler.get_source_colorspace()) << "' and " << (_scaler.is_source_full_range() ? "full" : "partial") << " range.";
throw std::runtime_error(sstr.str());
}
}
@ -1138,7 +1136,7 @@ ffmpeg_manager::ffmpeg_manager() : _factories()
if (!av_codec_is_encoder(codec))
continue;
if ((codec->type == AVMediaType::AVMEDIA_TYPE_AUDIO) || (codec->type == AVMediaType::AVMEDIA_TYPE_VIDEO)) {
if (codec->type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
try {
_factories.emplace(codec, std::make_shared<ffmpeg_factory>(this, codec));
} catch (const std::exception& ex) {

View File

@ -0,0 +1,89 @@
// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
// Copyright (C) 2020-2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// Copyright (C) 2020 Daniel Molkentin <daniel@molkentin.de>
#include "cfhd.hpp"
#include "common.hpp"
#include "encoders/encoder-ffmpeg.hpp"
#include "ffmpeg/tools.hpp"
#include "handler.hpp"
#include "plugin.hpp"
#include "warning-disable.hpp"
#include <map>
#include <string>
#include <utility>
#include <vector>
extern "C" {
#include <libavutil/opt.h>
}
#include "warning-enable.hpp"
using namespace streamfx::encoder::ffmpeg;
struct strings {
struct quality {
static constexpr const char* ffmpeg = "quality";
static constexpr const char* obs = "Quality";
static constexpr const char* i18n = "Encoder.FFmpeg.CineForm.Quality";
};
};
cfhd::cfhd() : handler("cfhd") {}
bool cfhd::has_keyframes(ffmpeg_factory* factory)
{
return false;
}
void cfhd::properties(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props)
{
// Try and acquire a valid context.
std::shared_ptr<AVCodecContext> ctx;
if (instance) {
ctx = std::shared_ptr<AVCodecContext>(instance->get_avcodeccontext(), [](AVCodecContext*) {});
} else { // If we don't have a context, create a temporary one that is automatically freed.
ctx = std::shared_ptr<AVCodecContext>(avcodec_alloc_context3(factory->get_avcodec()), [](AVCodecContext* v) { avcodec_free_context(&v); });
if (!ctx->priv_data) {
return;
}
}
{ // Quality parameter
auto to_string = [](const char* v) {
char buffer[1024];
snprintf(buffer, sizeof(buffer), "%s.%s", strings::quality::i18n, v);
return D_TRANSLATE(buffer);
};
auto p = obs_properties_add_list(props, strings::quality::obs, D_TRANSLATE(strings::quality::i18n), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
streamfx::ffmpeg::tools::avoption_list_add_entries(ctx->priv_data, strings::quality::ffmpeg, [&p, &to_string](const AVOption* opt) {
// FFmpeg returns this list in the wrong order. We want to start at the lowest, and go to the highest.
// So simply always insert at the top, and this will reverse the list.
obs_property_list_insert_string(p, 0, to_string(opt->name), opt->name);
});
}
}
std::string cfhd::help(ffmpeg_factory* factory)
{
return "https://github.com/Xaymar/obs-StreamFX/wiki/Encoder-FFmpeg-GoPro-CineForm";
}
void cfhd::defaults(ffmpeg_factory* factory, obs_data_t* settings)
{
obs_data_set_string(settings, strings::quality::obs, "film3+");
}
void cfhd::migrate(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings, uint64_t version) {}
void cfhd::update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings)
{
if (const char* v = obs_data_get_string(settings, strings::quality::obs); v && (v[0] != '\0')) {
av_opt_set(instance->get_avcodeccontext()->priv_data, strings::quality::ffmpeg, v, AV_OPT_SEARCH_CHILDREN);
}
}
static cfhd handler = cfhd();

View File

@ -0,0 +1,26 @@
// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
#pragma once
#include "handler.hpp"
namespace streamfx::encoder::ffmpeg {
class cfhd : public handler {
public:
cfhd();
virtual ~cfhd(){};
bool has_keyframes(ffmpeg_factory* factory) override;
std::string help(ffmpeg_factory* factory) override;
void defaults(ffmpeg_factory* factory, obs_data_t* settings) override;
void properties(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props) override;
void migrate(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings, uint64_t version) override;
void update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings) override;
};
} // namespace streamfx::encoder::ffmpeg

View File

@ -48,16 +48,17 @@ void dnxhd::defaults(ffmpeg_factory* factory, obs_data_t* settings)
void dnxhd::properties(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_properties_t* props)
{
AVCodecContext* ctx = instance->get_avcodeccontext();
//Create dummy context if null was passed to the function
if (!ctx) {
ctx = avcodec_alloc_context3(factory->get_avcodec());
// Try and acquire a valid context.
std::shared_ptr<AVCodecContext> ctx;
if (instance) {
ctx = std::shared_ptr<AVCodecContext>(instance->get_avcodeccontext(), [](AVCodecContext*) {});
} else { // If we don't have a context, create a temporary one that is automatically freed.
ctx = std::shared_ptr<AVCodecContext>(avcodec_alloc_context3(factory->get_avcodec()), [](AVCodecContext* v) { avcodec_free_context(&v); });
if (!ctx->priv_data) {
avcodec_free_context(&ctx);
return;
}
}
auto p = obs_properties_add_list(props, S_CODEC_DNXHR_PROFILE, D_TRANSLATE(S_CODEC_DNXHR_PROFILE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
streamfx::ffmpeg::tools::avoption_list_add_entries(ctx->priv_data, "profile", [&p](const AVOption* opt) {
@ -72,11 +73,6 @@ void dnxhd::properties(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_p
//Therefore, new entries will always be inserted at the top, effectively reversing the list
obs_property_list_insert_string(p, 0, dnx_profile_to_display_name(opt->name), opt->name);
});
//Free context if we created it here
if (ctx && ctx != instance->get_avcodeccontext()) {
avcodec_free_context(&ctx);
}
}
void dnxhd::update(ffmpeg_factory* factory, ffmpeg_instance* instance, obs_data_t* settings)

View File

@ -136,7 +136,6 @@ MODULE_EXPORT void obs_module_unload(void)
_streamfx_gfx_opengl.reset();
}
DLOG_INFO("Unloaded Version %s", STREAMFX_VERSION_STRING);
} catch (std::exception const& ex) {
DLOG_ERROR("Unexpected exception in function '%s': %s", __FUNCTION_NAME__, ex.what());

View File

@ -4,9 +4,25 @@
#include "warning-disable.hpp"
#include <Windows.h>
#include <mutex>
#include "warning-enable.hpp"
BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID)
std::shared_ptr<void> local_mutex;
std::shared_ptr<void> global_mutex;
BOOL WINAPI DllMain(HINSTANCE, DWORD dwReason, LPVOID)
{
if (dwReason == DLL_PROCESS_ATTACH) {
// Prevent installer from progressing while StreamFX is still active.
local_mutex = std::shared_ptr<void>(CreateMutexW(NULL, TRUE, L"Local\\StreamFX-Setup"), [](HANDLE p) {
ReleaseMutex(p);
CloseHandle(p);
});
global_mutex = std::shared_ptr<void>(CreateMutexW(NULL, TRUE, L"Global\\StreamFX-Setup"), [](HANDLE p) {
ReleaseMutex(p);
CloseHandle(p);
});
}
return TRUE;
}

View File

@ -118,7 +118,7 @@ function user32_SendTextMessageTimeoutA(
uTimeout: UINT;
out lpdwResult: DWORD): LRESULT;
external 'SendMessageTimeoutA@user32.dll stdcall';
procedure RefreshEnvironment();
var
S: AnsiString;
@ -238,7 +238,10 @@ var
begin
// Attempt to remove old version if it exists.
if (IsUpgrade()) then begin
UninstallOldVersion();
if (UninstallOldVersion() <> 0) then begin
Result := 'Removal of older @PROJECT_NAME@ version was cancelled. Unable to continue with normal installation process, Setup will now abort.';
exit;
end;
end;
// Also ensure that we have the necessary prerequisites installed to run the program.
@ -247,11 +250,11 @@ begin
// If this is a User install, register the necessary environment changes.
if (IsUserMode()) then begin
sPluginsPath := ExpandConstant('{userpf}\obs-studio\plugins\%module%\bin\');
sPluginsPath := ExpandConstant('{usercf}\obs-studio\plugins\%module%\bin\');
StringChangeEx(sPluginsPath, '\', '/', True);
RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'OBS_PLUGINS_PATH', sPluginsPath);
sPluginsDataPath := ExpandConstant('{userpf}\obs-studio\plugins\%module%\data\');
sPluginsDataPath := ExpandConstant('{usercf}\obs-studio\plugins\%module%\data\');
StringChangeEx(sPluginsDataPath, '\', '/', True);
RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'OBS_PLUGINS_DATA_PATH', sPluginsDataPath);
@ -268,8 +271,8 @@ begin
if (IsSystemMode()) then begin
// Default to ProgramData/obs-studio/@PROJECT_NAME@
Result := ExpandConstant('{commonappdata}\obs-studio\plugins\@PROJECT_NAME@');
end else if (IsUserMode()) then begin
Result := ExpandConstant('{userpf}\obs-studio\plugins\@PROJECT_NAME@');
end else if (IsUserMode()) then begin
Result := ExpandConstant('{usercf}\obs-studio\plugins\@PROJECT_NAME@');
end else begin
// If a path was given as an argument, use it.
if (Value <> '') then begin
@ -340,7 +343,7 @@ begin
Result := '';
if (RegQueryStringValue(HKCU64, AppRegistryKey(), 'UninstallString', sPath)) then begin
Result := sPath;
end;
end;
end;
function IsUserInstallPresent(): Boolean;
@ -404,7 +407,7 @@ begin
sUninstallerPath := GetUninstallerPath();
if (sUninstallerPath <> '') then begin
sUninstallerPath := RemoveQuotes(sUninstallerPath);
if Exec(sUninstallerPath, '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, iResultCode) then begin
if Exec(sUninstallerPath, '', '', SW_HIDE, ewWaitUntilTerminated, iResultCode) then begin
Result := iResultCode
end else begin
Result := 1
@ -440,7 +443,7 @@ begin
bIsSystemMode := False;
bIsUserMode := True;
bIsPortableMode := False;
WizardSelectComponents('startmenu');
end;
end;
@ -453,7 +456,7 @@ begin
bIsSystemMode := False;
bIsUserMode := False;
bIsPortableMode := True;
WizardSelectComponents('!startmenu');
end;
@ -521,7 +524,7 @@ begin
oSystemText.WordWrap := True
oSystemText.Caption := 'Install for all users of this System, which will require Administrator rights for future updates. May cause problems with Portable and Current User installations.';
oSystemText.OnClick := @OnModePageSystemChoiceClick;
// Not available without Administrator rights.
if (not IsAdmin()) then begin
oSystemWarningText := TLabel.Create(oSystemPanel);
@ -598,7 +601,7 @@ begin
oUserText.WordWrap := True
oUserText.Caption := 'Install for the current user only, which will allow you to use @PROJECT_NAME@. Updating will not require Administrator rights.';
oUserText.OnClick := @OnModePageUserChoiceClick;
// Not available with Administrator rights.
if (IsAdmin()) then begin
oUserWarningText := TLabel.Create(oUserPanel);
@ -680,7 +683,7 @@ begin
oPortableText.WordWrap := True
oPortableText.Caption := 'Install for a portable OBS Studio environment.';
oPortableText.OnClick := @OnModePagePortableChoiceClick;
// Warn about Administrator rights
if (IsAdmin()) then begin
oPortableWarningText := TLabel.Create(oPortablePanel);