feat(settings): Add Settings module with dependency management
Add comprehensive Settings module for managing system dependencies and application preferences: - Created settings_module.go with dependency checking system - Maps modules to required dependencies (FFmpeg, DVDAuthor, xorriso, Real-ESRGAN, Whisper) - Displays dependency status with visual indicators (green/red) - Shows platform-specific installation commands - Auto-enables/disables modules based on installed dependencies - Added Settings tile to main menu (always enabled) - Integrated module availability checking via isModuleAvailable() This provides users a centralized location to check and install missing dependencies, addressing the requirement to disable modules when dependencies aren't available. 🤖 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
879bec4309
commit
b079bff6fb
7
main.go
7
main.go
|
|
@ -96,6 +96,7 @@ var (
|
|||
{"compare", "Compare", utils.MustHex("#E91E63"), "Inspect", modules.HandleCompare}, // Pink (comparison)
|
||||
{"inspect", "Inspect", utils.MustHex("#F44336"), "Inspect", modules.HandleInspect}, // Red (analysis)
|
||||
{"player", "Player", utils.MustHex("#3F51B5"), "Playback", modules.HandlePlayer}, // Indigo (playback)
|
||||
{"settings", "Settings", utils.MustHex("#607D8B"), "Settings", nil}, // Blue Grey (settings)
|
||||
}
|
||||
|
||||
// Platform-specific configuration
|
||||
|
|
@ -1616,12 +1617,14 @@ func (s *appState) showMainMenu() {
|
|||
// Convert Module slice to ui.ModuleInfo slice
|
||||
var mods []ui.ModuleInfo
|
||||
for _, m := range modulesList {
|
||||
// Settings module is always enabled
|
||||
enabled := m.ID == "settings" || isModuleAvailable(m.ID)
|
||||
mods = append(mods, ui.ModuleInfo{
|
||||
ID: m.ID,
|
||||
Label: m.Label,
|
||||
Color: m.Color,
|
||||
Category: m.Category,
|
||||
Enabled: m.ID == "convert" || m.ID == "compare" || m.ID == "inspect" || m.ID == "merge" || m.ID == "thumb" || m.ID == "player" || m.ID == "filters" || m.ID == "upscale" || m.ID == "author" || m.ID == "subtitles" || m.ID == "rip", // Enabled modules
|
||||
Enabled: enabled,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -2692,6 +2695,8 @@ func (s *appState) showModule(id string) {
|
|||
s.showRipView()
|
||||
case "subtitles":
|
||||
s.showSubtitlesView()
|
||||
case "settings":
|
||||
s.showSettingsView()
|
||||
case "mainmenu":
|
||||
s.showMainMenu()
|
||||
default:
|
||||
|
|
|
|||
290
settings_module.go
Normal file
290
settings_module.go
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/ui"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
||||
)
|
||||
|
||||
// Dependency represents a system dependency
|
||||
type Dependency struct {
|
||||
Name string
|
||||
Command string // Command to check if installed
|
||||
Required bool // If true, core functionality requires this
|
||||
Description string
|
||||
InstallCmd string // Command to install (platform-specific)
|
||||
}
|
||||
|
||||
// ModuleDependencies maps module IDs to their required dependencies
|
||||
var moduleDependencies = map[string][]string{
|
||||
"convert": {"ffmpeg"},
|
||||
"merge": {"ffmpeg"},
|
||||
"trim": {"ffmpeg"},
|
||||
"filters": {"ffmpeg"},
|
||||
"upscale": {"ffmpeg", "realesrgan-ncnn-vulkan"},
|
||||
"audio": {"ffmpeg"},
|
||||
"author": {"ffmpeg", "dvdauthor", "xorriso"},
|
||||
"rip": {"ffmpeg", "xorriso"},
|
||||
"bluray": {"ffmpeg"},
|
||||
"subtitles": {"ffmpeg", "whisper"},
|
||||
"thumb": {"ffmpeg"},
|
||||
"compare": {"ffmpeg"},
|
||||
"inspect": {"ffmpeg"},
|
||||
"player": {"ffmpeg"},
|
||||
}
|
||||
|
||||
// AllDependencies defines all possible dependencies
|
||||
var allDependencies = map[string]Dependency{
|
||||
"ffmpeg": {
|
||||
Name: "FFmpeg",
|
||||
Command: "ffmpeg",
|
||||
Required: true,
|
||||
Description: "Core video processing engine",
|
||||
InstallCmd: getFFmpegInstallCmd(),
|
||||
},
|
||||
"dvdauthor": {
|
||||
Name: "DVDAuthor",
|
||||
Command: "dvdauthor",
|
||||
Required: false,
|
||||
Description: "DVD authoring tool",
|
||||
InstallCmd: getDVDAuthorInstallCmd(),
|
||||
},
|
||||
"xorriso": {
|
||||
Name: "xorriso",
|
||||
Command: "xorriso",
|
||||
Required: false,
|
||||
Description: "ISO creation and extraction",
|
||||
InstallCmd: getXorrisoInstallCmd(),
|
||||
},
|
||||
"realesrgan-ncnn-vulkan": {
|
||||
Name: "Real-ESRGAN",
|
||||
Command: "realesrgan-ncnn-vulkan",
|
||||
Required: false,
|
||||
Description: "AI video upscaling",
|
||||
InstallCmd: "See install.sh --skip-ai=false",
|
||||
},
|
||||
"whisper": {
|
||||
Name: "Whisper",
|
||||
Command: "whisper",
|
||||
Required: false,
|
||||
Description: "AI subtitle generation",
|
||||
InstallCmd: "pip3 install --user openai-whisper",
|
||||
},
|
||||
}
|
||||
|
||||
func getFFmpegInstallCmd() string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return "sudo apt-get install ffmpeg # or dnf/pacman/zypper"
|
||||
case "darwin":
|
||||
return "brew install ffmpeg"
|
||||
case "windows":
|
||||
return "Download from ffmpeg.org"
|
||||
default:
|
||||
return "See ffmpeg.org for installation"
|
||||
}
|
||||
}
|
||||
|
||||
func getDVDAuthorInstallCmd() string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return "sudo apt-get install dvdauthor # or dnf/pacman/zypper"
|
||||
case "darwin":
|
||||
return "brew install dvdauthor"
|
||||
default:
|
||||
return "./scripts/install.sh"
|
||||
}
|
||||
}
|
||||
|
||||
func getXorrisoInstallCmd() string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return "sudo apt-get install xorriso # or dnf/pacman/zypper"
|
||||
case "darwin":
|
||||
return "brew install xorriso"
|
||||
default:
|
||||
return "./scripts/install.sh"
|
||||
}
|
||||
}
|
||||
|
||||
// checkDependency checks if a command is available
|
||||
func checkDependency(command string) bool {
|
||||
_, err := exec.LookPath(command)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// getModuleDependencyStatus checks which dependencies a module is missing
|
||||
func getModuleDependencyStatus(moduleID string) (missing []string, hasAll bool) {
|
||||
deps, ok := moduleDependencies[moduleID]
|
||||
if !ok {
|
||||
return nil, true // Module has no dependencies
|
||||
}
|
||||
|
||||
for _, depName := range deps {
|
||||
dep, exists := allDependencies[depName]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
if !checkDependency(dep.Command) {
|
||||
missing = append(missing, depName)
|
||||
}
|
||||
}
|
||||
|
||||
return missing, len(missing) == 0
|
||||
}
|
||||
|
||||
// isModuleAvailable returns true if all required dependencies are installed
|
||||
func isModuleAvailable(moduleID string) bool {
|
||||
_, hasAll := getModuleDependencyStatus(moduleID)
|
||||
return hasAll
|
||||
}
|
||||
|
||||
func buildSettingsView(state *appState) fyne.CanvasObject {
|
||||
settingsColor := utils.MustHex("#607D8B") // Blue Grey for settings
|
||||
|
||||
backBtn := widget.NewButton("< BACK", func() {
|
||||
state.showMainMenu()
|
||||
})
|
||||
backBtn.Importance = widget.LowImportance
|
||||
|
||||
topBar := ui.TintedBar(settingsColor, container.NewHBox(backBtn, layout.NewSpacer()))
|
||||
bottomBar := moduleFooter(settingsColor, layout.NewSpacer(), state.statsBar)
|
||||
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItem("Dependencies", buildDependenciesTab(state)),
|
||||
container.NewTabItem("Preferences", buildPreferencesTab(state)),
|
||||
)
|
||||
tabs.SetTabLocation(container.TabLocationTop)
|
||||
|
||||
return container.NewBorder(topBar, bottomBar, nil, nil, tabs)
|
||||
}
|
||||
|
||||
func buildDependenciesTab(state *appState) fyne.CanvasObject {
|
||||
content := container.NewVBox()
|
||||
|
||||
// Header
|
||||
header := widget.NewLabel("System Dependencies")
|
||||
header.TextStyle = fyne.TextStyle{Bold: true}
|
||||
content.Add(header)
|
||||
|
||||
desc := widget.NewLabel("Manage VideoTools dependencies. Some modules require specific tools to be installed.")
|
||||
desc.Wrapping = fyne.TextWrapWord
|
||||
content.Add(desc)
|
||||
|
||||
content.Add(widget.NewSeparator())
|
||||
|
||||
// Check all dependencies
|
||||
for depName, dep := range allDependencies {
|
||||
isInstalled := checkDependency(dep.Command)
|
||||
|
||||
nameLabel := widget.NewLabel(dep.Name)
|
||||
nameLabel.TextStyle = fyne.TextStyle{Bold: true}
|
||||
|
||||
statusLabel := widget.NewLabel("")
|
||||
if isInstalled {
|
||||
statusLabel.SetText("✓ Installed")
|
||||
statusLabel.TextStyle = fyne.TextStyle{Italic: true}
|
||||
} else {
|
||||
statusLabel.SetText("✗ Not Installed")
|
||||
statusLabel.TextStyle = fyne.TextStyle{Italic: true}
|
||||
}
|
||||
|
||||
descLabel := widget.NewLabel(dep.Description)
|
||||
descLabel.TextStyle = fyne.TextStyle{Italic: true}
|
||||
|
||||
installLabel := widget.NewLabel(dep.InstallCmd)
|
||||
installLabel.Wrapping = fyne.TextWrapWord
|
||||
|
||||
var statusColor color.Color
|
||||
if isInstalled {
|
||||
statusColor = utils.MustHex("#4CAF50") // Green
|
||||
} else {
|
||||
statusColor = utils.MustHex("#F44336") // Red
|
||||
}
|
||||
|
||||
statusBg := canvas.NewRectangle(statusColor)
|
||||
statusBg.CornerRadius = 3
|
||||
statusBg.SetMinSize(fyne.NewSize(12, 12))
|
||||
|
||||
statusRow := container.NewHBox(statusBg, statusLabel)
|
||||
|
||||
infoBox := container.NewVBox(
|
||||
container.NewHBox(nameLabel, layout.NewSpacer(), statusRow),
|
||||
descLabel,
|
||||
)
|
||||
|
||||
if !isInstalled {
|
||||
infoBox.Add(widget.NewLabel("Install: " + installLabel.Text))
|
||||
}
|
||||
|
||||
// Check which modules need this dependency
|
||||
modulesNeeding := []string{}
|
||||
for modID, deps := range moduleDependencies {
|
||||
for _, d := range deps {
|
||||
if d == depName {
|
||||
// Find module name
|
||||
for _, m := range modulesList {
|
||||
if m.ID == modID {
|
||||
modulesNeeding = append(modulesNeeding, m.Label)
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(modulesNeeding) > 0 {
|
||||
neededLabel := widget.NewLabel("Required by: " + strings.Join(modulesNeeding, ", "))
|
||||
neededLabel.TextStyle = fyne.TextStyle{Italic: true}
|
||||
infoBox.Add(neededLabel)
|
||||
}
|
||||
|
||||
cardBg := canvas.NewRectangle(utils.MustHex("#171C2A"))
|
||||
cardBg.CornerRadius = 6
|
||||
card := container.NewPadded(container.NewMax(cardBg, infoBox))
|
||||
content.Add(card)
|
||||
}
|
||||
|
||||
// Refresh button
|
||||
content.Add(widget.NewSeparator())
|
||||
refreshBtn := widget.NewButton("Refresh Status", func() {
|
||||
state.showSettingsView()
|
||||
})
|
||||
content.Add(refreshBtn)
|
||||
|
||||
return container.NewVScroll(content)
|
||||
}
|
||||
|
||||
func buildPreferencesTab(state *appState) fyne.CanvasObject {
|
||||
content := container.NewVBox()
|
||||
|
||||
header := widget.NewLabel("Application Preferences")
|
||||
header.TextStyle = fyne.TextStyle{Bold: true}
|
||||
content.Add(header)
|
||||
|
||||
content.Add(widget.NewLabel("Preferences panel - Coming soon"))
|
||||
content.Add(widget.NewLabel("This will include settings for:"))
|
||||
content.Add(widget.NewLabel("• Default output directories"))
|
||||
content.Add(widget.NewLabel("• Default encoding presets"))
|
||||
content.Add(widget.NewLabel("• UI theme preferences"))
|
||||
content.Add(widget.NewLabel("• Automatic updates"))
|
||||
|
||||
return container.NewVScroll(content)
|
||||
}
|
||||
|
||||
func (s *appState) showSettingsView() {
|
||||
s.stopPreview()
|
||||
s.lastModule = s.active
|
||||
s.active = "settings"
|
||||
s.setContent(buildSettingsView(s))
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user