Skip to content

Conversation

@moonyuet
Copy link
Member

@moonyuet moonyuet commented Oct 10, 2024

Changelog Description

Implement a sequence-based workflow for publishing an editorial package in Unreal. This workflow should publish the OTIO timeline and an intermediate render in MP4 format.

Resolve #131,
Resolve #125
Resolve #124

  • otio representation implementation
  • Has unreal_export.py edited for the otio implementation
  • Edit rendering.py which can be used for both rendering and editorial intermediate rendering

Additional info

-It may need multiple testing to get the correct frame ranges
@jakubjezek001 this is the zipped file with otio and the clip:
il_UE_editorial_pkgMain_v006.zip

Testing notes:

  1. Select level sequence which includes MovieShotCutTrack/CameraCutTrack with multiple sections
  2. Create Editorial Package Instance with it
  3. Ayon -> Render
  4. Wait to render out all the sequences until finished
  5. Ayon -> Publish
  6. It should publish successfully with mp4 and otio
  7. Load otio in hiero/resolve

@moonyuet moonyuet changed the title Implementation of Editorial Publish(EDL only) Implementation of Editorial Publish Oct 11, 2024
@moonyuet moonyuet added the type: feature Adding something new and exciting to the product label Oct 15, 2024
@moonyuet moonyuet requested a review from antirotor November 4, 2024 10:57
@moonyuet moonyuet self-assigned this Nov 4, 2024
Copy link
Member

@jakubjezek001 jakubjezek001 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a call with Kayla I am adding some notes for the workflow improvements

extract_intermediate_representation

  • add settings for the plugin containing:
    • output extension - with validation for video type only restriction
    • custom ffmpeg args list
    • representation tag list
    • representation custom tags list
  • gets rendered exrs sequence
  • converts exrs to settings defined output file
  • use the output file as representation for integration
  • this representation only is used in extract_editorial_package plugin for otio ExternalRepresentations

extract_editorial_package

  • make sure all clips are having ExternalRepresentations inherited from extract_intermediate_representation with correct trim framerange
  • all media source paths should be relative to published otio file representation
  • find a way to pass sequence item's unique identifyer to otio clip metadata (this will be later used for loading and edit synchronization)

Copy link
Member

@LiborBatek LiborBatek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry but still not working when trying to load the editorial pckg into Resolve...

I will provide both otio and publish report too for inspection.

image

And publish report:
UE-Editorial-publish-report-250922-15-33.json.txt

QD_unreal_editorial_pkgMain_v006.otio.txt

Note: It could be loaded manually via native File > Import Timeline as seen on the scrncap below, also note missing burnins on the source mp4 file (Ayon didnt incorporate any)

Screenshot 2025-09-22 155659

@moonyuet moonyuet requested review from LiborBatek and jakubjezek001 and removed request for jakubjezek001 September 23, 2025 04:28
@moonyuet
Copy link
Member Author

moonyuet commented Sep 23, 2025

@jakubjezek001 can you review?
@LiborBatek can you test it again in regards to the updated commit?

@LiborBatek
Copy link
Member

@moonyuet I did re-test it but still no luck.... no burnins and cant Ayon load the editorial into Resolve...

no good news, sry

@moonyuet
Copy link
Member Author

moonyuet commented Sep 23, 2025

The updated commit includes the clips and tracks with unreal-specific metadata to identify the clips and tracks(They get different names with some version numbers)
il_UE_editorial_pkgTest_v010_otio.txt

Comment on lines +79 to +132
def _extract_intermediate(
self, instance, input_frames, render_dir, extension, ffmpeg_args):
"""Extract the intermediate representation for the instance.

Args:
instance (pyblish.api.Instance): The instance to extract.
input_frames (list): List of input frame file paths.
render_dir (str): The directory where the rendered files are located.
extension (str): The file extension for the intermediate representation.
ffpmeg_args (dict): Dictionary which stores the list of additional ffmpeg arguments.
"""
collection = pipeline.get_sequence_by_collection(input_frames)[0]
in_frame_start = min(collection.indexes)
# converting image sequence to image sequence
input_file = collection.format("{head}{padding}{tail}")
input_path = os.path.join(render_dir, input_file)
output_path = os.path.join(render_dir, f"{instance.name}.{extension}")
sequence_fps = instance.data["fps"]
input_args = ffmpeg_args.get("input", [])
default_input_args = [
"-start_number", str(in_frame_start),
"-framerate", str(sequence_fps),
]
if input_args:
default_input_args.extend(input_args)
default_input_args.extend(["-i", input_path])
all_intput_args = self._split_ffmpeg_args(default_input_args)
output_args = ffmpeg_args.get("output", [])
video_filters = ffmpeg_args.get("video_filters", [])
audio_filters = ffmpeg_args.get("audio_filters", [])

output_args = self._split_ffmpeg_args(output_args)
video_args_dentifiers = ["-vf", "-filter:v"]
audio_args_dentifiers = ["-af", "-filter:a"]
for arg in tuple(output_args):
for identifier in video_args_dentifiers:
if arg.startswith("{} ".format(identifier)):
output_args.remove(arg)
arg = arg.replace(identifier, "").strip()
video_filters.append(arg)

for identifier in audio_args_dentifiers:
if arg.startswith("{} ".format(identifier)):
output_args.remove(arg)
arg = arg.replace(identifier, "").strip()
audio_filters.append(arg)

all_args = [
subprocess.list2cmdline(get_ffmpeg_tool_args("ffmpeg"))
]
all_args.extend(all_intput_args)
if video_filters:
all_args.append("-filter:v")
all_args.append("\"{}\"".format(",".join(video_filters)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iLLiCiTiT could you have a look please?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for late response, not sure what should I be looking at?

@jakubjezek001 jakubjezek001 added the sponsored This is directly sponsored by a client or community member label Nov 7, 2025
Comment on lines 58 to 66
try:
project = get_current_project_name()
anatomy = Anatomy(project)
root = anatomy.roots['renders']
except Exception as e:
raise Exception((
"Could not find render root "
"in anatomy settings.")) from e
render_dir = f"{root}/{project}/editorial_pkg/{instance.data.get('output')}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
try:
project = get_current_project_name()
anatomy = Anatomy(project)
root = anatomy.roots['renders']
except Exception as e:
raise Exception((
"Could not find render root "
"in anatomy settings.")) from e
render_dir = f"{root}/{project}/editorial_pkg/{instance.data.get('output')}"
anatomy = instance.context.data["anatomy"]
try:
root = anatomy.roots['renders']
except Exception as e:
raise Exception((
"Could not find render root "
"in anatomy settings.")) from e
render_dir = f"{root}/{anatomy.project_name}/editorial_pkg/{instance.data.get('output')}"

Copy link
Member

@iLLiCiTiT iLLiCiTiT Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is forcing studio to have "renders" root. That probably should be settings related?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this is the current state where the renders root is really enforced. We need to change it to "normal" anatomy templates for Unreal with some sane defaults, but not in this PR. So there should be setting to select proper anatomy template.

The issue is that in Unreal, you don't want to have renders in Unreal project, you need them somewhere else.

Comment on lines 28 to 37
try:
project = get_current_project_name()
anatomy = Anatomy(project)
root = anatomy.roots['renders']
except Exception as e:
raise Exception((
"Could not find render root "
"in anatomy settings.")) from e

render_dir = f"{root}/{project}/editorial_pkg/{data.get('output')}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
try:
project = get_current_project_name()
anatomy = Anatomy(project)
root = anatomy.roots['renders']
except Exception as e:
raise Exception((
"Could not find render root "
"in anatomy settings.")) from e
render_dir = f"{root}/{project}/editorial_pkg/{data.get('output')}"
anatomy = instance.context.data["anatomy"]
try:
root = anatomy.roots['renders']
except Exception as e:
raise Exception((
"Could not find render root "
"in anatomy settings.")) from e
render_dir = f"{root}/{anatomy.project_name}/editorial_pkg/{data.get('output')}"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as above, the root should be in settings.

If the path is "hardcoded" (which seems as it is), it probably should be passed along instead of calculating it again at all the places?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sponsored This is directly sponsored by a client or community member type: feature Adding something new and exciting to the product

Projects

None yet

6 participants