Blender Kitsu: Add Operator to Import Playblasts into Edit #274
BIN
docs/media/addons/blender_kitsu/Shot_as_Image_Sequence.jpg
(Stored with Git LFS)
BIN
docs/media/addons/blender_kitsu/Shot_as_Image_Sequence.jpg
(Stored with Git LFS)
Binary file not shown.
BIN
docs/media/addons/blender_kitsu/import_playblast.jpg
(Stored with Git LFS)
Normal file
BIN
docs/media/addons/blender_kitsu/import_playblast.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/media/addons/blender_kitsu/media_panel.jpg
(Stored with Git LFS)
Normal file
BIN
docs/media/addons/blender_kitsu/media_panel.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/media/addons/blender_kitsu/shot_image_sequence.jpg
(Stored with Git LFS)
Normal file
BIN
docs/media/addons/blender_kitsu/shot_image_sequence.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -16,6 +16,7 @@ For each new task type, Anim/Layout etc needs to be added manually, then it can
|
|||||||
Returning to your edit .blend file, we can now load the playblast from the animation file into the edit.
|
Returning to your edit .blend file, we can now load the playblast from the animation file into the edit.
|
||||||
|
|
||||||
1. Open your edit .blend file inside the directory `/your_project_name/svn/edit`
|
1. Open your edit .blend file inside the directory `/your_project_name/svn/edit`
|
||||||
2. From the Sequencer Header select `Add>Movie`
|
2. Select the Metadata Strip associated with your shot
|
||||||
3. Navigate to the directory of the playblast for your shot's .blend file `your_project_name/shared/footage/pro/{sequence_name}/{shot_name}/{file_name}` and select the `.mp4` file
|
3. From the Sequencer Side Panel select `Import 1 Shot Playblast`
|
||||||
4. Place the new shot at the same timing as the corresponding metastrip
|
3. Select the Task Type you would like to load the playblast from and an empty channel
|
||||||
|
4. Your new playblast will be imported with the same timing as the corresponding metadata strip
|
@ -16,4 +16,4 @@ Renders approved by the render review Add-On can be automatically imported into
|
|||||||
|
|
||||||
1. Open your Edit .blend file
|
1. Open your Edit .blend file
|
||||||
2. Select the video strip representing the shot that has an approved render. In the Kitsu Sidebar under General Tools select, `^` to load the next playblast from that shot automatically, which is an **mp4 preview** of your final render
|
2. Select the video strip representing the shot that has an approved render. In the Kitsu Sidebar under General Tools select, `^` to load the next playblast from that shot automatically, which is an **mp4 preview** of your final render
|
||||||
3. Select the video strips representing all the shots you have approved renders for. Use `Shot as Image Sequence` to import the final image sequences for each shot as EXR or JPG and load it to a new channel in the VSE
|
3. Select the metadata strips representing all the shots you have approved renders for. Use `Import Image Sequence` operator to import the final image sequences for each shot as EXR or JPG and load it to a new channel in the VSE
|
||||||
|
@ -14,7 +14,7 @@ blender-kitsu is a Blender Add-on to interact with Kitsu from within Blender. It
|
|||||||
- [Push](#push)
|
- [Push](#push)
|
||||||
- [Pull](#pull)
|
- [Pull](#pull)
|
||||||
- [Multi Edit](#multi-edit)
|
- [Multi Edit](#multi-edit)
|
||||||
- [Shot as Image Sequence](#shot-as-image-sequence)
|
- [Import Media](#import-media)
|
||||||
- [General Sequence Editor Tools](#general-sequence-editor-tools)
|
- [General Sequence Editor Tools](#general-sequence-editor-tools)
|
||||||
- [Context](#context)
|
- [Context](#context)
|
||||||
- [Animation Tools](#animation-tools)
|
- [Animation Tools](#animation-tools)
|
||||||
@ -192,13 +192,21 @@ The `Multi Edit` panel only appears when you select multiple metadata strips tha
|
|||||||
![image info](/media/addons/blender_kitsu/sqe_multi_edit.jpg)
|
![image info](/media/addons/blender_kitsu/sqe_multi_edit.jpg)
|
||||||
|
|
||||||
It is meant to be way to quickly setup lots of shots if they don't exist on Kitsu yet. You specify the sequence all shots should belong to and adjust the `Shot Counter Start` value. In the preview property you can see how all shots will be named when you execute the `Multi Edit Strip` operator. <br/>
|
It is meant to be way to quickly setup lots of shots if they don't exist on Kitsu yet. You specify the sequence all shots should belong to and adjust the `Shot Counter Start` value. In the preview property you can see how all shots will be named when you execute the `Multi Edit Strip` operator. <br/>
|
||||||
|
##### Import Media
|
||||||
|
A collection of operators to Import media based on the Shot associated with the selected metadata strip(s). <br/>
|
||||||
|
|
||||||
##### Shot as Image Sequence
|
![Media Panel](/media/addons/blender_kitsu/media_panel.jpg)
|
||||||
The `Shot as Image Sequence` Operator will replace a playblast from your Playblast Root directory with an image sequence located in the Frames Root directory. The Shots Directory and the Frames Directory should have matching folder structures. Typically the format is `/{sequence_name}/{shot_name}/{shot_name}-{shot_task}/`
|
##### Import Playblast
|
||||||
|
With a metadata strip selected `Import Playblast` Operator will find an image sequence of a given task type located in the Frames Root directory.
|
||||||
|
![Import Playblast](/media/addons/blender_kitsu/import_playblast.jpg)
|
||||||
|
|
||||||
![Shot as Image Sequence](/media/addons/blender_kitsu/Shot_as_Image_Sequence.jpg)
|
Use this operator to import image sequences that have been approved via the [Render Review Add-On](/addons/render_review) Image Sequences can be loaded as either `EXR` or `JPG` sequences.
|
||||||
|
|
||||||
Use this operator to replace playblasts with image sequences that have been approved via the [Render Review Add-On](/addons/render_review) Image Sequences can be loaded as either `EXR` or `JPG` sequences.
|
##### Import Image Sequence
|
||||||
|
With a metadata strip selected `Import Image Sequence` Operator will find an image sequence of a given task type located in the Frames Root directory.
|
||||||
|
![Import Image Sequence](/media/addons/blender_kitsu/shot_image_sequence.jpg)
|
||||||
|
|
||||||
|
Use this operator to import image sequences that have been approved via the [Render Review Add-On](/addons/render_review) Image Sequences can be loaded as either `EXR` or `JPG` sequences.
|
||||||
|
|
||||||
###### Advanced Settings
|
###### Advanced Settings
|
||||||
If you check the `Advanced` checkbox next to the counter value, you have access to advance settings to customize the operator even more.
|
If you check the `Advanced` checkbox next to the counter value, you have access to advance settings to customize the operator even more.
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from bpy.types import Context
|
||||||
import gazu
|
import gazu
|
||||||
import contextlib
|
import contextlib
|
||||||
import colorsys
|
import colorsys
|
||||||
@ -2195,23 +2197,133 @@ class KITSU_OT_sqe_scan_for_media_updates(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class KITSU_OT_shot_image_sequence(bpy.types.Operator):
|
def get_used_channels(self: Any, context: bpy.types.Context, edit_text: str = "") -> List[str]:
|
||||||
bl_idname = "kitsu.shot_image_sequence"
|
used_channels = []
|
||||||
bl_label = "Shot as Image Sequence"
|
for seq in context.scene.sequence_editor.sequences_all:
|
||||||
bl_description = "Import image sequences for selected clips"
|
used_channels.append(seq.channel)
|
||||||
|
|
||||||
|
aval_channels = []
|
||||||
|
for channel in range(1, 100):
|
||||||
|
if channel not in used_channels:
|
||||||
|
aval_channels.append(channel)
|
||||||
|
|
||||||
|
return [f"{channel}" for channel in aval_channels]
|
||||||
|
|
||||||
|
|
||||||
|
def get_shot_task_types_enum_list(self, context: bpy.types.Context) -> List[Tuple[str, str, str]]:
|
||||||
|
active_project = cache.project_active_get()
|
||||||
|
return [
|
||||||
|
(t.id, t.name, "")
|
||||||
|
for t in TaskType.all_shot_task_types()
|
||||||
|
if t.id in active_project.task_types
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class KITSU_OT_sqe_import_playblast(bpy.types.Operator):
|
||||||
|
bl_idname = "kitsu.sqe_import_playblast"
|
||||||
|
bl_label = "Import Playblast"
|
||||||
|
bl_description = "Import playblast for selected metadata strips"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def get_used_channels(self: Any, context: bpy.types.Context, edit_text: str) -> List[str]:
|
channel_selection: bpy.props.StringProperty( # type: ignore
|
||||||
used_channels = []
|
name="Channel",
|
||||||
for seq in context.scene.sequence_editor.sequences_all:
|
description="Choose an empty target channel to place playblasts onto",
|
||||||
used_channels.append(seq.channel)
|
search=get_used_channels,
|
||||||
|
search_options={'SORT'},
|
||||||
|
)
|
||||||
|
|
||||||
aval_channels = []
|
task_type: bpy.props.EnumProperty( # type: ignore
|
||||||
for channel in range(1, 100):
|
name="Task Type",
|
||||||
if channel not in used_channels:
|
description="Choose a task type to import playblasts for",
|
||||||
aval_channels.append(channel)
|
items=get_shot_task_types_enum_list,
|
||||||
|
)
|
||||||
|
|
||||||
return [f"{channel}" for channel in aval_channels]
|
@classmethod
|
||||||
|
def poll(cls, context: bpy.types.Context) -> bool:
|
||||||
|
sqe = context.scene.sequence_editor
|
||||||
|
if not sqe:
|
||||||
|
return False
|
||||||
|
if not cache.project_active_get():
|
||||||
|
cls.poll_message_set("No Kitsu Project Found check Add-on Preferences")
|
||||||
|
return False
|
||||||
|
for strip in context.selected_sequences:
|
||||||
|
if strip.kitsu.shot_id == "":
|
||||||
|
cls.poll_message_set(f"Selected strip {strip.name} is not metadata strip'")
|
||||||
|
return False
|
||||||
|
if len(bpy.context.selected_sequences) == 0:
|
||||||
|
cls.poll_message_set("Please select one or more metadata strips")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
channels = [int(x) for x in get_used_channels(self, context)]
|
||||||
|
strip = context.selected_sequences[0]
|
||||||
|
channel = min(channels, key=lambda x: abs(x - strip.channel))
|
||||||
|
self.channel_selection = f"{channel}"
|
||||||
|
return context.window_manager.invoke_props_dialog(self, width=500)
|
||||||
|
|
||||||
|
def draw(self, context: bpy.types.Context) -> None:
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "channel_selection")
|
||||||
|
layout.prop(self, "task_type")
|
||||||
|
|
||||||
|
def execute(self, context: Context) -> Set[str] | Set[int]:
|
||||||
|
succeeded: Set[str] = set()
|
||||||
|
failed: Set[str] = set()
|
||||||
|
sequences = context.scene.sequence_editor.sequences
|
||||||
|
metadata_strips = [
|
||||||
|
strip for strip in context.selected_sequences if strip.kitsu.shot_id != ''
|
||||||
|
]
|
||||||
|
for metadata_strip in metadata_strips:
|
||||||
|
# TODO add try except if ID is not valid, do same for shot as image sequence.
|
||||||
|
shot = Shot.by_id(metadata_strip.kitsu.shot_id)
|
||||||
|
task_type_short_name = TaskType.by_id(self.task_type).get_short_name()
|
||||||
|
filepath = shot.get_latest_playblast_file(context, task_type_short_name)
|
||||||
|
if filepath:
|
||||||
|
playblast = sequences.new_movie(
|
||||||
|
name=Path(filepath).name,
|
||||||
|
filepath=filepath,
|
||||||
|
frame_start=int(metadata_strip.frame_start),
|
||||||
|
channel=int(self.channel_selection),
|
||||||
|
)
|
||||||
|
if playblast.frame_final_end > metadata_strip.frame_final_end:
|
||||||
|
playblast.frame_final_end = metadata_strip.frame_final_end
|
||||||
|
|
||||||
|
succeeded.add(metadata_strip.name)
|
||||||
|
else:
|
||||||
|
failed.add(metadata_strip.name)
|
||||||
|
|
||||||
|
if len(metadata_strips) == 1:
|
||||||
|
if len(failed) == 1:
|
||||||
|
self.report({"WARNING"}, f"Failed to import Playblast `{failed[0]}` does not exist")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
if len(succeeded) == 1:
|
||||||
|
self.report({"INFO"}, f"Imported Playblast from `{succeeded[0]}`")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
report_str = f"Imported {len(succeeded)} Playblast"
|
||||||
|
report_state = "INFO"
|
||||||
|
if failed:
|
||||||
|
report_state = "WARNING"
|
||||||
|
report_str += f" | Failed: {len(failed)}"
|
||||||
|
|
||||||
|
self.report(
|
||||||
|
{report_state},
|
||||||
|
report_str,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.report(
|
||||||
|
{report_state},
|
||||||
|
report_str,
|
||||||
|
)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class KITSU_OT_sqe_import_image_sequence(bpy.types.Operator):
|
||||||
|
bl_idname = "kitsu.sqe_import_image_sequence"
|
||||||
|
bl_label = "Import Image Sequence"
|
||||||
|
bl_description = "Import Image Sequence for selected metadata strips"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
channel_selection: bpy.props.StringProperty(
|
channel_selection: bpy.props.StringProperty(
|
||||||
name="Channel",
|
name="Channel",
|
||||||
@ -2234,6 +2346,12 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator):
|
|||||||
default=True,
|
default=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
task_type: bpy.props.EnumProperty( # type: ignore
|
||||||
|
name="Task Type",
|
||||||
|
description="Choose a task type to import playblasts for",
|
||||||
|
items=get_shot_task_types_enum_list,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context: bpy.types.Context) -> bool:
|
def poll(cls, context: bpy.types.Context) -> bool:
|
||||||
sqe = context.scene.sequence_editor
|
sqe = context.scene.sequence_editor
|
||||||
@ -2242,9 +2360,9 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator):
|
|||||||
if not cache.project_active_get():
|
if not cache.project_active_get():
|
||||||
cls.poll_message_set("No Kitsu Project Found check Add-on Preferences")
|
cls.poll_message_set("No Kitsu Project Found check Add-on Preferences")
|
||||||
return False
|
return False
|
||||||
for sqe in context.selected_sequences:
|
for strip in context.selected_sequences:
|
||||||
if sqe.type != 'MOVIE':
|
if strip.kitsu.shot_id == "":
|
||||||
cls.poll_message_set("Selected strips must be 'MOVIE'")
|
cls.poll_message_set(f"Selected strip {strip.name} is not metadata strip'")
|
||||||
return False
|
return False
|
||||||
if len(bpy.context.selected_sequences) == 0:
|
if len(bpy.context.selected_sequences) == 0:
|
||||||
cls.poll_message_set("Please select a 'MOVIE' strip")
|
cls.poll_message_set("Please select a 'MOVIE' strip")
|
||||||
@ -2268,7 +2386,7 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator):
|
|||||||
scene.view_settings.view_transform = 'Filmic'
|
scene.view_settings.view_transform = 'Filmic'
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
channels = [int(x) for x in self.get_used_channels(context, "")]
|
channels = [int(x) for x in get_used_channels(self, context)]
|
||||||
strip = context.selected_sequences[0]
|
strip = context.selected_sequences[0]
|
||||||
channel = min(channels, key=lambda x: abs(x - strip.channel))
|
channel = min(channels, key=lambda x: abs(x - strip.channel))
|
||||||
self.channel_selection = f"{channel}"
|
self.channel_selection = f"{channel}"
|
||||||
@ -2276,6 +2394,7 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator):
|
|||||||
|
|
||||||
def draw(self, context: bpy.types.Context) -> None:
|
def draw(self, context: bpy.types.Context) -> None:
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
layout.prop(self, "task_type")
|
||||||
layout.prop(self, "channel_selection")
|
layout.prop(self, "channel_selection")
|
||||||
layout.prop(self, "file_type")
|
layout.prop(self, "file_type")
|
||||||
layout.prop(self, "set_color_space")
|
layout.prop(self, "set_color_space")
|
||||||
@ -2288,23 +2407,13 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator):
|
|||||||
return match.group(1)
|
return match.group(1)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_metadata_strip(self, context, strip):
|
def import_strip(self, context, metadata_strip, directory, channel):
|
||||||
name = self.get_shot_name(strip)
|
|
||||||
for strip in context.scene.sequence_editor.sequences_all:
|
|
||||||
if strip.name == name:
|
|
||||||
return strip
|
|
||||||
return
|
|
||||||
|
|
||||||
def import_strip(self, context, strip, directory, channel):
|
|
||||||
# https://blender.stackexchange.com/questions/286946/how-to-add-image-sequence-in-sequencer-via-python
|
# https://blender.stackexchange.com/questions/286946/how-to-add-image-sequence-in-sequencer-via-python
|
||||||
frame_start = strip.frame_final_start
|
frame_start = metadata_strip.frame_final_start
|
||||||
frame_end = strip.frame_final_end
|
frame_end = metadata_strip.frame_final_end
|
||||||
|
|
||||||
files = []
|
files = []
|
||||||
metadata_strip = self.get_metadata_strip(context, strip)
|
|
||||||
if not metadata_strip:
|
|
||||||
self.report({'ERROR'}, f"No Metadata Strip found for {strip.name}")
|
|
||||||
return {'CANCELLED'}
|
|
||||||
shot = Shot.by_id(metadata_strip.kitsu.shot_id)
|
shot = Shot.by_id(metadata_strip.kitsu.shot_id)
|
||||||
start_frame = (
|
start_frame = (
|
||||||
shot.data.get('3d_start') if shot.data.get('3d_start') else bkglobals.FRAME_START
|
shot.data.get('3d_start') if shot.data.get('3d_start') else bkglobals.FRAME_START
|
||||||
@ -2313,7 +2422,7 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator):
|
|||||||
if file.name.endswith("mp4"):
|
if file.name.endswith("mp4"):
|
||||||
continue
|
continue
|
||||||
frame_number = int(file.name.split(".")[0])
|
frame_number = int(file.name.split(".")[0])
|
||||||
duration = strip.frame_duration + start_frame
|
duration = metadata_strip.frame_duration + start_frame
|
||||||
if self.file_type in file.name and frame_number < duration:
|
if self.file_type in file.name and frame_number < duration:
|
||||||
files.append({"name": file.name})
|
files.append({"name": file.name})
|
||||||
|
|
||||||
@ -2345,24 +2454,24 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator):
|
|||||||
fit_method='FIT',
|
fit_method='FIT',
|
||||||
)
|
)
|
||||||
if len_strip + 1 != len(context.scene.sequence_editor.sequences_all):
|
if len_strip + 1 != len(context.scene.sequence_editor.sequences_all):
|
||||||
print(f"Failed to import image sequence for {strip.name}")
|
print(f"Failed to import image sequence for {metadata_strip.name}")
|
||||||
return
|
return
|
||||||
|
|
||||||
new_strip = context.selected_sequences[0]
|
new_strip = context.selected_sequences[0]
|
||||||
|
|
||||||
new_strip.animation_offset_end = strip.animation_offset_end
|
new_strip.animation_offset_end = metadata_strip.animation_offset_end
|
||||||
new_strip.animation_offset_start = strip.animation_offset_start
|
new_strip.animation_offset_start = metadata_strip.animation_offset_start
|
||||||
|
|
||||||
new_strip.frame_offset_end = strip.frame_offset_end
|
new_strip.frame_offset_end = metadata_strip.frame_offset_end
|
||||||
new_strip.frame_offset_start = strip.frame_offset_start
|
new_strip.frame_offset_start = metadata_strip.frame_offset_start
|
||||||
new_strip.frame_start = strip.frame_start
|
new_strip.frame_start = metadata_strip.frame_start
|
||||||
new_strip.channel = channel
|
new_strip.channel = channel
|
||||||
new_strip.name = f"{self.get_shot_name(strip)}{self.file_type.lower()}"
|
new_strip.name = f"{self.get_shot_name(metadata_strip)}{self.file_type.lower()}"
|
||||||
new_strip.colorspace_settings.name = new_strip.colorspace_settings.name
|
new_strip.colorspace_settings.name = new_strip.colorspace_settings.name
|
||||||
|
|
||||||
def get_shot_seq_directory(self, context, strip):
|
def get_shot_seq_directory(self, context, filepath):
|
||||||
addon_prefs = prefs.addon_prefs_get(context)
|
addon_prefs = prefs.addon_prefs_get(context)
|
||||||
path_string = os.path.realpath(bpy.path.abspath(strip.filepath))
|
path_string = os.path.realpath(bpy.path.abspath(filepath))
|
||||||
path = Path(
|
path = Path(
|
||||||
path_string.replace(
|
path_string.replace(
|
||||||
addon_prefs.shot_playblast_root_dir, addon_prefs.frames_root_dir
|
addon_prefs.shot_playblast_root_dir, addon_prefs.frames_root_dir
|
||||||
@ -2372,6 +2481,8 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||||
# Get closest empty channel
|
# Get closest empty channel
|
||||||
|
succeeded = []
|
||||||
|
failed = []
|
||||||
channel = int(self.channel_selection)
|
channel = int(self.channel_selection)
|
||||||
addon_prefs = prefs.addon_prefs_get(context)
|
addon_prefs = prefs.addon_prefs_get(context)
|
||||||
if not (
|
if not (
|
||||||
@ -2386,12 +2497,42 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator):
|
|||||||
if self.set_color_space:
|
if self.set_color_space:
|
||||||
self.set_scene_colorspace(context)
|
self.set_scene_colorspace(context)
|
||||||
|
|
||||||
for strip in [strip for strip in context.selected_sequences if strip.type == 'MOVIE']:
|
metadata_strips = [
|
||||||
directory = self.get_shot_seq_directory(context, strip)
|
strip for strip in context.selected_sequences if strip.kitsu.shot_id != ''
|
||||||
|
]
|
||||||
|
|
||||||
|
for strip in metadata_strips:
|
||||||
|
shot = Shot().by_id(strip.kitsu.shot_id)
|
||||||
|
# TODO pass task type as variable
|
||||||
|
task_type_short_name = TaskType.by_id(self.task_type).get_short_name()
|
||||||
|
filepath = shot.get_latest_playblast_file(context, task_type_short_name)
|
||||||
|
directory = self.get_shot_seq_directory(context, filepath)
|
||||||
if not directory.exists():
|
if not directory.exists():
|
||||||
self.report({"ERROR"}, f"{directory._str} does not exist")
|
failed.append(str(directory))
|
||||||
return {"CANCELLED"}
|
continue
|
||||||
self.import_strip(context, strip, directory, channel)
|
self.import_strip(context, strip, directory, channel)
|
||||||
|
succeeded.append(str(directory))
|
||||||
|
|
||||||
|
if len(metadata_strips) == 1:
|
||||||
|
if len(failed) == 1:
|
||||||
|
self.report(
|
||||||
|
{"WARNING"}, f"Failed to import Image Sequence `{failed[0]}` does not exist"
|
||||||
|
)
|
||||||
|
return {"CANCELLED"}
|
||||||
|
if len(succeeded) == 1:
|
||||||
|
self.report({"INFO"}, f"Imported Image Sequence from `{succeeded[0]}`")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
report_str = f"Imported {len(succeeded)} Image Sequences"
|
||||||
|
report_state = "INFO"
|
||||||
|
if failed:
|
||||||
|
report_state = "WARNING"
|
||||||
|
report_str += f" | Failed: {len(failed)}"
|
||||||
|
|
||||||
|
self.report(
|
||||||
|
{report_state},
|
||||||
|
report_str,
|
||||||
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@ -2594,7 +2735,8 @@ classes = [
|
|||||||
KITSU_OT_sqe_scan_for_media_updates,
|
KITSU_OT_sqe_scan_for_media_updates,
|
||||||
KITSU_OT_sqe_change_strip_source,
|
KITSU_OT_sqe_change_strip_source,
|
||||||
KITSU_OT_sqe_clear_update_indicators,
|
KITSU_OT_sqe_clear_update_indicators,
|
||||||
KITSU_OT_shot_image_sequence,
|
KITSU_OT_sqe_import_image_sequence,
|
||||||
|
KITSU_OT_sqe_import_playblast,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,7 +50,8 @@ from ..sqe.ops import (
|
|||||||
KITSU_OT_sqe_scan_for_media_updates,
|
KITSU_OT_sqe_scan_for_media_updates,
|
||||||
KITSU_OT_sqe_change_strip_source,
|
KITSU_OT_sqe_change_strip_source,
|
||||||
KITSU_OT_sqe_clear_update_indicators,
|
KITSU_OT_sqe_clear_update_indicators,
|
||||||
KITSU_OT_shot_image_sequence,
|
KITSU_OT_sqe_import_image_sequence,
|
||||||
|
KITSU_OT_sqe_import_playblast,
|
||||||
)
|
)
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -121,6 +122,7 @@ class KITSU_PT_sqe_shot_tools(bpy.types.Panel):
|
|||||||
|
|
||||||
if self.poll_pull(context):
|
if self.poll_pull(context):
|
||||||
self.draw_pull(context)
|
self.draw_pull(context)
|
||||||
|
self.draw_media(context)
|
||||||
|
|
||||||
if self.poll_debug(context):
|
if self.poll_debug(context):
|
||||||
self.draw_debug(context)
|
self.draw_debug(context)
|
||||||
@ -639,6 +641,30 @@ class KITSU_PT_sqe_shot_tools(bpy.types.Panel):
|
|||||||
icon="MODIFIER_ON",
|
icon="MODIFIER_ON",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def draw_media(self, context: bpy.types.Context) -> None:
|
||||||
|
|
||||||
|
sel_metadata_strips = [strip for strip in context.selected_sequences if strip.kitsu.linked]
|
||||||
|
|
||||||
|
noun = get_selshots_noun(len(sel_metadata_strips), prefix=f"{len(sel_metadata_strips)}")
|
||||||
|
playblast = "Playblast" if len(sel_metadata_strips) <= 1 else "Playblasts"
|
||||||
|
|
||||||
|
sequence = "Sequence" if len(sel_metadata_strips) <= 1 else "Sequences"
|
||||||
|
|
||||||
|
# Create box.
|
||||||
|
layout = self.layout
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text="Media", icon="RENDER_ANIMATION")
|
||||||
|
box.operator(
|
||||||
|
KITSU_OT_sqe_import_playblast.bl_idname,
|
||||||
|
text=f"Import {noun} {playblast}",
|
||||||
|
icon="FILE_MOVIE",
|
||||||
|
)
|
||||||
|
box.operator(
|
||||||
|
KITSU_OT_sqe_import_image_sequence.bl_idname,
|
||||||
|
text=f"Import {noun} Image {sequence}",
|
||||||
|
icon="RENDER_RESULT",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class KITSU_PT_sqe_general_tools(bpy.types.Panel):
|
class KITSU_PT_sqe_general_tools(bpy.types.Panel):
|
||||||
"""
|
"""
|
||||||
@ -684,7 +710,6 @@ class KITSU_PT_sqe_general_tools(bpy.types.Panel):
|
|||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.label(text="General", icon="MODIFIER")
|
box.label(text="General", icon="MODIFIER")
|
||||||
|
|
||||||
box.operator(KITSU_OT_shot_image_sequence.bl_idname)
|
|
||||||
# Scan for outdated media and reset operator.
|
# Scan for outdated media and reset operator.
|
||||||
row = box.row(align=True)
|
row = box.row(align=True)
|
||||||
row.operator(
|
row.operator(
|
||||||
|
@ -27,6 +27,8 @@ import gazu
|
|||||||
from .logger import LoggerFactory
|
from .logger import LoggerFactory
|
||||||
from . import bkglobals
|
from . import bkglobals
|
||||||
from . import prefs
|
from . import prefs
|
||||||
|
from .models import FileListModel
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
logger = LoggerFactory.getLogger()
|
logger = LoggerFactory.getLogger()
|
||||||
|
|
||||||
@ -614,24 +616,54 @@ class Shot(Entity):
|
|||||||
def get_output_collection_name(self, task_type_short_name: str) -> str:
|
def get_output_collection_name(self, task_type_short_name: str) -> str:
|
||||||
return f"{self.get_task_name(task_type_short_name)}{bkglobals.DELIMITER}output"
|
return f"{self.get_task_name(task_type_short_name)}{bkglobals.DELIMITER}output"
|
||||||
|
|
||||||
def get_dir(self, context) -> str:
|
def get_shot_folder_tree(self, base_path: Path) -> str:
|
||||||
project_root_dir = prefs.project_root_dir_get(context)
|
|
||||||
all_shots_dir = project_root_dir.joinpath('pro').joinpath('shots')
|
|
||||||
|
|
||||||
# Add Episode to Path if available
|
# Add Episode to Path if available
|
||||||
if self.episode_id:
|
if self.episode_id:
|
||||||
base_dir = all_shots_dir.joinpath(self.episode_name)
|
base_dir = base_path.joinpath(self.episode_name)
|
||||||
else:
|
else:
|
||||||
base_dir = all_shots_dir
|
base_dir = base_path
|
||||||
|
|
||||||
seq = self.get_sequence()
|
seq = self.get_sequence()
|
||||||
shot_dir = base_dir.joinpath(seq.name).joinpath(self.name)
|
shot_dir = base_dir.joinpath(seq.name).joinpath(self.name)
|
||||||
return shot_dir.__str__()
|
return shot_dir
|
||||||
|
|
||||||
|
def get_dir(self, context) -> str:
|
||||||
|
project_root_dir = prefs.project_root_dir_get(context)
|
||||||
|
all_shots_dir = project_root_dir.joinpath('pro').joinpath('shots')
|
||||||
|
return str(self.get_shot_folder_tree(all_shots_dir))
|
||||||
|
|
||||||
def get_filepath(self, context, task_type_short_name: str) -> str:
|
def get_filepath(self, context, task_type_short_name: str) -> str:
|
||||||
file_name = self.get_task_name(task_type_short_name) + '.blend'
|
file_name = self.get_task_name(task_type_short_name) + '.blend'
|
||||||
return Path(self.get_dir(context)).joinpath(file_name).__str__()
|
return Path(self.get_dir(context)).joinpath(file_name).__str__()
|
||||||
|
|
||||||
|
def get_playblast_dir(self, context, task_type_short_name: str) -> str:
|
||||||
|
addon_prefs = prefs.addon_prefs_get(context)
|
||||||
|
playblsat_dir = addon_prefs.shot_playblast_root_dir
|
||||||
|
shot_dir = self.get_shot_folder_tree(Path(playblsat_dir))
|
||||||
|
task_dir = shot_dir.joinpath(self.name + bkglobals.DELIMITER + task_type_short_name)
|
||||||
|
return task_dir.__str__()
|
||||||
|
|
||||||
|
def get_latest_playblast_file(self, context, task_type_short_name: str):
|
||||||
|
filemodel = FileListModel()
|
||||||
|
filemodel.reset()
|
||||||
|
playblast_dir = Path(self.get_playblast_dir(context, task_type_short_name))
|
||||||
|
filemodel.root_path = playblast_dir
|
||||||
|
if len(filemodel.items) < 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
playblast_files = set()
|
||||||
|
for file in filemodel.items:
|
||||||
|
filepath = playblast_dir.joinpath(file)
|
||||||
|
if mimetypes.guess_type(filepath)[0].startswith('video'):
|
||||||
|
playblast_files.add(filepath)
|
||||||
|
|
||||||
|
playblast_files = sorted(playblast_files, key=lambda x: str(x), reverse=True)
|
||||||
|
file = playblast_files[0]
|
||||||
|
|
||||||
|
if not file.exists():
|
||||||
|
return
|
||||||
|
return str(file)
|
||||||
|
|
||||||
def update_data(self, data: Dict[str, Any]) -> Shot:
|
def update_data(self, data: Dict[str, Any]) -> Shot:
|
||||||
gazu.shot.update_shot_data(asdict(self), data=data)
|
gazu.shot.update_shot_data(asdict(self), data=data)
|
||||||
if not self.data:
|
if not self.data:
|
||||||
|
Loading…
Reference in New Issue
Block a user