Add interlacing analysis UI to Convert module
Integrated interlacing detection into the Convert module with: Features: - "Analyze Interlacing" button in metadata panel - Real-time analysis using FFmpeg idet filter (first 500 frames) - Color-coded results card showing: - Status (Progressive/Interlaced/Mixed) - Interlacing percentage - Field order (TFF/BFF/Unknown) - Confidence level - Recommendation text - Detailed frame counts Auto-updates: - Automatically suggests enabling deinterlacing if needed - Updates Convert deinterlace setting from "Off" to "Auto" when interlacing detected UI States: - Initial: Just "Analyze Interlacing" button - Analyzing: Shows progress message - Complete: Shows colored results card with full analysis Analysis runs in background goroutine with proper thread-safe UI updates. Next: Add to simple menu and Inspect module 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
49c865b1e3
commit
2acf568cc2
|
|
@ -8,7 +8,6 @@ import (
|
|||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DetectionResult contains the results of interlacing analysis
|
||||
|
|
|
|||
116
main.go
116
main.go
|
|
@ -38,6 +38,7 @@ import (
|
|||
"fyne.io/fyne/v2/widget"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/benchmark"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/convert"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/interlace"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/logging"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/modules"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/player"
|
||||
|
|
@ -611,6 +612,10 @@ type appState struct {
|
|||
mergeKeepAll bool
|
||||
mergeCodecMode string
|
||||
mergeChapters bool
|
||||
|
||||
// Interlacing detection state
|
||||
interlaceResult *interlace.DetectionResult
|
||||
interlaceAnalyzing bool
|
||||
}
|
||||
|
||||
type mergeClip struct {
|
||||
|
|
@ -5440,6 +5445,116 @@ Metadata: %s`,
|
|||
|
||||
coverContainer := container.NewMax(placeholder, coverImg)
|
||||
|
||||
// Interlacing Analysis Section
|
||||
analyzeBtn := widget.NewButton("Analyze Interlacing", func() {
|
||||
if state.source == nil {
|
||||
return
|
||||
}
|
||||
state.interlaceAnalyzing = true
|
||||
state.interlaceResult = nil
|
||||
state.showConvertView(state.source) // Refresh to show "Analyzing..."
|
||||
|
||||
go func() {
|
||||
detector := interlace.NewDetector(platformConfig.FFmpegPath, platformConfig.FFprobePath)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
result, err := detector.QuickAnalyze(ctx, state.source.Path)
|
||||
|
||||
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
||||
state.interlaceAnalyzing = false
|
||||
if err != nil {
|
||||
logging.Debug(logging.CatSystem, "interlacing analysis failed: %v", err)
|
||||
dialog.ShowError(fmt.Errorf("Analysis failed: %w", err), state.window)
|
||||
} else {
|
||||
state.interlaceResult = result
|
||||
logging.Debug(logging.CatSystem, "interlacing analysis complete: %s", result.Status)
|
||||
|
||||
// Auto-update deinterlace setting based on recommendation
|
||||
if result.SuggestDeinterlace && state.convert.Deinterlace == "Off" {
|
||||
state.convert.Deinterlace = "Auto"
|
||||
}
|
||||
}
|
||||
state.showConvertView(state.source) // Refresh to show results
|
||||
}, false)
|
||||
}()
|
||||
})
|
||||
analyzeBtn.Importance = widget.MediumImportance
|
||||
|
||||
var interlaceSection fyne.CanvasObject
|
||||
if state.interlaceAnalyzing {
|
||||
statusLabel := widget.NewLabel("Analyzing interlacing... (first 500 frames)")
|
||||
statusLabel.TextStyle = fyne.TextStyle{Italic: true}
|
||||
interlaceSection = container.NewVBox(
|
||||
widget.NewSeparator(),
|
||||
analyzeBtn,
|
||||
statusLabel,
|
||||
)
|
||||
} else if state.interlaceResult != nil {
|
||||
result := state.interlaceResult
|
||||
|
||||
// Status color
|
||||
var statusColor color.Color
|
||||
switch result.Status {
|
||||
case "Progressive":
|
||||
statusColor = color.RGBA{R: 76, G: 232, B: 112, A: 255} // Green
|
||||
case "Interlaced":
|
||||
statusColor = color.RGBA{R: 255, G: 193, B: 7, A: 255} // Yellow
|
||||
default:
|
||||
statusColor = color.RGBA{R: 255, G: 136, B: 68, A: 255} // Orange
|
||||
}
|
||||
|
||||
statusRect := canvas.NewRectangle(statusColor)
|
||||
statusRect.SetMinSize(fyne.NewSize(4, 0))
|
||||
statusRect.CornerRadius = 2
|
||||
|
||||
statusLabel := widget.NewLabel(result.Status)
|
||||
statusLabel.TextStyle = fyne.TextStyle{Bold: true}
|
||||
|
||||
percLabel := widget.NewLabel(fmt.Sprintf("%.1f%% interlaced frames", result.InterlacedPercent))
|
||||
fieldLabel := widget.NewLabel(fmt.Sprintf("Field Order: %s", result.FieldOrder))
|
||||
confLabel := widget.NewLabel(fmt.Sprintf("Confidence: %s", result.Confidence))
|
||||
recLabel := widget.NewLabel(result.Recommendation)
|
||||
recLabel.Wrapping = fyne.TextWrapWord
|
||||
|
||||
// Frame counts (collapsed by default)
|
||||
detailsLabel := widget.NewLabel(fmt.Sprintf(
|
||||
"Progressive: %d | TFF: %d | BFF: %d | Undetermined: %d | Total: %d",
|
||||
result.Progressive, result.TFF, result.BFF, result.Undetermined, result.TotalFrames,
|
||||
))
|
||||
detailsLabel.TextStyle = fyne.TextStyle{Italic: true}
|
||||
detailsLabel.Wrapping = fyne.TextWrapWord
|
||||
|
||||
resultCard := canvas.NewRectangle(utils.MustHex("#1E1E1E"))
|
||||
resultCard.CornerRadius = 4
|
||||
|
||||
resultContent := container.NewBorder(
|
||||
nil, nil,
|
||||
statusRect,
|
||||
nil,
|
||||
container.NewVBox(
|
||||
statusLabel,
|
||||
percLabel,
|
||||
fieldLabel,
|
||||
confLabel,
|
||||
widget.NewSeparator(),
|
||||
recLabel,
|
||||
detailsLabel,
|
||||
),
|
||||
)
|
||||
|
||||
interlaceSection = container.NewVBox(
|
||||
widget.NewSeparator(),
|
||||
analyzeBtn,
|
||||
container.NewPadded(container.NewMax(resultCard, resultContent)),
|
||||
)
|
||||
} else {
|
||||
interlaceSection = container.NewVBox(
|
||||
widget.NewSeparator(),
|
||||
analyzeBtn,
|
||||
)
|
||||
}
|
||||
|
||||
// Layout: metadata form on left, cover art on right (bottom-aligned)
|
||||
coverColumn := container.NewVBox(layout.NewSpacer(), coverContainer)
|
||||
contentArea := container.NewBorder(nil, nil, nil, coverColumn, info)
|
||||
|
|
@ -5448,6 +5563,7 @@ Metadata: %s`,
|
|||
top,
|
||||
widget.NewSeparator(),
|
||||
contentArea,
|
||||
interlaceSection,
|
||||
)
|
||||
return container.NewMax(outer, container.NewPadded(body)), updateCoverDisplay
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user