node build fixed

This commit is contained in:
ra_ma
2025-09-20 14:08:38 +01:00
parent c6ebbe069d
commit 3d298fa434
1516 changed files with 535727 additions and 2 deletions

View File

@@ -0,0 +1,212 @@
package transcoder
import (
"bufio"
"path/filepath"
"seanime/internal/mediastream/videofile"
"seanime/internal/util"
"seanime/internal/util/result"
"strconv"
"strings"
"sync"
"github.com/rs/zerolog"
)
type Keyframe struct {
Sha string
Keyframes []float64
IsDone bool
info *KeyframeInfo
}
type KeyframeInfo struct {
mutex sync.RWMutex
ready sync.WaitGroup
listeners []func(keyframes []float64)
}
func (kf *Keyframe) Get(idx int32) float64 {
kf.info.mutex.RLock()
defer kf.info.mutex.RUnlock()
return kf.Keyframes[idx]
}
func (kf *Keyframe) Slice(start int32, end int32) []float64 {
if end <= start {
return []float64{}
}
kf.info.mutex.RLock()
defer kf.info.mutex.RUnlock()
ref := kf.Keyframes[start:end]
ret := make([]float64, end-start)
copy(ret, ref)
return ret
}
func (kf *Keyframe) Length() (int32, bool) {
kf.info.mutex.RLock()
defer kf.info.mutex.RUnlock()
return int32(len(kf.Keyframes)), kf.IsDone
}
func (kf *Keyframe) add(values []float64) {
kf.info.mutex.Lock()
defer kf.info.mutex.Unlock()
kf.Keyframes = append(kf.Keyframes, values...)
for _, listener := range kf.info.listeners {
listener(kf.Keyframes)
}
}
func (kf *Keyframe) AddListener(callback func(keyframes []float64)) {
kf.info.mutex.Lock()
defer kf.info.mutex.Unlock()
kf.info.listeners = append(kf.info.listeners, callback)
}
var keyframes = result.NewResultMap[string, *Keyframe]()
func GetKeyframes(
path string,
hash string,
logger *zerolog.Logger,
settings *Settings,
) *Keyframe {
ret, _ := keyframes.GetOrSet(hash, func() (*Keyframe, error) {
kf := &Keyframe{
Sha: hash,
IsDone: false,
info: &KeyframeInfo{},
}
kf.info.ready.Add(1)
go func() {
keyframesPath := filepath.Join(settings.StreamDir, hash, "keyframes.json")
if err := getSavedInfo(keyframesPath, kf); err == nil {
logger.Trace().Msgf("transcoder: Keyframes Cache HIT")
kf.info.ready.Done()
return
}
err := getKeyframes(settings.FfprobePath, path, kf, hash, logger)
if err == nil {
saveInfo(keyframesPath, kf)
}
}()
return kf, nil
})
ret.info.ready.Wait()
return ret
}
func getKeyframes(ffprobePath string, path string, kf *Keyframe, hash string, logger *zerolog.Logger) error {
defer printExecTime(logger, "ffprobe analysis for %s", path)()
// Execute ffprobe to retrieve all IFrames. IFrames are specific points in the video we can divide it into segments.
// We instruct ffprobe to return the timestamp and flags of each frame.
// Although it's possible to request ffprobe to return only i-frames (keyframes) using the -skip_frame nokey option, this approach is highly inefficient.
// The inefficiency arises because when this option is used, ffmpeg processes every single frame, which significantly slows down the operation.
cmd := util.NewCmd(
"ffprobe",
"-loglevel", "error",
"-select_streams", "v:0",
"-show_entries", "packet=pts_time,flags",
"-of", "csv=print_section=0",
path,
)
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
scanner := bufio.NewScanner(stdout)
ret := make([]float64, 0, 1000)
max := 100
done := 0
for scanner.Scan() {
frame := scanner.Text()
if frame == "" {
continue
}
x := strings.Split(frame, ",")
pts, flags := x[0], x[1]
// if no video track
if pts == "N/A" {
break
}
// Only take keyframes
if flags[0] != 'K' {
continue
}
fpts, err := strconv.ParseFloat(pts, 64)
if err != nil {
return err
}
// Previously, the aim was to save only those keyframes that had a minimum gap of 3 seconds between them.
// This was to avoid creating segments as short as 0.2 seconds.
// However, there were instances where the -f segment muxer would ignore the specified segment time and choose a random keyframe to cut at.
// To counter this, treat every keyframe as a potential segment.
//if done == 0 && len(ret) == 0 {
//
// // There are instances where videos may not start exactly at 0:00. This needs to be considered,
// // and we should only include keyframes that occur after the video's start time. If not done so,
// // it can lead to a discrepancy in our segment count and potentially duplicate the same segment in the stream.
//
// // For simplicity in code comprehension, we designate 0 as the initial keyframe, even though it's not genuine.
// // This value is never actually passed to ffmpeg.
// ret = append(ret, 0)
// continue
//}
ret = append(ret, fpts)
if len(ret) == max {
kf.add(ret)
if done == 0 {
kf.info.ready.Done()
} else if done >= 500 {
max = 500
}
done += max
// clear the array without reallocing it
ret = ret[:0]
}
}
// If there is less than 2 (i.e. equals 0 or 1 (it happens for audio files with poster))
if len(ret) < 2 {
dummy, err := getDummyKeyframes(ffprobePath, path, hash)
if err != nil {
return err
}
ret = dummy
}
kf.add(ret)
if done == 0 {
kf.info.ready.Done()
}
kf.IsDone = true
return nil
}
func getDummyKeyframes(ffprobePath string, path string, sha string) ([]float64, error) {
dummyKeyframeDuration := float64(2)
info, err := videofile.FfprobeGetInfo(ffprobePath, path, sha)
if err != nil {
return nil, err
}
segmentCount := int((float64(info.Duration) / dummyKeyframeDuration) + 1)
ret := make([]float64, segmentCount)
for segmentIndex := 0; segmentIndex < segmentCount; segmentIndex += 1 {
ret[segmentIndex] = float64(segmentIndex) * dummyKeyframeDuration
}
return ret, nil
}