Apply current convert settings to snippets (scale/aspect/fps/bitrate/preset)
This commit is contained in:
parent
9245caeb4c
commit
a056765673
103
main.go
103
main.go
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
|
@ -69,6 +70,9 @@ var (
|
||||||
feedbackBundler = utils.NewFeedbackBundler()
|
feedbackBundler = utils.NewFeedbackBundler()
|
||||||
appVersion = "v0.1.0-dev14"
|
appVersion = "v0.1.0-dev14"
|
||||||
|
|
||||||
|
hwAccelProbeOnce sync.Once
|
||||||
|
hwAccelSupported atomic.Value // map[string]bool
|
||||||
|
|
||||||
modulesList = []Module{
|
modulesList = []Module{
|
||||||
{"convert", "Convert", utils.MustHex("#8B44FF"), "Convert", modules.HandleConvert}, // Violet
|
{"convert", "Convert", utils.MustHex("#8B44FF"), "Convert", modules.HandleConvert}, // Violet
|
||||||
{"merge", "Merge", utils.MustHex("#4488FF"), "Convert", modules.HandleMerge}, // Blue
|
{"merge", "Merge", utils.MustHex("#4488FF"), "Convert", modules.HandleMerge}, // Blue
|
||||||
|
|
@ -202,6 +206,50 @@ func effectiveHardwareAccel(cfg convertConfig) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hwAccelAvailable checks ffmpeg -hwaccels once and caches the result.
|
||||||
|
func hwAccelAvailable(accel string) bool {
|
||||||
|
accel = strings.ToLower(accel)
|
||||||
|
if accel == "" || accel == "none" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
hwAccelProbeOnce.Do(func() {
|
||||||
|
supported := make(map[string]bool)
|
||||||
|
cmd := exec.Command("ffmpeg", "-hide_banner", "-v", "error", "-hwaccels")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
hwAccelSupported.Store(supported)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(string(output), "\n") {
|
||||||
|
line = strings.ToLower(strings.TrimSpace(line))
|
||||||
|
switch line {
|
||||||
|
case "cuda":
|
||||||
|
supported["nvenc"] = true
|
||||||
|
case "qsv":
|
||||||
|
supported["qsv"] = true
|
||||||
|
case "vaapi":
|
||||||
|
supported["vaapi"] = true
|
||||||
|
case "videotoolbox":
|
||||||
|
supported["videotoolbox"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hwAccelSupported.Store(supported)
|
||||||
|
})
|
||||||
|
|
||||||
|
val := hwAccelSupported.Load()
|
||||||
|
if val == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
supported := val.(map[string]bool)
|
||||||
|
|
||||||
|
// Treat AMF as available if any GPU accel was detected; ffmpeg -hwaccels may not list it.
|
||||||
|
if accel == "amf" {
|
||||||
|
return supported["nvenc"] || supported["qsv"] || supported["vaapi"] || supported["videotoolbox"]
|
||||||
|
}
|
||||||
|
return supported[accel]
|
||||||
|
}
|
||||||
|
|
||||||
// openLogViewer opens a simple dialog showing the log content. If live is true, it auto-refreshes.
|
// openLogViewer opens a simple dialog showing the log content. If live is true, it auto-refreshes.
|
||||||
func (s *appState) openLogViewer(title, path string, live bool) {
|
func (s *appState) openLogViewer(title, path string, live bool) {
|
||||||
if strings.TrimSpace(path) == "" {
|
if strings.TrimSpace(path) == "" {
|
||||||
|
|
@ -2910,6 +2958,9 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
state.convert.OutputAspect = value
|
state.convert.OutputAspect = value
|
||||||
updateAspectBoxVisibility()
|
updateAspectBoxVisibility()
|
||||||
})
|
})
|
||||||
|
if state.convert.OutputAspect == "" {
|
||||||
|
state.convert.OutputAspect = "Source"
|
||||||
|
}
|
||||||
targetAspectSelectSimple.SetSelected(state.convert.OutputAspect)
|
targetAspectSelectSimple.SetSelected(state.convert.OutputAspect)
|
||||||
|
|
||||||
// Target File Size with smart presets + manual entry
|
// Target File Size with smart presets + manual entry
|
||||||
|
|
@ -5286,6 +5337,9 @@ func detectBestH265Encoder() string {
|
||||||
// determineVideoCodec maps user-friendly codec names to FFmpeg codec names
|
// determineVideoCodec maps user-friendly codec names to FFmpeg codec names
|
||||||
func determineVideoCodec(cfg convertConfig) string {
|
func determineVideoCodec(cfg convertConfig) string {
|
||||||
accel := effectiveHardwareAccel(cfg)
|
accel := effectiveHardwareAccel(cfg)
|
||||||
|
if accel != "" && accel != "none" && !hwAccelAvailable(accel) {
|
||||||
|
accel = "none"
|
||||||
|
}
|
||||||
switch cfg.VideoCodec {
|
switch cfg.VideoCodec {
|
||||||
case "H.264":
|
case "H.264":
|
||||||
if accel == "nvenc" {
|
if accel == "nvenc" {
|
||||||
|
|
@ -5455,7 +5509,7 @@ func (s *appState) startConvert(status *widget.Label, btn, cancelBtn *widget.But
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hardware acceleration for decoding (best-effort)
|
// Hardware acceleration for decoding (best-effort)
|
||||||
if accel := effectiveHardwareAccel(cfg); accel != "none" && accel != "" {
|
if accel := effectiveHardwareAccel(cfg); accel != "none" && accel != "" && hwAccelAvailable(accel) {
|
||||||
switch accel {
|
switch accel {
|
||||||
case "nvenc":
|
case "nvenc":
|
||||||
// NVENC encoders handle GPU directly; no hwaccel flag needed
|
// NVENC encoders handle GPU directly; no hwaccel flag needed
|
||||||
|
|
@ -6160,7 +6214,7 @@ func (s *appState) generateSnippet() {
|
||||||
logging.Debug(logging.CatFFMPEG, "snippet: added cover art input %s", s.convert.CoverArtPath)
|
logging.Debug(logging.CatFFMPEG, "snippet: added cover art input %s", s.convert.CoverArtPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build video filters (snippets should be fast - only apply essential filters)
|
// Build video filters using current settings (respect upscaling/AR/FPS)
|
||||||
var vf []string
|
var vf []string
|
||||||
|
|
||||||
// Skip deinterlacing for snippets - they're meant to be fast previews
|
// Skip deinterlacing for snippets - they're meant to be fast previews
|
||||||
|
|
@ -6178,6 +6232,8 @@ func (s *appState) generateSnippet() {
|
||||||
scaleFilter = "scale=-2:1440"
|
scaleFilter = "scale=-2:1440"
|
||||||
case "4K":
|
case "4K":
|
||||||
scaleFilter = "scale=-2:2160"
|
scaleFilter = "scale=-2:2160"
|
||||||
|
case "8K":
|
||||||
|
scaleFilter = "scale=-2:4320"
|
||||||
}
|
}
|
||||||
if scaleFilter != "" {
|
if scaleFilter != "" {
|
||||||
vf = append(vf, scaleFilter)
|
vf = append(vf, scaleFilter)
|
||||||
|
|
@ -6197,9 +6253,10 @@ func (s *appState) generateSnippet() {
|
||||||
vf = append(vf, "fps="+s.convert.FrameRate)
|
vf = append(vf, "fps="+s.convert.FrameRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WMV files must be re-encoded for MP4 compatibility (wmv3/wmav2 can't be copied to MP4)
|
// Decide if we must re-encode: filters, non-copy codec, or WMV
|
||||||
isWMV := strings.HasSuffix(strings.ToLower(src.Path), ".wmv")
|
isWMV := strings.HasSuffix(strings.ToLower(src.Path), ".wmv")
|
||||||
needsReencode := len(vf) > 0 || isWMV
|
forcedCodec := !strings.EqualFold(s.convert.VideoCodec, "Copy")
|
||||||
|
needsReencode := len(vf) > 0 || isWMV || forcedCodec
|
||||||
|
|
||||||
if len(vf) > 0 {
|
if len(vf) > 0 {
|
||||||
filterStr := strings.Join(vf, ",")
|
filterStr := strings.Join(vf, ",")
|
||||||
|
|
@ -6221,23 +6278,41 @@ func (s *appState) generateSnippet() {
|
||||||
args = append(args, "-c:v", "copy")
|
args = append(args, "-c:v", "copy")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Filters required - must re-encode
|
// Filters/codec require re-encode; use current settings
|
||||||
// Use configured codec or fallback to H.264 for compatibility
|
|
||||||
videoCodec := determineVideoCodec(s.convert)
|
videoCodec := determineVideoCodec(s.convert)
|
||||||
if videoCodec == "copy" {
|
if videoCodec == "copy" {
|
||||||
videoCodec = "libx264"
|
videoCodec = "libx264"
|
||||||
}
|
}
|
||||||
args = append(args, "-c:v", videoCodec)
|
args = append(args, "-c:v", videoCodec)
|
||||||
|
|
||||||
// Use configured CRF or fallback to quality preset
|
// Bitrate/quality from current mode
|
||||||
crf := s.convert.CRF
|
mode := s.convert.BitrateMode
|
||||||
if crf == "" {
|
if mode == "" {
|
||||||
crf = crfForQuality(s.convert.Quality)
|
mode = "CRF"
|
||||||
}
|
}
|
||||||
if videoCodec == "libx264" || videoCodec == "libx265" {
|
switch mode {
|
||||||
args = append(args, "-crf", crf)
|
case "CBR", "VBR":
|
||||||
// Use faster preset for snippets
|
vb := s.convert.VideoBitrate
|
||||||
args = append(args, "-preset", "veryfast")
|
if vb == "" {
|
||||||
|
vb = defaultBitrate(s.convert.VideoCodec, src.Width, src.Bitrate)
|
||||||
|
}
|
||||||
|
args = append(args, "-b:v", vb)
|
||||||
|
if mode == "CBR" {
|
||||||
|
args = append(args, "-minrate", vb, "-maxrate", vb, "-bufsize", vb)
|
||||||
|
}
|
||||||
|
default: // CRF/Target size fallback to CRF
|
||||||
|
crf := s.convert.CRF
|
||||||
|
if crf == "" {
|
||||||
|
crf = crfForQuality(s.convert.Quality)
|
||||||
|
}
|
||||||
|
if videoCodec == "libx264" || videoCodec == "libx265" {
|
||||||
|
args = append(args, "-crf", crf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preset from current settings
|
||||||
|
if s.convert.EncoderPreset != "" && (strings.Contains(videoCodec, "264") || strings.Contains(videoCodec, "265")) {
|
||||||
|
args = append(args, "-preset", s.convert.EncoderPreset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pixel format
|
// Pixel format
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user