aswf-aces-common 0.1.0rc1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: aswf-aces-common
3
+ Version: 0.1.0rc1
4
+ Summary: ACES shared protocols and data types
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+
9
+ # aswf-aces-common
10
+
11
+ Shared protocols and types used across the ACES AMF Python packages.
12
+
13
+ This is a foundation package. Most users should install [aswf-aces-amf-utils](../aces-amf-utils/) or [aswf-aces-amf-lib](../aces-amf-lib/) instead.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install aswf-aces-common
19
+ ```
20
+
21
+ ## Exports
22
+
23
+ ### `TransformRegistry` (Protocol)
24
+
25
+ Interface for transform ID registries. Implemented by `ACESTransformRegistry` in [aswf-aces-transforms](../aces-transforms/).
26
+
27
+ ```python
28
+ from aswf.aces.common import TransformRegistry
29
+
30
+ class MyRegistry:
31
+ def is_valid_transform_id(self, transform_id: str, *, version: str | None = None) -> bool: ...
32
+ def get_transform_info(self, transform_id: str, *, version: str | None = None) -> dict | None: ...
33
+ def list_transforms(self, *, category: str | None = None, version: str | None = None) -> list[dict]: ...
34
+ def list_versions(self) -> list[str]: ...
35
+ def get_equivalent_id(self, transform_id: str) -> str | None: ...
36
+ def get_equivalent_ids(self, transform_id: str) -> list[str]: ...
37
+ ```
38
+
39
+ ### `TransformURN` (Dataclass)
40
+
41
+ Parses ACES transform URNs into their component fields per the [Transform ID specification](https://docs.acescentral.com/system-components/transform-id-spec/).
42
+
43
+ ```
44
+ urn:ampas:aces:transformId:v{specVersion}:{TransformType}.{Namespace}.{Name}.a{ACESRelease}.v{TransformVersion}
45
+ ```
46
+
47
+ ```python
48
+ from aswf.aces.common import TransformURN
49
+
50
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1")
51
+
52
+ urn.raw # "urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1"
53
+ urn.spec_version # "v2.0"
54
+ urn.spec_major_version # 2
55
+ urn.spec_minor_version # 0
56
+ urn.transform_type # "CSC"
57
+ urn.namespace # "Academy"
58
+ urn.name # "ACES_to_ACEScct"
59
+ urn.version_suffix # "a2.v1"
60
+ urn.aces_release_version # 2
61
+ urn.transform_version # "v1"
62
+ urn.is_v1 # False
63
+ urn.is_v2 # True
64
+ ```
65
+
66
+ The version suffix format varies between ACES versions:
67
+
68
+ | ACES version | Suffix format | Example | `aces_release_version` | `transform_version` |
69
+ |---|---|---|---|---|
70
+ | v2.0 | `a{N}.v{N}` | `a2.v1` | `2` | `"v1"` |
71
+ | v1.x (semver) | `a{N}.{N}.{N}` | `a1.0.3` | `1` | `"0.3"` |
72
+ | v1.x (mixed) | `a{N}.v{N}.{N}` | `a1.v1.0` | `1` | `"v1.0"` |
73
+
74
+ `TransformURN.parse()` returns `None` for strings that are not valid ACES transform URNs (e.g. legacy short names like `ACEScsc.ACES_to_ACEScc.a1.0.3`).
75
+
76
+ #### Edge cases
77
+
78
+ Internal library and utility transforms (`ACESlib`, `ACESutil`) do not include a separate namespace field — their format is `{Type}.{Name}.{VersionSuffix}` rather than `{Type}.{Namespace}.{Name}.{VersionSuffix}`. For these transforms, the `name` field will contain the version suffix and `version_suffix` will be empty, so `aces_release_version` and `transform_version` will return `None`. These transforms are not typically referenced in AMF files.
79
+
80
+ ```python
81
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v1.5:ACESlib.Utilities.a1.0.3")
82
+ urn.transform_type # "ACESlib"
83
+ urn.namespace # "Utilities" (actually the name, not a namespace)
84
+ urn.name # "a1.0.3" (actually the version suffix)
85
+ urn.version_suffix # ""
86
+ urn.aces_release_version # None
87
+ urn.transform_version # None
88
+ ```
89
+
90
+ ### `TransformInfo` (Dataclass)
91
+
92
+ Metadata for a single ACES transform:
93
+
94
+ ```python
95
+ from aswf.aces.common import TransformInfo
96
+
97
+ # Fields: transform_id, user_name, transform_type, aces_version,
98
+ # inverse_transform_id, previous_equivalent_ids
99
+ ```
100
+
101
+ ### Constants
102
+
103
+ ```python
104
+ from aswf.aces.common import INPUT_TRANSFORM_TYPES, OUTPUT_TRANSFORM_TYPES
105
+
106
+ INPUT_TRANSFORM_TYPES # frozenset{"IDT", "CSC", "Input"}
107
+ OUTPUT_TRANSFORM_TYPES # frozenset{"ODT", "Output"}
108
+ ```
109
+
110
+ ## License
111
+
112
+ Apache-2.0
@@ -0,0 +1,104 @@
1
+ # aswf-aces-common
2
+
3
+ Shared protocols and types used across the ACES AMF Python packages.
4
+
5
+ This is a foundation package. Most users should install [aswf-aces-amf-utils](../aces-amf-utils/) or [aswf-aces-amf-lib](../aces-amf-lib/) instead.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install aswf-aces-common
11
+ ```
12
+
13
+ ## Exports
14
+
15
+ ### `TransformRegistry` (Protocol)
16
+
17
+ Interface for transform ID registries. Implemented by `ACESTransformRegistry` in [aswf-aces-transforms](../aces-transforms/).
18
+
19
+ ```python
20
+ from aswf.aces.common import TransformRegistry
21
+
22
+ class MyRegistry:
23
+ def is_valid_transform_id(self, transform_id: str, *, version: str | None = None) -> bool: ...
24
+ def get_transform_info(self, transform_id: str, *, version: str | None = None) -> dict | None: ...
25
+ def list_transforms(self, *, category: str | None = None, version: str | None = None) -> list[dict]: ...
26
+ def list_versions(self) -> list[str]: ...
27
+ def get_equivalent_id(self, transform_id: str) -> str | None: ...
28
+ def get_equivalent_ids(self, transform_id: str) -> list[str]: ...
29
+ ```
30
+
31
+ ### `TransformURN` (Dataclass)
32
+
33
+ Parses ACES transform URNs into their component fields per the [Transform ID specification](https://docs.acescentral.com/system-components/transform-id-spec/).
34
+
35
+ ```
36
+ urn:ampas:aces:transformId:v{specVersion}:{TransformType}.{Namespace}.{Name}.a{ACESRelease}.v{TransformVersion}
37
+ ```
38
+
39
+ ```python
40
+ from aswf.aces.common import TransformURN
41
+
42
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1")
43
+
44
+ urn.raw # "urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1"
45
+ urn.spec_version # "v2.0"
46
+ urn.spec_major_version # 2
47
+ urn.spec_minor_version # 0
48
+ urn.transform_type # "CSC"
49
+ urn.namespace # "Academy"
50
+ urn.name # "ACES_to_ACEScct"
51
+ urn.version_suffix # "a2.v1"
52
+ urn.aces_release_version # 2
53
+ urn.transform_version # "v1"
54
+ urn.is_v1 # False
55
+ urn.is_v2 # True
56
+ ```
57
+
58
+ The version suffix format varies between ACES versions:
59
+
60
+ | ACES version | Suffix format | Example | `aces_release_version` | `transform_version` |
61
+ |---|---|---|---|---|
62
+ | v2.0 | `a{N}.v{N}` | `a2.v1` | `2` | `"v1"` |
63
+ | v1.x (semver) | `a{N}.{N}.{N}` | `a1.0.3` | `1` | `"0.3"` |
64
+ | v1.x (mixed) | `a{N}.v{N}.{N}` | `a1.v1.0` | `1` | `"v1.0"` |
65
+
66
+ `TransformURN.parse()` returns `None` for strings that are not valid ACES transform URNs (e.g. legacy short names like `ACEScsc.ACES_to_ACEScc.a1.0.3`).
67
+
68
+ #### Edge cases
69
+
70
+ Internal library and utility transforms (`ACESlib`, `ACESutil`) do not include a separate namespace field — their format is `{Type}.{Name}.{VersionSuffix}` rather than `{Type}.{Namespace}.{Name}.{VersionSuffix}`. For these transforms, the `name` field will contain the version suffix and `version_suffix` will be empty, so `aces_release_version` and `transform_version` will return `None`. These transforms are not typically referenced in AMF files.
71
+
72
+ ```python
73
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v1.5:ACESlib.Utilities.a1.0.3")
74
+ urn.transform_type # "ACESlib"
75
+ urn.namespace # "Utilities" (actually the name, not a namespace)
76
+ urn.name # "a1.0.3" (actually the version suffix)
77
+ urn.version_suffix # ""
78
+ urn.aces_release_version # None
79
+ urn.transform_version # None
80
+ ```
81
+
82
+ ### `TransformInfo` (Dataclass)
83
+
84
+ Metadata for a single ACES transform:
85
+
86
+ ```python
87
+ from aswf.aces.common import TransformInfo
88
+
89
+ # Fields: transform_id, user_name, transform_type, aces_version,
90
+ # inverse_transform_id, previous_equivalent_ids
91
+ ```
92
+
93
+ ### Constants
94
+
95
+ ```python
96
+ from aswf.aces.common import INPUT_TRANSFORM_TYPES, OUTPUT_TRANSFORM_TYPES
97
+
98
+ INPUT_TRANSFORM_TYPES # frozenset{"IDT", "CSC", "Input"}
99
+ OUTPUT_TRANSFORM_TYPES # frozenset{"ODT", "Output"}
100
+ ```
101
+
102
+ ## License
103
+
104
+ Apache-2.0
@@ -0,0 +1,25 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ [build-system]
3
+ requires = ["setuptools>=61.0", "wheel"]
4
+ build-backend = "setuptools.build_meta"
5
+
6
+ [project]
7
+ name = "aswf-aces-common"
8
+ version = "0.1.0rc1"
9
+ description = "ACES shared protocols and data types"
10
+ readme = "README.md"
11
+ license = "Apache-2.0"
12
+ requires-python = ">=3.10"
13
+ dependencies = []
14
+
15
+ [tool.setuptools.packages.find]
16
+ where = ["src"]
17
+ include = ["aswf.aces.common*"]
18
+
19
+ [[tool.uv.index]]
20
+ name = "pypi"
21
+ url = "https://pypi.org/simple"
22
+ default = true
23
+
24
+ [tool.pytest.ini_options]
25
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,9 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright Contributors to the ACES Project.
3
+ """ACES shared protocols and data types."""
4
+
5
+ from aswf.aces.common.constants import INPUT_TRANSFORM_TYPES, OUTPUT_TRANSFORM_TYPES
6
+ from aswf.aces.common.protocols import TransformRegistry
7
+ from aswf.aces.common.types import TransformInfo, TransformURN
8
+
9
+ __all__ = ["INPUT_TRANSFORM_TYPES", "OUTPUT_TRANSFORM_TYPES", "TransformRegistry", "TransformInfo", "TransformURN"]
@@ -0,0 +1,112 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright Contributors to the ACES Project.
3
+ """
4
+ Constants derived from the AMF XSD schemas.
5
+
6
+ Transform type prefix constants map to XSD simpleType definitions that
7
+ restrict which transform ID URN prefixes are valid per container type.
8
+ The v1 and v2 schemas define different allowed prefixes.
9
+ """
10
+
11
+ # Transform types considered as input transforms (used by bulk_generator)
12
+ INPUT_TRANSFORM_TYPES: frozenset[str] = frozenset({"IDT", "CSC", "Input"})
13
+
14
+ # Transform types considered as output transforms (used by bulk_generator)
15
+ OUTPUT_TRANSFORM_TYPES: frozenset[str] = frozenset({"ODT", "Output"})
16
+
17
+ # Transform types considered as look transforms
18
+ LOOK_TRANSFORM_TYPES: frozenset[str] = frozenset({"LMT", "Look"})
19
+
20
+
21
+ # ──────────────────────────────────────────────────────────────────────
22
+ # AMF v2 XSD — allowed transform ID URN prefixes (v1.5 + v2.0 URNs)
23
+ # Source: packages/aces-amf-lib/src/aswf/aces/amf_lib/data/amf-schema/v2/acesMetadataFile.xsd
24
+ # ──────────────────────────────────────────────────────────────────────
25
+
26
+ # tnInputTransform (v2 XSD line 71): IDT, ACEScsc (v1.5) | Input, CSC (v2.0)
27
+ V2_INPUT_TRANSFORM_PREFIXES: frozenset[str] = frozenset({"IDT", "ACEScsc", "Input", "CSC"})
28
+ # tnOutputTransform (v2 XSD line 78): RRTODT (v1.5) | Output (v2.0)
29
+ V2_OUTPUT_TRANSFORM_PREFIXES: frozenset[str] = frozenset({"RRTODT", "Output"})
30
+ # tnLookTransform (v2 XSD line 64): LMT (v1.5) | Look (v2.0)
31
+ V2_LOOK_TRANSFORM_PREFIXES: frozenset[str] = frozenset({"LMT", "Look"})
32
+ # tnColorSpaceConversionTransform (v2 XSD line 92): ACEScsc (v1.5) | CSC (v2.0)
33
+ V2_CSC_TRANSFORM_PREFIXES: frozenset[str] = frozenset({"ACEScsc", "CSC"})
34
+ # tnInverseOutputTransform (v2 XSD line 85): InvRRTODT (v1.5) | InvOutput (v2.0)
35
+ V2_INVERSE_OUTPUT_TRANSFORM_PREFIXES: frozenset[str] = frozenset({"InvRRTODT", "InvOutput"})
36
+ # v1.x only sub-transforms (same in v2 XSD as v1 XSD — no v2.0 equivalents)
37
+ # tnReferenceRenderingTransform (v2 XSD line 100): RRT only
38
+ V2_RRT_TRANSFORM_PREFIXES: frozenset[str] = frozenset({"RRT"})
39
+ # tnInverseReferenceRenderingTransform (v2 XSD line 106): InvRRT only
40
+ V2_INVERSE_RRT_TRANSFORM_PREFIXES: frozenset[str] = frozenset({"InvRRT"})
41
+ # tnOutputDeviceTransform (v2 XSD line 112): ODT only
42
+ V2_ODT_TRANSFORM_PREFIXES: frozenset[str] = frozenset({"ODT"})
43
+ # tnInverseOutputDeviceTransform (v2 XSD line 118): InvODT only
44
+ V2_INVERSE_ODT_TRANSFORM_PREFIXES: frozenset[str] = frozenset({"InvODT"})
45
+
46
+
47
+ # ──────────────────────────────────────────────────────────────────────
48
+ # XSD regex patterns for transform IDs, keyed by transform type.
49
+ # Source: packages/aces-amf-lib/src/aswf/aces/amf_lib/data/amf-schema/v2/acesMetadataFile.xsd
50
+ #
51
+ # Each transform type maps to a list of regex patterns (from xs:pattern).
52
+ # A transform ID is valid if it matches ANY pattern for its type.
53
+ # The v2 XSD accepts both v1.5 and v2.0 URN forms.
54
+ # ──────────────────────────────────────────────────────────────────────
55
+
56
+ # Transform types that have no XSD pattern (library/utility transforms,
57
+ # not placed in AMF pipeline slots).
58
+ # TODO: Define patterns for these types.
59
+ TRANSFORM_TYPES_WITHOUT_PATTERN: frozenset[str] = frozenset({
60
+ "ACESlib", "ACESutil", "Lib", "InvLMT", "InvLook",
61
+ })
62
+
63
+ TRANSFORM_ID_PATTERNS: dict[str, list[str]] = {
64
+ # tnInputTransform
65
+ "IDT": [
66
+ r"urn:ampas:aces:transformId:v1\.5:IDT\.\S+\.\S+\.a\d+\.v\d+",
67
+ ],
68
+ "ACEScsc": [
69
+ r"urn:ampas:aces:transformId:v1\.5:(ACEScsc\.\S+\.\S+\.a\d+\.v\d+|ACEScsc\.Academy\.\S+\.a\d+\.\d+\.\d+)",
70
+ ],
71
+ "Input": [
72
+ r"urn:ampas:aces:transformId:v2\.0:Input\.\S+\.\S+\.a\d+\.v\d+",
73
+ ],
74
+ "CSC": [
75
+ r"urn:ampas:aces:transformId:v2\.0:CSC\.\S+\.\S+\.a\d+\.v\d+",
76
+ ],
77
+ # tnOutputTransform
78
+ "RRTODT": [
79
+ r"urn:ampas:aces:transformId:v1\.5:(RRTODT\.\S+\.\S+\.a\d+\.v\d+|RRTODT\.Academy\.\S+\.a\d+\.\d+\.\d+)",
80
+ ],
81
+ "Output": [
82
+ r"urn:ampas:aces:transformId:v2\.0:Output\.\S+\.\S+\.a\d+\.v\d+",
83
+ ],
84
+ # tnLookTransform
85
+ "LMT": [
86
+ r"urn:ampas:aces:transformId:v1\.5:(LMT\.\S+\.\S+\.a\d+\.v\d+|LMT\.Academy\.\S+\.a\d+\.v?\d+\.\d+)",
87
+ ],
88
+ "Look": [
89
+ r"urn:ampas:aces:transformId:v2\.0:Look\.\S+\.\S+\.a\d+\.v\d+",
90
+ ],
91
+ # tnReferenceRenderingTransform / tnInverseReferenceRenderingTransform
92
+ "RRT": [
93
+ r"urn:ampas:aces:transformId:v1\.5:RRT\.a\d+\.\d+\.\d+",
94
+ ],
95
+ "InvRRT": [
96
+ r"urn:ampas:aces:transformId:v1\.5:InvRRT\.a\d+\.\d+\.\d+",
97
+ ],
98
+ # tnOutputDeviceTransform / tnInverseOutputDeviceTransform
99
+ "ODT": [
100
+ r"urn:ampas:aces:transformId:v1\.5:(ODT\.\S+\.\S+\.a\d+\.v\d+|ODT\.Academy\.\S+\.a\d+\.\d+\.\d+)",
101
+ ],
102
+ "InvODT": [
103
+ r"urn:ampas:aces:transformId:v1\.5:(InvODT\.\S+\.\S+\.a\d+\.v\d+|InvODT\.Academy\.\S+\.a\d+\.\d+\.\d+)",
104
+ ],
105
+ # tnInverseOutputTransform
106
+ "InvRRTODT": [
107
+ r"urn:ampas:aces:transformId:v1\.5:(InvRRTODT\.\S+\.\S+\.a\d+\.v\d+|InvRRTODT\.Academy\.\S+\.a\d+\.\d+\.\d+)",
108
+ ],
109
+ "InvOutput": [
110
+ r"urn:ampas:aces:transformId:v2\.0:InvOutput\.\S+\.\S+\.a\d+\.v\d+",
111
+ ],
112
+ }
@@ -0,0 +1,27 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright Contributors to the ACES Project.
3
+ """Protocol definitions for transform registry interfaces."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Protocol, runtime_checkable
8
+
9
+
10
+ @runtime_checkable
11
+ class TransformRegistry(Protocol):
12
+ """Protocol for transform ID registries.
13
+
14
+ Implementations provide lookup and listing of known ACES transform URNs.
15
+ """
16
+
17
+ def is_valid_transform_id(self, transform_id: str, *, version: str | None = None) -> bool: ...
18
+
19
+ def get_transform_info(self, transform_id: str, *, version: str | None = None) -> dict | None: ...
20
+
21
+ def list_transforms(self, *, category: str | None = None, version: str | None = None) -> list[dict]: ...
22
+
23
+ def list_versions(self) -> list[str]: ...
24
+
25
+ def get_equivalent_id(self, transform_id: str) -> str | None: ...
26
+
27
+ def get_equivalent_ids(self, transform_id: str) -> list[str]: ...
@@ -0,0 +1,111 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright Contributors to the ACES Project.
3
+ """Shared ACES data types."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import re
8
+ from dataclasses import asdict, dataclass, field
9
+
10
+
11
+ _TRANSFORM_URN_RE = re.compile(
12
+ r"^urn:ampas:aces:transformId:"
13
+ r"v(\d+)\.(\d+):" # spec version: v2.0, v1.5
14
+ r"([A-Za-z][A-Za-z0-9]*)" # transform type: CSC, Output, IDT, ACEScsc...
15
+ r"\.([^.]+)" # namespace: Academy, Arri, Red...
16
+ r"\.(.+)" # name + version suffix
17
+ r"$"
18
+ )
19
+
20
+ # Matches the trailing version suffix: a2.v1, a1.0.3, a1.v1.0, etc.
21
+ _VERSION_SUFFIX_RE = re.compile(
22
+ r"^(.+)\.(a\d+(?:\.(?:v?\d+))+)$"
23
+ )
24
+
25
+ @dataclass(frozen=True)
26
+ class TransformURN:
27
+ """Parsed ACES transform URN per the Transform ID specification.
28
+
29
+ Format: urn:ampas:aces:transformId:v{specVersion}:{TransformType}.{Namespace}.{Name}.{VersionSuffix}
30
+
31
+ Example (v2.0):
32
+ urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1
33
+
34
+ Example (v1.x):
35
+ urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.ACES_to_ACEScct.a1.0.3
36
+ """
37
+
38
+ raw: str
39
+ spec_version: str
40
+ spec_major_version: int
41
+ spec_minor_version: int
42
+ transform_type: str
43
+ namespace: str
44
+ name: str
45
+ version_suffix: str
46
+
47
+ @classmethod
48
+ def parse(cls, urn: str) -> TransformURN | None:
49
+ """Parse a URN string. Returns None if not a valid ACES transform URN."""
50
+ m = _TRANSFORM_URN_RE.match(urn)
51
+ if m is None:
52
+ return None
53
+ spec_major, spec_minor, transform_type, namespace, name_and_version = m.groups()
54
+
55
+ # Split the trailing version suffix (e.g. "ACES_to_ACEScct.a2.v1" -> "ACES_to_ACEScct", "a2.v1")
56
+ vs = _VERSION_SUFFIX_RE.match(name_and_version)
57
+ if vs:
58
+ name, version_suffix = vs.groups()
59
+ else:
60
+ name = name_and_version
61
+ version_suffix = ""
62
+
63
+ return cls(
64
+ raw=urn,
65
+ spec_version=f"v{spec_major}.{spec_minor}",
66
+ spec_major_version=int(spec_major),
67
+ spec_minor_version=int(spec_minor),
68
+ transform_type=transform_type,
69
+ namespace=namespace,
70
+ name=name,
71
+ version_suffix=version_suffix,
72
+ )
73
+
74
+ @property
75
+ def is_v1(self) -> bool:
76
+ return self.spec_major_version == 1
77
+
78
+ @property
79
+ def is_v2(self) -> bool:
80
+ return self.spec_major_version == 2
81
+
82
+ @property
83
+ def aces_release_version(self) -> int | None:
84
+ """ACES release major version from the suffix (e.g. 2 from 'a2.v1')."""
85
+ m = re.match(r"a(\d+)", self.version_suffix)
86
+ return int(m.group(1)) if m else None
87
+
88
+ @property
89
+ def transform_version(self) -> str | None:
90
+ """Transform version from the suffix (e.g. 'v1' from 'a2.v1', '0.3' from 'a1.0.3')."""
91
+ m = re.match(r"a\d+\.(.*)", self.version_suffix)
92
+ return m.group(1) if m else None
93
+
94
+ def __str__(self) -> str:
95
+ return self.raw
96
+
97
+
98
+ @dataclass
99
+ class TransformInfo:
100
+ """Information about a single ACES transform."""
101
+
102
+ transform_id: str
103
+ user_name: str
104
+ transform_type: str
105
+ aces_version: str | None = None
106
+ inverse_transform_id: str | None = None
107
+ previous_equivalent_ids: list[str] = field(default_factory=list)
108
+
109
+ def to_dict(self) -> dict:
110
+ """Return a plain dict representation."""
111
+ return asdict(self)
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: aswf-aces-common
3
+ Version: 0.1.0rc1
4
+ Summary: ACES shared protocols and data types
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+
9
+ # aswf-aces-common
10
+
11
+ Shared protocols and types used across the ACES AMF Python packages.
12
+
13
+ This is a foundation package. Most users should install [aswf-aces-amf-utils](../aces-amf-utils/) or [aswf-aces-amf-lib](../aces-amf-lib/) instead.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install aswf-aces-common
19
+ ```
20
+
21
+ ## Exports
22
+
23
+ ### `TransformRegistry` (Protocol)
24
+
25
+ Interface for transform ID registries. Implemented by `ACESTransformRegistry` in [aswf-aces-transforms](../aces-transforms/).
26
+
27
+ ```python
28
+ from aswf.aces.common import TransformRegistry
29
+
30
+ class MyRegistry:
31
+ def is_valid_transform_id(self, transform_id: str, *, version: str | None = None) -> bool: ...
32
+ def get_transform_info(self, transform_id: str, *, version: str | None = None) -> dict | None: ...
33
+ def list_transforms(self, *, category: str | None = None, version: str | None = None) -> list[dict]: ...
34
+ def list_versions(self) -> list[str]: ...
35
+ def get_equivalent_id(self, transform_id: str) -> str | None: ...
36
+ def get_equivalent_ids(self, transform_id: str) -> list[str]: ...
37
+ ```
38
+
39
+ ### `TransformURN` (Dataclass)
40
+
41
+ Parses ACES transform URNs into their component fields per the [Transform ID specification](https://docs.acescentral.com/system-components/transform-id-spec/).
42
+
43
+ ```
44
+ urn:ampas:aces:transformId:v{specVersion}:{TransformType}.{Namespace}.{Name}.a{ACESRelease}.v{TransformVersion}
45
+ ```
46
+
47
+ ```python
48
+ from aswf.aces.common import TransformURN
49
+
50
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1")
51
+
52
+ urn.raw # "urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1"
53
+ urn.spec_version # "v2.0"
54
+ urn.spec_major_version # 2
55
+ urn.spec_minor_version # 0
56
+ urn.transform_type # "CSC"
57
+ urn.namespace # "Academy"
58
+ urn.name # "ACES_to_ACEScct"
59
+ urn.version_suffix # "a2.v1"
60
+ urn.aces_release_version # 2
61
+ urn.transform_version # "v1"
62
+ urn.is_v1 # False
63
+ urn.is_v2 # True
64
+ ```
65
+
66
+ The version suffix format varies between ACES versions:
67
+
68
+ | ACES version | Suffix format | Example | `aces_release_version` | `transform_version` |
69
+ |---|---|---|---|---|
70
+ | v2.0 | `a{N}.v{N}` | `a2.v1` | `2` | `"v1"` |
71
+ | v1.x (semver) | `a{N}.{N}.{N}` | `a1.0.3` | `1` | `"0.3"` |
72
+ | v1.x (mixed) | `a{N}.v{N}.{N}` | `a1.v1.0` | `1` | `"v1.0"` |
73
+
74
+ `TransformURN.parse()` returns `None` for strings that are not valid ACES transform URNs (e.g. legacy short names like `ACEScsc.ACES_to_ACEScc.a1.0.3`).
75
+
76
+ #### Edge cases
77
+
78
+ Internal library and utility transforms (`ACESlib`, `ACESutil`) do not include a separate namespace field — their format is `{Type}.{Name}.{VersionSuffix}` rather than `{Type}.{Namespace}.{Name}.{VersionSuffix}`. For these transforms, the `name` field will contain the version suffix and `version_suffix` will be empty, so `aces_release_version` and `transform_version` will return `None`. These transforms are not typically referenced in AMF files.
79
+
80
+ ```python
81
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v1.5:ACESlib.Utilities.a1.0.3")
82
+ urn.transform_type # "ACESlib"
83
+ urn.namespace # "Utilities" (actually the name, not a namespace)
84
+ urn.name # "a1.0.3" (actually the version suffix)
85
+ urn.version_suffix # ""
86
+ urn.aces_release_version # None
87
+ urn.transform_version # None
88
+ ```
89
+
90
+ ### `TransformInfo` (Dataclass)
91
+
92
+ Metadata for a single ACES transform:
93
+
94
+ ```python
95
+ from aswf.aces.common import TransformInfo
96
+
97
+ # Fields: transform_id, user_name, transform_type, aces_version,
98
+ # inverse_transform_id, previous_equivalent_ids
99
+ ```
100
+
101
+ ### Constants
102
+
103
+ ```python
104
+ from aswf.aces.common import INPUT_TRANSFORM_TYPES, OUTPUT_TRANSFORM_TYPES
105
+
106
+ INPUT_TRANSFORM_TYPES # frozenset{"IDT", "CSC", "Input"}
107
+ OUTPUT_TRANSFORM_TYPES # frozenset{"ODT", "Output"}
108
+ ```
109
+
110
+ ## License
111
+
112
+ Apache-2.0
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/aswf/aces/common/__init__.py
4
+ src/aswf/aces/common/constants.py
5
+ src/aswf/aces/common/protocols.py
6
+ src/aswf/aces/common/types.py
7
+ src/aswf_aces_common.egg-info/PKG-INFO
8
+ src/aswf_aces_common.egg-info/SOURCES.txt
9
+ src/aswf_aces_common.egg-info/dependency_links.txt
10
+ src/aswf_aces_common.egg-info/top_level.txt
11
+ tests/test_transform_urn.py
@@ -0,0 +1,107 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Tests for TransformURN parsing."""
3
+
4
+ import pytest
5
+
6
+ from aswf.aces.common.types import TransformURN
7
+
8
+
9
+ class TestTransformURNParse:
10
+ def test_parse_v15_csc(self):
11
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.ACES_to_ACEScct.a1.0.3")
12
+ assert urn is not None
13
+ assert urn.spec_version == "v1.5"
14
+ assert urn.spec_major_version == 1
15
+ assert urn.spec_minor_version == 5
16
+ assert urn.transform_type == "ACEScsc"
17
+ assert urn.namespace == "Academy"
18
+ assert urn.name == "ACES_to_ACEScct"
19
+ assert urn.version_suffix == "a1.0.3"
20
+ assert urn.is_v1 is True
21
+ assert urn.is_v2 is False
22
+
23
+ def test_parse_v20_csc(self):
24
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1")
25
+ assert urn is not None
26
+ assert urn.spec_version == "v2.0"
27
+ assert urn.spec_major_version == 2
28
+ assert urn.spec_minor_version == 0
29
+ assert urn.transform_type == "CSC"
30
+ assert urn.namespace == "Academy"
31
+ assert urn.name == "ACES_to_ACEScct"
32
+ assert urn.version_suffix == "a2.v1"
33
+ assert urn.is_v1 is False
34
+ assert urn.is_v2 is True
35
+
36
+ def test_parse_v20_output(self):
37
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v2.0:Output.Academy.Rec709-D65_100nit_in_Rec709-D65_BT1886.a2.v1")
38
+ assert urn is not None
39
+ assert urn.transform_type == "Output"
40
+ assert urn.namespace == "Academy"
41
+ assert urn.name == "Rec709-D65_100nit_in_Rec709-D65_BT1886"
42
+ assert urn.version_suffix == "a2.v1"
43
+
44
+ def test_parse_v15_lmt(self):
45
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v1.5:LMT.Academy.ReferenceGamutCompress.a1.v1.0")
46
+ assert urn is not None
47
+ assert urn.transform_type == "LMT"
48
+ assert urn.namespace == "Academy"
49
+ assert urn.name == "ReferenceGamutCompress"
50
+ assert urn.version_suffix == "a1.v1.0"
51
+ assert urn.is_v1 is True
52
+
53
+ def test_parse_v15_idt_manufacturer(self):
54
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v1.5:IDT.ARRI.ARRI-LogC4.a1.v1")
55
+ assert urn is not None
56
+ assert urn.transform_type == "IDT"
57
+ assert urn.namespace == "ARRI"
58
+ assert urn.name == "ARRI-LogC4"
59
+ assert urn.version_suffix == "a1.v1"
60
+
61
+ def test_parse_v20_look(self):
62
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v2.0:Look.Academy.ReferenceGamutCompress.a2.v1")
63
+ assert urn is not None
64
+ assert urn.transform_type == "Look"
65
+ assert urn.name == "ReferenceGamutCompress"
66
+ assert urn.version_suffix == "a2.v1"
67
+
68
+ def test_parse_returns_none_for_invalid(self):
69
+ assert TransformURN.parse("not-a-urn") is None
70
+ assert TransformURN.parse("") is None
71
+ assert TransformURN.parse("urn:ampas:aces:transformId:v1.5:") is None
72
+
73
+ def test_parse_returns_none_for_legacy_short_name(self):
74
+ assert TransformURN.parse("ACEScsc.ACES_to_ACEScc.a1.0.3") is None
75
+
76
+ def test_str_returns_raw(self):
77
+ raw = "urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1"
78
+ urn = TransformURN.parse(raw)
79
+ assert str(urn) == raw
80
+
81
+ def test_aces_release_version_v20(self):
82
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1")
83
+ assert urn.aces_release_version == 2
84
+ assert urn.transform_version == "v1"
85
+
86
+ def test_aces_release_version_v15_semver(self):
87
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.ACES_to_ACEScct.a1.0.3")
88
+ assert urn.aces_release_version == 1
89
+ assert urn.transform_version == "0.3"
90
+
91
+ def test_aces_release_version_v15_mixed(self):
92
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v1.5:LMT.Academy.ReferenceGamutCompress.a1.v1.0")
93
+ assert urn.aces_release_version == 1
94
+ assert urn.transform_version == "v1.0"
95
+
96
+ def test_aces_release_version_empty_suffix(self):
97
+ """ACESlib transforms may not parse a version suffix."""
98
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v1.5:ACESlib.ODT_Common.a1.1.0")
99
+ assert urn.aces_release_version is None
100
+ assert urn.transform_version is None
101
+
102
+ def test_frozen(self):
103
+ urn = TransformURN.parse("urn:ampas:aces:transformId:v2.0:CSC.Academy.ACES_to_ACEScct.a2.v1")
104
+ with pytest.raises(AttributeError):
105
+ urn.spec_version = "v3.0"
106
+
107
+