Merge pull request #258 from tokejepsen/2.x/feature/standalone_editorial

Standalone editorial
This commit is contained in:
Milan Kolar 2020-06-16 10:18:38 +02:00 committed by GitHub
commit 5ff939c283
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 277 additions and 8 deletions

View file

@ -44,10 +44,14 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin):
family = instance.data['family'].lower()
asset_type = ''
asset_type = instance.data.get(
"ftrackFamily", self.family_mapping[family]
)
asset_type = instance.data.get("ftrackFamily")
if not asset_type and family in self.family_mapping:
asset_type = self.family_mapping[family]
# Ignore this instance if neither "ftrackFamily" or a family mapping is
# found.
if not asset_type:
return
componentList = []
ft_session = instance.context.data["ftrackSession"]

View file

@ -7,7 +7,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin):
order = pyblish.api.ExtractorOrder - 0.01
label = "Extract Hierarchy To Avalon"
families = ["clip", "shot"]
families = ["clip", "shot", "editorial"]
def process(self, context):
if "hierarchyContext" not in context.data:

View file

@ -83,7 +83,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
"fbx",
"textures",
"action",
"harmony.template"
"harmony.template",
"editorial"
]
exclude_families = ["clip"]
db_representation_context_keys = [

View file

@ -117,7 +117,7 @@ class ExtractRender(pyblish.api.InstancePlugin):
"frameEnd": frame_end,
"fps": frame_rate,
"preview": True,
"tags": ["review"]
"tags": ["review", "ftrackreview"]
}
thumbnail = {
"name": "thumbnail",

View file

@ -0,0 +1,141 @@
import os
import opentimelineio as otio
from bson import json_util
import pyblish.api
from pype import lib
from avalon import io
class OTIO_View(pyblish.api.Action):
"""Currently disabled because OTIO requires PySide2. Issue on Qt.py:
https://github.com/PixarAnimationStudios/OpenTimelineIO/issues/289
"""
label = "OTIO View"
icon = "wrench"
on = "failed"
def process(self, context, plugin):
instance = context[0]
representation = instance.data["representations"][0]
file_path = os.path.join(
representation["stagingDir"], representation["files"]
)
lib._subprocess(["otioview", file_path])
class CollectShots(pyblish.api.InstancePlugin):
"""Collect Anatomy object into Context"""
order = pyblish.api.CollectorOrder
label = "Collect Shots"
hosts = ["standalonepublisher"]
families = ["editorial"]
actions = []
def process(self, instance):
representation = instance.data["representations"][0]
file_path = os.path.join(
representation["stagingDir"], representation["files"]
)
instance.context.data["editorialPath"] = file_path
extension = os.path.splitext(file_path)[1][1:]
kwargs = {}
if extension == "edl":
# EDL has no frame rate embedded so needs explicit frame rate else
# 24 is asssumed.
kwargs["rate"] = lib.get_asset()["data"]["fps"]
timeline = otio.adapters.read_from_file(file_path, **kwargs)
tracks = timeline.each_child(
descended_from_type=otio.schema.track.Track
)
asset_entity = instance.context.data["assetEntity"]
asset_name = asset_entity["name"]
# Project specific prefix naming. This needs to be replaced with some
# options to be more flexible.
asset_name = asset_name.split("_")[0]
shot_number = 10
for track in tracks:
self.log.info(track)
if "audio" in track.name.lower():
continue
instances = []
for child in track.each_child():
# Transitions are ignored, because Clips have the full frame
# range.
if isinstance(child, otio.schema.transition.Transition):
continue
frame_start = child.range_in_parent().start_time.value
frame_end = child.range_in_parent().end_time_inclusive().value
name = f"{asset_name}_sh{shot_number:04}"
label = f"{name} (framerange: {frame_start}-{frame_end})"
instances.append(
instance.context.create_instance(**{
"name": name,
"label": label,
"frameStart": frame_start,
"frameEnd": frame_end,
"family": "shot",
"families": ["review", "ftrack"],
"ftrackFamily": "review",
"asset": name,
"subset": "shotMain",
"representations": [],
"source": file_path
})
)
shot_number += 10
visual_hierarchy = [asset_entity]
while True:
visual_parent = io.find_one(
{"_id": visual_hierarchy[-1]["data"]["visualParent"]}
)
if visual_parent:
visual_hierarchy.append(visual_parent)
else:
visual_hierarchy.append(instance.context.data["projectEntity"])
break
context_hierarchy = None
for entity in visual_hierarchy:
childs = {}
if context_hierarchy:
name = context_hierarchy.pop("name")
childs = {name: context_hierarchy}
else:
for instance in instances:
childs[instance.data["name"]] = {
"childs": {},
"entity_type": "Shot",
"custom_attributes": {
"frameStart": instance.data["frameStart"],
"frameEnd": instance.data["frameEnd"]
}
}
context_hierarchy = {
"entity_type": entity["data"]["entityType"],
"childs": childs,
"name": entity["name"]
}
name = context_hierarchy.pop("name")
context_hierarchy = {name: context_hierarchy}
instance.context.data["hierarchyContext"] = context_hierarchy
self.log.info(
"Hierarchy:\n" +
json_util.dumps(context_hierarchy, sort_keys=True, indent=4)
)

View file

@ -42,7 +42,7 @@ class ExtractReviewSP(pyblish.api.InstancePlugin):
self.log.debug("Families In: `{}`".format(instance.data["families"]))
# get specific profile if was defined
specific_profiles = instance.data.get("repreProfiles")
specific_profiles = instance.data.get("repreProfiles", [])
new_repres = []
# filter out mov and img sequences

View file

@ -0,0 +1,95 @@
import os
import clique
import pype.api
import pype.lib
class ExtractShot(pype.api.Extractor):
"""Extract shot "mov" and "wav" files."""
label = "Extract Shot"
hosts = ["standalonepublisher"]
families = ["shot"]
def process(self, instance):
staging_dir = self.staging_dir(instance)
self.log.info("Outputting shot to {}".format(staging_dir))
editorial_path = instance.context.data["editorialPath"]
basename = os.path.splitext(os.path.basename(editorial_path))[0]
# Generate mov file.
fps = pype.lib.get_asset()["data"]["fps"]
input_path = os.path.join(
os.path.dirname(editorial_path), basename + ".mov"
)
shot_mov = os.path.join(staging_dir, instance.data["name"] + ".mov")
args = [
"ffmpeg",
"-ss", str(instance.data["frameStart"] / fps),
"-i", input_path,
"-t", str(
(instance.data["frameEnd"] - instance.data["frameStart"] + 1) /
fps
),
"-crf", "18",
"-pix_fmt", "yuv420p",
shot_mov
]
self.log.info(f"Processing: {args}")
output = pype.lib._subprocess(args)
self.log.info(output)
instance.data["representations"].append({
"name": "mov",
"ext": "mov",
"files": os.path.basename(shot_mov),
"stagingDir": staging_dir,
"frameStart": instance.data["frameStart"],
"frameEnd": instance.data["frameEnd"],
"fps": fps,
"thumbnail": True,
"tags": ["review", "ftrackreview"]
})
# Generate jpegs.
shot_jpegs = os.path.join(
staging_dir, instance.data["name"] + ".%04d.jpeg"
)
args = ["ffmpeg", "-i", shot_mov, shot_jpegs]
self.log.info(f"Processing: {args}")
output = pype.lib._subprocess(args)
self.log.info(output)
collection = clique.Collection(
head=instance.data["name"] + ".", tail='.jpeg', padding=4
)
for f in os.listdir(staging_dir):
if collection.match(f):
collection.add(f)
instance.data["representations"].append({
"name": "jpeg",
"ext": "jpeg",
"files": list(collection),
"stagingDir": staging_dir
})
# Generate wav file.
shot_wav = os.path.join(staging_dir, instance.data["name"] + ".wav")
args = ["ffmpeg", "-i", shot_mov, shot_wav]
self.log.info(f"Processing: {args}")
output = pype.lib._subprocess(args)
self.log.info(output)
instance.data["representations"].append({
"name": "wav",
"ext": "wav",
"files": os.path.basename(shot_wav),
"stagingDir": staging_dir
})
# Required for extract_review plugin (L222 onwards).
instance.data["fps"] = fps

View file

@ -0,0 +1,28 @@
import os
import pyblish.api
import pype.api
class ValidateEditorialResources(pyblish.api.InstancePlugin):
"""Validate there is a "mov" next to the editorial file."""
label = "Validate Editorial Resources"
hosts = ["standalonepublisher"]
families = ["editorial"]
order = pype.api.ValidateContentsOrder
def process(self, instance):
representation = instance.data["representations"][0]
staging_dir = representation["stagingDir"]
basename = os.path.splitext(
os.path.basename(representation["files"])
)[0]
files = [x for x in os.listdir(staging_dir)]
# Check for "mov" file.
filename = basename + ".mov"
filepath = os.path.join(staging_dir, filename)
msg = f"Missing \"{filepath}\"."
assert filename in files, msg