add generic templates

pull/5/head
ChristopheSeux 2024-04-16 14:28:02 +02:00
parent 2960bab63d
commit 0055e0f39e
11 changed files with 343 additions and 116 deletions

View File

@ -164,19 +164,24 @@ def parse(template, string):
if result := re.match(reg, string): if result := re.match(reg, string):
return result.groupdict() return result.groupdict()
def find_last(template, **kargs): def expand(template, **kargs):
#print(find_last, template, kargs) template = Path(template).as_posix()
pattern = Path(template).as_posix() template = expandvars(template)
pattern = expandvars(pattern)
for key, value in kargs.items(): 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'{\w+:(\d{2})d}', lambda x : '?' * int(x.groups()[0]), pattern)
pattern = re.sub(r'{.*?}', '*', pattern) pattern = re.sub(r'{.*?}', '*', pattern)
print(pattern)
filepaths = glob.glob(pattern) filepaths = glob.glob(pattern)
if filepaths: if filepaths:
return Path(max(filepaths)) return Path(max(filepaths))
else:
print(f'No preview found for template {pattern}')

View File

@ -5,6 +5,7 @@ import os
import bpy import bpy
from bpy.types import Operator from bpy.types import Operator
from bpy.props import IntProperty
import vse_toolbox import vse_toolbox
@ -37,6 +38,21 @@ class VSETB_OT_load_settings(Operator):
bl_label = 'Load Settings' bl_label = 'Load Settings'
bl_description = 'Load VSE ToolBox settings from config file' 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): def execute(self, context):
prefs = get_addon_prefs() prefs = get_addon_prefs()
settings = get_scene_settings() settings = get_scene_settings()
@ -48,6 +64,10 @@ class VSETB_OT_load_settings(Operator):
if not addon_config: if not addon_config:
return {'CANCELLED'} 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') addon_config['trackers'] = addon_config.get('trackers')
trackers_config = addon_config.pop('trackers') trackers_config = addon_config.pop('trackers')
@ -61,11 +81,10 @@ class VSETB_OT_load_settings(Operator):
for k, v in tracker_config.items(): for k, v in tracker_config.items():
setattr(tracker, k, v) setattr(tracker, k, v)
addon_config['spreadsheet_export'] = addon_config.get('spreadsheet_export', {}) spreadsheet_export_config = addon_config.pop('spreadsheet_export', {})
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', {})
addon_config['spreadsheet_import'] = addon_config.get('spreadsheet_import', {}) import_shots_config = addon_config.pop('import_shots', {})
spreadsheet_import_config = addon_config.pop('spreadsheet_import')
project_name = addon_config.get('project_name') project_name = addon_config.get('project_name')
if project_name not in settings.projects: if project_name not in settings.projects:
@ -77,12 +96,7 @@ class VSETB_OT_load_settings(Operator):
# Project Properties # Project Properties
project = settings.active_project project = settings.active_project
for k, v in addon_config.items(): self.set_config(project, addon_config)
try:
setattr(project, k, v)
except Exception:
print(f'Could not set property {k} with value {v} to project {settings.project_name}')
export_cells = project.spreadsheet_export.cells export_cells = project.spreadsheet_export.cells
for k, v in spreadsheet_export_config.items(): for k, v in spreadsheet_export_config.items():
@ -148,13 +162,53 @@ class VSETB_OT_load_settings(Operator):
except Exception: except Exception:
print(f'Could not set option {k} with value {v} to spreadsheet') 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'} return {'FINISHED'}
classes = ( classes = (
VSETB_OT_reload_addon, VSETB_OT_reload_addon,
VSETB_OT_load_settings VSETB_OT_load_settings,
VSETB_OT_add_template,
VSETB_OT_remove_template
) )
def register(): def register():

View File

@ -8,9 +8,10 @@ import bpy
from bpy.types import Operator from bpy.types import Operator
from bpy.props import BoolProperty 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.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() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
project_templates = {t.name: t.value for t in project.templates}
format_data = {**settings.format_data, **project.format_data} 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() start_time = time.perf_counter()
if project.render_single_file: if project.render_single_file:
single_file = True
if project.render_video: 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) video_path = video_template.format(**format_data)
render_scene(video_path) render_scene(video_path, attributes=render_attrs)
#background_render(output=video_path) #background_render(output=video_path)
if project.render_audio: 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) audio_path = audio_template.format(**format_data)
bpy.ops.sound.mixdown(filepath=audio_path) bpy.ops.sound.mixdown(filepath=audio_path)
if project.render_sequence: if project.render_sequence:
print('Render Sequences...') 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) #print(strip.name)
strip_settings = strip.vsetb_strip_settings strip_settings = strip.vsetb_strip_settings
strip_data = {**format_data, **strip_settings.format_data} strip_data = {**format_data, **strip_settings.format_data}
if project.render_sequence and project.render_video_per_sequence: 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) 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: 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) audio_render_path = audio_sequence_template.format(**strip_data)
render_sound(strip, audio_render_path) render_sound(strip, audio_render_path)
if project.render_shot: 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_settings = strip.vsetb_strip_settings
strip_data = {**format_data, **strip_settings.format_data} strip_data = {**format_data, **strip_settings.format_data}
if project.render_video_per_strip: 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) 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: 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) audio_render_path = audio_strip_template.format(**strip_data)
render_sound(strip, audio_render_path) 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') self.report({"INFO"}, f'Strips rendered in {time.perf_counter()-start_time} seconds')
return {"FINISHED"} return {"FINISHED"}

View File

@ -7,7 +7,7 @@ from tempfile import gettempdir
import bpy import bpy
from bpy.types import Operator, UIList 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, from vse_toolbox.constants import (EDITS, EDIT_SUFFIXES, MOVIES, MOVIE_SUFFIXES,
SOUNDS, SOUND_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) 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.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.file_utils import install_module, parse, find_last, expand
#from vse_toolbox.template import Template
class VSETB_OT_auto_select_files(Operator): class VSETB_OT_auto_select_files(Operator):
bl_idname = "vse_toolbox.auto_select_files" bl_idname = "vse_toolbox.auto_select_files"
@ -254,6 +254,38 @@ class VSETB_OT_unselect_task(Operator):
return {'FINISHED'} 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): def get_sequence_items(self, context):
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
@ -351,6 +383,9 @@ class VSETB_OT_import_shots(Operator):
if not last_comment: if not last_comment:
return return
last_preview = last_comment['previews'][0]
ext = last_preview['extension']
shot_name = shot['name'] shot_name = shot['name']
sequence_name = f"{shot['sequence_name']}_" sequence_name = f"{shot['sequence_name']}_"
if shot_name.startswith(sequence_name): if shot_name.startswith(sequence_name):
@ -358,10 +393,9 @@ class VSETB_OT_import_shots(Operator):
preview_dir = self.get_preview_dir() 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) filepath.parent.mkdir(parents=True, exist_ok=True)
last_preview = last_comment['previews'][0]
tracker.download_preview_file(last_preview, str(filepath)) tracker.download_preview_file(last_preview, str(filepath))
return filepath return filepath
@ -378,17 +412,20 @@ class VSETB_OT_import_shots(Operator):
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
import_shots = project.import_shots import_shots = project.import_shots
project_templates = {t.name: t.value for t in project.templates}
pattern = Path( for template in [import_shots.video_template] + list(import_shots.video_templates.keys()):
import_shots.shot_folder_template,
import_shots.import_video_template) template = expand(template, **project_templates)
# Normalize name in snake_case for now format_data = project.format_data
sequence = sequence.name.lower().replace(' ', '_') format_data.update(parse(project.sequence_template, sequence.name))
shot = parse(project.shot_template, shot_name)['shot'] format_data.update(parse(project.shot_template, shot_name))
task = task_type.name.lower().replace(' ', '_') # 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): def execute(self, context):
scn = context.scene scn = context.scene
@ -405,6 +442,9 @@ class VSETB_OT_import_shots(Operator):
conformed = False conformed = False
scn.sequence_editor_clear()
scn.sequence_editor_create()
self.set_sequencer_channels([t.name for t in task_types]) self.set_sequencer_channels([t.name for t in task_types])
frame_index = 1 frame_index = 1
for sequence in sequences: for sequence in sequences:
@ -412,6 +452,7 @@ class VSETB_OT_import_shots(Operator):
sequence_start = frame_index sequence_start = frame_index
for shot_data in shots_data: for shot_data in shots_data:
frames = int(shot_data['nb_frames']) frames = int(shot_data['nb_frames'])
frame_end = frame_index + frames
strip = scn.sequence_editor.sequences.new_effect( strip = scn.sequence_editor.sequences.new_effect(
name=shot_data['name'], name=shot_data['name'],
@ -425,25 +466,23 @@ class VSETB_OT_import_shots(Operator):
for task_type in task_types: for task_type in task_types:
if import_shots.import_source == 'DISK': if import_shots.import_source == 'DISK':
preview = self.find_shot_preview(sequence, shot_data['name'], task_type) preview = self.find_shot_preview(sequence, shot_data['name'], task_type)
#print(videos)
#return {"FINISHED"}
else: else:
preview = self.download_preview(task_type, shot_data) preview = self.download_preview(task_type, shot_data)
if not preview:
print(f'No preview found for shot {shot_data["name"]}')
if not preview: if not preview:
print(f'No preview found for shot {shot_data["name"]}')
continue continue
print(f'Loading Preview from {preview}') print(f'Loading Preview from {preview}')
# Load Video
channel_index = get_channel_index(f'{task_type.name} Video') channel_index = get_channel_index(f'{task_type.name} Video')
video_clip = import_movie(preview) video_clip = import_movie(preview, frame_start=frame_index, frame_end=frame_end)
video_clip.frame_start = frame_index
video_clip.channel = channel_index video_clip.channel = channel_index
if video_clip.frame_offset_end:
video_clip.color_tag = 'COLOR_01'
if not conformed: if not conformed:
self.conform_render(video_clip) self.conform_render(video_clip)
conformed = True conformed = True
@ -453,8 +492,10 @@ class VSETB_OT_import_shots(Operator):
# Load Audio # Load Audio
channel_index = get_channel_index(f'{task_type.name} Audio') channel_index = get_channel_index(f'{task_type.name} Audio')
audio_clip = import_sound(preview, frame_start=frame_index, 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 audio_clip.channel = channel_index
if video_clip.frame_offset_end:
audio_clip.color_tag = 'COLOR_01'
frame_index += frames frame_index += frames
@ -471,6 +512,8 @@ class VSETB_OT_import_shots(Operator):
scn.frame_start = 1 scn.frame_start = 1
scn.frame_end = frame_index -1 scn.frame_end = frame_index -1
#bpy.ops.vse_toolbox.set_stamps()
return {'FINISHED'} return {'FINISHED'}
def draw(self, context): def draw(self, context):
@ -489,8 +532,16 @@ class VSETB_OT_import_shots(Operator):
if import_shots.import_source == 'DISK': if import_shots.import_source == 'DISK':
#col.prop(import_shots, "sequence_dir_template", text='Sequence Dir') #col.prop(import_shots, "sequence_dir_template", text='Sequence Dir')
col.prop(import_shots, "shot_folder_template", text='Shot Folder') #col.prop(import_shots, "shot_folder_template", text='Shot Folder')
col.prop(import_shots, "import_video_template", text='Video') 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() col.separator()
else: else:
col.prop(import_shots, "previews_folder", text='Previews Folder') 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") # self.report({"ERROR"}, "Save your Blender file first")
# return {"CANCELLED"} # 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): def check(self, context):
return True return True
@ -590,6 +641,8 @@ classes = (
VSETB_OT_unselect_sequence, VSETB_OT_unselect_sequence,
VSETB_OT_unselect_task, VSETB_OT_unselect_task,
VSETB_OT_select_task, VSETB_OT_select_task,
VSETB_OT_add_import_template,
VSETB_OT_remove_import_template,
VSETB_UL_import_task, VSETB_UL_import_task,
VSETB_OT_auto_select_files, VSETB_OT_auto_select_files,
VSETB_OT_import_files, VSETB_OT_import_files,

View File

@ -155,7 +155,7 @@ class VSETB_OT_set_sequencer(Operator):
scn.view_settings.view_transform = 'Standard' scn.view_settings.view_transform = 'Standard'
scn.render.image_settings.file_format = 'FFMPEG' 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.constant_rate_factor = 'HIGH'
scn.render.ffmpeg.format = 'QUICKTIME' scn.render.ffmpeg.format = 'QUICKTIME'
scn.render.ffmpeg.audio_codec = 'AAC' scn.render.ffmpeg.audio_codec = 'AAC'
@ -304,8 +304,18 @@ class VSETB_OT_open_shot_folder(Operator):
@classmethod @classmethod
def poll(cls, context): 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 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): def execute(self, context):
strip = context.active_sequence_strip 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} 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) shot_folder_path = shot_folder_template.format(**format_data)
bpy.ops.wm.path_open(filepath=shot_folder_path) bpy.ops.wm.path_open(filepath=shot_folder_path)

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.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.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 from bpy.app.handlers import persistent
@ -36,7 +37,7 @@ class VSETB_OT_tracker_connect(Operator):
settings = get_scene_settings() settings = get_scene_settings()
try: try:
prefs.tracker.connect() 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"} return {"FINISHED"}
except Exception as e: except Exception as e:
print('e: ', 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 (r,g,b), a = map(lambda component: component / 255, bytes.fromhex(color_str[-6:])), 1.0
return (r,g,b,a) return (r,g,b,a)
def invoke(self, context, event):
self.ctrl = event.ctrl
return self.execute(context)
def execute(self, context): def execute(self, context):
settings = get_scene_settings() settings = get_scene_settings()
prefs = get_addon_prefs() prefs = get_addon_prefs()
@ -213,14 +218,16 @@ class VSETB_OT_load_projects(Operator):
if project.name not in project_names: if project.name not in project_names:
settings.projects.remove(list(settings.projects).index(project)) 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: if prev_project_name != '/' and prev_project_name in settings.projects:
settings.project_name = prev_project_name settings.project_name = prev_project_name
#if settings.active_project: #if settings.active_project:
# settings.active_project.set_strip_metadata() # settings.active_project.set_strip_metadata()
settings['projects_loaded'] = True
self.report({"INFO"}, 'Successfully Load Tracker Projects') self.report({"INFO"}, 'Successfully Load Tracker Projects')
@ -251,19 +258,16 @@ class VSETB_OT_new_episode(Operator):
tracker = prefs.tracker tracker = prefs.tracker
episode_name = settings.episode_template.format(index=int(self.episode_name)) 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) episode = tracker.get_episode(episode_name)
if episode: if episode:
self.report({'ERROR'}, f'Episode {episode_name} already exists') self.report({'ERROR'}, f'Episode {episode_name} already exists')
return {"CANCELLED"} return {"CANCELLED"}
tracker.new_episode(episode_name) tracker.new_episode(episode_name)
# tracker.get_episodes
tracker.update_project() tracker.update_project()
self.report({'INFO'}, f'Episode {episode_name} successfully created') self.report({'INFO'}, f'Episode {episode_name} successfully created')
return {'FINISHED'} return {'FINISHED'}
@ -302,8 +306,6 @@ class VSETB_OT_upload_to_tracker(Operator):
col.use_property_split = False col.use_property_split = False
col.prop(upload_to_tracker, 'render_strip_template', text='') col.prop(upload_to_tracker, 'render_strip_template', text='')
col.use_property_split = True col.use_property_split = True
#else:
# col.label(text=f'Source: {self.project.render_video_strip_template}')
col.separator() col.separator()
@ -329,21 +331,27 @@ class VSETB_OT_upload_to_tracker(Operator):
#self.report({'ERROR'}, f'Export not implemented yet.') #self.report({'ERROR'}, f'Export not implemented yet.')
prefs = get_addon_prefs() prefs = get_addon_prefs()
settings = get_scene_settings() settings = get_scene_settings()
tracker = prefs.tracker
upload_to_tracker = self.project.upload_to_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 episode = None
if settings.active_episode: if settings.active_episode:
episode = settings.active_episode.id episode = settings.active_episode.id
format_data = {**settings.format_data, **self.project.format_data} format_data = {**settings.format_data, **self.project.format_data}
tracker = prefs.tracker
status = upload_to_tracker.status status = upload_to_tracker.status
if status == 'CURRENT': if status == 'CURRENT':
status = None 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 strip_settings = strip.vsetb_strip_settings
sequence_name = get_strip_sequence_name(strip) sequence_name = get_strip_sequence_name(strip)
shot_name = strip.name shot_name = strip.name
@ -369,12 +377,17 @@ class VSETB_OT_upload_to_tracker(Operator):
strip_data = {**format_data, **strip_settings.format_data} strip_data = {**format_data, **strip_settings.format_data}
if upload_to_tracker.render_strips: 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: else:
preview_template = expandvars(self.project.render_video_strip_template) preview_template = expandvars(self.project.render_video_strip_template)
preview = preview_template.format(**strip_data) preview = preview_template.format(**strip_data)
preview = Path(os.path.abspath(bpy.path.abspath(preview))) preview = Path(os.path.abspath(bpy.path.abspath(preview)))
if upload_to_tracker.render_strips:
render_strip(strip, preview, attributes=render_attrs)
#print(preview) #print(preview)
if not preview.exists(): if not preview.exists():
print(f'The preview {preview} not exists') print(f'The preview {preview} not exists')
@ -387,7 +400,7 @@ class VSETB_OT_upload_to_tracker(Operator):
preview = None preview = None
comment_data = 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) comment_data = tracker.new_comment(task, comment=upload_to_tracker.comment, status=status)
if preview: if preview:
print('Upload preview from', 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: if task.comment and tracker_task.get('last_comment') != task.comment:
tracker.new_comment(tracker_task, comment=task.comment) tracker.new_comment(tracker_task, comment=task.comment)
context.window_manager.progress_end()
return {"FINISHED"} return {"FINISHED"}
@ -448,6 +463,7 @@ class VSETB_OT_open_shot_on_tracker(Operator):
return {"FINISHED"} return {"FINISHED"}
@persistent @persistent
def set_asset_items(scene=None): def set_asset_items(scene=None):
ASSET_ITEMS.clear() ASSET_ITEMS.clear()

View File

@ -78,7 +78,7 @@ class Kitsu(Tracker):
print(f'Info: Log in to kitsu as {login}') print(f'Info: Log in to kitsu as {login}')
res = gazu.log_in(login, password) res = gazu.log_in(login, password)
LOGIN = login 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'] return res['user']
except Exception as e: except Exception as e:
print(f'Error: {traceback.format_exc()}') print(f'Error: {traceback.format_exc()}')

View File

@ -16,6 +16,19 @@ import subprocess
from collections import defaultdict 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): def frame_to_timecode(frame, fps):
total_seconds = 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 strip_number = 0
for strip in strips: for strip in strips:
channel = get_channel_name(strip)
sequence_name = get_strip_sequence_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 if (by_sequence and prev_sequence_name and
sequence_name and sequence_name != prev_sequence_name): sequence_name and sequence_name != prev_sequence_name):
strip_number = 0 strip_number = 0
format_data = dict( format_data.update(
sequence_strip=sequence_name, sequence_strip=sequence_name,
episode=episode_name, episode=episode_name,
shot=str(strip_number*increment + start_number).zfill(padding)) shot=str(strip_number*increment + start_number).zfill(padding))
format_data.update(sequence_data)
name = template.format(**format_data) name = template.format(**format_data)
existing_strip = scn.sequence_editor.sequences_all.get(name) 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_end = scene_end
scn.frame_current = scene_current scn.frame_current = scene_current
def render_scene(output): def render_scene(output, attributes=None):
output = os.path.abspath(bpy.path.abspath(output)) output = os.path.abspath(bpy.path.abspath(str(output)))
scn = bpy.context.scene scn = bpy.context.scene
render_path = scn.render.filepath 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}') print(f'Render Strip to {scn.render.filepath}')
Path(output).parent.mkdir(exist_ok=True, parents=True) Path(output).parent.mkdir(exist_ok=True, parents=True)
bpy.ops.render.opengl(animation=True, sequencer=True) bpy.ops.render.opengl(animation=True, sequencer=True)
scn.render.filepath = render_path
def render_strip(strip, output): def render_strip(strip, output, attributes=None):
output = os.path.abspath(bpy.path.abspath(output)) output = os.path.abspath(bpy.path.abspath(str(output)))
scn = bpy.context.scene scn = bpy.context.scene
attributes = [ if attributes is None:
attributes = []
attributes += [
(scn, "frame_start", strip.frame_final_start), (scn, "frame_start", strip.frame_final_start),
(scn, "frame_end", strip.frame_final_end - 1), (scn, "frame_end", strip.frame_final_end - 1),
(scn, "frame_current"), (scn, "frame_current"),
@ -316,7 +338,6 @@ def import_edit(filepath, adapter="cmx_3600", channel='Shots', match_by='name'):
for s in strips: for s in strips:
s.channel = empty_channel s.channel = empty_channel
edl = Path(filepath) edl = Path(filepath)
try: try:
timeline = opentimelineio.adapters.read_from_file( 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 scn.frame_end = frame_end-1
return timeline return timeline
def import_movie(filepath): def import_movie(filepath, frame_start=None, frame_end=None):
scn = bpy.context.scene scn = bpy.context.scene
if frame_start is None:
frame_start = scn.frame_start
res_x = scn.render.resolution_x res_x = scn.render.resolution_x
res_y = scn.render.resolution_y res_y = scn.render.resolution_y
if bpy.data.is_saved: if bpy.data.is_saved:
relpath = Path(bpy.path.relpath(str(filepath))) relpath = Path(bpy.path.relpath(str(filepath)))
@ -412,7 +436,7 @@ def import_movie(filepath):
name=Path(filepath).stem, name=Path(filepath).stem,
filepath=str(filepath), filepath=str(filepath),
channel=get_channel_index('Movie'), channel=get_channel_index('Movie'),
frame_start=scn.frame_start frame_start=frame_start
) )
elem = strip.strip_elem_from_frame(scn.frame_current) elem = strip.strip_elem_from_frame(scn.frame_current)
@ -423,7 +447,8 @@ def import_movie(filepath):
if src_height != res_y: if src_height != res_y:
strip.transform.scale_y = (res_y / src_height) strip.transform.scale_y = (res_y / src_height)
if frame_end is not None:
strip.frame_final_end = frame_end
return strip return strip

View File

@ -39,20 +39,52 @@ class VSETB_PT_main(VSETB_main, Panel):
# row.operator('vse_toolbox.load_projects', icon='FILE_REFRESH', text='', emboss=False) # row.operator('vse_toolbox.load_projects', icon='FILE_REFRESH', text='', emboss=False)
def draw(self, context): def draw(self, context):
layout = self.layout
wm = context.window_manager wm = context.window_manager
scn = context.scene scn = context.scene
settings = get_scene_settings() settings = get_scene_settings()
prefs = get_addon_prefs() 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.operator('vse_toolbox.load_projects', icon='FILE_REFRESH', text='', emboss=False)
row.separator(factor=0.5)
row.prop(settings, 'project_name', text='') row.prop(settings, 'project_name', text='')
project = settings.active_project project = settings.active_project
if project and project.type == 'TVSHOW': if project and project.type == 'TVSHOW':
row.separator(factor=0.5)
row.prop(project, 'episode_name', text='') 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() # settings = get_scene_settings()
# prefs = get_addon_prefs() # prefs = get_addon_prefs()
@ -173,7 +205,7 @@ class VSETB_PT_settings(VSETB_main, Panel):
class VSETB_PT_imports(VSETB_main, Panel): class VSETB_PT_imports(VSETB_main, Panel):
bl_label = "Imports" bl_label = "Imports"
#bl_parent_id = "VSETB_PT_main" #bl_parent_id = "VSETB_PT_main"
bl_options = {'DEFAULT_CLOSED'} #bl_options = {'DEFAULT_CLOSED'}
def draw_header_preset(self, context): def draw_header_preset(self, context):
self.layout.operator('vse_toolbox.import_files', icon='IMPORT', text='', emboss=False) self.layout.operator('vse_toolbox.import_files', icon='IMPORT', text='', emboss=False)

View File

@ -63,6 +63,7 @@ def load_trackers():
print(f'Could not register Tracker {name}') print(f'Could not register Tracker {name}')
print(e) print(e)
def load_prefs(): def load_prefs():
prefs = get_addon_prefs() prefs = get_addon_prefs()
prefs_config_file = prefs.config_path prefs_config_file = prefs.config_path
@ -72,6 +73,9 @@ def load_prefs():
prefs_datas = read_file(os.path.expandvars(prefs_config_file)) prefs_datas = read_file(os.path.expandvars(prefs_config_file))
if not prefs_datas:
return
for tracker_data in prefs_datas['trackers']: for tracker_data in prefs_datas['trackers']:
tracker_name = norm_str(tracker_data['name']) tracker_name = norm_str(tracker_data['name'])
if not hasattr(prefs.trackers, tracker_name): if not hasattr(prefs.trackers, tracker_name):

View File

@ -159,6 +159,10 @@ class Episode(PropertyGroup):
return self.get(settings.project_name) return self.get(settings.project_name)
class Template(PropertyGroup):
value : StringProperty()
class SpreadsheetExportCell(PropertyGroup): class SpreadsheetExportCell(PropertyGroup):
export_name : StringProperty() export_name : StringProperty()
enabled : BoolProperty(default=True) enabled : BoolProperty(default=True)
@ -241,19 +245,21 @@ def get_task_type_items(self, context):
class ImportShots(PropertyGroup): class ImportShots(PropertyGroup):
import_source: EnumProperty(items=[(i, i.title(), '') for i in ('DISK', 'TRACKER')]) import_source: EnumProperty(items=[(i, i.title(), '') for i in ('DISK', 'TRACKER')])
import_task: EnumProperty(items=[(i, i.title().replace('_', ' '), '') 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( #sequence_dir_template: StringProperty(
# name="Sequence Template", default="$PROJECT_ROOT/sequences/{sequence}") # name="Sequence Template", default="$PROJECT_ROOT/sequences/{sequence}")
shot_folder_template: StringProperty( #shot_folder_template: StringProperty(
name="Shot Template", default="$PROJECT_ROOT/sequences/sq{sequence}/sh{shot}") # name="Shot Template", default="$PROJECT_ROOT/sequences/sq{sequence}/sh{shot}")
import_video_template : StringProperty( video_template : StringProperty(
name="Video Path", default="./{task}/render/{version}.{ext}") name="Video Path", default="//sources/{sequence}_{shot}_{task}.{ext}")
video_templates : CollectionProperty(type=PropertyGroup)
import_sequence: EnumProperty(items=[(i, i.title().replace('_', ' '), '') 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( previews_folder: StringProperty(
name="Previews Folder", default="//sources", subtype='DIR_PATH') name="Previews Folder", default="//sources", subtype='DIR_PATH')
@ -262,7 +268,7 @@ class ImportShots(PropertyGroup):
class UploadToTracker(PropertyGroup): class UploadToTracker(PropertyGroup):
render_strips: BoolProperty(default=False) render_strips: BoolProperty(default=False)
render_strip_template : StringProperty( 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) task : EnumProperty(items=get_task_type_items)
status : EnumProperty(items=get_task_status_items) status : EnumProperty(items=get_task_status_items)
@ -297,6 +303,9 @@ def on_episode_updated(self, context):
class Project(PropertyGroup): class Project(PropertyGroup):
id : StringProperty(default='') id : StringProperty(default='')
show_settings : BoolProperty()
templates : CollectionProperty(type=Template)
shot_start_number : IntProperty(name="Shot Start Number", default=10, min=0) shot_start_number : IntProperty(name="Shot Start Number", default=10, min=0)
shot_padding : IntProperty(name="Shot Padding", default=4, min=0, max=10) 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) name="Shot Increment", default=10, min=0, step=10)
sequence_template : StringProperty( sequence_template : StringProperty(
name="Sequence Name", default="sq{sequence}") name="Sequence Name", default="{sequence}")
episode_template : StringProperty( episode_template : StringProperty(
name="Episode Name", default="ep{episode}") name="Episode Name", default="{episode}")
shot_template : StringProperty( shot_template : StringProperty(
name="Shot Name", default="sq{sequence}_sh{shot}") name="Shot Name", default="{sequence}_{shot}")
render_video_template : StringProperty( render_video_template : StringProperty(
name="Movie Path", default="//render/{project_basename}.{ext}") name="Movie Path", default="//render/{project}.mov")
render_audio_template : StringProperty( render_audio_template : StringProperty(
name="Audio Path", default="//render/{project_basename}.wav") name="Audio Path", default="//render/{project}.wav")
render_video_strip_template : StringProperty( 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( render_audio_strip_template: StringProperty(
name="Sound Path", default="//render/sounds/{strip}.wav") name="Sound Path", default="//render/sounds/{strip}.wav")
render_video_sequence_template : StringProperty( 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( render_audio_sequence_template: StringProperty(
name="Sound Path", default="//render/sounds/{sequence}.wav") 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')]) #render_sound_format: EnumProperty(items=[(e, e, '') for e in ('mp3', 'wav')])
export_edl_template : StringProperty( 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) episode_name : EnumProperty(items=get_episodes_items, update=on_episode_updated)
episodes : CollectionProperty(type=Episode) episodes : CollectionProperty(type=Episode)
@ -394,6 +403,9 @@ class Project(PropertyGroup):
data['episode'] = norm_str(self.episode_name) data['episode'] = norm_str(self.episode_name)
data['project_basename'] = f"{data['project']}_{data['episode']}" data['project_basename'] = f"{data['project']}_{data['episode']}"
#for template in self.templates:
# data[template.name] = template.value
return data return data
def get_cell_types(self): def get_cell_types(self):
@ -745,6 +757,7 @@ classes = (
AssetType, AssetType,
TaskStatus, TaskStatus,
Episode, Episode,
Template,
Metadata, Metadata,
MetadataType, MetadataType,
Sequence, Sequence,