Kyoo/transcoder/src/filestream.go
2024-01-18 13:32:06 +01:00

107 lines
2.4 KiB
Go

package src
import (
"math"
"os/exec"
"strconv"
"strings"
)
type FileStream struct {
Path string
Keyframes []float64
CanTransmux bool
streams map[Quality]TranscodeStream
}
func NewFileStream(path string) (*FileStream, error) {
keyframes, can_transmux, err := GetKeyframes(path)
if err != nil {
return nil, err
}
return &FileStream{
Path: path,
Keyframes: keyframes,
CanTransmux: can_transmux,
streams: make(map[Quality]TranscodeStream),
}, nil
}
func GetKeyframes(path string) ([]float64, bool, error) {
// run ffprobe to return all IFrames, IFrames are points where we can split the video in segments.
out, err := exec.Command(
"ffprobe",
"-loglevel", "error",
"-select_streams", "v:0",
"-show_entries", "packet=pts_time,flags",
"-of", "csv=print_section=0",
path,
).Output()
// We ask ffprobe to return the time of each frame and it's flags
// We could ask it to return only i-frames (keyframes) with the -skip_frame nokey but using it is extremly slow
// since ffmpeg parses every frames when this flag is set.
if err != nil {
return nil, false, err
}
ret := make([]float64, 0, 300)
last := 0.
can_transmux := true
for _, frame := range strings.Split(string(out), "\n") {
x := strings.Split(frame, ",")
pts, flags := x[0], x[1]
// Only take keyframes
if flags[0] != 'K' {
continue
}
fpts, err := strconv.ParseFloat(pts, 64)
if err != nil {
return nil, false, err
}
// Only save keyframes with at least 3s betweens, we dont want a segment of 0.2s
if fpts-last < 3 {
continue
}
// If we have a segment of more than 20s, create new keyframes during transcode and disable transmuxing
if fpts-last > 20 {
can_transmux = false
fake_count := math.Ceil(fpts - last/4)
duration := (fpts - last) / fake_count
// let the last one be handled normally, this prevents floating points rounding
for fake_count > 1 {
fake_count--
last = last + duration
ret = append(ret, last)
}
}
last = fpts
ret = append(ret, fpts)
}
return ret, can_transmux, nil
}
func (fs *FileStream) IsDead() bool {
for _, s := range fs.streams {
if len(s.Clients) > 0 {
return false
}
}
// TODO: Also check how long this stream has been unused. We dont want to kill streams created 2min ago
return true
}
func (fs *FileStream) Destroy() {
// TODO: kill child process and delete data
}
func (fs *FileStream) GetMaster() string {
return ""
}