files: the player turns a phone's picture upright on the GPU, and shows HLG as it is

tonemap_vaapi refuses HLG, and the hardware path never honoured a rotation
(ffmpeg's autorotate is for frames in memory). HDR10 is still tone-mapped,
on the card and, failing that, in software before the upload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-10-23 14:56:16 -07:00
co-authored by Claude Opus 5.5
parent fc4126a0ed
commit 8c69826547
3 changed files with 92 additions and 20 deletions
+24 -4
View File
@@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"errors"
"math"
"os/exec"
"strconv"
"strings"
@@ -26,6 +27,15 @@ type Track struct {
Channels int `json:"channels,omitempty"`
AttachedPic bool `json:"attached_pic,omitempty"`
Spherical bool `json:"spherical,omitempty"` // an equirectangular 360 picture
Rotation int `json:"rotation,omitempty"` // degrees clockwise to turn it to be shown: 0, 90, 180, 270 (a phone held upright)
}
// Shown is the picture's size as it is shown, turned by its rotation.
func (t *Track) Shown() (w, h int) {
if t.Rotation == 90 || t.Rotation == 270 {
return t.Height, t.Width
}
return t.Width, t.Height
}
// Info is what a file holds: the picture to take, the sound to go with it, and the whole.
@@ -38,7 +48,12 @@ type Info struct {
Audio *Track `json:"audio,omitempty"` // the first sound stream
}
// HDR reports a picture that needs tone mapping to look right on an SDR screen.
// PQ reports HDR10 (and Dolby Vision's base layer): a picture that can't be shown on an SDR
// screen without tone mapping. HLG, the other kind, can, if a little flat.
func (i Info) PQ() bool { return i.Video != nil && i.Video.ColorTransfer == "smpte2084" }
// HDR reports a picture that needs tone mapping to look right on an SDR screen. (The player
// tone-maps only PQ: HLG is made to be shown on an SDR screen as it is.)
func (i Info) HDR() bool {
return i.Video != nil && (i.Video.ColorTransfer == "smpte2084" || i.Video.ColorTransfer == "arib-std-b67")
}
@@ -60,8 +75,9 @@ type probeJSON struct {
AttachedPic int `json:"attached_pic"`
} `json:"disposition"`
SideData []struct {
Type string `json:"side_data_type"`
Projection string `json:"projection"`
Type string `json:"side_data_type"`
Projection string `json:"projection"`
Rotation float64 `json:"rotation"`
} `json:"side_data_list"`
} `json:"streams"`
Format struct {
@@ -79,7 +95,7 @@ func Inspect(ctx context.Context, c Caps, path string, root bool) (Info, error)
return Info{}, errors.New("ffprobe is not installed")
}
argv := []string{c.FFprobe, "-v", "error", "-show_entries",
"stream=index,codec_type,codec_name,profile,pix_fmt,width,height,r_frame_rate,avg_frame_rate,color_transfer,channels,disposition:stream_side_data=side_data_type,projection:format=format_name,duration,bit_rate,size",
"stream=index,codec_type,codec_name,profile,pix_fmt,width,height,r_frame_rate,avg_frame_rate,color_transfer,channels,disposition:stream_side_data=side_data_type,projection,rotation:format=format_name,duration,bit_rate,size",
"-of", "json", path}
if root {
argv = priv.Wrap(argv...)
@@ -117,6 +133,10 @@ func Inspect(ctx context.Context, c Caps, path string, root bool) (Info, error)
if sd.Type == "Spherical Mapping" && strings.HasPrefix(sd.Projection, "equirectangular") {
st.Spherical = true
}
if sd.Type == "Display Matrix" {
// ffprobe's rotation is counter-clockwise; ffmpeg turns it back the other way
st.Rotation = ((-int(math.Round(sd.Rotation/90))*90)%360 + 360) % 360
}
}
st.Spherical = st.Spherical || (s.CodecType == "video" && s.Width == 2*s.Height && s.Width >= 3840)
st.FPS = fps(s.AvgFrameRate)