Persist convert config and tidy queue UI
This commit is contained in:
parent
9ea55f955e
commit
1448c12ac8
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"fyne.io/fyne/v2/layout"
|
"fyne.io/fyne/v2/layout"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
"git.leaktechnologies.dev/stu/VideoTools/internal/queue"
|
"git.leaktechnologies.dev/stu/VideoTools/internal/queue"
|
||||||
|
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BuildQueueView creates the queue viewer UI
|
// BuildQueueView creates the queue viewer UI
|
||||||
|
|
@ -114,10 +115,13 @@ func buildJobItem(
|
||||||
statusRect.SetMinSize(fyne.NewSize(6, 0))
|
statusRect.SetMinSize(fyne.NewSize(6, 0))
|
||||||
|
|
||||||
// Title and description
|
// Title and description
|
||||||
titleLabel := widget.NewLabel(job.Title)
|
titleText := utils.ShortenMiddle(job.Title, 60)
|
||||||
|
descText := utils.ShortenMiddle(job.Description, 90)
|
||||||
|
|
||||||
|
titleLabel := widget.NewLabel(titleText)
|
||||||
titleLabel.TextStyle = fyne.TextStyle{Bold: true}
|
titleLabel.TextStyle = fyne.TextStyle{Bold: true}
|
||||||
|
|
||||||
descLabel := widget.NewLabel(job.Description)
|
descLabel := widget.NewLabel(descText)
|
||||||
descLabel.TextStyle = fyne.TextStyle{Italic: true}
|
descLabel.TextStyle = fyne.TextStyle{Italic: true}
|
||||||
descLabel.Wrapping = fyne.TextWrapWord
|
descLabel.Wrapping = fyne.TextWrapWord
|
||||||
|
|
||||||
|
|
|
||||||
122
main.go
122
main.go
|
|
@ -432,6 +432,54 @@ func (c convertConfig) CoverLabel() string {
|
||||||
return filepath.Base(c.CoverArtPath)
|
return filepath.Base(c.CoverArtPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defaultConvertConfigPath returns the path to the persisted convert config.
|
||||||
|
func defaultConvertConfigPath() string {
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil || configDir == "" {
|
||||||
|
home := os.Getenv("HOME")
|
||||||
|
if home != "" {
|
||||||
|
configDir = filepath.Join(home, ".config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if configDir == "" {
|
||||||
|
return "convert.json"
|
||||||
|
}
|
||||||
|
return filepath.Join(configDir, "VideoTools", "convert.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadPersistedConvertConfig loads the saved convert configuration from disk.
|
||||||
|
func loadPersistedConvertConfig() (convertConfig, error) {
|
||||||
|
var cfg convertConfig
|
||||||
|
path := defaultConvertConfigPath()
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
if cfg.OutputAspect == "" {
|
||||||
|
cfg.OutputAspect = "Source"
|
||||||
|
cfg.AspectUserSet = false
|
||||||
|
} else if !strings.EqualFold(cfg.OutputAspect, "Source") {
|
||||||
|
cfg.AspectUserSet = true
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// savePersistedConvertConfig writes the convert configuration to disk.
|
||||||
|
func savePersistedConvertConfig(cfg convertConfig) error {
|
||||||
|
path := defaultConvertConfigPath()
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err := json.MarshalIndent(cfg, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, data, 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
type appState struct {
|
type appState struct {
|
||||||
window fyne.Window
|
window fyne.Window
|
||||||
active string
|
active string
|
||||||
|
|
@ -474,6 +522,12 @@ type appState struct {
|
||||||
autoCompare bool // Auto-load Compare module after conversion
|
autoCompare bool // Auto-load Compare module after conversion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *appState) persistConvertConfig() {
|
||||||
|
if err := savePersistedConvertConfig(s.convert); err != nil {
|
||||||
|
logging.Debug(logging.CatSystem, "failed to persist convert config: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *appState) stopPreview() {
|
func (s *appState) stopPreview() {
|
||||||
if s.anim != nil {
|
if s.anim != nil {
|
||||||
s.anim.Stop()
|
s.anim.Stop()
|
||||||
|
|
@ -2207,6 +2261,8 @@ func (s *appState) executeSnippetJob(ctx context.Context, job *queue.Job, progre
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appState) shutdown() {
|
func (s *appState) shutdown() {
|
||||||
|
s.persistConvertConfig()
|
||||||
|
|
||||||
// Stop queue without saving - we want a clean slate each session
|
// Stop queue without saving - we want a clean slate each session
|
||||||
if s.jobQueue != nil {
|
if s.jobQueue != nil {
|
||||||
s.jobQueue.Stop()
|
s.jobQueue.Stop()
|
||||||
|
|
@ -2355,6 +2411,12 @@ func runGUI() {
|
||||||
playerPaused: true,
|
playerPaused: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg, err := loadPersistedConvertConfig(); err == nil {
|
||||||
|
state.convert = cfg
|
||||||
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
logging.Debug(logging.CatSystem, "failed to load persisted convert config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize conversion stats bar
|
// Initialize conversion stats bar
|
||||||
state.statsBar = ui.NewConversionStatsBar(func() {
|
state.statsBar = ui.NewConversionStatsBar(func() {
|
||||||
// Clicking the stats bar opens the queue view
|
// Clicking the stats bar opens the queue view
|
||||||
|
|
@ -2823,7 +2885,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
transformHint := widget.NewLabel("Apply flips and rotation to correct video orientation")
|
transformHint := widget.NewLabel("Apply flips and rotation to correct video orientation")
|
||||||
transformHint.Wrapping = fyne.TextWrapWord
|
transformHint.Wrapping = fyne.TextWrapWord
|
||||||
|
|
||||||
aspectTargets := []string{"Source", "16:9", "4:3", "1:1", "9:16", "21:9"}
|
aspectTargets := []string{"Source", "16:9", "4:3", "5:4", "1:1", "9:16", "21:9"}
|
||||||
targetAspectSelect := widget.NewSelect(aspectTargets, func(value string) {
|
targetAspectSelect := widget.NewSelect(aspectTargets, func(value string) {
|
||||||
logging.Debug(logging.CatUI, "target aspect set to %s", value)
|
logging.Debug(logging.CatUI, "target aspect set to %s", value)
|
||||||
state.convert.OutputAspect = value
|
state.convert.OutputAspect = value
|
||||||
|
|
@ -3715,6 +3777,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
if updateQualityVisibility != nil {
|
if updateQualityVisibility != nil {
|
||||||
updateQualityVisibility()
|
updateQualityVisibility()
|
||||||
}
|
}
|
||||||
|
state.persistConvertConfig()
|
||||||
logging.Debug(logging.CatUI, "convert settings reset to defaults")
|
logging.Debug(logging.CatUI, "convert settings reset to defaults")
|
||||||
})
|
})
|
||||||
statusLabel := widget.NewLabel("")
|
statusLabel := widget.NewLabel("")
|
||||||
|
|
@ -3742,6 +3805,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
|
|
||||||
// Add to Queue button
|
// Add to Queue button
|
||||||
addQueueBtn := widget.NewButton("Add to Queue", func() {
|
addQueueBtn := widget.NewButton("Add to Queue", func() {
|
||||||
|
state.persistConvertConfig()
|
||||||
if err := state.addConvertToQueue(); err != nil {
|
if err := state.addConvertToQueue(); err != nil {
|
||||||
dialog.ShowError(err, state.window)
|
dialog.ShowError(err, state.window)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3758,6 +3822,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
convertBtn = widget.NewButton("CONVERT NOW", func() {
|
convertBtn = widget.NewButton("CONVERT NOW", func() {
|
||||||
|
state.persistConvertConfig()
|
||||||
// Add job to queue and start immediately
|
// Add job to queue and start immediately
|
||||||
if err := state.addConvertToQueue(); err != nil {
|
if err := state.addConvertToQueue(); err != nil {
|
||||||
dialog.ShowError(err, state.window)
|
dialog.ShowError(err, state.window)
|
||||||
|
|
@ -3839,49 +3904,24 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
|
|
||||||
// Load/Save config buttons
|
// Load/Save config buttons
|
||||||
loadCfgBtn := widget.NewButton("Load Config", func() {
|
loadCfgBtn := widget.NewButton("Load Config", func() {
|
||||||
dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
|
cfg, err := loadPersistedConvertConfig()
|
||||||
if err != nil || reader == nil {
|
if err != nil {
|
||||||
return
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
dialog.ShowInformation("No Config", "No saved config found yet. It will save automatically after your first change.", state.window)
|
||||||
|
} else {
|
||||||
|
dialog.ShowError(fmt.Errorf("failed to load config: %w", err), state.window)
|
||||||
}
|
}
|
||||||
path := reader.URI().Path()
|
return
|
||||||
reader.Close()
|
}
|
||||||
data, err := os.ReadFile(path)
|
state.convert = cfg
|
||||||
if err != nil {
|
state.showConvertView(state.source)
|
||||||
dialog.ShowError(fmt.Errorf("failed to read config: %w", err), state.window)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var cfg convertConfig
|
|
||||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
|
||||||
dialog.ShowError(fmt.Errorf("failed to parse config: %w", err), state.window)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cfg.OutputAspect == "" {
|
|
||||||
cfg.OutputAspect = "Source"
|
|
||||||
cfg.AspectUserSet = false
|
|
||||||
} else if !strings.EqualFold(cfg.OutputAspect, "Source") {
|
|
||||||
cfg.AspectUserSet = true
|
|
||||||
}
|
|
||||||
state.convert = cfg
|
|
||||||
state.showConvertView(state.source)
|
|
||||||
}, state.window)
|
|
||||||
})
|
})
|
||||||
saveCfgBtn := widget.NewButton("Save Config", func() {
|
saveCfgBtn := widget.NewButton("Save Config", func() {
|
||||||
dialog.ShowFileSave(func(writer fyne.URIWriteCloser, err error) {
|
if err := savePersistedConvertConfig(state.convert); err != nil {
|
||||||
if err != nil || writer == nil {
|
dialog.ShowError(fmt.Errorf("failed to save config: %w", err), state.window)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
path := writer.URI().Path()
|
dialog.ShowInformation("Config Saved", fmt.Sprintf("Saved to %s", defaultConvertConfigPath()), state.window)
|
||||||
defer writer.Close()
|
|
||||||
data, err := json.MarshalIndent(state.convert, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
dialog.ShowError(fmt.Errorf("failed to serialize config: %w", err), state.window)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
|
||||||
dialog.ShowError(fmt.Errorf("failed to save config: %w", err), state.window)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}, state.window)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
leftControls := container.NewHBox(resetBtn, loadCfgBtn, saveCfgBtn, autoCompareCheck)
|
leftControls := container.NewHBox(resetBtn, loadCfgBtn, saveCfgBtn, autoCompareCheck)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user