# SPDX-License-Identifier: GPL-2.0-or-later import re from pathlib import Path import os import bpy from bpy.app.handlers import persistent from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings from vse_toolbox.file_utils import install_module from vse_toolbox.constants import SOUND_SUFFIXES #import multiprocessing #from multiprocessing.pool import ThreadPool import subprocess 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) def get_strips(channel=0, selected_only=False): scn = bpy.context.scene if isinstance(channel, str): channel = get_channel_index(channel) strips = [s for s in scn.sequence_editor.sequences_all if s.channel==channel] if selected_only: strips = [s for s in strips if s.select] return sorted(strips, key=lambda x : x.frame_final_start) 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): 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 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' def rename_strips( strips, template, increment=10, start_number=0, by_sequence=False): scn = bpy.context.scene settings = get_scene_settings() project = settings.active_project episode_name = '' if settings.active_episode: episode_name = settings.active_episode.name prev_sequence_name = None strip_number = 0 for strip in strips: sequence_name = get_strip_sequence_name(strip) if (by_sequence and prev_sequence_name and sequence_name and sequence != prev_sequence_name): strip_number = 0 name = template.format( sequence=sequence_name, episode=episode_name, index=strip_number*increment+start_number ) 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 prev_sequence_name = sequence_name strip_number += 1 def set_channels(): scn = bpy.context.scene settings = get_scene_settings() items = settings.rna_type.bl_rna.properties['channel'].enum_items for i, c in enumerate(items.keys(), start=1): scn.sequence_editor.channels[i].name = c.title() def get_strip_render_path(strip, template): scn = bpy.context.scene suffix = Path(scn.render.frame_path()).suffix render_path = template.format(strip_name=strip.name, ext=suffix[1:]) return Path(os.path.abspath(bpy.path.abspath(render_path))) ''' # 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) def render_strips(strips, template): from functools import partial scn = bpy.context.scene # 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 """) 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) # def render_strip_background(index): # cmd = [bpy.app.binary_path, etc] # 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) # ] for strip in strips: #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) scn.render.filepath = str(get_strip_render_path(strip, template)) #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) scn.frame_start = scene_start scn.frame_end = scene_end scn.render.filepath = render_path ''' def render_strips(strips, template): scn = bpy.context.scene scene_start = scn.frame_start scene_end = scn.frame_end render_path = scn.render.filepath for strip in strips: scn.frame_start = strip.frame_final_start scn.frame_end = strip.frame_final_end - 1 scn.render.filepath = str(get_strip_render_path(strip, template)) print(f'Render Strip to {scn.render.filepath}') bpy.ops.render.opengl(animation=True, sequencer=True) scn.frame_start = scene_start scn.frame_end = scene_end scn.render.filepath = render_path def import_edit(filepath, adapter="cmx_3600", channel='Shots'): otio = install_module('opentimelineio') from otio.schema import ( Clip, ExternalReference, Gap, ImageSequenceReference, Stack, Timeline, Track, ) scn = bpy.context.scene sequencer = scn.sequence_editor.sequences if clear: for strip in get_strips(channel='Shots'): sequencer.remove(strip) edl = Path(filepath) try: timeline = otio.adapters.read_from_file( str(edl), adapter, rate=scn.render.fps, ignore_timecode_mismatch=True) except: print("[>.] read_from_file Failed. Using read_from_string method.") data = edl.read_text(encoding='latin-1') timeline = otio.adapters.read_from_string( 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 ) 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 if any(child.name.lower().endswith(ext) for ext in SOUND_SUFFIXES): channel = get_channel_index('Audio') continue channel = get_channel_index('Shots') frame_start = otio.opentime.to_frames( child.range_in_parent().start_time) frame_end = frame_start + otio.opentime.to_frames( child.range_in_parent().duration) try: strip = next((s for s in sequencer.sequences if s.vsetb_strip_settings.source_name == child.name)) if strip: if frame_start != strip.frame_final_start or frame_end !=strip.frame_final_end: self.report({'INFO'}, f'The strip {strip.name} is updated with new range') strip.frame_final_start = frame_start strip.frame_final_end = frame_end else: strip = sequencer.new_effect( name=child.name, type='COLOR', channel=channel, frame_start=frame_start, frame_end=frame_end, ) strip.blend_alpha = 0.0 strip.select = False strip.vsetb_strip_settings.source_name = child.name except Exception as e: print('e: ', e) continue scn.frame_end = frame_end-1 return timeline def import_movie(filepath): scn = bpy.context.scene res_x = scn.render.resolution_x res_y = scn.render.resolution_y strip = scn.sequence_editor.sequences.new_movie( name=filepath.stem, filepath=str(filepath), channel=get_channel_index('Movie'), frame_start=scn.frame_start ) 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) if bpy.data.is_saved: strip.filepath = bpy.path.relpath(str(filepath)) return strip def import_sound(filepath): scn = bpy.context.scene strip = scn.sequence_editor.sequences.new_sound( name=filepath.stem, filepath=str(filepath), channel=get_channel_index('Audio'), frame_start=scn.frame_start ) if bpy.data.is_saved: strip.sound.filepath = bpy.path.relpath(str(filepath)) strip.show_waveform = True if strip.frame_final_duration < 10000 else False return strip 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: scn.sequence_editor.sequences.remove(sequence) @persistent def set_active_strip(scene): #scn = bpy.context.scene settings = get_scene_settings() if not settings.auto_select_strip: return bpy.ops.sequencer.select_all(action="DESELECT") #scene.sequence_editor.active_strip = None shot_strip = get_strip_at('Shots') if shot_strip: shot_strip.select = True scene.sequence_editor.active_strip = shot_strip @persistent def update_text_strips(scene): #scn = bpy.context.scene format_data = { 'scene': scene, 'active_shot_name': 'None', 'active_shot_frame': 0, 'active_shot_duration': 0, 'active_shot_start': 0, 'active_shot_end': 0 } shot_strip = get_strip_at('Shots', frame=scene.frame_current) if shot_strip: format_data.update({ 'active_shot_name': shot_strip.name, 'active_shot_duration': shot_strip.frame_final_duration, 'active_shot_frame': scene.frame_current - shot_strip.frame_final_start + 1, 'active_shot_start': shot_strip.frame_final_start, 'active_shot_end': shot_strip.frame_final_end, }) for strip in scene.sequence_editor.sequences_all: if not strip.type == 'TEXT': continue if not is_strip_at(strip): continue if '{' in strip.text: strip['text_pattern'] = strip.text if 'text_pattern' in strip.keys(): strip.text = strip['text_pattern'].format(**format_data)