Skip to content

feat: download RAW/HEIC images as JPEG or PNG - #157

Merged
vishwajeet-13 merged 2 commits into
bwhtech:developfrom
vishwajeet-13:feat/download-image-format-choice
Sep 7, 2026
Merged

feat: download RAW/HEIC images as JPEG or PNG#157
vishwajeet-13 merged 2 commits into
bwhtech:developfrom
vishwajeet-13:feat/download-image-format-choice

Conversation

@vishwajeet-13

@vishwajeet-13 vishwajeet-13 commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

What

RAW (.arw, .cr2, .cr3, .dng, .nef, .orf, .raf, .rw2) and HEIC/HEIF
downloads 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:

  • RAW → the camera's embedded preview (same path as open_raw_preview, which the
    in-app preview already uses), at full resolution.
  • HEIC/HEIF → decoded via pillow-heif (libheif ships in the wheel — no apt
    package).
  • JPEG is quality 90, alpha flattened onto white; PNG keeps alpha.

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.)

Asset action menu Review page
asset-menu-format-choice.png review-download-format-choice.png

Changes

Area
vms/image_export.py new — format detection, render + encode, download handler
vms/api.py download_converted_asset(asset_name, format) (whitelisted)
vms/review_api.py download_guest_converted_asset(asset_name, token, format) (guest, token-validated)
useDownload().downloadConverted fetch → blob → download, toast on failure
lib/fileType.ts isConvertibleStill client mirror of the server check
ReviewHeader.vue, useAssetActions.ts Download control becomes an Original/JPEG/PNG menu for convertible stills
pyproject.toml pillow-heif>=0.18.0

Each converted download writes a Download audit-log row.

Out of scope: bulk download and version download (still originals only).

Review fixes (dc1a55f)

  • Frappe Linter (semgrep) was failing on this PR — fixed with nosemgrep for the tempfile write and the guest whitelist, matching the conventions already in review_api.py / thumbnails.py.
  • Greptile P1 (HEIC blocked): heic,heif added to the default allowed_extensions + vms.patches.allow_heic_uploads for existing sites.
  • Greptile P1 (worker exhaustion): 300 MB source cap + @rate_limit on both endpoints (30/min authed, 15/min guest, keyed on asset).
  • Greptile P2 (fetch): removed — the converted download is now an <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.tsnavigator.clipboard.readText undefined 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 and
    encode (alpha handling) run unconditionally; RAW/HEIC decode tests are gated on
    VMS_TEST_ARW / VMS_TEST_HEIC (same pattern as test_raw_images.py).
  • ruff check / ruff format, yarn typecheck, yarn lint, yarn build clean.
  • HEIC render → JPEG + PNG round-trip verified locally against a generated .heif.
  • Not yet exercised end-to-end against a live R2-backed asset — needs a HEIC/RAW
    upload on a configured site.

🤖 Generated with Claude Code

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-apps

greptile-apps Bot commented Sep 7, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds on-demand JPEG and PNG downloads for RAW and HEIC/HEIF assets across authenticated asset actions and guest review pages.

  • Adds server-side RAW-preview and HEIC decoding, image encoding, audit logging, source-size limits, and request rate limits.
  • Adds format-selection menus and converted-download handling in the Vue frontend.
  • Adds HEIC/HEIF upload defaults and a registered migration for existing sites.
  • Adds image detection, filename, alpha-handling, and optional real-file conversion tests.
  • The previous HEIC upload-path issue is fixed by the new default and migration.
  • The previous direct-fetch data-layer issue is fixed by using a browser download through the v2 method route.

Confidence Score: 5/5

The 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

Important Files Changed

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.

Fix all with Greploop Fix All in Claude Code Fix All in Codex

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

Comment thread vms/image_export.py
Comment thread vms/image_export.py
from vms.r2 import generate_presigned_view_url
from vms.raw_images import is_raw, open_raw_preview

HEIC_EXTENSIONS = frozenset({".heic", ".heif"})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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

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!

Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread frontend/src/composables/useDownload.ts Outdated
Comment on lines +55 to +57
const res = await fetch(`/api/method/${method}?${params.toString()}`, {
credentials: 'include',
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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)

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!

Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Comment on lines +54 to +57
triggerDownload(
`/api/v2/method/${method}?${params.toString()}`,
convertedName(fileName, format),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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!

Fix in Claude Code Fix in Codex

@vishwajeet-13
vishwajeet-13 merged commit cdb0df9 into bwhtech:develop Sep 7, 2026
4 of 5 checks passed
vishwajeet-13 added a commit that referenced this pull request Sep 7, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant