diff --git a/.gitignore b/.gitignore index 81bdb39..a55e432 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *pyc __pycache__ -.vscode \ No newline at end of file +.vscode +~syncthing~* \ No newline at end of file diff --git a/bl_utils.py b/bl_utils.py index 11bfdfa..4694fe7 100644 --- a/bl_utils.py +++ b/bl_utils.py @@ -11,6 +11,36 @@ from textwrap import dedent import bpy +class attr_set(): + """Receive a list of tuple [(data_path:python_obj, "attribute":str, "wanted value":str)] + before with statement : Store existing values, assign wanted value + after with statement: Restore values to their old values + """ + + def __init__(self, attrib_list): + """Initialization + + Args: + attrib_list (list[tuple]): a list of tuple with the + """ + self.store = [] + + for item in attrib_list: + prop, attr = item[:2] + self.store.append( (prop, attr, getattr(prop, attr)) ) + + for item in attrib_list: + prop, attr = item[:2] + if len(item) >= 3: + setattr(prop, attr, item[2]) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + for prop, attr, old_val in self.store: + setattr(prop, attr, old_val) + def abspath(path): path = os.path.abspath(bpy.path.abspath(path)) return Path(path) diff --git a/operators/exports.py b/operators/exports.py index 3ad5946..5cc0f4b 100644 --- a/operators/exports.py +++ b/operators/exports.py @@ -31,54 +31,83 @@ class VSETB_OT_render(Operator): scn = context.scene settings = get_scene_settings() - return context.window_manager.invoke_props_dialog(self, width=350) + return context.window_manager.invoke_props_dialog(self, width=500) + + def draw_export_row(self, layout, text, data, enable_prop, template_prop): + row = layout.split(factor=0.1) + heading = row.row(align=True) + heading.alignment = 'RIGHT' + heading.label(text=text) + heading.prop(data, enable_prop, text='') + row = row.row(align=True) + row.enabled = getattr(data, enable_prop) + row.prop(data, template_prop, text='') def draw(self, context): scn = context.scene settings = get_scene_settings() layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False + #layout.use_property_split = True + #layout.use_property_decorate = False box = layout.box() col = box.column() - col.label(text='Single File') - #col.use_property_split = True - #col.use_property_decorate = False - + row = col.row(align=True) + row.prop(settings.active_project, "render_single_file", text='') + row.label(icon='FILE_MOVIE') + row.label(text='Single File') - #col.prop(settings.active_project, "render_single_file") - - row = col.row() - row.prop(settings.active_project, "render_video") - row = col.row() - row.enabled = settings.active_project.render_video - row.prop(settings.active_project, "render_video_template", text='Video Path') + if settings.active_project.render_single_file: + self.draw_export_row(col ,'Video', settings.active_project, + 'render_video', + 'render_video_template' + ) + self.draw_export_row(col, 'Audio', settings.active_project, + 'render_audio', + 'render_audio_template' + ) + #layout.separator() + box = layout.box() + col = box.column() + row = col.row(align=True) + row.prop(settings.active_project, "render_sequence", text='') + row.label(icon='SEQUENCE') + row.label(text='One file per sequence') - row = col.row() - row.prop(settings.active_project, "render_audio") - row = col.row() - row.enabled = settings.active_project.render_audio - row.prop(settings.active_project, "render_audio_template", text='Audio Path') + if settings.active_project.render_sequence: + self.draw_export_row(col ,'Video', settings.active_project, + 'render_video_per_sequence', + 'render_video_sequence_template' + ) + self.draw_export_row(col, 'Audio', settings.active_project, + 'render_audio_per_sequence', + 'render_audio_sequence_template' + ) + + #col.prop(settings, 'channel', text='Channel') + #col.prop(self, 'selected_only') #layout.separator() box = layout.box() col = box.column() - col.label(text='File per strip') - - col.prop(settings.active_project, "render_video_per_strip", text='Render Video') - row = col.row() - row.enabled = settings.active_project.render_video_per_strip - row.prop(settings.active_project, "render_video_strip_template", text='Video Path') - - col.prop(settings.active_project, "render_audio_per_strip", text='Render Audio') - row = col.row() - row.enabled = settings.active_project.render_audio_per_strip - row.prop(settings.active_project, "render_audio_strip_template", text='Audio Path') - #row.prop(settings.active_project, "render_sound_format", expand=True) + row = col.row(align=True) + row.prop(settings.active_project, "render_shot", text='') + row.label(icon='SEQ_SEQUENCER') + row.label(text='One file per shot') + if settings.active_project.render_shot: + self.draw_export_row(col ,'Video', settings.active_project, + 'render_video_per_strip', + 'render_video_strip_template' + ) + self.draw_export_row(col, 'Audio', settings.active_project, + 'render_audio_per_strip', + 'render_audio_strip_template' + ) + row = layout.row() + row.prop(settings.active_project, "render_selected_only", text='Selected Only') #col.prop(settings, 'channel', text='Channel') #col.prop(self, 'selected_only') @@ -90,26 +119,49 @@ class VSETB_OT_render(Operator): format_data = {**settings.format_data, **project.format_data} start_time = time.perf_counter() - if project.render_video: - video_path = project.render_video_template.format(**format_data) - render_scene(video_path) - #background_render(output=video_path) + if project.render_single_file: + if project.render_video: + video_template = os.path.expandvars(project.render_video_template) + video_path = video_template.format(**format_data) + render_scene(video_path) + #background_render(output=video_path) - if project.render_audio: - audio_path = project.render_audio_template.format(**format_data) - bpy.ops.sound.mixdown(filepath=audio_path) + if project.render_audio: + audio_template = os.path.expandvars(project.render_audio_template) + audio_path = audio_template.format(**format_data) + bpy.ops.sound.mixdown(filepath=audio_path) - for strip in get_strips(channel='Shots', selected_only=True): - strip_settings = strip.vsetb_strip_settings - strip_data = {**format_data, **strip_settings.format_data} + if project.render_sequence: + print('Render Sequences...') + for strip in get_strips(channel='Sequences', selected_only=project.render_selected_only): + print(strip.name) + strip_settings = strip.vsetb_strip_settings + strip_data = {**format_data, **strip_settings.format_data} - if project.render_video_per_strip: - strip_render_path = project.render_video_strip_template.format(**strip_data) - render_strip(strip, strip_render_path) + if project.render_sequence and project.render_video_per_sequence: + video_sequence_template = os.path.expandvars(project.render_video_sequence_template) + sequence_render_path = video_sequence_template.format(**strip_data) + render_strip(strip, sequence_render_path) - if project.render_audio_per_strip: - audio_render_path = project.render_audio_strip_template.format(**strip_data) - render_sound(strip, audio_render_path) + if project.render_shot and project.render_audio_per_sequence: + audio_sequence_template = os.path.expandvars(project.render_audio_sequence_template) + audio_render_path = audio_sequence_template.format(**strip_data) + render_sound(strip, audio_render_path) + + if project.render_shot: + for strip in get_strips(channel='Shots', selected_only=project.render_selected_only): + strip_settings = strip.vsetb_strip_settings + strip_data = {**format_data, **strip_settings.format_data} + + if project.render_video_per_strip: + video_strip_template = os.path.expandvars(project.render_video_strip_template) + strip_render_path = video_strip_template.format(**strip_data) + render_strip(strip, strip_render_path) + + if project.render_audio_per_strip: + audio_strip_template = os.path.expandvars(project.render_audio_strip_template) + audio_render_path = audio_strip_template.format(**strip_data) + render_sound(strip, audio_render_path) self.report({"INFO"}, f'Strips rendered in {time.perf_counter()-start_time} seconds') diff --git a/operators/spreadsheet.py b/operators/spreadsheet.py index e4061cf..accc9ed 100644 --- a/operators/spreadsheet.py +++ b/operators/spreadsheet.py @@ -147,6 +147,7 @@ class VSETB_OT_spreadsheet_from_file(Operator): sheet = workbook.active rows = [[(c.value or '') for c in r] for r in sheet.rows] + workbook.close() else: self.report({'ERROR'}, f'File extension {filepath.suffix} should be in [.csv, .xlsx]') @@ -162,11 +163,15 @@ class VSETB_OT_spreadsheet_from_file(Operator): cell = import_cells.add() cell.name = cell_name + if cell_types.get(cell_name): + cell.import_name = cell_name + continue + matches = [(k, fuzzy_match(cell_name, k)) for k in cell_types.keys()] best_name, best_match = max(matches, key=lambda x: x[1]) cell.import_name = best_name - cell.enabled = best_match > 0.75 + cell.enabled = best_match > 0.85 project.spreadsheet_import.use_custom_cells = True @@ -338,7 +343,7 @@ class VSETB_OT_import_spreadsheet(Operator): spreadsheet = project.spreadsheet_import sequencer = scn.sequence_editor.sequences - assets_missing = [] + assets_missing = set() # Import Edit nb_frames_cell = next((c for c in spreadsheet.cells if c.import_name=='Nb Frames'), None) @@ -371,7 +376,7 @@ class VSETB_OT_import_spreadsheet(Operator): if spreadsheet.update_edit and (nb_frames_cell and nb_frames_cell.enabled): nb_frames = int(cell_data['Nb Frames']) - print('Import Edit') + #print('Import Edit') #print(frame_start, nb_frames, type(nb_frames)) frame_end = frame_start + nb_frames @@ -464,12 +469,15 @@ class VSETB_OT_import_spreadsheet(Operator): strip_settings.casting.update() else: - assets_missing.append(asset_name) + assets_missing.add(f'{cell_name} / {asset_name}') #print(f'Asset {asset_name} not found in Project for strip {strip.name}') #self.report({'WARNING'}, f'Asset {asset_name} not found in Project for strip {strip.name}') if assets_missing: - self.report({'WARNING'}, f'Some assets were missing {assets_missing[:5]}...') + print('Some assets were missing') + for asset in sorted(assets_missing): + print(asset) + self.report({'WARNING'}, f'Some assets were missing {list(assets_missing)[:5]}...') return {"FINISHED"} diff --git a/operators/tracker.py b/operators/tracker.py index 89c2823..9c38818 100644 --- a/operators/tracker.py +++ b/operators/tracker.py @@ -1,6 +1,8 @@ import os +from os.path import expandvars from pathlib import Path +from pprint import pprint import bpy from bpy.types import (Operator, ) @@ -159,6 +161,7 @@ class VSETB_OT_load_projects(Operator): metadata_type = project.metadata_types.add() metadata_type.name = metadata_data['name'] metadata_type.field_name = metadata_data['field_name'] + metadata_type.data_type = metadata_data['data_type'] #metadata_type['choices'] = metadata_data['choices'] if prefs.sort_metadata_items: @@ -345,7 +348,7 @@ class VSETB_OT_upload_to_tracker(Operator): sequence_name = get_strip_sequence_name(strip) shot_name = strip.name sequence = tracker.get_sequence(sequence_name, episode=episode) - metadata = strip_settings.metadata.to_dict() + metadata = strip_settings.metadata.to_dict(use_name=False) #print(metadata) if not sequence: @@ -365,7 +368,8 @@ class VSETB_OT_upload_to_tracker(Operator): if self.add_preview: strip_data = {**format_data, **strip_settings.format_data} - preview = project.render_video_strip_template.format(**strip_data) + preview_template = expandvars(project.render_video_strip_template) + preview = preview_template.format(**strip_data) preview = Path(os.path.abspath(bpy.path.abspath(preview))) #print(preview) if not preview.exists(): @@ -382,7 +386,6 @@ class VSETB_OT_upload_to_tracker(Operator): tracker.new_comment(task, comment=self.comment, status=status, preview=preview, set_main_preview=self.set_main_preview) if self.custom_data: - metadata = strip_settings.metadata.to_dict() description = strip_settings.description tracker.update_data(shot, metadata, frames=strip.frame_final_duration, description=description) @@ -391,13 +394,13 @@ class VSETB_OT_upload_to_tracker(Operator): tracker.update_casting(shot, casting) if self.tasks_comment: - for task_type in project.task_type: + for task_type in project.task_types: - task = getattr(strip_settings.tasks, task_type.name) + task = getattr(strip_settings.tasks, norm_name(task_type.name)) tracker_task = tracker.get_task(task_type.name, entity=shot) if task.comment and tracker_task.get('last_comment') != task.comment: - tracker.new_comment(task, comment=task.comment) + tracker.new_comment(tracker_task, comment=task.comment) return {"FINISHED"} diff --git a/resources/trackers/kitsu.py b/resources/trackers/kitsu.py index d83cdc9..9311aee 100644 --- a/resources/trackers/kitsu.py +++ b/resources/trackers/kitsu.py @@ -155,7 +155,7 @@ class Kitsu(Tracker): return gazu.asset.all_asset_types_for_project(project) def get_sequence(self, sequence, episode=None, project=None): - print(f'get_sequence({sequence=}, {project=})') + #print(f'get_sequence({sequence=}, {project=})') project = self.get_project(project) sequence_id = self.get_id(sequence) @@ -169,7 +169,7 @@ class Kitsu(Tracker): return gazu.shot.get_sequence_by_name(**params) def get_shot(self, shot, sequence, project=None): - print(f'get_shot({shot=}, {sequence=}, {project=})') + #print(f'get_shot({shot=}, {sequence=}, {project=})') project = self.get_project(project) sequence = self.get_sequence(sequence, project) @@ -237,11 +237,13 @@ class Kitsu(Tracker): preview_file_path=preview ) if set_main_preview: + #print('----', 'Settings') gazu.task.set_main_preview(preview_data) return preview_data def new_comment(self, task, status=None, comment='', preview=None, set_main_preview=False): + #print('new_comment', task, status, comment, preview, set_main_preview) #task = self.get_task(task) #print('Add Comment', status) @@ -347,9 +349,11 @@ class Kitsu(Tracker): else: entity['data'].update(data) + #print('######UPDATE DATA') #pprint(entity) entity_data = gazu.client.put(f"data/entities/{entity_id}", entity) - + #print() + #pprint(entity) return entity_data['data'] diff --git a/sequencer_utils.py b/sequencer_utils.py index 84aec15..438b483 100644 --- a/sequencer_utils.py +++ b/sequencer_utils.py @@ -7,7 +7,7 @@ import os import bpy from bpy.app.handlers import persistent -from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings +from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings, attr_set from vse_toolbox.file_utils import install_module, parse from vse_toolbox.constants import SOUND_SUFFIXES #import multiprocessing @@ -284,24 +284,19 @@ def render_strip(strip, output): output = os.path.abspath(bpy.path.abspath(output)) scn = bpy.context.scene - scene_start = scn.frame_start - scene_end = scn.frame_end - scene_current = scn.frame_current - render_path = scn.render.filepath - scn.frame_start = strip.frame_final_start - scn.frame_end = strip.frame_final_end - 1 - scn.render.filepath = output + attributes = [ + (scn, "frame_start", strip.frame_final_start), + (scn, "frame_end", strip.frame_final_end - 1), + (scn, "frame_current"), + (scn.render, "filepath", output), + ] - print(f'Render Strip to {scn.render.filepath}') Path(output).parent.mkdir(exist_ok=True, parents=True) - bpy.ops.render.opengl(animation=True, sequencer=True) + with attr_set(attributes): + print(f'Render Strip to {scn.render.filepath}') + bpy.ops.render.opengl(animation=True, sequencer=True) - scn.frame_start = scene_start - scn.frame_end = scene_end - scn.frame_current = scene_current - scn.render.filepath = render_path - def import_edit(filepath, adapter="cmx_3600", channel='Shots', match_by='name'): opentimelineio = install_module('opentimelineio') diff --git a/ui/panels.py b/ui/panels.py index 617f983..a83c837 100644 --- a/ui/panels.py +++ b/ui/panels.py @@ -78,7 +78,7 @@ class VSETB_PT_strip(Panel): @classmethod def poll(cls, context): - strip = context.scene.sequence_editor.active_strip + strip = context.active_sequence_strip return strip and get_channel_name(strip) == 'Shots' def draw(self, context): @@ -221,9 +221,14 @@ class VSETB_PT_exports(VSETB_main, Panel): class VSETB_PT_tracker(VSETB_main, Panel): bl_label = "Tracker" bl_parent_id = "VSETB_PT_main" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + return context.active_sequence_strip def draw_header_preset(self, context): - active_strip = context.scene.sequence_editor.active_strip + active_strip = context.active_sequence_strip self.layout.label(text=active_strip.name) def draw(self, context): @@ -232,10 +237,11 @@ class VSETB_PT_tracker(VSETB_main, Panel): class VSETB_PT_casting(VSETB_main, Panel): bl_label = "Casting" bl_parent_id = "VSETB_PT_tracker" + bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): - strip = context.scene.sequence_editor.active_strip + strip = context.active_sequence_strip return strip and get_channel_name(strip) == 'Shots' def draw(self, context): @@ -287,10 +293,11 @@ class VSETB_PT_casting(VSETB_main, Panel): class VSETB_PT_metadata(VSETB_main, Panel): bl_label = "Shot Metadata" bl_parent_id = "VSETB_PT_tracker" + bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): - return context.scene.sequence_editor.active_strip and get_scene_settings().active_project + return context.active_sequence_strip and get_scene_settings().active_project def draw(self, context): @@ -333,10 +340,11 @@ class VSETB_PT_metadata(VSETB_main, Panel): class VSETB_PT_comments(VSETB_main, Panel): bl_label = "Comments" bl_parent_id = "VSETB_PT_tracker" + bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): - return context.scene.sequence_editor.active_strip and get_scene_settings().active_project + return context.active_sequence_strip and get_scene_settings().active_project def draw(self, context): layout = self.layout diff --git a/ui/properties.py b/ui/properties.py index dd9694f..1957b9f 100644 --- a/ui/properties.py +++ b/ui/properties.py @@ -19,7 +19,7 @@ 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, parse -from vse_toolbox.sequencer_utils import get_strip_sequence_name +from vse_toolbox.sequencer_utils import get_strip_sequence_name, get_channel_name from vse_toolbox import constants @@ -104,6 +104,7 @@ class MetadataType(PropertyGroup): choices : CollectionProperty(type=PropertyGroup)#EnumProperty(items=lambda s, c: [(c, c.replace(' ', '_').upper(), '') for c in s['choices']]) field_name : StringProperty() entity_type : StringProperty() + data_type : StringProperty() class TaskType(PropertyGroup): @@ -117,6 +118,22 @@ class TaskStatus(PropertyGroup): class Metadata(CollectionPropertyGroup): __annotations__ = {} + def to_dict(self, use_name=True): + settings = get_scene_settings() + data = {} + for prop in self.props(): + name = prop.name + if not use_name: + name = prop.identifier + + metadata_type = settings.active_project.metadata_types[prop.name] + value = getattr(self, prop.identifier) + if metadata_type.data_type == 'boolean': + value = 'true' if value else 'false' + + data[name] = value + + return data class ShotTasks(PropertyGroup): __annotations__ = {} @@ -249,11 +266,26 @@ class Project(PropertyGroup): render_audio_strip_template: StringProperty( name="Sound Path", default="//render/sounds/{strip}.wav") - render_video: BoolProperty(name="Render Video", default=False) + render_video_sequence_template : StringProperty( + name="Sequence Path", default="//render/sequences/{sequence}.{ext}") + + render_audio_sequence_template: StringProperty( + name="Sound Path", default="//render/sounds/{sequence}.wav") + + render_single_file: BoolProperty(name="Render Single File", default=False) + render_sequence: BoolProperty(name="Render Sequences", default=False) + render_shot: BoolProperty(name="Render Shots", default=True) + render_selected_only: BoolProperty(name="Render Selected Only", default=True) + + render_video: BoolProperty(name="Render Video", default=True) render_audio: BoolProperty(name="Render Audio", default=False) - render_video_per_strip: BoolProperty(name="Render video per strip", default=True) - render_audio_per_strip: BoolProperty(name="Render audio per strip", default=False) + render_video_per_sequence: BoolProperty(name="Render video per sequence", default=True) + render_audio_per_sequence: BoolProperty(name="Render audio per sequence", default=False) + + render_video_per_strip: BoolProperty(name="Render video per shot", default=True) + render_audio_per_strip: BoolProperty(name="Render audio per shot", default=False) + #render_sound_format: EnumProperty(items=[(e, e, '') for e in ('mp3', 'wav')]) export_edl_template : StringProperty( @@ -606,11 +638,24 @@ class VSETB_PGT_strip_settings(PropertyGroup): project = settings.active_project strip = self.strip - data = parse(strip.name, template=project.shot_template) - data['index'] = int(data['index']) - data['sequence'] = get_strip_sequence_name(strip) - data['strip'] = strip.name - #data['shot'] = project.shot_template + channel = get_channel_name(strip) + + if channel == 'Sequences': + data = parse(strip.name, template=project.sequence_template) + data['index'] = int(data['index']) + data['sequence'] = strip.name + data['strip'] = strip.name + #data['shot'] = project.shot_template + + elif channel == "Shots": + + data = parse(strip.name, template=project.shot_template) + data['index'] = int(data['index']) + data['sequence'] = get_strip_sequence_name(strip) + data['strip'] = strip.name + #data['shot'] = project.shot_template + else: + return {} return data