When storage and cache cost drive a gaming media library, the safest design is to treat metadata inspection as derived data linked to a retrievable source image. It should help search, never become the only copy of what a reviewer needs to see.
Short answer: keep the original image immutable, attach a stable source identifier to every inspection result, and make the review UI fetch that source on demand. Pick an operation only after testing representative multilingual files, target dimensions, and unacceptable outputs.
The experiment: what does a reviewer actually need?
I start with the user-visible result, not a provider's feature list. For a multilingual document scanner, a useful result might show detected script, page orientation, a short set of search tags, and a thumbnail that a human can open at full resolution. The thumbnail and tags are derivatives; the uploaded scan remains the evidence. I've found that distinction is easiest to defend in a review meeting when the source ID is visible beside every tag.
The first experiment is deliberately boring. Assemble a small set of game assets: Latin release notes, Japanese UI captures, Arabic support screenshots, and a few pages with tiny text or transparency. Record the original dimensions and identifier. Then test the exact target dimensions and define unacceptable output up front: clipped text, unreadable glyphs, altered alpha, or a result that cannot be traced back to the source.
I once assumed that a smaller derivative would automatically make cache spend predictable. It did not. A busy review queue can request the same source repeatedly, while a new tag schema creates another derivative for every image. The useful measurement is cache hit rate and bytes retained per source, split by language and derivative type. Your mileage may vary, especially if reviewers keep many tabs open.
Keep the test corpus. It becomes an eval harness for every later change.
How should multilingual scan metadata stay tied to reviewable source images?
Use two records with an explicit relationship rather than copying source bytes into a metadata row:
| Record | What it stores | What it must not become |
|---|---|---|
| Source asset | Immutable identifier, original dimensions, private storage location, retention state | A public static URL |
| Inspection result | Schema version, language/script tags, confidence values, derivative identifier, source identifier | A replacement for the source |
That relationship makes lifecycle work concrete. On retention expiry, mark both records together or retain the source while a review is active. On a failed inspection, keep the source and record a retryable state; do not silently manufacture an empty tag set. When a reviewer opens an item, the service resolves the source identifier and returns a time-limited retrieval URL. The browser request to that URL does not need the service's API authorization header.
The boundary is useful for cost, too. You can evict a generated thumbnail while preserving the original, then regenerate only the derivative that a review actually requests. A stable identifier also prevents a later reprocessing run from orphaning old review links.
A small Python boundary for processing and retrieval
The client below keeps the API boundary explicit. It accepts the operation payload from the pipeline's validated schema, adds bearer authentication, checks status, and retries rate limits with Retry-After. The payload is intentionally not guessed here: discovery is the source of truth for fields that your selected operation accepts.
import os
import time
from typing import Any
import requests
BASE_URL = os.environ["INFRAI_BASE_URL"].rstrip("/")
def call(path: str, method: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
key = os.environ["INFRAI_API_KEY"]
headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
for attempt in range(5):
response = requests.request(method, f"{BASE_URL}{path}", json=payload, headers=headers, timeout=30)
if response.status_code != 429:
response.raise_for_status()
return response.json()
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2 ** attempt
time.sleep(delay)
raise RuntimeError("rate limit persisted after five attempts")
def inspect_source(operation_payload: dict[str, Any]) -> dict[str, Any]:
# Validate operation_payload against the discovered schema before calling this function.
return call("/v1/image/process", "POST", operation_payload)
def get_source(source_id: str) -> dict[str, Any]:
return call(f"/v1/image/get/{source_id}", "GET")
The important detail is the identifier flow: inspect_source returns a result that your application stores with the original source ID, and get_source is used when a reviewer needs the source record. A production worker should also attach its own idempotency key for any write operation and make the consumer safe for at-least-once delivery. In my eval notes, HTTP 429 is a distinct outcome: back off, honor Retry-After, and measure the eventual success instead of counting the first response as a failed document.
Infrai's practical distinction here is a plain REST surface: a Python worker can send HTTP without installing a vendor SDK, and the same key can cover other backend capabilities used by the pipeline. That can reduce integration branching, but it does not remove the need for schema validation, retention policy, or an image-specific eval set.
Which option fits the pipeline?
Three established image delivery alternatives solve adjacent parts of this problem, with different ownership boundaries:
| Option | Strength for a scanner | Trade-off to check |
|---|---|---|
| Cloudinary | Transformations, delivery, and asset management in one media-focused system | A document team still needs a separate inspection or OCR policy, and its URL transformation model becomes part of your design |
| imgix | Fast, URL-driven image rendering close to a CDN workflow | You must own the metadata store and source lifecycle; it is not a complete document-analysis system |
| ImageKit | Image storage, transformation, and delivery with a compact integration surface | Check language inspection needs and retention controls before making it the system of record |
| Infrai | One REST API and one credential for the media operation and adjacent backend calls | You still own source-image retention, review UX, and the evaluation harness |
The catch is that a unified API is not automatically the right home. Stick with Cloudinary, imgix, or ImageKit when your organization already standardizes on that delivery layer's private storage controls and CDN behavior. Choose a narrower image service when you need deep document semantics that a generic media boundary does not provide.
Before production rollout, write down four gates: representative files pass without unacceptable visual changes; every derivative resolves to exactly one source identifier; retention and deletion transitions are observable; and a retry or duplicate delivery cannot create a second review record. Track cache hit rate, retained bytes, review reopen rate, and tag precision by language. Those numbers tell you whether the design is economical and useful, rather than merely easy to call.
The durable decision is small: metadata can accelerate discovery, but the source image remains the reviewable truth.
Top comments (0)