mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #699 from pypeclub/feature/686-standalonepublisher-editorial-from-image-sequences
686 standalonepublisher editorial from image sequences
This commit is contained in:
commit
14f348811d
10 changed files with 555 additions and 145 deletions
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Optional:
|
||||
instance.data["remove"] -> mareker for removing
|
||||
"""
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectClearInstances(pyblish.api.InstancePlugin):
|
||||
"""Clear all marked instances"""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.4999
|
||||
label = "Clear Instances"
|
||||
hosts = ["standalonepublisher"]
|
||||
|
||||
def process(self, instance):
|
||||
self.log.debug(
|
||||
f"Instance: `{instance}` | "
|
||||
f"families: `{instance.data['families']}`")
|
||||
if instance.data.get("remove"):
|
||||
self.log.info(f"Removing: {instance}")
|
||||
instance.context.remove(instance)
|
||||
|
|
@ -1,3 +1,19 @@
|
|||
"""
|
||||
Optional:
|
||||
presets -> extensions (
|
||||
example of use:
|
||||
[".mov", ".mp4"]
|
||||
)
|
||||
presets -> source_dir (
|
||||
example of use:
|
||||
"C:/pathToFolder"
|
||||
"{root}/{project[name]}/inputs"
|
||||
"{root[work]}/{project[name]}/inputs"
|
||||
"./input"
|
||||
"../input"
|
||||
)
|
||||
"""
|
||||
|
||||
import os
|
||||
import opentimelineio as otio
|
||||
import pyblish.api
|
||||
|
|
@ -33,8 +49,10 @@ class CollectEditorial(pyblish.api.InstancePlugin):
|
|||
|
||||
# presets
|
||||
extensions = [".mov", ".mp4"]
|
||||
source_dir = None
|
||||
|
||||
def process(self, instance):
|
||||
root_dir = None
|
||||
# remove context test attribute
|
||||
if instance.context.data.get("subsetNamesCheck"):
|
||||
instance.context.data.pop("subsetNamesCheck")
|
||||
|
|
@ -53,19 +71,42 @@ class CollectEditorial(pyblish.api.InstancePlugin):
|
|||
# get video file path
|
||||
video_path = None
|
||||
basename = os.path.splitext(os.path.basename(file_path))[0]
|
||||
for f in os.listdir(staging_dir):
|
||||
self.log.debug(f"__ test file: `{f}`")
|
||||
# filter out by not sharing the same name
|
||||
if os.path.splitext(f)[0] not in basename:
|
||||
continue
|
||||
# filter out by respected extensions
|
||||
if os.path.splitext(f)[1] not in self.extensions:
|
||||
continue
|
||||
video_path = os.path.join(
|
||||
staging_dir, f
|
||||
)
|
||||
self.log.debug(f"__ video_path: `{video_path}`")
|
||||
instance.data["editorialVideoPath"] = video_path
|
||||
|
||||
if self.source_dir:
|
||||
source_dir = self.source_dir.replace("\\", "/")
|
||||
if ("./" in source_dir) or ("../" in source_dir):
|
||||
# get current working dir
|
||||
cwd = os.getcwd()
|
||||
# set cwd to staging dir for absolute path solving
|
||||
os.chdir(staging_dir)
|
||||
root_dir = os.path.abspath(source_dir)
|
||||
# set back original cwd
|
||||
os.chdir(cwd)
|
||||
elif "{" in source_dir:
|
||||
root_dir = source_dir
|
||||
else:
|
||||
root_dir = os.path.normpath(source_dir)
|
||||
|
||||
if root_dir:
|
||||
# search for source data will need to be done
|
||||
instance.data["editorialSourceRoot"] = root_dir
|
||||
instance.data["editorialSourcePath"] = None
|
||||
else:
|
||||
# source data are already found
|
||||
for f in os.listdir(staging_dir):
|
||||
# filter out by not sharing the same name
|
||||
if os.path.splitext(f)[0] not in basename:
|
||||
continue
|
||||
# filter out by respected extensions
|
||||
if os.path.splitext(f)[1] not in self.extensions:
|
||||
continue
|
||||
video_path = os.path.join(
|
||||
staging_dir, f
|
||||
)
|
||||
self.log.debug(f"__ video_path: `{video_path}`")
|
||||
instance.data["editorialSourceRoot"] = staging_dir
|
||||
instance.data["editorialSourcePath"] = video_path
|
||||
|
||||
instance.data["stagingDir"] = staging_dir
|
||||
|
||||
# get editorial sequence file into otio timeline object
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import pyblish.api
|
|||
import re
|
||||
import os
|
||||
from avalon import io
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
class CollectHierarchyInstance(pyblish.api.ContextPlugin):
|
||||
"""Collecting hierarchy context from `parents` and `hierarchy` data
|
||||
|
|
@ -60,7 +60,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin):
|
|||
|
||||
def create_hierarchy(self, instance):
|
||||
parents = list()
|
||||
hierarchy = ""
|
||||
hierarchy = list()
|
||||
visual_hierarchy = [instance.context.data["assetEntity"]]
|
||||
while True:
|
||||
visual_parent = io.find_one(
|
||||
|
|
@ -81,27 +81,74 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin):
|
|||
})
|
||||
|
||||
if self.shot_add_hierarchy:
|
||||
parent_template_patern = re.compile(r"\{([a-z]*?)\}")
|
||||
# fill the parents parts from presets
|
||||
shot_add_hierarchy = self.shot_add_hierarchy.copy()
|
||||
hierarchy_parents = shot_add_hierarchy["parents"].copy()
|
||||
for parent in hierarchy_parents:
|
||||
hierarchy_parents[parent] = hierarchy_parents[parent].format(
|
||||
**instance.data["anatomyData"])
|
||||
|
||||
# fill parent keys data template from anatomy data
|
||||
for parent_key in hierarchy_parents:
|
||||
hierarchy_parents[parent_key] = hierarchy_parents[
|
||||
parent_key].format(**instance.data["anatomyData"])
|
||||
|
||||
for _index, _parent in enumerate(
|
||||
shot_add_hierarchy["parents_path"].split("/")):
|
||||
parent_filled = _parent.format(**hierarchy_parents)
|
||||
parent_key = parent_template_patern.findall(_parent).pop()
|
||||
|
||||
# in case SP context is set to the same folder
|
||||
if (_index == 0) and ("folder" in parent_key) \
|
||||
and (parents[-1]["entityName"] == parent_filled):
|
||||
self.log.debug(f" skiping : {parent_filled}")
|
||||
continue
|
||||
|
||||
# in case first parent is project then start parents from start
|
||||
if (_index == 0) and ("project" in parent_key):
|
||||
self.log.debug("rebuilding parents from scratch")
|
||||
project_parent = parents[0]
|
||||
parents = [project_parent]
|
||||
self.log.debug(f"project_parent: {project_parent}")
|
||||
self.log.debug(f"parents: {parents}")
|
||||
continue
|
||||
|
||||
prnt = self.convert_to_entity(
|
||||
parent, hierarchy_parents[parent])
|
||||
parent_key, parent_filled)
|
||||
parents.append(prnt)
|
||||
hierarchy.append(parent_filled)
|
||||
|
||||
hierarchy = shot_add_hierarchy[
|
||||
"parents_path"].format(**hierarchy_parents)
|
||||
# convert hierarchy to string
|
||||
hierarchy = "/".join(hierarchy)
|
||||
|
||||
# assing to instance data
|
||||
instance.data["hierarchy"] = hierarchy
|
||||
instance.data["parents"] = parents
|
||||
|
||||
# print
|
||||
self.log.debug(f"Hierarchy: {hierarchy}")
|
||||
self.log.debug(f"parents: {parents}")
|
||||
|
||||
if self.shot_add_tasks:
|
||||
instance.data["tasks"] = self.shot_add_tasks
|
||||
tasks_to_add = dict()
|
||||
project_tasks = io.find_one({"type": "project"})["config"]["tasks"]
|
||||
for task_name, task_data in self.shot_add_tasks.items():
|
||||
try:
|
||||
if task_data["type"] in project_tasks.keys():
|
||||
tasks_to_add.update({task_name: task_data})
|
||||
else:
|
||||
raise KeyError(
|
||||
"Wrong FtrackTaskType `{}` for `{}` is not"
|
||||
" existing in `{}``".format(
|
||||
task_data["type"],
|
||||
task_name,
|
||||
list(project_tasks.keys())))
|
||||
except KeyError as error:
|
||||
raise KeyError(
|
||||
"Wrong presets: `{0}`".format(error)
|
||||
)
|
||||
|
||||
instance.data["tasks"] = tasks_to_add
|
||||
else:
|
||||
instance.data["tasks"] = list()
|
||||
instance.data["tasks"] = dict()
|
||||
|
||||
# updating hierarchy data
|
||||
instance.data["anatomyData"].update({
|
||||
|
|
@ -117,7 +164,8 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin):
|
|||
def processing_instance(self, instance):
|
||||
self.log.info(f"_ instance: {instance}")
|
||||
# adding anatomyData for burnins
|
||||
instance.data["anatomyData"] = instance.context.data["anatomyData"]
|
||||
instance.data["anatomyData"] = deepcopy(
|
||||
instance.context.data["anatomyData"])
|
||||
|
||||
asset = instance.data["asset"]
|
||||
assets_shared = instance.context.data.get("assetsShared")
|
||||
|
|
@ -133,9 +181,6 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin):
|
|||
shot_name = instance.data["asset"]
|
||||
self.log.debug(f"Shot Name: {shot_name}")
|
||||
|
||||
if instance.data["hierarchy"] not in shot_name:
|
||||
self.log.warning("wrong parent")
|
||||
|
||||
label = f"{shot_name} ({frame_start}-{frame_end})"
|
||||
instance.data["label"] = label
|
||||
|
||||
|
|
@ -150,7 +195,8 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin):
|
|||
"asset": instance.data["asset"],
|
||||
"hierarchy": instance.data["hierarchy"],
|
||||
"parents": instance.data["parents"],
|
||||
"tasks": instance.data["tasks"]
|
||||
"tasks": instance.data["tasks"],
|
||||
"anatomyData": instance.data["anatomyData"]
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -194,6 +240,7 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin):
|
|||
instance.data["parents"] = s_asset_data["parents"]
|
||||
instance.data["hierarchy"] = s_asset_data["hierarchy"]
|
||||
instance.data["tasks"] = s_asset_data["tasks"]
|
||||
instance.data["anatomyData"] = s_asset_data["anatomyData"]
|
||||
|
||||
# generate hierarchy data only on shot instances
|
||||
if 'shot' not in instance.data.get('family', ''):
|
||||
|
|
@ -224,7 +271,9 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin):
|
|||
|
||||
in_info['tasks'] = instance.data['tasks']
|
||||
|
||||
from pprint import pformat
|
||||
parents = instance.data.get('parents', [])
|
||||
self.log.debug(f"parents: {pformat(parents)}")
|
||||
|
||||
actual = {name: in_info}
|
||||
|
||||
|
|
@ -240,4 +289,5 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin):
|
|||
|
||||
# adding hierarchy context to instance
|
||||
context.data["hierarchyContext"] = final_context
|
||||
self.log.debug(f"hierarchyContext: {pformat(final_context)}")
|
||||
self.log.info("Hierarchy instance collected")
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
fps = instance.context.data["fps"]
|
||||
|
||||
instance.data.update({
|
||||
"fps": fps
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,266 @@
|
|||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import pyblish.api
|
||||
from copy import deepcopy
|
||||
import clique
|
||||
|
||||
|
||||
class CollectInstanceResources(pyblish.api.InstancePlugin):
|
||||
"""Collect instance's resources"""
|
||||
|
||||
# must be after `CollectInstances`
|
||||
order = pyblish.api.CollectorOrder + 0.011
|
||||
label = "Collect Instance Resources"
|
||||
hosts = ["standalonepublisher"]
|
||||
families = ["clip"]
|
||||
|
||||
def process(self, instance):
|
||||
self.context = instance.context
|
||||
self.log.info(f"Processing instance: {instance}")
|
||||
self.new_instances = []
|
||||
subset_files = dict()
|
||||
subset_dirs = list()
|
||||
anatomy = self.context.data["anatomy"]
|
||||
anatomy_data = deepcopy(self.context.data["anatomyData"])
|
||||
anatomy_data.update({"root": anatomy.roots})
|
||||
|
||||
subset = instance.data["subset"]
|
||||
clip_name = instance.data["clipName"]
|
||||
|
||||
editorial_source_root = instance.data["editorialSourceRoot"]
|
||||
editorial_source_path = instance.data["editorialSourcePath"]
|
||||
|
||||
# if `editorial_source_path` then loop trough
|
||||
if editorial_source_path:
|
||||
# add family if mov or mp4 found which is longer for
|
||||
# cutting `trimming` to enable `ExtractTrimmingVideoAudio` plugin
|
||||
staging_dir = os.path.normpath(
|
||||
tempfile.mkdtemp(prefix="pyblish_tmp_")
|
||||
)
|
||||
instance.data["stagingDir"] = staging_dir
|
||||
instance.data["families"] += ["trimming"]
|
||||
return
|
||||
|
||||
# if template patern in path then fill it with `anatomy_data`
|
||||
if "{" in editorial_source_root:
|
||||
editorial_source_root = editorial_source_root.format(
|
||||
**anatomy_data)
|
||||
|
||||
self.log.debug(f"root: {editorial_source_root}")
|
||||
# loop `editorial_source_root` and find clip name in folders
|
||||
# and look for any subset name alternatives
|
||||
for root, dirs, _files in os.walk(editorial_source_root):
|
||||
# search only for directories related to clip name
|
||||
correct_clip_dir = None
|
||||
for _d_search in dirs:
|
||||
# avoid all non clip dirs
|
||||
if _d_search not in clip_name:
|
||||
continue
|
||||
# found correct dir for clip
|
||||
correct_clip_dir = _d_search
|
||||
|
||||
# continue if clip dir was not found
|
||||
if not correct_clip_dir:
|
||||
continue
|
||||
|
||||
clip_dir_path = os.path.join(root, correct_clip_dir)
|
||||
subset_files_items = list()
|
||||
# list content of clip dir and search for subset items
|
||||
for subset_item in os.listdir(clip_dir_path):
|
||||
# avoid all items which are not defined as subsets by name
|
||||
if subset not in subset_item:
|
||||
continue
|
||||
|
||||
subset_item_path = os.path.join(
|
||||
clip_dir_path, subset_item)
|
||||
# if it is dir store it to `subset_dirs` list
|
||||
if os.path.isdir(subset_item_path):
|
||||
subset_dirs.append(subset_item_path)
|
||||
|
||||
# if it is file then store it to `subset_files` list
|
||||
if os.path.isfile(subset_item_path):
|
||||
subset_files_items.append(subset_item_path)
|
||||
|
||||
if subset_files_items:
|
||||
subset_files.update({clip_dir_path: subset_files_items})
|
||||
|
||||
# break the loop if correct_clip_dir was captured
|
||||
# no need to cary on if corect folder was found
|
||||
if correct_clip_dir:
|
||||
break
|
||||
|
||||
if subset_dirs:
|
||||
# look all dirs and check for subset name alternatives
|
||||
for _dir in subset_dirs:
|
||||
instance_data = deepcopy(
|
||||
{k: v for k, v in instance.data.items()})
|
||||
sub_dir = os.path.basename(_dir)
|
||||
# if subset name is only alternative then create new instance
|
||||
if sub_dir != subset:
|
||||
instance_data = self.duplicate_instance(
|
||||
instance_data, subset, sub_dir)
|
||||
|
||||
# create all representations
|
||||
self.create_representations(
|
||||
os.listdir(_dir), instance_data, _dir)
|
||||
|
||||
if sub_dir == subset:
|
||||
self.new_instances.append(instance_data)
|
||||
# instance.data.update(instance_data)
|
||||
|
||||
if subset_files:
|
||||
unique_subset_names = list()
|
||||
root_dir = list(subset_files.keys()).pop()
|
||||
files_list = subset_files[root_dir]
|
||||
search_patern = f"({subset}[A-Za-z0-9]+)(?=[\\._\\s])"
|
||||
for _file in files_list:
|
||||
patern = re.compile(search_patern)
|
||||
match = patern.findall(_file)
|
||||
if not match:
|
||||
continue
|
||||
match_subset = match.pop()
|
||||
if match_subset in unique_subset_names:
|
||||
continue
|
||||
unique_subset_names.append(match_subset)
|
||||
|
||||
self.log.debug(f"unique_subset_names: {unique_subset_names}")
|
||||
|
||||
for _un_subs in unique_subset_names:
|
||||
instance_data = self.duplicate_instance(
|
||||
instance.data, subset, _un_subs)
|
||||
|
||||
# create all representations
|
||||
self.create_representations(
|
||||
[os.path.basename(f) for f in files_list
|
||||
if _un_subs in f],
|
||||
instance_data, root_dir)
|
||||
|
||||
# remove the original instance as it had been used only
|
||||
# as template and is duplicated
|
||||
self.context.remove(instance)
|
||||
|
||||
# create all instances in self.new_instances into context
|
||||
for new_instance in self.new_instances:
|
||||
_new_instance = self.context.create_instance(
|
||||
new_instance["name"])
|
||||
_new_instance.data.update(new_instance)
|
||||
|
||||
def duplicate_instance(self, instance_data, subset, new_subset):
|
||||
|
||||
new_instance_data = dict()
|
||||
for _key, _value in instance_data.items():
|
||||
new_instance_data[_key] = _value
|
||||
if not isinstance(_value, str):
|
||||
continue
|
||||
if subset in _value:
|
||||
new_instance_data[_key] = _value.replace(
|
||||
subset, new_subset)
|
||||
|
||||
self.log.info(f"Creating new instance: {new_instance_data['name']}")
|
||||
self.new_instances.append(new_instance_data)
|
||||
return new_instance_data
|
||||
|
||||
def create_representations(
|
||||
self, files_list, instance_data, staging_dir):
|
||||
""" Create representations from Collection object
|
||||
"""
|
||||
# collecting frames for later frame start/end reset
|
||||
frames = list()
|
||||
# break down Collection object to collections and reminders
|
||||
collections, remainder = clique.assemble(files_list)
|
||||
# add staging_dir to instance_data
|
||||
instance_data["stagingDir"] = staging_dir
|
||||
# add representations to instance_data
|
||||
instance_data["representations"] = list()
|
||||
|
||||
collection_head_name = None
|
||||
# loop trough collections and create representations
|
||||
for _collection in collections:
|
||||
ext = _collection.tail
|
||||
collection_head_name = _collection.head
|
||||
frame_start = list(_collection.indexes)[0]
|
||||
frame_end = list(_collection.indexes)[-1]
|
||||
repre_data = {
|
||||
"frameStart": frame_start,
|
||||
"frameEnd": frame_end,
|
||||
"name": ext[1:],
|
||||
"ext": ext[1:],
|
||||
"files": [item for item in _collection],
|
||||
"stagingDir": staging_dir
|
||||
}
|
||||
|
||||
if "review" in instance_data["families"]:
|
||||
repre_data.update({
|
||||
"thumbnail": True,
|
||||
"frameStartFtrack": frame_start,
|
||||
"frameEndFtrack": frame_end,
|
||||
"step": 1,
|
||||
"fps": self.context.data.get("fps"),
|
||||
"name": "review",
|
||||
"tags": ["review", "ftrackreview", "delete"],
|
||||
})
|
||||
instance_data["representations"].append(repre_data)
|
||||
|
||||
# add to frames for frame range reset
|
||||
frames.append(frame_start)
|
||||
frames.append(frame_end)
|
||||
|
||||
# loop trough reminders and create representations
|
||||
for _reminding_file in remainder:
|
||||
ext = os.path.splitext(_reminding_file)[-1]
|
||||
if ext not in instance_data["extensions"]:
|
||||
continue
|
||||
if collection_head_name and (
|
||||
(collection_head_name + ext[1:]) not in _reminding_file
|
||||
) and (ext in [".mp4", ".mov"]):
|
||||
self.log.info(f"Skipping file: {_reminding_file}")
|
||||
continue
|
||||
frame_start = 1
|
||||
frame_end = 1
|
||||
|
||||
repre_data = {
|
||||
"name": ext[1:],
|
||||
"ext": ext[1:],
|
||||
"files": _reminding_file,
|
||||
"stagingDir": staging_dir
|
||||
}
|
||||
|
||||
# exception for thumbnail
|
||||
if "thumb" in _reminding_file:
|
||||
repre_data.update({
|
||||
'name': "thumbnail",
|
||||
'thumbnail': True
|
||||
})
|
||||
|
||||
# exception for mp4 preview
|
||||
if ".mp4" in _reminding_file:
|
||||
frame_start = 0
|
||||
frame_end = (
|
||||
(instance_data["frameEnd"] - instance_data["frameStart"])
|
||||
+ 1)
|
||||
# add review ftrack family into families
|
||||
for _family in ["review", "ftrack"]:
|
||||
if _family not in instance_data["families"]:
|
||||
instance_data["families"].append(_family)
|
||||
repre_data.update({
|
||||
"frameStart": frame_start,
|
||||
"frameEnd": frame_end,
|
||||
"frameStartFtrack": frame_start,
|
||||
"frameEndFtrack": frame_end,
|
||||
"step": 1,
|
||||
"fps": self.context.data.get("fps"),
|
||||
"name": "review",
|
||||
"tags": ["review", "ftrackreview", "delete"],
|
||||
})
|
||||
|
||||
# add to frames for frame range reset only if no collection
|
||||
if not collections:
|
||||
frames.append(frame_start)
|
||||
frames.append(frame_end)
|
||||
|
||||
instance_data["representations"].append(repre_data)
|
||||
|
||||
# reset frame start / end
|
||||
instance_data["frameStart"] = min(frames)
|
||||
instance_data["frameEnd"] = max(frames)
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
import os
|
||||
import opentimelineio as otio
|
||||
import tempfile
|
||||
import pyblish.api
|
||||
from pype import lib as plib
|
||||
|
||||
|
||||
class CollectClipInstances(pyblish.api.InstancePlugin):
|
||||
"""Collect Clips instances from editorial's OTIO sequence"""
|
||||
class CollectInstances(pyblish.api.InstancePlugin):
|
||||
"""Collect instances from editorial's OTIO sequence"""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.01
|
||||
label = "Collect Clips"
|
||||
label = "Collect Instances"
|
||||
hosts = ["standalonepublisher"]
|
||||
families = ["editorial"]
|
||||
|
||||
|
|
@ -18,31 +17,31 @@ class CollectClipInstances(pyblish.api.InstancePlugin):
|
|||
"referenceMain": {
|
||||
"family": "review",
|
||||
"families": ["clip", "ftrack"],
|
||||
# "ftrackFamily": "review",
|
||||
"extension": ".mp4"
|
||||
"extensions": [".mp4"]
|
||||
},
|
||||
"audioMain": {
|
||||
"family": "audio",
|
||||
"families": ["clip", "ftrack"],
|
||||
# "ftrackFamily": "audio",
|
||||
"extension": ".wav",
|
||||
# "version": 1
|
||||
"extensions": [".wav"],
|
||||
},
|
||||
"shotMain": {
|
||||
"family": "shot",
|
||||
"families": []
|
||||
}
|
||||
}
|
||||
timeline_frame_offset = None # if 900000 for edl default then -900000
|
||||
timeline_frame_start = 900000 # starndard edl default (10:00:00:00)
|
||||
timeline_frame_offset = None
|
||||
custom_start_frame = None
|
||||
|
||||
def process(self, instance):
|
||||
staging_dir = os.path.normpath(
|
||||
tempfile.mkdtemp(prefix="pyblish_tmp_")
|
||||
)
|
||||
# get context
|
||||
context = instance.context
|
||||
|
||||
instance_data_filter = [
|
||||
"editorialSourceRoot",
|
||||
"editorialSourcePath"
|
||||
]
|
||||
|
||||
# attribute for checking duplicity during creation
|
||||
if not context.data.get("assetNameCheck"):
|
||||
context.data["assetNameCheck"] = list()
|
||||
|
|
@ -68,15 +67,19 @@ class CollectClipInstances(pyblish.api.InstancePlugin):
|
|||
handle_start = int(asset_data["handleStart"])
|
||||
handle_end = int(asset_data["handleEnd"])
|
||||
|
||||
instances = []
|
||||
for track in tracks:
|
||||
self.log.debug(f"track.name: {track.name}")
|
||||
try:
|
||||
track_start_frame = (
|
||||
abs(track.source_range.start_time.value)
|
||||
)
|
||||
self.log.debug(f"track_start_frame: {track_start_frame}")
|
||||
track_start_frame -= self.timeline_frame_start
|
||||
except AttributeError:
|
||||
track_start_frame = 0
|
||||
|
||||
self.log.debug(f"track_start_frame: {track_start_frame}")
|
||||
|
||||
for clip in track.each_child():
|
||||
if clip.name is None:
|
||||
continue
|
||||
|
|
@ -103,7 +106,10 @@ class CollectClipInstances(pyblish.api.InstancePlugin):
|
|||
|
||||
# frame ranges data
|
||||
clip_in = clip.range_in_parent().start_time.value
|
||||
clip_in += track_start_frame
|
||||
clip_out = clip.range_in_parent().end_time_inclusive().value
|
||||
clip_out += track_start_frame
|
||||
self.log.info(f"clip_in: {clip_in} | clip_out: {clip_out}")
|
||||
|
||||
# add offset in case there is any
|
||||
if self.timeline_frame_offset:
|
||||
|
|
@ -131,14 +137,11 @@ class CollectClipInstances(pyblish.api.InstancePlugin):
|
|||
|
||||
# create shared new instance data
|
||||
instance_data = {
|
||||
"stagingDir": staging_dir,
|
||||
|
||||
# shared attributes
|
||||
"asset": name,
|
||||
"assetShareName": name,
|
||||
"editorialVideoPath": instance.data[
|
||||
"editorialVideoPath"],
|
||||
"item": clip,
|
||||
"clipName": clip_name,
|
||||
|
||||
# parent time properities
|
||||
"trackStartFrame": track_start_frame,
|
||||
|
|
@ -167,6 +170,10 @@ class CollectClipInstances(pyblish.api.InstancePlugin):
|
|||
"frameEndH": frame_end + handle_end
|
||||
}
|
||||
|
||||
for data_key in instance_data_filter:
|
||||
instance_data.update({
|
||||
data_key: instance.data.get(data_key)})
|
||||
|
||||
# adding subsets to context as instances
|
||||
for subset, properities in self.subsets.items():
|
||||
# adding Review-able instance
|
||||
|
|
@ -174,14 +181,20 @@ class CollectClipInstances(pyblish.api.InstancePlugin):
|
|||
subset_instance_data.update(properities)
|
||||
subset_instance_data.update({
|
||||
# unique attributes
|
||||
"name": f"{subset}_{name}",
|
||||
"label": f"{subset} {name} ({clip_in}-{clip_out})",
|
||||
"name": f"{name}_{subset}",
|
||||
"label": f"{name} {subset} ({clip_in}-{clip_out})",
|
||||
"subset": subset
|
||||
})
|
||||
instances.append(instance.context.create_instance(
|
||||
**subset_instance_data))
|
||||
# create new instance
|
||||
_instance = instance.context.create_instance(
|
||||
**subset_instance_data)
|
||||
self.log.debug(
|
||||
f"Instance: `{_instance}` | "
|
||||
f"families: `{subset_instance_data['families']}`")
|
||||
|
||||
context.data["assetsShared"][name] = {
|
||||
"_clipIn": clip_in,
|
||||
"_clipOut": clip_out
|
||||
}
|
||||
|
||||
self.log.debug("Instance: `{}` | families: `{}`")
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
import os
|
||||
import clique
|
||||
import pype.api
|
||||
|
||||
from pprint import pformat
|
||||
|
||||
|
||||
class ExtractShotData(pype.api.Extractor):
|
||||
"""Extract shot "mov" and "wav" files."""
|
||||
|
||||
label = "Extract Shot Data"
|
||||
hosts = ["standalonepublisher"]
|
||||
families = ["clip"]
|
||||
|
||||
# presets
|
||||
|
||||
def process(self, instance):
|
||||
representation = instance.data.get("representations")
|
||||
self.log.debug(f"_ representation: {representation}")
|
||||
|
||||
if not representation:
|
||||
instance.data["representations"] = list()
|
||||
|
||||
# get ffmpet path
|
||||
ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg")
|
||||
|
||||
# get staging dir
|
||||
staging_dir = self.staging_dir(instance)
|
||||
self.log.info("Staging dir set to: `{}`".format(staging_dir))
|
||||
|
||||
# Generate mov file.
|
||||
fps = instance.data["fps"]
|
||||
video_file_path = instance.data["editorialVideoPath"]
|
||||
ext = instance.data.get("extension", ".mov")
|
||||
|
||||
clip_trimed_path = os.path.join(
|
||||
staging_dir, instance.data["name"] + ext)
|
||||
#
|
||||
# # check video file metadata
|
||||
# input_data = plib.ffprobe_streams(video_file_path)[0]
|
||||
# self.log.debug(f"__ input_data: `{input_data}`")
|
||||
|
||||
start = float(instance.data["clipInH"])
|
||||
dur = float(instance.data["clipDurationH"])
|
||||
|
||||
if ext in ".wav":
|
||||
start += 0.5
|
||||
|
||||
args = [
|
||||
"\"{}\"".format(ffmpeg_path),
|
||||
"-ss", str(start / fps),
|
||||
"-i", f"\"{video_file_path}\"",
|
||||
"-t", str(dur / fps)
|
||||
]
|
||||
if ext in [".mov", ".mp4"]:
|
||||
args.extend([
|
||||
"-crf", "18",
|
||||
"-pix_fmt", "yuv420p"])
|
||||
elif ext in ".wav":
|
||||
args.extend([
|
||||
"-vn -acodec pcm_s16le",
|
||||
"-ar 48000 -ac 2"
|
||||
])
|
||||
|
||||
# add output path
|
||||
args.append(f"\"{clip_trimed_path}\"")
|
||||
|
||||
self.log.info(f"Processing: {args}")
|
||||
ffmpeg_args = " ".join(args)
|
||||
output = pype.api.subprocess(ffmpeg_args, shell=True)
|
||||
self.log.info(output)
|
||||
|
||||
repr = {
|
||||
"name": ext[1:],
|
||||
"ext": ext[1:],
|
||||
"files": os.path.basename(clip_trimed_path),
|
||||
"stagingDir": staging_dir,
|
||||
"frameStart": int(instance.data["frameStart"]),
|
||||
"frameEnd": int(instance.data["frameEnd"]),
|
||||
"frameStartFtrack": int(instance.data["frameStartH"]),
|
||||
"frameEndFtrack": int(instance.data["frameEndH"]),
|
||||
"fps": fps,
|
||||
}
|
||||
|
||||
if ext[1:] in ["mov", "mp4"]:
|
||||
repr.update({
|
||||
"thumbnail": True,
|
||||
"tags": ["review", "ftrackreview", "delete"]})
|
||||
|
||||
instance.data["representations"].append(repr)
|
||||
|
||||
self.log.debug(f"Instance data: {pformat(instance.data)}")
|
||||
|
|
@ -46,6 +46,7 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
files_len = 1
|
||||
file = files
|
||||
|
||||
staging_dir = None
|
||||
is_jpeg = False
|
||||
if file.endswith(".jpeg") or file.endswith(".jpg"):
|
||||
is_jpeg = True
|
||||
|
|
@ -106,7 +107,7 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
thumbnail_repre.pop("thumbnail")
|
||||
|
||||
filename = os.path.basename(full_thumbnail_path)
|
||||
staging_dir = os.path.dirname(full_thumbnail_path)
|
||||
staging_dir = staging_dir or os.path.dirname(full_thumbnail_path)
|
||||
|
||||
# create new thumbnail representation
|
||||
representation = {
|
||||
|
|
@ -121,4 +122,5 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
if not is_jpeg:
|
||||
representation["tags"].append("delete")
|
||||
|
||||
self.log.info(f"New representation {representation}")
|
||||
instance.data["representations"].append(representation)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
import pype.api
|
||||
|
||||
from pprint import pformat
|
||||
|
||||
|
||||
class ExtractTrimVideoAudio(pype.api.Extractor):
|
||||
"""Trim with ffmpeg "mov" and "wav" files."""
|
||||
|
||||
# must be before `ExtractThumbnailSP`
|
||||
order = pyblish.api.ExtractorOrder - 0.01
|
||||
label = "Extract Trim Video/Audio"
|
||||
hosts = ["standalonepublisher"]
|
||||
families = ["clip", "trimming"]
|
||||
|
||||
# make sure it is enabled only if at least both families are available
|
||||
match = pyblish.api.Subset
|
||||
|
||||
# presets
|
||||
|
||||
def process(self, instance):
|
||||
representation = instance.data.get("representations")
|
||||
self.log.debug(f"_ representation: {representation}")
|
||||
|
||||
if not representation:
|
||||
instance.data["representations"] = list()
|
||||
|
||||
# get ffmpet path
|
||||
ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg")
|
||||
|
||||
# get staging dir
|
||||
staging_dir = self.staging_dir(instance)
|
||||
self.log.info("Staging dir set to: `{}`".format(staging_dir))
|
||||
|
||||
# Generate mov file.
|
||||
fps = instance.data["fps"]
|
||||
video_file_path = instance.data["editorialSourcePath"]
|
||||
extensions = instance.data.get("extensions", [".mov"])
|
||||
|
||||
for ext in extensions:
|
||||
self.log.info("Processing ext: `{}`".format(ext))
|
||||
|
||||
clip_trimed_path = os.path.join(
|
||||
staging_dir, instance.data["name"] + ext)
|
||||
# # check video file metadata
|
||||
# input_data = plib.ffprobe_streams(video_file_path)[0]
|
||||
# self.log.debug(f"__ input_data: `{input_data}`")
|
||||
|
||||
start = float(instance.data["clipInH"])
|
||||
dur = float(instance.data["clipDurationH"])
|
||||
|
||||
if ext == ".wav":
|
||||
# offset time as ffmpeg is having bug
|
||||
start += 0.5
|
||||
# remove "review" from families
|
||||
instance.data["families"] = [
|
||||
fml for fml in instance.data["families"]
|
||||
if "trimming" not in fml
|
||||
]
|
||||
|
||||
args = [
|
||||
ffmpeg_path,
|
||||
"-ss", str(start / fps),
|
||||
"-i", f"\"{video_file_path}\"",
|
||||
"-t", str(dur / fps)
|
||||
]
|
||||
if ext in [".mov", ".mp4"]:
|
||||
args.extend([
|
||||
"-crf", "18",
|
||||
"-pix_fmt", "yuv420p"])
|
||||
elif ext in ".wav":
|
||||
args.extend([
|
||||
"-vn -acodec pcm_s16le",
|
||||
"-ar 48000 -ac 2"
|
||||
])
|
||||
|
||||
# add output path
|
||||
args.append(f"\"{clip_trimed_path}\"")
|
||||
|
||||
self.log.info(f"Processing: {args}")
|
||||
ffmpeg_args = " ".join(args)
|
||||
output = pype.api.subprocess(ffmpeg_args, shell=True)
|
||||
self.log.info(output)
|
||||
|
||||
repr = {
|
||||
"name": ext[1:],
|
||||
"ext": ext[1:],
|
||||
"files": os.path.basename(clip_trimed_path),
|
||||
"stagingDir": staging_dir,
|
||||
"frameStart": int(instance.data["frameStart"]),
|
||||
"frameEnd": int(instance.data["frameEnd"]),
|
||||
"frameStartFtrack": int(instance.data["frameStartH"]),
|
||||
"frameEndFtrack": int(instance.data["frameEndH"]),
|
||||
"fps": fps,
|
||||
}
|
||||
|
||||
if ext in [".mov", ".mp4"]:
|
||||
repr.update({
|
||||
"thumbnail": True,
|
||||
"tags": ["review", "ftrackreview", "delete"]})
|
||||
|
||||
instance.data["representations"].append(repr)
|
||||
|
||||
self.log.debug(f"Instance data: {pformat(instance.data)}")
|
||||
|
|
@ -7,7 +7,10 @@ class ValidateEditorialResources(pyblish.api.InstancePlugin):
|
|||
|
||||
label = "Validate Editorial Resources"
|
||||
hosts = ["standalonepublisher"]
|
||||
families = ["clip"]
|
||||
families = ["clip", "trimming"]
|
||||
|
||||
# make sure it is enabled only if at least both families are available
|
||||
match = pyblish.api.Subset
|
||||
|
||||
order = pype.api.ValidateContentsOrder
|
||||
|
||||
|
|
@ -15,6 +18,6 @@ class ValidateEditorialResources(pyblish.api.InstancePlugin):
|
|||
self.log.debug(
|
||||
f"Instance: {instance}, Families: "
|
||||
f"{[instance.data['family']] + instance.data['families']}")
|
||||
check_file = instance.data["editorialVideoPath"]
|
||||
check_file = instance.data["editorialSourcePath"]
|
||||
msg = f"Missing \"{check_file}\"."
|
||||
assert check_file, msg
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue