From d0abc5416e1f94bda15fc9655d0b9b2aa59d05dc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jun 2024 10:33:29 +0200 Subject: [PATCH] Add support for setting timeline framerate based on highest clip framerate and adjust clip start frames accordingly. Include method to calculate timecode start frame from metadata. Add support for setting timeline framerate based on highest clip framerate and adjust clip start frames accordingly. Include method to calculate timecode start frame from metadata. --- client/ayon_core/plugins/load/export_otio.py | 77 +++++++++++++++----- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/client/ayon_core/plugins/load/export_otio.py b/client/ayon_core/plugins/load/export_otio.py index ac236e1038..454babe67c 100644 --- a/client/ayon_core/plugins/load/export_otio.py +++ b/client/ayon_core/plugins/load/export_otio.py @@ -239,38 +239,53 @@ class ExportOTIOOptionsDialog(QtWidgets.QDialog): self.close() - def create_clip(self, name, clip_data): + def create_clip(self, name, clip_data, timeline_framerate): representation = clip_data["representation"] anatomy = clip_data["anatomy"] frames = clip_data["frames"] framerate = clip_data["framerate"] + print("_" * 50) + print(f"{name}") + print(f"{framerate}") # Get path to representation with correct frame number repre_path = get_representation_path_with_anatomy( representation, anatomy) - timecode_start_frame = 0 + media_start_frame = clip_start_frame = 0 if file_metadata := get_image_info_metadata( - repre_path, ["timecode"], self.log + repre_path, ["timecode", "duration"], self.log ): - # use otio to convert timecode into frame number - timecode_start_frame = self.OTIO.opentime.from_timecode( - file_metadata["timecode"], framerate) + framerate = round(framerate, 3) + media_start_frame = self.get_timecode_start_frame( + framerate, file_metadata) + clip_start_frame = self.get_timecode_start_frame( + timeline_framerate, file_metadata + ) + + if "duration" in file_metadata: + frames = int(float(file_metadata["duration"]) * framerate) repre_path = Path(repre_path) first_frame = representation["context"].get("frame") if first_frame is None: - range = self.OTIO.opentime.TimeRange( + media_range = self.OTIO.opentime.TimeRange( start_time=self.OTIO.opentime.RationalTime( - timecode_start_frame, framerate + media_start_frame, framerate ), duration=self.OTIO.opentime.RationalTime(frames, framerate), ) + clip_range = self.OTIO.opentime.TimeRange( + start_time=self.OTIO.opentime.RationalTime( + clip_start_frame, timeline_framerate + ), + duration=self.OTIO.opentime.RationalTime( + frames, timeline_framerate), + ) # Use 'repre_path' as single file media_reference = self.OTIO.schema.ExternalReference( - available_range=range, - target_url=repre_path.as_uri() + available_range=media_range, target_url=repre_path.as_posix() ) else: # This is sequence @@ -293,35 +308,59 @@ class ExportOTIOOptionsDialog(QtWidgets.QDialog): frame_str = str(repre_path)[len(file_prefix):][:len(file_suffix)] frame_padding = len(frame_str) - range = self.OTIO.opentime.TimeRange( + media_range = self.OTIO.opentime.TimeRange( start_time=self.OTIO.opentime.RationalTime( - timecode_start_frame.to_frames(), float(framerate) + media_start_frame, float(framerate) ), duration=self.OTIO.opentime.RationalTime( len(repre_files), framerate), ) - + clip_range = self.OTIO.opentime.TimeRange( + start_time=self.OTIO.opentime.RationalTime( + clip_start_frame, timeline_framerate + ), + duration=self.OTIO.opentime.RationalTime( + len(repre_files), timeline_framerate + ), + ) media_reference = self.OTIO.schema.ImageSequenceReference( - available_range=range, + available_range=media_range, start_frame=int(first_frame), frame_step=1, rate=framerate, - target_url_base=f"{repre_dir.as_uri()}/", + target_url_base=f"{repre_dir.as_posix()}/", name_prefix=file_prefix, name_suffix=file_suffix, frame_zero_padding=frame_padding, ) return self.OTIO.schema.Clip( - name=name, - media_reference=media_reference, - source_range=range + name=name, media_reference=media_reference, source_range=clip_range ) + def get_timecode_start_frame(self, framerate, file_metadata): + # use otio to convert timecode into frame number + timecode_start_frame = self.OTIO.opentime.from_timecode( + file_metadata["timecode"], framerate) + return timecode_start_frame.to_frames() + def export_otio(self, clips_data, output_path): + # first find the highest framerate and set it as default framerate + # for the timeline + timeline_framerate = 0 + for clip_data in clips_data.values(): + framerate = clip_data["framerate"] + if framerate > timeline_framerate: + timeline_framerate = framerate + + timeline_framerate = round(timeline_framerate, 3) clips = [ - self.create_clip(name, clip_data) + self.create_clip(name, clip_data, timeline_framerate) for name, clip_data in clips_data.items() ] timeline = self.OTIO.schema.timeline_from_clips(clips) + # set the timeline framerate to the highest framerate + timeline.global_start_time = self.OTIO.opentime.RationalTime( + 0, timeline_framerate) + self.OTIO.adapters.write_to_file(timeline, output_path)