diff --git a/transcoder/main.go b/transcoder/main.go index 152b0972..2c3d20db 100644 --- a/transcoder/main.go +++ b/transcoder/main.go @@ -176,13 +176,10 @@ func (h *Handler) GetInfo(c echo.Context) error { return err } - ret, err := src.GetInfo(path, sha) + ret, err := h.metadata.GetMetadata(path, sha) if err != nil { return err } - // Run extractors to have them in cache - src.Extract(ret.Path, sha) - go src.ExtractThumbnail(ret.Path, sha) return c.JSON(http.StatusOK, ret) } @@ -246,13 +243,12 @@ func (h *Handler) GetThumbnails(c echo.Context) error { if err != nil { return err } - - out, err := src.ExtractThumbnail(path, sha) + sprite, _, err := h.metadata.GetThumb(path, sha) if err != nil { return err } - return c.File(fmt.Sprintf("%s/sprite.png", out)) + return c.File(sprite) } // Get thumbnail vtt @@ -266,17 +262,17 @@ func (h *Handler) GetThumbnailsVtt(c echo.Context) error { if err != nil { return err } - - out, err := src.ExtractThumbnail(path, sha) + _, vtt, err := h.metadata.GetThumb(path, sha) if err != nil { return err } - return c.File(fmt.Sprintf("%s/sprite.vtt", out)) + return c.File(vtt) } type Handler struct { transcoder *src.Transcoder + metadata src.MetadataService } func main() { diff --git a/transcoder/src/metadata.go b/transcoder/src/metadata.go index 22c25a09..77c12687 100644 --- a/transcoder/src/metadata.go +++ b/transcoder/src/metadata.go @@ -5,11 +5,25 @@ import ( ) type MetadataService struct { - database *sqlx.DB - lock *RunLock[string, *MediaInfo] + database *sqlx.DB + lock *RunLock[string, *MediaInfo] + thumbLock *RunLock[string, interface{}] } func (s MetadataService) GetMetadata(path string, sha string) (*MediaInfo, error) { + ret, err := s.getMetadata(path, sha) + if err != nil { + return nil, err + } + + if ret.Versions.Thumbs < ThumbsVersion { + go s.ExtractThumbs(path, sha) + } + + return ret, nil +} + +func (s MetadataService) getMetadata(path string, sha string) (*MediaInfo, error) { var ret MediaInfo rows, err := s.database.Queryx(` select * from info as i where i.sha=$1; diff --git a/transcoder/src/thumbnails.go b/transcoder/src/thumbnails.go index fde155b9..2d6cf5d5 100644 --- a/transcoder/src/thumbnails.go +++ b/transcoder/src/thumbnails.go @@ -8,6 +8,7 @@ import ( "log" "math" "os" + "path/filepath" "sync" "github.com/disintegration/imaging" @@ -28,32 +29,60 @@ type Thumbnail struct { var thumbnails = NewCMap[string, *Thumbnail]() -func ExtractThumbnail(path string, sha string) (string, error) { - ret, _ := thumbnails.GetOrCreate(sha, func() *Thumbnail { - ret := &Thumbnail{ - path: fmt.Sprintf("%s/%s", Settings.Metadata, sha), - } - ret.ready.Add(1) - go func() { - extractThumbnail(path, ret.path) - ret.ready.Done() - }() - return ret - }) - ret.ready.Wait() - return ret.path, nil +const ThumbsVersion = 1 + +func getThumbGlob(sha string) string { + return fmt.Sprintf("%s/%s/thumbs-v*.*", Settings.Metadata, sha) } -func extractThumbnail(path string, out string) error { - defer printExecTime("extracting thumbnails for %s", path)() - os.MkdirAll(out, 0o755) - sprite_path := fmt.Sprintf("%s/sprite.png", out) - vtt_path := fmt.Sprintf("%s/sprite.vtt", out) +func getThumbPath(sha string) string { + return fmt.Sprintf("%s/%s/thumbs-v%d.png", Settings.Metadata, sha, ThumbsVersion) +} + +func getThumbVttPath(sha string) string { + return fmt.Sprintf("%s/%s/thumbs-v%d.vtt", Settings.Metadata, sha, ThumbsVersion) +} + +func (s MetadataService) GetThumb(path string, sha string) (string, string, error) { + sprite_path := getThumbPath(sha) + vtt_path := getThumbVttPath(sha) if _, err := os.Stat(sprite_path); err == nil { - return nil + return sprite_path, vtt_path, nil } + _, err := s.ExtractThumbs(path, sha) + if err != nil { + return "", "", err + } + return sprite_path, vtt_path, nil +} + +func (s MetadataService) ExtractThumbs(path string, sha string) (interface{}, error) { + get_running, set := s.thumbLock.Start(sha) + if get_running != nil { + return get_running() + } + + err := extractThumbnail(path, sha) + if err != nil { + return set(nil, err) + } + _, err = s.database.NamedExec( + `update info set ver_thumbs = :version where sha = :sha`, + map[string]interface{}{ + "sha": sha, + "version": ThumbsVersion, + }, + ) + return set(nil, err) +} + +func extractThumbnail(path string, sha string) error { + defer printExecTime("extracting thumbnails for %s", path)() + + os.MkdirAll(fmt.Sprintf("%s/%s", Settings.Metadata), 0o755) + gen, err := screengen.NewGenerator(path) if err != nil { log.Printf("Error reading video file: %v", err) @@ -102,7 +131,7 @@ func extractThumbnail(path string, out string) error { tsToVttTime(timestamps), tsToVttTime(ts), Settings.RoutePrefix, - base64.StdEncoding.EncodeToString([]byte(path)), + base64.RawURLEncoding.EncodeToString([]byte(path)), x, y, width, @@ -110,11 +139,20 @@ func extractThumbnail(path string, out string) error { ) } - err = os.WriteFile(vtt_path, []byte(vtt), 0o644) + // Cleanup old versions of thumbnails + files, err := filepath.Glob(getThumbGlob(sha)) + if err == nil { + for _, f := range files { + // ignore errors + os.Remove(f) + } + } + + err = os.WriteFile(getThumbVttPath(sha), []byte(vtt), 0o644) if err != nil { return err } - err = imaging.Save(sprite, sprite_path) + err = imaging.Save(sprite, getThumbPath(sha)) if err != nil { return err }