mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #790 from ynput/feature/integrate-reviewables-to-ayon
Review: Integrate reviewables to AYON
This commit is contained in:
commit
354e9af391
6 changed files with 205 additions and 7 deletions
|
|
@ -109,6 +109,7 @@ from .transcoding import (
|
|||
convert_ffprobe_fps_value,
|
||||
convert_ffprobe_fps_to_float,
|
||||
get_rescaled_command_arguments,
|
||||
get_media_mime_type,
|
||||
)
|
||||
|
||||
from .plugin_tools import (
|
||||
|
|
@ -209,6 +210,7 @@ __all__ = [
|
|||
"convert_ffprobe_fps_value",
|
||||
"convert_ffprobe_fps_to_float",
|
||||
"get_rescaled_command_arguments",
|
||||
"get_media_mime_type",
|
||||
|
||||
"compile_list_of_regexes",
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import collections
|
|||
import tempfile
|
||||
import subprocess
|
||||
import platform
|
||||
from typing import Optional
|
||||
|
||||
import xml.etree.ElementTree
|
||||
|
||||
|
|
@ -1455,3 +1456,87 @@ def get_oiio_input_and_channel_args(oiio_input_info, alpha_default=None):
|
|||
input_arg += ":ch={}".format(input_channels_str)
|
||||
|
||||
return input_arg, channels_arg
|
||||
|
||||
|
||||
def _get_media_mime_type_from_ftyp(content):
|
||||
if content[8:10] == b"qt":
|
||||
return "video/quicktime"
|
||||
|
||||
if content[8:12] == b"isom":
|
||||
return "video/mp4"
|
||||
if content[8:12] in (b"M4V\x20", b"mp42"):
|
||||
return "video/mp4v"
|
||||
# (
|
||||
# b"avc1", b"iso2", b"isom", b"mmp4", b"mp41", b"mp71",
|
||||
# b"msnv", b"ndas", b"ndsc", b"ndsh", b"ndsm", b"ndsp", b"ndss",
|
||||
# b"ndxc", b"ndxh", b"ndxm", b"ndxp", b"ndxs"
|
||||
# )
|
||||
return None
|
||||
|
||||
|
||||
def get_media_mime_type(filepath: str) -> Optional[str]:
|
||||
"""Determine Mime-Type of a file.
|
||||
|
||||
Args:
|
||||
filepath (str): Path to file.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Mime type or None if is unknown mime type.
|
||||
|
||||
"""
|
||||
if not filepath or not os.path.exists(filepath):
|
||||
return None
|
||||
|
||||
with open(filepath, "rb") as stream:
|
||||
content = stream.read()
|
||||
|
||||
content_len = len(content)
|
||||
# Pre-validation (largest definition check)
|
||||
# - hopefully there cannot be media defined in less than 12 bytes
|
||||
if content_len < 12:
|
||||
return None
|
||||
|
||||
# FTYP
|
||||
if content[4:8] == b"ftyp":
|
||||
return _get_media_mime_type_from_ftyp(content)
|
||||
|
||||
# BMP
|
||||
if content[0:2] == b"BM":
|
||||
return "image/bmp"
|
||||
|
||||
# Tiff
|
||||
if content[0:2] in (b"MM", b"II"):
|
||||
return "tiff"
|
||||
|
||||
# PNG
|
||||
if content[0:4] == b"\211PNG":
|
||||
return "image/png"
|
||||
|
||||
# SVG
|
||||
if b'xmlns="http://www.w3.org/2000/svg"' in content:
|
||||
return "image/svg+xml"
|
||||
|
||||
# JPEG, JFIF or Exif
|
||||
if (
|
||||
content[0:4] == b"\xff\xd8\xff\xdb"
|
||||
or content[6:10] in (b"JFIF", b"Exif")
|
||||
):
|
||||
return "image/jpeg"
|
||||
|
||||
# Webp
|
||||
if content[0:4] == b"RIFF" and content[8:12] == b"WEBP":
|
||||
return "image/webp"
|
||||
|
||||
# Gif
|
||||
if content[0:6] in (b"GIF87a", b"GIF89a"):
|
||||
return "gif"
|
||||
|
||||
# Adobe PhotoShop file (8B > Adobe, PS > PhotoShop)
|
||||
if content[0:4] == b"8BPS":
|
||||
return "image/vnd.adobe.photoshop"
|
||||
|
||||
# Windows ICO > this might be wild guess as multiple files can start
|
||||
# with this header
|
||||
if content[0:4] == b"\x00\x00\x01\x00":
|
||||
return "image/x-icon"
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -114,18 +114,19 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
# the database even if not used by the destination template
|
||||
db_representation_context_keys = [
|
||||
"project",
|
||||
"asset",
|
||||
"hierarchy",
|
||||
"folder",
|
||||
"task",
|
||||
"product",
|
||||
"subset",
|
||||
"family",
|
||||
"version",
|
||||
"representation",
|
||||
"username",
|
||||
"user",
|
||||
"output"
|
||||
"output",
|
||||
# OpenPype keys - should be removed
|
||||
"asset", # folder[name]
|
||||
"subset", # product[name]
|
||||
"family", # product[type]
|
||||
]
|
||||
|
||||
def process(self, instance):
|
||||
|
|
|
|||
|
|
@ -265,6 +265,9 @@ class IntegrateHeroVersion(
|
|||
project_name, "version", new_hero_version
|
||||
)
|
||||
|
||||
# Store hero entity to 'instance.data'
|
||||
instance.data["heroVersionEntity"] = new_hero_version
|
||||
|
||||
# Separate old representations into `to replace` and `to delete`
|
||||
old_repres_to_replace = {}
|
||||
old_repres_to_delete = {}
|
||||
|
|
|
|||
102
client/ayon_core/plugins/publish/integrate_review.py
Normal file
102
client/ayon_core/plugins/publish/integrate_review.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import os
|
||||
|
||||
import pyblish.api
|
||||
import ayon_api
|
||||
from ayon_api.server_api import RequestTypes
|
||||
|
||||
from ayon_core.lib import get_media_mime_type
|
||||
from ayon_core.pipeline.publish import get_publish_repre_path
|
||||
|
||||
|
||||
class IntegrateAYONReview(pyblish.api.InstancePlugin):
|
||||
label = "Integrate AYON Review"
|
||||
# Must happen after IntegrateAsset
|
||||
order = pyblish.api.IntegratorOrder + 0.15
|
||||
|
||||
def process(self, instance):
|
||||
project_name = instance.context.data["projectName"]
|
||||
src_version_entity = instance.data.get("versionEntity")
|
||||
src_hero_version_entity = instance.data.get("heroVersionEntity")
|
||||
for version_entity in (
|
||||
src_version_entity,
|
||||
src_hero_version_entity,
|
||||
):
|
||||
if not version_entity:
|
||||
continue
|
||||
|
||||
version_id = version_entity["id"]
|
||||
self._upload_reviewable(project_name, version_id, instance)
|
||||
|
||||
def _upload_reviewable(self, project_name, version_id, instance):
|
||||
ayon_con = ayon_api.get_server_api_connection()
|
||||
major, minor, _, _, _ = ayon_con.get_server_version_tuple()
|
||||
if (major, minor) < (1, 3):
|
||||
self.log.info(
|
||||
"Skipping reviewable upload, supported from server 1.3.x."
|
||||
f" Current server version {ayon_con.get_server_version()}"
|
||||
)
|
||||
return
|
||||
|
||||
uploaded_labels = set()
|
||||
for repre in instance.data["representations"]:
|
||||
repre_tags = repre.get("tags") or []
|
||||
# Ignore representations that are not reviewable
|
||||
if "webreview" not in repre_tags:
|
||||
continue
|
||||
|
||||
# exclude representations with are going to be published on farm
|
||||
if "publish_on_farm" in repre_tags:
|
||||
continue
|
||||
|
||||
# Skip thumbnails
|
||||
if repre.get("thumbnail") or "thumbnail" in repre_tags:
|
||||
continue
|
||||
|
||||
repre_path = get_publish_repre_path(
|
||||
instance, repre, False
|
||||
)
|
||||
if not repre_path or not os.path.exists(repre_path):
|
||||
# TODO log skipper path
|
||||
continue
|
||||
|
||||
content_type = get_media_mime_type(repre_path)
|
||||
if not content_type:
|
||||
self.log.warning(
|
||||
f"Could not determine Content-Type for {repre_path}"
|
||||
)
|
||||
continue
|
||||
|
||||
label = self._get_review_label(repre, uploaded_labels)
|
||||
query = ""
|
||||
if label:
|
||||
query = f"?label={label}"
|
||||
|
||||
endpoint = (
|
||||
f"/projects/{project_name}"
|
||||
f"/versions/{version_id}/reviewables{query}"
|
||||
)
|
||||
filename = os.path.basename(repre_path)
|
||||
# Upload the reviewable
|
||||
self.log.info(f"Uploading reviewable '{label or filename}' ...")
|
||||
|
||||
headers = ayon_con.get_headers(content_type)
|
||||
headers["x-file-name"] = filename
|
||||
self.log.info(f"Uploading reviewable {repre_path}")
|
||||
ayon_con.upload_file(
|
||||
endpoint,
|
||||
repre_path,
|
||||
headers=headers,
|
||||
request_type=RequestTypes.post,
|
||||
)
|
||||
|
||||
def _get_review_label(self, repre, uploaded_labels):
|
||||
# Use output name as label if available
|
||||
label = repre.get("outputName")
|
||||
if not label:
|
||||
return None
|
||||
orig_label = label
|
||||
idx = 0
|
||||
while label in uploaded_labels:
|
||||
idx += 1
|
||||
label = f"{orig_label}_{idx}"
|
||||
return label
|
||||
|
|
@ -1012,7 +1012,8 @@ DEFAULT_PUBLISH_VALUES = {
|
|||
"ext": "png",
|
||||
"tags": [
|
||||
"ftrackreview",
|
||||
"kitsureview"
|
||||
"kitsureview",
|
||||
"webreview"
|
||||
],
|
||||
"burnins": [],
|
||||
"ffmpeg_args": {
|
||||
|
|
@ -1052,7 +1053,8 @@ DEFAULT_PUBLISH_VALUES = {
|
|||
"tags": [
|
||||
"burnin",
|
||||
"ftrackreview",
|
||||
"kitsureview"
|
||||
"kitsureview",
|
||||
"webreview"
|
||||
],
|
||||
"burnins": [],
|
||||
"ffmpeg_args": {
|
||||
|
|
@ -1064,7 +1066,10 @@ DEFAULT_PUBLISH_VALUES = {
|
|||
"output": [
|
||||
"-pix_fmt yuv420p",
|
||||
"-crf 18",
|
||||
"-intra"
|
||||
"-c:a acc",
|
||||
"-b:a 192k",
|
||||
"-g 1",
|
||||
"-movflags faststart"
|
||||
]
|
||||
},
|
||||
"filter": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue