115 lines
2.7 KiB
Go
115 lines
2.7 KiB
Go
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
|
|
}
|