Compare commits
5 Commits
4c4d436a66
...
03569cd813
| Author | SHA1 | Date | |
|---|---|---|---|
| 03569cd813 | |||
| f50edeb9c6 | |||
| de81c9f999 | |||
| 16767a5ca6 | |||
| 3645291988 |
|
|
@ -156,7 +156,12 @@ func buildAuthorView(state *appState) fyne.CanvasObject {
|
|||
state.queueBtn = queueBtn
|
||||
state.updateQueueButtonLabel()
|
||||
|
||||
topBar := ui.TintedBar(authorColor, container.NewHBox(backBtn, layout.NewSpacer(), queueBtn))
|
||||
clearCompletedBtn := widget.NewButton("⌫", func() {
|
||||
state.clearCompletedJobs()
|
||||
})
|
||||
clearCompletedBtn.Importance = widget.LowImportance
|
||||
|
||||
topBar := ui.TintedBar(authorColor, container.NewHBox(backBtn, layout.NewSpacer(), clearCompletedBtn, queueBtn))
|
||||
bottomBar := moduleFooter(authorColor, layout.NewSpacer(), state.statsBar)
|
||||
|
||||
tabs := container.NewAppTabs(
|
||||
|
|
@ -1761,15 +1766,34 @@ func (s *appState) runAuthoringPipeline(ctx context.Context, paths []string, reg
|
|||
chapters = nil
|
||||
}
|
||||
|
||||
// Log details about encoded MPG files
|
||||
if logFn != nil {
|
||||
logFn(fmt.Sprintf("Created %d MPEG file(s):", len(mpgPaths)))
|
||||
for i, mpg := range mpgPaths {
|
||||
if info, err := os.Stat(mpg); err == nil {
|
||||
logFn(fmt.Sprintf(" %d. %s (%d bytes)", i+1, filepath.Base(mpg), info.Size()))
|
||||
} else {
|
||||
logFn(fmt.Sprintf(" %d. %s (stat failed: %v)", i+1, filepath.Base(mpg), err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmlPath := filepath.Join(workDir, "dvd.xml")
|
||||
if err := writeDVDAuthorXML(xmlPath, mpgPaths, region, aspect, chapters); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Log the XML content for debugging
|
||||
if xmlContent, err := os.ReadFile(xmlPath); err == nil {
|
||||
logFn("Generated DVD XML:")
|
||||
logFn(string(xmlContent))
|
||||
}
|
||||
|
||||
logFn("Authoring DVD structure...")
|
||||
logFn(fmt.Sprintf(">> dvdauthor -o %s -x %s", discRoot, xmlPath))
|
||||
if err := runCommandWithLogger(ctx, "dvdauthor", []string{"-o", discRoot, "-x", xmlPath}, logFn); err != nil {
|
||||
return err
|
||||
logFn(fmt.Sprintf("ERROR: dvdauthor failed: %v", err))
|
||||
return fmt.Errorf("dvdauthor structure creation failed: %w", err)
|
||||
}
|
||||
accumulatedProgress += progressForOtherStep
|
||||
progressFn(accumulatedProgress)
|
||||
|
|
@ -1777,7 +1801,8 @@ func (s *appState) runAuthoringPipeline(ctx context.Context, paths []string, reg
|
|||
logFn("Building DVD tables...")
|
||||
logFn(fmt.Sprintf(">> dvdauthor -o %s -T", discRoot))
|
||||
if err := runCommandWithLogger(ctx, "dvdauthor", []string{"-o", discRoot, "-T"}, logFn); err != nil {
|
||||
return err
|
||||
logFn(fmt.Sprintf("ERROR: dvdauthor -T failed: %v", err))
|
||||
return fmt.Errorf("dvdauthor table build failed: %w", err)
|
||||
}
|
||||
accumulatedProgress += progressForOtherStep
|
||||
progressFn(accumulatedProgress)
|
||||
|
|
@ -1789,15 +1814,24 @@ func (s *appState) runAuthoringPipeline(ctx context.Context, paths []string, reg
|
|||
if makeISO {
|
||||
tool, args, err := buildISOCommand(outputPath, discRoot, title)
|
||||
if err != nil {
|
||||
return err
|
||||
logFn(fmt.Sprintf("ERROR: ISO tool not found: %v", err))
|
||||
return fmt.Errorf("ISO creation setup failed: %w", err)
|
||||
}
|
||||
logFn("Creating ISO image...")
|
||||
logFn(fmt.Sprintf(">> %s %s", tool, strings.Join(args, " ")))
|
||||
if err := runCommandWithLogger(ctx, tool, args, logFn); err != nil {
|
||||
return err
|
||||
logFn(fmt.Sprintf("ERROR: ISO creation failed: %v", err))
|
||||
return fmt.Errorf("ISO creation failed: %w", err)
|
||||
}
|
||||
accumulatedProgress += progressForOtherStep
|
||||
progressFn(accumulatedProgress)
|
||||
|
||||
// Verify ISO was created
|
||||
if info, err := os.Stat(outputPath); err == nil {
|
||||
logFn(fmt.Sprintf("ISO created successfully: %s (%d bytes)", filepath.Base(outputPath), info.Size()))
|
||||
} else {
|
||||
logFn(fmt.Sprintf("WARNING: ISO file verification failed: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
progressFn(100.0)
|
||||
|
|
|
|||
|
|
@ -36,8 +36,13 @@ func buildFiltersView(state *appState) fyne.CanvasObject {
|
|||
state.queueBtn = queueBtn
|
||||
state.updateQueueButtonLabel()
|
||||
|
||||
clearCompletedBtn := widget.NewButton("⌫", func() {
|
||||
state.clearCompletedJobs()
|
||||
})
|
||||
clearCompletedBtn.Importance = widget.LowImportance
|
||||
|
||||
// Top bar with module color
|
||||
topBar := ui.TintedBar(filtersColor, container.NewHBox(backBtn, layout.NewSpacer(), queueBtn))
|
||||
topBar := ui.TintedBar(filtersColor, container.NewHBox(backBtn, layout.NewSpacer(), clearCompletedBtn, queueBtn))
|
||||
bottomBar := moduleFooter(filtersColor, layout.NewSpacer(), state.statsBar)
|
||||
|
||||
// Instructions
|
||||
|
|
|
|||
|
|
@ -42,7 +42,13 @@ func buildInspectView(state *appState) fyne.CanvasObject {
|
|||
})
|
||||
state.queueBtn = queueBtn
|
||||
state.updateQueueButtonLabel()
|
||||
topBar := ui.TintedBar(inspectColor, container.NewHBox(backBtn, layout.NewSpacer(), queueBtn))
|
||||
|
||||
clearCompletedBtn := widget.NewButton("⌫", func() {
|
||||
state.clearCompletedJobs()
|
||||
})
|
||||
clearCompletedBtn.Importance = widget.LowImportance
|
||||
|
||||
topBar := ui.TintedBar(inspectColor, container.NewHBox(backBtn, layout.NewSpacer(), clearCompletedBtn, queueBtn))
|
||||
bottomBar := moduleFooter(inspectColor, layout.NewSpacer(), state.statsBar)
|
||||
|
||||
// Instructions
|
||||
|
|
|
|||
|
|
@ -32,10 +32,19 @@ func SetColors(grid, text color.Color) {
|
|||
TextColor = text
|
||||
}
|
||||
|
||||
// MonoTheme ensures all text uses a monospace font
|
||||
// MonoTheme ensures all text uses a monospace font and swaps hover/selection colors
|
||||
type MonoTheme struct{}
|
||||
|
||||
func (m *MonoTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
|
||||
// Swap hover and selection colors
|
||||
switch name {
|
||||
case theme.ColorNameSelection:
|
||||
// Use the default hover color for selection
|
||||
return theme.DefaultTheme().Color(theme.ColorNameHover, variant)
|
||||
case theme.ColorNameHover:
|
||||
// Use the default selection color for hover
|
||||
return theme.DefaultTheme().Color(theme.ColorNameSelection, variant)
|
||||
}
|
||||
return theme.DefaultTheme().Color(name, variant)
|
||||
}
|
||||
|
||||
|
|
|
|||
127
main.go
127
main.go
|
|
@ -1745,6 +1745,13 @@ func (s *appState) showQueue() {
|
|||
s.startQueueAutoRefresh()
|
||||
}
|
||||
|
||||
// clearCompletedJobs removes all completed and failed jobs from the queue
|
||||
func (s *appState) clearCompletedJobs() {
|
||||
if s.jobQueue != nil {
|
||||
s.jobQueue.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
// refreshQueueView rebuilds the queue UI while preserving scroll position and inline active conversion.
|
||||
func (s *appState) refreshQueueView() {
|
||||
if s.active == "queue" {
|
||||
|
|
@ -6344,6 +6351,11 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
state.queueBtn = queueBtn
|
||||
state.updateQueueButtonLabel()
|
||||
|
||||
clearCompletedBtn := widget.NewButton("⌫", func() {
|
||||
state.clearCompletedJobs()
|
||||
})
|
||||
clearCompletedBtn.Importance = widget.LowImportance
|
||||
|
||||
// Command Preview toggle button
|
||||
cmdPreviewBtn := widget.NewButton("Command Preview", func() {
|
||||
state.convertCommandPreviewShow = !state.convertCommandPreviewShow
|
||||
|
|
@ -6360,60 +6372,16 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
cmdPreviewBtn.SetText("Show Preview")
|
||||
}
|
||||
|
||||
var (
|
||||
benchmarkStatus *canvas.Text
|
||||
benchmarkApplyBtn *widget.Button
|
||||
benchmarkIndicator fyne.CanvasObject
|
||||
)
|
||||
if cfg, err := loadBenchmarkConfig(); err == nil && len(cfg.History) > 0 {
|
||||
run := cfg.History[0]
|
||||
encoder := run.RecommendedEncoder
|
||||
preset := run.RecommendedPreset
|
||||
benchHW := "none"
|
||||
switch {
|
||||
case strings.Contains(encoder, "nvenc"):
|
||||
benchHW = "nvenc"
|
||||
case strings.Contains(encoder, "qsv"):
|
||||
benchHW = "qsv"
|
||||
case strings.Contains(encoder, "amf"):
|
||||
benchHW = "amf"
|
||||
case strings.Contains(encoder, "videotoolbox"):
|
||||
benchHW = "videotoolbox"
|
||||
}
|
||||
applied := friendlyCodecFromPreset(encoder) == state.convert.VideoCodec &&
|
||||
state.convert.EncoderPreset == preset &&
|
||||
state.convert.HardwareAccel == benchHW
|
||||
|
||||
// Only show benchmark indicator if settings are NOT already applied
|
||||
if !applied {
|
||||
statusColor := utils.MustHex("#FFC857")
|
||||
statusText := "Benchmark: Not Applied"
|
||||
benchmarkStatus = canvas.NewText(statusText, statusColor)
|
||||
benchmarkStatus.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
||||
benchmarkStatus.TextSize = 12
|
||||
|
||||
benchmarkApplyBtn = widget.NewButton("Apply Benchmark", func() {
|
||||
state.applyBenchmarkRecommendation(encoder, preset)
|
||||
// Hide the entire indicator once applied
|
||||
benchmarkIndicator.Hide()
|
||||
})
|
||||
benchmarkApplyBtn.Importance = widget.MediumImportance
|
||||
|
||||
benchmarkIndicator = container.NewHBox(benchmarkStatus, benchmarkApplyBtn)
|
||||
}
|
||||
}
|
||||
|
||||
// Build back bar with optional benchmark indicator
|
||||
// Build back bar
|
||||
backBarItems := []fyne.CanvasObject{
|
||||
back,
|
||||
layout.NewSpacer(),
|
||||
navButtons,
|
||||
layout.NewSpacer(),
|
||||
cmdPreviewBtn,
|
||||
clearCompletedBtn,
|
||||
queueBtn,
|
||||
}
|
||||
if benchmarkIndicator != nil {
|
||||
backBarItems = append(backBarItems, benchmarkIndicator)
|
||||
}
|
||||
backBarItems = append(backBarItems, cmdPreviewBtn, queueBtn)
|
||||
|
||||
backBar := ui.TintedBar(convertColor, container.NewHBox(backBarItems...))
|
||||
|
||||
|
|
@ -9155,34 +9123,43 @@ Metadata: %s`,
|
|||
metadata,
|
||||
)
|
||||
|
||||
info := widget.NewForm(
|
||||
widget.NewFormItem("File", widget.NewLabel(src.DisplayName)),
|
||||
widget.NewFormItem("Format Family", widget.NewLabel(utils.FirstNonEmpty(src.Format, "Unknown"))),
|
||||
widget.NewFormItem("Resolution", widget.NewLabel(fmt.Sprintf("%dx%d", src.Width, src.Height))),
|
||||
widget.NewFormItem("Aspect Ratio", widget.NewLabel(src.AspectRatioString())),
|
||||
widget.NewFormItem("Pixel Aspect Ratio", widget.NewLabel(par)),
|
||||
widget.NewFormItem("Duration", widget.NewLabel(src.DurationString())),
|
||||
widget.NewFormItem("Video Codec", widget.NewLabel(utils.FirstNonEmpty(src.VideoCodec, "Unknown"))),
|
||||
widget.NewFormItem("Video Bitrate", widget.NewLabel(bitrate)),
|
||||
widget.NewFormItem("Frame Rate", widget.NewLabel(fmt.Sprintf("%.2f fps", src.FrameRate))),
|
||||
widget.NewFormItem("Pixel Format", widget.NewLabel(utils.FirstNonEmpty(src.PixelFormat, "Unknown"))),
|
||||
widget.NewFormItem("Interlacing", widget.NewLabel(interlacing)),
|
||||
widget.NewFormItem("Color Space", widget.NewLabel(colorSpace)),
|
||||
widget.NewFormItem("Color Range", widget.NewLabel(colorRange)),
|
||||
widget.NewFormItem("GOP Size", widget.NewLabel(gopSize)),
|
||||
widget.NewFormItem("Audio Codec", widget.NewLabel(utils.FirstNonEmpty(src.AudioCodec, "Unknown"))),
|
||||
widget.NewFormItem("Audio Bitrate", widget.NewLabel(audioBitrate)),
|
||||
widget.NewFormItem("Audio Rate", widget.NewLabel(fmt.Sprintf("%d Hz", src.AudioRate))),
|
||||
widget.NewFormItem("Channels", widget.NewLabel(utils.ChannelLabel(src.Channels))),
|
||||
widget.NewFormItem("Chapters", widget.NewLabel(chapters)),
|
||||
widget.NewFormItem("Metadata", widget.NewLabel(metadata)),
|
||||
// Helper function to create compact key-value rows
|
||||
makeRow := func(key, value string) fyne.CanvasObject {
|
||||
keyLabel := widget.NewLabel(key + ":")
|
||||
keyLabel.TextStyle = fyne.TextStyle{Bold: true}
|
||||
valueLabel := widget.NewLabel(value)
|
||||
valueLabel.Wrapping = fyne.TextTruncate
|
||||
return container.NewBorder(nil, nil, keyLabel, nil, container.NewHBox(layout.NewSpacer(), valueLabel))
|
||||
}
|
||||
|
||||
// Organize metadata into a compact two-column grid
|
||||
col1 := container.NewVBox(
|
||||
makeRow("File", src.DisplayName),
|
||||
makeRow("Format", utils.FirstNonEmpty(src.Format, "Unknown")),
|
||||
makeRow("Resolution", fmt.Sprintf("%dx%d", src.Width, src.Height)),
|
||||
makeRow("Aspect Ratio", src.AspectRatioString()),
|
||||
makeRow("Duration", src.DurationString()),
|
||||
makeRow("Frame Rate", fmt.Sprintf("%.2f fps", src.FrameRate)),
|
||||
makeRow("Interlacing", interlacing),
|
||||
makeRow("Color Space", colorSpace),
|
||||
makeRow("Color Range", colorRange),
|
||||
makeRow("GOP Size", gopSize),
|
||||
)
|
||||
for _, item := range info.Items {
|
||||
if lbl, ok := item.Widget.(*widget.Label); ok {
|
||||
lbl.Wrapping = fyne.TextWrapWord
|
||||
lbl.TextStyle = fyne.TextStyle{} // prevent selection
|
||||
}
|
||||
}
|
||||
|
||||
col2 := container.NewVBox(
|
||||
makeRow("Video Codec", utils.FirstNonEmpty(src.VideoCodec, "Unknown")),
|
||||
makeRow("Video Bitrate", bitrate),
|
||||
makeRow("Pixel Format", utils.FirstNonEmpty(src.PixelFormat, "Unknown")),
|
||||
makeRow("Pixel AR", par),
|
||||
makeRow("Audio Codec", utils.FirstNonEmpty(src.AudioCodec, "Unknown")),
|
||||
makeRow("Audio Bitrate", audioBitrate),
|
||||
makeRow("Audio Rate", fmt.Sprintf("%d Hz", src.AudioRate)),
|
||||
makeRow("Channels", utils.ChannelLabel(src.Channels)),
|
||||
makeRow("Chapters", chapters),
|
||||
makeRow("Metadata", metadata),
|
||||
)
|
||||
|
||||
info := container.NewHBox(col1, col2)
|
||||
|
||||
// Copy metadata button - beside header text
|
||||
copyBtn := widget.NewButton("📋", func() {
|
||||
|
|
|
|||
|
|
@ -115,7 +115,12 @@ func buildRipView(state *appState) fyne.CanvasObject {
|
|||
state.queueBtn = queueBtn
|
||||
state.updateQueueButtonLabel()
|
||||
|
||||
topBar := ui.TintedBar(ripColor, container.NewHBox(backBtn, layout.NewSpacer(), queueBtn))
|
||||
clearCompletedBtn := widget.NewButton("⌫", func() {
|
||||
state.clearCompletedJobs()
|
||||
})
|
||||
clearCompletedBtn.Importance = widget.LowImportance
|
||||
|
||||
topBar := ui.TintedBar(ripColor, container.NewHBox(backBtn, layout.NewSpacer(), clearCompletedBtn, queueBtn))
|
||||
bottomBar := moduleFooter(ripColor, layout.NewSpacer(), state.statsBar)
|
||||
|
||||
sourceEntry := widget.NewEntry()
|
||||
|
|
|
|||
|
|
@ -134,7 +134,12 @@ func buildSubtitlesView(state *appState) fyne.CanvasObject {
|
|||
state.queueBtn = queueBtn
|
||||
state.updateQueueButtonLabel()
|
||||
|
||||
topBar := ui.TintedBar(subtitlesColor, container.NewHBox(backBtn, layout.NewSpacer(), queueBtn))
|
||||
clearCompletedBtn := widget.NewButton("⌫", func() {
|
||||
state.clearCompletedJobs()
|
||||
})
|
||||
clearCompletedBtn.Importance = widget.LowImportance
|
||||
|
||||
topBar := ui.TintedBar(subtitlesColor, container.NewHBox(backBtn, layout.NewSpacer(), clearCompletedBtn, queueBtn))
|
||||
bottomBar := moduleFooter(subtitlesColor, layout.NewSpacer(), state.statsBar)
|
||||
|
||||
videoEntry := widget.NewEntry()
|
||||
|
|
|
|||
|
|
@ -42,7 +42,13 @@ func buildThumbView(state *appState) fyne.CanvasObject {
|
|||
})
|
||||
state.queueBtn = queueBtn
|
||||
state.updateQueueButtonLabel()
|
||||
topBar := ui.TintedBar(thumbColor, container.NewHBox(backBtn, layout.NewSpacer(), queueBtn))
|
||||
|
||||
clearCompletedBtn := widget.NewButton("⌫", func() {
|
||||
state.clearCompletedJobs()
|
||||
})
|
||||
clearCompletedBtn.Importance = widget.LowImportance
|
||||
|
||||
topBar := ui.TintedBar(thumbColor, container.NewHBox(backBtn, layout.NewSpacer(), clearCompletedBtn, queueBtn))
|
||||
|
||||
// Instructions
|
||||
instructions := widget.NewLabel("Generate thumbnails from a video file. Load a video and configure settings.")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user