feat: download RAW/HEIC images as JPEG or PNG - #157
Conversation
The download control on a RAW (.arw, .cr2, …) or HEIC/HEIF asset now offers Original / JPEG / PNG. JPEG/PNG are re-encoded server-side from the camera's embedded preview (RAW) or a libheif decode (HEIC), at full resolution, and streamed back with Content-Disposition: attachment. Other files download unchanged. - vms/image_export.py: format detection, render + encode, download handler - vms.api.download_converted_asset / vms.review_api.download_guest_converted_asset - useDownload().downloadConverted + isConvertibleStill mirror on the client - ReviewHeader and the asset action menu gain the format submenu - pillow-heif dependency for HEIC decode Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N6kPRDGo6QbcVcc8DPkMTf
Greptile SummaryThe PR adds on-demand JPEG and PNG downloads for RAW and HEIC/HEIF assets across authenticated asset actions and guest review pages.
Confidence Score: 5/5The PR appears safe to merge, with a non-blocking converted-download error-reporting issue that can leave users with an error payload named as an image. The previous HEIC upload and frontend data-layer findings are addressed, and the earlier resource-exhaustion thread was manually resolved without explanation. The remaining new issue affects failure feedback rather than successful conversion behavior or access control. Files Needing Attention: frontend/src/composables/useDownload.ts
|
| Filename | Overview |
|---|---|
| vms/image_export.py | Implements bounded source retrieval, RAW/HEIC rendering, JPEG/PNG encoding, audit logging, and attachment responses. |
| frontend/src/composables/useDownload.ts | Initiates converted downloads through the v2 method route, but server failures are no longer surfaced through application error handling. |
| vms/review_api.py | Adds a token-validated and rate-limited guest conversion endpoint. |
| vms/api.py | Adds an authenticated and rate-limited converted-download endpoint. |
| vms/patches/allow_heic_uploads.py | Migrates existing VMS settings to permit HEIC and HEIF uploads. |
| vms/video_management_solution/doctype/vms_settings/vms_settings.json | Adds HEIC and HEIF to the default upload-extension policy. |
| frontend/src/components/review/ReviewHeader.vue | Adds Original, JPEG, and PNG choices for convertible still images on review pages. |
| frontend/src/components/assets/useAssetActions.ts | Adds equivalent format choices to authenticated asset actions. |
| vms/tests/test_image_export.py | Covers detection, output naming, alpha behavior, and optionally real RAW/HEIC decoding. |
Prompt To Fix All With AI
### Issue 1
frontend/src/composables/useDownload.ts:54-57
**Conversion Errors Download as Images**
When conversion fails because the token expired, the asset is oversized, R2 is unavailable, or decoding fails, this anchor-based request cannot inspect the HTTP response or show an error toast. The browser may download the Frappe error response under the requested `.jpg` or `.png` filename after telling the user the conversion is being prepared, leaving them with a corrupt-looking file and no actionable feedback.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (2): Last reviewed commit: "fix: address review — semgrep, HEIC allo..." | Re-trigger Greptile
| from vms.r2 import generate_presigned_view_url | ||
| from vms.raw_images import is_raw, open_raw_preview | ||
|
|
||
| HEIC_EXTENSIONS = frozenset({".heic", ".heif"}) |
There was a problem hiding this comment.
The conversion code supports HEIC and HEIF, but the default upload extension allowlist still omits heic and heif. On normally configured sites, the upload API rejects these files before they can reach the new conversion feature. Add both extensions to the default and provide an upgrade path for existing VMS Settings records.
Knowledge Base Used: Platform APIs and operations
Prompt To Fix With AI
This is a comment left during a code review.
Path: vms/image_export.py
Line: 13
Comment:
**HEIC uploads remain blocked**
The conversion code supports HEIC and HEIF, but the default upload extension allowlist still omits `heic` and `heif`. On normally configured sites, the upload API rejects these files before they can reach the new conversion feature. Add both extensions to the default and provide an upgrade path for existing `VMS Settings` records.
**Knowledge Base Used:** [Platform APIs and operations](https://app.greptile.com/bwh-tech/-/custom-context/knowledge-base/bwhtech/vms/-/docs/platform-api-and-operations.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in dc1a55f — added heic,heif to the default allowed_extensions in vms_settings.json and a vms.patches.allow_heic_uploads patch that extends existing VMS Settings records (inserts before arw, skips if already present).
| const res = await fetch(`/api/method/${method}?${params.toString()}`, { | ||
| credentials: 'include', | ||
| }) |
There was a problem hiding this comment.
This code calls fetch directly through /api/method/.... That violates the repository directive requiring frontend data calls to use frappe-ui's useCall, useList, or useDoc APIs through /api/v2/method/... or /api/v2/document/.... This repository requirement must be satisfied before merging while preserving the binary download behavior.
Context Used: CLAUDE.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/composables/useDownload.ts
Line: 55-57
Comment:
**Download bypasses data layer**
This code calls `fetch` directly through `/api/method/...`. That violates the repository directive requiring frontend data calls to use frappe-ui's `useCall`, `useList`, or `useDoc` APIs through `/api/v2/method/...` or `/api/v2/document/...`. This repository requirement must be satisfied before merging while preserving the binary download behavior.
**Context Used:** CLAUDE.md ([source](https://github.com/bwhtech/vms/blob/develop/CLAUDE.md))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in dc1a55f — removed the fetch(). The converted download is now a plain <a> navigation to /api/v2/method/... (same pattern as the existing R2 triggerDownload), with a "Preparing …" toast for feedback. It's a browser navigation that streams a file attachment, not a data call, so useCall/useList/useDoc don't apply — those can't stream a binary download to disk.
- semgrep: nosemgrep for the tempfile write and the guest whitelist, matching the conventions already in review_api.py / thumbnails.py - HEIC uploads: add heic,heif to the default allowed_extensions and a patch to extend existing VMS Settings records - conversion DoS surface: cap the source at 300 MB and rate-limit both endpoints (30/min authed, 15/min guest, keyed on asset) - frontend: drop the direct fetch(); the converted download is now an <a> navigation to /api/v2/method/... with a "Preparing …" toast - guest endpoint: token is optional so a missing token is a clean 401 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N6kPRDGo6QbcVcc8DPkMTf
| triggerDownload( | ||
| `/api/v2/method/${method}?${params.toString()}`, | ||
| convertedName(fileName, format), | ||
| ) |
There was a problem hiding this comment.
Conversion Errors Download as Images
When conversion fails because the token expired, the asset is oversized, R2 is unavailable, or decoding fails, this anchor-based request cannot inspect the HTTP response or show an error toast. The browser may download the Frappe error response under the requested .jpg or .png filename after telling the user the conversion is being prepared, leaving them with a corrupt-looking file and no actionable feedback.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/composables/useDownload.ts
Line: 54-57
Comment:
**Conversion Errors Download as Images**
When conversion fails because the token expired, the asset is oversized, R2 is unavailable, or decoding fails, this anchor-based request cannot inspect the HTTP response or show an error toast. The browser may download the Frappe error response under the requested `.jpg` or `.png` filename after telling the user the conversion is being prepared, leaving them with a corrupt-looking file and no actionable feedback.
**Knowledge Base Used:**
- [Platform APIs and operations](https://app.greptile.com/bwh-tech/-/custom-context/knowledge-base/bwhtech/vms/-/docs/platform-api-and-operations.md)
- [Media review and annotation](https://app.greptile.com/bwh-tech/-/custom-context/knowledge-base/bwhtech/vms/-/docs/review-and-annotation.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
The format menu shipped in #157 only reached the review page and the asset action menu — a guest on a /vms/shared/folder or /vms/shared/:project link could still only download a RAW/HEIC file in its original format. - vms.api.download_shared_converted_asset: guest endpoint, share-token scoped (same validation as get_shared_asset_download_url), 300 MB cap + 15/min rate limit via serve_converted_download - SharedProjectPage: per-asset Download becomes Original / JPEG / PNG for RAW/HEIC stills; "Download all" stays originals - MediaPreviewDialog: optional downloadMenu prop renders the same choice in the preview header Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N6kPRDGo6QbcVcc8DPkMTf
What
RAW (
.arw,.cr2,.cr3,.dng,.nef,.orf,.raf,.rw2) and HEIC/HEIFdownloads now offer a format choice: Original / JPEG / PNG. Ordinary images and
videos are unchanged.
JPEG/PNG are produced server-side and streamed back with
Content-Disposition: attachment:open_raw_preview, which thein-app preview already uses), at full resolution.
pillow-heif(libheif ships in the wheel — no aptpackage).
Screenshots
Format menu on a RAW/HEIC asset — the action menu and the guest review page. (Preview area is blank only because the local test site has no R2 configured.)
Changes
vms/image_export.pyvms/api.pydownload_converted_asset(asset_name, format)(whitelisted)vms/review_api.pydownload_guest_converted_asset(asset_name, token, format)(guest, token-validated)useDownload().downloadConvertedlib/fileType.tsisConvertibleStillReviewHeader.vue,useAssetActions.tspyproject.tomlpillow-heif>=0.18.0Each converted download writes a
Downloadaudit-log row.Out of scope: bulk download and version download (still originals only).
Review fixes (dc1a55f)
nosemgrepfor the tempfile write and the guest whitelist, matching the conventions already inreview_api.py/thumbnails.py.heic,heifadded to the defaultallowed_extensions+vms.patches.allow_heic_uploadsfor existing sites.@rate_limiton both endpoints (30/min authed, 15/min guest, keyed on asset).<a>navigation to/api/v2/method/...with a "Preparing …" toast.Playwright E2E ("UI Tests") is red, but it fails the same way on
develop(paste-upload.spec.ts→navigator.clipboard.readTextundefined in headless CI). Unrelated to this change; nothing here touches the paste/clipboard flow.Testing
vms/tests/test_image_export.py— new. Format detection, filename rewriting andencode (alpha handling) run unconditionally; RAW/HEIC decode tests are gated on
VMS_TEST_ARW/VMS_TEST_HEIC(same pattern astest_raw_images.py).ruff check/ruff format,yarn typecheck,yarn lint,yarn buildclean..heif.upload on a configured site.
🤖 Generated with Claude Code