From d0230672f400758ef73a0af4a4839bf9152ab169 Mon Sep 17 00:00:00 2001 From: Christophe SEUX Date: Sat, 22 Apr 2023 15:42:38 +0200 Subject: [PATCH] export xlsx --- file_utils.py | 23 +++- operators/operators.py | 110 ++++++++++++++---- panels.py | 27 +++-- properties.py | 55 +++++++-- resources/trackers/kitsu.py | 4 +- .../trackers/tracker_test/trackertest.py | 2 +- sequencer_utils.py | 9 ++ 7 files changed, 184 insertions(+), 46 deletions(-) diff --git a/file_utils.py b/file_utils.py index d587bec..a1b998c 100644 --- a/file_utils.py +++ b/file_utils.py @@ -1,10 +1,12 @@ import importlib import re import subprocess +import platform import sys import unicodedata from pathlib import Path + def install_module(module_name, package_name=None): '''Install a python module with pip or return it if already installed''' try: @@ -110,4 +112,23 @@ def read_file(path): else: data = txt - return data \ No newline at end of file + return data + +def open_file(filepath, env=None, select=False): + if platform.system() == 'Darwin': # macOS + cmd = ['open'] + if select: + cmd += ['-R'] + + elif platform.system() == 'Windows': # Windows + cmd = ['explorer'] + if select: + cmd += ['/select,'] + else: # linux variants + cmd = ['xdg-open'] + if select: + cmd = ['nemo'] + + cmd += [str(filepath)] + + subprocess.Popen(cmd, env=env) \ No newline at end of file diff --git a/operators/operators.py b/operators/operators.py index ba0e216..8e1b559 100644 --- a/operators/operators.py +++ b/operators/operators.py @@ -55,7 +55,7 @@ from vse_toolbox.sequencer_utils import ( ) 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 +from vse_toolbox.file_utils import install_module, norm_name, norm_str, open_file class VSETB_OT_tracker_connect(Operator): @@ -83,16 +83,27 @@ class VSETB_OT_tracker_connect(Operator): return {"CANCELLED"} +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 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"} - format : EnumProperty(items=[(i, i, '') for i in ('CSV', 'XLSL')]) + 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) @classmethod def poll(cls, context): @@ -122,13 +133,26 @@ class VSETB_OT_export_spreadsheet(Operator): col = layout.column() col.use_property_split = True - col.prop(self, "separator", expand=True, text='Separator') - row = col.row(align=True) + row = col.row(align=True, heading='Custom Name') + #row.use_property_split = True + row.prop(self, 'use_custom_name', text='') + sub = row.row(align=True) + sub.enabled = self.use_custom_name + sub.prop(self, 'custom_name', text='') + + col.separator() + + row = col.row(align=False) row.prop(self, "format", expand=True, text='Format') - if self.format == 'CSV': - col.prop(self, "delimiter", expand=True, text='Delimiter') + row.prop(self, 'show_settings', text='', icon='PREFERENCES') + if self.show_settings: + col.prop(self, "separator", expand=True, text='Separator') + if self.format == 'CSV': + col.prop(self, "delimiter", expand=True, text='Delimiter') - col.prop(self, 'export_path') + col.separator() + col.prop(self, 'open_folder', text='Open Folder') + col.prop(self, 'export_path', text='Export Path') def execute(self, context): #self.report({'ERROR'}, f'Export not implemented yet.') @@ -143,11 +167,30 @@ class VSETB_OT_export_spreadsheet(Operator): # Header rows.append([cell.export_name for cell in cells]) + separator = self.separator.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r') + delimiter = self.delimiter.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r') + for strip in get_strips('Shots'): row = [] for cell in cells: - if cell.is_metadata: + 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 self.use_custom_name: + if asset.get('metadata', {}).get(self.custom_name): + asset_castings.append(asset['metadata'][self.custom_name]) + else: + self.report({'ERROR'}, f'The asset {asset.tracker_name} has no data {self.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': @@ -188,14 +231,36 @@ class VSETB_OT_export_spreadsheet(Operator): #2023_04_11_kitsu_boris_ep01_shots export_path.parent.mkdir(parents=True, exist_ok=True) - print('Writing .csv file to', export_path) - with open(str(export_path), 'w', newline='\n', encoding='utf-8') as f: - writer = csv.writer(f) - for row in rows: - writer.writerow(row) + if self.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=self.delimiter) + for row in rows: + writer.writerow(row) - #if show_in_explorer: - # open_file(filepath, select=True) + elif self.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 self.open_folder: + open_file(export_path, select=True) return {"FINISHED"} @@ -230,7 +295,8 @@ class VSETB_OT_upload_to_tracker(Operator): 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')]) + 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) @@ -525,9 +591,8 @@ class VSETB_OT_load_assets(Operator): asset.id = asset_data['id'] asset.asset_type = asset_data['asset_type'] - datas = asset_data.get('data', {}) - for key, values in datas.items(): - asset[key] = values + #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: @@ -575,12 +640,13 @@ class VSETB_OT_load_projects(Operator): episode.name = episode_data['name'] episode.id = episode_data['id'] - for metadata_data in tracker.get_shots_metadata(project_data): - pprint(metadata_data) + 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'] + metadata_type['entity_type'] = metadata_data['entity_type'].upper() for status_data in tracker.get_task_statuses(project_data): #print(metadata_data) @@ -611,6 +677,8 @@ class VSETB_OT_load_projects(Operator): #bpy.ops.vse_toolbox.load_assets() + self.report({"INFO"}, 'Successfully Load Tracker Projects') + return {'FINISHED'} diff --git a/panels.py b/panels.py index 2935b78..9c20baa 100644 --- a/panels.py +++ b/panels.py @@ -1,8 +1,10 @@ # SPDX-License-Identifier: GPL-2.0-or-later +from pathlib import Path + import bpy from bpy.types import Panel -from pathlib import Path + from vse_toolbox.bl_utils import (get_addon_prefs, get_scene_settings, get_strip_settings) from vse_toolbox.constants import ASSET_PREVIEWS from vse_toolbox.sequencer_utils import (set_active_strip, get_channel_name) @@ -218,16 +220,7 @@ class VSETB_PT_casting(VSETB_main, Panel): row = layout.row() col = row.column() col.template_list("VSETB_UL_casting", "shot_casting", strip_settings, "casting", strip_settings, "casting_index", rows=6) - - if strip_settings.casting: - casting_item = strip_settings.casting[strip_settings.casting_index] - asset = casting_item.asset - if asset: - ico = ASSET_PREVIEWS.get(asset.preview) - if ico: - box = col.box() - box.template_icon(icon_value=ico.icon_id, scale=7.5) - + col_tool = row.column(align=True) col_tool.operator('vse_toolbox.casting_add', icon='ADD', text="") col_tool.operator('vse_toolbox.casting_remove', icon='REMOVE', text="") @@ -238,6 +231,18 @@ class VSETB_PT_casting(VSETB_main, Panel): col_tool.operator('vse_toolbox.copy_casting', icon='COPYDOWN', text="") col_tool.operator('vse_toolbox.paste_casting', icon='PASTEDOWN', text="") + if strip_settings.casting: + casting_item = strip_settings.casting[strip_settings.casting_index] + asset = casting_item.asset + if asset: + if asset.icon_id: + row = col.row(align=True) + #row.scale_y = 0.5 + # box = col.box() + # box.template_icon(icon_value=ico.icon_id, scale=7.5) + + row.template_icon_view(asset, "previews", show_labels=False) + class VSETB_PT_metadata(VSETB_main, Panel): bl_label = "Shot Metadata" diff --git a/properties.py b/properties.py index fff1891..4cec2ef 100644 --- a/properties.py +++ b/properties.py @@ -2,6 +2,7 @@ import bpy import os +from pathlib import Path from bpy.props import ( BoolProperty, @@ -14,7 +15,7 @@ from bpy.props import ( 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 +from vse_toolbox.constants import ASSET_PREVIEWS, TRACKERS, PREVIEWS_DIR from vse_toolbox.file_utils import norm_str @@ -79,6 +80,12 @@ class CollectionPropertyGroup(PropertyGroup): 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='') @@ -86,6 +93,7 @@ class Asset(PropertyGroup): asset_type : StringProperty(default='') tracker_name : StringProperty(default='') preview : StringProperty(default='') + previews : EnumProperty(items=get_preview_items) @property def label(self): @@ -112,9 +120,8 @@ class AssetCasting(PropertyGroup): class SpreadsheetCell(PropertyGroup): export_name : StringProperty() enabled : BoolProperty(default=True) - is_metadata : BoolProperty(default=False) field_name : StringProperty() - #type : EnumProperty(items=[(t, t, "")] for t in ('METADATA', 'STRIP_DATA')) + type : EnumProperty(items=[(t, t, "") for t in ('METADATA', 'SHOT', 'ASSET_TYPE')]) class AssetType(PropertyGroup): @@ -124,7 +131,8 @@ class AssetType(PropertyGroup): class MetadataType(PropertyGroup): choices = [] choice : EnumProperty(items=lambda s, c: [(c, c.replace(' ', '_').upper(), '') for c in s['choices']]) - field_name : bpy.props.StringProperty() + field_name : StringProperty() + entity_type : StringProperty() class TaskType(PropertyGroup): @@ -193,15 +201,25 @@ class Project(PropertyGroup): for cell_name in cell_names: cell = self.spreadsheet.add() cell.name = cell_name - cell.export_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 = self.spreadsheet.add() cell.name = metadata_type.name cell.export_name = metadata_type.name cell.field_name = metadata_type.field_name - cell.is_metadata = True + cell.type = "METADATA" + + for asset_type in self.asset_types: + cell = self.spreadsheet.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): @@ -213,6 +231,9 @@ class Project(PropertyGroup): 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 @@ -237,7 +258,6 @@ class VSETB_UL_casting(UIList): asset = item.asset if asset is None: - #TODO deal if asset was removed layout.label(text=f'Asset not Found ({item.get("_name", "...")})') return @@ -250,7 +270,9 @@ class VSETB_UL_casting(UIList): split = layout.split(factor=0.6) split.label(text=f"{asset.norm_name.title()}") split.label(text=f"{asset.asset_type.title()}") - split.prop(item, 'instance', text='') + sub = layout.row(align=True) + sub.alignment = 'RIGHT' + sub.prop(item, 'instance', text='') elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' @@ -388,12 +410,25 @@ from bpy.app.handlers import persistent @persistent def load_handler(dummy): settings = get_scene_settings() - if settings.active_project: - settings.active_project.set_strip_metadata() + 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) + print(preview_path) + + print(ASSET_PREVIEWS) + def register(): for cls in classes: bpy.utils.register_class(cls) diff --git a/resources/trackers/kitsu.py b/resources/trackers/kitsu.py index 200977f..40d8d3e 100644 --- a/resources/trackers/kitsu.py +++ b/resources/trackers/kitsu.py @@ -128,12 +128,12 @@ class Kitsu(Tracker): task_types = gazu.task.all_task_types_for_project(project) return [t for t in task_types if t['for_entity'].lower() == 'shot'] - def get_shots_metadata(self, project=None): + def get_metadata_types(self, project=None): project = self.get_project(project) metadatas = [] for metadata in gazu.project.all_metadata_descriptors(project): - if metadata['entity_type'] == 'Shot' and metadata['name']: + if metadata['name']: metadatas.append(metadata) return metadatas diff --git a/resources/trackers/tracker_test/trackertest.py b/resources/trackers/tracker_test/trackertest.py index ed85c24..9203e3b 100644 --- a/resources/trackers/tracker_test/trackertest.py +++ b/resources/trackers/tracker_test/trackertest.py @@ -52,7 +52,7 @@ class TrackerTest(Tracker): return assets - def get_shots_metadata(self, project): + def get_metadata_types(self, project): metadata = [] for md in gazu.project.all_metadata_descriptors(project): asset_type = md.get('asset_type') diff --git a/sequencer_utils.py b/sequencer_utils.py index 51f09c1..024c5ae 100644 --- a/sequencer_utils.py +++ b/sequencer_utils.py @@ -9,6 +9,7 @@ from bpy.app.handlers import persistent from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings from vse_toolbox.constants import SOUND_SUFFIXES +import multiprocessing def new_text_strip(name='Text', channel=0, start=0, end=50, text='Text', font_size=48, @@ -136,6 +137,14 @@ def render_strips(strips, template): scene_end = scn.frame_end render_path = scn.render.filepath + # pool = multiprocessing.Pool(4) + # p.map(func, range(1, 100)) + + # def render_strip_background(index): + # cmd = [bpy.app.binary_path, etc] + # process = subprocess.Popen(substr + " --index {}".format(index), shell=True, stdout=subprocess.PIPE) + + for strip in strips: #print(render_template, strip.name, path)