vse_toolbox/operators/sequencer.py

759 lines
25 KiB
Python
Raw Normal View History

2024-04-10 16:15:57 +02:00
from os.path import expandvars, abspath
from pathlib import Path
2023-05-02 18:38:16 +02:00
import bpy
from bpy.types import Operator
2024-05-28 14:49:28 +02:00
from bpy.props import (BoolProperty, StringProperty, FloatProperty,
IntProperty, EnumProperty)
2023-05-02 18:38:16 +02:00
2023-05-03 14:40:07 +02:00
from vse_toolbox.sequencer_utils import (get_strips, rename_strips, set_channels,
2024-05-28 14:49:28 +02:00
get_channel_index, new_text_strip, get_strip_at, get_channel_name,
create_shot_strip)
from vse_toolbox import auto_splitter
2024-03-26 17:54:03 +01:00
from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings
2024-04-10 16:15:57 +02:00
from shutil import copy2
2023-05-02 18:38:16 +02:00
class VSETB_OT_rename(Operator):
bl_idname = "vse_toolbox.strips_rename"
bl_label = "Rename Strips"
bl_description = "Rename Strips"
bl_options = {"REGISTER", "UNDO"}
#template : StringProperty(name="Strip Name", default="")
#increment : IntProperty(name="Increment", default=0)
2024-04-10 16:15:57 +02:00
selected_only : BoolProperty(name="Selected Only", default=True)
2023-05-02 18:38:16 +02:00
#start_number : IntProperty(name="Start Number", default=0, min=0)
#by_sequence : BoolProperty(
# name="Reset By Sequence",
# description="Reset Start Number for each sequence",
# default=False
#)
@classmethod
def poll(cls, context):
settings = get_scene_settings()
2024-04-10 16:15:57 +02:00
strip = context.active_sequence_strip
return settings.active_project and get_channel_name(strip) in ('Shots', 'Sequences')
2023-05-02 18:38:16 +02:00
def invoke(self, context, event):
scn = context.scene
settings = get_scene_settings()
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
scn = context.scene
settings = get_scene_settings()
project = settings.active_project
2024-04-10 16:15:57 +02:00
episode = project.episode_name
sequence = str(project.sequence_start_number).zfill(project.sequence_padding)
shot = str(project.shot_start_number).zfill(project.shot_padding)
strip = context.active_sequence_strip
channel_name = get_channel_name(strip)
2023-05-02 18:38:16 +02:00
col = layout.column()
col.use_property_split = True
col.use_property_decorate = False
2024-04-10 16:15:57 +02:00
if channel_name == 'Shots':
2023-05-02 18:38:16 +02:00
col.prop(project, 'shot_template', text='Shot Name')
col.prop(project, 'shot_start_number', text='Start Number')
col.prop(project, 'shot_increment', text='Increment')
2024-04-10 16:15:57 +02:00
col.prop(project, 'shot_padding', text='Padding')
2023-05-02 18:38:16 +02:00
col.prop(project, 'reset_by_sequence')
2024-04-10 16:15:57 +02:00
elif channel_name == 'Sequences':
2023-05-02 18:38:16 +02:00
col.prop(project, 'sequence_template' ,text='Sequence Name')
col.prop(project, 'sequence_start_number', text='Start Number')
col.prop(project, 'sequence_increment', text='Increment')
2024-04-10 16:15:57 +02:00
col.prop(project, 'sequence_padding', text='Padding')
2023-05-02 18:38:16 +02:00
col.prop(self, 'selected_only')
2024-04-10 16:15:57 +02:00
if channel_name == 'Shots':
label = project.shot_template.format(episode=episode, sequence=sequence, shot=shot)
elif channel_name == 'Sequences':
label = project.sequence_template.format(episode=episode, sequence=sequence)
col.label(text=f'Renaming {label}')
2023-05-02 18:38:16 +02:00
def execute(self, context):
scn = context.scene
settings = get_scene_settings()
project = settings.active_project
2024-04-10 16:15:57 +02:00
strip = context.active_sequence_strip
channel_name = get_channel_name(strip)
strips = get_strips(channel=channel_name, selected_only=self.selected_only)
if channel_name == 'Shots':
2023-05-02 18:38:16 +02:00
rename_strips(strips,
template=project.shot_template,
increment=project.shot_increment, start_number=project.shot_start_number,
2024-04-10 16:15:57 +02:00
by_sequence=project.reset_by_sequence,
padding=project.shot_padding
2023-05-02 18:38:16 +02:00
)
2024-04-10 16:15:57 +02:00
if channel_name == 'Sequences':
2023-05-02 18:38:16 +02:00
rename_strips(strips,
template=project.sequence_template,
2024-04-10 16:15:57 +02:00
increment=project.sequence_increment, start_number=project.sequence_start_number,
padding=project.sequence_padding
2023-05-02 18:38:16 +02:00
)
return {"FINISHED"}
2023-05-04 11:32:36 +02:00
2023-05-02 18:38:16 +02:00
class VSETB_OT_show_waveform(Operator):
bl_idname = "vse_toolbox.show_waveform"
bl_label = "Show Waveform"
bl_description = "Show Waveform of all audio strips"
bl_options = {"REGISTER", "UNDO"}
enabled : BoolProperty(default=True)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
scn = context.scene
for strip in get_strips(channel='Audio'):
strip.show_waveform = self.enabled
return {"FINISHED"}
class VSETB_OT_set_sequencer(Operator):
bl_idname = "vse_toolbox.set_sequencer"
bl_label = "Set Sequencer"
bl_description = "Set resolution, frame end and channel names"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return True
def execute(self, context):
scn = context.scene
set_channels()
2023-05-04 12:24:48 +02:00
movies = get_strips(channel='Movie')
movie = None
if movies:
movie = movies[0]
2023-05-02 18:38:16 +02:00
movie.transform.scale_x = movie.transform.scale_y = 1
elem = movie.strip_elem_from_frame(scn.frame_current)
scn.render.resolution_x = elem.orig_width
scn.render.resolution_y = elem.orig_height
else:
self.report({'INFO'}, f'Cannot set Resolution. No Movie Found.')
scn.view_settings.view_transform = 'Standard'
scn.render.image_settings.file_format = 'FFMPEG'
2024-04-16 14:28:02 +02:00
scn.render.ffmpeg.gopsize = 8
2023-05-02 18:38:16 +02:00
scn.render.ffmpeg.constant_rate_factor = 'HIGH'
scn.render.ffmpeg.format = 'QUICKTIME'
scn.render.ffmpeg.audio_codec = 'AAC'
scn.render.ffmpeg.audio_codec = 'MP3'
scn.render.ffmpeg.audio_mixrate = 44100
scn.render.ffmpeg.audio_bitrate = 128
2023-05-04 12:24:48 +02:00
shots = get_strips(channel='Shots')
if shots:
scn.frame_end = shots[-1].frame_final_end -1
elif movie:
scn.frame_end = movie.frame_final_end -1
2023-05-02 18:38:16 +02:00
return {"FINISHED"}
2024-05-24 09:44:19 +02:00
class VSETB_OT_auto_split(Operator):
"""Launch subprocess with ffmpeg and python to find and create each
shots strips from video source"""
bl_idname = "vse_toolbox.auto_split"
bl_label = "Auto Split"
bl_description = "Generate shots strips"
bl_options = {"REGISTER", "UNDO"}
threshold: FloatProperty(name="Threshold", default=0.6, min=0, max=1)
2024-05-28 14:49:28 +02:00
frame_first: IntProperty(name='Start Split')
frame_last: IntProperty(name='End Split')
movie_channel_name: EnumProperty(
items=lambda self, ctx: ((c.name, c.name, '') for c in ctx.scene.sequence_editor.channels),
name='Movie Channel')
2024-05-24 09:44:19 +02:00
def invoke(self, context, event):
2024-05-28 14:49:28 +02:00
self.frame_first = context.scene.frame_start
self.frame_last = context.scene.frame_end
if context.selected_sequences:
self.frame_first = min([s.frame_final_start for s in context.selected_sequences])
self.frame_last = max([s.frame_final_end for s in context.selected_sequences])
2024-05-24 09:44:19 +02:00
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
col = layout.column()
col.use_property_split = True
col.use_property_decorate = False
col.prop(self, 'threshold')
2024-05-28 14:49:28 +02:00
col.prop(self, 'movie_channel_name')
split_col = col.column(align=True)
split_col.prop(self, 'frame_first', text='Frame Split First')
split_col.prop(self, 'frame_last', text='Last')
2024-05-24 09:44:19 +02:00
col.prop(self, 'selected_only')
def execute(self, context):
context.window_manager.modal_handler_add(self)
return {'PASS_THROUGH'}
2024-05-24 09:44:19 +02:00
def modal(self, context, event):
2024-05-28 14:49:28 +02:00
strips = get_strips(channel=self.movie_channel_name)
2024-05-24 09:44:19 +02:00
2024-05-28 14:58:49 +02:00
i = 1
frame_start = self.frame_first
2024-05-24 11:12:13 +02:00
for strip in strips:
2024-05-24 09:44:19 +02:00
2024-05-24 11:12:13 +02:00
if strip.type != 'MOVIE':
continue
2024-05-24 09:44:19 +02:00
2024-05-28 14:49:28 +02:00
# Skip strip outside the frame range to create shot from.
if strip.frame_final_start >= self.frame_last or strip.frame_final_end <= self.frame_first:
continue
process = auto_splitter.launch_split(strip, self.threshold, frame_start=self.frame_first, frame_end=self.frame_last)
for line in process.stdout:
2024-05-28 14:49:28 +02:00
# Get frame split from the movie timeline (not from blender strips timeline)
frame_end = auto_splitter.get_split_time(line, fps=24)
if not frame_end:
continue
2024-05-28 14:49:28 +02:00
# Convert movie frame to strips frame
if frame_start+int(strip.frame_start) < self.frame_first:
frame_start = self.frame_first
frame_end += int(strip.frame_final_start)
if frame_end > self.frame_last:
frame_end = self.frame_last
create_shot_strip(
f'tmp_shot_{str(i).zfill(3)}',
2024-05-28 14:49:28 +02:00
start=frame_start,
end=frame_end
)
i += 1
frame_start = frame_end
2024-05-24 09:44:19 +02:00
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
2024-05-24 11:12:13 +02:00
process.wait()
2024-05-24 11:12:13 +02:00
2024-05-28 14:58:49 +02:00
# Last strip:
if frame_start < self.frame_last:
create_shot_strip(
f'tmp_shot_{str(i).zfill(3)}',
start=frame_start,
end=self.frame_last
)
2024-05-24 09:44:19 +02:00
return {'FINISHED'}
2023-05-02 18:38:16 +02:00
class VSETB_OT_set_stamps(Operator):
bl_idname = "vse_toolbox.set_stamps"
bl_label = "Set Stamps"
bl_description = "Set Stamps on Video"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
scn = context.scene
settings = get_scene_settings()
2023-06-21 13:46:28 +02:00
project = settings.active_project
2023-05-02 18:38:16 +02:00
#strip_settings = get_strip_settings()
channel_index = get_channel_index('Stamps')
for strip in get_strips('Stamps'):
if strip.type == 'META':
scn.sequence_editor.sequences.remove(strip)
bpy.ops.sequencer.select_all(action='DESELECT')
2023-05-04 11:32:36 +02:00
height = scn.render.resolution_y
width = scn.render.resolution_x
ratio = (height / 1080)
margin = 0.01
box_margin = 0.005
font_size = int(24*ratio)
crop_x = int(width * 0.4)
crop_max_y = int(height - font_size*2)
#crop_min_y = int(scn.render.resolution_y * 0.01)
2023-05-02 18:38:16 +02:00
stamp_params = dict(start=scn.frame_start, end=scn.frame_end,
2023-05-04 11:32:36 +02:00
font_size=font_size, y=margin, box_margin=box_margin, select=True, box_color=(0, 0, 0, 0.5))
2023-05-02 18:38:16 +02:00
# Project Name
2024-04-10 16:15:57 +02:00
project_text = '{project}'
2023-06-21 13:46:28 +02:00
if project.type == 'TVSHOW':
2024-04-10 16:15:57 +02:00
project_text = '{project} / ep{episode}'
project_strip_stamp = new_text_strip('project_stamp', channel=1, **stamp_params,
2023-06-21 13:46:28 +02:00
text=project_text, x=0.01, align_x='LEFT', align_y='BOTTOM')
2023-05-02 18:38:16 +02:00
project_strip_stamp.crop.max_x = crop_x * 2
project_strip_stamp.crop.max_y = crop_max_y
# Shot Name
2023-06-21 13:46:28 +02:00
2024-04-10 16:15:57 +02:00
shot_strip_stamp = new_text_strip('shot_stamp', channel=2, **stamp_params,
text='sq{sequence} / sh{shot}', align_y='BOTTOM')
2023-05-02 18:38:16 +02:00
shot_strip_stamp.crop.min_x = crop_x
shot_strip_stamp.crop.max_x = crop_x
shot_strip_stamp.crop.max_y = crop_max_y
2024-04-10 16:15:57 +02:00
# Frame
frame_strip_stamp = new_text_strip('frame_stamp', channel=3, **stamp_params,
text='{shot_frame} / {shot_duration} {timecode}', x=0.99, align_x='RIGHT', align_y='BOTTOM')
2023-05-02 18:38:16 +02:00
frame_strip_stamp.crop.min_x = crop_x *2
frame_strip_stamp.crop.max_y = crop_max_y
bpy.ops.sequencer.meta_make()
stamps_strip = context.active_sequence_strip
stamps_strip.name = 'Stamps'
stamps_strip.channel = channel_index
#stamps_strip = scn.sequence_editor.sequences.new_meta('Stamps', scn.frame_start, scn.frame_end)
#stamps_strip.channel = get_channel_index('Stamps')
scn.frame_set(scn.frame_current) # For update stamps
return {"FINISHED"}
2023-05-05 16:54:00 +02:00
class VSETB_OT_previous_shot(Operator):
bl_idname = "vse_toolbox.previous_shot"
bl_label = "Jump to Previous Shot"
bl_description = "Jump to Previous Shot"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
strips = get_strips('Shots')
if not strips:
return {"CANCELLED"}
active_strip = get_strip_at('Shots')
if active_strip is strips[0]:
return {"CANCELLED"}
active_strip_index = strips.index(active_strip)
next_shot = strips[active_strip_index - 1]
context.scene.frame_set(next_shot.frame_final_start)
bpy.ops.sequencer.select_all(action="DESELECT")
next_shot.select = True
context.scene.sequence_editor.active_strip = next_shot
return {"FINISHED"}
class VSETB_OT_next_shot(Operator):
bl_idname = "vse_toolbox.next_shot"
bl_label = "Jump to Next Shot"
bl_description = "Jump to Next Shot"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
strips = get_strips('Shots')
if not strips:
return {"CANCELLED"}
active_strip = get_strip_at('Shots')
if active_strip is strips[-1]:
return {"CANCELLED"}
active_strip_index = strips.index(active_strip)
next_shot = strips[active_strip_index + 1]
context.scene.frame_set(next_shot.frame_final_start)
bpy.ops.sequencer.select_all(action="DESELECT")
next_shot.select = True
context.scene.sequence_editor.active_strip = next_shot
return {"FINISHED"}
2024-04-10 16:15:57 +02:00
2024-04-25 16:19:25 +02:00
class VSETB_OT_open_strip_folder(Operator):
bl_idname = "vse_toolbox.open_strip_folder"
bl_label = "Open Strip Folder"
bl_description = "Open selected strip folder"
2024-03-26 17:54:03 +01:00
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
2024-04-25 16:19:25 +02:00
strip = context.active_sequence_strip
if not strip:
cls.poll_message_set('No active')
2024-04-16 14:28:02 +02:00
return
2024-04-25 16:19:25 +02:00
if not any(p in get_channel_name(strip) for p in ('Shots', 'Sequences', 'Movie', 'Video', 'Audio', 'Sound')):
cls.poll_message_set('Only for Shots, Sequences, Movie and Audio strips')
2024-04-16 14:28:02 +02:00
return
return True
2024-03-26 17:54:03 +01:00
def execute(self, context):
2024-04-25 16:19:25 +02:00
tpl_by_channel = {
'Shots': 'shot_dir',
'Sequences': 'sequence_dir'
}
2024-03-26 17:54:03 +01:00
strip = context.active_sequence_strip
settings = get_scene_settings()
project = settings.active_project
strip_settings = get_strip_settings()
format_data = {**settings.format_data, **project.format_data, **strip_settings.format_data}
2024-04-25 16:19:25 +02:00
channel_name = get_channel_name(strip)
if strip.type in ('MOVIE', 'IMAGE'):
path = Path(strip.filepath)
elif strip.type in ('SOUND'):
path = Path(strip.sound.filepath)
else:
folder_template = expandvars(project.templates[tpl_by_channel[channel_name]].value)
path = Path(folder_template.format(**format_data))
if not path.is_dir():
path = path.parent
2024-03-26 17:54:03 +01:00
2024-04-25 16:19:25 +02:00
bpy.ops.wm.path_open(filepath=str(path))
2024-04-10 16:15:57 +02:00
return {"FINISHED"}
class VSETB_OT_collect_files(Operator):
bl_idname = "vse_toolbox.collect_files"
bl_label = "Collect Files"
bl_description = "Collect Files"
bl_options = {"REGISTER", "UNDO"}
collect_folder: StringProperty(
name="Collect Folder", default="//sources", subtype='DIR_PATH')
@classmethod
def poll(cls, context):
if bpy.data.is_saved:
return True
cls.poll_message_set('Save the blend to collect files')
def execute(self, context):
strip = context.active_sequence_strip
settings = get_scene_settings()
project = settings.active_project
strips = [s for s in context.scene.sequence_editor.sequences_all if s.type in ('MOVIE', 'SOUND')]
context.window_manager.progress_begin(0, len(strips))
for i, strip in enumerate(strips):
context.window_manager.progress_update(i)
if strip.type == 'MOVIE':
src_path = strip.filepath
elif strip.type == 'SOUND':
src_path = strip.sound.filepath
src_path = Path(abspath(bpy.path.abspath(src_path)))
dst_path = Path(bpy.path.abspath(self.collect_folder), src_path.name)
if src_path == dst_path:
continue
dst_path.parent.mkdir(exist_ok=True, parents=True)
print(f'Copy file from {src_path} to {dst_path}')
copy2(str(src_path), str(dst_path))
rel_path = bpy.path.relpath(str(dst_path))
if len(Path(rel_path).as_posix()) < len(dst_path.as_posix()):
dst_path = rel_path
if strip.type == 'MOVIE':
strip.filepath = str(dst_path)
elif strip.type == 'SOUND':
strip.sound.filepath = str(dst_path)
context.window_manager.progress_end()
return {"FINISHED"}
def invoke(self, context, event):
scn = context.scene
settings = get_scene_settings()
return context.window_manager.invoke_props_dialog(self)
2024-05-14 11:49:47 +02:00
class VSETB_OT_insert_channel(Operator):
bl_idname = "vse_toolbox.insert_channel"
bl_label = "Insert Channel"
bl_description = "Insert a Channel bellow the active strip"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.active_sequence_strip
def execute(self, context):
scn = context.scene
channel_index = context.active_sequence_strip.channel
strips = list(scn.sequence_editor.sequences)
for strip in sorted(strips, key=lambda x: x.channel, reverse=True):
if strip.channel >= channel_index:
strip.channel += 1
channels = {i: (c.name, c.lock, c.mute) for i, c in enumerate(scn.sequence_editor.channels)}
for i in sorted(channels.keys()):
channel = scn.sequence_editor.channels[i]
#i = list(scn.sequence_editor.channels).index(i)
prev_channel = channels.get(i-1)
if i == channel_index:
channel.name = "Channel"
channel.lock = False
channel.mute = False
elif i >= channel_index and prev_channel is not None:
prev_name, prev_lock, prev_mute = prev_channel
channel.name = prev_name
if channel.lock != prev_lock:
channel.lock = prev_lock
if channel.mute != prev_mute:
channel.mute = prev_mute
return {'FINISHED'}
class VSETB_OT_remove_channel(Operator):
bl_idname = "vse_toolbox.remove_channel"
bl_label = "Remove Channel"
bl_description = "Remove Channel bellow the active strip"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.active_sequence_strip
def execute(self, context):
scn = context.scene
channel_index = context.active_sequence_strip.channel
if [s for s in scn.sequence_editor.sequences if s.channel == channel_index-1]:
self.report({"WARNING"}, "Channel Bellow not empty")
strips = list(scn.sequence_editor.sequences)
for strip in sorted(strips, key=lambda x: x.channel):
if strip.channel >= channel_index:
strip.channel -= 1
channels = {i: (c.name, c.lock, c.mute) for i, c in enumerate(scn.sequence_editor.channels)}
for i in sorted(channels.keys(), reverse=True):
channel = scn.sequence_editor.channels[i]
#i = list(scn.sequence_editor.channels).index(i)
prev_channel = channels.get(i+1)
if i >= channel_index-1 and prev_channel is not None:
prev_name, prev_lock, prev_mute = prev_channel
channel.name = prev_name
if channel.lock != prev_lock:
channel.lock = prev_lock
if channel.mute != prev_mute:
channel.mute = prev_mute
return {'FINISHED'}
2024-04-10 16:15:57 +02:00
class WM_OT_split_view(Operator):
"""Toggle Split sequencer view"""
bl_idname = "wm.split_view"
bl_label = "Split View"
def get_preview_areas(self, context):
return [
x for x in context.screen.areas
if x.type == "SEQUENCE_EDITOR" and x.spaces[0].view_type == "PREVIEW"
]
def invoke(self, context, event):
preview_areas = self.get_preview_areas(context)
if not preview_areas:
return {"CANCELLED"}
scn = context.scene
video_channels = [i for i, c in enumerate(scn.sequence_editor.channels) if 'Video' in c.name]
2024-04-16 18:28:36 +02:00
if len(video_channels) < 2:
self.report({"ERROR"}, 'You need two channels to split the view')
return {"CANCELLED"}
if len(preview_areas) == 1:
2024-04-10 16:15:57 +02:00
# Split area
with bpy.context.temp_override(area=preview_areas[0]):
bpy.ops.screen.area_split(direction="VERTICAL")
# Disable toolbar on right panel
# Update areas
preview_areas = self.get_preview_areas(context)
2024-04-16 18:28:36 +02:00
preview_areas[-1].spaces[0].display_channel = 0 #video_channels[-1]
2024-04-10 16:15:57 +02:00
preview_areas[0].spaces[0].display_channel = video_channels[-2]
preview_areas[0].spaces[0].show_gizmo_navigate = False
preview_areas[-1].spaces[0].show_gizmo_navigate = False
# Hide toolbar
#preview_areas[0].spaces[0].show_region_toolbar = False
else:
# Give the remaining area to have the left area's channel displayed
# Show toolbar of remaning area
#preview_areas[0].spaces[0].show_region_toolbar = True
# Join areas
# Simulates the mouse position as being between the two areas horizontally
# and a bit above the bottom corner vertically
cursor_x = int(preview_areas[0].x - (preview_areas[0].x - preview_areas[1].width) / 2)
cursor_y = preview_areas[0].y + 10
bpy.ops.screen.area_join(cursor=(cursor_x, cursor_y))
preview_areas = self.get_preview_areas(context)
preview_areas[0].spaces[0].display_channel = 0
# Force UI update, due to Blender bug, delete when fixed -> https://developer.blender.org/T65529
bpy.ops.screen.area_swap(cursor=(preview_areas[0].x, preview_areas[0].y))
bpy.ops.screen.area_swap(cursor=(preview_areas[0].x, preview_areas[0].y))
return {"FINISHED"}
2024-05-23 10:07:43 +02:00
class VSETB_OT_merge_shot_strips(Operator):
"""Merge selected shots strips (required at least two strips)."""
2024-04-10 16:15:57 +02:00
2024-05-23 10:07:43 +02:00
bl_idname = "vse_toolbox.merge_shot_strips"
bl_label = "Merge Shot Strips"
@classmethod
def poll(cls, context):
selected_strips = bpy.context.selected_sequences
if len(selected_strips) <= 1:
return False
return all(get_channel_name(strip) == 'Shots' for strip in selected_strips)
2024-04-10 16:15:57 +02:00
def execute(self, context):
2024-03-26 17:54:03 +01:00
2024-05-23 10:07:43 +02:00
selected_strips = bpy.context.selected_sequences
last_frame = selected_strips[-1].frame_final_end
for i in range(1, len(selected_strips)):
context.scene.sequence_editor.sequences.remove(selected_strips[i])
selected_strips[0].frame_final_end = last_frame
2024-03-26 17:54:03 +01:00
return {"FINISHED"}
2023-05-05 16:54:00 +02:00
addon_keymaps = []
def register_keymaps():
addon = bpy.context.window_manager.keyconfigs.addon
if not addon:
return
#print('VSE Toolbox Keymaps Register')
km = addon.keymaps.new(name="Sequencer", space_type="SEQUENCE_EDITOR")
kmi = km.keymap_items.new('vse_toolbox.previous_shot', type='LEFT_ARROW', value='PRESS', ctrl=True)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new('vse_toolbox.next_shot', type='RIGHT_ARROW', value='PRESS', ctrl=True)
addon_keymaps.append((km, kmi))
2024-05-23 10:07:43 +02:00
kmi = km.keymap_items.new('vse_toolbox.merge_shot_strips', type='J', value='PRESS', ctrl=True)
addon_keymaps.append((km, kmi))
2023-05-05 16:54:00 +02:00
def unregister_keymaps():
#print('unregister_keymaps', addon_keymaps)
for km, kmi in addon_keymaps:
if kmi in list(km.keymap_items):
km.keymap_items.remove(kmi)
addon_keymaps.clear()
2023-05-02 18:38:16 +02:00
classes = (
VSETB_OT_rename,
VSETB_OT_set_sequencer,
2024-05-24 09:44:19 +02:00
VSETB_OT_auto_split,
2023-05-02 18:38:16 +02:00
VSETB_OT_set_stamps,
VSETB_OT_show_waveform,
2023-05-05 16:54:00 +02:00
VSETB_OT_previous_shot,
2024-03-26 17:54:03 +01:00
VSETB_OT_next_shot,
2024-04-25 16:19:25 +02:00
VSETB_OT_open_strip_folder,
2024-04-10 16:15:57 +02:00
VSETB_OT_collect_files,
2024-05-14 11:49:47 +02:00
VSETB_OT_insert_channel,
VSETB_OT_remove_channel,
2024-05-23 10:07:43 +02:00
VSETB_OT_merge_shot_strips,
WM_OT_split_view,
2023-05-02 18:38:16 +02:00
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
2023-05-05 16:54:00 +02:00
register_keymaps()
2023-05-02 18:38:16 +02:00
def unregister():
for cls in reversed(classes):
2023-05-05 16:54:00 +02:00
bpy.utils.unregister_class(cls)
unregister_keymaps()