VSE: Add sound strip retiming support #105071

Closed
Richard Antalik wants to merge 5 commits from iss/blender:retiming-sound into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
41 changed files with 1928 additions and 110 deletions

View File

@ -165,6 +165,12 @@ AUD_API void AUD_SequenceEntry_move(AUD_SequenceEntry* entry, double begin, doub
(*entry)->move(begin, end, skip);
}
AUD_API void AUD_SequenceEntry_setAnimationData_constant_range(AUD_SequenceEntry* entry, AUD_AnimateablePropertyType type, int frame_start, int frame_end, float* data)
{
AnimateableProperty* prop = (*entry)->getAnimProperty(static_cast<AnimateablePropertyType>(type));
prop->write_range(data, frame_start, frame_end);
}
AUD_API void AUD_SequenceEntry_setAnimationData(AUD_SequenceEntry* entry, AUD_AnimateablePropertyType type, int frame, float* data, char animated)
{
AnimateableProperty* prop = (*entry)->getAnimProperty(static_cast<AnimateablePropertyType>(type));

View File

@ -68,6 +68,16 @@ extern AUD_API void AUD_Sequence_remove(AUD_Sound* sequence, AUD_SequenceEntry*
* Writes animation data to a sequence.
* \param sequence The sound scene.
* \param type The type of animation data.
* \param frame_start Start of the frame range.
* \param frame_end End of the frame range.
* \param data The data to write.
*/
AUD_API void AUD_SequenceEntry_setAnimationData_constant_range(AUD_SequenceEntry* entry, AUD_AnimateablePropertyType type, int frame_start, int frame_end, float* data);
/**
* Writes animation data to a sequenced entry.
* \param entry The sequenced entry.
* \param type The type of animation data.
* \param frame The frame this data is for.
* \param data The data to write.
* \param animated Whether the attribute is animated.

View File

@ -112,6 +112,14 @@ public:
*/
void write(const float* data, int position, int count);
/**
* Fills the properties frame range with constant value and marks it animated.
* \param data The new value.
* \param position_start The start position in the animation in frames.
* \param position_end The end position in the animation in frames.
*/
void write_range(const float* data, int position_start, int position_end);
/**
* Reads the properties value.
* \param position The position in the animation in frames.

View File

@ -63,6 +63,9 @@ private:
/// How many seconds are skipped at the beginning.
double m_skip;
/// The FPS of the scene.
float m_fps;
/// Whether the entry is muted.
bool m_muted;
@ -124,7 +127,7 @@ public:
* \param skip How much seconds should be skipped at the beginning.
* \param id The ID of the entry.
*/
SequenceEntry(std::shared_ptr<ISound> sound, double begin, double end, double skip, int id);
SequenceEntry(std::shared_ptr<ISound> sound, double begin, double end, double skip, float fps, int id);
virtual ~SequenceEntry();
/**

View File

@ -65,6 +65,19 @@ void AnimateableProperty::write(const float* data)
std::memcpy(getBuffer(), data, m_count * sizeof(float));
}
void AnimateableProperty::write_range(const float* data, int position_start, int position_end)
{
assureSize(position_end * m_count * sizeof(float), true);
float* buf = getBuffer();
for(int i = position_start; i < position_end; i++)
{
std::memcpy(buf + i * m_count, data, m_count * sizeof(float));
}
m_isAnimated = true;
}
void AnimateableProperty::write(const float* data, int position, int count)
{
std::lock_guard<std::recursive_mutex> lock(m_mutex);

View File

@ -153,7 +153,7 @@ std::shared_ptr<SequenceEntry> SequenceData::add(std::shared_ptr<ISound> sound,
{
std::lock_guard<std::recursive_mutex> lock(m_mutex);
std::shared_ptr<SequenceEntry> entry = std::shared_ptr<SequenceEntry>(new SequenceEntry(sound, begin, end, skip, m_id++));
std::shared_ptr<SequenceEntry> entry = std::shared_ptr<SequenceEntry>(new SequenceEntry(sound, begin, end, skip, m_fps, m_id++));
m_entries.push_back(entry);
m_entry_status++;

View File

@ -22,7 +22,7 @@
AUD_NAMESPACE_BEGIN
SequenceEntry::SequenceEntry(std::shared_ptr<ISound> sound, double begin, double end, double skip, int id) :
SequenceEntry::SequenceEntry(std::shared_ptr<ISound> sound, double begin, double end, double skip, float fps, int id) :
m_status(0),
m_pos_status(1),
m_sound_status(0),
@ -31,6 +31,7 @@ SequenceEntry::SequenceEntry(std::shared_ptr<ISound> sound, double begin, double
m_begin(begin),
m_end(end),
m_skip(skip),
m_fps(fps),
m_muted(false),
m_relative(true),
m_volume_max(1.0f),

View File

@ -241,10 +241,30 @@ bool SequenceHandle::seek(double position)
return false;
std::lock_guard<ILockable> lock(*m_entry);
double seekpos = position - m_entry->m_begin;
if(seekpos < 0)
seekpos = 0;
seekpos += m_entry->m_skip;
float seek_frame = (position - m_entry->m_begin) * m_entry->m_fps;
if(seek_frame < 0)
seek_frame = 0;
seek_frame += m_entry->m_skip * m_entry->m_fps;
AnimateableProperty* pitch_property = m_entry->getAnimProperty(AP_PITCH);
float target_frame = 0;
if(pitch_property != nullptr)
{
for(int i = 0; i < seek_frame; i++)
{
float pitch;
pitch_property->read(i, &pitch);
target_frame += pitch;
}
}
else
{
target_frame = seek_frame;
}
double seekpos = target_frame / m_entry->m_fps;
m_handle->setPitch(1.0f);
m_handle->seek(seekpos);

Binary file not shown.

View File

@ -1872,12 +1872,6 @@ class SEQUENCER_PT_time(SequencerButtonsPanel, Panel):
split.label(text="Channel")
split.prop(strip, "channel", text="")
if not is_effect:
split = layout.split(factor=0.5 + max_factor)
split.alignment = 'RIGHT'
split.label(text="Speed Factor")
split.prop(strip, "speed_factor", text="")
sub = layout.column(align=True)
split = sub.split(factor=0.5 + max_factor, align=True)
split.alignment = 'RIGHT'

View File

@ -2620,6 +2620,18 @@ class _defs_sequencer_generic:
options={"KEYMAP_FALLBACK"},
)
@ToolDef.from_fn
def retime():
return dict(
idname="builtin.retime",
label="Retime",
icon="ops.sequencer.retime",
widget="SEQUENCER_GGT_gizmo_retime",
operator=None,
keymap=None,
options={'KEYMAP_FALLBACK'},
)
@ToolDef.from_fn
def sample():
return dict(
@ -3308,6 +3320,7 @@ class SEQUENCER_PT_tools_active(ToolSelectPanelHelper, Panel):
"SEQUENCER": [
*_tools_select,
_defs_sequencer_generic.blade,
_defs_sequencer_generic.retime,
],
"SEQUENCER_PREVIEW": [
*_tools_select,

View File

@ -154,6 +154,11 @@ void BKE_sound_set_scene_sound_volume(void *handle, float volume, char animated)
void BKE_sound_set_scene_sound_pitch(void *handle, float pitch, char animated);
void BKE_sound_set_scene_sound_pitch_constant_range(void *handle,
int frame_start,
int frame_end,
float pitch);
void BKE_sound_set_scene_sound_pan(void *handle, float pan, char animated);
void BKE_sound_update_sequencer(struct Main *main, struct bSound *sound);

View File

@ -819,6 +819,15 @@ void BKE_sound_set_scene_sound_pitch(void *handle, float pitch, char animated)
AUD_SequenceEntry_setAnimationData(handle, AUD_AP_PITCH, sound_cfra, &pitch, animated);
}
void BKE_sound_set_scene_sound_pitch_constant_range(void *handle,
int frame_start,
int frame_end,
float pitch)
{
AUD_SequenceEntry_setAnimationData_constant_range(
handle, AUD_AP_PITCH, frame_start, frame_end, &pitch);
}
void BKE_sound_set_scene_sound_pan(void *handle, float pan, char animated)
{
AUD_SequenceEntry_setAnimationData(handle, AUD_AP_PANNING, sound_cfra, &pan, animated);

View File

@ -77,6 +77,7 @@
#include "SEQ_channels.h"
#include "SEQ_iterator.h"
#include "SEQ_retiming.h"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
@ -685,6 +686,25 @@ static bool seq_speed_factor_set(Sequence *seq, void *user_data)
return true;
}
static bool do_versions_sequencer_init_retiming_tool_data(Sequence *seq, void *user_data)
{
const Scene *scene = static_cast<const Scene *>(user_data);
if (seq->speed_factor == 1 || !SEQ_retiming_is_allowed(seq)) {
return true;
}
const int content_length = SEQ_time_strip_length_get(scene, seq);
SEQ_retiming_data_ensure(scene, seq);
SeqRetimingHandle *handle = &seq->retiming_handles[seq->retiming_handle_num - 1];
handle->strip_frame_index = round_fl_to_int(content_length / seq->speed_factor);
seq->speed_factor = 0.0f;
return true;
}
static void version_geometry_nodes_replace_transfer_attribute_node(bNodeTree *ntree)
{
using namespace blender;
@ -1205,6 +1225,16 @@ void do_versions_after_linking_300(Main *bmain, ReportList * /*reports*/)
*/
{
/* Keep this block, even when empty. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
Editing *ed = SEQ_editing_get(scene);
if (ed == nullptr) {
continue;
}
SEQ_for_each_callback(
&scene->ed->seqbase, do_versions_sequencer_init_retiming_tool_data, scene);
}
}
}

View File

@ -867,6 +867,7 @@ set_property(GLOBAL PROPERTY ICON_GEOM_NAMES
ops.sculpt.mask_by_color
ops.sculpt.mesh_filter
ops.sequencer.blade
ops.sequencer.retime
ops.transform.bone_envelope
ops.transform.bone_size
ops.transform.edge_slide

View File

@ -35,10 +35,13 @@ set(SRC
sequencer_drag_drop.c
sequencer_draw.c
sequencer_edit.c
sequencer_gizmo_retime.cc
sequencer_gizmo_retime_type.cc
sequencer_modifier.c
sequencer_ops.c
sequencer_preview.c
sequencer_proxy.c
sequencer_retiming.cc
sequencer_scopes.c
sequencer_select.c
sequencer_thumbnails.c

View File

@ -413,7 +413,6 @@ static void draw_seq_waveform_overlay(
const float frames_per_pixel = BLI_rctf_size_x(&region->v2d.cur) / region->winx;
const float samples_per_frame = SOUND_WAVE_SAMPLES_PER_SECOND / FPS;
float samples_per_pixel = samples_per_frame * frames_per_pixel;
/* Align strip start with nearest pixel to prevent waveform flickering. */
const float x1_aligned = align_frame_with_pixel(x1, frames_per_pixel);
@ -439,15 +438,17 @@ static void draw_seq_waveform_overlay(
size_t wave_data_len = 0;
/* Offset must be also aligned, otherwise waveform flickers when moving left handle. */
const float strip_offset = align_frame_with_pixel(seq->startofs + seq->anim_startofs,
frames_per_pixel);
float start_sample = strip_offset * samples_per_frame;
start_sample += seq->sound->offset_time * SOUND_WAVE_SAMPLES_PER_SECOND;
float start_frame = SEQ_time_left_handle_frame_get(scene, seq);
/* Add off-screen part of strip to offset. */
start_sample += (frame_start - x1_aligned) * samples_per_frame;
start_frame += (frame_start - x1_aligned);
start_frame += seq->sound->offset_time / FPS;
for (int i = 0; i < pixels_to_draw; i++) {
float sample = start_sample + i * samples_per_pixel;
float timeline_frame = start_frame + i * frames_per_pixel;
/* TODO: Use linear interpolation between frames to avoid bad drawing quality. */
float frame_index = SEQ_give_frame_index(scene, seq, timeline_frame);
float sample = frame_index * samples_per_frame;
int sample_index = round_fl_to_int(sample);
if (sample_index < 0) {
@ -468,6 +469,8 @@ static void draw_seq_waveform_overlay(
value_min = (1.0f - f) * value_min + f * waveform->data[sample_index * 3 + 3];
value_max = (1.0f - f) * value_max + f * waveform->data[sample_index * 3 + 4];
rms = (1.0f - f) * rms + f * waveform->data[sample_index * 3 + 5];
float samples_per_pixel = samples_per_frame * frames_per_pixel;
if (samples_per_pixel > 1.0f) {
/* We need to sum up the values we skip over until the next step. */
float next_pos = sample + samples_per_pixel;

View File

@ -0,0 +1,108 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
/** \file
* \ingroup spseq
*/
#include "MEM_guardedalloc.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "BKE_context.h"
#include "BLI_span.hh"
#include "RNA_access.h"
#include "UI_resources.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_gizmo_library.h"
#include "ED_gizmo_utils.h"
#include "SEQ_iterator.h"
#include "SEQ_retiming.h"
#include "SEQ_retiming.hh"
#include "SEQ_sequencer.h"
/* Own include. */
#include "sequencer_intern.h"
typedef struct GizmoGroup_retime {
wmGizmo *add_handle_gizmo;
wmGizmo *move_handle_gizmo;
wmGizmo *remove_handle_gizmo;
} GizmoGroup_retime;
static bool gizmogroup_retime_poll(const bContext *C, wmGizmoGroupType *gzgt)
{
/* Needed to prevent drawing gizmos when retiming tool is not activated. */
if (!ED_gizmo_poll_or_unlink_delayed_from_tool(C, gzgt)) {
return false;
}
if ((U.gizmo_flag & USER_GIZMO_DRAW) == 0) {
return false;
}
ScrArea *area = CTX_wm_area(C);
if (area == nullptr && area->spacetype != SPACE_SEQ) {
return false;
}
const SpaceSeq *sseq = (SpaceSeq *)area->spacedata.first;
if (sseq->gizmo_flag & (SEQ_GIZMO_HIDE | SEQ_GIZMO_HIDE_TOOL)) {
return false;
}
Editing *ed = SEQ_editing_get(CTX_data_scene(C));
Sequence *seq = ed->act_seq;
if (ed == nullptr || seq == nullptr || !SEQ_retiming_is_allowed(seq)) {
return false;
}
return true;
}
static void gizmogroup_retime_setup(const bContext * /* C */, wmGizmoGroup *gzgroup)
{
GizmoGroup_retime *ggd = (GizmoGroup_retime *)MEM_callocN(sizeof(GizmoGroup_retime), __func__);
/* Assign gizmos. */
const wmGizmoType *gzt_add_handle = WM_gizmotype_find("GIZMO_GT_retime_handle_add", true);
ggd->add_handle_gizmo = WM_gizmo_new_ptr(gzt_add_handle, gzgroup, nullptr);
const wmGizmoType *gzt_remove_handle = WM_gizmotype_find("GIZMO_GT_retime_handle_remove", true);
ggd->remove_handle_gizmo = WM_gizmo_new_ptr(gzt_remove_handle, gzgroup, nullptr);
const wmGizmoType *gzt_move_handle = WM_gizmotype_find("GIZMO_GT_retime_handle_move", true);
ggd->move_handle_gizmo = WM_gizmo_new_ptr(gzt_move_handle, gzgroup, nullptr);
gzgroup->customdata = ggd;
/* Assign operators. */
wmOperatorType *ot = WM_operatortype_find("SEQUENCER_OT_retiming_handle_move", true);
WM_gizmo_operator_set(ggd->move_handle_gizmo, 0, ot, nullptr);
ot = WM_operatortype_find("SEQUENCER_OT_retiming_handle_add", true);
WM_gizmo_operator_set(ggd->add_handle_gizmo, 0, ot, nullptr);
ot = WM_operatortype_find("SEQUENCER_OT_retiming_handle_remove", true);
WM_gizmo_operator_set(ggd->remove_handle_gizmo, 0, ot, nullptr);
}
void SEQUENCER_GGT_gizmo_retime(wmGizmoGroupType *gzgt)
{
gzgt->name = "Sequencer Transform Gizmo Retime";
gzgt->idname = "SEQUENCER_GGT_gizmo_retime";
gzgt->flag = WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL;
gzgt->gzmap_params.spaceid = SPACE_SEQ;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
gzgt->poll = gizmogroup_retime_poll;
gzgt->setup = gizmogroup_retime_setup;
}
/** \} */

View File

@ -0,0 +1,568 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
/** \file
* \ingroup spseq
*/
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_span.hh"
#include "DNA_anim_types.h"
#include "DNA_sequence_types.h"
#include "BKE_context.h"
#include "BKE_fcurve.h"
#include "BKE_scene.h"
#include "BLF_api.h"
#include "GPU_batch.h"
#include "GPU_batch_utils.h"
#include "GPU_immediate.h"
#include "GPU_immediate_util.h"
#include "GPU_matrix.h"
#include "GPU_select.h"
#include "GPU_state.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_gizmo_library.h"
#include "ED_screen.h"
#include "ED_view3d.h"
#include "UI_interface.h"
#include "UI_interface_icons.h"
#include "UI_resources.h"
#include "UI_view2d.h"
#include "SEQ_iterator.h"
#include "SEQ_retiming.h"
#include "SEQ_retiming.hh"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
/* Own include. */
#include "sequencer_intern.h"
using blender::MutableSpan;
#define REMOVE_GIZMO_HEIGHT 14.0f * U.dpi_fac /* Pixels from bottom of strip. */
#define RETIME_HANDLE_TRIANGLE_SIZE 14.0f * U.dpi_fac /* Size in pixels. */
#define RETIME_HANDLE_MOUSEOVER_THRESHOLD 16.0f * U.dpi_fac /* Size in pixels. */
#define RETIME_BUTTON_SIZE 0.6f /* Factor based on icon size. */
static float strip_y_rescale(const Sequence *seq, const float y_value)
{
const float y_range = SEQ_STRIP_OFSTOP - SEQ_STRIP_OFSBOTTOM;
return (y_value * y_range) + seq->machine + SEQ_STRIP_OFSBOTTOM;
}
static float handle_x_get(const Sequence *seq, const SeqRetimingHandle *handle)
{
const SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq);
const bool is_last_handle = (handle == last_handle);
return SEQ_time_start_frame_get(seq) + handle->strip_frame_index + (is_last_handle ? 1 : 0);
}
static const SeqRetimingHandle *mouse_over_handle_get(const Sequence *seq,
const View2D *v2d,
const int mval[2])
{
int best_distance = INT_MAX;
const SeqRetimingHandle *best_handle = nullptr;
MutableSpan handles = SEQ_retiming_handles_get(seq);
for (const SeqRetimingHandle &handle : handles) {
int distance = round_fl_to_int(
fabsf(UI_view2d_view_to_region_x(v2d, handle_x_get(seq, &handle)) - mval[0]));
if (distance < RETIME_HANDLE_MOUSEOVER_THRESHOLD && distance < best_distance) {
best_distance = distance;
best_handle = &handle;
}
}
return best_handle;
}
static float pixels_to_view_width(const bContext *C, const float width)
{
const View2D *v2d = UI_view2d_fromcontext(C);
float scale_x = UI_view2d_view_to_region_x(v2d, 1) - UI_view2d_view_to_region_x(v2d, 0.0f);
return width / scale_x;
}
static float pixels_to_view_height(const bContext *C, const float height)
{
const View2D *v2d = UI_view2d_fromcontext(C);
float scale_y = UI_view2d_view_to_region_y(v2d, 1) - UI_view2d_view_to_region_y(v2d, 0.0f);
return height / scale_y;
}
static float strip_start_screenspace_get(const bContext *C, const Sequence *seq)
{
const View2D *v2d = UI_view2d_fromcontext(C);
const Scene *scene = CTX_data_scene(C);
return UI_view2d_view_to_region_x(v2d, SEQ_time_left_handle_frame_get(scene, seq));
}
static float strip_end_screenspace_get(const bContext *C, const Sequence *seq)
{
const View2D *v2d = UI_view2d_fromcontext(C);
const Scene *scene = CTX_data_scene(C);
return UI_view2d_view_to_region_x(v2d, SEQ_time_right_handle_frame_get(scene, seq));
}
static Sequence *active_seq_from_context(const bContext *C)
{
const Editing *ed = SEQ_editing_get(CTX_data_scene(C));
return ed->act_seq;
}
static rctf strip_box_get(const bContext *C, const Sequence *seq)
{
const View2D *v2d = UI_view2d_fromcontext(C);
rctf rect;
rect.xmin = strip_start_screenspace_get(C, seq);
rect.xmax = strip_end_screenspace_get(C, seq);
rect.ymin = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 0));
rect.ymax = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 1));
return rect;
}
static rctf remove_box_get(const bContext *C, const Sequence *seq)
{
rctf rect = strip_box_get(C, seq);
rect.ymax = rect.ymin + REMOVE_GIZMO_HEIGHT;
return rect;
}
static bool mouse_is_inside_box(const rctf *box, const int mval[2])
{
return mval[0] >= box->xmin && mval[0] <= box->xmax && mval[1] >= box->ymin &&
mval[1] <= box->ymax;
}
/* -------------------------------------------------------------------- */
/** \name Retiming Add Handle Gizmo
* \{ */
typedef struct RetimeButtonGizmo {
wmGizmo gizmo;
int icon_id;
const Sequence *seq_under_mouse;
bool is_mouse_over_gizmo;
} RetimeButtonGizmo;
typedef struct ButtonDimensions {
float height;
float width;
float x;
float y;
} ButtonDimensions;
static ButtonDimensions button_dimensions_get(const bContext *C, const RetimeButtonGizmo *gizmo)
{
const Scene *scene = CTX_data_scene(C);
const View2D *v2d = UI_view2d_fromcontext(C);
const Sequence *seq = active_seq_from_context(C);
const float icon_height = UI_icon_get_height(gizmo->icon_id) * U.dpi_fac;
const float icon_width = UI_icon_get_width(gizmo->icon_id) * U.dpi_fac;
const float icon_x = UI_view2d_view_to_region_x(v2d, BKE_scene_frame_get(scene)) +
icon_width / 2;
const float icon_y = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 0.5)) -
icon_height / 2;
const ButtonDimensions dimensions = {icon_height, icon_width, icon_x, icon_y};
return dimensions;
}
static rctf button_box_get(const bContext *C, const RetimeButtonGizmo *gizmo)
{
ButtonDimensions button_dimensions = button_dimensions_get(C, gizmo);
float x_range = button_dimensions.width;
float y_range = button_dimensions.height;
rctf rect;
rect.xmin = button_dimensions.x;
rect.xmax = button_dimensions.x + x_range;
rect.ymin = button_dimensions.y;
rect.ymax = button_dimensions.y + y_range;
return rect;
}
static void gizmo_retime_handle_add_draw(const bContext *C, wmGizmo *gz)
{
RetimeButtonGizmo *gizmo = (RetimeButtonGizmo *)gz;
if (ED_screen_animation_playing(CTX_wm_manager(C))) {
return;
}
const ButtonDimensions button = button_dimensions_get(C, gizmo);
const rctf strip_box = strip_box_get(C, active_seq_from_context(C));
if (!BLI_rctf_isect_pt(&strip_box, button.x, button.y)) {
return;
}
wmOrtho2_region_pixelspace(CTX_wm_region(C));
GPU_blend(GPU_BLEND_ALPHA);
uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
const float alpha = gizmo->is_mouse_over_gizmo ? 1.0f : 0.6f;
immUniformColor4f(0.2f, 0.2f, 0.2f, alpha);
imm_draw_circle_fill_2d(pos,
button.x + button.width / 2,
button.y + button.height / 2,
button.width * RETIME_BUTTON_SIZE,
32);
immUnbindProgram();
GPU_polygon_smooth(false);
UI_icon_draw_alpha(button.x, button.y, gizmo->icon_id, alpha);
GPU_polygon_smooth(true);
GPU_blend(GPU_BLEND_NONE);
}
static int gizmo_retime_handle_add_test_select(bContext *C, wmGizmo *gz, const int mval[2])
{
RetimeButtonGizmo *gizmo = (RetimeButtonGizmo *)gz;
Sequence *seq = active_seq_from_context(C);
Sequence *mouse_over_seq = nullptr;
gizmo->is_mouse_over_gizmo = false;
/* Store strip under mouse cursor. */
const rctf strip_box = strip_box_get(C, seq);
if (mouse_is_inside_box(&strip_box, mval)) {
mouse_over_seq = seq;
}
if (gizmo->seq_under_mouse != mouse_over_seq) {
gizmo->seq_under_mouse = mouse_over_seq;
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, CTX_data_scene(C));
}
if (gizmo->seq_under_mouse == nullptr) {
return -1;
}
const rctf button_box = button_box_get(C, gizmo);
if (!mouse_is_inside_box(&button_box, mval)) {
return -1;
}
gizmo->is_mouse_over_gizmo = true;
const Scene *scene = CTX_data_scene(C);
wmGizmoOpElem *op_elem = WM_gizmo_operator_get(gz, 0);
RNA_int_set(&op_elem->ptr, "timeline_frame", BKE_scene_frame_get(scene));
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, CTX_data_scene(C));
return 0;
}
static void gizmo_retime_handle_add_setup(wmGizmo *gz)
{
RetimeButtonGizmo *gizmo = (RetimeButtonGizmo *)gz;
gizmo->icon_id = ICON_ADD;
}
void GIZMO_GT_retime_handle_add(wmGizmoType *gzt)
{
/* Identifiers. */
gzt->idname = "GIZMO_GT_retime_handle_add";
/* Api callbacks. */
gzt->setup = gizmo_retime_handle_add_setup;
gzt->draw = gizmo_retime_handle_add_draw;
gzt->test_select = gizmo_retime_handle_add_test_select;
gzt->struct_size = sizeof(RetimeButtonGizmo);
/* Currently only used for cursor display. */
RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Retiming Move Handle Gizmo
* \{ */
typedef struct RetimeHandleMoveGizmo {
wmGizmo gizmo;
const Sequence *mouse_over_seq;
int mouse_over_handle_x;
} RetimeHandleMoveGizmo;
static void retime_handle_draw(const bContext *C,
const RetimeHandleMoveGizmo *gizmo,
uint pos,
const Sequence *seq,
const SeqRetimingHandle *handle)
{
const Scene *scene = CTX_data_scene(C);
const float handle_x = handle_x_get(seq, handle);
if (handle_x == SEQ_time_left_handle_frame_get(scene, seq)) {
return;
}
if (handle_x == SEQ_time_right_handle_frame_get(scene, seq)) {
return;
}
const View2D *v2d = UI_view2d_fromcontext(C);
const rctf strip_box = strip_box_get(C, seq);
if (!BLI_rctf_isect_x(&strip_box, UI_view2d_view_to_region_x(v2d, handle_x))) {
return; /* Handle out of strip bounds. */
}
const int ui_triangle_size = RETIME_HANDLE_TRIANGLE_SIZE;
const float bottom = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 0.0f)) + 2;
const float top = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 1.0f)) - 2;
const float handle_position = UI_view2d_view_to_region_x(v2d, handle_x);
if (seq == gizmo->mouse_over_seq && handle_x == gizmo->mouse_over_handle_x) {
immUniformColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
else {
immUniformColor4f(0.65f, 0.65f, 0.65f, 1.0f);
}
immBegin(GPU_PRIM_TRI_FAN, 3);
immVertex2f(pos, handle_position - ui_triangle_size / 2, bottom);
immVertex2f(pos, handle_position + ui_triangle_size / 2, bottom);
immVertex2f(pos, handle_position, bottom + ui_triangle_size);
immEnd();
immBegin(GPU_PRIM_LINES, 2);
immVertex2f(pos, handle_position, bottom);
immVertex2f(pos, handle_position, top);
immEnd();
}
static void retime_speed_text_draw(const bContext *C,
const Sequence *seq,
const SeqRetimingHandle *handle)
{
SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq);
if (handle == last_handle) {
return;
}
const Scene *scene = CTX_data_scene(C);
const int start_frame = SEQ_time_left_handle_frame_get(scene, seq);
const int end_frame = SEQ_time_right_handle_frame_get(scene, seq);
int next_handle_index = SEQ_retiming_handle_index_get(seq, handle) + 1;
const SeqRetimingHandle *next_handle = &SEQ_retiming_handles_get(seq)[next_handle_index];
if (handle_x_get(seq, next_handle) < start_frame || handle_x_get(seq, handle) > end_frame) {
return; /* Label out of strip bounds. */
}
const float speed = SEQ_retiming_handle_speed_get(scene, seq, next_handle);
char label_str[20];
const size_t label_len = BLI_snprintf_rlen(
label_str, sizeof(label_str), "%d%%", round_fl_to_int(speed * 100.0f));
const float width = pixels_to_view_width(C, BLF_width(BLF_default(), label_str, label_len));
const float xmin = max_ff(SEQ_time_left_handle_frame_get(scene, seq), handle_x_get(seq, handle));
const float xmax = min_ff(SEQ_time_right_handle_frame_get(scene, seq),
handle_x_get(seq, next_handle));
const float text_x = (xmin + xmax - width) / 2;
const float text_y = strip_y_rescale(seq, 0) + pixels_to_view_height(C, 5);
if (width > xmax - xmin) {
return; /* Not enough space to draw label. */
}
const uchar col[4] = {255, 255, 255, 255};
UI_view2d_text_cache_add(UI_view2d_fromcontext(C), text_x, text_y, label_str, label_len, col);
}
static void gizmo_retime_handle_draw(const bContext *C, wmGizmo *gz)
{
const RetimeHandleMoveGizmo *gizmo = (RetimeHandleMoveGizmo *)gz;
const View2D *v2d = UI_view2d_fromcontext(C);
wmOrtho2_region_pixelspace(CTX_wm_region(C));
GPU_blend(GPU_BLEND_ALPHA);
uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
Sequence *seq = active_seq_from_context(C);
SEQ_retiming_data_ensure(CTX_data_scene(C), seq);
MutableSpan handles = SEQ_retiming_handles_get(seq);
for (const SeqRetimingHandle &handle : handles) {
retime_speed_text_draw(C, seq, &handle);
if (&handle == handles.begin()) {
continue; /* Ignore first handle. */
}
retime_handle_draw(C, gizmo, pos, seq, &handle);
}
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
UI_view2d_text_cache_draw(CTX_wm_region(C));
UI_view2d_view_ortho(v2d); /* 'UI_view2d_text_cache_draw()' messes up current view. */
}
static int gizmo_retime_handle_test_select(bContext *C, wmGizmo *gz, const int mval[2])
{
RetimeHandleMoveGizmo *gizmo = (RetimeHandleMoveGizmo *)gz;
Scene *scene = CTX_data_scene(C);
gizmo->mouse_over_seq = nullptr;
Sequence *seq = active_seq_from_context(C);
SEQ_retiming_data_ensure(CTX_data_scene(C), seq);
const SeqRetimingHandle *handle = mouse_over_handle_get(seq, UI_view2d_fromcontext(C), mval);
const int handle_index = SEQ_retiming_handle_index_get(seq, handle);
if (handle == nullptr) {
return -1;
}
if (handle_x_get(seq, handle) == SEQ_time_left_handle_frame_get(scene, seq) ||
handle_index == 0) {
return -1;
}
rctf strip_box = strip_box_get(C, seq);
BLI_rctf_resize_x(&strip_box, BLI_rctf_size_x(&strip_box) + 2 * RETIME_HANDLE_TRIANGLE_SIZE);
if (!mouse_is_inside_box(&strip_box, mval)) {
return -1;
}
gizmo->mouse_over_seq = seq;
gizmo->mouse_over_handle_x = handle_x_get(seq, handle);
wmGizmoOpElem *op_elem = WM_gizmo_operator_get(gz, 0);
RNA_int_set(&op_elem->ptr, "handle_index", handle_index);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return 0;
}
static int gizmo_retime_handle_cursor_get(wmGizmo *gz)
{
if (RNA_boolean_get(gz->ptr, "show_drag")) {
return WM_CURSOR_EW_SCROLL;
}
return WM_CURSOR_DEFAULT;
}
static void gizmo_retime_handle_setup(wmGizmo *gz)
{
gz->flag = WM_GIZMO_DRAW_MODAL;
}
void GIZMO_GT_retime_handle(wmGizmoType *gzt)
{
/* Identifiers. */
gzt->idname = "GIZMO_GT_retime_handle_move";
/* Api callbacks. */
gzt->setup = gizmo_retime_handle_setup;
gzt->draw = gizmo_retime_handle_draw;
gzt->test_select = gizmo_retime_handle_test_select;
gzt->cursor_get = gizmo_retime_handle_cursor_get;
gzt->struct_size = sizeof(RetimeHandleMoveGizmo);
/* Currently only used for cursor display. */
RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Retiming Remove Handle Gizmo
* \{ */
static void gizmo_retime_remove_draw(const bContext * /* C */, wmGizmo * /* gz */)
{
/* Pass. */
}
static int gizmo_retime_remove_test_select(bContext *C, wmGizmo *gz, const int mval[2])
{
Scene *scene = CTX_data_scene(C);
Sequence *seq = active_seq_from_context(C);
SEQ_retiming_data_ensure(CTX_data_scene(C), seq);
const SeqRetimingHandle *handle = mouse_over_handle_get(seq, UI_view2d_fromcontext(C), mval);
const int handle_index = SEQ_retiming_handle_index_get(seq, handle);
if (handle == nullptr) {
return -1;
}
if (handle_x_get(seq, handle) == SEQ_time_left_handle_frame_get(scene, seq) ||
handle_index == 0) {
return -1; /* Ignore first handle. */
}
SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq);
if (handle == last_handle) {
return -1; /* Last handle can not be removed. */
}
rctf box = remove_box_get(C, seq);
BLI_rctf_resize_x(&box, BLI_rctf_size_x(&box) + 2 * RETIME_HANDLE_TRIANGLE_SIZE);
if (!mouse_is_inside_box(&box, mval)) {
return -1;
}
wmGizmoOpElem *op_elem = WM_gizmo_operator_get(gz, 0);
RNA_int_set(&op_elem->ptr, "handle_index", handle_index);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return 0;
}
static int gizmo_retime_remove_cursor_get(wmGizmo *gz)
{
if (RNA_boolean_get(gz->ptr, "show_drag")) {
return WM_CURSOR_ERASER;
}
return WM_CURSOR_DEFAULT;
}
void GIZMO_GT_retime_remove(wmGizmoType *gzt)
{
/* Identifiers. */
gzt->idname = "GIZMO_GT_retime_handle_remove";
/* Api callbacks. */
gzt->draw = gizmo_retime_remove_draw;
gzt->test_select = gizmo_retime_remove_test_select;
gzt->cursor_get = gizmo_retime_remove_cursor_get;
gzt->struct_size = sizeof(wmGizmo);
/* Currently only used for cursor display. */
RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", "");
}
/** \} */

View File

@ -10,11 +10,17 @@
#include "DNA_sequence_types.h"
#include "RNA_access.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Internal exports only. */
struct ARegion;
struct ARegionType;
struct Depsgraph;
struct wmGizmoGroupType;
struct wmGizmoType;
struct Main;
struct Scene;
struct SeqCollection;
@ -298,3 +304,21 @@ int sequencer_image_seq_get_minmax_frame(struct wmOperator *op,
int *r_numdigits);
void sequencer_image_seq_reserve_frames(
struct wmOperator *op, struct StripElem *se, int len, int minframe, int numdigits);
/* sequencer_retiming.c */
void SEQUENCER_OT_retiming_reset(struct wmOperatorType *ot);
void SEQUENCER_OT_retiming_handle_move(struct wmOperatorType *ot);
void SEQUENCER_OT_retiming_handle_add(struct wmOperatorType *ot);
void SEQUENCER_OT_retiming_handle_remove(struct wmOperatorType *ot);
/* sequencer_gizmo_retime.c */
void SEQUENCER_GGT_gizmo_retime(struct wmGizmoGroupType *gzgt);
/* sequencer_gizmo_retime_type.c */
void GIZMO_GT_retime_handle_add(struct wmGizmoType *gzt);
void GIZMO_GT_retime_handle(struct wmGizmoType *gzt);
void GIZMO_GT_retime_remove(struct wmGizmoType *gzt);
#ifdef __cplusplus
}
#endif

View File

@ -68,6 +68,12 @@ void sequencer_operatortypes(void)
WM_operatortype_append(SEQUENCER_OT_cursor_set);
WM_operatortype_append(SEQUENCER_OT_scene_frame_range_update);
/* sequencer_retiming.c */
WM_operatortype_append(SEQUENCER_OT_retiming_reset);
WM_operatortype_append(SEQUENCER_OT_retiming_handle_move);
WM_operatortype_append(SEQUENCER_OT_retiming_handle_add);
WM_operatortype_append(SEQUENCER_OT_retiming_handle_remove);
/* sequencer_select.c */
WM_operatortype_append(SEQUENCER_OT_select_all);
WM_operatortype_append(SEQUENCER_OT_select);

View File

@ -0,0 +1,377 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2001-2002 NaN Holding BV. All rights reserved. */
/** \file
* \ingroup spseq
*/
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "DNA_anim_types.h"
#include "DNA_scene_types.h"
#include "BKE_context.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "SEQ_iterator.h"
#include "SEQ_relations.h"
#include "SEQ_retiming.h"
#include "SEQ_retiming.hh"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
#include "SEQ_transform.h"
#include "WM_api.h"
#include "RNA_define.h"
#include "UI_interface.h"
#include "UI_view2d.h"
#include "DEG_depsgraph.h"
/* Own include. */
#include "sequencer_intern.h"
using blender::MutableSpan;
static bool retiming_poll(bContext *C)
{
if (!sequencer_edit_poll(C)) {
return false;
}
const Editing *ed = SEQ_editing_get(CTX_data_scene(C));
Sequence *seq = ed->act_seq;
if (seq != nullptr && !SEQ_retiming_is_allowed(seq)) {
CTX_wm_operator_poll_msg_set(C, "This strip type can not be retimed");
return false;
}
return true;
}
static void retiming_handle_overlap(Scene *scene, Sequence *seq)
{
ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene));
SeqCollection *strips = SEQ_collection_create(__func__);
SEQ_collection_append_strip(seq, strips);
SeqCollection *dependant = SEQ_collection_create(__func__);
SEQ_collection_expand(scene, seqbase, strips, SEQ_query_strip_effect_chain);
SEQ_collection_remove_strip(seq, dependant);
SEQ_transform_handle_overlap(scene, seqbase, strips, dependant, true);
SEQ_collection_free(strips);
SEQ_collection_free(dependant);
}
/*-------------------------------------------------------------------- */
/** \name Retiming Reset
* \{ */
static int sequencer_retiming_reset_exec(bContext *C, wmOperator * /* op */)
{
Scene *scene = CTX_data_scene(C);
const Editing *ed = SEQ_editing_get(scene);
Sequence *seq = ed->act_seq;
SEQ_retiming_data_clear(seq);
retiming_handle_overlap(scene, seq);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_retiming_reset(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Reset Retiming";
ot->description = "Reset strip retiming";
ot->idname = "SEQUENCER_OT_retiming_reset";
/* api callbacks */
ot->exec = sequencer_retiming_reset_exec;
ot->poll = retiming_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Retiming Move Handle
* \{ */
static SeqRetimingHandle *closest_retiming_handle_get(const bContext *C,
const Sequence *seq,
const float mouse_x)
{
const View2D *v2d = UI_view2d_fromcontext(C);
int best_distance = INT_MAX;
SeqRetimingHandle *closest_handle = nullptr;
const float distance_threshold = UI_view2d_region_to_view_x(v2d, 10);
const float mouse_x_view = UI_view2d_region_to_view_x(v2d, mouse_x);
for (int i = 0; i < SEQ_retiming_handles_count(seq); i++) {
SeqRetimingHandle *handle = seq->retiming_handles + i;
const int distance = round_fl_to_int(fabsf(handle->strip_frame_index - mouse_x_view));
if (distance < distance_threshold && distance < best_distance) {
best_distance = distance;
closest_handle = handle;
}
}
return closest_handle;
}
static int sequencer_retiming_handle_move_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Scene *scene = CTX_data_scene(C);
const Editing *ed = SEQ_editing_get(scene);
Sequence *seq = ed->act_seq;
int handle_index = 0;
if (RNA_struct_property_is_set(op->ptr, "handle_index")) {
handle_index = RNA_int_get(op->ptr, "handle_index");
}
/* Ensure retiming handle at left handle position. This way user gets more predictable result
* when strips have offsets. */
const int left_handle_frame = SEQ_time_left_handle_frame_get(scene, seq);
if (SEQ_retiming_add_handle(seq, left_handle_frame) != nullptr) {
handle_index++; /* Advance index, because new handle was created. */
}
MutableSpan handles = SEQ_retiming_handles_get(seq);
SeqRetimingHandle *handle = nullptr;
if (RNA_struct_property_is_set(op->ptr, "handle_index")) {
BLI_assert(handle_index < handles.size());
handle = &handles[handle_index];
}
else {
handle = closest_retiming_handle_get(C, seq, event->mval[0]);
}
if (handle == nullptr) {
BKE_report(op->reports, RPT_ERROR, "No handle available");
return OPERATOR_CANCELLED;
}
op->customdata = handle;
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int sequencer_retiming_handle_move_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
Scene *scene = CTX_data_scene(C);
const ARegion *region = CTX_wm_region(C);
const View2D *v2d = &region->v2d;
const Editing *ed = SEQ_editing_get(scene);
Sequence *seq = ed->act_seq;
switch (event->type) {
case MOUSEMOVE: {
float mouse_x = UI_view2d_region_to_view_x(v2d, event->mval[0]);
int offset = 0;
SeqRetimingHandle *handle = (SeqRetimingHandle *)op->customdata;
SeqRetimingHandle *handle_prev = handle - 1;
/* Limit retiming handle movement. */
int xmin = SEQ_time_start_frame_get(seq) + handle_prev->strip_frame_index + 1;
mouse_x = max_ff(xmin, mouse_x);
offset = mouse_x - (SEQ_time_start_frame_get(seq) + handle->strip_frame_index);
SEQ_retiming_offset_handle(scene, seq, handle, offset);
SEQ_relations_invalidate_cache_raw(scene, seq);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_RUNNING_MODAL;
}
case LEFTMOUSE:
case EVT_RETKEY:
case EVT_SPACEKEY: {
retiming_handle_overlap(scene, seq);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
case EVT_ESCKEY:
case RIGHTMOUSE: {
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_CANCELLED;
}
}
return OPERATOR_RUNNING_MODAL;
}
void SEQUENCER_OT_retiming_handle_move(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Move Retiming Handle";
ot->description = "Move retiming handle";
ot->idname = "SEQUENCER_OT_retiming_handle_move";
/* api callbacks */
ot->invoke = sequencer_retiming_handle_move_invoke;
ot->modal = sequencer_retiming_handle_move_modal;
ot->poll = retiming_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int(ot->srna,
"handle_index",
0,
0,
INT_MAX,
"Handle Index",
"Index of handle to be moved",
0,
INT_MAX);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Retiming Add Handle
* \{ */
static int sequesequencer_retiming_handle_add_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
const Editing *ed = SEQ_editing_get(scene);
Sequence *seq = ed->act_seq;
float timeline_frame;
if (RNA_struct_property_is_set(op->ptr, "timeline_frame")) {
timeline_frame = RNA_int_get(op->ptr, "timeline_frame");
}
else {
timeline_frame = BKE_scene_frame_get(scene);
}
bool inserted = false;
const float end_frame = seq->start + SEQ_time_strip_length_get(scene, seq);
if (seq->start < timeline_frame && end_frame > timeline_frame) {
SEQ_retiming_add_handle(seq, timeline_frame);
inserted = true;
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return inserted ? OPERATOR_FINISHED : OPERATOR_PASS_THROUGH;
}
void SEQUENCER_OT_retiming_handle_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Retiming Handle";
ot->description = "Add retiming Handle";
ot->idname = "SEQUENCER_OT_retiming_handle_add";
/* api callbacks */
ot->exec = sequesequencer_retiming_handle_add_exec;
ot->poll = retiming_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int(ot->srna,
"timeline_frame",
0,
0,
INT_MAX,
"Timeline Frame",
"Frame where handle will be added",
0,
INT_MAX);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Retiming Remove Handle
* \{ */
static int sequencer_retiming_handle_remove_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
const Editing *ed = SEQ_editing_get(scene);
Sequence *seq = ed->act_seq;
SeqRetimingHandle *handle = (SeqRetimingHandle *)op->customdata;
SEQ_retiming_remove_handle(seq, handle);
SEQ_relations_invalidate_cache_raw(scene, seq);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static int sequencer_retiming_handle_remove_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
const Scene *scene = CTX_data_scene(C);
const Editing *ed = SEQ_editing_get(scene);
const Sequence *seq = ed->act_seq;
if (seq == nullptr) {
return OPERATOR_CANCELLED;
}
MutableSpan handles = SEQ_retiming_handles_get(seq);
SeqRetimingHandle *handle = nullptr;
if (RNA_struct_property_is_set(op->ptr, "handle_index")) {
const int handle_index = RNA_int_get(op->ptr, "handle_index");
BLI_assert(handle_index < handles.size());
handle = &handles[handle_index];
}
else {
handle = closest_retiming_handle_get(C, seq, event->mval[0]);
}
if (handle == nullptr) {
BKE_report(op->reports, RPT_ERROR, "No handle available");
return OPERATOR_CANCELLED;
}
op->customdata = handle;
return sequencer_retiming_handle_remove_exec(C, op);
}
void SEQUENCER_OT_retiming_handle_remove(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Remove Retiming Handle";
ot->description = "Remove retiming handle";
ot->idname = "SEQUENCER_OT_retiming_handle_remove";
/* api callbacks */
ot->invoke = sequencer_retiming_handle_remove_invoke;
ot->exec = sequencer_retiming_handle_remove_exec;
ot->poll = retiming_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_int(ot->srna,
"handle_index",
0,
0,
INT_MAX,
"Handle Index",
"Index of handle to be removed",
0,
INT_MAX);
}
/** \} */

View File

@ -494,10 +494,15 @@ static void sequencer_gizmos(void)
wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure(
&(const struct wmGizmoMapType_Params){SPACE_SEQ, RGN_TYPE_PREVIEW});
WM_gizmotype_append(GIZMO_GT_retime_handle_add);
WM_gizmotype_append(GIZMO_GT_retime_handle);
WM_gizmotype_append(GIZMO_GT_retime_remove);
WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d);
WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_translate);
WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_resize);
WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_rotate);
WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo_retime);
WM_gizmogrouptype_append_and_link(gzmap_type, SEQUENCER_GGT_navigate);
}
@ -645,6 +650,7 @@ static void sequencer_main_region_listener(const wmRegionListenerParams *params)
case ND_SEQUENCER:
case ND_RENDER_RESULT:
ED_region_tag_redraw(region);
WM_gizmomap_tag_refresh(region->gizmo_map);
break;
}
break;
@ -658,6 +664,7 @@ static void sequencer_main_region_listener(const wmRegionListenerParams *params)
case NC_SPACE:
if (wmn->data == ND_SPACE_SEQUENCER) {
ED_region_tag_redraw(region);
WM_gizmomap_tag_refresh(region->gizmo_map);
}
break;
case NC_ID:
@ -668,6 +675,7 @@ static void sequencer_main_region_listener(const wmRegionListenerParams *params)
case NC_SCREEN:
if (ELEM(wmn->data, ND_ANIMPLAY)) {
ED_region_tag_redraw(region);
WM_gizmomap_tag_refresh(region->gizmo_map);
}
break;
}
@ -1069,7 +1077,6 @@ void ED_spacetype_sequencer(void)
art->on_view2d_changed = sequencer_main_region_view2d_changed;
art->listener = sequencer_main_region_listener;
art->message_subscribe = sequencer_main_region_message_subscribe;
/* NOTE: inclusion of #ED_KEYMAP_GIZMO is currently for scripts and isn't used by default. */
art->keymapflag = ED_KEYMAP_TOOL | ED_KEYMAP_GIZMO | ED_KEYMAP_VIEW2D | ED_KEYMAP_FRAMES |
ED_KEYMAP_ANIMATION;
BLI_addhead(&st->regiontypes, art);

View File

@ -120,6 +120,12 @@ typedef struct Strip {
ColorManagedColorspaceSettings colorspace_settings;
} Strip;
typedef struct SeqRetimingHandle {
int strip_frame_index;
int _pad0[2];
float retiming_factor; /* Value between 0-1 mapped to original content range. */
} SeqRetimingHandle;
typedef struct SequenceRuntime {
SessionUUID session_uuid;
} SequenceRuntime;
@ -164,12 +170,12 @@ typedef struct Sequence {
float startstill, endstill;
/** Machine: the strip channel */
int machine;
int _pad3;
int _pad;
/** Starting and ending points of the effect strip. Undefined for other strip types. */
int startdisp, enddisp;
float sat;
float mul;
float _pad;
float _pad1;
short anim_preseek; /* UNUSED. */
/** Streamindex for movie or sound files with several streams. */
@ -231,7 +237,7 @@ typedef struct Sequence {
int8_t color_tag;
char alpha_mode;
char _pad4[2];
char _pad2[2];
int cache_flag;
@ -241,7 +247,7 @@ typedef struct Sequence {
/* Multiview */
char views_format;
char _pad1[3];
char _pad3[3];
struct Stereo3dFormat *stereo3d_format;
struct IDProperty *prop;
@ -251,9 +257,13 @@ typedef struct Sequence {
/* Playback rate of strip content in frames per second. */
float media_playback_rate;
/* Multiply strip playback speed. */
float speed_factor;
struct SeqRetimingHandle *retiming_handles;
void *_pad5;
int retiming_handle_num;
char _pad6[4];
SequenceRuntime runtime;
} Sequence;

View File

@ -468,6 +468,7 @@ void RNA_api_region_view3d(struct StructRNA *srna);
void RNA_api_texture(struct StructRNA *srna);
void RNA_api_sequences(BlenderRNA *brna, PropertyRNA *cprop, bool metastrip);
void RNA_api_sequence_elements(BlenderRNA *brna, PropertyRNA *cprop);
void RNA_api_sequence_retiming_handles(BlenderRNA *brna, PropertyRNA *cprop);
void RNA_api_sound(struct StructRNA *srna);
void RNA_api_vfont(struct StructRNA *srna);
void RNA_api_workspace(struct StructRNA *srna);

View File

@ -43,6 +43,7 @@
#include "SEQ_prefetch.h"
#include "SEQ_proxy.h"
#include "SEQ_relations.h"
#include "SEQ_retiming.h"
#include "SEQ_select.h"
#include "SEQ_sequencer.h"
#include "SEQ_sound.h"
@ -272,7 +273,7 @@ static int rna_SequenceEditor_elements_length(PointerRNA *ptr)
return (int)olen;
}
static void rna_SequenceEditor_elements_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
static void rna_Sequence_elements_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
{
Sequence *seq = (Sequence *)ptr->data;
rna_iterator_array_begin(iter,
@ -283,6 +284,85 @@ static void rna_SequenceEditor_elements_begin(CollectionPropertyIterator *iter,
NULL);
}
static int rna_Sequence_retiming_handles_length(PointerRNA *ptr)
{
return SEQ_retiming_handles_count((Sequence *)ptr->data);
}
static void rna_SequenceEditor_retiming_handles_begin(CollectionPropertyIterator *iter,
PointerRNA *ptr)
{
Sequence *seq = (Sequence *)ptr->data;
rna_iterator_array_begin(iter,
(void *)seq->retiming_handles,
sizeof(SeqRetimingHandle),
SEQ_retiming_handles_count(seq),
0,
NULL);
}
static Sequence *strip_by_handle_find(Scene *scene, SeqRetimingHandle *handle)
{
Editing *ed = SEQ_editing_get(scene);
SeqCollection *strips = SEQ_query_all_strips_recursive(&ed->seqbase);
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, strips) {
const int retiming_handle_count = SEQ_retiming_handles_count(seq);
SeqRetimingHandle *first = seq->retiming_handles;
SeqRetimingHandle *last = seq->retiming_handles + retiming_handle_count - 1;
if (handle >= first && handle <= last) {
return seq;
}
}
return NULL;
}
static void rna_Sequence_retiming_handle_remove(ID *id, SeqRetimingHandle *handle)
{
Scene *scene = (Scene *)id;
Sequence *seq = strip_by_handle_find(scene, handle);
if (seq == NULL) {
return;
}
SEQ_retiming_remove_handle(seq, handle);
SEQ_relations_invalidate_cache_raw(scene, seq);
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL);
}
static int rna_Sequence_retiming_handle_frame_get(PointerRNA *ptr)
{
SeqRetimingHandle *handle = (SeqRetimingHandle *)ptr->data;
Scene *scene = (Scene *)ptr->owner_id;
Sequence *seq = strip_by_handle_find(scene, handle);
if (seq == NULL) {
return 0;
}
return SEQ_time_start_frame_get(seq) + handle->strip_frame_index;
}
static void rna_Sequence_retiming_handle_frame_set(PointerRNA *ptr, int value)
{
SeqRetimingHandle *handle = (SeqRetimingHandle *)ptr->data;
Scene *scene = (Scene *)ptr->owner_id;
Sequence *seq = strip_by_handle_find(scene, handle);
if (seq == NULL) {
return;
}
const int offset = value - SEQ_time_start_frame_get(seq) + handle->strip_frame_index;
SEQ_retiming_offset_handle(scene, seq, handle, offset);
SEQ_relations_invalidate_cache_raw(scene, seq);
}
static void rna_Sequence_views_format_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
rna_Sequence_invalidate_raw_update(bmain, scene, ptr);
@ -819,19 +899,6 @@ static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *UNUSED(scene),
DEG_id_tag_update(ptr->owner_id, ID_RECALC_SEQUENCER_STRIPS | ID_RECALC_AUDIO);
}
static void rna_Sequence_speed_factor_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
SEQ_cache_cleanup(scene);
rna_Sequence_audio_update(bmain, scene, ptr);
}
static void rna_Sequence_speed_factor_set(PointerRNA *ptr, float value)
{
Sequence *seq = (Sequence *)ptr->data;
Scene *scene = (Scene *)ptr->owner_id;
SEQ_time_speed_factor_set(scene, seq, value);
}
static void rna_Sequence_pan_range(
PointerRNA *ptr, float *min, float *max, float *softmin, float *softmax)
{
@ -1493,6 +1560,31 @@ static void rna_def_strip_element(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Orig FPS", "Original frames per second");
}
static void rna_def_retiming_handle(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "RetimingHandle", NULL);
RNA_def_struct_ui_text(
srna,
"Retiming Handle",
"Handle mapped to particular frame that can be moved to change playback speed");
RNA_def_struct_sdna(srna, "SeqRetimingHandle");
prop = RNA_def_property(srna, "timeline_frame", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "strip_frame_index");
RNA_def_property_int_funcs(prop,
"rna_Sequence_retiming_handle_frame_get",
"rna_Sequence_retiming_handle_frame_set",
NULL);
RNA_def_property_ui_text(prop, "Timeline Frame", "Position of retiming handle in timeline");
FunctionRNA *func = RNA_def_function(srna, "remove", "rna_Sequence_retiming_handle_remove");
RNA_def_function_flag(func, FUNC_USE_SELF_ID);
RNA_def_function_ui_description(func, "Remove retiming handle");
}
static void rna_def_strip_crop(BlenderRNA *brna)
{
StructRNA *srna;
@ -2351,19 +2443,6 @@ static void rna_def_editor(BlenderRNA *brna)
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
}
static void rna_def_speed_factor(StructRNA *srna)
{
PropertyRNA *prop = RNA_def_property(srna, "speed_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "speed_factor");
RNA_def_property_float_default(prop, 1.0f);
RNA_def_property_range(prop, 0.1f, FLT_MAX);
RNA_def_property_ui_range(prop, 1.0f, 100.0f, 10.0, 3);
RNA_def_property_ui_text(prop, "Speed Factor", "Multiply playback speed");
RNA_def_property_float_funcs(
prop, NULL, "rna_Sequence_speed_factor_set", NULL); /* overlap test */
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_speed_factor_update");
}
static void rna_def_filter_video(StructRNA *srna)
{
PropertyRNA *prop;
@ -2564,7 +2643,7 @@ static void rna_def_image(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "SequenceElement");
RNA_def_property_ui_text(prop, "Elements", "");
RNA_def_property_collection_funcs(prop,
"rna_SequenceEditor_elements_begin",
"rna_Sequence_elements_begin",
"rna_iterator_array_next",
"rna_iterator_array_end",
"rna_iterator_array_get",
@ -2596,7 +2675,6 @@ static void rna_def_image(BlenderRNA *brna)
rna_def_proxy(srna);
rna_def_input(srna);
rna_def_color_management(srna);
rna_def_speed_factor(srna);
}
static void rna_def_meta(BlenderRNA *brna)
@ -2628,7 +2706,6 @@ static void rna_def_meta(BlenderRNA *brna)
rna_def_filter_video(srna);
rna_def_proxy(srna);
rna_def_input(srna);
rna_def_speed_factor(srna);
}
static void rna_def_scene(BlenderRNA *brna)
@ -2677,7 +2754,6 @@ static void rna_def_scene(BlenderRNA *brna)
rna_def_proxy(srna);
rna_def_input(srna);
rna_def_movie_types(srna);
rna_def_speed_factor(srna);
}
static void rna_def_movie(BlenderRNA *brna)
@ -2705,7 +2781,7 @@ static void rna_def_movie(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "SequenceElement");
RNA_def_property_ui_text(prop, "Elements", "");
RNA_def_property_collection_funcs(prop,
"rna_SequenceEditor_elements_begin",
"rna_Sequence_elements_begin",
"rna_iterator_array_next",
"rna_iterator_array_end",
"rna_iterator_array_get",
@ -2714,6 +2790,21 @@ static void rna_def_movie(BlenderRNA *brna)
NULL,
NULL);
prop = RNA_def_property(srna, "retiming_handles", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, NULL, "retiming_handles", NULL);
RNA_def_property_struct_type(prop, "RetimingHandle");
RNA_def_property_ui_text(prop, "Retiming Hndles", "");
RNA_def_property_collection_funcs(prop,
"rna_SequenceEditor_retiming_handles_begin",
"rna_iterator_array_next",
"rna_iterator_array_end",
"rna_iterator_array_get",
"rna_Sequence_retiming_handles_length",
NULL,
NULL,
NULL);
RNA_api_sequence_retiming_handles(brna, prop);
prop = RNA_def_property(srna, "filepath", PROP_STRING, PROP_FILEPATH);
RNA_def_property_ui_text(prop, "File", "");
RNA_def_property_string_funcs(prop,
@ -2761,7 +2852,6 @@ static void rna_def_movie(BlenderRNA *brna)
rna_def_input(srna);
rna_def_color_management(srna);
rna_def_movie_types(srna);
rna_def_speed_factor(srna);
}
static void rna_def_movieclip(BlenderRNA *brna)
@ -2789,7 +2879,6 @@ static void rna_def_movieclip(BlenderRNA *brna)
rna_def_filter_video(srna);
rna_def_input(srna);
rna_def_movie_types(srna);
rna_def_speed_factor(srna);
}
static void rna_def_mask(BlenderRNA *brna)
@ -2808,7 +2897,6 @@ static void rna_def_mask(BlenderRNA *brna)
rna_def_filter_video(srna);
rna_def_input(srna);
rna_def_speed_factor(srna);
}
static void rna_def_sound(BlenderRNA *brna)
@ -2851,7 +2939,6 @@ static void rna_def_sound(BlenderRNA *brna)
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, NULL);
rna_def_input(srna);
rna_def_speed_factor(srna);
}
static void rna_def_effect(BlenderRNA *brna)
@ -3613,6 +3700,7 @@ void RNA_def_sequencer(BlenderRNA *brna)
rna_def_color_balance(brna);
rna_def_strip_element(brna);
rna_def_retiming_handle(brna);
rna_def_strip_proxy(brna);
rna_def_strip_color_balance(brna);
rna_def_strip_crop(brna);

View File

@ -44,6 +44,7 @@
# include "SEQ_effects.h"
# include "SEQ_relations.h"
# include "SEQ_render.h"
# include "SEQ_retiming.h"
# include "SEQ_sequencer.h"
# include "SEQ_time.h"
@ -637,6 +638,29 @@ static void rna_Sequence_invalidate_cache_rnafunc(ID *id, Sequence *self, int ty
}
}
static SeqRetimingHandle *rna_Sequence_retiming_handles_add(ID *id,
Sequence *seq,
int timeline_frame)
{
Scene *scene = (Scene *)id;
SeqRetimingHandle *handle = SEQ_retiming_add_handle(seq, timeline_frame);
SEQ_relations_invalidate_cache_raw(scene, seq);
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL);
return handle;
}
static void rna_Sequence_retiming_handles_reset(ID *id, Sequence *seq)
{
Scene *scene = (Scene *)id;
SEQ_retiming_data_clear(seq);
SEQ_relations_invalidate_cache_raw(scene, seq);
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL);
}
#else
void RNA_api_sequence_strip(StructRNA *srna)
@ -744,6 +768,30 @@ void RNA_api_sequence_elements(BlenderRNA *brna, PropertyRNA *cprop)
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
}
void RNA_api_sequence_retiming_handles(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
RNA_def_property_srna(cprop, "RetimingHandles");
srna = RNA_def_struct(brna, "RetimingHandles", NULL);
RNA_def_struct_sdna(srna, "Sequence");
RNA_def_struct_ui_text(srna, "RetimingHandles", "Collection of RetimingHandle");
FunctionRNA *func = RNA_def_function(srna, "add", "rna_Sequence_retiming_handles_add");
RNA_def_function_flag(func, FUNC_USE_SELF_ID);
RNA_def_int(
func, "timeline_frame", 0, -MAXFRAME, MAXFRAME, "Timeline Frame", "", -MAXFRAME, MAXFRAME);
RNA_def_function_ui_description(func, "Add retiming handle");
/* return type */
PropertyRNA *parm = RNA_def_pointer(
func, "retiming_handle", "RetimingHandle", "", "New RetimingHandle");
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "reset", "rna_Sequence_retiming_handles_reset");
RNA_def_function_flag(func, FUNC_USE_SELF_ID);
RNA_def_function_ui_description(func, "Remove all retiming handles");
}
void RNA_api_sequences(BlenderRNA *brna, PropertyRNA *cprop, const bool metastrip)
{
StructRNA *srna;

View File

@ -42,6 +42,8 @@ set(SRC
SEQ_proxy.h
SEQ_relations.h
SEQ_render.h
SEQ_retiming.h
SEQ_retiming.hh
SEQ_select.h
SEQ_sequencer.h
SEQ_sound.h
@ -76,6 +78,7 @@ set(SRC
intern/strip_add.c
intern/strip_edit.c
intern/strip_relations.c
intern/strip_retiming.cc
intern/strip_select.c
intern/strip_time.c
intern/strip_time.h

View File

@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2004 Blender Foundation. All rights reserved. */
#pragma once
/** \file
* \ingroup sequencer
*/
#ifdef __cplusplus
extern "C" {
#endif
struct Scene;
struct Sequence;
struct SeqRetimingHandle;
int SEQ_retiming_handles_count(const struct Sequence *seq);
bool SEQ_retiming_is_active(const struct Sequence *seq);
void SEQ_retiming_data_ensure(const struct Scene *scene, struct Sequence *seq);
void SEQ_retiming_data_clear(struct Sequence *seq);
bool SEQ_retiming_is_allowed(const struct Sequence *seq);
/**
* Add new retiming handle.
* This function always reallocates memory, so when function is used all stored pointers will
* become invalid.
*/
struct SeqRetimingHandle *SEQ_retiming_add_handle(struct Sequence *seq, const int timeline_frame);
struct SeqRetimingHandle *SEQ_retiming_last_handle_get(const struct Sequence *seq);
void SEQ_retiming_remove_handle(struct Sequence *seq, struct SeqRetimingHandle *handle);
void SEQ_retiming_offset_handle(const struct Scene *scene,
struct Sequence *seq,
struct SeqRetimingHandle *handle,
const int offset);
float SEQ_retiming_handle_speed_get(const struct Scene *scene,
const struct Sequence *seq,
const struct SeqRetimingHandle *handle);
int SEQ_retiming_handle_index_get(const struct Sequence *seq,
const struct SeqRetimingHandle *handle);
int SEQ_retiming_handle_index_get(const struct Sequence *seq,
const struct SeqRetimingHandle *handle);
void SEQ_retiming_sound_animation_data_set(const struct Scene *scene, const struct Sequence *seq);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#pragma once
/** \file
* \ingroup sequencer
*/
#include "BLI_span.hh"
struct Sequence;
struct SeqRetimingHandle;
blender::MutableSpan<SeqRetimingHandle> SEQ_retiming_handles_get(const Sequence *seq);

View File

@ -73,6 +73,8 @@ int SEQ_time_find_next_prev_edit(struct Scene *scene,
bool SEQ_time_strip_intersects_frame(const struct Scene *scene,
const struct Sequence *seq,
int timeline_frame);
/* Convert timeline frame so strip frame index. */
float SEQ_give_frame_index(const struct Scene *scene, struct Sequence *seq, float timeline_frame);
/**
* Returns true if strip has frames without content to render.
*/
@ -136,7 +138,6 @@ void SEQ_time_start_frame_set(const struct Scene *scene, struct Sequence *seq, i
* \note this function is currently only used internally and in versioning code.
*/
void SEQ_time_update_meta_strip_range(const struct Scene *scene, struct Sequence *seq_meta);
#ifdef __cplusplus
}
#endif

View File

@ -2620,7 +2620,7 @@ float seq_speed_effect_target_frame_get(Scene *scene,
}
SEQ_effect_handle_get(seq_speed); /* Ensure, that data are initialized. */
int frame_index = seq_give_frame_index(scene, seq_speed, timeline_frame);
int frame_index = SEQ_give_frame_index(scene, seq_speed, timeline_frame);
SpeedControlVars *s = (SpeedControlVars *)seq_speed->effectdata;
const Sequence *source = seq_speed->seq1;

View File

@ -142,7 +142,7 @@ static float seq_cache_timeline_frame_to_frame_index(Scene *scene,
* images or extended frame range of movies will only generate one cache entry. No special
* treatment in converting frame index to timeline_frame is needed. */
if (ELEM(type, SEQ_CACHE_STORE_RAW, SEQ_CACHE_STORE_THUMBNAIL)) {
return seq_give_frame_index(scene, seq, timeline_frame);
return SEQ_give_frame_index(scene, seq, timeline_frame);
}
return timeline_frame - SEQ_time_start_frame_get(seq);

View File

@ -209,7 +209,7 @@ ImBuf *seq_proxy_fetch(const SeqRenderData *context, Sequence *seq, int timeline
}
if (proxy->storage & SEQ_STORAGE_PROXY_CUSTOM_FILE) {
int frameno = (int)seq_give_frame_index(context->scene, seq, timeline_frame) +
int frameno = (int)SEQ_give_frame_index(context->scene, seq, timeline_frame) +
seq->anim_startofs;
if (proxy->anim == NULL) {
if (seq_proxy_get_fname(

View File

@ -238,7 +238,7 @@ StripElem *SEQ_render_give_stripelem(const Scene *scene, Sequence *seq, int time
* all other strips don't use this...
*/
int frame_index = (int)seq_give_frame_index(scene, seq, timeline_frame);
int frame_index = (int)SEQ_give_frame_index(scene, seq, timeline_frame);
if (frame_index == -1 || se == NULL) {
return NULL;
@ -1042,7 +1042,7 @@ static ImBuf *seq_render_movie_strip_custom_file_proxy(const SeqRenderData *cont
}
}
int frameno = (int)seq_give_frame_index(context->scene, seq, timeline_frame) +
int frameno = (int)SEQ_give_frame_index(context->scene, seq, timeline_frame) +
seq->anim_startofs;
return IMB_anim_absolute(proxy->anim, frameno, IMB_TC_NONE, IMB_PROXY_NONE);
}
@ -1658,7 +1658,7 @@ static ImBuf *do_render_strip_uncached(const SeqRenderData *context,
bool *r_is_proxy_image)
{
ImBuf *ibuf = NULL;
float frame_index = seq_give_frame_index(context->scene, seq, timeline_frame);
float frame_index = SEQ_give_frame_index(context->scene, seq, timeline_frame);
int type = (seq->type & SEQ_TYPE_EFFECT) ? SEQ_TYPE_EFFECT : seq->type;
switch (type) {
case SEQ_TYPE_META: {

View File

@ -18,6 +18,7 @@
#include "BLI_listbase.h"
#include "BKE_fcurve.h"
#include "BKE_idprop.h"
#include "BKE_lib_id.h"
#include "BKE_sound.h"
@ -34,6 +35,7 @@
#include "SEQ_modifier.h"
#include "SEQ_proxy.h"
#include "SEQ_relations.h"
#include "SEQ_retiming.h"
#include "SEQ_select.h"
#include "SEQ_sequencer.h"
#include "SEQ_sound.h"
@ -218,6 +220,12 @@ static void seq_sequence_free_ex(Scene *scene,
SEQ_channels_free(&seq->channels);
}
if (seq->retiming_handles != NULL) {
MEM_freeN(seq->retiming_handles);
seq->retiming_handles = NULL;
seq->retiming_handle_num = 0;
}
MEM_freeN(seq);
}
@ -576,6 +584,11 @@ static Sequence *seq_dupli(const Scene *scene_src,
}
}
if (seq->retiming_handles != NULL) {
seqn->retiming_handles = MEM_dupallocN(seq->retiming_handles);
seqn->retiming_handle_num = seq->retiming_handle_num;
}
return seqn;
}
@ -749,6 +762,12 @@ static bool seq_write_data_cb(Sequence *seq, void *userdata)
LISTBASE_FOREACH (SeqTimelineChannel *, channel, &seq->channels) {
BLO_write_struct(writer, SeqTimelineChannel, channel);
}
if (seq->retiming_handles != NULL) {
int size = SEQ_retiming_handles_count(seq);
BLO_write_struct_array(writer, SeqRetimingHandle, size, seq->retiming_handles);
}
return true;
}
@ -818,6 +837,11 @@ static bool seq_read_data_cb(Sequence *seq, void *user_data)
SEQ_modifier_blend_read_data(reader, &seq->modifiers);
BLO_read_list(reader, &seq->channels);
if (seq->retiming_handles != NULL) {
BLO_read_data_address(reader, &seq->retiming_handles);
}
return true;
}
void SEQ_blend_read(BlendDataReader *reader, ListBase *seqbase)
@ -966,9 +990,7 @@ static bool seq_update_seq_cb(Sequence *seq, void *user_data)
}
BKE_sound_set_scene_sound_volume(
seq->scene_sound, seq->volume, (seq->flag & SEQ_AUDIO_VOLUME_ANIMATED) != 0);
BKE_sound_set_scene_sound_pitch(seq->scene_sound,
SEQ_sound_pitch_get(scene, seq),
(seq->flag & SEQ_AUDIO_PITCH_ANIMATED) != 0);
SEQ_retiming_sound_animation_data_set(scene, seq);
BKE_sound_set_scene_sound_pan(
seq->scene_sound, seq->pan, (seq->flag & SEQ_AUDIO_PAN_ANIMATED) != 0);
}

View File

@ -433,6 +433,7 @@ Sequence *SEQ_add_movie_strip(Main *bmain, Scene *scene, ListBase *seqbase, SeqL
if (load_data->flags & SEQ_LOAD_MOVIE_SYNC_FPS) {
scene->r.frs_sec = fps_denom;
scene->r.frs_sec_base = fps_num;
DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_FPS | ID_RECALC_SEQUENCER_STRIPS);
}
load_data->r_video_stream_start = IMD_anim_get_offset(anim_arr[0]);

View File

@ -275,9 +275,7 @@ static void seq_split_set_right_hold_offset(Main *bmain,
/* Adjust within range of strip contents. */
else if ((timeline_frame >= content_start) && (timeline_frame <= content_end)) {
seq->endofs = 0;
float speed_factor = (seq->type == SEQ_TYPE_SOUND_RAM) ?
seq_time_media_playback_rate_factor_get(scene, seq) :
seq_time_playback_rate_factor_get(scene, seq);
float speed_factor = seq_time_media_playback_rate_factor_get(scene, seq);
seq->anim_endofs += round_fl_to_int((content_end - timeline_frame) * speed_factor);
}
@ -296,9 +294,7 @@ static void seq_split_set_left_hold_offset(Main *bmain,
/* Adjust within range of strip contents. */
if ((timeline_frame >= content_start) && (timeline_frame <= content_end)) {
float speed_factor = (seq->type == SEQ_TYPE_SOUND_RAM) ?
seq_time_media_playback_rate_factor_get(scene, seq) :
seq_time_playback_rate_factor_get(scene, seq);
float speed_factor = seq_time_media_playback_rate_factor_get(scene, seq);
seq->anim_startofs += round_fl_to_int((timeline_frame - content_start) * speed_factor);
seq->start = timeline_frame;
seq->startofs = 0;

View File

@ -0,0 +1,376 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
/** \file
* \ingroup bke
*/
#include "MEM_guardedalloc.h"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_span.hh"
#include "BKE_fcurve.h"
#include "BKE_movieclip.h"
#include "BKE_scene.h"
#include "BKE_sound.h"
#include "DNA_anim_types.h"
#include "DNA_sound_types.h"
#include "IMB_imbuf.h"
#include "RNA_prototypes.h"
#include "SEQ_channels.h"
#include "SEQ_iterator.h"
#include "SEQ_relations.h"
#include "SEQ_render.h"
#include "SEQ_retiming.h"
#include "SEQ_retiming.hh"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
#include "SEQ_transform.h"
#include "sequencer.h"
#include "strip_time.h"
#include "utils.h"
using blender::MutableSpan;
MutableSpan<SeqRetimingHandle> SEQ_retiming_handles_get(const Sequence *seq)
{
blender::MutableSpan<SeqRetimingHandle> handles(seq->retiming_handles, seq->retiming_handle_num);
return handles;
}
struct SeqRetimingHandle *SEQ_retiming_last_handle_get(const struct Sequence *seq)
{
return seq->retiming_handles + seq->retiming_handle_num - 1;
}
int SEQ_retiming_handle_index_get(const Sequence *seq, const SeqRetimingHandle *handle)
{
return handle - seq->retiming_handles;
}
static bool seq_retiming_is_last_handle(const Sequence *seq, const SeqRetimingHandle *handle)
{
return SEQ_retiming_handle_index_get(seq, handle) == seq->retiming_handle_num - 1;
}
static const SeqRetimingHandle *retiming_find_segment_start_handle(const Sequence *seq,
const int frame_index)
{
const SeqRetimingHandle *start_handle = nullptr;
for (auto const &handle : SEQ_retiming_handles_get(seq)) {
if (seq_retiming_is_last_handle(seq, &handle)) {
break;
}
if (handle.strip_frame_index > frame_index) {
break;
}
start_handle = &handle;
}
return start_handle;
}
int SEQ_retiming_handles_count(const Sequence *seq)
{
return seq->retiming_handle_num;
}
void SEQ_retiming_data_ensure(const Scene *scene, Sequence *seq)
{
if (!SEQ_retiming_is_allowed(seq)) {
return;
}
if (seq->retiming_handles != nullptr) {
return;
}
seq->retiming_handles = (SeqRetimingHandle *)MEM_calloc_arrayN(
2, sizeof(SeqRetimingHandle), __func__);
SeqRetimingHandle *handle = seq->retiming_handles + 1;
handle->strip_frame_index = seq_time_strip_original_content_length_get(scene, seq) - 1;
handle->retiming_factor = 1.0f;
seq->retiming_handle_num = 2;
}
void SEQ_retiming_data_clear(Sequence *seq)
{
seq->retiming_handles = nullptr;
seq->retiming_handle_num = 0;
}
bool SEQ_retiming_is_active(const Sequence *seq)
{
return seq->retiming_handle_num > 1;
}
bool SEQ_retiming_is_allowed(const Sequence *seq)
{
return ELEM(seq->type,
SEQ_TYPE_SOUND_RAM,
SEQ_TYPE_IMAGE,
SEQ_TYPE_META,
SEQ_TYPE_SCENE,
SEQ_TYPE_MOVIE,
SEQ_TYPE_MOVIECLIP,
SEQ_TYPE_MASK);
}
float seq_retiming_evaluate(const Sequence *seq, const float frame_index)
{
const SeqRetimingHandle *previous_handle = retiming_find_segment_start_handle(seq, frame_index);
const SeqRetimingHandle *next_handle = previous_handle + 1;
const int previous_handle_index = previous_handle - seq->retiming_handles;
BLI_assert(previous_handle_index < seq->retiming_handle_num);
UNUSED_VARS_NDEBUG(previous_handle_index);
if (next_handle == nullptr) {
return 1.0f;
}
const int segment_length = next_handle->strip_frame_index - previous_handle->strip_frame_index;
const float segment_frame_index = frame_index - previous_handle->strip_frame_index;
const float segment_fac = segment_frame_index / (float)segment_length;
const float target_diff = next_handle->retiming_factor - previous_handle->retiming_factor;
return previous_handle->retiming_factor + (target_diff * segment_fac);
}
SeqRetimingHandle *SEQ_retiming_add_handle(Sequence *seq, const int timeline_frame)
{
float frame_index = timeline_frame - SEQ_time_start_frame_get(seq);
float value = seq_retiming_evaluate(seq, frame_index);
const SeqRetimingHandle *closest_handle = retiming_find_segment_start_handle(seq, frame_index);
if (closest_handle->strip_frame_index == frame_index) {
return nullptr; /* Retiming handle already exists. */
}
SeqRetimingHandle *handles = seq->retiming_handles;
size_t handle_count = SEQ_retiming_handles_count(seq);
const int new_handle_index = closest_handle - handles + 1;
BLI_assert(new_handle_index >= 0);
BLI_assert(new_handle_index < handle_count);
SeqRetimingHandle *new_handles = (SeqRetimingHandle *)MEM_callocN(
(handle_count + 1) * sizeof(SeqRetimingHandle), __func__);
if (new_handle_index > 0) {
memcpy(new_handles, handles, new_handle_index * sizeof(SeqRetimingHandle));
}
if (new_handle_index < handle_count) {
memcpy(new_handles + new_handle_index + 1,
handles + new_handle_index,
(handle_count - new_handle_index) * sizeof(SeqRetimingHandle));
}
MEM_freeN(handles);
seq->retiming_handles = new_handles;
seq->retiming_handle_num++;
SeqRetimingHandle *added_handle = (new_handles + new_handle_index);
added_handle->strip_frame_index = frame_index;
added_handle->retiming_factor = value;
return added_handle;
}
void SEQ_retiming_offset_handle(const Scene *scene,
Sequence *seq,
SeqRetimingHandle *handle,
const int offset)
{
if (handle->strip_frame_index == 0) {
return; /* First handle can not be moved. */
}
MutableSpan handles = SEQ_retiming_handles_get(seq);
for (; handle < handles.end(); handle++) {
handle->strip_frame_index += offset;
}
SEQ_time_update_meta_strip_range(scene, seq_sequence_lookup_meta_by_seq(scene, seq));
seq_time_update_effects_strip_range(scene, seq_sequence_lookup_effects_by_seq(scene, seq));
}
void SEQ_retiming_remove_handle(Sequence *seq, SeqRetimingHandle *handle)
{
SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq);
if (handle->strip_frame_index == 0 || handle == last_handle) {
return; /* First and last handle can not be removed. */
}
size_t handle_count = SEQ_retiming_handles_count(seq);
SeqRetimingHandle *handles = (SeqRetimingHandle *)MEM_callocN(
(handle_count - 1) * sizeof(SeqRetimingHandle), __func__);
const int handle_index = handle - seq->retiming_handles;
memcpy(handles, seq->retiming_handles, (handle_index) * sizeof(SeqRetimingHandle));
memcpy(handles + handle_index,
seq->retiming_handles + handle_index + 1,
(handle_count - handle_index - 1) * sizeof(SeqRetimingHandle));
MEM_freeN(seq->retiming_handles);
seq->retiming_handles = handles;
seq->retiming_handle_num--;
}
float SEQ_retiming_handle_speed_get(const Scene *scene,
const Sequence *seq,
const SeqRetimingHandle *handle)
{
if (handle->strip_frame_index == 0) {
return 1.0f;
}
const SeqRetimingHandle *handle_prev = handle - 1;
const int frame_index_max = seq_time_strip_original_content_length_get(scene, seq) - 1;
const int frame_retimed_prev = round_fl_to_int(handle_prev->retiming_factor * frame_index_max);
const int frame_index_prev = handle_prev->strip_frame_index;
const int frame_retimed = round_fl_to_int(handle->retiming_factor * frame_index_max);
const int frame_index = handle->strip_frame_index;
const int fragment_length_retimed = frame_retimed - frame_retimed_prev;
const int fragment_length_original = frame_index - frame_index_prev;
const float speed = (float)fragment_length_retimed / (float)fragment_length_original;
return speed;
}
using std::vector;
class RetimingRange {
public:
int start, end;
float speed;
enum eIntersectType {
FULL,
PARTIAL_START,
PARTIAL_END,
INSIDE,
NONE,
};
RetimingRange(int start_frame, int end_frame, float speed)
: start(start_frame), end(end_frame), speed(speed)
{
}
const eIntersectType intersect_type(const RetimingRange &other) const
{
if (other.start <= start && other.end >= end) {
return FULL;
}
if (other.start > start && other.start < end && other.end > start && other.end < end) {
return INSIDE;
}
if (other.start > start && other.start < end) {
return PARTIAL_END;
}
if (other.end > start && other.end < end) {
return PARTIAL_START;
}
return NONE;
}
};
class RetimingRangeData {
public:
vector<RetimingRange> ranges;
RetimingRangeData(const Scene *scene, const Sequence *seq)
{
MutableSpan handles = SEQ_retiming_handles_get(seq);
for (const SeqRetimingHandle &handle : handles) {
if (handle.strip_frame_index == 0) {
continue;
}
const SeqRetimingHandle *handle_prev = &handle - 1;
float speed = SEQ_retiming_handle_speed_get(scene, seq, &handle);
int frame_start = SEQ_time_start_frame_get(seq) + handle_prev->strip_frame_index;
int frame_end = SEQ_time_start_frame_get(seq) + handle.strip_frame_index;
RetimingRange range = RetimingRange(frame_start, frame_end, speed);
ranges.push_back(range);
}
}
RetimingRangeData &operator*=(const RetimingRangeData rhs)
{
if (ranges.size() == 0) {
for (const RetimingRange &rhs_range : rhs.ranges) {
RetimingRange range = RetimingRange(rhs_range.start, rhs_range.end, rhs_range.speed);
ranges.push_back(range);
}
}
else {
for (int i = 0; i < ranges.size(); i++) {
RetimingRange &range = ranges[i];
for (const RetimingRange &rhs_range : rhs.ranges) {
if (range.intersect_type(rhs_range) == range.NONE) {
continue;
}
else if (range.intersect_type(rhs_range) == range.FULL) {
range.speed *= rhs_range.speed;
}
else if (range.intersect_type(rhs_range) == range.PARTIAL_START) {
RetimingRange range_left = RetimingRange(
range.start, rhs_range.end, range.speed * rhs_range.speed);
range.start = rhs_range.end + 1;
ranges.insert(ranges.begin() + i, range_left);
}
else if (range.intersect_type(rhs_range) == range.PARTIAL_END) {
RetimingRange range_left = RetimingRange(
range.start, rhs_range.start - 1, range.speed);
range.start = rhs_range.start;
ranges.insert(ranges.begin() + i, range_left);
}
else if (range.intersect_type(rhs_range) == range.INSIDE) {
RetimingRange range_left = RetimingRange(
range.start, rhs_range.start - 1, range.speed);
RetimingRange range_mid = RetimingRange(
rhs_range.start, rhs_range.start, rhs_range.speed * range.speed);
range.start = rhs_range.end + 1;
ranges.insert(ranges.begin() + i, range_left);
ranges.insert(ranges.begin() + i, range_mid);
break;
}
}
}
}
return *this;
}
};
static RetimingRangeData seq_retiming_range_data_get(const Scene *scene, const Sequence *seq)
{
RetimingRangeData strip_retiming_data = RetimingRangeData(scene, seq);
const Sequence *meta_parent = seq_sequence_lookup_meta_by_seq(scene, seq);
if (meta_parent == nullptr) {
return strip_retiming_data;
}
RetimingRangeData meta_retiming_data = RetimingRangeData(scene, meta_parent);
strip_retiming_data *= meta_retiming_data;
return strip_retiming_data;
}
void SEQ_retiming_sound_animation_data_set(const Scene *scene, const Sequence *seq)
{
RetimingRangeData retiming_data = seq_retiming_range_data_get(scene, seq);
for (const RetimingRange &range : retiming_data.ranges) {
BKE_sound_set_scene_sound_pitch_constant_range(
seq->scene_sound, range.start, range.end, range.speed);
}
}

View File

@ -7,22 +7,31 @@
* \ingroup bke
*/
#include "MEM_guardedalloc.h"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BKE_fcurve.h"
#include "BKE_movieclip.h"
#include "BKE_scene.h"
#include "BKE_sound.h"
#include "DNA_anim_types.h"
#include "DNA_sound_types.h"
#include "IMB_imbuf.h"
#include "RNA_prototypes.h"
#include "SEQ_channels.h"
#include "SEQ_iterator.h"
#include "SEQ_relations.h"
#include "SEQ_render.h"
#include "SEQ_retiming.h"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
#include "SEQ_transform.h"
@ -44,16 +53,21 @@ float seq_time_media_playback_rate_factor_get(const Scene *scene, const Sequence
return seq->media_playback_rate / scene_playback_rate;
}
float seq_time_playback_rate_factor_get(const Scene *scene, const Sequence *seq)
int seq_time_strip_original_content_length_get(const Scene *scene, const Sequence *seq)
{
return seq_time_media_playback_rate_factor_get(scene, seq) * seq->speed_factor;
if (seq->type == SEQ_TYPE_SOUND_RAM) {
return seq->len;
}
return seq->len / seq_time_media_playback_rate_factor_get(scene, seq);
}
float seq_give_frame_index(const Scene *scene, Sequence *seq, float timeline_frame)
float SEQ_give_frame_index(const Scene *scene, Sequence *seq, float timeline_frame)
{
float frame_index;
float sta = SEQ_time_start_frame_get(seq);
float end = SEQ_time_content_end_frame_get(scene, seq) - 1;
const float length = seq_time_strip_original_content_length_get(scene, seq);
if (seq->type & SEQ_TYPE_EFFECT) {
end = SEQ_time_right_handle_frame_get(scene, seq);
@ -70,9 +84,15 @@ float seq_give_frame_index(const Scene *scene, Sequence *seq, float timeline_fra
frame_index = timeline_frame - sta;
}
/* Clamp frame index to strip frame range. */
frame_index = clamp_f(frame_index, 0, end - sta);
frame_index *= seq_time_playback_rate_factor_get(scene, seq);
if (SEQ_retiming_is_active(seq)) {
const float retiming_factor = seq_retiming_evaluate(seq, frame_index);
frame_index = retiming_factor * (length - 1);
}
else {
frame_index *= seq_time_media_playback_rate_factor_get(scene, seq);
/* Clamp frame index to strip frame range. */
frame_index = clamp_f(frame_index, 0, end - sta);
}
if (seq->strobe < 1.0f) {
seq->strobe = 1.0f;
@ -442,27 +462,6 @@ bool SEQ_time_strip_intersects_frame(const Scene *scene,
(SEQ_time_right_handle_frame_get(scene, seq) > timeline_frame);
}
void SEQ_time_speed_factor_set(const Scene *scene, Sequence *seq, const float speed_factor)
{
if (seq->type == SEQ_TYPE_SOUND_RAM) {
seq->speed_factor = speed_factor;
}
else {
const float left_handle_frame = SEQ_time_left_handle_frame_get(scene, seq);
const float unity_start_offset = seq->startofs * seq->speed_factor;
const float unity_end_offset = seq->endofs * seq->speed_factor;
/* Left handle is pivot point for content scaling - it must always show same frame. */
seq->speed_factor = speed_factor;
seq->startofs = unity_start_offset / speed_factor;
seq->start = left_handle_frame - seq->startofs;
seq->endofs = unity_end_offset / speed_factor;
}
SEQ_time_update_meta_strip_range(scene, seq_sequence_lookup_meta_by_seq(scene, seq));
seq_time_update_effects_strip_range(scene, seq_sequence_lookup_effects_by_seq(scene, seq));
}
bool SEQ_time_has_left_still_frames(const Scene *scene, const Sequence *seq)
{
return SEQ_time_left_handle_frame_get(scene, seq) < SEQ_time_start_frame_get(seq);
@ -480,11 +479,13 @@ bool SEQ_time_has_still_frames(const Scene *scene, const Sequence *seq)
int SEQ_time_strip_length_get(const Scene *scene, const Sequence *seq)
{
if (seq->type == SEQ_TYPE_SOUND_RAM) {
return seq->len;
if (SEQ_retiming_is_active(seq)) {
SeqRetimingHandle *handle_start = seq->retiming_handles;
SeqRetimingHandle *handle_end = seq->retiming_handles + (SEQ_retiming_handles_count(seq) - 1);
return handle_end->strip_frame_index - handle_start->strip_frame_index + 1;
}
return seq->len / seq_time_playback_rate_factor_get(scene, seq);
return seq_time_strip_original_content_length_get(scene, seq);
}
float SEQ_time_start_frame_get(const Sequence *seq)

View File

@ -16,7 +16,6 @@ struct Scene;
struct Sequence;
struct SeqCollection;
float seq_give_frame_index(const struct Scene *scene, struct Sequence *seq, float timeline_frame);
void seq_update_sound_bounds_recursive(const struct Scene *scene, struct Sequence *metaseq);
/* Describes gap between strips in timeline. */
@ -43,7 +42,9 @@ void seq_time_update_effects_strip_range(const struct Scene *scene, struct SeqCo
void seq_time_translate_handles(const struct Scene *scene, struct Sequence *seq, const int offset);
float seq_time_media_playback_rate_factor_get(const struct Scene *scene,
const struct Sequence *seq);
float seq_time_playback_rate_factor_get(const struct Scene *scene, const struct Sequence *seq);
int seq_time_strip_original_content_length_get(const struct Scene *scene,
const struct Sequence *seq);
float seq_retiming_evaluate(const struct Sequence *seq, const float frame_index);
#ifdef __cplusplus
}