forked from Leak_Technologies/VideoTools
Refactor to modular architecture with rainbow UI (v0.1.0-dev8)
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>
This commit is contained in:
parent
35b04bfe98
commit
18a14c6020
246
DONE.md
Normal file
246
DONE.md
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
# VideoTools - Completed Features
|
||||
|
||||
This file tracks completed features, fixes, and milestones.
|
||||
|
||||
## Version 0.1.0-dev7 (2025-11-23)
|
||||
|
||||
### Features
|
||||
- ✅ Changed default aspect ratio from 16:9 to Source across all instances
|
||||
- Updated initial state default
|
||||
- Updated empty fallback default
|
||||
- Updated reset button behavior
|
||||
- Updated clear video behavior
|
||||
- Updated hint label text
|
||||
|
||||
### Documentation
|
||||
- ✅ Created comprehensive MODULES.md with all planned modules
|
||||
- ✅ Created PERSISTENT_VIDEO_CONTEXT.md design document
|
||||
- ✅ Created VIDEO_PLAYER.md documenting custom player implementation
|
||||
- ✅ Reorganized docs into module-specific folders
|
||||
- ✅ Created detailed Convert module documentation
|
||||
- ✅ Created detailed Inspect module documentation
|
||||
- ✅ Created detailed Rip module documentation
|
||||
- ✅ Created docs/README.md navigation hub
|
||||
- ✅ Created TODO.md and DONE.md tracking files
|
||||
|
||||
## Version 0.1.0-dev6 and Earlier
|
||||
|
||||
### Core Application
|
||||
- ✅ Fyne-based GUI framework
|
||||
- ✅ Multi-module architecture with tile-based main menu
|
||||
- ✅ Application icon and branding
|
||||
- ✅ Debug logging system (VIDEOTOOLS_DEBUG environment variable)
|
||||
- ✅ Cross-module state management
|
||||
- ✅ Window initialization and sizing
|
||||
|
||||
### Convert Module (Partial Implementation)
|
||||
- ✅ Basic video conversion functionality
|
||||
- ✅ Format selection (MP4, MKV, WebM, MOV, AVI)
|
||||
- ✅ Codec selection (H.264, H.265, VP9)
|
||||
- ✅ Quality presets (CRF-based encoding)
|
||||
- ✅ Output aspect ratio selection
|
||||
- Source, 16:9, 4:3, 1:1, 9:16, 21:9
|
||||
- ✅ Aspect ratio handling methods
|
||||
- Auto, Letterbox, Pillarbox, Blur Fill
|
||||
- ✅ Deinterlacing options
|
||||
- Inverse telecine with default smoothing
|
||||
- ✅ Mode toggle (Simple/Advanced)
|
||||
- ✅ Output filename customization
|
||||
- ✅ Default output naming ("-convert" suffix)
|
||||
- ✅ Status indicator during conversion
|
||||
- ✅ Cancelable conversion process
|
||||
- ✅ FFmpeg command construction
|
||||
- ✅ Process management and execution
|
||||
|
||||
### Video Loading & Metadata
|
||||
- ✅ File selection dialog
|
||||
- ✅ FFprobe integration for metadata parsing
|
||||
- ✅ Video source structure with comprehensive metadata
|
||||
- Path, format, resolution, duration
|
||||
- Video/audio codecs
|
||||
- Bitrate, framerate, pixel format
|
||||
- Field order detection
|
||||
- ✅ Preview frame generation (24 frames)
|
||||
- ✅ Temporary directory management for previews
|
||||
|
||||
### Media Player
|
||||
- ✅ Embedded video playback using FFmpeg
|
||||
- ✅ Audio playback with SDL2
|
||||
- ✅ Frame-accurate rendering
|
||||
- ✅ Playback controls (play/pause)
|
||||
- ✅ Volume control
|
||||
- ✅ Seek functionality with progress bar
|
||||
- ✅ Player window sizing based on video aspect ratio
|
||||
- ✅ Frame pump system for smooth playback
|
||||
- ✅ Audio/video synchronization
|
||||
- ✅ Stable seeking and embedded video rendering
|
||||
|
||||
### Metadata Display
|
||||
- ✅ Metadata panel showing key video information
|
||||
- ✅ Resolution display
|
||||
- ✅ Duration formatting
|
||||
- ✅ Codec information
|
||||
- ✅ Aspect ratio display
|
||||
- ✅ Field order indication
|
||||
|
||||
### Inspect Module (Basic)
|
||||
- ✅ Video metadata viewing
|
||||
- ✅ Technical details display
|
||||
- ✅ Comprehensive information in Convert module metadata panel
|
||||
- ✅ Cover art preview capability
|
||||
|
||||
### UI Components
|
||||
- ✅ Main menu with 8 module tiles
|
||||
- Convert, Merge, Trim, Filters, Upscale, Audio, Thumb, Inspect
|
||||
- ✅ Module color coding for visual identification
|
||||
- ✅ Clear video control in metadata panel
|
||||
- ✅ Reset button for Convert settings
|
||||
- ✅ Status label for operation feedback
|
||||
- ✅ Progress indication during operations
|
||||
|
||||
### Git & Version Control
|
||||
- ✅ Git repository initialization
|
||||
- ✅ .gitignore configuration
|
||||
- ✅ Version tagging system (v0.1.0-dev1 through dev7)
|
||||
- ✅ Commit message formatting
|
||||
- ✅ Binary exclusion from repository
|
||||
- ✅ Build cache exclusion
|
||||
|
||||
### Build System
|
||||
- ✅ Go modules setup
|
||||
- ✅ Fyne dependencies integration
|
||||
- ✅ FFmpeg/FFprobe external tool integration
|
||||
- ✅ SDL2 integration for audio
|
||||
- ✅ OpenGL bindings (go-gl) for video rendering
|
||||
- ✅ Cross-platform file path handling
|
||||
|
||||
### Asset Management
|
||||
- ✅ Application icon (VT_Icon.svg)
|
||||
- ✅ Icon export to PNG format
|
||||
- ✅ Icon embedding in application
|
||||
|
||||
### Logging & Debugging
|
||||
- ✅ Category-based logging (SYS, UI, MODULE, etc.)
|
||||
- ✅ Timestamp formatting
|
||||
- ✅ Debug output toggle via environment variable
|
||||
- ✅ Comprehensive debug messages throughout application
|
||||
- ✅ Log file output (videotools.log)
|
||||
|
||||
### Error Handling
|
||||
- ✅ FFmpeg execution error capture
|
||||
- ✅ File selection cancellation handling
|
||||
- ✅ Video parsing error messages
|
||||
- ✅ Process cancellation cleanup
|
||||
|
||||
### Utility Functions
|
||||
- ✅ Duration formatting (seconds to HH:MM:SS)
|
||||
- ✅ Aspect ratio parsing and calculation
|
||||
- ✅ File path manipulation
|
||||
- ✅ Temporary directory creation and cleanup
|
||||
|
||||
## Technical Achievements
|
||||
|
||||
### Architecture
|
||||
- ✅ Clean separation between UI and business logic
|
||||
- ✅ Shared state management across modules
|
||||
- ✅ Modular design allowing easy addition of new modules
|
||||
- ✅ Event-driven UI updates
|
||||
|
||||
### FFmpeg Integration
|
||||
- ✅ Dynamic FFmpeg command building
|
||||
- ✅ Filter chain construction for complex operations
|
||||
- ✅ Stream mapping for video/audio handling
|
||||
- ✅ Process execution with proper cleanup
|
||||
- ✅ Progress parsing from FFmpeg output (basic)
|
||||
|
||||
### Media Playback
|
||||
- ✅ Custom media player implementation
|
||||
- ✅ Frame extraction and display pipeline
|
||||
- ✅ Audio decoding and playback
|
||||
- ✅ Synchronization between audio and video
|
||||
- ✅ Embedded playback within application window
|
||||
- ✅ Checkpoint system for playback position
|
||||
|
||||
### UI/UX
|
||||
- ✅ Responsive layout adapting to content
|
||||
- ✅ Intuitive module selection
|
||||
- ✅ Clear visual feedback during operations
|
||||
- ✅ Logical grouping of related controls
|
||||
- ✅ Helpful hint labels for user guidance
|
||||
|
||||
## Milestones
|
||||
|
||||
- **2025-11-23** - v0.1.0-dev7 released with Source aspect ratio default
|
||||
- **2025-11-22** - Documentation reorganization and expansion
|
||||
- **2025-11-21** - Last successful binary build (GCC compatibility)
|
||||
- **Earlier** - v0.1.0-dev1 through dev6 with progressive feature additions
|
||||
- dev6: Aspect ratio controls and cancelable converts
|
||||
- dev5: Icon and basic UI improvements
|
||||
- dev4: Build cache management
|
||||
- dev3: Media player checkpoint
|
||||
- Earlier: Initial implementation and architecture
|
||||
|
||||
## Development Progress
|
||||
|
||||
### Lines of Code (Estimated)
|
||||
- **main.go**: ~2,500 lines (comprehensive Convert module, UI, player)
|
||||
- **Documentation**: ~1,500 lines across multiple files
|
||||
- **Total**: ~4,000+ lines
|
||||
|
||||
### Modules Status
|
||||
- **Convert**: 60% complete (core functionality working, advanced features pending)
|
||||
- **Inspect**: 20% complete (basic metadata display, needs dedicated module)
|
||||
- **Merge**: 0% (planned)
|
||||
- **Trim**: 0% (planned)
|
||||
- **Filters**: 0% (planned)
|
||||
- **Upscale**: 0% (planned)
|
||||
- **Audio**: 0% (planned)
|
||||
- **Thumb**: 0% (planned)
|
||||
- **Rip**: 0% (planned)
|
||||
|
||||
### Documentation Status
|
||||
- **Module Documentation**: 30% complete
|
||||
- ✅ Convert: Complete
|
||||
- ✅ Inspect: Complete
|
||||
- ✅ Rip: Complete
|
||||
- ⏳ Others: Pending
|
||||
- **Design Documents**: 50% complete
|
||||
- ✅ Persistent Video Context
|
||||
- ✅ Module Overview
|
||||
- ⏳ Architecture
|
||||
- ⏳ FFmpeg Integration
|
||||
- **User Guides**: 0% complete
|
||||
|
||||
## Bug Fixes & Improvements
|
||||
|
||||
### Recent Fixes
|
||||
- ✅ Fixed aspect ratio default from 16:9 to Source (dev7)
|
||||
- ✅ Stabilized video seeking and embedded rendering
|
||||
- ✅ Improved player window positioning
|
||||
- ✅ Fixed clear video functionality
|
||||
- ✅ Resolved build caching issues
|
||||
- ✅ Removed binary from git repository
|
||||
|
||||
### Performance Improvements
|
||||
- ✅ Optimized preview frame generation
|
||||
- ✅ Efficient FFmpeg process management
|
||||
- ✅ Proper cleanup of temporary files
|
||||
- ✅ Responsive UI during long operations
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
### Technologies Used
|
||||
- **Fyne** - Cross-platform GUI framework
|
||||
- **FFmpeg/FFprobe** - Video processing and analysis
|
||||
- **SDL2** - Audio playback
|
||||
- **OpenGL (go-gl)** - Video rendering
|
||||
- **Go** - Primary programming language
|
||||
|
||||
### Community Resources
|
||||
- FFmpeg documentation and community
|
||||
- Fyne framework documentation
|
||||
- Go community and standard library
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2025-11-23*
|
||||
300
TODO.md
Normal file
300
TODO.md
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
# VideoTools TODO
|
||||
|
||||
This file tracks upcoming features, improvements, and known issues.
|
||||
|
||||
## Critical Issues
|
||||
|
||||
### Build System
|
||||
- [ ] **Fix GCC 15.2.1 CGO compilation hang** - Current builds hang during OpenGL/CGO compilation
|
||||
- Investigate GCC version compatibility
|
||||
- Test with older GCC versions
|
||||
- Consider Docker build environment
|
||||
- Document workaround for development builds
|
||||
|
||||
## Core Features
|
||||
|
||||
### Persistent Video Context
|
||||
- [ ] Implement video info bar UI component
|
||||
- [ ] Add "Clear Video" button globally accessible
|
||||
- [ ] Update all modules to check for `state.source`
|
||||
- [ ] Add "Use Different Video" option in modules
|
||||
- [ ] Implement auto-clear preferences
|
||||
- [ ] Add recent files tracking and dropdown menu
|
||||
- [ ] Test video persistence across module switches
|
||||
|
||||
### Convert Module Completion
|
||||
- [ ] Add hardware acceleration UI controls (NVENC, QSV, VAAPI)
|
||||
- [ ] Implement two-pass encoding mode
|
||||
- [ ] Add bitrate-based encoding option (not just CRF)
|
||||
- [ ] Implement custom FFmpeg arguments field
|
||||
- [ ] Add preset save/load functionality
|
||||
- [ ] Add batch conversion queue
|
||||
- [ ] Estimated file size calculator
|
||||
- [ ] Preview/comparison mode
|
||||
- [ ] Audio-only output option
|
||||
- [ ] Add more codec options (AV1, VP9)
|
||||
|
||||
### Merge Module (Not Started)
|
||||
- [ ] Design UI layout
|
||||
- [ ] Implement file list/order management
|
||||
- [ ] Add drag-and-drop reordering
|
||||
- [ ] Preview transitions
|
||||
- [ ] Handle mixed formats/resolutions
|
||||
- [ ] Audio normalization across clips
|
||||
- [ ] Transition effects (optional)
|
||||
- [ ] Chapter markers at join points
|
||||
|
||||
### Trim Module (Not Started)
|
||||
- [ ] Design UI with timeline
|
||||
- [ ] Implement frame-accurate seeking
|
||||
- [ ] Visual timeline with preview thumbnails
|
||||
- [ ] Multiple trim ranges selection
|
||||
- [ ] Chapter-based splitting
|
||||
- [ ] Smart copy mode (no re-encode)
|
||||
- [ ] Batch trim operations
|
||||
- [ ] Keyboard shortcuts for marking in/out points
|
||||
|
||||
### Filters Module (Not Started)
|
||||
- [ ] Design filter selection UI
|
||||
- [ ] Implement color correction filters
|
||||
- [ ] Brightness/Contrast
|
||||
- [ ] Saturation/Hue
|
||||
- [ ] Color balance
|
||||
- [ ] Curves/Levels
|
||||
- [ ] Implement enhancement filters
|
||||
- [ ] Sharpen/Blur
|
||||
- [ ] Denoise
|
||||
- [ ] Deband
|
||||
- [ ] Implement creative filters
|
||||
- [ ] Grayscale/Sepia
|
||||
- [ ] Vignette
|
||||
- [ ] Speed adjustment
|
||||
- [ ] Rotation/Flip
|
||||
- [ ] Implement stabilization
|
||||
- [ ] Add real-time preview
|
||||
- [ ] Filter presets
|
||||
- [ ] Custom filter chains
|
||||
|
||||
### Upscale Module (Not Started)
|
||||
- [ ] Design UI for upscaling
|
||||
- [ ] Implement traditional scaling (Lanczos, Bicubic)
|
||||
- [ ] Integrate Waifu2x (if feasible)
|
||||
- [ ] Integrate Real-ESRGAN (if feasible)
|
||||
- [ ] Add resolution presets
|
||||
- [ ] Quality vs. speed slider
|
||||
- [ ] Before/after comparison
|
||||
- [ ] Batch upscaling
|
||||
|
||||
### Audio Module (Not Started)
|
||||
- [ ] Design audio extraction UI
|
||||
- [ ] Implement audio track extraction
|
||||
- [ ] Audio track replacement/addition
|
||||
- [ ] Multi-track management
|
||||
- [ ] Volume normalization
|
||||
- [ ] Audio delay correction
|
||||
- [ ] Format conversion
|
||||
- [ ] Channel mapping
|
||||
- [ ] Audio-only operations
|
||||
|
||||
### Thumb Module (Not Started)
|
||||
- [ ] Design thumbnail generation UI
|
||||
- [ ] Single thumbnail extraction
|
||||
- [ ] Grid/contact sheet generation
|
||||
- [ ] Customizable layouts
|
||||
- [ ] Scene detection
|
||||
- [ ] Animated thumbnails
|
||||
- [ ] Batch processing
|
||||
- [ ] Template system
|
||||
|
||||
### Inspect Module (Partial)
|
||||
- [ ] Enhanced metadata display
|
||||
- [ ] Stream information viewer
|
||||
- [ ] Chapter viewer/editor
|
||||
- [ ] Cover art viewer/extractor
|
||||
- [ ] HDR metadata display
|
||||
- [ ] Export reports (text/JSON)
|
||||
- [ ] MediaInfo integration
|
||||
- [ ] Comparison mode (before/after conversion)
|
||||
|
||||
### Rip Module (Not Started)
|
||||
- [ ] Design disc ripping UI
|
||||
- [ ] DVD drive detection and scanning
|
||||
- [ ] Blu-ray drive support
|
||||
- [ ] ISO file loading
|
||||
- [ ] Title selection interface
|
||||
- [ ] Track management (audio/subtitle)
|
||||
- [ ] libdvdcss integration
|
||||
- [ ] libaacs integration
|
||||
- [ ] Batch ripping
|
||||
- [ ] Metadata lookup integration
|
||||
|
||||
## Additional Modules
|
||||
|
||||
### Subtitle Module (Proposed)
|
||||
- [ ] Requirements analysis
|
||||
- [ ] UI design
|
||||
- [ ] Extract subtitle tracks
|
||||
- [ ] Add/replace subtitles
|
||||
- [ ] Burn subtitles into video
|
||||
- [ ] Format conversion
|
||||
- [ ] Timing adjustment
|
||||
- [ ] Multi-language support
|
||||
|
||||
### Streams Module (Proposed)
|
||||
- [ ] Requirements analysis
|
||||
- [ ] UI design
|
||||
- [ ] Stream viewer/inspector
|
||||
- [ ] Stream selection/removal
|
||||
- [ ] Stream reordering
|
||||
- [ ] Map streams to outputs
|
||||
- [ ] Default flag management
|
||||
|
||||
### GIF Module (Proposed)
|
||||
- [ ] Requirements analysis
|
||||
- [ ] UI design
|
||||
- [ ] Video segment to GIF
|
||||
- [ ] Palette optimization
|
||||
- [ ] Frame rate control
|
||||
- [ ] Loop settings
|
||||
- [ ] Dithering options
|
||||
- [ ] Preview before export
|
||||
|
||||
### Crop Module (Proposed)
|
||||
- [ ] Requirements analysis
|
||||
- [ ] UI design
|
||||
- [ ] Visual crop selector
|
||||
- [ ] Auto-detect black bars
|
||||
- [ ] Aspect ratio presets
|
||||
- [ ] Preview with crop overlay
|
||||
- [ ] Batch crop with presets
|
||||
|
||||
### Screenshots Module (Proposed)
|
||||
- [ ] Requirements analysis
|
||||
- [ ] UI design
|
||||
- [ ] Single frame extraction
|
||||
- [ ] Burst capture
|
||||
- [ ] Scene-based capture
|
||||
- [ ] Format options
|
||||
- [ ] Batch processing
|
||||
|
||||
## UI/UX Improvements
|
||||
|
||||
### General Interface
|
||||
- [ ] Keyboard shortcuts system
|
||||
- [ ] Drag-and-drop file loading
|
||||
- [ ] Dark/light theme toggle
|
||||
- [ ] Custom color schemes
|
||||
- [ ] Window size/position persistence
|
||||
- [ ] Multi-window support
|
||||
- [ ] Responsive layout improvements
|
||||
|
||||
### Media Player
|
||||
- [ ] Enhanced playback controls
|
||||
- [ ] Frame-by-frame navigation
|
||||
- [ ] Playback speed control
|
||||
- [ ] A-B repeat loop
|
||||
- [ ] Snapshot/screenshot button
|
||||
- [ ] Audio waveform display
|
||||
- [ ] Subtitle display during playback
|
||||
|
||||
### Queue/Batch System
|
||||
- [ ] Global job queue
|
||||
- [ ] Priority management
|
||||
- [ ] Pause/resume individual jobs
|
||||
- [ ] Parallel processing option
|
||||
- [ ] Queue persistence
|
||||
- [ ] Estimated completion time
|
||||
- [ ] Job history
|
||||
|
||||
### Settings/Preferences
|
||||
- [ ] Settings dialog
|
||||
- [ ] Default output directory
|
||||
- [ ] FFmpeg path configuration
|
||||
- [ ] Hardware acceleration preferences
|
||||
- [ ] Auto-clear video behavior
|
||||
- [ ] Preview quality settings
|
||||
- [ ] Logging verbosity
|
||||
- [ ] Update checking
|
||||
|
||||
## Performance & Optimization
|
||||
|
||||
- [ ] Optimize preview frame generation
|
||||
- [ ] Cache metadata for recently opened files
|
||||
- [ ] Implement progressive loading for large files
|
||||
- [ ] Add GPU acceleration detection
|
||||
- [ ] Optimize memory usage for long videos
|
||||
- [ ] Background processing improvements
|
||||
- [ ] FFmpeg process management enhancements
|
||||
|
||||
## Testing & Quality
|
||||
|
||||
- [ ] Unit tests for core functions
|
||||
- [ ] Integration tests for FFmpeg commands
|
||||
- [ ] UI automation tests
|
||||
- [ ] Test suite for different video formats
|
||||
- [ ] Regression tests
|
||||
- [ ] Performance benchmarks
|
||||
- [ ] Error handling improvements
|
||||
- [ ] Logging system enhancements
|
||||
|
||||
## Documentation
|
||||
|
||||
### User Documentation
|
||||
- [ ] Complete README.md for all modules
|
||||
- [ ] Getting Started guide
|
||||
- [ ] Installation instructions (Windows, macOS, Linux)
|
||||
- [ ] Keyboard shortcuts reference
|
||||
- [ ] Workflow examples
|
||||
- [ ] FAQ section
|
||||
- [ ] Troubleshooting guide
|
||||
- [ ] Video tutorials (consider for future)
|
||||
|
||||
### Developer Documentation
|
||||
- [ ] Architecture overview
|
||||
- [ ] Code structure documentation
|
||||
- [ ] FFmpeg integration guide
|
||||
- [ ] Contributing guidelines
|
||||
- [ ] Build instructions for all platforms
|
||||
- [ ] Release process documentation
|
||||
- [ ] API documentation (if applicable)
|
||||
|
||||
## Packaging & Distribution
|
||||
|
||||
- [ ] Create installers for Windows (.exe/.msi)
|
||||
- [ ] Create macOS app bundle (.dmg)
|
||||
- [ ] Create Linux packages (.deb, .rpm, AppImage)
|
||||
- [ ] Set up CI/CD pipeline
|
||||
- [ ] Automatic builds for releases
|
||||
- [ ] Code signing (Windows/macOS)
|
||||
- [ ] Update mechanism
|
||||
- [ ] Crash reporting system
|
||||
|
||||
## Future Considerations
|
||||
|
||||
- [ ] Plugin system for extending functionality
|
||||
- [ ] Scripting/automation support
|
||||
- [ ] Command-line interface mode
|
||||
- [ ] Web-based remote control
|
||||
- [ ] Cloud storage integration
|
||||
- [ ] Collaborative features
|
||||
- [ ] AI-powered scene detection
|
||||
- [ ] AI-powered quality enhancement
|
||||
- [ ] Streaming output support
|
||||
- [ ] Live input support (webcam, capture card)
|
||||
|
||||
## Known Issues
|
||||
|
||||
- **Build hangs on GCC 15.2.1** - CGO compilation freezes during OpenGL binding compilation
|
||||
- No Windows/macOS builds tested yet
|
||||
- Limited error messages for FFmpeg failures
|
||||
- No progress indication during metadata parsing
|
||||
- Preview frames not cleaned up on crash
|
||||
|
||||
## Research Needed
|
||||
|
||||
- [ ] Best practices for FFmpeg filter chain optimization
|
||||
- [ ] GPU acceleration capabilities across platforms
|
||||
- [ ] AI upscaling integration options
|
||||
- [ ] Disc copy protection legal landscape
|
||||
- [ ] Cross-platform video codecs support
|
||||
- [ ] HDR/Dolby Vision handling
|
||||
BIN
assets/logo/VT_Icon.png
Normal file
BIN
assets/logo/VT_Icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
186
docs/MODULES.md
Normal file
186
docs/MODULES.md
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
# VideoTools Modules
|
||||
|
||||
This document describes all the modules in VideoTools and their purpose. Each module is designed to handle specific FFmpeg operations with a user-friendly interface.
|
||||
|
||||
## Core Modules
|
||||
|
||||
### Convert
|
||||
Convert is the primary module for video transcoding and format conversion. This handles:
|
||||
- Codec conversion (H.264, H.265/HEVC, VP9, AV1, etc.)
|
||||
- Container format changes (MP4, MKV, WebM, MOV, etc.)
|
||||
- Quality presets (CRF-based and bitrate-based encoding)
|
||||
- Resolution changes and aspect ratio handling (letterbox, pillarbox, crop, stretch)
|
||||
- Deinterlacing and inverse telecine for legacy footage
|
||||
- Hardware acceleration support (NVENC, QSV, VAAPI)
|
||||
- Two-pass encoding for optimal quality/size balance
|
||||
|
||||
**FFmpeg Features:** Video/audio encoding, filtering, format conversion
|
||||
|
||||
### Merge
|
||||
Merge joins multiple video clips into a single output file. Features include:
|
||||
- Concatenate clips with different formats, codecs, or resolutions
|
||||
- Automatic transcoding to unified output format
|
||||
- Re-encoding or stream copying (when formats match)
|
||||
- Maintains or normalizes audio levels across clips
|
||||
- Handles mixed framerates and aspect ratios
|
||||
- Optional transition effects between clips
|
||||
|
||||
**FFmpeg Features:** Concat demuxer/filter, stream mapping
|
||||
|
||||
### Trim
|
||||
Trim provides timeline editing capabilities for cutting and splitting video. Features include:
|
||||
- Precise frame-accurate cutting with timestamp or frame number input
|
||||
- Split single video into multiple segments
|
||||
- Extract specific scenes or time ranges
|
||||
- Chapter-based splitting (soft split without re-encoding)
|
||||
- Batch trim operations for multiple cuts in one pass
|
||||
- Smart copy mode (no re-encode when possible)
|
||||
|
||||
**FFmpeg Features:** Seeking, segment muxer, chapter metadata
|
||||
|
||||
### Filters
|
||||
Filters module provides video and audio processing effects:
|
||||
- **Color Correction:** Brightness, contrast, saturation, hue, color balance
|
||||
- **Image Enhancement:** Sharpen, blur, denoise, deband
|
||||
- **Video Effects:** Grayscale, sepia, vignette, fade in/out
|
||||
- **Audio Effects:** Normalize, equalize, noise reduction, tempo change
|
||||
- **Correction:** Stabilization, deshake, lens distortion
|
||||
- **Creative:** Speed adjustment, reverse playback, rotation/flip
|
||||
- **Overlay:** Watermarks, logos, text, timecode burn-in
|
||||
|
||||
**FFmpeg Features:** Video/audio filter graphs, complex filters
|
||||
|
||||
### Upscale
|
||||
Upscale increases video resolution using advanced scaling algorithms:
|
||||
- **AI-based:** Waifu2x, Real-ESRGAN (via external integration)
|
||||
- **Traditional:** Lanczos, Bicubic, Spline, Super-resolution
|
||||
- **Target resolutions:** 720p, 1080p, 1440p, 4K, custom
|
||||
- Noise reduction and artifact mitigation during upscaling
|
||||
- Batch processing for multiple files
|
||||
- Quality presets balancing speed vs. output quality
|
||||
|
||||
**FFmpeg Features:** Scale filter, super-resolution filters
|
||||
|
||||
### Audio
|
||||
Audio module handles all audio track operations:
|
||||
- Extract audio tracks to separate files (MP3, AAC, FLAC, WAV, OGG)
|
||||
- Replace or add audio tracks to video
|
||||
- Audio format conversion and codec changes
|
||||
- Multi-track management (select, reorder, remove tracks)
|
||||
- Volume normalization and adjustment
|
||||
- Audio delay/sync correction
|
||||
- Stereo/mono/surround channel mapping
|
||||
- Sample rate and bitrate conversion
|
||||
|
||||
**FFmpeg Features:** Audio stream mapping, audio encoding, audio filters
|
||||
|
||||
### Thumb
|
||||
Thumbnail and preview generation module:
|
||||
- Generate single or grid thumbnails from video
|
||||
- Contact sheet creation with customizable layouts
|
||||
- Extract frames at specific timestamps or intervals
|
||||
- Animated thumbnails (short preview clips)
|
||||
- Smart scene detection for representative frames
|
||||
- Batch thumbnail generation
|
||||
- Custom resolution and quality settings
|
||||
|
||||
**FFmpeg Features:** Frame extraction, select filter, tile filter
|
||||
|
||||
### Inspect
|
||||
Comprehensive metadata viewer and editor:
|
||||
- **Technical Details:** Codec, resolution, framerate, bitrate, pixel format
|
||||
- **Stream Information:** All video/audio/subtitle streams with full details
|
||||
- **Container Metadata:** Title, artist, album, year, genre, cover art
|
||||
- **Advanced Info:** Color space, HDR metadata, field order, GOP structure
|
||||
- **Chapter Viewer:** Display and edit chapter markers
|
||||
- **Subtitle Info:** List all subtitle tracks and languages
|
||||
- **MediaInfo Integration:** Extended technical analysis
|
||||
- Edit and update metadata fields
|
||||
|
||||
**FFmpeg Features:** ffprobe, metadata filters
|
||||
|
||||
### Rip (formerly "Remux")
|
||||
Extract and convert content from optical media and disc images:
|
||||
- Rip directly from DVD/Blu-ray drives to video files
|
||||
- Extract from ISO, IMG, and other disc image formats
|
||||
- Title and chapter selection
|
||||
- Preserve or transcode during extraction
|
||||
- Handle copy protection (via libdvdcss/libaacs when available)
|
||||
- Subtitle and audio track selection
|
||||
- Batch ripping of multiple titles
|
||||
- Output to lossless or compressed formats
|
||||
|
||||
**FFmpeg Features:** DVD/Blu-ray input, concat, stream copying
|
||||
|
||||
## Additional Suggested Modules
|
||||
|
||||
### Subtitle
|
||||
Dedicated subtitle handling module:
|
||||
- Extract subtitle tracks (SRT, ASS, SSA, VTT)
|
||||
- Add or replace subtitle files
|
||||
- Burn (hardcode) subtitles into video
|
||||
- Convert between subtitle formats
|
||||
- Adjust subtitle timing/sync
|
||||
- Multi-language subtitle management
|
||||
|
||||
**FFmpeg Features:** Subtitle filters, subtitle codec support
|
||||
|
||||
### Streams
|
||||
Advanced stream management for complex files:
|
||||
- View all streams (video/audio/subtitle/data) in detail
|
||||
- Select which streams to keep or remove
|
||||
- Reorder stream priority/default flags
|
||||
- Map streams to different output files
|
||||
- Handle multiple video angles or audio tracks
|
||||
- Copy or transcode individual streams
|
||||
|
||||
**FFmpeg Features:** Stream mapping, stream selection
|
||||
|
||||
### GIF
|
||||
Create animated GIFs from videos:
|
||||
- Convert video segments to GIF format
|
||||
- Optimize file size with palette generation
|
||||
- Frame rate and resolution control
|
||||
- Loop settings and duration limits
|
||||
- Dithering options for better quality
|
||||
- Preview before final export
|
||||
|
||||
**FFmpeg Features:** Palettegen, paletteuse filters
|
||||
|
||||
### Crop
|
||||
Precise cropping and aspect ratio tools:
|
||||
- Visual crop selection with preview
|
||||
- Auto-detect black bars
|
||||
- Aspect ratio presets
|
||||
- Maintain aspect ratio or free-form crop
|
||||
- Batch crop with saved presets
|
||||
|
||||
**FFmpeg Features:** Crop filter, cropdetect
|
||||
|
||||
### Screenshots
|
||||
Extract still images from video:
|
||||
- Single frame extraction at specific time
|
||||
- Burst capture (multiple frames)
|
||||
- Scene-based capture
|
||||
- Format options (PNG, JPEG, BMP, TIFF)
|
||||
- Resolution and quality control
|
||||
|
||||
**FFmpeg Features:** Frame extraction, image encoding
|
||||
|
||||
## Module Coverage Summary
|
||||
|
||||
This module set covers all major FFmpeg capabilities:
|
||||
- ✅ Transcoding and format conversion
|
||||
- ✅ Concatenation and merging
|
||||
- ✅ Trimming and splitting
|
||||
- ✅ Video/audio filtering and effects
|
||||
- ✅ Scaling and upscaling
|
||||
- ✅ Audio extraction and manipulation
|
||||
- ✅ Thumbnail generation
|
||||
- ✅ Metadata viewing and editing
|
||||
- ✅ Optical media ripping
|
||||
- ✅ Subtitle handling
|
||||
- ✅ Stream management
|
||||
- ✅ GIF creation
|
||||
- ✅ Cropping
|
||||
- ✅ Screenshot capture
|
||||
317
docs/PERSISTENT_VIDEO_CONTEXT.md
Normal file
317
docs/PERSISTENT_VIDEO_CONTEXT.md
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
# 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
|
||||
1. **Load**: User selects a video in any module (Convert, Filter, etc.)
|
||||
2. **Persist**: Video remains loaded when switching between modules
|
||||
3. **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
|
||||
```go
|
||||
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
|
||||
```go
|
||||
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
|
||||
```go
|
||||
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
|
||||
```go
|
||||
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
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
type Preferences struct {
|
||||
AutoClearVideo string // "never", "on_success", "on_module_switch"
|
||||
}
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `never`: Only clear when user clicks "Clear" button
|
||||
- `on_success`: Clear after successful operation when switching modules
|
||||
- `on_module_switch`: Always clear when switching modules
|
||||
|
||||
### Video Info Bar Implementation
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
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.source` in 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
|
||||
42
docs/README.md
Normal file
42
docs/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# VideoTools Documentation
|
||||
|
||||
VideoTools is a comprehensive FFmpeg GUI wrapper that provides user-friendly interfaces for common video processing tasks.
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
### Core Modules (Implemented/Planned)
|
||||
- [Convert](convert/) - Video transcoding and format conversion
|
||||
- [Merge](merge/) - Join multiple video clips
|
||||
- [Trim](trim/) - Cut and split videos
|
||||
- [Filters](filters/) - Video and audio effects
|
||||
- [Upscale](upscale/) - Resolution enhancement
|
||||
- [Audio](audio/) - Audio track operations
|
||||
- [Thumb](thumb/) - Thumbnail generation
|
||||
- [Inspect](inspect/) - Metadata viewing and editing
|
||||
- [Rip](rip/) - DVD/Blu-ray extraction
|
||||
|
||||
### Additional Modules (Proposed)
|
||||
- [Subtitle](subtitle/) - Subtitle management
|
||||
- [Streams](streams/) - Multi-stream handling
|
||||
- [GIF](gif/) - Animated GIF creation
|
||||
- [Crop](crop/) - Video cropping tools
|
||||
- [Screenshots](screenshots/) - Frame extraction
|
||||
|
||||
## Design Documents
|
||||
- [Persistent Video Context](PERSISTENT_VIDEO_CONTEXT.md) - Cross-module video state management
|
||||
- [Module Overview](MODULES.md) - Complete module feature list
|
||||
- [Custom Video Player](VIDEO_PLAYER.md) - Embedded playback implementation
|
||||
|
||||
## Development
|
||||
- [Architecture](architecture/) - Application structure and design patterns *(coming soon)*
|
||||
- [FFmpeg Integration](ffmpeg/) - FFmpeg command building and execution *(coming soon)*
|
||||
- [Contributing](CONTRIBUTING.md) - Contribution guidelines *(coming soon)*
|
||||
|
||||
## User Guides
|
||||
- [Getting Started](getting-started.md) - Installation and first steps *(coming soon)*
|
||||
- [Workflows](workflows/) - Common multi-module workflows *(coming soon)*
|
||||
- [Keyboard Shortcuts](shortcuts.md) - Keyboard shortcuts reference *(coming soon)*
|
||||
|
||||
## Quick Links
|
||||
- [Module Feature Matrix](MODULES.md#module-coverage-summary)
|
||||
- [Persistent Video Context Design](PERSISTENT_VIDEO_CONTEXT.md)
|
||||
665
docs/VIDEO_PLAYER.md
Normal file
665
docs/VIDEO_PLAYER.md
Normal file
|
|
@ -0,0 +1,665 @@
|
|||
# Custom Video Player Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
VideoTools features a custom-built media player for embedded video playback within the application. This was developed as a complex but necessary component to provide frame-accurate preview and playback capabilities integrated directly into the Fyne UI.
|
||||
|
||||
## Why Custom Implementation?
|
||||
|
||||
### Initial Approach: External ffplay
|
||||
The project initially attempted to use `ffplay` (FFmpeg's built-in player) by embedding it in the application window. This approach had several challenges:
|
||||
|
||||
- **Window Management**: Embedding external player windows into Fyne's UI proved difficult
|
||||
- **Control Integration**: Limited programmatic control over ffplay
|
||||
- **Platform Differences**: X11 window embedding behaves differently across platforms
|
||||
- **UI Consistency**: External player doesn't match application theming
|
||||
|
||||
### Final Solution: Custom FFmpeg-Based Player
|
||||
A custom player was built using FFmpeg as a frame/audio source with manual rendering:
|
||||
|
||||
- **Full Control**: Complete programmatic control over playback
|
||||
- **Native Integration**: Renders directly into Fyne canvas
|
||||
- **Consistent UI**: Matches application look and feel
|
||||
- **Frame Accuracy**: Precise seeking and frame-by-frame control
|
||||
|
||||
## Architecture
|
||||
|
||||
### Dual-Stream Design
|
||||
|
||||
The player uses **two separate FFmpeg processes** running simultaneously:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ playSession │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Video Stream │ │ Audio Stream │ │
|
||||
│ │ (FFmpeg) │ │ (FFmpeg) │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │
|
||||
│ │ RGB24 frames │ s16le PCM │
|
||||
│ │ (raw video) │ (raw audio) │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Frame Pump │ │ Audio Player │ │
|
||||
│ │ (goroutine) │ │ (SDL2/oto) │ │
|
||||
│ └──────┬───────┘ └──────────────┘ │
|
||||
│ │ │
|
||||
│ │ Update Fyne canvas.Image │
|
||||
│ ▼ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ UI Display │ │
|
||||
│ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Component Breakdown
|
||||
|
||||
#### 1. Video Stream (`runVideo`)
|
||||
|
||||
**FFmpeg Command:**
|
||||
```bash
|
||||
ffmpeg -hide_banner -loglevel error \
|
||||
-ss <offset> \
|
||||
-i <video_file> \
|
||||
-vf scale=<targetW>:<targetH> \
|
||||
-f rawvideo \
|
||||
-pix_fmt rgb24 \
|
||||
-r <fps> \
|
||||
-
|
||||
```
|
||||
|
||||
**Purpose:** Extract video frames as raw RGB data
|
||||
|
||||
**Process:**
|
||||
1. Starts FFmpeg to decode video
|
||||
2. Scales frames to target display resolution
|
||||
3. Outputs RGB24 pixel data to stdout
|
||||
4. Frames read by goroutine and displayed
|
||||
|
||||
**Frame Pacing:**
|
||||
- Calculates frame duration from source FPS: `frameDuration = 1 / fps`
|
||||
- Sleeps between frames to maintain proper playback speed
|
||||
- Honors pause state by skipping frame updates
|
||||
|
||||
**Frame Pump Loop:**
|
||||
```go
|
||||
frameSize := targetW * targetH * 3 // RGB = 3 bytes per pixel
|
||||
buf := make([]byte, frameSize)
|
||||
|
||||
for {
|
||||
// Read exactly one frame worth of data
|
||||
io.ReadFull(stdout, buf)
|
||||
|
||||
// Respect pause state
|
||||
if paused {
|
||||
continue (wait for unpause)
|
||||
}
|
||||
|
||||
// Pace to source FPS
|
||||
waitUntil(nextFrameTime)
|
||||
|
||||
// Update canvas image
|
||||
updateImage(buf)
|
||||
|
||||
// Schedule next frame
|
||||
nextFrameTime += frameDuration
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Audio Stream (`runAudio`)
|
||||
|
||||
**FFmpeg Command:**
|
||||
```bash
|
||||
ffmpeg -hide_banner -loglevel error \
|
||||
-ss <offset> \
|
||||
-i <video_file> \
|
||||
-vn \ # No video
|
||||
-ac 2 \ # Stereo
|
||||
-ar 48000 \ # 48kHz sample rate
|
||||
-f s16le \ # 16-bit signed little-endian
|
||||
-
|
||||
```
|
||||
|
||||
**Purpose:** Extract audio as raw PCM data
|
||||
|
||||
**Audio Playback:**
|
||||
- Uses SDL2/oto library for cross-platform audio output
|
||||
- Fixed format: 48kHz, stereo (2 channels), 16-bit PCM
|
||||
- Direct pipe from FFmpeg to audio device
|
||||
|
||||
**Volume Control:**
|
||||
- Software gain adjustment before playback
|
||||
- Real-time volume multiplication on PCM samples
|
||||
- Mute by zeroing audio buffer
|
||||
- Volume range: 0-100 (can amplify up to 200% in code)
|
||||
|
||||
**Volume Processing:**
|
||||
```go
|
||||
gain := volume / 100.0
|
||||
|
||||
for each 16-bit sample {
|
||||
sample := readInt16(audioData)
|
||||
amplified := int16(float64(sample) * gain)
|
||||
// Clamp to prevent distortion
|
||||
amplified = clamp(amplified, -32768, 32767)
|
||||
writeInt16(audioData, amplified)
|
||||
}
|
||||
|
||||
audioPlayer.Write(audioData)
|
||||
```
|
||||
|
||||
#### 3. Synchronization
|
||||
|
||||
**Shared State:**
|
||||
- Both streams start from same offset timestamp
|
||||
- `paused` flag affects both video and audio loops
|
||||
- `current` position tracks playback time
|
||||
- No explicit A/V sync mechanism (relies on OS scheduling)
|
||||
|
||||
**Synchronization Strategy:**
|
||||
- Video paced by sleep timing between frames
|
||||
- Audio paced by audio device buffer consumption
|
||||
- Both start from same `-ss` offset
|
||||
- Generally stays synchronized for short clips
|
||||
- May drift on longer playback (known limitation)
|
||||
|
||||
### State Management
|
||||
|
||||
#### playSession Structure
|
||||
|
||||
```go
|
||||
type playSession struct {
|
||||
mu sync.Mutex
|
||||
|
||||
// File info
|
||||
path string
|
||||
fps float64
|
||||
width int // Original dimensions
|
||||
height int
|
||||
targetW int // Display dimensions
|
||||
targetH int
|
||||
|
||||
// Playback state
|
||||
paused bool
|
||||
current float64 // Current position (seconds)
|
||||
frameN int // Frame counter
|
||||
|
||||
// Volume
|
||||
volume float64 // 0-100
|
||||
muted bool
|
||||
|
||||
// FFmpeg processes
|
||||
videoCmd *exec.Cmd
|
||||
audioCmd *exec.Cmd
|
||||
|
||||
// Control channels
|
||||
stop chan struct{}
|
||||
done chan struct{}
|
||||
|
||||
// UI callbacks
|
||||
prog func(float64) // Progress update callback
|
||||
img *canvas.Image // Fyne image to render to
|
||||
}
|
||||
```
|
||||
|
||||
## Implemented Features
|
||||
|
||||
### ✅ Play/Pause
|
||||
- **Play**: Starts or resumes both video and audio streams
|
||||
- **Pause**: Halts frame updates and audio output
|
||||
- Preserves current position when paused
|
||||
- No resource cleanup during pause (streams keep running)
|
||||
|
||||
### ✅ Seek
|
||||
- Jump to any timestamp in the video
|
||||
- **Implementation**: Stop both streams, restart at new position
|
||||
- Preserves pause state across seeks
|
||||
- Updates progress indicator immediately
|
||||
|
||||
**Known Issue:** Seeking restarts FFmpeg processes, causing brief interruption
|
||||
|
||||
### ✅ Volume Control
|
||||
- Range: 0-100 (UI) / 0-200 (code max)
|
||||
- Real-time volume adjustment without restarting audio
|
||||
- Software mixing/gain control
|
||||
- Automatic mute at volume 0
|
||||
- No crackling/popping during adjustment
|
||||
|
||||
### ✅ Embedded Playback
|
||||
- Renders directly into Fyne `canvas.Image`
|
||||
- No external windows
|
||||
- Respects Fyne layout system
|
||||
- Scales to target dimensions
|
||||
|
||||
### ✅ Progress Tracking
|
||||
- Reports current playback position
|
||||
- Callback to update UI slider/display
|
||||
- Accurate to ~frame duration
|
||||
|
||||
### ✅ Resource Management
|
||||
- Properly kills FFmpeg processes on stop
|
||||
- Cleans up goroutines
|
||||
- No zombie processes
|
||||
- Handles early termination gracefully
|
||||
|
||||
## Current Limitations
|
||||
|
||||
### ❌ No Fullscreen Support
|
||||
- Controller interface includes `FullScreen()` method
|
||||
- Currently returns "player unavailable" error
|
||||
- Would require:
|
||||
- Dedicated fullscreen window
|
||||
- Escaping fullscreen (ESC key handling)
|
||||
- Preserving playback state during transition
|
||||
- Overlay controls in fullscreen mode
|
||||
|
||||
**Future Implementation:**
|
||||
```go
|
||||
func (s *appState) enterFullscreen() {
|
||||
// Create new fullscreen window
|
||||
fsWindow := fyne.CurrentApp().NewWindow("Playback")
|
||||
fsWindow.SetFullScreen(true)
|
||||
|
||||
// Transfer playback to fullscreen canvas
|
||||
// Preserve playback position
|
||||
// Add overlay controls
|
||||
}
|
||||
```
|
||||
|
||||
### Limited Audio Format
|
||||
- Fixed at 48kHz, stereo, 16-bit
|
||||
- Doesn't adapt to source format
|
||||
- Mono sources upconverted to stereo
|
||||
- Other sample rates resampled
|
||||
|
||||
**Why:** Simplifies audio playback code, 48kHz/stereo is standard
|
||||
|
||||
### A/V Sync Drift
|
||||
- No PTS (Presentation Timestamp) tracking
|
||||
- Relies on OS thread scheduling
|
||||
- May drift on long playback (>5 minutes)
|
||||
- Seek resynchronizes
|
||||
|
||||
**Mitigation:** Primarily used for short previews, not long playback
|
||||
|
||||
### Seeking Performance
|
||||
- Restarts FFmpeg processes
|
||||
- Brief audio/video gap during seek
|
||||
- Not instantaneous like native players
|
||||
- ~100-500ms interruption
|
||||
|
||||
**Why:** Simpler than maintaining seekable streams
|
||||
|
||||
### No Speed Control
|
||||
- Playback speed fixed at 1.0×
|
||||
- No fast-forward/rewind
|
||||
- No slow-motion
|
||||
|
||||
**Future:** Could adjust frame pacing and audio playback rate
|
||||
|
||||
### No Subtitle Support
|
||||
- Video-only rendering
|
||||
- Subtitles not displayed during playback
|
||||
- Would require subtitle stream parsing and rendering
|
||||
|
||||
## Implementation Challenges Overcome
|
||||
|
||||
### 1. Frame Pacing
|
||||
**Challenge:** How fast to pump frames to avoid flicker or lag?
|
||||
|
||||
**Solution:** Calculate exact frame duration from FPS:
|
||||
```go
|
||||
frameDuration := time.Duration(float64(time.Second) / fps)
|
||||
nextFrameAt := time.Now()
|
||||
|
||||
for {
|
||||
// Process frame...
|
||||
|
||||
// Wait until next frame time
|
||||
nextFrameAt = nextFrameAt.Add(frameDuration)
|
||||
sleepUntil(nextFrameAt)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Image Updates in Fyne
|
||||
**Challenge:** Fyne's `canvas.Image` needs proper refresh
|
||||
|
||||
**Solution:**
|
||||
```go
|
||||
img.Resource = canvas.NewImageFromImage(frameImage)
|
||||
img.Refresh() // Trigger redraw
|
||||
```
|
||||
|
||||
### 3. Pause State Handling
|
||||
**Challenge:** Pause without destroying streams (avoid restart delay)
|
||||
|
||||
**Solution:** Keep streams running but:
|
||||
- Skip frame updates in video loop
|
||||
- Skip audio writes in audio loop
|
||||
- Resume instantly by unsetting pause flag
|
||||
|
||||
### 4. Volume Adjustment
|
||||
**Challenge:** Adjust volume without restarting audio stream
|
||||
|
||||
**Solution:** Apply gain to PCM samples in real-time:
|
||||
```go
|
||||
if !muted {
|
||||
sample *= (volume / 100.0)
|
||||
clamp(sample)
|
||||
}
|
||||
write(audioBuffer, sample)
|
||||
```
|
||||
|
||||
### 5. Clean Shutdown
|
||||
**Challenge:** Stop playback without leaving orphaned FFmpeg processes
|
||||
|
||||
**Solution:**
|
||||
```go
|
||||
func stopLocked() {
|
||||
close(stopChannel) // Signal goroutines to exit
|
||||
|
||||
if videoCmd != nil {
|
||||
videoCmd.Process.Kill()
|
||||
videoCmd.Wait() // Clean up zombie
|
||||
}
|
||||
|
||||
if audioCmd != nil {
|
||||
audioCmd.Process.Kill()
|
||||
audioCmd.Wait()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Seeking While Paused
|
||||
**Challenge:** Seek should work whether playing or paused
|
||||
|
||||
**Solution:**
|
||||
```go
|
||||
func Seek(offset float64) {
|
||||
wasPaused := paused
|
||||
|
||||
stopStreams()
|
||||
startStreams(offset)
|
||||
|
||||
if wasPaused {
|
||||
// Ensure pause state restored after restart
|
||||
time.AfterFunc(30*time.Millisecond, func() {
|
||||
paused = true
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Video Frame Processing
|
||||
|
||||
**Frame Size Calculation:**
|
||||
```
|
||||
frameSize = width × height × 3 bytes (RGB24)
|
||||
Example: 640×360 = 691,200 bytes per frame
|
||||
```
|
||||
|
||||
**Reading Frames:**
|
||||
```go
|
||||
buf := make([]byte, targetW * targetH * 3)
|
||||
|
||||
for {
|
||||
// Read exactly one frame
|
||||
n, err := io.ReadFull(stdout, buf)
|
||||
|
||||
if n == frameSize {
|
||||
// Convert to image.RGBA
|
||||
img := image.NewRGBA(image.Rect(0, 0, targetW, targetH))
|
||||
|
||||
// Copy RGB24 → RGBA
|
||||
for i := 0; i < targetW * targetH; i++ {
|
||||
img.Pix[i*4+0] = buf[i*3+0] // R
|
||||
img.Pix[i*4+1] = buf[i*3+1] // G
|
||||
img.Pix[i*4+2] = buf[i*3+2] // B
|
||||
img.Pix[i*4+3] = 255 // A (opaque)
|
||||
}
|
||||
|
||||
updateCanvas(img)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Audio Processing
|
||||
|
||||
**Audio Format:**
|
||||
- **Sample Rate**: 48,000 Hz
|
||||
- **Channels**: 2 (stereo)
|
||||
- **Bit Depth**: 16-bit signed integer
|
||||
- **Byte Order**: Little-endian
|
||||
- **Format**: s16le (signed 16-bit little-endian)
|
||||
|
||||
**Buffer Size:**
|
||||
- 4096 bytes (2048 samples, 1024 per channel)
|
||||
- ~21ms of audio at 48kHz stereo
|
||||
|
||||
**Volume Control Math:**
|
||||
```go
|
||||
// Read 16-bit sample (2 bytes)
|
||||
sample := int16(binary.LittleEndian.Uint16(audioData[i:i+2]))
|
||||
|
||||
// Apply gain
|
||||
amplified := int(float64(sample) * gain)
|
||||
|
||||
// Clamp to prevent overflow/distortion
|
||||
if amplified > 32767 {
|
||||
amplified = 32767
|
||||
} else if amplified < -32768 {
|
||||
amplified = -32768
|
||||
}
|
||||
|
||||
// Write back
|
||||
binary.LittleEndian.PutUint16(audioData[i:i+2], uint16(int16(amplified)))
|
||||
```
|
||||
|
||||
### Performance Characteristics
|
||||
|
||||
**CPU Usage:**
|
||||
- **Video Decoding**: ~5-15% per core (depends on codec)
|
||||
- **Audio Decoding**: ~1-2% per core
|
||||
- **Frame Rendering**: ~2-5% (image conversion + Fyne refresh)
|
||||
- **Total**: ~10-25% CPU for 720p H.264 playback
|
||||
|
||||
**Memory Usage:**
|
||||
- **Frame Buffers**: ~2-3 MB (multiple frames buffered)
|
||||
- **Audio Buffers**: ~100 KB
|
||||
- **FFmpeg Processes**: ~50-100 MB each
|
||||
- **Total**: ~150-250 MB during playback
|
||||
|
||||
**Startup Time:**
|
||||
- FFmpeg process spawn: ~50-100ms
|
||||
- First frame decode: ~100-300ms
|
||||
- Total time to first frame: ~150-400ms
|
||||
|
||||
## Integration with VideoTools
|
||||
|
||||
### Usage in Convert Module
|
||||
|
||||
The player is embedded in the metadata panel:
|
||||
|
||||
```go
|
||||
// Create player surface
|
||||
playerImg := canvas.NewImageFromImage(image.NewRGBA(...))
|
||||
playerSurface := container.NewStack(playerImg)
|
||||
|
||||
// Create play session
|
||||
session := newPlaySession(
|
||||
videoPath,
|
||||
sourceWidth, sourceHeight,
|
||||
fps,
|
||||
displayWidth, displayHeight,
|
||||
progressCallback,
|
||||
playerImg,
|
||||
)
|
||||
|
||||
// Playback controls
|
||||
playBtn := widget.NewButton("Play", func() {
|
||||
session.Play()
|
||||
})
|
||||
|
||||
pauseBtn := widget.NewButton("Pause", func() {
|
||||
session.Pause()
|
||||
})
|
||||
|
||||
seekSlider := widget.NewSlider(0, duration)
|
||||
seekSlider.OnChanged = func(val float64) {
|
||||
session.Seek(val)
|
||||
}
|
||||
```
|
||||
|
||||
### Player Window Sizing
|
||||
|
||||
Aspect ratio preserved based on source video:
|
||||
|
||||
```go
|
||||
targetW := 508 // Fixed width for UI layout
|
||||
targetH := int(float64(targetW) * (float64(sourceH) / float64(sourceW)))
|
||||
|
||||
// E.g., 1920×1080 → 508×286
|
||||
// E.g., 1280×720 → 508×286
|
||||
// E.g., 720×480 → 508×339
|
||||
```
|
||||
|
||||
## Alternative Player (ffplay-based)
|
||||
|
||||
The `internal/player` package contains a platform-specific `ffplay` wrapper:
|
||||
|
||||
### Controller Interface
|
||||
|
||||
```go
|
||||
type Controller interface {
|
||||
Load(path string, offset float64) error
|
||||
SetWindow(x, y, w, h int)
|
||||
Play() error
|
||||
Pause() error
|
||||
Seek(offset float64) error
|
||||
SetVolume(level float64) error
|
||||
FullScreen() error
|
||||
Stop() error
|
||||
Close()
|
||||
}
|
||||
```
|
||||
|
||||
### Implementations
|
||||
|
||||
- **Stub** (`controller_stub.go`): Returns errors for all operations
|
||||
- **Linux** (`controller_linux.go`): Uses X11 window embedding (partially implemented)
|
||||
- **Windows/macOS**: Not implemented
|
||||
|
||||
**Status:** This approach was largely abandoned in favor of the custom `playSession` implementation due to window embedding complexity.
|
||||
|
||||
## Future Improvements
|
||||
|
||||
### High Priority
|
||||
1. **Fullscreen Mode**
|
||||
- Dedicated fullscreen window
|
||||
- Overlay controls with auto-hide
|
||||
- ESC key to exit
|
||||
- Maintain playback position
|
||||
|
||||
2. **Better A/V Sync**
|
||||
- PTS (Presentation Timestamp) tracking
|
||||
- Adjust frame pacing based on audio clock
|
||||
- Detect and correct drift
|
||||
|
||||
3. **Smoother Seeking**
|
||||
- Keep streams alive during seek (use -ss on open pipe)
|
||||
- Reduce interruption time
|
||||
- Consider keyframe-aware seeking
|
||||
|
||||
### Medium Priority
|
||||
4. **Speed Control**
|
||||
- Playback speed adjustment (0.5×, 1.5×, 2×)
|
||||
- Maintain pitch for audio (atempo filter)
|
||||
|
||||
5. **Subtitle Support**
|
||||
- Parse subtitle streams
|
||||
- Render text overlays
|
||||
- Subtitle track selection
|
||||
|
||||
6. **Format Adaptation**
|
||||
- Auto-detect audio channels/sample rate
|
||||
- Adapt audio pipeline to source format
|
||||
- Reduce resampling overhead
|
||||
|
||||
### Low Priority
|
||||
7. **Performance Optimization**
|
||||
- GPU-accelerated decoding (hwaccel)
|
||||
- Frame buffer pooling
|
||||
- Reduce memory allocations
|
||||
|
||||
8. **Enhanced Controls**
|
||||
- Frame-by-frame stepping (← → keys)
|
||||
- Skip forward/backward (10s, 30s jumps)
|
||||
- A-B repeat loop
|
||||
- Playback markers
|
||||
|
||||
## See Also
|
||||
|
||||
- [Convert Module](convert/) - Uses player for video preview
|
||||
- [FFmpeg Integration](ffmpeg/) - FFmpeg command building *(coming soon)*
|
||||
- [Architecture](architecture/) - Overall application structure *(coming soon)*
|
||||
|
||||
## Developer Notes
|
||||
|
||||
### Testing the Player
|
||||
|
||||
```go
|
||||
// Minimal test setup
|
||||
session := newPlaySession(
|
||||
"test.mp4",
|
||||
1920, 1080, // Source dimensions
|
||||
29.97, // FPS
|
||||
640, 360, // Target dimensions
|
||||
func(pos float64) {
|
||||
fmt.Printf("Position: %.2fs\n", pos)
|
||||
},
|
||||
canvasImage,
|
||||
)
|
||||
|
||||
session.Play()
|
||||
time.Sleep(5 * time.Second)
|
||||
session.Pause()
|
||||
session.Seek(30.0)
|
||||
session.Play()
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
Enable FFmpeg logging:
|
||||
```go
|
||||
debugLog(logCatFFMPEG, "message")
|
||||
```
|
||||
|
||||
Set environment variable:
|
||||
```bash
|
||||
VIDEOTOOLS_DEBUG=1 ./VideoTools
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Black screen:** FFmpeg failed to start or decode
|
||||
- Check stderr output
|
||||
- Verify file path is valid
|
||||
- Test FFmpeg command manually
|
||||
|
||||
**No audio:** SDL2/oto initialization failed
|
||||
- Check audio device availability
|
||||
- Verify SDL2 libraries installed
|
||||
- Test with different sample rate
|
||||
|
||||
**Choppy playback:** FPS mismatch or CPU overload
|
||||
- Check calculated frameDuration
|
||||
- Verify FPS detection
|
||||
- Monitor CPU usage
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2025-11-23*
|
||||
256
docs/convert/README.md
Normal file
256
docs/convert/README.md
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
# Convert Module
|
||||
|
||||
The Convert module is the primary tool for video transcoding and format conversion in VideoTools.
|
||||
|
||||
## Overview
|
||||
|
||||
Convert handles all aspects of changing video codec, container format, quality, resolution, and aspect ratio. It's designed to be the most frequently used module for everyday video conversion tasks.
|
||||
|
||||
## Features
|
||||
|
||||
### Codec Support
|
||||
- **H.264 (AVC)** - Universal compatibility, excellent quality/size balance
|
||||
- **H.265 (HEVC)** - Better compression than H.264, smaller files
|
||||
- **VP9** - Open-source, efficient for web delivery
|
||||
- **AV1** - Next-gen codec, best compression (slower encoding)
|
||||
- **Legacy codecs** - MPEG-4, MPEG-2, etc.
|
||||
|
||||
### Container Formats
|
||||
- **MP4** - Universal playback support
|
||||
- **MKV** - Feature-rich, supports multiple tracks
|
||||
- **WebM** - Web-optimized format
|
||||
- **MOV** - Apple/professional workflows
|
||||
- **AVI** - Legacy format support
|
||||
|
||||
### Quality Presets
|
||||
|
||||
#### CRF (Constant Rate Factor)
|
||||
Quality-based encoding for predictable visual results:
|
||||
- **High Quality** - CRF 18 (near-lossless, large files)
|
||||
- **Standard** - CRF 23 (recommended default)
|
||||
- **Efficient** - CRF 28 (good quality, smaller files)
|
||||
- **Compressed** - CRF 32 (streaming/preview)
|
||||
- **Custom** - User-defined CRF value
|
||||
|
||||
#### Bitrate-Based
|
||||
For specific file size targets:
|
||||
- **High** - 8-12 Mbps (1080p) / 20-30 Mbps (4K)
|
||||
- **Medium** - 4-6 Mbps (1080p) / 10-15 Mbps (4K)
|
||||
- **Low** - 2-3 Mbps (1080p) / 5-8 Mbps (4K)
|
||||
- **Custom** - User-defined bitrate
|
||||
|
||||
### Resolution & Aspect Ratio
|
||||
|
||||
#### Resolution Presets
|
||||
- **Source** - Keep original resolution
|
||||
- **4K** - 3840×2160
|
||||
- **1440p** - 2560×1440
|
||||
- **1080p** - 1920×1080
|
||||
- **720p** - 1280×720
|
||||
- **480p** - 854×480
|
||||
- **Custom** - User-defined dimensions
|
||||
|
||||
#### Aspect Ratio Handling
|
||||
- **Source** - Preserve original aspect ratio (default as of v0.1.0-dev7)
|
||||
- **16:9** - Standard widescreen
|
||||
- **4:3** - Classic TV/monitor ratio
|
||||
- **1:1** - Square (social media)
|
||||
- **9:16** - Vertical/mobile video
|
||||
- **21:9** - Ultra-widescreen
|
||||
- **Custom** - User-defined ratio
|
||||
|
||||
#### Aspect Ratio Methods
|
||||
- **Auto** - Smart handling based on source/target
|
||||
- **Letterbox** - Add black bars top/bottom
|
||||
- **Pillarbox** - Add black bars left/right
|
||||
- **Blur Fill** - Blur background instead of black bars
|
||||
- **Crop** - Cut edges to fill frame
|
||||
- **Stretch** - Distort to fill (not recommended)
|
||||
|
||||
### Deinterlacing
|
||||
|
||||
#### Inverse Telecine
|
||||
For content converted from film (24fps → 30fps):
|
||||
- Automatically detects 3:2 pulldown
|
||||
- Recovers original progressive frames
|
||||
- Default: Enabled with smooth blending
|
||||
|
||||
#### Deinterlace Modes
|
||||
- **Auto** - Detect and deinterlace if needed
|
||||
- **Yadif** - High-quality deinterlacer
|
||||
- **Bwdif** - Motion-adaptive deinterlacing
|
||||
- **W3fdif** - Weston 3-field deinterlacing
|
||||
- **Off** - No deinterlacing
|
||||
|
||||
### Hardware Acceleration
|
||||
|
||||
When available, use GPU encoding for faster processing:
|
||||
- **NVENC** - NVIDIA GPUs (RTX, GTX, Quadro)
|
||||
- **QSV** - Intel Quick Sync Video
|
||||
- **VAAPI** - Intel/AMD (Linux)
|
||||
- **VideoToolbox** - Apple Silicon/Intel Macs
|
||||
- **AMF** - AMD GPUs
|
||||
|
||||
### Advanced Options
|
||||
|
||||
#### Encoding Modes
|
||||
- **Simple** - One-pass encoding (fast)
|
||||
- **Two-Pass** - Optimal quality for target bitrate (slower)
|
||||
|
||||
#### Audio Options
|
||||
- Codec selection (AAC, MP3, Opus, Vorbis, FLAC)
|
||||
- Bitrate control
|
||||
- Sample rate conversion
|
||||
- Channel mapping (stereo, mono, 5.1, etc.)
|
||||
|
||||
#### Metadata
|
||||
- Copy or strip metadata
|
||||
- Add custom title, artist, album, etc.
|
||||
- Embed cover art
|
||||
|
||||
## Usage Guide
|
||||
|
||||
### Basic Conversion
|
||||
|
||||
1. **Load Video**
|
||||
- Click "Select Video" or use already loaded video
|
||||
- Preview appears with metadata
|
||||
|
||||
2. **Choose Format**
|
||||
- Select output container (MP4, MKV, etc.)
|
||||
- Auto-selects compatible codec
|
||||
|
||||
3. **Set Quality**
|
||||
- Choose preset or custom CRF/bitrate
|
||||
- Preview estimated file size
|
||||
|
||||
4. **Configure Output**
|
||||
- Set output filename/location
|
||||
- Choose aspect ratio and resolution
|
||||
|
||||
5. **Convert**
|
||||
- Click "Convert" button
|
||||
- Monitor progress bar
|
||||
- Cancel anytime if needed
|
||||
|
||||
### Common Workflows
|
||||
|
||||
#### Modern Efficient Encoding
|
||||
```
|
||||
Format: MP4
|
||||
Codec: H.265
|
||||
Quality: CRF 26
|
||||
Resolution: Source
|
||||
Aspect: Source
|
||||
```
|
||||
Result: Smaller file, good quality
|
||||
|
||||
#### Universal Compatibility
|
||||
```
|
||||
Format: MP4
|
||||
Codec: H.264
|
||||
Quality: CRF 23
|
||||
Resolution: 1080p
|
||||
Aspect: 16:9
|
||||
```
|
||||
Result: Plays anywhere
|
||||
|
||||
#### Web/Streaming Optimized
|
||||
```
|
||||
Format: WebM
|
||||
Codec: VP9
|
||||
Quality: Two-pass 4Mbps
|
||||
Resolution: 1080p
|
||||
Aspect: Source
|
||||
```
|
||||
Result: Efficient web delivery
|
||||
|
||||
#### DVD/Older Content
|
||||
```
|
||||
Format: MP4
|
||||
Codec: H.264
|
||||
Quality: CRF 20
|
||||
Deinterlace: Yadif
|
||||
Inverse Telecine: On
|
||||
```
|
||||
Result: Clean progressive video
|
||||
|
||||
## FFmpeg Integration
|
||||
|
||||
### Command Building
|
||||
|
||||
The Convert module builds FFmpeg commands based on user selections:
|
||||
|
||||
```bash
|
||||
# Basic conversion
|
||||
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac output.mp4
|
||||
|
||||
# With aspect ratio handling (letterbox)
|
||||
ffmpeg -i input.mp4 -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" -c:v libx264 -crf 23 output.mp4
|
||||
|
||||
# With deinterlacing
|
||||
ffmpeg -i input.mp4 -vf "yadif=1,bwdif" -c:v libx264 -crf 23 output.mp4
|
||||
|
||||
# Two-pass encoding
|
||||
ffmpeg -i input.mp4 -c:v libx264 -b:v 4M -pass 1 -f null /dev/null
|
||||
ffmpeg -i input.mp4 -c:v libx264 -b:v 4M -pass 2 output.mp4
|
||||
```
|
||||
|
||||
### Filter Chain Construction
|
||||
|
||||
Multiple filters are chained automatically:
|
||||
```bash
|
||||
-vf "yadif,scale=1920:1080,unsharp=5:5:1.0:5:5:0.0"
|
||||
↑ ↑ ↑
|
||||
deinterlace resize sharpen
|
||||
```
|
||||
|
||||
## Tips & Best Practices
|
||||
|
||||
### Quality vs. File Size
|
||||
- Start with CRF 23, adjust if needed
|
||||
- Higher CRF = smaller file, lower quality
|
||||
- H.265 ~30% smaller than H.264 at same quality
|
||||
- AV1 ~40% smaller but much slower to encode
|
||||
|
||||
### Hardware Acceleration
|
||||
- NVENC is 5-10× faster but slightly larger files
|
||||
- Use for quick previews or when speed matters
|
||||
- CPU encoding gives better quality/size ratio
|
||||
|
||||
### Aspect Ratio
|
||||
- Use "Source" to preserve original (default)
|
||||
- Use "Auto" for smart handling when changing resolution
|
||||
- Avoid "Stretch" - distorts video badly
|
||||
|
||||
### Deinterlacing
|
||||
- Only use if source is interlaced (1080i, 720i, DVD)
|
||||
- Progressive sources (1080p, web videos) don't need it
|
||||
- Inverse telecine recovers film sources
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Conversion Failed
|
||||
- Check FFmpeg output for errors
|
||||
- Verify source file isn't corrupted
|
||||
- Try different codec/format combination
|
||||
|
||||
### Quality Issues
|
||||
- Increase quality setting (lower CRF)
|
||||
- Check source quality - can't improve bad source
|
||||
- Try two-pass encoding for better results
|
||||
|
||||
### Slow Encoding
|
||||
- Enable hardware acceleration if available
|
||||
- Lower resolution or use faster preset
|
||||
- H.265/AV1 are slower than H.264
|
||||
|
||||
### Audio Out of Sync
|
||||
- Check if source has variable frame rate
|
||||
- Use audio delay correction if needed
|
||||
- Try re-encoding audio track
|
||||
|
||||
## See Also
|
||||
- [Filters Module](../filters/) - Apply effects before converting
|
||||
- [Inspect Module](../inspect/) - View detailed source information
|
||||
- [Persistent Video Context](../PERSISTENT_VIDEO_CONTEXT.md) - Using video across modules
|
||||
247
docs/inspect/README.md
Normal file
247
docs/inspect/README.md
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
# Inspect Module
|
||||
|
||||
The Inspect module provides comprehensive metadata viewing and technical analysis of video files.
|
||||
|
||||
## Overview
|
||||
|
||||
Inspect is a read-only module designed to display detailed information about video files that doesn't fit in the compact metadata panel shown in other modules. It's useful for technical analysis, troubleshooting, and understanding video file characteristics.
|
||||
|
||||
## Features
|
||||
|
||||
### Technical Details
|
||||
- **Video Codec** - H.264, H.265, VP9, etc.
|
||||
- **Container Format** - MP4, MKV, AVI, etc.
|
||||
- **Resolution** - Width × Height in pixels
|
||||
- **Frame Rate** - Exact fps (23.976, 29.97, 30, 60, etc.)
|
||||
- **Aspect Ratio** - Display aspect ratio (DAR) and pixel aspect ratio (PAR)
|
||||
- **Bitrate** - Overall, video, and audio bitrates
|
||||
- **Duration** - Precise timestamp
|
||||
- **File Size** - Human-readable format
|
||||
- **Pixel Format** - yuv420p, yuv444p, rgb24, etc.
|
||||
- **Color Space** - BT.709, BT.601, BT.2020, etc.
|
||||
- **Color Range** - Limited (TV) or Full (PC)
|
||||
- **Bit Depth** - 8-bit, 10-bit, 12-bit
|
||||
|
||||
### Stream Information
|
||||
|
||||
#### Video Streams
|
||||
For each video stream:
|
||||
- Stream index and type
|
||||
- Codec name and profile
|
||||
- Resolution and aspect ratio
|
||||
- Frame rate and time base
|
||||
- Bitrate
|
||||
- GOP structure (keyframe interval)
|
||||
- Encoding library/settings
|
||||
|
||||
#### Audio Streams
|
||||
For each audio stream:
|
||||
- Stream index and type
|
||||
- Codec name
|
||||
- Sample rate (44.1kHz, 48kHz, etc.)
|
||||
- Bit depth (16-bit, 24-bit, etc.)
|
||||
- Channels (stereo, 5.1, 7.1, etc.)
|
||||
- Bitrate
|
||||
- Language tag
|
||||
|
||||
#### Subtitle Streams
|
||||
For each subtitle stream:
|
||||
- Stream index and type
|
||||
- Subtitle format (SRT, ASS, PGS, etc.)
|
||||
- Language tag
|
||||
- Default/forced flags
|
||||
|
||||
### Container Metadata
|
||||
|
||||
#### Common Tags
|
||||
- **Title** - Media title
|
||||
- **Artist/Author** - Creator
|
||||
- **Album** - Collection name
|
||||
- **Year** - Release year
|
||||
- **Genre** - Content category
|
||||
- **Comment** - Description
|
||||
- **Track Number** - Position in album
|
||||
- **Cover Art** - Embedded image
|
||||
|
||||
#### Technical Metadata
|
||||
- **Creation Time** - When file was created
|
||||
- **Encoder** - Software used to create file
|
||||
- **Handler Name** - Video/audio handler
|
||||
- **Timecode** - Start timecode for professional footage
|
||||
|
||||
### Chapter Information
|
||||
- Chapter count
|
||||
- Chapter titles
|
||||
- Start/end timestamps for each chapter
|
||||
- Chapter thumbnail (if available)
|
||||
|
||||
### Advanced Analysis
|
||||
|
||||
#### HDR Metadata
|
||||
For HDR content:
|
||||
- **Color Primaries** - BT.2020, DCI-P3
|
||||
- **Transfer Characteristics** - PQ (ST.2084), HLG
|
||||
- **Mastering Display** - Peak luminance, color gamut
|
||||
- **Content Light Level** - MaxCLL, MaxFALL
|
||||
|
||||
#### Interlacing Detection
|
||||
- Field order (progressive, top-field-first, bottom-field-first)
|
||||
- Telecine flags
|
||||
- Repeat field flags
|
||||
|
||||
#### Variable Frame Rate
|
||||
- Detection of VFR content
|
||||
- Frame rate range (min/max)
|
||||
- Frame duplication patterns
|
||||
|
||||
### Cover Art Viewer
|
||||
- Display embedded cover art
|
||||
- Show resolution and format
|
||||
- Extract to separate file option
|
||||
|
||||
### MediaInfo Integration
|
||||
When available, show extended MediaInfo output:
|
||||
- Writing library details
|
||||
- Encoding settings reconstruction
|
||||
- Format-specific technical data
|
||||
|
||||
## Usage Guide
|
||||
|
||||
### Basic Inspection
|
||||
|
||||
1. **Load Video**
|
||||
- Select video file or use already loaded video
|
||||
- Inspection loads automatically
|
||||
|
||||
2. **Review Information**
|
||||
- Browse through categorized sections
|
||||
- Copy technical details to clipboard
|
||||
- Export full report
|
||||
|
||||
### Viewing Streams
|
||||
|
||||
Navigate to "Streams" tab to see all tracks:
|
||||
- Identify default streams
|
||||
- Check language tags
|
||||
- Verify codec compatibility
|
||||
|
||||
### Checking Metadata
|
||||
|
||||
Open "Metadata" tab to view/copy tags:
|
||||
- Useful for organizing media libraries
|
||||
- Verify embedded information
|
||||
- Check for privacy concerns (GPS, camera info)
|
||||
|
||||
### Chapter Navigation
|
||||
|
||||
If video has chapters:
|
||||
- View chapter list with timestamps
|
||||
- Preview chapter thumbnails
|
||||
- Use for planning trim operations
|
||||
|
||||
## Export Options
|
||||
|
||||
### Text Report
|
||||
Export all information as plain text file:
|
||||
```
|
||||
VideoTools Inspection Report
|
||||
File: example.mp4
|
||||
Date: 2025-11-23
|
||||
|
||||
== GENERAL ==
|
||||
Format: QuickTime / MOV
|
||||
Duration: 00:10:23.456
|
||||
File Size: 512.3 MB
|
||||
...
|
||||
```
|
||||
|
||||
### JSON Export
|
||||
Structured data for programmatic use:
|
||||
```json
|
||||
{
|
||||
"format": "mov,mp4,m4a,3gp,3g2,mj2",
|
||||
"duration": 623.456,
|
||||
"bitrate": 6892174,
|
||||
"streams": [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Clipboard Copy
|
||||
Quick copy of specific details:
|
||||
- Right-click any field → Copy
|
||||
- Copy entire section
|
||||
- Copy full ffprobe output
|
||||
|
||||
## Integration with Other Modules
|
||||
|
||||
### Pre-Convert Analysis
|
||||
Before converting, check:
|
||||
- Source codec and quality
|
||||
- HDR metadata (may need special handling)
|
||||
- Audio tracks (which to keep?)
|
||||
- Subtitle availability
|
||||
|
||||
### Post-Convert Verification
|
||||
After conversion, compare:
|
||||
- File size reduction
|
||||
- Bitrate changes
|
||||
- Metadata preservation
|
||||
- Stream count/types
|
||||
|
||||
### Troubleshooting Aid
|
||||
When something goes wrong:
|
||||
- Verify source file integrity
|
||||
- Check for unusual formats
|
||||
- Identify problematic streams
|
||||
- Get exact technical specs for support
|
||||
|
||||
## FFmpeg Integration
|
||||
|
||||
Inspect uses `ffprobe` for metadata extraction:
|
||||
|
||||
```bash
|
||||
# Basic probe
|
||||
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4
|
||||
|
||||
# Include chapters
|
||||
ffprobe -v quiet -print_format json -show_chapters input.mp4
|
||||
|
||||
# Frame-level analysis (for advanced detection)
|
||||
ffprobe -v quiet -select_streams v:0 -show_frames input.mp4
|
||||
```
|
||||
|
||||
## Tips & Best Practices
|
||||
|
||||
### Understanding Codecs
|
||||
- **H.264 Baseline** - Basic compatibility (phones, old devices)
|
||||
- **H.264 Main** - Standard use (most common)
|
||||
- **H.264 High** - Better quality (Blu-ray, streaming)
|
||||
- **H.265 Main** - Consumer HDR content
|
||||
- **H.265 Main10** - 10-bit color depth
|
||||
|
||||
### Bitrate Interpretation
|
||||
| Quality | 1080p Bitrate | 4K Bitrate |
|
||||
|---------|---------------|------------|
|
||||
| Low | 2-4 Mbps | 8-12 Mbps |
|
||||
| Medium | 5-8 Mbps | 15-25 Mbps |
|
||||
| High | 10-15 Mbps | 30-50 Mbps |
|
||||
| Lossless | 50+ Mbps | 100+ Mbps |
|
||||
|
||||
### Frame Rate Notes
|
||||
- **23.976** - Film transferred to video (NTSC)
|
||||
- **24** - Film, cinema
|
||||
- **25** - PAL standard
|
||||
- **29.97** - NTSC standard
|
||||
- **30** - Modern digital
|
||||
- **50/60** - High frame rate, sports
|
||||
- **120+** - Slow motion source
|
||||
|
||||
### Color Space
|
||||
- **BT.601** - SD content (DVD, old TV)
|
||||
- **BT.709** - HD content (Blu-ray, modern)
|
||||
- **BT.2020** - UHD/HDR content
|
||||
|
||||
## See Also
|
||||
- [Convert Module](../convert/) - Use inspection data to inform conversion settings
|
||||
- [Filters Module](../filters/) - Understand color space before applying filters
|
||||
- [Streams Module](../streams/) - Manage individual streams found in inspection
|
||||
297
docs/rip/README.md
Normal file
297
docs/rip/README.md
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
# Rip Module
|
||||
|
||||
Extract and convert content from DVDs, Blu-rays, and disc images.
|
||||
|
||||
## Overview
|
||||
|
||||
The Rip module (formerly "Remux") handles extraction of video content from optical media and disc image files. It can rip directly from physical drives or work with ISO/IMG files, providing options for both lossless extraction and transcoding during the rip process.
|
||||
|
||||
> **Note:** This module is currently in planning phase. Features described below are proposed functionality.
|
||||
|
||||
## Features
|
||||
|
||||
### Source Support
|
||||
|
||||
#### Physical Media
|
||||
- **DVD** - Standard DVDs with VOB structure
|
||||
- **Blu-ray** - BD structure with M2TS files
|
||||
- **CD** - Video CDs (VCD/SVCD)
|
||||
- Direct drive access for ripping
|
||||
|
||||
#### Disc Images
|
||||
- **ISO** - Standard disc image format
|
||||
- **IMG** - Raw disc images
|
||||
- **BIN/CUE** - CD image pairs
|
||||
- Mount and extract without burning
|
||||
|
||||
### Title Selection
|
||||
|
||||
#### Auto-Detection
|
||||
- Scan disc for all titles
|
||||
- Identify main feature (longest title)
|
||||
- List all extras/bonus content
|
||||
- Show duration and chapter count for each
|
||||
|
||||
#### Manual Selection
|
||||
- Preview titles before ripping
|
||||
- Select multiple titles for batch rip
|
||||
- Choose specific chapters from titles
|
||||
- Merge chapters from different titles
|
||||
|
||||
### Track Management
|
||||
|
||||
#### Video Tracks
|
||||
- Select video angle (for multi-angle DVDs)
|
||||
- Choose video quality/stream
|
||||
|
||||
#### Audio Tracks
|
||||
- List all audio tracks with language
|
||||
- Select which tracks to include
|
||||
- Reorder track priority
|
||||
- Convert audio format during rip
|
||||
|
||||
#### Subtitle Tracks
|
||||
- List all subtitle languages
|
||||
- Extract or burn subtitles
|
||||
- Select multiple subtitle tracks
|
||||
- Convert subtitle formats
|
||||
|
||||
### Rip Modes
|
||||
|
||||
#### Direct Copy (Lossless)
|
||||
Fast extraction with no quality loss:
|
||||
- Copy VOB → MKV/MP4 container
|
||||
- No re-encoding
|
||||
- Preserves original quality
|
||||
- Fastest option
|
||||
- Larger file sizes
|
||||
|
||||
#### Transcode
|
||||
Convert during extraction:
|
||||
- Choose output codec (H.264, H.265, etc.)
|
||||
- Set quality/bitrate
|
||||
- Resize if desired
|
||||
- Compress to smaller file
|
||||
- Slower but more flexible
|
||||
|
||||
#### Smart Mode
|
||||
Automatically choose best approach:
|
||||
- Copy if already efficient codec
|
||||
- Transcode if old/inefficient codec
|
||||
- Optimize settings for content type
|
||||
|
||||
### Copy Protection Handling
|
||||
|
||||
#### DVD CSS
|
||||
- Use libdvdcss when available
|
||||
- Automatic decryption during rip
|
||||
- Legal for personal use (varies by region)
|
||||
|
||||
#### Blu-ray AACS
|
||||
- Use libaacs for AACS decryption
|
||||
- Support for BD+ (limited)
|
||||
- Requires key database
|
||||
|
||||
#### Region Codes
|
||||
- Detect region restrictions
|
||||
- Handle multi-region discs
|
||||
- RPC-1 drive support
|
||||
|
||||
### Quality Settings
|
||||
|
||||
#### Presets
|
||||
- **Archival** - Lossless or very high quality
|
||||
- **Standard** - Good quality, moderate size
|
||||
- **Efficient** - Smaller files, acceptable quality
|
||||
- **Custom** - User-defined settings
|
||||
|
||||
#### Special Handling
|
||||
- Deinterlace DVD content automatically
|
||||
- Inverse telecine for film sources
|
||||
- Upscale SD content to HD (optional)
|
||||
- HDR passthrough for Blu-ray
|
||||
|
||||
### Batch Processing
|
||||
|
||||
#### Multiple Titles
|
||||
- Queue all titles from disc
|
||||
- Process sequentially
|
||||
- Different settings per title
|
||||
- Automatic naming
|
||||
|
||||
#### Multiple Discs
|
||||
- Load multiple ISO files
|
||||
- Batch rip entire series
|
||||
- Consistent settings across discs
|
||||
- Progress tracking
|
||||
|
||||
### Output Options
|
||||
|
||||
#### Naming Templates
|
||||
Automatic file naming:
|
||||
```
|
||||
{disc_name}_Title{title_num}_Chapter{start}-{end}
|
||||
Star_Wars_Title01_Chapter01-25.mp4
|
||||
```
|
||||
|
||||
#### Metadata
|
||||
- Auto-populate from disc info
|
||||
- Lookup online databases (IMDB, TheTVDB)
|
||||
- Chapter markers preserved
|
||||
- Cover art extraction
|
||||
|
||||
#### Organization
|
||||
- Create folder per disc
|
||||
- Separate folders for extras
|
||||
- Season/episode structure for TV
|
||||
- Automatic file organization
|
||||
|
||||
## Usage Guide
|
||||
|
||||
### Ripping a DVD
|
||||
|
||||
1. **Insert Disc or Load ISO**
|
||||
- Physical disc: Insert and click "Scan Drive"
|
||||
- ISO file: Click "Load ISO" and select file
|
||||
|
||||
2. **Scan Disc**
|
||||
- Application analyzes disc structure
|
||||
- Lists all titles with duration/chapters
|
||||
- Main feature highlighted
|
||||
|
||||
3. **Select Title(s)**
|
||||
- Choose main feature or specific titles
|
||||
- Select desired chapters
|
||||
- Preview title information
|
||||
|
||||
4. **Configure Tracks**
|
||||
- Select audio tracks (e.g., English 5.1)
|
||||
- Choose subtitle tracks if desired
|
||||
- Set track order/defaults
|
||||
|
||||
5. **Choose Rip Mode**
|
||||
- Direct Copy for fastest/lossless
|
||||
- Transcode to save space
|
||||
- Configure quality settings
|
||||
|
||||
6. **Set Output**
|
||||
- Choose output folder
|
||||
- Set filename or use template
|
||||
- Select container format
|
||||
|
||||
7. **Start Rip**
|
||||
- Click "Start Ripping"
|
||||
- Monitor progress
|
||||
- Can queue multiple titles
|
||||
|
||||
### Ripping a Blu-ray
|
||||
|
||||
Similar to DVD but with additional considerations:
|
||||
- Much larger files (20-40GB for feature)
|
||||
- Better quality settings available
|
||||
- HDR preservation options
|
||||
- Multi-audio track handling
|
||||
|
||||
### Batch Ripping a TV Series
|
||||
|
||||
1. **Load all disc ISOs** for season
|
||||
2. **Scan each disc** to identify episodes
|
||||
3. **Enable batch mode**
|
||||
4. **Configure naming** with episode numbers
|
||||
5. **Set consistent quality** for all
|
||||
6. **Start batch rip**
|
||||
|
||||
## FFmpeg Integration
|
||||
|
||||
### Direct Copy Example
|
||||
```bash
|
||||
# Extract VOB to MKV without re-encoding
|
||||
ffmpeg -i /dev/dvd -map 0 -c copy output.mkv
|
||||
|
||||
# Extract specific title
|
||||
ffmpeg -i dvd://1 -map 0 -c copy title_01.mkv
|
||||
```
|
||||
|
||||
### Transcode Example
|
||||
```bash
|
||||
# Rip DVD with H.264 encoding
|
||||
ffmpeg -i dvd://1 \
|
||||
-vf yadif,scale=720:480 \
|
||||
-c:v libx264 -crf 20 \
|
||||
-c:a aac -b:a 192k \
|
||||
output.mp4
|
||||
```
|
||||
|
||||
### Multi-Track Example
|
||||
```bash
|
||||
# Preserve multiple audio and subtitle tracks
|
||||
ffmpeg -i dvd://1 \
|
||||
-map 0:v:0 \
|
||||
-map 0:a:0 -map 0:a:1 \
|
||||
-map 0:s:0 -map 0:s:1 \
|
||||
-c copy output.mkv
|
||||
```
|
||||
|
||||
## Tips & Best Practices
|
||||
|
||||
### DVD Quality
|
||||
- Original DVD is 720×480 (NTSC) or 720×576 (PAL)
|
||||
- Always deinterlace DVD content
|
||||
- Consider upscaling to 1080p for modern displays
|
||||
- Use inverse telecine for film sources (24fps)
|
||||
|
||||
### Blu-ray Handling
|
||||
- Main feature typically 20-50GB
|
||||
- Consider transcoding to H.265 to reduce size
|
||||
- Preserve 1080p resolution
|
||||
- Keep high bitrate audio (DTS-HD, TrueHD)
|
||||
|
||||
### File Size Management
|
||||
| Source | Direct Copy | H.264 CRF 20 | H.265 CRF 24 |
|
||||
|--------|-------------|--------------|--------------|
|
||||
| DVD (2hr) | 4-8 GB | 2-4 GB | 1-3 GB |
|
||||
| Blu-ray (2hr) | 30-50 GB | 6-10 GB | 4-6 GB |
|
||||
|
||||
### Legal Considerations
|
||||
- Ripping for personal backup is legal in many regions
|
||||
- Circumventing copy protection may have legal restrictions
|
||||
- Distribution of ripped content is typically illegal
|
||||
- Check local laws and regulations
|
||||
|
||||
### Drive Requirements
|
||||
- DVD-ROM drive for DVD ripping
|
||||
- Blu-ray drive for Blu-ray ripping (obviously)
|
||||
- RPC-1 (region-free) firmware helpful
|
||||
- External drives work fine
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Can't Read Disc
|
||||
- Clean disc surface
|
||||
- Try different drive
|
||||
- Check drive region code
|
||||
- Verify disc isn't damaged
|
||||
|
||||
### Copy Protection Errors
|
||||
- Install libdvdcss (DVD) or libaacs (Blu-ray)
|
||||
- Update key database
|
||||
- Check disc region compatibility
|
||||
- Try different disc copy
|
||||
|
||||
### Slow Ripping
|
||||
- Direct copy is fastest
|
||||
- Transcoding is CPU-intensive
|
||||
- Use hardware acceleration if available
|
||||
- Check drive speed settings
|
||||
|
||||
### Audio/Video Sync Issues
|
||||
- Common with VFR content
|
||||
- Use -vsync parameter
|
||||
- Force constant frame rate
|
||||
- Check source for corruption
|
||||
|
||||
## See Also
|
||||
- [Convert Module](../convert/) - Transcode ripped files further
|
||||
- [Streams Module](../streams/) - Manage multi-track ripped files
|
||||
- [Subtitle Module](../subtitle/) - Handle extracted subtitle tracks
|
||||
- [Inspect Module](../inspect/) - Analyze ripped output quality
|
||||
82
internal/logging/logging.go
Normal file
82
internal/logging/logging.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
filePath string
|
||||
file *os.File
|
||||
history []string
|
||||
debugEnabled bool
|
||||
logger = log.New(os.Stderr, "[videotools] ", log.LstdFlags|log.Lmicroseconds)
|
||||
)
|
||||
|
||||
const historyMax = 500
|
||||
|
||||
// Category represents a log category
|
||||
type Category string
|
||||
|
||||
const (
|
||||
CatUI Category = "[UI]"
|
||||
CatCLI Category = "[CLI]"
|
||||
CatFFMPEG Category = "[FFMPEG]"
|
||||
CatSystem Category = "[SYS]"
|
||||
CatModule Category = "[MODULE]"
|
||||
)
|
||||
|
||||
// Init initializes the logging system
|
||||
func Init() {
|
||||
filePath = os.Getenv("VIDEOTOOLS_LOG_FILE")
|
||||
if filePath == "" {
|
||||
filePath = "videotools.log"
|
||||
}
|
||||
f, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "videotools: cannot open log file %s: %v\n", filePath, err)
|
||||
return
|
||||
}
|
||||
file = f
|
||||
}
|
||||
|
||||
// Close closes the log file
|
||||
func Close() {
|
||||
if file != nil {
|
||||
file.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// SetDebug enables or disables debug logging
|
||||
func SetDebug(on bool) {
|
||||
debugEnabled = on
|
||||
Debug(CatSystem, "debug logging toggled -> %v (VIDEOTOOLS_DEBUG=%s)", on, os.Getenv("VIDEOTOOLS_DEBUG"))
|
||||
}
|
||||
|
||||
// Debug logs a debug message with a category
|
||||
func Debug(cat Category, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf("%s %s", cat, fmt.Sprintf(format, args...))
|
||||
timestamp := time.Now().Format(time.RFC3339Nano)
|
||||
if file != nil {
|
||||
fmt.Fprintf(file, "%s %s\n", timestamp, msg)
|
||||
}
|
||||
history = append(history, fmt.Sprintf("%s %s", timestamp, msg))
|
||||
if len(history) > historyMax {
|
||||
history = history[len(history)-historyMax:]
|
||||
}
|
||||
if debugEnabled {
|
||||
logger.Printf("%s %s", timestamp, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// FilePath returns the current log file path
|
||||
func FilePath() string {
|
||||
return filePath
|
||||
}
|
||||
|
||||
// History returns the log history
|
||||
func History() []string {
|
||||
return history
|
||||
}
|
||||
57
internal/modules/handlers.go
Normal file
57
internal/modules/handlers.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package modules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/logging"
|
||||
)
|
||||
|
||||
// Module handlers - each handles the logic for a specific module
|
||||
|
||||
// HandleConvert handles the convert module
|
||||
func HandleConvert(files []string) {
|
||||
logging.Debug(logging.CatFFMPEG, "convert handler invoked with %v", files)
|
||||
fmt.Println("convert", files)
|
||||
}
|
||||
|
||||
// HandleMerge handles the merge module
|
||||
func HandleMerge(files []string) {
|
||||
logging.Debug(logging.CatFFMPEG, "merge handler invoked with %v", files)
|
||||
fmt.Println("merge", files)
|
||||
}
|
||||
|
||||
// HandleTrim handles the trim module
|
||||
func HandleTrim(files []string) {
|
||||
logging.Debug(logging.CatModule, "trim handler invoked with %v", files)
|
||||
fmt.Println("trim", files)
|
||||
}
|
||||
|
||||
// HandleFilters handles the filters module
|
||||
func HandleFilters(files []string) {
|
||||
logging.Debug(logging.CatModule, "filters handler invoked with %v", files)
|
||||
fmt.Println("filters", files)
|
||||
}
|
||||
|
||||
// HandleUpscale handles the upscale module
|
||||
func HandleUpscale(files []string) {
|
||||
logging.Debug(logging.CatModule, "upscale handler invoked with %v", files)
|
||||
fmt.Println("upscale", files)
|
||||
}
|
||||
|
||||
// HandleAudio handles the audio module
|
||||
func HandleAudio(files []string) {
|
||||
logging.Debug(logging.CatModule, "audio handler invoked with %v", files)
|
||||
fmt.Println("audio", files)
|
||||
}
|
||||
|
||||
// HandleThumb handles the thumb module
|
||||
func HandleThumb(files []string) {
|
||||
logging.Debug(logging.CatModule, "thumb handler invoked with %v", files)
|
||||
fmt.Println("thumb", files)
|
||||
}
|
||||
|
||||
// HandleInspect handles the inspect module
|
||||
func HandleInspect(files []string) {
|
||||
logging.Debug(logging.CatModule, "inspect handler invoked with %v", files)
|
||||
fmt.Println("inspect", files)
|
||||
}
|
||||
129
internal/ui/components.go
Normal file
129
internal/ui/components.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
var (
|
||||
// GridColor is the color used for grid lines and borders
|
||||
GridColor color.Color
|
||||
// TextColor is the main text color
|
||||
TextColor color.Color
|
||||
)
|
||||
|
||||
// SetColors sets the UI colors
|
||||
func SetColors(grid, text color.Color) {
|
||||
GridColor = grid
|
||||
TextColor = text
|
||||
}
|
||||
|
||||
// MonoTheme ensures all text uses a monospace font
|
||||
type MonoTheme struct{}
|
||||
|
||||
func (m *MonoTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
|
||||
return theme.DefaultTheme().Color(name, variant)
|
||||
}
|
||||
|
||||
func (m *MonoTheme) Font(style fyne.TextStyle) fyne.Resource {
|
||||
style.Monospace = true
|
||||
return theme.DefaultTheme().Font(style)
|
||||
}
|
||||
|
||||
func (m *MonoTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
|
||||
return theme.DefaultTheme().Icon(name)
|
||||
}
|
||||
|
||||
func (m *MonoTheme) Size(name fyne.ThemeSizeName) float32 {
|
||||
return theme.DefaultTheme().Size(name)
|
||||
}
|
||||
|
||||
// ModuleTile is a clickable tile widget for module selection
|
||||
type ModuleTile struct {
|
||||
widget.BaseWidget
|
||||
label string
|
||||
color color.Color
|
||||
onTapped func()
|
||||
}
|
||||
|
||||
// NewModuleTile creates a new module tile
|
||||
func NewModuleTile(label string, col color.Color, tapped func()) *ModuleTile {
|
||||
m := &ModuleTile{
|
||||
label: strings.ToUpper(label),
|
||||
color: col,
|
||||
onTapped: tapped,
|
||||
}
|
||||
m.ExtendBaseWidget(m)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *ModuleTile) CreateRenderer() fyne.WidgetRenderer {
|
||||
bg := canvas.NewRectangle(m.color)
|
||||
bg.CornerRadius = 8
|
||||
bg.StrokeColor = GridColor
|
||||
bg.StrokeWidth = 1
|
||||
|
||||
txt := canvas.NewText(m.label, TextColor)
|
||||
txt.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
||||
txt.Alignment = fyne.TextAlignCenter
|
||||
txt.TextSize = 20
|
||||
|
||||
return &moduleTileRenderer{
|
||||
tile: m,
|
||||
bg: bg,
|
||||
label: txt,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ModuleTile) Tapped(*fyne.PointEvent) {
|
||||
if m.onTapped != nil {
|
||||
m.onTapped()
|
||||
}
|
||||
}
|
||||
|
||||
type moduleTileRenderer struct {
|
||||
tile *ModuleTile
|
||||
bg *canvas.Rectangle
|
||||
label *canvas.Text
|
||||
}
|
||||
|
||||
func (r *moduleTileRenderer) Layout(size fyne.Size) {
|
||||
r.bg.Resize(size)
|
||||
// Center the label by positioning it in the middle
|
||||
labelSize := r.label.MinSize()
|
||||
r.label.Resize(labelSize)
|
||||
x := (size.Width - labelSize.Width) / 2
|
||||
y := (size.Height - labelSize.Height) / 2
|
||||
r.label.Move(fyne.NewPos(x, y))
|
||||
}
|
||||
|
||||
func (r *moduleTileRenderer) MinSize() fyne.Size {
|
||||
return fyne.NewSize(220, 110)
|
||||
}
|
||||
|
||||
func (r *moduleTileRenderer) Refresh() {
|
||||
r.bg.FillColor = r.tile.color
|
||||
r.bg.Refresh()
|
||||
r.label.Text = r.tile.label
|
||||
r.label.Refresh()
|
||||
}
|
||||
|
||||
func (r *moduleTileRenderer) Destroy() {}
|
||||
|
||||
func (r *moduleTileRenderer) Objects() []fyne.CanvasObject {
|
||||
return []fyne.CanvasObject{r.bg, r.label}
|
||||
}
|
||||
|
||||
// TintedBar creates a colored bar container
|
||||
func TintedBar(col color.Color, body fyne.CanvasObject) fyne.CanvasObject {
|
||||
rect := canvas.NewRectangle(col)
|
||||
rect.SetMinSize(fyne.NewSize(0, 48))
|
||||
padded := container.NewPadded(body)
|
||||
return container.NewMax(rect, padded)
|
||||
}
|
||||
75
internal/ui/mainmenu.go
Normal file
75
internal/ui/mainmenu.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/logging"
|
||||
)
|
||||
|
||||
// ModuleInfo contains information about a module for display
|
||||
type ModuleInfo struct {
|
||||
ID string
|
||||
Label string
|
||||
Color color.Color
|
||||
}
|
||||
|
||||
// BuildMainMenu creates the main menu view with module tiles
|
||||
func BuildMainMenu(modules []ModuleInfo, onModuleClick func(string), titleColor, queueColor, textColor color.Color) fyne.CanvasObject {
|
||||
title := canvas.NewText("VIDEOTOOLS", titleColor)
|
||||
title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
||||
title.TextSize = 28
|
||||
|
||||
queueTile := buildQueueTile(0, 0, queueColor, textColor)
|
||||
|
||||
header := container.New(layout.NewHBoxLayout(),
|
||||
title,
|
||||
layout.NewSpacer(),
|
||||
queueTile,
|
||||
)
|
||||
|
||||
var tileObjects []fyne.CanvasObject
|
||||
for _, mod := range modules {
|
||||
modID := mod.ID // Capture for closure
|
||||
tileObjects = append(tileObjects, buildModuleTile(mod, func() {
|
||||
onModuleClick(modID)
|
||||
}))
|
||||
}
|
||||
|
||||
grid := container.NewGridWithColumns(3, tileObjects...)
|
||||
|
||||
padding := canvas.NewRectangle(color.Transparent)
|
||||
padding.SetMinSize(fyne.NewSize(0, 14))
|
||||
|
||||
body := container.New(layout.NewVBoxLayout(),
|
||||
header,
|
||||
padding,
|
||||
grid,
|
||||
)
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
// buildModuleTile creates a single module tile
|
||||
func buildModuleTile(mod ModuleInfo, tapped func()) fyne.CanvasObject {
|
||||
logging.Debug(logging.CatUI, "building tile %s color=%v", mod.ID, mod.Color)
|
||||
return container.NewPadded(NewModuleTile(mod.Label, mod.Color, tapped))
|
||||
}
|
||||
|
||||
// buildQueueTile creates the queue status tile
|
||||
func buildQueueTile(done, total int, queueColor, textColor color.Color) fyne.CanvasObject {
|
||||
rect := canvas.NewRectangle(queueColor)
|
||||
rect.CornerRadius = 8
|
||||
rect.SetMinSize(fyne.NewSize(160, 60))
|
||||
|
||||
text := canvas.NewText(fmt.Sprintf("QUEUE: %d/%d", done, total), textColor)
|
||||
text.Alignment = fyne.TextAlignCenter
|
||||
text.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
||||
text.TextSize = 18
|
||||
|
||||
return container.NewMax(rect, container.NewCenter(text))
|
||||
}
|
||||
238
internal/utils/utils.go
Normal file
238
internal/utils/utils.go
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"git.leaktechnologies.dev/stu/VideoTools/internal/logging"
|
||||
)
|
||||
|
||||
// Color utilities
|
||||
|
||||
// MustHex parses a hex color string or exits on error
|
||||
func MustHex(h string) color.NRGBA {
|
||||
c, err := ParseHexColor(h)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "invalid color %q: %v\n", h, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// ParseHexColor parses a hex color string like "#RRGGBB"
|
||||
func ParseHexColor(s string) (color.NRGBA, error) {
|
||||
s = strings.TrimPrefix(s, "#")
|
||||
if len(s) != 6 {
|
||||
return color.NRGBA{}, fmt.Errorf("want 6 digits, got %q", s)
|
||||
}
|
||||
var r, g, b uint8
|
||||
if _, err := fmt.Sscanf(s, "%02x%02x%02x", &r, &g, &b); err != nil {
|
||||
return color.NRGBA{}, err
|
||||
}
|
||||
return color.NRGBA{R: r, G: g, B: b, A: 0xff}, nil
|
||||
}
|
||||
|
||||
// String utilities
|
||||
|
||||
// FirstNonEmpty returns the first non-empty string or "--"
|
||||
func FirstNonEmpty(values ...string) string {
|
||||
for _, v := range values {
|
||||
if strings.TrimSpace(v) != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return "--"
|
||||
}
|
||||
|
||||
// Parsing utilities
|
||||
|
||||
// ParseFloat parses a float64 from a string
|
||||
func ParseFloat(s string) (float64, error) {
|
||||
return strconv.ParseFloat(strings.TrimSpace(s), 64)
|
||||
}
|
||||
|
||||
// ParseInt parses an int from a string
|
||||
func ParseInt(s string) (int, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return 0, fmt.Errorf("empty")
|
||||
}
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
|
||||
// ParseFraction parses a fraction string like "24000/1001" or "30"
|
||||
func ParseFraction(s string) float64 {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" || s == "0" {
|
||||
return 0
|
||||
}
|
||||
parts := strings.Split(s, "/")
|
||||
num, err := strconv.ParseFloat(parts[0], 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
return num
|
||||
}
|
||||
den, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil || den == 0 {
|
||||
return 0
|
||||
}
|
||||
return num / den
|
||||
}
|
||||
|
||||
// Math utilities
|
||||
|
||||
// GCD returns the greatest common divisor of two integers
|
||||
func GCD(a, b int) int {
|
||||
if a < 0 {
|
||||
a = -a
|
||||
}
|
||||
if b < 0 {
|
||||
b = -b
|
||||
}
|
||||
for b != 0 {
|
||||
a, b = b, a%b
|
||||
}
|
||||
if a == 0 {
|
||||
return 1
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// SimplifyRatio simplifies a width/height ratio
|
||||
func SimplifyRatio(w, h int) (int, int) {
|
||||
if w <= 0 || h <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
g := GCD(w, h)
|
||||
return w / g, h / g
|
||||
}
|
||||
|
||||
// MaxInt returns the maximum of two integers
|
||||
func MaxInt(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Aspect ratio utilities
|
||||
|
||||
// AspectRatioFloat calculates the aspect ratio as a float
|
||||
func AspectRatioFloat(w, h int) float64 {
|
||||
if w <= 0 || h <= 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(w) / float64(h)
|
||||
}
|
||||
|
||||
// ParseAspectValue parses an aspect ratio string like "16:9"
|
||||
func ParseAspectValue(val string) float64 {
|
||||
val = strings.TrimSpace(val)
|
||||
switch val {
|
||||
case "16:9":
|
||||
return 16.0 / 9.0
|
||||
case "4:3":
|
||||
return 4.0 / 3.0
|
||||
case "1:1":
|
||||
return 1
|
||||
case "9:16":
|
||||
return 9.0 / 16.0
|
||||
case "21:9":
|
||||
return 21.0 / 9.0
|
||||
}
|
||||
parts := strings.Split(val, ":")
|
||||
if len(parts) == 2 {
|
||||
n, err1 := strconv.ParseFloat(parts[0], 64)
|
||||
d, err2 := strconv.ParseFloat(parts[1], 64)
|
||||
if err1 == nil && err2 == nil && d != 0 {
|
||||
return n / d
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// RatiosApproxEqual checks if two ratios are approximately equal
|
||||
func RatiosApproxEqual(a, b, tol float64) bool {
|
||||
if a == 0 || b == 0 {
|
||||
return false
|
||||
}
|
||||
diff := math.Abs(a - b)
|
||||
if b != 0 {
|
||||
diff = diff / b
|
||||
}
|
||||
return diff <= tol
|
||||
}
|
||||
|
||||
// Audio utilities
|
||||
|
||||
// ChannelLabel returns a human-readable label for a channel count
|
||||
func ChannelLabel(ch int) string {
|
||||
switch ch {
|
||||
case 1:
|
||||
return "Mono"
|
||||
case 2:
|
||||
return "Stereo"
|
||||
case 6:
|
||||
return "5.1"
|
||||
case 8:
|
||||
return "7.1"
|
||||
default:
|
||||
if ch <= 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%d ch", ch)
|
||||
}
|
||||
}
|
||||
|
||||
// Image utilities
|
||||
|
||||
// CopyRGBToRGBA expands packed RGB bytes into RGBA while forcing opaque alpha
|
||||
func CopyRGBToRGBA(dst, src []byte) {
|
||||
di := 0
|
||||
for si := 0; si+2 < len(src) && di+3 < len(dst); si, di = si+3, di+4 {
|
||||
dst[di] = src[si]
|
||||
dst[di+1] = src[si+1]
|
||||
dst[di+2] = src[si+2]
|
||||
dst[di+3] = 0xff
|
||||
}
|
||||
}
|
||||
|
||||
// UI utilities
|
||||
|
||||
// MakeIconButton creates a low-importance button with a symbol
|
||||
func MakeIconButton(symbol, tooltip string, tapped func()) *widget.Button {
|
||||
btn := widget.NewButton(symbol, tapped)
|
||||
btn.Importance = widget.LowImportance
|
||||
return btn
|
||||
}
|
||||
|
||||
// LoadAppIcon loads the application icon from standard locations
|
||||
func LoadAppIcon() fyne.Resource {
|
||||
search := []string{
|
||||
filepath.Join("assets", "logo", "VT_Icon.svg"),
|
||||
}
|
||||
if exe, err := os.Executable(); err == nil {
|
||||
dir := filepath.Dir(exe)
|
||||
search = append(search, filepath.Join(dir, "assets", "logo", "VT_Icon.svg"))
|
||||
}
|
||||
for _, p := range search {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
res, err := fyne.LoadResourceFromPath(p)
|
||||
if err != nil {
|
||||
logging.Debug(logging.CatUI, "failed to load icon %s: %v", p, err)
|
||||
continue
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user