mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge branch 'develop' into enhancement/1524-yn-0156-usd-contribution-workflow-layer-strength-configured-hierarchically
This commit is contained in:
commit
448d32fa42
7 changed files with 160 additions and 62 deletions
|
|
@ -169,7 +169,9 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
||||||
|
|
||||||
settings_category = "core"
|
settings_category = "core"
|
||||||
# Supported extensions
|
# Supported extensions
|
||||||
image_exts = {"exr", "jpg", "jpeg", "png", "dpx", "tga", "tiff", "tif"}
|
image_exts = {
|
||||||
|
"exr", "jpg", "jpeg", "png", "dpx", "tga", "tiff", "tif", "psd"
|
||||||
|
}
|
||||||
video_exts = {"mov", "mp4"}
|
video_exts = {"mov", "mp4"}
|
||||||
supported_exts = image_exts | video_exts
|
supported_exts = image_exts | video_exts
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
||||||
"unreal",
|
"unreal",
|
||||||
"houdini",
|
"houdini",
|
||||||
"batchdelivery",
|
"batchdelivery",
|
||||||
"webpublisher",
|
|
||||||
]
|
]
|
||||||
settings_category = "core"
|
settings_category = "core"
|
||||||
enabled = False
|
enabled = False
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ Todos:
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
from ayon_core.lib import (
|
from ayon_core.lib import (
|
||||||
|
|
@ -22,6 +23,7 @@ from ayon_core.lib import (
|
||||||
is_oiio_supported,
|
is_oiio_supported,
|
||||||
|
|
||||||
run_subprocess,
|
run_subprocess,
|
||||||
|
get_rescaled_command_arguments,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,17 +33,20 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
|
||||||
Thumbnail source must be a single image or video filepath.
|
Thumbnail source must be a single image or video filepath.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
label = "Extract Thumbnail (from source)"
|
label = "Extract Thumbnail from source"
|
||||||
# Before 'ExtractThumbnail' in global plugins
|
# Before 'ExtractThumbnail' in global plugins
|
||||||
order = pyblish.api.ExtractorOrder - 0.00001
|
order = pyblish.api.ExtractorOrder - 0.00001
|
||||||
|
|
||||||
def process(self, instance):
|
# Settings
|
||||||
|
target_size = {
|
||||||
|
"type": "resize",
|
||||||
|
"resize": {"width": 1920, "height": 1080}
|
||||||
|
}
|
||||||
|
background_color = (0, 0, 0, 0.0)
|
||||||
|
|
||||||
|
def process(self, instance: pyblish.api.Instance):
|
||||||
self._create_context_thumbnail(instance.context)
|
self._create_context_thumbnail(instance.context)
|
||||||
|
|
||||||
product_name = instance.data["productName"]
|
|
||||||
self.log.debug(
|
|
||||||
"Processing instance with product name {}".format(product_name)
|
|
||||||
)
|
|
||||||
thumbnail_source = instance.data.get("thumbnailSource")
|
thumbnail_source = instance.data.get("thumbnailSource")
|
||||||
if not thumbnail_source:
|
if not thumbnail_source:
|
||||||
self.log.debug("Thumbnail source not filled. Skipping.")
|
self.log.debug("Thumbnail source not filled. Skipping.")
|
||||||
|
|
@ -69,6 +74,8 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
|
||||||
"outputName": "thumbnail",
|
"outputName": "thumbnail",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new_repre["tags"].append("delete")
|
||||||
|
|
||||||
# adding representation
|
# adding representation
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
"Adding thumbnail representation: {}".format(new_repre)
|
"Adding thumbnail representation: {}".format(new_repre)
|
||||||
|
|
@ -76,7 +83,11 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
|
||||||
instance.data["representations"].append(new_repre)
|
instance.data["representations"].append(new_repre)
|
||||||
instance.data["thumbnailPath"] = dst_filepath
|
instance.data["thumbnailPath"] = dst_filepath
|
||||||
|
|
||||||
def _create_thumbnail(self, context, thumbnail_source):
|
def _create_thumbnail(
|
||||||
|
self,
|
||||||
|
context: pyblish.api.Context,
|
||||||
|
thumbnail_source: str,
|
||||||
|
) -> Optional[str]:
|
||||||
if not thumbnail_source:
|
if not thumbnail_source:
|
||||||
self.log.debug("Thumbnail source not filled. Skipping.")
|
self.log.debug("Thumbnail source not filled. Skipping.")
|
||||||
return
|
return
|
||||||
|
|
@ -131,7 +142,7 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
|
||||||
|
|
||||||
self.log.warning("Thumbnail has not been created.")
|
self.log.warning("Thumbnail has not been created.")
|
||||||
|
|
||||||
def _instance_has_thumbnail(self, instance):
|
def _instance_has_thumbnail(self, instance: pyblish.api.Instance) -> bool:
|
||||||
if "representations" not in instance.data:
|
if "representations" not in instance.data:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"Instance does not have 'representations' key filled"
|
"Instance does not have 'representations' key filled"
|
||||||
|
|
@ -143,14 +154,24 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_thumbnail_oiio(self, src_path, dst_path):
|
def create_thumbnail_oiio(
|
||||||
|
self,
|
||||||
|
src_path: str,
|
||||||
|
dst_path: str,
|
||||||
|
) -> bool:
|
||||||
self.log.debug("Outputting thumbnail with OIIO: {}".format(dst_path))
|
self.log.debug("Outputting thumbnail with OIIO: {}".format(dst_path))
|
||||||
oiio_cmd = get_oiio_tool_args(
|
resolution_args = self._get_resolution_args(
|
||||||
"oiiotool",
|
"oiiotool", src_path
|
||||||
"-a", src_path,
|
|
||||||
"--ch", "R,G,B",
|
|
||||||
"-o", dst_path
|
|
||||||
)
|
)
|
||||||
|
oiio_cmd = get_oiio_tool_args("oiiotool", "-a", src_path)
|
||||||
|
if resolution_args:
|
||||||
|
# resize must be before -o
|
||||||
|
oiio_cmd.extend(resolution_args)
|
||||||
|
else:
|
||||||
|
# resize provides own -ch, must be only one
|
||||||
|
oiio_cmd.extend(["--ch", "R,G,B"])
|
||||||
|
|
||||||
|
oiio_cmd.extend(["-o", dst_path])
|
||||||
self.log.debug("Running: {}".format(" ".join(oiio_cmd)))
|
self.log.debug("Running: {}".format(" ".join(oiio_cmd)))
|
||||||
try:
|
try:
|
||||||
run_subprocess(oiio_cmd, logger=self.log)
|
run_subprocess(oiio_cmd, logger=self.log)
|
||||||
|
|
@ -162,7 +183,15 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_thumbnail_ffmpeg(self, src_path, dst_path):
|
def create_thumbnail_ffmpeg(
|
||||||
|
self,
|
||||||
|
src_path: str,
|
||||||
|
dst_path: str,
|
||||||
|
) -> bool:
|
||||||
|
resolution_args = self._get_resolution_args(
|
||||||
|
"ffmpeg", src_path
|
||||||
|
)
|
||||||
|
|
||||||
max_int = str(2147483647)
|
max_int = str(2147483647)
|
||||||
ffmpeg_cmd = get_ffmpeg_tool_args(
|
ffmpeg_cmd = get_ffmpeg_tool_args(
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
|
|
@ -171,9 +200,13 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
|
||||||
"-probesize", max_int,
|
"-probesize", max_int,
|
||||||
"-i", src_path,
|
"-i", src_path,
|
||||||
"-frames:v", "1",
|
"-frames:v", "1",
|
||||||
dst_path
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ffmpeg_cmd.extend(resolution_args)
|
||||||
|
|
||||||
|
# possible resize must be before output args
|
||||||
|
ffmpeg_cmd.append(dst_path)
|
||||||
|
|
||||||
self.log.debug("Running: {}".format(" ".join(ffmpeg_cmd)))
|
self.log.debug("Running: {}".format(" ".join(ffmpeg_cmd)))
|
||||||
try:
|
try:
|
||||||
run_subprocess(ffmpeg_cmd, logger=self.log)
|
run_subprocess(ffmpeg_cmd, logger=self.log)
|
||||||
|
|
@ -185,10 +218,37 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _create_context_thumbnail(self, context):
|
def _create_context_thumbnail(
|
||||||
|
self,
|
||||||
|
context: pyblish.api.Context,
|
||||||
|
):
|
||||||
if "thumbnailPath" in context.data:
|
if "thumbnailPath" in context.data:
|
||||||
return
|
return
|
||||||
|
|
||||||
thumbnail_source = context.data.get("thumbnailSource")
|
thumbnail_source = context.data.get("thumbnailSource")
|
||||||
thumbnail_path = self._create_thumbnail(context, thumbnail_source)
|
context.data["thumbnailPath"] = self._create_thumbnail(
|
||||||
context.data["thumbnailPath"] = thumbnail_path
|
context, thumbnail_source
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_resolution_args(
|
||||||
|
self,
|
||||||
|
application: str,
|
||||||
|
input_path: str,
|
||||||
|
) -> List[str]:
|
||||||
|
# get settings
|
||||||
|
if self.target_size["type"] == "source":
|
||||||
|
return []
|
||||||
|
|
||||||
|
resize = self.target_size["resize"]
|
||||||
|
target_width = resize["width"]
|
||||||
|
target_height = resize["height"]
|
||||||
|
|
||||||
|
# form arg string per application
|
||||||
|
return get_rescaled_command_arguments(
|
||||||
|
application,
|
||||||
|
input_path,
|
||||||
|
target_width,
|
||||||
|
target_height,
|
||||||
|
bg_color=self.background_color,
|
||||||
|
log=self.log,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import List, Dict, Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -13,8 +15,8 @@ class TabItem:
|
||||||
class InterpreterConfig:
|
class InterpreterConfig:
|
||||||
width: Optional[int]
|
width: Optional[int]
|
||||||
height: Optional[int]
|
height: Optional[int]
|
||||||
splitter_sizes: List[int] = field(default_factory=list)
|
splitter_sizes: list[int] = field(default_factory=list)
|
||||||
tabs: List[TabItem] = field(default_factory=list)
|
tabs: list[TabItem] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class AbstractInterpreterController(ABC):
|
class AbstractInterpreterController(ABC):
|
||||||
|
|
@ -27,7 +29,7 @@ class AbstractInterpreterController(ABC):
|
||||||
self,
|
self,
|
||||||
width: int,
|
width: int,
|
||||||
height: int,
|
height: int,
|
||||||
splitter_sizes: List[int],
|
splitter_sizes: list[int],
|
||||||
tabs: List[Dict[str, str]],
|
tabs: list[dict[str, str]],
|
||||||
):
|
) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from typing import List, Dict
|
from __future__ import annotations
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from ayon_core.lib import JSONSettingRegistry
|
from ayon_core.lib import JSONSettingRegistry
|
||||||
from ayon_core.lib.local_settings import get_launcher_local_dir
|
from ayon_core.lib.local_settings import get_launcher_local_dir
|
||||||
|
|
@ -11,13 +12,15 @@ from .abstract import (
|
||||||
|
|
||||||
|
|
||||||
class InterpreterController(AbstractInterpreterController):
|
class InterpreterController(AbstractInterpreterController):
|
||||||
def __init__(self):
|
def __init__(self, name: Optional[str] = None) -> None:
|
||||||
|
if name is None:
|
||||||
|
name = "python_interpreter_tool"
|
||||||
self._registry = JSONSettingRegistry(
|
self._registry = JSONSettingRegistry(
|
||||||
"python_interpreter_tool",
|
name,
|
||||||
get_launcher_local_dir(),
|
get_launcher_local_dir(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_config(self):
|
def get_config(self) -> InterpreterConfig:
|
||||||
width = None
|
width = None
|
||||||
height = None
|
height = None
|
||||||
splitter_sizes = []
|
splitter_sizes = []
|
||||||
|
|
@ -54,9 +57,9 @@ class InterpreterController(AbstractInterpreterController):
|
||||||
self,
|
self,
|
||||||
width: int,
|
width: int,
|
||||||
height: int,
|
height: int,
|
||||||
splitter_sizes: List[int],
|
splitter_sizes: list[int],
|
||||||
tabs: List[Dict[str, str]],
|
tabs: list[dict[str, str]],
|
||||||
):
|
) -> None:
|
||||||
self._registry.set_item("width", width)
|
self._registry.set_item("width", width)
|
||||||
self._registry.set_item("height", height)
|
self._registry.set_item("height", height)
|
||||||
self._registry.set_item("splitter_sizes", splitter_sizes)
|
self._registry.set_item("splitter_sizes", splitter_sizes)
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,42 @@
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
|
|
||||||
|
class _CustomSTD:
|
||||||
|
def __init__(self, orig_std, write_callback):
|
||||||
|
self.orig_std = orig_std
|
||||||
|
self._valid_orig = bool(orig_std)
|
||||||
|
self._write_callback = write_callback
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.orig_std, attr)
|
||||||
|
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
if key in ("orig_std", "_valid_orig", "_write_callback"):
|
||||||
|
super().__setattr__(key, value)
|
||||||
|
else:
|
||||||
|
setattr(self.orig_std, key, value)
|
||||||
|
|
||||||
|
def write(self, text):
|
||||||
|
if self._valid_orig:
|
||||||
|
self.orig_std.write(text)
|
||||||
|
self._write_callback(text)
|
||||||
|
|
||||||
|
|
||||||
class StdOEWrap:
|
class StdOEWrap:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._origin_stdout_write = None
|
|
||||||
self._origin_stderr_write = None
|
|
||||||
self._listening = False
|
|
||||||
self.lines = collections.deque()
|
self.lines = collections.deque()
|
||||||
|
|
||||||
if not sys.stdout:
|
|
||||||
sys.stdout = open(os.devnull, "w")
|
|
||||||
|
|
||||||
if not sys.stderr:
|
|
||||||
sys.stderr = open(os.devnull, "w")
|
|
||||||
|
|
||||||
if self._origin_stdout_write is None:
|
|
||||||
self._origin_stdout_write = sys.stdout.write
|
|
||||||
|
|
||||||
if self._origin_stderr_write is None:
|
|
||||||
self._origin_stderr_write = sys.stderr.write
|
|
||||||
|
|
||||||
self._listening = True
|
self._listening = True
|
||||||
sys.stdout.write = self._stdout_listener
|
|
||||||
sys.stderr.write = self._stderr_listener
|
self._stdout_wrap = _CustomSTD(sys.stdout, self._listener)
|
||||||
|
self._stderr_wrap = _CustomSTD(sys.stderr, self._listener)
|
||||||
|
|
||||||
|
sys.stdout = self._stdout_wrap
|
||||||
|
sys.stderr = self._stderr_wrap
|
||||||
|
|
||||||
def stop_listen(self):
|
def stop_listen(self):
|
||||||
self._listening = False
|
self._listening = False
|
||||||
|
|
||||||
def _stdout_listener(self, text):
|
def _listener(self, text):
|
||||||
if self._listening:
|
if self._listening:
|
||||||
self.lines.append(text)
|
self.lines.append(text)
|
||||||
if self._origin_stdout_write is not None:
|
|
||||||
self._origin_stdout_write(text)
|
|
||||||
|
|
||||||
def _stderr_listener(self, text):
|
|
||||||
if self._listening:
|
|
||||||
self.lines.append(text)
|
|
||||||
if self._origin_stderr_write is not None:
|
|
||||||
self._origin_stderr_write(text)
|
|
||||||
|
|
|
||||||
|
|
@ -501,6 +501,18 @@ class UseDisplayViewModel(BaseSettingsModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtractThumbnailFromSourceModel(BaseSettingsModel):
|
||||||
|
"""Thumbnail extraction from source files using ffmpeg and oiiotool."""
|
||||||
|
enabled: bool = SettingsField(True)
|
||||||
|
|
||||||
|
target_size: ResizeModel = SettingsField(
|
||||||
|
default_factory=ResizeModel, title="Target size"
|
||||||
|
)
|
||||||
|
background_color: ColorRGBA_uint8 = SettingsField(
|
||||||
|
(0, 0, 0, 0.0), title="Background color"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExtractOIIOTranscodeOutputModel(BaseSettingsModel):
|
class ExtractOIIOTranscodeOutputModel(BaseSettingsModel):
|
||||||
_layout = "expanded"
|
_layout = "expanded"
|
||||||
name: str = SettingsField(
|
name: str = SettingsField(
|
||||||
|
|
@ -1276,6 +1288,16 @@ class PublishPuginsModel(BaseSettingsModel):
|
||||||
default_factory=ExtractThumbnailModel,
|
default_factory=ExtractThumbnailModel,
|
||||||
title="Extract Thumbnail"
|
title="Extract Thumbnail"
|
||||||
)
|
)
|
||||||
|
ExtractThumbnailFromSource: ExtractThumbnailFromSourceModel = SettingsField( # noqa: E501
|
||||||
|
default_factory=ExtractThumbnailFromSourceModel,
|
||||||
|
title="Extract Thumbnail from source",
|
||||||
|
description=(
|
||||||
|
"Extract thumbnails from explicit file set in "
|
||||||
|
"instance.data['thumbnailSource'] using oiiotool"
|
||||||
|
" or ffmpeg."
|
||||||
|
"Used when artist provided thumbnail source."
|
||||||
|
)
|
||||||
|
)
|
||||||
ExtractOIIOTranscode: ExtractOIIOTranscodeModel = SettingsField(
|
ExtractOIIOTranscode: ExtractOIIOTranscodeModel = SettingsField(
|
||||||
default_factory=ExtractOIIOTranscodeModel,
|
default_factory=ExtractOIIOTranscodeModel,
|
||||||
title="Extract OIIO Transcode"
|
title="Extract OIIO Transcode"
|
||||||
|
|
@ -1515,6 +1537,16 @@ DEFAULT_PUBLISH_VALUES = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ExtractThumbnailFromSource": {
|
||||||
|
"enabled": True,
|
||||||
|
"target_size": {
|
||||||
|
"type": "resize",
|
||||||
|
"resize": {
|
||||||
|
"width": 300,
|
||||||
|
"height": 170
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
"ExtractOIIOTranscode": {
|
"ExtractOIIOTranscode": {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"profiles": []
|
"profiles": []
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue