Files
goencode/internal/encoder/ffmpeg.go
T

134 lines
3.4 KiB
Go

package encoder
import (
"bytes"
"fmt"
"os/exec"
"strconv"
"strings"
)
// FFmpegManager handles execution and probing
type FFmpegManager struct {
BinaryPath string
}
func NewManager(binaryPath string) *FFmpegManager {
if binaryPath == "" {
binaryPath = "ffmpeg"
}
return &FFmpegManager{BinaryPath: binaryPath}
}
func (m *FFmpegManager) ProbeCodec(filePath, streamType string) (string, error) {
cmd := exec.Command("ffprobe", "-v", "quiet", "-select_streams", streamType+":0",
"-show_entries", "stream=codec_name", "-of", "csv=p=0", filePath)
out, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("ffprobe error: %w", err)
}
return strings.TrimSpace(string(out)), nil
}
func (m *FFmpegManager) ProbeResolution(filePath string) (int, int, error) {
cmd := exec.Command("ffprobe", "-v", "quiet", "-select_streams", "v:0",
"-show_entries", "stream=width,height", "-of", "csv=p=0", filePath)
out, err := cmd.Output()
if err != nil {
return 0, 0, fmt.Errorf("ffprobe error: %w", err)
}
parts := strings.Split(strings.TrimSpace(string(out)), ",")
if len(parts) != 2 {
return 0, 0, fmt.Errorf("invalid resolution format")
}
w, err1 := strconv.Atoi(parts[0])
h, err2 := strconv.Atoi(parts[1])
if err1 != nil || err2 != nil {
return 0, 0, fmt.Errorf("invalid resolution numbers")
}
return w, h, nil
}
func (m *FFmpegManager) ProbeDuration(filePath string) (float64, error) {
cmd := exec.Command("ffprobe", "-v", "quiet", "-show_entries", "format=duration", "-of", "csv=p=0", filePath)
out, err := cmd.Output()
if err != nil {
return 0, fmt.Errorf("ffprobe error: %w", err)
}
return strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
}
func (m *FFmpegManager) ValidateFile(filePath string) error {
cmd := exec.Command("ffprobe", "-v", "quiet", "-show_format", "-show_streams", filePath)
if err := cmd.Run(); err != nil {
return fmt.Errorf("file validation failed: %w", err)
}
return nil
}
// ParseProgress is a utility to parse time=XX:XX:XX.XX from stderr line to calculate percentage if duration is known.
// Will be used by the queue worker reading from a pipe.
func ParseProgress(line string, durationSec float64) float64 {
// Example line: frame= 123 fps= 30 q=22.0 size= 2048kB time=00:00:10.50 bitrate=1500.0kbits/s speed=1.5x
idx := strings.Index(line, "time=")
if idx == -1 {
return -1
}
timeStr := line[idx+5:]
spaceIdx := strings.Index(timeStr, " ")
if spaceIdx != -1 {
timeStr = timeStr[:spaceIdx]
}
// timeStr is HH:MM:SS.ms
parts := strings.Split(timeStr, ":")
if len(parts) != 3 {
return -1
}
h, _ := strconv.ParseFloat(parts[0], 64)
m, _ := strconv.ParseFloat(parts[1], 64)
s, _ := strconv.ParseFloat(parts[2], 64)
currentSec := h*3600 + m*60 + s
if durationSec > 0 {
return (currentSec / durationSec) * 100.0
}
return -1
}
// ShlexSplit splits a command string like a shell would.
// A simple implementation since go doesn't have shlex.split built-in.
func ShlexSplit(s string) []string {
var args []string
var buf bytes.Buffer
inQuotes := false
var quoteChar rune
for _, r := range s {
if inQuotes {
if r == quoteChar {
inQuotes = false
} else {
buf.WriteRune(r)
}
} else {
if r == '\'' || r == '"' {
inQuotes = true
quoteChar = r
} else if r == ' ' {
if buf.Len() > 0 {
args = append(args, buf.String())
buf.Reset()
}
} else {
buf.WriteRune(r)
}
}
}
if buf.Len() > 0 {
args = append(args, buf.String())
}
return args
}