Re-add thumbnailing

This commit is contained in:
Neale Pickett 2023-04-02 13:04:30 -06:00
parent 1807f374ca
commit b4054e26b1
5 changed files with 105 additions and 8 deletions

View File

@ -2,16 +2,16 @@ FROM golang:1-alpine AS build
WORKDIR /src WORKDIR /src
COPY go.* ./ COPY go.* ./
RUN go mod download -x RUN go mod download -x
COPY pkg ./pkg/
COPY cmd ./cmd/ COPY cmd ./cmd/
RUN CGO_ENABLED=0 GOOS=linux go install ./... RUN go install ./...
FROM alpine AS runtime FROM alpine AS runtime
WORKDIR /target WORKDIR /target
COPY web web COPY web web
COPY --from=build /go/bin/ . COPY --from=build /go/bin/ .
FROM scratch FROM alpine
RUN apk --no-cache add ffmpeg ffprobe
COPY --from=runtime /target / COPY --from=runtime /target /
WORKDIR /web WORKDIR /web
ENTRYPOINT ["/webfs"] ENTRYPOINT ["/webfs"]

View File

@ -1,16 +1,25 @@
package main package main
import ( import (
"context"
"flag" "flag"
"fmt"
"log" "log"
"net/http" "net/http"
"os"
"os/exec"
"path"
"strings" "strings"
ffprobe "gopkg.in/vansante/go-ffprobe.v2"
"golang.org/x/net/webdav" "golang.org/x/net/webdav"
) )
type Handler struct { type Handler struct {
webdav.Handler webdav.Handler
dataRoot string
thumbnailRoot string
} }
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@ -19,18 +28,95 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/") { if strings.HasSuffix(r.URL.Path, "/") {
r.URL.Path += "index.html" r.URL.Path += "index.html"
} }
if r.FormValue("thumbnail") != "" {
h.ServeThumbnail(w, r)
return
}
} }
h.Handler.ServeHTTP(w, r) h.Handler.ServeHTTP(w, r)
} }
/* cleanPath returns a sanitized HTTP path.
*
* This removes .. from everything,
* and will never return any path above /
*/
func cleanPath(p string) string {
if p == "" {
return "/"
}
if p[0] != '/' {
p = "/" + p
}
return path.Clean(p)
}
func (h *Handler) ServeThumbnail(w http.ResponseWriter, req *http.Request) {
reqPath := cleanPath(req.URL.Path)
reqPath = strings.TrimLeft(reqPath, "/")
thumbnailPath := path.Join(h.thumbnailRoot, reqPath) + ".webp"
// If there's not already a thumbnail, make one
if _, err := os.Stat(thumbnailPath); err != nil {
srcPath := path.Join(h.dataRoot, reqPath)
ffdata, err := ffprobe.ProbeURL(context.Background(), srcPath, "-hide_banner")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
isVideo := ffdata.Format.DurationSeconds > 0.0
// XXX: some day soon you will want CommandContext
cmd := exec.Command("ffmpeg", "-hide_banner", "-loglevel", "error")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if isVideo {
skipSeconds := ffdata.Format.StartTimeSeconds + (ffdata.Format.DurationSeconds * 0.25)
cmd.Args = append(cmd.Args,
"-ss", fmt.Sprintf("%f", skipSeconds),
"-i", srcPath,
"-frames:v", "5",
"-filter:v", "scale='min(320,iw)':'min(200,ih)':force_original_aspect_ratio=decrease,fps=2",
"-loop", "0",
thumbnailPath,
)
} else {
cmd.Args = append(cmd.Args,
"-i", srcPath,
"-filter:v", "scale='min(320,iw)':'min(200,ih)':force_original_aspect_ratio=decrease",
thumbnailPath,
)
}
if err := os.MkdirAll(path.Dir(thumbnailPath), 0755); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := cmd.Run(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
http.ServeFile(w, req, thumbnailPath)
}
func main() { func main() {
address := flag.String("listen", ":8080", "Address to listen to") address := flag.String("listen", ":8080", "Address to listen to")
directory := flag.String("root", ".", "Directory to serve") dataRoot := flag.String("root", ".", "Directory to serve")
thumbnailRoot := flag.String("thumbnails", "/thumbnails", "Where to store thumbnails")
flag.Parse() flag.Parse()
handler := &Handler{} handler := &Handler{
handler.FileSystem = webdav.Dir(*directory) Handler: webdav.Handler{
handler.LockSystem = webdav.NewMemLS() FileSystem: webdav.Dir(*dataRoot),
LockSystem: webdav.NewMemLS(),
},
dataRoot: *dataRoot,
thumbnailRoot: *thumbnailRoot,
}
log.Println("Listening on", *address) log.Println("Listening on", *address)
http.ListenAndServe(*address, handler) http.ListenAndServe(*address, handler)

6
docker-compose.yaml Normal file
View File

@ -0,0 +1,6 @@
services:
webfs:
image: git.woozle.org/neale/webfs
build: .
ports:
- 8080:8080

5
go.mod
View File

@ -2,4 +2,7 @@ module woozle.org/neale/webfs
go 1.15 go 1.15
require golang.org/x/net v0.7.0 require (
golang.org/x/net v0.7.0
gopkg.in/vansante/go-ffprobe.v2 v2.1.1
)

2
go.sum
View File

@ -26,3 +26,5 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/vansante/go-ffprobe.v2 v2.1.1 h1:DIh5fMn+tlBvG7pXyUZdemVmLdERnf2xX6XOFF+0BBU=
gopkg.in/vansante/go-ffprobe.v2 v2.1.1/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE=