110 lines
2.6 KiB
Go
110 lines
2.6 KiB
Go
package convert
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Progress struct {
|
|
Percent float64 `json:"percent"`
|
|
OutTimeSec float64 `json:"outTimeSec"`
|
|
TotalSec float64 `json:"totalSec"`
|
|
FPS float64 `json:"fps"`
|
|
Speed string `json:"speed"`
|
|
TotalSize int64 `json:"totalSize"`
|
|
ETASec float64 `json:"etaSec"`
|
|
Done bool `json:"done"`
|
|
Error string `json:"error,omitempty"`
|
|
OutputPath string `json:"outputPath,omitempty"`
|
|
OutputBytes int64 `json:"outputBytes,omitempty"`
|
|
}
|
|
|
|
type progressParser struct {
|
|
totalSec float64
|
|
current Progress
|
|
}
|
|
|
|
func newProgressParser(totalSec float64) *progressParser {
|
|
return &progressParser{
|
|
totalSec: totalSec,
|
|
current: Progress{
|
|
TotalSec: totalSec,
|
|
},
|
|
}
|
|
}
|
|
|
|
// feed parses a single line from `ffmpeg -progress pipe:1` and returns (Progress, true) when a block completes.
|
|
func (p *progressParser) feed(line string) (Progress, bool) {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
return Progress{}, false
|
|
}
|
|
eq := strings.IndexByte(line, '=')
|
|
if eq <= 0 {
|
|
return Progress{}, false
|
|
}
|
|
key := strings.TrimSpace(line[:eq])
|
|
val := strings.TrimSpace(line[eq+1:])
|
|
|
|
switch key {
|
|
case "fps":
|
|
if v, err := strconv.ParseFloat(val, 64); err == nil {
|
|
p.current.FPS = v
|
|
}
|
|
case "total_size":
|
|
if v, err := strconv.ParseInt(val, 10, 64); err == nil && v >= 0 {
|
|
p.current.TotalSize = v
|
|
}
|
|
case "out_time_us":
|
|
if v, err := strconv.ParseInt(val, 10, 64); err == nil && v >= 0 {
|
|
p.current.OutTimeSec = float64(v) / 1_000_000.0
|
|
}
|
|
case "out_time_ms":
|
|
// Note: ffmpeg's out_time_ms is actually microseconds in many versions,
|
|
// so only use if out_time_us wasn't set.
|
|
if p.current.OutTimeSec == 0 {
|
|
if v, err := strconv.ParseInt(val, 10, 64); err == nil && v >= 0 {
|
|
p.current.OutTimeSec = float64(v) / 1_000_000.0
|
|
}
|
|
}
|
|
case "speed":
|
|
p.current.Speed = val
|
|
case "progress":
|
|
if p.totalSec > 0 {
|
|
pct := (p.current.OutTimeSec / p.totalSec) * 100.0
|
|
if pct > 100 {
|
|
pct = 100
|
|
}
|
|
if pct < 0 {
|
|
pct = 0
|
|
}
|
|
p.current.Percent = pct
|
|
|
|
if sp := parseSpeedFactor(p.current.Speed); sp > 0 && p.current.OutTimeSec < p.totalSec {
|
|
p.current.ETASec = (p.totalSec - p.current.OutTimeSec) / sp
|
|
} else {
|
|
p.current.ETASec = 0
|
|
}
|
|
}
|
|
if val == "end" {
|
|
p.current.Percent = 100
|
|
p.current.ETASec = 0
|
|
p.current.Done = true
|
|
}
|
|
return p.current, true
|
|
}
|
|
return Progress{}, false
|
|
}
|
|
|
|
func parseSpeedFactor(speedStr string) float64 {
|
|
s := strings.TrimSpace(strings.TrimSuffix(speedStr, "x"))
|
|
if s == "" || s == "N/A" {
|
|
return 0
|
|
}
|
|
v, err := strconv.ParseFloat(s, 64)
|
|
if err != nil || v <= 0 {
|
|
return 0
|
|
}
|
|
return v
|
|
}
|