Add rfc6381 codec on the /info endpoint

This commit is contained in:
Zoe Roux 2024-03-30 22:47:35 +01:00
parent 9a9e43269d
commit 3de751c880
No known key found for this signature in database
2 changed files with 149 additions and 15 deletions

130
transcoder/src/codec.go Normal file
View File

@ -0,0 +1,130 @@
package src
import (
"fmt"
"log"
"strings"
"github.com/zoriya/go-mediainfo"
)
// convert mediainfo to RFC 6381, waiting for either of those tickets to be resolved:
//
// https://sourceforge.net/p/mediainfo/feature-requests/499
// https://trac.ffmpeg.org/ticket/6617
//
// this code is addapted from https://github.com/jellyfin/jellyfin/blob/master/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
func GetMimeCodec(mi *mediainfo.File, kind mediainfo.StreamKind, i int) *string {
codec := Or(
mi.Parameter(kind, i, "InternetMediaType"),
mi.Parameter(kind, i, "Format"),
)
log.Printf("codec: %s", codec)
switch codec {
case "video/H264", "AVC":
ret := "avc1"
info := strings.Split(strings.ToLower(mi.Parameter(kind, i, "Format_Profile")), "@")
format := info[0]
switch format {
case "high":
ret += ".6400"
case "main":
ret += ".4D40"
case "baseline":
ret += ".42E0"
default:
// Default to constrained baseline if profile is invalid
ret += ".4240"
}
// level format is l3.1 for level 31
level := ParseFloat(info[1][1:])
log.Printf("%s %s %f", format, info[1], level)
ret += fmt.Sprintf("%02x", int(level*10))
return &ret
case "video/H265", "HEVC":
// The h265 syntax is a bit of a mystery at the time this comment was written.
// This is what I've found through various sources:
// FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN]
ret := "hvc1"
info := strings.Split(strings.ToLower(mi.Parameter(kind, i, "Format_Profile")), "@")
profile := info[0]
if profile == "main 10" {
ret += ".2.4"
} else {
ret += ".1.4"
}
level := ParseFloat(info[1][:1])
ret += fmt.Sprintf(".L%02X.BO", int(level*30))
return &ret
case "AV1":
// https://aomedia.org/av1/specification/annex-a/
// FORMAT: [codecTag].[profile].[level][tier].[bitDepth]
ret := "av01"
info := strings.Split(strings.ToLower(mi.Parameter(kind, i, "Format_Profile")), "@")
profile := info[0]
switch profile {
case "main":
ret += ".0"
case "high":
ret += ".1"
case "professional":
ret += ".2"
default:
// Default to Main
ret += ".0"
}
// level is not defined in mediainfo. using a default
// Default to the maximum defined level 6.3
level := 19
bitdepth := ParseUint(mi.Parameter(kind, i, "BitDepth"))
if bitdepth != 8 && bitdepth != 10 && bitdepth != 12 {
// Default to 8 bits
bitdepth = 8
}
tierflag := 'M'
ret += fmt.Sprintf(".%02X%c.%02d", level, tierflag, bitdepth)
return &ret
case "AAC":
ret := "mp4a"
profile := strings.ToLower(mi.Parameter(kind, i, "Format_AdditionalFeatures"))
switch profile {
case "he":
ret += ".40.5"
case "lc":
ret += ".40.2"
default:
ret += ".40.2"
}
return &ret
case "audio/opus", "Opus":
ret := "Opus"
return &ret
case "AC-3":
ret := "mp4a.a5"
return &ret
case "audio/x-flac", "FLAC":
ret := "fLaC"
return &ret
default:
return nil
}
}

View File

@ -42,8 +42,10 @@ type MediaInfo struct {
}
type Video struct {
/// The codec of this stream (defined as the RFC 6381).
/// The human readable codec name.
Codec string `json:"codec"`
/// The codec of this stream (defined as the RFC 6381).
MimeCodec *string `json:"mimeCodec"`
/// The language of this stream (as a ISO-639-2 language code)
Language *string `json:"language"`
/// The max quality of this video track.
@ -63,8 +65,10 @@ type Audio struct {
Title *string `json:"title"`
/// The language of this stream (as a ISO-639-2 language code)
Language *string `json:"language"`
/// The codec of this stream.
/// The human readable codec name.
Codec string `json:"codec"`
/// The codec of this stream (defined as the RFC 6381).
MimeCodec *string `json:"mimeCodec"`
/// Is this stream the default one of it's type?
IsDefault bool `json:"isDefault"`
/// Is this stream tagged as forced? (useful only for subtitles)
@ -263,28 +267,28 @@ func getInfo(path string, route string) (*MediaInfo, error) {
Container: OrNull(mi.Parameter(mediainfo.StreamGeneral, 0, "Format")),
Videos: Map(make([]Video, ParseUint(mi.Parameter(mediainfo.StreamVideo, 0, "StreamCount"))), func(_ Video, i int) Video {
return Video{
// This codec is not in the right format (does not include bitdepth...).
Codec: mi.Parameter(mediainfo.StreamVideo, 0, "Format"),
Language: OrNull(mi.Parameter(mediainfo.StreamVideo, 0, "Language")),
Quality: QualityFromHeight(ParseUint(mi.Parameter(mediainfo.StreamVideo, 0, "Height"))),
Width: ParseUint(mi.Parameter(mediainfo.StreamVideo, 0, "Width")),
Height: ParseUint(mi.Parameter(mediainfo.StreamVideo, 0, "Height")),
Codec: mi.Parameter(mediainfo.StreamVideo, i, "Format"),
MimeCodec: GetMimeCodec(mi, mediainfo.StreamVideo, i),
Language: OrNull(mi.Parameter(mediainfo.StreamVideo, i, "Language")),
Quality: QualityFromHeight(ParseUint(mi.Parameter(mediainfo.StreamVideo, i, "Height"))),
Width: ParseUint(mi.Parameter(mediainfo.StreamVideo, i, "Width")),
Height: ParseUint(mi.Parameter(mediainfo.StreamVideo, i, "Height")),
Bitrate: ParseUint(
Or(
mi.Parameter(mediainfo.StreamVideo, 0, "BitRate"),
mi.Parameter(mediainfo.StreamVideo, 0, "OverallBitRate"),
mi.Parameter(mediainfo.StreamVideo, 0, "BitRate_Nominal"),
mi.Parameter(mediainfo.StreamVideo, i, "BitRate"),
mi.Parameter(mediainfo.StreamVideo, i, "OverallBitRate"),
mi.Parameter(mediainfo.StreamVideo, i, "BitRate_Nominal"),
),
),
}
}),
Audios: Map(make([]Audio, ParseUint(mi.Parameter(mediainfo.StreamAudio, 0, "StreamCount"))), func(_ Audio, i int) Audio {
return Audio{
Index: uint32(i),
Title: OrNull(mi.Parameter(mediainfo.StreamAudio, i, "Title")),
Language: OrNull(mi.Parameter(mediainfo.StreamAudio, i, "Language")),
// TODO: format is invalid. Channels count missing...
Index: uint32(i),
Title: OrNull(mi.Parameter(mediainfo.StreamAudio, i, "Title")),
Language: OrNull(mi.Parameter(mediainfo.StreamAudio, i, "Language")),
Codec: mi.Parameter(mediainfo.StreamAudio, i, "Format"),
MimeCodec: GetMimeCodec(mi, mediainfo.StreamAudio, i),
IsDefault: mi.Parameter(mediainfo.StreamAudio, i, "Default") == "Yes",
IsForced: mi.Parameter(mediainfo.StreamAudio, i, "Forced") == "Yes",
}