From dc87f856e494c74c0ffb765a6a9344cecce08f94 Mon Sep 17 00:00:00 2001 From: David Xenakis Date: Fri, 19 Jan 2024 15:41:14 -0700 Subject: [PATCH 1/3] Adds frame seeking hotkeys. Also, refactor the method for finding the right timestamp to start/end at when a trim exists. The new method uses a binary search, so it should be more efficient than a linear seek. I use this method to wrap around in the frame seeking. Tested using trimmed videos at different speeds, and everything works as expected. Updated Readme with new hotkeys --- README.md | 4 + replay-source.c | 231 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 160 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 2afda78..b59f1cc 100644 --- a/README.md +++ b/README.md @@ -140,3 +140,7 @@ Enable the capturing of replays, adds the replay filters. Disable the automatic next scene switching function. * **Enable next scene** Enable the automatic next scene switching function. +* **Previous frame** +Rewind the playback head one frame. Automatically pauses playback. +* **Next frame** +Advance the playback head one frame. Automatically pauses playback. diff --git a/replay-source.c b/replay-source.c index deb7a4b..5030a40 100644 --- a/replay-source.c +++ b/replay-source.c @@ -9,6 +9,7 @@ #include #include #include "replay.h" +#include #include #define VISIBILITY_ACTION_RESTART 0 @@ -93,6 +94,8 @@ struct replay_source { obs_hotkey_id disable_next_scene_hotkey; obs_hotkey_id next_scene_current_hotkey; obs_hotkey_id next_scene_hotkey; + obs_hotkey_id next_frame_hotkey; + obs_hotkey_id prev_frame_hotkey; uint64_t start_timestamp; uint64_t previous_frame_timestamp; uint64_t pause_timestamp; @@ -107,6 +110,7 @@ struct replay_source { bool restart; bool active; bool end; + bool stepped; enum saving_status saving_status; int replay_position; @@ -553,23 +557,13 @@ static void replay_update_position(struct replay_source *context, bool lock) context->start_timestamp = obs_get_video_frame_time(); context->backward = context->backward_start; if (!context->backward && context->current_replay.trim_front != 0) { - if (context->speed_percent == 100.0f) { - context->start_timestamp -= - context->current_replay.trim_front; - } else { - context->start_timestamp -= - (uint64_t)(context->current_replay.trim_front * - 100.0 / context->speed_percent); - } + context->start_timestamp -= + (uint64_t)(context->current_replay.trim_front * + 100.0 / context->speed_percent); } else if (context->backward && context->current_replay.trim_end != 0) { - if (context->speed_percent == 100.0f) { - context->start_timestamp -= - context->current_replay.trim_end; - } else { - context->start_timestamp -= - (uint64_t)(context->current_replay.trim_end * - 100.0 / context->speed_percent); - } + context->start_timestamp -= + (uint64_t)(context->current_replay.trim_end * + 100.0 / context->speed_percent); } context->pause_timestamp = 0; if (context->backward && context->current_replay.video_frame_count) { @@ -1386,6 +1380,40 @@ static void replay_retrieve(struct replay_source *context) } } +// Finds closest frame in the current replay given a desired timestamp. +uint64_t find_closest_frame(void *data, uint64_t ts, bool le) { + struct replay_source *c = data; + int64_t count = c->current_replay.video_frame_count; + struct obs_source_frame **frames = c->current_replay.video_frames; + if (ts < c->current_replay.first_frame_timestamp) { + return 0; + } else if (ts > c->current_replay.last_frame_timestamp) { + return count - 1; + } + + int64_t i = 0, mid = 0, j = count; + while (i < j) { + mid = (i + j) / 2; + if (frames[mid]->timestamp == ts) { + return mid; + } else if (frames[mid]->timestamp > ts) { + if (mid > 0 && frames[mid-1]->timestamp < ts) { + return le ? mid - 1 : mid; + } + j = mid; + } else if (frames[mid]->timestamp < ts) { + if (mid < count - 1 && frames[mid+1]->timestamp > ts) { + return le ? mid : mid + 1; + } + i = mid + 1; + } + } + if (i == mid + 1) { + return le ? mid : mid + 1; + } + return le ? mid - 1 : mid; +} + static void replay_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed) { @@ -1534,6 +1562,64 @@ static void replay_next_scene_hotkey(void *data, obs_hotkey_id id, } } +void replay_step_frames(void *data, bool pressed, bool forward, uint64_t num_frames) { + struct replay_source *c = data; + if (c->current_replay.video_frame_count && pressed) { + uint64_t next_pos = c->video_frame_position; + if (forward) { + // Check for wrap at end + if (next_pos + num_frames >= c->current_replay.video_frame_count || + c->current_replay.video_frames[next_pos + num_frames]->timestamp > + c->current_replay.last_frame_timestamp - c->current_replay.trim_end) { + next_pos = find_closest_frame(c, c->current_replay.first_frame_timestamp + c->current_replay.trim_front, false); + } else { + next_pos += num_frames; + } + + } else { + // Check for wrap at beginning + if (c->video_frame_position < num_frames || + c->current_replay.video_frames[c->video_frame_position - num_frames]->timestamp < + c->current_replay.first_frame_timestamp + c->current_replay.trim_front) { + next_pos = find_closest_frame(c, c->current_replay.last_frame_timestamp - c->current_replay.trim_end, true); + } else { + next_pos -= num_frames; + } + } + if (c->play) { + c->play = false; + c->pause_timestamp = obs_get_video_frame_time(); + obs_source_signal(c->source, "media_pause"); + + } + c->stepped = true; + int64_t prev_time = c->current_replay.video_frames[next_pos]->timestamp; + int64_t next_time = c->current_replay.video_frames[c->video_frame_position]->timestamp; + int64_t time_diff = (int64_t)((next_time - prev_time) * 100 / c->speed_percent); + if (c->backward) { + time_diff *= -1; + } + c->start_timestamp += time_diff; + c->video_frame_position = next_pos; + } +} + +static void replay_next_frame_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + replay_step_frames(data, pressed, true, 1); +} + +static void replay_prev_frame_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + replay_step_frames(data, pressed, false, 1); +} + static void replay_next_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed) { @@ -2079,6 +2165,10 @@ static void replay_source_update(void *data, obs_data_t *settings) true); } else if (strcmp(execute_action, "SwitchToNextScene") == 0) { replay_next_scene_hotkey(context, 0, NULL, true); + } else if (strcmp(execute_action, "NextFrame") == 0) { + replay_next_frame_hotkey(context, 0, NULL, true); + } else if (strcmp(execute_action, "PrevFrame") == 0) { + replay_prev_frame_hotkey(context, 0, NULL, true); } obs_data_erase(settings, SETTING_EXECUTE_ACTION); } @@ -2581,6 +2671,16 @@ static void *replay_source_create(obs_data_t *settings, obs_source_t *source) obs_hotkey_register_source(source, "ReplaySource.NextScene", obs_module_text("SwitchToNextScene"), replay_next_scene_hotkey, context); + + context->prev_frame_hotkey = + obs_hotkey_register_source(source, "ReplaySource.PrevFrame", + obs_module_text("PrevFrame"), + replay_prev_frame_hotkey, context); + + context->next_frame_hotkey = + obs_hotkey_register_source(source, "ReplaySource.NextFrame", + obs_module_text("NextFrame"), + replay_next_frame_hotkey, context); return context; } @@ -2993,6 +3093,17 @@ static void replay_source_tick(void *data, float seconds) obs_source_output_video(context->source, f); obs_source_frame_destroy(f); } + if (context->stepped) { + context->stepped = false; + struct obs_source_frame *frame = + context->current_replay + .video_frames[context->video_frame_position]; + uint64_t t = frame->timestamp; + frame->timestamp = os_timestamp; + obs_source_output_video(context->source, frame); + frame->timestamp = t; + replay_update_text(context); + } pthread_mutex_unlock(&context->video_mutex); return; } @@ -3004,9 +3115,10 @@ static void replay_source_tick(void *data, float seconds) if (context->current_replay.video_frame_count) { if (context->video_frame_position >= - context->current_replay.video_frame_count) + context->current_replay.video_frame_count) { context->video_frame_position = context->current_replay.video_frame_count - 1; + } struct obs_source_frame *frame = context->current_replay .video_frames[context->video_frame_position]; @@ -3022,17 +3134,11 @@ static void replay_source_tick(void *data, float seconds) context->start_timestamp = os_timestamp; context->restart = false; if (context->current_replay.trim_end != 0) { - if (context->speed_percent == 100.0f) { - context->start_timestamp -= - context->current_replay - .trim_end; - } else { - context->start_timestamp -= - (uint64_t)(context->current_replay - .trim_end * - 100.0 / - context->speed_percent); - } + context->start_timestamp -= + (uint64_t)(context->current_replay + .trim_end * + 100.0 / + context->speed_percent); if (context->current_replay.trim_end < 0) { @@ -3047,21 +3153,10 @@ static void replay_source_tick(void *data, float seconds) &context->video_mutex); return; } - while (frame->timestamp > - context->current_replay - .last_frame_timestamp - - context->current_replay - .trim_end) { - if (context->video_frame_position == - 0) { - context->video_frame_position = - context->current_replay - .video_frame_count; - } - context->video_frame_position--; - frame = context->current_replay.video_frames - [context->video_frame_position]; - } + uint64_t desired_ts = context->current_replay.last_frame_timestamp - context->current_replay.trim_end; + int64_t desired_frame_num = find_closest_frame(context, desired_ts, true); + frame = context->current_replay.video_frames + [desired_frame_num]; } } @@ -3075,7 +3170,7 @@ static void replay_source_tick(void *data, float seconds) frame->timestamp) * 100.0 / context->speed_percent); - struct obs_source_frame *output_frame = NULL; + struct obs_source_frame *output_frame = frame; while (context->play && video_duration >= source_duration) { output_frame = frame; @@ -3102,10 +3197,10 @@ static void replay_source_tick(void *data, float seconds) 100.0 / context->speed_percent); } - if (output_frame) { - replay_output_frame(context, output_frame); - } else if (context->video_frame_position == 0) { + if (context->video_frame_position == 0) { replay_source_end_action(context); + } else { + replay_output_frame(context, output_frame); } } else { if (context->restart) { @@ -3116,17 +3211,11 @@ static void replay_source_tick(void *data, float seconds) frame = context->current_replay.video_frames [context->video_frame_position]; if (context->current_replay.trim_front != 0) { - if (context->speed_percent == 100.0f) { - context->start_timestamp -= - context->current_replay - .trim_front; - } else { - context->start_timestamp -= - (uint64_t)(context->current_replay - .trim_front * - 100.0 / - context->speed_percent); - } + context->start_timestamp -= + (uint64_t)(context->current_replay + .trim_front * + 100.0 / + context->speed_percent); if (context->current_replay.trim_front < 0) { uint64_t t = frame->timestamp; @@ -3140,21 +3229,13 @@ static void replay_source_tick(void *data, float seconds) &context->video_mutex); return; } - while (frame->timestamp < - context->current_replay + uint64_t desired_ts = context->current_replay .first_frame_timestamp + context->current_replay - .trim_front) { - context->video_frame_position++; - if (context->video_frame_position >= - context->current_replay - .video_frame_count) { - context->video_frame_position = - 0; - } - frame = context->current_replay.video_frames - [context->video_frame_position]; - } + .trim_front; + int64_t desired_frame_num = find_closest_frame(context, desired_ts, false); + frame = context->current_replay.video_frames + [desired_frame_num]; } } if (context->start_timestamp > os_timestamp) { @@ -3277,7 +3358,7 @@ static void replay_source_tick(void *data, float seconds) context->current_replay .first_frame_timestamp) * 100.0 / context->speed_percent); - struct obs_source_frame *output_frame = NULL; + struct obs_source_frame *output_frame = frame; while (context->play && video_duration >= source_duration) { output_frame = frame; @@ -3306,9 +3387,7 @@ static void replay_source_tick(void *data, float seconds) 100.0 / context->speed_percent); } - if (output_frame) { - replay_output_frame(context, output_frame); - } else if (context->video_frame_position >= + if (context->video_frame_position >= context->current_replay.video_frame_count - 1) { context->video_frame_position = @@ -3316,6 +3395,8 @@ static void replay_source_tick(void *data, float seconds) .video_frame_count - 1; replay_source_end_action(context); + } else { + replay_output_frame(context, output_frame); } } } else if (context->current_replay.audio_frame_count) { From 7b8ce33ed3ef3d13893850e622d4f96643fd7fca Mon Sep 17 00:00:00 2001 From: David Xenakis Date: Tue, 30 Jan 2024 13:11:41 -0700 Subject: [PATCH 2/3] Address PR comments and add setting to step 'N' frames --- README.md | 4 ++++ data/locale/en-US.ini | 1 + data/locale/es-ES.ini | 1 + data/locale/it-IT.ini | 1 + replay-source.c | 54 +++++++++++++++++++++++++++++++++++++------ replay.h | 1 + 6 files changed, 55 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b59f1cc..65a7773 100644 --- a/README.md +++ b/README.md @@ -144,3 +144,7 @@ Enable the automatic next scene switching function. Rewind the playback head one frame. Automatically pauses playback. * **Next frame** Advance the playback head one frame. Automatically pauses playback. +* **Previous N frames** +Rewind the playback head N frames. Automatically pauses playback. Set N in source settings. +* **Next N frames** +Advance the playback head N frames. Automatically pauses playback. Set N in source settings. diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 80d9277..00b6c82 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -15,6 +15,7 @@ Pause="Pause" Continue="Continue" None="None" StartDelay="Start Delay" +FrameStepCount="Step 'N' Frames" EndAction="End Action" HideAfterSingle="Hide after single" HideAfterAll="Hide after all" diff --git a/data/locale/es-ES.ini b/data/locale/es-ES.ini index e890009..a374a3d 100644 --- a/data/locale/es-ES.ini +++ b/data/locale/es-ES.ini @@ -15,6 +15,7 @@ Pause="Pausar" Continue="Continuar" None="Ninguna" StartDelay="Retardo de Inicio" +FrameStepCount="Paso 'N' fotogramas" EndAction="Acción al Finalizar" HideAfterSingle="Ocultar después de una" HideAfterAll="Ocultar después de todas" diff --git a/data/locale/it-IT.ini b/data/locale/it-IT.ini index 7269506..ed6b5cf 100644 --- a/data/locale/it-IT.ini +++ b/data/locale/it-IT.ini @@ -15,6 +15,7 @@ Pause="Pausa" Continue="Continua" None="Nessuna" StartDelay="Ritardo di inizio" +FrameStepCount="Passaggio 'N' fotogrammi" EndAction="Azione alla Fine" HideAfterSingle="Nascondi dopo il Singolo" HideAfterAll="Nascondi dopo tutti" diff --git a/replay-source.c b/replay-source.c index 5030a40..a691d4d 100644 --- a/replay-source.c +++ b/replay-source.c @@ -96,11 +96,14 @@ struct replay_source { obs_hotkey_id next_scene_hotkey; obs_hotkey_id next_frame_hotkey; obs_hotkey_id prev_frame_hotkey; + obs_hotkey_id next_n_frames_hotkey; + obs_hotkey_id prev_n_frames_hotkey; uint64_t start_timestamp; uint64_t previous_frame_timestamp; uint64_t pause_timestamp; int64_t start_delay; int64_t retrieve_delay; + int64_t frame_step_count; uint64_t retrieve_timestamp; uint64_t threshold_timestamp; struct obs_source_audio audio; @@ -690,6 +693,7 @@ static void replay_source_defaults(obs_data_t *settings) obs_data_set_default_int(settings, SETTING_VISIBILITY_ACTION, VISIBILITY_ACTION_CONTINUE); obs_data_set_default_int(settings, SETTING_START_DELAY, 0); + obs_data_set_default_int(settings, SETTING_FRAME_STEP_COUNT, 5); obs_data_set_default_int(settings, SETTING_END_ACTION, END_ACTION_LOOP); obs_data_set_default_bool(settings, SETTING_BACKWARD, false); obs_data_set_default_string(settings, SETTING_FILE_FORMAT, @@ -1593,17 +1597,26 @@ void replay_step_frames(void *data, bool pressed, bool forward, uint64_t num_fra } c->stepped = true; - int64_t prev_time = c->current_replay.video_frames[next_pos]->timestamp; - int64_t next_time = c->current_replay.video_frames[c->video_frame_position]->timestamp; + int64_t next_time = c->current_replay.video_frames[next_pos]->timestamp; + int64_t prev_time = c->current_replay.video_frames[c->video_frame_position]->timestamp; int64_t time_diff = (int64_t)((next_time - prev_time) * 100 / c->speed_percent); if (c->backward) { time_diff *= -1; } - c->start_timestamp += time_diff; + c->start_timestamp -= time_diff; c->video_frame_position = next_pos; } } +static void replay_next_n_frames_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + struct replay_source *c = data; + replay_step_frames(data, pressed, true, c->frame_step_count); +} + static void replay_next_frame_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed) { @@ -1612,6 +1625,15 @@ static void replay_next_frame_hotkey(void *data, obs_hotkey_id id, replay_step_frames(data, pressed, true, 1); } +static void replay_prev_n_frames_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + struct replay_source *c = data; + replay_step_frames(data, pressed, false, c->frame_step_count); +} + static void replay_prev_frame_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed) { @@ -2169,7 +2191,12 @@ static void replay_source_update(void *data, obs_data_t *settings) replay_next_frame_hotkey(context, 0, NULL, true); } else if (strcmp(execute_action, "PrevFrame") == 0) { replay_prev_frame_hotkey(context, 0, NULL, true); + } else if (strcmp(execute_action, "NextNFrames") == 0) { + replay_next_n_frames_hotkey(context, 0, NULL, true); + } else if (strcmp(execute_action, "PrevNFrames") == 0) { + replay_prev_n_frames_hotkey(context, 0, NULL, true); } + obs_data_erase(settings, SETTING_EXECUTE_ACTION); } const char *source_name = obs_data_get_string(settings, SETTING_SOURCE); @@ -2263,6 +2290,8 @@ static void replay_source_update(void *data, obs_data_t *settings) obs_data_get_int(settings, SETTING_START_DELAY) * 1000000; context->retrieve_delay = obs_data_get_int(settings, SETTING_RETRIEVE_DELAY) * 1000000; + context->frame_step_count = + obs_data_get_int(settings, SETTING_FRAME_STEP_COUNT); context->replay_max = (int)obs_data_get_int(settings, SETTING_REPLAYS); replay_purge_replays(context); @@ -2682,6 +2711,16 @@ static void *replay_source_create(obs_data_t *settings, obs_source_t *source) obs_module_text("NextFrame"), replay_next_frame_hotkey, context); + context->prev_n_frames_hotkey = + obs_hotkey_register_source(source, "ReplaySource.PrevNFrames", + obs_module_text("PrevNFrames"), + replay_prev_n_frames_hotkey, context); + + context->next_n_frames_hotkey = + obs_hotkey_register_source(source, "ReplaySource.NextNFrames", + obs_module_text("NextNFrames"), + replay_next_n_frames_hotkey, context); + return context; } @@ -3199,9 +3238,8 @@ static void replay_source_tick(void *data, float seconds) } if (context->video_frame_position == 0) { replay_source_end_action(context); - } else { - replay_output_frame(context, output_frame); } + replay_output_frame(context, output_frame); } else { if (context->restart) { context->video_frame_position = 0; @@ -3395,9 +3433,8 @@ static void replay_source_tick(void *data, float seconds) .video_frame_count - 1; replay_source_end_action(context); - } else { - replay_output_frame(context, output_frame); } + replay_output_frame(context, output_frame); } } else if (context->current_replay.audio_frame_count) { //no video, only audio @@ -3691,6 +3728,9 @@ static obs_properties_t *replay_source_properties(void *data) obs_module_text("StartDelay"), -100000, 100000, 1000); obs_property_int_set_suffix(prop, "ms"); + prop = obs_properties_add_int(props, SETTING_FRAME_STEP_COUNT, + obs_module_text("FrameStepCount"), 1, + 1000, 1); prop = obs_properties_add_list(props, SETTING_END_ACTION, obs_module_text("EndAction"), diff --git a/replay.h b/replay.h index fda94c1..899360f 100644 --- a/replay.h +++ b/replay.h @@ -64,6 +64,7 @@ void replay_filter_check(void *data); #define SETTING_BACKWARD "backward" #define SETTING_VISIBILITY_ACTION "visibility_action" #define SETTING_START_DELAY "start_delay" +#define SETTING_FRAME_STEP_COUNT "frame_step_count" //#define TEXT_START_DELAY "Start delay (ms)" #define SETTING_END_ACTION "end_action" #define SETTING_SOURCE "source" From 054aab82aa89462faaa9725646666b072131c45f Mon Sep 17 00:00:00 2001 From: David Xenakis Date: Tue, 30 Jan 2024 13:19:23 -0700 Subject: [PATCH 3/3] Update locales and module text --- README.md | 4 ++-- data/locale/en-US.ini | 4 ++++ data/locale/es-ES.ini | 4 ++++ data/locale/it-IT.ini | 4 ++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 65a7773..a58552a 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ Enable the automatic next scene switching function. Rewind the playback head one frame. Automatically pauses playback. * **Next frame** Advance the playback head one frame. Automatically pauses playback. -* **Previous N frames** +* **Step backward N frames** Rewind the playback head N frames. Automatically pauses playback. Set N in source settings. -* **Next N frames** +* **Step forward N frames** Advance the playback head N frames. Automatically pauses playback. Set N in source settings. diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 00b6c82..2ce79e1 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -68,6 +68,10 @@ DisableNextScene="Disable Next Scene" EnableNextScene="Enable Next Scene" SetNextSceneToCurrent="Set Next Scene To Current" SwitchToNextScene="Switch To Next Scene" +PrevFrame="Step Backwards One Frame" +NextFrame="Step Forwards One Frame" +PrevNFrames="Step Backwards 'N' Frames" +NextNFrames="Step Forwards 'N' Frames" # win-dshow text VideoCaptureDevice="Video Capture Device" diff --git a/data/locale/es-ES.ini b/data/locale/es-ES.ini index a374a3d..a4d3f8e 100644 --- a/data/locale/es-ES.ini +++ b/data/locale/es-ES.ini @@ -66,6 +66,10 @@ DisableNextScene="Deshabilitar siguiente escena" EnableNextScene="Habilitar siguiente escena" SetNextSceneToCurrent="Establecer siguiente escena a la actual" SwitchToNextScene="Cambiar a la siguiente escena" +PrevFrame="Retroceder un fotograma" +NextFrame="Paso un fotograma" +PrevNFrames="Retroceder 'N' fotogramas" +NextNFrames="Paso adelante 'N' fotogramas" # win-dshow text VideoCaptureDevice="Dispositivo de captura de vídeo" diff --git a/data/locale/it-IT.ini b/data/locale/it-IT.ini index ed6b5cf..c4b1156 100644 --- a/data/locale/it-IT.ini +++ b/data/locale/it-IT.ini @@ -66,6 +66,10 @@ DisableNextScene="Disabilita Prossima Scena" EnableNextScene="Abilita Prossima Scena" SetNextSceneToCurrent="Imposta Scena Corrente come Prossima" SwitchToNextScene="Passa alla Prossima Scena" +PrevFrame="Passo indietro di un fotogramma" +NextFrame="Passo avanti di un fotogramma" +PrevNFrames="Passo indietro di 'N' fotogrammi" +NextNFrames="Passo avanti di 'N' fotogrammi" # win-dshow text VideoCaptureDevice="Dispositivo di Acquisizione Video"