package caddyhugo import ( "fmt" "image" _ "image/gif" "image/jpeg" _ "image/png" "io" "net/http" "os" "path" "path/filepath" "strconv" "strings" "github.com/nfnt/resize" ) type MediaSource struct { StorageDir string ThumbDir string } func (ms *MediaSource) LocationOrig(m Media) string { return path.Join(ms.StorageDir, m.Name) } func (ms *MediaSource) receiveNewMedia(name string, r io.Reader) error { dest := path.Join(ms.StorageDir, name) f, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return err } _, err = io.Copy(f, r) if err != nil { return err } return f.Close() } type Media struct { Type string Name string } func (ms *MediaSource) ThumbMax(m Media, maxDim int) (string, int, int, error) { f, err := os.Open(ms.LocationOrig(m)) if err != nil { return "", 0, 0, err } defer f.Close() img, _, err := image.Decode(f) if err != nil { return "", 0, 0, err } rect := img.Bounds() width := rect.Dx() height := rect.Dy() if width > height { height = height * maxDim / width width = maxDim } else { width = width * maxDim / height height = maxDim } src, err := ms.ThumbImage(img, m, width, height) return src, width, height, err } func (ms *MediaSource) ByName(name string) *Media { return &Media{ Type: "image", Name: name, } } func (ms *MediaSource) Thumb(m Media, width, height int) (string, error) { f, err := os.Open(ms.LocationOrig(m)) if err != nil { return "", err } defer f.Close() img, _, err := image.Decode(f) if err != nil { return "", err } return ms.ThumbImage(img, m, width, height) } func (ms *MediaSource) ThumbImage(img image.Image, m Media, width, height int) (string, error) { thumbSlug := filepath.Join(m.Name, fmt.Sprintf("%d/%d.jpg", width, height)) thumbLoc := filepath.Join(ms.ThumbDir, thumbSlug) os.MkdirAll(path.Dir(thumbLoc), 0755) fthumb, err := os.OpenFile(thumbLoc, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0655) if err != nil { return "", err } img = resize.Resize(uint(width), uint(height), img, resize.Bilinear) err = jpeg.Encode(fthumb, img, nil) if err != nil { return "", err } err = fthumb.Close() if err != nil { return "", err } return path.Join("/media", thumbSlug), nil } func (ms *MediaSource) Walk() ([]*Media, error) { var media []*Media err := filepath.Walk(ms.StorageDir, func(name string, fi os.FileInfo, err error) error { if err != nil { return err } if fi.IsDir() { return nil } media = append(media, ms.ByName(path.Base(name))) return nil }) return media, err } func (ch *CaddyHugo) uploadMedia(w http.ResponseWriter, r *http.Request) (int, error) { if ch.Media == nil { http.NotFound(w, r) return 404, nil } mr, err := r.MultipartReader() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return 400, nil } for { part, err := mr.NextPart() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return 400, nil } name := part.FileName() if name != "" { err = ch.Media.receiveNewMedia(name, part) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return 500, nil } } } return 200, nil } func (ch *CaddyHugo) serveMediaPage(w http.ResponseWriter, r *http.Request) (int, error) { if ch.Media == nil { http.NotFound(w, r) return 404, nil } io.WriteString(w, `
`) io.WriteString(w, UploadPage("media")) io.WriteString(w, "
") if ch.Media != nil { media, err := ch.Media.Walk() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return 500, nil } for _, m := range media { src, width, height, err := ch.Media.ThumbMax(*m, 100) if err != nil { fmt.Fprintf(w, `
error rendering %q: %v
`, m.Name, err) continue } fmt.Fprintf(w, `

📋
`, width, height, src, src) } } io.WriteString(w, ``) return 200, nil } func (ch *CaddyHugo) serveMedia(w http.ResponseWriter, r *http.Request) (int, error) { if ch.Media == nil { http.NotFound(w, r) return 404, nil } segs := strings.Split(r.URL.Path, "/") name := segs[2] file := "" var err error m := ch.Media.ByName(name) switch len(segs) { case 3: file = name case 4: var max int max, err = strconv.Atoi(removeExtension(segs[3])) if err != nil { http.Error(w, fmt.Sprintf("expected /media/filename, /media/filename/maxDim, or /media/filename/width/height..."), http.StatusBadRequest) return 400, nil } file, _, _, err = ch.Media.ThumbMax(*m, max) case 5: var width, height int width, err = strconv.Atoi(segs[3]) if err != nil { http.Error(w, fmt.Sprintf("expected /media/filename, /media/filename/maxDim, or /media/filename/width/height.."), http.StatusBadRequest) return 400, nil } height, err = strconv.Atoi(removeExtension(segs[4])) if err != nil { http.Error(w, fmt.Sprintf("expected /media/filename, /media/filename/maxDim, or /media/filename/width/height."), http.StatusBadRequest) return 400, nil } file, err = ch.Media.Thumb(*m, width, height) default: http.Error(w, fmt.Sprintf("expected /media/filename, /media/filename/maxDim, or /media/filename/width/height"), http.StatusBadRequest) return 400, nil } if err != nil { http.Error(w, fmt.Sprintf("unable to load thumb"), http.StatusInternalServerError) return 500, nil } if file[0] == '/' { file = file[1:] } file = path.Join(ch.Media.ThumbDir, file[len("media/"):]) http.ServeFile(w, r, file) return 200, nil } func removeExtension(name string) string { ext := path.Ext(name) return name[:len(name)-len(ext)] }