From 6a2f1fff3f648d874778fc2ddab1a70eba013f46 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Wed, 3 Dec 2025 10:00:14 -0500 Subject: [PATCH] Add target file size feature and fix multiple encoding issues - Add TargetFileSize mode with automatic bitrate calculation - Add CalculateBitrateForTargetSize and ParseFileSize utility functions - Fix NVENC hardware encoding (remove incorrect -hwaccel cuda flag) - Fix auto-detection override when hardware accel set to none - Fix 10-bit pixel format incompatibility (change to 8-bit yuv420p) - Add enhanced video metadata display (PAR, color space, GOP size, audio bitrate, chapters) - Improve error reporting with FFmpeg stderr capture and exit code interpretation - Add interpretFFmpegError function for human-readable error messages --- assets/logo/VT_Icon.png | Bin 39589 -> 46089 bytes internal/convert/ffmpeg.go | 140 ++++++++++++++++++++---- internal/convert/types.go | 84 +++++++++++++- main.go | 218 +++++++++++++++++++++++++++++++++---- 4 files changed, 401 insertions(+), 41 deletions(-) diff --git a/assets/logo/VT_Icon.png b/assets/logo/VT_Icon.png index dace2043bf9425b728e3aacb2977b4ead42d83e1..9fe8e7b4ad99a78792b0006752a2400df84b0eb3 100644 GIT binary patch literal 46089 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 diff --git a/internal/convert/ffmpeg.go b/internal/convert/ffmpeg.go index fc43990..42319d1 100644 --- a/internal/convert/ffmpeg.go +++ b/internal/convert/ffmpeg.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "time" "git.leaktechnologies.dev/stu/VideoTools/internal/logging" @@ -96,26 +97,34 @@ func ProbeVideo(path string) (*VideoSource, error) { var result struct { Format struct { - Filename string `json:"filename"` - Format string `json:"format_long_name"` - Duration string `json:"duration"` - FormatName string `json:"format_name"` - BitRate string `json:"bit_rate"` + Filename string `json:"filename"` + Format string `json:"format_long_name"` + Duration string `json:"duration"` + FormatName string `json:"format_name"` + BitRate string `json:"bit_rate"` + Tags map[string]interface{} `json:"tags"` } `json:"format"` - Streams []struct { - Index int `json:"index"` - CodecType string `json:"codec_type"` - CodecName string `json:"codec_name"` - Width int `json:"width"` - Height int `json:"height"` - Duration string `json:"duration"` - BitRate string `json:"bit_rate"` - PixFmt string `json:"pix_fmt"` - SampleRate string `json:"sample_rate"` - Channels int `json:"channels"` - AvgFrameRate string `json:"avg_frame_rate"` - FieldOrder string `json:"field_order"` - Disposition struct { + Chapters []interface{} `json:"chapters"` + Streams []struct { + Index int `json:"index"` + CodecType string `json:"codec_type"` + CodecName string `json:"codec_name"` + Width int `json:"width"` + Height int `json:"height"` + Duration string `json:"duration"` + BitRate string `json:"bit_rate"` + PixFmt string `json:"pix_fmt"` + SampleRate string `json:"sample_rate"` + Channels int `json:"channels"` + AvgFrameRate string `json:"avg_frame_rate"` + FieldOrder string `json:"field_order"` + SampleAspectRat string `json:"sample_aspect_ratio"` + DisplayAspect string `json:"display_aspect_ratio"` + ColorSpace string `json:"color_space"` + ColorRange string `json:"color_range"` + ColorPrimaries string `json:"color_primaries"` + ColorTransfer string `json:"color_transfer"` + Disposition struct { AttachedPic int `json:"attached_pic"` } `json:"disposition"` } `json:"streams"` @@ -137,6 +146,22 @@ func ProbeVideo(path string) (*VideoSource, error) { src.Duration = val } } + + // Check for chapters + src.HasChapters = len(result.Chapters) > 0 + + // Check for metadata (title, artist, copyright, etc.) + if result.Format.Tags != nil && len(result.Format.Tags) > 0 { + // Look for common metadata tags + for key := range result.Format.Tags { + lowerKey := strings.ToLower(key) + if lowerKey == "title" || lowerKey == "artist" || lowerKey == "copyright" || + lowerKey == "comment" || lowerKey == "description" || lowerKey == "album" { + src.HasMetadata = true + break + } + } + } // Track if we've found the main video stream (not cover art) foundMainVideo := false var coverArtStreamIndex int = -1 @@ -170,6 +195,23 @@ func ProbeVideo(path string) (*VideoSource, error) { if stream.PixFmt != "" { src.PixelFormat = stream.PixFmt } + + // Capture additional metadata + if stream.SampleAspectRat != "" && stream.SampleAspectRat != "0:1" { + src.SampleAspectRatio = stream.SampleAspectRat + } + + // Color space information + if stream.ColorSpace != "" && stream.ColorSpace != "unknown" { + src.ColorSpace = stream.ColorSpace + } else if stream.ColorPrimaries != "" && stream.ColorPrimaries != "unknown" { + // Fallback to color primaries if color_space is not set + src.ColorSpace = stream.ColorPrimaries + } + + if stream.ColorRange != "" && stream.ColorRange != "unknown" { + src.ColorRange = stream.ColorRange + } } if src.Bitrate == 0 { if br, err := utils.ParseInt(stream.BitRate); err == nil { @@ -185,6 +227,9 @@ func ProbeVideo(path string) (*VideoSource, error) { if stream.Channels > 0 { src.Channels = stream.Channels } + if br, err := utils.ParseInt(stream.BitRate); err == nil && br > 0 { + src.AudioBitrate = br + } } } } @@ -207,5 +252,62 @@ func ProbeVideo(path string) (*VideoSource, error) { } } + // Probe GOP size by examining a few frames (only if we have video) + if foundMainVideo && src.Duration > 0 { + gopSize := detectGOPSize(ctx, path) + if gopSize > 0 { + src.GOPSize = gopSize + } + } + return src, nil } + +// detectGOPSize attempts to detect GOP size by examining key frames +func detectGOPSize(ctx context.Context, path string) int { + // Use ffprobe to show frames and look for key_frame markers + // We'll analyze the first 300 frames (about 10 seconds at 30fps) + cmd := exec.CommandContext(ctx, "ffprobe", + "-v", "quiet", + "-select_streams", "v:0", + "-show_entries", "frame=pict_type,key_frame", + "-read_intervals", "%+#300", + "-print_format", "json", + path, + ) + + out, err := cmd.Output() + if err != nil { + return 0 + } + + var result struct { + Frames []struct { + KeyFrame int `json:"key_frame"` + PictType string `json:"pict_type"` + } `json:"frames"` + } + + if err := json.Unmarshal(out, &result); err != nil { + return 0 + } + + // Find distances between key frames + var keyFramePositions []int + for i, frame := range result.Frames { + if frame.KeyFrame == 1 { + keyFramePositions = append(keyFramePositions, i) + } + } + + // Calculate average GOP size + if len(keyFramePositions) >= 2 { + var totalDistance int + for i := 1; i < len(keyFramePositions); i++ { + totalDistance += keyFramePositions[i] - keyFramePositions[i-1] + } + return totalDistance / (len(keyFramePositions) - 1) + } + + return 0 +} diff --git a/internal/convert/types.go b/internal/convert/types.go index 1b9bec2..4aa2e34 100644 --- a/internal/convert/types.go +++ b/internal/convert/types.go @@ -28,8 +28,9 @@ type ConvertConfig struct { VideoCodec string // H.264, H.265, VP9, AV1, Copy EncoderPreset string // ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow CRF string // Manual CRF value (0-51, or empty to use Quality preset) - BitrateMode string // CRF, CBR, VBR + BitrateMode string // CRF, CBR, VBR, TargetSize VideoBitrate string // For CBR/VBR modes (e.g., "5000k") + TargetFileSize string // Target file size (e.g., "25MB", "100MB", "8MB") - requires BitrateMode=TargetSize TargetResolution string // Source, 720p, 1080p, 1440p, 4K, or custom FrameRate string // Source, 24, 30, 60, or custom PixelFormat string // yuv420p, yuv422p, yuv444p @@ -76,7 +77,8 @@ type VideoSource struct { Duration float64 VideoCodec string AudioCodec string - Bitrate int + Bitrate int // Video bitrate in bits per second + AudioBitrate int // Audio bitrate in bits per second FrameRate float64 PixelFormat string AudioRate int @@ -84,6 +86,14 @@ type VideoSource struct { FieldOrder string PreviewFrames []string EmbeddedCoverArt string // Path to extracted embedded cover art, if any + + // Advanced metadata + SampleAspectRatio string // Pixel Aspect Ratio (SAR) - e.g., "1:1", "40:33" + ColorSpace string // Color space/primaries - e.g., "bt709", "bt601" + ColorRange string // Color range - "tv" (limited) or "pc" (full) + GOPSize int // GOP size / keyframe interval + HasChapters bool // Whether file has embedded chapters + HasMetadata bool // Whether file has title/copyright/etc metadata } // DurationString returns a human-readable duration string (HH:MM:SS or MM:SS) @@ -155,6 +165,76 @@ func ResolveTargetAspect(val string, src *VideoSource) float64 { return 0 } +// CalculateBitrateForTargetSize calculates the required video bitrate to hit a target file size +// targetSize: target file size in bytes +// duration: video duration in seconds +// audioBitrate: audio bitrate in bits per second +// Returns: video bitrate in bits per second +func CalculateBitrateForTargetSize(targetSize int64, duration float64, audioBitrate int) int { + if duration <= 0 { + return 0 + } + + // Reserve 3% for container overhead + targetSize = int64(float64(targetSize) * 0.97) + + // Calculate total bits available + totalBits := targetSize * 8 + + // Calculate audio bits + audioBits := int64(float64(audioBitrate) * duration) + + // Remaining bits for video + videoBits := totalBits - audioBits + if videoBits < 0 { + videoBits = totalBits / 2 // Fallback: split 50/50 if audio is too large + } + + // Calculate video bitrate + videoBitrate := int(float64(videoBits) / duration) + + // Minimum bitrate sanity check (100 kbps) + if videoBitrate < 100000 { + videoBitrate = 100000 + } + + return videoBitrate +} + +// ParseFileSize parses a file size string like "25MB", "100MB", "1.5GB" into bytes +func ParseFileSize(sizeStr string) (int64, error) { + sizeStr = strings.TrimSpace(strings.ToUpper(sizeStr)) + if sizeStr == "" { + return 0, fmt.Errorf("empty size string") + } + + // Extract number and unit + var value float64 + var unit string + + _, err := fmt.Sscanf(sizeStr, "%f%s", &value, &unit) + if err != nil { + return 0, fmt.Errorf("invalid size format: %s", sizeStr) + } + + // Convert to bytes + multiplier := int64(1) + switch unit { + case "KB": + multiplier = 1024 + case "MB": + multiplier = 1024 * 1024 + case "GB": + multiplier = 1024 * 1024 * 1024 + case "B", "": + multiplier = 1 + default: + return 0, fmt.Errorf("unknown unit: %s", unit) + } + + return int64(value * float64(multiplier)), nil +} + // AspectFilters returns FFmpeg filter strings for aspect ratio conversion func AspectFilters(target float64, mode string) []string { if target <= 0 { diff --git a/main.go b/main.go index 019975d..205a1a9 100644 --- a/main.go +++ b/main.go @@ -117,8 +117,9 @@ type convertConfig struct { VideoCodec string // H.264, H.265, VP9, AV1, Copy EncoderPreset string // ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow CRF string // Manual CRF value (0-51, or empty to use Quality preset) - BitrateMode string // CRF, CBR, VBR + BitrateMode string // CRF, CBR, VBR, TargetSize VideoBitrate string // For CBR/VBR modes (e.g., "5000k") + TargetFileSize string // Target file size (e.g., "25MB", "100MB") - requires BitrateMode=TargetSize TargetResolution string // Source, 720p, 1080p, 1440p, 4K, or custom FrameRate string // Source, 24, 30, 60, or custom PixelFormat string // yuv420p, yuv422p, yuv444p @@ -946,12 +947,15 @@ func (s *appState) executeConvertJob(ctx context.Context, job *queue.Job, progre args = append(args, "-i", coverArtPath) } - // Hardware acceleration + // Hardware acceleration for decoding + // Note: NVENC doesn't need -hwaccel for encoding, only for decoding hardwareAccel, _ := cfg["hardwareAccel"].(string) if hardwareAccel != "none" && hardwareAccel != "" { switch hardwareAccel { case "nvenc": - args = append(args, "-hwaccel", "cuda") + // For NVENC, we don't add -hwaccel flags + // The h264_nvenc/hevc_nvenc encoder handles GPU encoding directly + // Only add hwaccel if we want GPU decoding too, which can cause issues case "vaapi": args = append(args, "-hwaccel", "vaapi") case "qsv": @@ -1210,6 +1214,9 @@ func (s *appState) executeConvertJob(ctx context.Context, job *queue.Job, progre logging.Debug(logging.CatFFMPEG, "queue convert command: ffmpeg %s", strings.Join(args, " ")) + // Also print to stdout for debugging + fmt.Printf("\n=== FFMPEG COMMAND ===\nffmpeg %s\n======================\n\n", strings.Join(args, " ")) + // Execute FFmpeg cmd := exec.CommandContext(ctx, "ffmpeg", args...) stdout, err := cmd.StdoutPipe() @@ -1217,6 +1224,10 @@ func (s *appState) executeConvertJob(ctx context.Context, job *queue.Job, progre return fmt.Errorf("failed to create stdout pipe: %w", err) } + // Capture stderr for error messages + var stderrBuf strings.Builder + cmd.Stderr = &stderrBuf + if err := cmd.Start(); err != nil { return fmt.Errorf("failed to start ffmpeg: %w", err) } @@ -1250,7 +1261,35 @@ func (s *appState) executeConvertJob(ctx context.Context, job *queue.Job, progre } if err := cmd.Wait(); err != nil { - return fmt.Errorf("ffmpeg failed: %w", err) + stderrOutput := stderrBuf.String() + errorExplanation := interpretFFmpegError(err) + + // Check if this is a hardware encoding failure + isHardwareFailure := strings.Contains(stderrOutput, "No capable devices found") || + strings.Contains(stderrOutput, "Cannot load") || + strings.Contains(stderrOutput, "not available") && + (strings.Contains(stderrOutput, "nvenc") || + strings.Contains(stderrOutput, "qsv") || + strings.Contains(stderrOutput, "vaapi") || + strings.Contains(stderrOutput, "videotoolbox")) + + if isHardwareFailure && hardwareAccel != "none" && hardwareAccel != "" { + logging.Debug(logging.CatFFMPEG, "hardware encoding failed, will suggest software fallback") + return fmt.Errorf("hardware encoding (%s) failed - no compatible hardware found\n\nPlease disable hardware acceleration in the conversion settings and try again with software encoding.\n\nFFmpeg output:\n%s", hardwareAccel, stderrOutput) + } + + var errorMsg string + if errorExplanation != "" { + errorMsg = fmt.Sprintf("ffmpeg failed: %v - %s", err, errorExplanation) + } else { + errorMsg = fmt.Sprintf("ffmpeg failed: %v", err) + } + + if stderrOutput != "" { + logging.Debug(logging.CatFFMPEG, "ffmpeg stderr: %s", stderrOutput) + return fmt.Errorf("%s\n\nFFmpeg output:\n%s", errorMsg, stderrOutput) + } + return fmt.Errorf("%s", errorMsg) } logging.Debug(logging.CatFFMPEG, "queue conversion completed: %s", outputPath) @@ -1362,7 +1401,7 @@ func runGUI() { VideoBitrate: "5000k", TargetResolution: "Source", FrameRate: "Source", - PixelFormat: "yuv420p10le", + PixelFormat: "yuv420p", HardwareAccel: "none", TwoPass: false, H264Profile: "main", @@ -2264,33 +2303,88 @@ func buildMetadataPanel(state *appState, src *videoSource, min fyne.Size) (fyne. bitrate = fmt.Sprintf("%d kbps", src.Bitrate/1000) } + audioBitrate := "--" + if src.AudioBitrate > 0 { + audioBitrate = fmt.Sprintf("%d kbps", src.AudioBitrate/1000) + } + + // Format advanced metadata + par := utils.FirstNonEmpty(src.SampleAspectRatio, "1:1 (Square)") + if par == "1:1" || par == "1:1 (Square)" { + par = "1:1 (Square)" + } else { + par = par + " (Non-square)" + } + + colorSpace := utils.FirstNonEmpty(src.ColorSpace, "Unknown") + colorRange := utils.FirstNonEmpty(src.ColorRange, "Unknown") + if colorRange == "tv" { + colorRange = "Limited (TV)" + } else if colorRange == "pc" || colorRange == "jpeg" { + colorRange = "Full (PC)" + } + + interlacing := "Progressive" + if src.FieldOrder != "" && src.FieldOrder != "progressive" && src.FieldOrder != "unknown" { + interlacing = "Interlaced (" + src.FieldOrder + ")" + } + + gopSize := "--" + if src.GOPSize > 0 { + gopSize = fmt.Sprintf("%d frames", src.GOPSize) + } + + chapters := "No" + if src.HasChapters { + chapters = "Yes" + } + + metadata := "No" + if src.HasMetadata { + metadata = "Yes (title/copyright/etc)" + } + // Build metadata string for copying metadataText := fmt.Sprintf(`File: %s Format: %s Resolution: %dx%d Aspect Ratio: %s +Pixel Aspect Ratio: %s Duration: %s Video Codec: %s Video Bitrate: %s Frame Rate: %.2f fps Pixel Format: %s -Field Order: %s +Interlacing: %s +Color Space: %s +Color Range: %s +GOP Size: %s Audio Codec: %s +Audio Bitrate: %s Audio Rate: %d Hz -Channels: %s`, +Channels: %s +Chapters: %s +Metadata: %s`, src.DisplayName, utils.FirstNonEmpty(src.Format, "Unknown"), src.Width, src.Height, src.AspectRatioString(), + par, src.DurationString(), utils.FirstNonEmpty(src.VideoCodec, "Unknown"), bitrate, src.FrameRate, utils.FirstNonEmpty(src.PixelFormat, "Unknown"), - utils.FirstNonEmpty(src.FieldOrder, "Unknown"), + interlacing, + colorSpace, + colorRange, + gopSize, utils.FirstNonEmpty(src.AudioCodec, "Unknown"), + audioBitrate, src.AudioRate, utils.ChannelLabel(src.Channels), + chapters, + metadata, ) info := widget.NewForm( @@ -2298,15 +2392,22 @@ Channels: %s`, widget.NewFormItem("Format", widget.NewLabel(utils.FirstNonEmpty(src.Format, "Unknown"))), widget.NewFormItem("Resolution", widget.NewLabel(fmt.Sprintf("%dx%d", src.Width, src.Height))), widget.NewFormItem("Aspect Ratio", widget.NewLabel(src.AspectRatioString())), + widget.NewFormItem("Pixel Aspect Ratio", widget.NewLabel(par)), widget.NewFormItem("Duration", widget.NewLabel(src.DurationString())), widget.NewFormItem("Video Codec", widget.NewLabel(utils.FirstNonEmpty(src.VideoCodec, "Unknown"))), widget.NewFormItem("Video Bitrate", widget.NewLabel(bitrate)), widget.NewFormItem("Frame Rate", widget.NewLabel(fmt.Sprintf("%.2f fps", src.FrameRate))), widget.NewFormItem("Pixel Format", widget.NewLabel(utils.FirstNonEmpty(src.PixelFormat, "Unknown"))), - widget.NewFormItem("Field Order", widget.NewLabel(utils.FirstNonEmpty(src.FieldOrder, "Unknown"))), + widget.NewFormItem("Interlacing", widget.NewLabel(interlacing)), + widget.NewFormItem("Color Space", widget.NewLabel(colorSpace)), + widget.NewFormItem("Color Range", widget.NewLabel(colorRange)), + widget.NewFormItem("GOP Size", widget.NewLabel(gopSize)), widget.NewFormItem("Audio Codec", widget.NewLabel(utils.FirstNonEmpty(src.AudioCodec, "Unknown"))), + widget.NewFormItem("Audio Bitrate", widget.NewLabel(audioBitrate)), widget.NewFormItem("Audio Rate", widget.NewLabel(fmt.Sprintf("%d Hz", src.AudioRate))), widget.NewFormItem("Channels", widget.NewLabel(utils.ChannelLabel(src.Channels))), + widget.NewFormItem("Chapters", widget.NewLabel(chapters)), + widget.NewFormItem("Metadata", widget.NewLabel(metadata)), ) for _, item := range info.Items { if lbl, ok := item.Widget.(*widget.Label); ok { @@ -3583,10 +3684,8 @@ func determineVideoCodec(cfg convertConfig) string { return "h264_qsv" } else if cfg.HardwareAccel == "videotoolbox" { return "h264_videotoolbox" - } else if cfg.HardwareAccel == "none" || cfg.HardwareAccel == "" { - // Auto-detect best available encoder - return detectBestH264Encoder() } + // When set to "none" or empty, use software encoder return "libx264" case "H.265": if cfg.HardwareAccel == "nvenc" { @@ -3595,10 +3694,8 @@ func determineVideoCodec(cfg convertConfig) string { return "hevc_qsv" } else if cfg.HardwareAccel == "videotoolbox" { return "hevc_videotoolbox" - } else if cfg.HardwareAccel == "none" || cfg.HardwareAccel == "" { - // Auto-detect best available encoder - return detectBestH265Encoder() } + // When set to "none" or empty, use software encoder return "libx265" case "VP9": return "libvpx-vp9" @@ -3711,11 +3808,13 @@ func (s *appState) startConvert(status *widget.Label, btn, cancelBtn *widget.But args = append(args, "-i", cfg.CoverArtPath) } - // Hardware acceleration + // Hardware acceleration for decoding + // Note: NVENC doesn't need -hwaccel for encoding, only for decoding if cfg.HardwareAccel != "none" && cfg.HardwareAccel != "" { switch cfg.HardwareAccel { case "nvenc": - args = append(args, "-hwaccel", "cuda") + // For NVENC, we don't add -hwaccel flags + // The h264_nvenc/hevc_nvenc encoder handles GPU encoding directly case "vaapi": args = append(args, "-hwaccel", "vaapi") case "qsv": @@ -4053,9 +4152,36 @@ func (s *appState) startConvert(status *widget.Label, btn, cancelBtn *widget.But s.convertCancel = nil return } - logging.Debug(logging.CatFFMPEG, "convert failed: %v stderr=%s", err, strings.TrimSpace(stderr.String())) + stderrOutput := strings.TrimSpace(stderr.String()) + logging.Debug(logging.CatFFMPEG, "convert failed: %v stderr=%s", err, stderrOutput) fyne.CurrentApp().Driver().DoFromGoroutine(func() { - s.showErrorWithCopy("Conversion Failed", fmt.Errorf("convert failed: %w", err)) + errorExplanation := interpretFFmpegError(err) + var errorMsg error + + // Check if this is a hardware encoding failure + isHardwareFailure := strings.Contains(stderrOutput, "No capable devices found") || + strings.Contains(stderrOutput, "Cannot load") || + strings.Contains(stderrOutput, "not available") && + (strings.Contains(stderrOutput, "nvenc") || + strings.Contains(stderrOutput, "qsv") || + strings.Contains(stderrOutput, "vaapi") || + strings.Contains(stderrOutput, "videotoolbox")) + + if isHardwareFailure && s.convert.HardwareAccel != "none" && s.convert.HardwareAccel != "" { + errorMsg = fmt.Errorf("Hardware encoding (%s) failed - no compatible hardware found.\n\nPlease disable hardware acceleration in the conversion settings and try again with software encoding.\n\nFFmpeg output:\n%s", s.convert.HardwareAccel, stderrOutput) + } else { + baseMsg := "convert failed: " + err.Error() + if errorExplanation != "" { + baseMsg = fmt.Sprintf("convert failed: %v - %s", err, errorExplanation) + } + + if stderrOutput != "" { + errorMsg = fmt.Errorf("%s\n\nFFmpeg output:\n%s", baseMsg, stderrOutput) + } else { + errorMsg = fmt.Errorf("%s", baseMsg) + } + } + s.showErrorWithCopy("Conversion Failed", errorMsg) s.convertBusy = false s.convertActiveIn = "" s.convertActiveOut = "" @@ -4115,6 +4241,49 @@ func etaOrDash(s string) string { return s } +// interpretFFmpegError adds a human-readable explanation for common FFmpeg error codes +func interpretFFmpegError(err error) string { + if err == nil { + return "" + } + + // Extract exit code from error + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + exitCode := exitErr.ExitCode() + + // Common FFmpeg/OS error codes and their meanings + switch exitCode { + case 1: + return "Generic error (check FFmpeg output for details)" + case 2: + return "Invalid command line arguments" + case 126: + return "Command cannot execute (permission denied)" + case 127: + return "Command not found (is FFmpeg installed?)" + case 137: + return "Process killed (out of memory?)" + case 139: + return "Segmentation fault (FFmpeg crashed)" + case 143: + return "Process terminated by signal (SIGTERM)" + case 187: + return "Protocol/format not found or filter syntax error (check input file format and filter settings)" + case 255: + return "FFmpeg error (check output for details)" + default: + if exitCode > 128 && exitCode < 160 { + signal := exitCode - 128 + return fmt.Sprintf("Process terminated by signal %d", signal) + } + return fmt.Sprintf("Exit code %d", exitCode) + } + } + + return "" +} + func aspectFilters(target float64, mode string) []string { if target <= 0 { return nil @@ -4384,7 +4553,8 @@ type videoSource struct { Duration float64 VideoCodec string AudioCodec string - Bitrate int + Bitrate int // Video bitrate in bits per second + AudioBitrate int // Audio bitrate in bits per second FrameRate float64 PixelFormat string AudioRate int @@ -4392,6 +4562,14 @@ type videoSource struct { FieldOrder string PreviewFrames []string EmbeddedCoverArt string // Path to extracted embedded cover art, if any + + // Advanced metadata + SampleAspectRatio string // Pixel Aspect Ratio (SAR) - e.g., "1:1", "40:33" + ColorSpace string // Color space/primaries - e.g., "bt709", "bt601" + ColorRange string // Color range - "tv" (limited) or "pc" (full) + GOPSize int // GOP size / keyframe interval + HasChapters bool // Whether file has embedded chapters + HasMetadata bool // Whether file has title/copyright/etc metadata } func (v *videoSource) DurationString() string {