Files

117 lines
2.8 KiB
Go
Raw Permalink Normal View History

2026-07-29 02:17:57 +08:00
package ffmpeg
import (
"regexp"
"strconv"
"strings"
)
// Progress holds real-time encoding progress from ffmpeg stderr.
type Progress struct {
Frame int64 `json:"frame"`
FPS float64 `json:"fps"`
Q float64 `json:"q"`
Size string `json:"size"`
Time string `json:"time"`
Bitrate string `json:"bitrate"`
Speed string `json:"speed"`
Eta string `json:"eta"`
Percent float64 `json:"percent"`
}
2026-07-30 14:30:21 +08:00
// kvPairRe matches a single key=value token from an ffmpeg progress line.
// Keys are alphanumeric (e.g. frame, dup, Lsize). Values are the contiguous
// non-whitespace characters after the '=' — this handles N/A, kbits/s, times,
// and numeric values equally.
var kvPairRe = regexp.MustCompile(`(\w+)=\s*(\S+)`)
2026-07-29 02:17:57 +08:00
2026-07-30 14:30:21 +08:00
// parseProgressLine parses an ffmpeg stderr progress line into a Progress struct.
// Instead of relying on a fixed field order, it extracts every key=value pair
// and picks the ones we care about. This tolerates extra fields (dup, drop,
// Lsize vs size, etc.) that appear between standard ffmpeg releases.
2026-07-29 02:17:57 +08:00
func parseProgressLine(line string) (Progress, bool) {
2026-07-30 14:30:21 +08:00
pairs := kvPairRe.FindAllStringSubmatch(line, -1)
if len(pairs) == 0 {
return Progress{}, false
}
kv := make(map[string]string, len(pairs))
for _, m := range pairs {
kv[m[1]] = m[2]
}
// Frame is required — without it we don't have a real progress line.
frameStr, ok := kv["frame"]
if !ok {
2026-07-29 02:17:57 +08:00
return Progress{}, false
}
2026-07-30 14:30:21 +08:00
frame, _ := strconv.ParseInt(frameStr, 10, 64)
fps, _ := strconv.ParseFloat(kv["fps"], 64)
q, _ := strconv.ParseFloat(kv["q"], 64)
// ffmpeg emits either "size" or "Lsize" depending on context.
size := kv["size"]
if size == "" {
size = kv["Lsize"]
}
if size == "N/A" {
size = ""
}
bitrate := kv["bitrate"]
if bitrate == "N/A" {
bitrate = ""
}
2026-07-29 02:17:57 +08:00
return Progress{
Frame: frame,
FPS: fps,
Q: q,
2026-07-30 14:30:21 +08:00
Size: size,
Time: kv["time"],
Bitrate: bitrate,
Speed: kv["speed"],
2026-07-29 02:17:57 +08:00
}, true
}
// parseSpeedMultiplier converts "1.5x" → 1.5.
func parseSpeedMultiplier(s string) float64 {
s = strings.TrimSuffix(s, "x")
v, _ := strconv.ParseFloat(s, 64)
return v
}
// formatSeconds converts seconds to "MM:SS" or "HH:MM:SS".
func formatSeconds(secs float64) string {
if secs < 0 {
secs = 0
}
h := int(secs) / 3600
m := (int(secs) % 3600) / 60
s := int(secs) % 60
if h > 0 {
return strconv.Itoa(h) + ":" + pad2(m) + ":" + pad2(s)
}
return pad2(m) + ":" + pad2(s)
}
func pad2(n int) string {
if n < 10 {
return "0" + strconv.Itoa(n)
}
return strconv.Itoa(n)
}
// parseTimeSeconds converts "HH:MM:SS.mm" to seconds.
func parseTimeSeconds(t string) float64 {
parts := strings.Split(t, ":")
if len(parts) != 3 {
return 0
}
h, _ := strconv.ParseFloat(parts[0], 64)
m, _ := strconv.ParseFloat(parts[1], 64)
s, _ := strconv.ParseFloat(parts[2], 64)
return h*3600 + m*60 + s
}