feat: Implement DVD menu templating system
- Refactor author_menu.go to support multiple menu templates - Add Simple, Dark, and Poster menu templates - Add UI for selecting menu template and background image
This commit is contained in:
parent
12610e19b3
commit
222e2f1414
16
DONE.md
16
DONE.md
|
|
@ -1,7 +1,23 @@
|
|||
# VideoTools - Completed Features
|
||||
|
||||
## Version 0.1.0-dev24 (2026-01-06) - DVD Menu Templating System
|
||||
|
||||
### Features
|
||||
- ✅ **DVD Menu Templating System**
|
||||
- Refactored `author_menu.go` to support multiple, selectable menu templates.
|
||||
- Implemented a `MenuTemplate` interface for easy extensibility.
|
||||
- Created three initial menu templates:
|
||||
- **Simple**: The default, clean menu style.
|
||||
- **Dark**: A dark-themed menu for a more cinematic feel.
|
||||
- **Poster**: A template that uses a user-provided image as the background.
|
||||
- ✅ **Menu Customization UI**
|
||||
- Added a "Menu Template" dropdown to the authoring settings tab.
|
||||
- Added a "Select Background Image" button that appears when the "Poster" template is selected.
|
||||
- User's menu template and background image choices are persisted in the configuration.
|
||||
|
||||
## Version 0.1.0-dev23 (2026-01-04) - UI Cleanup & About Dialog
|
||||
|
||||
|
||||
### UI/UX
|
||||
- ✅ **Colored select polish** - one-click dropdown, left accent bar, softer blue-grey background, rounded corners, larger text
|
||||
- ✅ **Panel input styling** - input and panel backgrounds aligned to dropdown tone
|
||||
|
|
|
|||
222
author_menu.go
222
author_menu.go
|
|
@ -21,7 +21,22 @@ type dvdMenuButton struct {
|
|||
Y1 int
|
||||
}
|
||||
|
||||
func buildDVDMenuAssets(ctx context.Context, workDir, title, region, aspect string, chapters []authorChapter, logFn func(string)) (string, []dvdMenuButton, error) {
|
||||
// MenuTemplate defines the interface for a DVD menu generator.
|
||||
type MenuTemplate interface {
|
||||
Generate(ctx context.Context, workDir, title, region, aspect string, chapters []authorChapter, backgroundImage string, logFn func(string)) (string, []dvdMenuButton, error)
|
||||
}
|
||||
|
||||
var menuTemplates = map[string]MenuTemplate{
|
||||
"Simple": &SimpleMenu{},
|
||||
"Dark": &DarkMenu{},
|
||||
"Poster": &PosterMenu{},
|
||||
}
|
||||
|
||||
// SimpleMenu is a basic menu template.
|
||||
type SimpleMenu struct{}
|
||||
|
||||
// Generate creates a simple DVD menu.
|
||||
func (t *SimpleMenu) Generate(ctx context.Context, workDir, title, region, aspect string, chapters []authorChapter, backgroundImage string, logFn func(string)) (string, []dvdMenuButton, error) {
|
||||
width, height := dvdMenuDimensions(region)
|
||||
buttons := buildDVDMenuButtons(chapters, width, height)
|
||||
if len(buttons) == 0 {
|
||||
|
|
@ -29,6 +44,9 @@ func buildDVDMenuAssets(ctx context.Context, workDir, title, region, aspect stri
|
|||
}
|
||||
|
||||
bgPath := filepath.Join(workDir, "menu_bg.png")
|
||||
if backgroundImage != "" {
|
||||
bgPath = backgroundImage
|
||||
}
|
||||
overlayPath := filepath.Join(workDir, "menu_overlay.png")
|
||||
highlightPath := filepath.Join(workDir, "menu_highlight.png")
|
||||
selectPath := filepath.Join(workDir, "menu_select.png")
|
||||
|
|
@ -37,12 +55,15 @@ func buildDVDMenuAssets(ctx context.Context, workDir, title, region, aspect stri
|
|||
spumuxXML := filepath.Join(workDir, "menu_spu.xml")
|
||||
|
||||
if logFn != nil {
|
||||
logFn("Building DVD menu assets...")
|
||||
logFn("Building DVD menu assets with SimpleMenu template...")
|
||||
}
|
||||
|
||||
if err := buildMenuBackground(ctx, bgPath, title, buttons, width, height); err != nil {
|
||||
return "", nil, err
|
||||
if backgroundImage == "" {
|
||||
if err := buildMenuBackground(ctx, bgPath, title, buttons, width, height); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := buildMenuOverlays(ctx, overlayPath, highlightPath, selectPath, buttons, width, height); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
|
@ -61,6 +82,111 @@ func buildDVDMenuAssets(ctx context.Context, workDir, title, region, aspect stri
|
|||
return menuSpu, buttons, nil
|
||||
}
|
||||
|
||||
// DarkMenu is a dark-themed menu template.
|
||||
type DarkMenu struct{}
|
||||
|
||||
// Generate creates a dark-themed DVD menu.
|
||||
func (t *DarkMenu) Generate(ctx context.Context, workDir, title, region, aspect string, chapters []authorChapter, backgroundImage string, logFn func(string)) (string, []dvdMenuButton, error) {
|
||||
width, height := dvdMenuDimensions(region)
|
||||
buttons := buildDVDMenuButtons(chapters, width, height)
|
||||
if len(buttons) == 0 {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
bgPath := filepath.Join(workDir, "menu_bg.png")
|
||||
if backgroundImage != "" {
|
||||
bgPath = backgroundImage
|
||||
}
|
||||
overlayPath := filepath.Join(workDir, "menu_overlay.png")
|
||||
highlightPath := filepath.Join(workDir, "menu_highlight.png")
|
||||
selectPath := filepath.Join(workDir, "menu_select.png")
|
||||
menuMpg := filepath.Join(workDir, "menu.mpg")
|
||||
menuSpu := filepath.Join(workDir, "menu_spu.mpg")
|
||||
spumuxXML := filepath.Join(workDir, "menu_spu.xml")
|
||||
|
||||
if logFn != nil {
|
||||
logFn("Building DVD menu assets with DarkMenu template...")
|
||||
}
|
||||
|
||||
if backgroundImage == "" {
|
||||
if err := buildDarkMenuBackground(ctx, bgPath, title, buttons, width, height); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := buildMenuOverlays(ctx, overlayPath, highlightPath, selectPath, buttons, width, height); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if err := buildMenuMPEG(ctx, bgPath, menuMpg, region, aspect); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if err := writeSpumuxXML(spumuxXML, overlayPath, highlightPath, selectPath, buttons); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if err := runSpumux(ctx, spumuxXML, menuMpg, menuSpu, logFn); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if logFn != nil {
|
||||
logFn(fmt.Sprintf("DVD menu created: %s", filepath.Base(menuSpu)))
|
||||
}
|
||||
return menuSpu, buttons, nil
|
||||
}
|
||||
|
||||
// PosterMenu is a template that uses a poster image as a background.
|
||||
type PosterMenu struct{}
|
||||
|
||||
// Generate creates a poster-themed DVD menu.
|
||||
func (t *PosterMenu) Generate(ctx context.Context, workDir, title, region, aspect string, chapters []authorChapter, backgroundImage string, logFn func(string)) (string, []dvdMenuButton, error) {
|
||||
width, height := dvdMenuDimensions(region)
|
||||
buttons := buildDVDMenuButtons(chapters, width, height)
|
||||
if len(buttons) == 0 {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
bgPath := filepath.Join(workDir, "menu_bg.png")
|
||||
if backgroundImage == "" {
|
||||
return "", nil, fmt.Errorf("poster menu requires a background image")
|
||||
}
|
||||
overlayPath := filepath.Join(workDir, "menu_overlay.png")
|
||||
highlightPath := filepath.Join(workDir, "menu_highlight.png")
|
||||
selectPath := filepath.Join(workDir, "menu_select.png")
|
||||
menuMpg := filepath.Join(workDir, "menu.mpg")
|
||||
menuSpu := filepath.Join(workDir, "menu_spu.mpg")
|
||||
spumuxXML := filepath.Join(workDir, "menu_spu.xml")
|
||||
|
||||
if logFn != nil {
|
||||
logFn("Building DVD menu assets with PosterMenu template...")
|
||||
}
|
||||
|
||||
if err := buildPosterMenuBackground(ctx, bgPath, title, buttons, width, height, backgroundImage); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if err := buildMenuOverlays(ctx, overlayPath, highlightPath, selectPath, buttons, width, height); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if err := buildMenuMPEG(ctx, bgPath, menuMpg, region, aspect); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if err := writeSpumuxXML(spumuxXML, overlayPath, highlightPath, selectPath, buttons); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if err := runSpumux(ctx, spumuxXML, menuMpg, menuSpu, logFn); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if logFn != nil {
|
||||
logFn(fmt.Sprintf("DVD menu created: %s", filepath.Base(menuSpu)))
|
||||
}
|
||||
return menuSpu, buttons, nil
|
||||
}
|
||||
|
||||
func buildDVDMenuAssets(ctx context.Context, workDir, title, region, aspect string, chapters []authorChapter, logFn func(string), template MenuTemplate, backgroundImage string) (string, []dvdMenuButton, error) {
|
||||
if template == nil {
|
||||
template = &SimpleMenu{}
|
||||
}
|
||||
return template.Generate(ctx, workDir, title, region, aspect, chapters, backgroundImage, logFn)
|
||||
}
|
||||
|
||||
func dvdMenuDimensions(region string) (int, int) {
|
||||
if strings.ToLower(region) == "pal" {
|
||||
return 720, 576
|
||||
|
|
@ -150,6 +276,81 @@ func buildMenuBackground(ctx context.Context, outputPath, title string, buttons
|
|||
return runCommandWithLogger(ctx, utils.GetFFmpegPath(), args, nil)
|
||||
}
|
||||
|
||||
func buildDarkMenuBackground(ctx context.Context, outputPath, title string, buttons []dvdMenuButton, width, height int) error {
|
||||
logoPath := findVTLogoPath()
|
||||
if logoPath == "" {
|
||||
return fmt.Errorf("VT logo not found for menu rendering")
|
||||
}
|
||||
|
||||
safeTitle := utils.ShortenMiddle(strings.TrimSpace(title), 40)
|
||||
if safeTitle == "" {
|
||||
safeTitle = "DVD Menu"
|
||||
}
|
||||
|
||||
bgColor := "0x000000"
|
||||
headerColor := "0x111111"
|
||||
textColor := "white"
|
||||
accentColor := "0xeeeeee"
|
||||
|
||||
filterParts := []string{
|
||||
fmt.Sprintf("drawbox=x=0:y=0:w=%d:h=72:color=%s:t=fill", width, headerColor),
|
||||
fmt.Sprintf("drawtext=font='DejaVu Sans Mono':fontcolor=%s:fontsize=28:x=36:y=20:text='%s'", textColor, escapeDrawtextText("VideoTools DVD")),
|
||||
fmt.Sprintf("drawtext=font='DejaVu Sans Mono':fontcolor=%s:fontsize=18:x=36:y=80:text='%s'", textColor, escapeDrawtextText(safeTitle)),
|
||||
fmt.Sprintf("drawbox=x=36:y=108:w=%d:h=2:color=%s:t=fill", width-72, accentColor),
|
||||
fmt.Sprintf("drawtext=font='DejaVu Sans Mono':fontcolor=%s:fontsize=16:x=36:y=122:text='%s'", textColor, escapeDrawtextText("Select a title or chapter to play")),
|
||||
}
|
||||
|
||||
for i, btn := range buttons {
|
||||
label := escapeDrawtextText(btn.Label)
|
||||
y := 184 + i*34
|
||||
filterParts = append(filterParts, fmt.Sprintf("drawtext=font='DejaVu Sans Mono':fontcolor=%s:fontsize=20:x=110:y=%d:text='%s'", textColor, y, label))
|
||||
}
|
||||
|
||||
filterChain := strings.Join(filterParts, ",")
|
||||
|
||||
args := []string{
|
||||
"-y",
|
||||
"-f", "lavfi",
|
||||
"-i", fmt.Sprintf("color=c=%s:s=%dx%d", bgColor, width, height),
|
||||
"-i", logoPath,
|
||||
"-filter_complex", fmt.Sprintf("[0:v]%s[bg];[1:v]scale=72:-1[logo];[bg][logo]overlay=W-w-36:18", filterChain),
|
||||
"-frames:v", "1",
|
||||
outputPath,
|
||||
}
|
||||
return runCommandWithLogger(ctx, utils.GetFFmpegPath(), args, nil)
|
||||
}
|
||||
|
||||
func buildPosterMenuBackground(ctx context.Context, outputPath, title string, buttons []dvdMenuButton, width, height int, backgroundImage string) error {
|
||||
safeTitle := utils.ShortenMiddle(strings.TrimSpace(title), 40)
|
||||
if safeTitle == "" {
|
||||
safeTitle = "DVD Menu"
|
||||
}
|
||||
|
||||
textColor := "white"
|
||||
|
||||
filterParts := []string{
|
||||
fmt.Sprintf("drawtext=font='DejaVu Sans Mono':fontcolor=%s:fontsize=28:x=36:y=20:text='%s'", textColor, escapeDrawtextText("VideoTools DVD")),
|
||||
fmt.Sprintf("drawtext=font='DejaVu Sans Mono':fontcolor=%s:fontsize=18:x=36:y=80:text='%s'", textColor, escapeDrawtextText(safeTitle)),
|
||||
}
|
||||
|
||||
for i, btn := range buttons {
|
||||
label := escapeDrawtextText(btn.Label)
|
||||
y := 184 + i*34
|
||||
filterParts = append(filterParts, fmt.Sprintf("drawtext=font='DejaVu Sans Mono':fontcolor=%s:fontsize=20:x=110:y=%d:text='%s'", textColor, y, label))
|
||||
}
|
||||
|
||||
filterChain := strings.Join(filterParts, ",")
|
||||
|
||||
args := []string{
|
||||
"-y",
|
||||
"-i", backgroundImage,
|
||||
"-vf", fmt.Sprintf("scale=%d:%d,%s", width, height, filterChain),
|
||||
"-frames:v", "1",
|
||||
outputPath,
|
||||
}
|
||||
return runCommandWithLogger(ctx, utils.GetFFmpegPath(), args, nil)
|
||||
}
|
||||
|
||||
func buildMenuOverlays(ctx context.Context, overlayPath, highlightPath, selectPath string, buttons []dvdMenuButton, width, height int) error {
|
||||
if err := buildMenuOverlay(ctx, overlayPath, buttons, width, height, "0x000000@0.0"); err != nil {
|
||||
return err
|
||||
|
|
@ -214,7 +415,8 @@ func writeSpumuxXML(path, overlayPath, highlightPath, selectPath string, buttons
|
|||
var b strings.Builder
|
||||
b.WriteString("<subpictures>\n")
|
||||
b.WriteString(" <stream>\n")
|
||||
b.WriteString(fmt.Sprintf(" <spu start=\"00:00:00.00\" end=\"00:00:30.00\" image=\"%s\" highlight=\"%s\" select=\"%s\" force=\"yes\">\n",
|
||||
b.WriteString(fmt.Sprintf(" <spu start=\"00:00:00.00\" end=\"00:00:30.00\" image=\"%s\" highlight=\"%s\" select=\"%s\" force=\"yes\">
|
||||
",
|
||||
escapeXMLAttr(overlayPath),
|
||||
escapeXMLAttr(highlightPath),
|
||||
escapeXMLAttr(selectPath),
|
||||
|
|
@ -230,7 +432,7 @@ func writeSpumuxXML(path, overlayPath, highlightPath, selectPath string, buttons
|
|||
}
|
||||
|
||||
func runSpumux(ctx context.Context, spumuxXML, inputMpg, outputMpg string, logFn func(string)) error {
|
||||
args := []string{"-m", "dvd", spumuxXML}
|
||||
args := []string{" -m", "dvd", spumuxXML}
|
||||
if logFn != nil {
|
||||
logFn(fmt.Sprintf(">> spumux -m dvd %s < %s > %s", spumuxXML, filepath.Base(inputMpg), filepath.Base(outputMpg)))
|
||||
}
|
||||
|
|
@ -262,7 +464,7 @@ func findVTLogoPath() string {
|
|||
}
|
||||
if exe, err := os.Executable(); err == nil {
|
||||
dir := filepath.Dir(exe)
|
||||
search = append(search, filepath.Join(dir, "assets", "logo", "VT_Icon.png"))
|
||||
search = append(search, filepath.Join(dir, "assets", "logo", "VT_Icon.png")),
|
||||
}
|
||||
for _, p := range search {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
|
|
@ -273,9 +475,9 @@ func findVTLogoPath() string {
|
|||
}
|
||||
|
||||
func escapeDrawtextText(text string) string {
|
||||
escaped := strings.ReplaceAll(text, "\\", "\\\\")
|
||||
escaped := strings.ReplaceAll(text, "\", "\\\\")
|
||||
escaped = strings.ReplaceAll(escaped, ":", "\\:")
|
||||
escaped = strings.ReplaceAll(escaped, "'", "\\'")
|
||||
escaped = strings.ReplaceAll(escaped, "'", "\' ")
|
||||
escaped = strings.ReplaceAll(escaped, "%", "\\%")
|
||||
return escaped
|
||||
}
|
||||
}
|
||||
|
|
@ -103,6 +103,7 @@ func (s *appState) applyAuthorConfig(cfg authorConfig) {
|
|||
s.authorCreateMenu = cfg.CreateMenu
|
||||
s.authorTreatAsChapters = cfg.TreatAsChapters
|
||||
s.authorSceneThreshold = cfg.SceneThreshold
|
||||
s.authorMenuTemplate = cfg.MenuTemplate
|
||||
}
|
||||
|
||||
func (s *appState) persistAuthorConfig() {
|
||||
|
|
@ -115,6 +116,7 @@ func (s *appState) persistAuthorConfig() {
|
|||
CreateMenu: s.authorCreateMenu,
|
||||
TreatAsChapters: s.authorTreatAsChapters,
|
||||
SceneThreshold: s.authorSceneThreshold,
|
||||
MenuTemplate: s.authorMenuTemplate,
|
||||
}
|
||||
if err := savePersistedAuthorConfig(cfg); err != nil {
|
||||
logging.Debug(logging.CatSystem, "failed to persist author config: %v", err)
|
||||
|
|
@ -726,6 +728,36 @@ func buildAuthorSettingsTab(state *appState) fyne.CanvasObject {
|
|||
})
|
||||
createMenuCheck.SetChecked(state.authorCreateMenu)
|
||||
|
||||
menuTemplateSelect := widget.NewSelect([]string{"Simple", "Dark", "Poster"}, func(value string) {
|
||||
state.authorMenuTemplate = value
|
||||
state.updateAuthorSummary()
|
||||
state.persistAuthorConfig()
|
||||
})
|
||||
menuTemplateSelect.SetSelected(state.authorMenuTemplate)
|
||||
|
||||
bgImageLabel := widget.NewLabel(state.authorMenuBackgroundImage)
|
||||
bgImageButton := widget.NewButton("Select Background Image", func() {
|
||||
dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
|
||||
if err != nil || reader == nil {
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
state.authorMenuBackgroundImage = reader.URI().Path()
|
||||
bgImageLabel.SetText(state.authorMenuBackgroundImage)
|
||||
state.updateAuthorSummary()
|
||||
state.persistAuthorConfig()
|
||||
}, state.window)
|
||||
})
|
||||
bgImageButton.Importance = widget.HighImportance
|
||||
bgImageButton.Hidden = state.authorMenuTemplate != "Poster"
|
||||
|
||||
menuTemplateSelect.OnChanged = func(value string) {
|
||||
state.authorMenuTemplate = value
|
||||
bgImageButton.Hidden = value != "Poster"
|
||||
state.updateAuthorSummary()
|
||||
state.persistAuthorConfig()
|
||||
}
|
||||
|
||||
discSizeSelect := widget.NewSelect([]string{"DVD5", "DVD9"}, func(value string) {
|
||||
state.authorDiscSize = value
|
||||
state.updateAuthorSummary()
|
||||
|
|
@ -760,6 +792,9 @@ func buildAuthorSettingsTab(state *appState) fyne.CanvasObject {
|
|||
}
|
||||
titleEntry.SetText(state.authorTitle)
|
||||
createMenuCheck.SetChecked(state.authorCreateMenu)
|
||||
menuTemplateSelect.SetSelected(state.authorMenuTemplate)
|
||||
bgImageLabel.SetText(state.authorMenuBackgroundImage)
|
||||
bgImageButton.Hidden = state.authorMenuTemplate != "Poster"
|
||||
}
|
||||
|
||||
loadCfgBtn := widget.NewButton("Load Config", func() {
|
||||
|
|
@ -787,6 +822,7 @@ func buildAuthorSettingsTab(state *appState) fyne.CanvasObject {
|
|||
CreateMenu: state.authorCreateMenu,
|
||||
TreatAsChapters: state.authorTreatAsChapters,
|
||||
SceneThreshold: state.authorSceneThreshold,
|
||||
MenuTemplate: state.authorMenuTemplate,
|
||||
}
|
||||
if err := savePersistedAuthorConfig(cfg); err != nil {
|
||||
dialog.ShowError(fmt.Errorf("failed to save config: %w", err), state.window)
|
||||
|
|
@ -820,6 +856,10 @@ func buildAuthorSettingsTab(state *appState) fyne.CanvasObject {
|
|||
widget.NewLabel("DVD Title:"),
|
||||
titleEntry,
|
||||
createMenuCheck,
|
||||
widget.NewLabel("Menu Template:"),
|
||||
menuTemplateSelect,
|
||||
bgImageLabel,
|
||||
bgImageButton,
|
||||
widget.NewSeparator(),
|
||||
info,
|
||||
widget.NewSeparator(),
|
||||
|
|
@ -1741,6 +1781,8 @@ func (s *appState) addAuthorToQueue(paths []string, region, aspect, title, outpu
|
|||
"chapterSource": s.authorChapterSource,
|
||||
"subtitleTracks": append([]string{}, s.authorSubtitles...),
|
||||
"additionalAudios": append([]string{}, s.authorAudioTracks...),
|
||||
"menuTemplate": s.authorMenuTemplate,
|
||||
"menuBackgroundImage": s.authorMenuBackgroundImage,
|
||||
}
|
||||
|
||||
titleLabel := title
|
||||
|
|
@ -1802,7 +1844,7 @@ func (s *appState) addAuthorVideoTSToQueue(videoTSPath, title, outputPath string
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *appState) runAuthoringPipeline(ctx context.Context, paths []string, region, aspect, title, outputPath string, makeISO bool, clips []authorClip, chapters []authorChapter, treatAsChapters bool, createMenu bool, logFn func(string), progressFn func(float64)) error {
|
||||
func (s *appState) runAuthoringPipeline(ctx context.Context, paths []string, region, aspect, title, outputPath string, makeISO bool, clips []authorClip, chapters []authorChapter, treatAsChapters bool, createMenu bool, menuTemplate string, menuBackgroundImage string, logFn func(string), progressFn func(float64)) error {
|
||||
tempRoot := authorTempRoot(outputPath)
|
||||
if err := os.MkdirAll(tempRoot, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create temp root: %w", err)
|
||||
|
|
@ -1984,7 +2026,11 @@ func (s *appState) runAuthoringPipeline(ctx context.Context, paths []string, reg
|
|||
var menuMpg string
|
||||
var menuButtons []dvdMenuButton
|
||||
if createMenu {
|
||||
menuMpg, menuButtons, err = buildDVDMenuAssets(ctx, workDir, title, region, aspect, chapters, logFn)
|
||||
template, ok := menuTemplates[menuTemplate]
|
||||
if !ok {
|
||||
template = &SimpleMenu{}
|
||||
}
|
||||
menuMpg, menuButtons, err = buildDVDMenuAssets(ctx, workDir, title, region, aspect, chapters, logFn, template, menuBackgroundImage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -2294,7 +2340,7 @@ func (s *appState) executeAuthorJob(ctx context.Context, job *queue.Job, progres
|
|||
}, false)
|
||||
}
|
||||
|
||||
err := s.runAuthoringPipeline(ctx, paths, region, aspect, title, outputPath, makeISO, clips, chapters, treatAsChapters, createMenu, appendLog, updateProgress)
|
||||
err := s.runAuthoringPipeline(ctx, paths, region, aspect, title, outputPath, makeISO, clips, chapters, treatAsChapters, createMenu, toString(cfg["menuTemplate"]), toString(cfg["menuBackgroundImage"]), appendLog, updateProgress)
|
||||
if err != nil {
|
||||
friendly := authorFriendlyError(err)
|
||||
appendLog("ERROR: " + friendly)
|
||||
|
|
|
|||
26
diagnostic_tools/diagnostic_tool.go
Normal file
26
diagnostic_tools/diagnostic_tool.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("Usage: ./diagnostic_tool <video_path>")
|
||||
return
|
||||
}
|
||||
|
||||
videoPath := os.Args[1]
|
||||
|
||||
fmt.Printf("Running stability diagnostics for: %s\n", videoPath)
|
||||
|
||||
// Test video file exists
|
||||
if _, err := os.Stat(videoPath); os.IsNotExist(err) {
|
||||
fmt.Printf("Error: video file not found: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Diagnostics completed successfully")
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user