From b1cba11f6b796e8a0ed5225c0c52c243648f74a7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Oct 2025 11:32:36 +0200 Subject: [PATCH 01/11] Skips audio collection in editorial contexts. Prevents duplicate audio collection when editorial context already handles audio processing. - Introduces function to get audio instances. - Checks for existing audio instances to avoid duplication. - Skips default audio collection if audio is already provided. --- .../plugins/publish/collect_audio.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/client/ayon_core/plugins/publish/collect_audio.py b/client/ayon_core/plugins/publish/collect_audio.py index 2949ff1196..901c589ddc 100644 --- a/client/ayon_core/plugins/publish/collect_audio.py +++ b/client/ayon_core/plugins/publish/collect_audio.py @@ -46,6 +46,19 @@ class CollectAudio(pyblish.api.ContextPlugin): audio_product_name = "audioMain" def process(self, context): + # Make sure Editorial related products are excluded + # since those are maintained by ExtractOtioAudioTracks + audio_instances = self.get_audio_instances(context) + self.log.debug("Audio instances: {}".format(len(audio_instances))) + + # QUESTION: perhaps there is a better way to do this? + # This is having limitation for cases where no audio instance + # is found but still in editorial context. We should perhaps rather + # check if the instance is in particular editorial context. + if len(audio_instances) >= 1: + self.log.info("Audio provided from related instances") + return + # Fake filtering by family inside context plugin filtered_instances = [] for instance in pyblish.api.instances_by_plugin( @@ -102,6 +115,24 @@ class CollectAudio(pyblish.api.ContextPlugin): }] self.log.debug("Audio Data added to instance ...") + def get_audio_instances(self, context): + """Return only instances which are having audio in families + + Args: + context (pyblish.context): context of publisher + + Returns: + list: list of selected instances + """ + return [ + _i for _i in context + # filter only those with audio product type or family + # and also with reviewAudio data key + if bool("audio" in ( + _i.data.get("families", []) + [_i.data["productType"]]) + ) or _i.data.get("reviewAudio") + ] + def query_representations(self, project_name, folder_paths): """Query representations related to audio products for passed folders. From d8dab916196e6177d1a49c50ead3c320ec7f6f76 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Oct 2025 11:32:55 +0200 Subject: [PATCH 02/11] Adds audio to sibling reviewable instances Ensures audio is added to sibling instances needing audio for reviewable media. - Checks for sibling instances with the same parent ID. - Adds audio information to those instances. --- .../publish/extract_otio_audio_tracks.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py index 3a450a4f33..77e71e587f 100644 --- a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py +++ b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py @@ -125,6 +125,31 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): }) inst.data["audio"] = audio_attr + # Make sure if the audio instance is having siblink instances + # which needs audio for reviewable media so it is also added + # to its instance data + # Retrieve instance data from parent instance shot instance. + parent_instance_id = inst.data["parent_instance_id"] + for sibl_instance in inst.context: + sibl_parent_instance_id = sibl_instance.data.get( + "parent_instance_id") + # make sure the instance is not the same instance + # and the parent instance id is the same + if ( + sibl_instance.id is not inst.id and + sibl_parent_instance_id == parent_instance_id + ): + self.log.info( + "Adding audio to Sibling instance: " + f"{sibl_instance.data['label']}" + ) + audio_attr = sibl_instance.data.get("audio") or [] + audio_attr.append({ + "filename": audio_fpath, + "offset": 0 + }) + sibl_instance.data["audio"] = audio_attr + # add generated audio file to created files for recycling if audio_fpath not in created_files: created_files.append(audio_fpath) From 0b51e17a8a6bfa1d411fab3442f4f7bbabaadc6f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 Oct 2025 16:21:32 +0200 Subject: [PATCH 03/11] Fixes audio duplication in sibling instances. Ensures audio is only added to relevant sibling instances, preventing duplication. - Prevents adding audio to the same instance. - Streamlines audio assignment logic. --- .../plugins/publish/extract_otio_audio_tracks.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py index 77e71e587f..925ea03964 100644 --- a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py +++ b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py @@ -65,9 +65,9 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): # remove full mixed audio file os.remove(audio_temp_fpath) - def add_audio_to_instances(self, audio_file, instances): + def add_audio_to_instances(self, audio_file, audio_instances): created_files = [] - for inst in instances: + for inst in audio_instances: name = inst.data["folderPath"] recycling_file = [f for f in created_files if name in f] @@ -134,11 +134,10 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): sibl_parent_instance_id = sibl_instance.data.get( "parent_instance_id") # make sure the instance is not the same instance + if sibl_instance.id == inst.id: + continue # and the parent instance id is the same - if ( - sibl_instance.id is not inst.id and - sibl_parent_instance_id == parent_instance_id - ): + if sibl_parent_instance_id == parent_instance_id: self.log.info( "Adding audio to Sibling instance: " f"{sibl_instance.data['label']}" From 34b292b06a2a5d0f999ed0093252b10081c9e186 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Oct 2025 15:33:55 +0200 Subject: [PATCH 04/11] revert audio collector changes --- .../plugins/publish/collect_audio.py | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_audio.py b/client/ayon_core/plugins/publish/collect_audio.py index 901c589ddc..2949ff1196 100644 --- a/client/ayon_core/plugins/publish/collect_audio.py +++ b/client/ayon_core/plugins/publish/collect_audio.py @@ -46,19 +46,6 @@ class CollectAudio(pyblish.api.ContextPlugin): audio_product_name = "audioMain" def process(self, context): - # Make sure Editorial related products are excluded - # since those are maintained by ExtractOtioAudioTracks - audio_instances = self.get_audio_instances(context) - self.log.debug("Audio instances: {}".format(len(audio_instances))) - - # QUESTION: perhaps there is a better way to do this? - # This is having limitation for cases where no audio instance - # is found but still in editorial context. We should perhaps rather - # check if the instance is in particular editorial context. - if len(audio_instances) >= 1: - self.log.info("Audio provided from related instances") - return - # Fake filtering by family inside context plugin filtered_instances = [] for instance in pyblish.api.instances_by_plugin( @@ -115,24 +102,6 @@ class CollectAudio(pyblish.api.ContextPlugin): }] self.log.debug("Audio Data added to instance ...") - def get_audio_instances(self, context): - """Return only instances which are having audio in families - - Args: - context (pyblish.context): context of publisher - - Returns: - list: list of selected instances - """ - return [ - _i for _i in context - # filter only those with audio product type or family - # and also with reviewAudio data key - if bool("audio" in ( - _i.data.get("families", []) + [_i.data["productType"]]) - ) or _i.data.get("reviewAudio") - ] - def query_representations(self, project_name, folder_paths): """Query representations related to audio products for passed folders. From 182e457505a15d8872a311b56147dbd600a67dac Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Oct 2025 15:35:12 +0200 Subject: [PATCH 05/11] Improve logic for checking already existing audio key --- client/ayon_core/plugins/publish/collect_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/collect_audio.py b/client/ayon_core/plugins/publish/collect_audio.py index 2949ff1196..273e966cfd 100644 --- a/client/ayon_core/plugins/publish/collect_audio.py +++ b/client/ayon_core/plugins/publish/collect_audio.py @@ -52,7 +52,7 @@ class CollectAudio(pyblish.api.ContextPlugin): context, self.__class__ ): # Skip instances that already have audio filled - if instance.data.get("audio"): + if "audio" in instance.data: self.log.debug( "Skipping Audio collection. It is already collected" ) From d2fdae67e75e5a5b99c74793ba0b0210518b785e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Oct 2025 15:47:11 +0200 Subject: [PATCH 06/11] Adds audio instance attribute collection Adds a collector to identify audio instances and link them to sibling instances. This ensures that sibling instances, requiring audio for reviewable media, inherit audio attributes. The collector checks and links audio if: - The sibling instance shares the same parent ID. - The instance is not the audio instance itself. --- .../publish/extract_otio_audio_tracks.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py index 925ea03964..e0bea02082 100644 --- a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py +++ b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py @@ -8,6 +8,56 @@ from ayon_core.lib import ( run_subprocess ) +# pridat collector + +class CollectParentAudioInstanceAttribute(pyblish.api.ContextPlugin): + """Collect audio instance attribute""" + + order = pyblish.api.CollectorOrder + label = "Collect Audio Instance Attribute" + hosts = ["hiero", "resolve", "flame"] + + def process(self, context): + + audio_instances = self.get_audio_instances(context) + + for inst in audio_instances: + # Make sure if the audio instance is having siblink instances + # which needs audio for reviewable media so it is also added + # to its instance data + # Retrieve instance data from parent instance shot instance. + parent_instance_id = inst.data["parent_instance_id"] + for sibl_instance in inst.context: + sibl_parent_instance_id = sibl_instance.data.get( + "parent_instance_id") + # make sure the instance is not the same instance + if sibl_instance.id == inst.id: + continue + # and the parent instance id is the same + if sibl_parent_instance_id == parent_instance_id: + self.log.info( + "Adding audio to Sibling instance: " + f"{sibl_instance.data['label']}" + ) + sibl_instance.data["audio"] = None + + def get_audio_instances(self, context): + """Return only instances which are having audio in families + + Args: + context (pyblish.context): context of publisher + + Returns: + list: list of selected instances + """ + return [ + _i for _i in context + # filter only those with audio product type or family + # and also with reviewAudio data key + if bool("audio" in ( + _i.data.get("families", []) + [_i.data["productType"]]) + ) or _i.data.get("reviewAudio") + ] class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): """Extract Audio tracks from OTIO timeline. From 90852663d1ee89fb6734cd1793090c29eb5ee3d2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Oct 2025 15:49:15 +0200 Subject: [PATCH 07/11] ruff improvements --- client/ayon_core/plugins/publish/extract_otio_audio_tracks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py index e0bea02082..08e786f067 100644 --- a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py +++ b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py @@ -8,7 +8,6 @@ from ayon_core.lib import ( run_subprocess ) -# pridat collector class CollectParentAudioInstanceAttribute(pyblish.api.ContextPlugin): """Collect audio instance attribute""" @@ -59,6 +58,7 @@ class CollectParentAudioInstanceAttribute(pyblish.api.ContextPlugin): ) or _i.data.get("reviewAudio") ] + class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): """Extract Audio tracks from OTIO timeline. From 5dc462c62acc03c9619de980da5632543e9cf702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 5 Nov 2025 16:53:23 +0100 Subject: [PATCH 08/11] Update client/ayon_core/plugins/publish/extract_otio_audio_tracks.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/extract_otio_audio_tracks.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py index 08e786f067..fce6eb4e93 100644 --- a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py +++ b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py @@ -49,14 +49,14 @@ class CollectParentAudioInstanceAttribute(pyblish.api.ContextPlugin): Returns: list: list of selected instances """ - return [ - _i for _i in context - # filter only those with audio product type or family - # and also with reviewAudio data key - if bool("audio" in ( - _i.data.get("families", []) + [_i.data["productType"]]) - ) or _i.data.get("reviewAudio") - ] + audio_instances = [] + for instance in context: + if ( + instace.data["productType"] == "audio" + or instace.data.get("reviewAudio") + ): + audio_instances.append(instance) + return audio_instances class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): From e4b3aafc9491ad3fdafdefb18ec4c5740af5e76d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Nov 2025 17:07:53 +0100 Subject: [PATCH 09/11] Refactors audio instance collection for clarity Simplifies audio instance identification. The code now uses dedicated functions to collect and manage audio instances and their associations. --- .../publish/extract_otio_audio_tracks.py | 105 ++++++++---------- 1 file changed, 46 insertions(+), 59 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py index fce6eb4e93..6a955df725 100644 --- a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py +++ b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py @@ -1,5 +1,6 @@ import os import tempfile +import collections import pyblish @@ -9,54 +10,58 @@ from ayon_core.lib import ( ) +def get_audio_instances(context): + """Return only instances which are having audio in families + + Args: + context (pyblish.context): context of publisher + + Returns: + list: list of selected instances + """ + audio_instances = [] + for instance in context: + if not instance.data.get("parent_instance_id"): + continue + if ( + instance.data["productType"] == "audio" + or instance.data.get("reviewAudio") + ): + audio_instances.append(instance) + return audio_instances + + class CollectParentAudioInstanceAttribute(pyblish.api.ContextPlugin): """Collect audio instance attribute""" order = pyblish.api.CollectorOrder label = "Collect Audio Instance Attribute" - hosts = ["hiero", "resolve", "flame"] def process(self, context): - audio_instances = self.get_audio_instances(context) + audio_instances = get_audio_instances(context) - for inst in audio_instances: - # Make sure if the audio instance is having siblink instances - # which needs audio for reviewable media so it is also added - # to its instance data - # Retrieve instance data from parent instance shot instance. - parent_instance_id = inst.data["parent_instance_id"] - for sibl_instance in inst.context: - sibl_parent_instance_id = sibl_instance.data.get( - "parent_instance_id") - # make sure the instance is not the same instance - if sibl_instance.id == inst.id: - continue - # and the parent instance id is the same - if sibl_parent_instance_id == parent_instance_id: - self.log.info( - "Adding audio to Sibling instance: " - f"{sibl_instance.data['label']}" - ) - sibl_instance.data["audio"] = None - - def get_audio_instances(self, context): - """Return only instances which are having audio in families - - Args: - context (pyblish.context): context of publisher - - Returns: - list: list of selected instances - """ - audio_instances = [] + # create mapped instances by parent id + instances_by_parent_id = collections.defaultdict(list) for instance in context: - if ( - instace.data["productType"] == "audio" - or instace.data.get("reviewAudio") - ): - audio_instances.append(instance) - return audio_instances + parent_instance_id = instance.data.get("parent_instance_id") + if not parent_instance_id: + continue + instances_by_parent_id[parent_instance_id].append(instance) + + # distribute audio related attribute + for audio_instance in audio_instances: + parent_instance_id = audio_instance.data["parent_instance_id"] + + for sibl_instance in instances_by_parent_id[parent_instance_id]: + # exclude the same audio instance + if sibl_instance.id == audio_instance.id: + continue + self.log.info( + "Adding audio to Sibling instance: " + f"{sibl_instance.data['label']}" + ) + sibl_instance.data["audio"] = None class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): @@ -69,7 +74,6 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): order = pyblish.api.ExtractorOrder - 0.44 label = "Extract OTIO Audio Tracks" - hosts = ["hiero", "resolve", "flame"] def process(self, context): """Convert otio audio track's content to audio representations @@ -78,13 +82,14 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): context (pyblish.Context): context of publisher """ # split the long audio file to peces devided by isntances - audio_instances = self.get_audio_instances(context) - self.log.debug("Audio instances: {}".format(len(audio_instances))) + audio_instances = get_audio_instances(context) if len(audio_instances) < 1: self.log.info("No audio instances available") return + self.log.debug("Audio instances: {}".format(len(audio_instances))) + # get sequence otio_timeline = context.data["otioTimeline"] @@ -203,24 +208,6 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): if audio_fpath not in created_files: created_files.append(audio_fpath) - def get_audio_instances(self, context): - """Return only instances which are having audio in families - - Args: - context (pyblish.context): context of publisher - - Returns: - list: list of selected instances - """ - return [ - _i for _i in context - # filter only those with audio product type or family - # and also with reviewAudio data key - if bool("audio" in ( - _i.data.get("families", []) + [_i.data["productType"]]) - ) or _i.data.get("reviewAudio") - ] - def get_audio_track_items(self, otio_timeline): """Get all audio clips form OTIO audio tracks From f22ec30e34cd08e754e3721215e55eb446d81334 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Nov 2025 11:48:50 +0100 Subject: [PATCH 10/11] Refactors audio extraction for correct publishing Updates audio extraction logic to address issues when publishing without an audio product. - Improves audio file handling and ensures correct representation assignments. - Adds helper functions for better code organization and readability. - Improves sibling instance processing. - Fixes an issue where audio wasn't extracted correctly for certain cases. --- .../publish/extract_otio_audio_tracks.py | 156 ++++++++++-------- 1 file changed, 88 insertions(+), 68 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py index 6a955df725..6ad7dd85db 100644 --- a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py +++ b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py @@ -1,13 +1,12 @@ +import collections +import hashlib import os import tempfile -import collections +import uuid +from pathlib import Path import pyblish - -from ayon_core.lib import ( - get_ffmpeg_tool_args, - run_subprocess -) +from ayon_core.lib import get_ffmpeg_tool_args, run_subprocess def get_audio_instances(context): @@ -31,6 +30,24 @@ def get_audio_instances(context): return audio_instances +def map_instances_by_parent_id(context): + """Create a mapping of instances by their parent id + + Args: + context (pyblish.context): context of publisher + + Returns: + dict: mapping of instances by their parent id + """ + instances_by_parent_id = collections.defaultdict(list) + for instance in context: + parent_instance_id = instance.data.get("parent_instance_id") + if not parent_instance_id: + continue + instances_by_parent_id[parent_instance_id].append(instance) + return instances_by_parent_id + + class CollectParentAudioInstanceAttribute(pyblish.api.ContextPlugin): """Collect audio instance attribute""" @@ -42,12 +59,7 @@ class CollectParentAudioInstanceAttribute(pyblish.api.ContextPlugin): audio_instances = get_audio_instances(context) # create mapped instances by parent id - instances_by_parent_id = collections.defaultdict(list) - for instance in context: - parent_instance_id = instance.data.get("parent_instance_id") - if not parent_instance_id: - continue - instances_by_parent_id[parent_instance_id].append(instance) + instances_by_parent_id = map_instances_by_parent_id(context) # distribute audio related attribute for audio_instance in audio_instances: @@ -75,6 +87,8 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): order = pyblish.api.ExtractorOrder - 0.44 label = "Extract OTIO Audio Tracks" + temp_dir = None + def process(self, context): """Convert otio audio track's content to audio representations @@ -99,8 +113,8 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): if not audio_inputs: return - # temp file - audio_temp_fpath = self.create_temp_file("audio") + # Convert all available audio into single file for trimming + audio_temp_fpath = self.create_temp_file("timeline_audio_track") # create empty audio with longest duration empty = self.create_empty(audio_inputs) @@ -114,19 +128,25 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): # remove empty os.remove(empty["mediaPath"]) + # create mapped instances by parent id + instances_by_parent_id = map_instances_by_parent_id(context) + # cut instance framerange and add to representations - self.add_audio_to_instances(audio_temp_fpath, audio_instances) + self.add_audio_to_instances( + audio_temp_fpath, audio_instances, instances_by_parent_id) # remove full mixed audio file os.remove(audio_temp_fpath) - def add_audio_to_instances(self, audio_file, audio_instances): + def add_audio_to_instances( + self, audio_file, audio_instances, instances_by_parent_id): created_files = [] - for inst in audio_instances: - name = inst.data["folderPath"] + for audio_instance in audio_instances: + folder_path = audio_instance.data["folderPath"] + file_suffix = folder_path.replace("/", "-") - recycling_file = [f for f in created_files if name in f] - audio_clip = inst.data["otioClip"] + recycling_file = [f for f in created_files if file_suffix in f] + audio_clip = audio_instance.data["otioClip"] audio_range = audio_clip.range_in_parent() duration = audio_range.duration.to_frames() @@ -139,74 +159,70 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): start_sec = relative_start_time.to_seconds() duration_sec = audio_range.duration.to_seconds() - # temp audio file - audio_fpath = self.create_temp_file(name) + # shot related audio file + shot_audio_fpath = self.create_temp_file(file_suffix) cmd = get_ffmpeg_tool_args( "ffmpeg", "-ss", str(start_sec), "-t", str(duration_sec), "-i", audio_file, - audio_fpath + shot_audio_fpath ) # run subprocess self.log.debug("Executing: {}".format(" ".join(cmd))) run_subprocess(cmd, logger=self.log) - else: - audio_fpath = recycling_file.pop() - if "audio" in ( - inst.data["families"] + [inst.data["productType"]] - ): + # add generated audio file to created files for recycling + if shot_audio_fpath not in created_files: + created_files.append(shot_audio_fpath) + else: + shot_audio_fpath = recycling_file.pop() + + # audio file needs to be published as representation + if audio_instance.data["productType"] == "audio": # create empty representation attr - if "representations" not in inst.data: - inst.data["representations"] = [] + if "representations" not in audio_instance.data: + audio_instance.data["representations"] = [] # add to representations - inst.data["representations"].append({ - "files": os.path.basename(audio_fpath), + audio_instance.data["representations"].append({ + "files": os.path.basename(shot_audio_fpath), "name": "wav", "ext": "wav", - "stagingDir": os.path.dirname(audio_fpath), + "stagingDir": os.path.dirname(shot_audio_fpath), "frameStart": 0, "frameEnd": duration }) - elif "reviewAudio" in inst.data.keys(): - audio_attr = inst.data.get("audio") or [] + # audio file needs to be reviewable too + elif "reviewAudio" in audio_instance.data.keys(): + audio_attr = audio_instance.data.get("audio") or [] audio_attr.append({ - "filename": audio_fpath, + "filename": shot_audio_fpath, "offset": 0 }) - inst.data["audio"] = audio_attr + audio_instance.data["audio"] = audio_attr # Make sure if the audio instance is having siblink instances # which needs audio for reviewable media so it is also added # to its instance data # Retrieve instance data from parent instance shot instance. - parent_instance_id = inst.data["parent_instance_id"] - for sibl_instance in inst.context: - sibl_parent_instance_id = sibl_instance.data.get( - "parent_instance_id") - # make sure the instance is not the same instance - if sibl_instance.id == inst.id: + parent_instance_id = audio_instance.data["parent_instance_id"] + for sibl_instance in instances_by_parent_id[parent_instance_id]: + # exclude the same audio instance + if sibl_instance.id == audio_instance.id: continue - # and the parent instance id is the same - if sibl_parent_instance_id == parent_instance_id: - self.log.info( - "Adding audio to Sibling instance: " - f"{sibl_instance.data['label']}" - ) - audio_attr = sibl_instance.data.get("audio") or [] - audio_attr.append({ - "filename": audio_fpath, - "offset": 0 - }) - sibl_instance.data["audio"] = audio_attr - - # add generated audio file to created files for recycling - if audio_fpath not in created_files: - created_files.append(audio_fpath) + self.log.info( + "Adding audio to Sibling instance: " + f"{sibl_instance.data['label']}" + ) + audio_attr = sibl_instance.data.get("audio") or [] + audio_attr.append({ + "filename": shot_audio_fpath, + "offset": 0 + }) + sibl_instance.data["audio"] = audio_attr def get_audio_track_items(self, otio_timeline): """Get all audio clips form OTIO audio tracks @@ -382,19 +398,23 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): os.remove(filters_tmp_filepath) - def create_temp_file(self, name): + def create_temp_file(self, file_suffix): """Create temp wav file Args: - name (str): name to be used in file name + file_suffix (str): name to be used in file name Returns: str: temp fpath """ - name = name.replace("/", "_") - return os.path.normpath( - tempfile.mktemp( - prefix="pyblish_tmp_{}_".format(name), - suffix=".wav" - ) - ) + extension = ".wav" + # get 8 characters + hash = hashlib.md5(str(uuid.uuid4()).encode()).hexdigest()[:8] + file_name = f"{hash}_{file_suffix}{extension}" + + if not self.temp_dir: + audio_temp_dir = tempfile.mkdtemp(prefix="AYON_audio_") + self.temp_dir = Path(audio_temp_dir) + self.temp_dir.mkdir(parents=True, exist_ok=True) + + return (self.temp_dir / file_name).as_posix() From 6ae58b458477b15325634fa0e4771ca44044464d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Nov 2025 12:34:47 +0100 Subject: [PATCH 11/11] Refactors: Avoids errors when publishing no audio Refactors the audio extraction process to avoid errors when no audio instances are present in the scene. - Prevents processing if no audio instances are found. - Ensures correct handling of missing audio data. - Renames temp directory variable for clarity. --- .../publish/extract_otio_audio_tracks.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py index 6ad7dd85db..1df96b2918 100644 --- a/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py +++ b/client/ayon_core/plugins/publish/extract_otio_audio_tracks.py @@ -58,6 +58,10 @@ class CollectParentAudioInstanceAttribute(pyblish.api.ContextPlugin): audio_instances = get_audio_instances(context) + # no need to continue if no audio instances found + if not audio_instances: + return + # create mapped instances by parent id instances_by_parent_id = map_instances_by_parent_id(context) @@ -87,7 +91,7 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): order = pyblish.api.ExtractorOrder - 0.44 label = "Extract OTIO Audio Tracks" - temp_dir = None + temp_dir_path = None def process(self, context): """Convert otio audio track's content to audio representations @@ -98,8 +102,8 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): # split the long audio file to peces devided by isntances audio_instances = get_audio_instances(context) - if len(audio_instances) < 1: - self.log.info("No audio instances available") + # no need to continue if no audio instances found + if not audio_instances: return self.log.debug("Audio instances: {}".format(len(audio_instances))) @@ -412,9 +416,9 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin): hash = hashlib.md5(str(uuid.uuid4()).encode()).hexdigest()[:8] file_name = f"{hash}_{file_suffix}{extension}" - if not self.temp_dir: - audio_temp_dir = tempfile.mkdtemp(prefix="AYON_audio_") - self.temp_dir = Path(audio_temp_dir) - self.temp_dir.mkdir(parents=True, exist_ok=True) + if not self.temp_dir_path: + audio_temp_dir_path = tempfile.mkdtemp(prefix="AYON_audio_") + self.temp_dir_path = Path(audio_temp_dir_path) + self.temp_dir_path.mkdir(parents=True, exist_ok=True) - return (self.temp_dir / file_name).as_posix() + return (self.temp_dir_path / file_name).as_posix()