Skip to content

feat: process GutenbergKit media uploads per the app's media settings - #23142

Draft
dcalhoun wants to merge 10 commits into
trunkfrom
feat/process-gutenberg-kit-media-uploads
Draft

feat: process GutenbergKit media uploads per the app's media settings#23142
dcalhoun wants to merge 10 commits into
trunkfrom
feat/process-gutenberg-kit-media-uploads

Conversation

@dcalhoun

@dcalhoun dcalhoun commented Jul 23, 2026

Copy link
Copy Markdown
Member

Description

Ref CMM-1249.

Device media picked in the GutenbergKit editor (the new, experimental block editor / NBE) currently uploads raw from the WebView straight to the WordPress REST API, bypassing the app's media settings that the legacy editor honors (image optimization/resizing, quality, EXIF location stripping, video optimization).

This PR integrates GutenbergKit's new MediaUploadDelegate (from GutenbergKit#357) so device uploads are processed natively per the app's media settings before upload, mirroring WordPress-iOS#25824. GutenbergKit runs a localhost relay server while the editor is open, hands each staged file to processFile, uploads via its default uploader, and relays WordPress's raw response to the editor verbatim — so attachment objects, media_details.sizes, blob-preview replacement, save-locking and error notices all behave exactly like a direct upload.

What GBKMediaUploadProcessor.processFile does (mirrors the iOS decision table, adapted to Android's pipeline)

  • Images — optimized via the existing WPMediaUtils.getOptimizedMedia (max dimension + quality from App Settings), with GPS EXIF stripped when the "Remove location" setting is on. The reported mime type/filename is corrected to the actual output format (non-PNG inputs, including HEIC, re-encode to JPEG). When processing would be a no-op (optimization off, no strip, no rotation), the original passes through untouched — no needless lossy re-encode.
  • Sideways-captured images — physically rotated for self-hosted sites when optimization is off (WP.com rotates server-side; parity with legacy, #5737).
  • GIFs — always pass through to preserve animation.
  • Videos — the free-plan 5-minute duration limit is always enforced; transcoding runs (via the existing WPVideoUtils/m4m path) only when the "Optimize videos" setting is on, keeping the transcoded file only when it's smaller than the original. Transcodes are serialized to bound codec/memory pressure. This matches Android's legacy behavior (where video optimization is opt-in), rather than iOS's always-transcode.
  • File types disallowed by the site plan (e.g. audio on free WP.com plans) are rejected up front with a localized message relayed to the editor as a notice.

The delegate is set unconditionally for the post/page GutenbergKit editor. If GutenbergKit's upload server fails to start, uploads degrade to the existing WebView path unchanged.

Testing instructions

All in the GutenbergKit editor (enable the experimental block editor for the site). Change settings between runs in App Settings → Media.

Image optimization:

  1. With "Optimize images" on, insert a photo wider than the max-size setting (e.g. > 2000px).
  • Uploaded image is resized to the configured max dimension and is a JPEG.
  1. Turn "Optimize images" off and "Remove location" off; insert a photo.
  • Uploaded bytes are identical to the original (no re-encode).

Location stripping:
3. Insert a JPEG, PNG, or WebP photo with GPS EXIF, with "Remove location" on (try both with optimization on and off).

  • The uploaded file has no GPS EXIF.

GIF:
4. Insert an animated GIF.

  • It uploads unchanged and still animates.

Video:
5. With "Optimize videos" on, insert a video wider than the width setting.

  • Uploaded video is a transcoded MP4 at the configured width.
  1. Turn "Optimize videos" off; insert a video.
  • The original video uploads.

Disallowed type:

  1. On a free WP.com site, insert a video or audio.
  • The editor shows a localized notice that the file type is disallowed.

dcalhoun and others added 6 commits July 23, 2026 10:15
Consumes the snapshot build of wordpress-mobile/GutenbergKit#357, which
adds the MediaUploadDelegate API for host-side media upload processing.
To be swapped to a tagged release before merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds video optimization and strip-image-location accessors to
AppPrefsWrapper, and a stripImageLocation passthrough to
MediaUtilsWrapper, so the upcoming GutenbergKit media upload processor
can read settings and strip GPS EXIF through injectable, testable
wrappers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements GutenbergKit's MediaUploadDelegate to process device media
picked in the experimental block editor before upload, honoring the
app's media settings the same way the legacy editor pipeline does:

- Images are optimized per the max size/quality settings via
  WPMediaUtils.getOptimizedMedia, with GPS EXIF stripped when the
  strip-location setting is on, and the reported mime type/extension
  corrected to the actual output format (non-PNG inputs, including
  HEIC, re-encode to JPEG).
- When processing would be a no-op, the original file passes through
  untouched, avoiding a needless lossy re-encode.
- Sideways-captured images are rotated for self-hosted sites when
  optimization is off (WP.com rotates server-side; issue #5737).
- GIFs always pass through to preserve animation.
- Videos enforce the free-plan 5-minute duration limit and transcode
  via WPVideoUtils/m4m only when the optimize-video setting is on,
  keeping the transcoded file only when smaller than the original.
  Transcodes are serialized to bound codec/memory pressure.
- File types disallowed by the site plan are rejected with a localized
  message relayed to the editor as a notice.

Nothing wires the processor yet; that lands separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds JVM unit tests for the processor's decision table: optimized
image output with corrected mime/extension (incl. HEIC to JPEG and PNG
passthrough), no-op short-circuit, GPS stripping onto the optimized
output and onto a copy when optimization is off, GIF passthrough,
self-hosted orientation fix, plan-disallowed type and over-limit video
rejections with localized messages, video passthrough when
optimization is disabled, and same-path optimization results treated
as unprocessed.

Also adds a File-based isProhibitedVideoDuration overload to
MediaUtilsWrapper so the processor avoids Uri.fromFile, keeping it
testable on the JVM.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sets the media upload delegate on GutenbergView so device media picked
in the experimental block editor is processed per the app's media
settings before upload. The fragment stores and forwards the delegate
following the existing setNetworkRequestListener pattern (GutenbergView
tolerates assignment before or after page load), and the activity
constructs the processor with its SiteModel and injected wrappers.

If GutenbergKit's upload server fails to start, uploads degrade to the
existing WebView path unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consumes the PR-build snapshot of WordPress-Utils-Android#156, which
stops ImageUtils.getImageOrientation from logging a spurious
"Volume data not found" error on every optimized upload staged in the
app cache dir (GutenbergKit native media uploads). To be swapped to a
tagged release once that PR merges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dangermattic

dangermattic commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
1 Message
📖 This PR is still a Draft: some checks will be skipped.

Generated by 🚫 Danger

@wpmobilebot

Copy link
Copy Markdown
Contributor

Project dependencies changes

list
! Upgraded Dependencies
org.wordpress.gutenbergkit:android:357-45219181e3aa60b5ce3119367758d90e6b6beb0b, (changed from v0.17.2)
org.wordpress:utils:156-2e542df55715dff22c21def058bea551ee9569d0, (changed from 3.14.0)
tree
 +--- project :libs:editor
-|    \--- org.wordpress.gutenbergkit:android:v0.17.2
+|    \--- org.wordpress.gutenbergkit:android:357-45219181e3aa60b5ce3119367758d90e6b6beb0b
 +--- project :libs:fluxc
-|    \--- org.wordpress:utils:3.14.0
-|         +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 -> 1.9.24 (*)
-|         +--- org.apache.commons:commons-text:1.10.0 -> 1.15.0
-|         |    \--- org.apache.commons:commons-lang3:3.20.0
-|         +--- com.android.volley:volley:1.2.0 -> 1.2.1
-|         +--- com.google.android.material:material:1.2.1 -> 1.14.0 (*)
-|         +--- androidx.swiperefreshlayout:swiperefreshlayout:1.1.0 -> 1.2.0 (*)
-|         +--- androidx.recyclerview:recyclerview:1.0.0 -> 1.3.2 (*)
-|         +--- org.greenrobot:eventbus:3.3.1 (*)
-|         \--- androidx.core:core-ktx:1.5.0 -> 1.16.0 (*)
+|    \--- org.wordpress:utils:156-2e542df55715dff22c21def058bea551ee9569d0
+|         +--- org.apache.commons:commons-text:1.10.0 -> 1.15.0
+|         |    \--- org.apache.commons:commons-lang3:3.20.0
+|         +--- com.google.android.material:material:1.2.1 -> 1.14.0 (*)
+|         +--- androidx.swiperefreshlayout:swiperefreshlayout:1.1.0 -> 1.2.0 (*)
+|         +--- androidx.recyclerview:recyclerview:1.0.0 -> 1.3.2 (*)
+|         +--- org.greenrobot:eventbus:3.3.1 (*)
+|         +--- org.greenrobot:eventbus-java:3.3.1
+|         +--- androidx.core:core:1.5.0 -> 1.16.0 (*)
+|         \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.4.10 (*)
-+--- org.wordpress:utils:{strictly 3.14.0} -> 3.14.0 (*)
++--- org.wordpress:utils:{strictly 156-2e542df55715dff22c21def058bea551ee9569d0} -> 156-2e542df55715dff22c21def058bea551ee9569d0 (*)
-\--- org.wordpress.gutenbergkit:android:v0.17.2 (*)
+\--- org.wordpress.gutenbergkit:android:357-45219181e3aa60b5ce3119367758d90e6b6beb0b (*)

@wpmobilebot

wpmobilebot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

App Icon📲 You can test the changes from this Pull Request in WordPress Android by scanning the QR code below to install the corresponding build.

App NameWordPress Android
Build TypeDebug
Versionpr23142-d70d4a0
Build Number1498
Application IDorg.wordpress.android.prealpha
Commitd70d4a0
Installation URL6oojm00a3mpp0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

wpmobilebot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

App Icon📲 You can test the changes from this Pull Request in Jetpack Android by scanning the QR code below to install the corresponding build.

App NameJetpack Android
Build TypeDebug
Versionpr23142-d70d4a0
Build Number1498
Application IDcom.jetpack.android.prealpha
Commitd70d4a0
Installation URL1ftfjjkihs19g
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

Copy link
Copy Markdown
Contributor

🤖 Build Failure Analysis

This build has failures. Claude has analyzed them - check the build annotations for details.

processVideo and processImage are guard-clause decision tables where
early returns read clearer than nesting; suppress ReturnCount to match
the existing house style rather than fragment the logic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 42.85714% with 52 lines in your changes missing coverage. Please review.
✅ Project coverage is 37.81%. Comparing base (64302cb) to head (d70d4a0).
⚠️ Report is 7 commits behind head on trunk.

Files with missing lines Patch % Lines
...android/ui/posts/editor/GBKMediaUploadProcessor.kt 45.88% 40 Missing and 6 partials ⚠️
.../org/wordpress/android/ui/prefs/AppPrefsWrapper.kt 0.00% 4 Missing ⚠️
...va/org/wordpress/android/util/MediaUtilsWrapper.kt 0.00% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##            trunk   #23142   +/-   ##
=======================================
  Coverage   37.81%   37.81%           
=======================================
  Files        2343     2344    +1     
  Lines      127397   127488   +91     
  Branches    17681    17704   +23     
=======================================
+ Hits        48170    48209   +39     
- Misses      75271    75317   +46     
- Partials     3956     3962    +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

dcalhoun and others added 3 commits July 24, 2026 15:06
EXIF_MIME_TYPES gated the copy-and-strip branch on formats that can
carry GPS, but androidx ExifInterface.saveAttributes() can only rewrite
JPEG, PNG, and WebP. The set omitted PNG (so location-bearing PNGs
uploaded un-stripped, a regression vs. the legacy pipeline) and included
HEIC/HEIF (where saveAttributes throws an IOException that stripLocation
swallows, uploading a still-geotagged copy while appearing to honor the
setting).

Correct the set to {JPEG, PNG, WebP} and cover the PNG-stripped and
HEIC-passthrough cases in the decision-table tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
transcodeVideo logs each caught exception via .message, so Detekt's
SwallowedException never fires — only TooGenericExceptionCaught does
(NullPointerException and Exception are both on its generic-names list).
Trim the suppression to the rule that actually triggers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GutenbergKit validates uploads in the WebView against the site's
allowedMimeTypes (fetched from /wp-block-editor/v1/settings and cached on
disk) before the request reaches this delegate, so it rejects most
disallowed types with its own localized message first. Document that this
app-side check is a fallback for the cache-miss/undefined-settings path
and can over-reject when its static MimeTypes table is stricter than the
server's cached list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants