Compare commits
4 Commits
cfe21e786d
...
d14225f402
| Author | SHA1 | Date | |
|---|---|---|---|
| d14225f402 | |||
| c6e352e436 | |||
| 4fa7011e99 | |||
| 16a655e785 |
|
|
@ -12,6 +12,7 @@ import (
|
|||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/queue"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
||||
)
|
||||
|
||||
// BuildQueueView creates the queue viewer UI
|
||||
|
|
@ -114,10 +115,13 @@ func buildJobItem(
|
|||
statusRect.SetMinSize(fyne.NewSize(6, 0))
|
||||
|
||||
// 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}
|
||||
|
||||
descLabel := widget.NewLabel(job.Description)
|
||||
descLabel := widget.NewLabel(descText)
|
||||
descLabel.TextStyle = fyne.TextStyle{Italic: true}
|
||||
descLabel.Wrapping = fyne.TextWrapWord
|
||||
|
||||
|
|
|
|||
133
main.go
133
main.go
|
|
@ -432,6 +432,54 @@ func (c convertConfig) CoverLabel() string {
|
|||
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 {
|
||||
window fyne.Window
|
||||
active string
|
||||
|
|
@ -474,6 +522,12 @@ type appState struct {
|
|||
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() {
|
||||
if s.anim != nil {
|
||||
s.anim.Stop()
|
||||
|
|
@ -1405,10 +1459,6 @@ func (s *appState) jobExecutor(ctx context.Context, job *queue.Job, progressCall
|
|||
|
||||
// executeConvertJob executes a conversion job from the queue
|
||||
func (s *appState) executeConvertJob(ctx context.Context, job *queue.Job, progressCallback func(float64)) error {
|
||||
return s.executeConvertJobWithFallback(ctx, job, progressCallback, false)
|
||||
}
|
||||
|
||||
func (s *appState) executeConvertJobWithFallback(ctx context.Context, job *queue.Job, progressCallback func(float64), hwFallbackTried bool) error {
|
||||
cfg := job.Config
|
||||
inputPath := cfg["inputPath"].(string)
|
||||
outputPath := cfg["outputPath"].(string)
|
||||
|
|
@ -2211,6 +2261,8 @@ func (s *appState) executeSnippetJob(ctx context.Context, job *queue.Job, progre
|
|||
}
|
||||
|
||||
func (s *appState) shutdown() {
|
||||
s.persistConvertConfig()
|
||||
|
||||
// Stop queue without saving - we want a clean slate each session
|
||||
if s.jobQueue != nil {
|
||||
s.jobQueue.Stop()
|
||||
|
|
@ -2359,6 +2411,12 @@ func runGUI() {
|
|||
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
|
||||
state.statsBar = ui.NewConversionStatsBar(func() {
|
||||
// Clicking the stats bar opens the queue view
|
||||
|
|
@ -2827,7 +2885,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
transformHint := widget.NewLabel("Apply flips and rotation to correct video orientation")
|
||||
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) {
|
||||
logging.Debug(logging.CatUI, "target aspect set to %s", value)
|
||||
state.convert.OutputAspect = value
|
||||
|
|
@ -3719,6 +3777,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
if updateQualityVisibility != nil {
|
||||
updateQualityVisibility()
|
||||
}
|
||||
state.persistConvertConfig()
|
||||
logging.Debug(logging.CatUI, "convert settings reset to defaults")
|
||||
})
|
||||
statusLabel := widget.NewLabel("")
|
||||
|
|
@ -3746,6 +3805,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
|
||||
// Add to Queue button
|
||||
addQueueBtn := widget.NewButton("Add to Queue", func() {
|
||||
state.persistConvertConfig()
|
||||
if err := state.addConvertToQueue(); err != nil {
|
||||
dialog.ShowError(err, state.window)
|
||||
} else {
|
||||
|
|
@ -3762,6 +3822,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
}
|
||||
|
||||
convertBtn = widget.NewButton("CONVERT NOW", func() {
|
||||
state.persistConvertConfig()
|
||||
// Add job to queue and start immediately
|
||||
if err := state.addConvertToQueue(); err != nil {
|
||||
dialog.ShowError(err, state.window)
|
||||
|
|
@ -3843,49 +3904,24 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
|
||||
// Load/Save config buttons
|
||||
loadCfgBtn := widget.NewButton("Load Config", func() {
|
||||
dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
|
||||
if err != nil || reader == nil {
|
||||
return
|
||||
cfg, err := loadPersistedConvertConfig()
|
||||
if err != nil {
|
||||
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()
|
||||
reader.Close()
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
state.convert = cfg
|
||||
state.showConvertView(state.source)
|
||||
})
|
||||
saveCfgBtn := widget.NewButton("Save Config", func() {
|
||||
dialog.ShowFileSave(func(writer fyne.URIWriteCloser, err error) {
|
||||
if err != nil || writer == nil {
|
||||
return
|
||||
}
|
||||
path := writer.URI().Path()
|
||||
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)
|
||||
if err := savePersistedConvertConfig(state.convert); err != nil {
|
||||
dialog.ShowError(fmt.Errorf("failed to save config: %w", err), state.window)
|
||||
return
|
||||
}
|
||||
dialog.ShowInformation("Config Saved", fmt.Sprintf("Saved to %s", defaultConvertConfigPath()), state.window)
|
||||
})
|
||||
|
||||
leftControls := container.NewHBox(resetBtn, loadCfgBtn, saveCfgBtn, autoCompareCheck)
|
||||
|
|
@ -6223,16 +6259,13 @@ func (s *appState) startConvert(status *widget.Label, btn, cancelBtn *widget.But
|
|||
strings.Contains(stderrOutput, "vaapi") ||
|
||||
strings.Contains(stderrOutput, "videotoolbox"))
|
||||
|
||||
if isHardwareFailure && !hwFallbackTried && resolvedAccel != "none" && resolvedAccel != "" {
|
||||
if isHardwareFailure && !strings.EqualFold(s.convert.HardwareAccel, "none") && resolvedAccel != "none" && resolvedAccel != "" {
|
||||
s.convert.HardwareAccel = "none"
|
||||
if logFile != nil {
|
||||
fmt.Fprintf(logFile, "\nAuto-fallback: retrying with software encoder at %s\n", time.Now().Format(time.RFC3339))
|
||||
fmt.Fprintf(logFile, "\nAuto-fallback: hardware encoder failed; switched to software for next attempt at %s\n", time.Now().Format(time.RFC3339))
|
||||
_ = logFile.Close()
|
||||
}
|
||||
s.convertCancel = nil
|
||||
if err := s.executeConvertJobWithFallback(ctx, job, progressCallback, true); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ echo ""
|
|||
echo "🔨 Building VideoTools..."
|
||||
# Fyne needs cgo for GLFW/OpenGL bindings; build with CGO enabled.
|
||||
export CGO_ENABLED=1
|
||||
export GOCACHE="$PROJECT_ROOT/.cache/go-build"
|
||||
export GOMODCACHE="$PROJECT_ROOT/.cache/go-mod"
|
||||
mkdir -p "$GOCACHE" "$GOMODCACHE"
|
||||
if go build -o "$BUILD_OUTPUT" .; then
|
||||
echo "✓ Build successful! (VideoTools $APP_VERSION)"
|
||||
echo ""
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user