- Apply go formatting across internal packages - Clean up imports and code style
334 lines
10 KiB
Go
334 lines
10 KiB
Go
package convert
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// DVDNTSCPreset creates a ConvertConfig optimized for DVD-Video NTSC output
|
|
// This preset generates MPEG-2 program streams (.mpg) that are:
|
|
// - Fully DVD-compliant (720x480@29.97fps NTSC)
|
|
// - Region-free
|
|
// - Compatible with DVDStyler and professional DVD authoring software
|
|
// - Playable on PS2, standalone DVD players, and modern systems
|
|
func DVDNTSCPreset() ConvertConfig {
|
|
return ConvertConfig{
|
|
SelectedFormat: FormatOption{Label: "MPEG-2 (DVD NTSC)", Ext: ".mpg", VideoCodec: "mpeg2video"},
|
|
Quality: "Standard (CRF 23)", // DVD uses bitrate control, not CRF
|
|
Mode: "Advanced",
|
|
VideoCodec: "MPEG-2",
|
|
EncoderPreset: "medium",
|
|
BitrateMode: "CBR", // DVD requires constant bitrate
|
|
VideoBitrate: "6000k",
|
|
TargetResolution: "720x480",
|
|
FrameRate: "29.97",
|
|
PixelFormat: "yuv420p",
|
|
HardwareAccel: "none", // MPEG-2 encoding doesn't benefit much from GPU acceleration
|
|
AudioCodec: "AC-3",
|
|
AudioBitrate: "192k",
|
|
AudioChannels: "Stereo",
|
|
InverseTelecine: false, // Set based on source
|
|
AspectHandling: "letterbox",
|
|
OutputAspect: "source",
|
|
}
|
|
}
|
|
|
|
// DVDValidationWarning represents a validation issue with DVD encoding
|
|
type DVDValidationWarning struct {
|
|
Severity string // "info", "warning", "error"
|
|
Message string
|
|
Action string // What will be done to fix it
|
|
}
|
|
|
|
// ValidateDVDNTSC performs comprehensive validation on a video for DVD-NTSC output
|
|
func ValidateDVDNTSC(src *VideoSource, cfg ConvertConfig) []DVDValidationWarning {
|
|
var warnings []DVDValidationWarning
|
|
|
|
if src == nil {
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "error",
|
|
Message: "No video source selected",
|
|
Action: "Cannot proceed without a source video",
|
|
})
|
|
return warnings
|
|
}
|
|
|
|
// 1. Framerate Validation
|
|
if src.FrameRate > 0 {
|
|
normalizedRate := normalizeFrameRate(src.FrameRate)
|
|
switch normalizedRate {
|
|
case "23.976":
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "warning",
|
|
Message: fmt.Sprintf("Input framerate is %.2f fps (23.976p)", src.FrameRate),
|
|
Action: "Will apply 3:2 pulldown to convert to 29.97fps (requires interlacing)",
|
|
})
|
|
case "24.0":
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "warning",
|
|
Message: fmt.Sprintf("Input framerate is %.2f fps (24p)", src.FrameRate),
|
|
Action: "Will apply 3:2 pulldown to convert to 29.97fps (requires interlacing)",
|
|
})
|
|
case "29.97":
|
|
// Perfect - no warning
|
|
case "30.0":
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "info",
|
|
Message: fmt.Sprintf("Input framerate is %.2f fps (30p)", src.FrameRate),
|
|
Action: "Will convert to 29.97fps (NTSC standard)",
|
|
})
|
|
case "59.94":
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "warning",
|
|
Message: fmt.Sprintf("Input framerate is %.2f fps (59.94p)", src.FrameRate),
|
|
Action: "Will decimate to 29.97fps (dropping every other frame)",
|
|
})
|
|
case "60.0":
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "warning",
|
|
Message: fmt.Sprintf("Input framerate is %.2f fps (60p)", src.FrameRate),
|
|
Action: "Will decimate to 29.97fps (dropping every other frame)",
|
|
})
|
|
case "vfr":
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "error",
|
|
Message: "Input is Variable Frame Rate (VFR)",
|
|
Action: "Will force constant frame rate at 29.97fps (may cause sync issues)",
|
|
})
|
|
default:
|
|
if src.FrameRate < 15 {
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "error",
|
|
Message: fmt.Sprintf("Input framerate is %.2f fps (too low for DVD)", src.FrameRate),
|
|
Action: "Cannot encode - DVD requires minimum 23.976fps",
|
|
})
|
|
} else if src.FrameRate > 60 {
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "warning",
|
|
Message: fmt.Sprintf("Input framerate is %.2f fps (higher than DVD standard)", src.FrameRate),
|
|
Action: "Will decimate to 29.97fps",
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Resolution Validation
|
|
if src.Width != 720 || src.Height != 480 {
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "info",
|
|
Message: fmt.Sprintf("Input resolution is %dx%d (not 720x480)", src.Width, src.Height),
|
|
Action: "Will scale to 720x480 with aspect-ratio correction",
|
|
})
|
|
}
|
|
|
|
// 3. Audio Sample Rate Validation
|
|
if src.AudioRate > 0 {
|
|
if src.AudioRate != 48000 {
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "warning",
|
|
Message: fmt.Sprintf("Audio sample rate is %d Hz (not 48 kHz)", src.AudioRate),
|
|
Action: "Will resample to 48 kHz (DVD standard)",
|
|
})
|
|
}
|
|
}
|
|
|
|
// 4. Interlacing Analysis
|
|
if !src.IsProgressive() {
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "info",
|
|
Message: "Input is interlaced",
|
|
Action: "Will encode as interlaced (progressive deinterlacing not applied)",
|
|
})
|
|
} else {
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "info",
|
|
Message: "Input is progressive",
|
|
Action: "Will encode as progressive (no interlacing applied)",
|
|
})
|
|
}
|
|
|
|
// 5. Bitrate Validation
|
|
maxDVDBitrate := 9000.0
|
|
if strings.HasSuffix(cfg.VideoBitrate, "k") {
|
|
bitrateStr := strings.TrimSuffix(cfg.VideoBitrate, "k")
|
|
var bitrate float64
|
|
if _, err := fmt.Sscanf(bitrateStr, "%f", &bitrate); err == nil {
|
|
if bitrate > maxDVDBitrate {
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "error",
|
|
Message: fmt.Sprintf("Video bitrate %s exceeds DVD maximum of %.0fk", cfg.VideoBitrate, maxDVDBitrate),
|
|
Action: "Will cap at 9000k (PS2 safe limit)",
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// 6. Audio Codec Validation
|
|
if cfg.AudioCodec != "AC-3" && cfg.AudioCodec != "Copy" {
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "warning",
|
|
Message: fmt.Sprintf("Audio codec is %s (DVD standard is AC-3)", cfg.AudioCodec),
|
|
Action: "Recommend using AC-3 for maximum compatibility",
|
|
})
|
|
}
|
|
|
|
// 7. Aspect Ratio Validation
|
|
if src.Width > 0 && src.Height > 0 {
|
|
sourceAspect := float64(src.Width) / float64(src.Height)
|
|
validAspects := map[string]float64{
|
|
"4:3": 1.333,
|
|
"16:9": 1.778,
|
|
}
|
|
found := false
|
|
for _, ratio := range validAspects {
|
|
// Allow 1% tolerance
|
|
if diff := sourceAspect - ratio; diff < 0 && diff > -0.02 || diff >= 0 && diff < 0.02 {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
warnings = append(warnings, DVDValidationWarning{
|
|
Severity: "warning",
|
|
Message: fmt.Sprintf("Aspect ratio is %.2f:1 (not standard 4:3 or 16:9)", sourceAspect),
|
|
Action: fmt.Sprintf("Will apply %s with aspect correction", cfg.AspectHandling),
|
|
})
|
|
}
|
|
}
|
|
|
|
return warnings
|
|
}
|
|
|
|
// normalizeFrameRate categorizes a framerate value
|
|
func normalizeFrameRate(rate float64) string {
|
|
if rate < 15 {
|
|
return "low"
|
|
}
|
|
// Check for common framerates with tolerance
|
|
checks := []struct {
|
|
name string
|
|
min, max float64
|
|
}{
|
|
{"23.976", 23.9, 24.0},
|
|
{"24.0", 23.99, 24.01},
|
|
{"29.97", 29.9, 30.0},
|
|
{"30.0", 30.0, 30.01},
|
|
{"59.94", 59.9, 60.0},
|
|
{"60.0", 60.0, 60.01},
|
|
}
|
|
for _, c := range checks {
|
|
if rate >= c.min && rate <= c.max {
|
|
return c.name
|
|
}
|
|
}
|
|
return fmt.Sprintf("%.2f", rate)
|
|
}
|
|
|
|
// BuildDVDFFmpegArgs constructs FFmpeg arguments for DVD-NTSC encoding
|
|
// This ensures all parameters are DVD-compliant and correctly formatted
|
|
func BuildDVDFFmpegArgs(inputPath, outputPath string, cfg ConvertConfig, src *VideoSource) []string {
|
|
args := []string{
|
|
"-y",
|
|
"-hide_banner",
|
|
"-loglevel", "error",
|
|
"-i", inputPath,
|
|
}
|
|
|
|
// Video filters
|
|
var vf []string
|
|
|
|
// Scaling to DVD resolution with aspect preservation
|
|
if src.Width != 720 || src.Height != 480 {
|
|
// Use scale filter with aspect ratio handling
|
|
vf = append(vf, "scale=720:480:force_original_aspect_ratio=1")
|
|
|
|
// Add aspect ratio handling (pad/crop)
|
|
switch cfg.AspectHandling {
|
|
case "letterbox":
|
|
vf = append(vf, "pad=720:480:(ow-iw)/2:(oh-ih)/2")
|
|
case "pillarbox":
|
|
vf = append(vf, "pad=720:480:(ow-iw)/2:(oh-ih)/2")
|
|
}
|
|
}
|
|
|
|
// Set Display Aspect Ratio (DAR) - tell decoder the aspect
|
|
if cfg.OutputAspect == "16:9" {
|
|
vf = append(vf, "setdar=16/9")
|
|
} else {
|
|
vf = append(vf, "setdar=4/3")
|
|
}
|
|
|
|
// Set Sample Aspect Ratio (SAR) - DVD standard
|
|
vf = append(vf, "setsar=1")
|
|
|
|
// Framerate - always to 29.97 for NTSC
|
|
vf = append(vf, "fps=30000/1001")
|
|
|
|
if len(vf) > 0 {
|
|
args = append(args, "-vf", strings.Join(vf, ","))
|
|
}
|
|
|
|
// Video codec - MPEG-2 for DVD
|
|
args = append(args,
|
|
"-c:v", "mpeg2video",
|
|
"-r", "30000/1001",
|
|
"-b:v", "6000k",
|
|
"-maxrate", "9000k",
|
|
"-bufsize", "1835k",
|
|
"-g", "15", // GOP size
|
|
"-flags", "+mv4", // Use four motion vector candidates
|
|
"-pix_fmt", "yuv420p",
|
|
)
|
|
|
|
// Optional: Interlacing flags
|
|
// If the source is interlaced, we can preserve that:
|
|
if !src.IsProgressive() {
|
|
args = append(args, "-flags", "+ilme+ildct")
|
|
}
|
|
|
|
// Audio codec - AC-3 (Dolby Digital)
|
|
args = append(args,
|
|
"-c:a", "ac3",
|
|
"-b:a", "192k",
|
|
"-ar", "48000",
|
|
"-ac", "2",
|
|
)
|
|
|
|
// Progress monitoring
|
|
args = append(args,
|
|
"-progress", "pipe:1",
|
|
"-nostats",
|
|
outputPath,
|
|
)
|
|
|
|
return args
|
|
}
|
|
|
|
// DVDNTSCInfo returns a human-readable description of the DVD-NTSC preset
|
|
func DVDNTSCInfo() string {
|
|
return `DVD-NTSC (Region-Free) Output
|
|
|
|
This preset generates professional-grade MPEG-2 program streams (.mpg) compatible with:
|
|
- DVD authoring software (DVDStyler, Adobe Encore, etc.)
|
|
- PlayStation 2 and standalone DVD players
|
|
- Modern media centers and PC-based DVD players
|
|
|
|
Technical Specifications:
|
|
Video Codec: MPEG-2 (mpeg2video)
|
|
Container: MPEG Program Stream (.mpg)
|
|
Resolution: 720x480 (NTSC Full D1)
|
|
Frame Rate: 29.97 fps (30000/1001)
|
|
Aspect Ratio: 4:3 or 16:9 (user-selectable)
|
|
Bitrate: 6000 kbps (average), 9000 kbps (max)
|
|
GOP Size: 15 frames
|
|
Interlacing: Progressive or Interlaced (auto-detected)
|
|
|
|
Audio Codec: AC-3 (Dolby Digital)
|
|
Channels: Stereo (2.0)
|
|
Bitrate: 192 kbps
|
|
Sample Rate: 48 kHz (mandatory)
|
|
|
|
The output is guaranteed to be importable directly into DVDStyler without
|
|
re-encoding warnings, and will play flawlessly on PS2 and standalone players.`
|
|
}
|