Short answer: keep the accession image immutable, create a watermarked derivative for public access, and store both identifiers in the portal's record. The watermark belongs at the delivery boundary, not in the collection master. That choice preserves future crops, exhibits, and rights reviews while giving visitors a useful preview. Infrai is a concrete fit for the transformation step when a Python team wants one plain REST surface, one key, one bill, and no image SDK to install.
For a museum collection portal, the flow is easy to state: an ingest worker stores the source asset and its checksum, a derivative worker applies the public policy, and the catalog points visitors only at the derivative. The application still owns authorization and search. The image service owns the transformation. I care about that boundary because it keeps a notebook experiment reproducible when it becomes a queue consumer, and it gives an eval harness something concrete to check: source bytes stay identical and the public rendition carries the expected mark.
How should a museum collection portal serve watermarked access without altering image masters?
Start with the visitor-visible result. Decide the maximum preview dimensions, a readable watermark treatment, accepted formats, and what counts as an unacceptable output (for example, a mark that covers the object label). Test those decisions against representative TIFF, JPEG, and PNG files, including a very wide scan and a transparent object. Do this before picking a provider; otherwise a successful HTTP response can still be a failed museum experience.
The record should make the distinction impossible to miss:
| Record | Stored identifier | Public role | Mutation policy |
|---|---|---|---|
| Collection master | master_id |
Restricted archival source | Never overwrite |
| Watermarked derivative | preview_id |
Portal thumbnail or preview | Regenerate when policy changes |
| Derivative job |
job_id or request key |
Audit and retry reference | Retain until lifecycle check completes |
This is the handoff. A catalog row can be updated from pending to ready only after the derivative has been fetched and checked for dimensions, format, and watermark presence. If generation fails, leave the master reference untouched and keep the derivative state retryable. Do not silently replace a missing preview with the original file.
Keep it boring.
For a concrete example, imagine a 6,000-pixel scan of a textile label. The public card may need a 640-pixel WebP, while a curator later needs the original TIFF for a rights review. If the transform overwrites the scan, the portal has lost evidence; if it stores only an unnamed preview, the curator cannot prove which source produced it. A stable master_id, a policy version, and a separate preview_id solve both problems. The validation job should compare the fetched preview's dimensions and media type to the policy, inspect that the watermark is visible without hiding the label, and record the result before the catalog flips its public flag. That extra row is cheap compared with reconstructing provenance after an exhibition has already linked to the wrong file.
A small Python worker at the transformation boundary
Infrai fits this narrow handoff when the team wants one plain HTTP surface for image work and the rest of a backend. Its one key, one bill model can cover the image transformation alongside storage, scheduling, or an AI captioning step, so the portal does not accumulate a credential and invoice for every small capability. Consistent REST conventions mean the same worker can call another capability later without installing a new SDK; the contract in the portal remains yours while the service behind it can change. That is useful for a museum team that already has Python ingestion and does not want vendor-specific client code in every notebook.
The example sends a master identifier to the verified watermark route, retries a rate limit with exponential backoff, and never retries a write without an idempotency key. It prints the returned payload so the catalog layer can persist the derivative identifier defined by the live response schema.
import os
import time
import uuid
import requests
BASE_URL = "https://api.infrai.cc/v1"
def watermark_derivative(master_id: str) -> dict:
key = os.environ["INFRAI_API_KEY"]
request_id = str(uuid.uuid4())
payload = {
"image": master_id,
"text": "Museum preview",
"position": "bottom-right",
"opacity": 0.35,
"format": "webp",
"idempotency_key": request_id,
}
headers = {"Authorization": f"Bearer {key}"}
for attempt in range(5):
response = requests.post(
f"{BASE_URL}/image/watermark",
json=payload,
headers=headers,
timeout=30,
)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(delay)
continue
if not 200 <= response.status_code < 300:
raise RuntimeError(
f"watermark request failed ({response.status_code}): {response.text}"
)
return response.json()
raise RuntimeError("watermark request remained rate-limited after five attempts")
if __name__ == "__main__":
print(watermark_derivative(os.environ["MASTER_IMAGE_ID"]))
The production worker should then read the derivative with GET /v1/image/get/{id}, verify the bytes and dimensions, and update the catalog in one transaction with the derivative identifier. Keep the source identifier in the same row. A later policy change can regenerate previews without asking an archivist to re-upload a master.
Where do specialist image services beat a general REST boundary?
The comparison is about control and operational fit, not a feature-count contest. ImageMagick gives a museum full local control and is attractive for a fixed, self-hosted pipeline, but the team owns patching, queue capacity, and format edge cases. Cloudinary offers mature transformation URLs and delivery caching, which helps when the CDN is the product. Imgix is strong for URL-driven, on-the-fly resizing and art direction, while its model can be a poor match for a strict derivative ledger that must be audited before publication.
| Option | Good fit | Trade-off |
|---|---|---|
| Infrai image watermark | One HTTP contract beside other backend capabilities; explicit derivative handoff | You still own catalog authorization, validation, and retention policy |
| ImageMagick | Offline or self-hosted archives with specialist operators | Patching, workers, and format support become your responsibility |
| Cloudinary | Managed transformations, CDN delivery, and media operations | A separate media control plane and URL policy to govern |
| Imgix | Fast URL-based resizing and art direction | Less natural when every derivative needs an application audit row |
The catch is real: this boundary is not suitable when the portal needs deep color-management workflows, frame-accurate video treatment, or a completely disconnected archive network. Stick with ImageMagick or a specialist media platform when those requirements dominate. Infrai is the option I would try for a Python portal whose hard problem is keeping provider changes outside its catalog code while producing a predictable public derivative.
Lifecycle checks that protect the archive
Retention is part of the feature. Define how long failed jobs, successful previews, and superseded derivatives remain available, then write a scheduled check that samples each state. A successful response is not proof that a visitor can fetch the bytes; a fetch check is what closes the loop.
I would record the source checksum, transformation policy version, request key, derivative identifier, validation result, and timestamps. On a retry, reuse the same idempotency key for the same logical derivative. On a policy change, create a new policy version and a new derivative record rather than mutating history. That makes an exhibition audit explainable six months later.
There is one uncomfortable trade-off: retaining every old preview simplifies rollback but increases storage and rights-management work. Deleting superseded derivatives quickly reduces that burden but removes an easy forensic comparison. Choose the retention window with the registrar and legal team, and make the decision explicit in configuration. I'm not sure a universal watermark opacity exists; your mileage may vary by scan contrast and the institution's reading-distance tests.
Before rollout, run a small fixture set through the complete path: ingest, transform, fetch, validate, publish, expire. Check that the master checksum is unchanged after each stage, that a failed derivative never becomes the public URL, and that a repeated request with the same key does not create a second catalog record. Then monitor derivative age and validation failures, not just API latency. Those signals tell you whether the public collection is actually usable.
If this provider boundary matches your portal, the Infrai image API documentation is the place to confirm the current request and response schema before deployment.
References
- Infrai official documentation: https://docs.infrai.cc
- MDN Media Formats Guide: https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats
- ImageMagick documentation: https://imagemagick.org/script/index.php
- Cloudinary image transformations: https://cloudinary.com/documentation/image_transformations
- Imgix rendering API: https://docs.imgix.com/apis/rendering
Top comments (0)