Major refactoring to improve code organization and enhance UI: Architecture: - Split monolithic main.go into modular internal/ package structure - Created internal/logging for centralized logging system - Created internal/modules for module handler functions - Created internal/ui for UI components and layouts - Created internal/utils for shared utility functions UI Enhancements: - Implemented rainbow gradient across 8 module buttons (violet→red) - Increased module button text size to 20 for better readability - Fixed text centering on module tiles - Converted Simple/Advanced mode toggle to tabs to save vertical space - Added vertical scrollbars to prevent UI overflow - Added metadata copy button (📋) to copy all metadata to clipboard Video Processing: - Fixed aspect ratio conversion to default to center-crop behavior - Added 6 aspect handling modes: Auto, Crop, Letterbox, Pillarbox, Blur Fill, Stretch - Fixed blur fill to maintain source resolution with padding (no scaling) - Ensured all FFmpeg filters produce even-numbered dimensions for H.264 Known Issues: - WMV files still produce FFmpeg error 234 during aspect conversions (requires codec-specific handling in future update) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
8.6 KiB
8.6 KiB
Persistent Video Context Design
Overview
Videos loaded in any module remain in memory, allowing users to seamlessly work across multiple modules without reloading. This enables workflows like: load once → convert → generate thumbnails → apply filters → inspect metadata.
User Experience
Video Lifecycle
- Load: User selects a video in any module (Convert, Filter, etc.)
- Persist: Video remains loaded when switching between modules
- Clear: Video is cleared either:
- Manual: User clicks "Clear Video" button
- Auto (optional): After successful task completion when leaving a module
- Replace: Loading a new video replaces the current one
UI Components
Persistent Video Info Bar
Display at top of application when video is loaded:
┌─────────────────────────────────────────────────────────────┐
│ 📹 example.mp4 | 1920×1080 | 10:23 | H.264 | [Clear] [↻] │
└─────────────────────────────────────────────────────────────┘
Shows:
- Filename (clickable to show full path)
- Resolution
- Duration
- Codec
- Clear button (unload video)
- Reload button (refresh metadata)
Module Video Controls
Each module shows one of two states:
When No Video Loaded:
┌────────────────────────────────┐
│ [Select Video File] │
│ or │
│ [Select from Recent ▼] │
└────────────────────────────────┘
When Video Loaded:
┌────────────────────────────────┐
│ ✓ Using: example.mp4 │
│ [Use Different Video] [Clear] │
└────────────────────────────────┘
Workflow Examples
Multi-Operation Workflow
1. User opens Convert module
2. Loads "vacation.mp4"
3. Converts to H.265 → saves "vacation-h265.mp4"
4. Switches to Thumb module (vacation.mp4 still loaded)
5. Generates thumbnail grid → saves "vacation-grid.png"
6. Switches to Filter module (vacation.mp4 still loaded)
7. Applies color correction → saves "vacation-color.mp4"
8. Manually clicks "Clear" when done
Quick Comparison Workflow
1. Load video in Convert module
2. Test conversion with different settings:
- H.264 CRF 23
- H.265 CRF 28
- VP9 CRF 30
3. Compare outputs in Inspect module
4. Video stays loaded for entire comparison session
Technical Implementation
State Management
Current appState Structure
type appState struct {
source *videoSource // Shared across all modules
convert convertConfig
player *player.Player
// ... other module states
}
The source field is already global to the app state, so it persists across module switches.
Video Source Structure
type videoSource struct {
Path string
DisplayName string
Format string
Width int
Height int
Duration float64
VideoCodec string
AudioCodec string
Bitrate int
FrameRate float64
PreviewFrames []string
// ... other metadata
}
Module Integration
Loading Video in Any Module
func loadVideoInModule(state *appState) {
// Open file dialog
file := openFileDialog()
// Parse video metadata (ffprobe)
source := parseVideoMetadata(file)
// Set in global state
state.source = source
// Refresh UI to show video info bar
state.showVideoInfoBar()
// Update current module with loaded video
state.refreshCurrentModule()
}
Checking for Loaded Video
func buildModuleView(state *appState) fyne.CanvasObject {
if state.source != nil {
// Video already loaded
return buildModuleWithVideo(state, state.source)
} else {
// No video loaded
return buildModuleVideoSelector(state)
}
}
Clearing Video
func (s *appState) clearVideo() {
// Stop any playback
s.stopPlayer()
// Clear source
s.source = nil
// Clean up preview frames
if s.currentFrame != "" {
os.RemoveAll(filepath.Dir(s.currentFrame))
}
// Reset module states (optional)
s.resetModuleDefaults()
// Refresh UI
s.hideVideoInfoBar()
s.refreshCurrentModule()
}
Auto-Clear Options
Add user preference for auto-clear behavior:
type Preferences struct {
AutoClearVideo string // "never", "on_success", "on_module_switch"
}
Options:
never: Only clear when user clicks "Clear" buttonon_success: Clear after successful operation when switching moduleson_module_switch: Always clear when switching modules
Video Info Bar Implementation
func (s *appState) buildVideoInfoBar() fyne.CanvasObject {
if s.source == nil {
return container.NewMax() // Empty container
}
// File info
filename := widget.NewLabel(s.source.DisplayName)
filename.TextStyle = fyne.TextStyle{Bold: true}
// Video specs
specs := fmt.Sprintf("%dx%d | %s | %s",
s.source.Width,
s.source.Height,
formatDuration(s.source.Duration),
s.source.VideoCodec)
specsLabel := widget.NewLabel(specs)
// Clear button
clearBtn := widget.NewButton("Clear", func() {
s.clearVideo()
})
// Reload button (refresh metadata)
reloadBtn := widget.NewButton("↻", func() {
s.reloadVideoMetadata()
})
// Icon
icon := widget.NewIcon(theme.MediaVideoIcon())
return container.NewBorder(nil, nil,
container.NewHBox(icon, filename),
container.NewHBox(reloadBtn, clearBtn),
specsLabel,
)
}
Recent Files Integration
Enhance with recent files list for quick access:
func (s *appState) buildRecentFilesMenu() *fyne.Menu {
items := []*fyne.MenuItem{}
for _, path := range s.getRecentFiles() {
path := path // Capture for closure
items = append(items, fyne.NewMenuItem(
filepath.Base(path),
func() { s.loadVideoFromPath(path) },
))
}
return fyne.NewMenu("Recent Files", items...)
}
Benefits
User Benefits
- Efficiency: Load once, use everywhere
- Workflow: Natural multi-step processing
- Speed: No repeated file selection/parsing
- Context: Video stays "in focus" during work session
Technical Benefits
- Performance: Single metadata parse per video load
- Memory: Shared video info across modules
- Simplicity: Consistent state management
- Flexibility: Easy to add new modules that leverage loaded video
Migration Path
Phase 1: Add Video Info Bar
- Implement persistent video info bar at top of window
- Show when
state.source != nil - Add "Clear" button
Phase 2: Update Module Loading
- Check for
state.sourcein each module's build function - Show "Using: [filename]" when video is already loaded
- Add "Use Different Video" option
Phase 3: Add Preferences
- Add auto-clear settings
- Implement auto-clear logic on module switch
- Add auto-clear on success option
Phase 4: Recent Files
- Implement recent files tracking
- Add recent files dropdown in video selectors
- Persist recent files list
Future Enhancements
Multi-Video Support
For advanced users who want to work with multiple videos:
- Video tabs or dropdown selector
- "Pin" videos to keep multiple in memory
- Quick switch between loaded videos
Batch Processing
Extend to batch operations on loaded video:
- Queue multiple operations
- Execute as single FFmpeg pass when possible
- Show operation queue in video info bar
Workspace/Project Files
Save entire session state:
- Currently loaded video(s)
- Module settings
- Queued operations
- Allow resuming work sessions
Implementation Checklist
- Design and implement video info bar component
- Add
clearVideo()method to appState - Update all module build functions to check for
state.source - Add "Use Different Video" buttons to modules
- Implement auto-clear preferences
- Add recent files tracking and menu
- Update Convert module (already partially implemented)
- Update other modules (Merge, Trim, Filters, etc.)
- Add keyboard shortcuts (Ctrl+W to clear video, etc.)
- Write user documentation
- Add tooltips explaining persistent video behavior