Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Ftrack: Add more metadata to ftrack components #3612

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions openpype/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
get_ffmpeg_codec_args,
get_ffmpeg_format_args,
convert_ffprobe_fps_value,
convert_ffprobe_fps_to_float,
)
from .avalon_context import (
CURRENT_DOC_SCHEMAS,
Expand Down Expand Up @@ -287,6 +288,7 @@
"get_ffmpeg_codec_args",
"get_ffmpeg_format_args",
"convert_ffprobe_fps_value",
"convert_ffprobe_fps_to_float",

"CURRENT_DOC_SCHEMAS",
"PROJECT_NAME_ALLOWED_SYMBOLS",
Expand Down
37 changes: 37 additions & 0 deletions openpype/lib/transcoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,3 +938,40 @@ def convert_ffprobe_fps_value(str_value):
fps = int(fps)

return str(fps)


def convert_ffprobe_fps_to_float(value):
"""Convert string value of frame rate to float.

Copy of 'convert_ffprobe_fps_value' which raises exceptions on invalid
value, does not convert value to string and does not return "Unknown"
string.

Args:
value (str): Value to be converted.

Returns:
Float: Converted frame rate in float. If divisor in value is '0' then
'0.0' is returned.

Raises:
ValueError: Passed value is invalid for conversion.
"""

if not value:
raise ValueError("Got empty value.")

items = value.split("/")
if len(items) == 1:
return float(items[0])

if len(items) > 2:
raise ValueError((
"FPS expression contains multiple dividers \"{}\"."
).format(value))

dividend = float(items.pop(0))
divisor = float(items.pop(0))
if divisor == 0.0:
return 0.0
return dividend / divisor
43 changes: 40 additions & 3 deletions openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def process(self, instance):

asset_versions_data_by_id = {}
used_asset_versions = []

# Iterate over components and publish
for data in component_list:
self.log.debug("data: {}".format(data))
Expand Down Expand Up @@ -116,9 +117,6 @@ def process(self, instance):
asset_version_status_ids_by_name
)

# Component
self.create_component(session, asset_version_entity, data)

# Store asset version and components items that were
version_id = asset_version_entity["id"]
if version_id not in asset_versions_data_by_id:
Expand All @@ -135,6 +133,8 @@ def process(self, instance):
if asset_version_entity not in used_asset_versions:
used_asset_versions.append(asset_version_entity)

self._create_components(session, asset_versions_data_by_id)

instance.data["ftrackIntegratedAssetVersionsData"] = (
asset_versions_data_by_id
)
Expand Down Expand Up @@ -623,3 +623,40 @@ def create_component(self, session, asset_version_entity, data):
session.rollback()
session._configure_locations()
six.reraise(tp, value, tb)

def _create_components(self, session, asset_versions_data_by_id):
for item in asset_versions_data_by_id.values():
asset_version_entity = item["asset_version"]
component_items = item["component_items"]

component_entities = session.query(
(
"select id, name from Component where version_id is \"{}\""
).format(asset_version_entity["id"])
).all()

existing_component_names = {
component["name"]
for component in component_entities
}

contain_review = "ftrackreview-mp4" in existing_component_names
thumbnail_component_item = None
for component_item in component_items:
component_data = component_item.get("component_data") or {}
component_name = component_data.get("name")
if component_name == "ftrackreview-mp4":
contain_review = True
elif component_name == "ftrackreview-image":
thumbnail_component_item = component_item

if contain_review and thumbnail_component_item:
thumbnail_component_item["component_data"]["name"] = (
"thumbnail"
)

# Component
for component_item in component_items:
self.create_component(
session, asset_version_entity, component_item
)
159 changes: 129 additions & 30 deletions openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import copy
import pyblish.api

from openpype.lib import get_ffprobe_streams
from openpype.lib.transcoding import (
get_ffprobe_streams,
convert_ffprobe_fps_to_float,
)
from openpype.lib.profiles_filtering import filter_profiles


Expand Down Expand Up @@ -79,11 +82,6 @@ def process(self, instance):
).format(family))
return

# Prepare FPS
instance_fps = instance.data.get("fps")
if instance_fps is None:
instance_fps = instance.context.data["fps"]

status_name = self._get_asset_version_status_name(instance)

# Base of component item data
Expand Down Expand Up @@ -168,10 +166,7 @@ def process(self, instance):
# Add item to component list
component_list.append(thumbnail_item)

if (
not review_representations
and first_thumbnail_component is not None
):
if first_thumbnail_component is not None:
width = first_thumbnail_component_repre.get("width")
height = first_thumbnail_component_repre.get("height")
if not width or not height:
Expand Down Expand Up @@ -253,20 +248,9 @@ def process(self, instance):
first_thumbnail_component[
"asset_data"]["name"] = extended_asset_name

frame_start = repre.get("frameStartFtrack")
frame_end = repre.get("frameEndFtrack")
if frame_start is None or frame_end is None:
frame_start = instance.data["frameStart"]
frame_end = instance.data["frameEnd"]

# Frame end of uploaded video file should be duration in frames
# - frame start is always 0
# - frame end is duration in frames
duration = frame_end - frame_start + 1

fps = repre.get("fps")
if fps is None:
fps = instance_fps
component_meta = self._prepare_component_metadata(
instance, repre, repre_path, True
)

# Change location
review_item["component_path"] = repre_path
Expand All @@ -275,11 +259,7 @@ def process(self, instance):
# Default component name is "main".
"name": "ftrackreview-mp4",
"metadata": {
"ftr_meta": json.dumps({
"frameIn": 0,
"frameOut": int(duration),
"frameRate": float(fps)
})
"ftr_meta": json.dumps(component_meta)
}
}

Expand Down Expand Up @@ -322,6 +302,13 @@ def process(self, instance):
component_data = copy_src_item["component_data"]
component_name = component_data["name"]
component_data["name"] = component_name + "_src"
component_meta = self._prepare_component_metadata(
instance, repre, copy_src_item["component_path"], False
)
if component_meta:
component_data["metadata"] = {
"ftr_meta": json.dumps(component_meta)
}
component_list.append(copy_src_item)

# Add others representations as component
Expand All @@ -339,9 +326,17 @@ def process(self, instance):
):
other_item["asset_data"]["name"] = extended_asset_name

other_item["component_data"] = {
component_meta = self._prepare_component_metadata(
instance, repre, published_path, False
)
component_data = {
"name": repre["name"]
}
if component_meta:
component_data["metadata"] = {
"ftr_meta": json.dumps(component_meta)
}
other_item["component_data"] = component_data
other_item["component_location_name"] = unmanaged_location_name
other_item["component_path"] = published_path
component_list.append(other_item)
Expand Down Expand Up @@ -424,3 +419,107 @@ def _get_asset_version_status_name(self, instance):
return None

return matching_profile["status"] or None

def _prepare_component_metadata(
self, instance, repre, component_path, is_review
):
extension = os.path.splitext(component_path)[-1]
streams = []
try:
streams = get_ffprobe_streams(component_path)
except Exception:
self.log.debug((
"Failed to retrieve information about intput {}"
).format(component_path))

# Find video streams
video_streams = [
stream
for stream in streams
if stream["codec_type"] == "video"
]
# Skip if there are not video streams
# - exr is special case which can have issues with reading through
# ffmpegh but we want to set fps for it
if not video_streams and extension not in [".exr"]:
return {}

stream_width = None
stream_height = None
stream_fps = None
frame_out = None
for video_stream in video_streams:
tmp_width = video_stream.get("width")
tmp_height = video_stream.get("height")
if tmp_width and tmp_height:
stream_width = tmp_width
stream_height = tmp_height

input_framerate = video_stream.get("r_frame_rate")
duration = video_stream.get("duration")
if input_framerate is None or duration is None:
continue
try:
stream_fps = convert_ffprobe_fps_to_float(
input_framerate
)
except ValueError:
self.log.warning((
"Could not convert ffprobe fps to float \"{}\""
).format(input_framerate))
continue

stream_width = tmp_width
stream_height = tmp_height

self.log.debug("FPS from stream is {} and duration is {}".format(
input_framerate, duration
))
frame_out = float(duration) * stream_fps
break

# Prepare FPS
instance_fps = instance.data.get("fps")
if instance_fps is None:
instance_fps = instance.context.data["fps"]

if not is_review:
output = {}
fps = stream_fps or instance_fps
if fps:
output["frameRate"] = fps

if stream_width and stream_height:
output["width"] = int(stream_width)
output["height"] = int(stream_height)
return output

frame_start = repre.get("frameStartFtrack")
frame_end = repre.get("frameEndFtrack")
if frame_start is None or frame_end is None:
frame_start = instance.data["frameStart"]
frame_end = instance.data["frameEnd"]

fps = None
repre_fps = repre.get("fps")
if repre_fps is not None:
repre_fps = float(repre_fps)

fps = stream_fps or repre_fps or instance_fps

# Frame end of uploaded video file should be duration in frames
# - frame start is always 0
# - frame end is duration in frames
if not frame_out:
frame_out = frame_end - frame_start + 1

# Ftrack documentation says that it is required to have
# 'width' and 'height' in review component. But with those values
# review video does not play.
component_meta = {
"frameIn": 0,
"frameOut": frame_out,
"frameRate": float(fps)
}

return component_meta
1 change: 1 addition & 0 deletions openpype/plugins/publish/extract_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ def _render_output_definitions(
os.unlink(f)

new_repre.update({
"fps": temp_data["fps"],
"name": "{}_{}".format(output_name, output_ext),
"outputName": output_name,
"outputDef": output_def,
Expand Down