Created
February 9, 2022 14:53
-
-
Save Fornax96/c1f05b43d00a542992bcf0d73b9b4d4e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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