diff --git a/file_utils.py b/file_utils.py index dea5fa3..2e70c5a 100644 --- a/file_utils.py +++ b/file_utils.py @@ -164,19 +164,24 @@ def parse(template, string): if result := re.match(reg, string): return result.groupdict() -def find_last(template, **kargs): - #print(find_last, template, kargs) - pattern = Path(template).as_posix() - pattern = expandvars(pattern) +def expand(template, **kargs): + template = Path(template).as_posix() + template = expandvars(template) for key, value in kargs.items(): - pattern = re.sub(r'\{%s\}'%key, value, pattern) + template = re.sub(r'\{%s\}'%key, value, template) + + return template + +def find_last(template, **kargs): + + pattern = expand(template, **kargs) pattern = re.sub(r'{\w+:(\d{2})d}', lambda x : '?' * int(x.groups()[0]), pattern) pattern = re.sub(r'{.*?}', '*', pattern) - print(pattern) - filepaths = glob.glob(pattern) if filepaths: - return Path(max(filepaths)) \ No newline at end of file + return Path(max(filepaths)) + else: + print(f'No preview found for template {pattern}') \ No newline at end of file diff --git a/operators/addon.py b/operators/addon.py index 38a91e8..7633863 100644 --- a/operators/addon.py +++ b/operators/addon.py @@ -5,6 +5,7 @@ import os import bpy from bpy.types import Operator +from bpy.props import IntProperty import vse_toolbox @@ -37,6 +38,21 @@ class VSETB_OT_load_settings(Operator): bl_label = 'Load Settings' bl_description = 'Load VSE ToolBox settings from config file' + def set_config(self, data, values): + for k, v in values.items(): + if not hasattr(data, k): + print(f'{repr(data)} has no attribute {k}') + continue + + if isinstance(v, list): + data[k] = v + return + + try: + setattr(data, k, v) + except Exception: + print(f'Could not set property {k} with value {v} to {repr(data)}') + def execute(self, context): prefs = get_addon_prefs() settings = get_scene_settings() @@ -48,6 +64,10 @@ class VSETB_OT_load_settings(Operator): if not addon_config: return {'CANCELLED'} + + # Environ set + for k, v in addon_config.get('environ', {}).items(): + os.environ[k] = str(v) addon_config['trackers'] = addon_config.get('trackers') trackers_config = addon_config.pop('trackers') @@ -61,11 +81,10 @@ class VSETB_OT_load_settings(Operator): for k, v in tracker_config.items(): setattr(tracker, k, v) - addon_config['spreadsheet_export'] = addon_config.get('spreadsheet_export', {}) - spreadsheet_export_config = addon_config.pop('spreadsheet_export') - - addon_config['spreadsheet_import'] = addon_config.get('spreadsheet_import', {}) - spreadsheet_import_config = addon_config.pop('spreadsheet_import') + spreadsheet_export_config = addon_config.pop('spreadsheet_export', {}) + spreadsheet_import_config = addon_config.pop('spreadsheet_import', {}) + upload_to_tracker_config = addon_config.pop('upload_to_tracker', {}) + import_shots_config = addon_config.pop('import_shots', {}) project_name = addon_config.get('project_name') if project_name not in settings.projects: @@ -77,12 +96,7 @@ class VSETB_OT_load_settings(Operator): # Project Properties project = settings.active_project - for k, v in addon_config.items(): - try: - setattr(project, k, v) - except Exception: - print(f'Could not set property {k} with value {v} to project {settings.project_name}') - + self.set_config(project, addon_config) export_cells = project.spreadsheet_export.cells for k, v in spreadsheet_export_config.items(): @@ -148,13 +162,53 @@ class VSETB_OT_load_settings(Operator): except Exception: print(f'Could not set option {k} with value {v} to spreadsheet') - self.report({"INFO"}, 'Settings loaded with sucess') + # Upload to Tracker + self.set_config(project.upload_to_tracker, upload_to_tracker_config) + + # Import Shots + self.set_config(project.import_shots, import_shots_config) + + self.report({"INFO"}, 'Settings loaded with success') + return {'FINISHED'} + + +class VSETB_OT_add_template(Operator): + bl_idname = "vse_toolbox.add_template" + bl_label = "Add Template" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + scn = context.scene + settings = get_scene_settings() + project = settings.active_project + + project.templates.add() + + return {'FINISHED'} + + +class VSETB_OT_remove_template(Operator): + bl_idname = "vse_toolbox.remove_template" + bl_label = "Remove Template" + bl_options = {"REGISTER", "UNDO"} + + index : IntProperty() + + def execute(self, context): + scn = context.scene + settings = get_scene_settings() + project = settings.active_project + + project.templates.remove(self.index) + return {'FINISHED'} classes = ( VSETB_OT_reload_addon, - VSETB_OT_load_settings + VSETB_OT_load_settings, + VSETB_OT_add_template, + VSETB_OT_remove_template ) def register(): diff --git a/operators/exports.py b/operators/exports.py index f308ed4..9eaf7f2 100644 --- a/operators/exports.py +++ b/operators/exports.py @@ -8,9 +8,10 @@ import bpy from bpy.types import Operator from bpy.props import BoolProperty -from vse_toolbox.sequencer_utils import (get_strips, render_strip, render_sound, render_scene) +from vse_toolbox.sequencer_utils import (get_strips, render_strip, render_sound, + render_scene, get_render_attributes) from vse_toolbox.bl_utils import (get_scene_settings, background_render) -from vse_toolbox.file_utils import install_module +from vse_toolbox.file_utils import install_module, expand @@ -116,53 +117,67 @@ class VSETB_OT_render(Operator): settings = get_scene_settings() project = settings.active_project + project_templates = {t.name: t.value for t in project.templates} format_data = {**settings.format_data, **project.format_data} + single_file = False + sequence_strips = [] + shot_strips = [] + + render_attrs = get_render_attributes() + start_time = time.perf_counter() if project.render_single_file: + single_file = True if project.render_video: - video_template = os.path.expandvars(project.render_video_template) + video_template = expand(project.render_video_template, **project_templates) video_path = video_template.format(**format_data) - render_scene(video_path) + render_scene(video_path, attributes=render_attrs) #background_render(output=video_path) if project.render_audio: - audio_template = os.path.expandvars(project.render_audio_template) + audio_template = expand(project.render_audio_template, **project_templates) audio_path = audio_template.format(**format_data) bpy.ops.sound.mixdown(filepath=audio_path) if project.render_sequence: print('Render Sequences...') - for strip in get_strips(channel='Sequences', selected_only=project.render_selected_only): + sequence_strips = get_strips(channel='Sequences', selected_only=project.render_selected_only) + for strip in sequence_strips: #print(strip.name) strip_settings = strip.vsetb_strip_settings strip_data = {**format_data, **strip_settings.format_data} if project.render_sequence and project.render_video_per_sequence: - video_sequence_template = os.path.expandvars(project.render_video_sequence_template) + video_sequence_template = expand(project.render_video_sequence_template, **project_templates) sequence_render_path = video_sequence_template.format(**strip_data) - render_strip(strip, sequence_render_path) + render_strip(strip, sequence_render_path, attributes=render_attrs) if project.render_shot and project.render_audio_per_sequence: - audio_sequence_template = os.path.expandvars(project.render_audio_sequence_template) + audio_sequence_template = expand(project.render_audio_sequence_template, **project_templates) 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): + shot_strips = get_strips(channel='Shots', selected_only=project.render_selected_only) + for strip in shot_strips: 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) + video_strip_template = expand(project.render_video_strip_template, **project_templates) strip_render_path = video_strip_template.format(**strip_data) - render_strip(strip, strip_render_path) + render_strip(strip, strip_render_path, attributes=render_attrs) if project.render_audio_per_strip: - audio_strip_template = os.path.expandvars(project.render_audio_strip_template) + audio_strip_template = expand(project.render_audio_strip_template, **project_templates) audio_render_path = audio_strip_template.format(**strip_data) render_sound(strip, audio_render_path) + if not single_file and not sequence_strips and not shot_strips: + self.report({"ERROR"}, f'No strips rendered, select sequence or shot strips') + return {"CANCELLED"} + self.report({"INFO"}, f'Strips rendered in {time.perf_counter()-start_time} seconds') return {"FINISHED"} diff --git a/operators/imports.py b/operators/imports.py index 8a75078..84089f8 100644 --- a/operators/imports.py +++ b/operators/imports.py @@ -7,7 +7,7 @@ from tempfile import gettempdir import bpy from bpy.types import Operator, UIList -from bpy.props import (CollectionProperty, BoolProperty, EnumProperty, StringProperty) +from bpy.props import (CollectionProperty, BoolProperty, EnumProperty, StringProperty, IntProperty) from vse_toolbox.constants import (EDITS, EDIT_SUFFIXES, MOVIES, MOVIE_SUFFIXES, SOUNDS, SOUND_SUFFIXES) @@ -16,8 +16,8 @@ from vse_toolbox.sequencer_utils import (clean_sequencer, import_edit, import_mo import_sound, get_strips, get_channel_index, get_empty_channel, scale_clip_to_fit) from vse_toolbox.bl_utils import (get_scene_settings, get_addon_prefs, get_scene_settings) -from vse_toolbox.file_utils import install_module, parse, find_last -#from vse_toolbox.template import Template +from vse_toolbox.file_utils import install_module, parse, find_last, expand + class VSETB_OT_auto_select_files(Operator): bl_idname = "vse_toolbox.auto_select_files" @@ -254,6 +254,38 @@ class VSETB_OT_unselect_task(Operator): return {'FINISHED'} +class VSETB_OT_add_import_template(Operator): + bl_idname = "vse_toolbox.add_import_template" + bl_label = "Add Template" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + scn = context.scene + settings = get_scene_settings() + project = settings.active_project + + project.import_shots.video_templates.add() + + return {'FINISHED'} + + +class VSETB_OT_remove_import_template(Operator): + bl_idname = "vse_toolbox.remove_import_template" + bl_label = "Remove Template" + bl_options = {"REGISTER", "UNDO"} + + index : IntProperty() + + def execute(self, context): + scn = context.scene + settings = get_scene_settings() + project = settings.active_project + + project.import_shots.video_templates.remove(self.index) + + return {'FINISHED'} + + def get_sequence_items(self, context): settings = get_scene_settings() project = settings.active_project @@ -351,6 +383,9 @@ class VSETB_OT_import_shots(Operator): if not last_comment: return + last_preview = last_comment['previews'][0] + + ext = last_preview['extension'] shot_name = shot['name'] sequence_name = f"{shot['sequence_name']}_" if shot_name.startswith(sequence_name): @@ -358,10 +393,9 @@ class VSETB_OT_import_shots(Operator): preview_dir = self.get_preview_dir() - filepath = Path(preview_dir, f'{sequence_name}{shot_name}_{task_type.name}') + filepath = Path(preview_dir, f'{sequence_name}{shot_name}_{task_type.name}.{ext}') filepath.parent.mkdir(parents=True, exist_ok=True) - last_preview = last_comment['previews'][0] tracker.download_preview_file(last_preview, str(filepath)) return filepath @@ -378,17 +412,20 @@ class VSETB_OT_import_shots(Operator): settings = get_scene_settings() project = settings.active_project import_shots = project.import_shots + project_templates = {t.name: t.value for t in project.templates} - pattern = Path( - import_shots.shot_folder_template, - import_shots.import_video_template) + for template in [import_shots.video_template] + list(import_shots.video_templates.keys()): + + template = expand(template, **project_templates) - # Normalize name in snake_case for now - sequence = sequence.name.lower().replace(' ', '_') - shot = parse(project.shot_template, shot_name)['shot'] - task = task_type.name.lower().replace(' ', '_') + format_data = project.format_data + format_data.update(parse(project.sequence_template, sequence.name)) + format_data.update(parse(project.shot_template, shot_name)) + # Normalize name in snake_case for now + format_data.update(task=task_type.name.lower().replace(' ', '_')) - return find_last(pattern, sequence=sequence, shot=shot, task=task) + if last_preview := find_last(template, **format_data): + return last_preview def execute(self, context): scn = context.scene @@ -405,6 +442,9 @@ class VSETB_OT_import_shots(Operator): conformed = False + scn.sequence_editor_clear() + scn.sequence_editor_create() + self.set_sequencer_channels([t.name for t in task_types]) frame_index = 1 for sequence in sequences: @@ -412,6 +452,7 @@ class VSETB_OT_import_shots(Operator): sequence_start = frame_index for shot_data in shots_data: frames = int(shot_data['nb_frames']) + frame_end = frame_index + frames strip = scn.sequence_editor.sequences.new_effect( name=shot_data['name'], @@ -425,25 +466,23 @@ class VSETB_OT_import_shots(Operator): for task_type in task_types: if import_shots.import_source == 'DISK': - preview = self.find_shot_preview(sequence, shot_data['name'], task_type) - #print(videos) - #return {"FINISHED"} - + preview = self.find_shot_preview(sequence, shot_data['name'], task_type) else: preview = self.download_preview(task_type, shot_data) + if not preview: + print(f'No preview found for shot {shot_data["name"]}') if not preview: - print(f'No preview found for shot {shot_data["name"]}') continue print(f'Loading Preview from {preview}') - - # Load Video channel_index = get_channel_index(f'{task_type.name} Video') - video_clip = import_movie(preview) - video_clip.frame_start = frame_index + video_clip = import_movie(preview, frame_start=frame_index, frame_end=frame_end) video_clip.channel = channel_index + if video_clip.frame_offset_end: + video_clip.color_tag = 'COLOR_01' + if not conformed: self.conform_render(video_clip) conformed = True @@ -453,8 +492,10 @@ class VSETB_OT_import_shots(Operator): # Load Audio channel_index = get_channel_index(f'{task_type.name} Audio') audio_clip = import_sound(preview, frame_start=frame_index, - frame_end=video_clip.frame_final_end) + frame_end=frame_end) audio_clip.channel = channel_index + if video_clip.frame_offset_end: + audio_clip.color_tag = 'COLOR_01' frame_index += frames @@ -471,6 +512,8 @@ class VSETB_OT_import_shots(Operator): scn.frame_start = 1 scn.frame_end = frame_index -1 + #bpy.ops.vse_toolbox.set_stamps() + return {'FINISHED'} def draw(self, context): @@ -489,8 +532,16 @@ class VSETB_OT_import_shots(Operator): if import_shots.import_source == 'DISK': #col.prop(import_shots, "sequence_dir_template", text='Sequence Dir') - col.prop(import_shots, "shot_folder_template", text='Shot Folder') - col.prop(import_shots, "import_video_template", text='Video') + #col.prop(import_shots, "shot_folder_template", text='Shot Folder') + row = col.row(align=True) + row.prop(import_shots, "video_template", text='') + row.operator("vse_toolbox.add_import_template", text='', icon='ADD') + + for i, template in enumerate(import_shots.video_templates): + row = col.row(align=True) + row.prop(template, "name", text='') + row.operator("vse_toolbox.remove_import_template", text='', icon='REMOVE').index=i + col.separator() else: col.prop(import_shots, "previews_folder", text='Previews Folder') @@ -579,7 +630,7 @@ class VSETB_OT_import_shots(Operator): # self.report({"ERROR"}, "Save your Blender file first") # return {"CANCELLED"} - return context.window_manager.invoke_props_dialog(self, width=400) + return context.window_manager.invoke_props_dialog(self, width=350) def check(self, context): return True @@ -590,6 +641,8 @@ classes = ( VSETB_OT_unselect_sequence, VSETB_OT_unselect_task, VSETB_OT_select_task, + VSETB_OT_add_import_template, + VSETB_OT_remove_import_template, VSETB_UL_import_task, VSETB_OT_auto_select_files, VSETB_OT_import_files, diff --git a/operators/sequencer.py b/operators/sequencer.py index 1e04b5c..df28b4d 100644 --- a/operators/sequencer.py +++ b/operators/sequencer.py @@ -155,7 +155,7 @@ class VSETB_OT_set_sequencer(Operator): scn.view_settings.view_transform = 'Standard' scn.render.image_settings.file_format = 'FFMPEG' - scn.render.ffmpeg.gopsize = 5 + scn.render.ffmpeg.gopsize = 8 scn.render.ffmpeg.constant_rate_factor = 'HIGH' scn.render.ffmpeg.format = 'QUICKTIME' scn.render.ffmpeg.audio_codec = 'AAC' @@ -304,8 +304,18 @@ class VSETB_OT_open_shot_folder(Operator): @classmethod def poll(cls, context): + settings = get_scene_settings() + project = settings.active_project + if not project.templates.get('shot_dir'): + cls.poll_message_set('No shot_dir template setted') + return + strip = context.active_sequence_strip - return strip and get_channel_name(strip) == 'Shots' + if not strip or get_channel_name(strip) != 'Shots': + cls.poll_message_set('No shot strip active') + return + + return True def execute(self, context): strip = context.active_sequence_strip @@ -316,7 +326,7 @@ class VSETB_OT_open_shot_folder(Operator): format_data = {**settings.format_data, **project.format_data, **strip_settings.format_data} - shot_folder_template = expandvars(project.import_shots.shot_folder_template) + shot_folder_template = expandvars(project.templates['shot_dir'].value) shot_folder_path = shot_folder_template.format(**format_data) bpy.ops.wm.path_open(filepath=shot_folder_path) diff --git a/operators/tracker.py b/operators/tracker.py index 4b75465..9e21a81 100644 --- a/operators/tracker.py +++ b/operators/tracker.py @@ -12,10 +12,11 @@ from bpy.props import (BoolProperty, EnumProperty, StringProperty) from vse_toolbox.constants import (ASSET_PREVIEWS, PREVIEWS_DIR, ASSET_ITEMS) -from vse_toolbox.sequencer_utils import (get_strips, get_strip_render_path, get_strip_sequence_name) +from vse_toolbox.sequencer_utils import (get_strips, get_strip_render_path, get_strip_sequence_name, + render_strip, get_render_attributes) from vse_toolbox.bl_utils import (get_addon_prefs, get_scene_settings) -from vse_toolbox.file_utils import (norm_name,) +from vse_toolbox.file_utils import (norm_name, expand) from bpy.app.handlers import persistent @@ -36,7 +37,7 @@ class VSETB_OT_tracker_connect(Operator): settings = get_scene_settings() try: prefs.tracker.connect() - self.report({'INFO'}, f'Sucessfully login to {settings.tracker_name.title()}') + self.report({'INFO'}, f'successfully login to {settings.tracker_name.title()}') return {"FINISHED"} except Exception as e: print('e: ', e) @@ -121,6 +122,10 @@ class VSETB_OT_load_projects(Operator): (r,g,b), a = map(lambda component: component / 255, bytes.fromhex(color_str[-6:])), 1.0 return (r,g,b,a) + def invoke(self, context, event): + self.ctrl = event.ctrl + return self.execute(context) + def execute(self, context): settings = get_scene_settings() prefs = get_addon_prefs() @@ -213,14 +218,16 @@ class VSETB_OT_load_projects(Operator): if project.name not in project_names: settings.projects.remove(list(settings.projects).index(project)) - bpy.ops.vse_toolbox.load_settings() + if self.ctrl or not settings.get('projects_loaded'): + bpy.ops.vse_toolbox.load_settings() if prev_project_name != '/' and prev_project_name in settings.projects: settings.project_name = prev_project_name #if settings.active_project: # settings.active_project.set_strip_metadata() - + + settings['projects_loaded'] = True self.report({"INFO"}, 'Successfully Load Tracker Projects') @@ -251,19 +258,16 @@ class VSETB_OT_new_episode(Operator): 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'} @@ -302,8 +306,6 @@ class VSETB_OT_upload_to_tracker(Operator): col.use_property_split = False col.prop(upload_to_tracker, 'render_strip_template', text='') col.use_property_split = True - #else: - # col.label(text=f'Source: {self.project.render_video_strip_template}') col.separator() @@ -329,21 +331,27 @@ class VSETB_OT_upload_to_tracker(Operator): #self.report({'ERROR'}, f'Export not implemented yet.') prefs = get_addon_prefs() settings = get_scene_settings() + tracker = prefs.tracker + upload_to_tracker = self.project.upload_to_tracker + render_attrs = get_render_attributes() + project_templates = {t.name: t.value for t in self.project.templates} episode = None if settings.active_episode: episode = settings.active_episode.id format_data = {**settings.format_data, **self.project.format_data} - - tracker = prefs.tracker - + status = upload_to_tracker.status if status == 'CURRENT': status = None + + shot_strips = get_strips(channel='Shots', selected_only=True) + context.window_manager.progress_begin(0, len(shot_strips)) - for strip in get_strips(channel='Shots', selected_only=True): + for i, strip in enumerate(shot_strips): + context.window_manager.progress_update(i) strip_settings = strip.vsetb_strip_settings sequence_name = get_strip_sequence_name(strip) shot_name = strip.name @@ -369,12 +377,17 @@ class VSETB_OT_upload_to_tracker(Operator): strip_data = {**format_data, **strip_settings.format_data} if upload_to_tracker.render_strips: - preview_template = expandvars(upload_to_tracker.render_strip_template) + preview_template = expand(upload_to_tracker.render_strip_template, **project_templates) + strip_data['ext'] = 'mov' else: preview_template = expandvars(self.project.render_video_strip_template) preview = preview_template.format(**strip_data) preview = Path(os.path.abspath(bpy.path.abspath(preview))) + + if upload_to_tracker.render_strips: + render_strip(strip, preview, attributes=render_attrs) + #print(preview) if not preview.exists(): print(f'The preview {preview} not exists') @@ -387,7 +400,7 @@ class VSETB_OT_upload_to_tracker(Operator): preview = None comment_data = None - if status or comment or preview: + if status or upload_to_tracker.comment or preview: comment_data = tracker.new_comment(task, comment=upload_to_tracker.comment, status=status) if preview: print('Upload preview from', preview) @@ -423,6 +436,8 @@ class VSETB_OT_upload_to_tracker(Operator): if task.comment and tracker_task.get('last_comment') != task.comment: tracker.new_comment(tracker_task, comment=task.comment) + context.window_manager.progress_end() + return {"FINISHED"} @@ -448,6 +463,7 @@ class VSETB_OT_open_shot_on_tracker(Operator): return {"FINISHED"} + @persistent def set_asset_items(scene=None): ASSET_ITEMS.clear() diff --git a/resources/trackers/kitsu.py b/resources/trackers/kitsu.py index 7cb1d24..38a97dc 100644 --- a/resources/trackers/kitsu.py +++ b/resources/trackers/kitsu.py @@ -78,7 +78,7 @@ class Kitsu(Tracker): print(f'Info: Log in to kitsu as {login}') res = gazu.log_in(login, password) LOGIN = login - print(f'Info: Sucessfully login to Kitsu as {res["user"]["full_name"]}') + print(f'Info: successfully login to Kitsu as {res["user"]["full_name"]}') return res['user'] except Exception as e: print(f'Error: {traceback.format_exc()}') diff --git a/sequencer_utils.py b/sequencer_utils.py index e53954a..ae44863 100644 --- a/sequencer_utils.py +++ b/sequencer_utils.py @@ -16,6 +16,19 @@ import subprocess from collections import defaultdict +def get_render_attributes(): + scn = bpy.context.scene + return [ + (scn.view_settings, "view_transform", 'Standard'), + (scn.render.image_settings, "file_format", 'FFMPEG'), + (scn.render.ffmpeg, "gopsize", 8), + (scn.render.ffmpeg, "constant_rate_factor", 'HIGH'), + (scn.render.ffmpeg, "format", 'QUICKTIME'), + (scn.render.ffmpeg, "audio_codec", 'MP3'), + (scn.render.ffmpeg, "audio_mixrate", 44100), + (scn.render.ffmpeg, "audio_bitrate", 128) + ] + def frame_to_timecode(frame, fps): total_seconds = frame / fps @@ -109,20 +122,23 @@ def rename_strips(strips, template, increment=10, start_number=0, padding=3, by_ strip_number = 0 for strip in strips: + channel = get_channel_name(strip) sequence_name = get_strip_sequence_name(strip) - sequence_data = parse(project.sequence_template, sequence_name) + format_data = {} + if channel == 'Shots': + format_data = parse(project.sequence_template, sequence_name) + else: + format_data['sequence'] = str(strip_number*increment + start_number).zfill(padding) if (by_sequence and prev_sequence_name and sequence_name and sequence_name != prev_sequence_name): strip_number = 0 - format_data = dict( + format_data.update( sequence_strip=sequence_name, episode=episode_name, shot=str(strip_number*increment + start_number).zfill(padding)) - format_data.update(sequence_data) - name = template.format(**format_data) existing_strip = scn.sequence_editor.sequences_all.get(name) @@ -270,26 +286,32 @@ def render_sound(strip, output): scn.frame_end = scene_end scn.frame_current = scene_current -def render_scene(output): - output = os.path.abspath(bpy.path.abspath(output)) +def render_scene(output, attributes=None): + output = os.path.abspath(bpy.path.abspath(str(output))) scn = bpy.context.scene render_path = scn.render.filepath - scn.render.filepath = output + if attributes is None: + attributes = [] + + attributes += [ + (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) - - scn.render.filepath = render_path -def render_strip(strip, output): - output = os.path.abspath(bpy.path.abspath(output)) +def render_strip(strip, output, attributes=None): + output = os.path.abspath(bpy.path.abspath(str(output))) scn = bpy.context.scene - attributes = [ + if attributes is None: + attributes = [] + + attributes += [ (scn, "frame_start", strip.frame_final_start), (scn, "frame_end", strip.frame_final_end - 1), (scn, "frame_current"), @@ -316,7 +338,6 @@ def import_edit(filepath, adapter="cmx_3600", channel='Shots', match_by='name'): for s in strips: s.channel = empty_channel - edl = Path(filepath) try: timeline = opentimelineio.adapters.read_from_file( @@ -397,11 +418,14 @@ def import_edit(filepath, adapter="cmx_3600", channel='Shots', match_by='name'): scn.frame_end = frame_end-1 return timeline -def import_movie(filepath): +def import_movie(filepath, frame_start=None, frame_end=None): scn = bpy.context.scene + if frame_start is None: + frame_start = scn.frame_start + res_x = scn.render.resolution_x - res_y = scn.render.resolution_y + res_y = scn.render.resolution_y if bpy.data.is_saved: relpath = Path(bpy.path.relpath(str(filepath))) @@ -412,7 +436,7 @@ def import_movie(filepath): name=Path(filepath).stem, filepath=str(filepath), channel=get_channel_index('Movie'), - frame_start=scn.frame_start + frame_start=frame_start ) elem = strip.strip_elem_from_frame(scn.frame_current) @@ -423,7 +447,8 @@ def import_movie(filepath): if src_height != res_y: strip.transform.scale_y = (res_y / src_height) - + if frame_end is not None: + strip.frame_final_end = frame_end return strip diff --git a/ui/panels.py b/ui/panels.py index 002a98e..755f653 100644 --- a/ui/panels.py +++ b/ui/panels.py @@ -39,20 +39,52 @@ class VSETB_PT_main(VSETB_main, Panel): # row.operator('vse_toolbox.load_projects', icon='FILE_REFRESH', text='', emboss=False) def draw(self, context): + layout = self.layout wm = context.window_manager scn = context.scene settings = get_scene_settings() prefs = get_addon_prefs() - row = self.layout.row(align=False) + row = layout.row(align=True) row.operator('vse_toolbox.load_projects', icon='FILE_REFRESH', text='', emboss=False) + row.separator(factor=0.5) row.prop(settings, 'project_name', text='') project = settings.active_project if project and project.type == 'TVSHOW': + row.separator(factor=0.5) row.prop(project, 'episode_name', text='') + + row.separator(factor=0.5) + row.prop(project, "show_settings", icon="PREFERENCES", text='') + if project.show_settings: + box = layout.box() + split = box.split(factor=0.3) + name_col = split.column() + template_col = split.column() + + row = name_col.row(align=True) # Padding + row.separator(factor=0.5) + row.label(text="Name") + + row = template_col.row(align=True) # Padding + row.separator(factor=0.5) + row.label(text="Template") + + row.operator("vse_toolbox.add_template", text="", icon='ADD', emboss=False) + + for i, template in enumerate(project.templates): + row = name_col.row() + row.prop(template, "name", text="") + + row = template_col.row(align=True) + subrow = row.row() + subrow.prop(template, "value", text="") + row.separator(factor=0.25) + row.operator("vse_toolbox.remove_template", text="", icon='REMOVE', emboss=False).index = i + # settings = get_scene_settings() # prefs = get_addon_prefs() @@ -173,7 +205,7 @@ class VSETB_PT_settings(VSETB_main, Panel): class VSETB_PT_imports(VSETB_main, Panel): bl_label = "Imports" #bl_parent_id = "VSETB_PT_main" - bl_options = {'DEFAULT_CLOSED'} + #bl_options = {'DEFAULT_CLOSED'} def draw_header_preset(self, context): self.layout.operator('vse_toolbox.import_files', icon='IMPORT', text='', emboss=False) diff --git a/ui/preferences.py b/ui/preferences.py index d895d8d..30252b3 100644 --- a/ui/preferences.py +++ b/ui/preferences.py @@ -63,6 +63,7 @@ def load_trackers(): print(f'Could not register Tracker {name}') print(e) + def load_prefs(): prefs = get_addon_prefs() prefs_config_file = prefs.config_path @@ -72,6 +73,9 @@ def load_prefs(): prefs_datas = read_file(os.path.expandvars(prefs_config_file)) + if not prefs_datas: + return + for tracker_data in prefs_datas['trackers']: tracker_name = norm_str(tracker_data['name']) if not hasattr(prefs.trackers, tracker_name): diff --git a/ui/properties.py b/ui/properties.py index 287006a..bfa6f40 100644 --- a/ui/properties.py +++ b/ui/properties.py @@ -159,6 +159,10 @@ class Episode(PropertyGroup): return self.get(settings.project_name) +class Template(PropertyGroup): + value : StringProperty() + + class SpreadsheetExportCell(PropertyGroup): export_name : StringProperty() enabled : BoolProperty(default=True) @@ -241,19 +245,21 @@ def get_task_type_items(self, context): class ImportShots(PropertyGroup): import_source: EnumProperty(items=[(i, i.title(), '') for i in ('DISK', 'TRACKER')]) import_task: EnumProperty(items=[(i, i.title().replace('_', ' '), '') - for i in ('LAST', 'FROM_LIST', 'ALL')], default='FROM_LIST') + for i in ('FROM_LIST',)], default='FROM_LIST') #('LAST', 'FROM_LIST', 'ALL') #sequence_dir_template: StringProperty( # name="Sequence Template", default="$PROJECT_ROOT/sequences/{sequence}") - shot_folder_template: StringProperty( - name="Shot Template", default="$PROJECT_ROOT/sequences/sq{sequence}/sh{shot}") + #shot_folder_template: StringProperty( + # name="Shot Template", default="$PROJECT_ROOT/sequences/sq{sequence}/sh{shot}") - import_video_template : StringProperty( - name="Video Path", default="./{task}/render/{version}.{ext}") + video_template : StringProperty( + name="Video Path", default="//sources/{sequence}_{shot}_{task}.{ext}") + + video_templates : CollectionProperty(type=PropertyGroup) import_sequence: EnumProperty(items=[(i, i.title().replace('_', ' '), '') - for i in ('SELECTED_STRIPS', 'FROM_LIST', 'ALL')], default='FROM_LIST') + for i in ('FROM_LIST',)], default='FROM_LIST') #('SELECTED_STRIPS', 'FROM_LIST', 'ALL') previews_folder: StringProperty( name="Previews Folder", default="//sources", subtype='DIR_PATH') @@ -262,7 +268,7 @@ class ImportShots(PropertyGroup): class UploadToTracker(PropertyGroup): render_strips: BoolProperty(default=False) render_strip_template : StringProperty( - name="Movie Path", default="//render/{project_basename}.{ext}") + name="Movie Path", default="//render/{strip}.{ext}") task : EnumProperty(items=get_task_type_items) status : EnumProperty(items=get_task_status_items) @@ -297,6 +303,9 @@ def on_episode_updated(self, context): class Project(PropertyGroup): id : StringProperty(default='') + show_settings : BoolProperty() + templates : CollectionProperty(type=Template) + shot_start_number : IntProperty(name="Shot Start Number", default=10, min=0) shot_padding : IntProperty(name="Shot Padding", default=4, min=0, max=10) @@ -316,28 +325,28 @@ class Project(PropertyGroup): name="Shot Increment", default=10, min=0, step=10) sequence_template : StringProperty( - name="Sequence Name", default="sq{sequence}") + name="Sequence Name", default="{sequence}") episode_template : StringProperty( - name="Episode Name", default="ep{episode}") + name="Episode Name", default="{episode}") shot_template : StringProperty( - name="Shot Name", default="sq{sequence}_sh{shot}") + name="Shot Name", default="{sequence}_{shot}") render_video_template : StringProperty( - name="Movie Path", default="//render/{project_basename}.{ext}") + name="Movie Path", default="//render/{project}.mov") render_audio_template : StringProperty( - name="Audio Path", default="//render/{project_basename}.wav") + name="Audio Path", default="//render/{project}.wav") render_video_strip_template : StringProperty( - name="Strip Path", default="//render/shots/{strip}.{ext}") + name="Strip Path", default="//render/shots/{strip}.mov") render_audio_strip_template: StringProperty( name="Sound Path", default="//render/sounds/{strip}.wav") render_video_sequence_template : StringProperty( - name="Sequence Path", default="//render/sequences/{sequence}.{ext}") + name="Sequence Path", default="//render/sequences/{sequence}.mov") render_audio_sequence_template: StringProperty( name="Sound Path", default="//render/sounds/{sequence}.wav") @@ -359,7 +368,7 @@ class Project(PropertyGroup): #render_sound_format: EnumProperty(items=[(e, e, '') for e in ('mp3', 'wav')]) export_edl_template : StringProperty( - name="Edl Path", default="//render/{project_basename}.edl") + name="Edl Path", default="//render/{project}.edl") episode_name : EnumProperty(items=get_episodes_items, update=on_episode_updated) episodes : CollectionProperty(type=Episode) @@ -394,6 +403,9 @@ class Project(PropertyGroup): data['episode'] = norm_str(self.episode_name) data['project_basename'] = f"{data['project']}_{data['episode']}" + #for template in self.templates: + # data[template.name] = template.value + return data def get_cell_types(self): @@ -745,6 +757,7 @@ classes = ( AssetType, TaskStatus, Episode, + Template, Metadata, MetadataType, Sequence,