transition-stinger.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911
  1. #include <obs-module.h>
  2. #include <util/dstr.h>
  3. #include "util/platform.h"
  4. #define TIMING_TIME 0
  5. #define TIMING_FRAME 1
  6. enum matte_layout {
  7. MATTE_LAYOUT_HORIZONTAL,
  8. MATTE_LAYOUT_VERTICAL,
  9. MATTE_LAYOUT_SEPARATE_FILE,
  10. MATTE_LAYOUT_MASK,
  11. };
  12. enum fade_style { FADE_STYLE_FADE_OUT_FADE_IN, FADE_STYLE_CROSS_FADE };
  13. struct stinger_info {
  14. obs_source_t *source;
  15. obs_source_t *media_source;
  16. obs_source_t *matte_source;
  17. uint64_t duration_ns;
  18. uint64_t duration_frames;
  19. uint64_t transition_point_ns;
  20. uint64_t transition_point_frame;
  21. float transition_point;
  22. float transition_a_mul;
  23. float transition_b_mul;
  24. bool transitioning;
  25. bool transition_point_is_frame;
  26. int monitoring_type;
  27. enum fade_style fade_style;
  28. bool track_matte_enabled;
  29. int matte_layout;
  30. float matte_width_factor;
  31. float matte_height_factor;
  32. bool invert_matte;
  33. bool do_texrender;
  34. bool matte_rendered;
  35. gs_effect_t *matte_effect;
  36. gs_eparam_t *ep_a_tex;
  37. gs_eparam_t *ep_b_tex;
  38. gs_eparam_t *ep_matte_tex;
  39. gs_eparam_t *ep_invert_matte;
  40. gs_texrender_t *matte_tex;
  41. gs_texrender_t *stinger_tex;
  42. float (*mix_a)(void *data, float t);
  43. float (*mix_b)(void *data, float t);
  44. };
  45. static const char *stinger_get_name(void *type_data)
  46. {
  47. UNUSED_PARAMETER(type_data);
  48. return obs_module_text("StingerTransition");
  49. }
  50. static float mix_a_fade_in_out(void *data, float t);
  51. static float mix_b_fade_in_out(void *data, float t);
  52. static float mix_a_cross_fade(void *data, float t);
  53. static float mix_b_cross_fade(void *data, float t);
  54. static void stinger_update(void *data, obs_data_t *settings)
  55. {
  56. struct stinger_info *s = data;
  57. const char *path = obs_data_get_string(settings, "path");
  58. bool hw_decode = obs_data_get_bool(settings, "hw_decode");
  59. obs_data_t *media_settings = obs_data_create();
  60. obs_data_set_string(media_settings, "local_file", path);
  61. obs_data_set_bool(media_settings, "hw_decode", hw_decode);
  62. obs_data_set_bool(media_settings, "looping", false);
  63. obs_source_release(s->media_source);
  64. struct dstr name;
  65. dstr_init_copy(&name, obs_source_get_name(s->source));
  66. dstr_cat(&name, " (Stinger)");
  67. s->media_source = obs_source_create_private("ffmpeg_source", name.array,
  68. media_settings);
  69. dstr_free(&name);
  70. obs_data_release(media_settings);
  71. int64_t point = obs_data_get_int(settings, "transition_point");
  72. s->transition_point_is_frame = obs_data_get_int(settings, "tp_type") ==
  73. TIMING_FRAME;
  74. if (s->transition_point_is_frame)
  75. s->transition_point_frame = (uint64_t)point;
  76. else
  77. s->transition_point_ns = (uint64_t)(point * 1000000LL);
  78. bool track_matte_was_enabled = s->track_matte_enabled;
  79. s->track_matte_enabled =
  80. obs_data_get_bool(settings, "track_matte_enabled");
  81. s->matte_layout = (int)obs_data_get_int(settings, "track_matte_layout");
  82. s->matte_width_factor =
  83. (s->matte_layout == MATTE_LAYOUT_HORIZONTAL ? 2.0f : 1.0f);
  84. s->matte_height_factor =
  85. (s->matte_layout == MATTE_LAYOUT_VERTICAL ? 2.0f : 1.0f);
  86. s->invert_matte = obs_data_get_bool(settings, "invert_matte");
  87. s->do_texrender = s->track_matte_enabled &&
  88. s->matte_layout < MATTE_LAYOUT_SEPARATE_FILE;
  89. if (s->matte_source) {
  90. obs_source_release(s->matte_source);
  91. s->matte_source = NULL;
  92. }
  93. if (s->track_matte_enabled &&
  94. s->matte_layout == MATTE_LAYOUT_SEPARATE_FILE) {
  95. const char *tm_path =
  96. obs_data_get_string(settings, "track_matte_path");
  97. obs_data_t *tm_media_settings = obs_data_create();
  98. obs_data_set_string(tm_media_settings, "local_file", tm_path);
  99. obs_data_set_bool(tm_media_settings, "looping", false);
  100. s->matte_source = obs_source_create_private(
  101. "ffmpeg_source", NULL, tm_media_settings);
  102. obs_data_release(tm_media_settings);
  103. // no need to output sound from the matte video
  104. obs_source_set_muted(s->matte_source, true);
  105. }
  106. s->monitoring_type =
  107. (int)obs_data_get_int(settings, "audio_monitoring");
  108. obs_source_set_monitoring_type(s->media_source, s->monitoring_type);
  109. s->fade_style =
  110. (enum fade_style)obs_data_get_int(settings, "audio_fade_style");
  111. switch (s->fade_style) {
  112. default:
  113. case FADE_STYLE_FADE_OUT_FADE_IN:
  114. s->mix_a = mix_a_fade_in_out;
  115. s->mix_b = mix_b_fade_in_out;
  116. break;
  117. case FADE_STYLE_CROSS_FADE:
  118. s->mix_a = mix_a_cross_fade;
  119. s->mix_b = mix_b_cross_fade;
  120. break;
  121. }
  122. if (s->track_matte_enabled != track_matte_was_enabled) {
  123. obs_enter_graphics();
  124. gs_texrender_destroy(s->matte_tex);
  125. gs_texrender_destroy(s->stinger_tex);
  126. s->matte_tex = NULL;
  127. s->stinger_tex = NULL;
  128. if (s->track_matte_enabled) {
  129. s->matte_tex = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
  130. s->stinger_tex =
  131. gs_texrender_create(GS_RGBA, GS_ZS_NONE);
  132. }
  133. obs_leave_graphics();
  134. }
  135. }
  136. static void *stinger_create(obs_data_t *settings, obs_source_t *source)
  137. {
  138. struct stinger_info *s = bzalloc(sizeof(*s));
  139. s->source = source;
  140. s->mix_a = mix_a_fade_in_out;
  141. s->mix_b = mix_b_fade_in_out;
  142. char *effect_file = obs_module_file("stinger_matte_transition.effect");
  143. char *error_string = NULL;
  144. obs_enter_graphics();
  145. s->matte_effect =
  146. gs_effect_create_from_file(effect_file, &error_string);
  147. obs_leave_graphics();
  148. if (!s->matte_effect) {
  149. blog(LOG_ERROR,
  150. "Could not open stinger_matte_transition.effect: %s",
  151. error_string);
  152. bfree(error_string);
  153. bfree(s);
  154. return NULL;
  155. }
  156. bfree(effect_file);
  157. s->ep_a_tex = gs_effect_get_param_by_name(s->matte_effect, "a_tex");
  158. s->ep_b_tex = gs_effect_get_param_by_name(s->matte_effect, "b_tex");
  159. s->ep_matte_tex =
  160. gs_effect_get_param_by_name(s->matte_effect, "matte_tex");
  161. s->ep_invert_matte =
  162. gs_effect_get_param_by_name(s->matte_effect, "invert_matte");
  163. obs_transition_enable_fixed(s->source, true, 0);
  164. obs_source_update(source, settings);
  165. return s;
  166. }
  167. static void stinger_destroy(void *data)
  168. {
  169. struct stinger_info *s = data;
  170. obs_source_release(s->media_source);
  171. obs_source_release(s->matte_source);
  172. obs_enter_graphics();
  173. gs_texrender_destroy(s->matte_tex);
  174. gs_texrender_destroy(s->stinger_tex);
  175. gs_effect_destroy(s->matte_effect);
  176. obs_leave_graphics();
  177. bfree(s);
  178. }
  179. static void stinger_defaults(obs_data_t *settings)
  180. {
  181. obs_data_set_default_bool(settings, "hw_decode", true);
  182. }
  183. static void stinger_matte_render(void *data, gs_texture_t *a, gs_texture_t *b,
  184. float t, uint32_t cx, uint32_t cy)
  185. {
  186. struct stinger_info *s = data;
  187. struct vec4 background;
  188. vec4_zero(&background);
  189. obs_source_t *matte_source =
  190. (s->matte_layout == MATTE_LAYOUT_SEPARATE_FILE
  191. ? s->matte_source
  192. : s->media_source);
  193. float matte_cx = (float)obs_source_get_width(matte_source) /
  194. s->matte_width_factor;
  195. float matte_cy = (float)obs_source_get_height(matte_source) /
  196. s->matte_height_factor;
  197. float width_offset = (s->matte_layout == MATTE_LAYOUT_HORIZONTAL
  198. ? (-matte_cx)
  199. : 0.0f);
  200. float height_offset =
  201. (s->matte_layout == MATTE_LAYOUT_VERTICAL ? (-matte_cy) : 0.0f);
  202. // Track matte media render
  203. if (matte_cx > 0 && matte_cy > 0) {
  204. float scale_x = (float)cx / matte_cx;
  205. float scale_y = (float)cy / matte_cy;
  206. const enum gs_color_space space =
  207. obs_source_get_color_space(matte_source, 0, NULL);
  208. enum gs_color_format format = gs_get_format_from_space(space);
  209. if (gs_texrender_get_format(s->matte_tex) != format) {
  210. gs_texrender_destroy(s->matte_tex);
  211. s->matte_tex = gs_texrender_create(format, GS_ZS_NONE);
  212. }
  213. if (gs_texrender_begin_with_color_space(s->matte_tex, cx, cy,
  214. space)) {
  215. gs_matrix_scale3f(scale_x, scale_y, 1.0f);
  216. gs_matrix_translate3f(width_offset, height_offset,
  217. 0.0f);
  218. gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0);
  219. gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f,
  220. 100.0f);
  221. obs_source_video_render(matte_source);
  222. gs_texrender_end(s->matte_tex);
  223. }
  224. }
  225. const bool previous = gs_framebuffer_srgb_enabled();
  226. gs_enable_framebuffer_srgb(true);
  227. /* texture setters look reversed, but they aren't */
  228. const char *tech_name = "StingerMatte";
  229. if (gs_get_color_space() == GS_CS_SRGB) {
  230. /* users want nonlinear fade */
  231. gs_effect_set_texture(s->ep_a_tex, a);
  232. gs_effect_set_texture(s->ep_b_tex, b);
  233. } else {
  234. /* nonlinear fade is too wrong, so use linear fade */
  235. gs_effect_set_texture_srgb(s->ep_a_tex, a);
  236. gs_effect_set_texture_srgb(s->ep_b_tex, b);
  237. tech_name = "StingerMatteLinear";
  238. }
  239. gs_effect_set_texture(s->ep_matte_tex,
  240. gs_texrender_get_texture(s->matte_tex));
  241. gs_effect_set_bool(s->ep_invert_matte, s->invert_matte);
  242. while (gs_effect_loop(s->matte_effect, tech_name))
  243. gs_draw_sprite(NULL, 0, cx, cy);
  244. gs_enable_framebuffer_srgb(previous);
  245. UNUSED_PARAMETER(t);
  246. }
  247. static void stinger_texrender(struct stinger_info *s, uint32_t source_cx,
  248. uint32_t source_cy, uint32_t media_cx,
  249. uint32_t media_cy, enum gs_color_space space)
  250. {
  251. enum gs_color_format format = gs_get_format_from_space(space);
  252. if (gs_texrender_get_format(s->stinger_tex) != format) {
  253. gs_texrender_destroy(s->stinger_tex);
  254. s->stinger_tex = gs_texrender_create(format, GS_ZS_NONE);
  255. }
  256. if (gs_texrender_begin_with_color_space(s->stinger_tex, source_cx,
  257. source_cy, space)) {
  258. float cx = (float)media_cx / s->matte_width_factor;
  259. float cy = (float)media_cy / s->matte_height_factor;
  260. gs_ortho(0.0f, cx, 0.0f, cy, -100.0f, 100.0f);
  261. gs_blend_state_push();
  262. gs_enable_blending(false);
  263. obs_source_video_render(s->media_source);
  264. gs_blend_state_pop();
  265. gs_texrender_end(s->stinger_tex);
  266. }
  267. }
  268. static const char *
  269. get_tech_name_and_multiplier(enum gs_color_space current_space,
  270. enum gs_color_space source_space,
  271. float *multiplier)
  272. {
  273. const char *tech_name = "Draw";
  274. *multiplier = 1.f;
  275. switch (source_space) {
  276. case GS_CS_SRGB:
  277. case GS_CS_SRGB_16F:
  278. switch (current_space) {
  279. case GS_CS_709_SCRGB:
  280. tech_name = "DrawMultiply";
  281. *multiplier = obs_get_video_sdr_white_level() / 80.0f;
  282. }
  283. break;
  284. case GS_CS_709_EXTENDED:
  285. switch (current_space) {
  286. case GS_CS_SRGB:
  287. case GS_CS_SRGB_16F:
  288. tech_name = "DrawTonemap";
  289. break;
  290. case GS_CS_709_SCRGB:
  291. tech_name = "DrawMultiply";
  292. *multiplier = obs_get_video_sdr_white_level() / 80.0f;
  293. }
  294. break;
  295. case GS_CS_709_SCRGB:
  296. switch (current_space) {
  297. case GS_CS_SRGB:
  298. case GS_CS_SRGB_16F:
  299. tech_name = "DrawMultiplyTonemap";
  300. *multiplier = 80.0f / obs_get_video_sdr_white_level();
  301. break;
  302. case GS_CS_709_EXTENDED:
  303. tech_name = "DrawMultiply";
  304. *multiplier = 80.0f / obs_get_video_sdr_white_level();
  305. }
  306. }
  307. return tech_name;
  308. }
  309. static void stinger_video_render(void *data, gs_effect_t *effect)
  310. {
  311. struct stinger_info *s = data;
  312. uint32_t media_cx = obs_source_get_width(s->media_source);
  313. uint32_t media_cy = obs_source_get_height(s->media_source);
  314. if (s->track_matte_enabled) {
  315. bool ready = obs_source_active(s->media_source) && !!media_cx &&
  316. !!media_cy;
  317. if (ready) {
  318. if (!s->matte_rendered)
  319. s->matte_rendered = true;
  320. obs_transition_video_render(s->source,
  321. stinger_matte_render);
  322. } else {
  323. obs_transition_video_render_direct(
  324. s->source, s->matte_rendered
  325. ? OBS_TRANSITION_SOURCE_B
  326. : OBS_TRANSITION_SOURCE_A);
  327. }
  328. if (s->matte_layout == MATTE_LAYOUT_MASK)
  329. return;
  330. } else {
  331. float t = obs_transition_get_time(s->source);
  332. bool use_a = t < s->transition_point;
  333. enum obs_transition_target target =
  334. use_a ? OBS_TRANSITION_SOURCE_A
  335. : OBS_TRANSITION_SOURCE_B;
  336. if (!obs_transition_video_render_direct(s->source, target))
  337. return;
  338. }
  339. /* --------------------- */
  340. uint32_t source_cx = obs_source_get_width(s->source);
  341. uint32_t source_cy = obs_source_get_height(s->source);
  342. float source_cxf = (float)source_cx;
  343. float source_cyf = (float)source_cy;
  344. if (!media_cx || !media_cy)
  345. return;
  346. if (s->do_texrender) {
  347. const enum gs_color_space space =
  348. obs_source_get_color_space(s->media_source, 0, NULL);
  349. stinger_texrender(s, source_cx, source_cy, media_cx, media_cy,
  350. space);
  351. const bool previous = gs_framebuffer_srgb_enabled();
  352. gs_enable_framebuffer_srgb(true);
  353. float multiplier;
  354. const char *technique = get_tech_name_and_multiplier(
  355. gs_get_color_space(), space, &multiplier);
  356. gs_effect_t *e = obs_get_base_effect(OBS_EFFECT_DEFAULT);
  357. gs_eparam_t *p_image = gs_effect_get_param_by_name(e, "image");
  358. gs_eparam_t *p_multiplier =
  359. gs_effect_get_param_by_name(e, "multiplier");
  360. gs_texture_t *tex = gs_texrender_get_texture(s->stinger_tex);
  361. gs_effect_set_texture_srgb(p_image, tex);
  362. gs_effect_set_float(p_multiplier, multiplier);
  363. while (gs_effect_loop(e, technique))
  364. gs_draw_sprite(NULL, 0, source_cx, source_cy);
  365. gs_enable_framebuffer_srgb(previous);
  366. } else {
  367. const bool previous = gs_set_linear_srgb(true);
  368. gs_matrix_push();
  369. gs_matrix_scale3f(source_cxf / (float)media_cx,
  370. source_cyf / (float)media_cy, 1.0f);
  371. obs_source_video_render(s->media_source);
  372. gs_matrix_pop();
  373. gs_set_linear_srgb(previous);
  374. }
  375. UNUSED_PARAMETER(effect);
  376. }
  377. static void stinger_video_tick(void *data, float seconds)
  378. {
  379. struct stinger_info *s = data;
  380. if (s->track_matte_enabled) {
  381. gs_texrender_reset(s->stinger_tex);
  382. gs_texrender_reset(s->matte_tex);
  383. }
  384. UNUSED_PARAMETER(seconds);
  385. }
  386. static inline float calc_fade(float t, float mul)
  387. {
  388. t *= mul;
  389. return t > 1.0f ? 1.0f : t;
  390. }
  391. static float mix_a_fade_in_out(void *data, float t)
  392. {
  393. struct stinger_info *s = data;
  394. return 1.0f - calc_fade(t, s->transition_a_mul);
  395. }
  396. static float mix_b_fade_in_out(void *data, float t)
  397. {
  398. struct stinger_info *s = data;
  399. return 1.0f - calc_fade(1.0f - t, s->transition_b_mul);
  400. }
  401. static float mix_a_cross_fade(void *data, float t)
  402. {
  403. UNUSED_PARAMETER(data);
  404. return 1.0f - t;
  405. }
  406. static float mix_b_cross_fade(void *data, float t)
  407. {
  408. UNUSED_PARAMETER(data);
  409. return t;
  410. }
  411. static bool stinger_audio_render(void *data, uint64_t *ts_out,
  412. struct obs_source_audio_mix *audio,
  413. uint32_t mixers, size_t channels,
  414. size_t sample_rate)
  415. {
  416. struct stinger_info *s = data;
  417. uint64_t ts = 0;
  418. if (!s) {
  419. return false;
  420. }
  421. if (!obs_source_audio_pending(s->media_source)) {
  422. ts = obs_source_get_audio_timestamp(s->media_source);
  423. if (!ts)
  424. return false;
  425. }
  426. bool success = obs_transition_audio_render(s->source, ts_out, audio,
  427. mixers, channels,
  428. sample_rate, s->mix_a,
  429. s->mix_b);
  430. if (!ts)
  431. return success;
  432. if (!*ts_out || ts < *ts_out)
  433. *ts_out = ts;
  434. struct obs_source_audio_mix child_audio;
  435. obs_source_get_audio_mix(s->media_source, &child_audio);
  436. for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
  437. if ((mixers & (1 << mix)) == 0)
  438. continue;
  439. for (size_t ch = 0; ch < channels; ch++) {
  440. register float *out = audio->output[mix].data[ch];
  441. register float *in = child_audio.output[mix].data[ch];
  442. register float *end = in + AUDIO_OUTPUT_FRAMES;
  443. while (in < end)
  444. *(out++) += *(in++);
  445. }
  446. }
  447. return true;
  448. }
  449. static void stinger_transition_start(void *data)
  450. {
  451. struct stinger_info *s = data;
  452. if (s->media_source) {
  453. calldata_t cd = {0};
  454. proc_handler_t *ph =
  455. obs_source_get_proc_handler(s->media_source);
  456. proc_handler_t *matte_ph =
  457. s->matte_source
  458. ? obs_source_get_proc_handler(s->matte_source)
  459. : NULL;
  460. if (s->transitioning) {
  461. proc_handler_call(ph, "restart", &cd);
  462. if (matte_ph) {
  463. proc_handler_call(matte_ph, "restart", &cd);
  464. }
  465. return;
  466. }
  467. s->matte_rendered = false;
  468. proc_handler_call(ph, "get_duration", &cd);
  469. proc_handler_call(ph, "get_nb_frames", &cd);
  470. s->duration_ns =
  471. (uint64_t)calldata_int(&cd, "duration") + 250000000ULL;
  472. s->duration_frames = (uint64_t)calldata_int(&cd, "num_frames");
  473. if (s->transition_point_is_frame)
  474. s->transition_point =
  475. (float)((long double)s->transition_point_frame /
  476. (long double)s->duration_frames);
  477. else
  478. s->transition_point =
  479. (float)((long double)s->transition_point_ns /
  480. (long double)s->duration_ns);
  481. if (s->transition_point > 0.999f)
  482. s->transition_point = 0.999f;
  483. else if (s->transition_point < 0.001f)
  484. s->transition_point = 0.001f;
  485. s->transition_a_mul = (1.0f / s->transition_point);
  486. s->transition_b_mul = (1.0f / (1.0f - s->transition_point));
  487. if (s->track_matte_enabled && s->matte_source) {
  488. proc_handler_call(matte_ph, "get_duration", &cd);
  489. uint64_t tm_duration_ns =
  490. (uint64_t)calldata_int(&cd, "duration");
  491. s->duration_ns = ((tm_duration_ns > s->duration_ns)
  492. ? (tm_duration_ns)
  493. : (s->duration_ns));
  494. obs_source_add_active_child(s->source, s->matte_source);
  495. }
  496. obs_transition_enable_fixed(
  497. s->source, true, (uint32_t)(s->duration_ns / 1000000));
  498. calldata_free(&cd);
  499. obs_source_add_active_child(s->source, s->media_source);
  500. }
  501. s->transitioning = true;
  502. }
  503. static void stinger_transition_stop(void *data)
  504. {
  505. struct stinger_info *s = data;
  506. if (s->media_source)
  507. obs_source_remove_active_child(s->source, s->media_source);
  508. if (s->matte_source)
  509. obs_source_remove_active_child(s->source, s->matte_source);
  510. s->transitioning = false;
  511. }
  512. static void stinger_enum_active_sources(void *data,
  513. obs_source_enum_proc_t enum_callback,
  514. void *param)
  515. {
  516. struct stinger_info *s = data;
  517. if (s->media_source && s->transitioning)
  518. enum_callback(s->source, s->media_source, param);
  519. if (s->matte_source && s->transitioning)
  520. enum_callback(s->source, s->matte_source, param);
  521. }
  522. static void stinger_enum_all_sources(void *data,
  523. obs_source_enum_proc_t enum_callback,
  524. void *param)
  525. {
  526. struct stinger_info *s = data;
  527. if (s->media_source)
  528. enum_callback(s->source, s->media_source, param);
  529. if (s->matte_source)
  530. enum_callback(s->source, s->matte_source, param);
  531. }
  532. #define FILE_FILTER " (*.mp4 *.ts *.mov *.wmv *.flv *.mkv *.avi *.gif *.webm);;"
  533. static bool transition_point_type_modified(obs_properties_t *ppts,
  534. obs_property_t *p, obs_data_t *s)
  535. {
  536. int64_t type = obs_data_get_int(s, "tp_type");
  537. obs_property_t *prop_transition_point =
  538. obs_properties_get(ppts, "transition_point");
  539. if (type == TIMING_TIME) {
  540. obs_property_set_description(
  541. prop_transition_point,
  542. obs_module_text("TransitionPoint"));
  543. obs_property_int_set_suffix(prop_transition_point, " ms");
  544. } else {
  545. obs_property_set_description(
  546. prop_transition_point,
  547. obs_module_text("TransitionPointFrame"));
  548. obs_property_int_set_suffix(prop_transition_point, "");
  549. }
  550. UNUSED_PARAMETER(p);
  551. return true;
  552. }
  553. static bool track_matte_layout_modified(obs_properties_t *ppts,
  554. obs_property_t *p, obs_data_t *s)
  555. {
  556. int matte_layout = (int)obs_data_get_int(s, "track_matte_layout");
  557. obs_property_t *prop_matte_path =
  558. obs_properties_get(ppts, "track_matte_path");
  559. bool uses_separate_file = (matte_layout == MATTE_LAYOUT_SEPARATE_FILE);
  560. obs_property_set_visible(prop_matte_path, uses_separate_file);
  561. UNUSED_PARAMETER(p);
  562. return true;
  563. }
  564. static bool track_matte_enabled_modified(obs_properties_t *ppts,
  565. obs_property_t *p, obs_data_t *s)
  566. {
  567. bool track_matte_enabled = obs_data_get_bool(s, "track_matte_enabled");
  568. obs_property_t *prop_tp_type = obs_properties_get(ppts, "tp_type");
  569. if (track_matte_enabled) {
  570. obs_property_set_description(
  571. prop_tp_type,
  572. obs_module_text("AudioTransitionPointType"));
  573. } else {
  574. obs_property_set_description(
  575. prop_tp_type, obs_module_text("TransitionPointType"));
  576. }
  577. UNUSED_PARAMETER(p);
  578. return true;
  579. }
  580. static obs_properties_t *stinger_properties(void *data)
  581. {
  582. obs_properties_t *ppts = obs_properties_create();
  583. struct dstr filter = {0};
  584. obs_properties_set_flags(ppts, OBS_PROPERTIES_DEFER_UPDATE);
  585. dstr_copy(&filter, obs_module_text("FileFilter.VideoFiles"));
  586. dstr_cat(&filter, FILE_FILTER);
  587. dstr_cat(&filter, obs_module_text("FileFilter.AllFiles"));
  588. dstr_cat(&filter, " (*.*)");
  589. // main stinger settings
  590. obs_properties_add_path(ppts, "path", obs_module_text("VideoFile"),
  591. OBS_PATH_FILE, filter.array, NULL);
  592. obs_property_t *p = obs_properties_add_list(
  593. ppts, "tp_type", obs_module_text("TransitionPointType"),
  594. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  595. obs_properties_add_bool(ppts, "hw_decode",
  596. obs_module_text("HardwareDecode"));
  597. obs_property_list_add_int(p, obs_module_text("TransitionPointTypeTime"),
  598. TIMING_TIME);
  599. obs_property_list_add_int(
  600. p, obs_module_text("TransitionPointTypeFrame"), TIMING_FRAME);
  601. obs_property_set_modified_callback(p, transition_point_type_modified);
  602. obs_properties_add_int(ppts, "transition_point",
  603. obs_module_text("TransitionPoint"), 0, 120000,
  604. 1);
  605. // track matte properties
  606. {
  607. obs_properties_t *track_matte_group = obs_properties_create();
  608. p = obs_properties_add_list(track_matte_group,
  609. "track_matte_layout",
  610. obs_module_text("TrackMatteLayout"),
  611. OBS_COMBO_TYPE_LIST,
  612. OBS_COMBO_FORMAT_INT);
  613. obs_property_list_add_int(
  614. p, obs_module_text("TrackMatteLayoutHorizontal"),
  615. MATTE_LAYOUT_HORIZONTAL);
  616. obs_property_list_add_int(
  617. p, obs_module_text("TrackMatteLayoutVertical"),
  618. MATTE_LAYOUT_VERTICAL);
  619. /* TODO: Requires way to synchronize or combine two media files
  620. * together */
  621. #if 0
  622. obs_property_list_add_int(
  623. p, obs_module_text("TrackMatteLayoutSeparateFile"),
  624. MATTE_LAYOUT_SEPARATE_FILE);
  625. #endif
  626. obs_property_list_add_int(
  627. p, obs_module_text("TrackMatteLayoutMask"),
  628. MATTE_LAYOUT_MASK);
  629. obs_property_set_modified_callback(p,
  630. track_matte_layout_modified);
  631. obs_properties_add_path(track_matte_group, "track_matte_path",
  632. obs_module_text("TrackMatteVideoFile"),
  633. OBS_PATH_FILE, filter.array, NULL);
  634. obs_properties_add_bool(track_matte_group, "invert_matte",
  635. obs_module_text("InvertTrackMatte"));
  636. p = obs_properties_add_group(
  637. ppts, "track_matte_enabled",
  638. obs_module_text("TrackMatteEnabled"),
  639. OBS_GROUP_CHECKABLE, track_matte_group);
  640. obs_property_set_modified_callback(
  641. p, track_matte_enabled_modified);
  642. }
  643. dstr_free(&filter);
  644. // audio output settings
  645. obs_property_t *monitor_list = obs_properties_add_list(
  646. ppts, "audio_monitoring", obs_module_text("AudioMonitoring"),
  647. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  648. obs_property_list_add_int(monitor_list,
  649. obs_module_text("AudioMonitoring.None"),
  650. OBS_MONITORING_TYPE_NONE);
  651. obs_property_list_add_int(
  652. monitor_list, obs_module_text("AudioMonitoring.MonitorOnly"),
  653. OBS_MONITORING_TYPE_MONITOR_ONLY);
  654. obs_property_list_add_int(monitor_list,
  655. obs_module_text("AudioMonitoring.Both"),
  656. OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT);
  657. // audio fade settings
  658. obs_property_t *audio_fade_style = obs_properties_add_list(
  659. ppts, "audio_fade_style", obs_module_text("AudioFadeStyle"),
  660. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  661. obs_property_list_add_int(
  662. audio_fade_style,
  663. obs_module_text("AudioFadeStyle.FadeOutFadeIn"),
  664. FADE_STYLE_FADE_OUT_FADE_IN);
  665. obs_property_list_add_int(audio_fade_style,
  666. obs_module_text("AudioFadeStyle.CrossFade"),
  667. FADE_STYLE_CROSS_FADE);
  668. UNUSED_PARAMETER(data);
  669. return ppts;
  670. }
  671. static void missing_file_callback(void *src, const char *new_path, void *data)
  672. {
  673. struct stinger_info *s = src;
  674. obs_data_t *settings = obs_source_get_settings(s->source);
  675. const char *type = data;
  676. if (strcmp(type, "media_source") == 0) {
  677. obs_data_set_string(settings, "path", new_path);
  678. } else if (strcmp(type, "matte_source") == 0) {
  679. obs_data_set_string(settings, "track_matte_path", new_path);
  680. }
  681. obs_source_update(s->source, settings);
  682. obs_data_release(settings);
  683. }
  684. static obs_missing_files_t *stinger_missing_files(void *data)
  685. {
  686. struct stinger_info *s = data;
  687. obs_data_t *settings = obs_source_get_settings(s->source);
  688. obs_missing_files_t *files = obs_missing_files_create();
  689. const char *path = obs_data_get_string(settings, "path");
  690. if (strcmp(path, "") != 0) {
  691. if (!os_file_exists(path)) {
  692. obs_missing_file_t *file = obs_missing_file_create(
  693. path, missing_file_callback,
  694. OBS_MISSING_FILE_SOURCE, s->source,
  695. (void *)"media_source");
  696. obs_missing_files_add_file(files, file);
  697. }
  698. }
  699. const char *track_matte_path =
  700. obs_data_get_string(settings, "track_matte_path");
  701. if (strcmp(track_matte_path, "") != 0) {
  702. if (!os_file_exists(track_matte_path)) {
  703. obs_missing_file_t *file = obs_missing_file_create(
  704. track_matte_path, missing_file_callback,
  705. OBS_MISSING_FILE_SOURCE, s->source,
  706. (void *)"matte_source");
  707. obs_missing_files_add_file(files, file);
  708. }
  709. }
  710. obs_data_release(settings);
  711. return files;
  712. }
  713. static enum gs_color_space
  714. stinger_get_color_space(void *data, size_t count,
  715. const enum gs_color_space *preferred_spaces)
  716. {
  717. UNUSED_PARAMETER(count);
  718. UNUSED_PARAMETER(preferred_spaces);
  719. struct stinger_info *s = data;
  720. return obs_transition_video_get_color_space(s->source);
  721. }
  722. struct obs_source_info stinger_transition = {
  723. .id = "obs_stinger_transition",
  724. .type = OBS_SOURCE_TYPE_TRANSITION,
  725. .get_name = stinger_get_name,
  726. .create = stinger_create,
  727. .destroy = stinger_destroy,
  728. .update = stinger_update,
  729. .get_defaults = stinger_defaults,
  730. .video_render = stinger_video_render,
  731. .video_tick = stinger_video_tick,
  732. .audio_render = stinger_audio_render,
  733. .missing_files = stinger_missing_files,
  734. .get_properties = stinger_properties,
  735. .enum_active_sources = stinger_enum_active_sources,
  736. .enum_all_sources = stinger_enum_all_sources,
  737. .transition_start = stinger_transition_start,
  738. .transition_stop = stinger_transition_stop,
  739. .video_get_color_space = stinger_get_color_space,
  740. };