Add FPS counter, queue improvements, Compare fixes, and comprehensive documentation

Features:
- FPS counter in conversion status showing real-time encoding speed
- Job queue now displays FPS, encoding speed (e.g., "1.2x"), and ETA for running conversions
- Copy Comparison button exports side-by-side metadata comparison report
- Auto-compare checkbox in Convert module - automatically loads Compare view after conversion
- Convert Now properly adds job to queue and displays in Job Queue with live stats
- Module badge colors in job queue now match main menu tile colors
- Fixed fullscreen compare window sizing (reduced player dimensions to prevent overflow)

Bug Fixes:
- Fixed queue state management - only one job runs at a time (prevents multiple jobs showing "running")
- Fixed Compare module slot assignment - single video drops now fill empty slot instead of overwriting
- Fixed job queue scroll rubber banding (no longer jumps back to top)
- Enhanced crop detection validation for WMV/AVI formats with dimension clamping and bounds checking

Documentation:
- VT_Player integration notes with API requirements for keyframing and trim features
- LosslessCut feature analysis for Trim module inspiration
- Video metadata guide covering MP4/MKV custom fields and NFO generation
- Trim module design specification
- Compare fullscreen mode documentation
- Updated VIDEO_PLAYER_FORK.md to mark fork as completed

Technical Changes:
- Added state tracking for FPS, speed, and ETA (main.go:197-199)
- Enhanced queue processJobs() to check for running jobs before starting new ones
- Improved Compare module drag-and-drop logic with smart slot assignment (both code paths)
- Added deferred scroll position restoration to prevent UI jumping
- Job queue Config map now carries conversion stats for display

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Stu Leak 2025-12-04 07:35:48 -05:00
parent 815319b3f5
commit 5b8fc452af
11 changed files with 2698 additions and 142 deletions

150
docs/COMPARE_FULLSCREEN.md Normal file
View File

@ -0,0 +1,150 @@
# Compare Module - Fullscreen Mode
## Overview
The Compare module now includes a **Fullscreen Compare** mode that displays two videos side-by-side in a larger view, optimized for detailed visual comparison.
## Features
### Current (v0.1)
- ✅ Side-by-side fullscreen layout
- ✅ Larger video players for better visibility
- ✅ Individual playback controls for each video
- ✅ File labels showing video names
- ✅ Back button to return to regular Compare view
- ✅ Pink colored header/footer matching Compare module
### Planned (Future - requires VT_Player enhancements)
- ⏳ **Synchronized playback** - Play/Pause both videos simultaneously
- ⏳ **Linked seeking** - Seek to same timestamp in both videos
- ⏳ **Frame-by-frame sync** - Step through both videos in lockstep
- ⏳ **Volume link** - Adjust volume on both players together
- ⏳ **Playback speed sync** - Change speed on both players at once
## Usage
### Accessing Fullscreen Mode
1. Load two videos in the Compare module
2. Click the **"Fullscreen Compare"** button
3. Videos will display side-by-side in larger players
### Controls
- **Individual players**: Each video has its own play/pause/seek controls
- **"Play Both" button**: Placeholder for future synchronized playback
- **"Pause Both" button**: Placeholder for future synchronized pause
- **"< BACK TO COMPARE"**: Return to regular Compare view
## Use Cases
### Visual Quality Comparison
Compare encoding settings or compression quality:
- Original vs. compressed
- Different codec outputs
- Before/after color grading
- Different resolution scaling
### Frame-Accurate Comparison
When VT_Player sync is implemented:
- Compare edits side-by-side
- Check for sync issues in re-encodes
- Validate frame-accurate cuts
- Compare different filter applications
### A/B Testing
Test different processing settings:
- Different deinterlacing methods
- Upscaling algorithms
- Noise reduction levels
- Color correction approaches
## Technical Notes
### Current Implementation
- Uses standard `buildVideoPane()` for each side
- 640x360 minimum player size (scales with window)
- Independent playback state per video
- No shared controls between players yet
### VT_Player API Requirements for Sync
For synchronized playback, VT_Player will need:
```go
// Playback state access
player.IsPlaying() bool
player.GetPosition() time.Duration
// Event callbacks
player.OnPlaybackStateChanged(callback func(playing bool))
player.OnPositionChanged(callback func(position time.Duration))
// Synchronized control
player.SyncWith(otherPlayer *Player)
player.Unsync()
```
### Synchronization Strategy
When VT_Player supports it:
1. **Master-Slave Pattern**: One player is master, other follows
2. **Linked Events**: Play/pause/seek events trigger on both
3. **Position Polling**: Periodically check for drift and correct
4. **Frame-Accurate Sync**: Step both players frame-by-frame together
## Keyboard Shortcuts (Planned)
When implemented in VT_Player:
- `Space` - Play/Pause both videos
- `J` / `L` - Rewind/Forward both videos
- `←` / `→` - Step both videos frame-by-frame
- `K` - Pause both videos
- `0-9` - Seek to percentage (0% to 90%) in both
- `Esc` - Exit fullscreen mode
## UI Layout
```
┌─────────────────────────────────────────────────────────────┐
< BACK TO COMPARE Pink header
├─────────────────────────────────────────────────────────────┤
│ │
│ Side-by-side fullscreen comparison. Use individual... │
│ │
│ [▶ Play Both] [⏸ Pause Both] │
│ ───────────────────────────────────────────────────────── │
│ │
│ ┌─────────────────────────┬─────────────────────────────┐ │
│ │ File 1: video1.mp4 │ File 2: video2.mp4 │ │
│ ├─────────────────────────┼─────────────────────────────┤ │
│ │ │ │ │
│ │ Video Player 1 │ Video Player 2 │ │
│ │ (640x360 min) │ (640x360 min) │ │
│ │ │ │ │
│ │ [Play] [Pause] [Seek] │ [Play] [Pause] [Seek] │ │
│ │ │ │ │
│ └─────────────────────────┴─────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
← Pink footer
```
## Future Enhancements
### v0.2 - Synchronized Playback
- Implement master-slave sync between players
- Add "Link" toggle button to enable/disable sync
- Visual indicator when players are synced
### v0.3 - Advanced Sync
- Offset compensation (e.g., if videos start at different times)
- Manual sync adjustment (nudge one video forward/back)
- Sync validation indicator (shows if videos are in sync)
### v0.4 - Comparison Tools
- Split-screen view with adjustable divider
- A/B quick toggle (show only one at a time)
- Difference overlay (highlight changed regions)
- Frame difference metrics display
## Notes
- Fullscreen mode is accessible from regular Compare view
- Videos must be loaded before entering fullscreen mode
- Synchronized controls are placeholders until VT_Player API is enhanced
- Window can be resized freely - players will scale
- Each player maintains independent state for now

View File

@ -0,0 +1,460 @@
# LosslessCut Features - Inspiration for VideoTools Trim Module
## Overview
LosslessCut is a mature, feature-rich video trimming application built on Electron/React with FFmpeg backend. This document extracts key features and UX patterns that should inspire VideoTools' Trim module development.
---
## 🎯 Core Trim Features to Adopt
### 1. **Segment-Based Editing** ⭐⭐⭐ (HIGHEST PRIORITY)
LosslessCut uses "segments" as first-class citizens rather than simple In/Out points.
**How it works:**
- Each segment has: start time, end time (optional), label, tags, and segment number
- Multiple segments can exist on timeline simultaneously
- Segments without end time = "markers" (vertical lines on timeline)
- Segments can be reordered by drag-drop in segment list
**Benefits for VideoTools:**
- User can mark multiple trim regions in one session
- Export all segments at once (batch trim)
- Save/load trim projects for later refinement
- More flexible than single In/Out point workflow
**Implementation priority:** HIGH
- Start with single segment (In/Out points)
- Phase 2: Add multiple segments support
- Phase 3: Add segment labels/tags
**Example workflow:**
```
1. User loads video
2. Finds first good section: 0:30 to 1:45 → Press I, seek, press O → Segment 1 created
3. Press + to add new segment
4. Finds second section: 3:20 to 5:10 → Segment 2 created
5. Export → Creates 2 output files (or 1 merged file if mode set)
```
---
### 2. **Keyboard-First Workflow** ⭐⭐⭐ (HIGHEST PRIORITY)
LosslessCut is designed for speed via keyboard shortcuts.
**Essential shortcuts:**
| Key | Action | Notes |
|-----|--------|-------|
| `SPACE` | Play/Pause | Standard |
| `I` | Set segment In point | Industry standard (Adobe, FCP) |
| `O` | Set segment Out point | Industry standard |
| `←` / `→` | Seek backward/forward | Frame or keyframe stepping |
| `,` / `.` | Frame step | Precise frame-by-frame (1 frame) |
| `+` | Add new segment | Quick workflow |
| `B` | Split segment at cursor | Divide segment into two |
| `BACKSPACE` | Delete segment/cutpoint | Quick removal |
| `E` | Export | Fast export shortcut |
| `C` | Capture screenshot | Snapshot current frame |
| Mouse wheel | Seek timeline | Smooth scrubbing |
**Why keyboard shortcuts matter:**
- Professional users edit faster with keyboard
- Reduces mouse movement fatigue
- Enables "flow state" editing
- Standard shortcuts reduce learning curve
**Implementation for VideoTools:**
- Integrate keyboard handling into VT_Player
- Show keyboard shortcut overlay (SHIFT+/)
- Allow user customization later
---
### 3. **Timeline Zoom** ⭐⭐⭐ (HIGH PRIORITY)
Timeline can zoom in/out for precision editing.
**How it works:**
- Zoom slider or mouse wheel on timeline
- Zoomed view shows: thumbnails, waveform, keyframes
- Timeline scrolls horizontally when zoomed
- Zoom follows playhead (keeps current position centered)
**Benefits:**
- Find exact cut points in long videos
- Frame-accurate editing even in 2-hour files
- See waveform detail for audio-based cuts
**Implementation notes:**
- VT_Player needs horizontal scrolling timeline widget
- Zoom level: 1x (full video) to 100x (extreme detail)
- Auto-scroll to keep playhead in view
---
### 4. **Waveform Display** ⭐⭐ (MEDIUM PRIORITY)
Audio waveform shown on timeline for visual reference.
**Features:**
- Shows amplitude over time
- Useful for finding speech/silence boundaries
- Click waveform to seek
- Updates as timeline zooms
**Use cases:**
- Trim silence from beginning/end
- Find exact start of dialogue
- Cut between sentences
- Detect audio glitches
**Implementation:**
- FFmpeg can generate waveform images: `ffmpeg -i input.mp4 -filter_complex showwavespic output.png`
- Display as timeline background
- Optional feature (enable/disable)
---
### 5. **Keyframe Visualization** ⭐⭐ (MEDIUM PRIORITY)
Timeline shows video keyframes (I-frames) as markers.
**Why keyframes matter:**
- Lossless copy (`-c copy`) only cuts at keyframes
- Cutting between keyframes requires re-encode
- Users need visual feedback on keyframe positions
**How LosslessCut handles it:**
- Vertical lines on timeline = keyframes
- Color-coded: bright = keyframe, dim = P/B frame
- "Smart cut" mode: cuts at keyframe + re-encodes small section
**Implementation for VideoTools:**
- Probe keyframes: `ffprobe -select_streams v -show_frames -show_entries frame=pict_type,pts_time`
- Display on timeline
- Warn user if cut point not on keyframe (when using `-c copy`)
---
### 6. **Invert Cut Mode** ⭐⭐ (MEDIUM PRIORITY)
Yin-yang toggle: Keep segments vs. Remove segments
**Two modes:**
1. **Keep mode** (default): Export marked segments, discard rest
2. **Cut mode** (inverted): Remove marked segments, keep rest
**Example:**
```
Video: [────────────────────]
Segments: [ SEG1 ] [ SEG2 ]
Keep mode → Output: SEG1.mp4, SEG2.mp4
Cut mode → Output: parts between segments (commercials removed)
```
**Use cases:**
- **Keep**: Extract highlights from long recording
- **Cut**: Remove commercials from TV recording
**Implementation:**
- Simple boolean toggle in UI
- Changes FFmpeg command logic
- Useful for both workflows
---
### 7. **Merge Mode** ⭐⭐ (MEDIUM PRIORITY)
Option to merge multiple segments into single output file.
**Export options:**
- **Separate files**: Each segment → separate file
- **Merge cuts**: All segments → 1 merged file
- **Merge + separate**: Both outputs
**FFmpeg technique:**
```bash
# Create concat file listing segments
echo "file 'segment1.mp4'" > concat.txt
echo "file 'segment2.mp4'" >> concat.txt
# Merge with concat demuxer
ffmpeg -f concat -safe 0 -i concat.txt -c copy merged.mp4
```
**Implementation:**
- UI toggle: "Merge segments"
- Temp directory for segment exports
- Concat demuxer for lossless merge
- Clean up temp files after
---
### 8. **Manual Timecode Entry** ⭐ (LOW PRIORITY)
Type exact timestamps instead of scrubbing.
**Features:**
- Click In/Out time → text input appears
- Type: `1:23:45.123` or `83.456`
- Formats: HH:MM:SS.mmm, MM:SS, seconds
- Paste timestamps from clipboard
**Use cases:**
- User has exact timestamps from notes
- Import cut times from CSV/spreadsheet
- Frame-accurate entry (1:23:45.033)
**Implementation:**
- Text input next to In/Out displays
- Parse various time formats
- Validate against video duration
---
### 9. **Project Files (.llc)** ⭐ (LOW PRIORITY - FUTURE)
Save segments to file, resume editing later.
**LosslessCut project format (JSON5):**
```json
{
"version": 1,
"cutSegments": [
{
"start": 30.5,
"end": 105.3,
"name": "Opening scene",
"tags": { "category": "intro" }
},
{
"start": 180.0,
"end": 245.7,
"name": "Action sequence"
}
]
}
```
**Benefits:**
- Resume trim session after closing app
- Share trim points with team
- Version control trim decisions
**Implementation (later):**
- Simple JSON format
- Save/load from File menu
- Auto-save to temp on changes
---
## 🎨 UX Patterns to Adopt
### 1. **Timeline Interaction Model**
- Click timeline → seek to position
- Drag timeline → scrub (live preview)
- Mouse wheel → seek forward/backward
- Shift+wheel → zoom timeline
- Right-click → context menu (set In/Out, add segment, etc.)
### 2. **Visual Feedback**
- **Current time indicator**: Vertical line with triangular markers (top/bottom)
- **Segment visualization**: Colored rectangles on timeline
- **Hover preview**: Show timestamp on hover
- **Segment labels**: Display segment names on timeline
### 3. **Segment List Panel**
LosslessCut shows sidebar with all segments:
```
┌─ Segments ─────────────────┐
│ 1. [00:30 - 01:45] Intro │ ← Selected
│ 2. [03:20 - 05:10] Action │
│ 3. [07:00 - 09:30] Ending │
└────────────────────────────┘
```
**Features:**
- Click segment → select & seek to start
- Drag to reorder
- Right-click for options (rename, delete, duplicate)
### 4. **Export Preview Dialog**
Before final export, show summary:
```
┌─ Export Preview ──────────────────────────┐
│ Export mode: Separate files │
│ Output format: MP4 (same as source) │
│ Keyframe mode: Smart cut │
│ │
│ Segments to export: │
│ 1. Intro.mp4 (0:30 - 1:45) → 1.25 min │
│ 2. Action.mp4 (3:20 - 5:10) → 1.83 min │
│ 3. Ending.mp4 (7:00 - 9:30) → 2.50 min │
│ │
│ Total output size: ~125 MB │
│ │
│ [Cancel] [Export] │
└───────────────────────────────────────────┘
```
---
## 🚀 Advanced Features (Future Inspiration)
### 1. **Scene Detection**
Auto-create segments at scene changes.
```bash
ffmpeg -i input.mp4 -filter_complex \
"select='gt(scene,0.4)',metadata=print:file=scenes.txt" \
-f null -
```
### 2. **Silence Detection**
Auto-trim silent sections.
```bash
ffmpeg -i input.mp4 -af silencedetect=noise=-30dB:d=0.5 -f null -
```
### 3. **Black Screen Detection**
Find and remove black sections.
```bash
ffmpeg -i input.mp4 -vf blackdetect=d=0.5:pix_th=0.10 -f null -
```
### 4. **Chapter Import/Export**
- Load MKV/MP4 chapters as segments
- Export segments as chapter markers
- Useful for DVD/Blu-ray rips
### 5. **Thumbnail Scrubbing**
- Generate thumbnail strip
- Show preview on timeline hover
- Faster visual navigation
---
## 📋 Implementation Roadmap for VideoTools
### Phase 1: Essential Trim (Week 1-2)
**Goal:** Basic usable trim functionality
- ✅ VT_Player keyframing API (In/Out points)
- ✅ Keyboard shortcuts (I, O, Space, ←/→)
- ✅ Timeline markers visualization
- ✅ Single segment export
- ✅ Keep/Cut mode toggle
### Phase 2: Professional Workflow (Week 3-4)
**Goal:** Multi-segment editing
- Multiple segments support
- Segment list panel
- Drag-to-reorder segments
- Merge mode
- Timeline zoom
### Phase 3: Visual Enhancements (Week 5-6)
**Goal:** Precision editing
- Waveform display
- Keyframe visualization
- Frame-accurate stepping
- Manual timecode entry
### Phase 4: Advanced Features (Week 7+)
**Goal:** Power user tools
- Project save/load
- Scene detection
- Silence detection
- Export presets
- Batch processing
---
## 🎓 Key Lessons from LosslessCut
### 1. **Start Simple, Scale Later**
LosslessCut began with basic trim, added features over time. Don't over-engineer initial release.
### 2. **Keyboard Shortcuts are Critical**
Professional users demand keyboard efficiency. Design around keyboard-first workflow.
### 3. **Visual Feedback Matters**
Users need to SEE what they're doing:
- Timeline markers
- Segment rectangles
- Waveforms
- Keyframes
### 4. **Lossless is Tricky**
Educate users about keyframes, smart cut, and when re-encode is necessary.
### 5. **FFmpeg Does the Heavy Lifting**
LosslessCut is primarily a UI wrapper around FFmpeg. Focus on great UX, let FFmpeg handle processing.
---
## 🔗 References
- **LosslessCut GitHub**: https://github.com/mifi/lossless-cut
- **Documentation**: `~/tools/lossless-cut/docs.md`
- **Source code**: `~/tools/lossless-cut/src/`
- **Keyboard shortcuts**: `~/tools/lossless-cut/README.md` (search "keyboard")
---
## 💡 VideoTools-Specific Considerations
### Advantages VideoTools Has:
1. **Native Go + Fyne**: Faster startup, smaller binary than Electron
2. **Integrated workflow**: Trim → Convert → Compare in one app
3. **Queue system**: Already have batch processing foundation
4. **Smart presets**: Leverage existing quality presets
### Unique Features to Add:
1. **Trim + Convert**: Set In/Out, choose quality preset, export in one step
2. **Compare integration**: Auto-load trimmed vs. original for verification
3. **Batch trim**: Apply same trim offsets to multiple files (e.g., remove first 30s from all)
4. **Smart defaults**: Detect intros/outros and suggest trim points
---
## ✅ Action Items for VT_Player Team
Based on LosslessCut analysis, VT_Player needs:
### Essential APIs:
1. **Keyframe API**
```go
SetInPoint(time.Duration)
SetOutPoint(time.Duration)
GetInPoint() (time.Duration, bool)
GetOutPoint() (time.Duration, bool)
ClearKeyframes()
```
2. **Timeline Visualization**
- Draw In/Out markers on timeline
- Highlight segment region between markers
- Support multiple segments (future)
3. **Keyboard Shortcuts**
- I/O for In/Out points
- ←/→ for frame stepping
- Space for play/pause
- Mouse wheel for seek
4. **Frame Navigation**
```go
StepForward() // Next frame
StepBackward() // Previous frame
GetCurrentFrame() int64
SeekToFrame(int64)
```
5. **Timeline Zoom** (Phase 2)
```go
SetZoomLevel(float64) // 1.0 to 100.0
GetZoomLevel() float64
ScrollToTime(time.Duration)
```
### Reference Implementation:
- Study LosslessCut's Timeline.tsx for zoom logic
- Study TimelineSeg.tsx for segment visualization
- Study useSegments.tsx for segment state management
---
**Document created**: 2025-12-04
**Source**: LosslessCut v3.x codebase analysis
**Next steps**: Share with VT_Player team, begin Phase 1 implementation

169
docs/TRIM_MODULE_DESIGN.md Normal file
View File

@ -0,0 +1,169 @@
# Trim Module Design
## Overview
The Trim module allows users to cut portions of video files using visual keyframe markers. Users can set In/Out points on the timeline and preview the trimmed segment before processing.
## Core Features
### 1. Visual Timeline Editing
- Load video with VT_Player
- Set **In Point** (start of keep region) - Press `I` or click button
- Set **Out Point** (end of keep region) - Press `O` or click button
- Visual markers on timeline showing trim region
- Scrub through video to find exact frames
### 2. Keyframe Controls
```
[In Point] ←────────────────→ [Out Point]
0:10 Keep Region 2:45
═══════════════════════════════════════════
```
### 3. Frame-Accurate Navigation
- `←` / `→` - Step backward/forward one frame
- `Shift+←` / `Shift+→` - Jump 1 second
- `I` - Set In Point at current position
- `O` - Set Out Point at current position
- `Space` - Play/Pause
- `C` - Clear all keyframes
### 4. Multiple Trim Modes
#### Mode 1: Keep Region (Default)
Keep video between In and Out points, discard rest.
```
Input: [─────IN════════OUT─────]
Output: [════════]
```
#### Mode 2: Cut Region
Remove video between In and Out points, keep rest.
```
Input: [─────IN════════OUT─────]
Output: [─────] [─────]
```
#### Mode 3: Multiple Segments (Advanced)
Define multiple keep/cut regions using segment list.
## UI Layout
```
┌─────────────────────────────────────────────┐
< TRIM Cyan header bar
├─────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Video Player (VT_Player) │ │
│ │ │ │
│ │ [Timeline with In/Out markers] │ │
│ │ ────I═══════════════O──────── │ │
│ │ │ │
│ │ [Play] [Pause] [In] [Out] [Clear] │ │
│ └───────────────────────────────────────┘ │
│ │
│ Trim Mode: ○ Keep Region ○ Cut Region │
│ │
│ In Point: 00:01:23.456 [Set In] [Clear] │
│ Out Point: 00:04:56.789 [Set Out] [Clear] │
│ Duration: 00:03:33.333 │
│ │
│ Output Settings: │
│ ┌─────────────────────────────────────┐ │
│ │ Format: [Same as source ▼] │ │
│ │ Re-encode: [ ] Smart copy (fast) │ │
│ │ Quality: [Source quality] │ │
│ └─────────────────────────────────────┘ │
│ │
│ [Preview Trimmed] [Add to Queue] │
│ │
└─────────────────────────────────────────────┘
← Cyan footer bar
```
## VT_Player API Requirements
### Required Methods
```go
// Keyframe management
player.SetInPoint(position time.Duration)
player.SetOutPoint(position time.Duration)
player.GetInPoint() time.Duration
player.GetOutPoint() time.Duration
player.ClearKeyframes()
// Frame-accurate navigation
player.StepForward() // Advance one frame
player.StepBackward() // Go back one frame
player.GetCurrentTime() time.Duration
player.GetFrameRate() float64
// Visual feedback
player.ShowMarkers(in, out time.Duration) // Draw on timeline
```
### Required Events
```go
// Keyboard shortcuts
- OnKeyPress('I') -> Set In Point
- OnKeyPress('O') -> Set Out Point
- OnKeyPress('→') -> Step Forward
- OnKeyPress('←') -> Step Backward
- OnKeyPress('Space') -> Play/Pause
- OnKeyPress('C') -> Clear Keyframes
```
## FFmpeg Integration
### Keep Region Mode
```bash
ffmpeg -i input.mp4 -ss 00:01:23.456 -to 00:04:56.789 -c copy output.mp4
```
### Cut Region Mode (Complex filter)
```bash
ffmpeg -i input.mp4 \
-filter_complex "[0:v]split[v1][v2]; \
[v1]trim=start=0:end=83.456[v1t]; \
[v2]trim=start=296.789[v2t]; \
[v1t][v2t]concat=n=2:v=1:a=0[outv]" \
-map [outv] output.mp4
```
### Smart Copy (Fast)
- Use `-c copy` when no re-encoding needed
- Only works at keyframe boundaries
- Show warning if In/Out not at keyframes
## Workflow
1. **Load Video** - Drag video onto Trim tile or use Load button
2. **Navigate** - Scrub or use keyboard to find start point
3. **Set In** - Press `I` or click "Set In" button
4. **Find End** - Navigate to end of region to keep
5. **Set Out** - Press `O` or click "Set Out" button
6. **Preview** - Click "Preview Trimmed" to see result
7. **Queue** - Click "Add to Queue" to process
## Technical Notes
### Precision Considerations
- Frame-accurate requires seeking to exact frame boundaries
- Display timestamps with millisecond precision (HH:MM:SS.mmm)
- VT_Player must handle fractional frame positions
- Consider GOP (Group of Pictures) boundaries for smart copy
### Performance
- Preview shouldn't require full re-encode
- Show preview using VT_Player with constrained timeline
- Cache preview segments for quick playback testing
## Future Enhancements
- Multiple trim regions in single operation
- Batch trim multiple files with same In/Out offsets
- Save trim presets (e.g., "Remove first 30s and last 10s")
- Visual waveform for audio-based trimming
- Chapter-aware trimming (trim to chapter boundaries)
## Module Color
**Cyan** - #44DDFF (already defined in modulesList)

View File

@ -0,0 +1,612 @@
# Video Metadata Guide for VideoTools
## Overview
This guide covers adding custom metadata fields to video files, NFO generation, and integration with VideoTools modules.
---
## 📦 Container Format Metadata Capabilities
### MP4 / MOV (MPEG-4)
**Metadata storage:** Atoms in `moov` container
**Standard iTunes-compatible tags:**
```
©nam - Title
©ART - Artist
©alb - Album
©day - Year
©gen - Genre
©cmt - Comment
desc - Description
©too - Encoding tool
©enc - Encoded by
cprt - Copyright
```
**Custom tags (with proper keys):**
```
----:com.apple.iTunes:DIRECTOR - Director
----:com.apple.iTunes:PERFORMERS - Performers
----:com.apple.iTunes:STUDIO - Studio/Production
----:com.apple.iTunes:SERIES - Series name
----:com.apple.iTunes:SCENE - Scene number
----:com.apple.iTunes:CATEGORIES - Categories/Tags
```
**Setting metadata with FFmpeg:**
```bash
ffmpeg -i input.mp4 -c copy \
-metadata title="Scene Title" \
-metadata artist="Performer Name" \
-metadata album="Series Name" \
-metadata date="2025" \
-metadata genre="Category" \
-metadata comment="Scene description" \
-metadata description="Full scene info" \
output.mp4
```
**Custom fields:**
```bash
ffmpeg -i input.mp4 -c copy \
-metadata:s:v:0 custom_field="Custom Value" \
output.mp4
```
---
### MKV (Matroska)
**Metadata storage:** Tags element (XML-based)
**Built-in tag support:**
```xml
<Tags>
<Tag>
<Simple>
<Name>TITLE</Name>
<String>Scene Title</String>
</Simple>
<Simple>
<Name>ARTIST</Name>
<String>Performer Name</String>
</Simple>
<Simple>
<Name>DIRECTOR</Name>
<String>Director Name</String>
</Simple>
<Simple>
<Name>STUDIO</Name>
<String>Production Studio</String>
</Simple>
<!-- Arbitrary custom tags -->
<Simple>
<Name>PERFORMERS</Name>
<String>Performer 1, Performer 2</String>
</Simple>
<Simple>
<Name>SCENE_NUMBER</Name>
<String>EP042</String>
</Simple>
<Simple>
<Name>CATEGORIES</Name>
<String>Cat1, Cat2, Cat3</String>
</Simple>
</Tag>
</Tags>
```
**Setting metadata with FFmpeg:**
```bash
ffmpeg -i input.mkv -c copy \
-metadata title="Scene Title" \
-metadata artist="Performer Name" \
-metadata director="Director" \
-metadata studio="Studio Name" \
output.mkv
```
**Advantages of MKV:**
- Unlimited custom tags (any key-value pairs)
- Can attach files (NFO, images, scripts)
- Hierarchical metadata structure
- Best for archival/preservation
---
### MOV (QuickTime)
Same as MP4 (both use MPEG-4 structure), but QuickTime supports additional proprietary tags.
---
## 📄 NFO File Format
NFO (Info) files are plain text/XML files that contain detailed metadata. Common in media libraries (Kodi, Plex, etc.).
### NFO Format for Movies:
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<movie>
<title>Scene Title</title>
<originaltitle>Original Title</originaltitle>
<sorttitle>Sort Title</sorttitle>
<year>2025</year>
<releasedate>2025-12-04</releasedate>
<plot>Scene description and plot summary</plot>
<runtime>45</runtime> <!-- minutes -->
<studio>Production Studio</studio>
<director>Director Name</director>
<actor>
<name>Performer 1</name>
<role>Role 1</role>
<thumb>path/to/performer1.jpg</thumb>
</actor>
<actor>
<name>Performer 2</name>
<role>Role 2</role>
</actor>
<genre>Category 1</genre>
<genre>Category 2</genre>
<tag>Tag1</tag>
<tag>Tag2</tag>
<rating>8.5</rating>
<userrating>9.0</userrating>
<fileinfo>
<streamdetails>
<video>
<codec>h264</codec>
<width>1920</width>
<height>1080</height>
<durationinseconds>2700</durationinseconds>
<aspect>1.777778</aspect>
</video>
<audio>
<codec>aac</codec>
<channels>2</channels>
</audio>
</streamdetails>
</fileinfo>
<!-- Custom fields -->
<series>Series Name</series>
<episode>42</episode>
<scene_number>EP042</scene_number>
</movie>
```
### NFO Format for TV Episodes:
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<episodedetails>
<title>Episode Title</title>
<showtitle>Series Name</showtitle>
<season>1</season>
<episode>5</episode>
<aired>2025-12-04</aired>
<plot>Episode description</plot>
<runtime>30</runtime>
<director>Director Name</director>
<actor>
<name>Performer 1</name>
<role>Character</role>
</actor>
<studio>Production Studio</studio>
<rating>8.0</rating>
</episodedetails>
```
---
## 🛠️ VideoTools Integration Plan
### Module: **Metadata Editor** (New Module)
**Purpose:** Edit video metadata and generate NFO files
**Features:**
1. **Load video** → Extract existing metadata
2. **Edit fields** → Standard + custom fields
3. **NFO generation** → Auto-generate from metadata
4. **Embed metadata** → Write back to video file (lossless remux)
5. **Batch metadata** → Apply same metadata to multiple files
6. **Templates** → Save/load metadata templates
**UI Layout:**
```
┌─────────────────────────────────────────────────┐
< METADATA Purple header
├─────────────────────────────────────────────────┤
│ │
│ File: scene_042.mp4 │
│ │
│ ┌─ Basic Info ──────────────────────────────┐ │
│ │ Title: [________________] │ │
│ │ Studio: [________________] │ │
│ │ Series: [________________] │ │
│ │ Scene #: [____] │ │
│ │ Date: [2025-12-04] │ │
│ │ Duration: 45:23 (auto) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌─ Performers ────────────────────────────────┐ │
│ │ Performer 1: [________________] [X] │ │
│ │ Performer 2: [________________] [X] │ │
│ │ [+ Add Performer] │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌─ Categories/Tags ──────────────────────────┐ │
│ │ [Tag1] [Tag2] [Tag3] [+ Add] │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌─ Description ────────────────────────────────┐ │
│ │ [Multiline text area for plot/description] │ │
│ │ │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌─ Custom Fields ────────────────────────────┐ │
│ │ Director: [________________] │ │
│ │ IMDB ID: [________________] │ │
│ │ Custom 1: [________________] │ │
│ │ [+ Add Field] │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ [Generate NFO] [Embed in Video] [Save Template]│
│ │
└─────────────────────────────────────────────────┘
```
---
## 🔧 Implementation Details
### 1. Reading Metadata
**Using FFprobe:**
```bash
ffprobe -v quiet -print_format json -show_format input.mp4
# Output includes:
{
"format": {
"filename": "input.mp4",
"tags": {
"title": "Scene Title",
"artist": "Performer Name",
"album": "Series Name",
"date": "2025",
"genre": "Category",
"comment": "Description"
}
}
}
```
**Go implementation:**
```go
type VideoMetadata struct {
Title string
Studio string
Series string
SceneNumber string
Date string
Performers []string
Director string
Categories []string
Description string
CustomFields map[string]string
}
func probeMetadata(path string) (*VideoMetadata, error) {
cmd := exec.Command("ffprobe",
"-v", "quiet",
"-print_format", "json",
"-show_format",
path,
)
output, err := cmd.Output()
if err != nil {
return nil, err
}
var result struct {
Format struct {
Tags map[string]string `json:"tags"`
} `json:"format"`
}
json.Unmarshal(output, &result)
metadata := &VideoMetadata{
Title: result.Format.Tags["title"],
Studio: result.Format.Tags["studio"],
Series: result.Format.Tags["album"],
Date: result.Format.Tags["date"],
Categories: strings.Split(result.Format.Tags["genre"], ", "),
Description: result.Format.Tags["comment"],
CustomFields: make(map[string]string),
}
return metadata, nil
}
```
---
### 2. Writing Metadata
**Using FFmpeg (lossless remux):**
```go
func embedMetadata(inputPath string, metadata *VideoMetadata, outputPath string) error {
args := []string{
"-i", inputPath,
"-c", "copy", // Lossless copy
}
// Add standard tags
if metadata.Title != "" {
args = append(args, "-metadata", fmt.Sprintf("title=%s", metadata.Title))
}
if metadata.Studio != "" {
args = append(args, "-metadata", fmt.Sprintf("studio=%s", metadata.Studio))
}
if metadata.Series != "" {
args = append(args, "-metadata", fmt.Sprintf("album=%s", metadata.Series))
}
if metadata.Date != "" {
args = append(args, "-metadata", fmt.Sprintf("date=%s", metadata.Date))
}
if len(metadata.Categories) > 0 {
args = append(args, "-metadata", fmt.Sprintf("genre=%s", strings.Join(metadata.Categories, ", ")))
}
if metadata.Description != "" {
args = append(args, "-metadata", fmt.Sprintf("comment=%s", metadata.Description))
}
// Add custom fields
for key, value := range metadata.CustomFields {
args = append(args, "-metadata", fmt.Sprintf("%s=%s", key, value))
}
args = append(args, outputPath)
cmd := exec.Command("ffmpeg", args...)
return cmd.Run()
}
```
---
### 3. Generating NFO
```go
func generateNFO(metadata *VideoMetadata, videoPath string) (string, error) {
nfo := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<movie>
<title>` + escapeXML(metadata.Title) + `</title>
<studio>` + escapeXML(metadata.Studio) + `</studio>
<series>` + escapeXML(metadata.Series) + `</series>
<year>` + metadata.Date + `</year>
<plot>` + escapeXML(metadata.Description) + `</plot>
`
// Add performers
for _, performer := range metadata.Performers {
nfo += ` <actor>
<name>` + escapeXML(performer) + `</name>
</actor>
`
}
// Add categories/genres
for _, category := range metadata.Categories {
nfo += ` <genre>` + escapeXML(category) + `</genre>
`
}
// Add custom fields
for key, value := range metadata.CustomFields {
nfo += ` <` + key + `>` + escapeXML(value) + `</` + key + `>
`
}
nfo += `</movie>`
// Save to file (same name as video + .nfo extension)
nfoPath := strings.TrimSuffix(videoPath, filepath.Ext(videoPath)) + ".nfo"
return nfoPath, os.WriteFile(nfoPath, []byte(nfo), 0644)
}
func escapeXML(s string) string {
s = strings.ReplaceAll(s, "&", "&amp;")
s = strings.ReplaceAll(s, "<", "&lt;")
s = strings.ReplaceAll(s, ">", "&gt;")
s = strings.ReplaceAll(s, "\"", "&quot;")
s = strings.ReplaceAll(s, "'", "&apos;")
return s
}
```
---
### 4. Attaching NFO to MKV
MKV supports embedded attachments (like NFO files):
```bash
# Attach NFO file to MKV
mkvpropedit video.mkv --add-attachment scene_info.nfo --attachment-mime-type text/plain --attachment-name "scene_info.nfo"
# Or with FFmpeg (re-mux required)
ffmpeg -i input.mkv -i scene_info.nfo -c copy \
-attach scene_info.nfo -metadata:s:t:0 mimetype=text/plain \
output.mkv
```
**Go implementation:**
```go
func attachNFOtoMKV(mkvPath string, nfoPath string) error {
cmd := exec.Command("mkvpropedit", mkvPath,
"--add-attachment", nfoPath,
"--attachment-mime-type", "text/plain",
"--attachment-name", filepath.Base(nfoPath),
)
return cmd.Run()
}
```
---
## 📋 Metadata Templates
Allow users to save metadata templates for batch processing.
**Template JSON:**
```json
{
"name": "Studio XYZ Default Template",
"fields": {
"studio": "Studio XYZ",
"series": "Series Name",
"categories": ["Category1", "Category2"],
"custom_fields": {
"director": "John Doe",
"producer": "Jane Smith"
}
}
}
```
**Usage:**
1. User creates template with common studio/series info
2. Load template when editing new video
3. Only fill in unique fields (title, performers, date, scene #)
4. Batch apply template to multiple files
---
## 🎯 Use Cases
### 1. Adult Content Library
```
Title: "Scene Title"
Studio: "Production Studio"
Series: "Series Name - Season 2"
Scene Number: "EP042"
Performers: ["Performer A", "Performer B"]
Director: "Director Name"
Categories: ["Category1", "Category2", "Category3"]
Date: "2025-12-04"
Description: "Full scene description and plot"
```
### 2. Personal Video Archive
```
Title: "Birthday Party 2025"
Event: "John's 30th Birthday"
Location: "Los Angeles, CA"
People: ["John", "Sarah", "Mike", "Emily"]
Date: "2025-06-15"
Description: "John's surprise birthday party"
```
### 3. Movie Collection
```
Title: "Movie Title"
Original Title: "原題"
Director: "Christopher Nolan"
Year: "2024"
IMDB ID: "tt1234567"
Actors: ["Actor 1", "Actor 2"]
Genre: ["Sci-Fi", "Thriller"]
Rating: "8.5/10"
```
---
## 🔌 Integration with Existing Modules
### Convert Module
- **Checkbox**: "Preserve metadata" (default: on)
- **Checkbox**: "Copy metadata from source" (default: on)
- Allow adding/editing metadata before conversion
### Inspect Module
- **Add tab**: "Metadata" to view/edit metadata
- Show both standard and custom fields
- Quick edit without re-encoding
### Compare Module
- **Add**: "Compare Metadata" button
- Show metadata diff between two files
- Highlight differences
---
## 🚀 Implementation Roadmap
### Phase 1: Basic Metadata Support (Week 1)
- Read metadata with ffprobe
- Display in Inspect module
- Edit basic fields (title, artist, date, comment)
- Write metadata with FFmpeg (lossless)
### Phase 2: NFO Generation (Week 2)
- NFO file generation
- Save alongside video file
- Load NFO and populate fields
- Template system
### Phase 3: Advanced Metadata (Week 3)
- Custom fields support
- Performers list
- Categories/tags
- Metadata Editor module UI
### Phase 4: Batch & Templates (Week 4)
- Metadata templates
- Batch apply to multiple files
- MKV attachment support (embed NFO)
---
## 📚 References
### FFmpeg Metadata Documentation
- https://ffmpeg.org/ffmpeg-formats.html#Metadata
- https://wiki.multimedia.cx/index.php/FFmpeg_Metadata
### NFO Format Standards
- Kodi NFO: https://kodi.wiki/view/NFO_files
- Plex Agents: https://support.plex.tv/articles/
### Matroska Tags
- https://www.matroska.org/technical/specs/tagging/index.html
---
## ✅ Summary
**Yes, you can absolutely store custom metadata in video files!**
**Best format for rich metadata:** MKV (unlimited custom tags + file attachments)
**Most compatible:** MP4/MOV (iTunes tags work in QuickTime, VLC, etc.)
**Recommended approach for VideoTools:**
1. Support both embedded metadata (in video file) AND sidecar NFO files
2. MKV: Embed NFO as attachment + metadata tags
3. MP4: Metadata tags + separate .nfo file
4. Allow users to choose what metadata to embed
5. Generate NFO for media center compatibility (Kodi, Plex, Jellyfin)
**Next steps:**
1. Add basic metadata reading to `probeVideo()` function
2. Add metadata display to Inspect module
3. Create Metadata Editor module
4. Implement NFO generation
5. Add metadata templates
This would be a killer feature for VideoTools! 🚀

View File

@ -1,12 +1,16 @@
# Video Player Fork Plan
## Status: COMPLETED ✅
**VT_Player has been forked as a separate project for independent development.**
## Overview
The video player component will be extracted into a separate project to allow independent development and improvement of video playback controls while keeping VideoTools focused on video processing.
The video player component has been extracted into a separate project (VT_Player) to allow independent development and improvement of video playback controls while keeping VideoTools focused on video processing.
## Current Player Integration
The player is currently embedded in VideoTools at:
- `internal/player/` - Player implementation
- `main.go` - Player state and controls in Convert module
The player is used in VideoTools at:
- Convert module - Video preview and playback
- Compare module - Side-by-side video comparison (as of dev13)
- Inspect module - Single video playback with metadata (as of dev13)
- Preview frame display
- Playback controls (play/pause, seek, volume)
@ -19,17 +23,20 @@ The player is currently embedded in VideoTools at:
- Can be used by other projects
### 2. Improved Controls
Current limitations to address:
Features to develop in VT_Player:
- **Keyframing** - Mark in/out points for trimming and chapter creation
- Tighten up video controls
- Better seek bar with thumbnails on hover
- Improved timeline scrubbing
- Keyboard shortcuts for playback
- Frame-accurate stepping
- Playback speed controls
- Frame-accurate stepping (←/→ keys for frame-by-frame)
- Playback speed controls (0.25x to 2x)
- Better volume control UI
- Timeline markers for chapters
- Visual in/out point indicators
### 3. Clean API
The forked player should expose a clean API:
VT_Player should expose a clean API for VideoTools integration:
```go
type Player interface {
Load(path string) error
@ -38,36 +45,42 @@ type Player interface {
Seek(position time.Duration)
GetFrame(position time.Duration) (image.Image, error)
SetVolume(level float64)
// Keyframing support for Trim/Chapter modules
SetInPoint(position time.Duration)
SetOutPoint(position time.Duration)
GetInPoint() time.Duration
GetOutPoint() time.Duration
ClearKeyframes()
Close()
}
```
## Migration Strategy
## VT_Player Development Strategy
### Phase 1: Extract to Separate Module
1. Create new repository: `github.com/yourusername/fyne-videoplayer`
2. Copy `internal/player/` to new repo
3. Extract player dependencies
4. Create clean API surface
5. Add comprehensive tests
### Phase 1: Core Player Features ✅
- [x] Basic playback controls (play/pause/seek)
- [x] Volume control
- [x] Frame preview display
- [x] Integration with VideoTools modules
### Phase 2: Update VideoTools
1. Import fyne-videoplayer as dependency
2. Replace internal/player with external package
3. Update player instantiation
4. Verify all playback features work
5. Remove old internal/player code
### Phase 2: Enhanced Controls (Current Focus)
Priority features for Trim/Chapter module integration:
- [ ] **Keyframe markers** - Set In/Out points visually on timeline
- [ ] **Frame-accurate stepping** - ←/→ keys for frame-by-frame navigation
- [ ] **Visual timeline with markers** - Show In/Out points on seek bar
- [ ] **Keyboard shortcuts** - I (in), O (out), Space (play/pause), ←/→ (step)
- [ ] **Export keyframe data** - Return In/Out timestamps to VideoTools
### Phase 3: Enhance Player (Post-Fork)
Features to add after fork:
### Phase 3: Advanced Features (Future)
- [ ] Thumbnail preview on seek bar hover
- [ ] Frame-accurate stepping (←/→ keys)
- [ ] Playback speed controls (0.25x to 2x)
- [ ] Improved volume slider
- [ ] Keyboard shortcuts (Space, K, J, L, etc.)
- [ ] Timeline markers
- [ ] Improved volume slider with visual feedback
- [ ] Chapter markers on timeline
- [ ] Subtitle support
- [ ] Multi-audio track switching
- [ ] Zoom timeline for precision editing
## Technical Considerations
@ -89,18 +102,37 @@ Player must work on:
- Low CPU usage during playback
- Fast seeking
## Timeline
1. **Week 1-2**: Extract player code, create repo, clean API
2. **Week 3**: Integration testing, update VideoTools
3. **Week 4+**: Enhanced controls and features
## VideoTools Module Integration
### Modules Using VT_Player
1. **Convert Module** - Preview video before conversion
2. **Compare Module** - Side-by-side video playback for comparison
3. **Inspect Module** - Single video playback with detailed metadata
4. **Trim Module** (planned) - Keyframe-based trimming with In/Out points
5. **Chapter Module** (planned) - Mark chapter points on timeline
### Integration Requirements for Trim/Chapter
The Trim and Chapter modules will require:
- Keyframe API to set In/Out points
- Visual markers on timeline showing trim regions
- Frame-accurate seeking for precise cuts
- Ability to export timestamp data for FFmpeg commands
- Preview of trimmed segment before processing
## Benefits
- **VideoTools**: Leaner codebase, focus on processing
- **Player**: Independent evolution, reusable component
- **Users**: Better video controls, more reliable playback
- **Developers**: Easier to contribute to either project
- **VideoTools**: Leaner codebase, focus on video processing
- **VT_Player**: Independent evolution, reusable component, dedicated feature development
- **Users**: Professional-grade video controls, precise editing capabilities
- **Developers**: Easier to contribute, clear separation of concerns
## Development Philosophy
- **VT_Player**: Focus on playback, navigation, and visual controls
- **VideoTools**: Focus on video processing, encoding, and batch operations
- Clean API boundary allows independent versioning
- VT_Player features can be tested independently before VideoTools integration
## Notes
- Keep player dependency minimal in VideoTools
- Player should be optional - frame display can work without playback
- Consider using player in Compare module for side-by-side playback (future)
- VT_Player repo: Separate project with independent development cycle
- VideoTools will import VT_Player as external dependency
- Keyframing features are priority for Trim/Chapter module development
- Compare module demonstrates successful multi-player integration

View File

@ -0,0 +1,373 @@
# VT_Player Integration Notes for Lead Developer
## Project Context
**VideoTools Repository**: https://git.leaktechnologies.dev/Leak_Technologies/VideoTools.git
**VT_Player**: Forked video player component for independent development
VT_Player was forked from VideoTools to enable dedicated development of video playback controls and features without impacting the main VideoTools codebase.
## Current Integration Points
### VideoTools Modules Using VT_Player
1. **Convert Module** - Preview video before/during conversion
2. **Compare Module** - Side-by-side video comparison (2 players)
3. **Inspect Module** - Single video playback with metadata display
4. **Compare Fullscreen** - Larger side-by-side view (planned: synchronized playback)
### Current VT_Player Usage Pattern
```go
// VideoTools calls buildVideoPane() which creates player
videoPane := buildVideoPane(state, fyne.NewSize(320, 180), videoSource, updateCallback)
// buildVideoPane internally:
// - Creates player.Controller
// - Sets up playback controls
// - Returns fyne.CanvasObject with player UI
```
## Priority Features Needed in VT_Player
### 1. Keyframing API (HIGHEST PRIORITY)
**Required for**: Trim Module, Chapter Module
```go
// Proposed API
type KeyframeController interface {
// Set keyframe markers
SetInPoint(position time.Duration) error
SetOutPoint(position time.Duration) error
ClearInPoint()
ClearOutPoint()
ClearAllKeyframes()
// Get keyframe data
GetInPoint() (time.Duration, bool) // Returns position and hasInPoint
GetOutPoint() (time.Duration, bool)
GetSegmentDuration() time.Duration // Duration between In and Out
// Visual feedback
ShowKeyframeMarkers(show bool) // Toggle marker visibility on timeline
HighlightSegment(in, out time.Duration) // Highlight region between markers
}
```
**Use Case**: User scrubs video, presses `I` to set In point, scrubs to end, presses `O` to set Out point. Visual markers show on timeline. VideoTools reads timestamps for FFmpeg trim command.
### 2. Frame-Accurate Navigation (HIGH PRIORITY)
**Required for**: Trim Module, Compare sync
```go
type FrameNavigationController interface {
// Step through video frame-by-frame
StepForward() error // Advance exactly 1 frame
StepBackward() error // Go back exactly 1 frame
// Frame info
GetCurrentFrame() int64 // Current frame number
GetFrameAtTime(time.Duration) int64 // Frame number at timestamp
GetTimeAtFrame(int64) time.Duration // Timestamp of frame number
GetTotalFrames() int64
// Seek to exact frame
SeekToFrame(frameNum int64) error
}
```
**Use Case**: User finds exact frame for cut point using arrow keys (←/→), sets In/Out markers precisely.
### 3. Synchronized Playback API (MEDIUM PRIORITY)
**Required for**: Compare Fullscreen, Compare Module sync
```go
type SyncController interface {
// Link two players together
SyncWith(otherPlayer player.Controller) error
Unsync()
IsSynced() bool
GetSyncMaster() player.Controller
// Callbacks for sync events
OnPlayStateChanged(callback func(playing bool))
OnPositionChanged(callback func(position time.Duration))
// Sync with offset (for videos that don't start at same time)
SetSyncOffset(offset time.Duration)
GetSyncOffset() time.Duration
}
```
**Use Case**: Compare module loads two videos. User clicks "Play Both" button. Both players play in sync. When one player is paused/seeked, other follows.
### 4. Playback Speed Control (MEDIUM PRIORITY)
**Required for**: Trim Module, general UX improvement
```go
type PlaybackSpeedController interface {
SetPlaybackSpeed(speed float64) error // 0.25x to 2.0x
GetPlaybackSpeed() float64
GetSupportedSpeeds() []float64 // [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0]
}
```
**Use Case**: User slows playback to 0.25x to find exact frame for trim point.
## Integration Architecture
### Current Pattern
```
VideoTools (main.go)
└─> buildVideoPane()
└─> player.New()
└─> player.Controller interface
└─> Returns fyne.CanvasObject
```
### Proposed Enhanced Pattern
```
VideoTools (main.go)
└─> buildVideoPane()
└─> player.NewEnhanced()
├─> player.Controller (basic playback)
├─> player.KeyframeController (trim support)
├─> player.FrameNavigationController (frame stepping)
├─> player.SyncController (multi-player sync)
└─> player.PlaybackSpeedController (speed control)
└─> Returns fyne.CanvasObject
```
### Backward Compatibility
- Keep existing `player.Controller` interface unchanged
- Add new optional interfaces
- VideoTools checks if player implements enhanced interfaces:
```go
if keyframer, ok := player.(KeyframeController); ok {
// Use keyframe features
}
```
## Technical Requirements
### 1. Timeline Visual Enhancements
Current timeline needs:
- **In/Out Point Markers**: Visual indicators (⬇️ symbols or colored bars)
- **Segment Highlight**: Show region between In and Out with different color
- **Frame Number Display**: Show current frame number alongside timestamp
- **Marker Drag Support**: Allow dragging markers to adjust In/Out points
### 2. Keyboard Shortcuts
Essential shortcuts for VT_Player:
| Key | Action | Notes |
|-----|--------|-------|
| `Space` | Play/Pause | Standard |
| `←` | Step backward 1 frame | Frame-accurate |
| `→` | Step forward 1 frame | Frame-accurate |
| `Shift+←` | Jump back 1 second | Quick navigation |
| `Shift+→` | Jump forward 1 second | Quick navigation |
| `I` | Set In Point | Trim support |
| `O` | Set Out Point | Trim support |
| `C` | Clear keyframes | Reset markers |
| `K` | Pause | Video editor standard |
| `J` | Rewind | Video editor standard |
| `L` | Fast forward | Video editor standard |
| `0-9` | Seek to % | 0=start, 5=50%, 9=90% |
### 3. Performance Considerations
- **Frame stepping**: Must be instant, no lag
- **Keyframe display**: Update timeline without stuttering
- **Sync**: Maximum 1-frame drift between synced players
- **Memory**: Don't load entire video into RAM for frame navigation
### 4. FFmpeg Integration
VT_Player should expose frame-accurate timestamps that VideoTools can use:
```bash
# Example: VideoTools gets In=83.456s, Out=296.789s from VT_Player
ffmpeg -ss 83.456 -to 296.789 -i input.mp4 -c copy output.mp4
```
Frame-accurate seeking requires:
- Seek to nearest keyframe before target
- Decode frames until exact target reached
- Display correct frame with minimal latency
## Data Flow Examples
### Trim Module Workflow
```
1. User loads video in Trim module
2. VideoTools creates VT_Player with keyframe support
3. User navigates with arrow keys (VT_Player handles frame stepping)
4. User presses 'I' → VT_Player sets In point marker
5. User navigates to end point
6. User presses 'O' → VT_Player sets Out point marker
7. User clicks "Preview Trim" → VT_Player plays segment between markers
8. User clicks "Add to Queue"
9. VideoTools reads keyframes: in = player.GetInPoint(), out = player.GetOutPoint()
10. VideoTools builds FFmpeg command with timestamps
11. FFmpeg trims video
```
### Compare Sync Workflow
```
1. User loads 2 videos in Compare module
2. VideoTools creates 2 VT_Player instances
3. User clicks "Play Both"
4. VideoTools calls: player1.SyncWith(player2)
5. VideoTools calls: player1.Play()
6. VT_Player automatically plays player2 in sync
7. User pauses player1 → VT_Player pauses player2
8. User seeks player1 → VT_Player seeks player2 to same position
```
## Testing Requirements
VT_Player should include tests for:
1. **Keyframe Accuracy**
- Set In/Out points, verify exact timestamps returned
- Clear markers, verify they're removed
- Test edge cases (In > Out, negative times, beyond duration)
2. **Frame Navigation**
- Step forward/backward through entire video
- Verify frame numbers are sequential
- Test at video start (can't go back) and end (can't go forward)
3. **Sync Reliability**
- Play two videos for 30 seconds, verify max drift < 1 frame
- Pause/seek operations propagate correctly
- Unsync works properly
4. **Performance**
- Frame step latency < 50ms
- Timeline marker updates < 16ms (60fps)
- Memory usage stable during long playback sessions
## Communication Protocol
### VideoTools → VT_Player
VideoTools will request features through interface methods:
```go
// Example: VideoTools wants to enable trim mode
if trimmer, ok := player.(TrimController); ok {
trimmer.EnableTrimMode(true)
trimmer.OnInPointSet(func(t time.Duration) {
// Update VideoTools UI to show In point timestamp
})
trimmer.OnOutPointSet(func(t time.Duration) {
// Update VideoTools UI to show Out point timestamp
})
}
```
### VT_Player → VideoTools
VT_Player communicates state changes through callbacks:
```go
player.OnPlaybackStateChanged(func(playing bool) {
// VideoTools updates UI (play button ↔ pause button)
})
player.OnPositionChanged(func(position time.Duration) {
// VideoTools updates position display
})
player.OnKeyframeSet(func(markerType string, position time.Duration) {
// VideoTools logs keyframe for FFmpeg command
})
```
## Migration Strategy
### Phase 1: Core API (Immediate)
- Define interfaces for keyframe, frame nav, sync
- Implement basic keyframe markers (In/Out points)
- Add frame stepping (←/→ keys)
- Document API for VideoTools integration
### Phase 2: Visual Enhancements (Week 2)
- Enhanced timeline with marker display
- Segment highlighting between In/Out
- Frame number display
- Keyboard shortcuts
### Phase 3: Sync Features (Week 3)
- Implement synchronized playback API
- Master-slave pattern for linked players
- Offset compensation for non-aligned videos
### Phase 4: Advanced Features (Week 4+)
- Playback speed control
- Timeline zoom for precision editing
- Thumbnail preview on hover
- Chapter markers
## Notes for VT_Player Developer
1. **Keep backward compatibility**: Existing VideoTools code using basic player.Controller should continue working
2. **Frame-accurate is critical**: Trim module requires exact frame positioning. Off-by-one frame errors are unacceptable.
3. **Performance over features**: Frame stepping must be instant. Users will hold arrow keys to scrub through video.
4. **Visual feedback matters**: Keyframe markers must be immediately visible. Timeline updates should be smooth.
5. **Cross-platform testing**: VT_Player must work on Linux (GNOME/X11/Wayland), macOS, and Windows
6. **FFmpeg integration**: VT_Player doesn't run FFmpeg, but must provide precise timestamps that VideoTools can pass to FFmpeg
7. **Minimize dependencies**: Keep VT_Player focused on playback/navigation. VideoTools handles video processing.
## Questions to Consider
1. **Keyframe storage**: Should keyframes be stored in VT_Player or passed back to VideoTools immediately?
2. **Sync drift handling**: If synced players drift apart, which one is "correct"? Should we periodically resync?
3. **Frame stepping during playback**: Can user step frame-by-frame while video is playing, or must they pause first?
4. **Memory management**: For long videos (hours), how do we efficiently support frame-accurate navigation without excessive memory?
5. **Hardware acceleration**: Should frame stepping use GPU decoding, or is CPU sufficient for single frames?
## Current VideoTools Status
### Working Modules
- ✅ Convert - Video conversion with preview
- ✅ Compare - Side-by-side comparison (basic)
- ✅ Inspect - Single video with metadata
- ✅ Compare Fullscreen - Larger view (sync placeholders added)
### Planned Modules Needing VT_Player Features
- ⏳ Trim - **Needs**: Keyframing + frame navigation
- ⏳ Chapter - **Needs**: Multiple keyframe markers on timeline
- ⏳ Merge - May need synchronized preview of multiple clips
### Auto-Compare Feature (NEW)
- ✅ Checkbox in Convert module: "Compare After"
- ✅ After conversion completes, automatically loads:
- File 1 (Original) = source video
- File 2 (Converted) = output video
- ✅ User can immediately inspect conversion quality
## Contact & Coordination
For questions about VideoTools integration:
- Review this document
- Check `/docs/VIDEO_PLAYER_FORK.md` for fork strategy
- Check `/docs/TRIM_MODULE_DESIGN.md` for detailed trim module requirements
- Check `/docs/COMPARE_FULLSCREEN.md` for sync requirements
VideoTools will track VT_Player changes and update integration code as new features become available.

View File

@ -330,6 +330,22 @@ func (q *Queue) processJobs() {
return
}
// Check if there's already a running job (only process one at a time)
hasRunningJob := false
for _, job := range q.jobs {
if job.Status == JobStatusRunning {
hasRunningJob = true
break
}
}
// If a job is already running, wait and check again later
if hasRunningJob {
q.mu.Unlock()
time.Sleep(500 * time.Millisecond)
continue
}
// Find highest priority pending job
var nextJob *Job
highestPriority := -1

View File

@ -4,6 +4,7 @@ import (
"fmt"
"image/color"
"strings"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
@ -49,11 +50,13 @@ func (m *MonoTheme) Size(name fyne.ThemeSizeName) float32 {
// ModuleTile is a clickable tile widget for module selection
type ModuleTile struct {
widget.BaseWidget
label string
color color.Color
enabled bool
onTapped func()
onDropped func([]fyne.URI)
label string
color color.Color
enabled bool
onTapped func()
onDropped func([]fyne.URI)
flashing bool
draggedOver bool
}
// NewModuleTile creates a new module tile
@ -72,15 +75,40 @@ func NewModuleTile(label string, col color.Color, enabled bool, tapped func(), d
// DraggedOver implements desktop.Droppable interface
func (m *ModuleTile) DraggedOver(pos fyne.Position) {
logging.Debug(logging.CatUI, "DraggedOver tile=%s enabled=%v pos=%v", m.label, m.enabled, pos)
if m.enabled {
m.draggedOver = true
m.Refresh()
}
}
// DraggedOut is called when drag leaves the tile
func (m *ModuleTile) DraggedOut() {
logging.Debug(logging.CatUI, "DraggedOut tile=%s", m.label)
m.draggedOver = false
m.Refresh()
}
// Dropped implements desktop.Droppable interface
func (m *ModuleTile) Dropped(pos fyne.Position, items []fyne.URI) {
fmt.Printf("[DROPTILE] Dropped on tile=%s enabled=%v itemCount=%d\n", m.label, m.enabled, len(items))
logging.Debug(logging.CatUI, "Dropped on tile=%s enabled=%v items=%v", m.label, m.enabled, items)
// Reset dragged over state
m.draggedOver = false
if m.enabled && m.onDropped != nil {
fmt.Printf("[DROPTILE] Calling callback for %s\n", m.label)
logging.Debug(logging.CatUI, "Calling onDropped callback for %s", m.label)
// Trigger flash animation
m.flashing = true
m.Refresh()
// Reset flash after 300ms
time.AfterFunc(300*time.Millisecond, func() {
m.flashing = false
m.Refresh()
})
m.onDropped(items)
} else {
fmt.Printf("[DROPTILE] Drop IGNORED on %s: enabled=%v hasCallback=%v\n", m.label, m.enabled, m.onDropped != nil)
logging.Debug(logging.CatUI, "Drop ignored: enabled=%v hasCallback=%v", m.enabled, m.onDropped != nil)
}
}
@ -145,6 +173,22 @@ func (r *moduleTileRenderer) MinSize() fyne.Size {
func (r *moduleTileRenderer) Refresh() {
r.bg.FillColor = r.tile.color
// Apply visual feedback based on state
if r.tile.flashing {
// Flash animation - white outline
r.bg.StrokeColor = color.White
r.bg.StrokeWidth = 3
} else if r.tile.draggedOver {
// Dragging over - cyan/blue outline to indicate drop zone
r.bg.StrokeColor = color.NRGBA{R: 0, G: 200, B: 255, A: 255}
r.bg.StrokeWidth = 3
} else {
// Normal state
r.bg.StrokeColor = GridColor
r.bg.StrokeWidth = 1
}
r.bg.Refresh()
r.label.Text = r.tile.label
r.label.Refresh()

View File

@ -34,18 +34,25 @@ func BuildMainMenu(modules []ModuleInfo, onModuleClick func(string), onModuleDro
)
var tileObjects []fyne.CanvasObject
for _, mod := range modules {
modID := mod.ID // Capture for closure
for i := range modules {
mod := modules[i] // Create new variable for this iteration
modID := mod.ID // Capture for closure
var tapFunc func()
var dropFunc func([]fyne.URI)
if mod.Enabled {
// Create new closure with properly captured modID
id := modID // Explicit capture
tapFunc = func() {
onModuleClick(modID)
onModuleClick(id)
}
dropFunc = func(items []fyne.URI) {
onModuleDrop(modID, items)
fmt.Printf("[MAINMENU] dropFunc called for module=%s itemCount=%d\n", id, len(items))
logging.Debug(logging.CatUI, "MainMenu dropFunc called for module=%s itemCount=%d", id, len(items))
onModuleDrop(id, items)
}
}
fmt.Printf("[MAINMENU] Creating tile for module=%s enabled=%v hasDropFunc=%v\n", modID, mod.Enabled, dropFunc != nil)
logging.Debug(logging.CatUI, "Creating tile for module=%s enabled=%v hasDropFunc=%v", modID, mod.Enabled, dropFunc != nil)
tileObjects = append(tileObjects, buildModuleTile(mod, tapFunc, dropFunc))
}

View File

@ -228,7 +228,22 @@ func getStatusText(job *queue.Job) string {
if job.StartedAt != nil {
elapsed = fmt.Sprintf(" | Elapsed: %s", time.Since(*job.StartedAt).Round(time.Second))
}
return fmt.Sprintf("Status: Running | Progress: %.1f%%%s", job.Progress, elapsed)
// Add FPS and speed info if available in Config
var extras string
if job.Config != nil {
if fps, ok := job.Config["fps"].(float64); ok && fps > 0 {
extras += fmt.Sprintf(" | %.0f fps", fps)
}
if speed, ok := job.Config["speed"].(float64); ok && speed > 0 {
extras += fmt.Sprintf(" | %.2fx", speed)
}
if etaDuration, ok := job.Config["eta"].(time.Duration); ok && etaDuration > 0 {
extras += fmt.Sprintf(" | ETA %s", etaDuration.Round(time.Second))
}
}
return fmt.Sprintf("Status: Running | Progress: %.1f%%%s%s", job.Progress, elapsed, extras)
case queue.JobStatusPaused:
return "Status: Paused"
case queue.JobStatusCompleted:
@ -259,23 +274,23 @@ func buildModuleBadge(t queue.JobType) fyne.CanvasObject {
return container.NewMax(bg, container.NewCenter(label))
}
// moduleColor maps job types to distinct colors for quick visual scanning.
// moduleColor maps job types to distinct colors matching the main module colors
func moduleColor(t queue.JobType) color.Color {
switch t {
case queue.JobTypeConvert:
return color.RGBA{R: 76, G: 232, B: 112, A: 255} // green
return color.RGBA{R: 139, G: 68, B: 255, A: 255} // Violet (#8B44FF)
case queue.JobTypeMerge:
return color.RGBA{R: 68, G: 136, B: 255, A: 255} // blue
return color.RGBA{R: 68, G: 136, B: 255, A: 255} // Blue (#4488FF)
case queue.JobTypeTrim:
return color.RGBA{R: 255, G: 193, B: 7, A: 255} // amber
return color.RGBA{R: 68, G: 221, B: 255, A: 255} // Cyan (#44DDFF)
case queue.JobTypeFilter:
return color.RGBA{R: 160, G: 86, B: 255, A: 255} // purple
return color.RGBA{R: 68, G: 255, B: 136, A: 255} // Green (#44FF88)
case queue.JobTypeUpscale:
return color.RGBA{R: 255, G: 138, B: 101, A: 255} // coral
return color.RGBA{R: 170, G: 255, B: 68, A: 255} // Yellow-Green (#AAFF44)
case queue.JobTypeAudio:
return color.RGBA{R: 255, G: 215, B: 64, A: 255} // gold
return color.RGBA{R: 255, G: 215, B: 68, A: 255} // Yellow (#FFD744)
case queue.JobTypeThumb:
return color.RGBA{R: 102, G: 217, B: 239, A: 255} // teal
return color.RGBA{R: 255, G: 136, B: 68, A: 255} // Orange (#FF8844)
default:
return color.Gray{Y: 180}
}

850
main.go

File diff suppressed because it is too large Load Diff