diff --git a/transcoder/src/codec.go b/transcoder/src/codec.go new file mode 100644 index 00000000..6aefe265 --- /dev/null +++ b/transcoder/src/codec.go @@ -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 + } +} diff --git a/transcoder/src/info.go b/transcoder/src/info.go index de8413a5..06643e02 100644 --- a/transcoder/src/info.go +++ b/transcoder/src/info.go @@ -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", }