Client-side media: convert HEIC/HEIF image sequences (Live Photo / burst) to video - #79647
Client-side media: convert HEIC/HEIF image sequences (Live Photo / burst) to video#79647adamsilverstein wants to merge 5 commits into
Conversation
|
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. |
|
Size Change: +2.03 kB (+0.03%) Total Size: 7.68 MB 📦 View Changed
|
7f7f045 to
a5c4ac7
Compare
Add parseHeicSequence() to extract the temporal HEVC frames from an image-sequence file (msf1 brand — Apple Live Photo HEVC sequences and Android bursts). Unlike still HEIC, sequences store frames as samples of a track in the moov/mdat boxes, so this demuxes the stbl sample tables (stsd/hvcC, stsz, stsc, stco/co64, stts, stss) into individual access units ready for the WebCodecs VideoDecoder. Tested against a real 120-frame 256x144 25fps msf1 fixture.
Refactor the mediabunny encode loop into a shared encodeFramesToVideo helper and add convertHeicSequenceToVideo: it decodes the demuxed HEVC frames with the WebCodecs VideoDecoder (pipelined with a bounded look-ahead so a long sequence never holds all its full-res frames at once) and re-encodes them to MP4/WebM, exactly like the GIF path but sourced from a VideoDecoder instead of an ImageDecoder. Add a thin upload-media wrapper that demuxes on the main thread (keeping heic-parser in upload-media) and hands the samples to the worker. Reuses the GIF path's Unsupported-error fallback contract.
Detect image/heic-sequence and image/heif-sequence in prepareItem and convert them to a web-safe video before upload (the server cannot decode multi-frame HEVC sequences). The video is uploaded as the attachment. When WebCodecs is unavailable, fall through to upload the original file so the server can still collapse it to a single still frame.
Add prepareItem tests covering the sequence-to-video routing (convert and upload, unsupported fall-through, hard-failure cancel, WebCodecs gate) and document the new behavior in the upload-media and video-conversion changelogs.
a5c4ac7 to
0b27ad8
Compare
|
Flaky tests detected in 0b27ad8. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/28458230046
|
…-video # Conflicts: # packages/upload-media/CHANGELOG.md
What
Adds client-side support for HEIC/HEIF image sequences — Apple Live Photos (HEVC sequences) and Android bursts (
image/heic-sequence,image/heif-sequence;.heics/.heifs) — by converting them to a web-safe video (MP4/WebM) in the browser before upload.Resolves part of #79642.
Today these files cannot be handled at all: GD/Imagick decode only a single frame, so WordPress core deliberately collapses sequence uploads to one still frame (see Trac #65297 / wordpress-develop#12352). Client-side conversion is the only path that preserves the motion.
Note
Builds on #78410 (Animated GIF → video via mediabunny), now merged to trunk. The video encoding reuses the
@wordpress/video-conversionworker introduced there.How
The existing still-HEIC pipeline only parses the
meta/iloc/pitmstructure of a single image. Image sequences instead store their frames as samples of an ISOBMFF movie track (moov→trak→mdia→minf→stbl), so the work splits into three pieces:packages/upload-media/src/heic-parser.ts): newparseHeicSequence()walks the sample tables (stsd/hvcC,stsz,stsc,stco/co64,stts,stss) and extracts the temporal HEVC access units, their durations, and keyframe flags. Reuses the existingReader/buildCodecStringhelpers.@wordpress/video-conversion): the GIF encode loop is refactored into a sharedencodeFramesToVideohelper, andconvertHeicSequenceToVideodecodes the frames with the WebCodecsVideoDecoder(platform HEVC codec) and re-encodes them with mediabunny — exactly like the GIF path, but sourced from aVideoDecoderinstead of anImageDecoder. Decoding is pipelined with a bounded look-ahead (8 frames) so a long sequence never materializes all of its full-res frames at once.packages/upload-media/src/store/private-actions.ts):prepareItemdetects the sequence MIME types and converts them to video before upload. The video becomes the attachment. When WebCodecs is unavailable it falls through to upload the original file (the server collapses it to a still).Parsing happens on the main thread (keeping
heic-parserin@wordpress/upload-media); the heavy decode + encode runs in the worker, off the main thread.flowchart LR A[".heics / .heifs"] --> B["parseHeicSequence()<br/>(main thread)"] B -->|HEVC samples| C["VideoDecoder<br/>(worker)"] C -->|VideoFrames| D["mediabunny encode<br/>(worker)"] D --> E["MP4 / WebM"] E --> F["upload as video attachment"]Prior art researched
media-experimentsplugin recognizes the sequence brands (msf1/hevc/hevx) but decodes only the first frame vialibheif-jsand discards the rest; its only image→video path is GIF→video viaffmpeg.wasm. No sequence→video support to reuse.VideoSampleSource+Output+Mp4OutputFormat/WebMOutputFormat) is exactly what this reuses. The demuxer is the net-new piece.Testing instructions
npm run test:unit packages/upload-media packages/video-conversionmsf1.heicfixture (256×144, 25fps, 1 keyframe + 119 delta frames), verifying frame count, codec config, sync/delta flags, and per-frame timing..heics(or an Android burst) into the editor with client-side media enabled; it should upload as a short looping MP4/WebM.Open questions / follow-ups (from #79642)
<video>(e.g. the GIF block variation) vs. a still-with-motion is left as a follow-up..heicsis not yet sideloaded alongside the video (the motion is preserved in the video itself).ctts); deeply reordered streams would need composition-time handling.