Revert "feat(upscale): redesign layout and add encoding controls"
This reverts commit ed5be79f4c.
This commit is contained in:
parent
ed5be79f4c
commit
970a2328a9
|
|
@ -1,159 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2/app"
|
|
||||||
"git.leaktechnologies.dev/stu/VideoTools/internal/logging"
|
|
||||||
"git.leaktechnologies.dev/stu/VideoTools/internal/player"
|
|
||||||
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CrashInfo struct {
|
|
||||||
Timestamp time.Time
|
|
||||||
Error error
|
|
||||||
StackTrace string
|
|
||||||
VideoPath string
|
|
||||||
OSInfo string
|
|
||||||
MemStats runtime.MemStats
|
|
||||||
Goroutines int
|
|
||||||
}
|
|
||||||
|
|
||||||
var crashLog []CrashInfo
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("VideoTools Crash Diagnostic Tool")
|
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
|
||||||
fmt.Println("Usage: ./diagnostic_tool <video_path>")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
videoPath := os.Args[1]
|
|
||||||
if videoPath == "" {
|
|
||||||
fmt.Println("Error: video path required")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(videoPath); os.IsNotExist(err) {
|
|
||||||
fmt.Printf("Error: video file not found: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with unified player
|
|
||||||
testUnifiedPlayerStability(videoPath)
|
|
||||||
|
|
||||||
// Test with dual-process player (for comparison)
|
|
||||||
testDualProcessStability(videoPath)
|
|
||||||
|
|
||||||
// Generate crash report
|
|
||||||
generateCrashReport()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testUnifiedPlayerStability(videoPath string) {
|
|
||||||
fmt.Printf("Testing unified player with: %s\n", videoPath)
|
|
||||||
|
|
||||||
config := &player.Config{
|
|
||||||
Backend: player.BackendAuto,
|
|
||||||
WindowX: 100,
|
|
||||||
WindowY: 100,
|
|
||||||
WindowWidth: 800,
|
|
||||||
WindowHeight: 600,
|
|
||||||
Volume: 100,
|
|
||||||
Muted: false,
|
|
||||||
HardwareAccel: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
p := player.NewUnifiedPlayer(config)
|
|
||||||
if p == nil {
|
|
||||||
fmt.Printf("ERROR: Failed to create unified player: %v\n", fmt.Errorf("unified player creation failed"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.Load(videoPath, 0); err != nil {
|
|
||||||
fmt.Printf("ERROR: Failed to load video: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.Play(); err != nil {
|
|
||||||
fmt.Printf("ERROR: Failed to start playback: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Unified player test: PLAYING...")
|
|
||||||
|
|
||||||
// Test seeking
|
|
||||||
if err := p.SeekToTime(10 * time.Second); err != nil {
|
|
||||||
fmt.Printf("ERROR: Seek failed: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Unified player test: SEEKING TO 10s - SUCCESS\n")
|
|
||||||
|
|
||||||
// Test video info
|
|
||||||
info := p.GetVideoInfo()
|
|
||||||
if info != nil {
|
|
||||||
fmt.Printf("Video info: %dx%d @ %.2ffps %v duration %v\n",
|
|
||||||
info.Width, info.Height, info.FrameRate, info.Duration)
|
|
||||||
} else {
|
|
||||||
fmt.Println("ERROR: Failed to get video info\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Unified player test: COMPLETED SUCCESSFULLY")
|
|
||||||
|
|
||||||
p.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDualProcessStability(videoPath string) {
|
|
||||||
fmt.Printf("Testing dual-process player with: %s\n", videoPath)
|
|
||||||
|
|
||||||
// Simulate dual-process behavior for comparison
|
|
||||||
fmt.Println("Dual-process test: Would stutter, have A/V desync, no frame-accurate seeking")
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateCrashReport() {
|
|
||||||
fmt.Println("=== CRASH REPORT ===")
|
|
||||||
if len(crashLog) > 0 {
|
|
||||||
fmt.Printf("Total crashes: %d\n", len(crashLog))
|
|
||||||
for i, crash := range crashLog {
|
|
||||||
fmt.Printf("Crash %d at %v: %v\n", i+1, crash.Timestamp, crash.Error)
|
|
||||||
fmt.Printf(" Path: %s\n", crash.VideoPath)
|
|
||||||
fmt.Printf(" Error: %v\n", crash.Error)
|
|
||||||
if crash.StackTrace != "" {
|
|
||||||
fmt.Printf(" Stack: %s\n", crash.StackTrace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save detailed crash log
|
|
||||||
logPath := filepath.Join(getLogsDir(), "crash_diagnostics.log")
|
|
||||||
file, err := os.Create(logPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("ERROR: Failed to create crash log: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// Write crash information
|
|
||||||
for _, crash := range crashLog {
|
|
||||||
file.WriteString(fmt.Sprintf("[%s] CRASH #%d\n", i+1))
|
|
||||||
file.WriteString(fmt.Sprintf("Time: %v\n", crash.Timestamp.Format(time.RFC3339)))
|
|
||||||
file.WriteString(fmt.Sprintf("Video: %s\n", crash.VideoPath))
|
|
||||||
file.WriteString(fmt.Sprintf("Error: %v\n", crash.Error))
|
|
||||||
if crash.StackTrace != "" {
|
|
||||||
file.WriteString(fmt.Sprintf("Stack: %s\n", crash.StackTrace))
|
|
||||||
}
|
|
||||||
file.WriteString(fmt.Sprintf("OS: %s\n", crash.OSInfo))
|
|
||||||
file.WriteString(fmt.Sprintf("Memory: %v\n", crash.MemStats))
|
|
||||||
file.WriteString(fmt.Sprintf("Goroutines: %v\n", crash.Goroutines))
|
|
||||||
file.WriteString("---\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
file.WriteString(fmt.Sprintf("Crashes in session: %d\n", len(crashLog)))
|
|
||||||
fmt.Printf("Crash report saved to: %s\n", logPath)
|
|
||||||
}
|
|
||||||
454
main.go
454
main.go
|
|
@ -1098,10 +1098,6 @@ type appState struct {
|
||||||
upscaleMotionInterpolation bool // Use motion interpolation for frame rate changes
|
upscaleMotionInterpolation bool // Use motion interpolation for frame rate changes
|
||||||
upscaleBlurEnabled bool // Apply blur in upscale pipeline
|
upscaleBlurEnabled bool // Apply blur in upscale pipeline
|
||||||
upscaleBlurSigma float64 // Blur strength (sigma)
|
upscaleBlurSigma float64 // Blur strength (sigma)
|
||||||
upscaleEncoderPreset string // libx264 preset for upscale output
|
|
||||||
upscaleBitrateMode string // CRF, CBR, VBR
|
|
||||||
upscaleBitratePreset string // preset label for bitrate modes
|
|
||||||
upscaleManualBitrate string // manual bitrate value (e.g., 2500k)
|
|
||||||
|
|
||||||
// Snippet settings
|
// Snippet settings
|
||||||
snippetLength int // Length of snippet in seconds (default: 20)
|
snippetLength int // Length of snippet in seconds (default: 20)
|
||||||
|
|
@ -5562,10 +5558,6 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre
|
||||||
useMotionInterp, _ := cfg["useMotionInterpolation"].(bool)
|
useMotionInterp, _ := cfg["useMotionInterpolation"].(bool)
|
||||||
sourceFrameRate := toFloat(cfg["sourceFrameRate"])
|
sourceFrameRate := toFloat(cfg["sourceFrameRate"])
|
||||||
qualityPreset, _ := cfg["qualityPreset"].(string)
|
qualityPreset, _ := cfg["qualityPreset"].(string)
|
||||||
encoderPreset, _ := cfg["encoderPreset"].(string)
|
|
||||||
bitrateMode, _ := cfg["bitrateMode"].(string)
|
|
||||||
bitratePreset, _ := cfg["bitratePreset"].(string)
|
|
||||||
manualBitrate, _ := cfg["manualBitrate"].(string)
|
|
||||||
blurEnabled, _ := cfg["blurEnabled"].(bool)
|
blurEnabled, _ := cfg["blurEnabled"].(bool)
|
||||||
blurSigma := toFloat(cfg["blurSigma"])
|
blurSigma := toFloat(cfg["blurSigma"])
|
||||||
|
|
||||||
|
|
@ -5598,91 +5590,6 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre
|
||||||
crfValue = 16
|
crfValue = 16
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveBitrate := func() string {
|
|
||||||
if strings.TrimSpace(manualBitrate) != "" {
|
|
||||||
return manualBitrate
|
|
||||||
}
|
|
||||||
switch bitratePreset {
|
|
||||||
case "0.5 Mbps - Ultra Low":
|
|
||||||
return "500k"
|
|
||||||
case "1.0 Mbps - Very Low":
|
|
||||||
return "1000k"
|
|
||||||
case "1.5 Mbps - Low":
|
|
||||||
return "1500k"
|
|
||||||
case "2.0 Mbps - Medium-Low":
|
|
||||||
return "2000k"
|
|
||||||
case "2.5 Mbps - Medium":
|
|
||||||
return "2500k"
|
|
||||||
case "4.0 Mbps - Good":
|
|
||||||
return "4000k"
|
|
||||||
case "6.0 Mbps - High":
|
|
||||||
return "6000k"
|
|
||||||
case "8.0 Mbps - Very High":
|
|
||||||
return "8000k"
|
|
||||||
default:
|
|
||||||
return "2500k"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parseBitrateKbps := func(val string) int {
|
|
||||||
v := strings.TrimSpace(strings.ToLower(val))
|
|
||||||
if v == "" {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
mult := 1.0
|
|
||||||
if strings.HasSuffix(v, "k") {
|
|
||||||
v = strings.TrimSuffix(v, "k")
|
|
||||||
mult = 1
|
|
||||||
} else if strings.HasSuffix(v, "m") {
|
|
||||||
v = strings.TrimSuffix(v, "m")
|
|
||||||
mult = 1000
|
|
||||||
}
|
|
||||||
num, err := strconv.ParseFloat(v, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(num * mult)
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizeBitrateMode := func(mode string) string {
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(strings.ToUpper(mode), "CBR"):
|
|
||||||
return "CBR"
|
|
||||||
case strings.HasPrefix(strings.ToUpper(mode), "VBR"):
|
|
||||||
return "VBR"
|
|
||||||
default:
|
|
||||||
return "CRF"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appendEncodingArgs := func(args []string) []string {
|
|
||||||
preset := encoderPreset
|
|
||||||
if strings.TrimSpace(preset) == "" {
|
|
||||||
preset = "slow"
|
|
||||||
}
|
|
||||||
mode := normalizeBitrateMode(bitrateMode)
|
|
||||||
args = append(args, "-preset", preset)
|
|
||||||
switch mode {
|
|
||||||
case "CBR", "VBR":
|
|
||||||
bitrateVal := resolveBitrate()
|
|
||||||
args = append(args, "-b:v", bitrateVal)
|
|
||||||
kbps := parseBitrateKbps(bitrateVal)
|
|
||||||
if kbps > 0 {
|
|
||||||
maxrate := kbps
|
|
||||||
bufsize := kbps * 2
|
|
||||||
if mode == "VBR" {
|
|
||||||
maxrate = kbps * 2
|
|
||||||
bufsize = kbps * 4
|
|
||||||
}
|
|
||||||
args = append(args, "-maxrate", fmt.Sprintf("%dk", maxrate))
|
|
||||||
args = append(args, "-bufsize", fmt.Sprintf("%dk", bufsize))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
args = append(args, "-crf", strconv.Itoa(crfValue))
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build filter chain
|
// Build filter chain
|
||||||
var baseFilters []string
|
var baseFilters []string
|
||||||
|
|
||||||
|
|
@ -5942,9 +5849,10 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre
|
||||||
reassembleArgs = append(reassembleArgs, "-vf", finalScale)
|
reassembleArgs = append(reassembleArgs, "-vf", finalScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
reassembleArgs = append(reassembleArgs, "-c:v", "libx264")
|
|
||||||
reassembleArgs = appendEncodingArgs(reassembleArgs)
|
|
||||||
reassembleArgs = append(reassembleArgs,
|
reassembleArgs = append(reassembleArgs,
|
||||||
|
"-c:v", "libx264",
|
||||||
|
"-preset", "slow",
|
||||||
|
"-crf", strconv.Itoa(crfValue),
|
||||||
"-pix_fmt", "yuv420p",
|
"-pix_fmt", "yuv420p",
|
||||||
"-c:a", "copy",
|
"-c:a", "copy",
|
||||||
"-shortest",
|
"-shortest",
|
||||||
|
|
@ -6000,9 +5908,10 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use lossless MKV by default for upscales; copy audio
|
// Use lossless MKV by default for upscales; copy audio
|
||||||
args = append(args, "-c:v", "libx264")
|
|
||||||
args = appendEncodingArgs(args)
|
|
||||||
args = append(args,
|
args = append(args,
|
||||||
|
"-c:v", "libx264",
|
||||||
|
"-preset", "slow",
|
||||||
|
"-crf", strconv.Itoa(crfValue),
|
||||||
"-pix_fmt", "yuv420p",
|
"-pix_fmt", "yuv420p",
|
||||||
"-c:a", "copy",
|
"-c:a", "copy",
|
||||||
"-progress", "pipe:1",
|
"-progress", "pipe:1",
|
||||||
|
|
@ -11358,22 +11267,8 @@ func (p *playSession) runAudio(offset float64) {
|
||||||
p.audioActive.Store(false)
|
p.audioActive.Store(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Use new UnifiedPlayer with proper A/V synchronization
|
pr, pw := io.Pipe()
|
||||||
unifiedPlayer := player.NewUnifiedPlayer(pr)
|
player := ctx.NewPlayer(pr)
|
||||||
if player == nil {
|
|
||||||
logging.Error(logging.CatPlayer, "audio player creation failed (video-only playback)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer pr.Close()
|
|
||||||
defer pw.Close()
|
|
||||||
|
|
||||||
player.Play()
|
|
||||||
p.audioActive.Store(true) // Mark audio as active
|
|
||||||
localPlayer := unifiedPlayer
|
|
||||||
if localPlayer != nil {
|
|
||||||
s.window.Canvas().SetContent(localPlayer)
|
|
||||||
}
|
|
||||||
return localPlayer, nil
|
|
||||||
if player == nil {
|
if player == nil {
|
||||||
logging.Debug(logging.CatFFMPEG, "audio player creation failed (video-only playback)")
|
logging.Debug(logging.CatFFMPEG, "audio player creation failed (video-only playback)")
|
||||||
p.audioActive.Store(false)
|
p.audioActive.Store(false)
|
||||||
|
|
@ -14830,11 +14725,11 @@ func buildCompareView(state *appState) fyne.CanvasObject {
|
||||||
|
|
||||||
// Scrollable metadata area for file 1 - use smaller minimum
|
// Scrollable metadata area for file 1 - use smaller minimum
|
||||||
file1InfoScroll := container.NewVScroll(file1Info)
|
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
|
// Scrollable metadata area for file 2 - use smaller minimum
|
||||||
file2InfoScroll := container.NewVScroll(file2Info)
|
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)
|
// File 1 column: header, video player, metadata (using Border to make metadata expand)
|
||||||
file1Column := container.NewBorder(
|
file1Column := container.NewBorder(
|
||||||
|
|
@ -15034,18 +14929,6 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
if state.upscaleQualityPreset == "" {
|
if state.upscaleQualityPreset == "" {
|
||||||
state.upscaleQualityPreset = "Near-lossless (CRF 16)"
|
state.upscaleQualityPreset = "Near-lossless (CRF 16)"
|
||||||
}
|
}
|
||||||
if state.upscaleEncoderPreset == "" {
|
|
||||||
state.upscaleEncoderPreset = "slow"
|
|
||||||
}
|
|
||||||
if state.upscaleBitrateMode == "" {
|
|
||||||
state.upscaleBitrateMode = "CRF"
|
|
||||||
}
|
|
||||||
if state.upscaleBitratePreset == "" {
|
|
||||||
state.upscaleBitratePreset = "2.5 Mbps - Medium"
|
|
||||||
}
|
|
||||||
if state.upscaleManualBitrate == "" {
|
|
||||||
state.upscaleManualBitrate = "2500k"
|
|
||||||
}
|
|
||||||
if state.upscaleAIPreset == "" {
|
if state.upscaleAIPreset == "" {
|
||||||
state.upscaleAIPreset = "Balanced"
|
state.upscaleAIPreset = "Balanced"
|
||||||
state.upscaleAIScale = 4.0
|
state.upscaleAIScale = 4.0
|
||||||
|
|
@ -15141,6 +15024,62 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
methodInfo.TextStyle = fyne.TextStyle{Italic: true}
|
methodInfo.TextStyle = fyne.TextStyle{Italic: true}
|
||||||
methodInfo.Wrapping = fyne.TextWrapWord
|
methodInfo.Wrapping = fyne.TextWrapWord
|
||||||
|
|
||||||
|
traditionalSection := widget.NewCard("Traditional Scaling (FFmpeg)", "", container.NewVBox(
|
||||||
|
widget.NewLabel("Classic upscaling methods - always available"),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Scaling Algorithm:"),
|
||||||
|
methodSelect,
|
||||||
|
),
|
||||||
|
methodLabel,
|
||||||
|
widget.NewSeparator(),
|
||||||
|
methodInfo,
|
||||||
|
))
|
||||||
|
|
||||||
|
// Resolution Selection Section
|
||||||
|
resLabel := widget.NewLabel(fmt.Sprintf("Target: %s", state.upscaleTargetRes))
|
||||||
|
resSelect := widget.NewSelect([]string{
|
||||||
|
"Match Source",
|
||||||
|
"2X (relative)",
|
||||||
|
"4X (relative)",
|
||||||
|
"720p (1280x720)",
|
||||||
|
"1080p (1920x1080)",
|
||||||
|
"1440p (2560x1440)",
|
||||||
|
"4K (3840x2160)",
|
||||||
|
"8K (7680x4320)",
|
||||||
|
"Custom",
|
||||||
|
}, func(s string) {
|
||||||
|
state.upscaleTargetRes = s
|
||||||
|
resLabel.SetText(fmt.Sprintf("Target: %s", s))
|
||||||
|
})
|
||||||
|
resSelect.SetSelected(state.upscaleTargetRes)
|
||||||
|
|
||||||
|
resolutionSection := widget.NewCard("Target Resolution", "", container.NewVBox(
|
||||||
|
widget.NewLabel("Select output resolution"),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Resolution:"),
|
||||||
|
resSelect,
|
||||||
|
),
|
||||||
|
resLabel,
|
||||||
|
sourceResLabel,
|
||||||
|
))
|
||||||
|
|
||||||
|
qualitySelect := widget.NewSelect([]string{
|
||||||
|
"Lossless (CRF 0)",
|
||||||
|
"Near-lossless (CRF 16)",
|
||||||
|
"High (CRF 18)",
|
||||||
|
}, func(s string) {
|
||||||
|
state.upscaleQualityPreset = s
|
||||||
|
})
|
||||||
|
qualitySelect.SetSelected(state.upscaleQualityPreset)
|
||||||
|
|
||||||
|
qualitySection := widget.NewCard("Output Quality", "", container.NewVBox(
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Quality:"),
|
||||||
|
qualitySelect,
|
||||||
|
),
|
||||||
|
widget.NewLabel("Lower CRF = higher quality/larger files"),
|
||||||
|
))
|
||||||
|
|
||||||
blurLabel := widget.NewLabel(fmt.Sprintf("Blur Strength: %.2f", state.upscaleBlurSigma))
|
blurLabel := widget.NewLabel(fmt.Sprintf("Blur Strength: %.2f", state.upscaleBlurSigma))
|
||||||
blurSlider := widget.NewSlider(0.0, 8.0)
|
blurSlider := widget.NewSlider(0.0, 8.0)
|
||||||
blurSlider.Step = 0.1
|
blurSlider.Step = 0.1
|
||||||
|
|
@ -15165,178 +15104,10 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
blurSlider.Disable()
|
blurSlider.Disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
mediumBlue := utils.MustHex("#13182B")
|
blurSection := widget.NewCard("Blur (Optional)", "", container.NewVBox(
|
||||||
navyBlue := utils.MustHex("#191F35")
|
widget.NewLabel("Apply a soft blur during upscale processing"),
|
||||||
|
blurCheck,
|
||||||
buildUpscaleBox := func(title string, content fyne.CanvasObject) fyne.CanvasObject {
|
container.NewVBox(blurLabel, blurSlider),
|
||||||
bg := canvas.NewRectangle(navyBlue)
|
|
||||||
bg.CornerRadius = 10
|
|
||||||
bg.StrokeColor = gridColor
|
|
||||||
bg.StrokeWidth = 1
|
|
||||||
body := container.NewVBox(
|
|
||||||
widget.NewLabelWithStyle(title, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
|
||||||
widget.NewSeparator(),
|
|
||||||
content,
|
|
||||||
)
|
|
||||||
return container.NewMax(bg, container.NewPadded(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolution Selection Section
|
|
||||||
resLabel := widget.NewLabel(fmt.Sprintf("Target: %s", state.upscaleTargetRes))
|
|
||||||
resSelect := widget.NewSelect([]string{
|
|
||||||
"Match Source",
|
|
||||||
"2X (relative)",
|
|
||||||
"4X (relative)",
|
|
||||||
"720p (1280x720)",
|
|
||||||
"1080p (1920x1080)",
|
|
||||||
"1440p (2560x1440)",
|
|
||||||
"4K (3840x2160)",
|
|
||||||
"8K (7680x4320)",
|
|
||||||
"Custom",
|
|
||||||
}, func(s string) {
|
|
||||||
state.upscaleTargetRes = s
|
|
||||||
resLabel.SetText(fmt.Sprintf("Target: %s", s))
|
|
||||||
})
|
|
||||||
resSelect.SetSelected(state.upscaleTargetRes)
|
|
||||||
|
|
||||||
resolutionSection := buildUpscaleBox("Target Resolution", container.NewVBox(
|
|
||||||
widget.NewLabel("Select output resolution"),
|
|
||||||
container.NewGridWithColumns(2,
|
|
||||||
widget.NewLabel("Resolution:"),
|
|
||||||
resSelect,
|
|
||||||
),
|
|
||||||
resLabel,
|
|
||||||
sourceResLabel,
|
|
||||||
))
|
|
||||||
|
|
||||||
qualitySelect := widget.NewSelect([]string{
|
|
||||||
"Lossless (CRF 0)",
|
|
||||||
"Near-lossless (CRF 16)",
|
|
||||||
"High (CRF 18)",
|
|
||||||
}, func(s string) {
|
|
||||||
state.upscaleQualityPreset = s
|
|
||||||
})
|
|
||||||
qualitySelect.SetSelected(state.upscaleQualityPreset)
|
|
||||||
|
|
||||||
encoderPresetSelect := widget.NewSelect([]string{
|
|
||||||
"ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow",
|
|
||||||
}, func(s string) {
|
|
||||||
state.upscaleEncoderPreset = s
|
|
||||||
})
|
|
||||||
encoderPresetSelect.SetSelected(state.upscaleEncoderPreset)
|
|
||||||
|
|
||||||
var updateEncodingVisibility func()
|
|
||||||
|
|
||||||
bitrateModeSelect := widget.NewSelect([]string{
|
|
||||||
"CRF (Constant Rate Factor)",
|
|
||||||
"CBR (Constant Bitrate)",
|
|
||||||
"VBR (Variable Bitrate)",
|
|
||||||
}, func(s string) {
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(s, "CRF"):
|
|
||||||
state.upscaleBitrateMode = "CRF"
|
|
||||||
case strings.HasPrefix(s, "CBR"):
|
|
||||||
state.upscaleBitrateMode = "CBR"
|
|
||||||
case strings.HasPrefix(s, "VBR"):
|
|
||||||
state.upscaleBitrateMode = "VBR"
|
|
||||||
default:
|
|
||||||
state.upscaleBitrateMode = s
|
|
||||||
}
|
|
||||||
if updateEncodingVisibility != nil {
|
|
||||||
updateEncodingVisibility()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
switch state.upscaleBitrateMode {
|
|
||||||
case "CBR":
|
|
||||||
bitrateModeSelect.SetSelected("CBR (Constant Bitrate)")
|
|
||||||
case "VBR":
|
|
||||||
bitrateModeSelect.SetSelected("VBR (Variable Bitrate)")
|
|
||||||
default:
|
|
||||||
bitrateModeSelect.SetSelected("CRF (Constant Rate Factor)")
|
|
||||||
}
|
|
||||||
|
|
||||||
type bitratePreset struct {
|
|
||||||
Label string
|
|
||||||
Bitrate string
|
|
||||||
}
|
|
||||||
presets := []bitratePreset{
|
|
||||||
{Label: "0.5 Mbps - Ultra Low", Bitrate: "500k"},
|
|
||||||
{Label: "1.0 Mbps - Very Low", Bitrate: "1000k"},
|
|
||||||
{Label: "1.5 Mbps - Low", Bitrate: "1500k"},
|
|
||||||
{Label: "2.0 Mbps - Medium-Low", Bitrate: "2000k"},
|
|
||||||
{Label: "2.5 Mbps - Medium", Bitrate: "2500k"},
|
|
||||||
{Label: "4.0 Mbps - Good", Bitrate: "4000k"},
|
|
||||||
{Label: "6.0 Mbps - High", Bitrate: "6000k"},
|
|
||||||
{Label: "8.0 Mbps - Very High", Bitrate: "8000k"},
|
|
||||||
{Label: "Manual", Bitrate: ""},
|
|
||||||
}
|
|
||||||
bitratePresetLookup := make(map[string]bitratePreset)
|
|
||||||
var bitratePresetLabels []string
|
|
||||||
for _, p := range presets {
|
|
||||||
bitratePresetLookup[p.Label] = p
|
|
||||||
bitratePresetLabels = append(bitratePresetLabels, p.Label)
|
|
||||||
}
|
|
||||||
|
|
||||||
manualBitrateEntry := widget.NewEntry()
|
|
||||||
manualBitrateEntry.SetPlaceHolder("e.g., 2500k")
|
|
||||||
manualBitrateEntry.SetText(state.upscaleManualBitrate)
|
|
||||||
manualBitrateEntry.OnChanged = func(val string) {
|
|
||||||
state.upscaleManualBitrate = val
|
|
||||||
}
|
|
||||||
|
|
||||||
bitratePresetSelect := widget.NewSelect(bitratePresetLabels, func(s string) {
|
|
||||||
state.upscaleBitratePreset = s
|
|
||||||
preset := bitratePresetLookup[s]
|
|
||||||
if preset.Bitrate == "" {
|
|
||||||
manualBitrateEntry.Show()
|
|
||||||
} else {
|
|
||||||
state.upscaleManualBitrate = preset.Bitrate
|
|
||||||
manualBitrateEntry.SetText(preset.Bitrate)
|
|
||||||
manualBitrateEntry.Hide()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
bitratePresetSelect.SetSelected(state.upscaleBitratePreset)
|
|
||||||
if bitratePresetLookup[state.upscaleBitratePreset].Bitrate == "" {
|
|
||||||
manualBitrateEntry.Show()
|
|
||||||
} else {
|
|
||||||
manualBitrateEntry.Hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateEncodingVisibility = func() {
|
|
||||||
mode := state.upscaleBitrateMode
|
|
||||||
if mode == "" || mode == "CRF" {
|
|
||||||
qualitySelect.Enable()
|
|
||||||
bitratePresetSelect.Hide()
|
|
||||||
manualBitrateEntry.Hide()
|
|
||||||
} else {
|
|
||||||
qualitySelect.Disable()
|
|
||||||
bitratePresetSelect.Show()
|
|
||||||
if bitratePresetLookup[state.upscaleBitratePreset].Bitrate == "" {
|
|
||||||
manualBitrateEntry.Show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateEncodingVisibility()
|
|
||||||
|
|
||||||
encodingSection := buildUpscaleBox("Video Encoding", container.NewVBox(
|
|
||||||
container.NewGridWithColumns(2,
|
|
||||||
widget.NewLabel("Encoder Preset:"),
|
|
||||||
encoderPresetSelect,
|
|
||||||
),
|
|
||||||
container.NewGridWithColumns(2,
|
|
||||||
widget.NewLabel("Quality Preset:"),
|
|
||||||
qualitySelect,
|
|
||||||
),
|
|
||||||
container.NewGridWithColumns(2,
|
|
||||||
widget.NewLabel("Bitrate Mode:"),
|
|
||||||
bitrateModeSelect,
|
|
||||||
),
|
|
||||||
container.NewGridWithColumns(2,
|
|
||||||
widget.NewLabel("Bitrate Preset:"),
|
|
||||||
bitratePresetSelect,
|
|
||||||
),
|
|
||||||
manualBitrateEntry,
|
|
||||||
widget.NewLabel("CRF mode controls quality; bitrate modes control size."),
|
|
||||||
))
|
))
|
||||||
|
|
||||||
// Frame Rate Section
|
// Frame Rate Section
|
||||||
|
|
@ -15352,7 +15123,7 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
})
|
})
|
||||||
motionInterpCheck.SetChecked(state.upscaleMotionInterpolation)
|
motionInterpCheck.SetChecked(state.upscaleMotionInterpolation)
|
||||||
|
|
||||||
frameRateSection := buildUpscaleBox("Frame Rate", container.NewVBox(
|
frameRateSection := widget.NewCard("Frame Rate", "", container.NewVBox(
|
||||||
widget.NewLabel("Convert frame rate (optional)"),
|
widget.NewLabel("Convert frame rate (optional)"),
|
||||||
container.NewGridWithColumns(2,
|
container.NewGridWithColumns(2,
|
||||||
widget.NewLabel("Target FPS:"),
|
widget.NewLabel("Target FPS:"),
|
||||||
|
|
@ -15369,8 +15140,8 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
aiModelLabel = aiModelOptions[0]
|
aiModelLabel = aiModelOptions[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI Upscaling Section (nested under Scaling)
|
// AI Upscaling Section
|
||||||
var aiContent fyne.CanvasObject
|
var aiSection *widget.Card
|
||||||
if state.upscaleAIAvailable {
|
if state.upscaleAIAvailable {
|
||||||
var aiTileSelect *widget.Select
|
var aiTileSelect *widget.Select
|
||||||
var aiTTACheck *widget.Check
|
var aiTTACheck *widget.Check
|
||||||
|
|
@ -15579,7 +15350,7 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
denoiseHint.TextStyle = fyne.TextStyle{Italic: true}
|
denoiseHint.TextStyle = fyne.TextStyle{Italic: true}
|
||||||
updateDenoiseAvailability(state.upscaleAIModel)
|
updateDenoiseAvailability(state.upscaleAIModel)
|
||||||
|
|
||||||
aiContent = container.NewVBox(
|
aiSection = widget.NewCard("AI Upscaling", "✓ Available", container.NewVBox(
|
||||||
widget.NewLabel("Real-ESRGAN detected - enhanced quality available"),
|
widget.NewLabel("Real-ESRGAN detected - enhanced quality available"),
|
||||||
aiEnabledCheck,
|
aiEnabledCheck,
|
||||||
container.NewGridWithColumns(2,
|
container.NewGridWithColumns(2,
|
||||||
|
|
@ -15617,49 +15388,26 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
container.NewGridWithColumns(3, aiThreadsLoad, aiThreadsProc, aiThreadsSave),
|
container.NewGridWithColumns(3, aiThreadsLoad, aiThreadsProc, aiThreadsSave),
|
||||||
),
|
),
|
||||||
widget.NewLabel("Note: AI upscaling is slower but produces higher quality results"),
|
widget.NewLabel("Note: AI upscaling is slower but produces higher quality results"),
|
||||||
)
|
))
|
||||||
} else {
|
} else {
|
||||||
backendNote := "Real-ESRGAN not detected. Install for enhanced quality:"
|
backendNote := "Real-ESRGAN not detected. Install for enhanced quality:"
|
||||||
if state.upscaleAIBackend == "python" {
|
if state.upscaleAIBackend == "python" {
|
||||||
backendNote = "Python Real-ESRGAN detected, but the ncnn backend is required for now."
|
backendNote = "Python Real-ESRGAN detected, but the ncnn backend is required for now."
|
||||||
}
|
}
|
||||||
aiContent = container.NewVBox(
|
aiSection = widget.NewCard("AI Upscaling", "Not Available", container.NewVBox(
|
||||||
widget.NewLabel(backendNote),
|
widget.NewLabel(backendNote),
|
||||||
widget.NewLabel("https://github.com/xinntao/Real-ESRGAN"),
|
widget.NewLabel("https://github.com/xinntao/Real-ESRGAN"),
|
||||||
widget.NewLabel("Traditional scaling methods will be used."),
|
widget.NewLabel("Traditional scaling methods will be used."),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
aiSection := container.NewVBox(
|
|
||||||
widget.NewLabelWithStyle("AI Upscaling", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
|
||||||
widget.NewSeparator(),
|
|
||||||
aiContent,
|
|
||||||
)
|
|
||||||
|
|
||||||
traditionalSection := buildUpscaleBox("Scaling", container.NewVBox(
|
|
||||||
widget.NewLabel("Classic upscaling methods - always available"),
|
|
||||||
container.NewGridWithColumns(2,
|
|
||||||
widget.NewLabel("Scaling Algorithm:"),
|
|
||||||
methodSelect,
|
|
||||||
),
|
|
||||||
methodLabel,
|
|
||||||
widget.NewSeparator(),
|
|
||||||
methodInfo,
|
|
||||||
widget.NewSeparator(),
|
|
||||||
widget.NewLabel("Optional blur"),
|
|
||||||
blurCheck,
|
|
||||||
container.NewVBox(blurLabel, blurSlider),
|
|
||||||
widget.NewSeparator(),
|
|
||||||
aiSection,
|
|
||||||
))
|
|
||||||
|
|
||||||
// Filter Integration Section
|
// Filter Integration Section
|
||||||
applyFiltersCheck := widget.NewCheck("Apply filters before upscaling", func(checked bool) {
|
applyFiltersCheck := widget.NewCheck("Apply filters before upscaling", func(checked bool) {
|
||||||
state.upscaleApplyFilters = checked
|
state.upscaleApplyFilters = checked
|
||||||
})
|
})
|
||||||
applyFiltersCheck.SetChecked(state.upscaleApplyFilters)
|
applyFiltersCheck.SetChecked(state.upscaleApplyFilters)
|
||||||
|
|
||||||
filterIntegrationSection := buildUpscaleBox("Filter Integration", container.NewVBox(
|
filterIntegrationSection := widget.NewCard("Filter Integration", "", container.NewVBox(
|
||||||
widget.NewLabel("Apply color correction and filters from Filters module"),
|
widget.NewLabel("Apply color correction and filters from Filters module"),
|
||||||
applyFiltersCheck,
|
applyFiltersCheck,
|
||||||
widget.NewLabel("Filters will be applied before upscaling for best quality"),
|
widget.NewLabel("Filters will be applied before upscaling for best quality"),
|
||||||
|
|
@ -15704,10 +15452,6 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
"inputPath": state.upscaleFile.Path,
|
"inputPath": state.upscaleFile.Path,
|
||||||
"outputPath": outputPath,
|
"outputPath": outputPath,
|
||||||
"method": state.upscaleMethod,
|
"method": state.upscaleMethod,
|
||||||
"encoderPreset": state.upscaleEncoderPreset,
|
|
||||||
"bitrateMode": state.upscaleBitrateMode,
|
|
||||||
"bitratePreset": state.upscaleBitratePreset,
|
|
||||||
"manualBitrate": state.upscaleManualBitrate,
|
|
||||||
"targetWidth": float64(targetWidth),
|
"targetWidth": float64(targetWidth),
|
||||||
"targetHeight": float64(targetHeight),
|
"targetHeight": float64(targetHeight),
|
||||||
"targetPreset": state.upscaleTargetRes,
|
"targetPreset": state.upscaleTargetRes,
|
||||||
|
|
@ -15777,38 +15521,22 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
addQueueBtn.Importance = widget.MediumImportance
|
addQueueBtn.Importance = widget.MediumImportance
|
||||||
|
|
||||||
// Main content
|
// Main content
|
||||||
spacing := func() fyne.CanvasObject {
|
|
||||||
spacer := canvas.NewRectangle(color.Transparent)
|
|
||||||
spacer.SetMinSize(fyne.NewSize(0, 10))
|
|
||||||
return spacer
|
|
||||||
}
|
|
||||||
|
|
||||||
metaPanel, _ := buildMetadataPanel(state, state.upscaleFile, fyne.NewSize(0, 200))
|
|
||||||
|
|
||||||
leftPanel := container.NewVBox(
|
leftPanel := container.NewVBox(
|
||||||
instructions,
|
instructions,
|
||||||
spacing(),
|
widget.NewSeparator(),
|
||||||
buildUpscaleBox("Video", container.NewVBox(
|
fileLabel,
|
||||||
fileLabel,
|
loadBtn,
|
||||||
loadBtn,
|
filtersNavBtn,
|
||||||
filtersNavBtn,
|
|
||||||
videoContainer,
|
|
||||||
)),
|
|
||||||
spacing(),
|
|
||||||
metaPanel,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
settingsPanel := container.NewVBox(
|
settingsPanel := container.NewVBox(
|
||||||
traditionalSection,
|
traditionalSection,
|
||||||
spacing(),
|
|
||||||
resolutionSection,
|
resolutionSection,
|
||||||
spacing(),
|
qualitySection,
|
||||||
encodingSection,
|
blurSection,
|
||||||
spacing(),
|
|
||||||
frameRateSection,
|
frameRateSection,
|
||||||
spacing(),
|
aiSection,
|
||||||
filterIntegrationSection,
|
filterIntegrationSection,
|
||||||
spacing(),
|
|
||||||
container.NewGridWithColumns(2, applyBtn, addQueueBtn),
|
container.NewGridWithColumns(2, applyBtn, addQueueBtn),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -15816,15 +15544,13 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
// Adaptive height for small screens
|
// Adaptive height for small screens
|
||||||
// Avoid rigid min sizes so window snapping works across modules.
|
// Avoid rigid min sizes so window snapping works across modules.
|
||||||
|
|
||||||
split := container.NewHSplit(leftPanel, settingsScroll)
|
mainContent := container.New(&fixedHSplitLayout{ratio: 0.6},
|
||||||
split.Offset = 0.58
|
container.NewVBox(leftPanel, videoContainer),
|
||||||
mainContent := split
|
settingsScroll,
|
||||||
|
|
||||||
content := container.NewMax(
|
|
||||||
canvas.NewRectangle(mediumBlue),
|
|
||||||
container.NewPadded(mainContent),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
content := container.NewPadded(mainContent)
|
||||||
|
|
||||||
return container.NewBorder(topBar, bottomBar, nil, nil, content)
|
return container.NewBorder(topBar, bottomBar, nil, nil, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user