# SPDX-License-Identifier: GPL-2.0-or-later import bpy import importlib import json import re import time from pprint import pprint import csv from datetime import datetime import os import vse_toolbox from bpy_extras.io_utils import ImportHelper from bpy.props import ( CollectionProperty, BoolProperty, EnumProperty, IntProperty, StringProperty, ) from bpy.types import ( Operator, ) from pathlib import Path from vse_toolbox.constants import ( ASSET_PREVIEWS, CASTING_BUFFER, CONFIG_DIR, EDITS, EDIT_SUFFIXES, MOVIES, MOVIE_SUFFIXES, PREVIEWS_DIR, SOUNDS, SOUND_SUFFIXES, TASK_ITEMS ) from vse_toolbox.sequencer_utils import ( clean_sequencer, get_strips, set_active_strip, import_edit, import_movie, import_sound, rename_strips, render_strips, set_channels, get_channel_index, new_text_strip, get_strip_render_path, get_strip_sequence_name ) from vse_toolbox.bl_utils import get_addon_prefs, get_scene_settings, get_strip_settings from vse_toolbox.file_utils import install_module, norm_name, norm_str, open_file class VSETB_OT_tracker_connect(Operator): bl_idname = "vse_toolbox.tracker_connect" bl_label = "Connect to Tracker" bl_description = "Connect to Tracker" @classmethod def poll(cls, context): prefs = get_addon_prefs() if prefs.tracker: return True def execute(self, context): prefs = get_addon_prefs() settings = get_scene_settings() try: prefs.tracker.connect() self.report({'INFO'}, f'Sucessfully login to {settings.tracker_name.title()}') return {"FINISHED"} except Exception as e: print('e: ', e) self.report({'ERROR'}, f'Cannot connect to tracker.') return {"CANCELLED"} class VSETB_OT_export_spreadsheet(Operator): bl_idname = "vse_toolbox.export_spreadsheet" bl_label = "Export Spreadsheet" bl_description = "Export Shot data in a table as a csv or an xlsl" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): settings = get_scene_settings() return settings.active_project def invoke(self, context, event): settings = get_scene_settings() project = settings.active_project return context.window_manager.invoke_props_dialog(self) def draw(self, context): scn = context.scene settings = get_scene_settings() project = settings.active_project options = project.spreadsheet_options layout = self.layout row = layout.row() row.template_list("VSETB_UL_spreadsheet", "spreadsheet", project, "spreadsheet", project, "spreadsheet_index", rows=8) col_tool = row.column(align=True) col_tool.operator('vse_toolbox.spreadsheet_move', icon='TRIA_UP', text="").direction = 'UP' col_tool.operator('vse_toolbox.spreadsheet_move', icon='TRIA_DOWN', text="").direction = 'DOWN' col = layout.column() col.use_property_split = True row = col.row(align=True, heading='Custom Name') #row.use_property_split = True row.prop(options, 'use_custom_name', text='') sub = row.row(align=True) sub.enabled = options.use_custom_name sub.prop(options, 'custom_name', text='') col.separator() row = col.row(align=False) row.prop(options, "format", expand=True, text='Format') row.prop(options, 'show_settings', text='', icon='PREFERENCES') if options.show_settings: col.prop(options, "separator", expand=True, text='Separator') if options.format == 'CSV': col.prop(options, "delimiter", expand=True, text='Delimiter') col.separator() col.prop(options, 'open_folder', text='Open Folder') col.prop(options, 'export_path', text='Export Path') def execute(self, context): #self.report({'ERROR'}, f'Export not implemented yet.') prefs = get_addon_prefs() settings = get_scene_settings() project = settings.active_project options = project.spreadsheet_options episode = settings.active_episode cells = [cell for cell in project.spreadsheet if cell.enabled] rows = [] # Header rows.append([cell.export_name for cell in cells]) separator = options.separator.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r') delimiter = options.delimiter.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r') for strip in get_strips('Shots'): row = [] for cell in cells: if cell.type == "METADATA": row += [getattr(strip.vsetb_strip_settings.metadata, cell.field_name)] elif cell.type == "ASSET_TYPE": asset_castings = [] for asset_casting in strip.vsetb_strip_settings.casting: asset = asset_casting.asset if not asset.asset_type == cell.name: continue if options.use_custom_name: if asset.get('metadata', {}).get(options.custom_name): asset_castings.append(asset['metadata'][options.custom_name]) else: self.report({'ERROR'}, f'The asset {asset.tracker_name} has no data {options.custom_name}') else: asset_castings.append(asset.tracker_name) row += [separator.join(asset_castings)] elif cell.field_name == 'EPISODE': row += [settings.active_episode.name] elif cell.field_name == 'SEQUENCE': row += [get_strip_sequence_name(strip)] elif cell.field_name == 'SHOT': row += [strip.name] elif cell.field_name == 'DESCRIPTION': row += [strip.vsetb_strip_settings.description] elif cell.field_name == 'FRAMES': row += [strip.frame_final_duration] rows.append(row) #print(rows) export_path = Path(os.path.abspath(bpy.path.abspath(options.export_path))) export_name = export_path.name if export_path.suffix or export_name.endswith('{ext}'): export_path = export_path.parent else: # It's a directory if project.type == 'TVSHOW': export_name = '{date}_{project}_{episode}_{tracker}_shots.{ext}' else: export_name = '{date}_{project}_{tracker}_shots.{ext}' date = datetime.now().strftime('%Y_%m_%d') project_name = project.name.replace(' ', '_').lower() episode_name = episode.name.replace(' ', '_').lower() if episode else 'episode' ext = options.format.lower() export_name = export_name.format(date=date, project=project_name, episode=episode_name, tracker=settings.tracker_name.lower(), ext=ext) export_path = export_path / export_name #2023_04_11_kitsu_boris_ep01_shots export_path.parent.mkdir(parents=True, exist_ok=True) if options.format == 'CSV': print('Writing .csv file to', export_path) with open(str(export_path), 'w', newline='\n', encoding='utf-8') as f: writer = csv.writer(f, delimiter=options.delimiter) for row in rows: writer.writerow(row) elif options.format == 'XLSX': try: import openpyxl except ModuleNotFoundError(): self.report({'INFO'}, 'Installing openpyxl') openpyxl = install_module('openpyxl') from openpyxl import Workbook workbook = Workbook() worksheet = workbook.active for row in rows: worksheet.append(row) for col in worksheet.columns: letter = col[0].column_letter worksheet.column_dimensions[letter].auto_size = True # Save the file workbook.save(str(export_path)) if options.open_folder: open_file(export_path, select=True) return {"FINISHED"} def get_task_status_items(self, context): settings = get_scene_settings() project = settings.active_project status_items = [('CURRENT', 'Current', '')] if project: status_items += [(t.name, t.name, '') for t in project.task_statuses] return status_items def get_task_type_items(self, context): settings = get_scene_settings() project = settings.active_project if not project: return [('NONE', 'None', '')] return [(t.name, t.name, '') for t in project.task_types] class VSETB_OT_upload_to_tracker(Operator): bl_idname = "vse_toolbox.upload_to_tracker" bl_label = "Upload to tracker" bl_description = "Upload selected strip to tracker" bl_options = {"REGISTER", "UNDO"} task : EnumProperty(items=get_task_type_items) status : EnumProperty(items=get_task_status_items) comment : StringProperty() add_preview : BoolProperty(default=True) preview_mode : EnumProperty(items=[(m, m.title().replace('_', ' '), '') for m in ('ONLY_NEW', 'REPLACE', 'ADD')]) set_main_preview : BoolProperty(default=True) casting : BoolProperty(default=True) custom_data : BoolProperty(default=True) @classmethod def poll(cls, context): return True def invoke(self, context, event): prefs = get_addon_prefs() settings = get_scene_settings() project = settings.active_project tracker = prefs.tracker tracker.connect() #self.bl_label = f"Upload to {settings.tracker_name.title()}" return context.window_manager.invoke_props_dialog(self) def draw(self, context): scn = context.scene settings = get_scene_settings() layout = self.layout col = layout.column() col.use_property_split = True col.prop(self, 'task', text='Task') col.prop(self, 'status', text='Status') col.prop(self, 'comment', text='Comment') row = col.row(heading='Add Preview') row.prop(self, 'add_preview', text='') row.prop(self, 'preview_mode', text='') col.separator() col.prop(self, 'casting', text='Casting') col.prop(self, 'custom_data', text='Custom Data') def execute(self, context): #self.report({'ERROR'}, f'Export not implemented yet.') prefs = get_addon_prefs() settings = get_scene_settings() project = settings.active_project tracker = prefs.tracker status = self.status if status == 'CURRENT': status = None for strip in get_strips(channel='Shots', selected_only=True): sequence_name = get_strip_sequence_name(strip) shot_name = strip.name sequence = tracker.get_sequence(sequence_name) metadata = strip.vsetb_strip_settings.metadata.to_dict() #print(metadata) if not sequence: self.report({"INFO"}, f'Create sequence {sequence_name} in Kitsu') sequence = tracker.new_sequence(sequence_name) shot = tracker.get_shot(shot_name, sequence=sequence) if not shot: self.report({"INFO"}, f'Create shot {shot_name} in Kitsu') shot = tracker.new_shot(shot_name, sequence=sequence) task = tracker.get_task(self.task, entity=shot) if not task: task = tracker.new_task(shot, task_type=self.task) # print('\n', 'task comment') # print(task['last_comment']) preview = None if self.add_preview: preview = Path(get_strip_render_path(strip, project.render_template)) #print(preview) if not preview.exists(): preview = None elif task.get('last_comment') and task['last_comment']['previews']: if self.preview_mode == 'REPLACE': tracker.remove_comment(task['last_comment']) elif self.preview_mode == 'ONLY_NEW': preview = None #print(f'{preview=}') #print(f'{status=}') if status or preview: tracker.new_comment(task, comment=self.comment, status=status, preview=preview, set_main_preview=self.set_main_preview) if self.custom_data: metadata = strip.vsetb_strip_settings.metadata.to_dict() description = strip.vsetb_strip_settings.description tracker.update_data(shot, metadata, frames=strip.frame_final_duration, description=description) if self.casting: casting = [{'asset_id': a.id, 'nb_occurences': a.instance} for a in strip.vsetb_strip_settings.casting] tracker.update_casting(shot, casting) return {"FINISHED"} class VSETB_OT_auto_select_files(Operator): bl_idname = "import.auto_select_files" bl_label = "Auto Select" bl_description = "Auto Select Files" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return True def get_items(self, items=[]): if not items: return [('NONE', 'None', '', 0)] return [(e, e, '', i) for i, e in enumerate(sorted(items))] def execute(self, context): params = context.space_data.params directory = Path(params.directory.decode()) EDITS.clear() MOVIES.clear() SOUNDS.clear() edits = [] movies = [] sounds = [] for file_entry in directory.glob('*'): if file_entry.is_dir(): continue if file_entry.suffix in EDIT_SUFFIXES: edits.append(file_entry.name) elif file_entry.suffix in MOVIE_SUFFIXES: movies.append(file_entry.name) elif file_entry.suffix in SOUND_SUFFIXES: sounds.append(file_entry.name) EDITS.extend(self.get_items(items=edits)) MOVIES.extend(self.get_items(items=movies)) SOUNDS.extend(self.get_items(items=sounds)) return {'FINISHED'} class VSETB_OT_import_files(Operator): bl_idname = "vse_toolbox.import_files" bl_label = "Import" bl_description = "Import Edit" bl_options = {"REGISTER", "UNDO"} directory : StringProperty(subtype='DIR_PATH') filepath: StringProperty( name="File Path", description="Filepath used for importing the file", maxlen=1024, subtype='FILE_PATH', ) files : CollectionProperty(type=bpy.types.OperatorFileListElement) clean_sequencer : BoolProperty( name="Clean Sequencer", default=False, description="Clean all existing strips in sequencer", ) import_edit : BoolProperty(name='', default=True) edit: EnumProperty(name='', items=lambda s, c: EDITS) import_movie : BoolProperty(name='', default=False) movie: EnumProperty(name='', items=lambda s, c: MOVIES) import_sound : BoolProperty(name='', default=False) sound: EnumProperty(name='', items=lambda s, c: SOUNDS) @classmethod def poll(cls, context): return True def draw(self, context): scn = context.scene settings = get_scene_settings() layout = self.layout layout.use_property_split = True layout.use_property_decorate = False col = layout.column(align=True) col.operator('import.auto_select_files', text='Auto Select') row = self.layout.row(heading="Import Edit", align=True) row.prop(self, 'import_edit') sub = row.row(align=True) sub.active = self.import_edit sub.prop(self, 'edit') row = self.layout.row(heading="Import Movie", align=True) row.prop(self, 'import_movie') sub = row.row() sub.active = self.import_movie sub.prop(self, 'movie') row = self.layout.row(heading="Import Sound", align=True) row.prop(self, 'import_sound') sub = row.row() sub.active = self.import_sound sub.prop(self, 'sound') col = layout.column() col.separator() col.prop(self, 'clean_sequencer') def invoke(self, context, event): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} def execute(self, context): otio = install_module('opentimelineio') edit_filepath = Path(self.directory, self.edit) if not edit_filepath.exists(): self.import_edit = False movie_filepath = Path(self.directory, self.movie) if not movie_filepath.exists(): self.import_movie = False sound_filepath = Path(self.directory, self.sound) if not sound_filepath.exists(): self.import_sound = False if self.clean_sequencer: clean_sequencer( edit=self.import_edit, movie=self.import_movie, sound=self.import_sound, ) if self.import_edit: print(f'[>.] Loading Edit from: {str(edit_filepath)}') import_edit(edit_filepath, adapter="cmx_3600") if self.import_movie: print(f'[>.] Loading Movie from: {str(movie_filepath)}') import_movie(movie_filepath) if self.import_sound: print(f'[>.] Loading Audio from: {str(sound_filepath)}') import_sound(sound_filepath) elif not self.import_sound and self.import_movie: print(f'[>.] Loading Audio from Movie: {str(movie_filepath)}') import_sound(movie_filepath) context.scene.sequence_editor.sequences.update() return {"FINISHED"} class VSETB_OT_load_assets(Operator): bl_idname = "vse_toolbox.load_assets" bl_label = "Load Assets for current projects" bl_description = "Load Assets" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): settings = get_scene_settings() if settings.active_project: return True def execute(self, context): settings = get_scene_settings() prefs = get_addon_prefs() tracker = prefs.tracker tracker.connect() project = settings.active_project project.assets.clear() assets = tracker.get_assets(project['id']) if not assets: self.report({'ERROR'}, f'No Assets found for {project.name}.') for asset_data in assets: asset = project.assets.add() asset.name = asset_data['id'] asset.norm_name = norm_name(asset_data['name'], separator=' ', format=str.lower) asset.tracker_name = asset_data['name'] asset.id = asset_data['id'] asset.asset_type = asset_data['asset_type'] #for key, value in asset_data.get('data', {}).items(): asset['metadata'] = asset_data.get('data', {}) preview_id = asset_data.get('preview_file_id') if preview_id: asset.preview = preview_id preview_path = Path(PREVIEWS_DIR / project.id / preview_id).with_suffix('.png') tracker.download_preview(preview_id, preview_path) if preview_id not in ASSET_PREVIEWS: ASSET_PREVIEWS.load(preview_id, preview_path.as_posix(), 'IMAGE', True) self.report({'INFO'}, f'Assets for {project.name} successfully loaded') return {"FINISHED"} class VSETB_OT_load_projects(Operator): bl_idname = "vse_toolbox.load_projects" bl_label = "Load Projects" bl_description = "Load Projects" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return True def execute(self, context): settings = get_scene_settings() prefs = get_addon_prefs() tracker = prefs.tracker old_project_name = settings.project_name.replace(' ', '_').upper() settings.projects.clear() tracker.connect() for project_data in tracker.get_projects(): project = settings.projects.add() project.type = project_data['production_type'].upper().replace(' ', '') project.name = project_data['name'] project.id = project_data['id'] if project.type == 'TVSHOW': for episode_data in tracker.get_episodes(project_data): episode = project.episodes.add() episode.name = episode_data['name'] episode.id = episode_data['id'] for metadata_data in tracker.get_metadata_types(project_data): #pprint(metadata_data) metadata_type = project.metadata_types.add() metadata_type.name = metadata_data['name'] metadata_type.field_name = metadata_data['field_name'] #metadata_type['choices'] = metadata_data['choices'] for choice in metadata_data['choices']: choice_item = metadata_type.choices.add() choice_item.name = choice metadata_type['entity_type'] = metadata_data['entity_type'].upper() for status_data in tracker.get_task_statuses(project_data): #print(metadata_data) task_status = project.task_statuses.add() task_status.name = status_data['short_name'].upper() for task_type_data in tracker.get_shot_task_types(project_data): task_type = project.task_types.add() task_type.name = task_type_data['name'] for asset_type_data in tracker.get_asset_types(project_data): asset_type = project.asset_types.add() asset_type.name = asset_type_data['name'] project.set_spreadsheet() for project in settings.projects: #print(project.name, project.name.replace(' ', '_').upper(), old_project_name) if project.name.replace(' ', '_').upper() == old_project_name: #print('Restore Project Name') settings.project_name = project.name #else: # print('Could Not restore Project Name') #if settings.active_project: # settings.active_project.set_strip_metadata() #bpy.ops.vse_toolbox.load_assets() self.report({"INFO"}, 'Successfully Load Tracker Projects') return {'FINISHED'} class VSETB_OT_new_episode(Operator): bl_idname = "vse_toolbox.new_episode" bl_label = "New Epispde" bl_description = "Add new Episode to Project" bl_options = {"REGISTER", "UNDO"} episode_name : StringProperty(name="Episode Name", default="") @classmethod def poll(cls, context): return True def invoke(self, context, event): scn = context.scene settings = get_scene_settings() return context.window_manager.invoke_props_dialog(self) def execute(self, context): settings = get_scene_settings() prefs = get_addon_prefs() tracker = prefs.tracker episode_name = settings.episode_template.format(index=int(self.episode_name)) #print(self.episode_name) #print('episode_name: ', episode_name) episode = tracker.get_episode(episode_name) if episode: self.report({'ERROR'}, f'Episode {episode_name} already exists') return {"CANCELLED"} tracker.new_episode(episode_name) # tracker.get_episodes tracker.update_project() self.report({'INFO'}, f'Episode {episode_name} successfully created') return {'FINISHED'} class VSETB_OT_reload_addon(Operator): bl_idname = "vse_toolbox.reload_addon" bl_options = {"UNDO"} bl_label = 'Reload VSE ToolBox Addon' bl_description = 'Reload The VSE ToolBox Addon' def execute(self, context): print('Execute reload') vse_toolbox.unregister() importlib.reload(vse_toolbox) vse_toolbox.register() return {'FINISHED'} 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) channel_name : StringProperty(name="Channel Name", default="") #selected_only : BoolProperty(name="Selected Only", default=False) 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): return True def invoke(self, context, event): scn = context.scene settings = get_scene_settings() return context.window_manager.invoke_props_dialog(self) def draw(self, context): scn = context.scene settings = get_scene_settings() layout = self.layout col = layout.column() col.use_property_split = True col.prop(self, 'template') col.prop(self, 'start_number') if self.channel_name == 'Shots': col.prop(self, 'by_sequence') #col.prop(self, 'selected_only') def execute(self, context): scn = context.scene #strips = get_strips(channel=self.channel_name, selected_only=self.selected_only) strips = get_strips(channel=self.channel_name, selected_only=True) rename_strips( strips, self.template, increment=self.increment, start_number=self.start_number, by_sequence=self.by_sequence ) return {"FINISHED"} class VSETB_OT_render(Operator): bl_idname = "vse_toolbox.strips_render" bl_label = "Render Shots Strips" bl_description = "Render Shots Strips" bl_options = {"REGISTER", "UNDO"} #selected_only : BoolProperty(name="Selected Only", default=False) @classmethod def poll(cls, context): settings = get_scene_settings() return settings.active_project def invoke(self, context, event): scn = context.scene settings = get_scene_settings() return context.window_manager.invoke_props_dialog(self) def draw(self, context): scn = context.scene settings = get_scene_settings() layout = self.layout col = layout.column() col.use_property_split = True col.use_property_decorate = False #col.prop(settings, 'channel', text='Channel') #col.prop(self, 'selected_only') col.prop(settings.active_project, "render_template") def execute(self, context): scn = context.scene settings = get_scene_settings() strips = get_strips(channel='Shots', selected_only=True) start_time = time.perf_counter() render_strips(strips, settings.active_project.render_template) self.report({"INFO"}, f'Strips rendered in {time.perf_counter()-start_time} seconds') return {"FINISHED"} 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() movie = get_strips(channel='Movie') if movie: movie = movie[0] 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' scn.render.ffmpeg.gopsize = 5 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 return {"FINISHED"} def get_asset_items(self, context): settings = get_scene_settings() project = settings.active_project return [(a.id, a.label, '', i) for i, a in enumerate(project.assets)] class VSETB_OT_casting_add(Operator): bl_idname = "vse_toolbox.casting_add" bl_label = "Casting Add" bl_description = "Add Asset to Castin" bl_options = {"REGISTER", "UNDO"} bl_property = "asset_name" asset_name : EnumProperty(name='', items=get_asset_items) @classmethod def poll(cls, context): active_strip = context.scene.sequence_editor.active_strip if active_strip: return True def invoke(self, context, event): context.window_manager.invoke_search_popup(self) return {'FINISHED'} def execute(self, context): scn = context.scene active_strip = scn.sequence_editor.active_strip settings = get_scene_settings() strip_settings = get_strip_settings() project = settings.active_project if strip_settings.casting.get(self.asset_name): asset = project.assets[self.asset_name] self.report({'WARNING'}, f"Asset {asset.label} already casted.") return {"CANCELLED"} item = strip_settings.casting.add() asset = project.assets[self.asset_name] item.name = asset.name item.id = project.assets[self.asset_name].id item['_name'] = asset.label strip_settings.casting.update() return {"FINISHED"} class VSETB_OT_casting_remove(Operator): bl_idname = "vse_toolbox.casting_remove" bl_label = "Remove Item from Casting" bl_description = "Remove Item from Casting" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): active_strip = context.scene.sequence_editor.active_strip if active_strip: return True def invoke(self, context, event): scn = context.scene strip_settings = get_strip_settings() idx = strip_settings.casting_index try: item = strip_settings.casting[idx] except IndexError: pass else: item = strip_settings.casting[strip_settings.casting_index] label = item.asset.label if item.asset.label else 'Empty' info = f"Item {label} removed from casting" strip_settings.casting.remove(idx) if strip_settings.casting_index == 0: strip_settings.casting_index = 0 else: strip_settings.casting_index -= 1 self.report({'INFO'}, info) return {"FINISHED"} class VSETB_OT_casting_move(Operator): bl_idname = "vse_toolbox.casting_move" bl_label = "Move Casting items" bl_description = "Move Casting items" bl_options = {"REGISTER", "UNDO"} direction: EnumProperty( items=( ('UP', "Up", ""), ('DOWN', "Down", ""), ) ) asset_name : StringProperty() @classmethod def poll(cls, context): active_strip = context.scene.sequence_editor.active_strip if active_strip: return True def execute(self, context): scn = context.scene strip_settings = get_strip_settings() idx = strip_settings.casting_index try: item = strip_settings.casting[idx] except IndexError: pass else: if self.direction == 'DOWN' and idx < len(strip_settings.casting) - 1: item_next = strip_settings.casting[idx+1].name strip_settings.casting.move(idx, idx+1) strip_settings.casting_index += 1 elif self.direction == 'UP' and idx >= 1: item_prev = strip_settings.casting[idx-1].name strip_settings.casting.move(idx, idx-1) strip_settings.casting_index -= 1 info = f"Item {item.asset.label} moved to position {(item.asset.label, strip_settings.casting_index + 1)}" self.report({'INFO'}, info) return {"FINISHED"} class VSETB_OT_spreadsheet_move(Operator): bl_idname = "vse_toolbox.spreadsheet_move" bl_label = "Move Spreadsheet items" bl_description = "Move Spreadsheet items" bl_options = {"REGISTER", "UNDO"} direction: EnumProperty( items=( ('UP', "Up", ""), ('DOWN', "Down", ""), ) ) def execute(self, context): scn = context.scene project = get_scene_settings().active_project idx = project.spreadsheet_index try: item = project.spreadsheet[idx] except IndexError: pass else: if self.direction == 'DOWN' and idx < len(project.spreadsheet) - 1: item_next = project.spreadsheet[idx+1].name project.spreadsheet.move(idx, idx+1) project.spreadsheet_index += 1 elif self.direction == 'UP' and idx >= 1: item_prev = project.spreadsheet[idx-1].name project.spreadsheet.move(idx, idx-1) project.spreadsheet_index -= 1 return {"FINISHED"} class VSETB_OT_copy_casting(Operator): bl_idname = "vse_toolbox.copy_casting" bl_label = "Copy Casting" bl_description = "Copy Casting from active strip" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): active_strip = context.scene.sequence_editor.active_strip strip_settings = get_strip_settings() if active_strip and strip_settings.casting: return True def execute(self, context): scn = context.scene active_strip = scn.sequence_editor.active_strip strip_settings = get_strip_settings() datas = [c.to_dict() for c in strip_settings.casting] CASTING_BUFFER.write_text(json.dumps(datas), encoding='utf-8') return {"FINISHED"} class VSETB_OT_paste_casting(Operator): bl_idname = "vse_toolbox.paste_casting" bl_label = "Paste Casting" bl_description = "Paste Casting to active strip" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): active_strip = context.scene.sequence_editor.active_strip if active_strip: return True def execute(self, context): scn = context.scene strip_settings = get_strip_settings() if not CASTING_BUFFER.exists(): self.report({'ERROR'}, f'No Casting to copy.') return {"CANCELLED"} casting_datas = json.loads(CASTING_BUFFER.read_text()) for casting_item in casting_datas: for strip in context.selected_sequences: strip_settings = strip.vsetb_strip_settings if strip_settings.casting.get(casting_item['name']): continue item = strip.vsetb_strip_settings.casting.add() for k, v in casting_item.items(): setattr(item, k, v) strip_settings.casting.update() return {"FINISHED"} 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() #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') crop_x = int(scn.render.resolution_x * 0.4) crop_max_y = int(scn.render.resolution_y * 0.96) crop_min_y = int(scn.render.resolution_y * 0.01) stamp_params = dict(start=scn.frame_start, end=scn.frame_end, font_size=22, y=0.015, box_margin=0.005, select=True, box_color=(0, 0, 0, 0.5)) # Project Name project_strip_stamp = new_text_strip('project_name_stamp', channel=1, **stamp_params, text=settings.active_project.name, x=0.01, align_x='LEFT', align_y='BOTTOM') project_strip_stamp.crop.max_x = crop_x * 2 project_strip_stamp.crop.max_y = crop_max_y project_strip_stamp.crop.min_y = crop_min_y # Shot Name shot_strip_stamp = new_text_strip('shot_name_stamp', channel=2, **stamp_params, text='{active_shot_name}', align_y='BOTTOM') 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 shot_strip_stamp.crop.min_y = crop_min_y # Frame Range frame_strip_stamp = new_text_strip('frame_range_stamp', channel=3, **stamp_params, text='{active_shot_frame} / {active_shot_duration}', x=0.99, align_x='RIGHT', align_y='BOTTOM') frame_strip_stamp.crop.min_x = crop_x *2 frame_strip_stamp.crop.max_y = crop_max_y frame_strip_stamp.crop.min_y = crop_min_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"} classes = ( VSETB_OT_auto_select_files, VSETB_OT_casting_add, VSETB_OT_casting_remove, VSETB_OT_casting_move, VSETB_OT_spreadsheet_move, VSETB_OT_copy_casting, VSETB_OT_paste_casting, VSETB_OT_export_spreadsheet, VSETB_OT_import_files, VSETB_OT_load_assets, VSETB_OT_load_projects, VSETB_OT_new_episode, VSETB_OT_reload_addon, VSETB_OT_rename, VSETB_OT_render, VSETB_OT_set_sequencer, VSETB_OT_tracker_connect, VSETB_OT_set_stamps, VSETB_OT_upload_to_tracker, VSETB_OT_show_waveform ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls)