Suport multi video files

This commit is contained in:
Zoe Roux 2024-08-07 17:23:54 +02:00
parent 7d3c73a1e9
commit fa03d835ed
11 changed files with 146 additions and 100 deletions

View File

@ -74,7 +74,7 @@ func (h *Handler) GetVideoIndex(c echo.Context) error {
return err return err
} }
ret, err := h.transcoder.GetVideoIndex(path, int32(video), quality, client, sha) ret, err := h.transcoder.GetVideoIndex(path, uint32(video), quality, client, sha)
if err != nil { if err != nil {
return err return err
} }
@ -102,7 +102,7 @@ func (h *Handler) GetAudioIndex(c echo.Context) error {
return err return err
} }
ret, err := h.transcoder.GetAudioIndex(path, int32(audio), client, sha) ret, err := h.transcoder.GetAudioIndex(path, uint32(audio), client, sha)
if err != nil { if err != nil {
return err return err
} }
@ -138,7 +138,7 @@ func (h *Handler) GetVideoSegment(c echo.Context) error {
ret, err := h.transcoder.GetVideoSegment( ret, err := h.transcoder.GetVideoSegment(
path, path,
int32(video), uint32(video),
quality, quality,
segment, segment,
client, client,
@ -173,7 +173,7 @@ func (h *Handler) GetAudioSegment(c echo.Context) error {
return err return err
} }
ret, err := h.transcoder.GetAudioSegment(path, int32(audio), segment, client, sha) ret, err := h.transcoder.GetAudioSegment(path, uint32(audio), segment, client, sha)
if err != nil { if err != nil {
return err return err
} }

View File

@ -7,10 +7,10 @@ import (
type AudioStream struct { type AudioStream struct {
Stream Stream
index int32 index uint32
} }
func (t *Transcoder) NewAudioStream(file *FileStream, idx int32) (*AudioStream, error) { func (t *Transcoder) NewAudioStream(file *FileStream, idx uint32) (*AudioStream, error) {
log.Printf("Creating a audio stream %d for %s", idx, file.Info.Path) log.Printf("Creating a audio stream %d for %s", idx, file.Info.Path)
keyframes, err := t.metadataService.GetKeyframes(file.Info, false, idx) keyframes, err := t.metadataService.GetKeyframes(file.Info, false, idx)

View File

@ -16,11 +16,11 @@ type FileStream struct {
Out string Out string
Info *MediaInfo Info *MediaInfo
videos CMap[VideoKey, *VideoStream] videos CMap[VideoKey, *VideoStream]
audios CMap[int32, *AudioStream] audios CMap[uint32, *AudioStream]
} }
type VideoKey struct { type VideoKey struct {
idx int32 idx uint32
quality Quality quality Quality
} }
@ -29,7 +29,7 @@ func (t *Transcoder) newFileStream(path string, sha string) *FileStream {
transcoder: t, transcoder: t,
Out: fmt.Sprintf("%s/%s", Settings.Outpath, sha), Out: fmt.Sprintf("%s/%s", Settings.Outpath, sha),
videos: NewCMap[VideoKey, *VideoStream](), videos: NewCMap[VideoKey, *VideoStream](),
audios: NewCMap[int32, *AudioStream](), audios: NewCMap[uint32, *AudioStream](),
} }
ret.ready.Add(1) ret.ready.Add(1)
@ -67,51 +67,8 @@ func (fs *FileStream) Destroy() {
func (fs *FileStream) GetMaster() string { func (fs *FileStream) GetMaster() string {
master := "#EXTM3U\n" master := "#EXTM3U\n"
if fs.Info.Video != nil {
var transmux_quality Quality
for _, quality := range Qualities {
if quality.Height() >= fs.Info.Video.Quality.Height() || quality.AverageBitrate() >= fs.Info.Video.Bitrate {
transmux_quality = quality
break
}
}
// original stream
{
bitrate := float64(fs.Info.Video.Bitrate)
master += "#EXT-X-STREAM-INF:"
master += fmt.Sprintf("AVERAGE-BANDWIDTH=%d,", int(math.Min(bitrate*0.8, float64(transmux_quality.AverageBitrate()))))
master += fmt.Sprintf("BANDWIDTH=%d,", int(math.Min(bitrate, float64(transmux_quality.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) // TODO: support multiples audio qualities (and original)
// 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"
for _, quality := range Qualities {
same_codec := fs.Info.Video.MimeCodec != nil && strings.HasPrefix(*fs.Info.Video.MimeCodec, transmux_prefix)
inc_lvl := quality.Height() < fs.Info.Video.Quality.Height() ||
(quality.Height() == fs.Info.Video.Quality.Height() && !same_codec)
if inc_lvl {
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\",", transmux_codec)
master += "AUDIO=\"audio\","
master += "CLOSED-CAPTIONS=NONE\n"
master += fmt.Sprintf("./%s/index.m3u8\n", quality)
}
}
}
for _, audio := range fs.Info.Audios { for _, audio := range fs.Info.Audios {
master += "#EXT-X-MEDIA:TYPE=AUDIO," master += "#EXT-X-MEDIA:TYPE=AUDIO,"
master += "GROUP-ID=\"audio\"," master += "GROUP-ID=\"audio\","
@ -128,12 +85,93 @@ func (fs *FileStream) GetMaster() string {
if audio.IsDefault { if audio.IsDefault {
master += "DEFAULT=YES," master += "DEFAULT=YES,"
} }
master += "CHANNELS=\"2\","
master += fmt.Sprintf("URI=\"./audio/%d/index.m3u8\"\n", audio.Index) master += fmt.Sprintf("URI=\"./audio/%d/index.m3u8\"\n", audio.Index)
} }
// 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.Quality().Height() ||
(quality.Height() == def_video.Quality().Height() && !same_codec)
})
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"
} else {
master += fmt.Sprintf("URI=\"./%d/%s/index.m3u8\"\n", video.Index, quality)
}
}
}
// 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 == 0 {
// 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("./%s/index.m3u8\n", quality)
}
}
return master return master
} }
func (fs *FileStream) getVideoStream(idx int32, quality Quality) (*VideoStream, error) { func (fs *FileStream) getVideoStream(idx uint32, quality Quality) (*VideoStream, error) {
var err error var err error
stream, _ := fs.videos.GetOrCreate(VideoKey{idx, quality}, func() *VideoStream { stream, _ := fs.videos.GetOrCreate(VideoKey{idx, quality}, func() *VideoStream {
var ret *VideoStream var ret *VideoStream
@ -148,7 +186,7 @@ func (fs *FileStream) getVideoStream(idx int32, quality Quality) (*VideoStream,
return stream, nil return stream, nil
} }
func (fs *FileStream) GetVideoIndex(idx int32, quality Quality) (string, error) { func (fs *FileStream) GetVideoIndex(idx uint32, quality Quality) (string, error) {
stream, err := fs.getVideoStream(idx, quality) stream, err := fs.getVideoStream(idx, quality)
if err != nil { if err != nil {
return "", err return "", err
@ -156,7 +194,7 @@ func (fs *FileStream) GetVideoIndex(idx int32, quality Quality) (string, error)
return stream.GetIndex() return stream.GetIndex()
} }
func (fs *FileStream) GetVideoSegment(idx int32, quality Quality, segment int32) (string, error) { func (fs *FileStream) GetVideoSegment(idx uint32, quality Quality, segment int32) (string, error) {
stream, err := fs.getVideoStream(idx, quality) stream, err := fs.getVideoStream(idx, quality)
if err != nil { if err != nil {
return "", err return "", err
@ -164,7 +202,7 @@ func (fs *FileStream) GetVideoSegment(idx int32, quality Quality, segment int32)
return stream.GetSegment(segment) return stream.GetSegment(segment)
} }
func (fs *FileStream) getAudioStream(audio int32) (*AudioStream, error) { func (fs *FileStream) getAudioStream(audio uint32) (*AudioStream, error) {
var err error var err error
stream, _ := fs.audios.GetOrCreate(audio, func() *AudioStream { stream, _ := fs.audios.GetOrCreate(audio, func() *AudioStream {
var ret *AudioStream var ret *AudioStream
@ -179,7 +217,7 @@ func (fs *FileStream) getAudioStream(audio int32) (*AudioStream, error) {
return stream, nil return stream, nil
} }
func (fs *FileStream) GetAudioIndex(audio int32) (string, error) { func (fs *FileStream) GetAudioIndex(audio uint32) (string, error) {
stream, err := fs.getAudioStream(audio) stream, err := fs.getAudioStream(audio)
if err != nil { if err != nil {
return "", nil return "", nil
@ -187,7 +225,7 @@ func (fs *FileStream) GetAudioIndex(audio int32) (string, error) {
return stream.GetIndex() return stream.GetIndex()
} }
func (fs *FileStream) GetAudioSegment(audio int32, segment int32) (string, error) { func (fs *FileStream) GetAudioSegment(audio uint32, segment int32) (string, error) {
stream, err := fs.getAudioStream(audio) stream, err := fs.getAudioStream(audio)
if err != nil { if err != nil {
return "", nil return "", nil

View File

@ -65,8 +65,6 @@ type Video struct {
Codec string `json:"codec"` Codec string `json:"codec"`
/// The codec of this stream (defined as the RFC 6381). /// The codec of this stream (defined as the RFC 6381).
MimeCodec *string `json:"mimeCodec"` MimeCodec *string `json:"mimeCodec"`
/// The max quality of this video track.
Quality Quality `json:"quality"`
/// The width of the video stream /// The width of the video stream
Width uint32 `json:"width"` Width uint32 `json:"width"`
/// The height of the video stream /// The height of the video stream
@ -251,7 +249,6 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
MimeCodec: GetMimeCodec(stream), MimeCodec: GetMimeCodec(stream),
Title: OrNull(stream.Tags.Title), Title: OrNull(stream.Tags.Title),
Language: NullIfUnd(lang.String()), Language: NullIfUnd(lang.String()),
Quality: QualityFromHeight(uint32(stream.Height)),
Width: uint32(stream.Width), Width: uint32(stream.Width),
Height: uint32(stream.Height), Height: uint32(stream.Height),
// ffmpeg does not report bitrate in mkv files, fallback to bitrate of the whole container // ffmpeg does not report bitrate in mkv files, fallback to bitrate of the whole container
@ -325,9 +322,5 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
ret.MimeCodec = &container ret.MimeCodec = &container
} }
} }
if len(ret.Videos) > 0 {
ret.Video = &ret.Videos[0]
}
return &ret, nil return &ret, nil
} }

View File

@ -83,10 +83,10 @@ func (kf *Keyframe) Scan(src interface{}) error {
type KeyframeKey struct { type KeyframeKey struct {
Sha string Sha string
IsVideo bool IsVideo bool
Index int32 Index uint32
} }
func (s *MetadataService) GetKeyframes(info *MediaInfo, isVideo bool, idx int32) (*Keyframe, error) { func (s *MetadataService) GetKeyframes(info *MediaInfo, isVideo bool, idx uint32) (*Keyframe, error) {
get_running, set := s.keyframeLock.Start(KeyframeKey{ get_running, set := s.keyframeLock.Start(KeyframeKey{
Sha: info.Sha, Sha: info.Sha,
IsVideo: isVideo, IsVideo: isVideo,
@ -138,7 +138,7 @@ func (s *MetadataService) GetKeyframes(info *MediaInfo, isVideo bool, idx int32)
// Retrive video's keyframes and store them inside the kf var. // Retrive video's keyframes and store them inside the kf var.
// Returns when all key frames are retrived (or an error occurs) // Returns when all key frames are retrived (or an error occurs)
// info.ready.Done() is called when more than 100 are retrived (or extraction is done) // info.ready.Done() is called when more than 100 are retrived (or extraction is done)
func getVideoKeyframes(path string, video_idx int32, kf *Keyframe) error { func getVideoKeyframes(path string, video_idx uint32, kf *Keyframe) error {
defer printExecTime("ffprobe keyframe analysis for %s video n%d", path, video_idx)() defer printExecTime("ffprobe keyframe analysis for %s video n%d", path, video_idx)()
// run ffprobe to return all IFrames, IFrames are points where we can split the video in segments. // run ffprobe to return all IFrames, IFrames are points where we can split the video in segments.
// We ask ffprobe to return the time of each frame and it's flags // We ask ffprobe to return the time of each frame and it's flags
@ -224,7 +224,7 @@ func getVideoKeyframes(path string, video_idx int32, kf *Keyframe) error {
} }
// we can pretty much cut audio at any point so no need to get specific frames, just cut every 4s // we can pretty much cut audio at any point so no need to get specific frames, just cut every 4s
func getAudioKeyframes(info *MediaInfo, audio_idx int32, kf *Keyframe) error { func getAudioKeyframes(info *MediaInfo, audio_idx uint32, kf *Keyframe) error {
dummyKeyframeDuration := float64(4) dummyKeyframeDuration := float64(4)
segmentCount := int((float64(info.Duration) / dummyKeyframeDuration) + 1) segmentCount := int((float64(info.Duration) / dummyKeyframeDuration) + 1)
kf.Keyframes = make([]float64, segmentCount) kf.Keyframes = make([]float64, segmentCount)

View File

@ -130,7 +130,6 @@ func (s *MetadataService) getMetadata(path string, sha string) (*MediaInfo, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
v.Quality = QualityFromHeight(v.Height)
ret.Videos = append(ret.Videos, v) ret.Videos = append(ret.Videos, v)
} }

View File

@ -110,10 +110,10 @@ func (q Quality) Height() uint32 {
panic("Invalid quality value") panic("Invalid quality value")
} }
func QualityFromHeight(height uint32) Quality { func (video *Video) Quality() Quality {
qualities := Qualities qualities := Qualities
for _, quality := range qualities { for _, quality := range qualities {
if quality.Height() >= height { if quality.Height() >= video.Height || quality.AverageBitrate() >= video.Bitrate {
return quality return quality
} }
} }

View File

@ -9,7 +9,7 @@ type ClientInfo struct {
client string client string
path string path string
video *VideoKey video *VideoKey
audio int32 audio *uint32
vhead int32 vhead int32
ahead int32 ahead int32
} }
@ -60,7 +60,7 @@ func (t *Tracker) start() {
if info.video == nil { if info.video == nil {
info.video = old.video info.video = old.video
} }
if info.audio == -1 { if info.audio == nil {
info.audio = old.audio info.audio = old.audio
} }
if info.vhead == -1 { if info.vhead == -1 {
@ -77,14 +77,14 @@ func (t *Tracker) start() {
// now that the new info is stored and fixed, kill old streams // now that the new info is stored and fixed, kill old streams
if ok && old.path == info.path { if ok && old.path == info.path {
if old.audio != info.audio && old.audio != -1 { if old.audio != info.audio && old.audio != nil {
t.KillAudioIfDead(old.path, old.audio) t.KillAudioIfDead(old.path, *old.audio)
} }
if old.video != info.video && old.video != nil { if old.video != info.video && old.video != nil {
t.KillVideoIfDead(old.path, *old.video) t.KillVideoIfDead(old.path, *old.video)
} }
if old.vhead != -1 && Abs(info.vhead-old.vhead) > 100 { if old.vhead != -1 && Abs(info.vhead-old.vhead) > 100 {
t.KillOrphanedHeads(old.path, old.video, -1) t.KillOrphanedHeads(old.path, old.video, nil)
} }
if old.ahead != -1 && Abs(info.ahead-old.ahead) > 100 { if old.ahead != -1 && Abs(info.ahead-old.ahead) > 100 {
t.KillOrphanedHeads(old.path, nil, old.audio) t.KillOrphanedHeads(old.path, nil, old.audio)
@ -106,7 +106,7 @@ func (t *Tracker) start() {
delete(t.visitDate, client) delete(t.visitDate, client)
if !t.KillStreamIfDead(info.path) { if !t.KillStreamIfDead(info.path) {
audio_cleanup := info.audio != -1 && t.KillAudioIfDead(info.path, info.audio) audio_cleanup := info.audio != nil && t.KillAudioIfDead(info.path, *info.audio)
video_cleanup := info.video != nil && t.KillVideoIfDead(info.path, *info.video) video_cleanup := info.video != nil && t.KillVideoIfDead(info.path, *info.video)
if !audio_cleanup || !video_cleanup { if !audio_cleanup || !video_cleanup {
t.KillOrphanedHeads(info.path, info.video, info.audio) t.KillOrphanedHeads(info.path, info.video, info.audio)
@ -150,9 +150,9 @@ func (t *Tracker) DestroyStreamIfOld(path string) {
stream.Destroy() stream.Destroy()
} }
func (t *Tracker) KillAudioIfDead(path string, audio int32) bool { func (t *Tracker) KillAudioIfDead(path string, audio uint32) bool {
for _, stream := range t.clients { for _, stream := range t.clients {
if stream.path == path && stream.audio == audio { if stream.path == path && stream.audio != nil && *stream.audio == audio {
return false return false
} }
} }
@ -190,7 +190,7 @@ func (t *Tracker) KillVideoIfDead(path string, video VideoKey) bool {
return true return true
} }
func (t *Tracker) KillOrphanedHeads(path string, video *VideoKey, audio int32) { func (t *Tracker) KillOrphanedHeads(path string, video *VideoKey, audio *uint32) {
stream, ok := t.transcoder.streams.Get(path) stream, ok := t.transcoder.streams.Get(path)
if !ok { if !ok {
return return
@ -202,8 +202,8 @@ func (t *Tracker) KillOrphanedHeads(path string, video *VideoKey, audio int32) {
t.killOrphanedeheads(&vstream.Stream, true) t.killOrphanedeheads(&vstream.Stream, true)
} }
} }
if audio != -1 { if audio != nil {
astream, aok := stream.audios.Get(audio) astream, aok := stream.audios.Get(*audio)
if aok { if aok {
t.killOrphanedeheads(&astream.Stream, false) t.killOrphanedeheads(&astream.Stream, false)
} }

View File

@ -56,7 +56,7 @@ func (t *Transcoder) GetMaster(path string, client string, sha string) (string,
client: client, client: client,
path: path, path: path,
video: nil, video: nil,
audio: -1, audio: nil,
vhead: -1, vhead: -1,
ahead: -1, ahead: -1,
} }
@ -65,7 +65,7 @@ func (t *Transcoder) GetMaster(path string, client string, sha string) (string,
func (t *Transcoder) GetVideoIndex( func (t *Transcoder) GetVideoIndex(
path string, path string,
video int32, video uint32,
quality Quality, quality Quality,
client string, client string,
sha string, sha string,
@ -78,7 +78,7 @@ func (t *Transcoder) GetVideoIndex(
client: client, client: client,
path: path, path: path,
video: &VideoKey{video, quality}, video: &VideoKey{video, quality},
audio: -1, audio: nil,
vhead: -1, vhead: -1,
ahead: -1, ahead: -1,
} }
@ -87,7 +87,7 @@ func (t *Transcoder) GetVideoIndex(
func (t *Transcoder) GetAudioIndex( func (t *Transcoder) GetAudioIndex(
path string, path string,
audio int32, audio uint32,
client string, client string,
sha string, sha string,
) (string, error) { ) (string, error) {
@ -98,7 +98,7 @@ func (t *Transcoder) GetAudioIndex(
t.clientChan <- ClientInfo{ t.clientChan <- ClientInfo{
client: client, client: client,
path: path, path: path,
audio: audio, audio: &audio,
vhead: -1, vhead: -1,
ahead: -1, ahead: -1,
} }
@ -107,7 +107,7 @@ func (t *Transcoder) GetAudioIndex(
func (t *Transcoder) GetVideoSegment( func (t *Transcoder) GetVideoSegment(
path string, path string,
video int32, video uint32,
quality Quality, quality Quality,
segment int32, segment int32,
client string, client string,
@ -122,7 +122,7 @@ func (t *Transcoder) GetVideoSegment(
path: path, path: path,
video: &VideoKey{video, quality}, video: &VideoKey{video, quality},
vhead: segment, vhead: segment,
audio: -1, audio: nil,
ahead: -1, ahead: -1,
} }
return stream.GetVideoSegment(video, quality, segment) return stream.GetVideoSegment(video, quality, segment)
@ -130,7 +130,7 @@ func (t *Transcoder) GetVideoSegment(
func (t *Transcoder) GetAudioSegment( func (t *Transcoder) GetAudioSegment(
path string, path string,
audio int32, audio uint32,
segment int32, segment int32,
client string, client string,
sha string, sha string,
@ -142,7 +142,7 @@ func (t *Transcoder) GetAudioSegment(
t.clientChan <- ClientInfo{ t.clientChan <- ClientInfo{
client: client, client: client,
path: path, path: path,
audio: audio, audio: &audio,
ahead: segment, ahead: segment,
vhead: -1, vhead: -1,
} }

View File

@ -15,3 +15,13 @@ func printExecTime(message string, args ...any) func() {
log.Printf("%s finished in %s", msg, time.Since(start)) log.Printf("%s finished in %s", msg, time.Since(start))
} }
} }
func Filter[E any](s []E, f func(E) bool) []E {
s2 := make([]E, 0, len(s))
for _, e := range s {
if f(e) {
s2 = append(s2, e)
}
}
return s2
}

View File

@ -7,11 +7,11 @@ import (
type VideoStream struct { type VideoStream struct {
Stream Stream
idx int32 video *Video
quality Quality quality Quality
} }
func (t *Transcoder) NewVideoStream(file *FileStream, idx int32, quality Quality) (*VideoStream, error) { func (t *Transcoder) NewVideoStream(file *FileStream, idx uint32, quality Quality) (*VideoStream, error) {
log.Printf( log.Printf(
"Creating a new video stream for %s (n %d) in quality %s", "Creating a new video stream for %s (n %d) in quality %s",
file.Info.Path, file.Info.Path,
@ -25,8 +25,14 @@ func (t *Transcoder) NewVideoStream(file *FileStream, idx int32, quality Quality
} }
ret := new(VideoStream) ret := new(VideoStream)
ret.idx = idx
ret.quality = quality ret.quality = quality
for _, video := range file.Info.Videos {
if video.Index == idx {
ret.video = &video
break
}
}
NewStream(file, keyframes, ret, &ret.Stream) NewStream(file, keyframes, ret, &ret.Stream)
return ret, nil return ret, nil
} }
@ -54,7 +60,7 @@ func closestMultiple(n int32, x int32) int32 {
func (vs *VideoStream) getTranscodeArgs(segments string) []string { func (vs *VideoStream) getTranscodeArgs(segments string) []string {
args := []string{ args := []string{
"-map", fmt.Sprint("0:V:%d", vs.idx), "-map", fmt.Sprint("0:V:%d", vs.video.Index),
} }
if vs.quality == Original { if vs.quality == Original {
@ -65,7 +71,7 @@ func (vs *VideoStream) getTranscodeArgs(segments string) []string {
} }
args = append(args, Settings.HwAccel.EncodeFlags...) args = append(args, Settings.HwAccel.EncodeFlags...)
width := int32(float64(vs.quality.Height()) / float64(vs.file.Info.Video.Height) * float64(vs.file.Info.Video.Width)) width := int32(float64(vs.quality.Height()) / float64(vs.video.Height) * float64(vs.video.Width))
// force a width that is a multiple of two else some apps behave badly. // force a width that is a multiple of two else some apps behave badly.
width = closestMultiple(width, 2) width = closestMultiple(width, 2)
args = append(args, args = append(args,