files: seek anywhere in a re-encode; it restarts from there
A re-encode's playlist is now the whole film on a fixed 4 s grid. A segment that isn't on disk and isn't coming soon moves the encode there; runs stop short of segments already made, and gaps are filled after. Every run is cut the same way (-ss, -output_ts_offset start+10, frag_discont), so segments from different runs join. Repackaging keeps its growing playlist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ff1373e838
commit
450a9d3327
@@ -212,6 +212,69 @@ func hlsArgs(dir, in string, src Info, r Recipe, copyCodec bool) []string {
|
||||
|
||||
const hlsSeconds = 4
|
||||
|
||||
// tsBase is added to every run's timestamps. A run from the top would otherwise start just
|
||||
// below zero (the B-frame delay) and the muxer would shift it to make room, while a run from a
|
||||
// seek point would not: with the base, no run is ever shifted, and a segment means the same
|
||||
// stretch of the film whichever run made it.
|
||||
const tsBase = 10
|
||||
|
||||
// segArgs is one run of a seekable re-encode: segments from..to-1 of the fixed 4 s grid.
|
||||
// Every run is made the same way, the first included, so their segments are interchangeable:
|
||||
// an accurate seek to from×4 s, timestamps put back where that is in the film (+ tsBase),
|
||||
// kept as they are in each fragment (frag_discont), numbered from `from`, and an end just
|
||||
// short of segment `to`, which is already on disk, so the run never writes over it. Segments
|
||||
// land in dir as seg%05d.m4s; init is the init segment's name (only the first run's is used).
|
||||
func segArgs(dir, in string, src Info, r Recipe, from, to, n int, init string) []string {
|
||||
fps := 30.0
|
||||
if src.Video != nil && src.Video.FPS > 0 {
|
||||
fps = src.Video.FPS
|
||||
}
|
||||
start := from * hlsSeconds
|
||||
a := []string{"-nostdin", "-hide_banner", "-loglevel", "error", "-y"}
|
||||
a = append(a, r.Pre...)
|
||||
a = append(a, "-ss", strconv.Itoa(start), "-i", in, "-map", "0:v:0", "-map", "0:a:0?", "-sn", "-dn")
|
||||
if r.VF != "" {
|
||||
a = append(a, "-vf", r.VF)
|
||||
}
|
||||
a = append(a, r.Video...)
|
||||
switch {
|
||||
case src.Audio == nil:
|
||||
a = append(a, "-an")
|
||||
case src.Audio.Codec == "aac":
|
||||
a = append(a, "-c:a", "copy")
|
||||
default:
|
||||
a = append(a, "-c:a", "aac", "-b:a", "160k", "-ac", "2")
|
||||
}
|
||||
gop := int(math.Round(fps * hlsSeconds))
|
||||
if gop < 24 {
|
||||
gop = 24
|
||||
}
|
||||
if gop > 300 {
|
||||
gop = 300
|
||||
}
|
||||
a = append(a, "-g", strconv.Itoa(gop), "-force_key_frames", fmt.Sprintf("expr:gte(t,n_forced*%d)", hlsSeconds))
|
||||
if to < n {
|
||||
a = append(a, "-t", strconv.FormatFloat(float64((to-from)*hlsSeconds)-0.5/fps, 'f', 3, 64))
|
||||
}
|
||||
a = append(a, "-output_ts_offset", strconv.Itoa(start+tsBase),
|
||||
"-f", "hls", "-hls_time", strconv.Itoa(hlsSeconds), "-hls_playlist_type", "event", "-hls_segment_type", "fmp4",
|
||||
"-hls_segment_options", "movflags=+frag_discont", "-start_number", strconv.Itoa(from),
|
||||
"-hls_fmp4_init_filename", init, "-hls_segment_filename", dir+"/seg%05d.m4s", "-hls_flags", "independent_segments+temp_file",
|
||||
"-progress", dir+"/progress", "-stats_period", "1", dir+"/run.m3u8")
|
||||
return a
|
||||
}
|
||||
|
||||
// segCount is how many 4 s segments a seekable re-encode of a file this long lists. The last
|
||||
// 0.3 s are left out of the count, so a container whose sound runs on past the picture never
|
||||
// lists a segment no frame starts.
|
||||
func segCount(duration float64) int {
|
||||
d := duration - 0.3
|
||||
if d < 0 {
|
||||
d = 0
|
||||
}
|
||||
return int(d/hlsSeconds) + 1
|
||||
}
|
||||
|
||||
func has(list []string, x string) bool {
|
||||
for _, v := range list {
|
||||
if v == x {
|
||||
|
||||
Reference in New Issue
Block a user