Detect and attribute Bria AI, fal.ai, and Apple Photos Clean Up

Corpus-mined vendor gaps (42k-file metadata scan, 2026-07-23):
- Bria AI signs C2PA as "Bria Artificial Intelligence" with source type
  empty (no trainedAlgorithmicMedia), so identify was completely blind;
  registered with asserts_ai like Dreamina.
- fal.ai ("fal - Features & Labels Inc.", fal-ai/<model> generators)
  was detected via the source type but never attributed; registered,
  also asserts_ai as a pure generative platform.
- Apple Photos Clean Up (Apple Intelligence object removal) was detected
  as a generic made-with-AI tag; now attributed as an AI edit via the
  photoshop:Credit marker, and the credit value joins
  AI_GENERATOR_TOKENS so removal strips it in parity.
Each new test was verified red without its fix.
This commit is contained in:
Victor Kuznetsov
2026-07-23 16:15:48 -07:00
parent eb544806f6
commit c11181b9b5
4 changed files with 81 additions and 1 deletions
+12 -1
View File
@@ -312,6 +312,9 @@ _AI_VENDOR_TOKENS: tuple[tuple[str, str], ...] = (
("elevenlabs", "ElevenLabs"),
("eleven labs", "ElevenLabs"),
("black forest", "Black Forest Labs"),
("fal-ai", "fal.ai"),
("bria", "Bria"),
("apple photos clean up", "Apple"),
)
@@ -673,7 +676,15 @@ def identify(image_path: Path, *, check_visible: bool = True, check_invisible: b
watermarks.append("IPTC digitalSourceType (Made with AI)")
caveats.append(_IPTC_ONLY_CAVEAT)
if platform is None:
platform = "Made-with-AI tag (e.g. Meta AI); platform not specified"
# Apple Photos Clean Up (Apple Intelligence object removal) marks
# the edit with photoshop:Credit / IPTC "Apple Photos Clean Up"
# next to compositeWithTrainedAlgorithmicMedia -- corpus-measured
# 2026-07-23 (35 files); it was detected but never attributed.
platform = (
"Apple Photos (Clean Up AI edit)"
if b"Apple Photos Clean Up" in head
else "Made-with-AI tag (e.g. Meta AI); platform not specified"
)
# ── IPTC 2025.1 AI-disclosure fields (Iptc4xmpExt:AISystemUsed etc.) ─
iptc_ai = any(m in head for m in IPTC_AI_FIELD_MARKERS)
@@ -184,6 +184,20 @@ C2PA_AI_VENDORS: tuple[C2paAiVendor, ...] = (
# manifest alone marks AI generation. Verified against the mined retained
# corpus, 2026-06-20. ElevenLabs does not use SynthID.
C2paAiVendor(b"Eleven Labs", "ElevenLabs", "ElevenLabs", "ElevenLabs"),
# fal.ai (generative inference platform, issuer "fal - Features & Labels
# Inc." / common name "fal.ai", claim generators like "fal-ai/seedvr",
# "fal-ai/gpt-image-2"). Corpus-measured 2026-07-23 on the retained
# uploads (17 files): the files carry trainedAlgorithmicMedia, so the
# verdict already fired, but the platform stayed unattributed. fal.ai is
# a pure generative platform, so ``asserts_ai`` also covers its output
# that omits the source-type.
C2paAiVendor(b"fal-ai", "fal.ai", "fal.ai", "fal.ai", asserts_ai=True),
# Bria AI (bria.ai, generative platform) signs as "Bria Artificial
# Intelligence" with a "Bria Ai" claim generator and source type
# ``empty`` (NOT trainedAlgorithmicMedia), so a real signed file was
# completely missed by identify -- corpus-found 2026-07-23. A pure-AI
# vendor with distinctive strings, so ``asserts_ai`` is safe here.
C2paAiVendor(b"Bria", "Bria Artificial Intelligence", "Bria AI", "Bria", asserts_ai=True),
# Truepic is a C2PA signing authority, not an AI generator: no platform label,
# never asserts is_ai (the verdict comes from the digital-source-type).
C2paAiVendor(b"Truepic", "Truepic", None, None),
@@ -307,6 +321,13 @@ AI_GENERATOR_TOKENS: frozenset[str] = frozenset(
"novelai",
"reve.com",
"aphrodite ai",
# Corpus-mined 2026-07-23:
# - Apple Photos Clean Up (Apple Intelligence object removal): XMP
# photoshop:Credit / IPTC credit value; composite source-type
# covered detection, this token covers removal parity (35 files).
# - fal-ai: generative-platform generator string (17 files).
"apple photos clean up",
"fal-ai",
}
)
+31
View File
@@ -171,6 +171,23 @@ class TestIdentifyNonPng:
assert r.platform == "ElevenLabs"
assert not any("SynthID" in w for w in r.watermarks) # ElevenLabs does not use SynthID
def test_fal_ai_attributed(self, tmp_path: Path):
# fal.ai signs as "fal - Features & Labels Inc." with a "fal-ai/<model>"
# claim generator; corpus-measured 2026-07-23 (17 files).
path = self._c2pa_jpeg(tmp_path, b"fal - Features & Labels Inc. fal-ai/seedvr trainedAlgorithmicMedia")
r = identify(path, check_visible=False, check_invisible=False)
assert r.is_ai_generated is True
assert r.platform == "fal.ai"
def test_bria_attributed_without_source_type(self, tmp_path: Path):
# Bria signs as "Bria Artificial Intelligence" with source type ``empty``
# (NO trainedAlgorithmicMedia) -- a pure-generator asserts_ai vendor, so
# the issuer/generator strings alone must flag AI. Corpus-found 2026-07-23.
path = self._c2pa_jpeg(tmp_path, b"Bria Artificial Intelligence Bria Ai c2pa.created c2pa.edited")
r = identify(path, check_visible=False, check_invisible=False)
assert r.is_ai_generated is True
assert r.platform == "Bria AI"
def test_stability_ai_issuer_attributed_no_synthid(self, tmp_path: Path):
path = self._c2pa_jpeg(tmp_path, b"Stability AI ... trainedAlgorithmicMedia")
r = identify(path, check_visible=False)
@@ -285,6 +302,20 @@ class TestIdentifyRealSamples:
assert r.is_ai_generated is True
assert any("IPTC" in w for w in r.watermarks)
def test_apple_clean_up_attributed(self, tmp_path: Path):
# Apple Photos Clean Up (Apple Intelligence object removal) marks the
# AI edit via photoshop:Credit next to compositeWithTrainedAlgorithmicMedia
# -- it must be attributed, not reported as a generic made-with-AI tag.
# Corpus-measured 2026-07-23 (35 files).
p = tmp_path / "apple_cleanup.jpg"
p.write_bytes(
b'\xff\xd8\xff\xe1<x:xmpmeta photoshop:Credit="Apple Photos Clean Up" '
b"Iptc4xmpExt:DigitalSourceType=compositeWithTrainedAlgorithmicMedia></x:xmpmeta>\xff\xd9"
)
r = identify(p, check_visible=False, check_invisible=False)
assert r.is_ai_generated is True
assert r.platform == "Apple Photos (Clean Up AI edit)"
def test_flux_bfl_c2pa_png(self):
# flux-1.png: real Black Forest Labs FLUX.2 Playground output (signed C2PA).
r = identify(SAMPLES_DIR / "flux-1.png", check_visible=False)
+17
View File
@@ -777,6 +777,23 @@ class TestExifGenerator:
remove_ai_metadata(src, out)
assert exif_generator(out) is None
def test_apple_clean_up_removal_parity(self, tmp_path: Path):
# The "Apple Photos Clean Up" credit is an AI-edit VALUE under a
# non-AI key, so removal must drop it by value too (corpus 2026-07-23).
from PIL.PngImagePlugin import PngInfo
from remove_ai_watermarks.metadata import remove_ai_metadata
info = PngInfo()
info.add_text("Software", "Apple Photos Clean Up")
src = tmp_path / "apple.png"
Image.new("RGB", (64, 64)).save(src, pnginfo=info)
assert exif_generator(src) is not None
out = tmp_path / "clean.png"
remove_ai_metadata(src, out)
assert exif_generator(out) is None
def test_imagedescription_tag_ai_tool_detected(self, tmp_path: Path):
# ...and the EXIF ImageDescription field.
exif = piexif.dump(