From 3b17416b8253b60fb1fcab7713333940dcb81a24 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Tue, 24 May 2016 07:31:48 -0700 Subject: [PATCH] image-source: Add image slideshow source --- plugins/image-source/CMakeLists.txt | 11 +- plugins/image-source/data/locale/en-US.ini | 10 + plugins/image-source/image-source.c | 3 + plugins/image-source/obs-slideshow.c | 451 +++++++++++++++++++++ 4 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 plugins/image-source/obs-slideshow.c diff --git a/plugins/image-source/CMakeLists.txt b/plugins/image-source/CMakeLists.txt index 9966d04d5..4159cf56e 100644 --- a/plugins/image-source/CMakeLists.txt +++ b/plugins/image-source/CMakeLists.txt @@ -1,11 +1,18 @@ project(image-source) +if(MSVC) + set(image-source_PLATFORM_DEPS + w32-pthreads) +endif() + set(image-source_SOURCES - image-source.c) + image-source.c + obs-slideshow.c) add_library(image-source MODULE ${image-source_SOURCES}) target_link_libraries(image-source - libobs) + libobs + ${image-source_PLATFORM_DEPS}) install_obs_plugin_with_data(image-source data) diff --git a/plugins/image-source/data/locale/en-US.ini b/plugins/image-source/data/locale/en-US.ini index 6b0d15579..06ff1c5d9 100644 --- a/plugins/image-source/data/locale/en-US.ini +++ b/plugins/image-source/data/locale/en-US.ini @@ -1,3 +1,13 @@ ImageInput="Image" File="Image File" UnloadWhenNotShowing="Unload image when not showing" + +SlideShow="Image Slide Show" +SlideShow.TransitionSpeed="Transition Speed (milliseconds)" +SlideShow.SlideTime="Time Between Slides (milliseconds)" +SlideShow.Files="Image Files" +SlideShow.Transition="Transition" +SlideShow.Transition.Cut="Cut" +SlideShow.Transition.Fade="Fade" +SlideShow.Transition.Swipe="Swipe" +SlideShow.Transition.Slide="Slide" diff --git a/plugins/image-source/image-source.c b/plugins/image-source/image-source.c index 3cbda6565..91ced6c4d 100644 --- a/plugins/image-source/image-source.c +++ b/plugins/image-source/image-source.c @@ -270,8 +270,11 @@ static struct obs_source_info image_source_info = { OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("image-source", "en-US") +extern struct obs_source_info slideshow_info; + bool obs_module_load(void) { obs_register_source(&image_source_info); + obs_register_source(&slideshow_info); return true; } diff --git a/plugins/image-source/obs-slideshow.c b/plugins/image-source/obs-slideshow.c new file mode 100644 index 000000000..56178984d --- /dev/null +++ b/plugins/image-source/obs-slideshow.c @@ -0,0 +1,451 @@ +#include +#include +#include +#include + +#define do_log(level, format, ...) \ + blog(level, "[slideshow: '%s'] " format, \ + obs_source_get_name(ss->source), ##__VA_ARGS__) + +#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) + +#define S_TR_SPEED "transition_speed" +#define S_SLIDE_TIME "slide_time" +#define S_TRANSITION "transition" +#define S_FILES "files" + +#define TR_CUT "cut" +#define TR_FADE "fade" +#define TR_SWIPE "swipe" +#define TR_SLIDE "slide" + +#define T_(text) obs_module_text("SlideShow." text) +#define T_TR_SPEED T_("TransitionSpeed") +#define T_SLIDE_TIME T_("SlideTime") +#define T_TRANSITION T_("Transition") +#define T_FILES T_("Files") + +#define T_TR_(text) obs_module_text("SlideShow.Transition." text) +#define T_TR_CUT T_TR_("Cut") +#define T_TR_FADE T_TR_("Fade") +#define T_TR_SWIPE T_TR_("Swipe") +#define T_TR_SLIDE T_TR_("Slide") + +/* ------------------------------------------------------------------------- */ + +struct image_file_data { + char *path; + obs_source_t *source; +}; + +struct slideshow { + obs_source_t *source; + + float slide_time; + uint32_t tr_speed; + const char *tr_name; + obs_source_t *transition; + + float elapsed; + size_t cur_item; + + uint32_t cx; + uint32_t cy; + + pthread_mutex_t mutex; + DARRAY(struct image_file_data) files; +}; + +static obs_source_t *get_transition(struct slideshow *ss) +{ + obs_source_t *tr; + + pthread_mutex_lock(&ss->mutex); + tr = ss->transition; + obs_source_addref(tr); + pthread_mutex_unlock(&ss->mutex); + + return tr; +} + +static obs_source_t *get_source(struct darray *array, const char *path) +{ + DARRAY(struct image_file_data) files; + obs_source_t *source = NULL; + + files.da = *array; + + for (size_t i = 0; i < files.num; i++) { + const char *cur_path = files.array[i].path; + + if (strcmp(path, cur_path) == 0) { + source = files.array[i].source; + obs_source_addref(source); + break; + } + } + + return source; +} + +static obs_source_t *create_source_from_file(const char *file) +{ + obs_data_t *settings = obs_data_create(); + obs_source_t *source; + + obs_data_set_string(settings, "file", file); + obs_data_set_bool(settings, "unload", false); + source = obs_source_create_private("image_source", NULL, settings); + + obs_data_release(settings); + return source; +} + +static void free_files(struct darray *array) +{ + DARRAY(struct image_file_data) files; + files.da = *array; + + for (size_t i = 0; i < files.num; i++) { + bfree(files.array[i].path); + obs_source_release(files.array[i].source); + } + + da_free(files); +} + +/* ------------------------------------------------------------------------- */ + +static const char *ss_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return obs_module_text("SlideShow"); +} + +static void ss_update(void *data, obs_data_t *settings) +{ + DARRAY(struct image_file_data) new_files; + DARRAY(struct image_file_data) old_files; + obs_source_t *new_tr = NULL; + obs_source_t *old_tr = NULL; + struct slideshow *ss = data; + obs_data_array_t *array; + const char *tr_name; + uint32_t new_duration; + uint32_t new_speed; + uint32_t cx = 0; + uint32_t cy = 0; + size_t count; + + /* ------------------------------------- */ + /* get settings data */ + + da_init(new_files); + + tr_name = obs_data_get_string(settings, S_TRANSITION); + if (astrcmpi(tr_name, TR_CUT) == 0) + tr_name = "cut_transition"; + else if (astrcmpi(tr_name, TR_SWIPE) == 0) + tr_name = "swipe_transition"; + else if (astrcmpi(tr_name, TR_SLIDE) == 0) + tr_name = "slide_transition"; + else + tr_name = "fade_transition"; + + if (!ss->tr_name || strcmp(tr_name, ss->tr_name) != 0) + new_tr = obs_source_create_private(tr_name, NULL, NULL); + + new_duration = (uint32_t)obs_data_get_int(settings, S_SLIDE_TIME); + new_speed = (uint32_t)obs_data_get_int(settings, S_TR_SPEED); + + array = obs_data_get_array(settings, S_FILES); + count = obs_data_array_count(array); + + /* ------------------------------------- */ + /* create new list of sources */ + + for (size_t i = 0; i < count; i++) { + obs_data_t *item = obs_data_array_item(array, i); + const char *path = obs_data_get_string(item, "value"); + struct image_file_data data; + obs_source_t *new_source; + + pthread_mutex_lock(&ss->mutex); + new_source = get_source(&ss->files.da, path); + pthread_mutex_unlock(&ss->mutex); + + if (!new_source) + new_source = get_source(&new_files.da, path); + if (!new_source) + new_source = create_source_from_file(path); + + if (new_source) { + uint32_t new_cx = obs_source_get_width(new_source); + uint32_t new_cy = obs_source_get_height(new_source); + + data.path = bstrdup(path); + data.source = new_source; + da_push_back(new_files, &data); + + if (new_cx > cx) cx = new_cx; + if (new_cy > cy) cy = new_cy; + } + + obs_data_release(item); + } + + /* ------------------------------------- */ + /* update settings data */ + + pthread_mutex_lock(&ss->mutex); + + old_files.da = ss->files.da; + ss->files.da = new_files.da; + if (new_tr) { + old_tr = ss->transition; + ss->transition = new_tr; + } + + if (new_duration < 50) + new_duration = 50; + if (new_speed > (new_duration - 50)) + new_speed = new_duration - 50; + + ss->tr_speed = new_speed; + ss->tr_name = tr_name; + ss->slide_time = (float)new_duration / 1000.0f; + + pthread_mutex_unlock(&ss->mutex); + + /* ------------------------------------- */ + /* clean up and restart transition */ + + if (old_tr) + obs_source_release(old_tr); + free_files(&old_files.da); + + ss->cx = cx; + ss->cy = cy; + ss->cur_item = 0; + ss->elapsed = 0.0f; + obs_transition_set_size(ss->transition, cx, cy); + obs_transition_set_alignment(ss->transition, OBS_ALIGN_CENTER); + obs_transition_set_scale_type(ss->transition, + OBS_TRANSITION_SCALE_ASPECT); + + if (new_tr) + obs_source_add_active_child(ss->source, new_tr); + if (ss->files.num) + obs_transition_start(ss->transition, OBS_TRANSITION_MODE_AUTO, + ss->tr_speed, ss->files.array[0].source); + + obs_data_array_release(array); +} + +static void ss_destroy(void *data) +{ + struct slideshow *ss = data; + + obs_source_release(ss->transition); + free_files(&ss->files.da); + pthread_mutex_destroy(&ss->mutex); + bfree(ss); +} + +static void *ss_create(obs_data_t *settings, obs_source_t *source) +{ + struct slideshow *ss = bzalloc(sizeof(*ss)); + ss->source = source; + + pthread_mutex_init_value(&ss->mutex); + if (pthread_mutex_init(&ss->mutex, NULL) != 0) + goto error; + + obs_source_update(source, NULL); + + UNUSED_PARAMETER(settings); + return ss; + +error: + ss_destroy(ss); + return NULL; +} + +static void ss_video_render(void *data, gs_effect_t *effect) +{ + struct slideshow *ss = data; + obs_source_t *transition = get_transition(ss); + + if (transition) { + obs_source_video_render(transition); + obs_source_release(transition); + } + + UNUSED_PARAMETER(effect); +} + +static void ss_video_tick(void *data, float seconds) +{ + struct slideshow *ss = data; + + if (!ss->transition || !ss->slide_time) + return; + + ss->elapsed += seconds; + if (ss->elapsed > ss->slide_time) { + ss->elapsed -= ss->slide_time; + + if (++ss->cur_item >= ss->files.num) + ss->cur_item = 0; + + if (ss->files.num) + obs_transition_start(ss->transition, + OBS_TRANSITION_MODE_AUTO, ss->tr_speed, + ss->files.array[ss->cur_item].source); + } +} + +static inline bool ss_audio_render_(obs_source_t *transition, uint64_t *ts_out, + struct obs_source_audio_mix *audio_output, + uint32_t mixers, size_t channels, size_t sample_rate) +{ + struct obs_source_audio_mix child_audio; + uint64_t source_ts; + + if (obs_source_audio_pending(transition)) + return false; + + source_ts = obs_source_get_audio_timestamp(transition); + if (!source_ts) + return false; + + obs_source_get_audio_mix(transition, &child_audio); + for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) { + if ((mixers & (1 << mix)) == 0) + continue; + + for (size_t ch = 0; ch < channels; ch++) { + float *out = audio_output->output[mix].data[ch]; + float *in = child_audio.output[mix].data[ch]; + + memcpy(out, in, AUDIO_OUTPUT_FRAMES * + MAX_AUDIO_CHANNELS * sizeof(float)); + } + } + + *ts_out = source_ts; + + UNUSED_PARAMETER(sample_rate); + return true; +} + +static bool ss_audio_render(void *data, uint64_t *ts_out, + struct obs_source_audio_mix *audio_output, + uint32_t mixers, size_t channels, size_t sample_rate) +{ + struct slideshow *ss = data; + obs_source_t *transition = get_transition(ss); + bool success; + + if (!transition) + return false; + + success = ss_audio_render_(transition, ts_out, audio_output, mixers, + channels, sample_rate); + + obs_source_release(transition); + return success; +} + +static void ss_enum_sources(void *data, obs_source_enum_proc_t cb, void *param) +{ + struct slideshow *ss = data; + + pthread_mutex_lock(&ss->mutex); + if (ss->transition) + cb(ss->source, ss->transition, param); + pthread_mutex_unlock(&ss->mutex); +} + +static uint32_t ss_width(void *data) +{ + struct slideshow *ss = data; + return ss->transition ? ss->cx : 0; +} + +static uint32_t ss_height(void *data) +{ + struct slideshow *ss = data; + return ss->transition ? ss->cy : 0; +} + +static void ss_defaults(obs_data_t *settings) +{ + obs_data_set_default_string(settings, S_TRANSITION, "fade"); + obs_data_set_default_int(settings, S_SLIDE_TIME, 8000); + obs_data_set_default_int(settings, S_TR_SPEED, 700); +} + +static const char *file_filter = + "Image files (*.bmp *.tga *.png *.jpeg *.jpg *.gif)"; + +static obs_properties_t *ss_properties(void *data) +{ + obs_properties_t *ppts = obs_properties_create(); + struct slideshow *ss = data; + struct dstr path = {0}; + obs_property_t *p; + + p = obs_properties_add_list(ppts, S_TRANSITION, T_TRANSITION, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, T_TR_CUT, TR_CUT); + obs_property_list_add_string(p, T_TR_FADE, TR_FADE); + obs_property_list_add_string(p, T_TR_SWIPE, TR_SWIPE); + obs_property_list_add_string(p, T_TR_SLIDE, TR_SLIDE); + + obs_properties_add_int(ppts, S_SLIDE_TIME, T_SLIDE_TIME, + 50, 3600000, 50); + obs_properties_add_int(ppts, S_TR_SPEED, T_TR_SPEED, + 0, 3600000, 50); + + if (ss) { + pthread_mutex_lock(&ss->mutex); + if (ss->files.num) { + struct image_file_data *last = da_end(ss->files); + const char *slash; + + dstr_copy(&path, last->path); + dstr_replace(&path, "\\", "/"); + slash = strrchr(path.array, '/'); + if (slash) + dstr_resize(&path, slash - path.array + 1); + } + pthread_mutex_unlock(&ss->mutex); + } + + obs_properties_add_editable_list(ppts, S_FILES, T_FILES, + OBS_EDITABLE_LIST_TYPE_FILES, file_filter, path.array); + dstr_free(&path); + + return ppts; +} + +struct obs_source_info slideshow_info = { + .id = "slideshow", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_VIDEO | + OBS_SOURCE_CUSTOM_DRAW | + OBS_SOURCE_COMPOSITE, + .get_name = ss_getname, + .create = ss_create, + .destroy = ss_destroy, + .update = ss_update, + .video_render = ss_video_render, + .video_tick = ss_video_tick, + .audio_render = ss_audio_render, + .enum_active_sources = ss_enum_sources, + .get_width = ss_width, + .get_height = ss_height, + .get_defaults = ss_defaults, + .get_properties = ss_properties +};