Add Real-ESRGAN upscale controls and pipeline
This commit is contained in:
parent
28e2f40b75
commit
271c83ec74
10
DONE.md
10
DONE.md
|
|
@ -30,7 +30,15 @@ This file tracks completed features, fixes, and milestones.
|
||||||
- **UI Polish**
|
- **UI Polish**
|
||||||
- "Run Benchmark" button highlighted (HighImportance) on first run
|
- "Run Benchmark" button highlighted (HighImportance) on first run
|
||||||
- Returns to normal styling after initial benchmark
|
- Returns to normal styling after initial benchmark
|
||||||
- Guides new users to run initial benchmark
|
- Guides new users to run initial benchmark
|
||||||
|
|
||||||
|
- ✅ **AI Upscale Integration (Real-ESRGAN)**
|
||||||
|
- Added model presets with anime/general variants
|
||||||
|
- Processing presets (Ultra Fast → Maximum Quality) with tile/TTA tuning
|
||||||
|
- Upscale factor selection + output adjustment slider
|
||||||
|
- Tile size, output frame format, GPU and thread controls
|
||||||
|
- ncnn backend pipeline (extract → AI upscale → reassemble)
|
||||||
|
- Filters and frame rate conversion applied before AI upscaling
|
||||||
|
|
||||||
- ✅ **Bitrate Preset Simplification**
|
- ✅ **Bitrate Preset Simplification**
|
||||||
- Reduced from 13 confusing options to 6 clear presets
|
- Reduced from 13 confusing options to 6 clear presets
|
||||||
|
|
|
||||||
1
TODO.md
1
TODO.md
|
|
@ -68,6 +68,7 @@ This file tracks upcoming features, improvements, and known issues.
|
||||||
- Reduce module video pane min sizes to allow GNOME snapping
|
- Reduce module video pane min sizes to allow GNOME snapping
|
||||||
- Cache/temp directory setting with SSD recommendation
|
- Cache/temp directory setting with SSD recommendation
|
||||||
- Frame interpolation presets in Filters with Upscale linkage
|
- Frame interpolation presets in Filters with Upscale linkage
|
||||||
|
- Real-ESRGAN AI upscale controls with ncnn pipeline (models, presets, tiles, TTA)
|
||||||
|
|
||||||
*Last Updated: 2025-12-20*
|
*Last Updated: 2025-12-20*
|
||||||
|
|
||||||
|
|
|
||||||
699
main.go
699
main.go
|
|
@ -814,6 +814,21 @@ type appState struct {
|
||||||
upscaleAIEnabled bool // Use AI upscaling if available
|
upscaleAIEnabled bool // Use AI upscaling if available
|
||||||
upscaleAIModel string // realesrgan, realesrgan-anime, none
|
upscaleAIModel string // realesrgan, realesrgan-anime, none
|
||||||
upscaleAIAvailable bool // Runtime detection
|
upscaleAIAvailable bool // Runtime detection
|
||||||
|
upscaleAIBackend string // ncnn, python
|
||||||
|
upscaleAIPreset string // Ultra Fast, Fast, Balanced, High Quality, Maximum Quality
|
||||||
|
upscaleAIScale float64 // Base outscale when not matching target
|
||||||
|
upscaleAIScaleUseTarget bool // Use target resolution to compute scale
|
||||||
|
upscaleAIOutputAdjust float64 // Post scale adjustment multiplier
|
||||||
|
upscaleAIFaceEnhance bool // Face enhancement (Python only)
|
||||||
|
upscaleAIDenoise float64 // Denoise strength (0-1, model-specific)
|
||||||
|
upscaleAITile int // Tile size for AI upscaling
|
||||||
|
upscaleAIGPU int // GPU index (if supported)
|
||||||
|
upscaleAIGPUAuto bool // Auto-select GPU
|
||||||
|
upscaleAIThreadsLoad int // Threading for load stage
|
||||||
|
upscaleAIThreadsProc int // Threading for processing stage
|
||||||
|
upscaleAIThreadsSave int // Threading for save stage
|
||||||
|
upscaleAITTA bool // Test-time augmentation
|
||||||
|
upscaleAIOutputFormat string // png, jpg, webp
|
||||||
upscaleApplyFilters bool // Apply filters from Filters module
|
upscaleApplyFilters bool // Apply filters from Filters module
|
||||||
upscaleFilterChain []string // Transferred filters from Filters module
|
upscaleFilterChain []string // Transferred filters from Filters module
|
||||||
upscaleFrameRate string // Source, 24, 30, 60, or custom
|
upscaleFrameRate string // Source, 24, 30, 60, or custom
|
||||||
|
|
@ -4385,10 +4400,29 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre
|
||||||
if v, ok := cfg["preserveAR"].(bool); ok {
|
if v, ok := cfg["preserveAR"].(bool); ok {
|
||||||
preserveAR = v
|
preserveAR = v
|
||||||
}
|
}
|
||||||
// useAI := cfg["useAI"].(bool) // TODO: Implement AI upscaling in future
|
useAI := false
|
||||||
|
if v, ok := cfg["useAI"].(bool); ok {
|
||||||
|
useAI = v
|
||||||
|
}
|
||||||
|
aiBackend, _ := cfg["aiBackend"].(string)
|
||||||
|
aiModel, _ := cfg["aiModel"].(string)
|
||||||
|
aiScale := toFloat(cfg["aiScale"])
|
||||||
|
aiScaleUseTarget, _ := cfg["aiScaleUseTarget"].(bool)
|
||||||
|
aiOutputAdjust := toFloat(cfg["aiOutputAdjust"])
|
||||||
|
aiFaceEnhance, _ := cfg["aiFaceEnhance"].(bool)
|
||||||
|
aiDenoise := toFloat(cfg["aiDenoise"])
|
||||||
|
aiTile := int(toFloat(cfg["aiTile"]))
|
||||||
|
aiGPU := int(toFloat(cfg["aiGPU"]))
|
||||||
|
aiGPUAuto, _ := cfg["aiGPUAuto"].(bool)
|
||||||
|
aiThreadsLoad := int(toFloat(cfg["aiThreadsLoad"]))
|
||||||
|
aiThreadsProc := int(toFloat(cfg["aiThreadsProc"]))
|
||||||
|
aiThreadsSave := int(toFloat(cfg["aiThreadsSave"]))
|
||||||
|
aiTTA, _ := cfg["aiTTA"].(bool)
|
||||||
|
aiOutputFormat, _ := cfg["aiOutputFormat"].(string)
|
||||||
applyFilters := cfg["applyFilters"].(bool)
|
applyFilters := cfg["applyFilters"].(bool)
|
||||||
frameRate, _ := cfg["frameRate"].(string)
|
frameRate, _ := cfg["frameRate"].(string)
|
||||||
useMotionInterp, _ := cfg["useMotionInterpolation"].(bool)
|
useMotionInterp, _ := cfg["useMotionInterpolation"].(bool)
|
||||||
|
sourceFrameRate := toFloat(cfg["sourceFrameRate"])
|
||||||
|
|
||||||
if progressCallback != nil {
|
if progressCallback != nil {
|
||||||
progressCallback(0)
|
progressCallback(0)
|
||||||
|
|
@ -4410,39 +4444,293 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build filter chain
|
// Build filter chain
|
||||||
var filters []string
|
var baseFilters []string
|
||||||
|
|
||||||
// Add filters from Filters module if requested
|
// Add filters from Filters module if requested
|
||||||
if applyFilters {
|
if applyFilters {
|
||||||
if filterChain, ok := cfg["filterChain"].([]interface{}); ok {
|
if filterChain, ok := cfg["filterChain"].([]interface{}); ok {
|
||||||
for _, f := range filterChain {
|
for _, f := range filterChain {
|
||||||
if filterStr, ok := f.(string); ok {
|
if filterStr, ok := f.(string); ok {
|
||||||
filters = append(filters, filterStr)
|
baseFilters = append(baseFilters, filterStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if filterChain, ok := cfg["filterChain"].([]string); ok {
|
||||||
|
baseFilters = append(baseFilters, filterChain...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add scale filter (preserve aspect by default)
|
|
||||||
scaleFilter := buildUpscaleFilter(targetWidth, targetHeight, method, preserveAR)
|
|
||||||
logging.Debug(logging.CatFFMPEG, "upscale: target=%dx%d preserveAR=%v method=%s filter=%s", targetWidth, targetHeight, preserveAR, method, scaleFilter)
|
|
||||||
filters = append(filters, scaleFilter)
|
|
||||||
|
|
||||||
// Add frame rate conversion if requested
|
// Add frame rate conversion if requested
|
||||||
if frameRate != "" && frameRate != "Source" {
|
if frameRate != "" && frameRate != "Source" {
|
||||||
if useMotionInterp {
|
if useMotionInterp {
|
||||||
// Use motion interpolation for smooth frame rate changes
|
// Use motion interpolation for smooth frame rate changes
|
||||||
filters = append(filters, fmt.Sprintf("minterpolate=fps=%s:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1", frameRate))
|
baseFilters = append(baseFilters, fmt.Sprintf("minterpolate=fps=%s:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1", frameRate))
|
||||||
} else {
|
} else {
|
||||||
// Simple frame rate change (duplicates/drops frames)
|
// Simple frame rate change (duplicates/drops frames)
|
||||||
filters = append(filters, "fps="+frameRate)
|
baseFilters = append(baseFilters, "fps="+frameRate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if useAI {
|
||||||
|
if aiBackend != "ncnn" {
|
||||||
|
return fmt.Errorf("AI upscaling backend not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
if aiModel == "" {
|
||||||
|
aiModel = "realesrgan-x4plus"
|
||||||
|
}
|
||||||
|
if aiOutputFormat == "" {
|
||||||
|
aiOutputFormat = "png"
|
||||||
|
}
|
||||||
|
if aiOutputAdjust <= 0 {
|
||||||
|
aiOutputAdjust = 1.0
|
||||||
|
}
|
||||||
|
if aiScale <= 0 {
|
||||||
|
aiScale = 4.0
|
||||||
|
}
|
||||||
|
if aiThreadsLoad <= 0 {
|
||||||
|
aiThreadsLoad = 1
|
||||||
|
}
|
||||||
|
if aiThreadsProc <= 0 {
|
||||||
|
aiThreadsProc = 2
|
||||||
|
}
|
||||||
|
if aiThreadsSave <= 0 {
|
||||||
|
aiThreadsSave = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
outScale := aiScale
|
||||||
|
if aiScaleUseTarget {
|
||||||
|
switch targetPreset {
|
||||||
|
case "", "Match Source":
|
||||||
|
outScale = 1.0
|
||||||
|
case "2X (relative)":
|
||||||
|
outScale = 2.0
|
||||||
|
case "4X (relative)":
|
||||||
|
outScale = 4.0
|
||||||
|
default:
|
||||||
|
if sourceHeight > 0 && targetHeight > 0 {
|
||||||
|
outScale = float64(targetHeight) / float64(sourceHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outScale *= aiOutputAdjust
|
||||||
|
if outScale < 0.1 {
|
||||||
|
outScale = 0.1
|
||||||
|
} else if outScale > 8.0 {
|
||||||
|
outScale = 8.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if progressCallback != nil {
|
||||||
|
progressCallback(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
workDir, err := os.MkdirTemp(utils.TempDir(), "vt-ai-upscale-")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create temp dir: %w", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(workDir)
|
||||||
|
|
||||||
|
inputFramesDir := filepath.Join(workDir, "frames_in")
|
||||||
|
outputFramesDir := filepath.Join(workDir, "frames_out")
|
||||||
|
if err := os.MkdirAll(inputFramesDir, 0o755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create frames dir: %w", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(outputFramesDir, 0o755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create frames dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var preFilter string
|
||||||
|
if len(baseFilters) > 0 {
|
||||||
|
preFilter = strings.Join(baseFilters, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
frameExt := strings.ToLower(aiOutputFormat)
|
||||||
|
if frameExt == "jpeg" {
|
||||||
|
frameExt = "jpg"
|
||||||
|
}
|
||||||
|
framePattern := filepath.Join(inputFramesDir, "frame_%08d."+frameExt)
|
||||||
|
extractArgs := []string{"-y", "-hide_banner", "-i", inputPath}
|
||||||
|
if preFilter != "" {
|
||||||
|
extractArgs = append(extractArgs, "-vf", preFilter)
|
||||||
|
}
|
||||||
|
extractArgs = append(extractArgs, "-start_number", "0", framePattern)
|
||||||
|
|
||||||
|
logFile, logPath, _ := createConversionLog(inputPath, outputPath, extractArgs)
|
||||||
|
if logFile != nil {
|
||||||
|
fmt.Fprintln(logFile, "Stage: extract frames for AI upscaling")
|
||||||
|
}
|
||||||
|
|
||||||
|
runFFmpegWithProgress := func(args []string, duration float64, startPct, endPct float64) error {
|
||||||
|
cmd := exec.CommandContext(ctx, platformConfig.FFmpegPath, args...)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
|
stderr, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create stderr pipe: %w", err)
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return fmt.Errorf("failed to start ffmpeg: %w", err)
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(stderr)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if logFile != nil {
|
||||||
|
fmt.Fprintln(logFile, line)
|
||||||
|
}
|
||||||
|
if strings.Contains(line, "time=") && duration > 0 {
|
||||||
|
if idx := strings.Index(line, "time="); idx != -1 {
|
||||||
|
timeStr := line[idx+5:]
|
||||||
|
if spaceIdx := strings.Index(timeStr, " "); spaceIdx != -1 {
|
||||||
|
timeStr = timeStr[:spaceIdx]
|
||||||
|
}
|
||||||
|
var h, m int
|
||||||
|
var s float64
|
||||||
|
if _, err := fmt.Sscanf(timeStr, "%d:%d:%f", &h, &m, &s); err == nil {
|
||||||
|
currentTime := float64(h*3600+m*60) + s
|
||||||
|
progress := startPct + ((currentTime / duration) * (endPct - startPct))
|
||||||
|
if progressCallback != nil {
|
||||||
|
progressCallback(progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
duration := toFloat(cfg["duration"])
|
||||||
|
if err := runFFmpegWithProgress(extractArgs, duration, 1, 35); err != nil {
|
||||||
|
if logFile != nil {
|
||||||
|
fmt.Fprintf(logFile, "\nStatus: failed during extraction at %s\nError: %v\n", time.Now().Format(time.RFC3339), err)
|
||||||
|
_ = logFile.Close()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to extract frames: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if progressCallback != nil {
|
||||||
|
progressCallback(40)
|
||||||
|
}
|
||||||
|
|
||||||
|
aiArgs := []string{
|
||||||
|
"-i", inputFramesDir,
|
||||||
|
"-o", outputFramesDir,
|
||||||
|
"-n", aiModel,
|
||||||
|
"-s", fmt.Sprintf("%.2f", outScale),
|
||||||
|
"-j", fmt.Sprintf("%d:%d:%d", aiThreadsLoad, aiThreadsProc, aiThreadsSave),
|
||||||
|
"-f", frameExt,
|
||||||
|
}
|
||||||
|
if aiTile > 0 {
|
||||||
|
aiArgs = append(aiArgs, "-t", strconv.Itoa(aiTile))
|
||||||
|
}
|
||||||
|
if !aiGPUAuto {
|
||||||
|
aiArgs = append(aiArgs, "-g", strconv.Itoa(aiGPU))
|
||||||
|
}
|
||||||
|
if aiTTA {
|
||||||
|
aiArgs = append(aiArgs, "-x")
|
||||||
|
}
|
||||||
|
if aiModel == "realesr-general-x4v3" {
|
||||||
|
aiArgs = append(aiArgs, "-dn", fmt.Sprintf("%.2f", aiDenoise))
|
||||||
|
}
|
||||||
|
if aiFaceEnhance && logFile != nil {
|
||||||
|
fmt.Fprintln(logFile, "Note: face enhancement requested but not supported in ncnn backend")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logFile != nil {
|
||||||
|
fmt.Fprintln(logFile, "Stage: Real-ESRGAN")
|
||||||
|
fmt.Fprintf(logFile, "Command: realesrgan-ncnn-vulkan %s\n", strings.Join(aiArgs, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
aiCmd := exec.CommandContext(ctx, "realesrgan-ncnn-vulkan", aiArgs...)
|
||||||
|
utils.ApplyNoWindow(aiCmd)
|
||||||
|
aiOut, err := aiCmd.CombinedOutput()
|
||||||
|
if logFile != nil && len(aiOut) > 0 {
|
||||||
|
fmt.Fprintln(logFile, string(aiOut))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if logFile != nil {
|
||||||
|
fmt.Fprintf(logFile, "\nStatus: failed during AI upscale at %s\nError: %v\n", time.Now().Format(time.RFC3339), err)
|
||||||
|
_ = logFile.Close()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("AI upscaling failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if progressCallback != nil {
|
||||||
|
progressCallback(70)
|
||||||
|
}
|
||||||
|
|
||||||
|
if frameRate == "" || frameRate == "Source" {
|
||||||
|
if sourceFrameRate <= 0 {
|
||||||
|
if src, err := probeVideo(inputPath); err == nil && src != nil {
|
||||||
|
sourceFrameRate = src.FrameRate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if fps, err := strconv.ParseFloat(frameRate, 64); err == nil {
|
||||||
|
sourceFrameRate = fps
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceFrameRate <= 0 {
|
||||||
|
sourceFrameRate = 30.0
|
||||||
|
}
|
||||||
|
|
||||||
|
reassemblePattern := filepath.Join(outputFramesDir, "frame_%08d."+frameExt)
|
||||||
|
reassembleArgs := []string{
|
||||||
|
"-y",
|
||||||
|
"-hide_banner",
|
||||||
|
"-framerate", fmt.Sprintf("%.3f", sourceFrameRate),
|
||||||
|
"-i", reassemblePattern,
|
||||||
|
"-i", inputPath,
|
||||||
|
"-map", "0:v:0",
|
||||||
|
"-map", "1:a?",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final scale to ensure target height/aspect (optional)
|
||||||
|
if targetPreset != "" && targetPreset != "Match Source" {
|
||||||
|
finalScale := buildUpscaleFilter(targetWidth, targetHeight, method, preserveAR)
|
||||||
|
reassembleArgs = append(reassembleArgs, "-vf", finalScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
reassembleArgs = append(reassembleArgs,
|
||||||
|
"-c:v", "libx264",
|
||||||
|
"-preset", "slow",
|
||||||
|
"-crf", "0",
|
||||||
|
"-pix_fmt", "yuv420p",
|
||||||
|
"-c:a", "copy",
|
||||||
|
"-shortest",
|
||||||
|
outputPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
if logFile != nil {
|
||||||
|
fmt.Fprintln(logFile, "Stage: reassemble")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runFFmpegWithProgress(reassembleArgs, duration, 70, 100); err != nil {
|
||||||
|
if logFile != nil {
|
||||||
|
fmt.Fprintf(logFile, "\nStatus: failed during reassemble at %s\nError: %v\n", time.Now().Format(time.RFC3339), err)
|
||||||
|
_ = logFile.Close()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to reassemble upscaled video: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if logFile != nil {
|
||||||
|
fmt.Fprintf(logFile, "\nStatus: completed at %s\n", time.Now().Format(time.RFC3339))
|
||||||
|
_ = logFile.Close()
|
||||||
|
job.LogPath = logPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if progressCallback != nil {
|
||||||
|
progressCallback(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add scale filter (preserve aspect by default)
|
||||||
|
scaleFilter := buildUpscaleFilter(targetWidth, targetHeight, method, preserveAR)
|
||||||
|
logging.Debug(logging.CatFFMPEG, "upscale: target=%dx%d preserveAR=%v method=%s filter=%s", targetWidth, targetHeight, preserveAR, method, scaleFilter)
|
||||||
|
baseFilters = append(baseFilters, scaleFilter)
|
||||||
|
|
||||||
// Combine filters
|
// Combine filters
|
||||||
var vfilter string
|
var vfilter string
|
||||||
if len(filters) > 0 {
|
if len(baseFilters) > 0 {
|
||||||
vfilter = strings.Join(filters, ",")
|
vfilter = strings.Join(baseFilters, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build FFmpeg command
|
// Build FFmpeg command
|
||||||
|
|
@ -4804,6 +5092,18 @@ func buildFFmpegCommandFromJob(job *queue.Job) string {
|
||||||
args = append(args, "-ac", "2")
|
args = append(args, "-ac", "2")
|
||||||
case "5.1":
|
case "5.1":
|
||||||
args = append(args, "-ac", "6")
|
args = append(args, "-ac", "6")
|
||||||
|
case "Left to Stereo":
|
||||||
|
// Copy left channel to both left and right
|
||||||
|
args = append(args, "-af", "pan=stereo|c0=c0|c1=c0")
|
||||||
|
case "Right to Stereo":
|
||||||
|
// Copy right channel to both left and right
|
||||||
|
args = append(args, "-af", "pan=stereo|c0=c1|c1=c1")
|
||||||
|
case "Mix to Stereo":
|
||||||
|
// Downmix both channels together, then duplicate to L+R
|
||||||
|
args = append(args, "-af", "pan=stereo|c0=0.5*c0+0.5*c1|c1=0.5*c0+0.5*c1")
|
||||||
|
case "Swap L/R":
|
||||||
|
// Swap left and right channels
|
||||||
|
args = append(args, "-af", "pan=stereo|c0=c1|c1=c0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6699,7 +6999,16 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
audioBitrateSelect.SetSelected(state.convert.AudioBitrate)
|
audioBitrateSelect.SetSelected(state.convert.AudioBitrate)
|
||||||
|
|
||||||
// Audio Channels
|
// Audio Channels
|
||||||
audioChannelsSelect := widget.NewSelect([]string{"Source", "Mono", "Stereo", "5.1"}, func(value string) {
|
audioChannelsSelect := widget.NewSelect([]string{
|
||||||
|
"Source",
|
||||||
|
"Mono",
|
||||||
|
"Stereo",
|
||||||
|
"5.1",
|
||||||
|
"Left to Stereo",
|
||||||
|
"Right to Stereo",
|
||||||
|
"Mix to Stereo",
|
||||||
|
"Swap L/R",
|
||||||
|
}, func(value string) {
|
||||||
state.convert.AudioChannels = value
|
state.convert.AudioChannels = value
|
||||||
logging.Debug(logging.CatUI, "audio channels set to %s", value)
|
logging.Debug(logging.CatUI, "audio channels set to %s", value)
|
||||||
})
|
})
|
||||||
|
|
@ -12804,15 +13113,29 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
state.upscaleTargetRes = "Match Source"
|
state.upscaleTargetRes = "Match Source"
|
||||||
}
|
}
|
||||||
if state.upscaleAIModel == "" {
|
if state.upscaleAIModel == "" {
|
||||||
state.upscaleAIModel = "realesrgan" // General purpose AI model
|
state.upscaleAIModel = "realesrgan-x4plus" // General purpose AI model
|
||||||
}
|
}
|
||||||
if state.upscaleFrameRate == "" {
|
if state.upscaleFrameRate == "" {
|
||||||
state.upscaleFrameRate = "Source"
|
state.upscaleFrameRate = "Source"
|
||||||
}
|
}
|
||||||
|
if state.upscaleAIPreset == "" {
|
||||||
|
state.upscaleAIPreset = "Balanced"
|
||||||
|
state.upscaleAIScale = 4.0
|
||||||
|
state.upscaleAIScaleUseTarget = true
|
||||||
|
state.upscaleAIOutputAdjust = 1.0
|
||||||
|
state.upscaleAIDenoise = 0.5
|
||||||
|
state.upscaleAITile = 512
|
||||||
|
state.upscaleAIOutputFormat = "png"
|
||||||
|
state.upscaleAIGPUAuto = true
|
||||||
|
state.upscaleAIThreadsLoad = 1
|
||||||
|
state.upscaleAIThreadsProc = 2
|
||||||
|
state.upscaleAIThreadsSave = 2
|
||||||
|
}
|
||||||
|
|
||||||
// Check AI availability on first load
|
// Check AI availability on first load
|
||||||
if !state.upscaleAIAvailable {
|
if state.upscaleAIBackend == "" {
|
||||||
state.upscaleAIAvailable = checkAIUpscaleAvailable()
|
state.upscaleAIBackend = detectAIUpscaleBackend()
|
||||||
|
state.upscaleAIAvailable = state.upscaleAIBackend != ""
|
||||||
}
|
}
|
||||||
if len(state.filterActiveChain) > 0 {
|
if len(state.filterActiveChain) > 0 {
|
||||||
state.upscaleFilterChain = append([]string{}, state.filterActiveChain...)
|
state.upscaleFilterChain = append([]string{}, state.filterActiveChain...)
|
||||||
|
|
@ -12950,23 +13273,67 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
widget.NewLabel("Motion interpolation creates smooth in-between frames"),
|
widget.NewLabel("Motion interpolation creates smooth in-between frames"),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
aiModelOptions := aiUpscaleModelOptions()
|
||||||
|
aiModelLabel := aiUpscaleModelLabel(state.upscaleAIModel)
|
||||||
|
if aiModelLabel == "" && len(aiModelOptions) > 0 {
|
||||||
|
aiModelLabel = aiModelOptions[0]
|
||||||
|
}
|
||||||
|
|
||||||
// AI Upscaling Section
|
// AI Upscaling Section
|
||||||
var aiSection *widget.Card
|
var aiSection *widget.Card
|
||||||
if state.upscaleAIAvailable {
|
if state.upscaleAIAvailable {
|
||||||
aiModelSelect := widget.NewSelect([]string{
|
var aiTileSelect *widget.Select
|
||||||
"realesrgan (General Purpose)",
|
var aiTTACheck *widget.Check
|
||||||
"realesrgan-anime (Anime/Animation)",
|
var aiDenoiseSlider *widget.Slider
|
||||||
}, func(s string) {
|
var denoiseHint *widget.Label
|
||||||
if strings.Contains(s, "anime") {
|
|
||||||
state.upscaleAIModel = "realesrgan-anime"
|
applyAIPreset := func(preset string) {
|
||||||
} else {
|
state.upscaleAIPreset = preset
|
||||||
state.upscaleAIModel = "realesrgan"
|
switch preset {
|
||||||
|
case "Ultra Fast":
|
||||||
|
state.upscaleAITile = 800
|
||||||
|
state.upscaleAITTA = false
|
||||||
|
case "Fast":
|
||||||
|
state.upscaleAITile = 800
|
||||||
|
state.upscaleAITTA = false
|
||||||
|
case "Balanced":
|
||||||
|
state.upscaleAITile = 512
|
||||||
|
state.upscaleAITTA = false
|
||||||
|
case "High Quality":
|
||||||
|
state.upscaleAITile = 256
|
||||||
|
state.upscaleAITTA = false
|
||||||
|
case "Maximum Quality":
|
||||||
|
state.upscaleAITile = 0
|
||||||
|
state.upscaleAITTA = true
|
||||||
|
}
|
||||||
|
if aiTileSelect != nil {
|
||||||
|
switch state.upscaleAITile {
|
||||||
|
case 256:
|
||||||
|
aiTileSelect.SetSelected("256")
|
||||||
|
case 512:
|
||||||
|
aiTileSelect.SetSelected("512")
|
||||||
|
case 800:
|
||||||
|
aiTileSelect.SetSelected("800")
|
||||||
|
default:
|
||||||
|
aiTileSelect.SetSelected("Auto")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aiTTACheck != nil {
|
||||||
|
aiTTACheck.SetChecked(state.upscaleAITTA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDenoiseAvailability := func(model string) {
|
||||||
|
if aiDenoiseSlider == nil || denoiseHint == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if model == "realesr-general-x4v3" {
|
||||||
|
aiDenoiseSlider.Enable()
|
||||||
|
denoiseHint.SetText("Denoise available on General Tiny model")
|
||||||
|
} else {
|
||||||
|
aiDenoiseSlider.Disable()
|
||||||
|
denoiseHint.SetText("Denoise only supported on General Tiny model")
|
||||||
}
|
}
|
||||||
})
|
|
||||||
if strings.Contains(state.upscaleAIModel, "anime") {
|
|
||||||
aiModelSelect.SetSelected("realesrgan-anime (Anime/Animation)")
|
|
||||||
} else {
|
|
||||||
aiModelSelect.SetSelected("realesrgan (General Purpose)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aiEnabledCheck := widget.NewCheck("Use AI Upscaling", func(checked bool) {
|
aiEnabledCheck := widget.NewCheck("Use AI Upscaling", func(checked bool) {
|
||||||
|
|
@ -12974,6 +13341,154 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
})
|
})
|
||||||
aiEnabledCheck.SetChecked(state.upscaleAIEnabled)
|
aiEnabledCheck.SetChecked(state.upscaleAIEnabled)
|
||||||
|
|
||||||
|
aiModelSelect := widget.NewSelect(aiModelOptions, func(s string) {
|
||||||
|
state.upscaleAIModel = aiUpscaleModelID(s)
|
||||||
|
aiModelLabel = s
|
||||||
|
updateDenoiseAvailability(state.upscaleAIModel)
|
||||||
|
})
|
||||||
|
if aiModelLabel != "" {
|
||||||
|
aiModelSelect.SetSelected(aiModelLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
aiPresetSelect := widget.NewSelect([]string{"Ultra Fast", "Fast", "Balanced", "High Quality", "Maximum Quality"}, func(s string) {
|
||||||
|
applyAIPreset(s)
|
||||||
|
})
|
||||||
|
aiPresetSelect.SetSelected(state.upscaleAIPreset)
|
||||||
|
|
||||||
|
aiScaleSelect := widget.NewSelect([]string{"Match Target", "1x", "2x", "3x", "4x", "8x"}, func(s string) {
|
||||||
|
if s == "Match Target" {
|
||||||
|
state.upscaleAIScaleUseTarget = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.upscaleAIScaleUseTarget = false
|
||||||
|
switch s {
|
||||||
|
case "1x":
|
||||||
|
state.upscaleAIScale = 1
|
||||||
|
case "2x":
|
||||||
|
state.upscaleAIScale = 2
|
||||||
|
case "3x":
|
||||||
|
state.upscaleAIScale = 3
|
||||||
|
case "4x":
|
||||||
|
state.upscaleAIScale = 4
|
||||||
|
case "8x":
|
||||||
|
state.upscaleAIScale = 8
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if state.upscaleAIScaleUseTarget {
|
||||||
|
aiScaleSelect.SetSelected("Match Target")
|
||||||
|
} else {
|
||||||
|
aiScaleSelect.SetSelected(fmt.Sprintf("%.0fx", state.upscaleAIScale))
|
||||||
|
}
|
||||||
|
|
||||||
|
aiAdjustLabel := widget.NewLabel(fmt.Sprintf("Adjustment: %.2fx", state.upscaleAIOutputAdjust))
|
||||||
|
aiAdjustSlider := widget.NewSlider(0.5, 2.0)
|
||||||
|
aiAdjustSlider.Value = state.upscaleAIOutputAdjust
|
||||||
|
aiAdjustSlider.Step = 0.05
|
||||||
|
aiAdjustSlider.OnChanged = func(v float64) {
|
||||||
|
state.upscaleAIOutputAdjust = v
|
||||||
|
aiAdjustLabel.SetText(fmt.Sprintf("Adjustment: %.2fx", v))
|
||||||
|
}
|
||||||
|
|
||||||
|
aiDenoiseLabel := widget.NewLabel(fmt.Sprintf("Denoise: %.2f", state.upscaleAIDenoise))
|
||||||
|
aiDenoiseSlider = widget.NewSlider(0.0, 1.0)
|
||||||
|
aiDenoiseSlider.Value = state.upscaleAIDenoise
|
||||||
|
aiDenoiseSlider.Step = 0.05
|
||||||
|
aiDenoiseSlider.OnChanged = func(v float64) {
|
||||||
|
state.upscaleAIDenoise = v
|
||||||
|
aiDenoiseLabel.SetText(fmt.Sprintf("Denoise: %.2f", v))
|
||||||
|
}
|
||||||
|
|
||||||
|
aiTileSelect = widget.NewSelect([]string{"Auto", "256", "512", "800"}, func(s string) {
|
||||||
|
switch s {
|
||||||
|
case "Auto":
|
||||||
|
state.upscaleAITile = 0
|
||||||
|
case "256":
|
||||||
|
state.upscaleAITile = 256
|
||||||
|
case "512":
|
||||||
|
state.upscaleAITile = 512
|
||||||
|
case "800":
|
||||||
|
state.upscaleAITile = 800
|
||||||
|
}
|
||||||
|
})
|
||||||
|
switch state.upscaleAITile {
|
||||||
|
case 256:
|
||||||
|
aiTileSelect.SetSelected("256")
|
||||||
|
case 512:
|
||||||
|
aiTileSelect.SetSelected("512")
|
||||||
|
case 800:
|
||||||
|
aiTileSelect.SetSelected("800")
|
||||||
|
default:
|
||||||
|
aiTileSelect.SetSelected("Auto")
|
||||||
|
}
|
||||||
|
|
||||||
|
aiOutputFormatSelect := widget.NewSelect([]string{"PNG", "JPG", "WEBP"}, func(s string) {
|
||||||
|
state.upscaleAIOutputFormat = strings.ToLower(s)
|
||||||
|
})
|
||||||
|
switch strings.ToLower(state.upscaleAIOutputFormat) {
|
||||||
|
case "jpg", "jpeg":
|
||||||
|
aiOutputFormatSelect.SetSelected("JPG")
|
||||||
|
case "webp":
|
||||||
|
aiOutputFormatSelect.SetSelected("WEBP")
|
||||||
|
default:
|
||||||
|
aiOutputFormatSelect.SetSelected("PNG")
|
||||||
|
}
|
||||||
|
|
||||||
|
aiFaceCheck := widget.NewCheck("Face Enhancement (requires Python/GFPGAN)", func(checked bool) {
|
||||||
|
state.upscaleAIFaceEnhance = checked
|
||||||
|
})
|
||||||
|
aiFaceAvailable := checkAIFaceEnhanceAvailable(state.upscaleAIBackend)
|
||||||
|
if !aiFaceAvailable {
|
||||||
|
aiFaceCheck.Disable()
|
||||||
|
}
|
||||||
|
aiFaceCheck.SetChecked(state.upscaleAIFaceEnhance && aiFaceAvailable)
|
||||||
|
|
||||||
|
aiTTACheck = widget.NewCheck("Enable TTA (slower, higher quality)", func(checked bool) {
|
||||||
|
state.upscaleAITTA = checked
|
||||||
|
})
|
||||||
|
aiTTACheck.SetChecked(state.upscaleAITTA)
|
||||||
|
|
||||||
|
aiGPUSelect := widget.NewSelect([]string{"Auto", "0", "1", "2"}, func(s string) {
|
||||||
|
if s == "Auto" {
|
||||||
|
state.upscaleAIGPUAuto = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.upscaleAIGPUAuto = false
|
||||||
|
if gpu, err := strconv.Atoi(s); err == nil {
|
||||||
|
state.upscaleAIGPU = gpu
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if state.upscaleAIGPUAuto {
|
||||||
|
aiGPUSelect.SetSelected("Auto")
|
||||||
|
} else {
|
||||||
|
aiGPUSelect.SetSelected(strconv.Itoa(state.upscaleAIGPU))
|
||||||
|
}
|
||||||
|
|
||||||
|
threadOptions := []string{"1", "2", "3", "4"}
|
||||||
|
aiThreadsLoad := widget.NewSelect(threadOptions, func(s string) {
|
||||||
|
if v, err := strconv.Atoi(s); err == nil {
|
||||||
|
state.upscaleAIThreadsLoad = v
|
||||||
|
}
|
||||||
|
})
|
||||||
|
aiThreadsLoad.SetSelected(strconv.Itoa(state.upscaleAIThreadsLoad))
|
||||||
|
|
||||||
|
aiThreadsProc := widget.NewSelect(threadOptions, func(s string) {
|
||||||
|
if v, err := strconv.Atoi(s); err == nil {
|
||||||
|
state.upscaleAIThreadsProc = v
|
||||||
|
}
|
||||||
|
})
|
||||||
|
aiThreadsProc.SetSelected(strconv.Itoa(state.upscaleAIThreadsProc))
|
||||||
|
|
||||||
|
aiThreadsSave := widget.NewSelect(threadOptions, func(s string) {
|
||||||
|
if v, err := strconv.Atoi(s); err == nil {
|
||||||
|
state.upscaleAIThreadsSave = v
|
||||||
|
}
|
||||||
|
})
|
||||||
|
aiThreadsSave.SetSelected(strconv.Itoa(state.upscaleAIThreadsSave))
|
||||||
|
|
||||||
|
denoiseHint = widget.NewLabel("")
|
||||||
|
denoiseHint.TextStyle = fyne.TextStyle{Italic: true}
|
||||||
|
updateDenoiseAvailability(state.upscaleAIModel)
|
||||||
|
|
||||||
aiSection = widget.NewCard("AI Upscaling", "✓ Available", container.NewVBox(
|
aiSection = widget.NewCard("AI Upscaling", "✓ Available", container.NewVBox(
|
||||||
widget.NewLabel("Real-ESRGAN detected - enhanced quality available"),
|
widget.NewLabel("Real-ESRGAN detected - enhanced quality available"),
|
||||||
aiEnabledCheck,
|
aiEnabledCheck,
|
||||||
|
|
@ -12981,6 +13496,36 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
widget.NewLabel("AI Model:"),
|
widget.NewLabel("AI Model:"),
|
||||||
aiModelSelect,
|
aiModelSelect,
|
||||||
),
|
),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Processing Preset:"),
|
||||||
|
aiPresetSelect,
|
||||||
|
),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Upscale Factor:"),
|
||||||
|
aiScaleSelect,
|
||||||
|
),
|
||||||
|
container.NewVBox(aiAdjustLabel, aiAdjustSlider),
|
||||||
|
container.NewVBox(aiDenoiseLabel, aiDenoiseSlider, denoiseHint),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Tile Size:"),
|
||||||
|
aiTileSelect,
|
||||||
|
),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Output Frames:"),
|
||||||
|
aiOutputFormatSelect,
|
||||||
|
),
|
||||||
|
aiFaceCheck,
|
||||||
|
aiTTACheck,
|
||||||
|
widget.NewSeparator(),
|
||||||
|
widget.NewLabel("Advanced (ncnn backend)"),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("GPU:"),
|
||||||
|
aiGPUSelect,
|
||||||
|
),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Threads (Load/Proc/Save):"),
|
||||||
|
container.NewGridWithColumns(3, aiThreadsLoad, aiThreadsProc, aiThreadsSave),
|
||||||
|
),
|
||||||
widget.NewLabel("Note: AI upscaling is slower but produces higher quality results"),
|
widget.NewLabel("Note: AI upscaling is slower but produces higher quality results"),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -13050,9 +13595,25 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
"preserveAR": preserveAspect,
|
"preserveAR": preserveAspect,
|
||||||
"useAI": state.upscaleAIEnabled && state.upscaleAIAvailable,
|
"useAI": state.upscaleAIEnabled && state.upscaleAIAvailable,
|
||||||
"aiModel": state.upscaleAIModel,
|
"aiModel": state.upscaleAIModel,
|
||||||
|
"aiBackend": state.upscaleAIBackend,
|
||||||
|
"aiPreset": state.upscaleAIPreset,
|
||||||
|
"aiScale": state.upscaleAIScale,
|
||||||
|
"aiScaleUseTarget": state.upscaleAIScaleUseTarget,
|
||||||
|
"aiOutputAdjust": state.upscaleAIOutputAdjust,
|
||||||
|
"aiFaceEnhance": state.upscaleAIFaceEnhance,
|
||||||
|
"aiDenoise": state.upscaleAIDenoise,
|
||||||
|
"aiTile": float64(state.upscaleAITile),
|
||||||
|
"aiGPU": float64(state.upscaleAIGPU),
|
||||||
|
"aiGPUAuto": state.upscaleAIGPUAuto,
|
||||||
|
"aiThreadsLoad": float64(state.upscaleAIThreadsLoad),
|
||||||
|
"aiThreadsProc": float64(state.upscaleAIThreadsProc),
|
||||||
|
"aiThreadsSave": float64(state.upscaleAIThreadsSave),
|
||||||
|
"aiTTA": state.upscaleAITTA,
|
||||||
|
"aiOutputFormat": state.upscaleAIOutputFormat,
|
||||||
"applyFilters": state.upscaleApplyFilters,
|
"applyFilters": state.upscaleApplyFilters,
|
||||||
"filterChain": state.upscaleFilterChain,
|
"filterChain": state.upscaleFilterChain,
|
||||||
"duration": state.upscaleFile.Duration,
|
"duration": state.upscaleFile.Duration,
|
||||||
|
"sourceFrameRate": state.upscaleFile.FrameRate,
|
||||||
"frameRate": state.upscaleFrameRate,
|
"frameRate": state.upscaleFrameRate,
|
||||||
"useMotionInterpolation": state.upscaleMotionInterpolation,
|
"useMotionInterpolation": state.upscaleMotionInterpolation,
|
||||||
},
|
},
|
||||||
|
|
@ -13124,27 +13685,83 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
return container.NewBorder(topBar, bottomBar, nil, nil, content)
|
return container.NewBorder(topBar, bottomBar, nil, nil, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkAIUpscaleAvailable checks if Real-ESRGAN is available on the system
|
// detectAIUpscaleBackend returns the available Real-ESRGAN backend ("ncnn", "python", or "").
|
||||||
func checkAIUpscaleAvailable() bool {
|
func detectAIUpscaleBackend() string {
|
||||||
// Check for realesrgan-ncnn-vulkan (most common binary distribution)
|
if _, err := exec.LookPath("realesrgan-ncnn-vulkan"); err == nil {
|
||||||
cmd := exec.Command("realesrgan-ncnn-vulkan", "--help")
|
return "ncnn"
|
||||||
if err := cmd.Run(); err == nil {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for Python-based Real-ESRGAN
|
cmd := exec.Command("python3", "-c", "import realesrgan")
|
||||||
cmd = exec.Command("python3", "-c", "import realesrgan")
|
|
||||||
if err := cmd.Run(); err == nil {
|
if err := cmd.Run(); err == nil {
|
||||||
return true
|
return "python"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for alternative Python command
|
|
||||||
cmd = exec.Command("python", "-c", "import realesrgan")
|
cmd = exec.Command("python", "-c", "import realesrgan")
|
||||||
if err := cmd.Run(); err == nil {
|
if err := cmd.Run(); err == nil {
|
||||||
return true
|
return "python"
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkAIFaceEnhanceAvailable verifies whether face enhancement tooling is available.
|
||||||
|
func checkAIFaceEnhanceAvailable(backend string) bool {
|
||||||
|
if backend != "python" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
cmd := exec.Command("python3", "-c", "import realesrgan, gfpgan")
|
||||||
|
if err := cmd.Run(); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
cmd = exec.Command("python", "-c", "import realesrgan, gfpgan")
|
||||||
|
return cmd.Run() == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func aiUpscaleModelOptions() []string {
|
||||||
|
return []string{
|
||||||
|
"General (RealESRGAN_x4plus)",
|
||||||
|
"Anime/Illustration (RealESRGAN_x4plus_anime_6B)",
|
||||||
|
"Anime Video (realesr-animevideov3)",
|
||||||
|
"General Tiny (realesr-general-x4v3)",
|
||||||
|
"2x General (RealESRGAN_x2plus)",
|
||||||
|
"Clean Restore (realesrnet-x4plus)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func aiUpscaleModelID(label string) string {
|
||||||
|
switch label {
|
||||||
|
case "Anime/Illustration (RealESRGAN_x4plus_anime_6B)":
|
||||||
|
return "realesrgan-x4plus-anime"
|
||||||
|
case "Anime Video (realesr-animevideov3)":
|
||||||
|
return "realesr-animevideov3"
|
||||||
|
case "General Tiny (realesr-general-x4v3)":
|
||||||
|
return "realesr-general-x4v3"
|
||||||
|
case "2x General (RealESRGAN_x2plus)":
|
||||||
|
return "realesrgan-x2plus"
|
||||||
|
case "Clean Restore (realesrnet-x4plus)":
|
||||||
|
return "realesrnet-x4plus"
|
||||||
|
default:
|
||||||
|
return "realesrgan-x4plus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func aiUpscaleModelLabel(modelID string) string {
|
||||||
|
switch modelID {
|
||||||
|
case "realesrgan-x4plus-anime":
|
||||||
|
return "Anime/Illustration (RealESRGAN_x4plus_anime_6B)"
|
||||||
|
case "realesr-animevideov3":
|
||||||
|
return "Anime Video (realesr-animevideov3)"
|
||||||
|
case "realesr-general-x4v3":
|
||||||
|
return "General Tiny (realesr-general-x4v3)"
|
||||||
|
case "realesrgan-x2plus":
|
||||||
|
return "2x General (RealESRGAN_x2plus)"
|
||||||
|
case "realesrnet-x4plus":
|
||||||
|
return "Clean Restore (realesrnet-x4plus)"
|
||||||
|
case "realesrgan-x4plus":
|
||||||
|
return "General (RealESRGAN_x4plus)"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseResolutionPreset parses resolution preset strings and returns target dimensions and whether to preserve aspect.
|
// parseResolutionPreset parses resolution preset strings and returns target dimensions and whether to preserve aspect.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user