Support VIDEO_TS drop to ISO

This commit is contained in:
Stu Leak 2025-12-23 21:10:46 -05:00
parent 68ce3c2168
commit ee67bffbd9
2 changed files with 133 additions and 3 deletions

View File

@ -75,6 +75,7 @@ func buildAuthorView(state *appState) fyne.CanvasObject {
}
func buildVideoClipsTab(state *appState) fyne.CanvasObject {
state.authorVideoTSPath = strings.TrimSpace(state.authorVideoTSPath)
list := container.NewVBox()
listScroll := container.NewVScroll(list)
@ -152,6 +153,7 @@ func buildVideoClipsTab(state *appState) fyne.CanvasObject {
state.authorClips = []authorClip{}
state.authorChapters = nil
state.authorChapterSource = ""
state.authorVideoTSPath = ""
rebuildList()
state.updateAuthorSummary()
})
@ -596,7 +598,9 @@ func buildAuthorDiscTab(state *appState) fyne.CanvasObject {
func authorSummary(state *appState) string {
summary := "Ready to generate:\n\n"
if len(state.authorClips) > 0 {
if state.authorVideoTSPath != "" {
summary += fmt.Sprintf("VIDEO_TS: %s\n", filepath.Base(filepath.Dir(state.authorVideoTSPath)))
} else if len(state.authorClips) > 0 {
summary += fmt.Sprintf("Videos: %d\n", len(state.authorClips))
for i, clip := range state.authorClips {
summary += fmt.Sprintf(" %d. %s (%.2fs)\n", i+1, clip.DisplayName, clip.Duration)
@ -620,7 +624,7 @@ func authorSummary(state *appState) string {
summary += fmt.Sprintf("Disc Size: %s\n", state.authorDiscSize)
summary += fmt.Sprintf("Region: %s\n", state.authorRegion)
summary += fmt.Sprintf("Aspect Ratio: %s\n", state.authorAspectRatio)
if outPath := authorDefaultOutputPath(state.authorOutputType, state.authorTitle, authorSummaryPaths(state)); outPath != "" {
if outPath := authorDefaultOutputPath(state.authorOutputType, authorOutputTitle(state), authorSummaryPaths(state)); outPath != "" {
summary += fmt.Sprintf("Output Path: %s\n", outPath)
}
if state.authorTitle != "" {
@ -700,6 +704,9 @@ func authorTotalDuration(state *appState) float64 {
}
func authorSummaryPaths(state *appState) []string {
if state.authorVideoTSPath != "" {
return []string{state.authorVideoTSPath}
}
if len(state.authorClips) > 0 {
paths := make([]string, 0, len(state.authorClips))
for _, clip := range state.authorClips {
@ -713,6 +720,17 @@ func authorSummaryPaths(state *appState) []string {
return nil
}
func authorOutputTitle(state *appState) string {
title := strings.TrimSpace(state.authorTitle)
if title != "" {
return title
}
if state.authorVideoTSPath != "" {
return filepath.Base(filepath.Dir(state.authorVideoTSPath))
}
return defaultAuthorTitle(authorSummaryPaths(state))
}
func authorTargetBitrateKbps(discSize string, totalSeconds float64) int {
if totalSeconds <= 0 {
return 0
@ -1016,6 +1034,19 @@ func (s *appState) setAuthorProgress(percent float64) {
}
func (s *appState) startAuthorGeneration() {
if s.authorVideoTSPath != "" {
title := authorOutputTitle(s)
outputPath := authorDefaultOutputPath("iso", title, []string{s.authorVideoTSPath})
if outputPath == "" {
dialog.ShowError(fmt.Errorf("failed to resolve output path"), s.window)
return
}
if err := s.addAuthorVideoTSToQueue(s.authorVideoTSPath, title, outputPath, true); err != nil {
dialog.ShowError(err, s.window)
}
return
}
paths, primary, err := s.authorSourcePaths()
if err != nil {
dialog.ShowError(err, s.window)
@ -1300,6 +1331,34 @@ func (s *appState) addAuthorToQueue(paths []string, region, aspect, title, outpu
return nil
}
func (s *appState) addAuthorVideoTSToQueue(videoTSPath, title, outputPath string, startNow bool) error {
if s.jobQueue == nil {
return fmt.Errorf("queue not initialized")
}
job := &queue.Job{
Type: queue.JobTypeAuthor,
Title: fmt.Sprintf("Author ISO: %s", title),
Description: fmt.Sprintf("VIDEO_TS -> %s", utils.ShortenMiddle(filepath.Base(outputPath), 40)),
InputFile: videoTSPath,
OutputFile: outputPath,
Config: map[string]interface{}{
"videoTSPath": videoTSPath,
"outputPath": outputPath,
"makeISO": true,
"title": title,
},
}
s.resetAuthorLog()
s.setAuthorStatus("Queued authoring job...")
s.setAuthorProgress(0)
s.jobQueue.Add(job)
if startNow && !s.jobQueue.IsRunning() {
s.jobQueue.Start()
}
return nil
}
func (s *appState) runAuthoringPipeline(ctx context.Context, paths []string, region, aspect, title, outputPath string, makeISO bool, clips []authorClip, chapters []authorChapter, treatAsChapters bool, logFn func(string), progressFn func(float64)) error {
workDir, err := os.MkdirTemp(utils.TempDir(), "videotools-author-")
if err != nil {
@ -1437,6 +1496,58 @@ func (s *appState) executeAuthorJob(ctx context.Context, job *queue.Job, progres
if cfg == nil {
return fmt.Errorf("author job config missing")
}
if videoTSPath := strings.TrimSpace(toString(cfg["videoTSPath"])); videoTSPath != "" {
outputPath := toString(cfg["outputPath"])
title := toString(cfg["title"])
if err := ensureAuthorDependencies(true); err != nil {
return err
}
logFile, logPath, logErr := createAuthorLog([]string{videoTSPath}, outputPath, true, "", "", title)
if logErr != nil {
logging.Debug(logging.CatSystem, "author log open failed: %v", logErr)
} else {
job.LogPath = logPath
defer logFile.Close()
}
appendLog := func(line string) {
if logFile != nil {
fmt.Fprintln(logFile, line)
}
app := fyne.CurrentApp()
if app != nil && app.Driver() != nil {
app.Driver().DoFromGoroutine(func() {
s.appendAuthorLog(line)
}, false)
}
}
updateProgress := func(percent float64) {
progressCallback(percent)
app := fyne.CurrentApp()
if app != nil && app.Driver() != nil {
app.Driver().DoFromGoroutine(func() {
s.setAuthorProgress(percent)
}, false)
}
}
appendLog(fmt.Sprintf("Authoring ISO from VIDEO_TS: %s", videoTSPath))
tool, args, err := buildISOCommand(outputPath, videoTSPath, title)
if err != nil {
return err
}
appendLog(fmt.Sprintf(">> %s %s", tool, strings.Join(args, " ")))
updateProgress(10)
if err := runCommandWithLogger(ctx, tool, args, appendLog); err != nil {
return err
}
updateProgress(100)
appendLog("ISO creation completed successfully.")
return nil
}
rawPaths, _ := cfg["paths"].([]interface{})
var paths []string
for _, p := range rawPaths {

21
main.go
View File

@ -926,6 +926,7 @@ type appState struct {
authorProgress float64
authorProgressBar *widget.ProgressBar
authorStatusLabel *widget.Label
authorVideoTSPath string
// Subtitles module state
subtitleVideoPath string
@ -9519,12 +9520,22 @@ func (s *appState) handleDrop(pos fyne.Position, items []fyne.URI) {
// If in author module, add video clips
if s.active == "author" {
var videoPaths []string
var videoTSPath string
for _, uri := range items {
if uri.Scheme() != "file" {
continue
}
path := uri.Path()
if info, err := os.Stat(path); err == nil && info.IsDir() {
if strings.EqualFold(filepath.Base(path), "VIDEO_TS") {
videoTSPath = path
break
}
videoTSChild := filepath.Join(path, "VIDEO_TS")
if info, err := os.Stat(videoTSChild); err == nil && info.IsDir() {
videoTSPath = videoTSChild
break
}
videos := s.findVideoFiles(path)
videoPaths = append(videoPaths, videos...)
} else if s.isVideoFile(path) {
@ -9532,6 +9543,15 @@ func (s *appState) handleDrop(pos fyne.Position, items []fyne.URI) {
}
}
if videoTSPath != "" {
s.authorVideoTSPath = videoTSPath
s.authorClips = nil
s.authorFile = nil
s.authorOutputType = "iso"
s.showAuthorView()
return
}
if len(videoPaths) == 0 {
logging.Debug(logging.CatUI, "no valid video files in dropped items")
return
@ -12565,7 +12585,6 @@ func buildCompareView(state *appState) fyne.CanvasObject {
return container.NewBorder(topBar, bottomBar, nil, nil, content)
}
// buildThumbView creates the thumbnail generation UI
func buildThumbView(state *appState) fyne.CanvasObject {
thumbColor := moduleColor("thumb")