功能完善

UI改进
This commit is contained in:
sansen
2026-07-30 14:30:21 +08:00
parent 7e391d9381
commit f5dce6efe5
18 changed files with 533 additions and 230 deletions
+29 -10
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"os/exec"
"strconv"
"strings"
"syscall"
)
@@ -141,6 +142,24 @@ func escapeFilterPath(path string) string {
return s
}
// qualityArg maps a CRF-like quality value to encoder-specific arguments.
// Software encoders use -crf, NVENC uses -cq, QSV uses -global_quality,
// and AMF uses -qp_i/-qp_p for constant-quality encoding.
func qualityArg(codec string, crf int) []string {
v := strconv.Itoa(crf)
lower := strings.ToLower(codec)
switch {
case strings.Contains(lower, "nvenc"):
return []string{"-cq", v}
case strings.Contains(lower, "qsv"):
return []string{"-global_quality", v}
case strings.Contains(lower, "amf"):
return []string{"-qp_i", v, "-qp_p", v}
default:
return []string{"-crf", v}
}
}
func tailLines(s string, n int) string {
lines := strings.Split(s, "\n")
if len(lines) > n {
@@ -197,11 +216,11 @@ func BuildEncodeArgs(input string, output string, s EncodeSettings, hwAccel stri
args = append(args, "-i", input)
if s.HWEncoder != "" {
args = append(args, "-c:v", s.HWEncoder)
} else {
args = append(args, "-c:v", s.VideoCodec)
codec := s.HWEncoder
if codec == "" {
codec = s.VideoCodec
}
args = append(args, "-c:v", codec)
if s.Preset != "" {
// libx* uses named presets (fast, medium…), HW encoders use p1p7
@@ -209,7 +228,7 @@ func BuildEncodeArgs(input string, output string, s EncodeSettings, hwAccel stri
}
if s.CRF > 0 {
args = append(args, "-crf", fmt.Sprintf("%d", s.CRF))
args = append(args, qualityArg(codec, s.CRF)...)
} else if s.VideoBitrate != "" {
args = append(args, "-b:v", s.VideoBitrate)
}
@@ -327,16 +346,16 @@ func BuildSubtitleArgs(input string, output string, s SubtitleSettings, encode E
args = append(args, "-vf", strings.Join(subFilters, ","))
}
if encode.HWEncoder != "" {
args = append(args, "-c:v", encode.HWEncoder)
} else {
args = append(args, "-c:v", encode.VideoCodec)
codec := encode.HWEncoder
if codec == "" {
codec = encode.VideoCodec
}
args = append(args, "-c:v", codec)
if encode.Preset != "" {
args = append(args, "-preset", encode.Preset)
}
if encode.CRF > 0 {
args = append(args, "-crf", fmt.Sprintf("%d", encode.CRF))
args = append(args, qualityArg(codec, encode.CRF)...)
} else if encode.VideoBitrate != "" {
args = append(args, "-b:v", encode.VideoBitrate)
}
+43 -12
View File
@@ -19,28 +19,59 @@ type Progress struct {
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+)`,
)
// 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+)`)
// 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.
func parseProgressLine(line string) (Progress, bool) {
m := progressRe.FindStringSubmatch(line)
if m == nil {
pairs := kvPairRe.FindAllStringSubmatch(line, -1)
if len(pairs) == 0 {
return Progress{}, false
}
frame, _ := strconv.ParseInt(m[1], 10, 64)
fps, _ := strconv.ParseFloat(m[2], 64)
q, _ := strconv.ParseFloat(m[3], 64)
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 {
return Progress{}, false
}
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 = ""
}
return Progress{
Frame: frame,
FPS: fps,
Q: q,
Size: m[4],
Time: m[5],
Bitrate: m[6],
Speed: m[7],
Size: size,
Time: kv["time"],
Bitrate: bitrate,
Speed: kv["speed"],
}, true
}
+1 -1
View File
@@ -203,7 +203,7 @@ func (m *Manager) runTask(t *Task) {
if sub.Source == "internal" {
tmpPath := t.OutputFile + fmt.Sprintf(".sub_%d_tmp.srt", i)
if _, err := m.exec.RunSync("-y", "-i", t.InputFile,
"-map", fmt.Sprintf("0:s:%d", sub.Index), "-c:s", "srt", tmpPath); err != nil {
"-map", fmt.Sprintf("0:%d", sub.Index), "-c:s", "srt", tmpPath); err != nil {
m.completeTask(t, false, fmt.Sprintf("提取字幕轨道 #%d 失败: %v", sub.Index, err))
return
}