govd/util/av/thumbnail.go
2025-04-18 01:10:59 +02:00

117 lines
2.7 KiB
Go

package av
import (
"errors"
"fmt"
"image/jpeg"
"os"
"time"
"github.com/asticode/go-astiav"
)
func ExtractVideoThumbnail(videoPath string, imagePath string) error {
astiav.SetLogLevel(astiav.LogLevelQuiet)
formatCtx := astiav.AllocFormatContext()
defer formatCtx.Free()
err := formatCtx.OpenInput(videoPath, nil, nil)
if err != nil {
return fmt.Errorf("failed opening input: %w", err)
}
err = formatCtx.FindStreamInfo(nil)
if err != nil {
return fmt.Errorf("failed finding stream info: %w", err)
}
stream, codec, err := formatCtx.FindBestStream(
astiav.MediaTypeVideo, -1, -1)
if err != nil {
return fmt.Errorf("failed finding best video stream: %w", err)
}
codecParameters := stream.CodecParameters()
decoder := astiav.FindDecoder(codec.ID())
if decoder == nil {
return fmt.Errorf("no decoder found for codec %s", codec.String())
}
codecCtx := astiav.AllocCodecContext(decoder)
defer codecCtx.Free()
err = codecParameters.ToCodecContext(codecCtx)
if err != nil {
return fmt.Errorf("failed setting codec parameters: %w", err)
}
err = codecCtx.Open(decoder, nil)
if err != nil {
return fmt.Errorf("failed opening codec: %w", err)
}
packet := astiav.AllocPacket()
defer packet.Free()
frame := astiav.AllocFrame()
defer frame.Free()
startTime := time.Now()
timeout := 5 * time.Second
// read frames until we find a video frame or timeout
for time.Since(startTime) < timeout {
err := formatCtx.ReadFrame(packet)
if err != nil {
if errors.Is(err, astiav.ErrEof) {
return fmt.Errorf("end of file reached before finding video frame")
}
return fmt.Errorf("failed reading frame: %w", err)
}
if packet.StreamIndex() != stream.Index() {
packet.Unref()
continue
}
err = codecCtx.SendPacket(packet)
if err != nil {
return fmt.Errorf("failed sending packet to decoder: %w", err)
}
err = codecCtx.ReceiveFrame(frame)
if err != nil {
if errors.Is(err, astiav.ErrEagain) || errors.Is(err, astiav.ErrEof) {
packet.Unref()
continue
}
return fmt.Errorf("failed receiving frame from decoder: %w", err)
}
img, err := frame.Data().GuessImageFormat()
if err != nil {
return fmt.Errorf("failed guessing image format: %w", err)
}
err = frame.Data().ToImage(img)
if err != nil {
return fmt.Errorf("failed converting frame to image: %w", err)
}
packet.Unref()
file, err := os.Create(imagePath)
if err != nil {
return fmt.Errorf("failed creating image file: %w", err)
}
defer file.Close()
err = jpeg.Encode(file, img, nil)
if err != nil {
return fmt.Errorf("failed encoding image: %w", err)
}
return nil
}
return fmt.Errorf("timeout while waiting for video frame")
}