# SPDX-License-Identifier: GPL-2.0-or-later import bpy import os from pathlib import Path from bpy.props import ( BoolProperty, CollectionProperty, EnumProperty, IntProperty, PointerProperty, StringProperty, ) from bpy.types import PropertyGroup, UIList from pprint import pprint as pp from vse_toolbox.bl_utils import get_addon_prefs, get_scene_settings from vse_toolbox.constants import ASSET_PREVIEWS, TRACKERS, PREVIEWS_DIR from vse_toolbox.file_utils import norm_str def get_episodes_items(self, context): settings = get_scene_settings() project = settings.active_project if not project: return [('/', '/', '', 0)] episodes = project.episodes if not episodes: return [('/', '/', '', 0)] return [(e, e, '', i) for i, e in enumerate(episodes.keys())] def get_project_items(self, context): if not self.projects: return [('/', '/', '', 0)] return [(p, p, '', i) for i, p in enumerate(self.projects.keys())] def on_project_updated(self, context): settings = get_scene_settings() settings['episodes'] = 0 #print('Update active Project') bpy.ops.vse_toolbox.load_assets() if settings.active_project: settings.active_project.set_strip_metadata() os.environ['TRACKER_PROJECT_ID'] = settings.active_project.id def on_episode_updated(self, context): settings = get_scene_settings() os.environ['TRACKER_EPISODE_ID'] = settings.active_episode.id def get_tracker_items(self, context): return [(norm_str(a.__name__, format=str.upper), a.__name__, "", i) for i, a in enumerate(TRACKERS)] class CollectionPropertyGroup(PropertyGroup): def __iter__(self): return (v for v in self.values()) def props(self): return [p for p in self.bl_rna.properties if p.identifier not in ('rna_type', 'name')] def keys(self): return [k for k in self.bl_rna.properties.keys() if k not in ('rna_type', 'name')] def values(self): return [getattr(self, k) for k in self.keys()] def items(self): return self.to_dict().items() def to_dict(self, use_name=True): if use_name: return {p.name: getattr(self, p.identifier) for p in self.props()} else: return {k: getattr(self, k) for k in self.keys()} def get_preview_items(self, context): if self.icon_id: return [(self.preview, self.tracker_name, '', self.icon_id, 0)] return [] class Asset(PropertyGroup): name : StringProperty(default='') id : StringProperty(default='') norm_name : StringProperty(default='') asset_type : StringProperty(default='') tracker_name : StringProperty(default='') preview : StringProperty(default='') previews : EnumProperty(items=get_preview_items) @property def label(self): return f"{self.asset_type} / {self.tracker_name}" @property def icon_id(self): ico = ASSET_PREVIEWS.get(self.preview) if ico: return ico.icon_id class AssetCasting(PropertyGroup): id : StringProperty(default='') instance : IntProperty(default=1) @property def asset(self): settings = get_scene_settings() project = settings.active_project return project.assets.get(self.id) def to_dict(self): return {'id': self.id, 'instance': self.instance, 'name': self.asset.name if self.asset else None, '_name': self.get('_name') } class AssetType(PropertyGroup): __annotations__ = {} class MetadataType(PropertyGroup): #choices = [] choices : CollectionProperty(type=PropertyGroup)#EnumProperty(items=lambda s, c: [(c, c.replace(' ', '_').upper(), '') for c in s['choices']]) field_name : StringProperty() entity_type : StringProperty() class TaskType(PropertyGroup): __annotations__ = {} class TaskStatus(PropertyGroup): __annotations__ = {} class Metadata(CollectionPropertyGroup): __annotations__ = {} class Episode(PropertyGroup): id : StringProperty(default='') @property def active(self): settings = get_scene_settings() return self.get(settings.project_name) class SpreadsheetExportCell(PropertyGroup): export_name : StringProperty() enabled : BoolProperty(default=True) field_name : StringProperty() type : EnumProperty(items=[(t, t, "") for t in ('METADATA', 'SHOT', 'ASSET_TYPE')]) #sort : BoolProperty(default=True) def get_cell_items(self, context): settings = get_scene_settings() project = settings.active_project return [(cell, cell, '') for cell in project.get_cell_names()] class SpreadsheetImportCell(PropertyGroup): enabled : BoolProperty(default=True) import_name : EnumProperty(items=get_cell_items) def get_custom_name_items(self, context): settings = get_scene_settings() project = settings.active_project return [(m.field_name, m.name, '') for m in project.metadata_types if m.entity_type=='ASSET'] class SpreadsheetExport(PropertyGroup): use_custom_cells: BoolProperty(default=False) format : EnumProperty(items=[(i, i, '') for i in ('CSV', 'XLSX')]) separator : StringProperty(default='\\n') delimiter : StringProperty(default=';') export_path : StringProperty(default='//export') use_custom_name : BoolProperty(default=False) custom_name : EnumProperty(items=get_custom_name_items, description='Use a custom name for asset using a metadata value') open_folder : BoolProperty(default=False) show_settings : BoolProperty(default=False) cells: CollectionProperty(type=SpreadsheetExportCell) cell_index : IntProperty(name='Spreadsheet Index', default=0) class SpreadsheetImport(PropertyGroup): use_custom_cells: BoolProperty(default=False) separator : StringProperty(default='\\n') delimiter : StringProperty(default=';') use_custom_name : BoolProperty(default=False) custom_name : EnumProperty(items=get_custom_name_items, description='Use a custom name for asset using a metadata value') cells: CollectionProperty(type=SpreadsheetImportCell) cell_index : IntProperty(name='Spreadsheet Index', default=0) import_casting: BoolProperty(default=True) import_custom_data: BoolProperty(default=True) update_edit: BoolProperty(default=True) class Project(PropertyGroup): id : StringProperty(default='') shot_start_number : IntProperty(name="Shot Start Number", default=10, min=0) sequence_start_number : IntProperty(name="Sequence Start Number", default=10, min=0) reset_by_sequence : BoolProperty( name="Reset By Sequence", description="Reset Start Number for each sequence", default=False ) sequence_increment : IntProperty( name="Sequence Increment", default=10, min=0, step=10) shot_increment : IntProperty( name="Shot Increment", default=10, min=0, step=10) sequence_template : StringProperty( name="Sequence Name", default="sq{index:03d}") episode_template : StringProperty( name="Episode Name", default="ep{index:03d}") shot_template : StringProperty( name="Shot Name", default="{sequence}_sh{index:04d}") render_template : StringProperty( name="Render Name", default="//render/{strip_name}.{ext}") episode_name : EnumProperty(items=get_episodes_items, update=on_episode_updated) episodes : CollectionProperty(type=Episode) assets : CollectionProperty(type=Asset) asset_types : CollectionProperty(type=AssetType) metadata_types : CollectionProperty(type=MetadataType) task_types : CollectionProperty(type=TaskType) task_statuses : CollectionProperty(type=TaskStatus) spreadsheet_import: PointerProperty(type=SpreadsheetImport) spreadsheet_export: PointerProperty(type=SpreadsheetExport) type : StringProperty() def get_cell_names(self): settings = get_scene_settings() project = settings.active_project cell_names = ['Sequence', 'Shot', 'Frames', 'Description'] if project.type == 'TVSHOW': cell_names.insert(0, 'Episode') for metadata_type in project.metadata_types: if metadata_type['entity_type'] == "SHOT": cell_names += [metadata_type.name] for asset_type in project.asset_types: cell_names += [asset_type.name] return cell_names def set_spreadsheet(self): cell_names = ['Sequence', 'Shot', 'Frames', 'Description'] if self.type == 'TVSHOW': cell_names.insert(0, 'Episode') # Export SpreadSheet spreadsheet = self.spreadsheet_export for cell_name in cell_names: cell = spreadsheet.cells.add() cell.name = cell_name cell.export_name = 'Name' if cell_name == 'Shot' else cell_name cell.field_name = cell_name.upper() cell.type = "SHOT" for metadata_type in self.metadata_types: if not metadata_type['entity_type'] == "SHOT": continue cell = spreadsheet.cells.add() cell.name = metadata_type.name cell.export_name = metadata_type.name cell.field_name = metadata_type.field_name cell.type = "METADATA" for asset_type in self.asset_types: cell = spreadsheet.cells.add() cell.name = asset_type.name cell.export_name = asset_type.name cell.field_name = asset_type.name.upper() cell.type = "ASSET_TYPE" def set_strip_metadata(self): # Clear Metadatas for attr in list(Metadata.__annotations__.keys()): if hasattr(Metadata, attr): delattr(Metadata, attr) del Metadata.__annotations__[attr] for metadata_type in self.metadata_types: if not metadata_type.entity_type == "SHOT": continue field_name = metadata_type.field_name name = metadata_type.name #if metadata_type.get('choices'): # prop = #bpy.props.EnumProperty(items=[(c, c, '') for c in ['/'] + metadata_type['choices']], name=name) #else: # prop = #bpy.props.StringProperty(name=name) prop = bpy.props.StringProperty(name=name) Metadata.__annotations__[field_name] = prop setattr(Metadata, field_name, prop) class VSETB_UL_casting(UIList): order_by_type : BoolProperty(default=False) def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): settings = get_scene_settings() project = settings.active_project asset = item.asset if asset is None: layout.label(text=f'Asset not Found ({item.get("_name", "...")})') return icon_id = asset.icon_id params = {'icon_value': icon_id} if icon_id else {'icon': 'BLANK1'} # Make sure your code supports all 3 layout types if self.layout_type in {'DEFAULT', 'COMPACT'}: layout.label(**params) split = layout.split(factor=0.6) split.label(text=f"{asset.norm_name.title()}") split.label(text=f"{asset.asset_type.title()}") sub = layout.row(align=True) sub.alignment = 'RIGHT' sub.prop(item, 'instance', text='') elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' layout.label(text="") def draw_filter(self, context, layout): row = layout.row() subrow = row.row(align=True) subrow.prop(self, "filter_name", text="") subrow.prop(self, "use_filter_invert", text="", icon='ARROW_LEFTRIGHT') subrow.separator() subrow.prop(self, "order_by_type", text="Order by Type", icon='MESH_DATA') def filter_items(self, context, data, propname): """Filter and order items in the list.""" helper_funcs = bpy.types.UI_UL_list filtered = [] ordered = [] items = getattr(data, propname) # Filtering by name if self.filter_name: filtered = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, items, "name", reverse=self.use_filter_sort_alpha) # Order by types if self.order_by_type: _sort = [(idx, casting_item) for idx, casting_item in enumerate(items)] sort_items = helper_funcs.sort_items_helper ordered = sort_items(_sort, lambda x: x[1].asset.label) return filtered, ordered class VSETB_UL_spreadsheet_import(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): settings = get_scene_settings() project = settings.active_project layout.use_property_split = True layout.use_property_decorate = False row = layout.row(align=True) row.alignment = 'LEFT' row.prop(item, 'enabled', text='') layout.label(text=item.name) layout.prop(item, 'import_name', text='') class VSETB_UL_spreadsheet_export(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): settings = get_scene_settings() project = settings.active_project layout.use_property_split = True layout.use_property_decorate = False row = layout.row(align=True) row.alignment = 'LEFT' row.prop(item, 'enabled', text='') layout.label(text=item.name) layout.prop(item, 'export_name', text='') class VSETB_PGT_scene_settings(PropertyGroup): projects : CollectionProperty(type=Project) project_name : EnumProperty(items=get_project_items, update=on_project_updated) tracker_name : EnumProperty(items=get_tracker_items) toogle_prefs : BoolProperty( description='Toogle VSE ToolBox Preferences', default=True) auto_select_strip : BoolProperty( name='Auto Select Strip',description='Auto select strip', default=True) channel : EnumProperty( items=[ ('AUDIO', 'Audio', '', 0), ('MOVIE', 'Movie', '', 1), ('SHOTS', 'Shots', '', 2), ('SEQUENCES', 'Sequences', '', 3), ('STAMPS', 'Sequences', '', 4), ] ) sequence_channel_name : StringProperty( name="Sequences Channel Name", default="Sequences") shot_channel_name : StringProperty( name="Shot Channel Name", default="Shots") @property def active_project(self): settings = get_scene_settings() return settings.projects.get(settings.project_name) @property def active_episode(self): project = self.active_project if project: return project.episodes.get(project.episode_name) class VSETB_PGT_strip_settings(PropertyGroup): casting : CollectionProperty(type=AssetCasting) casting_index : IntProperty(name='Casting Index', default=0) source_name : StringProperty(name='') metadata : PointerProperty(type=Metadata) description : StringProperty() classes = ( Asset, AssetCasting, SpreadsheetImportCell, SpreadsheetExportCell, AssetType, TaskStatus, Episode, Metadata, MetadataType, TaskType, SpreadsheetImport, SpreadsheetExport, Project, VSETB_UL_spreadsheet_import, VSETB_UL_spreadsheet_export, VSETB_UL_casting, VSETB_PGT_scene_settings, VSETB_PGT_strip_settings, ) from bpy.app.handlers import persistent @persistent def load_handler(dummy): settings = get_scene_settings() project = settings.active_project if project: project.set_strip_metadata() #settings.active_project.set_spreadsheet() os.environ['TRACKER_PROJECT_ID'] = settings.active_project.id for asset in project.assets: preview_id = asset.preview preview_path = Path(PREVIEWS_DIR / project.id / preview_id).with_suffix('.png') if preview_path.exists() and preview_id not in ASSET_PREVIEWS: ASSET_PREVIEWS.load(preview_id, preview_path.as_posix(), 'IMAGE', True) def register(): for cls in classes: bpy.utils.register_class(cls) bpy.types.Scene.vsetb_settings = PointerProperty(type=VSETB_PGT_scene_settings) bpy.types.Sequence.vsetb_strip_settings = PointerProperty(type=VSETB_PGT_strip_settings) #load_metadata_types() bpy.app.handlers.load_post.append(load_handler) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) del bpy.types.Sequence.vsetb_strip_settings del bpy.types.Scene.vsetb_settings bpy.app.handlers.load_post.remove(load_handler)