mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Add get_image_info_metadata function for image metadata retrieval.
- Added a new function to retrieve metadata from image files. - The function first tries OpenImageIO and then falls back to FFprobe if needed.
This commit is contained in:
parent
5678b2f842
commit
cdd0aa7795
6 changed files with 157 additions and 23 deletions
|
|
@ -122,6 +122,7 @@ from .transcoding import (
|
|||
convert_ffprobe_fps_value,
|
||||
convert_ffprobe_fps_to_float,
|
||||
get_rescaled_command_arguments,
|
||||
get_image_info_metadata,
|
||||
)
|
||||
|
||||
from .plugin_tools import (
|
||||
|
|
@ -159,15 +160,11 @@ __all__ = [
|
|||
"get_local_site_id",
|
||||
"get_ayon_username",
|
||||
"get_openpype_username",
|
||||
|
||||
"initialize_ayon_connection",
|
||||
|
||||
"CacheItem",
|
||||
"NestedCacheItem",
|
||||
|
||||
"emit_event",
|
||||
"register_event_callback",
|
||||
|
||||
"get_ayon_launcher_args",
|
||||
"get_openpype_execute_args",
|
||||
"get_linux_launcher_args",
|
||||
|
|
@ -178,10 +175,8 @@ __all__ = [
|
|||
"run_openpype_process",
|
||||
"path_to_subprocess_arg",
|
||||
"CREATE_NO_WINDOW",
|
||||
|
||||
"env_value_to_bool",
|
||||
"get_paths_from_environ",
|
||||
|
||||
"ToolNotFoundError",
|
||||
"find_executable",
|
||||
"get_oiio_tools_path",
|
||||
|
|
@ -189,13 +184,10 @@ __all__ = [
|
|||
"get_ffmpeg_tool_path",
|
||||
"get_ffmpeg_tool_args",
|
||||
"is_oiio_supported",
|
||||
|
||||
"AbstractAttrDef",
|
||||
|
||||
"UIDef",
|
||||
"UISeparatorDef",
|
||||
"UILabelDef",
|
||||
|
||||
"UnknownDef",
|
||||
"NumberDef",
|
||||
"TextDef",
|
||||
|
|
@ -203,14 +195,12 @@ __all__ = [
|
|||
"BoolDef",
|
||||
"FileDef",
|
||||
"FileDefItem",
|
||||
|
||||
"import_filepath",
|
||||
"modules_from_path",
|
||||
"recursive_bases_from_class",
|
||||
"classes_from_module",
|
||||
"import_module_from_dirpath",
|
||||
"is_func_signature_supported",
|
||||
|
||||
"get_transcode_temp_directory",
|
||||
"should_convert_for_ffmpeg",
|
||||
"convert_for_ffmpeg",
|
||||
|
|
@ -222,33 +212,26 @@ __all__ = [
|
|||
"convert_ffprobe_fps_value",
|
||||
"convert_ffprobe_fps_to_float",
|
||||
"get_rescaled_command_arguments",
|
||||
"get_image_info_metadata",
|
||||
|
||||
"compile_list_of_regexes",
|
||||
|
||||
"filter_profiles",
|
||||
|
||||
"prepare_template_data",
|
||||
"source_hash",
|
||||
|
||||
"format_file_size",
|
||||
"collect_frames",
|
||||
"create_hard_link",
|
||||
"version_up",
|
||||
"get_version_from_path",
|
||||
"get_last_version_from_path",
|
||||
|
||||
"TemplateUnsolved",
|
||||
"StringTemplate",
|
||||
"FormatObject",
|
||||
|
||||
"terminal",
|
||||
|
||||
"get_datetime_data",
|
||||
"get_timestamp",
|
||||
"get_formatted_current_time",
|
||||
|
||||
"Logger",
|
||||
|
||||
"is_in_ayon_launcher_process",
|
||||
"is_running_from_build",
|
||||
"is_using_ayon_console",
|
||||
|
|
|
|||
|
|
@ -834,6 +834,90 @@ def get_ffprobe_streams(path_to_file, logger=None):
|
|||
return get_ffprobe_data(path_to_file, logger)["streams"]
|
||||
|
||||
|
||||
def get_image_info_metadata(
|
||||
path_to_file,
|
||||
keys=None,
|
||||
logger=None
|
||||
):
|
||||
"""Get metadata from image file.
|
||||
|
||||
At first it will try to detect if the image input is supported by
|
||||
OpenImageIO. If it is then it gets the metadata from the image using
|
||||
OpenImageIO. If it is not supported by OpenImageIO then it will try to
|
||||
get the metadata using FFprobe.
|
||||
|
||||
Args:
|
||||
path_to_file (str): Path to image file.
|
||||
keys (list[str]): List of keys that should be returned. If None then
|
||||
all keys are returned.
|
||||
logger (logging.Logger): Logger used for logging.
|
||||
"""
|
||||
if logger is None:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def _ffprobe_metadata_conversion(metadata):
|
||||
"""Convert ffprobe metadata unified format."""
|
||||
output = {}
|
||||
for key, val in metadata.items():
|
||||
if key in ("tags", "disposition"):
|
||||
output.update(val)
|
||||
else:
|
||||
output[key] = val
|
||||
return output
|
||||
|
||||
metadata_stream = None
|
||||
ext = os.path.splitext(path_to_file)[-1].lower()
|
||||
if ext not in IMAGE_EXTENSIONS:
|
||||
logger.info((
|
||||
"File extension \"{}\" is not supported by OpenImageIO."
|
||||
" Trying to get metadata using FFprobe."
|
||||
).format(ext))
|
||||
ffprobe_stream = get_ffprobe_data(path_to_file, logger)
|
||||
if "streams" in ffprobe_stream and len(ffprobe_stream["streams"]) > 0:
|
||||
metadata_stream = _ffprobe_metadata_conversion(
|
||||
ffprobe_stream["streams"][0])
|
||||
|
||||
if not metadata_stream and is_oiio_supported():
|
||||
oiio_stream = get_oiio_info_for_input(path_to_file, logger=logger)
|
||||
if "attribs" in (oiio_stream or {}):
|
||||
metadata_stream = {}
|
||||
for key, val in oiio_stream["attribs"].items():
|
||||
if "smpte:" in key.lower():
|
||||
key = key.replace("smpte:", "")
|
||||
metadata_stream[key.lower()] = val
|
||||
for key, val in oiio_stream.items():
|
||||
if key == "attribs":
|
||||
continue
|
||||
metadata_stream[key] = val
|
||||
else:
|
||||
logger.info((
|
||||
"OpenImageIO is not supported on this system."
|
||||
" Trying to get metadata using FFprobe."
|
||||
))
|
||||
ffprobe_stream = get_ffprobe_data(path_to_file, logger)
|
||||
if "streams" in ffprobe_stream and len(ffprobe_stream["streams"]) > 0:
|
||||
metadata_stream = _ffprobe_metadata_conversion(
|
||||
ffprobe_stream["streams"][0])
|
||||
|
||||
if not metadata_stream:
|
||||
logger.warning("Failed to get metadata from image file.")
|
||||
return {}
|
||||
|
||||
if keys is None:
|
||||
return metadata_stream
|
||||
|
||||
output = {}
|
||||
for key in keys:
|
||||
for k, v in metadata_stream.items():
|
||||
if key == k:
|
||||
output[key] = v
|
||||
break
|
||||
if isinstance(v, dict) and key in v:
|
||||
output[key] = v[key]
|
||||
break
|
||||
return output
|
||||
|
||||
|
||||
def get_ffmpeg_format_args(ffprobe_data, source_ffmpeg_cmd=None):
|
||||
"""Copy format from input metadata for output.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
|
|
@ -6,6 +5,7 @@ from qtpy import QtWidgets, QtCore, QtGui
|
|||
|
||||
from ayon_api import get_representations
|
||||
from ayon_core.pipeline import load, Anatomy
|
||||
from ayon_core.lib import get_image_info_metadata
|
||||
from ayon_core import resources, style
|
||||
from ayon_core.pipeline.load import get_representation_path_with_anatomy
|
||||
from ayon_core.tools.utils import show_message_dialog
|
||||
|
|
@ -44,6 +44,7 @@ class ExportOTIOOptionsDialog(QtWidgets.QDialog):
|
|||
# Not all hosts have OpenTimelineIO available.
|
||||
import opentimelineio as OTIO
|
||||
self.OTIO = OTIO
|
||||
self.log = log
|
||||
|
||||
super(ExportOTIOOptionsDialog, self).__init__(parent=parent)
|
||||
|
||||
|
|
@ -247,12 +248,23 @@ class ExportOTIOOptionsDialog(QtWidgets.QDialog):
|
|||
# Get path to representation with correct frame number
|
||||
repre_path = get_representation_path_with_anatomy(
|
||||
representation, anatomy)
|
||||
|
||||
timecode_start_frame = 0
|
||||
if file_metadata := get_image_info_metadata(
|
||||
repre_path, ["timecode"], self.log
|
||||
):
|
||||
# use otio to convert timecode into frame number
|
||||
timecode_start_frame = self.OTIO.opentime.from_timecode(
|
||||
file_metadata["timecode"], framerate)
|
||||
|
||||
repre_path = Path(repre_path)
|
||||
|
||||
first_frame = representation["context"].get("frame")
|
||||
if first_frame is None:
|
||||
range = self.OTIO.opentime.TimeRange(
|
||||
start_time=self.OTIO.opentime.RationalTime(0, framerate),
|
||||
start_time=self.OTIO.opentime.RationalTime(
|
||||
timecode_start_frame, framerate
|
||||
),
|
||||
duration=self.OTIO.opentime.RationalTime(frames, framerate),
|
||||
)
|
||||
# Use 'repre_path' as single file
|
||||
|
|
@ -282,9 +294,11 @@ class ExportOTIOOptionsDialog(QtWidgets.QDialog):
|
|||
frame_padding = len(frame_str)
|
||||
|
||||
range = self.OTIO.opentime.TimeRange(
|
||||
start_time=self.OTIO.opentime.RationalTime(0, framerate),
|
||||
start_time=self.OTIO.opentime.RationalTime(
|
||||
timecode_start_frame.to_frames(), float(framerate)
|
||||
),
|
||||
duration=self.OTIO.opentime.RationalTime(
|
||||
len(repre_files), framerate)
|
||||
len(repre_files), framerate),
|
||||
)
|
||||
|
||||
media_reference = self.OTIO.schema.ImageSequenceReference(
|
||||
|
|
|
|||
53
client/ayon_core/tests/lib/test_transcoding.py
Normal file
53
client/ayon_core/tests/lib/test_transcoding.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import pytest
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from ayon_core.lib.transcoding import get_image_info_metadata
|
||||
|
||||
logger = logging.getLogger('test_transcoding')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"resources_path_factory, metadata, expected, test_id",
|
||||
[
|
||||
(
|
||||
Path(__file__).parent.parent
|
||||
/ "resources"
|
||||
/ "lib"
|
||||
/ "transcoding"
|
||||
/ "a01vfxd_sh010_plateP01_v002.1013.exr",
|
||||
["timecode"],
|
||||
{"timecode": "01:00:06:03"},
|
||||
"test_01",
|
||||
),
|
||||
(
|
||||
Path(__file__).parent.parent
|
||||
/ "resources"
|
||||
/ "lib"
|
||||
/ "transcoding"
|
||||
/ "a01vfxd_sh010_plateP01_v002.1013.exr",
|
||||
["timecode", "width", "height", "duration"],
|
||||
{"timecode": "01:00:06:03", "width": 1920, "height": 1080},
|
||||
"test_02",
|
||||
),
|
||||
(
|
||||
Path(__file__).parent.parent
|
||||
/ "resources"
|
||||
/ "lib"
|
||||
/ "transcoding"
|
||||
/ "a01vfxd_sh010_plateP01_v002.mov",
|
||||
["width", "height", "duration"],
|
||||
{"width": 1920, "height": 1080, "duration": "0.041708"},
|
||||
"test_03",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_image_info_metadata_happy_path(
|
||||
resources_path_factory, metadata, expected, test_id
|
||||
):
|
||||
path_to_file = resources_path_factory.as_posix()
|
||||
|
||||
returned_data = get_image_info_metadata(path_to_file, metadata, logger)
|
||||
logger.debug(f"Returned data: {returned_data}")
|
||||
|
||||
assert returned_data == expected
|
||||
Binary file not shown.
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue