refactor: add Language field to convertConfig; decouple benchmark from codec/preset; add language/hw-accel selectors to Settings Preferences
This commit is contained in:
parent
016d2a4f8a
commit
26a73029d4
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build linux && !gstreamer
|
//go:build linux && !gstreamer && !gstreamer
|
||||||
|
|
||||||
package player
|
package player
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !gstreamer
|
//go:build !gstreamer && windows
|
||||||
|
|
||||||
package player
|
package player
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,10 +95,12 @@ type GStreamerPlayer struct {
|
||||||
volume float64
|
volume float64
|
||||||
queued *image.RGBA
|
queued *image.RGBA
|
||||||
lastErr string
|
lastErr string
|
||||||
backend Backend
|
backend BackendType
|
||||||
config Config
|
config Config
|
||||||
seekMu sync.Mutex
|
seekMu sync.Mutex
|
||||||
events chan busEvent
|
events chan busEvent
|
||||||
|
preview bool
|
||||||
|
lastSeekTarget time.Duration
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
|
|
@ -107,13 +109,10 @@ type GStreamerPlayer struct {
|
||||||
|
|
||||||
// Bus handling
|
// Bus handling
|
||||||
busCh chan *C.GstMessage
|
busCh chan *C.GstMessage
|
||||||
|
eos chan struct{}
|
||||||
// Bus loop controls
|
|
||||||
busStop chan struct{}
|
|
||||||
busDone chan struct{}
|
busDone chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
// Seek coalescing
|
|
||||||
lastSeekTarget time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type busEvent struct {
|
type busEvent struct {
|
||||||
|
|
@ -607,6 +606,72 @@ func (p *GStreamerPlayer) busLoop() {
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.busQuit:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
p.mu.Lock()
|
||||||
|
bus := p.bus
|
||||||
|
p.mu.Unlock()
|
||||||
|
if bus == nil {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := C.gst_bus_timed_pop_filtered(bus, 200*1000*1000, C.vt_gst_message_mask())
|
||||||
|
if msg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
msgType := C.vt_gst_message_type(msg)
|
||||||
|
switch msgType {
|
||||||
|
case C.GST_MESSAGE_ERROR:
|
||||||
|
p.mu.Lock()
|
||||||
|
if errMsg != nil {
|
||||||
|
p.lastErr = C.GoString(errMsg)
|
||||||
|
C.vt_gst_free_error(errMsg)
|
||||||
|
} else {
|
||||||
|
p.lastErr = "gstreamer error"
|
||||||
|
}
|
||||||
|
p.mode = StateError
|
||||||
|
p.mu.Unlock()
|
||||||
|
p.pushEvent(busEvent{Kind: "error", Info: p.lastErr})
|
||||||
|
case C.GST_MESSAGE_EOS:
|
||||||
|
p.mu.Lock()
|
||||||
|
p.eos = true
|
||||||
|
p.mode = StateEOS
|
||||||
|
p.mu.Unlock()
|
||||||
|
p.pushEvent(busEvent{Kind: "eos"})
|
||||||
|
case C.GST_MESSAGE_STATE_CHANGED:
|
||||||
|
var oldState C.GstState
|
||||||
|
var newState C.GstState
|
||||||
|
var pending C.GstState
|
||||||
|
C.vt_gst_parse_state_changed(msg, &oldState, &newState, &pending)
|
||||||
|
p.mu.Lock()
|
||||||
|
p.state = newState
|
||||||
|
p.mu.Unlock()
|
||||||
|
p.pushEvent(busEvent{Kind: "state_changed", State: newState})
|
||||||
|
case C.GST_MESSAGE_DURATION_CHANGED:
|
||||||
|
p.updateDuration()
|
||||||
|
p.pushEvent(busEvent{Kind: "duration_changed"})
|
||||||
|
case C.GST_MESSAGE_CLOCK_LOST:
|
||||||
|
p.mu.Lock()
|
||||||
|
shouldRecover := !p.paused && p.pipeline != nil
|
||||||
|
p.mu.Unlock()
|
||||||
|
if shouldRecover {
|
||||||
|
C.gst_element_set_state(p.pipeline, C.GST_STATE_PAUSED)
|
||||||
|
C.gst_element_set_state(p.pipeline, C.GST_STATE_PLAYING)
|
||||||
|
}
|
||||||
|
p.pushEvent(busEvent{Kind: "clock_lost"})
|
||||||
|
}
|
||||||
|
C.gst_message_unref(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-p.busQuit:
|
case <-p.busQuit:
|
||||||
|
|
@ -644,7 +709,7 @@ func (p *GStreamerPlayer) busLoop() {
|
||||||
p.pushEvent(evt)
|
p.pushEvent(evt)
|
||||||
case C.GST_MESSAGE_EOS:
|
case C.GST_MESSAGE_EOS:
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
p.eos = true
|
p.eos = true
|
||||||
p.mode = StateEOS
|
p.mode = StateEOS
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
p.pushEvent(busEvent{Kind: "eos"})
|
p.pushEvent(busEvent{Kind: "eos"})
|
||||||
|
|
@ -671,6 +736,12 @@ func (p *GStreamerPlayer) busLoop() {
|
||||||
p.pushEvent(busEvent{Kind: "clock_lost"})
|
p.pushEvent(busEvent{Kind: "clock_lost"})
|
||||||
}
|
}
|
||||||
C.gst_message_unref(msg)
|
C.gst_message_unref(msg)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
p.pushEvent(busEvent{Kind: "clock_lost"})
|
||||||
|
}
|
||||||
|
C.gst_message_unref(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
51
internal/player/gstreamer_player_stub.go
Normal file
51
internal/player/gstreamer_player_stub.go
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
//go:build !gstreamer
|
||||||
|
|
||||||
|
package player
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reuse types from vtplayer.go to avoid redeclaration conflicts.
|
||||||
|
type busEvent struct {
|
||||||
|
Type int
|
||||||
|
Info string
|
||||||
|
State PlayerState
|
||||||
|
}
|
||||||
|
|
||||||
|
// GStreamerPlayer is a stub used when the gstreamer build tag is not enabled.
|
||||||
|
type GStreamerPlayer struct{}
|
||||||
|
|
||||||
|
// NewGStreamerPlayer returns an error because GStreamer is not available in this build.
|
||||||
|
func NewGStreamerPlayer(config Config) (*GStreamerPlayer, error) {
|
||||||
|
return nil, errors.New("gstreamer not available; build with -tags gstreamer")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GStreamerPlayer) Load(path string, offset time.Duration) error {
|
||||||
|
return errors.New("gstreamer not available")
|
||||||
|
}
|
||||||
|
func (p *GStreamerPlayer) Play() error { return errors.New("gstreamer not available") }
|
||||||
|
func (p *GStreamerPlayer) Pause() error { return errors.New("gstreamer not available") }
|
||||||
|
func (p *GStreamerPlayer) SeekToTime(offset time.Duration) error {
|
||||||
|
return errors.New("gstreamer not available")
|
||||||
|
}
|
||||||
|
func (p *GStreamerPlayer) SeekToFrame(frame int64) error {
|
||||||
|
return errors.New("gstreamer not available")
|
||||||
|
}
|
||||||
|
func (p *GStreamerPlayer) GetCurrentTime() time.Duration { return 0 }
|
||||||
|
func (p *GStreamerPlayer) GetFrameImage() (*image.RGBA, error) {
|
||||||
|
return nil, errors.New("gstreamer not available")
|
||||||
|
}
|
||||||
|
func (p *GStreamerPlayer) SetVolume(level float64) error {
|
||||||
|
return errors.New("gstreamer not available")
|
||||||
|
}
|
||||||
|
func (p *GStreamerPlayer) SetWindow(x, y, w, h int) {}
|
||||||
|
func (p *GStreamerPlayer) SetFullScreen(fullscreen bool) error {
|
||||||
|
return errors.New("gstreamer not available")
|
||||||
|
}
|
||||||
|
func (p *GStreamerPlayer) Stop() error { return nil }
|
||||||
|
func (p *GStreamerPlayer) Close() {}
|
||||||
|
func (p *GStreamerPlayer) Events() <-chan busEvent { return nil }
|
||||||
|
func (p *GStreamerPlayer) State() PlayerState { return StateStopped }
|
||||||
25
main.go
25
main.go
|
|
@ -768,6 +768,7 @@ type convertConfig struct {
|
||||||
AspectUserSet bool // Tracks if user explicitly set OutputAspect
|
AspectUserSet bool // Tracks if user explicitly set OutputAspect
|
||||||
ForceAspect bool // Force DAR/SAR metadata even when no aspect conversion
|
ForceAspect bool // Force DAR/SAR metadata even when no aspect conversion
|
||||||
TempDir string // Optional temp/cache directory override
|
TempDir string // Optional temp/cache directory override
|
||||||
|
Language string // UI language preference ("System" or BCP47 tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c convertConfig) OutputFile() string {
|
func (c convertConfig) OutputFile() string {
|
||||||
|
|
@ -837,6 +838,7 @@ func defaultConvertConfig() convertConfig {
|
||||||
AspectUserSet: false,
|
AspectUserSet: false,
|
||||||
ForceAspect: true,
|
ForceAspect: true,
|
||||||
TempDir: "",
|
TempDir: "",
|
||||||
|
Language: "System",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2930,8 +2932,8 @@ func (s *appState) saveBenchmarkRun(results []benchmark.Result, encoder, preset
|
||||||
func (s *appState) applyBenchmarkRecommendation(encoder, preset string) {
|
func (s *appState) applyBenchmarkRecommendation(encoder, preset string) {
|
||||||
logging.Debug(logging.CatSystem, "applied benchmark recommendation: encoder=%s preset=%s", encoder, preset)
|
logging.Debug(logging.CatSystem, "applied benchmark recommendation: encoder=%s preset=%s", encoder, preset)
|
||||||
|
|
||||||
// Map encoder to hardware acceleration setting
|
// Map encoder to hardware acceleration setting only; do not touch codec/preset.
|
||||||
hwAccel := "none"
|
hwAccel := s.convert.HardwareAccel
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(encoder, "nvenc"):
|
case strings.Contains(encoder, "nvenc"):
|
||||||
hwAccel = "nvenc"
|
hwAccel = "nvenc"
|
||||||
|
|
@ -2943,26 +2945,11 @@ func (s *appState) applyBenchmarkRecommendation(encoder, preset string) {
|
||||||
hwAccel = "videotoolbox"
|
hwAccel = "videotoolbox"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map encoder to friendly codec to align Convert defaults
|
|
||||||
if codec := friendlyCodecFromPreset(encoder); codec != "" {
|
|
||||||
s.convert.VideoCodec = codec
|
|
||||||
}
|
|
||||||
|
|
||||||
// Respect user's quality preference: if they have slow/slower set, upgrade the preset
|
|
||||||
currentPreset := strings.ToLower(s.convert.EncoderPreset)
|
|
||||||
if currentPreset == "slow" || currentPreset == "slower" {
|
|
||||||
// User prefers quality over speed - upgrade benchmark preset to slower
|
|
||||||
preset = "slow"
|
|
||||||
logging.Debug(logging.CatSystem, "user prefers quality - upgraded preset to 'slow'")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.convert.EncoderPreset = preset
|
|
||||||
s.convert.HardwareAccel = hwAccel
|
s.convert.HardwareAccel = hwAccel
|
||||||
s.persistConvertConfig()
|
s.persistConvertConfig()
|
||||||
|
|
||||||
dialog.ShowInformation("Benchmark Settings Applied",
|
// Minimal notice without confirmation loops.
|
||||||
fmt.Sprintf("Applied recommended defaults:\n\nEncoder: %s\nPreset: %s\nHardware Accel: %s\n\nThese are now set as your Convert defaults.",
|
logging.Info(logging.CatSystem, "benchmark applied hardware acceleration: %s", hwAccel)
|
||||||
encoder, preset, hwAccel), s.window)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appState) showBenchmarkHistory() {
|
func (s *appState) showBenchmarkHistory() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user