Add queue error copy, auto naming helper, and metadata templating
This commit is contained in:
parent
c908b22128
commit
53b1b839c5
83
internal/metadata/naming.go
Normal file
83
internal/metadata/naming.go
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tokenPattern = regexp.MustCompile(`<([a-zA-Z0-9_-]+)>`)
|
||||||
|
|
||||||
|
// RenderTemplate applies a simple <token> template to the provided metadata map.
|
||||||
|
// It returns the rendered string and a boolean indicating whether any tokens were resolved.
|
||||||
|
func RenderTemplate(pattern string, meta map[string]string, fallback string) (string, bool) {
|
||||||
|
pattern = strings.TrimSpace(pattern)
|
||||||
|
if pattern == "" {
|
||||||
|
return fallback, false
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := make(map[string]string, len(meta))
|
||||||
|
for k, v := range meta {
|
||||||
|
key := strings.ToLower(strings.TrimSpace(k))
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val := sanitize(v)
|
||||||
|
if val != "" {
|
||||||
|
normalized[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved := false
|
||||||
|
rendered := tokenPattern.ReplaceAllStringFunc(pattern, func(tok string) string {
|
||||||
|
match := tokenPattern.FindStringSubmatch(tok)
|
||||||
|
if len(match) != 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
key := strings.ToLower(match[1])
|
||||||
|
if val := normalized[key]; val != "" {
|
||||||
|
resolved = true
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
})
|
||||||
|
|
||||||
|
rendered = cleanup(rendered)
|
||||||
|
if rendered == "" {
|
||||||
|
return fallback, false
|
||||||
|
}
|
||||||
|
return rendered, resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitize(value string) string {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
value = strings.Map(func(r rune) rune {
|
||||||
|
switch r {
|
||||||
|
case '<', '>', '"', '/', '\\', '|', '?', '*', ':':
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if unicode.IsControl(r) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}, value)
|
||||||
|
|
||||||
|
// Collapse repeated whitespace
|
||||||
|
value = strings.Join(strings.Fields(value), " ")
|
||||||
|
return strings.Trim(value, " .-_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanup(s string) string {
|
||||||
|
// Remove leftover template brackets or duplicate separators.
|
||||||
|
s = strings.ReplaceAll(s, "<>", "")
|
||||||
|
for strings.Contains(s, " ") {
|
||||||
|
s = strings.ReplaceAll(s, " ", " ")
|
||||||
|
}
|
||||||
|
for strings.Contains(s, "__") {
|
||||||
|
s = strings.ReplaceAll(s, "__", "_")
|
||||||
|
}
|
||||||
|
for strings.Contains(s, "--") {
|
||||||
|
s = strings.ReplaceAll(s, "--", "-")
|
||||||
|
}
|
||||||
|
return strings.Trim(s, " .-_")
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package ui
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
|
@ -28,6 +29,7 @@ func BuildQueueView(
|
||||||
onStart func(),
|
onStart func(),
|
||||||
onClear func(),
|
onClear func(),
|
||||||
onClearAll func(),
|
onClearAll func(),
|
||||||
|
onCopyError func(string),
|
||||||
titleColor, bgColor, textColor color.Color,
|
titleColor, bgColor, textColor color.Color,
|
||||||
) (fyne.CanvasObject, *container.Scroll) {
|
) (fyne.CanvasObject, *container.Scroll) {
|
||||||
// Header
|
// Header
|
||||||
|
|
@ -71,7 +73,7 @@ func BuildQueueView(
|
||||||
jobItems = append(jobItems, container.NewCenter(emptyMsg))
|
jobItems = append(jobItems, container.NewCenter(emptyMsg))
|
||||||
} else {
|
} else {
|
||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
jobItems = append(jobItems, buildJobItem(job, onPause, onResume, onCancel, onRemove, onMoveUp, onMoveDown, bgColor, textColor))
|
jobItems = append(jobItems, buildJobItem(job, onPause, onResume, onCancel, onRemove, onMoveUp, onMoveDown, onCopyError, bgColor, textColor))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,6 +101,7 @@ func buildJobItem(
|
||||||
onRemove func(string),
|
onRemove func(string),
|
||||||
onMoveUp func(string),
|
onMoveUp func(string),
|
||||||
onMoveDown func(string),
|
onMoveDown func(string),
|
||||||
|
onCopyError func(string),
|
||||||
bgColor, textColor color.Color,
|
bgColor, textColor color.Color,
|
||||||
) fyne.CanvasObject {
|
) fyne.CanvasObject {
|
||||||
// Status color
|
// Status color
|
||||||
|
|
@ -157,6 +160,11 @@ func buildJobItem(
|
||||||
widget.NewButton("Cancel", func() { onCancel(job.ID) }),
|
widget.NewButton("Cancel", func() { onCancel(job.ID) }),
|
||||||
)
|
)
|
||||||
case queue.JobStatusCompleted, queue.JobStatusFailed, queue.JobStatusCancelled:
|
case queue.JobStatusCompleted, queue.JobStatusFailed, queue.JobStatusCancelled:
|
||||||
|
if job.Status == queue.JobStatusFailed && strings.TrimSpace(job.Error) != "" && onCopyError != nil {
|
||||||
|
buttons = append(buttons,
|
||||||
|
widget.NewButton("Copy Error", func() { onCopyError(job.ID) }),
|
||||||
|
)
|
||||||
|
}
|
||||||
buttons = append(buttons,
|
buttons = append(buttons,
|
||||||
widget.NewButton("Remove", func() { onRemove(job.ID) }),
|
widget.NewButton("Remove", func() { onRemove(job.ID) }),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
79
naming_helpers.go
Normal file
79
naming_helpers.go
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.leaktechnologies.dev/stu/VideoTools/internal/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func defaultOutputBase(src *videoSource) string {
|
||||||
|
if src == nil {
|
||||||
|
return "converted"
|
||||||
|
}
|
||||||
|
base := strings.TrimSuffix(src.DisplayName, filepath.Ext(src.DisplayName))
|
||||||
|
return base + "-convert"
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveOutputBase returns the output base for a source.
|
||||||
|
// keepExisting preserves manual edits when auto-naming is disabled; it is ignored when auto-naming is on.
|
||||||
|
func (s *appState) resolveOutputBase(src *videoSource, keepExisting bool) string {
|
||||||
|
fallback := defaultOutputBase(src)
|
||||||
|
|
||||||
|
// Auto-naming overrides manual values.
|
||||||
|
if s.convert.UseAutoNaming && src != nil && strings.TrimSpace(s.convert.AutoNameTemplate) != "" {
|
||||||
|
if name, ok := metadata.RenderTemplate(s.convert.AutoNameTemplate, buildNamingMetadata(src), fallback); ok || name != "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
if keepExisting {
|
||||||
|
if base := strings.TrimSpace(s.convert.OutputBase); base != "" {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildNamingMetadata(src *videoSource) map[string]string {
|
||||||
|
meta := map[string]string{}
|
||||||
|
if src == nil {
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
meta["filename"] = strings.TrimSuffix(filepath.Base(src.Path), filepath.Ext(src.Path))
|
||||||
|
meta["format"] = src.Format
|
||||||
|
meta["codec"] = src.VideoCodec
|
||||||
|
if src.Width > 0 && src.Height > 0 {
|
||||||
|
meta["width"] = fmt.Sprintf("%d", src.Width)
|
||||||
|
meta["height"] = fmt.Sprintf("%d", src.Height)
|
||||||
|
meta["resolution"] = fmt.Sprintf("%dx%d", src.Width, src.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range src.Metadata {
|
||||||
|
meta[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
aliasMetadata(meta, "title", "title")
|
||||||
|
aliasMetadata(meta, "scene", "title", "comment", "description")
|
||||||
|
aliasMetadata(meta, "studio", "studio", "publisher", "label")
|
||||||
|
aliasMetadata(meta, "actress", "actress", "performer", "performers", "artist", "actors", "cast")
|
||||||
|
aliasMetadata(meta, "series", "series", "album")
|
||||||
|
aliasMetadata(meta, "date", "date", "year")
|
||||||
|
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func aliasMetadata(meta map[string]string, target string, keys ...string) {
|
||||||
|
if meta[target] != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, key := range keys {
|
||||||
|
if val := meta[strings.ToLower(key)]; strings.TrimSpace(val) != "" {
|
||||||
|
meta[target] = val
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user