# Local clinical photo/video intake

Use this reference when extending a private health dashboard with iPhone Camera Roll photos, HEIC/HEIF, HEVC/H.265 MOV/MP4, symptom images, or document-media previews.

## One shared trust boundary

Document intake and mobile capture must call the same media validator. Never fork separate MIME or limit logic.

Make the format decision from all applicable layers:

1. inspect magic bytes and BMFF `ftyp` brands;
2. perform a real bounded decode for images;
3. for videos, require local `ffprobe` to identify the container and an allowed `hevc` video stream.

Treat browser MIME, filename and extension as hints only. Reject AVIF/unknown BMFF brands, encrypted/corrupt containers, unknown codecs, excess streams and auxiliary content outside the explicit policy.

## Decoder readiness and provenance

- Pin Pillow and `pillow-heif` in a private runtime requirements file; install with `uv pip install --python <private-venv-python> -r <requirements>`.
- Register the HEIF opener centrally once.
- Importability is not readiness. At server and worker startup, synthesize a tiny HEIF image, encode it, reopen it through Pillow, fully load it and verify dimensions. Fail closed if this test fails.
- Persist technical provenance: Python, Pillow, pillow-heif, libheif/libde265 and ffmpeg/ffprobe versions. Do not persist source paths, original filenames, EXIF or GPS.
- FFmpeg uses `-version`, not necessarily `--version`; version probes must tolerate tool-specific flags.

## Stable originals and bounded child processes

- Quarantine directories are `0700`; files and archived originals are `0600`.
- Open originals with `O_NOFOLLOW`, verify owner/type/mode with `fstat`, hash from the stable descriptor, and copy from that descriptor.
- Keep the original byte-for-byte unchanged and SHA-256 stable. Never replace it with a normalized image or transcoded video.
- Perform image decoding in a subprocess with explicit wall-clock, CPU, address-space and output-file limits. Pillow decompression-bomb warnings are errors.
- Invoke ffprobe/ffmpeg only with fixed argument arrays and `shell=False`. Restrict input protocols to local file/pipe and disable stdin.
- Temporary derivative filenames may not reveal their output container. Add explicit FFmpeg muxer/codec arguments such as `-f image2 -c:v mjpeg` for posters and `-f mp4` for proxies.

## Safe derivatives

### Images

- Apply EXIF orientation before scaling.
- Decode only the primary HEIF image into the browser preview.
- Record frame/auxiliary counts and show a review warning for additional content; never imply those frames were reviewed.
- Render into a new RGB canvas and save without EXIF, GPS, XMP, ICC or embedded thumbnail data.
- OCR document images only from this normalized derivative. Symptom photos receive no OCR or medical interpretation.

### Videos

- Keep only HEVC video in a validated MOV/MP4 container; allow at most one optional explicitly allowlisted audio stream.
- Generate a metadata-free JPEG poster locally.
- If browser playback is required, generate a bounded H.264/yuv420p MP4 proxy with metadata removed; retain the HEVC original.
- If preview/proxy generation fails, keep the original private and mark preview failure. Do not mark it reviewed.

## Health semantics

- The user-selected health day is authoritative.
- EXIF and QuickTime creation timestamps are unconfirmed metadata only. They may be displayed as such but never become the medical event date automatically.
- Never auto-pair Live Photo HEIC and MOV components by filename or timestamp. Pair only through an explicit user action.
- Every media asset begins `unverified`. It may be linked to exactly one explicit domain owner (capture entry, document, examination day, etc.). No media upload writes canonical medical facts.

## Additive schema compatibility

Prefer additive media tables/columns and opaque media links. If a frozen legacy table has a restrictive semantic CHECK (for example, document type only `pdf|image`), do not rebuild the canonical table merely to add `video`. Preserve the compatible legacy value and derive the richer type from the additive media row in the V5 read model. Prove migration idempotency, integrity, foreign keys and restore from a copy before touching the private DB.

## V4 freeze and hash discipline

A hash is meaningful only with its absolute artifact path and algorithm. When hashes differ:

1. recover both absolute paths;
2. hash the same bound V4 runtime artifact before and after with SHA-256;
3. run the Git diff across all V4 source/artifact paths;
4. stop only if the bound artifact or V4 source really changed;
5. otherwise document that historical hashes referred to different files and establish one binding path as the baseline.

## Focused synthetic acceptance

Use no patient media. Generate local fixtures:

- HEIC and multi-frame HEIF with Pillow/pillow-heif;
- JPEG/PNG with EXIF/GPS, then verify the derivative has no EXIF;
- HEVC MOV/MP4 with FFmpeg `lavfi` color sources;
- H.264 MP4 as a forbidden-codec negative case;
- corrupt HEIC, foreign content under a media-looking name and sparse oversize files.

Assert original/derivative hashes differ while the original hash stays stable. Exercise the private day timeline, unverified status, 390 px overflow/touch targets, browser back/forward context, no browser storage and zero external requests. Keep browser additions to 5–7 scenarios and run only directly affected regressions before one private read-only smoke.
