(
rawBuffer: Buffer,
meta: { vibe: string; palette: string; promptUsed: string },
)
| 156 | * fires a throttled notifySystemError alert. |
| 157 | */ |
| 158 | export async function finalizePortrait( |
| 159 | rawBuffer: Buffer, |
| 160 | meta: { vibe: string; palette: string; promptUsed: string }, |
| 161 | ): Promise<GeneratePortraitResult> { |
| 162 | const badgedBuffer = await compositeAIBadge(rawBuffer); |
| 163 | |
| 164 | if (!isC2PASigningEnabled()) { |
| 165 | return { imageBuffer: badgedBuffer, promptUsed: meta.promptUsed }; |
| 166 | } |
| 167 | try { |
| 168 | const signed = signC2PA(badgedBuffer, { |
| 169 | claimGenerator: 'AAO Portrait Generator', |
| 170 | title: 'AAO Member Portrait', |
| 171 | softwareAgent: { name: GEMINI_IMAGE_MODEL, version: GEMINI_IMAGE_VERSION }, |
| 172 | attributes: { |
| 173 | vibe: meta.vibe, |
| 174 | palette: meta.palette, |
| 175 | prompt_sha256: createHash('sha256').update(meta.promptUsed).digest('hex'), |
| 176 | }, |
| 177 | }); |
| 178 | return { |
| 179 | imageBuffer: signed.signedBuffer, |
| 180 | promptUsed: meta.promptUsed, |
| 181 | c2pa: { signedAt: new Date(), manifestDigest: signed.manifestDigest }, |
| 182 | }; |
| 183 | } catch (err) { |
| 184 | const errorMessage = err instanceof Error ? err.message : String(err); |
| 185 | logger.error({ err, vibe: meta.vibe, palette: meta.palette }, 'C2PA signing failed for portrait'); |
| 186 | notifySystemError({ |
| 187 | source: 'c2pa-portrait-signing', |
| 188 | errorMessage: `Portrait signing failed (vibe=${meta.vibe}, palette=${meta.palette}): ${errorMessage}`, |
| 189 | }); |
| 190 | if (process.env.C2PA_STRICT === 'true') { |
| 191 | throw err; |
| 192 | } |
| 193 | return { imageBuffer: badgedBuffer, promptUsed: meta.promptUsed }; |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * Composite a small "AI" badge in the bottom-right corner of the portrait. |
no test coverage detected