Repository navigation
Media: prompt to convert animated GIF uploads to video - #80075
adamsilverstein wants to merge 20 commits into
Conversation
Follow-up to #78410 addressing design feedback in #79787: converting an uploaded animated GIF to a video changes the media type, softens the image, and swaps the block (losing image tools), so it should be the author's explicit choice rather than an automatic swap. - Stop automatically replacing the Image block with a Video block when a GIF upload's sideloaded video companion becomes available. - Remove the bespoke 'Display as video' / 'Display as GIF' toolbar buttons. - Offer the conversion through the block Transforms API instead: an animated GIF Image block (whose companion video exists) can transform to the Video block's GIF variation via the block switcher, and back to an Image block. The switcher also handles disabled target blocks and undo out of the box. The companion video is still transcoded and sideloaded at upload time; only the block swap became opt-in.
The spec now proves the upload keeps the Image block (no automatic swap), then exercises the round trip through the block switcher: Image to the Video block's GIF variation and back to the original GIF. Wait for the editor to finish setting up before inserting the block (an insert dispatched during setup is wiped by the initial blocks reset) and poll for the sideloaded companion in the attachment metadata, which is recorded slightly after the upload queue drains. Also point a stale upload-media comment at the new transform location.
In prompt mode (gifConvert: 'prompt'), an animated GIF uploads as a plain image and no companion video is transcoded automatically. Instead the store keeps a GifConversion record (attachment ID + original file) so the host application can ask the user first and trigger the conversion via the new resolveGifConversion() private action, which reuses the exact companion sideload flow. This enables an upload-time prompt in the editor per the feedback on #79787 while leaving automatic (true) and disabled (false) behavior unchanged for other consumers.
Adds the upload-time prompt requested in #79787: uploading an animated GIF keeps the upload non-blocking (the GIF uploads as a plain image) and asks whether to convert it to the Video block's GIF variation, steering towards conversion as the primary, focused action. A 'Remember my choice' checkbox persists the answer to the new core/media animatedGifUploads preference ('ask' | 'gif' | 'video'), which is also editable under Preferences > Media. A remembered 'video' resolves conversions automatically; the resulting block swap runs the regular Image to Video block transform, matching its availability rules.
Covers: prompt mode records a pending conversion instead of enqueueing the transcode sideload; resolveGifConversion discards on 'gif', enqueues the mirrored companion sideload on 'video', and ignores non-pending records; the reducer add/update/remove cases; and cancellation cleanup for the original upload, the requested transcode, and (negatively) an unrelated child sideload.
…t item A sideloaded companion only becomes part of the attachment once a parent item's Finalize operation reports it via mediaFinalize() - sideloads merely accumulate their result on their parent. The initial resolveGifConversion() implementation reused the long-gone original upload item as parentId, so the transcoded video uploaded but was never recorded on the attachment and the editor's block swap never fired. Queue a bare parent item whose only operation is Finalize instead. The existing machinery does the rest: the transcode sideload and its poster sibling accumulate on it, the Finalize gate waits for both, and the conversion record is marked converted only after finalization, when the refetched attachment actually carries animated_video. Also updates the e2e suite for the prompt flow: convert with round-trip transform back, decline skips the transcode, remember stops prompting, and a remembered 'video' converts automatically.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Another challenge is transparency: we don't know if the GIF has transparency until we at least partially process it - and video conversion is only offered if there is no transparency detected since video can't represent transparency well. So I think we will need to at least process the gif a bit to know whether we can convert it to a video. I already avoid doing the video transcode if the user selects no or hits escape. That said, I think we could do what you suggest and show the modal before doing the upload - although since the GIF always gets uploaded even if you convert to video so I'm not sure why waiting to upload makes sense here. I think what you are really after is a more of a different feeling...
so maybe instead of
we try this: As soon as the gif is dropped we determine if it is a conversion candidate, showing the modal immediately if so while the upload continues in the background. maybe avoid showing the upload progress snackbar in this case since its distracting.
to me this feels important, as a hypothetical user who regularly uploads GIFs, I want WordPress to remember my conversion preference. I can add a settings under Preferences->Media. |
The conversion record that drives the editor prompt was created in generateThumbnails(), i.e. only after the GIF finished uploading, so the modal appeared with a noticeable delay after the drop. Create it in prepareItem() instead, right after the animated/transparency checks pass: both only read the local file bytes, so the prompt can appear as part of the drop action while the upload continues in the background. Since no attachment exists at that point, records are now keyed by the original upload item ID instead of the attachment ID, and the attachment ID is stamped onto the record once the upload completes. An answer that arrives while the upload is still running marks the record 'accepted'; generateThumbnails() then runs the transcode as a regular companion sideload of the still-pending item (single Finalize, no synthetic parent needed). The bare-finalize-parent path remains for answers that arrive after the upload finished, with the parent now carrying the record key via gifConversionItemId.
The prompt now opens during the upload, so the progress snackbar would pop up behind it and compete for attention. Hide (and retract) the notice while the prompt is visible; it comes back if uploads are still running once the prompt is answered. Audible announcements are kept so screen reader users still hear about the upload itself.
The prompt now opens while the GIF is still uploading; verify the upload progress and completion snackbars stay hidden while it is open.
The base PR #80072 was squash-merged to trunk with a final change this branch predates: the Video-to-Image transform back was removed (undo is the way back), along with utils/gif-conversion.js, the Video block prefetch, and their tests. Take trunk's version of all block-library files (this PR never touches block-library) and port the prompt e2e's round-trip section to undo semantics accordingly.
|
Size Change: +1.94 kB (+0.03%) Total Size: 7.72 MB 📦 View Changed
|
|
Flaky tests detected in 3709fd2. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/29437238650
|
There was a problem hiding this comment.
Conceptually I like the idea of showing a modal to confirm what the user intends to do when they go to upload a media item (in this case gifs). A few thoughts on the approach code-wise:
- In other discussions it was brought up the idea that we might eventually have a general purpose modal that shows up for lots of different upload / drag and drop cases. So, how might we structure this for the idea that there could be many different kinds of prompts, not just a GifConversionPrompt?
- Currently this this code subscribes to some stuff to determine whether or not to show the modal. This might be unnecessary most of the time as we only need to open the modal in response to something that's happening based on a user interaction (in this case uploading a file). So an option might be to trigger the modal to open via an action — I'm wondering if that could simplify things a little? The potential downside is figuring out the right shape of how we want to open the modal.
- The above question leads me to: should we consider what a general upload / drag and drop prompt might look like, and what the shape of data it needs might be?
- Also, if we use an imperative action to open a prompt, I'm wondering if that'll bypass the need to have gifConversions in state?
These are just observations from having a quick look over the code, by the way — so consider them thoughts rather than a review. I think my main hesitation over this PR is just that it's making lots of decisions quite quickly for this particular feature (nice work there), but I'd really like us to also consider how we'll eventually move toward some of the ideas @jasmussen raised about other kinds of drag-and-drop modal prompts.
We don't need to solve it all just yet — but getting a sense of how we might step in that direction, and what the mechanism is for triggering the prompt, might help here.
Very good question to ask. This needs a little design work, for me coming to mind, keep it concise, and make the buttons really big, multiline buttons. I feel like @fcoveram might have instincts since he has worked closely with the media work overall. One thing stands out though: such a dialog cannot be dismissed with a checkbox. It should always be there. Or, probably at least. I do wonder if this is one of those cases where we should land this PR without the "remember my choice" options: extract those options to a separate PR, and come back to them only if feedback tells us users really want it? |
|
On a side note, I am working on showing video conversion progress (since it can take some time for larger GIFs) in #80325 and would appreciate some design feedback about how/where to show progress. Since we know the conversion progress exactly I'm thinking we can do better than the current long running spinner for conversions.
Good point @jasmussen - I instinctively wanted to add this for completeness, but its not really needed and complicates the PR unnecessarily. I agree we can add this later and will remove for now.
OK |
The prompt now always appears for each animated GIF upload rather than offering to remember the answer. Per PR discussion, a per-upload conversion is a genuine choice, not a nag to silence with a checkbox; a remembered preference, if wanted, is better revisited separately once the general drag-and-drop prompt direction is settled. Removes the CheckboxControl and the Preferences > Media 'Animated GIF uploads' SelectControl, along with the core/media animatedGifUploads preference plumbing. gifConvert is now always 'prompt'. The store's prompt/accepted machinery is unchanged.
|
Removed "Remember my choice" in 3709fd2 as discussed with @jasmussen: the checkbox, the Preferences → Media "Animated GIF uploads" setting, and the whole @andrewserong thanks for the architectural read - a few honest thoughts:
So: I'd love to keep this focused, and treat the general drag-and-drop prompt (imperative trigger + shared data shape) as the next step with design input. Does that split sound reasonable? |
…oad-prompt # Conflicts: # packages/block-editor/src/components/provider/use-media-upload-settings.js
The lint-staged formatter had reflowed the 'needs:' arrays in upload-release-to-plugin-repo.yml. This is unrelated to the GIF conversion prompt; restore the file to match trunk. Committed with --no-verify because the format hook would otherwise re-apply the reflow.
|
@swissspidy good catch on the workflow file - that was an accidental |
I've started to break this out separately so we can discuss the architecture/design. |
Introduce a store-level primitive for asking the user to decide something
about an upload, decoupled from any specific upload lifecycle:
- uploadPrompts state slice of { id, type, itemId?, data? } records
- private requestUploadPrompt(descriptor) / resolveUploadPrompt(id) actions
- private getUploadPrompts() selector
The flow that needs an answer requests a typed prompt; a host application
renders the UI matching 'type' and resolves it once the user answers. This
is the shared seam for upload/drag-and-drop prompts. GIF conversion becomes
its first consumer in a follow-up commit; the gifConversions lifecycle
records stay as-is.
…ction
Two changes to prepareItem's animated-GIF handling:
- Skip conversion for multi-file drops. A multi-file drop is implicitly a
gallery, and a Video block cannot be inserted into a gallery, so an
animated GIF that arrives as part of a batch now uploads as a plain image
with no conversion offered — matching the block switcher transform's
existing gallery guard, and avoiding a prompt (and wasted transcode) that
could never result in a swap. A lone GIF still converts.
- In prompt mode, dispatch requestUploadPrompt({ type: 'gif-conversion' })
right after recording the pending conversion, so the modal is triggered by
an explicit action rather than the editor deriving visibility from the
gifConversions lifecycle records.
Replace the editor's state-derived GIF prompt with a generic host driven by the uploadPrompts primitive, so the modal opens in response to an explicit requestUploadPrompt() action rather than the editor observing the upload lifecycle records: - UploadPromptHost (always mounted, generic) renders the pending prompts, mapping each prompt's 'type' to a component. GIF conversion is the only registered type today; a general drag-and-drop prompt would register here. - GifConversionPrompt becomes a transient, single-item modal rendered by the host while a 'gif-conversion' prompt is pending; on answer it resolves the gif conversion and dismisses the prompt. - GifConversionBlockSwapper (always mounted, separate) owns the completion side effect: it swaps Image->Video once the transcode finishes, which happens after the prompt has closed, so it cannot live in the prompt. - Snackbar suppression now keys off useHasActiveUploadPrompt() (any prompt open) instead of reaching into the gif conversion records. The gifConversions lifecycle records are unchanged; only the modal-open and snackbar-suppression paths moved onto the generic primitive.
…t dispatch - Add an e2e case: dropping multiple animated GIFs into a Gallery never shows the conversion prompt and leaves them as Image blocks (no transcode). - Add requestUploadPrompt to the private ActionCreators type so prepareItem's dispatch.requestUploadPrompt() call typechecks.
…ow-up Remove the multi-file drop gate from prepareItem. Reliably detecting that an upload belongs to a gallery is not possible at the upload-store layer: upload paths batch inconsistently (a one-shot multi-file upload is a single batch, but the Gallery block uploads each file separately), and the authoritative signal - the target block context cannot hold a Video block - lives in the editor, not the store. A gallery-context gate belongs in the editor layer and will be a follow-up. The block switcher transform's canInsertBlockType guard already prevents an actual Image->Video swap inside a gallery, so no broken conversion can occur in the meantime. The action-triggered prompt itself is unchanged (requestUploadPrompt on drop).
|
@andrewserong I took your "trigger the modal via an action" point and built it out, as a small reusable primitive rather than gif-specific glue so a future general drag-and-drop prompt can adopt it directly. Pushed as a few commits on top. What changed
On removing On the multi-drop / gallery case (from a separate thread): I tried suppressing the prompt for multi-file drops in the upload store, but it's not reliably detectable there — upload paths batch inconsistently (a one-shot multi-file upload is a single batch, but the Gallery block uploads each file separately), and the authoritative "this can't become a Video block" signal lives in the editor's block context. So I've pulled that out and will do it properly as a follow-up at the editor layer (gating on Convert/decline flows verified green via the e2e; unit + typecheck clean. Happy to iterate on the primitive's shape. |
|
Thanks for the updates here!
I haven't taken a close look at the code again just yet, but I did want to mention that I'm a little cautious about the inclusion of the modal at this stage due to the current hanging / conversion timing issue being looked at in: #80260 (comment) Given that we're past Beta 1 and that the feature itself is still buggy, would it be worth parking adding a conversion modal for 7.1? Basically, I'm wondering if we should try to keep things as invisible to the user for 7.1, and once we're more confident in the feature, expose it a little more deliberately in 7.2? I thought it worth asking the question now rather than spending too much time polishing something if it's not going to be included in the release. (I don't have a strong opinion here, just mindful of whatever's going to help us release things smoothly) |
Its fine to leave this off for now, I'd like to get more design review as well. |
|
Apologies for chiming late here, but I need to disagree with this idea. I don’t think this is the right path for suggesting content changes. Using a modal to recommend a block transformation interrupts the UX abruptly by forcing users to make a decision that could be intentionally triggered. In a way, it feels like infantilizing users as if they are not aware of what media file they’re adding. I fully agree with the idea of suggesting a block transformation, but the friction should be less prominent. I’m drawn to add a suggestion in context, by either an action in the toolbar, as part of 58418 discussion, or a dismissible |
I appreciate your careful attention to the UX. One of the sources my proposal here come from older discussion in the make/core space, slack chats, and older GitHub chats where @mtias suggested we should generally do more with providing you choice when you upload material to WordPress. This is to leverage the broad features of WordPress itself and its ability to handle many different filetypes, and notably the conversations zeroed in on MD files, which you might both want to add as files you can download, or you might want to insert their content inside a post. OR, if you drop an MD file in the Posts list, we can offer to convert it to a draft post, because WordPress knows how to handle drafts. This is proposed less as intentional friction, and more as ambiguating between choices with the intent to be helpful. It's both a "did you know" on features you might not have been aware of, but also simply offering an upfront choice where ambiguity is present, rather than making that choice in advance. If I read you correctly, your concern is mostly with the GIF detection: whether we should offer a conversion offer on upload or after. I agree this is a bit of a gray-area, since the choice is less: do you want to embed as a link or upload. Media you virtually always want to embed. I guess that also translates to concern about automatically converting to video. I could agree a notice in the image inspector is also a good idea to explore. But in case your concern is with on-upload modals in general, including for other filetypes, let's connect, I think there's some nuance I would like to relay and I believe I might convince you it can be a beautiful experience. |
|
Thanks for the feedback on this exploration @jasmussen and @fcoveram. Closing this out for now. |


What?
Closes #80136.
Adds the upload-time prompt for animated GIFs proposed by @jasmussen in #79787: uploading an animated GIF asks whether to convert it to a video (the Video block's GIF variation) or keep it as a GIF. Converting is the steered default.
Stacked on #80072 (opt-in block transforms); only the last commits are new.
Why?
#80072 made the conversion opt-in via the block switcher, but the block switcher is a discovery-dependent surface. The prompt from the issue discussion makes the choice explicit at the moment it matters - upload time - while still leaving the author in control:
The prompt appears for every animated GIF upload. An earlier iteration added a "Remember my choice" checkbox and a Preferences → Media setting; per the discussion below that was removed - a per-upload conversion is a genuine choice rather than a nag to silence, and a remembered preference (if it turns out users want it) is better revisited separately once the direction for a general drag-and-drop prompt is settled.
The prompt is opened by an explicit action, not by the editor observing the upload pipeline. This addresses @andrewserong's review point and is built as a small, reusable primitive so a future general upload/drag-and-drop prompt can adopt it:
@wordpress/upload-mediagains a genericuploadPromptsstore slice with privaterequestUploadPrompt( { type, itemId, data } )/resolveUploadPrompt( id )actions and agetUploadPrompts()selector. The flow that needs an answer requests a typed prompt; the record is deliberately decoupled from any specific upload lifecycle. GIF conversion is its first consumer.@wordpress/editorrenders prompts through a genericUploadPromptHostthat maps each prompt'stypeto a component (only'gif-conversion'today). Adding a new prompt later = register a type + component and callrequestUploadPrompt().Flow, end to end:
@wordpress/upload-media: thegifConvertsetting accepts a new'prompt'value. In prompt mode, as soon as a drop passes the animated/opacity checks (both read only the local file bytes),prepareItemrecords a pendingGifConversionlifecycle record and dispatchesrequestUploadPrompt( { type: 'gif-conversion', itemId } )— so the modal opens immediately while the GIF uploads as a plain image in the background (the upload is never blocked).resolveGifConversion( itemId, 'video' | 'gif' )then discards the record or enqueues the exact same TranscodeGif companion sideload the automatic mode uses (mid-upload answers ride the running upload; post-upload answers use a bare finalize parent).true/falsebehavior is unchanged for other consumers.@wordpress/editor:UploadPromptHostrenders a transient, single-GIFGifConversionPromptmodal while its prompt is pending; on answer it resolves the conversion and dismisses the prompt. A separate, always-mountedGifConversionBlockSwapperowns the completion side effect — once the companion video is recorded it swaps matching Image blocks via the regular Image → Video transform from GIF to video conversion: make it opt-in. Switching via block transforms #80072 (switchToBlockType+ itscanInsertBlockTypeguard). The swap is deliberately separate from the prompt because the transcode finishes after the modal has closed. The upload progress snackbar is suppressed while any prompt is open (useHasActiveUploadPrompt).Declining ("Not now") skips the transcode entirely rather than hiding its result, so no CPU or storage is spent on a declined conversion. Consequence: such an image won't offer the block-switcher Video transform (it's gated on the companion existing) - converting later means re-uploading. An on-demand transcode from the switcher could be a follow-up.
Testing Instructions
media_detailshas noanimated_video).Screenshots or screencast
The prompt after uploading an animated GIF (the upload itself already completed - it is never blocked by the dialog):