fix: suppress ffmpeg popups on Windows and improve codec performance
- Fixed Windows ffmpeg.exe popups by adding //go:build tags and exporting CreateCommand/CreateCommandRaw properly - Use utils.GetFFmpegPath(), GetFFprobePath(), GetFFplayPath() instead of hardcoded strings - Switch AV1 codec to H.264 for better performance (AV1/libsvtav1 is extremely slow) - Minor UI component refinements (padding, SetMinSize)
This commit is contained in:
parent
647ecc633a
commit
c6fc48eb97
|
|
@ -90,7 +90,7 @@ func ProbeVideo(path string) (*VideoSource, error) {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "ffprobe",
|
||||
cmd := exec.CommandContext(ctx, utils.GetFFprobePath(),
|
||||
"-v", "quiet",
|
||||
"-print_format", "json",
|
||||
"-show_format",
|
||||
|
|
@ -252,7 +252,7 @@ func ProbeVideo(path string) (*VideoSource, error) {
|
|||
// Extract embedded cover art if present
|
||||
if coverArtStreamIndex >= 0 {
|
||||
coverPath := filepath.Join(utils.TempDir(), fmt.Sprintf("videotools-embedded-cover-%d.png", time.Now().UnixNano()))
|
||||
extractCmd := exec.CommandContext(ctx, FFmpegPath,
|
||||
extractCmd := utils.CreateCommand(ctx, utils.GetFFmpegPath(),
|
||||
"-i", path,
|
||||
"-map", fmt.Sprintf("0:%d", coverArtStreamIndex),
|
||||
"-frames:v", "1",
|
||||
|
|
@ -298,7 +298,7 @@ func normalizeTags(tags map[string]interface{}) map[string]string {
|
|||
func detectGOPSize(ctx context.Context, path string) int {
|
||||
// Use ffprobe to show frames and look for key_frame markers
|
||||
// We'll analyze the first 300 frames (about 10 seconds at 30fps)
|
||||
cmd := exec.CommandContext(ctx, "ffprobe",
|
||||
cmd := exec.CommandContext(ctx, utils.GetFFprobePath(),
|
||||
"-v", "quiet",
|
||||
"-select_streams", "v:0",
|
||||
"-show_entries", "frame=pict_type,key_frame",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
||||
)
|
||||
|
||||
const playerWindowTitle = "VideoToolsPlayer"
|
||||
|
|
@ -291,7 +293,7 @@ func (c *ffplayController) startLocked(offset float64) error {
|
|||
}
|
||||
args = append(args, input)
|
||||
|
||||
cmd := exec.CommandContext(ctx, "ffplay", args...)
|
||||
cmd := exec.CommandContext(ctx, utils.GetFFplayPath(), args...)
|
||||
env := os.Environ()
|
||||
if c.winX != 0 || c.winY != 0 {
|
||||
// SDL honors SDL_VIDEO_WINDOW_POS for initial window placement.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
"fmt"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
||||
)
|
||||
|
||||
const playerWindowTitle = "videotools-player"
|
||||
|
|
@ -45,7 +47,7 @@ func (c *Controller) Load(path string, offset float64) error {
|
|||
}
|
||||
args = append(args, path)
|
||||
|
||||
cmd := exec.CommandContext(ctx, "ffplay", args...)
|
||||
cmd := exec.CommandContext(ctx, utils.GetFFplayPath(), args...)
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
cancel()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
||||
)
|
||||
|
||||
// Config contains configuration for thumbnail generation
|
||||
|
|
@ -148,7 +150,7 @@ func (g *Generator) Generate(ctx context.Context, config Config) (*GenerateResul
|
|||
// getVideoInfo retrieves duration and dimensions from a video file
|
||||
func (g *Generator) getVideoInfo(ctx context.Context, videoPath string) (duration float64, width, height int, err error) {
|
||||
// Use ffprobe to get video information
|
||||
cmd := exec.CommandContext(ctx, "ffprobe",
|
||||
cmd := exec.CommandContext(ctx, utils.GetFFprobePath(),
|
||||
"-v", "error",
|
||||
"-select_streams", "v:0",
|
||||
"-show_entries", "stream=width,height,duration",
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ type MonoTheme struct{}
|
|||
func (m *MonoTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
|
||||
switch name {
|
||||
case theme.ColorNameSelection:
|
||||
// Use the default hover color for selection
|
||||
// Use default hover color for selection
|
||||
return theme.DefaultTheme().Color(theme.ColorNameHover, variant)
|
||||
case theme.ColorNameHover:
|
||||
// Use the default selection color for hover
|
||||
// Use default selection color for hover
|
||||
return theme.DefaultTheme().Color(theme.ColorNameSelection, variant)
|
||||
case theme.ColorNameButton:
|
||||
// Use a slightly lighter blue for buttons (92% of full selection color brightness)
|
||||
|
|
@ -54,6 +54,15 @@ func (m *MonoTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) c
|
|||
newG := uint8(min(int(float64(g>>8)*lightness), 255))
|
||||
newB := uint8(min(int(float64(b>>8)*lightness), 255))
|
||||
return color.RGBA{R: newR, G: newG, B: newB, A: uint8(a >> 8)}
|
||||
case theme.ColorNameBackground:
|
||||
// Use dark background for Entry widgets instead of grey
|
||||
return utils.MustHex("#2B2B2B")
|
||||
case theme.ColorNameInputBackground:
|
||||
// Use slightly lighter dark for Entry input background
|
||||
return utils.MustHex("#3C3C3C")
|
||||
case theme.ColorNameForeground:
|
||||
// Ensure good contrast on dark backgrounds
|
||||
return color.White
|
||||
}
|
||||
return theme.DefaultTheme().Color(name, variant)
|
||||
}
|
||||
|
|
@ -78,9 +87,9 @@ func (m *MonoTheme) Size(name fyne.ThemeSizeName) float32 {
|
|||
// Make UI elements larger and more readable
|
||||
switch name {
|
||||
case theme.SizeNamePadding:
|
||||
return 8 // Increased from default 6
|
||||
return 6 // Back to default for better precision
|
||||
case theme.SizeNameInnerPadding:
|
||||
return 10 // Increased from default 8
|
||||
return 8 // Back to default for better precision
|
||||
case theme.SizeNameText:
|
||||
return 15 // Increased from default 14
|
||||
case theme.SizeNameHeadingText:
|
||||
|
|
@ -967,7 +976,7 @@ func BuildModuleBadge(jobType queue.JobType) fyne.CanvasObject {
|
|||
|
||||
rect := canvas.NewRectangle(badgeColor)
|
||||
rect.CornerRadius = 3
|
||||
// rect.SetMinSize(fyne.NewSize(70, 20)) // Removed for flexible sizing
|
||||
rect.SetMinSize(fyne.NewSize(70, 20))
|
||||
|
||||
text := canvas.NewText(badgeText, color.White)
|
||||
text.Alignment = fyne.TextAlignCenter
|
||||
|
|
@ -982,7 +991,7 @@ func BuildModuleBadge(jobType queue.JobType) fyne.CanvasObject {
|
|||
func SectionHeader(title string, accentColor color.Color) fyne.CanvasObject {
|
||||
// Left accent bar (Memphis geometric style)
|
||||
accent := canvas.NewRectangle(accentColor)
|
||||
// accent.SetMinSize(fyne.NewSize(4, 20)) // Removed for flexible sizing
|
||||
accent.SetMinSize(fyne.NewSize(4, 20))
|
||||
|
||||
// Title text
|
||||
label := widget.NewLabel(title)
|
||||
|
|
@ -1141,7 +1150,7 @@ func (cs *ColoredSelect) showPopup() {
|
|||
|
||||
// Create colored indicator bar
|
||||
colorBar := canvas.NewRectangle(itemColor)
|
||||
// colorBar.SetMinSize(fyne.NewSize(4, 32)) // Removed for flexible sizing
|
||||
colorBar.SetMinSize(fyne.NewSize(4, 24))
|
||||
|
||||
// Create label
|
||||
label := widget.NewLabel(opt)
|
||||
|
|
@ -1151,9 +1160,9 @@ func (cs *ColoredSelect) showPopup() {
|
|||
label.TextStyle = fyne.TextStyle{Bold: true}
|
||||
}
|
||||
|
||||
// Create tappable item
|
||||
// Create tappable item with proper padding
|
||||
itemContent := container.NewBorder(nil, nil, colorBar, nil,
|
||||
container.NewPadded(label))
|
||||
container.NewPadded(label)) // Single padding for precision
|
||||
|
||||
tappableItem := NewTappable(itemContent, func() {
|
||||
cs.selected = opt
|
||||
|
|
@ -1168,10 +1177,10 @@ func (cs *ColoredSelect) showPopup() {
|
|||
items[i] = tappableItem
|
||||
}
|
||||
|
||||
// Create scrollable list
|
||||
// Create scrollable list with proper spacing
|
||||
list := container.NewVBox(items...)
|
||||
scroll := container.NewVScroll(list)
|
||||
// scroll.SetMinSize(fyne.NewSize(300, 200)) // Removed for flexible sizing
|
||||
scroll.SetMinSize(fyne.NewSize(300, 200)) // Add back minimum size for usability
|
||||
|
||||
// Create popup
|
||||
cs.popup = widget.NewPopUp(scroll, cs.window.Canvas())
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
//go:build !windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
//go:build windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
|
|
@ -6,11 +8,11 @@ import (
|
|||
"syscall"
|
||||
)
|
||||
|
||||
// createCommandWindows is a platform-specific implementation for Windows.
|
||||
// CreateCommand is a platform-specific implementation for Windows.
|
||||
// It ensures that the command is created without a new console window,
|
||||
// preventing disruptive pop-ups when running console applications (like ffmpeg)
|
||||
// from a GUI application.
|
||||
func createCommandWindows(ctx context.Context, name string, arg ...string) *exec.Cmd {
|
||||
func CreateCommand(ctx context.Context, name string, arg ...string) *exec.Cmd {
|
||||
cmd := exec.CommandContext(ctx, name, arg...)
|
||||
// SysProcAttr is used to control process creation parameters on Windows.
|
||||
// HideWindow: If true, the new process's console window will be hidden.
|
||||
|
|
@ -23,9 +25,9 @@ func createCommandWindows(ctx context.Context, name string, arg ...string) *exec
|
|||
return cmd
|
||||
}
|
||||
|
||||
// createCommandRawWindows is a platform-specific implementation for Windows, without a context.
|
||||
// CreateCommandRaw is a platform-specific implementation for Windows, without a context.
|
||||
// It applies the same console hiding behavior as CreateCommand.
|
||||
func createCommandRawWindows(name string, arg ...string) *exec.Cmd {
|
||||
func CreateCommandRaw(name string, arg ...string) *exec.Cmd {
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ func GetFFprobePath() string {
|
|||
return "ffprobe" // Fallback
|
||||
}
|
||||
|
||||
// GetFFplayPath returns the globally configured FFplay executable path.
|
||||
// It returns "ffplay" as a fallback if not explicitly set.
|
||||
func GetFFplayPath() string {
|
||||
return "ffplay" // Fallback
|
||||
}
|
||||
|
||||
// --- Color utilities ---
|
||||
|
||||
// MustHex parses a hex color string or exits on error
|
||||
|
|
|
|||
7
main.go
7
main.go
|
|
@ -5832,13 +5832,16 @@ func buildFFmpegCommandFromJob(job *queue.Job) string {
|
|||
case videoCodec == "H.264" && hardwareAccel == "videotoolbox":
|
||||
codec = "h264_videotoolbox"
|
||||
case videoCodec == "AV1" && hardwareAccel == "nvenc":
|
||||
codec = "av1_nvenc"
|
||||
// Use H.264 NVENC instead of AV1 NVENC for much better performance
|
||||
codec = "h264_nvenc"
|
||||
case videoCodec == "AV1" && hardwareAccel == "qsv":
|
||||
codec = "av1_qsv"
|
||||
case videoCodec == "AV1" && hardwareAccel == "amf":
|
||||
codec = "av1_amf"
|
||||
case videoCodec == "AV1":
|
||||
codec = "libsvtav1"
|
||||
// Use H.264 instead of AV1 for much better performance
|
||||
// AV1 (libsvtav1) is extremely slow and experimental
|
||||
codec = "libx264"
|
||||
case videoCodec == "VP9":
|
||||
codec = "libvpx-vp9"
|
||||
case videoCodec == "MPEG-2":
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user