rewrite import_shots and cleanup

pull/5/head
ChristopheSeux 2024-04-10 16:15:57 +02:00
parent dae9d55494
commit 2960bab63d
15 changed files with 1068 additions and 269 deletions

View File

@ -5,13 +5,11 @@ bl_info = {
"description": "In-House Video editing Tools", "description": "In-House Video editing Tools",
"author": "Samuel Bernou, Clement Ducarteron, Christophe Seux", "author": "Samuel Bernou, Clement Ducarteron, Christophe Seux",
"version": (0, 1, 0), "version": (0, 1, 0),
"blender": (3, 4, 1), "blender": (4, 0, 2),
"location": "Sequencer", "location": "Sequencer",
"warning": "", "warning": "",
"category": "Sequencer", "category": "Sequencer",
} }
# "doc_url": "https://github.com/Pullusb/REPONAME",
# "tracker_url": "https://github.com/Pullusb/REPONAME/issues",
import sys import sys
from pathlib import Path from pathlib import Path
@ -21,6 +19,7 @@ from vse_toolbox import ui
from vse_toolbox import operators from vse_toolbox import operators
from vse_toolbox.constants import ASSET_PREVIEWS from vse_toolbox.constants import ASSET_PREVIEWS
from vse_toolbox.sequencer_utils import set_active_strip, update_text_strips from vse_toolbox.sequencer_utils import set_active_strip, update_text_strips
from vse_toolbox.bl_utils import get_addon_prefs
modules = ( modules = (
@ -35,6 +34,7 @@ if 'bpy' in locals():
import bpy import bpy
def register(): def register():
bpy.app.handlers.frame_change_post.append(update_text_strips) bpy.app.handlers.frame_change_post.append(update_text_strips)
bpy.app.handlers.render_pre.append(update_text_strips) bpy.app.handlers.render_pre.append(update_text_strips)
@ -44,6 +44,8 @@ def register():
bpy.app.handlers.frame_change_post.append(set_active_strip) bpy.app.handlers.frame_change_post.append(set_active_strip)
prefs = get_addon_prefs()
#print('\n\n-------------------', prefs.config_path)
def unregister(): def unregister():
@ -54,12 +56,11 @@ def unregister():
bpy.utils.previews.remove(ASSET_PREVIEWS) bpy.utils.previews.remove(ASSET_PREVIEWS)
except Exception as e: except Exception as e:
pass pass
# print(f"!-- {e} --!")
# print(f"No preview collection found. {ASSET_PREVIEWS} can't be remove.")
bpy.app.handlers.frame_change_post.remove(set_active_strip) bpy.app.handlers.frame_change_post.remove(set_active_strip)
for module in reversed(modules): for module in reversed(modules):
module.unregister() module.unregister()
if __name__ == "__main__": if __name__ == "__main__":
register() register()

View File

@ -25,6 +25,9 @@ PROJECT_ITEMS = []
EPISODE_ITEMS = [] EPISODE_ITEMS = []
CONFIG_DIR = Path(appdirs.user_config_dir(__package__.split('.')[0])) CONFIG_DIR = Path(appdirs.user_config_dir(__package__.split('.')[0]))
APP_TEMPLATES_DIR = Path(MODULE_DIR, 'resources', 'app_templates')
REVIEW_TEMPLATE_BLEND = Path(APP_TEMPLATES_DIR, 'Review', 'startup.blend')
PREVIEWS_DIR = CONFIG_DIR / 'thumbnails' PREVIEWS_DIR = CONFIG_DIR / 'thumbnails'
TASK_ITEMS = [] TASK_ITEMS = []

View File

@ -5,6 +5,9 @@ import platform
import sys import sys
import unicodedata import unicodedata
from pathlib import Path from pathlib import Path
from os.path import expandvars
import json
import glob
def install_module(module_name, package_name=None): def install_module(module_name, package_name=None):
@ -109,13 +112,15 @@ def read_file(path):
yaml = install_module('yaml') yaml = install_module('yaml')
try: try:
data = yaml.safe_load(txt) data = yaml.safe_load(txt)
except Exception: except Exception as e:
print(e)
print(f'Could not load yaml file {path}') print(f'Could not load yaml file {path}')
return return
elif path.suffix.lower() == '.json': elif path.suffix.lower() == '.json':
try: try:
data = json.loads(txt) data = json.loads(txt)
except Exception: except Exception as e:
print(e)
print(f'Could not load json file {path}') print(f'Could not load json file {path}')
return return
else: else:
@ -142,11 +147,36 @@ def open_file(filepath, env=None, select=False):
subprocess.Popen(cmd, env=env) subprocess.Popen(cmd, env=env)
def parse(string, template): # def parse(string, template):
template = re.sub(r'{index:(.+?)}', r'(?P<index>.+)', template) # template = re.sub(r'{index:(.+?)}', r'(?P<index>.+)', template)
reg = re.sub(r'{(.+?)}', r'(?P<_\1>.+)', template) # reg = re.sub(r'{(.+?)}', r'(?P<_\1>.+)', template)
values = list(re.search(reg, string).groups()) # values = list(re.search(reg, string).groups())
keys = re.findall(r'{(.+?)}', template) + ['index'] # keys = re.findall(r'{(.+?)}', template) + ['index']
return dict(zip(keys, values)) # return dict(zip(keys, values))
def parse(template, string):
reg = re.sub(r'{[^}]*}|\.', lambda m: m.group() if m.group().startswith('{') else r'\.', template)
reg = re.sub(r'{(.+?)}', r'(?P<\1>.*)', reg)
#print(string, template, reg)
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)
for key, value in kargs.items():
pattern = re.sub(r'\{%s\}'%key, value, pattern)
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))

View File

@ -40,15 +40,26 @@ class VSETB_OT_load_settings(Operator):
def execute(self, context): def execute(self, context):
prefs = get_addon_prefs() prefs = get_addon_prefs()
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project
if not prefs.config_path: if not prefs.config_path:
return {'FINISHED'} return {'CANCELLED'}
addon_config = read_file(os.path.expandvars(prefs.config_path)) addon_config = read_file(os.path.expandvars(prefs.config_path))
if not addon_config:
return {'CANCELLED'}
addon_config['trackers'] = addon_config.get('trackers') addon_config['trackers'] = addon_config.get('trackers')
trackers = addon_config.pop('trackers') trackers_config = addon_config.pop('trackers')
for tracker_config in trackers_config:
tracker_name = norm_str(tracker_config.pop('name'))
if not hasattr(prefs.trackers, tracker_name):
continue
tracker = getattr(prefs.trackers, tracker_name)
for k, v in tracker_config.items():
setattr(tracker, k, v)
addon_config['spreadsheet_export'] = addon_config.get('spreadsheet_export', {}) 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')
@ -57,10 +68,15 @@ class VSETB_OT_load_settings(Operator):
spreadsheet_import_config = addon_config.pop('spreadsheet_import') 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: if project_name not in settings.projects:
project = settings.projects.add()
project.name = project_name
settings.project_name = project_name settings.project_name = project_name
#if project_name and project_name in settings.projects:
# settings.project_name = project_name
# Project Properties # Project Properties
project = settings.active_project
for k, v in addon_config.items(): for k, v in addon_config.items():
try: try:
setattr(project, k, v) setattr(project, k, v)
@ -132,6 +148,7 @@ 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')
return {'FINISHED'} return {'FINISHED'}

View File

@ -134,7 +134,7 @@ class VSETB_OT_render(Operator):
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): for strip in get_strips(channel='Sequences', selected_only=project.render_selected_only):
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}

View File

@ -1,5 +1,9 @@
from pathlib import Path from pathlib import Path
from os.path import expandvars
import re
import glob
from tempfile import gettempdir
import bpy import bpy
from bpy.types import Operator, UIList from bpy.types import Operator, UIList
@ -9,11 +13,11 @@ from vse_toolbox.constants import (EDITS, EDIT_SUFFIXES, MOVIES, MOVIE_SUFFIXES,
SOUNDS, SOUND_SUFFIXES) SOUNDS, SOUND_SUFFIXES)
from vse_toolbox.sequencer_utils import (clean_sequencer, import_edit, import_movie, from vse_toolbox.sequencer_utils import (clean_sequencer, import_edit, import_movie,
import_sound, get_strips, get_channel_index) import_sound, get_strips, get_channel_index, get_empty_channel, scale_clip_to_fit)
from vse_toolbox.bl_utils import 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 from vse_toolbox.file_utils import install_module, parse, find_last
#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"
@ -194,15 +198,26 @@ class VSETB_UL_import_task(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, def draw_item(self, context, layout, data, item, icon, active_data,
active_propname, index): active_propname, index):
layout.separator(factor=0.5)
layout.prop(item, 'import_enabled', text='') layout.prop(item, 'import_enabled', text='')
layout.label(text=item.name) layout.label(text=item.name)
class VSETB_OT_import_shots(Operator):
bl_idname = "vse_toolbox.import_shots" def get_task_items(self, context):
bl_label = "Import Shots" settings = get_scene_settings()
bl_description = "Import Shots for disk or tracker" project = settings.active_project
return [(t, t, '') for t in project.task_types.keys()]
class VSETB_OT_select_task(Operator):
bl_idname = "vse_toolbox.select_task"
bl_label = "Select Task"
bl_description = "Select Task"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_property = "task"
task: EnumProperty(name="My Search", items=get_task_items)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return True return True
@ -212,76 +227,369 @@ class VSETB_OT_import_shots(Operator):
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
from gadget import Project project.task_types[self.task].import_enabled = True
p = Project.from_env() return {'FINISHED'}
p.shots.fetch()
def invoke(self, context, event):
context.window_manager.invoke_search_popup(self)
return {"FINISHED"}
class VSETB_OT_unselect_task(Operator):
bl_idname = "vse_toolbox.unselect_task"
bl_label = "Unselect Task"
bl_description = "Unselect Task"
bl_options = {"REGISTER", "UNDO"}
task : StringProperty()
def execute(self, context):
scn = context.scene
settings = get_scene_settings()
project = settings.active_project
project.task_types[self.task].import_enabled = False
return {'FINISHED'}
def get_sequence_items(self, context):
settings = get_scene_settings()
project = settings.active_project
return [(t, t, '') for t in project.sequences.keys()]
class VSETB_OT_select_sequence(Operator):
bl_idname = "vse_toolbox.select_sequence"
bl_label = "Select Sequence"
bl_description = "Select Sequence"
bl_options = {"REGISTER", "UNDO"}
bl_property = "sequence"
sequence: EnumProperty(name="My Search", items=get_sequence_items)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
scn = context.scene
settings = get_scene_settings()
project = settings.active_project
project.sequences[self.sequence].import_enabled = True
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.invoke_search_popup(self)
return {"FINISHED"}
class VSETB_OT_unselect_sequence(Operator):
bl_idname = "vse_toolbox.unselect_sequence"
bl_label = "Unselect sequence"
bl_description = "Unselect sequence"
bl_options = {"REGISTER", "UNDO"}
sequence : StringProperty()
def execute(self, context):
scn = context.scene
settings = get_scene_settings()
project = settings.active_project
project.sequences[self.sequence].import_enabled = False
return {'FINISHED'}
class VSETB_OT_import_shots(Operator):
bl_idname = "vse_toolbox.import_shots"
bl_label = "Import Shots"
bl_description = "Import Shots for disk or tracker"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return get_scene_settings().active_project
def set_sequencer_channels(self, task_types):
scn = bpy.context.scene
channels = ['Shots', 'Sequences', 'Stamps']
#channel_index = len(task_types * 3) + len(channels) + 1
channel_index = 1
for task_type in task_types:
audio_channel = scn.sequence_editor.channels[channel_index]
audio_channel.name = f'{task_type} Audio'
audio_channel.mute = (task_type != task_types[-1])
video_channel = scn.sequence_editor.channels[channel_index+1]
video_channel.name = f'{task_type} Video'
tasks = [t for t in project.task_types if t.import_enabled] channel_index += 3
for i, task_type in enumerate(tasks):
channel_index = 9 + i
scn.sequence_editor.channels[channel_index].name = task_type.name for channel in channels:
scn.sequence_editor.channels[channel_index].name = channel
channel_index += 1
# Create Channels: def get_preview_dir(self):
scn.sequence_editor.channels[9] preview_dir = Path(bpy.app.tempdir, 'previews')
if bpy.data.filepath:
preview_dir = Path(bpy.data.filepath).parent / 'previews'
return preview_dir
for strip in get_strips('Shots'): def download_preview(self, task_type, shot):
for i, task_type in enumerate(tasks): prefs = get_addon_prefs()
shot = p.shots[strip.name] tracker = prefs.tracker
versions = shot.tasks[task_type.name].outputs[0].versions.fetch()
if versions: task = tracker.get_task(task_type.id or task_type.name, entity=shot)
print(versions[-1]) last_comment = tracker.get_last_comment_with_preview(task)
if not last_comment:
imported_strip = import_movie(versions[-1].path) return
imported_strip.frame_start = strip.frame_start
if imported_strip.frame_final_duration != strip.frame_final_duration: shot_name = shot['name']
print(f'strip {strip.name} has different duration') sequence_name = f"{shot['sequence_name']}_"
imported_strip.color_tag = 'COLOR_03' if shot_name.startswith(sequence_name):
shot_name = shot_name.replace(sequence_name, '', 1)
preview_dir = self.get_preview_dir()
filepath = Path(preview_dir, f'{sequence_name}{shot_name}_{task_type.name}')
filepath.parent.mkdir(parents=True, exist_ok=True)
last_preview = last_comment['previews'][0]
tracker.download_preview_file(last_preview, str(filepath))
return filepath
def conform_render(self, clip):
scn = bpy.context.scene
scn.render.resolution_x = clip.elements[0].orig_width
scn.render.resolution_y = clip.elements[0].orig_height
scn.render.fps = int(clip.elements[0].orig_fps)
scn.view_settings.view_transform = 'Standard'
def find_shot_preview(self, sequence, shot_name, task_type):
settings = get_scene_settings()
project = settings.active_project
import_shots = project.import_shots
pattern = Path(
import_shots.shot_folder_template,
import_shots.import_video_template)
# 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(' ', '_')
return find_last(pattern, sequence=sequence, shot=shot, task=task)
def execute(self, context):
scn = context.scene
settings = get_scene_settings()
project = settings.active_project
import_shots = project.import_shots
prefs = get_addon_prefs()
tracker = prefs.tracker
tracker.admin_connect()
task_types = [t for t in project.task_types if t.import_enabled]
sequences = [s for s in project.sequences if s.import_enabled]
conformed = False
self.set_sequencer_channels([t.name for t in task_types])
frame_index = 1
for sequence in sequences:
shots_data = tracker.get_shots(sequence=sequence.id)
sequence_start = frame_index
for shot_data in shots_data:
frames = int(shot_data['nb_frames'])
strip = scn.sequence_editor.sequences.new_effect(
name=shot_data['name'],
type='COLOR',
channel=get_channel_index('Shots'),
frame_start=frame_index,
frame_end=frame_index + frames
)
strip.blend_alpha = 0
strip.color = (0.5, 0.5, 0.5)
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"}
imported_strip.frame_final_end = strip.frame_final_end else:
preview = self.download_preview(task_type, shot_data)
imported_strip.channel = get_channel_index(task_type.name) if not preview:
print(get_channel_index(task_type.name), imported_strip.channel) print(f'No preview found for shot {shot_data["name"]}')
else: continue
print(f'No movie for {strip.name} of task {task_type.name}')
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.channel = channel_index
# for task_type in tasks: if not conformed:
# for strip in get_strips(task_type.name): self.conform_render(video_clip)
# strip.channel = get_channel_index(task_type.name) conformed = True
scale_clip_to_fit(video_clip)
# 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)
audio_clip.channel = channel_index
frame_index += frames
strip = scn.sequence_editor.sequences.new_effect(
name=sequence.name,
type='COLOR',
channel=get_channel_index('Sequences'),
frame_start=sequence_start,
frame_end=frame_index
)
strip.blend_alpha = 0
strip.color = (0.25, 0.25, 0.25)
scn.frame_start = 1
scn.frame_end = frame_index -1
return {'FINISHED'} return {'FINISHED'}
def draw(self, context): def draw(self, context):
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
import_shots = project.import_shots
layout = self.layout layout = self.layout
col = layout.column(align=False) col = layout.column(align=False)
col.use_property_split = True col.use_property_split = True
col.use_property_decorate = False col.use_property_decorate = False
row = col.row(align=True) row = col.row(align=True)
row.prop(project, "import_source", text='Source', expand=True) row.prop(import_shots, "import_source", text='Videos Source', expand=True)
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.separator()
else:
col.prop(import_shots, "previews_folder", text='Previews Folder')
col.separator()
#if bpy.data.filepath:
#col.label(text=f' {self.get_preview_dir()}')
#else:
# col.label(icon="ERROR", text="Save your Blender to keep the previews")
row = col.row(align=True) row = col.row(align=True)
row.prop(project, 'import_task', text='Import Tasks') row.prop(import_shots, 'import_task', text='Import Tasks')
row.operator("vse_toolbox.select_task", text='', icon='ADD')
if project.import_task == 'SELECTED': tasks = [t for t in project.task_types if t.import_enabled]
col.use_property_split = False
col.template_list("VSETB_UL_import_task", "import_task", project, "task_types", project, "task_type_index", rows=8) if import_shots.import_task == "FROM_LIST":
if tasks:
split = col.split(factor=0.4)
split.row()
box = split.box()
box_col = box.column(align=True)
for task_type in tasks:
row = box_col.row(align=True)
sub = row.row(align=True)
sub.enabled = False
sub.alignment = 'LEFT'
sub.scale_x = 0.15
sub.prop(task_type, 'color', text='')
op = row.operator("vse_toolbox.unselect_task", text=task_type.name)
op.task = task_type.name
row.separator(factor=0.5)
op = row.operator("vse_toolbox.unselect_task", text='', icon='REMOVE', emboss=False)
op.task = task_type.name
else:
split = col.split(factor=0.4)
split.row()
row = split.row()
row.label(icon="ERROR")
row.label(text='Add at least one task')
# Choose Sequences
row = col.row(align=True)
row.prop(import_shots, 'import_sequence', text='Import Sequences')
row.operator("vse_toolbox.select_sequence", text='', icon='ADD')
sequences = [s for s in project.sequences if s.import_enabled]
if import_shots.import_sequence == "FROM_LIST":
if sequences:
split = col.split(factor=0.4)
split.row()
box = split.box()
box_col = box.column(align=True)
for sequence in sequences:
row = box_col.row(align=True)
op = row.operator("vse_toolbox.unselect_sequence", text=sequence.name)
op.sequence = sequence.name
row.separator(factor=0.5)
op = row.operator("vse_toolbox.unselect_sequence", text='', icon='REMOVE', emboss=False)
op.sequence = sequence.name
else:
split = col.split(factor=0.4)
split.row()
row = split.row()
row.label(icon="ERROR")
row.label(text='Add at least one Sequence')
def invoke(self, context, event): def invoke(self, context, event):
scn = context.scene scn = context.scene
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
tmp_dir = Path(gettempdir(), 'reviews')
if bpy.data.filepath and Path(project.import_shots.previews_folder) == tmp_dir:
tmp_dir = '//sources'
if not bpy.data.filepath and project.import_shots.previews_folder == '//sources':
project.import_shots.previews_folder = str(tmp_dir)
# if not bpy.data.filepath:
# self.report({"ERROR"}, "Save your Blender file first")
# return {"CANCELLED"}
return context.window_manager.invoke_props_dialog(self) return context.window_manager.invoke_props_dialog(self, width=400)
def check(self, context):
return True
classes = ( classes = (
VSETB_OT_select_sequence,
VSETB_OT_unselect_sequence,
VSETB_OT_unselect_task,
VSETB_OT_select_task,
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

@ -1,4 +1,5 @@
from os.path import expandvars from os.path import expandvars, abspath
from pathlib import Path
import bpy import bpy
from bpy.types import Operator from bpy.types import Operator
from bpy.props import (BoolProperty, StringProperty) from bpy.props import (BoolProperty, StringProperty)
@ -7,6 +8,8 @@ from vse_toolbox.sequencer_utils import (get_strips, rename_strips, set_channels
get_channel_index, new_text_strip, get_strip_at, get_channel_name) get_channel_index, new_text_strip, get_strip_at, get_channel_name)
from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings
from vse_toolbox.constants import REVIEW_TEMPLATE_BLEND
from shutil import copy2
class VSETB_OT_rename(Operator): class VSETB_OT_rename(Operator):
@ -17,8 +20,7 @@ class VSETB_OT_rename(Operator):
#template : StringProperty(name="Strip Name", default="") #template : StringProperty(name="Strip Name", default="")
#increment : IntProperty(name="Increment", default=0) #increment : IntProperty(name="Increment", default=0)
channel_name : StringProperty(name="Channel Name", default="") selected_only : BoolProperty(name="Selected Only", default=True)
selected_only : BoolProperty(name="Selected Only", default=False)
#start_number : IntProperty(name="Start Number", default=0, min=0) #start_number : IntProperty(name="Start Number", default=0, min=0)
#by_sequence : BoolProperty( #by_sequence : BoolProperty(
# name="Reset By Sequence", # name="Reset By Sequence",
@ -29,7 +31,8 @@ class VSETB_OT_rename(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
settings = get_scene_settings() settings = get_scene_settings()
return settings.active_project strip = context.active_sequence_strip
return settings.active_project and get_channel_name(strip) in ('Shots', 'Sequences')
def invoke(self, context, event): def invoke(self, context, event):
scn = context.scene scn = context.scene
@ -43,39 +46,62 @@ class VSETB_OT_rename(Operator):
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
episode = project.episode_name
sequence = str(project.sequence_start_number).zfill(project.sequence_padding)
shot = str(project.shot_start_number).zfill(project.shot_padding)
strip = context.active_sequence_strip
channel_name = get_channel_name(strip)
col = layout.column() col = layout.column()
col.use_property_split = True col.use_property_split = True
col.use_property_decorate = False col.use_property_decorate = False
if self.channel_name == 'Shots': if channel_name == 'Shots':
col.prop(project, 'shot_template', text='Shot Name') col.prop(project, 'shot_template', text='Shot Name')
col.prop(project, 'shot_start_number', text='Start Number') col.prop(project, 'shot_start_number', text='Start Number')
col.prop(project, 'shot_increment', text='Increment') col.prop(project, 'shot_increment', text='Increment')
col.prop(project, 'shot_padding', text='Padding')
col.prop(project, 'reset_by_sequence') col.prop(project, 'reset_by_sequence')
elif self.channel_name == 'Sequences':
elif channel_name == 'Sequences':
col.prop(project, 'sequence_template' ,text='Sequence Name') col.prop(project, 'sequence_template' ,text='Sequence Name')
col.prop(project, 'sequence_start_number', text='Start Number') col.prop(project, 'sequence_start_number', text='Start Number')
col.prop(project, 'sequence_increment', text='Increment') col.prop(project, 'sequence_increment', text='Increment')
col.prop(project, 'sequence_padding', text='Padding')
col.prop(self, 'selected_only') col.prop(self, 'selected_only')
if channel_name == 'Shots':
label = project.shot_template.format(episode=episode, sequence=sequence, shot=shot)
elif channel_name == 'Sequences':
label = project.sequence_template.format(episode=episode, sequence=sequence)
col.label(text=f'Renaming {label}')
def execute(self, context): def execute(self, context):
scn = context.scene scn = context.scene
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
strips = get_strips(channel=self.channel_name, selected_only=self.selected_only) strip = context.active_sequence_strip
if self.channel_name == 'Shots': channel_name = get_channel_name(strip)
strips = get_strips(channel=channel_name, selected_only=self.selected_only)
if channel_name == 'Shots':
rename_strips(strips, rename_strips(strips,
template=project.shot_template, template=project.shot_template,
increment=project.shot_increment, start_number=project.shot_start_number, increment=project.shot_increment, start_number=project.shot_start_number,
by_sequence=project.reset_by_sequence by_sequence=project.reset_by_sequence,
padding=project.shot_padding
) )
if self.channel_name == 'Sequences': if channel_name == 'Sequences':
rename_strips(strips, rename_strips(strips,
template=project.sequence_template, template=project.sequence_template,
increment=project.sequence_increment, start_number=project.sequence_start_number increment=project.sequence_increment, start_number=project.sequence_start_number,
padding=project.sequence_padding
) )
return {"FINISHED"} return {"FINISHED"}
@ -180,10 +206,10 @@ class VSETB_OT_set_stamps(Operator):
font_size=font_size, y=margin, box_margin=box_margin, select=True, box_color=(0, 0, 0, 0.5)) font_size=font_size, y=margin, box_margin=box_margin, select=True, box_color=(0, 0, 0, 0.5))
# Project Name # Project Name
project_text = '{project_name}' project_text = '{project}'
if project.type == 'TVSHOW': if project.type == 'TVSHOW':
project_text = '{project_name} / {episode_name}' project_text = '{project} / ep{episode}'
project_strip_stamp = new_text_strip('project_name_stamp', channel=1, **stamp_params, project_strip_stamp = new_text_strip('project_stamp', channel=1, **stamp_params,
text=project_text, x=0.01, align_x='LEFT', align_y='BOTTOM') text=project_text, x=0.01, align_x='LEFT', align_y='BOTTOM')
project_strip_stamp.crop.max_x = crop_x * 2 project_strip_stamp.crop.max_x = crop_x * 2
@ -191,16 +217,16 @@ class VSETB_OT_set_stamps(Operator):
# Shot Name # Shot Name
shot_strip_stamp = new_text_strip('shot_name_stamp', channel=2, **stamp_params, shot_strip_stamp = new_text_strip('shot_stamp', channel=2, **stamp_params,
text='{sequence_name} / {shot_name}', align_y='BOTTOM') text='sq{sequence} / sh{shot}', align_y='BOTTOM')
shot_strip_stamp.crop.min_x = crop_x shot_strip_stamp.crop.min_x = crop_x
shot_strip_stamp.crop.max_x = crop_x shot_strip_stamp.crop.max_x = crop_x
shot_strip_stamp.crop.max_y = crop_max_y shot_strip_stamp.crop.max_y = crop_max_y
# Frame Range # Frame
frame_strip_stamp = new_text_strip('frame_range_stamp', channel=3, **stamp_params, frame_strip_stamp = new_text_strip('frame_stamp', channel=3, **stamp_params,
text='{shot_frame} / {shot_duration}', x=0.99, align_x='RIGHT', align_y='BOTTOM') text='{shot_frame} / {shot_duration} {timecode}', x=0.99, align_x='RIGHT', align_y='BOTTOM')
frame_strip_stamp.crop.min_x = crop_x *2 frame_strip_stamp.crop.min_x = crop_x *2
frame_strip_stamp.crop.max_y = crop_max_y frame_strip_stamp.crop.max_y = crop_max_y
@ -269,10 +295,11 @@ class VSETB_OT_next_shot(Operator):
return {"FINISHED"} return {"FINISHED"}
class VSETB_OT_open_shot_dir(Operator):
bl_idname = "vse_toolbox.open_shot_dir" class VSETB_OT_open_shot_folder(Operator):
bl_label = "Open Shot Dir" bl_idname = "vse_toolbox.open_shot_folder"
bl_description = "Open Shot Dir" bl_label = "Open Shot Folder"
bl_description = "Open Shot Folder"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@classmethod @classmethod
@ -289,13 +316,154 @@ class VSETB_OT_open_shot_dir(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_dir_template = expandvars(project.shot_dir_template) shot_folder_template = expandvars(project.import_shots.shot_folder_template)
shot_dir_path = shot_dir_template.format(**format_data) shot_folder_path = shot_folder_template.format(**format_data)
bpy.ops.wm.path_open(filepath=shot_dir_path) bpy.ops.wm.path_open(filepath=shot_folder_path)
return {"FINISHED"} return {"FINISHED"}
class VSETB_OT_collect_files(Operator):
bl_idname = "vse_toolbox.collect_files"
bl_label = "Collect Files"
bl_description = "Collect Files"
bl_options = {"REGISTER", "UNDO"}
collect_folder: StringProperty(
name="Collect Folder", default="//sources", subtype='DIR_PATH')
@classmethod
def poll(cls, context):
if bpy.data.is_saved:
return True
cls.poll_message_set('Save the blend to collect files')
def execute(self, context):
strip = context.active_sequence_strip
settings = get_scene_settings()
project = settings.active_project
strips = [s for s in context.scene.sequence_editor.sequences_all if s.type in ('MOVIE', 'SOUND')]
context.window_manager.progress_begin(0, len(strips))
for i, strip in enumerate(strips):
context.window_manager.progress_update(i)
if strip.type == 'MOVIE':
src_path = strip.filepath
elif strip.type == 'SOUND':
src_path = strip.sound.filepath
src_path = Path(abspath(bpy.path.abspath(src_path)))
dst_path = Path(bpy.path.abspath(self.collect_folder), src_path.name)
if src_path == dst_path:
continue
dst_path.parent.mkdir(exist_ok=True, parents=True)
print(f'Copy file from {src_path} to {dst_path}')
copy2(str(src_path), str(dst_path))
rel_path = bpy.path.relpath(str(dst_path))
if len(Path(rel_path).as_posix()) < len(dst_path.as_posix()):
dst_path = rel_path
if strip.type == 'MOVIE':
strip.filepath = str(dst_path)
elif strip.type == 'SOUND':
strip.sound.filepath = str(dst_path)
context.window_manager.progress_end()
return {"FINISHED"}
def invoke(self, context, event):
scn = context.scene
settings = get_scene_settings()
return context.window_manager.invoke_props_dialog(self)
class WM_OT_split_view(Operator):
"""Toggle Split sequencer view"""
bl_idname = "wm.split_view"
bl_label = "Split View"
def get_preview_areas(self, context):
return [
x for x in context.screen.areas
if x.type == "SEQUENCE_EDITOR" and x.spaces[0].view_type == "PREVIEW"
]
def invoke(self, context, event):
preview_areas = self.get_preview_areas(context)
if not preview_areas:
return {"CANCELLED"}
scn = context.scene
video_channels = [i for i, c in enumerate(scn.sequence_editor.channels) if 'Video' in c.name]
if len(video_channels) == 2 and len(preview_areas) == 1:
# Split area
with bpy.context.temp_override(area=preview_areas[0]):
bpy.ops.screen.area_split(direction="VERTICAL")
# Disable toolbar on right panel
# Update areas
preview_areas = self.get_preview_areas(context)
preview_areas[-1].spaces[0].display_channel = video_channels[-1]
preview_areas[0].spaces[0].display_channel = video_channels[-2]
preview_areas[0].spaces[0].show_gizmo_navigate = False
preview_areas[-1].spaces[0].show_gizmo_navigate = False
# Hide toolbar
#preview_areas[0].spaces[0].show_region_toolbar = False
else:
# Give the remaining area to have the left area's channel displayed
# Show toolbar of remaning area
#preview_areas[0].spaces[0].show_region_toolbar = True
# Join areas
# Simulates the mouse position as being between the two areas horizontally
# and a bit above the bottom corner vertically
cursor_x = int(preview_areas[0].x - (preview_areas[0].x - preview_areas[1].width) / 2)
cursor_y = preview_areas[0].y + 10
bpy.ops.screen.area_join(cursor=(cursor_x, cursor_y))
preview_areas = self.get_preview_areas(context)
preview_areas[0].spaces[0].display_channel = 0
# Force UI update, due to Blender bug, delete when fixed -> https://developer.blender.org/T65529
bpy.ops.screen.area_swap(cursor=(preview_areas[0].x, preview_areas[0].y))
bpy.ops.screen.area_swap(cursor=(preview_areas[0].x, preview_areas[0].y))
return {"FINISHED"}
'''
class VSETB_OT_set_review_workspace(Operator):
"""Set Review Workspace"""
bl_idname = "vse_toolbox.set_workspace"
bl_label = "Set Review Workspace"
def execute(self, context):
bpy.ops.workspace.append_activate(idname='Review', filepath=str(REVIEW_TEMPLATE_BLEND))
return {"FINISHED"}
'''
addon_keymaps = [] addon_keymaps = []
def register_keymaps(): def register_keymaps():
addon = bpy.context.window_manager.keyconfigs.addon addon = bpy.context.window_manager.keyconfigs.addon
@ -331,7 +499,9 @@ classes = (
VSETB_OT_show_waveform, VSETB_OT_show_waveform,
VSETB_OT_previous_shot, VSETB_OT_previous_shot,
VSETB_OT_next_shot, VSETB_OT_next_shot,
VSETB_OT_open_shot_dir VSETB_OT_open_shot_folder,
VSETB_OT_collect_files,
WM_OT_split_view
) )
def register(): def register():

View File

@ -3,6 +3,8 @@ import os
from os.path import expandvars from os.path import expandvars
from pathlib import Path from pathlib import Path
from pprint import pprint from pprint import pprint
import webbrowser
from functools import partial
import bpy import bpy
from bpy.types import (Operator, ) from bpy.types import (Operator, )
@ -59,7 +61,7 @@ class VSETB_OT_load_assets(Operator):
prefs = get_addon_prefs() prefs = get_addon_prefs()
tracker = prefs.tracker tracker = prefs.tracker
tracker.connect() tracker.admin_connect()
project = settings.active_project project = settings.active_project
project.assets.clear() project.assets.clear()
@ -125,7 +127,7 @@ class VSETB_OT_load_projects(Operator):
tracker = prefs.tracker tracker = prefs.tracker
prev_project_name = settings.project_name prev_project_name = settings.project_name
tracker.connect() tracker.admin_connect()
project_datas = tracker.get_projects() project_datas = tracker.get_projects()
for project_data in sorted(project_datas, key=lambda x: x['name']): for project_data in sorted(project_datas, key=lambda x: x['name']):
@ -137,7 +139,6 @@ class VSETB_OT_load_projects(Operator):
project.name = project_data['name'] project.name = project_data['name']
project.id = project_data['id'] project.id = project_data['id']
if project.type == 'TVSHOW': if project.type == 'TVSHOW':
episode_datas = tracker.get_episodes(project_data) episode_datas = tracker.get_episodes(project_data)
for episode_data in episode_datas: for episode_data in episode_datas:
@ -154,6 +155,17 @@ class VSETB_OT_load_projects(Operator):
for ep in reversed(project.episodes): for ep in reversed(project.episodes):
if ep.name not in ep_names: if ep.name not in ep_names:
project.episodes.remove(list(project.episodes).index(ep)) project.episodes.remove(list(project.episodes).index(ep))
else:
# Add sequences
sequences_data = tracker.get_sequences(project_data)
for sequence_data in sequences_data:
sequence = project.sequences.get(sequence_data['name'])
if not sequence:
sequence = project.sequences.add()
sequence.name = sequence_data['name']
sequence.id = sequence_data['id']
project.metadata_types.clear() project.metadata_types.clear()
for metadata_data in tracker.get_metadata_types(project_data): for metadata_data in tracker.get_metadata_types(project_data):
@ -183,6 +195,7 @@ class VSETB_OT_load_projects(Operator):
for task_type_data in tracker.get_shot_task_types(project_data): for task_type_data in tracker.get_shot_task_types(project_data):
task_type = project.task_types.add() task_type = project.task_types.add()
task_type.name = task_type_data['name'] task_type.name = task_type_data['name']
task_type.id = task_type_data['id']
task_type.color = self.hex_to_rgb(task_type_data['color'])[:3] task_type.color = self.hex_to_rgb(task_type_data['color'])[:3]
project.set_shot_tasks() project.set_shot_tasks()
@ -200,11 +213,11 @@ 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))
# Restore previous project
settings.project_name = prev_project_name
bpy.ops.vse_toolbox.load_settings() 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: #if settings.active_project:
# settings.active_project.set_strip_metadata() # settings.active_project.set_strip_metadata()
@ -216,7 +229,7 @@ class VSETB_OT_load_projects(Operator):
class VSETB_OT_new_episode(Operator): class VSETB_OT_new_episode(Operator):
bl_idname = "vse_toolbox.new_episode" bl_idname = "vse_toolbox.new_episode"
bl_label = "New Epispde" bl_label = "New Episode"
bl_description = "Add new Episode to Project" bl_description = "Add new Episode to Project"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@ -254,42 +267,12 @@ class VSETB_OT_new_episode(Operator):
return {'FINISHED'} return {'FINISHED'}
def get_task_status_items(self, context):
settings = get_scene_settings()
project = settings.active_project
status_items = [('CURRENT', 'Current', '')]
if project:
status_items += [(t.name, t.name, '') for t in project.task_statuses]
return status_items
def get_task_type_items(self, context):
settings = get_scene_settings()
project = settings.active_project
if not project:
return [('NONE', 'None', '')]
return [(t.name, t.name, '') for t in project.task_types]
class VSETB_OT_upload_to_tracker(Operator): class VSETB_OT_upload_to_tracker(Operator):
bl_idname = "vse_toolbox.upload_to_tracker" bl_idname = "vse_toolbox.upload_to_tracker"
bl_label = "Upload to tracker" bl_label = "Upload to tracker"
bl_description = "Upload selected strip to tracker" bl_description = "Upload selected strip to tracker"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
task : EnumProperty(items=get_task_type_items)
status : EnumProperty(items=get_task_status_items)
comment : StringProperty()
add_preview : BoolProperty(default=True)
preview_mode : EnumProperty(items=[(m, m.title().replace('_', ' '), '')
for m in ('ONLY_NEW', 'REPLACE', 'ADD')])
set_main_preview : BoolProperty(default=True)
casting : BoolProperty(default=True)
custom_data : BoolProperty(default=True)
tasks_comment: BoolProperty(default=True)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return True return True
@ -297,49 +280,66 @@ class VSETB_OT_upload_to_tracker(Operator):
def invoke(self, context, event): def invoke(self, context, event):
prefs = get_addon_prefs() prefs = get_addon_prefs()
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project self.project = settings.active_project
tracker = prefs.tracker tracker = prefs.tracker
tracker.connect() tracker.connect()
#self.bl_label = f"Upload to {settings.tracker_name.title()}" #self.bl_label = f"Upload to {settings.tracker_name.title()}"
return context.window_manager.invoke_props_dialog(self) return context.window_manager.invoke_props_dialog(self, width=350)
def draw(self, context): def draw(self, context):
scn = context.scene scn = context.scene
settings = get_scene_settings() upload_to_tracker = self.project.upload_to_tracker
layout = self.layout layout = self.layout
col = layout.column() col = layout.column()
col.use_property_split = True col.use_property_split = True
col.prop(self, 'task', text='Task') col.use_property_decorate = False
col.prop(self, 'status', text='Status') col.prop(upload_to_tracker, 'render_strips', text='Render Strips')
col.prop(self, 'comment', text='Comment') if upload_to_tracker.render_strips:
row = col.row(heading='Add Preview') col.use_property_split = False
row.prop(self, 'add_preview', text='') col.prop(upload_to_tracker, 'render_strip_template', text='')
row.prop(self, 'preview_mode', text='') col.use_property_split = True
#else:
# col.label(text=f'Source: {self.project.render_video_strip_template}')
col.separator() col.separator()
col.prop(self, 'casting', text='Casting')
col.prop(self, 'custom_data', text='Custom Data') col.prop(upload_to_tracker, 'task', text='Task')
col.prop(self, 'tasks_comment', text='Tasks Comment') col.prop(upload_to_tracker, 'status', text='Status')
col.prop(self, 'set_main_preview', text='Set Main Preview') col.prop(upload_to_tracker, 'comment', text='Comment')
row = col.row(heading='Add Preview')
row.prop(upload_to_tracker, 'add_preview', text='')
row.prop(upload_to_tracker, 'preview_mode', text='')
if upload_to_tracker.add_preview and not upload_to_tracker.render_strips:
col.use_property_split = False
col.prop(self.project, 'render_video_strip_template', text='')
col.use_property_split = True
col.separator()
col.prop(upload_to_tracker, 'custom_data', text='Custom Data')
col.prop(upload_to_tracker, 'update_frames', text='Update frames')
col.prop(upload_to_tracker, 'casting', text='Casting')
col.prop(upload_to_tracker, 'tasks_comment', text='Tasks Comment')
col.prop(upload_to_tracker, 'set_main_preview', text='Set Main Preview')
def execute(self, context): def execute(self, context):
#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()
project = settings.active_project upload_to_tracker = self.project.upload_to_tracker
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, **project.format_data} format_data = {**settings.format_data, **self.project.format_data}
tracker = prefs.tracker tracker = prefs.tracker
status = self.status status = upload_to_tracker.status
if status == 'CURRENT': if status == 'CURRENT':
status = None status = None
@ -360,15 +360,19 @@ class VSETB_OT_upload_to_tracker(Operator):
self.report({"INFO"}, f'Create shot {shot_name} in Kitsu') self.report({"INFO"}, f'Create shot {shot_name} in Kitsu')
shot = tracker.new_shot(shot_name, sequence=sequence) shot = tracker.new_shot(shot_name, sequence=sequence)
task = tracker.get_task(self.task, entity=shot) task = tracker.get_task(upload_to_tracker.task, entity=shot)
if not task: if not task:
task = tracker.new_task(shot, task_type=self.task) task = tracker.new_task(shot, task_type=upload_to_tracker.task)
preview = None preview = None
if self.add_preview: if upload_to_tracker.add_preview:
strip_data = {**format_data, **strip_settings.format_data} strip_data = {**format_data, **strip_settings.format_data}
preview_template = expandvars(project.render_video_strip_template) if upload_to_tracker.render_strips:
preview_template = expandvars(upload_to_tracker.render_strip_template)
else:
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)))
#print(preview) #print(preview)
@ -377,24 +381,41 @@ class VSETB_OT_upload_to_tracker(Operator):
preview = None preview = None
elif task.get('last_comment') and task['last_comment']['previews']: elif task.get('last_comment') and task['last_comment']['previews']:
if self.preview_mode == 'REPLACE': if upload_to_tracker.preview_mode == 'REPLACE':
tracker.remove_comment(task['last_comment']) tracker.remove_comment(task['last_comment'])
elif self.preview_mode == 'ONLY_NEW': elif upload_to_tracker.preview_mode == 'ONLY_NEW':
preview = None preview = None
if status or preview: comment_data = None
tracker.new_comment(task, comment=self.comment, status=status, preview=preview, set_main_preview=self.set_main_preview) if status or comment or preview:
comment_data = tracker.new_comment(task, comment=upload_to_tracker.comment, status=status)
if preview:
print('Upload preview from', preview)
preview_data = tracker.new_preview(
task=task,
comment=comment_data,
preview=preview)
if self.custom_data: if upload_to_tracker.set_main_preview:
description = strip_settings.description bpy.app.timers.register(partial(tracker.set_main_preview, preview_data), first_interval=10)
tracker.update_data(shot, metadata, frames=strip.frame_final_duration, description=description)
params = {}
if upload_to_tracker.custom_data:
params['custom_data'] = metadata
params['description'] = strip_settings.description
if self.casting: if upload_to_tracker.update_frames:
params['frames'] = strip.frame_final_duration
if params:
tracker.update_data(shot, **params)
if upload_to_tracker.casting:
casting = [{'asset_id': a.id, 'nb_occurences': a.instance} for a in strip_settings.casting] casting = [{'asset_id': a.id, 'nb_occurences': a.instance} for a in strip_settings.casting]
tracker.update_casting(shot, casting) tracker.update_casting(shot, casting)
if self.tasks_comment: if upload_to_tracker.tasks_comment:
for task_type in project.task_types: for task_type in self.project.task_types:
task = getattr(strip_settings.tasks, norm_name(task_type.name)) task = getattr(strip_settings.tasks, norm_name(task_type.name))
tracker_task = tracker.get_task(task_type.name, entity=shot) tracker_task = tracker.get_task(task_type.name, entity=shot)
@ -405,6 +426,28 @@ class VSETB_OT_upload_to_tracker(Operator):
return {"FINISHED"} return {"FINISHED"}
class VSETB_OT_open_shot_on_tracker(Operator):
bl_idname = "vse_toolbox.open_shot_on_tracker"
bl_label = "Open Shot on Tracker"
bl_description = "Open Shot on Tracker"
@classmethod
def poll(cls, context):
prefs = get_addon_prefs()
if prefs.tracker:
return True
def execute(self, context):
prefs = get_addon_prefs()
tracker = prefs.tracker
strips = get_strips(channel='Shots', selected_only=True)
url = tracker.get_shots_search_url([s.name for s in strips])
webbrowser.open_new_tab(url)
return {"FINISHED"}
@persistent @persistent
def set_asset_items(scene=None): def set_asset_items(scene=None):
ASSET_ITEMS.clear() ASSET_ITEMS.clear()
@ -420,6 +463,7 @@ classes = (
VSETB_OT_new_episode, VSETB_OT_new_episode,
VSETB_OT_tracker_connect, VSETB_OT_tracker_connect,
VSETB_OT_upload_to_tracker, VSETB_OT_upload_to_tracker,
VSETB_OT_open_shot_on_tracker
) )
def register(): def register():

View File

@ -0,0 +1,45 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.app.handlers import persistent
def update_factory_startup_screens():
screen = bpy.data.screens["Review"]
def update_factory_startup_ffmpeg_preset():
from bpy import context
preset = "H264_in_MP4"
preset_filepath = bpy.utils.preset_find(preset, preset_path="ffmpeg")
if not preset_filepath:
print("Preset %r not found" % preset)
for scene in bpy.data.scenes:
render = scene.render
render.image_settings.file_format = 'FFMPEG'
if preset_filepath:
with context.temp_override(scene=scene):
bpy.ops.script.python_file_run(filepath=preset_filepath)
render.ffmpeg.audio_codec = 'AAC'
render.ffmpeg.audio_bitrate = 128
render.ffmpeg.audio_mixrate = 44100
@persistent
def load_handler(_):
update_factory_startup_screens()
if bpy.app.build_options.codec_ffmpeg:
update_factory_startup_ffmpeg_preset()
def register():
bpy.app.handlers.load_factory_startup_post.append(load_handler)
def unregister():
bpy.app.handlers.load_factory_startup_post.remove(load_handler)

Binary file not shown.

View File

@ -1,6 +1,7 @@
import bpy import bpy
import os import os
from os.path import expandvars
import re import re
import urllib3 import urllib3
import traceback import traceback
@ -26,20 +27,36 @@ class Kitsu(Tracker):
url: StringProperty() url: StringProperty()
login: StringProperty() login: StringProperty()
password: StringProperty(subtype='PASSWORD') password: StringProperty(subtype='PASSWORD')
admin_login : StringProperty()
admin_password : StringProperty(subtype='PASSWORD')
def connect(self, url=None, login=None, password=None): def admin_connect(self):
url = self.url
if not url.endswith('/api'):
url += '/api'
login = expandvars(self.admin_login or self.login)
password = expandvars(self.admin_password or self.password)
try:
res = gazu.log_in(login, password)
LOGIN = login
return res['user']
except Exception as e:
print(e)
def connect(self):
'''Connect to kitsu api using provided url, login and password''' '''Connect to kitsu api using provided url, login and password'''
global LOGIN global LOGIN
urllib3.disable_warnings() urllib3.disable_warnings()
if url is None: url = expandvars(self.url)
url = self.url login = expandvars(self.login)
if login is None: password = expandvars(self.password)
login = self.login
if password is None:
password = self.password
if not url: if not url:
print(f'Kitsu Url: {self.url} is empty') print(f'Kitsu Url: {self.url} is empty')
@ -168,6 +185,16 @@ class Kitsu(Tracker):
return gazu.shot.get_sequence_by_name(**params) return gazu.shot.get_sequence_by_name(**params)
def get_sequences(self, project=None, episode=None):
#print(f'get_sequence({sequence=}, {project=})')
project = self.get_project(project)
if episode:
episode = self.get_episode(episode)
return gazu.shot.all_sequences_for_episode(episode)
else:
return gazu.shot.all_sequences_for_project(project)
def get_shot(self, shot, sequence, project=None): def get_shot(self, shot, sequence, project=None):
#print(f'get_shot({shot=}, {sequence=}, {project=})') #print(f'get_shot({shot=}, {sequence=}, {project=})')
project = self.get_project(project) project = self.get_project(project)
@ -182,6 +209,12 @@ class Kitsu(Tracker):
return gazu.shot.get_shot_by_name(sequence, shot) return gazu.shot.get_shot_by_name(sequence, shot)
def get_shots(self, sequence):
#print(f'get_sequence({sequence=}, {project=})')
#project = self.get_project(project)
return gazu.shot.all_shots_for_sequence(sequence)
def get_asset(self, asset, asset_type=None, project=None): def get_asset(self, asset, asset_type=None, project=None):
#print('get_asset', "name", name, 'asset_type', asset_type) #print('get_asset', "name", name, 'asset_type', asset_type)
@ -211,6 +244,29 @@ class Kitsu(Tracker):
task = self.get_id(task) task = self.get_id(task)
return gazu.task.get_last_comment_for_task(task) return gazu.task.get_last_comment_for_task(task)
def get_last_comment_with_preview(self, task):
task = self.get_id(task)
comments = gazu.task.all_comments_for_task(task)
for comment in comments:
if comment['previews']:
return comment
def download_preview_file(self, preview, filepath):
preview_id = self.get_id(preview)
return gazu.files.download_preview_file(preview_id, str(filepath))
def get_shots_search_url(self, shot_names, project=None, episode=None):
project = self.get_project(project)
url = gazu.client.get_host().replace('/api', f'/productions/{project}')
if episode:
episode = self.get_episode(episode)
url += f'/episodes/{episode["id"]}'
url += f'/shots'
return f'{url}?search={" ".join(shot_names)}'
def get_task(self, task=None, entity=None): def get_task(self, task=None, entity=None):
entity = self.get_id(entity) entity = self.get_id(entity)
@ -225,6 +281,9 @@ class Kitsu(Tracker):
return task return task
def set_main_preview(self, preview_data):
gazu.task.set_main_preview(preview_data)
def new_preview(self, task, comment, preview, set_main_preview=False): def new_preview(self, task, comment, preview, set_main_preview=False):
#print('new_preview', task, comment, preview, set_main_preview) #print('new_preview', task, comment, preview, set_main_preview)
@ -238,7 +297,7 @@ class Kitsu(Tracker):
if set_main_preview: if set_main_preview:
#print('----', 'Settings') #print('----', 'Settings')
gazu.task.set_main_preview(preview_data) self.set_main_preview(preview_data)
return preview_data return preview_data
@ -327,16 +386,13 @@ class Kitsu(Tracker):
return shot return shot
def update_data(self, entity, data, name=None, description=None, frames=None, clear=False): def update_data(self, entity, custom_data={}, name=None, description=None, frames=None, clear=False):
if isinstance(entity, dict): if isinstance(entity, dict):
entity_id = entity['id'] entity_id = entity['id']
else: else:
entity_id = self.get_id(entity) entity_id = self.get_id(entity)
entity = gazu.client.fetch_one('entities', entity_id) entity = gazu.client.fetch_one('entities', entity_id)
if data.get('custom_data'):
data['data'] = data.pop('custom_data')
if name: if name:
entity['name'] = name entity['name'] = name
if description: if description:
@ -345,9 +401,9 @@ class Kitsu(Tracker):
entity['nb_frames'] = frames entity['nb_frames'] = frames
if clear or not entity['data']: if clear or not entity['data']:
entity['data'] = data entity['data'] = custom_data
else: else:
entity['data'].update(data) entity['data'].update(custom_data)
#print('######UPDATE DATA') #print('######UPDATE DATA')
#pprint(entity) #pprint(entity)
@ -387,6 +443,11 @@ class Kitsu(Tracker):
return gazu.casting.update_shot_casting(project, shot_id, norm_casting) return gazu.casting.update_shot_casting(project, shot_id, norm_casting)
def draw_prefs(self, layout): def draw_prefs(self, layout):
layout.prop(self, 'url', text='Url') col = layout.column(align=False)
layout.prop(self, 'login', text='Login') col.prop(self, 'url', text='Url')
layout.prop(self, 'password', text='Password') col.prop(self, 'login', text='Login')
col.prop(self, 'password', text='Password')
col.separator()
col.prop(self, 'admin_login', text='Admin Login')
col.prop(self, 'admin_password', text='Admin Password')

View File

@ -13,6 +13,7 @@ from vse_toolbox.constants import SOUND_SUFFIXES
#import multiprocessing #import multiprocessing
#from multiprocessing.pool import ThreadPool #from multiprocessing.pool import ThreadPool
import subprocess import subprocess
from collections import defaultdict
def frame_to_timecode(frame, fps): def frame_to_timecode(frame, fps):
@ -95,8 +96,7 @@ def get_strip_sequence_name(strip):
else: else:
return 'NoSequence' return 'NoSequence'
def rename_strips( def rename_strips(strips, template, increment=10, start_number=0, padding=3, by_sequence=False):
strips, template, increment=10, start_number=0, by_sequence=False):
scn = bpy.context.scene scn = bpy.context.scene
settings = get_scene_settings() settings = get_scene_settings()
@ -110,16 +110,20 @@ def rename_strips(
for strip in strips: for strip in strips:
sequence_name = get_strip_sequence_name(strip) sequence_name = get_strip_sequence_name(strip)
sequence_data = parse(project.sequence_template, sequence_name)
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
name = template.format( format_data = dict(
sequence=sequence_name, sequence_strip=sequence_name,
episode=episode_name, episode=episode_name,
index=strip_number*increment+start_number 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) existing_strip = scn.sequence_editor.sequences_all.get(name)
if existing_strip: if existing_strip:
@ -399,8 +403,13 @@ def import_movie(filepath):
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:
relpath = Path(bpy.path.relpath(str(filepath)))
if len(relpath.as_posix()) < len(filepath.as_posix()):
filepath = relpath
strip = scn.sequence_editor.sequences.new_movie( strip = scn.sequence_editor.sequences.new_movie(
name=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=scn.frame_start
@ -414,28 +423,48 @@ 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 bpy.data.is_saved:
strip.filepath = bpy.path.relpath(str(filepath))
return strip return strip
def import_sound(filepath): def scale_clip_to_fit(strip):
scn = bpy.context.scene
res = scn.render.resolution_x, scn.render.resolution_y
strip_res = strip.elements[0].orig_width, strip.elements[0].orig_height
#print(strip.name, strip_res, width, height)
strip.transform.scale_x = res[0] / strip_res[0]
strip.transform.scale_y = strip.transform.scale_x
def import_sound(filepath, frame_start=None, frame_end=None):
scn = bpy.context.scene scn = bpy.context.scene
strip = scn.sequence_editor.sequences.new_sound( if frame_start is None:
name=filepath.stem, frame_start = scn.frame_start
filepath=str(filepath),
channel=get_channel_index('Audio'),
frame_start=scn.frame_start
)
if bpy.data.is_saved: if bpy.data.is_saved:
strip.sound.filepath = bpy.path.relpath(str(filepath)) relpath = Path(bpy.path.relpath(str(filepath)))
if len(relpath.as_posix()) < len(filepath.as_posix()):
filepath = relpath
strip.show_waveform = True if strip.frame_final_duration < 10000 else False strip = scn.sequence_editor.sequences.new_sound(
name=f'{filepath.stem} Audio',
filepath=str(filepath),
channel=get_channel_index('Audio'),
frame_start=frame_start
)
if frame_end is not None:
strip.frame_final_end = frame_end
#strip.show_waveform = True if strip.frame_final_duration < 10000 else False
return strip return strip
def get_empty_channel():
return max(s.channel for s in bpy.context.scene.sequence_editor.sequences) + 1
def clean_sequencer(edit=False, movie=False, sound=False): def clean_sequencer(edit=False, movie=False, sound=False):
scn = bpy.context.scene scn = bpy.context.scene
sequences = [] sequences = []
@ -470,44 +499,51 @@ def set_active_strip(scene):
@persistent @persistent
def update_text_strips(scene): def update_text_strips(scene):
if not scene.sequence_editor or not scene.sequence_editor.sequences_all:
return
#print("update_text_strips") #print("update_text_strips")
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
episode = settings.active_episode episode = settings.active_episode
class MissingKey(dict):
def __missing__(self, key):
return '{' + key + '}'
scn = bpy.context.scene scn = bpy.context.scene
if not scn.sequence_editor: if not scn.sequence_editor:
return return
format_data = { format_data = {
'scene': scene, 'scene': scene,
'project_name': 'None', 'project': '...',
'episode_name': 'None', 'episode': '...',
'sequence_name': 'None', 'sequence': '...',
'strip_name': 'None', 'sequence_strip': '...',
'shot_name': 'None', 'shot': '...',
'shot_strip': '...',
'shot_frame': 0, 'shot_frame': 0,
'shot_duration': 0, 'shot_duration': 0,
'shot_start': 0, 'shot_start': 0,
'shot_end': 0, 'shot_end': 0,
'time_code': '' 'timecode': ''
} }
shot_strip = get_strip_at('Shots', frame=scene.frame_current) shot_strip = get_strip_at('Shots', frame=scene.frame_current)
if shot_strip: if shot_strip:
format_data.update({ format_data.update({
'project_name': project.name, 'project': project.name,
'episode_name': episode.name if episode else '', 'episode': episode.name if episode else '',
'sequence_name': get_strip_sequence_name(shot_strip),
'strip_name': shot_strip.name,
'shot_name': 'sh{index:04d}'.format(**shot_strip.vsetb_strip_settings.format_data),
'shot_duration': shot_strip.frame_final_duration, 'shot_duration': shot_strip.frame_final_duration,
'shot_frame': scene.frame_current - shot_strip.frame_final_start + 1, 'shot_frame': scene.frame_current - shot_strip.frame_final_start + 1,
'shot_start': shot_strip.frame_final_start, 'shot_start': shot_strip.frame_final_start,
'shot_end': shot_strip.frame_final_end, 'shot_end': shot_strip.frame_final_end,
'timecode' : frame_to_timecode(scene.frame_current, scene.render.fps) 'timecode' : frame_to_timecode(scene.frame_current, scene.render.fps)
}) })
format_data.update(shot_strip.vsetb_strip_settings.format_data)
for strip in scene.sequence_editor.sequences_all: for strip in scene.sequence_editor.sequences_all:
if not strip.type == 'TEXT': if not strip.type == 'TEXT':
@ -520,4 +556,4 @@ def update_text_strips(scene):
strip['text_pattern'] = strip.text strip['text_pattern'] = strip.text
if 'text_pattern' in strip.keys(): if 'text_pattern' in strip.keys():
strip.text = strip['text_pattern'].format(**format_data) strip.text = strip['text_pattern'].format_map(MissingKey(**format_data))

View File

@ -7,7 +7,7 @@ from bpy.types import Panel, Menu
from bl_ui.utils import PresetPanel from bl_ui.utils import PresetPanel
from vse_toolbox.bl_utils import (get_addon_prefs, get_scene_settings, get_strip_settings) from vse_toolbox.bl_utils import (get_addon_prefs, get_scene_settings, get_strip_settings)
from vse_toolbox.constants import ASSET_PREVIEWS from vse_toolbox.constants import ASSET_PREVIEWS, REVIEW_TEMPLATE_BLEND
from vse_toolbox.sequencer_utils import (set_active_strip, get_channel_name, get_strips) from vse_toolbox.sequencer_utils import (set_active_strip, get_channel_name, get_strips)
from vse_toolbox.file_utils import norm_str from vse_toolbox.file_utils import norm_str
@ -20,27 +20,53 @@ class VSETB_main:
class VSETB_PT_main(VSETB_main, Panel): class VSETB_PT_main(VSETB_main, Panel):
bl_options = {"HIDE_HEADER"}
def draw_header_preset(self, context): # def draw_header(self, context):
self.layout.operator('vse_toolbox.load_projects', icon='FILE_REFRESH', text='', emboss=False) # settings = get_scene_settings()
# prefs = get_addon_prefs()
# row = self.layout.row(align=True)
# row.label('VSE Toolbox')
# row.prop(settings, 'project_name', text='')
# project = settings.active_project
# if project and project.type == 'TVSHOW':
# row.prop(project, 'episode_name', text='')
# row.operator('vse_toolbox.load_projects', icon='FILE_REFRESH', text='', emboss=False)
def draw(self, context): def draw(self, context):
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.operator('vse_toolbox.load_projects', icon='FILE_REFRESH', text='', emboss=False)
row.prop(settings, 'project_name', text='')
project = settings.active_project project = settings.active_project
layout = self.layout if project and project.type == 'TVSHOW':
col = layout.column() row.prop(project, 'episode_name', text='')
col.prop(settings, 'project_name', text='Project') # settings = get_scene_settings()
# prefs = get_addon_prefs()
# project = settings.active_project
if project: # layout = self.layout
if project.type == 'TVSHOW': # col = layout.column()
col.prop(project, 'episode_name', text='Episodes')
# col.prop(settings, 'project_name', text='Project')
# if project:
# if project.type == 'TVSHOW':
# col.prop(project, 'episode_name', text='Episodes')
#col.separator() #col.separator()
@ -94,7 +120,7 @@ class VSETB_PT_strip(Panel):
class VSETB_PT_sequencer(VSETB_main, Panel): class VSETB_PT_sequencer(VSETB_main, Panel):
bl_label = "Sequencer" bl_label = "Sequencer"
bl_parent_id = "VSETB_PT_main" #bl_parent_id = "VSETB_PT_main"
def draw_header_preset(self, context): def draw_header_preset(self, context):
settings = get_scene_settings() settings = get_scene_settings()
@ -113,42 +139,19 @@ class VSETB_PT_sequencer(VSETB_main, Panel):
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
strip = context.active_sequence_strip
channel = get_channel_name(strip)
col = layout.column() col = layout.column()
col.operator('vse_toolbox.set_sequencer', text='Set-Up Sequencer', icon='SEQ_SEQUENCER') col.operator('vse_toolbox.set_sequencer', text='Set-Up Sequencer', icon='SEQ_SEQUENCER')
col.operator('vse_toolbox.strips_rename', text=f'Rename {channel}', icon='SORTALPHA')
#row = col.row()
shot_label = ''
sequence_label = ''
if project:
episode = project.episode_name
sequence_label = project.sequence_template.format(episode=episode, index=project.sequence_start_number)
shot_label = project.shot_template.format(episode=episode, sequence=sequence_label, index=project.shot_start_number)
#row.separator()
strip = context.active_sequence_strip
channel_name = get_channel_name(strip) or ''
if channel_name == 'Shots':
label = shot_label
elif channel_name == 'Sequences':
label = sequence_label
else:
label = 'Not Supported'
row = col.row(align=True)
if label == 'Not Supported':
row.enabled = False
op = row.operator('vse_toolbox.strips_rename', text=f'Rename {channel_name} ( {label} )', icon='SORTALPHA')
op.channel_name = channel_name
col.operator('vse_toolbox.set_stamps', text='Set Stamps', icon='COLOR') col.operator('vse_toolbox.set_stamps', text='Set Stamps', icon='COLOR')
col.operator("vse_toolbox.collect_files", text='Collect Files', icon='PACKAGE')
class VSETB_PT_settings(VSETB_main, Panel): class VSETB_PT_settings(VSETB_main, Panel):
bl_label = "Settings" bl_label = "Settings"
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):
@ -169,7 +172,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):
@ -197,7 +200,7 @@ class VSETB_PT_presets(PresetPanel, Panel):
class VSETB_PT_exports(VSETB_main, Panel): class VSETB_PT_exports(VSETB_main, Panel):
bl_label = "Exports" bl_label = "Exports"
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):
@ -208,20 +211,20 @@ class VSETB_PT_exports(VSETB_main, Panel):
layout = self.layout layout = self.layout
settings = get_scene_settings() settings = get_scene_settings()
project = settings.active_project project = settings.active_project
col = layout.column(align=False)
# TODO FAIRE DES VRAIS OPS # TODO FAIRE DES VRAIS OPS
layout.operator('vse_toolbox.strips_render', text='Render Strips', icon='SEQUENCE') col.operator('vse_toolbox.strips_render', text='Render Strips', icon='SEQUENCE')
tracker_label = settings.tracker_name.title().replace('_', ' ') tracker_label = settings.tracker_name.title().replace('_', ' ')
layout.operator('vse_toolbox.upload_to_tracker', text=f'Upload to {tracker_label}', icon='EXPORT') col.operator('vse_toolbox.upload_to_tracker', text=f'Upload to {tracker_label}', icon='EXPORT')
layout.operator('vse_toolbox.export_spreadsheet', text='Export Spreadsheet', icon='SPREADSHEET') col.operator('vse_toolbox.export_spreadsheet', text='Export Spreadsheet', icon='SPREADSHEET')
layout.operator('vse_toolbox.export_edl', text='Export edl', icon='SEQ_SEQUENCER') col.operator('vse_toolbox.export_edl', text='Export edl', icon='SEQ_SEQUENCER')
class VSETB_PT_tracker(VSETB_main, Panel): class VSETB_PT_tracker(VSETB_main, Panel):
bl_label = "Tracker" bl_label = "Tracker"
bl_parent_id = "VSETB_PT_main" #bl_parent_id = "VSETB_PT_main"
bl_options = {'DEFAULT_CLOSED'} bl_options = {'DEFAULT_CLOSED'}
@classmethod @classmethod
@ -403,8 +406,13 @@ class VSETB_MT_main_menu(Menu):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.operator('vse_toolbox.open_shot_dir', text='Open Shot', icon='FILE_FOLDER') op = layout.operator('workspace.append_activate', text='Set Review Workspace', icon="WORKSPACE")
op.idname = 'Review'
op.filepath = str(REVIEW_TEMPLATE_BLEND)
layout.operator("wm.split_view", icon="ARROW_LEFTRIGHT")
layout.operator('vse_toolbox.open_shot_folder', text='Open Shot Folder', icon='FILE_FOLDER')
layout.operator('vse_toolbox.open_shot_on_tracker', text='Open Shot on Tracker', icon='URL')
def draw_vse_toolbox_menu(self, context): def draw_vse_toolbox_menu(self, context):
self.layout.menu("VSETB_MT_main_menu") self.layout.menu("VSETB_MT_main_menu")

View File

@ -29,6 +29,7 @@ from vse_toolbox.file_utils import (
) )
from vse_toolbox.resources.trackers.kitsu import Kitsu from vse_toolbox.resources.trackers.kitsu import Kitsu
def load_trackers(): def load_trackers():
from vse_toolbox.resources.trackers.tracker import Tracker from vse_toolbox.resources.trackers.tracker import Tracker
@ -112,7 +113,9 @@ class VSETB_Prefs(AddonPreferences):
layout = self.layout layout = self.layout
layout.prop(self, 'config_path', text='Config Path') row = layout.row(align=True)
row.prop(self, 'config_path', text='Config Path')
row.operator("vse_toolbox.load_settings", icon="CHECKMARK", text='')
layout.prop(self, "sort_metadata_items", text='Sort Metadata Items') layout.prop(self, "sort_metadata_items", text='Sort Metadata Items')
col = layout.column(align=True) col = layout.column(align=True)

View File

@ -110,6 +110,12 @@ class MetadataType(PropertyGroup):
class TaskType(PropertyGroup): class TaskType(PropertyGroup):
color : FloatVectorProperty(subtype='COLOR') color : FloatVectorProperty(subtype='COLOR')
import_enabled : BoolProperty(default=False) import_enabled : BoolProperty(default=False)
id : StringProperty(default='')
class Sequence(PropertyGroup):
import_enabled : BoolProperty(default=False)
id : StringProperty(default='')
class TaskStatus(PropertyGroup): class TaskStatus(PropertyGroup):
@ -211,6 +217,66 @@ class SpreadsheetImport(PropertyGroup):
update_edit: BoolProperty(default=True) update_edit: BoolProperty(default=True)
def get_task_status_items(self, context):
settings = get_scene_settings()
project = settings.active_project
status_items = [('CURRENT', 'Current', '')]
if project:
status_items += [(t.name, t.name, '') for t in project.task_statuses]
return status_items
def get_task_type_items(self, context):
settings = get_scene_settings()
project = settings.active_project
if not project:
return [('NONE', 'None', '')]
return [(t.name, t.name, '') for t in project.task_types]
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')
#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}")
import_video_template : StringProperty(
name="Video Path", default="./{task}/render/{version}.{ext}")
import_sequence: EnumProperty(items=[(i, i.title().replace('_', ' '), '')
for i in ('SELECTED_STRIPS', 'FROM_LIST', 'ALL')], default='FROM_LIST')
previews_folder: StringProperty(
name="Previews Folder", default="//sources", subtype='DIR_PATH')
class UploadToTracker(PropertyGroup):
render_strips: BoolProperty(default=False)
render_strip_template : StringProperty(
name="Movie Path", default="//render/{project_basename}.{ext}")
task : EnumProperty(items=get_task_type_items)
status : EnumProperty(items=get_task_status_items)
comment : StringProperty()
add_preview : BoolProperty(default=True)
preview_mode : EnumProperty(items=[(m, m.title().replace('_', ' '), '')
for m in ('ONLY_NEW', 'REPLACE', 'ADD')])
set_main_preview : BoolProperty(default=True)
casting : BoolProperty(default=True)
update_frames: BoolProperty(default=True)
custom_data : BoolProperty(default=True)
tasks_comment: BoolProperty(default=True)
def get_episodes_items(self, context): def get_episodes_items(self, context):
settings = get_scene_settings() settings = get_scene_settings()
@ -232,7 +298,10 @@ class Project(PropertyGroup):
id : StringProperty(default='') id : StringProperty(default='')
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)
sequence_start_number : IntProperty(name="Sequence Start Number", default=10, min=0) sequence_start_number : IntProperty(name="Sequence Start Number", default=10, min=0)
sequence_padding : IntProperty(name="Sequence Padding", default=3, min=0, max=10)
reset_by_sequence : BoolProperty( reset_by_sequence : BoolProperty(
name="Reset By Sequence", name="Reset By Sequence",
@ -247,13 +316,13 @@ 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{index:03d}") name="Sequence Name", default="sq{sequence}")
episode_template : StringProperty( episode_template : StringProperty(
name="Episode Name", default="ep{index:03d}") name="Episode Name", default="ep{episode}")
shot_template : StringProperty( shot_template : StringProperty(
name="Shot Name", default="{sequence}_sh{index:04d}") name="Shot Name", default="sq{sequence}_sh{shot}")
render_video_template : StringProperty( render_video_template : StringProperty(
name="Movie Path", default="//render/{project_basename}.{ext}") name="Movie Path", default="//render/{project_basename}.{ext}")
@ -300,17 +369,15 @@ class Project(PropertyGroup):
task_types : CollectionProperty(type=TaskType) task_types : CollectionProperty(type=TaskType)
task_type_index : IntProperty() task_type_index : IntProperty()
task_statuses : CollectionProperty(type=TaskStatus) task_statuses : CollectionProperty(type=TaskStatus)
sequences : CollectionProperty(type=Sequence)
spreadsheet_import: PointerProperty(type=SpreadsheetImport) spreadsheet_import: PointerProperty(type=SpreadsheetImport)
spreadsheet_export: PointerProperty(type=SpreadsheetExport) spreadsheet_export: PointerProperty(type=SpreadsheetExport)
type : StringProperty() type : StringProperty()
import_source: EnumProperty(items=[(i, i.title(), '') for i in ('DISK', 'TRACKER')]) upload_to_tracker: PointerProperty(type=UploadToTracker)
import_task: EnumProperty(items=[(i, i.title(), '') for i in ('LAST', 'SELECTED', 'ALL')], default='LAST') import_shots: PointerProperty(type=ImportShots)
shot_dir_template: StringProperty(
name="Shot Template", default="$PROJECT_ROOT/sequences/{sequence}/sh{index:04d}")
@property @property
def active_episode(self): def active_episode(self):
@ -354,8 +421,6 @@ class Project(PropertyGroup):
return cell_types return cell_types
def set_spreadsheet(self): def set_spreadsheet(self):
cell_names = ['Sequence', 'Shot', 'Nb Frames', 'Description'] cell_names = ['Sequence', 'Shot', 'Nb Frames', 'Description']
if self.type == 'TVSHOW': if self.type == 'TVSHOW':
cell_names.insert(0, 'Episode') cell_names.insert(0, 'Episode')
@ -563,6 +628,7 @@ def on_project_updated(self, context):
os.environ['TRACKER_PROJECT_ID'] = settings.active_project.id os.environ['TRACKER_PROJECT_ID'] = settings.active_project.id
class VSETB_PGT_scene_settings(PropertyGroup): class VSETB_PGT_scene_settings(PropertyGroup):
projects : CollectionProperty(type=Project) projects : CollectionProperty(type=Project)
@ -649,17 +715,21 @@ class VSETB_PGT_strip_settings(PropertyGroup):
channel = get_channel_name(strip) channel = get_channel_name(strip)
if channel == 'Sequences': if channel == 'Sequences':
data = parse(strip.name, template=project.sequence_template) data = parse(project.sequence_template, strip.name)
data['index'] = int(data['index']) #data['index'] = int(data['index'])
data['sequence'] = strip.name #data['sequence'] = strip.name
data['strip'] = strip.name data['strip'] = strip.name
#data['shot'] = project.shot_template #data['shot'] = project.shot_template
elif channel == "Shots": elif channel == "Shots":
data = {}
if sequence_strip_name := get_strip_sequence_name(strip):
data = parse(project.sequence_template, sequence_strip_name)
data['sequence_strip'] = sequence_strip_name
data.update(parse(project.shot_template, strip.name))
#data['index'] = int(data['index'])
data = parse(strip.name, template=project.shot_template)
data['index'] = int(data['index'])
data['sequence'] = get_strip_sequence_name(strip)
data['strip'] = strip.name data['strip'] = strip.name
#data['shot'] = project.shot_template #data['shot'] = project.shot_template
else: else:
@ -677,11 +747,14 @@ classes = (
Episode, Episode,
Metadata, Metadata,
MetadataType, MetadataType,
Sequence,
TaskType, TaskType,
ShotTasks, ShotTasks,
ShotTask, ShotTask,
SpreadsheetImport, SpreadsheetImport,
SpreadsheetExport, SpreadsheetExport,
ImportShots,
UploadToTracker,
Project, Project,
VSETB_UL_spreadsheet_import, VSETB_UL_spreadsheet_import,
VSETB_UL_spreadsheet_export, VSETB_UL_spreadsheet_export,