mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
add standalone publish plugins to special folder
This commit is contained in:
parent
2a60c5ff9e
commit
290b2009db
4 changed files with 1 additions and 7 deletions
117
pype/plugins/standalonepublisher/publish/collect_context.py
Normal file
117
pype/plugins/standalonepublisher/publish/collect_context.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
"""
|
||||
Requires:
|
||||
environment -> SAPUBLISH_INPATH
|
||||
environment -> SAPUBLISH_OUTPATH
|
||||
|
||||
Provides:
|
||||
context -> returnJsonPath (str)
|
||||
context -> project
|
||||
context -> asset
|
||||
instance -> destination_list (list)
|
||||
instance -> representations (list)
|
||||
instance -> source (list)
|
||||
instance -> representations
|
||||
"""
|
||||
|
||||
import os
|
||||
import pyblish.api
|
||||
from avalon import io
|
||||
import json
|
||||
import logging
|
||||
import clique
|
||||
|
||||
log = logging.getLogger("collector")
|
||||
|
||||
|
||||
class CollectContextDataSAPublish(pyblish.api.ContextPlugin):
|
||||
"""
|
||||
Collecting temp json data sent from a host context
|
||||
and path for returning json data back to hostself.
|
||||
"""
|
||||
|
||||
label = "Collect Context - SA Publish"
|
||||
order = pyblish.api.CollectorOrder - 0.49
|
||||
hosts = ["standalonepublisher"]
|
||||
|
||||
def process(self, context):
|
||||
# get json paths from os and load them
|
||||
io.install()
|
||||
input_json_path = os.environ.get("SAPUBLISH_INPATH")
|
||||
output_json_path = os.environ.get("SAPUBLISH_OUTPATH")
|
||||
|
||||
# context.data["stagingDir"] = os.path.dirname(input_json_path)
|
||||
context.data["returnJsonPath"] = output_json_path
|
||||
|
||||
with open(input_json_path, "r") as f:
|
||||
in_data = json.load(f)
|
||||
|
||||
asset_name = in_data['asset']
|
||||
family_preset_key = in_data.get('family_preset_key', '')
|
||||
family = in_data['family']
|
||||
subset = in_data['subset']
|
||||
|
||||
# Load presets
|
||||
presets = context.data.get("presets")
|
||||
if not presets:
|
||||
from pypeapp import config
|
||||
presets = config.get_presets()
|
||||
|
||||
# Get from presets anatomy key that will be used for getting template
|
||||
# - default integrate new is used if not set
|
||||
anatomy_key = presets.get(
|
||||
"standalone_publish", {}).get(
|
||||
"families", {}).get(
|
||||
family_preset_key, {}).get(
|
||||
"anatomy_template"
|
||||
)
|
||||
|
||||
project = io.find_one({'type': 'project'})
|
||||
asset = io.find_one({
|
||||
'type': 'asset',
|
||||
'name': asset_name
|
||||
})
|
||||
context.data['project'] = project
|
||||
context.data['asset'] = asset
|
||||
|
||||
instance = context.create_instance(subset)
|
||||
|
||||
instance.data.update({
|
||||
"subset": subset,
|
||||
"asset": asset_name,
|
||||
"label": subset,
|
||||
"name": subset,
|
||||
"family": family,
|
||||
"frameStart": in_data.get("representations", [None])[0].get("frameStart", None),
|
||||
"frameEnd": in_data.get("representations", [None])[0].get("frameEnd", None),
|
||||
"families": [family, 'ftrack'],
|
||||
})
|
||||
self.log.info("collected instance: {}".format(instance.data))
|
||||
self.log.info("parsing data: {}".format(in_data))
|
||||
|
||||
instance.data['destination_list'] = list()
|
||||
instance.data['representations'] = list()
|
||||
instance.data['source'] = 'standalone publisher'
|
||||
|
||||
for component in in_data['representations']:
|
||||
|
||||
component['destination'] = component['files']
|
||||
component['stagingDir'] = component['stagingDir']
|
||||
# Do not set anatomy_template if not specified
|
||||
if anatomy_key:
|
||||
component['anatomy_template'] = anatomy_key
|
||||
if isinstance(component['files'], list):
|
||||
collections, remainder = clique.assemble(component['files'])
|
||||
self.log.debug("collecting sequence: {}".format(collections))
|
||||
instance.data["frameStart"] = int(component["frameStart"])
|
||||
instance.data["frameEnd"] = int(component["frameEnd"])
|
||||
instance.data['fps'] = int(component['fps'])
|
||||
|
||||
if component["preview"]:
|
||||
instance.data["families"].append("review")
|
||||
instance.data["repreProfiles"] = ["h264"]
|
||||
component["tags"] = ["review", "delete"]
|
||||
self.log.debug("Adding review family")
|
||||
|
||||
instance.data["representations"].append(component)
|
||||
|
||||
self.log.info(in_data)
|
||||
186
pype/plugins/standalonepublisher/publish/extract_review.py
Normal file
186
pype/plugins/standalonepublisher/publish/extract_review.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import os
|
||||
|
||||
import pyblish.api
|
||||
from pype.vendor import clique
|
||||
import pype.api
|
||||
|
||||
|
||||
class ExtractReviewSaP(pyblish.api.InstancePlugin):
|
||||
"""Extracting Review mov file for Ftrack
|
||||
|
||||
Compulsory attribute of representation is tags list with "review",
|
||||
otherwise the representation is ignored.
|
||||
|
||||
All new represetnations are created and encoded by ffmpeg following
|
||||
presets found in `pype-config/presets/plugins/global/publish.json:ExtractReview:outputs`. To change the file extension
|
||||
filter values use preset's attributes `ext_filter`
|
||||
"""
|
||||
|
||||
label = "Extract Review SaP"
|
||||
order = pyblish.api.ExtractorOrder + 0.02
|
||||
families = ["review"]
|
||||
hosts = ["standalonepublisher"]
|
||||
|
||||
def process(self, instance):
|
||||
# adding plugin attributes from presets
|
||||
presets = instance.context.data["presets"]
|
||||
publish_presets = presets["plugins"]["global"]["publish"]
|
||||
try:
|
||||
plugin_attrs = publish_presets[self.__class__.__name__]
|
||||
except KeyError:
|
||||
raise KeyError("Preset for plugin \"{}\" are not set".format(
|
||||
self.__class__.__name__
|
||||
))
|
||||
|
||||
output_profiles = plugin_attrs.get("outputs", {})
|
||||
|
||||
fps = instance.data.get("fps")
|
||||
start_frame = instance.data.get("frameStart")
|
||||
|
||||
self.log.debug("Families In: `{}`".format(instance.data["families"]))
|
||||
|
||||
# get specific profile if was defined
|
||||
specific_profiles = instance.data.get("repreProfiles")
|
||||
|
||||
new_repres = []
|
||||
# filter out mov and img sequences
|
||||
for repre in instance.data["representations"]:
|
||||
tags = repre.get("tags", [])
|
||||
if "review" not in tags:
|
||||
continue
|
||||
|
||||
staging_dir = repre["stagingDir"]
|
||||
for name in specific_profiles:
|
||||
profile = output_profiles.get(name)
|
||||
if not profile:
|
||||
self.log.warning(
|
||||
"Profile \"{}\" was not found in presets".format(name)
|
||||
)
|
||||
continue
|
||||
|
||||
self.log.debug("Processing profile: {}".format(name))
|
||||
|
||||
ext = profile.get("ext", None)
|
||||
if not ext:
|
||||
ext = "mov"
|
||||
self.log.debug((
|
||||
"`ext` attribute not in output profile \"{}\"."
|
||||
" Setting to default ext: `mov`"
|
||||
).format(name))
|
||||
|
||||
if isinstance(repre["files"], list):
|
||||
collections, remainder = clique.assemble(repre["files"])
|
||||
|
||||
full_input_path = os.path.join(
|
||||
staging_dir,
|
||||
collections[0].format("{head}{padding}{tail}")
|
||||
)
|
||||
filename = collections[0].format('{head}')
|
||||
if filename.endswith("."):
|
||||
filename = filename[:-1]
|
||||
else:
|
||||
full_input_path = os.path.join(staging_dir, repre["files"])
|
||||
filename = repre["files"].split(".")[0]
|
||||
|
||||
repr_file = filename + "_{0}.{1}".format(name, ext)
|
||||
|
||||
full_output_path = os.path.join(staging_dir, repr_file)
|
||||
|
||||
self.log.info("input {}".format(full_input_path))
|
||||
self.log.info("output {}".format(full_output_path))
|
||||
|
||||
repre_new = repre.copy()
|
||||
|
||||
new_tags = [x for x in tags if x != "delete"]
|
||||
p_tags = profile.get("tags", [])
|
||||
self.log.info("p_tags: `{}`".format(p_tags))
|
||||
|
||||
for _tag in p_tags:
|
||||
if _tag not in new_tags:
|
||||
new_tags.append(_tag)
|
||||
|
||||
self.log.info("new_tags: `{}`".format(new_tags))
|
||||
|
||||
input_args = []
|
||||
|
||||
# overrides output file
|
||||
input_args.append("-y")
|
||||
|
||||
# preset's input data
|
||||
input_args.extend(profile.get("input", []))
|
||||
|
||||
# necessary input data
|
||||
# adds start arg only if image sequence
|
||||
if isinstance(repre["files"], list):
|
||||
input_args.extend([
|
||||
"-start_number {}".format(start_frame),
|
||||
"-framerate {}".format(fps)
|
||||
])
|
||||
|
||||
input_args.append("-i {}".format(full_input_path))
|
||||
|
||||
output_args = []
|
||||
# preset's output data
|
||||
output_args.extend(profile.get("output", []))
|
||||
|
||||
# set length of video by len of inserted files
|
||||
output_args.append("-frames {}".format(len(repre["files"])))
|
||||
|
||||
# letter_box
|
||||
lb_string = (
|
||||
"-filter:v "
|
||||
"drawbox=0:0:iw:round((ih-(iw*(1/{0})))/2):t=fill:c=black,"
|
||||
"drawbox=0:ih-round((ih-(iw*(1/{0})))/2):iw:"
|
||||
"round((ih-(iw*(1/{0})))/2):t=fill:c=black"
|
||||
)
|
||||
letter_box = profile.get("letter_box", None)
|
||||
if letter_box:
|
||||
output_args.append(lb_string.format(letter_box))
|
||||
|
||||
# output filename
|
||||
output_args.append(full_output_path)
|
||||
|
||||
ffmpeg_path = os.getenv("FFMPEG_PATH", "")
|
||||
if ffmpeg_path:
|
||||
ffmpeg_path += "/ffmpeg"
|
||||
else:
|
||||
ffmpeg_path = "ffmpeg"
|
||||
|
||||
mov_args = [
|
||||
ffmpeg_path,
|
||||
" ".join(input_args),
|
||||
" ".join(output_args)
|
||||
]
|
||||
subprcs_cmd = " ".join(mov_args)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(subprcs_cmd))
|
||||
output = pype.api.subprocess(subprcs_cmd)
|
||||
self.log.debug("Output: {}".format(output))
|
||||
|
||||
# create representation data
|
||||
repre_new.update({
|
||||
"name": name,
|
||||
"ext": ext,
|
||||
"files": repr_file,
|
||||
"tags": new_tags,
|
||||
"outputName": name
|
||||
})
|
||||
if repre_new.get("preview"):
|
||||
repre_new.pop("preview")
|
||||
if repre_new.get("thumbnail"):
|
||||
repre_new.pop("thumbnail")
|
||||
|
||||
# adding representation
|
||||
self.log.debug("Adding: {}".format(repre_new))
|
||||
new_repres.append(repre_new)
|
||||
|
||||
for repre in instance.data["representations"]:
|
||||
if "delete" in repre.get("tags", []):
|
||||
instance.data["representations"].remove(repre)
|
||||
|
||||
for repre in new_repres:
|
||||
self.log.debug("Adding repre: \"{}\"".format(
|
||||
repre
|
||||
))
|
||||
instance.data["representations"].append(repre)
|
||||
126
pype/plugins/standalonepublisher/publish/extract_thumbnail.py
Normal file
126
pype/plugins/standalonepublisher/publish/extract_thumbnail.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import os
|
||||
import tempfile
|
||||
import subprocess
|
||||
import pyblish.api
|
||||
import pype.api
|
||||
|
||||
|
||||
class ExtractThumbnail(pyblish.api.InstancePlugin):
|
||||
"""Extract jpeg thumbnail from component input from standalone publisher
|
||||
|
||||
Uses jpeg file from component if possible (when single or multiple jpegs
|
||||
are loaded to component selected as thumbnail) otherwise extracts from
|
||||
input file/s single jpeg to temp.
|
||||
"""
|
||||
|
||||
label = "Extract Thumbnail"
|
||||
hosts = ["standalonepublisher"]
|
||||
order = pyblish.api.ExtractorOrder
|
||||
|
||||
def process(self, instance):
|
||||
repres = instance.data.get('representations')
|
||||
if not repres:
|
||||
return
|
||||
|
||||
thumbnail_repre = None
|
||||
for repre in repres:
|
||||
if repre.get("thumbnail"):
|
||||
thumbnail_repre = repre
|
||||
break
|
||||
|
||||
if not thumbnail_repre:
|
||||
return
|
||||
|
||||
files = thumbnail_repre.get("files")
|
||||
if not files:
|
||||
return
|
||||
|
||||
if isinstance(files, list):
|
||||
files_len = len(files)
|
||||
file = str(files[0])
|
||||
else:
|
||||
files_len = 1
|
||||
file = files
|
||||
|
||||
is_jpeg = False
|
||||
if file.endswith(".jpeg") or file.endswith(".jpg"):
|
||||
is_jpeg = True
|
||||
|
||||
if is_jpeg and files_len == 1:
|
||||
# skip if already is single jpeg file
|
||||
return
|
||||
|
||||
elif is_jpeg:
|
||||
# use first frame as thumbnail if is sequence of jpegs
|
||||
full_thumbnail_path = file
|
||||
self.log.info(
|
||||
"For thumbnail is used file: {}".format(full_thumbnail_path)
|
||||
)
|
||||
|
||||
else:
|
||||
# Convert to jpeg if not yet
|
||||
full_input_path = os.path.join(thumbnail_repre["stagingDir"], file)
|
||||
self.log.info("input {}".format(full_input_path))
|
||||
|
||||
full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1]
|
||||
self.log.info("output {}".format(full_thumbnail_path))
|
||||
|
||||
config_data = instance.context.data.get("output_repre_config", {})
|
||||
|
||||
proj_name = os.environ.get("AVALON_PROJECT", "__default__")
|
||||
profile = config_data.get(
|
||||
proj_name,
|
||||
config_data.get("__default__", {})
|
||||
)
|
||||
|
||||
ffmpeg_path = os.getenv("FFMPEG_PATH", "")
|
||||
if ffmpeg_path:
|
||||
ffmpeg_path += "/ffmpeg"
|
||||
else:
|
||||
ffmpeg_path = "ffmpeg"
|
||||
|
||||
jpeg_items = []
|
||||
jpeg_items.append(ffmpeg_path)
|
||||
# override file if already exists
|
||||
jpeg_items.append("-y")
|
||||
# add input filters from peresets
|
||||
if profile:
|
||||
jpeg_items.extend(profile.get('input', []))
|
||||
# input file
|
||||
jpeg_items.append("-i {}".format(full_input_path))
|
||||
# extract only single file
|
||||
jpeg_items.append("-vframes 1")
|
||||
# output file
|
||||
jpeg_items.append(full_thumbnail_path)
|
||||
|
||||
subprocess_jpeg = " ".join(jpeg_items)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(subprocess_jpeg))
|
||||
subprocess.Popen(
|
||||
subprocess_jpeg,
|
||||
stdout=subprocess.PIPE,
|
||||
shell=True
|
||||
)
|
||||
|
||||
# remove thumbnail key from origin repre
|
||||
thumbnail_repre.pop("thumbnail")
|
||||
|
||||
filename = os.path.basename(full_thumbnail_path)
|
||||
staging_dir = os.path.dirname(full_thumbnail_path)
|
||||
|
||||
# create new thumbnail representation
|
||||
representation = {
|
||||
'name': 'jpg',
|
||||
'ext': 'jpg',
|
||||
'files': filename,
|
||||
"stagingDir": staging_dir,
|
||||
"thumbnail": True,
|
||||
"tags": []
|
||||
}
|
||||
|
||||
# # add Delete tag when temp file was rendered
|
||||
# if not is_jpeg:
|
||||
# representation["tags"].append("delete")
|
||||
|
||||
instance.data["representations"].append(representation)
|
||||
Loading…
Add table
Add a link
Reference in a new issue