rewrite import_shots and cleanup

This commit is contained in:
ChristopheSeux
2024-04-10 16:15:57 +02:00
parent dae9d55494
commit 2960bab63d
15 changed files with 1068 additions and 269 deletions
+351 -43
View File
@@ -1,5 +1,9 @@
from pathlib import Path
from os.path import expandvars
import re
import glob
from tempfile import gettempdir
import bpy
from bpy.types import Operator, UIList
@@ -9,11 +13,11 @@ from vse_toolbox.constants import (EDITS, EDIT_SUFFIXES, MOVIES, MOVIE_SUFFIXES,
SOUNDS, SOUND_SUFFIXES)
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.file_utils import install_module
from vse_toolbox.bl_utils import (get_scene_settings, get_addon_prefs, get_scene_settings)
from vse_toolbox.file_utils import install_module, parse, find_last
#from vse_toolbox.template import Template
class VSETB_OT_auto_select_files(Operator):
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,
active_propname, index):
layout.separator(factor=0.5)
layout.prop(item, 'import_enabled', text='')
layout.label(text=item.name)
class VSETB_OT_import_shots(Operator):
bl_idname = "vse_toolbox.import_shots"
bl_label = "Import Shots"
bl_description = "Import Shots for disk or tracker"
def get_task_items(self, context):
settings = get_scene_settings()
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_property = "task"
task: EnumProperty(name="My Search", items=get_task_items)
@classmethod
def poll(cls, context):
return True
@@ -212,76 +227,369 @@ class VSETB_OT_import_shots(Operator):
settings = get_scene_settings()
project = settings.active_project
from gadget import Project
project.task_types[self.task].import_enabled = True
p = Project.from_env()
p.shots.fetch()
return {'FINISHED'}
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]
for i, task_type in enumerate(tasks):
channel_index = 9 + i
channel_index += 3
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:
scn.sequence_editor.channels[9]
def get_preview_dir(self):
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'):
for i, task_type in enumerate(tasks):
shot = p.shots[strip.name]
versions = shot.tasks[task_type.name].outputs[0].versions.fetch()
def download_preview(self, task_type, shot):
prefs = get_addon_prefs()
tracker = prefs.tracker
if versions:
print(versions[-1])
imported_strip = import_movie(versions[-1].path)
imported_strip.frame_start = strip.frame_start
task = tracker.get_task(task_type.id or task_type.name, entity=shot)
last_comment = tracker.get_last_comment_with_preview(task)
if not last_comment:
return
if imported_strip.frame_final_duration != strip.frame_final_duration:
print(f'strip {strip.name} has different duration')
imported_strip.color_tag = 'COLOR_03'
shot_name = shot['name']
sequence_name = f"{shot['sequence_name']}_"
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)
print(get_channel_index(task_type.name), imported_strip.channel)
else:
print(f'No movie for {strip.name} of task {task_type.name}')
if not preview:
print(f'No preview found for shot {shot_data["name"]}')
continue
print(f'Loading Preview from {preview}')
# Load Video
channel_index = get_channel_index(f'{task_type.name} Video')
video_clip = import_movie(preview)
video_clip.frame_start = frame_index
video_clip.channel = channel_index
# for task_type in tasks:
# for strip in get_strips(task_type.name):
# strip.channel = get_channel_index(task_type.name)
if not conformed:
self.conform_render(video_clip)
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'}
def draw(self, context):
settings = get_scene_settings()
project = settings.active_project
import_shots = project.import_shots
layout = self.layout
col = layout.column(align=False)
col.use_property_split = True
col.use_property_decorate = False
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.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':
col.use_property_split = False
col.template_list("VSETB_UL_import_task", "import_task", project, "task_types", project, "task_type_index", rows=8)
tasks = [t for t in project.task_types if t.import_enabled]
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):
scn = context.scene
settings = get_scene_settings()
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 = (
VSETB_OT_select_sequence,
VSETB_OT_unselect_sequence,
VSETB_OT_unselect_task,
VSETB_OT_select_task,
VSETB_UL_import_task,
VSETB_OT_auto_select_files,
VSETB_OT_import_files,