You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
93 lines
1.7 KiB
93 lines
1.7 KiB
package video
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os/exec"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
defaultCacheEntries = 200
|
|
thumbnailWidth = 160
|
|
thumbnailHeight = 90
|
|
defaultFFmpegPath = "ffmpeg"
|
|
)
|
|
|
|
type Cache struct {
|
|
mu sync.Mutex
|
|
max int
|
|
ffmpegPath string
|
|
entries map[string][]byte
|
|
order []string
|
|
}
|
|
|
|
func NewCache(max int, ffmpegPath string) *Cache {
|
|
if max <= 0 {
|
|
max = defaultCacheEntries
|
|
}
|
|
if ffmpegPath == "" {
|
|
ffmpegPath = defaultFFmpegPath
|
|
}
|
|
return &Cache{
|
|
max: max,
|
|
ffmpegPath: ffmpegPath,
|
|
entries: make(map[string][]byte),
|
|
}
|
|
}
|
|
|
|
func (c *Cache) Thumbnail(absPath string) ([]byte, error) {
|
|
if cached, ok := c.get(absPath); ok {
|
|
return cached, nil
|
|
}
|
|
|
|
cmd := exec.Command(
|
|
c.ffmpegPath,
|
|
"-loglevel", "error",
|
|
"-ss", "0",
|
|
"-i", absPath,
|
|
"-vframes", "1",
|
|
"-vf", fmt.Sprintf("scale=%d:%d:force_original_aspect_ratio=decrease", thumbnailWidth, thumbnailHeight),
|
|
"-f", "image2",
|
|
"-vcodec", "mjpeg",
|
|
"pipe:1",
|
|
)
|
|
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("generate video thumbnail: %w%s", err, stderrSuffix(stderr.String()))
|
|
}
|
|
|
|
c.add(absPath, output)
|
|
return append([]byte(nil), output...), nil
|
|
}
|
|
|
|
func (c *Cache) get(key string) ([]byte, bool) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
value, ok := c.entries[key]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return append([]byte(nil), value...), true
|
|
}
|
|
|
|
func (c *Cache) add(key string, value []byte) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if _, ok := c.entries[key]; !ok {
|
|
c.order = append(c.order, key)
|
|
}
|
|
c.entries[key] = append([]byte(nil), value...)
|
|
|
|
for len(c.order) > c.max {
|
|
oldest := c.order[0]
|
|
c.order = c.order[1:]
|
|
delete(c.entries, oldest)
|
|
}
|
|
}
|
|
|