internal/thumbs is internal/media, with Inspect: what a file holds, from ffprobe
The package was named for its first job; it now also decides what a file is for the Files tab (image, video, audio) and reads its streams, which the viewer will need. Inspect is the richer probe — codec, profile, pixel format, size, frame rate, HDR transfer, the audio beside it, container and duration — run as root through sudo when asked. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
beec9269fe
commit
af189b1fa1
+24
-24
@@ -24,7 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"hamlab/internal/priv"
|
||||
"hamlab/internal/thumbs"
|
||||
"hamlab/internal/media"
|
||||
)
|
||||
|
||||
const thumbSize = 256
|
||||
@@ -32,7 +32,7 @@ const thumbTimeout = 30 * time.Second
|
||||
const capsTTL = 10 * time.Minute // how long a probe is believed; a refresh after an install or a group change does not wait
|
||||
|
||||
type thumbSvc struct {
|
||||
cache *thumbs.Cache
|
||||
cache *media.Cache
|
||||
self string
|
||||
sem chan struct{}
|
||||
mu sync.Mutex
|
||||
@@ -42,14 +42,14 @@ type thumbSvc struct {
|
||||
|
||||
type capsEntry struct {
|
||||
mu sync.Mutex
|
||||
caps thumbs.Caps
|
||||
caps media.Caps
|
||||
err error
|
||||
at time.Time
|
||||
}
|
||||
|
||||
type thumbCall struct {
|
||||
done chan struct{}
|
||||
res thumbs.Result
|
||||
res media.Result
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -61,11 +61,11 @@ func newThumbSvc(dataDir, self string) *thumbSvc {
|
||||
if n > 4 {
|
||||
n = 4
|
||||
}
|
||||
return &thumbSvc{cache: thumbs.NewCache(filepath.Join(dataDir, "thumbs.db")), self: self, sem: make(chan struct{}, n), caps: map[bool]*capsEntry{}, calls: map[string]*thumbCall{}}
|
||||
return &thumbSvc{cache: media.NewCache(filepath.Join(dataDir, "thumbs.db")), self: self, sem: make(chan struct{}, n), caps: map[bool]*capsEntry{}, calls: map[string]*thumbCall{}}
|
||||
}
|
||||
|
||||
// capsFor is what previews can be made with, probed once and kept for a while.
|
||||
func (t *thumbSvc) capsFor(ctx context.Context, root, refresh bool) (thumbs.Caps, error) {
|
||||
func (t *thumbSvc) capsFor(ctx context.Context, root, refresh bool) (media.Caps, error) {
|
||||
t.mu.Lock()
|
||||
e := t.caps[root]
|
||||
if e == nil {
|
||||
@@ -79,9 +79,9 @@ func (t *thumbSvc) capsFor(ctx context.Context, root, refresh bool) (thumbs.Caps
|
||||
return e.caps, e.err
|
||||
}
|
||||
if root {
|
||||
e.caps, e.err = thumbs.ProbeRoot(ctx, t.self)
|
||||
e.caps, e.err = media.ProbeRoot(ctx, t.self)
|
||||
} else {
|
||||
e.caps, e.err = thumbs.Probe(ctx), nil
|
||||
e.caps, e.err = media.Probe(ctx), nil
|
||||
}
|
||||
e.at = time.Now()
|
||||
mode := "as " + map[bool]string{false: "the daemon", true: "root"}[root]
|
||||
@@ -97,7 +97,7 @@ func (t *thumbSvc) capsFor(ctx context.Context, root, refresh bool) (thumbs.Caps
|
||||
}
|
||||
|
||||
// describeCaps is the one line that says how previews get made here.
|
||||
func describeCaps(c thumbs.Caps) string {
|
||||
func describeCaps(c media.Caps) string {
|
||||
if len(c.GPUs) == 0 {
|
||||
return "CPU only"
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func describeCaps(c thumbs.Caps) string {
|
||||
}
|
||||
|
||||
// make produces a preview, sharing the work when the same file is asked for twice at once.
|
||||
func (t *thumbSvc) make(ctx context.Context, root bool, key, path string) (thumbs.Result, error) {
|
||||
func (t *thumbSvc) make(ctx context.Context, root bool, key, path string) (media.Result, error) {
|
||||
t.mu.Lock()
|
||||
if c, ok := t.calls[key]; ok {
|
||||
t.mu.Unlock()
|
||||
@@ -125,7 +125,7 @@ func (t *thumbSvc) make(ctx context.Context, root bool, key, path string) (thumb
|
||||
case <-c.done:
|
||||
return c.res, c.err
|
||||
case <-ctx.Done():
|
||||
return thumbs.Result{}, ctx.Err()
|
||||
return media.Result{}, ctx.Err()
|
||||
}
|
||||
}
|
||||
c := &thumbCall{done: make(chan struct{})}
|
||||
@@ -153,11 +153,11 @@ func (t *thumbSvc) make(ctx context.Context, root bool, key, path string) (thumb
|
||||
c.err = err
|
||||
return c.res, c.err
|
||||
}
|
||||
o := thumbs.Opts{Size: thumbSize, Timeout: thumbTimeout}
|
||||
o := media.Opts{Size: thumbSize, Timeout: thumbTimeout}
|
||||
if root {
|
||||
c.res, c.err = thumbs.MakeRoot(wctx, t.self, caps, path, o)
|
||||
c.res, c.err = media.MakeRoot(wctx, t.self, caps, path, o)
|
||||
} else {
|
||||
c.res, c.err = thumbs.Make(wctx, caps, path, o)
|
||||
c.res, c.err = media.Make(wctx, caps, path, o)
|
||||
}
|
||||
return c.res, c.err
|
||||
}
|
||||
@@ -175,13 +175,13 @@ func (s *Server) filesThumb(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if thumbs.Kind(p) == "" {
|
||||
if media.Kind(p) == "" {
|
||||
jsonErr(w, 415, "no preview for this kind of file")
|
||||
return
|
||||
}
|
||||
root := r.URL.Query().Get("sudo") == "1"
|
||||
if mt, sz, ok := parseV(r.URL.Query().Get("v")); ok {
|
||||
key := thumbs.Key(p, sz, mt)
|
||||
key := media.Key(p, sz, mt)
|
||||
if r.Header.Get("If-None-Match") == `"`+key+`"` {
|
||||
w.WriteHeader(304)
|
||||
return
|
||||
@@ -199,16 +199,16 @@ func (s *Server) filesThumb(w http.ResponseWriter, r *http.Request) {
|
||||
jsonErr(w, 400, "not a regular file")
|
||||
return
|
||||
}
|
||||
key := thumbs.Key(p, in.Size, in.Mtime)
|
||||
key := media.Key(p, in.Size, in.Mtime)
|
||||
if s.serveCachedThumb(w, key) {
|
||||
return
|
||||
}
|
||||
res, err := s.thumbs.make(r.Context(), root, key, p)
|
||||
if err != nil {
|
||||
var de *thumbs.DecodeError
|
||||
var de *media.DecodeError
|
||||
switch {
|
||||
case errors.As(err, &de):
|
||||
if err := s.thumbs.cache.Put(key, thumbs.Entry{Path: p, Failure: de.Msg}); err != nil {
|
||||
if err := s.thumbs.cache.Put(key, media.Entry{Path: p, Failure: de.Msg}); err != nil {
|
||||
log.Printf("thumbs: cache: %v", err)
|
||||
}
|
||||
log.Printf("thumbs: %s: %s", p, de.Msg)
|
||||
@@ -223,7 +223,7 @@ func (s *Server) filesThumb(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := s.thumbs.cache.Put(key, thumbs.Entry{Path: p, Engine: res.Engine, Codec: res.Codec, Width: res.Width, Height: res.Height, JPEG: res.JPEG}); err != nil {
|
||||
if err := s.thumbs.cache.Put(key, media.Entry{Path: p, Engine: res.Engine, Codec: res.Codec, Width: res.Width, Height: res.Height, JPEG: res.JPEG}); err != nil {
|
||||
log.Printf("thumbs: cache: %v", err)
|
||||
}
|
||||
writeThumb(w, key, res.JPEG, res.Engine)
|
||||
@@ -272,8 +272,8 @@ type thumbStatus struct {
|
||||
Engine string `json:"engine"` // gpu | cpu | ""
|
||||
Summary string `json:"summary"`
|
||||
FFmpeg string `json:"ffmpeg,omitempty"`
|
||||
GPUs []thumbs.GPU `json:"gpus"`
|
||||
Unusable []thumbs.GPU `json:"unusable,omitempty"`
|
||||
GPUs []media.GPU `json:"gpus"`
|
||||
Unusable []media.GPU `json:"unusable,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Install *thumbInstall `json:"install,omitempty"` // ffmpeg is missing and this can put it on
|
||||
GPUFix bool `json:"gpu_fix,omitempty"` // a render node is there but closed to the daemon's user, and this can open it
|
||||
@@ -300,7 +300,7 @@ func (s *Server) thumbsStatus(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
caps, err := s.thumbs.capsFor(r.Context(), root, r.URL.Query().Get("refresh") == "1")
|
||||
st := thumbStatus{GPUs: []thumbs.GPU{}, User: s.user, Cache: thumbCache{Path: s.thumbs.cache.Path}}
|
||||
st := thumbStatus{GPUs: []media.GPU{}, User: s.user, Cache: thumbCache{Path: s.thumbs.cache.Path}}
|
||||
st.Cache.Entries, st.Cache.Bytes = s.thumbs.cache.Stats()
|
||||
if root {
|
||||
st.User = "root"
|
||||
@@ -312,7 +312,7 @@ func (s *Server) thumbsStatus(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
st.FFmpeg, st.GPUs, st.Unusable, st.Error = caps.Version, caps.GPUs, caps.Unusable, caps.Error
|
||||
if st.GPUs == nil {
|
||||
st.GPUs = []thumbs.GPU{}
|
||||
st.GPUs = []media.GPU{}
|
||||
}
|
||||
st.OK = caps.Error == ""
|
||||
switch {
|
||||
|
||||
Reference in New Issue
Block a user