Skip to content

BuildNot yet confirmed elsewhere1 publisher3 min readPublished

Portrait MP4 frames decoded with WebCodecs come out sideways in all four tested browsers

Portrait MP4 clips decoded with mp4box.js and VideoDecoder came out 1920x1080 and sideways in Chromium, Chrome, Firefox and WebKit, a dev.to post reports. The rotation lives in the track header matrix that the demuxer exposes, so the app has to read it and turn the canvas itself.

The Engineer · Build desk

How we use AISend a correction

Tools report different rotations for one clip Rotation each path reports for the author's tagged portrait test sample, from a single developer's own test.

Three figures. The author's snippet reading the tkhd matrix gives 90 degrees for the tagged sample. ffmpeg prints a rotation of -90.00 degrees for the same file. Frames from the author's own decoder reported rotation 0 in both Chromium-based browsers.

Tools report different rotations for one clip
FigureValueNoteClaim
Angle from the tkhd matrix, author's snippet90 degrees10
ffmpeg's printed rotation−90 degrees11
Decoder frames' rotation0 rotation valuein both Chromium-based browsers6

What happened

  • The same file played upright at 1080x1920 in a <video> element in all four browsers, and drawImage(video) onto a canvas was upright too.
  • The test clip was built with ffmpeg 7.1: pixels stored sideways at 1920x1080 plus a rotation tag, the usual way portrait video is stored.
  • Frames built from the video element carried a VideoFrame.rotation value in Chromium 149 and Chrome 154, but Firefox 151 and WebKit 26.5 had no such property.

Why it matters

  • constraint An app with its own demux and decode path cannot wait on VideoFrame.rotation. It reads 0 on decoder frames in Chromium and Chrome and does not exist in Firefox or WebKit.
  • cost Each such app takes on a tkhd parse, a width and height swap, and a canvas rotation. A 180-degree clip passes every size check, so a bug there ships quietly.
  • decision Exporters must choose between baking upright pixels and re-writing the tag. In this test only baking played upright everywhere, because Firefox and WebKit showed the tagged WebM sideways.

The author wrote, "A decoder that is fed raw samples only ever gets the picture." [17] The angle is in the container. In MP4 and MOV it is a 3x3 matrix in the track header box, tkhd, and mp4box returns it along with the track [8]. The 90-degree sample's matrix was [0, 65536, 0, -65536, 0, 0, 0, 0, 1073741824], while the upright sample with no tag returned identity [9]. The values are fixed-point, so 65536 is 1.0, and the first two entries are enough to recover the angle [9][10]:

``` const [a, b] = trak.tkhd.matrix; // 65536 = 1.0 const turn = (Math.round(Math.atan2(b, a) * 180 / Math.PI) + 360) % 360; ```

With a = 0 and b = 65536, that gives 90 degrees [20].

Two details tripped the author. ffmpeg prints a rotation of -90.00 degrees for the same file, so the author checks the computed turn against the painted marker blocks instead of trusting a sign [11]. And mp4box reports track_width and track_height as 1920x1080, the stored size, so a canvas sized from the track comes out landscape [12]. For a quarter turn the author swaps width and height, then rotates the context before drawing the frame [12]. A 180-degree clip keeps its size, so a width check never flags an upside-down frame [13].

The createImageBitmap(video) results show how far the browsers drift apart. Chrome 154 returned 1080x1920 with square blocks, and Chromium 149 returned a 1920x1080 bitmap that was upright but squashed, with the 160x160 blocks at about 284x90 [7]. Those blocks were stretched by about 1.78 across and squeezed to 0.56 down, which match 1920/1080 and 1080/1920 [19]. We think Chromium rotated the picture and then fitted it into the stored-size box. The post does not give a cause.

On the way out, the author tried keeping the sideways pixels and writing the tag again. ffmpeg with -noautorotate to VP9 kept the 1920x1080 sideways pixels, and the output file still carried a -90 rotation that ffmpeg could detect [15]. In Chromium 149 and Chrome 154 that WebM played upright at 1080x1920, while Firefox 151 and WebKit 26.5 played it lying down at 1920x1080 [15]. Upright pixels with no tag were upright on every path in every browser tested, so the author rotates at encode time and writes no tag [16]. We think that is the right call when you do not control the player.

The scope is narrow. Every tag was written by ffmpeg, and the post does not cover phone originals, HEVC, iPhone Safari or Android [18]. WebKit was tested on macOS [2]. Mirrored matrices, which the author says a front camera might write, were not tested, and the snippet ignores a flip [14]. For the result to transfer to your app, your clips would need a plain rotation matrix in tkhd, and your frames would have to come from VideoDecoder fed by your own demuxer.

What to watch

  • Whether anyone repeats the test with phone originals, HEVC, iPhone Safari or Android, where the tags were not written by ffmpeg.
  • Whether Firefox and WebKit add VideoFrame.rotation, and whether Chromium fills it in on frames produced by VideoDecoder.
  • Whether mirrored front-camera matrices break the atan2 snippet, since it ignores a flip.

Clarity's read

What the record supports and how the coverage leans. The claims behind it follow.

Reality

Evidence62
Adoption
Insufficient
Hype gap0
Incentives
Insufficient
Confidence58
Why these scores

Claim ledger

Ranked by verification strength, evidence, and original report placement.

  1. [1]

    A portrait test clip run through the author's pipeline, mp4box.js 0.5.2 to demux and then VideoDecoder, produced frames that all came out 1920x1080 with the picture lying on its side.

    ReportedSupportedSource: dev.to post by an individual developer (single-source test)View cited source
  2. [2]

    Chromium 149, Chrome 154, Firefox 151 and WebKit 26.5 (macOS) all returned lying-down frames from the decoder path, and all four played the same file upright.

    ReportedSupportedSource: dev.to post author's own testView cited source
  3. [3]

    The clip is not phone footage: the author used ffmpeg 7.1 on a Wikimedia Commons wind turbine clip to store the pixels sideways at 1920x1080 and write a rotation tag.

    ReportedSupportedSource: dev.to post authorView cited source

Sources

1 independent publisher whose own reporting we read for this story.

  1. dev.to

    1 article · October 13, 2026

    WebCodecs gave me portrait video lying on its side in all four browsers I tested

Share your take

Let Clarity write the post for you.

Signed-in readers get a short post drafted on this story in the register they choose — narrative, analytical, or a direct position — editable to the last word before it goes anywhere. The share buttons at the top of this story work without an account.

Topics and entities

Follow any of these and your For You feed starts watching them — no settings page required.

Loading related stories