Blender 5.0 compat + STB XML import + sequence/stamps improvements

- Fix Blender 5.0 API: active_sequence_strip → active_strip, bracket
  property access on AddonPreferences, _RestrictContext during register()
- Fix new_effect() frame_end → length for Blender 5.0
- Fix OTIO adapter kwargs (rate/ignore_timecode_mismatch only for cmx_3600)
- Fix OTIO global_start_time RationalTime → int conversion
- Add Import STB XML operator with movie strip import and XML patching
  for Storyboard Pro transitions missing <alignment>
- Add Create Sequence Strip operator (select shots → create sequence)
- Improve Set Stamps: channel at top, no conflict with existing strips,
  use raw strip names in templates
- TVSHOW episode fixes: sequence loading, episode creation
- Kitsu: new_asset, new_episode, admin_connect fix, gazu version pin
- Fix escape warnings in file_utils regex patterns
- Guard handler registration against duplicates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 16:09:03 +01:00
co-authored by Claude Opus 4.6
parent c46d78f2ae
commit d2d64dc8a8
14 changed files with 548 additions and 122 deletions
+30 -22
View File
@@ -41,17 +41,17 @@ def frame_to_timecode(frame, fps):
return timecode
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,
x=0.5, y=0.5, anchor_x='CENTER', anchor_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)
strips = bpy.context.scene.sequence_editor.strips
strip = strips.new_effect(name, 'TEXT', channel, frame_start=start, length=end - start)
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.anchor_y = anchor_y
strip.anchor_x = anchor_x
strip.channel = channel
strip.font_size = font_size
@@ -74,7 +74,7 @@ def get_strips(channel=0, selected_only=False):
if isinstance(channel, str):
channel = get_channel_index(channel)
strips = [s for s in scn.sequence_editor.sequences if s.channel==channel]
strips = [s for s in scn.sequence_editor.strips if s.channel==channel]
if selected_only:
strips = [s for s in strips if s.select]
@@ -141,7 +141,7 @@ def rename_strips(strips, template, increment=10, start_number=0, padding=3, by_
name = template.format(**format_data)
existing_strip = scn.sequence_editor.sequences_all.get(name)
existing_strip = scn.sequence_editor.strips_all.get(name)
if existing_strip:
existing_strip.name = f"{name}_tmp"
@@ -331,7 +331,7 @@ def import_edit(filepath, adapter="cmx_3600", channel='Shots', match_by='name'):
from opentimelineio.schema import Clip
scn = bpy.context.scene
sequencer = scn.sequence_editor.sequences
sequencer = scn.sequence_editor.strips
strips = get_strips(channel='Shots')
shot_channel = get_channel_index('Shots')
@@ -341,18 +341,26 @@ def import_edit(filepath, adapter="cmx_3600", channel='Shots', match_by='name'):
s.channel = empty_channel
edl = Path(filepath)
# Extra kwargs only supported by cmx_3600 adapter
extra_kwargs = {}
if adapter == 'cmx_3600':
extra_kwargs = dict(rate=scn.render.fps, ignore_timecode_mismatch=True)
try:
timeline = opentimelineio.adapters.read_from_file(
str(edl), adapter, rate=scn.render.fps, ignore_timecode_mismatch=True)
str(edl), adapter, **extra_kwargs)
except:
print("[>.] read_from_file Failed. Using read_from_string method.")
data = edl.read_text(encoding='latin-1')
timeline = opentimelineio.adapters.read_from_string(
data, adapter, rate=scn.render.fps, ignore_timecode_mismatch=True)
data, adapter, **extra_kwargs)
scn.frame_start = (
0 if timeline.global_start_time is None else timeline.global_start_time
)
global_start = timeline.global_start_time
if global_start is None:
scn.frame_start = 0
else:
scn.frame_start = int(opentimelineio.opentime.to_frames(global_start))
# Get all video clips only
clips = []
@@ -411,7 +419,7 @@ def import_edit(filepath, adapter="cmx_3600", channel='Shots', match_by='name'):
type='COLOR',
channel=shot_channel,
frame_start=frame_start,
frame_end=frame_end,
length=frame_end - frame_start,
)
strip.blend_alpha = 0.0
@@ -439,7 +447,7 @@ def import_movie(filepath, frame_start=None, frame_end=None):
if len(relpath.as_posix()) < len(filepath.as_posix()):
filepath = relpath
strip = scn.sequence_editor.sequences.new_movie(
strip = scn.sequence_editor.strips.new_movie(
name=Path(filepath).stem,
filepath=str(filepath),
channel=get_channel_index('Movie'),
@@ -480,7 +488,7 @@ def import_sound(filepath, frame_start=None, frame_end=None):
if len(relpath.as_posix()) < len(filepath.as_posix()):
filepath = relpath
strip = scn.sequence_editor.sequences.new_sound(
strip = scn.sequence_editor.strips.new_sound(
name=f'{filepath.stem} Audio',
filepath=str(filepath),
channel=get_channel_index('Audio'),
@@ -495,7 +503,7 @@ def import_sound(filepath, frame_start=None, frame_end=None):
return strip
def get_empty_channel():
return max(s.channel for s in bpy.context.scene.sequence_editor.sequences) + 1
return max(s.channel for s in bpy.context.scene.sequence_editor.strips) + 1
def clean_sequencer(edit=False, movie=False, sound=False):
scn = bpy.context.scene
@@ -509,7 +517,7 @@ def clean_sequencer(edit=False, movie=False, sound=False):
sequences.extend(get_strips('Audio'))
for sequence in sequences:
scn.sequence_editor.sequences.remove(sequence)
scn.sequence_editor.strips.remove(sequence)
@persistent
def set_active_strip(scene):
@@ -531,7 +539,7 @@ def set_active_strip(scene):
@persistent
def update_text_strips(scene):
if not scene.sequence_editor or not scene.sequence_editor.sequences_all:
if not scene.sequence_editor or not scene.sequence_editor.strips_all:
return
#print("update_text_strips")
@@ -577,7 +585,7 @@ def update_text_strips(scene):
})
format_data.update(shot_strip.vsetb_strip_settings.format_data)
for strip in scene.sequence_editor.sequences_all:
for strip in scene.sequence_editor.strips_all:
if not strip.type == 'TEXT':
continue
@@ -593,12 +601,12 @@ def update_text_strips(scene):
def create_shot_strip(name, start, end, channel='Shots'):
shot_strip = bpy.context.scene.sequence_editor.sequences.new_effect(
shot_strip = bpy.context.scene.sequence_editor.strips.new_effect(
name,
'COLOR',
get_channel_index(channel),
frame_start=start,
frame_end=end
length=end - start
)
shot_strip.blend_alpha = 0
shot_strip.color = (0.5, 0.5, 0.5)