"""
Adobe DNG SDK Conversion Process
================================
Defines various objects implementing raw conversion based on *Adobe DNG SDK*
and *dcraw*:
- :func:`colour_hdri.convert_raw_files_to_dng_files`
- :func:`colour_hdri.convert_dng_files_to_intermediate_files`
- :func:`colour_hdri.read_dng_files_exif_tags`
"""
from __future__ import annotations
import logging
import os
import platform
import re
import shlex
import subprocess
import numpy as np
from colour.hints import Callable, List, Mapping, Sequence, Tuple
from colour.utilities import CanonicalMapping, optional
from colour.utilities.documentation import (
DocstringText,
is_documentation_building,
)
from colour_hdri.utilities import (
EXIFTag,
parse_exif_array,
parse_exif_number,
parse_exif_string,
path_exists,
read_exif_tags,
)
__author__ = "Colour Developers"
__copyright__ = "Copyright 2015 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"
__all__ = [
"RAW_CONVERTER",
"RAW_CONVERTER_ARGUMENTS_BAYER_CFA",
"RAW_CONVERTER_ARGUMENTS_DEMOSAICING",
"DNG_CONVERTER",
"DNG_CONVERTER_ARGUMENTS",
"DNG_EXIF_TAGS_BINDING",
"convert_raw_files_to_dng_files",
"convert_dng_files_to_intermediate_files",
"read_dng_files_exif_tags",
]
_IS_MACOS_PLATFORM: bool = platform.system() == "Darwin"
"""Whether the current platform is *macOS*."""
_IS_WINDOWS_PLATFORM: bool = platform.system() in ("Windows", "Microsoft")
"""Whether the current platform is *Windows*."""
RAW_CONVERTER: str = "dcraw"
if is_documentation_building(): # pragma: no cover
RAW_CONVERTER = DocstringText(RAW_CONVERTER)
RAW_CONVERTER.__doc__ = """
Command line raw conversion application, typically Dave Coffin's *dcraw*.
"""
RAW_CONVERTER_ARGUMENTS_BAYER_CFA: str = '-t 0 -D -W -4 -T "{raw_file}"'
if _IS_WINDOWS_PLATFORM:
RAW_CONVERTER_ARGUMENTS_BAYER_CFA = RAW_CONVERTER_ARGUMENTS_BAYER_CFA.replace(
'"', ""
)
if is_documentation_building(): # pragma: no cover
RAW_CONVERTER_ARGUMENTS_BAYER_CFA = DocstringText(RAW_CONVERTER_ARGUMENTS_BAYER_CFA)
RAW_CONVERTER_ARGUMENTS_BAYER_CFA.__doc__ = """
Arguments for the command line raw conversion application for non
demosaiced linear *tiff* file format output.
"""
RAW_CONVERTER_ARGUMENTS_DEMOSAICING: str = (
'-t 0 -H 1 -r 1 1 1 1 -4 -q 3 -o 0 -T "{raw_file}"'
)
if _IS_WINDOWS_PLATFORM:
RAW_CONVERTER_ARGUMENTS_DEMOSAICING = RAW_CONVERTER_ARGUMENTS_DEMOSAICING.replace(
'"', ""
)
if is_documentation_building(): # pragma: no cover
RAW_CONVERTER_ARGUMENTS_DEMOSAICING = DocstringText(
RAW_CONVERTER_ARGUMENTS_DEMOSAICING
)
RAW_CONVERTER_ARGUMENTS_DEMOSAICING.__doc__ = """
Arguments for the command line raw conversion application for demosaiced
linear *tiff* file format output.
"""
if _IS_MACOS_PLATFORM:
DNG_CONVERTER: str = (
"/Applications/Adobe DNG Converter.app/Contents/MacOS/Adobe DNG Converter"
)
elif _IS_WINDOWS_PLATFORM:
DNG_CONVERTER: str = "Adobe DNG Converter"
else:
# https://rawpedia.rawtherapee.com/How_to_convert_raw_formats_to_DNG
DNG_CONVERTER: str = "Adobe-DNG-Converter"
if is_documentation_building(): # pragma: no cover
DNG_CONVERTER = DocstringText(DNG_CONVERTER)
DNG_CONVERTER.__doc__ = """
Command line *DNG* conversion application, typically *Adobe DNG Converter*.
"""
DNG_CONVERTER_ARGUMENTS: str = '-cr7.1 -l -d "{output_directory}" "{raw_file}"'
if _IS_WINDOWS_PLATFORM:
DNG_CONVERTER_ARGUMENTS = DNG_CONVERTER_ARGUMENTS.replace('"', "")
if is_documentation_building(): # pragma: no cover
DNG_CONVERTER_ARGUMENTS = DocstringText(DNG_CONVERTER_ARGUMENTS)
DNG_CONVERTER_ARGUMENTS.__doc__ = """
Arguments for the command line *DNG* conversion application.
"""
DNG_EXIF_TAGS_BINDING: CanonicalMapping = CanonicalMapping(
{
"EXIF": CanonicalMapping(
{
"Make": (parse_exif_string, None),
"Camera Model Name": (parse_exif_string, None),
"Camera Serial Number": (parse_exif_string, None),
"Lens Model": (parse_exif_string, None),
"DNG Lens Info": (parse_exif_string, None),
"Focal Length": (parse_exif_number, None),
"Exposure Time": (parse_exif_number, None),
"F Number": (parse_exif_number, None),
"ISO": (parse_exif_number, None),
"CFA Pattern 2": (
lambda x: parse_exif_array(x, np.int_),
None,
),
"CFA Plane Color": (
lambda x: parse_exif_array(x, np.int_),
None,
),
"Black Level Repeat Dim": (
lambda x: parse_exif_array(x, np.int_),
None,
),
"Black Level": (lambda x: parse_exif_array(x, np.int_), None),
"White Level": (lambda x: parse_exif_array(x, np.int_), None),
"Samples Per Pixel": (
lambda x: parse_exif_number(x, np.int_),
None,
),
"Active Area": (lambda x: parse_exif_array(x, np.int_), None),
"Orientation": (lambda x: parse_exif_number(x, np.int_), None),
"Camera Calibration Sig": (parse_exif_string, None),
"Profile Calibration Sig": (parse_exif_string, None),
"Calibration Illuminant 1": (
lambda x: parse_exif_number(x, np.int_),
17,
),
"Calibration Illuminant 2": (
lambda x: parse_exif_number(x, np.int_),
21,
),
"Color Matrix 1": (
lambda x: parse_exif_array(x, np.float_, (3, 3)),
"1 0 0 0 1 0 0 0 1",
),
"Color Matrix 2": (
lambda x: parse_exif_array(x, np.float_, (3, 3)),
"1 0 0 0 1 0 0 0 1",
),
"Camera Calibration 1": (
lambda x: parse_exif_array(x, np.float_, (3, 3)),
"1 0 0 0 1 0 0 0 1",
),
"Camera Calibration 2": (
lambda x: parse_exif_array(x, np.float_, (3, 3)),
"1 0 0 0 1 0 0 0 1",
),
"Analog Balance": (
lambda x: parse_exif_array(x, np.float_),
"1 1 1",
),
"Reduction Matrix 1": (
lambda x: parse_exif_array(x, np.float_, (3, 3)),
"1 0 0 0 1 0 0 0 1",
),
"Reduction Matrix 2": (
lambda x: parse_exif_array(x, np.float_, (3, 3)),
"1 0 0 0 1 0 0 0 1",
),
"Forward Matrix 1": (
lambda x: parse_exif_array(x, np.float_, (3, 3)),
"1 0 0 0 1 0 0 0 1",
),
"Forward Matrix 2": (
lambda x: parse_exif_array(x, np.float_, (3, 3)),
"1 0 0 0 1 0 0 0 1",
),
"As Shot Neutral": (
lambda x: parse_exif_array(x, np.float_),
"1 1 1",
),
"Baseline Exposure": (
lambda x: parse_exif_number(x, np.float_),
None,
),
"Baseline Noise": (
lambda x: parse_exif_number(x, np.float_),
None,
),
}
)
}
)
DNG_EXIF_TAGS_BINDING.__doc__ = """
Exif tags binding for a *dng* file.
"""
[docs]
def convert_raw_files_to_dng_files(
raw_files: Sequence[str],
output_directory: str,
dng_converter: str | None = None,
dng_converter_arguments: str | None = None,
) -> List[str]:
"""
Convert given raw files to *dng* files using given output directory.
Parameters
----------
raw_files
Raw files to convert to *dng* files.
output_directory
Output directory.
dng_converter
Command line *DNG* conversion application, typically
*Adobe DNG Converter*.
dng_converter_arguments
Arguments for the command line *DNG* conversion application.
Returns
-------
:class:`list`
*dng* files.
Raises
------
RuntimeError
If the *DNG* converter is not available.
"""
dng_converter = optional(dng_converter, DNG_CONVERTER)
dng_converter_arguments = optional(dng_converter_arguments, DNG_CONVERTER_ARGUMENTS)
dng_files = []
for raw_file in raw_files:
raw_file_extension = os.path.splitext(raw_file)[1]
dng_file = os.path.join(
output_directory,
os.path.basename(re.sub(f"{raw_file_extension}$", ".dng", raw_file)),
)
if path_exists(dng_file):
os.remove(dng_file)
logging.info(
'Converting "{raw_file}" file to "{dng_file}" file.',
extra={"raw_file": raw_file, "dng_file": dng_file},
)
command = [
dng_converter,
*shlex.split(
dng_converter_arguments.format(
output_directory=output_directory, raw_file=raw_file
),
posix=not _IS_WINDOWS_PLATFORM,
),
]
subprocess.call(command, shell=_IS_WINDOWS_PLATFORM) # noqa: S603
dng_files.append(dng_file)
return dng_files