|
|
@@ -4,12 +4,17 @@
|
|
|
#define TIMING_TIME 0
|
|
|
#define TIMING_FRAME 1
|
|
|
|
|
|
+#define MATTE_LAYOUT_HORIZONTAL 0
|
|
|
+#define MATTE_LAYOUT_VERTICAL 1
|
|
|
+#define MATTE_LAYOUT_SEPARATE_FILE 2
|
|
|
+
|
|
|
enum fade_style { FADE_STYLE_FADE_OUT_FADE_IN, FADE_STYLE_CROSS_FADE };
|
|
|
|
|
|
struct stinger_info {
|
|
|
obs_source_t *source;
|
|
|
|
|
|
obs_source_t *media_source;
|
|
|
+ obs_source_t *matte_source;
|
|
|
|
|
|
uint64_t duration_ns;
|
|
|
uint64_t duration_frames;
|
|
|
@@ -23,6 +28,20 @@ struct stinger_info {
|
|
|
int monitoring_type;
|
|
|
enum fade_style fade_style;
|
|
|
|
|
|
+ bool track_matte_enabled;
|
|
|
+ int matte_layout;
|
|
|
+ float matte_width_factor;
|
|
|
+ float matte_height_factor;
|
|
|
+ bool invert_matte;
|
|
|
+
|
|
|
+ gs_effect_t *matte_effect;
|
|
|
+ gs_eparam_t *ep_a_tex;
|
|
|
+ gs_eparam_t *ep_b_tex;
|
|
|
+ gs_eparam_t *ep_matte_tex;
|
|
|
+ gs_eparam_t *ep_invert_matte;
|
|
|
+
|
|
|
+ gs_texrender_t *matte_tex;
|
|
|
+
|
|
|
float (*mix_a)(void *data, float t);
|
|
|
float (*mix_b)(void *data, float t);
|
|
|
};
|
|
|
@@ -67,6 +86,36 @@ static void stinger_update(void *data, obs_data_t *settings)
|
|
|
else
|
|
|
s->transition_point_ns = (uint64_t)(point * 1000000LL);
|
|
|
|
|
|
+ s->track_matte_enabled =
|
|
|
+ obs_data_get_bool(settings, "track_matte_enabled");
|
|
|
+ s->matte_layout = obs_data_get_int(settings, "track_matte_layout");
|
|
|
+ s->matte_width_factor =
|
|
|
+ (s->matte_layout == MATTE_LAYOUT_HORIZONTAL ? 2.0f : 1.0f);
|
|
|
+ s->matte_height_factor =
|
|
|
+ (s->matte_layout == MATTE_LAYOUT_VERTICAL ? 2.0f : 1.0f);
|
|
|
+ s->invert_matte = obs_data_get_bool(settings, "invert_matte");
|
|
|
+
|
|
|
+ if (s->matte_source) {
|
|
|
+ obs_source_release(s->matte_source);
|
|
|
+ s->matte_source = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (s->track_matte_enabled &&
|
|
|
+ s->matte_layout == MATTE_LAYOUT_SEPARATE_FILE) {
|
|
|
+ const char *tm_path =
|
|
|
+ obs_data_get_string(settings, "track_matte_path");
|
|
|
+
|
|
|
+ obs_data_t *tm_media_settings = obs_data_create();
|
|
|
+ obs_data_set_string(tm_media_settings, "local_file", tm_path);
|
|
|
+
|
|
|
+ s->matte_source = obs_source_create_private(
|
|
|
+ "ffmpeg_source", NULL, tm_media_settings);
|
|
|
+ obs_data_release(tm_media_settings);
|
|
|
+
|
|
|
+ // no need to output sound from the matte video
|
|
|
+ obs_source_set_muted(s->matte_source, true);
|
|
|
+ }
|
|
|
+
|
|
|
s->monitoring_type =
|
|
|
(int)obs_data_get_int(settings, "audio_monitoring");
|
|
|
obs_source_set_monitoring_type(s->media_source, s->monitoring_type);
|
|
|
@@ -95,6 +144,33 @@ static void *stinger_create(obs_data_t *settings, obs_source_t *source)
|
|
|
s->mix_a = mix_a_fade_in_out;
|
|
|
s->mix_b = mix_b_fade_in_out;
|
|
|
|
|
|
+ char *effect_file = obs_module_file("stinger_matte_transition.effect");
|
|
|
+ char *error_string = NULL;
|
|
|
+ obs_enter_graphics();
|
|
|
+ s->matte_effect =
|
|
|
+ gs_effect_create_from_file(effect_file, &error_string);
|
|
|
+ obs_leave_graphics();
|
|
|
+
|
|
|
+ if (!s->matte_effect) {
|
|
|
+ blog(LOG_ERROR,
|
|
|
+ "Could not open stinger_matte_transition.effect: %s",
|
|
|
+ error_string);
|
|
|
+ bfree(error_string);
|
|
|
+ bfree(s);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ bfree(effect_file);
|
|
|
+
|
|
|
+ s->ep_a_tex = gs_effect_get_param_by_name(s->matte_effect, "a_tex");
|
|
|
+ s->ep_b_tex = gs_effect_get_param_by_name(s->matte_effect, "b_tex");
|
|
|
+ s->ep_matte_tex =
|
|
|
+ gs_effect_get_param_by_name(s->matte_effect, "matte_tex");
|
|
|
+ s->ep_invert_matte =
|
|
|
+ gs_effect_get_param_by_name(s->matte_effect, "invert_matte");
|
|
|
+
|
|
|
+ s->matte_tex = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
|
|
|
+
|
|
|
obs_transition_enable_fixed(s->source, true, 0);
|
|
|
obs_source_update(source, settings);
|
|
|
return s;
|
|
|
@@ -104,6 +180,12 @@ static void stinger_destroy(void *data)
|
|
|
{
|
|
|
struct stinger_info *s = data;
|
|
|
obs_source_release(s->media_source);
|
|
|
+ obs_source_release(s->matte_source);
|
|
|
+
|
|
|
+ gs_texrender_destroy(s->matte_tex);
|
|
|
+
|
|
|
+ gs_effect_destroy(s->matte_effect);
|
|
|
+
|
|
|
bfree(s);
|
|
|
}
|
|
|
|
|
|
@@ -112,31 +194,92 @@ static void stinger_defaults(obs_data_t *settings)
|
|
|
obs_data_set_default_bool(settings, "hw_decode", true);
|
|
|
}
|
|
|
|
|
|
+static void stinger_matte_render(void *data, gs_texture_t *a, gs_texture_t *b,
|
|
|
+ float t, uint32_t cx, uint32_t cy)
|
|
|
+{
|
|
|
+ struct stinger_info *s = data;
|
|
|
+
|
|
|
+ struct vec4 background;
|
|
|
+ vec4_zero(&background);
|
|
|
+
|
|
|
+ obs_source_t *matte_source =
|
|
|
+ (s->matte_layout == MATTE_LAYOUT_SEPARATE_FILE
|
|
|
+ ? s->matte_source
|
|
|
+ : s->media_source);
|
|
|
+
|
|
|
+ float matte_cx = (float)obs_source_get_width(matte_source) /
|
|
|
+ s->matte_width_factor;
|
|
|
+ float matte_cy = (float)obs_source_get_height(matte_source) /
|
|
|
+ s->matte_height_factor;
|
|
|
+
|
|
|
+ float width_offset = (s->matte_layout == MATTE_LAYOUT_HORIZONTAL
|
|
|
+ ? (-matte_cx)
|
|
|
+ : 0.0f);
|
|
|
+ float height_offset =
|
|
|
+ (s->matte_layout == MATTE_LAYOUT_VERTICAL ? (-matte_cy) : 0.0f);
|
|
|
+
|
|
|
+ // Track matte media render
|
|
|
+ gs_texrender_reset(s->matte_tex);
|
|
|
+ if (matte_cx > 0 && matte_cy > 0) {
|
|
|
+ float scale_x = (float)cx / matte_cx;
|
|
|
+ float scale_y = (float)cy / matte_cy;
|
|
|
+
|
|
|
+ if (gs_texrender_begin(s->matte_tex, cx, cy)) {
|
|
|
+ gs_matrix_push();
|
|
|
+ gs_matrix_scale3f(scale_x, scale_y, 1.0f);
|
|
|
+ gs_matrix_translate3f(width_offset, height_offset,
|
|
|
+ 0.0f);
|
|
|
+ gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0);
|
|
|
+ obs_source_video_render(matte_source);
|
|
|
+ gs_matrix_pop();
|
|
|
+
|
|
|
+ gs_texrender_end(s->matte_tex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ gs_effect_set_texture(s->ep_a_tex, a);
|
|
|
+ gs_effect_set_texture(s->ep_b_tex, b);
|
|
|
+ gs_effect_set_texture(s->ep_matte_tex,
|
|
|
+ gs_texrender_get_texture(s->matte_tex));
|
|
|
+ gs_effect_set_bool(s->ep_invert_matte, s->invert_matte);
|
|
|
+
|
|
|
+ while (gs_effect_loop(s->matte_effect, "StingerMatte"))
|
|
|
+ gs_draw_sprite(NULL, 0, cx, cy);
|
|
|
+
|
|
|
+ UNUSED_PARAMETER(t);
|
|
|
+}
|
|
|
+
|
|
|
static void stinger_video_render(void *data, gs_effect_t *effect)
|
|
|
{
|
|
|
struct stinger_info *s = data;
|
|
|
|
|
|
- float t = obs_transition_get_time(s->source);
|
|
|
- bool use_a = t < s->transition_point;
|
|
|
+ if (s->track_matte_enabled) {
|
|
|
+ obs_transition_video_render(s->source, stinger_matte_render);
|
|
|
+ } else {
|
|
|
+ float t = obs_transition_get_time(s->source);
|
|
|
+ bool use_a = t < s->transition_point;
|
|
|
|
|
|
- enum obs_transition_target target = use_a ? OBS_TRANSITION_SOURCE_A
|
|
|
- : OBS_TRANSITION_SOURCE_B;
|
|
|
+ enum obs_transition_target target =
|
|
|
+ use_a ? OBS_TRANSITION_SOURCE_A
|
|
|
+ : OBS_TRANSITION_SOURCE_B;
|
|
|
|
|
|
- if (!obs_transition_video_render_direct(s->source, target))
|
|
|
- return;
|
|
|
+ if (!obs_transition_video_render_direct(s->source, target))
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
/* --------------------- */
|
|
|
|
|
|
float source_cx = (float)obs_source_get_width(s->source);
|
|
|
float source_cy = (float)obs_source_get_height(s->source);
|
|
|
+
|
|
|
uint32_t media_cx = obs_source_get_width(s->media_source);
|
|
|
uint32_t media_cy = obs_source_get_height(s->media_source);
|
|
|
|
|
|
if (!media_cx || !media_cy)
|
|
|
return;
|
|
|
|
|
|
- float scale_x = source_cx / (float)media_cx;
|
|
|
- float scale_y = source_cy / (float)media_cy;
|
|
|
+ float scale_x = source_cx / ((float)media_cx / s->matte_width_factor);
|
|
|
+ float scale_y = source_cy / ((float)media_cy / s->matte_height_factor);
|
|
|
|
|
|
gs_matrix_push();
|
|
|
gs_matrix_scale3f(scale_x, scale_y, 1.0f);
|
|
|
@@ -184,6 +327,10 @@ static bool stinger_audio_render(void *data, uint64_t *ts_out,
|
|
|
struct stinger_info *s = data;
|
|
|
uint64_t ts = 0;
|
|
|
|
|
|
+ if (!s) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
if (!obs_source_audio_pending(s->media_source)) {
|
|
|
ts = obs_source_get_audio_timestamp(s->media_source);
|
|
|
if (!ts)
|
|
|
@@ -229,9 +376,14 @@ static void stinger_transition_start(void *data)
|
|
|
|
|
|
proc_handler_t *ph =
|
|
|
obs_source_get_proc_handler(s->media_source);
|
|
|
+ proc_handler_t *matte_ph =
|
|
|
+ obs_source_get_proc_handler(s->matte_source);
|
|
|
|
|
|
if (s->transitioning) {
|
|
|
proc_handler_call(ph, "restart", &cd);
|
|
|
+ if (matte_ph) {
|
|
|
+ proc_handler_call(matte_ph, "restart", &cd);
|
|
|
+ }
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -258,6 +410,18 @@ static void stinger_transition_start(void *data)
|
|
|
s->transition_a_mul = (1.0f / s->transition_point);
|
|
|
s->transition_b_mul = (1.0f / (1.0f - s->transition_point));
|
|
|
|
|
|
+ if (s->track_matte_enabled) {
|
|
|
+ proc_handler_call(matte_ph, "get_duration", &cd);
|
|
|
+ uint64_t tm_duration_ns =
|
|
|
+ (uint64_t)calldata_int(&cd, "duration");
|
|
|
+
|
|
|
+ s->duration_ns = ((tm_duration_ns > s->duration_ns)
|
|
|
+ ? (tm_duration_ns)
|
|
|
+ : (s->duration_ns));
|
|
|
+
|
|
|
+ obs_source_add_active_child(s->source, s->matte_source);
|
|
|
+ }
|
|
|
+
|
|
|
obs_transition_enable_fixed(
|
|
|
s->source, true, (uint32_t)(s->duration_ns / 1000000));
|
|
|
|
|
|
@@ -276,6 +440,9 @@ static void stinger_transition_stop(void *data)
|
|
|
if (s->media_source)
|
|
|
obs_source_remove_active_child(s->source, s->media_source);
|
|
|
|
|
|
+ if (s->matte_source)
|
|
|
+ obs_source_remove_active_child(s->source, s->matte_source);
|
|
|
+
|
|
|
s->transitioning = false;
|
|
|
}
|
|
|
|
|
|
@@ -286,6 +453,9 @@ static void stinger_enum_active_sources(void *data,
|
|
|
struct stinger_info *s = data;
|
|
|
if (s->media_source && s->transitioning)
|
|
|
enum_callback(s->source, s->media_source, param);
|
|
|
+
|
|
|
+ if (s->matte_source && s->transitioning)
|
|
|
+ enum_callback(s->source, s->matte_source, param);
|
|
|
}
|
|
|
|
|
|
static void stinger_enum_all_sources(void *data,
|
|
|
@@ -295,6 +465,9 @@ static void stinger_enum_all_sources(void *data,
|
|
|
struct stinger_info *s = data;
|
|
|
if (s->media_source)
|
|
|
enum_callback(s->source, s->media_source, param);
|
|
|
+
|
|
|
+ if (s->matte_source)
|
|
|
+ enum_callback(s->source, s->matte_source, param);
|
|
|
}
|
|
|
|
|
|
#define FILE_FILTER \
|
|
|
@@ -304,17 +477,56 @@ static bool transition_point_type_modified(obs_properties_t *ppts,
|
|
|
obs_property_t *p, obs_data_t *s)
|
|
|
{
|
|
|
int64_t type = obs_data_get_int(s, "tp_type");
|
|
|
- p = obs_properties_get(ppts, "transition_point");
|
|
|
+
|
|
|
+ obs_property_t *prop_transition_point =
|
|
|
+ obs_properties_get(ppts, "transition_point");
|
|
|
|
|
|
if (type == TIMING_TIME) {
|
|
|
obs_property_set_description(
|
|
|
- p, obs_module_text("TransitionPoint"));
|
|
|
- obs_property_int_set_suffix(p, " ms");
|
|
|
+ prop_transition_point,
|
|
|
+ obs_module_text("TransitionPoint"));
|
|
|
+ } else {
|
|
|
+ obs_property_set_description(
|
|
|
+ prop_transition_point,
|
|
|
+ obs_module_text("TransitionPointFrame"));
|
|
|
+ }
|
|
|
+
|
|
|
+ bool uses_ms_prefix = (type == TIMING_TIME);
|
|
|
+ obs_property_int_set_suffix(p, (uses_ms_prefix ? " ms" : ""));
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static bool track_matte_layout_modified(obs_properties_t *ppts,
|
|
|
+ obs_property_t *p, obs_data_t *s)
|
|
|
+{
|
|
|
+ int matte_layout = obs_data_get_int(s, "track_matte_layout");
|
|
|
+ obs_property_t *prop_matte_path =
|
|
|
+ obs_properties_get(ppts, "track_matte_path");
|
|
|
+
|
|
|
+ bool uses_separate_file = (matte_layout == MATTE_LAYOUT_SEPARATE_FILE);
|
|
|
+ obs_property_set_visible(prop_matte_path, uses_separate_file);
|
|
|
+
|
|
|
+ UNUSED_PARAMETER(p);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static bool track_matte_enabled_modified(obs_properties_t *ppts,
|
|
|
+ obs_property_t *p, obs_data_t *s)
|
|
|
+{
|
|
|
+ bool track_matte_enabled = obs_data_get_bool(s, "track_matte_enabled");
|
|
|
+ obs_property_t *prop_tp_type = obs_properties_get(ppts, "tp_type");
|
|
|
+
|
|
|
+ if (track_matte_enabled) {
|
|
|
+ obs_property_set_description(
|
|
|
+ prop_tp_type,
|
|
|
+ obs_module_text("AudioTransitionPointType"));
|
|
|
} else {
|
|
|
obs_property_set_description(
|
|
|
- p, obs_module_text("TransitionPointFrame"));
|
|
|
- obs_property_int_set_suffix(p, "");
|
|
|
+ prop_tp_type, obs_module_text("TransitionPointType"));
|
|
|
}
|
|
|
+
|
|
|
+ UNUSED_PARAMETER(p);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@@ -324,8 +536,10 @@ static obs_properties_t *stinger_properties(void *data)
|
|
|
|
|
|
obs_properties_set_flags(ppts, OBS_PROPERTIES_DEFER_UPDATE);
|
|
|
|
|
|
+ // main stinger settings
|
|
|
obs_properties_add_path(ppts, "path", obs_module_text("VideoFile"),
|
|
|
OBS_PATH_FILE, FILE_FILTER, NULL);
|
|
|
+
|
|
|
obs_property_t *p = obs_properties_add_list(
|
|
|
ppts, "tp_type", obs_module_text("TransitionPointType"),
|
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
|
@@ -344,6 +558,45 @@ static obs_properties_t *stinger_properties(void *data)
|
|
|
obs_module_text("TransitionPoint"), 0, 120000,
|
|
|
1);
|
|
|
|
|
|
+ // track matte properties
|
|
|
+ {
|
|
|
+ obs_properties_t *track_matte_group = obs_properties_create();
|
|
|
+
|
|
|
+ p = obs_properties_add_list(track_matte_group,
|
|
|
+ "track_matte_layout",
|
|
|
+ obs_module_text("TrackMatteLayout"),
|
|
|
+ OBS_COMBO_TYPE_LIST,
|
|
|
+ OBS_COMBO_FORMAT_INT);
|
|
|
+ obs_property_list_add_int(
|
|
|
+ p, obs_module_text("TrackMatteLayoutHorizontal"),
|
|
|
+ MATTE_LAYOUT_HORIZONTAL);
|
|
|
+ obs_property_list_add_int(
|
|
|
+ p, obs_module_text("TrackMatteLayoutVertical"),
|
|
|
+ MATTE_LAYOUT_VERTICAL);
|
|
|
+ obs_property_list_add_int(
|
|
|
+ p, obs_module_text("TrackMatteLayoutSeparateFile"),
|
|
|
+ MATTE_LAYOUT_SEPARATE_FILE);
|
|
|
+
|
|
|
+ obs_property_set_modified_callback(p,
|
|
|
+ track_matte_layout_modified);
|
|
|
+
|
|
|
+ obs_properties_add_path(track_matte_group, "track_matte_path",
|
|
|
+ obs_module_text("TrackMatteVideoFile"),
|
|
|
+ OBS_PATH_FILE, FILE_FILTER, NULL);
|
|
|
+
|
|
|
+ obs_properties_add_bool(track_matte_group, "invert_matte",
|
|
|
+ obs_module_text("InvertTrackMatte"));
|
|
|
+
|
|
|
+ p = obs_properties_add_group(
|
|
|
+ ppts, "track_matte_enabled",
|
|
|
+ obs_module_text("TrackMatteEnabled"),
|
|
|
+ OBS_GROUP_CHECKABLE, track_matte_group);
|
|
|
+
|
|
|
+ obs_property_set_modified_callback(
|
|
|
+ p, track_matte_enabled_modified);
|
|
|
+ }
|
|
|
+
|
|
|
+ // audio output settings
|
|
|
obs_property_t *monitor_list = obs_properties_add_list(
|
|
|
ppts, "audio_monitoring", obs_module_text("AudioMonitoring"),
|
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
|
@@ -357,6 +610,7 @@ static obs_properties_t *stinger_properties(void *data)
|
|
|
obs_module_text("AudioMonitoring.Both"),
|
|
|
OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT);
|
|
|
|
|
|
+ // audio fade settings
|
|
|
obs_property_t *audio_fade_style = obs_properties_add_list(
|
|
|
ppts, "audio_fade_style", obs_module_text("AudioFadeStyle"),
|
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|