Wire convert state manager callbacks
This commit is contained in:
parent
222e2f1414
commit
618cfd208e
|
|
@ -1,34 +1,102 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"sync"
|
||||
)
|
||||
import "sync"
|
||||
|
||||
// StateManager coordinates Convert UI state updates without direct widget coupling.
|
||||
// Callbacks are registered by UI code to keep widgets in sync.
|
||||
type StateManager struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
// Current mode settings
|
||||
crfMode CRFMode
|
||||
vbrMode VBRMode
|
||||
currentQuality string
|
||||
currentBitrate string
|
||||
currentCRFValue int64
|
||||
currentVBRValue int64
|
||||
|
||||
// Registered widgets for synchronization
|
||||
qualityWidgets []*widget.Select
|
||||
bitrateWidgets []*widget.Select
|
||||
mu sync.RWMutex
|
||||
quality string
|
||||
bitrateMode string
|
||||
manualQualityOption string
|
||||
onQualityChange []func(string)
|
||||
onBitrateModeChange []func(string)
|
||||
}
|
||||
|
||||
type CRFMode string
|
||||
type VBRMode string
|
||||
func NewStateManager(quality, bitrateMode, manualQualityOption string) *StateManager {
|
||||
if manualQualityOption == "" {
|
||||
manualQualityOption = "Manual (CRF)"
|
||||
}
|
||||
return &StateManager{
|
||||
quality: quality,
|
||||
bitrateMode: bitrateMode,
|
||||
manualQualityOption: manualQualityOption,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
CRFManual CRFMode = "manual"
|
||||
CRFQuality CRFMode = "quality"
|
||||
CRFBitrate CRFMode = "bitrate"
|
||||
VBRStandard VBRMode = "standard"
|
||||
VBRHQ VBRMode = "hq"
|
||||
VBRConstrained VBRMode = "constrained"
|
||||
)
|
||||
func (m *StateManager) Quality() string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.quality
|
||||
}
|
||||
|
||||
func (m *StateManager) BitrateMode() string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.bitrateMode
|
||||
}
|
||||
|
||||
func (m *StateManager) ManualQualityOption() string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.manualQualityOption
|
||||
}
|
||||
|
||||
func (m *StateManager) SetQuality(val string) bool {
|
||||
m.mu.Lock()
|
||||
if m.quality == val {
|
||||
m.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
m.quality = val
|
||||
callbacks := append([]func(string){}, m.onQualityChange...)
|
||||
m.mu.Unlock()
|
||||
|
||||
for _, cb := range callbacks {
|
||||
cb(val)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *StateManager) SetBitrateMode(val string) bool {
|
||||
m.mu.Lock()
|
||||
if m.bitrateMode == val {
|
||||
m.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
m.bitrateMode = val
|
||||
callbacks := append([]func(string){}, m.onBitrateModeChange...)
|
||||
m.mu.Unlock()
|
||||
|
||||
for _, cb := range callbacks {
|
||||
cb(val)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *StateManager) OnQualityChange(fn func(string)) {
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.onQualityChange = append(m.onQualityChange, fn)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *StateManager) OnBitrateModeChange(fn func(string)) {
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.onBitrateModeChange = append(m.onBitrateModeChange, fn)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *StateManager) IsManualQuality(val string) bool {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if val != "" {
|
||||
return val == m.manualQualityOption
|
||||
}
|
||||
return m.quality == m.manualQualityOption
|
||||
}
|
||||
|
|
|
|||
111
main.go
111
main.go
|
|
@ -44,6 +44,7 @@ import (
|
|||
"git.leaktechnologies.dev/stu/VideoTools/internal/modules"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/player"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/queue"
|
||||
statepkg "git.leaktechnologies.dev/stu/VideoTools/internal/state"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/sysinfo"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/ui"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
||||
|
|
@ -1116,35 +1117,35 @@ type appState struct {
|
|||
sidebarVisible bool
|
||||
|
||||
// Author module state
|
||||
authorFile *videoSource
|
||||
authorChapters []authorChapter
|
||||
authorSceneThreshold float64
|
||||
authorDetecting bool
|
||||
authorClips []authorClip // Multiple video clips for compilation
|
||||
authorOutputType string // "dvd" or "iso"
|
||||
authorRegion string // "NTSC", "PAL", "AUTO"
|
||||
authorAspectRatio string // "4:3", "16:9", "AUTO"
|
||||
authorCreateMenu bool // Whether to create DVD menu
|
||||
authorMenuTemplate string // "Simple", "Dark", "Poster"
|
||||
authorMenuBackgroundImage string // Path to a user-selected background image
|
||||
authorTitle string // DVD title
|
||||
authorSubtitles []string // Subtitle file paths
|
||||
authorAudioTracks []string // Additional audio tracks
|
||||
authorSummaryLabel *widget.Label
|
||||
authorTreatAsChapters bool // Treat multiple clips as chapters
|
||||
authorChapterSource string // embedded, scenes, clips, manual
|
||||
authorChaptersRefresh func() // Refresh hook for chapter list UI
|
||||
authorDiscSize string // "DVD5" or "DVD9"
|
||||
authorLogText string
|
||||
authorLogLines []string // Circular buffer for last N lines
|
||||
authorLogFilePath string // Path to log file for full viewing
|
||||
authorLogEntry *widget.Entry
|
||||
authorLogScroll *container.Scroll
|
||||
authorProgress float64
|
||||
authorProgressBar *widget.ProgressBar
|
||||
authorStatusLabel *widget.Label
|
||||
authorCancelBtn *widget.Button
|
||||
authorVideoTSPath string
|
||||
authorFile *videoSource
|
||||
authorChapters []authorChapter
|
||||
authorSceneThreshold float64
|
||||
authorDetecting bool
|
||||
authorClips []authorClip // Multiple video clips for compilation
|
||||
authorOutputType string // "dvd" or "iso"
|
||||
authorRegion string // "NTSC", "PAL", "AUTO"
|
||||
authorAspectRatio string // "4:3", "16:9", "AUTO"
|
||||
authorCreateMenu bool // Whether to create DVD menu
|
||||
authorMenuTemplate string // "Simple", "Dark", "Poster"
|
||||
authorMenuBackgroundImage string // Path to a user-selected background image
|
||||
authorTitle string // DVD title
|
||||
authorSubtitles []string // Subtitle file paths
|
||||
authorAudioTracks []string // Additional audio tracks
|
||||
authorSummaryLabel *widget.Label
|
||||
authorTreatAsChapters bool // Treat multiple clips as chapters
|
||||
authorChapterSource string // embedded, scenes, clips, manual
|
||||
authorChaptersRefresh func() // Refresh hook for chapter list UI
|
||||
authorDiscSize string // "DVD5" or "DVD9"
|
||||
authorLogText string
|
||||
authorLogLines []string // Circular buffer for last N lines
|
||||
authorLogFilePath string // Path to log file for full viewing
|
||||
authorLogEntry *widget.Entry
|
||||
authorLogScroll *container.Scroll
|
||||
authorProgress float64
|
||||
authorProgressBar *widget.ProgressBar
|
||||
authorStatusLabel *widget.Label
|
||||
authorCancelBtn *widget.Button
|
||||
authorVideoTSPath string
|
||||
|
||||
// Rip module state
|
||||
ripSourcePath string
|
||||
|
|
@ -7035,6 +7036,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
_ = registerCallback
|
||||
|
||||
manualQualityOption := "Manual (CRF)"
|
||||
stateMgr := statepkg.NewStateManager(state.convert.Quality, state.convert.BitrateMode, manualQualityOption)
|
||||
var crfEntry *widget.Entry
|
||||
var manualCrfRow *fyne.Container
|
||||
var crfContainer *fyne.Container
|
||||
|
|
@ -7055,7 +7057,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
}
|
||||
|
||||
// State setters with automatic widget synchronization
|
||||
setQuality := func(val string) {
|
||||
applyQuality := func(val string) {
|
||||
if uiState.quality == val {
|
||||
return // No change
|
||||
}
|
||||
|
|
@ -7103,6 +7105,12 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
callCallback("updateEncodingControls")
|
||||
}
|
||||
|
||||
stateMgr.OnQualityChange(applyQuality)
|
||||
|
||||
setQuality := func(val string) {
|
||||
stateMgr.SetQuality(val)
|
||||
}
|
||||
|
||||
setResolution := func(val string) {
|
||||
if uiState.resolution == val {
|
||||
return
|
||||
|
|
@ -8093,24 +8101,9 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
"VBR": "VBR (Variable Bitrate)",
|
||||
"Target Size": "Target Size (Calculate from file size)",
|
||||
}
|
||||
bitrateModeSelect = widget.NewSelect(bitrateModeOptions, func(value string) {
|
||||
// Extract short code from label
|
||||
if shortCode, ok := bitrateModeMap[value]; ok {
|
||||
state.convert.BitrateMode = shortCode
|
||||
} else {
|
||||
state.convert.BitrateMode = value
|
||||
}
|
||||
applyBitrateMode := func(value string) {
|
||||
state.convert.BitrateMode = normalizeBitrateMode(value)
|
||||
logging.Debug(logging.CatUI, "bitrate mode set to %s", state.convert.BitrateMode)
|
||||
if state.convert.BitrateMode == "CRF" && state.convert.Quality == manualQualityOption {
|
||||
if crfEntry != nil {
|
||||
crfEntry.Enable()
|
||||
}
|
||||
if manualCrfRow != nil {
|
||||
manualCrfRow.Show()
|
||||
}
|
||||
} else if manualCrfRow != nil {
|
||||
manualCrfRow.Hide()
|
||||
}
|
||||
if updateEncodingControls != nil {
|
||||
updateEncodingControls()
|
||||
}
|
||||
|
|
@ -8120,6 +8113,15 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
if buildCommandPreview != nil {
|
||||
buildCommandPreview()
|
||||
}
|
||||
}
|
||||
stateMgr.OnBitrateModeChange(applyBitrateMode)
|
||||
bitrateModeSelect = widget.NewSelect(bitrateModeOptions, func(value string) {
|
||||
// Extract short code from label
|
||||
if shortCode, ok := bitrateModeMap[value]; ok {
|
||||
stateMgr.SetBitrateMode(shortCode)
|
||||
} else {
|
||||
stateMgr.SetBitrateMode(value)
|
||||
}
|
||||
})
|
||||
// Set selected using full label
|
||||
if fullLabel, ok := reverseMap[state.convert.BitrateMode]; ok {
|
||||
|
|
@ -8128,9 +8130,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
bitrateModeSelect.SetSelected(state.convert.BitrateMode)
|
||||
}
|
||||
state.convert.BitrateMode = normalizeBitrateMode(state.convert.BitrateMode)
|
||||
if state.convert.BitrateMode != "CRF" && manualCrfRow != nil {
|
||||
manualCrfRow.Hide()
|
||||
}
|
||||
stateMgr.SetBitrateMode(state.convert.BitrateMode)
|
||||
|
||||
// Manual CRF entry
|
||||
// CRF entry with debouncing (300ms delay) and validation
|
||||
|
|
@ -8654,8 +8654,8 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
}
|
||||
|
||||
// Move to CBR for predictable output when a preset is chosen
|
||||
if preset.Bitrate != "" && state.convert.BitrateMode != "CBR" && state.convert.BitrateMode != "VBR" {
|
||||
state.convert.BitrateMode = "CBR"
|
||||
if preset.Bitrate != "" && stateMgr.BitrateMode() != "CBR" && stateMgr.BitrateMode() != "VBR" {
|
||||
stateMgr.SetBitrateMode("CBR")
|
||||
if label, ok := reverseMap["CBR"]; ok {
|
||||
bitrateModeSelect.SetSelected(label)
|
||||
} else {
|
||||
|
|
@ -11172,7 +11172,8 @@ func (p *playSession) startLocked(offset float64) {
|
|||
p.syncOffset = 0
|
||||
logging.Debug(logging.CatFFMPEG, "playSession start path=%s offset=%.3f fps=%.3f target=%dx%d", p.path, offset, p.fps, p.targetW, p.targetH)
|
||||
p.runVideo(offset)
|
||||
p.runAudio(offset)
|
||||
// TEMPORARY: Disable audio to prevent A/V sync crashes
|
||||
// p.runAudio(offset) will be re-enabled when UnifiedPlayer is properly integrated
|
||||
}
|
||||
|
||||
func (p *playSession) runVideo(offset float64) {
|
||||
|
|
@ -14818,11 +14819,11 @@ func buildCompareView(state *appState) fyne.CanvasObject {
|
|||
|
||||
// Scrollable metadata area for file 1 - use smaller minimum
|
||||
file1InfoScroll := container.NewVScroll(file1Info)
|
||||
// Avoid rigid min sizes so window snapping works across modules.
|
||||
// Avoid rigid min sizes so window snapping works across modules.
|
||||
|
||||
// Scrollable metadata area for file 2 - use smaller minimum
|
||||
file2InfoScroll := container.NewVScroll(file2Info)
|
||||
// Avoid rigid min sizes so window snapping works across modules.
|
||||
// Avoid rigid min sizes so window snapping works across modules.
|
||||
|
||||
// File 1 column: header, video player, metadata (using Border to make metadata expand)
|
||||
file1Column := container.NewBorder(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user