Re-add thumbnailing
This commit is contained in:
parent
1807f374ca
commit
b4054e26b1
|
@ -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"]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
services:
|
||||||
|
webfs:
|
||||||
|
image: git.woozle.org/neale/webfs
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
5
go.mod
5
go.mod
|
@ -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
2
go.sum
|
@ -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=
|
||||||
|
|
Loading…
Reference in New Issue