2010-02-08 14:43:44 +00:00
|
|
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU General Public License
|
|
|
|
# as published by the Free Software Foundation; either version 2
|
|
|
|
# of the License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software Foundation,
|
2010-02-12 13:34:04 +00:00
|
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
2010-02-08 14:43:44 +00:00
|
|
|
#
|
|
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
|
|
|
|
# <pep8 compliant>
|
|
|
|
|
|
|
|
import bpy
|
2011-08-12 06:57:00 +00:00
|
|
|
from bpy.types import Operator
|
2010-02-08 14:43:44 +00:00
|
|
|
|
2019-10-31 07:00:18 +00:00
|
|
|
from bpy.props import (
|
|
|
|
EnumProperty,
|
|
|
|
FloatProperty,
|
|
|
|
IntProperty,
|
|
|
|
)
|
2010-03-06 01:40:29 +00:00
|
|
|
|
2010-05-16 12:15:04 +00:00
|
|
|
|
2011-08-12 06:57:00 +00:00
|
|
|
class SequencerCrossfadeSounds(Operator):
|
2012-07-03 09:02:41 +00:00
|
|
|
"""Do cross-fading volume animation of two selected sound strips"""
|
2010-02-08 14:43:44 +00:00
|
|
|
|
|
|
|
bl_idname = "sequencer.crossfade_sounds"
|
|
|
|
bl_label = "Crossfade sounds"
|
2010-03-01 00:03:51 +00:00
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
2010-02-08 14:43:44 +00:00
|
|
|
|
2010-08-09 01:37:09 +00:00
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
2012-02-29 12:11:06 +00:00
|
|
|
if context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip:
|
|
|
|
return context.scene.sequence_editor.active_strip.type == 'SOUND'
|
2012-02-29 11:23:27 +00:00
|
|
|
else:
|
2012-02-29 12:11:06 +00:00
|
|
|
return False
|
2010-02-08 14:43:44 +00:00
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
seq1 = None
|
|
|
|
seq2 = None
|
|
|
|
for s in context.scene.sequence_editor.sequences:
|
2010-07-15 16:56:04 +00:00
|
|
|
if s.select and s.type == 'SOUND':
|
2010-09-18 10:43:32 +00:00
|
|
|
if seq1 is None:
|
2010-02-08 14:43:44 +00:00
|
|
|
seq1 = s
|
2010-09-18 10:43:32 +00:00
|
|
|
elif seq2 is None:
|
2010-02-08 14:43:44 +00:00
|
|
|
seq2 = s
|
|
|
|
else:
|
|
|
|
seq2 = None
|
|
|
|
break
|
2010-09-18 10:43:32 +00:00
|
|
|
if seq2 is None:
|
2011-09-19 14:00:42 +00:00
|
|
|
self.report({'ERROR'}, "Select 2 sound strips")
|
2010-02-08 14:43:44 +00:00
|
|
|
return {'CANCELLED'}
|
2010-04-01 21:44:56 +00:00
|
|
|
if seq1.frame_final_start > seq2.frame_final_start:
|
2010-02-08 14:43:44 +00:00
|
|
|
s = seq1
|
|
|
|
seq1 = seq2
|
|
|
|
seq2 = s
|
2010-04-01 21:44:56 +00:00
|
|
|
if seq1.frame_final_end > seq2.frame_final_start:
|
|
|
|
tempcfra = context.scene.frame_current
|
|
|
|
context.scene.frame_current = seq2.frame_final_start
|
2011-12-22 03:56:21 +00:00
|
|
|
seq1.keyframe_insert("volume")
|
2010-04-01 21:44:56 +00:00
|
|
|
context.scene.frame_current = seq1.frame_final_end
|
2010-02-08 14:43:44 +00:00
|
|
|
seq1.volume = 0
|
2011-12-22 03:56:21 +00:00
|
|
|
seq1.keyframe_insert("volume")
|
|
|
|
seq2.keyframe_insert("volume")
|
2010-04-01 21:44:56 +00:00
|
|
|
context.scene.frame_current = seq2.frame_final_start
|
2010-02-08 14:43:44 +00:00
|
|
|
seq2.volume = 0
|
2011-12-22 03:56:21 +00:00
|
|
|
seq2.keyframe_insert("volume")
|
2010-04-01 21:44:56 +00:00
|
|
|
context.scene.frame_current = tempcfra
|
2010-02-08 14:43:44 +00:00
|
|
|
return {'FINISHED'}
|
|
|
|
else:
|
2011-09-19 14:00:42 +00:00
|
|
|
self.report({'ERROR'}, "The selected strips don't overlap")
|
2010-02-08 14:43:44 +00:00
|
|
|
return {'CANCELLED'}
|
|
|
|
|
2010-02-14 11:21:21 +00:00
|
|
|
|
2020-02-16 20:39:12 +00:00
|
|
|
class SequencerSplitMulticam(Operator):
|
|
|
|
"""Split multi-cam strip and select camera"""
|
2010-05-02 17:36:38 +00:00
|
|
|
|
2020-02-16 20:39:12 +00:00
|
|
|
bl_idname = "sequencer.split_multicam"
|
|
|
|
bl_label = "Split multicam"
|
2010-05-02 17:36:38 +00:00
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
2018-07-11 20:18:09 +00:00
|
|
|
camera: IntProperty(
|
2018-06-26 17:41:37 +00:00
|
|
|
name="Camera",
|
|
|
|
min=1, max=32,
|
|
|
|
soft_min=1, soft_max=32,
|
|
|
|
default=1,
|
|
|
|
)
|
2010-05-02 17:36:38 +00:00
|
|
|
|
2010-08-09 01:37:09 +00:00
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
2010-05-02 17:36:38 +00:00
|
|
|
if context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip:
|
|
|
|
return context.scene.sequence_editor.active_strip.type == 'MULTICAM'
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def execute(self, context):
|
2010-09-09 18:03:57 +00:00
|
|
|
camera = self.camera
|
2010-05-02 17:36:38 +00:00
|
|
|
|
|
|
|
s = context.scene.sequence_editor.active_strip
|
|
|
|
|
2010-07-19 17:39:25 +00:00
|
|
|
if s.multicam_source == camera or camera >= s.channel:
|
2010-05-30 19:29:58 +00:00
|
|
|
return {'FINISHED'}
|
|
|
|
|
2010-07-15 16:56:04 +00:00
|
|
|
if not s.select:
|
|
|
|
s.select = True
|
2010-05-16 12:15:04 +00:00
|
|
|
|
2010-05-02 17:36:38 +00:00
|
|
|
cfra = context.scene.frame_current
|
2020-02-16 20:39:12 +00:00
|
|
|
bpy.ops.sequencer.split(frame=cfra, type='SOFT', side='RIGHT')
|
2010-05-03 22:17:05 +00:00
|
|
|
for s in context.scene.sequence_editor.sequences_all:
|
2010-07-15 16:56:04 +00:00
|
|
|
if s.select and s.type == 'MULTICAM' and s.frame_final_start <= cfra and cfra < s.frame_final_end:
|
2010-05-02 17:36:38 +00:00
|
|
|
context.scene.sequence_editor.active_strip = s
|
2010-05-16 12:15:04 +00:00
|
|
|
|
2010-05-02 17:36:38 +00:00
|
|
|
context.scene.sequence_editor.active_strip.multicam_source = camera
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
2010-05-16 12:15:04 +00:00
|
|
|
|
2011-08-12 06:57:00 +00:00
|
|
|
class SequencerDeinterlaceSelectedMovies(Operator):
|
2012-07-03 09:02:41 +00:00
|
|
|
"""Deinterlace all selected movie sources"""
|
2010-05-03 22:17:05 +00:00
|
|
|
|
|
|
|
bl_idname = "sequencer.deinterlace_selected_movies"
|
|
|
|
bl_label = "Deinterlace Movies"
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
2010-08-09 01:37:09 +00:00
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
2013-04-20 13:23:53 +00:00
|
|
|
return (context.scene and context.scene.sequence_editor)
|
2010-05-03 22:17:05 +00:00
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
for s in context.scene.sequence_editor.sequences_all:
|
2010-07-15 16:56:04 +00:00
|
|
|
if s.select and s.type == 'MOVIE':
|
2010-08-20 06:09:58 +00:00
|
|
|
s.use_deinterlace = True
|
2010-05-03 22:17:05 +00:00
|
|
|
|
2010-05-16 12:15:04 +00:00
|
|
|
return {'FINISHED'}
|
2017-03-18 09:03:24 +00:00
|
|
|
|
|
|
|
|
2019-09-14 00:24:02 +00:00
|
|
|
class SequencerFadesClear(Operator):
|
2019-09-14 19:42:04 +00:00
|
|
|
"""Removes fade animation from selected sequences"""
|
2019-09-14 00:24:02 +00:00
|
|
|
bl_idname = "sequencer.fades_clear"
|
|
|
|
bl_label = "Clear Fades"
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
|
|
|
return context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
fcurves = context.scene.animation_data.action.fcurves
|
2019-09-22 23:31:08 +00:00
|
|
|
fcurve_map = {
|
|
|
|
curve.data_path: curve
|
|
|
|
for curve in fcurves
|
|
|
|
if curve.data_path.startswith("sequence_editor.sequences_all")
|
|
|
|
}
|
2019-09-14 00:24:02 +00:00
|
|
|
for sequence in context.selected_sequences:
|
2019-09-22 01:26:45 +00:00
|
|
|
animated_property = "volume" if hasattr(sequence, "volume") else "blend_alpha"
|
|
|
|
data_path = sequence.path_from_id() + "." + animated_property
|
|
|
|
curve = fcurve_map.get(data_path)
|
|
|
|
if curve:
|
|
|
|
fcurves.remove(curve)
|
2019-09-14 00:24:02 +00:00
|
|
|
setattr(sequence, animated_property, 1.0)
|
2020-06-18 03:31:42 +00:00
|
|
|
sequence.invalidate('COMPOSITE')
|
2019-09-22 01:26:45 +00:00
|
|
|
|
2019-09-22 23:35:27 +00:00
|
|
|
return {'FINISHED'}
|
2019-09-14 00:24:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SequencerFadesAdd(Operator):
|
2019-09-14 19:42:04 +00:00
|
|
|
"""Adds or updates a fade animation for either visual or audio strips"""
|
2019-09-14 00:24:02 +00:00
|
|
|
bl_idname = "sequencer.fades_add"
|
|
|
|
bl_label = "Add Fades"
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
2019-10-31 07:00:18 +00:00
|
|
|
duration_seconds: FloatProperty(
|
2019-09-14 00:24:02 +00:00
|
|
|
name="Fade Duration",
|
|
|
|
description="Duration of the fade in seconds",
|
|
|
|
default=1.0,
|
|
|
|
min=0.01)
|
2019-10-31 07:00:18 +00:00
|
|
|
type: EnumProperty(
|
2019-09-14 19:35:42 +00:00
|
|
|
items=(
|
|
|
|
('IN_OUT', 'Fade In And Out', 'Fade selected strips in and out'),
|
|
|
|
('IN', 'Fade In', 'Fade in selected strips'),
|
|
|
|
('OUT', 'Fade Out', 'Fade out selected strips'),
|
2020-07-01 09:54:12 +00:00
|
|
|
('CURSOR_FROM', 'From Current Frame',
|
|
|
|
'Fade from the time cursor to the end of overlapping sequences'),
|
|
|
|
('CURSOR_TO', 'To Current Frame',
|
|
|
|
'Fade from the start of sequences under the time cursor to the current frame'),
|
2019-09-14 19:35:42 +00:00
|
|
|
),
|
2019-09-14 00:24:02 +00:00
|
|
|
name="Fade type",
|
2020-06-01 04:41:12 +00:00
|
|
|
description="Fade in, out, both in and out, to, or from the current frame. Default is both in and out",
|
2019-09-14 00:24:02 +00:00
|
|
|
default='IN_OUT')
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
|
|
|
# Can't use context.selected_sequences as it can have an impact on performances
|
|
|
|
return context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip
|
|
|
|
|
|
|
|
def execute(self, context):
|
2019-09-14 19:56:19 +00:00
|
|
|
from math import floor
|
|
|
|
|
2019-09-14 00:24:02 +00:00
|
|
|
# We must create a scene action first if there's none
|
|
|
|
scene = context.scene
|
|
|
|
if not scene.animation_data:
|
|
|
|
scene.animation_data_create()
|
|
|
|
if not scene.animation_data.action:
|
|
|
|
action = bpy.data.actions.new(scene.name + "Action")
|
|
|
|
scene.animation_data.action = action
|
|
|
|
|
|
|
|
sequences = context.selected_sequences
|
2019-09-14 19:35:42 +00:00
|
|
|
if self.type in {'CURSOR_TO', 'CURSOR_FROM'}:
|
|
|
|
sequences = [
|
|
|
|
s for s in sequences
|
|
|
|
if s.frame_final_start < context.scene.frame_current < s.frame_final_end
|
|
|
|
]
|
2019-09-14 00:24:02 +00:00
|
|
|
|
|
|
|
max_duration = min(sequences, key=lambda s: s.frame_final_duration).frame_final_duration
|
|
|
|
max_duration = floor(max_duration / 2.0) if self.type == 'IN_OUT' else max_duration
|
|
|
|
|
|
|
|
faded_sequences = []
|
|
|
|
for sequence in sequences:
|
|
|
|
duration = self.calculate_fade_duration(context, sequence)
|
|
|
|
duration = min(duration, max_duration)
|
|
|
|
if not self.is_long_enough(sequence, duration):
|
|
|
|
continue
|
|
|
|
|
|
|
|
animated_property = 'volume' if hasattr(sequence, 'volume') else 'blend_alpha'
|
|
|
|
fade_fcurve = self.fade_find_or_create_fcurve(context, sequence, animated_property)
|
|
|
|
fades = self.calculate_fades(sequence, fade_fcurve, animated_property, duration)
|
2019-09-14 19:35:42 +00:00
|
|
|
self.fade_animation_clear(fade_fcurve, fades)
|
2019-09-14 00:24:02 +00:00
|
|
|
self.fade_animation_create(fade_fcurve, fades)
|
|
|
|
faded_sequences.append(sequence)
|
2020-06-18 03:31:42 +00:00
|
|
|
sequence.invalidate('COMPOSITE')
|
2019-09-14 00:24:02 +00:00
|
|
|
|
|
|
|
sequence_string = "sequence" if len(faded_sequences) == 1 else "sequences"
|
2020-06-26 02:30:03 +00:00
|
|
|
self.report({'INFO'}, "Added fade animation to %d %s." % (len(faded_sequences), sequence_string))
|
2019-09-22 23:35:27 +00:00
|
|
|
return {'FINISHED'}
|
2019-09-14 00:24:02 +00:00
|
|
|
|
|
|
|
def calculate_fade_duration(self, context, sequence):
|
|
|
|
frame_current = context.scene.frame_current
|
|
|
|
duration = 0.0
|
|
|
|
if self.type == 'CURSOR_TO':
|
|
|
|
duration = abs(frame_current - sequence.frame_final_start)
|
|
|
|
elif self.type == 'CURSOR_FROM':
|
|
|
|
duration = abs(sequence.frame_final_end - frame_current)
|
|
|
|
else:
|
|
|
|
duration = calculate_duration_frames(context, self.duration_seconds)
|
|
|
|
return max(1, duration)
|
|
|
|
|
|
|
|
def is_long_enough(self, sequence, duration=0.0):
|
2019-09-14 19:35:42 +00:00
|
|
|
minimum_duration = duration * 2 if self.type == 'IN_OUT' else duration
|
2019-09-14 00:24:02 +00:00
|
|
|
return sequence.frame_final_duration >= minimum_duration
|
|
|
|
|
|
|
|
def calculate_fades(self, sequence, fade_fcurve, animated_property, duration):
|
|
|
|
"""
|
|
|
|
Returns a list of Fade objects
|
|
|
|
"""
|
|
|
|
fades = []
|
2019-09-14 19:35:42 +00:00
|
|
|
if self.type in {'IN', 'IN_OUT', 'CURSOR_TO'}:
|
2019-09-14 00:24:02 +00:00
|
|
|
fade = Fade(sequence, fade_fcurve, 'IN', animated_property, duration)
|
|
|
|
fades.append(fade)
|
2019-09-14 19:35:42 +00:00
|
|
|
if self.type in {'OUT', 'IN_OUT', 'CURSOR_FROM'}:
|
2019-09-14 00:24:02 +00:00
|
|
|
fade = Fade(sequence, fade_fcurve, 'OUT', animated_property, duration)
|
|
|
|
fades.append(fade)
|
|
|
|
return fades
|
|
|
|
|
|
|
|
def fade_find_or_create_fcurve(self, context, sequence, animated_property):
|
|
|
|
"""
|
|
|
|
Iterates over all the fcurves until it finds an fcurve with a data path
|
|
|
|
that corresponds to the sequence.
|
|
|
|
Returns the matching FCurve or creates a new one if the function can't find a match.
|
|
|
|
"""
|
|
|
|
fade_fcurve = None
|
|
|
|
fcurves = context.scene.animation_data.action.fcurves
|
|
|
|
searched_data_path = sequence.path_from_id(animated_property)
|
|
|
|
for fcurve in fcurves:
|
|
|
|
if fcurve.data_path == searched_data_path:
|
|
|
|
fade_fcurve = fcurve
|
|
|
|
break
|
|
|
|
if not fade_fcurve:
|
|
|
|
fade_fcurve = fcurves.new(data_path=searched_data_path)
|
|
|
|
return fade_fcurve
|
|
|
|
|
2019-09-14 19:35:42 +00:00
|
|
|
def fade_animation_clear(self, fade_fcurve, fades):
|
2019-09-14 00:24:02 +00:00
|
|
|
"""
|
|
|
|
Removes existing keyframes in the fades' time range, in fast mode, without
|
|
|
|
updating the fcurve
|
|
|
|
"""
|
|
|
|
keyframe_points = fade_fcurve.keyframe_points
|
|
|
|
for fade in fades:
|
|
|
|
for keyframe in keyframe_points:
|
|
|
|
# The keyframe points list doesn't seem to always update as the
|
|
|
|
# operator re-runs Leading to trying to remove nonexistent keyframes
|
|
|
|
try:
|
|
|
|
if fade.start.x < keyframe.co[0] <= fade.end.x:
|
2019-09-14 19:35:42 +00:00
|
|
|
keyframe_points.remove(keyframe, fast=True)
|
2019-09-14 00:24:02 +00:00
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
fade_fcurve.update()
|
|
|
|
|
|
|
|
def fade_animation_create(self, fade_fcurve, fades):
|
|
|
|
"""
|
|
|
|
Inserts keyframes in the fade_fcurve in fast mode using the Fade objects.
|
|
|
|
Updates the fcurve after having inserted all keyframes to finish the animation.
|
|
|
|
"""
|
|
|
|
keyframe_points = fade_fcurve.keyframe_points
|
|
|
|
for fade in fades:
|
|
|
|
for point in (fade.start, fade.end):
|
|
|
|
keyframe_points.insert(frame=point.x, value=point.y, options={'FAST'})
|
|
|
|
fade_fcurve.update()
|
|
|
|
# The graph editor and the audio waveforms only redraw upon "moving" a keyframe
|
|
|
|
keyframe_points[-1].co = keyframe_points[-1].co
|
|
|
|
|
|
|
|
|
|
|
|
class Fade:
|
2019-09-14 19:56:19 +00:00
|
|
|
# Data structure to represent fades.
|
|
|
|
__slots__ = (
|
|
|
|
"type",
|
|
|
|
"animated_property",
|
|
|
|
"duration",
|
|
|
|
"max_value",
|
|
|
|
"start",
|
|
|
|
"end",
|
|
|
|
)
|
2019-09-14 00:24:02 +00:00
|
|
|
|
|
|
|
def __init__(self, sequence, fade_fcurve, type, animated_property, duration):
|
2019-09-14 19:56:19 +00:00
|
|
|
from mathutils import Vector
|
2019-09-14 00:24:02 +00:00
|
|
|
self.type = type
|
|
|
|
self.animated_property = animated_property
|
|
|
|
self.duration = duration
|
|
|
|
self.max_value = self.calculate_max_value(sequence, fade_fcurve)
|
|
|
|
|
|
|
|
if type == 'IN':
|
|
|
|
self.start = Vector((sequence.frame_final_start, 0.0))
|
|
|
|
self.end = Vector((sequence.frame_final_start + self.duration, self.max_value))
|
|
|
|
elif type == 'OUT':
|
|
|
|
self.start = Vector((sequence.frame_final_end - self.duration, self.max_value))
|
|
|
|
self.end = Vector((sequence.frame_final_end, 0.0))
|
|
|
|
|
|
|
|
def calculate_max_value(self, sequence, fade_fcurve):
|
|
|
|
"""
|
|
|
|
Returns the maximum Y coordinate the fade animation should use for a given sequence
|
|
|
|
Uses either the sequence's value for the animated property, or the next keyframe after the fade
|
|
|
|
"""
|
|
|
|
max_value = 0.0
|
|
|
|
|
|
|
|
if not fade_fcurve.keyframe_points:
|
|
|
|
max_value = getattr(sequence, self.animated_property, 1.0)
|
|
|
|
else:
|
|
|
|
if self.type == 'IN':
|
|
|
|
fade_end = sequence.frame_final_start + self.duration
|
|
|
|
keyframes = (k for k in fade_fcurve.keyframe_points if k.co[0] >= fade_end)
|
|
|
|
if self.type == 'OUT':
|
|
|
|
fade_start = sequence.frame_final_end - self.duration
|
|
|
|
keyframes = (k for k in reversed(fade_fcurve.keyframe_points) if k.co[0] <= fade_start)
|
|
|
|
try:
|
|
|
|
max_value = next(keyframes).co[1]
|
|
|
|
except StopIteration:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return max_value if max_value > 0.0 else 1.0
|
|
|
|
|
|
|
|
def __repr__(self):
|
2020-06-26 02:30:03 +00:00
|
|
|
return "Fade %r: %r to %r" % (self.type, self.start, self.end)
|
2019-09-14 00:24:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
def calculate_duration_frames(context, duration_seconds):
|
|
|
|
return round(duration_seconds * context.scene.render.fps / context.scene.render.fps_base)
|
|
|
|
|
|
|
|
|
2017-03-18 09:03:24 +00:00
|
|
|
classes = (
|
|
|
|
SequencerCrossfadeSounds,
|
2020-02-16 20:39:12 +00:00
|
|
|
SequencerSplitMulticam,
|
2017-03-18 09:03:24 +00:00
|
|
|
SequencerDeinterlaceSelectedMovies,
|
2019-09-22 01:26:45 +00:00
|
|
|
SequencerFadesClear,
|
|
|
|
SequencerFadesAdd,
|
2018-06-04 06:49:13 +00:00
|
|
|
)
|