From a9804b3ad3174d96cd4b4d056332cb4698c70c49 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Sat, 20 Dec 2025 21:33:55 -0500 Subject: [PATCH] Add Author module skeleton with tabbed interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- DONE.md | 15 ++++ internal/modules/handlers.go | 8 +- main.go | 146 ++++++++++++++++++++++++++++++++++- 3 files changed, 164 insertions(+), 5 deletions(-) diff --git a/DONE.md b/DONE.md index 47539df..4321fa3 100644 --- a/DONE.md +++ b/DONE.md @@ -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 diff --git a/internal/modules/handlers.go b/internal/modules/handlers.go index b76ae89..0ca04e7 100644 --- a/internal/modules/handlers.go +++ b/internal/modules/handlers.go @@ -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) diff --git a/main.go b/main.go index 740a5fb..a12a17e 100644 --- a/main.go +++ b/main.go @@ -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 {