mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 04:04:21 -04:00
Add master m3u8 creation
This commit is contained in:
parent
b312403201
commit
4193b6b391
@ -1,6 +1,7 @@
|
|||||||
package src
|
package src
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -11,19 +12,37 @@ type FileStream struct {
|
|||||||
Path string
|
Path string
|
||||||
Keyframes []float64
|
Keyframes []float64
|
||||||
CanTransmux bool
|
CanTransmux bool
|
||||||
|
Info *MediaInfo
|
||||||
streams map[Quality]TranscodeStream
|
streams map[Quality]TranscodeStream
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileStream(path string) (*FileStream, error) {
|
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)
|
keyframes, can_transmux, err := GetKeyframes(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
info := <-info_chan
|
||||||
|
if info.err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &FileStream{
|
return &FileStream{
|
||||||
Path: path,
|
Path: path,
|
||||||
Keyframes: keyframes,
|
Keyframes: keyframes,
|
||||||
CanTransmux: can_transmux,
|
CanTransmux: can_transmux,
|
||||||
|
Info: info.info,
|
||||||
streams: make(map[Quality]TranscodeStream),
|
streams: make(map[Quality]TranscodeStream),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -102,5 +121,45 @@ func (fs *FileStream) Destroy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FileStream) GetMaster() string {
|
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
|
||||||
}
|
}
|
||||||
|
@ -158,10 +158,10 @@ var SubtitleExtensions = map[string]string{
|
|||||||
"vtt": "vtt",
|
"vtt": "vtt",
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetInfo(path string) (MediaInfo, error) {
|
func GetInfo(path string) (*MediaInfo, error) {
|
||||||
mi, err := mediainfo.Open(path)
|
mi, err := mediainfo.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MediaInfo{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer mi.Close()
|
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_begin := ParseUint(mi.Parameter(mediainfo.StreamMenu, 0, "Chapters_Pos_Begin"))
|
||||||
chapters_end := ParseUint(mi.Parameter(mediainfo.StreamMenu, 0, "Chapters_Pos_End"))
|
chapters_end := ParseUint(mi.Parameter(mediainfo.StreamMenu, 0, "Chapters_Pos_End"))
|
||||||
|
|
||||||
return MediaInfo{
|
return &MediaInfo{
|
||||||
Sha: sha,
|
Sha: sha,
|
||||||
Path: path,
|
Path: path,
|
||||||
// Remove leading .
|
// Remove leading .
|
||||||
|
@ -14,6 +14,57 @@ const (
|
|||||||
Original Quality = "original"
|
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 {
|
func (q Quality) Height() uint32 {
|
||||||
switch q {
|
switch q {
|
||||||
case P240:
|
case P240:
|
||||||
@ -39,7 +90,7 @@ func (q Quality) Height() uint32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func QualityFromHeight(height uint32) Quality {
|
func QualityFromHeight(height uint32) Quality {
|
||||||
qualities := []Quality{P240, P360, P480, P720, P1080, P1440, P4k, P8k, Original}
|
qualities := Qualities
|
||||||
for _, quality := range qualities {
|
for _, quality := range qualities {
|
||||||
if quality.Height() >= height {
|
if quality.Height() >= height {
|
||||||
return quality
|
return quality
|
||||||
|
Loading…
x
Reference in New Issue
Block a user