mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-03 05:34:23 -04:00
wip: Switch to fmp4 intead of mgepg
This commit is contained in:
parent
28b130540b
commit
2808da8f6f
@ -109,7 +109,7 @@ func (h *Handler) GetAudioIndex(c echo.Context) error {
|
|||||||
//
|
//
|
||||||
// Retrieve a chunk of a transmuxed video.
|
// Retrieve a chunk of a transmuxed video.
|
||||||
//
|
//
|
||||||
// Path: /:path/:quality/segments-:chunk.ts
|
// Path: /:path/:quality/segments-:chunk.m4s
|
||||||
func (h *Handler) GetVideoSegment(c echo.Context) error {
|
func (h *Handler) GetVideoSegment(c echo.Context) error {
|
||||||
quality, err := src.QualityFromString(c.Param("quality"))
|
quality, err := src.QualityFromString(c.Param("quality"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -139,7 +139,7 @@ func (h *Handler) GetVideoSegment(c echo.Context) error {
|
|||||||
//
|
//
|
||||||
// Retrieve a chunk of a transcoded audio.
|
// Retrieve a chunk of a transcoded audio.
|
||||||
//
|
//
|
||||||
// Path: /:path/audio/:audio/segments-:chunk.ts
|
// Path: /:path/audio/:audio/segments-:chunk.m4s
|
||||||
func (h *Handler) GetAudioSegment(c echo.Context) error {
|
func (h *Handler) GetAudioSegment(c echo.Context) error {
|
||||||
audio, err := strconv.ParseInt(c.Param("audio"), 10, 32)
|
audio, err := strconv.ParseInt(c.Param("audio"), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -19,7 +19,7 @@ func NewAudioStream(file *FileStream, idx int32) *AudioStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (as *AudioStream) getOutPath(encoder_id int) string {
|
func (as *AudioStream) getOutPath(encoder_id int) string {
|
||||||
return fmt.Sprintf("%s/segment-a%d-%d-%%d.ts", as.file.Out, as.index, encoder_id)
|
return fmt.Sprintf("%s/segment-a%d-%d-%%d.m4s", as.file.Out, as.index, encoder_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *AudioStream) getFlags() Flags {
|
func (as *AudioStream) getFlags() Flags {
|
||||||
|
@ -34,12 +34,12 @@ type Stream struct {
|
|||||||
file *FileStream
|
file *FileStream
|
||||||
segments []Segment
|
segments []Segment
|
||||||
heads []Head
|
heads []Head
|
||||||
// the lock used for the the heads
|
// the lock used for the heads
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type Segment struct {
|
type Segment struct {
|
||||||
// channel open if the segment is not ready. closed if ready.
|
// channel open if the segment is not ready. Closed if ready.
|
||||||
// one can check if segment 1 is open by doing:
|
// one can check if segment 1 is open by doing:
|
||||||
//
|
//
|
||||||
// ts.isSegmentReady(1).
|
// ts.isSegmentReady(1).
|
||||||
@ -119,8 +119,8 @@ func toSegmentStr(segments []float64) string {
|
|||||||
func (ts *Stream) run(start int32) error {
|
func (ts *Stream) run(start int32) error {
|
||||||
// Start the transcode up to the 100th segment (or less)
|
// Start the transcode up to the 100th segment (or less)
|
||||||
length, is_done := ts.file.Keyframes.Length()
|
length, is_done := ts.file.Keyframes.Length()
|
||||||
end := min(start+100, length)
|
end := min(start+4, length)
|
||||||
// if keyframes analysys is not finished, always have a 1-segment padding
|
// if keyframes analysis is not finished, always have a 1-segment padding
|
||||||
// for the extra segment needed for precise split (look comment before -to flag)
|
// for the extra segment needed for precise split (look comment before -to flag)
|
||||||
if !is_done {
|
if !is_done {
|
||||||
end -= 2
|
end -= 2
|
||||||
@ -156,19 +156,19 @@ func (ts *Stream) run(start int32) error {
|
|||||||
|
|
||||||
// Include both the start and end delimiter because -ss and -to are not accurate
|
// Include both the start and end delimiter because -ss and -to are not accurate
|
||||||
// Having an extra segment allows us to cut precisely the segments we want with the
|
// Having an extra segment allows us to cut precisely the segments we want with the
|
||||||
// -f segment that does cut the begining and the end at the keyframe like asked
|
// -f segment that does cut the beginning and the end at the keyframe like asked
|
||||||
start_ref := float64(0)
|
start_ref := float64(0)
|
||||||
start_segment := start
|
start_segment := start
|
||||||
if start != 0 {
|
if start != 0 {
|
||||||
// we always take on segment before the current one, for different reasons for audio/video:
|
// we always take on segment before the current one, for different reasons for audio/video:
|
||||||
// - Audio: we need context before the starting point, without that ffmpeg doesnt know what to do and leave ~100ms of silence
|
// - Audio: we need context before the starting point, without that ffmpeg doesn't know what to do and leave ~100ms of silence
|
||||||
// - Video: if a segment is really short (between 20 and 100ms), the padding given in the else block bellow is not enough and
|
// - Video: if a segment is really short (between 20 and 100ms), the padding given in the else block bellow is not enough and
|
||||||
// the previous segment is played another time. the -segment_times is way more precise so it does not do the same with this one
|
// the previous segment is played another time. The -segment_times is way more precise so it does not do the same with this one
|
||||||
start_segment = start - 1
|
start_segment = start - 1
|
||||||
if ts.handle.getFlags()&AudioF != 0 {
|
if ts.handle.getFlags()&AudioF != 0 {
|
||||||
start_ref = ts.file.Keyframes.Get(start_segment)
|
start_ref = ts.file.Keyframes.Get(start_segment)
|
||||||
} else {
|
} else {
|
||||||
// the param for the -ss takes the keyframe before the specificed time
|
// the param for the -ss takes the keyframe before the specified time
|
||||||
// (if the specified time is a keyframe, it either takes that keyframe or the one before)
|
// (if the specified time is a keyframe, it either takes that keyframe or the one before)
|
||||||
// to prevent this weird behavior, we specify a bit after the keyframe that interest us
|
// to prevent this weird behavior, we specify a bit after the keyframe that interest us
|
||||||
|
|
||||||
@ -222,7 +222,7 @@ func (ts *Stream) run(start int32) error {
|
|||||||
end_ref := ts.file.Keyframes.Get(end + 1)
|
end_ref := ts.file.Keyframes.Get(end + 1)
|
||||||
// it seems that the -to is confused when -ss seek before the given time (because it searches for a keyframe)
|
// it seems that the -to is confused when -ss seek before the given time (because it searches for a keyframe)
|
||||||
// add back the time that would be lost otherwise
|
// add back the time that would be lost otherwise
|
||||||
// this only appens when -to is before -i but having -to after -i gave a bug (not sure, don't remember)
|
// this only happen when -to is before -i but having -to after -i gave a bug (not sure, don't remember)
|
||||||
end_ref += start_ref - ts.file.Keyframes.Get(start_segment)
|
end_ref += start_ref - ts.file.Keyframes.Get(start_segment)
|
||||||
args = append(args,
|
args = append(args,
|
||||||
"-to", fmt.Sprintf("%.6f", end_ref),
|
"-to", fmt.Sprintf("%.6f", end_ref),
|
||||||
@ -247,12 +247,13 @@ func (ts *Stream) run(start int32) error {
|
|||||||
"-f", "segment",
|
"-f", "segment",
|
||||||
// needed for rounding issues when forcing keyframes
|
// needed for rounding issues when forcing keyframes
|
||||||
// recommended value is 1/(2*frame_rate), which for a 24fps is ~0.021
|
// recommended value is 1/(2*frame_rate), which for a 24fps is ~0.021
|
||||||
// we take a little bit more than that to be extra safe but too much can be harmfull
|
// we take a little bit more than that to be extra safe but too much can be harmful
|
||||||
// when segments are short (can make the video repeat itself)
|
// when segments are short (can make the video repeat itself)
|
||||||
"-segment_time_delta", "0.05",
|
"-segment_time_delta", "0.05",
|
||||||
"-segment_format", "mpegts",
|
"-segment_format", "mp4",
|
||||||
|
"-segment_format_options", "movflags=dash",
|
||||||
"-segment_times", toSegmentStr(Map(segments, func(seg float64, _ int) float64 {
|
"-segment_times", toSegmentStr(Map(segments, func(seg float64, _ int) float64 {
|
||||||
// segment_times want durations, not timestamps so we must substract the -ss param
|
// segment_times want durations, not timestamps so we must subtract the -ss param
|
||||||
// since we give a greater value to -ss to prevent wrong seeks but -segment_times
|
// since we give a greater value to -ss to prevent wrong seeks but -segment_times
|
||||||
// needs precise segments, we use the keyframe we want to seek to as a reference.
|
// needs precise segments, we use the keyframe we want to seek to as a reference.
|
||||||
return seg - ts.file.Keyframes.Get(start_segment)
|
return seg - ts.file.Keyframes.Get(start_segment)
|
||||||
@ -292,7 +293,7 @@ func (ts *Stream) run(start int32) error {
|
|||||||
|
|
||||||
if segment < start {
|
if segment < start {
|
||||||
// This happen because we use -f segments for accurate cutting (since -ss is not)
|
// This happen because we use -f segments for accurate cutting (since -ss is not)
|
||||||
// check comment at begining of function for more info
|
// check comment at beginning of function for more info
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ts.lock.Lock()
|
ts.lock.Lock()
|
||||||
@ -307,7 +308,7 @@ func (ts *Stream) run(start int32) error {
|
|||||||
ts.segments[segment].encoder = encoder_id
|
ts.segments[segment].encoder = encoder_id
|
||||||
close(ts.segments[segment].channel)
|
close(ts.segments[segment].channel)
|
||||||
if segment == end-1 {
|
if segment == end-1 {
|
||||||
// file finished, ffmped will finish soon on it's own
|
// file finished, ffmpeg will finish soon on it's own
|
||||||
should_stop = true
|
should_stop = true
|
||||||
} else if ts.isSegmentReady(segment + 1) {
|
} else if ts.isSegmentReady(segment + 1) {
|
||||||
cmd.Process.Signal(os.Interrupt)
|
cmd.Process.Signal(os.Interrupt)
|
||||||
@ -333,7 +334,7 @@ func (ts *Stream) run(start int32) error {
|
|||||||
if exiterr, ok := err.(*exec.ExitError); ok && exiterr.ExitCode() == 255 {
|
if exiterr, ok := err.(*exec.ExitError); ok && exiterr.ExitCode() == 255 {
|
||||||
log.Printf("ffmpeg %d was killed by us", encoder_id)
|
log.Printf("ffmpeg %d was killed by us", encoder_id)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
log.Printf("ffmpeg %d occured an error: %s: %s", encoder_id, err, stderr.String())
|
log.Printf("ffmpeg %d occurred an error: %s: %s", encoder_id, err, stderr.String())
|
||||||
} else {
|
} else {
|
||||||
log.Printf("ffmpeg %d finished successfully", encoder_id)
|
log.Printf("ffmpeg %d finished successfully", encoder_id)
|
||||||
}
|
}
|
||||||
@ -351,7 +352,7 @@ func (ts *Stream) GetIndex() (string, error) {
|
|||||||
// playlist type is event since we can append to the list if Keyframe.IsDone is false.
|
// playlist type is event since we can append to the list if Keyframe.IsDone is false.
|
||||||
// start time offset makes the stream start at 0s instead of ~3segments from the end (requires version 6 of hls)
|
// start time offset makes the stream start at 0s instead of ~3segments from the end (requires version 6 of hls)
|
||||||
index := `#EXTM3U
|
index := `#EXTM3U
|
||||||
#EXT-X-VERSION:6
|
#EXT-X-VERSION:7
|
||||||
#EXT-X-PLAYLIST-TYPE:EVENT
|
#EXT-X-PLAYLIST-TYPE:EVENT
|
||||||
#EXT-X-START:TIME-OFFSET=0
|
#EXT-X-START:TIME-OFFSET=0
|
||||||
#EXT-X-TARGETDURATION:4
|
#EXT-X-TARGETDURATION:4
|
||||||
@ -362,13 +363,13 @@ func (ts *Stream) GetIndex() (string, error) {
|
|||||||
|
|
||||||
for segment := int32(0); segment < length-1; segment++ {
|
for segment := int32(0); segment < length-1; segment++ {
|
||||||
index += fmt.Sprintf("#EXTINF:%.6f\n", ts.file.Keyframes.Get(segment+1)-ts.file.Keyframes.Get(segment))
|
index += fmt.Sprintf("#EXTINF:%.6f\n", ts.file.Keyframes.Get(segment+1)-ts.file.Keyframes.Get(segment))
|
||||||
index += fmt.Sprintf("segment-%d.ts\n", segment)
|
index += fmt.Sprintf("segment-%d.m4s\n", segment)
|
||||||
}
|
}
|
||||||
// do not forget to add the last segment between the last keyframe and the end of the file
|
// do not forget to add the last segment between the last keyframe and the end of the file
|
||||||
// if the keyframes extraction is not done, do not bother to add it, it will be retrived on the next index retrival
|
// if the keyframes extraction is not done, do not bother to add it, it will be retrieval on the next index retrieval
|
||||||
if is_done {
|
if is_done {
|
||||||
index += fmt.Sprintf("#EXTINF:%.6f\n", float64(ts.file.Info.Duration)-ts.file.Keyframes.Get(length-1))
|
index += fmt.Sprintf("#EXTINF:%.6f\n", float64(ts.file.Info.Duration)-ts.file.Keyframes.Get(length-1))
|
||||||
index += fmt.Sprintf("segment-%d.ts\n", length-1)
|
index += fmt.Sprintf("segment-%d.m4s\n", length-1)
|
||||||
index += `#EXT-X-ENDLIST`
|
index += `#EXT-X-ENDLIST`
|
||||||
}
|
}
|
||||||
return index, nil
|
return index, nil
|
||||||
@ -395,19 +396,19 @@ func (ts *Stream) GetSegment(segment int32) (string, error) {
|
|||||||
if !ready {
|
if !ready {
|
||||||
// Only start a new encode if there is too big a distance between the current encoder and the segment.
|
// Only start a new encode if there is too big a distance between the current encoder and the segment.
|
||||||
if distance > 60 || !is_scheduled {
|
if distance > 60 || !is_scheduled {
|
||||||
log.Printf("Creating new head for %d since closest head is %fs aways", segment, distance)
|
log.Printf("Creating new head for %d since closest head is %fs away", segment, distance)
|
||||||
err := ts.run(segment)
|
err := ts.run(segment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Waiting for segment %d since encoder head is %fs aways", segment, distance)
|
log.Printf("Waiting for segment %d since encoder head is %fs away", segment, distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-readyChan:
|
case <-readyChan:
|
||||||
case <-time.After(60 * time.Second):
|
case <-time.After(60 * time.Second):
|
||||||
return "", errors.New("could not retrive the selected segment (timeout)")
|
return "", errors.New("could not retrieve the selected segment (timeout)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ts.prerareNextSegements(segment)
|
ts.prerareNextSegements(segment)
|
||||||
@ -428,7 +429,7 @@ func (ts *Stream) prerareNextSegements(segment int32) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// only start encode for segments not planned (getMinEncoderDistance returns Inf for them)
|
// only start encode for segments not planned (getMinEncoderDistance returns Inf for them)
|
||||||
// or if they are 60s away (asume 5s per segments)
|
// or if they are 60s away (assume 5s per segments)
|
||||||
if ts.getMinEncoderDistance(i) < 60+(5*float64(i-segment)) {
|
if ts.getMinEncoderDistance(i) < 60+(5*float64(i-segment)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ func (vs *VideoStream) getFlags() Flags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (vs *VideoStream) getOutPath(encoder_id int) string {
|
func (vs *VideoStream) getOutPath(encoder_id int) string {
|
||||||
return fmt.Sprintf("%s/segment-%s-%d-%%d.ts", vs.file.Out, vs.quality, encoder_id)
|
return fmt.Sprintf("%s/segment-%s-%d-%%d.m4s", vs.file.Out, vs.quality, encoder_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func closestMultiple(n int32, x int32) int32 {
|
func closestMultiple(n int32, x int32) int32 {
|
||||||
|
@ -72,7 +72,7 @@ func GetClientId(c echo.Context) (string, error) {
|
|||||||
|
|
||||||
func ParseSegment(segment string) (int32, error) {
|
func ParseSegment(segment string) (int32, error) {
|
||||||
var ret int32
|
var ret int32
|
||||||
_, err := fmt.Sscanf(segment, "segment-%d.ts", &ret)
|
_, err := fmt.Sscanf(segment, "segment-%d.m4s", &ret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, echo.NewHTTPError(http.StatusBadRequest, "Could not parse segment.")
|
return 0, echo.NewHTTPError(http.StatusBadRequest, "Could not parse segment.")
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user