Skip to content

publish_plugins

CollectUSDAssetLayerContributionsProfileModel

Bases: BaseSettingsModel

Define additional contributions to the USD Asset or Shot.

Source code in server/settings/publish_plugins.py
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
class CollectUSDAssetLayerContributionsProfileModel(BaseSettingsModel):
    """Define additional contributions to the USD Asset or Shot."""
    _layout = "expanded"
    task_types: list[str] = SettingsField(
        default_factory=list,
        title="Task Types",
        enum_resolver=task_types_enum,
        description=(
            "The current create context task type to filter against. This"
            " allows to filter the profile to only be valid if currently "
            " creating from within that task type."
        ),
    )
    product_names: list[str] = SettingsField(
        default_factory=list,
        title="Product names",
    )
    contributions: list[ContributionSearchLayersModel] = SettingsField(
        default_factory=list,
        title="Contributions",
    )

    @validator("contributions")
    def validate_unique_outputs(cls, value):
        ensure_unique_names(value)
        return value

CollectUSDLayerContributionsProfileModel

Bases: BaseSettingsModel

Profile to define USD contribution attribute defaults.

Source code in server/settings/publish_plugins.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
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
class CollectUSDLayerContributionsProfileModel(BaseSettingsModel):
    """Profile to define USD contribution attribute defaults."""
    _layout = "expanded"
    product_base_types: list[str] = SettingsField(
        default_factory=list,
        title="Product base types",
        description=(
            "The product base types to match this profile to. When matched,"
            " the settings below would apply to the instance as default"
            " attributes."
        ),
        section="Filter"
    )
    task_types: list[str] = SettingsField(
        default_factory=list,
        title="Task Types",
        enum_resolver=task_types_enum,
        description=(
            "The current create context task type to filter against. This"
            " allows to filter the profile to only be valid if currently "
            " creating from within that task type."
        ),
    )
    task_names: list[str] = SettingsField(
        default_factory=list,
        title="Task names",
    )
    contribution_enabled: bool = SettingsField(
        True,
        title="Contribution Enabled (default)",
        description=(
            "The default state for USD Contribution being marked enabled or"
            " disabled for this profile."
        ),
        section="Instance attribute defaults",
    )
    contribution_layer: str = SettingsField(
        "",
        title="Contribution Department Layer",
        description=(
            "The default contribution layer to apply the contribution to when"
            " matching this profile. The layer name should be in the"
            " 'Department Layer Orders' list to get a sensible order."
        ),
    )
    contribution_target_product: str = SettingsField(
        "usdAsset",
        title="Target Product",
        description=(
            "The default destination product name to apply the contribution to"
            " when matching this profile."
            " Usually e.g. 'usdAsset' or 'usdShot'."
        ),
    )
    contribution_apply_as_variant: bool = SettingsField(
        True,
        title="Apply as variant",
        description=(
            "The default 'Apply as variant' state for instances matching this"
            " profile. Usually enabled for asset contributions and disabled"
            " for shot contributions."
        ),
    )
    contribution_variant_set_name: str = SettingsField(
        "{layer}",
        title="Variant Set name",
        description=(
            "The default variant set name for instances matching this profile."
        ),
    )
    contribution_variant: str = SettingsField(
        "{variant}",
        title="Variant Name",
        description=(
            "The default variant name for instances matching this profile."
        ),
    )
    contribution_variant_default_policy: str = SettingsField(
        "if_not_set",
        title="Set as default variant selection",
        enum_resolver=contribution_variant_default_policy_enum,
        description=(
            "Controls whether this contribution's variant name is authored "
            "as the selected default for the variant set. Use "
            f"'{CONTRIBUTION_VARIANT_DEFAULT_POLICY['never']}' to leave the "
            "variant selection unchanged, "
            f"'{CONTRIBUTION_VARIANT_DEFAULT_POLICY['if_not_set']}' to set "
            "it only when no selection exists, or "
            f"'{CONTRIBUTION_VARIANT_DEFAULT_POLICY['always']}' to always "
            "override the current selection."
        ),
    )

ContributionSearchLayersModel

Bases: BaseSettingsModel

Additional contribution to add into the USD Asset or Shot.

Source code in server/settings/publish_plugins.py
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
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
class ContributionSearchLayersModel(BaseSettingsModel):
    """Additional contribution to add into the USD Asset or Shot."""
    _layout = "expanded"
    name: str = SettingsField(
        "",
        title="Layer Identifier",
        description=(
            "Specific layer identifier to search for in the Sdf layer. If it"
            " is already found the original layer identifier will be swapped"
            " with this newer, so you can contribute updates to the same"
            " product."
        ),
    )
    order: int = SettingsField(
        0,
        title="Order",
        description=(
            "Strength of the contribution among other contributions. "
            "Higher order means a higher strength and stacks the layer on top."
        ),
    )
    type: str = SettingsField(
        "sublayer",
        title="Load",
        enum_resolver=usd_contribution_layering_types_enum_resolver,
        conditional_enum=True,
        description=(
            "How to add the contribution: as a sublayer, as a reference on a"
            " prim or as a reference inside a variant on a prim."
        ),
    )

    # Load type specific-settings
    reference: ReferenceContributionModel = SettingsField(
        default_factory=ReferenceContributionModel,
        title="Reference Contribution"
    )
    variant: VariantContributionModel = SettingsField(
        default_factory=VariantContributionModel,
        title="Variant Contribution",
    )

    only_if_existing: bool = SettingsField(
        default=True,
        title="Add only if version exists",
        description=(
            "Add the layer only if the given representation filepath exists."
        )
    )

    # Now we need to define what file or path the user should be loading.
    # These can be AYON Entity URIs or a 'searched product' at a specific
    # folder path or parent folder path
    load_from: str = SettingsField(
        "source_path",
        title="Layer method",
        enum_resolver=usd_contribution_load_from_enum_resolver,
        conditional_enum=True,
        description=(
            "How to define the layer to contribute: an explicit source path"
            " or a product found in a parent folder of a given folder type."
        ),
    )

    source_path: str = SettingsField(
        "",
        title="Source Path",
        description=(
            "Layer path. This can be an AYON entity URI or a relative file"
            " path. It will be taken 1:1 into the result so use with caution."
        ),
    )
    search_product: SearchParentProductModel = SettingsField(
        default_factory=SearchParentProductModel,
        title="Find product in first parent folder of a given folder type",
    )

EntityListFolderModel

Bases: BaseSettingsModel

Folder must have label and can be scoped to views.

Scope of the folder can be defined for all views or use just the view matching list type of created list. In case the list folder already exists the settings are not used and we just make sure the list can be seen under the folder.

Source code in server/settings/publish_plugins.py
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
class EntityListFolderModel(BaseSettingsModel):
    """Folder must have label and can be scoped to views.

    Scope of the folder can be defined for all views or use just the view
        matching list type of created list. In case the list folder already
        exists the settings are not used and we just make sure the list can
        be seen under the folder.

    """
    _layout = "expanded"
    label: str = SettingsField(
        "",
        title="Folder label",
        description=(
            "The label of the folder to create. "
            "Anatomy formattable template for the name."
        ),
    )
    # Don't use explicit scope enum, rather ask if the folder should be seen
    #   everywhere or just in the list type matching created list.
    scope_def: str = SettingsField(
        "all",
        enum_resolver=list_folder_scope_def,
        title="Scope",
    )

ExtractBurninDef

Bases: BaseSettingsModel

Source code in server/settings/publish_plugins.py
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
class ExtractBurninDef(BaseSettingsModel):
    _isGroup = True
    _layout = "expanded"
    name: str = SettingsField(
        "",
        title="Name",
        description=(
            "Unique name of the burnin. Review outputs in *Extract Review*"
            " can link to it by this name."
        ),
    )
    TOP_LEFT: str = SettingsField(
        "",
        title="Top Left",
        description=(
            "Text to burn in at this position. Supports template keys, e.g."
            " `{folder[name]}`, `{task[name]}`, `{anatomy[version]}`,"
            " `{username}`, `{comment}`, `{yy}-{mm}-{dd}`, `{frame_start}`,"
            " `{frame_end}`, `{current_frame}` and `{timecode}`."
        ),
    )
    TOP_CENTERED: str = SettingsField("", title="Top Centered")
    TOP_RIGHT: str = SettingsField("", title="Top Right")
    BOTTOM_LEFT: str = SettingsField("", title="Bottom Left")
    BOTTOM_CENTERED: str = SettingsField("", title="Bottom Centered")
    BOTTOM_RIGHT: str = SettingsField("", title="Bottom Right")
    filter: ExtractBurninDefFilter = SettingsField(
        default_factory=ExtractBurninDefFilter,
        title="Additional filtering"
    )

    @validator("name")
    def validate_name(cls, value):
        """Ensure name does not contain weird characters"""
        return normalize_name(value)

validate_name(value)

Ensure name does not contain weird characters

Source code in server/settings/publish_plugins.py
1636
1637
1638
1639
@validator("name")
def validate_name(cls, value):
    """Ensure name does not contain weird characters"""
    return normalize_name(value)

ExtractOIIOPostProcessModel

Bases: BaseSettingsModel

Process representation images with oiiotool on publish.

This could be used to convert images to different formats, convert to scanline images or flatten deep images.

Source code in server/settings/publish_plugins.py
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
class ExtractOIIOPostProcessModel(BaseSettingsModel):
    """Process representation images with `oiiotool` on publish.

    This could be used to convert images to different formats, convert to
    scanline images or flatten deep images.
    """
    enabled: bool = SettingsField(True)
    profiles: list[ExtractOIIOPostProcessProfileModel] = SettingsField(
        default_factory=list, title="Profiles"
    )

ExtractOIIOTranscodeModel

Bases: BaseSettingsModel

Transcode images to another colorspace using OIIO.

Mostly aimed at transcoding for reviewables (it'll process and output only RGBA channels).

Source code in server/settings/publish_plugins.py
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
class ExtractOIIOTranscodeModel(BaseSettingsModel):
    """Transcode images to another colorspace using OIIO.

    Mostly aimed at transcoding for reviewables (it'll process and output
    only RGBA channels).
    """
    enabled: bool = SettingsField(True)
    profiles: list[ExtractOIIOTranscodeProfileModel] = SettingsField(
        default_factory=list, title="Profiles"
    )

ExtractOTIOReviewModel

Bases: BaseSettingsModel

Extract review representation from OTIO based input products.

Source code in server/settings/publish_plugins.py
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
class ExtractOTIOReviewModel(BaseSettingsModel):
    """Extract review representation from OTIO based input products.
    """
    representation_name: str = SettingsField(
        "",
        title="Representation name",
        description=(
            "The name of the representation generated by the OTIO extractor. "
            "(Defaults to the Output Extension.)"
        )
    )
    output_ext: str = SettingsField(
        "png",
        title="Output extension",
        # TODO: need to work-out the plugin logic
        # before we allow more than 'png' extension.
        disabled=True,
    )
    default_width: int = SettingsField(
        1280,
        ge=0,
        le=100000,
        title="Default width"
    )
    default_height: int = SettingsField(
        720,
        ge=0,
        le=100000,
        title="Default height"
    )

ExtractReviewOutputDefModel

Bases: BaseSettingsModel

Source code in server/settings/publish_plugins.py
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
class ExtractReviewOutputDefModel(BaseSettingsModel):
    _layout = "expanded"
    name: str = SettingsField(
        "",
        title="Name",
        description=(
            "Unique name of the output, used in the representation name,"
            " e.g. `h264`."
        ),
    )
    ext: str = SettingsField(
        "",
        title="Output extension",
        description="File extension of the output, e.g. `mp4` or `png`.",
    )
    # TODO use some different source of tags
    tags: list[str] = SettingsField(
        default_factory=list,
        title="Tags",
        description=(
            "Tags added to the output representation. Common tags:\n"
            "- `webreview`: upload as reviewable to AYON\n"
            "- `burnin`: add burnins using *Extract Burnin*\n"
            "- `delete`: do not keep the file after publishing\n"
            "- `no-handles`: exclude handles from the output\n"
            "- `ftrackreview` / `kitsureview`: send to the tracker"
        ),
    )
    burnins: list[str] = SettingsField(
        default_factory=list,
        title="Link to a burnin by name",
        description=(
            "Names of burnin definitions from *Extract Burnin* to use for"
            " this output. Empty means all matching burnins are used."
        ),
    )
    ffmpeg_args: ExtractReviewFFmpegModel = SettingsField(
        default_factory=ExtractReviewFFmpegModel,
        title="FFmpeg arguments"
    )
    filter: ExtractReviewFilterModel = SettingsField(
        default_factory=ExtractReviewFilterModel,
        title="Additional output filtering"
    )
    overscan_crop: str = SettingsField(
        "",
        title="Overscan crop",
        description=(
            "Crop input overscan. See the documentation for more information."
        )
    )
    overscan_color: ColorRGBA_uint8 = SettingsField(
        (0, 0, 0, 0.0),
        title="Overscan color",
        description=(
            "Overscan color is used when input aspect ratio is not"
            " same as output aspect ratio."
        )
    )
    # overscan_color: ColorRGB_uint8 = SettingsField(
    #     (0, 0, 0),
    #     title="Overscan color",
    #     description=(
    #         "Overscan color is used when input aspect ratio is not"
    #         " same as output aspect ratio."
    #     )
    # )
    width: int = SettingsField(
        0,
        ge=0,
        le=100000,
        title="Output width",
        description=(
            "Width and Height must be both set to higher"
            " value than 0 else source resolution is used."
        )
    )
    height: int = SettingsField(
        0,
        title="Output height",
        ge=0,
        le=100000,
    )
    scale_pixel_aspect: bool = SettingsField(
        True,
        title="Scale pixel aspect",
        description=(
            "Rescale input when it's pixel aspect ratio is not 1."
            " Useful for anamorphic reviews."
        )
    )
    bg_color: ColorRGBA_uint8 = SettingsField(
        (0, 0, 0, 0.0),
        description=(
            "Background color is used only when input have transparency"
            " and Alpha is higher than 0."
        ),
        title="Background color",
    )
    letter_box: ExtractReviewLetterBox = SettingsField(
        default_factory=ExtractReviewLetterBox,
        title="Letter Box"
    )
    fill_missing_frames: str = SettingsField(
        title="Handle missing frames",
        default="closest_existing",
        description="How to handle gaps in sequence frame ranges.",
        enum_resolver=_handle_missing_frames_enum
    )

    @validator("name")
    def validate_name(cls, value):
        """Ensure name does not contain weird characters"""
        return normalize_name(value)

validate_name(value)

Ensure name does not contain weird characters

Source code in server/settings/publish_plugins.py
1502
1503
1504
1505
@validator("name")
def validate_name(cls, value):
    """Ensure name does not contain weird characters"""
    return normalize_name(value)

ExtractThumbnailFromSourceModel

Bases: BaseSettingsModel

Thumbnail extraction from source files using ffmpeg and oiiotool.

Source code in server/settings/publish_plugins.py
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
class ExtractThumbnailFromSourceModel(BaseSettingsModel):
    """Thumbnail extraction from source files using ffmpeg and oiiotool."""
    enabled: bool = SettingsField(True)

    target_size: ResizeModel = SettingsField(
        default_factory=ResizeModel, title="Target size"
    )
    background_color: ColorRGBA_uint8 = SettingsField(
        (0, 0, 0, 0.0), title="Background color"
    )

IntegrateProductGroupModel

Bases: BaseSettingsModel

Group published products by filtering logic.

Set all published instances as a part of specific group named according to 'Template'.

Implemented all variants of placeholders '{task}', '{product[type]}', '{host}', '{product[name]}', '{renderlayer}'.

Source code in server/settings/publish_plugins.py
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
class IntegrateProductGroupModel(BaseSettingsModel):
    """Group published products by filtering logic.

    Set all published instances as a part of specific group named according
     to 'Template'.

    Implemented all variants of placeholders '{task}', '{product[type]}',
    '{host}', '{product[name]}', '{renderlayer}'.
    """

    _isGroup = True
    product_grouping_profiles: list[IntegrateProductGroupProfile] = (
        SettingsField(
            default_factory=list,
            title="Product group profiles"
        )
    )

PreIntegrateThumbnailsModel

Bases: BaseSettingsModel

Explicitly set if Thumbnail representation should be integrated.

If no matching profile set, existing state from Host implementation is kept.

Source code in server/settings/publish_plugins.py
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
class PreIntegrateThumbnailsModel(BaseSettingsModel):
    """Explicitly set if Thumbnail representation should be integrated.

    If no matching profile set, existing state from Host implementation
    is kept.
    """

    _isGroup = True
    enabled: bool = SettingsField(True)
    integrate_profiles: list[PreIntegrateThumbnailsProfile] = SettingsField(
        default_factory=list,
        title="Integrate profiles"
    )

ValidateIntentModel

Bases: BaseSettingsModel

Validate if Publishing intent was selected.

It is possible to disable validation for specific publishing context with profiles.

Source code in server/settings/publish_plugins.py
809
810
811
812
813
814
815
816
817
818
class ValidateIntentModel(BaseSettingsModel):
    """Validate if Publishing intent was selected.

    It is possible to disable validation for specific publishing context
    with profiles.
    """

    _isGroup = True
    enabled: bool = SettingsField(False)
    profiles: list[ValidateIntentProfile] = SettingsField(default_factory=list)

contribution_variant_default_policy_enum()

Return the available variant default policies for the settings UI.

Source code in server/settings/publish_plugins.py
37
38
39
40
41
42
def contribution_variant_default_policy_enum():
    """Return the available variant default policies for the settings UI."""
    return [
        {"value": value, "label": label}
        for value, label in CONTRIBUTION_VARIANT_DEFAULT_POLICY.items()
    ]

ensure_unique_resolution_option(objects, field_name=None)

Ensure a list of objects have unique option attributes.

This function checks if the list of objects has unique 'width', 'height' and 'pixel_aspect' properties.

Source code in server/settings/publish_plugins.py
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
def ensure_unique_resolution_option(
        objects: list[Any], field_name: str | None = None) -> None:  # noqa: C901
    """Ensure a list of objects have unique option attributes.

    This function checks if the list of objects has unique 'width',
    'height' and 'pixel_aspect' properties.
    """
    options = set()
    for obj in objects:
        item_test_text = f"{obj.width}x{obj.height}x{obj.pixel_aspect}"
        if item_test_text in options:
            raise BadRequestException(
                f"Duplicate option '{item_test_text}'")

        options.add(item_test_text)