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 (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
|
|
@ -28,6 +29,7 @@ func BuildQueueView(
|
|||
onStart func(),
|
||||
onClear func(),
|
||||
onClearAll func(),
|
||||
onCopyError func(string),
|
||||
titleColor, bgColor, textColor color.Color,
|
||||
) (fyne.CanvasObject, *container.Scroll) {
|
||||
// Header
|
||||
|
|
@ -71,7 +73,7 @@ func BuildQueueView(
|
|||
jobItems = append(jobItems, container.NewCenter(emptyMsg))
|
||||
} else {
|
||||
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),
|
||||
onMoveUp func(string),
|
||||
onMoveDown func(string),
|
||||
onCopyError func(string),
|
||||
bgColor, textColor color.Color,
|
||||
) fyne.CanvasObject {
|
||||
// Status color
|
||||
|
|
@ -157,6 +160,11 @@ func buildJobItem(
|
|||
widget.NewButton("Cancel", func() { onCancel(job.ID) }),
|
||||
)
|
||||
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,
|
||||
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