Files
convert/internal/convert/progress.go
T
m4xx1m 65387c5dcd
Build Windows Installer / build (push) Successful in 4m11s
feat: ConvertGUI v2.0 (Wails + preview/trim + NSIS + Gitea CI)
2026-11-12 17:30:44 +03:00

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
}