feat: FFmpeg GUI desktop application
Wails v2 + Vue3 + Go project with: - Encode/Remux/Subtitle burn with hardware acceleration - Real-time progress with ffmpeg stderr parsing (\r delimiter handling) - Task queue with cancel support - Per-task log viewer with color-coded output - Custom frameless window with resize support - Dark/light theme toggle - Hardware encoder detection (NVENC/QSV/AMF)
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
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"`
|
||||
}
|
||||
|
||||
var progressRe = regexp.MustCompile(
|
||||
`frame=\s*(\d+)\s+fps=\s*([\d.]+)\s+q=\s*([\d.-]+)\s+(?:size=\s*(\S+)\s+)?time=\s*([\d:.]+)\s+bitrate=\s*(\S+)\s+speed=\s*(\S+)`,
|
||||
)
|
||||
|
||||
func parseProgressLine(line string) (Progress, bool) {
|
||||
m := progressRe.FindStringSubmatch(line)
|
||||
if m == nil {
|
||||
return Progress{}, false
|
||||
}
|
||||
|
||||
frame, _ := strconv.ParseInt(m[1], 10, 64)
|
||||
fps, _ := strconv.ParseFloat(m[2], 64)
|
||||
q, _ := strconv.ParseFloat(m[3], 64)
|
||||
|
||||
return Progress{
|
||||
Frame: frame,
|
||||
FPS: fps,
|
||||
Q: q,
|
||||
Size: m[4],
|
||||
Time: m[5],
|
||||
Bitrate: m[6],
|
||||
Speed: m[7],
|
||||
}, 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
|
||||
}
|
||||
Reference in New Issue
Block a user