# SPDX-License-Identifier: GPL-2.0-or-later import importlib import json import re import time from pprint import pprint import csv from datetime import datetime import os from pathlib import Path import bpy from bpy.types import (Operator, Menu) from bpy_extras.io_utils import ImportHelper from bpy.props import (CollectionProperty, BoolProperty, EnumProperty, IntProperty, StringProperty) from bpy.types import PropertyGroup from bl_operators.presets import AddPresetBase import vse_toolbox 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) #bpy.types.VSETB_PT_presets.draw_panel_header(col_tool) #col_tool.operator('wm.call_menu', icon="PRESET").name = 'VSETB_MT_spreadsheet_presets' #col_tool.operator('vse_toolbox.load_spreadsheet_preset', icon='PRESET', text="") op = col_tool.operator('wm.call_panel', icon="PRESET", emboss=False, text='') op.name = 'VSETB_PT_presets' op.keep_open = False col_tool.separator() 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 episode = None if settings.active_episode: episode = settings.active_episode.id 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, episode=episode) 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, episode=episode) 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(): print(f'The preview {preview} not 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 if self.channel_name == 'Shots': col.prop(settings, 'shot_template') col.prop(settings, 'shot_start_number') col.prop(settings, 'reset_by_sequence') elif self.channel_name == 'Shots': col.prop(settings, 'sequence_template') col.prop(settings, 'sequence_start_number') 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=self.selected_only) 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"} class VSETB_OT_casting_replace(Operator): bl_idname = "vse_toolbox.casting_replace" bl_label = "Replace Asset" bl_description = "Replace Asset of selected strips" old_asset : StringProperty() new_asset : StringProperty() assets : CollectionProperty(type=PropertyGroup) def execute(self, context): prefs = get_addon_prefs() settings = get_scene_settings() new_asset = next(a for a in settings.active_project.assets if a.tracker_name == self.new_asset) for strip in get_strips('Shots', selected_only=True): strip_settings = strip.vsetb_strip_settings for asset_casting in strip_settings.casting: if asset_casting.asset.tracker_name == self.old_asset: print(f'Replace casting on {strip.name}') asset_casting.name = new_asset.name asset_casting.id = new_asset.id asset_casting['_name'] = new_asset.label strip_settings.casting.update() self.assets.clear() return {'FINISHED'} def invoke(self, context, event): settings = get_scene_settings() project = settings.active_project self.assets.clear() for asset in project.assets: item = self.assets.add() item.name = asset.tracker_name strip = context.active_sequence_strip asset_casting_index = strip.vsetb_strip_settings.casting_index active_asset = strip.vsetb_strip_settings.casting[asset_casting_index].asset self.old_asset = active_asset.tracker_name self.new_asset = '' return context.window_manager.invoke_props_dialog(self) # def draw(self, context): scn = context.scene settings = get_scene_settings() project = settings.active_project layout = self.layout col = layout.column() col.use_property_split = True col.prop_search(self, 'old_asset', self, 'assets', text='Old Asset', icon='ASSET_MANAGER') col.prop_search(self, 'new_asset', self, 'assets', text='New Asset', icon='ASSET_MANAGER') 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_MT_spreadsheet_presets(Menu): bl_label = 'Presets' preset_subdir = 'vse_toolbox' preset_operator = 'script.execute_preset' draw = Menu.draw_preset class VSETB_OT_add_spreadsheet_preset(AddPresetBase, Operator): bl_idname = "vse_toolbox.add_spreadsheet_preset" bl_label = "Add Spreadsheet Preset" bl_description = "Add Spreadsheet Preset" bl_options = {"REGISTER", "UNDO"} preset_menu = 'VSETB_MT_spreadsheet_presets' #preset_menu = 'VSETB_OT_MT_spreadsheet_presets' # Common variable used for all preset values #C.scene.vsetb_settings.active_project.spreadsheet_options preset_defines = [ 'scene = bpy.context.scene', 'settings = scene.vsetb_settings', 'project = settings.active_project' ] # Properties to store in the preset preset_values = [ 'project.spreadsheet', 'project.spreadsheet_options' ] # Directory to store the presets preset_subdir = 'vse_toolbox' 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_add_spreadsheet_preset, VSETB_MT_spreadsheet_presets, VSETB_OT_spreadsheet_move, VSETB_OT_copy_casting, VSETB_OT_paste_casting, VSETB_OT_casting_replace, 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)