vse_toolbox/sequencer_utils.py

601 lines
18 KiB
Python
Raw Normal View History

2023-03-14 13:38:04 +01:00
# SPDX-License-Identifier: GPL-2.0-or-later
import re
2023-04-20 00:12:39 +02:00
from pathlib import Path
2023-04-21 21:44:05 +02:00
import os
2023-03-14 13:38:04 +01:00
2023-04-20 00:12:39 +02:00
import bpy
2023-03-17 20:03:38 +01:00
from bpy.app.handlers import persistent
2023-04-20 00:12:39 +02:00
2024-03-14 17:44:05 +01:00
from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings, attr_set
2023-05-08 18:25:04 +02:00
from vse_toolbox.file_utils import install_module, parse
2023-04-04 12:28:31 +02:00
from vse_toolbox.constants import SOUND_SUFFIXES
2023-04-22 21:12:21 +02:00
#import multiprocessing
#from multiprocessing.pool import ThreadPool
import subprocess
2024-04-10 16:15:57 +02:00
from collections import defaultdict
2023-03-14 13:38:04 +01:00
2023-04-19 10:37:38 +02:00
2024-04-16 14:28:02 +02:00
def get_render_attributes():
scn = bpy.context.scene
return [
(scn.view_settings, "view_transform", 'Standard'),
(scn.render.image_settings, "file_format", 'FFMPEG'),
(scn.render.ffmpeg, "gopsize", 8),
(scn.render.ffmpeg, "constant_rate_factor", 'HIGH'),
(scn.render.ffmpeg, "format", 'QUICKTIME'),
(scn.render.ffmpeg, "audio_codec", 'MP3'),
(scn.render.ffmpeg, "audio_mixrate", 44100),
(scn.render.ffmpeg, "audio_bitrate", 128)
]
2024-02-13 16:01:31 +01:00
def frame_to_timecode(frame, fps):
total_seconds = frame / fps
hours = int(total_seconds // 3600)
minutes = int((total_seconds % 3600) // 60)
seconds = int(total_seconds % 60)
frames = int((total_seconds * fps) % fps)
timecode = f"{hours:02d}:{minutes:02d}:{seconds:02d}:{frames:02d}"
return timecode
2023-04-14 18:55:00 +02:00
def new_text_strip(name='Text', channel=0, start=0, end=50, text='Text', font_size=48,
x=0.5, y=0.5, align_x='CENTER', align_y='CENTER', select=False,
box_color=None, box_margin=0.005):
sequences = bpy.context.scene.sequence_editor.sequences
strip = sequences.new_effect(name, 'TEXT', channel, frame_start=start, frame_end=end)
strip.select = select
strip.text = text
strip.location.x = x
strip.location.y = y
strip.align_y = align_y
strip.align_x = align_x
strip.channel = channel
strip.font_size = font_size
if box_color:
strip.use_box = True
strip.box_color = box_color
strip.box_margin = box_margin
return strip
def is_strip_at(strip, frame=None):
if frame is None:
frame = bpy.context.scene.frame_current
return (strip.frame_final_start <= frame < strip.frame_final_end)
2023-03-17 20:03:38 +01:00
2023-03-14 13:38:04 +01:00
def get_strips(channel=0, selected_only=False):
scn = bpy.context.scene
if isinstance(channel, str):
2023-04-14 18:55:00 +02:00
channel = get_channel_index(channel)
2023-03-14 13:38:04 +01:00
2023-06-21 11:14:56 +02:00
strips = [s for s in scn.sequence_editor.sequences if s.channel==channel]
2023-03-14 13:38:04 +01:00
if selected_only:
strips = [s for s in strips if s.select]
return sorted(strips, key=lambda x : x.frame_final_start)
2023-04-14 18:55:00 +02:00
def get_strip_at(channel=0, frame=None):
strips = get_strips(channel=channel)
return next((s for s in strips if is_strip_at(s, frame)), None)
def get_channel_index(name):
2023-03-16 18:32:17 +01:00
scn = bpy.context.scene
channel_id = 0
channel = scn.sequence_editor.channels.get(name)
if channel:
channel_id = scn.sequence_editor.channels.keys().index(name)
return channel_id
2023-04-14 18:55:00 +02:00
def get_channel_name(strip):
if not strip:
return
scn = bpy.context.scene
return scn.sequence_editor.channels[strip.channel].name
def get_strip_sequence_name(strip):
sequence_strip = get_strip_at(channel='Sequences', frame=strip.frame_final_start)
if sequence_strip:
return sequence_strip.name
else:
return 'NoSequence'
2023-03-14 13:38:04 +01:00
2024-04-10 16:15:57 +02:00
def rename_strips(strips, template, increment=10, start_number=0, padding=3, by_sequence=False):
2023-03-14 13:38:04 +01:00
scn = bpy.context.scene
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-14 13:38:04 +01:00
project = settings.active_project
2023-04-14 18:55:00 +02:00
episode_name = ''
if settings.active_episode:
2023-04-19 10:37:38 +02:00
episode_name = settings.active_episode.name
2023-03-14 13:38:04 +01:00
2023-04-14 18:55:00 +02:00
prev_sequence_name = None
2023-03-14 13:38:04 +01:00
strip_number = 0
for strip in strips:
2024-04-16 14:28:02 +02:00
channel = get_channel_name(strip)
2023-04-14 18:55:00 +02:00
sequence_name = get_strip_sequence_name(strip)
2024-04-16 14:28:02 +02:00
format_data = {}
if channel == 'Shots':
format_data = parse(project.sequence_template, sequence_name)
else:
format_data['sequence'] = str(strip_number*increment + start_number).zfill(padding)
2023-03-14 13:38:04 +01:00
2023-04-14 18:55:00 +02:00
if (by_sequence and prev_sequence_name and
2023-05-03 14:40:07 +02:00
sequence_name and sequence_name != prev_sequence_name):
2023-03-14 13:38:04 +01:00
strip_number = 0
2024-04-16 14:28:02 +02:00
format_data.update(
2024-04-10 16:15:57 +02:00
sequence_strip=sequence_name,
2023-04-14 18:55:00 +02:00
episode=episode_name,
2024-04-10 16:15:57 +02:00
shot=str(strip_number*increment + start_number).zfill(padding))
name = template.format(**format_data)
2023-03-14 13:38:04 +01:00
existing_strip = scn.sequence_editor.sequences_all.get(name)
if existing_strip:
existing_strip.name = f"{name}_tmp"
print(f'Renaming {strip.name} -> {name}')
strip.name = name
2023-04-14 18:55:00 +02:00
prev_sequence_name = sequence_name
2023-03-14 13:38:04 +01:00
strip_number += 1
def set_channels():
scn = bpy.context.scene
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-16 18:32:17 +01:00
items = settings.rna_type.bl_rna.properties['channel'].enum_items
2023-03-14 13:38:04 +01:00
for i, c in enumerate(items.keys(), start=1):
scn.sequence_editor.channels[i].name = c.title()
2023-04-20 00:12:39 +02:00
def get_strip_render_path(strip, template):
2023-04-20 18:16:34 +02:00
scn = bpy.context.scene
2023-05-08 18:25:04 +02:00
settings = get_scene_settings()
project = settings.active_project
2023-04-21 21:44:05 +02:00
suffix = Path(scn.render.frame_path()).suffix
2023-05-08 18:25:04 +02:00
strip_data = parse(strip.name, template=project.shot_template)
2023-05-19 11:51:05 +02:00
#print(strip_data)
2023-05-08 18:25:04 +02:00
index = int(strip_data['index'])
sequence = get_strip_sequence_name(strip)
render_path = template.format(episode=project.episode_name, sequence=sequence,
strip=strip.name, index=index, ext=suffix[1:])
2023-04-21 21:44:05 +02:00
return Path(os.path.abspath(bpy.path.abspath(render_path)))
2023-04-20 00:12:39 +02:00
2023-05-08 18:25:04 +02:00
2023-04-22 21:12:21 +02:00
'''
# def render_strip_background(blender_path, filepath, start, end, output):
# cmd = [
# blender_path, '-b', '--factory-startup', str(tmp_path), '-a',
# '-s', str(start), '-e', str(end),
# '-o', str(output)
# ]
# print(cmd)
# process = subprocess.call(cmd)
2023-04-20 00:12:39 +02:00
def render_strips(strips, template):
2023-04-22 21:12:21 +02:00
from functools import partial
2023-04-20 00:12:39 +02:00
scn = bpy.context.scene
2023-04-22 21:12:21 +02:00
# scene_start = scn.frame_start
# scene_end = scn.frame_end
# render_path = scn.render.filepath
tmp_name = Path(bpy.data.filepath).name if bpy.data.filepath else 'Untitled.blend'
tmp_path = Path(bpy.app.tempdir, tmp_name)
bpy.ops.wm.save_as_mainfile(filepath=str(tmp_path), copy=True)
script_code = dedent(f"""
import bpy
for
2023-04-20 00:12:39 +02:00
2023-04-22 21:12:21 +02:00
""")
script_path = Path(bpy.app.tempdir) / 'bundle_library.py'
script_path.write_text(script_code)
cmd = [bpy.app.binary_path, tmp_path, '--python', ]
# nb_threads = min(multiprocessing.cpu_count()-2, 8)
# print(nb_threads)
# pool = multiprocessing.Pool(nb_threads)
#arguments = [(bpy.app.binary_path, str(tmp_path), s.frame_final_start, s.frame_final_end-1, str(get_strip_render_path(s, template))) for s in strips]
#print(arguments)
#pool.starmap(render_strip_background, arguments)
2023-04-22 15:42:38 +02:00
# def render_strip_background(index):
# cmd = [bpy.app.binary_path, etc]
2023-04-22 21:12:21 +02:00
# process = subprocess.Popen(cmd)
# pool = ThreadPool(nb_threads)
# for strip in strips:
# start = strip.frame_final_start
# end = strip.frame_final_end-1
# output = str(get_strip_render_path(strip, template))
# cmd = [
# bpy.app.binary_path, '-b', str(tmp_path),
# '-s', str(start), '-e', str(end), '-a',
# '-o', str(output)
# ]
2023-04-22 15:42:38 +02:00
2023-03-14 13:38:04 +01:00
for strip in strips:
2023-04-20 00:12:39 +02:00
#print(render_template, strip.name, path)
scn.frame_start = strip.frame_final_start
scn.frame_end = strip.frame_final_end - 1
## render animatic
#strip_render_path = render_template.format(strip_name=strip.name, ext=Path(scn.render.frame_path()).suffix)
2023-04-21 21:44:05 +02:00
scn.render.filepath = str(get_strip_render_path(strip, template))
2023-04-20 00:12:39 +02:00
#print(scn.render.filepath)
print(f'Render Strip to {scn.render.filepath}')
#bpy.ops.render.render(animation=True)
bpy.ops.render.opengl(animation=True, sequencer=True)
2023-04-22 21:12:21 +02:00
scn.frame_start = scene_start
scn.frame_end = scene_end
scn.render.filepath = render_path
'''
2023-07-20 17:27:22 +02:00
def render_sound(strip, output):
output = os.path.abspath(bpy.path.abspath(output))
#ext = os.path.splitext(output)[1:]
scn = bpy.context.scene
scene_start = scn.frame_start
scene_end = scn.frame_end
scene_current = scn.frame_current
scn.frame_start = strip.frame_final_start
scn.frame_end = strip.frame_final_end - 1
print(f'Render Strip to {scn.render.filepath}')
Path(output).parent.mkdir(exist_ok=True, parents=True)
bpy.ops.sound.mixdown(filepath=output)#, container=ext.upper()
scn.frame_start = scene_start
scn.frame_end = scene_end
scn.frame_current = scene_current
2024-04-16 14:28:02 +02:00
def render_scene(output, attributes=None):
output = os.path.abspath(bpy.path.abspath(str(output)))
2023-10-09 18:30:06 +02:00
scn = bpy.context.scene
render_path = scn.render.filepath
2024-04-16 14:28:02 +02:00
if attributes is None:
attributes = []
attributes += [
(scn.render, "filepath", output),
]
2023-10-09 18:30:06 +02:00
Path(output).parent.mkdir(exist_ok=True, parents=True)
2024-05-01 22:13:38 +02:00
with attr_set(attributes):
print(f'Render Strip to {scn.render.filepath}')
bpy.ops.render.opengl(animation=True, sequencer=True)
2023-10-09 18:30:06 +02:00
2024-04-16 14:28:02 +02:00
def render_strip(strip, output, attributes=None):
output = os.path.abspath(bpy.path.abspath(str(output)))
2023-05-19 11:51:05 +02:00
2023-04-22 21:12:21 +02:00
scn = bpy.context.scene
2024-04-16 14:28:02 +02:00
if attributes is None:
attributes = []
attributes += [
2024-03-14 17:44:05 +01:00
(scn, "frame_start", strip.frame_final_start),
(scn, "frame_end", strip.frame_final_end - 1),
(scn, "frame_current"),
(scn.render, "filepath", output),
]
2023-04-22 21:12:21 +02:00
2023-07-20 17:27:22 +02:00
Path(output).parent.mkdir(exist_ok=True, parents=True)
2024-03-14 17:44:05 +01:00
with attr_set(attributes):
print(f'Render Strip to {scn.render.filepath}')
bpy.ops.render.opengl(animation=True, sequencer=True)
2023-04-22 21:12:21 +02:00
2023-07-11 16:27:43 +02:00
def import_edit(filepath, adapter="cmx_3600", channel='Shots', match_by='name'):
2023-05-03 14:40:07 +02:00
opentimelineio = install_module('opentimelineio')
2023-05-03 09:15:22 +02:00
2023-05-19 11:51:05 +02:00
from opentimelineio.schema import Clip
2023-03-16 18:32:17 +01:00
2023-03-14 13:38:04 +01:00
scn = bpy.context.scene
2023-04-27 14:30:56 +02:00
sequencer = scn.sequence_editor.sequences
2023-07-11 16:27:43 +02:00
strips = get_strips(channel='Shots')
shot_channel = get_channel_index('Shots')
2023-03-14 13:38:04 +01:00
2023-07-11 16:56:35 +02:00
# Move all strips on an empty channel
empty_channel = max((s.channel for s in sequencer), default=0) + 1
for s in strips:
s.channel = empty_channel
2023-03-16 18:32:17 +01:00
edl = Path(filepath)
2023-03-14 13:38:04 +01:00
try:
2023-05-03 14:40:07 +02:00
timeline = opentimelineio.adapters.read_from_file(
2023-03-14 13:38:04 +01:00
str(edl), adapter, rate=scn.render.fps, ignore_timecode_mismatch=True)
except:
2023-03-16 18:32:17 +01:00
print("[>.] read_from_file Failed. Using read_from_string method.")
2023-03-14 13:38:04 +01:00
data = edl.read_text(encoding='latin-1')
2023-05-04 11:32:36 +02:00
timeline = opentimelineio.adapters.read_from_string(
2023-03-14 13:38:04 +01:00
data, adapter, rate=scn.render.fps, ignore_timecode_mismatch=True)
scn.frame_start = (
0 if timeline.global_start_time is None else timeline.global_start_time
)
2023-07-11 16:27:43 +02:00
# Get all video clips only
clips = []
2023-03-16 18:32:17 +01:00
for track in timeline.tracks:
for child in track.each_child(shallow_search=True):
# FIXME Exclude Gaps for now. Gaps are Transitions, Blank Spaces...
if not isinstance(child, Clip):
continue
# FIXME Exclude Audio for now
2023-04-04 12:28:31 +02:00
if any(child.name.lower().endswith(ext) for ext in SOUND_SUFFIXES):
2023-03-16 18:32:17 +01:00
continue
2023-03-14 13:38:04 +01:00
2023-07-11 16:27:43 +02:00
clips.append(child)
clips.sort(key=lambda x: x.range_in_parent().start_time)
for i, clip in enumerate(clips):
2023-03-16 18:32:17 +01:00
2023-07-11 16:27:43 +02:00
frame_start = opentimelineio.opentime.to_frames(
clip.range_in_parent().start_time)
frame_end = frame_start + opentimelineio.opentime.to_frames(
clip.range_in_parent().duration)
strip = None
if match_by.lower() == 'name':
strip = next((s for s in strips if s.vsetb_strip_settings.source_name == clip.name), None)
elif match_by.lower() == 'index':
try:
strip = strips[i]
except IndexError:
print(f'No strip found for {clip.name}')
if strip:
if frame_start != strip.frame_final_start or frame_end !=strip.frame_final_end:
print(f'The strip {strip.name} is updated with new range')
#self.report({'INFO'}, f'The strip {strip.name} is updated with new range')
2023-07-11 16:56:35 +02:00
strip.channel = shot_channel
2023-07-11 16:27:43 +02:00
strip.frame_final_start = frame_start
strip.frame_final_end = frame_end
else:
print('Create a new strip')
strip = sequencer.new_effect(
name=clip.name,
type='COLOR',
channel=shot_channel,
frame_start=frame_start,
frame_end=frame_end,
)
strip.blend_alpha = 0.0
strip.select = False
strip.vsetb_strip_settings.source_name = clip.name
#except Exception as e:
# print('e: ', e)
# continue
2023-03-14 13:38:04 +01:00
2023-03-16 18:32:17 +01:00
scn.frame_end = frame_end-1
2023-03-14 13:38:04 +01:00
return timeline
2024-04-16 14:28:02 +02:00
def import_movie(filepath, frame_start=None, frame_end=None):
2023-03-16 18:32:17 +01:00
scn = bpy.context.scene
2024-04-16 14:28:02 +02:00
if frame_start is None:
frame_start = scn.frame_start
2023-03-16 18:32:17 +01:00
res_x = scn.render.resolution_x
2024-04-16 14:28:02 +02:00
res_y = scn.render.resolution_y
2023-03-16 18:32:17 +01:00
2024-04-10 16:15:57 +02:00
if bpy.data.is_saved:
relpath = Path(bpy.path.relpath(str(filepath)))
if len(relpath.as_posix()) < len(filepath.as_posix()):
filepath = relpath
2023-03-16 18:32:17 +01:00
strip = scn.sequence_editor.sequences.new_movie(
2024-04-10 16:15:57 +02:00
name=Path(filepath).stem,
2023-03-16 18:32:17 +01:00
filepath=str(filepath),
2023-04-14 18:55:00 +02:00
channel=get_channel_index('Movie'),
2024-04-16 14:28:02 +02:00
frame_start=frame_start
2023-03-16 18:32:17 +01:00
)
elem = strip.strip_elem_from_frame(scn.frame_current)
src_width, src_height = elem.orig_width, elem.orig_height
if src_width != res_x:
strip.transform.scale_x = (res_x / src_width)
if src_height != res_y:
strip.transform.scale_y = (res_y / src_height)
2024-04-16 14:28:02 +02:00
if frame_end is not None:
strip.frame_final_end = frame_end
2023-03-16 18:32:17 +01:00
return strip
2024-04-10 16:15:57 +02:00
def scale_clip_to_fit(strip):
scn = bpy.context.scene
res = scn.render.resolution_x, scn.render.resolution_y
strip_res = strip.elements[0].orig_width, strip.elements[0].orig_height
#print(strip.name, strip_res, width, height)
strip.transform.scale_x = res[0] / strip_res[0]
strip.transform.scale_y = strip.transform.scale_x
def import_sound(filepath, frame_start=None, frame_end=None):
2023-03-16 18:32:17 +01:00
scn = bpy.context.scene
2024-04-10 16:15:57 +02:00
if frame_start is None:
frame_start = scn.frame_start
if bpy.data.is_saved:
relpath = Path(bpy.path.relpath(str(filepath)))
if len(relpath.as_posix()) < len(filepath.as_posix()):
filepath = relpath
2023-03-16 18:32:17 +01:00
strip = scn.sequence_editor.sequences.new_sound(
2024-04-10 16:15:57 +02:00
name=f'{filepath.stem} Audio',
2023-03-16 18:32:17 +01:00
filepath=str(filepath),
2023-04-14 18:55:00 +02:00
channel=get_channel_index('Audio'),
2024-04-10 16:15:57 +02:00
frame_start=frame_start
2023-03-14 13:38:04 +01:00
)
2024-04-10 16:15:57 +02:00
if frame_end is not None:
strip.frame_final_end = frame_end
2023-03-16 18:32:17 +01:00
2024-04-10 16:15:57 +02:00
#strip.show_waveform = True if strip.frame_final_duration < 10000 else False
2023-04-22 21:12:21 +02:00
2023-03-14 13:38:04 +01:00
return strip
2023-03-16 18:32:17 +01:00
2024-04-10 16:15:57 +02:00
def get_empty_channel():
return max(s.channel for s in bpy.context.scene.sequence_editor.sequences) + 1
2023-03-16 18:32:17 +01:00
def clean_sequencer(edit=False, movie=False, sound=False):
scn = bpy.context.scene
sequences = []
if edit:
sequences.extend(get_strips('Shots'))
if movie:
sequences.extend(get_strips('Movie'))
if sound:
sequences.extend(get_strips('Audio'))
for sequence in sequences:
2023-03-17 20:03:38 +01:00
scn.sequence_editor.sequences.remove(sequence)
@persistent
2023-04-14 18:55:00 +02:00
def set_active_strip(scene):
2023-05-03 14:40:07 +02:00
scn = bpy.context.scene
if not scn.sequence_editor:
return
2023-03-17 20:03:38 +01:00
2023-05-03 14:40:07 +02:00
if not get_scene_settings().auto_select_strip:
2023-03-17 20:03:38 +01:00
return
2023-05-03 14:40:07 +02:00
2023-04-14 18:55:00 +02:00
#scene.sequence_editor.active_strip = None
2023-03-17 20:03:38 +01:00
2023-04-14 18:55:00 +02:00
shot_strip = get_strip_at('Shots')
if shot_strip:
2023-05-03 14:40:07 +02:00
bpy.ops.sequencer.select_all(action="DESELECT")
2023-04-14 18:55:00 +02:00
shot_strip.select = True
scene.sequence_editor.active_strip = shot_strip
2023-03-17 20:03:38 +01:00
2023-04-14 18:55:00 +02:00
@persistent
def update_text_strips(scene):
2024-04-10 16:15:57 +02:00
if not scene.sequence_editor or not scene.sequence_editor.sequences_all:
return
2023-05-05 16:54:00 +02:00
2023-05-11 11:37:35 +02:00
#print("update_text_strips")
2023-06-21 13:46:28 +02:00
settings = get_scene_settings()
project = settings.active_project
episode = settings.active_episode
2023-05-05 16:54:00 +02:00
2024-04-10 16:15:57 +02:00
class MissingKey(dict):
def __missing__(self, key):
return '{' + key + '}'
2023-05-03 14:40:07 +02:00
scn = bpy.context.scene
if not scn.sequence_editor:
return
2023-04-14 18:55:00 +02:00
format_data = {
'scene': scene,
2024-04-10 16:15:57 +02:00
'project': '...',
'episode': '...',
'sequence': '...',
'sequence_strip': '...',
'shot': '...',
'shot_strip': '...',
2023-06-21 13:46:28 +02:00
'shot_frame': 0,
'shot_duration': 0,
'shot_start': 0,
'shot_end': 0,
2024-04-10 16:15:57 +02:00
'timecode': ''
2023-04-14 18:55:00 +02:00
}
shot_strip = get_strip_at('Shots', frame=scene.frame_current)
if shot_strip:
format_data.update({
2024-04-10 16:15:57 +02:00
'project': project.name,
'episode': episode.name if episode else '',
2023-06-21 13:46:28 +02:00
'shot_duration': shot_strip.frame_final_duration,
'shot_frame': scene.frame_current - shot_strip.frame_final_start + 1,
'shot_start': shot_strip.frame_final_start,
'shot_end': shot_strip.frame_final_end,
2024-02-13 16:01:31 +01:00
'timecode' : frame_to_timecode(scene.frame_current, scene.render.fps)
2023-04-14 18:55:00 +02:00
})
2024-04-10 16:15:57 +02:00
format_data.update(shot_strip.vsetb_strip_settings.format_data)
2023-04-14 18:55:00 +02:00
for strip in scene.sequence_editor.sequences_all:
if not strip.type == 'TEXT':
continue
2023-03-17 20:03:38 +01:00
2023-04-14 18:55:00 +02:00
if not is_strip_at(strip):
2023-03-17 20:03:38 +01:00
continue
2023-04-14 18:55:00 +02:00
if '{' in strip.text:
strip['text_pattern'] = strip.text
if 'text_pattern' in strip.keys():
strip.text = strip['text_pattern'].format_map(MissingKey(**format_data))
def create_shot_strip(name, start, end):
shot_strip = bpy.context.scene.sequence_editor.sequences.new_effect(
name,
'COLOR',
get_channel_index('Shots'),
frame_start=start,
frame_end=end
)
shot_strip.blend_alpha = 0
shot_strip.color = (0.5, 0.5, 0.5)
return shot_strip