Skip to content

Instantly share code, notes, and snippets.

@Fornax96
Created February 9, 2022 14:53
Show Gist options
  • Save Fornax96/c1f05b43d00a542992bcf0d73b9b4d4e to your computer and use it in GitHub Desktop.
Save Fornax96/c1f05b43d00a542992bcf0d73b9b4d4e to your computer and use it in GitHub Desktop.
func videoThumbnail(ps *pixelstore.PixelStore, sourceFile pixelstore.FileID) (image.Image, error) {
// To generate thumbnails from video files we use FFMPEG. One challenge is
// that FFMPEG needs to seek to the end of the video to decode the stream
// metadata. Saving the whole file to disk would be inefficient. Luckily
// FFMPEG supports reading (and seeking) videos over HTTP. Here we start a
// HTTP server on a random TCP port, then the address of the listener is
// given to FFMPEG to read the file from. When FFMPEG exits we shut down the
// listener and the HTTP server. The resulting thumbnail image is piped to
// stdout and saved in a buffer. The buffer is then decoded and returned
// from the function
var start = time.Now()
var listener, err = net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, fmt.Errorf("failed to listen on TCP: %w", err)
}
defer listener.Close()
var mux = http.NewServeMux()
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
file, err := ps.Read(sourceFile)
if err != nil {
log.Error("Failed to read video file: %s", err)
rw.WriteHeader(http.StatusInternalServerError)
return
}
http.ServeContent(rw, r, "video_file", time.Time{}, file)
file.Close()
})
// Start the HTTP server in the background. This function returns when the
// TCP listener is closed
go http.Serve(listener, mux)
// The start of a video is often black, so we take the frame at half a
// second for our thumbnail
cmd := exec.Command(
"ffmpeg",
"-i", "http://"+listener.Addr().String(),
"-ss", "00:00:01.000",
"-vframes", "1",
"-c:v", "png",
"-f", "image2pipe",
"pipe:1",
)
// Create a buffer for holding the image data
var buf = &bytes.Buffer{}
cmd.Stdout = buf
cmd.Stderr = io.Discard
if err := cmd.Run(); err != nil {
log.Debug("ffmpeg returned error: %s", err)
return nil, errThumbnailFileTypeIncompatible
}
log.Debug("Generated thumbnail for %s with FFMPEG in %s", sourceFile, time.Since(start))
// Decode the original file into raw image data
return imaging.Decode(buf, imaging.AutoOrientation(true))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment