node build fixed
This commit is contained in:
261
seanime-2.9.10/internal/mediastream/transcoder/filestream.go
Normal file
261
seanime-2.9.10/internal/mediastream/transcoder/filestream.go
Normal file
@@ -0,0 +1,261 @@
|
||||
package transcoder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"seanime/internal/mediastream/videofile"
|
||||
"seanime/internal/util/result"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// FileStream represents a stream of file data.
|
||||
// It holds the keyframes, media information, video streams, and audio streams.
|
||||
type FileStream struct {
|
||||
ready sync.WaitGroup // A WaitGroup to synchronize go routines.
|
||||
err error // An error that might occur during processing.
|
||||
Path string // The path of the file.
|
||||
Out string // The output path.
|
||||
Keyframes *Keyframe // The keyframes of the video.
|
||||
Info *videofile.MediaInfo // The media information of the file.
|
||||
videos *result.Map[Quality, *VideoStream] // A map of video streams.
|
||||
audios *result.Map[int32, *AudioStream] // A map of audio streams.
|
||||
logger *zerolog.Logger
|
||||
settings *Settings
|
||||
}
|
||||
|
||||
// NewFileStream creates a new FileStream.
|
||||
func NewFileStream(
|
||||
path string,
|
||||
sha string,
|
||||
mediaInfo *videofile.MediaInfo,
|
||||
settings *Settings,
|
||||
logger *zerolog.Logger,
|
||||
) *FileStream {
|
||||
ret := &FileStream{
|
||||
Path: path,
|
||||
Out: filepath.Join(settings.StreamDir, sha),
|
||||
videos: result.NewResultMap[Quality, *VideoStream](),
|
||||
audios: result.NewResultMap[int32, *AudioStream](),
|
||||
logger: logger,
|
||||
settings: settings,
|
||||
Info: mediaInfo,
|
||||
}
|
||||
|
||||
ret.ready.Add(1)
|
||||
go func() {
|
||||
defer ret.ready.Done()
|
||||
ret.Keyframes = GetKeyframes(path, sha, logger, settings)
|
||||
}()
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Kill stops all streams.
|
||||
func (fs *FileStream) Kill() {
|
||||
fs.videos.Range(func(_ Quality, s *VideoStream) bool {
|
||||
s.Kill()
|
||||
return true
|
||||
})
|
||||
fs.audios.Range(func(_ int32, s *AudioStream) bool {
|
||||
s.Kill()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Destroy stops all streams and removes the output directory.
|
||||
func (fs *FileStream) Destroy() {
|
||||
fs.logger.Debug().Msg("filestream: Destroying streams")
|
||||
fs.Kill()
|
||||
_ = os.RemoveAll(fs.Out)
|
||||
}
|
||||
|
||||
// GetMaster generates the master playlist.
|
||||
func (fs *FileStream) GetMaster() string {
|
||||
master := "#EXTM3U\n"
|
||||
if fs.Info.Video != nil {
|
||||
var transmuxQuality Quality
|
||||
for _, quality := range Qualities {
|
||||
if quality.Height() >= fs.Info.Video.Quality.Height() || quality.AverageBitrate() >= fs.Info.Video.Bitrate {
|
||||
transmuxQuality = quality
|
||||
break
|
||||
}
|
||||
}
|
||||
{
|
||||
bitrate := float64(fs.Info.Video.Bitrate)
|
||||
master += "#EXT-X-STREAM-INF:"
|
||||
master += fmt.Sprintf("AVERAGE-BANDWIDTH=%d,", int(math.Min(bitrate*0.8, float64(transmuxQuality.AverageBitrate()))))
|
||||
master += fmt.Sprintf("BANDWIDTH=%d,", int(math.Min(bitrate, float64(transmuxQuality.MaxBitrate()))))
|
||||
master += fmt.Sprintf("RESOLUTION=%dx%d,", fs.Info.Video.Width, fs.Info.Video.Height)
|
||||
if fs.Info.Video.MimeCodec != nil {
|
||||
master += fmt.Sprintf("CODECS=\"%s\",", *fs.Info.Video.MimeCodec)
|
||||
}
|
||||
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)
|
||||
// codec is the prefix + the level, the level is not part of the codec we want to compare for the same_codec check bellow
|
||||
transmuxPrefix := "avc1.6400"
|
||||
transmuxCodec := transmuxPrefix + "28"
|
||||
|
||||
for _, quality := range Qualities {
|
||||
sameCodec := fs.Info.Video.MimeCodec != nil && strings.HasPrefix(*fs.Info.Video.MimeCodec, transmuxPrefix)
|
||||
includeLvl := quality.Height() < fs.Info.Video.Quality.Height() || (quality.Height() == fs.Info.Video.Quality.Height() && !sameCodec)
|
||||
|
||||
if includeLvl {
|
||||
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\",", transmuxCodec)
|
||||
master += "AUDIO=\"audio\","
|
||||
master += "CLOSED-CAPTIONS=NONE\n"
|
||||
master += fmt.Sprintf("./%s/index.m3u8\n", quality)
|
||||
}
|
||||
}
|
||||
|
||||
//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)
|
||||
}
|
||||
if audio.IsDefault {
|
||||
master += "DEFAULT=YES,"
|
||||
}
|
||||
master += "CHANNELS=\"2\","
|
||||
master += fmt.Sprintf("URI=\"./audio/%d/index.m3u8\"\n", audio.Index)
|
||||
}
|
||||
return master
|
||||
}
|
||||
|
||||
// GetVideoIndex gets the index of a video stream of a specific quality.
|
||||
func (fs *FileStream) GetVideoIndex(quality Quality) (string, error) {
|
||||
stream := fs.getVideoStream(quality)
|
||||
return stream.GetIndex()
|
||||
}
|
||||
|
||||
// getVideoStream gets a video stream of a specific quality.
|
||||
// It creates a new stream if it does not exist.
|
||||
func (fs *FileStream) getVideoStream(quality Quality) *VideoStream {
|
||||
stream, _ := fs.videos.GetOrSet(quality, func() (*VideoStream, error) {
|
||||
return NewVideoStream(fs, quality, fs.logger, fs.settings), nil
|
||||
})
|
||||
return stream
|
||||
}
|
||||
|
||||
// GetVideoSegment gets a segment of a video stream of a specific quality.
|
||||
//func (fs *FileStream) GetVideoSegment(quality Quality, segment int32) (string, error) {
|
||||
// stream := fs.getVideoStream(quality)
|
||||
// return stream.GetSegment(segment)
|
||||
//}
|
||||
|
||||
// GetVideoSegment gets a segment of a video stream of a specific quality.
|
||||
func (fs *FileStream) GetVideoSegment(quality Quality, segment int32) (string, error) {
|
||||
streamLogger.Debug().Msgf("filestream: Retrieving video segment %d (%s)", segment, quality)
|
||||
// Debug
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
debugStreamRequest(fmt.Sprintf("video %s, segment %d", quality, segment), ctx)
|
||||
|
||||
//stream := fs.getVideoStream(quality)
|
||||
//return stream.GetSegment(segment)
|
||||
|
||||
// Channel to signal completion
|
||||
done := make(chan struct{})
|
||||
|
||||
var ret string
|
||||
var err error
|
||||
|
||||
// Execute the retrieval operation in a goroutine
|
||||
go func() {
|
||||
defer close(done)
|
||||
stream := fs.getVideoStream(quality)
|
||||
ret, err = stream.GetSegment(segment)
|
||||
}()
|
||||
|
||||
// Wait for either the operation to complete or the timeout to occur
|
||||
select {
|
||||
case <-done:
|
||||
return ret, err
|
||||
case <-ctx.Done():
|
||||
return "", fmt.Errorf("filestream: timeout while retrieving video segment %d (%s)", segment, quality)
|
||||
}
|
||||
}
|
||||
|
||||
// GetAudioIndex gets the index of an audio stream of a specific index.
|
||||
func (fs *FileStream) GetAudioIndex(audio int32) (string, error) {
|
||||
stream := fs.getAudioStream(audio)
|
||||
return stream.GetIndex()
|
||||
}
|
||||
|
||||
// GetAudioSegment gets a segment of an audio stream of a specific index.
|
||||
func (fs *FileStream) GetAudioSegment(audio int32, segment int32) (string, error) {
|
||||
streamLogger.Debug().Msgf("filestream: Retrieving audio %d segment %d", audio, segment)
|
||||
// Debug
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
debugStreamRequest(fmt.Sprintf("audio %d, segment %d", audio, segment), ctx)
|
||||
|
||||
stream := fs.getAudioStream(audio)
|
||||
return stream.GetSegment(segment)
|
||||
}
|
||||
|
||||
// getAudioStream gets an audio stream of a specific index.
|
||||
// It creates a new stream if it does not exist.
|
||||
func (fs *FileStream) getAudioStream(audio int32) *AudioStream {
|
||||
stream, _ := fs.audios.GetOrSet(audio, func() (*AudioStream, error) {
|
||||
return NewAudioStream(fs, audio, fs.logger, fs.settings), nil
|
||||
})
|
||||
return stream
|
||||
}
|
||||
|
||||
func debugStreamRequest(text string, ctx context.Context) {
|
||||
//ctx, cancel := context.WithCancel(context.Background())
|
||||
//defer cancel()
|
||||
if debugStream {
|
||||
start := time.Now()
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
if debugStream {
|
||||
time.Sleep(2 * time.Second)
|
||||
streamLogger.Debug().Msgf("t: %s has been running for %.2f", text, time.Since(start).Seconds())
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user