fix: macOS portability — pure --json stdout for the SynthID scorer, BSD realpath probe (#70)

* fix: keep the SynthID scorer's --json stdout pure

The reverse-SynthID upstream prints progress ('CodebookV4 loaded: ...')
straight to stdout. image_meta.py parses the scorer's stdout with
json.loads, so the leak corrupts every score payload into
{'available': False, 'error': 'bad scorer JSON: ...'}.

Redirect stdout to stderr around the upstream calls so --json owns
stdout. Regression test drives the real script against a deliberately
noisy stub upstream (with a stub cv2, so it runs without OpenCV).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: probe realpath -m support instead of realpath presence

macOS ships BSD realpath, which exists but has no -m flag, so
'command -v realpath' takes the GNU branch and both setup bootstraps
abort on the first path they normalize. Probe the flag itself; BSD
systems fall through to the portable pwd fallback already in place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Adriel <adriel@Adriels-MacBook-Pro-2026.local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Guillaume Meyer (The Opinionated Man) <1385518+guillaumemeyer@users.noreply.github.com>
This commit is contained in:
Oudscent
2026-08-15 14:53:49 -07:00
committed by GitHub
co-authored by Claude Opus 5 Adriel Guillaume Meyer
parent 1eeda9be89
commit 97c32d58ff
4 changed files with 85 additions and 8 deletions
+11 -6
View File
@@ -16,6 +16,7 @@ Exit codes:
from __future__ import annotations
import argparse
import contextlib
import json
import os
import sys
@@ -90,13 +91,17 @@ def main() -> int:
return 2
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
codebook_v4 = SpectralCodebookV4()
codebook_v4.load(str(codebook))
# Upstream prints progress ("CodebookV4 loaded: ...") straight to
# stdout, which corrupts --json for any caller that parses us
# (image_meta.py json.loads our stdout). Keep stdout ours alone.
with contextlib.redirect_stdout(sys.stderr):
codebook_v4 = SpectralCodebookV4()
codebook_v4.load(str(codebook))
extractor = RobustSynthIDExtractor()
result = extractor.detect_from_v4_codebook(
rgb, codebook_v4, model=args.model
)
extractor = RobustSynthIDExtractor()
result = extractor.detect_from_v4_codebook(
rgb, codebook_v4, model=args.model
)
except Exception as e:
print(f"scorer error: {e}", file=sys.stderr)
return 1
+1 -1
View File
@@ -58,7 +58,7 @@ done
DIR="${DIR:-$DEFAULT_DIR}"
mkdir -p "$(dirname "$DIR")"
if command -v realpath >/dev/null 2>&1; then
if realpath -m . >/dev/null 2>&1; then # BSD/macOS realpath has no -m
DIR="$(realpath -m "$DIR")"
else
DIR="$(cd "$(dirname "$DIR")" && pwd)/$(basename "$DIR")"
+1 -1
View File
@@ -63,7 +63,7 @@ done
DIR="${DIR:-$DEFAULT_DIR}"
mkdir -p "$(dirname "$DIR")"
if command -v realpath >/dev/null 2>&1; then
if realpath -m . >/dev/null 2>&1; then # BSD/macOS realpath has no -m
DIR="$(realpath -m "$DIR")"
else
DIR="$(cd "$(dirname "$DIR")" && pwd)/$(basename "$DIR")"