Source code for cdl_convert.write

#!/usr/bin/env python
"""CDL Convert Write Module

Provides writing capabilities for all supported ASC CDL output formats.

Public Functions:
    write_cc(ColorCorrection) -> None: Write ColorCorrection to .cc XML format.

    write_ccc(Union[ColorCorrection, ColorCollection]) -> None: Write to .ccc
        XML format (ColorCorrectionCollection) with automatic collection
        wrapping.

    write_cdl(Union[ColorCorrection, ColorCollection]) -> None: Write to .cdl
        XML format (ColorDecisionList).

    write_nk(ColorCorrection) -> None: Write ColorCorrection to Nuke
        OCIOCDLTransform format.

    write_rnh_cdl(ColorCorrection) -> None: Write to Rhythm & Hues
        space-separated format.

Example Usage:
    >>> from pathlib import Path
    >>> from cdl_convert import ColorCorrection, ColorCollection, write_cc, write_ccc
    >>>
    >>> # Write single correction
    >>> cc = ColorCorrection("shot_001")
    >>> cc.file_out = Path("output.cc")
    >>> cc.slope = [1.2, 1.1, 1.0]
    >>>
    >>> try:
    ...     write_cc(cc)
    ...     print(f"Successfully wrote {cc.file_out}")
    ... except CDLConvertError as e:
    ...     print(f"Write error: {e}")
    >>>
    >>> # Write collection with format handling
    >>> collection = ColorCollection()
    >>> collection.append_child(cc)
    >>> collection.file_out = Path("collection.ccc")
    >>> write_ccc(collection)

## License

The MIT License (MIT)

cdl_convert
Copyright (c) 2015-2025 Sean Wallitsch
http://github.com/shidarin/cdl_convert/

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

"""

# ==============================================================================
# IMPORTS
# ==============================================================================

# Standard Imports

# cdl_convert imports
from cdl_convert import config
from cdl_convert.collection import ColorCollection
from cdl_convert.correction import ColorCorrection
from cdl_convert.exceptions import CDLConvertError

# ==============================================================================
# EXPORTS
# ==============================================================================

__all__ = [
    "write_cc",
    "write_ccc",
    "write_cdl",
    "write_nk",
    "write_rnh_cdl",
]

# ==============================================================================
# PRIVATE FUNCTIONS
# ==============================================================================


def _temp_container(cdl: ColorCorrection) -> ColorCollection:
    """Build a temporary collection container for a single CDL file.

    This helper function creates a temporary ColorCollection to wrap a single
    ColorCorrection for writing operations that require collection format.

    Args:
        cdl (ColorCorrection): The ColorCorrection to wrap in a collection.

    Returns:
        ColorCollection: A temporary collection containing the single
            correction.

    """
    temp_cdl = ColorCollection()
    orig_parent = cdl.parent
    temp_cdl.append_child(cdl)
    cdl.parent = orig_parent  # Restore original parentage away from temp cdl
    temp_cdl._file_out = cdl.file_out
    return temp_cdl


# ==============================================================================
# PUBLIC FUNCTIONS
# ==============================================================================


[docs] def write_cc(cdl: ColorCorrection) -> None: """Write a ColorCorrection to a .cc XML file. Args: cdl (ColorCorrection): The ColorCorrection instance to write. Must have a valid file_out attribute set to the target file path. Raises: CDLConvertError: If the file cannot be written, with the original OSError chained for context. This includes permission errors, disk space issues, or invalid file paths. Example: >>> from pathlib import Path >>> cc = ColorCorrection("test_id") >>> cc.file_out = Path("output.cc") >>> cc.slope = [1.2, 1.1, 1.0] >>> write_cc(cc) # Writes XML to output.cc """ if cdl.file_out is None: raise CDLConvertError( "Output file path not set. Set cdl.file_out before writing." ) try: with open( cdl.file_out, "w", encoding=config.config.output_encoding ) as cdl_f: cdl_f.write(cdl.xml_root) except OSError as e: raise CDLConvertError( f"Failed to write CC file '{cdl.file_out}': {e}" ) from e
# ==============================================================================
[docs] def write_ccc(cdl: ColorCorrection | ColorCollection) -> None: """Write a ColorCollection to a .ccc XML file. Accepts either a single ColorCorrection (which gets wrapped in a temporary collection) or a full ColorCollection. Args: cdl (Union[ColorCorrection, ColorCollection]): The color correction data to write. If a ColorCorrection is provided, it will be wrapped in a temporary ColorCollection. Raises: CDLConvertError: If the file cannot be written, with the original OSError chained for context. This includes permission errors, disk space issues, or invalid file paths. Example: >>> from pathlib import Path >>> # Write a single correction >>> cc = ColorCorrection("test_id") >>> cc.file_out = Path("output.ccc") >>> write_ccc(cc) >>> # Write a collection >>> collection = ColorCollection() >>> collection.append_child(cc) >>> collection.file_out = Path("collection.ccc") >>> write_ccc(collection) """ if not isinstance(cdl, ColorCollection): cdl = _temp_container(cdl) if cdl.file_out is None: raise CDLConvertError( "Output file path not set. Set cdl.file_out before writing." ) collection_type = cdl.type cdl.set_to_ccc() try: with open( cdl.file_out, "w", encoding=config.config.output_encoding ) as cdl_f: cdl_f.write(cdl.xml_root) except OSError as e: raise CDLConvertError( f"Failed to write CCC file '{cdl.file_out}': {e}" ) from e finally: cdl.type = collection_type
# ==============================================================================
[docs] def write_cdl(cdl: ColorCorrection | ColorCollection) -> None: """Write a ColorCollection to a .cdl XML file. Accepts either a single ColorCorrection (which gets wrapped in a temporary collection) or a full ColorCollection. Args: cdl (Union[ColorCorrection, ColorCollection]): The color correction data to write. If a ColorCorrection is provided, it will be wrapped in a temporary ColorCollection. Raises: CDLConvertError: If the file cannot be written, with the original OSError chained for context. This includes permission errors, disk space issues, or invalid file paths. Example: >>> from pathlib import Path >>> # Write a single correction as CDL >>> cc = ColorCorrection("test_id") >>> cc.file_out = Path("output.cdl") >>> write_cdl(cc) >>> # Write a collection as CDL >>> collection = ColorCollection() >>> collection.append_child(cc) >>> collection.file_out = Path("collection.cdl") >>> write_cdl(collection) """ if not isinstance(cdl, ColorCollection): cdl = _temp_container(cdl) if cdl.file_out is None: raise CDLConvertError( "Output file path not set. Set cdl.file_out before writing." ) collection_type = cdl.type cdl.set_to_cdl() try: with open( cdl.file_out, "w", encoding=config.config.output_encoding ) as cdl_f: cdl_f.write(cdl.xml_root) except OSError as e: raise CDLConvertError( f"Failed to write CDL file '{cdl.file_out}': {e}" ) from e finally: cdl.type = collection_type
# ==============================================================================
[docs] def write_nk(cdl: ColorCorrection) -> None: """Write ColorCorrection to Nuke OCIOCDLTransform format. Generates a Nuke script file containing an OCIOCDLTransform node with the CDL color correction values. The output format uses Nuke's standard node syntax with curly braces for structure. The generated OCIOCDLTransform node can be directly loaded into Nuke and will apply the specified color correction values. Args: cdl (ColorCorrection): The ColorCorrection instance to write. Must have a valid file_out attribute set to the target file path. Raises: CDLConvertError: If file_out is not set or if the file cannot be written, with the original OSError chained for context. Example: >>> from pathlib import Path >>> cc = ColorCorrection("shot_001") >>> cc.file_out = Path("output.nk") >>> cc.slope = [1.2, 1.1, 1.0] >>> cc.sat = 0.9 >>> write_nk(cc) # Writes Nuke node to output.nk """ # Import here to avoid circular imports from cdl_convert.correction import _de_exponent, _sanitize if cdl.file_out is None: raise CDLConvertError( "Output file path not set. Set cdl.file_out before writing." ) # Format values with _de_exponent to avoid scientific notation slope_str = " ".join([_de_exponent(i) for i in cdl.slope]) offset_str = " ".join([_de_exponent(i) for i in cdl.offset]) power_str = " ".join([_de_exponent(i) for i in cdl.power]) sat_str = _de_exponent(cdl.sat) # Sanitize name for Nuke name = _sanitize(cdl.id) # Generate Nuke OCIOCDLTransform node content nk_cdl = f"""OCIOCDLTransform {{ slope {{{slope_str}}} offset {{{offset_str}}} power {{{power_str}}} saturation {sat_str} name {name} }} """ try: with open( cdl.file_out, "w", encoding=config.config.output_encoding ) as cdl_f: cdl_f.write(nk_cdl) except OSError as e: raise CDLConvertError( f"Failed to write Nuke file '{cdl.file_out}': {e}" ) from e
# ==============================================================================
[docs] def write_rnh_cdl(cdl: ColorCorrection) -> None: """Writes the ColorCorrection to a space separated .cdl file""" # Import here to avoid circular imports from .correction import _de_exponent if cdl.file_out is None: raise CDLConvertError( "Output file path not set. Set cdl.file_out before writing." ) values = list(cdl.slope) values.extend(cdl.offset) values.extend(cdl.power) values.append(cdl.sat) # Use _de_exponent to avoid scientific notation (consistent with XML output) str_values = [_de_exponent(i) for i in values] str_values = [_de_exponent(i) for i in values] ss_cdl = " ".join(str_values) try: with open( cdl.file_out, "w", encoding=config.config.output_encoding ) as cdl_f: cdl_f.write(ss_cdl) except OSError as e: raise CDLConvertError( f"Failed to write RNH CDL file '{cdl.file_out}': {e}" ) from e
# ============================================================================== # GLOBALS # ============================================================================== OUTPUT_FORMATS = { "cc": write_cc, "ccc": write_ccc, "cdl": write_cdl, "nk": write_nk, "rcdl": write_rnh_cdl, }