diff --git a/transcoder/src/filestream.go b/transcoder/src/filestream.go index 3cd17901..555e573f 100644 --- a/transcoder/src/filestream.go +++ b/transcoder/src/filestream.go @@ -1,6 +1,7 @@ package src import ( + "fmt" "math" "os/exec" "strconv" @@ -11,19 +12,37 @@ type FileStream struct { Path string Keyframes []float64 CanTransmux bool + Info *MediaInfo streams map[Quality]TranscodeStream } func NewFileStream(path string) (*FileStream, error) { + info_chan := make(chan struct { + info *MediaInfo + err error + }) + go func() { + ret, err := GetInfo(path) + info_chan <- struct { + info *MediaInfo + err error + }{ret, err} + }() + keyframes, can_transmux, err := GetKeyframes(path) if err != nil { return nil, err } + info := <-info_chan + if info.err != nil { + return nil, err + } return &FileStream{ Path: path, Keyframes: keyframes, CanTransmux: can_transmux, + Info: info.info, streams: make(map[Quality]TranscodeStream), }, nil } @@ -102,5 +121,45 @@ func (fs *FileStream) Destroy() { } func (fs *FileStream) GetMaster() string { - return "" + master := "#EXTM3U\n" + // TODO: also check if the codec is valid in a hls before putting transmux + if fs.CanTransmux { + master += "#EXT-X-STREAM-INF:" + master += fmt.Sprintf("AVERAGE-BANDWIDTH=%d,", fs.Info.Video.Bitrate) + master += fmt.Sprintf("BANDWIDTH=%d,", int(float32(fs.Info.Video.Bitrate)*1.2)) + master += fmt.Sprintf("RESOLUTION=%dx%d,", fs.Info.Video.Width, fs.Info.Video.Height) + master += "AUDIO=\"audio\"," + master += "CLOSED-CAPTIONS=NONE\n" + master += fmt.Sprintf("./%s/index.m3u8\n", Original) + } + aspectRatio := float32(fs.Info.Video.Width) / float32(fs.Info.Video.Height) + for _, quality := range Qualities { + if quality.Height() < fs.Info.Video.Quality.Height() && quality.AverageBitrate() < fs.Info.Video.Bitrate { + master += "#EXT-X-STREAM-INF:" + master += fmt.Sprintf("AVERAGE-BANDWIDTH=%d,", quality.AverageBitrate()) + master += fmt.Sprintf("BANDWIDTH=%d,", quality.MaxBitrate()) + master += fmt.Sprintf("RESOLUTION=%dx%d,", int(aspectRatio*float32(quality.Height())+0.5), quality.Height()) + master += "CODECS=\"avc1.640028\"," + master += "AUDIO=\"audio\"," + master += "CLOSED-CAPTIONS=NONE\n" + master += fmt.Sprintf("./%s/index.m3u8\n", quality) + } + } + for _, audio := range fs.Info.Audios { + master += "#EXT-X-MEDIA:TYPE=AUDIO," + master += "GROUP-ID=\"audio\"," + if audio.Language != nil { + master += fmt.Sprintf("LANGUAGE=\"%s\",", *audio.Language) + } + if audio.Title != nil { + master += fmt.Sprintf("NAME=\"%s\",", *audio.Title) + } else if audio.Language != nil { + master += fmt.Sprintf("NAME=\"%s\",", *audio.Language) + } else { + master += fmt.Sprintf("NAME=\"Audio %d\",", audio.Index) + } + master += "DEFAULT=YES," + master += fmt.Sprintf("URI=\"./audio/%d/index.m3u8\"\n", audio.Index) + } + return master } diff --git a/transcoder/src/info.go b/transcoder/src/info.go index ecc83f8e..1aabf8db 100644 --- a/transcoder/src/info.go +++ b/transcoder/src/info.go @@ -158,10 +158,10 @@ var SubtitleExtensions = map[string]string{ "vtt": "vtt", } -func GetInfo(path string) (MediaInfo, error) { +func GetInfo(path string) (*MediaInfo, error) { mi, err := mediainfo.Open(path) if err != nil { - return MediaInfo{}, err + return nil, err } defer mi.Close() @@ -181,7 +181,7 @@ func GetInfo(path string) (MediaInfo, error) { chapters_begin := ParseUint(mi.Parameter(mediainfo.StreamMenu, 0, "Chapters_Pos_Begin")) chapters_end := ParseUint(mi.Parameter(mediainfo.StreamMenu, 0, "Chapters_Pos_End")) - return MediaInfo{ + return &MediaInfo{ Sha: sha, Path: path, // Remove leading . diff --git a/transcoder/src/quality.go b/transcoder/src/quality.go index 702fe5ba..d9b634dd 100644 --- a/transcoder/src/quality.go +++ b/transcoder/src/quality.go @@ -14,6 +14,57 @@ const ( Original Quality = "original" ) +var Qualities = []Quality{P240, P360, P480, P720, P1080, P1440, P4k, P8k, Original} + +// I'm not entierly sure about the values for bitrates. Double checking would be nice. +func (v Quality) AverageBitrate() uint32 { + switch v { + case P240: + return 400000 + case P360: + return 800000 + case P480: + return 1200000 + case P720: + return 2400000 + case P1080: + return 4800000 + case P1440: + return 9600000 + case P4k: + return 16000000 + case P8k: + return 28000000 + case Original: + panic("Original quality must be handled specially") + } + panic("Invalid quality value") +} + +func (v Quality) MaxBitrate() uint32 { + switch v { + case P240: + return 700000 + case P360: + return 1400000 + case P480: + return 2100000 + case P720: + return 4000000 + case P1080: + return 8000000 + case P1440: + return 12000000 + case P4k: + return 28000000 + case P8k: + return 40000000 + case Original: + panic("Original quality must be handled specially") + } + panic("Invalid quality value") +} + func (q Quality) Height() uint32 { switch q { case P240: @@ -39,7 +90,7 @@ func (q Quality) Height() uint32 { } func QualityFromHeight(height uint32) Quality { - qualities := []Quality{P240, P360, P480, P720, P1080, P1440, P4k, P8k, Original} + qualities := Qualities for _, quality := range qualities { if quality.Height() >= height { return quality