Add Author module skeleton with tabbed interface

Renamed "DVD Author" to "Author" for broader disc production workflow.
Created foundation for complete authoring pipeline with three main tasks:

**Module Structure:**
- Tabbed interface with Chapters, Rip DVD/ISO, and Author Disc tabs
- Added authorChapter struct (timestamp, title, auto-detected flag)
- Added author module state fields (file, chapters, threshold, detecting)

**Chapters Tab (Basic UI):**
- File selection with video probing integration
- Scene detection sensitivity slider (0.1-0.9 threshold)
- Placeholder UI for chapter list and controls
- Add Chapter and Export Chapters buttons (placeholders)
- Foundation for FFmpeg scdet scene detection

**Rip DVD/ISO Tab:**
- Placeholder for high-quality disc extraction
- Will support lossless ripping (like FLAC from CD)
- Preserve all audio/subtitle tracks

**Author Disc Tab:**
- Placeholder for VIDEO_TS/ISO creation
- Will support burn-ready output, NTSC/PAL, menus

Changes:
- Modified main.go: Added authorChapter struct, author state fields,
  showAuthorView(), buildAuthorView(), buildChaptersTab(),
  buildRipTab(), buildAuthorDiscTab()
- Modified internal/modules/handlers.go: Renamed HandleDVDAuthor to
  HandleAuthor with updated comment
- Updated DONE.md with Author module skeleton details

Next steps: Implement FFmpeg scene detection, chapter list UI,
and DVD/ISO ripping functionality.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Stu Leak 2025-12-20 21:33:55 -05:00
parent 364d2099f5
commit a9804b3ad3
3 changed files with 164 additions and 5 deletions

15
DONE.md
View File

@ -114,6 +114,21 @@ This file tracks completed features, fixes, and milestones.
- Maintains existing options (Source, Mono, Stereo, 5.1)
- Solves problem of videos with music in one ear and vocals in the other
- ✅ **Author Module Skeleton** (2025-12-20 continuation)
- Renamed "DVD Author" module to "Author" for broader scope
- Created tabbed interface structure with 3 tabs:
- **Chapters Tab** - Scene detection and chapter management
- **Rip DVD/ISO Tab** - High-quality disc extraction (like FLAC from CD)
- **Author Disc Tab** - VIDEO_TS/ISO creation for burning
- Implemented basic Chapters tab UI:
- File selection with video probing
- Scene detection sensitivity slider (0.1-0.9 threshold)
- Placeholder chapter list
- Add/Export chapter buttons (to be implemented)
- Added authorChapter struct for storing chapter data
- Added author module state fields to appState
- Foundation for complete disc production workflow
### Features (2025-12-18 Session)
- ✅ **History Sidebar Enhancements**
- Delete button ("×") on each history entry

View File

@ -44,10 +44,10 @@ func HandleAudio(files []string) {
fmt.Println("audio", files)
}
// HandleDVDAuthor handles the DVD authoring module (placeholder)
func HandleDVDAuthor(files []string) {
logging.Debug(logging.CatModule, "dvd author handler invoked with %v", files)
fmt.Println("dvd author", files)
// HandleAuthor handles the disc authoring module (DVD/Blu-ray) (placeholder)
func HandleAuthor(files []string) {
logging.Debug(logging.CatModule, "author handler invoked with %v", files)
fmt.Println("author", files)
}
// HandleSubtitles handles the subtitles module (placeholder)

146
main.go
View File

@ -88,7 +88,7 @@ var (
{"filters", "Filters", utils.MustHex("#44FF88"), "Convert", modules.HandleFilters}, // Green
{"upscale", "Upscale", utils.MustHex("#AAFF44"), "Advanced", modules.HandleUpscale}, // Yellow-Green
{"audio", "Audio", utils.MustHex("#FFD744"), "Convert", modules.HandleAudio}, // Yellow
{"dvd-author", "DVD Author", utils.MustHex("#FFAA44"), "Convert", modules.HandleDVDAuthor}, // Orange
{"author", "Author", utils.MustHex("#FFAA44"), "Convert", modules.HandleAuthor}, // Orange
{"subtitles", "Subtitles", utils.MustHex("#44A6FF"), "Convert", modules.HandleSubtitles}, // Azure
{"thumb", "Thumb", utils.MustHex("#FF8844"), "Screenshots", modules.HandleThumb}, // Orange
{"compare", "Compare", utils.MustHex("#FF44AA"), "Inspect", modules.HandleCompare}, // Pink
@ -845,6 +845,12 @@ type appState struct {
// History sidebar state
historyEntries []ui.HistoryEntry
sidebarVisible bool
// Author module state
authorFile *videoSource
authorChapters []authorChapter
authorSceneThreshold float64
authorDetecting bool
}
type mergeClip struct {
@ -853,6 +859,12 @@ type mergeClip struct {
Duration float64
}
type authorChapter struct {
Timestamp float64 // Timestamp in seconds
Title string // Chapter title/name
Auto bool // True if auto-detected, false if manual
}
func (s *appState) persistConvertConfig() {
if err := savePersistedConvertConfig(s.convert); err != nil {
logging.Debug(logging.CatSystem, "failed to persist convert config: %v", err)
@ -2583,6 +2595,19 @@ func (s *appState) showUpscaleView() {
s.setContent(buildUpscaleView(s))
}
func (s *appState) showAuthorView() {
s.stopPreview()
s.lastModule = s.active
s.active = "author"
// Initialize scene detection threshold if not set
if s.authorSceneThreshold == 0 {
s.authorSceneThreshold = 0.3
}
s.setContent(buildAuthorView(s))
}
func (s *appState) showMergeView() {
s.stopPreview()
s.lastModule = s.active
@ -13846,6 +13871,125 @@ func parseResolutionPreset(preset string, srcW, srcH int) (width, height int, pr
}
// buildUpscaleFilter builds the FFmpeg scale filter string with the selected method
func buildAuthorView(state *appState) fyne.CanvasObject {
authorColor := moduleColor("author")
// Back button
backBtn := widget.NewButton("< BACK", func() {
state.showMainMenu()
})
backBtn.Importance = widget.LowImportance
// Title
title := canvas.NewText("AUTHOR", authorColor)
title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
title.TextSize = 20
header := container.NewBorder(nil, nil, backBtn, nil, container.NewCenter(title))
// Create tabs for different authoring tasks
tabs := container.NewAppTabs(
container.NewTabItem("Chapters", buildChaptersTab(state)),
container.NewTabItem("Rip DVD/ISO", buildRipTab(state)),
container.NewTabItem("Author Disc", buildAuthorDiscTab(state)),
)
tabs.SetTabLocation(container.TabLocationTop)
return container.NewBorder(header, nil, nil, nil, tabs)
}
func buildChaptersTab(state *appState) fyne.CanvasObject {
// File selection
var fileLabel *widget.Label
if state.authorFile != nil {
fileLabel = widget.NewLabel(fmt.Sprintf("File: %s", filepath.Base(state.authorFile.Path)))
fileLabel.TextStyle = fyne.TextStyle{Bold: true}
} else {
fileLabel = widget.NewLabel("No file loaded")
}
selectBtn := widget.NewButton("Select Video", func() {
dialog.ShowFileOpen(func(uc fyne.URIReadCloser, err error) {
if err != nil || uc == nil {
return
}
defer uc.Close()
path := uc.URI().Path()
src, err := probeVideo(path)
if err != nil {
dialog.ShowError(fmt.Errorf("failed to load video: %w", err), state.window)
return
}
state.authorFile = src
fileLabel.SetText(fmt.Sprintf("File: %s", filepath.Base(src.Path)))
}, state.window)
})
// Scene detection threshold
thresholdLabel := widget.NewLabel(fmt.Sprintf("Detection Sensitivity: %.2f", state.authorSceneThreshold))
thresholdSlider := widget.NewSlider(0.1, 0.9)
thresholdSlider.Value = state.authorSceneThreshold
thresholdSlider.Step = 0.05
thresholdSlider.OnChanged = func(v float64) {
state.authorSceneThreshold = v
thresholdLabel.SetText(fmt.Sprintf("Detection Sensitivity: %.2f", v))
}
// Detect scenes button
detectBtn := widget.NewButton("Detect Scenes", func() {
if state.authorFile == nil {
dialog.ShowInformation("No File", "Please select a video file first", state.window)
return
}
// TODO: Implement scene detection
dialog.ShowInformation("Scene Detection", "Scene detection will be implemented in the next step", state.window)
})
detectBtn.Importance = widget.HighImportance
// Chapter list (placeholder)
chapterList := widget.NewLabel("No chapters detected yet")
// Add manual chapter button
addChapterBtn := widget.NewButton("+ Add Chapter", func() {
// TODO: Implement manual chapter addition
dialog.ShowInformation("Add Chapter", "Manual chapter addition will be implemented soon", state.window)
})
// Export chapters button
exportBtn := widget.NewButton("Export Chapters", func() {
// TODO: Implement chapter export
dialog.ShowInformation("Export", "Chapter export will be implemented soon", state.window)
})
controls := container.NewVBox(
fileLabel,
selectBtn,
widget.NewSeparator(),
widget.NewLabel("Scene Detection:"),
thresholdLabel,
thresholdSlider,
detectBtn,
widget.NewSeparator(),
widget.NewLabel("Chapters:"),
container.NewScroll(chapterList),
container.NewHBox(addChapterBtn, exportBtn),
)
return container.NewPadded(controls)
}
func buildRipTab(state *appState) fyne.CanvasObject {
placeholder := widget.NewLabel("DVD/ISO ripping will be implemented here.\n\nFeatures:\n• Mount and scan DVD/ISO\n• Select titles and tracks\n• Rip at highest quality (like FLAC from CD)\n• Preserve all audio and subtitle tracks")
placeholder.Wrapping = fyne.TextWrapWord
return container.NewCenter(placeholder)
}
func buildAuthorDiscTab(state *appState) fyne.CanvasObject {
placeholder := widget.NewLabel("Disc authoring will be implemented here.\n\nFeatures:\n• Create VIDEO_TS folder structure\n• Generate burn-ready ISO\n• NTSC/PAL selection\n• Menu creation\n• Chapter integration")
placeholder.Wrapping = fyne.TextWrapWord
return container.NewCenter(placeholder)
}
func buildUpscaleFilter(targetWidth, targetHeight int, method string, preserveAspect bool) string {
// Ensure even dimensions for encoders
makeEven := func(v int) int {