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:
parent
815319b3f5
commit
5b8fc452af
150
docs/COMPARE_FULLSCREEN.md
Normal file
150
docs/COMPARE_FULLSCREEN.md
Normal 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
|
||||
460
docs/LOSSLESSCUT_INSPIRATION.md
Normal file
460
docs/LOSSLESSCUT_INSPIRATION.md
Normal 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
169
docs/TRIM_MODULE_DESIGN.md
Normal 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)
|
||||
612
docs/VIDEO_METADATA_GUIDE.md
Normal file
612
docs/VIDEO_METADATA_GUIDE.md
Normal 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, "&", "&")
|
||||
s = strings.ReplaceAll(s, "<", "<")
|
||||
s = strings.ReplaceAll(s, ">", ">")
|
||||
s = strings.ReplaceAll(s, "\"", """)
|
||||
s = strings.ReplaceAll(s, "'", "'")
|
||||
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! 🚀
|
||||
|
|
@ -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
|
||||
|
|
|
|||
373
docs/VT_PLAYER_INTEGRATION_NOTES.md
Normal file
373
docs/VT_PLAYER_INTEGRATION_NOTES.md
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user