package ffmpeg import ( "bytes" "fmt" "os/exec" "strconv" "strings" ) type VideoProcessor interface { GenerateThumbnail(videoData []byte) ([]byte, error) GetDuration(videoData []byte) (int, error) TrimVideo(videoData []byte, maxDuration int) ([]byte, error) } type videoProcessor struct { } func NewVideoProcessor() VideoProcessor { return &videoProcessor{} } func (vp *videoProcessor) GenerateThumbnail(videoData []byte) ([]byte, error) { cmd := exec.Command("ffmpeg", "-i", "pipe:0", // читаем из stdin "-ss", "00:00:01", "-vframes", "1", "-q:v", "2", "-f", "image2pipe", // вывод в pipe "-c:v", "mjpeg", "pipe:1", // пишем в stdout ) cmd.Stdin = bytes.NewReader(videoData) var output bytes.Buffer cmd.Stdout = &output cmd.Stderr = &output // capture stderr for error messages err := cmd.Run() if err != nil { return nil, fmt.Errorf("ffmpeg failed: %s, error: %w", output.String(), err) } if output.Len() == 0 { return nil, fmt.Errorf("thumbnail generation produced empty output") } return output.Bytes(), nil } func (vp *videoProcessor) GetDuration(videoData []byte) (int, error) { cmd := exec.Command("ffprobe", "-i", "pipe:0", "-show_entries", "format=duration", "-v", "quiet", "-of", "csv=p=0", ) cmd.Stdin = bytes.NewReader(videoData) output, err := cmd.Output() if err != nil { debugCmd := exec.Command("ffprobe", "-i", "pipe:0") debugCmd.Stdin = bytes.NewReader(videoData) debugOutput, _ := debugCmd.CombinedOutput() return 0, fmt.Errorf("ffprobe failed: %w, debug output: %s", err, string(debugOutput)) } durationStr := strings.TrimSpace(string(output)) duration, err := strconv.ParseFloat(durationStr, 64) if err != nil { return 0, fmt.Errorf("failed to parse duration '%s': %w", durationStr, err) } return int(duration), nil } func (vp *videoProcessor) TrimVideo(videoData []byte, maxDuration int) ([]byte, error) { // Сначала получаем длительность duration, err := vp.GetDuration(videoData) if err != nil { return nil, fmt.Errorf("failed to get video duration: %w", err) } // Если видео короче или равно лимиту, возвращаем оригинал if duration <= maxDuration { return videoData, nil } // Обрезаем видео cmd := exec.Command("ffmpeg", "-i", "pipe:0", "-t", strconv.Itoa(maxDuration), "-c", "copy", "-f", "mp4", "pipe:1", ) cmd.Stdin = bytes.NewReader(videoData) var output bytes.Buffer cmd.Stdout = &output cmd.Stderr = &output err = cmd.Run() if err != nil { return nil, fmt.Errorf("ffmpeg trim failed: %s, error: %w", output.String(), err) } if output.Len() == 0 { return nil, fmt.Errorf("trimmed video is empty") } return output.Bytes(), nil }