add generic templates

This commit is contained in:
ChristopheSeux
2024-04-16 14:28:02 +02:00
parent 2960bab63d
commit 0055e0f39e
11 changed files with 343 additions and 116 deletions
+67 -13
View File
@@ -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():
+28 -13
View File
@@ -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"}
+79 -26
View File
@@ -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,
+13 -3
View File
@@ -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)
+34 -18
View File
@@ -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()