mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
226 lines
6.1 KiB
Go
226 lines
6.1 KiB
Go
package src
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type FileStream struct {
|
|
transcoder *Transcoder
|
|
ready sync.WaitGroup
|
|
err error
|
|
Out string
|
|
Info *MediaInfo
|
|
videos CMap[VideoKey, *VideoStream]
|
|
audios CMap[uint32, *AudioStream]
|
|
}
|
|
|
|
type VideoKey struct {
|
|
idx uint32
|
|
quality Quality
|
|
}
|
|
|
|
func (t *Transcoder) newFileStream(path string, sha string) *FileStream {
|
|
ret := &FileStream{
|
|
transcoder: t,
|
|
Out: fmt.Sprintf("%s/%s", Settings.Outpath, sha),
|
|
videos: NewCMap[VideoKey, *VideoStream](),
|
|
audios: NewCMap[uint32, *AudioStream](),
|
|
}
|
|
|
|
ret.ready.Add(1)
|
|
go func() {
|
|
defer ret.ready.Done()
|
|
info, err := t.metadataService.GetMetadata(path, sha)
|
|
ret.Info = info
|
|
if err != nil {
|
|
ret.err = err
|
|
}
|
|
}()
|
|
|
|
return ret
|
|
}
|
|
|
|
func (fs *FileStream) Kill() {
|
|
fs.videos.lock.Lock()
|
|
defer fs.videos.lock.Unlock()
|
|
fs.audios.lock.Lock()
|
|
defer fs.audios.lock.Unlock()
|
|
|
|
for _, s := range fs.videos.data {
|
|
s.Kill()
|
|
}
|
|
for _, s := range fs.audios.data {
|
|
s.Kill()
|
|
}
|
|
}
|
|
|
|
func (fs *FileStream) Destroy() {
|
|
log.Printf("Removing all transcode cache files for %s", fs.Info.Path)
|
|
fs.Kill()
|
|
_ = os.RemoveAll(fs.Out)
|
|
}
|
|
|
|
func (fs *FileStream) GetMaster() string {
|
|
master := "#EXTM3U\n"
|
|
|
|
// TODO: support multiples audio qualities (and original)
|
|
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)
|
|
}
|
|
if audio.IsDefault {
|
|
master += "DEFAULT=YES,"
|
|
}
|
|
master += "CHANNELS=\"2\","
|
|
master += fmt.Sprintf("URI=\"audio/%d/index.m3u8\"\n", audio.Index)
|
|
}
|
|
master += "\n"
|
|
|
|
// codec is the prefix + the level, the level is not part of the codec we want to compare for the same_codec check bellow
|
|
transmux_prefix := "avc1.6400"
|
|
transmux_codec := transmux_prefix + "28"
|
|
audio_codec := "mp4a.40.2"
|
|
|
|
var def_video *Video
|
|
for _, video := range fs.Info.Videos {
|
|
if video.IsDefault {
|
|
def_video = &video
|
|
break
|
|
}
|
|
}
|
|
if def_video == nil && len(fs.Info.Videos) > 0 {
|
|
def_video = &fs.Info.Videos[0]
|
|
}
|
|
|
|
if def_video != nil {
|
|
qualities := Filter(Qualities, func(quality Quality) bool {
|
|
same_codec := def_video.MimeCodec != nil && strings.HasPrefix(*def_video.MimeCodec, transmux_prefix)
|
|
return quality.Height() < def_video.Height ||
|
|
(quality.Height() == def_video.Height && !same_codec)
|
|
})
|
|
qualities = append(qualities, Original)
|
|
|
|
for _, quality := range qualities {
|
|
for _, video := range fs.Info.Videos {
|
|
master += "#EXT-X-MEDIA:TYPE=VIDEO,"
|
|
master += fmt.Sprintf("GROUP-ID=\"%s\",", quality)
|
|
if video.Language != nil {
|
|
master += fmt.Sprintf("LANGUAGE=\"%s\",", *video.Language)
|
|
}
|
|
if video.Title != nil {
|
|
master += fmt.Sprintf("NAME=\"%s\",", *video.Title)
|
|
} else if video.Language != nil {
|
|
master += fmt.Sprintf("NAME=\"%s\",", *video.Language)
|
|
} else {
|
|
master += fmt.Sprintf("NAME=\"Video %d\",", video.Index)
|
|
}
|
|
if video == *def_video {
|
|
master += "DEFAULT=YES\n"
|
|
} else {
|
|
master += fmt.Sprintf("URI=\"%d/%s/index.m3u8\"\n", video.Index, quality)
|
|
}
|
|
}
|
|
}
|
|
master += "\n"
|
|
|
|
// original stream
|
|
{
|
|
bitrate := float64(def_video.Bitrate)
|
|
master += "#EXT-X-STREAM-INF:"
|
|
master += fmt.Sprintf("AVERAGE-BANDWIDTH=%d,", int(math.Min(bitrate*0.8, float64(def_video.Quality().AverageBitrate()))))
|
|
master += fmt.Sprintf("BANDWIDTH=%d,", int(math.Min(bitrate, float64(def_video.Quality().MaxBitrate()))))
|
|
master += fmt.Sprintf("RESOLUTION=%dx%d,", def_video.Width, def_video.Height)
|
|
if def_video.MimeCodec != nil {
|
|
master += fmt.Sprintf("CODECS=\"%s\",", strings.Join([]string{*def_video.MimeCodec, audio_codec}, ","))
|
|
}
|
|
master += "AUDIO=\"audio\","
|
|
master += "CLOSED-CAPTIONS=NONE\n"
|
|
master += fmt.Sprintf("%d/%s/index.m3u8\n", def_video.Index, Original)
|
|
}
|
|
|
|
aspectRatio := float32(def_video.Width) / float32(def_video.Height)
|
|
|
|
for i, quality := range qualities {
|
|
if i == len(qualities)-1 {
|
|
// skip the original stream that already got handled
|
|
continue
|
|
}
|
|
|
|
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 += fmt.Sprintf("CODECS=\"%s\",", strings.Join([]string{transmux_codec, audio_codec}, ","))
|
|
master += "AUDIO=\"audio\","
|
|
master += "CLOSED-CAPTIONS=NONE\n"
|
|
master += fmt.Sprintf("%d/%s/index.m3u8\n", def_video.Index, quality)
|
|
}
|
|
}
|
|
|
|
return master
|
|
}
|
|
|
|
func (fs *FileStream) getVideoStream(idx uint32, quality Quality) (*VideoStream, error) {
|
|
stream, _ := fs.videos.GetOrCreate(VideoKey{idx, quality}, func() *VideoStream {
|
|
ret, _ := fs.transcoder.NewVideoStream(fs, idx, quality)
|
|
return ret
|
|
})
|
|
stream.ready.Wait()
|
|
return stream, nil
|
|
}
|
|
|
|
func (fs *FileStream) GetVideoIndex(idx uint32, quality Quality) (string, error) {
|
|
stream, err := fs.getVideoStream(idx, quality)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return stream.GetIndex()
|
|
}
|
|
|
|
func (fs *FileStream) GetVideoSegment(idx uint32, quality Quality, segment int32) (string, error) {
|
|
stream, err := fs.getVideoStream(idx, quality)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return stream.GetSegment(segment)
|
|
}
|
|
|
|
func (fs *FileStream) getAudioStream(audio uint32) (*AudioStream, error) {
|
|
stream, _ := fs.audios.GetOrCreate(audio, func() *AudioStream {
|
|
ret, _ := fs.transcoder.NewAudioStream(fs, audio)
|
|
return ret
|
|
})
|
|
stream.ready.Wait()
|
|
return stream, nil
|
|
}
|
|
|
|
func (fs *FileStream) GetAudioIndex(audio uint32) (string, error) {
|
|
stream, err := fs.getAudioStream(audio)
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
return stream.GetIndex()
|
|
}
|
|
|
|
func (fs *FileStream) GetAudioSegment(audio uint32, segment int32) (string, error) {
|
|
stream, err := fs.getAudioStream(audio)
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
return stream.GetSegment(segment)
|
|
}
|