131 lines
3.4 KiB
Go
131 lines
3.4 KiB
Go
package ffmpeg
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
)
|
|
|
|
type VideoProcessor interface {
|
|
GenerateThumbnail(videoData []byte) ([]byte, error)
|
|
TrimVideo(videoData []byte, maxDuration int) ([]byte, error)
|
|
}
|
|
|
|
type videoProcessor struct {
|
|
tempDir string // опционально: каталог для временных файлов
|
|
}
|
|
|
|
func NewVideoProcessor() VideoProcessor {
|
|
return &videoProcessor{
|
|
tempDir: os.TempDir(), // используем системный temp каталог
|
|
}
|
|
}
|
|
|
|
func (vp *videoProcessor) withTempFile(pattern string, data []byte, fn func(filename string) error) error {
|
|
// Создаем временный файл
|
|
file, err := os.CreateTemp(vp.tempDir, pattern)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create temp file: %w", err)
|
|
}
|
|
tempPath := file.Name()
|
|
|
|
// Гарантируем удаление файла
|
|
defer func() {
|
|
if _, err := os.Stat(tempPath); err == nil {
|
|
os.Remove(tempPath)
|
|
}
|
|
}()
|
|
|
|
// Записываем данные
|
|
if _, err := file.Write(data); err != nil {
|
|
return fmt.Errorf("failed to write to temp file: %w", err)
|
|
}
|
|
file.Close()
|
|
|
|
// Вызываем функцию обработки
|
|
return fn(tempPath)
|
|
}
|
|
|
|
func (vp *videoProcessor) GenerateThumbnail(videoData []byte) ([]byte, error) {
|
|
var thumbnailData []byte
|
|
|
|
err := vp.withTempFile("thumbnail_*.mp4", videoData, func(inputPath string) error {
|
|
cmd := exec.Command("ffmpeg",
|
|
"-i", inputPath,
|
|
"-ss", "00:00:01",
|
|
"-vframes", "1",
|
|
"-q:v", "2",
|
|
"-f", "image2pipe",
|
|
"-c:v", "mjpeg",
|
|
"pipe:1",
|
|
)
|
|
|
|
var output bytes.Buffer
|
|
var stderr bytes.Buffer
|
|
cmd.Stdout = &output
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("ffmpeg failed: %s, error: %w", stderr.String(), err)
|
|
}
|
|
|
|
thumbnailData = output.Bytes()
|
|
return nil
|
|
})
|
|
|
|
return thumbnailData, err
|
|
}
|
|
|
|
func (vp *videoProcessor) TrimVideo(videoData []byte, maxDuration int) ([]byte, error) {
|
|
// 1. Создаем временный файл для входного видео
|
|
inputFile, err := os.CreateTemp(vp.tempDir, "trim_input_*.mp4")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create temp file: %w", err)
|
|
}
|
|
defer os.Remove(inputFile.Name())
|
|
defer inputFile.Close()
|
|
|
|
// 2. Записываем видео данные во временный файл
|
|
if _, err := inputFile.Write(videoData); err != nil {
|
|
return nil, fmt.Errorf("failed to write to temp file: %w", err)
|
|
}
|
|
inputFile.Close()
|
|
|
|
// 3. Создаем временный файл для выходного видео
|
|
outputFile, err := os.CreateTemp(vp.tempDir, "trim_output_*.mp4")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create output temp file: %w", err)
|
|
}
|
|
defer os.Remove(outputFile.Name())
|
|
defer outputFile.Close()
|
|
|
|
// 4. Выполняем обрезку
|
|
cmd := exec.Command("ffmpeg",
|
|
"-i", inputFile.Name(),
|
|
"-t", fmt.Sprintf("%d", maxDuration),
|
|
"-c", "copy",
|
|
"-y", // overwrite output file
|
|
outputFile.Name(),
|
|
)
|
|
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return nil, fmt.Errorf("ffmpeg trim failed: %s, error: %w", stderr.String(), err)
|
|
}
|
|
|
|
// 5. Читаем обрезанное видео
|
|
trimmedData, err := os.ReadFile(outputFile.Name())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read trimmed video: %w", err)
|
|
}
|
|
|
|
if len(trimmedData) == 0 {
|
|
return nil, fmt.Errorf("trimmed video is empty")
|
|
}
|
|
|
|
return trimmedData, nil
|
|
}
|