Skip to content

chemigram.core.stages.darktable_cli

chemigram.core.stages.darktable_cli

DarktableCliStage: invokes darktable-cli to render raw + XMP into JPEG.

Per ADR-005, only one darktable-cli runs per configdir at a time; darktable holds an exclusive lock on library.db inside the configdir. This stage uses a class-level lock dict keyed by configdir to enforce single-process serialization. Cross-process coordination is out of scope — a caller bug if violated.

Per CLAUDE.md "darktable-cli invocation form", every invocation uses::

darktable-cli <raw> <xmp> <output> \
  --width N --height N --hq <bool> \
  --apply-custom-presets false \
  --core --configdir <isolated>

Binary path resolution:

  1. Explicit binary= constructor argument (highest precedence)
  2. $DARKTABLE_CLI environment variable
  3. "darktable-cli" resolved via $PATH

The env-var override exists for the macOS .app-bundle install case: /Applications/darktable.app/Contents/MacOS/darktable-cli cannot be naively symlinked onto PATH because macOS resolves bundle resources from the binary's invocation path. Either install a thin exec wrapper on PATH or set DARKTABLE_CLI to the absolute path.

DarktableCliStage

DarktableCliStage(binary=None, timeout_seconds=None)

v1's only render stage: invoke darktable-cli.

Source code in src/chemigram/core/stages/darktable_cli.py
def __init__(
    self,
    binary: str | None = None,
    timeout_seconds: float | None = None,
) -> None:
    self.binary = binary or os.environ.get("DARKTABLE_CLI") or "darktable-cli"
    self.timeout_seconds = (
        timeout_seconds if timeout_seconds is not None else self.DEFAULT_TIMEOUT_SECONDS
    )

clear_locks classmethod

clear_locks()

Clear the per-configdir lock cache.

Safe to call when no renders are in flight; callers must guarantee that. Useful for long-running processes that have cycled through many distinct configdirs and want to reclaim the lock-table memory.

Source code in src/chemigram/core/stages/darktable_cli.py
@classmethod
def clear_locks(cls) -> None:
    """Clear the per-configdir lock cache.

    Safe to call when no renders are in flight; callers must
    guarantee that. Useful for long-running processes that have
    cycled through many distinct configdirs and want to reclaim
    the lock-table memory.
    """
    with cls._locks_dict_lock:
        cls._configdir_locks.clear()

run

run(context)

Invoke darktable-cli per the canonical CLAUDE.md form.

Returns a :class:StageResult capturing success, the output path, wall-clock duration, and stderr (always; useful on failure for diagnosis).

Source code in src/chemigram/core/stages/darktable_cli.py
def run(self, context: StageContext) -> StageResult:
    """Invoke ``darktable-cli`` per the canonical CLAUDE.md form.

    Returns a :class:`StageResult` capturing success, the output
    path, wall-clock duration, and stderr (always; useful on
    failure for diagnosis).
    """
    lock = self._lock_for_configdir(context.configdir)
    with lock:
        return self._run_locked(context)