VideoTools/author_module_temp.go
Stu Leak d031afa269 Enhance Author module structure and implement drag-and-drop support
- Add authorClips, authorSubtitles, authorOutputType fields to appState
- Create authorClip struct for video clip management
- Implement drag-and-drop support for video clips and subtitles
- Add Settings tab with output type, region, aspect ratio options
- Create Video Clips tab with file management
- Add Subtitles tab for track management
- Prepare framework for DVD/ISO generation
- Update HandleAuthor to work with drag-and-drop system
- Add comprehensive file validation and error handling
- Support for multiple video clips compilation
- Ready for chapter detection and DVD authoring implementation
2025-12-22 20:09:43 -05:00

334 lines
9.2 KiB
Go

package main
import (
"fmt"
"path/filepath"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
"git.leaktechnologies.dev/stu/VideoTools/internal/ui"
)
// buildVideoClipsTab creates the video clips tab with drag-and-drop support
func buildVideoClipsTab(state *appState) fyne.CanvasObject {
// Video clips list with drag-and-drop support
list := container.NewVBox()
rebuildList := func() {
list.Objects = nil
if len(state.authorClips) == 0 {
emptyLabel := widget.NewLabel("Drag and drop video files here\nor click 'Add Files' to select videos")
emptyLabel.Alignment = fyne.TextAlignCenter
// Make empty state a drop target
emptyDrop := ui.NewDroppable(container.NewCenter(emptyLabel), func(items []fyne.URI) {
var paths []string
for _, uri := range items {
if uri.Scheme() == "file" {
paths = append(paths, uri.Path())
}
}
if len(paths) > 0 {
state.addAuthorFiles(paths)
}
})
list.Add(container.NewMax(emptyDrop))
} else {
for i, clip := range state.authorClips {
idx := i
card := widget.NewCard(clip.DisplayName, fmt.Sprintf("%.2fs", clip.Duration), nil)
// Remove button
removeBtn := widget.NewButton("Remove", func() {
state.authorClips = append(state.authorClips[:idx], state.authorClips[idx+1:]...)
rebuildList()
})
removeBtn.Importance = widget.MediumImportance
// Duration label
durationLabel := widget.NewLabel(fmt.Sprintf("Duration: %.2f seconds", clip.Duration))
durationLabel.TextStyle = fyne.TextStyle{Italic: true}
cardContent := container.NewVBox(
durationLabel,
widget.NewSeparator(),
removeBtn,
)
card.SetContent(cardContent)
list.Add(card)
}
}
}
// Add files button
addBtn := widget.NewButton("Add Files", func() {
dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
if err != nil || reader == nil {
return
}
defer reader.Close()
state.addAuthorFiles([]string{reader.URI().Path()})
}, state.window)
})
addBtn.Importance = widget.HighImportance
// Clear all button
clearBtn := widget.NewButton("Clear All", func() {
state.authorClips = []authorClip{}
rebuildList()
})
clearBtn.Importance = widget.MediumImportance
// Compile button
compileBtn := widget.NewButton("COMPILE TO DVD", func() {
if len(state.authorClips) == 0 {
dialog.ShowInformation("No Clips", "Please add video clips first", state.window)
return
}
// TODO: Implement compilation to DVD
dialog.ShowInformation("Compile", "DVD compilation will be implemented", state.window)
})
compileBtn.Importance = widget.HighImportance
controls := container.NewVBox(
widget.NewLabel("Video Clips:"),
container.NewScroll(list),
widget.NewSeparator(),
container.NewHBox(addBtn, clearBtn, compileBtn),
)
// Initialize the list
rebuildList()
return container.NewPadded(controls)
}
// addAuthorFiles helper function
func (s *appState) addAuthorFiles(paths []string) {
for _, path := range paths {
src, err := probeVideo(path)
if err != nil {
dialog.ShowError(fmt.Errorf("failed to load video %s: %w", filepath.Base(path), err), s.window)
continue
}
clip := authorClip{
Path: path,
DisplayName: filepath.Base(path),
Duration: src.Duration,
Chapters: []authorChapter{},
}
s.authorClips = append(s.authorClips, clip)
}
}
// buildSubtitlesTab creates the subtitles tab with drag-and-drop support
func buildSubtitlesTab(state *appState) fyne.CanvasObject {
// Subtitle files list with drag-and-drop support
list := container.NewVBox()
rebuildSubList := func() {
list.Objects = nil
if len(state.authorSubtitles) == 0 {
emptyLabel := widget.NewLabel("Drag and drop subtitle files here\nor click 'Add Subtitles' to select")
emptyLabel.Alignment = fyne.TextAlignCenter
// Make empty state a drop target
emptyDrop := ui.NewDroppable(container.NewCenter(emptyLabel), func(items []fyne.URI) {
var paths []string
for _, uri := range items {
if uri.Scheme() == "file" {
paths = append(paths, uri.Path())
}
}
if len(paths) > 0 {
state.authorSubtitles = append(state.authorSubtitles, paths...)
rebuildSubList()
}
})
list.Add(container.NewMax(emptyDrop))
} else {
for i, path := range state.authorSubtitles {
idx := i
card := widget.NewCard(filepath.Base(path), "", nil)
// Remove button
removeBtn := widget.NewButton("Remove", func() {
state.authorSubtitles = append(state.authorSubtitles[:idx], state.authorSubtitles[idx+1:]...)
rebuildSubList()
})
removeBtn.Importance = widget.MediumImportance
cardContent := container.NewVBox(removeBtn)
card.SetContent(cardContent)
list.Add(card)
}
}
}
// Add subtitles button
addBtn := widget.NewButton("Add Subtitles", func() {
dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
if err != nil || reader == nil {
return
}
defer reader.Close()
state.authorSubtitles = append(state.authorSubtitles, reader.URI().Path())
rebuildSubList()
}, state.window)
})
addBtn.Importance = widget.HighImportance
// Clear all button
clearBtn := widget.NewButton("Clear All", func() {
state.authorSubtitles = []string{}
rebuildSubList()
})
clearBtn.Importance = widget.MediumImportance
controls := container.NewVBox(
widget.NewLabel("Subtitle Tracks:"),
container.NewScroll(list),
widget.NewSeparator(),
container.NewHBox(addBtn, clearBtn),
)
// Initialize
rebuildSubList()
return container.NewPadded(controls)
}
// buildAuthorSettingsTab creates the author settings tab
func buildAuthorSettingsTab(state *appState) fyne.CanvasObject {
// Output type selection
outputType := widget.NewSelect([]string{"DVD (VIDEO_TS)", "ISO Image"})
outputType.OnChanged = func(value string) {
if value == "DVD (VIDEO_TS)" {
state.authorOutputType = "dvd"
} else {
state.authorOutputType = "iso"
}
})
if state.authorOutputType == "iso" {
outputType.SetSelected("ISO Image")
}
// Region selection
regionSelect := widget.NewSelect([]string{"AUTO", "NTSC", "PAL"})
regionSelect.OnChanged = func(value string) {
state.authorRegion = value
})
if state.authorRegion == "" {
state.authorRegion = "AUTO"
regionSelect.SetSelected("AUTO")
} else {
regionSelect.SetSelected(state.authorRegion)
}
// Aspect ratio selection
aspectSelect := widget.NewSelect([]string{"AUTO", "4:3", "16:9"})
aspectSelect.OnChanged = func(value string) {
state.authorAspectRatio = value
})
if state.authorAspectRatio == "" {
state.authorAspectRatio = "AUTO"
aspectSelect.SetSelected("AUTO")
} else {
aspectSelect.SetSelected(state.authorAspectRatio)
}
// DVD title entry
titleEntry := widget.NewEntry()
titleEntry.SetPlaceHolder("DVD Title")
titleEntry.SetText(state.authorTitle)
titleEntry.OnChanged = func(value string) {
state.authorTitle = value
}
// Create menu checkbox
createMenuCheck := widget.NewCheck("Create DVD Menu", func(checked bool) {
state.authorCreateMenu = checked
})
createMenuCheck.SetChecked(state.authorCreateMenu)
controls := container.NewVBox(
widget.NewLabel("Output Settings:"),
widget.NewSeparator(),
widget.NewLabel("Output Type:"),
outputType,
widget.NewLabel("Region:"),
regionSelect,
widget.NewLabel("Aspect Ratio:"),
aspectSelect,
widget.NewLabel("DVD Title:"),
titleEntry,
createMenuCheck,
)
return container.NewPadded(controls)
}
// buildAuthorDiscTab creates the DVD generation tab
func buildAuthorDiscTab(state *appState) fyne.CanvasObject {
// Generate DVD/ISO
generateBtn := widget.NewButton("GENERATE DVD", func() {
if len(state.authorClips) == 0 {
dialog.ShowInformation("No Content", "Please add video clips first", state.window)
return
}
// Show compilation options
dialog.ShowInformation("DVD Generation",
"DVD/ISO generation will be implemented in next step.\n\n"+
"Features planned:\n"+
"• Create VIDEO_TS folder structure\n"+
"• Generate burn-ready ISO\n"+
"• Include subtitle tracks\n"+
"• Include alternate audio tracks\n"+
"• Support for alternate camera angles", state.window)
})
generateBtn.Importance = widget.HighImportance
// Show summary
summary := "Ready to generate:\n\n"
if len(state.authorClips) > 0 {
summary += fmt.Sprintf("Video Clips: %d\n", len(state.authorClips))
for i, clip := range state.authorClips {
summary += fmt.Sprintf(" %d. %s (%.2fs)\n", i+1, clip.DisplayName, clip.Duration)
}
}
if len(state.authorSubtitles) > 0 {
summary += fmt.Sprintf("Subtitle Tracks: %d\n", len(state.authorSubtitles))
for i, path := range state.authorSubtitles {
summary += fmt.Sprintf(" %d. %s\n", i+1, filepath.Base(path))
}
}
summary += fmt.Sprintf("Output Type: %s\n", state.authorOutputType)
summary += fmt.Sprintf("Region: %s\n", state.authorRegion)
summary += fmt.Sprintf("Aspect Ratio: %s\n", state.authorAspectRatio)
if state.authorTitle != "" {
summary += fmt.Sprintf("DVD Title: %s\n", state.authorTitle)
}
summaryLabel := widget.NewLabel(summary)
summaryLabel.Wrapping = fyne.TextWrapWord
controls := container.NewVBox(
widget.NewLabel("Generate DVD/ISO:"),
widget.NewSeparator(),
summaryLabel,
widget.NewSeparator(),
generateBtn,
)
return container.NewPadded(controls)
}