webfs/cmd/webfs/main.go

150 lines
3.4 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path"
"strings"
ffprobe "gopkg.in/vansante/go-ffprobe.v2"
"golang.org/x/net/webdav"
)
type Handler struct {
webdav.Handler
dataRoot string
thumbnailRoot string
filterVideo string
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET", "HEAD":
if strings.HasSuffix(r.URL.Path, "/") {
r.URL.Path += "index.html"
}
r.ParseForm()
if _, ok := r.Form["thumbnail"]; (h.thumbnailRoot != "") && ok {
h.ServeThumbnail(w, r)
return
}
}
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) makeThumbnail(reqPath, thumbnailPath string) error {
srcPath := path.Join(h.dataRoot, reqPath)
// Run ffprobe to figure out what kind of file this is
ffdata, err := ffprobe.ProbeURL(context.Background(), srcPath, "-hide_banner")
if err != nil {
return err
}
isVideo := ffdata.Format.DurationSeconds > 1.0
skipSeconds := ffdata.Format.StartTimeSeconds + (ffdata.Format.DurationSeconds * 0.25)
// Build up ffmpeg invocation
// 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 {
cmd.Args = append(cmd.Args,
"-ss", fmt.Sprintf("%f", skipSeconds),
"-i", srcPath,
"-frames:v", "5",
"-filter:v", h.filterVideo+",fps=2",
"-loop", "0",
"-map_metadata", "0",
thumbnailPath,
)
} else {
cmd.Args = append(cmd.Args,
"-i", srcPath,
"-filter:v", h.filterVideo,
"-map_metadata", "0",
thumbnailPath,
)
}
// Make sure the thumbnail has a directory to live in
if err := os.MkdirAll(path.Dir(thumbnailPath), 0755); err != nil {
return err
}
// Convert!
if err := cmd.Run(); err != nil {
return err
}
return nil
}
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 {
if err := h.makeThumbnail(reqPath, thumbnailPath); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
http.ServeFile(w, req, thumbnailPath)
}
func main() {
address := flag.String("listen", ":8080", "Address to listen to")
dataRoot := flag.String("root", ".", "Directory to serve")
thumbnailRoot := flag.String("thumbnails", "/thumbnails", "Where to store thumbnails")
filterVideo := flag.String(
"filter:v",
"scale='min(320,iw)':'min(200,ih)':force_original_aspect_ratio=decrease",
"ffmpeg video filter",
)
flag.Parse()
if _, err := os.Stat(*thumbnailRoot); err != nil {
log.Println("Disabling thumbnail generation:", err)
*thumbnailRoot = ""
}
handler := &Handler{
Handler: webdav.Handler{
FileSystem: webdav.Dir(*dataRoot),
LockSystem: webdav.NewMemLS(),
},
dataRoot: *dataRoot,
thumbnailRoot: *thumbnailRoot,
filterVideo: *filterVideo,
}
log.Println("Listening on", *address)
log.Fatal(http.ListenAndServe(*address, handler))
}