diff --git a/transcoder/main.go b/transcoder/main.go index 8346da96..c093f7e4 100644 --- a/transcoder/main.go +++ b/transcoder/main.go @@ -264,7 +264,7 @@ func (h *Handler) GetSubtitle(c echo.Context) error { // Get thumbnail sprite // -// Get a sprite file containing all the thumbnails of the show +// Get a sprite file containing all the thumbnails of the show. // // Path: /:resource/:slug/thumbnails.png func (h *Handler) GetThumbnails(c echo.Context) error { @@ -276,7 +276,10 @@ func (h *Handler) GetThumbnails(c echo.Context) error { return err } - out, err := src.ExtractThumbnail(path) + out, err := h.thumbnails.ExtractThumbnail( + path, + fmt.Sprintf("%s/%s/thumbnails.png", resource, slug), + ) if err != nil { return err } @@ -299,7 +302,10 @@ func (h *Handler) GetThumbnailsVtt(c echo.Context) error { return err } - out, err := src.ExtractThumbnail(path) + out, err := h.thumbnails.ExtractThumbnail( + path, + fmt.Sprintf("%s/%s/thumbnails.png", resource, slug), + ) if err != nil { return err } @@ -310,6 +316,7 @@ func (h *Handler) GetThumbnailsVtt(c echo.Context) error { type Handler struct { transcoder *src.Transcoder extractor *src.Extractor + thumbnails *src.ThumbnailsCreator } func main() { @@ -322,7 +329,11 @@ func main() { e.Logger.Fatal(err) return } - h := Handler{transcoder: transcoder, extractor: src.NewExtractor()} + h := Handler{ + transcoder: transcoder, + extractor: src.NewExtractor(), + thumbnails: src.NewThumbnailsCreator(), + } e.GET("/:resource/:slug/direct", DirectStream) e.GET("/:resource/:slug/master.m3u8", h.GetMaster) diff --git a/transcoder/src/thumbnails.go b/transcoder/src/thumbnails.go index 025216fa..cb651422 100644 --- a/transcoder/src/thumbnails.go +++ b/transcoder/src/thumbnails.go @@ -7,6 +7,7 @@ import ( "log" "math" "os" + "sync" "github.com/disintegration/imaging" "gitlab.com/opennota/screengen" @@ -15,7 +16,53 @@ import ( // We want to have a thumbnail every ${interval} seconds. var default_interval = 10 -func ExtractThumbnail(path string) (string, error) { +type ThumbnailsCreator struct { + created map[string]string + running map[string]chan struct{} + lock sync.Mutex +} + +func NewThumbnailsCreator() *ThumbnailsCreator { + return &ThumbnailsCreator{ + created: make(map[string]string), + running: make(map[string]chan struct{}), + } +} + +func (t *ThumbnailsCreator) ExtractThumbnail(path string, name string) (string, error) { + t.lock.Lock() + defer t.lock.Unlock() + + out, ok := t.created[path] + if ok { + return out, nil + } + + wait, ok := t.running[path] + if ok { + t.lock.Unlock() + <-wait + t.lock.Lock() + out = t.created[path] + return out, nil + } + wait = make(chan struct{}) + t.running[path] = wait + + t.lock.Unlock() + + out, err := extractThumbnail(path, name) + t.lock.Lock() + // this will be unlocked by the defer at the top of the function. + + delete(t.running, path) + if err == nil { + t.created[path] = out + } + return out, err +} + +func extractThumbnail(path string, name string) (string, error) { ret, err := GetInfo(path) if err != nil { return "", err