Skip to content

rendering

Rendering API wrapper for Blackmagic Design DaVinci Resolve.

delete_all_processed_jobs()

Delete all processed jobs

Source code in client/ayon_resolve/api/rendering.py
196
197
198
199
200
201
202
203
204
205
def delete_all_processed_jobs():
    """Delete all processed jobs"""
    bmr_project = get_current_resolve_project()
    if not _PROCESSING_JOBS:
        return

    for job_id in _PROCESSING_JOBS:
        bmr_project.DeleteRenderJob(job_id)

    _PROCESSING_JOBS.clear()

is_rendering_in_progress()

Check if rendering is in progress

Source code in client/ayon_resolve/api/rendering.py
157
158
159
160
161
162
163
def is_rendering_in_progress():
    """Check if rendering is in progress"""
    bmr_project = get_current_resolve_project()
    if not bmr_project:
        return False

    return bmr_project.IsRenderingInProgress()

modify_preset_file(xml_path, staging_dir, data)

Copy xml_path to staging_dir and apply data overrides.

Each key in data is either a bare element tag name (e.g. "NumFramesOfHandles") or a slash-separated path expression (e.g. "Parent/Child").

  • Bare name – every matching element in the document is updated.
  • Path – the leaf element at the given path is updated; if absent it is created as the first child of its parent element.

Parameters:

Name Type Description Default
xml_path Path

Source XML preset file.

required
staging_dir Path

Directory where the modified copy is written.

required
data dict

Mapping of XML tag / path → new text value.

required

Returns:

Name Type Description
Path Path

Path to the modified copy of the preset in staging_dir.

Raises:

Type Description
AttributeError

Logged as a warning when a bare tag is not found.

Source code in client/ayon_resolve/api/rendering.py
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
def modify_preset_file(
    xml_path: Path,
    staging_dir: Path,
    data: dict,
) -> Path:
    """Copy *xml_path* to *staging_dir* and apply *data* overrides.

    Each key in *data* is either a bare element tag name (e.g.
    ``"NumFramesOfHandles"``) or a slash-separated path expression (e.g.
    ``"Parent/Child"``).

    * **Bare name** – every matching element in the document is updated.
    * **Path** – the leaf element at the given path is updated; if absent
      it is created as the first child of its parent element.

    Args:
        xml_path (Path): Source XML preset file.
        staging_dir (Path): Directory where the modified copy is written.
        data (dict): Mapping of XML tag / path → new text value.

    Returns:
        Path: Path to the modified copy of the preset in *staging_dir*.

    Raises:
        AttributeError: Logged as a warning when a bare tag is not found.
    """
    temp_path = staging_dir / xml_path.name

    # Read raw text upfront so we can extract the prolog (XML declaration +
    # any prolog-level comments).  Python's ElementTree parser drops prolog
    # comments silently; we preserve them by re-attaching the prolog to the
    # written output.
    raw_text = xml_path.read_text(encoding="utf-8")
    prolog = _extract_prolog(raw_text)

    # insert_comments=True preserves comments that live *inside* elements.
    parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True))
    tree = ET.parse(xml_path, parser=parser)

    for key, value in data.items():
        log.debug(f"Setting {key} to {value}")
        try:
            if "/" in key:
                # Normalise to a descendant XPath expression.
                xpath = key if key.startswith("./") else f".//{key}"

                *parent_parts, leaf = xpath.split("/")
                parent_path = "/".join(parent_parts)

                parent = tree.find(parent_path)
                element = parent.find(leaf)
                if element is None:
                    _append_element(parent, leaf, value)
                else:
                    log.debug(f"Setting string 1 {key} to {value}")
                    element.text = str(value)
            else:
                elements = tree.findall(f".//{key}")
                if not elements:
                    raise AttributeError(key)
                for element in elements:
                    log.debug(f"Setting string 2 {key} to {value}")
                    element.text = str(value)
        except AttributeError:
            log.warning(f"Cannot set '{key}': tag not found. Skipping.")

    # Write the modified element tree as unicode text (no xml_declaration —
    # the original prolog, which already contains the declaration and any
    # prolog-level comments, is prepended directly).
    buf = io.StringIO()
    tree.write(buf, encoding="unicode", xml_declaration=False)
    temp_path.write_text(prolog + buf.getvalue(), encoding="utf-8")

    return temp_path

render_all_timelines(target_render_directory)

Render all of the timelines of current project.

Parameters:

Name Type Description Default
target_render_directory Path

Path to target render directory

required

Returns:

Name Type Description
bool

True if all renders are successful, False otherwise

Source code in client/ayon_resolve/api/rendering.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def render_all_timelines(target_render_directory):
    """Render all of the timelines of current project.

    Args:
        target_render_directory (Path): Path to target render directory

    Returns:
        bool: True if all renders are successful, False otherwise
    """
    bmr_project = get_current_resolve_project()
    with maintain_page_by_name("Deliver"):
        timelineCount = bmr_project.GetTimelineCount()
        all_timelines = [
            bmr_project.GetTimelineByIndex(index + 1)
            for index in range(0, int(timelineCount))
        ]
        return _render_timelines(all_timelines, target_render_directory)

render_clip_to_intermediate_file(timeline_item, target_render_directory)

Render a single TimelineItem's range on the currently active timeline.

Uses the render settings already configured on the project (format, codec, preset). Caller is responsible for setting those up before calling this function (e.g. via set_render_preset_from_file and set_format_and_codec).

Parameters:

Name Type Description Default
timeline_item TimelineItem

A Resolve TimelineItem object from the active timeline.

required
target_render_directory Path

Directory where rendered files are written.

required

Returns:

Name Type Description
Path Path | list[Path]

Single file path for container formats (QuickTime, MXF, …).

Path | list[Path]

list[Path]: Sorted list of frame paths for image sequences (EXR, DPX, …).

Raises:

Type Description
RuntimeError

If the render job cannot be created, started, or completes with a non-"Complete" status, or if no output files are found.

Source code in client/ayon_resolve/api/rendering.py
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
def render_clip_to_intermediate_file(
    timeline_item: resolve.TimelineItem,
    target_render_directory: Path
) -> Path | list[Path]:
    """Render a single TimelineItem's range on the currently active timeline.

    Uses the render settings already configured on the project (format, codec,
    preset). Caller is responsible for setting those up before calling this
    function (e.g. via ``set_render_preset_from_file`` and
    ``set_format_and_codec``).

    Args:
        timeline_item: A Resolve ``TimelineItem`` object from the active timeline.
        target_render_directory (Path): Directory where rendered files are written.

    Returns:
        Path: Single file path for container formats (QuickTime, MXF, …).
        list[Path]: Sorted list of frame paths for image sequences (EXR, DPX, …).

    Raises:
        RuntimeError: If the render job cannot be created, started, or completes
            with a non-"Complete" status, or if no output files are found.
    """
    bmr_project = get_current_resolve_project()
    media_pool_item = timeline_item.GetMediaPoolItem()

    render_settings = {
        "SelectAllFrames": False,
        "MarkIn":    timeline_item.GetStart(),
        "MarkOut":   timeline_item.GetEnd() - 1,
        "TargetDir": target_render_directory.as_posix(),
        "CustomName": timeline_item.GetName(),
        "FrameRate": float(media_pool_item.GetClipProperty("FPS")),
    }
    log.info(f"Clip render settings: {pformat(render_settings)}")

    if not bmr_project.SetRenderSettings(render_settings):
        raise RuntimeError("SetRenderSettings failed for clip render.")

    with _solo_video_track(timeline_item):
        job_id = bmr_project.AddRenderJob()
        if not job_id:
            raise RuntimeError("AddRenderJob failed for clip render.")

        log.info(f"Clip render job created: {job_id}")
        try:
            if not bmr_project.StartRendering([job_id], isInteractiveMode=False):
                raise RuntimeError(f"StartRendering failed for job '{job_id}'.")
            wait_for_rendering_completion()

            status = bmr_project.GetRenderJobStatus(job_id)
            if status.get("JobStatus") != "Complete":
                raise RuntimeError(
                    f"Clip render job '{job_id}' did not complete: {status}"
                )
        finally:
            log.info(f"Deleting clip render job: {job_id}")
            bmr_project.DeleteRenderJob(job_id)

    rendered = _collect_rendered_files(target_render_directory)
    if not rendered:
        msg = f"No rendered files found in '{target_render_directory}'."
        raise RuntimeError(msg)

    if rendered[0].suffix.lstrip(".").lower() in _IMAGE_SEQUENCE_EXTS:
        log.info("Clip rendered as image sequence: %d frames", len(rendered))
        return rendered  # list[Path]

    log.info("Clip rendered as single file: %s", rendered[0])
    return rendered[0]  # Path

render_single_timeline(timeline, target_render_directory)

Render single timeline

Process is taking a defined timeline and render it to temporary intermediate file which will be lately used by Extract Review plugin for conversion to review file.

Parameters:

Name Type Description Default
timeline Timeline

Timeline object

required
target_render_directory Path

Path to target render directory

required

Returns:

Name Type Description
bool

True if rendering is successful, False otherwise

Source code in client/ayon_resolve/api/rendering.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def render_single_timeline(timeline, target_render_directory):
    """Render single timeline

    Process is taking a defined timeline and render it to temporary
    intermediate file which will be lately used by Extract Review plugin
    for conversion to review file.

    Args:
        timeline (resolve.Timeline): Timeline object
        target_render_directory (Path): Path to target render directory

    Returns:
        bool: True if rendering is successful, False otherwise
    """
    return _render_timelines([timeline], target_render_directory)

render_timeline_intermediate_file(timeline, target_render_directory, preset_path, file_format, codec)

Render timeline to an intermediate file in target_render_directory.

Parameters:

Name Type Description Default
timeline Timeline

Active Resolve Timeline object.

required
target_render_directory Path

Staging directory for the output.

required
preset_path Path

Path to the render preset XML file.

required
file_format str

Resolve format name (e.g. "QuickTime").

required
codec str

Resolve codec name (e.g. "H.264").

required

Returns:

Name Type Description
Path Path | list[Path]

Path to the rendered file.

Path | list[Path]

list[Path]: List of paths to the rendered files.

Raises:

Type Description
RuntimeError

If the render preset cannot be loaded, the format and codec cannot be set, or the timeline cannot be rendered.

Source code in client/ayon_resolve/api/rendering.py
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
def render_timeline_intermediate_file(
    timeline: resolve.Timeline,
    target_render_directory: Path,
    preset_path: Path,
    file_format: str,
    codec: str,
) -> Path | list[Path]:
    """Render *timeline* to an intermediate file in *target_render_directory*.

    Args:
        timeline: Active Resolve Timeline object.
        target_render_directory (Path): Staging directory for the output.
        preset_path (Path): Path to the render preset XML file.
        file_format (str): Resolve format name (e.g. ``"QuickTime"``).
        codec (str): Resolve codec name (e.g. ``"H.264"``).

    Returns:
        Path: Path to the rendered file.
        list[Path]: List of paths to the rendered files.

    Raises:
        RuntimeError: If the render preset cannot be loaded, the format and codec
            cannot be set, or the timeline cannot be rendered.
    """
    log.info(f"Rendering timeline to '{target_render_directory}'")

    with maintain_page_by_name("Deliver"):
        if not set_render_preset_from_file(preset_path.as_posix()):
            raise RuntimeError("Unable to load render preset.")

        format_extension = set_format_and_codec(file_format, codec)
        if not format_extension:
            raise RuntimeError("Unable to set render format and codec.")

        if not render_single_timeline(timeline, target_render_directory):
            raise RuntimeError("Unable to render timeline.")

    rendered_files = _collect_rendered_files(
        target_render_directory, f"*.{format_extension}"
    )
    if not rendered_files:
        msg = (
            f"No rendered files with extension '{format_extension}' found "
            f"in '{target_render_directory}'."
        )
        raise RuntimeError(msg)

    if len(rendered_files) > 1:
        return rendered_files

    return rendered_files[0]

wait_for_rendering_completion()

Wait for rendering completion

Source code in client/ayon_resolve/api/rendering.py
166
167
168
169
170
def wait_for_rendering_completion():
    """Wait for rendering completion"""
    while is_rendering_in_progress():
        time.sleep(_SLEEP_TIME)
    return