You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
caddy-hugo2/media.go

287 lines
6.4 KiB

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, `<html>
<head><style>
.img { float: left; text-align: center; max-width: 19.9vw; min-height: 120px;}
.copy { cursor: pointer; }
</style></head>
<body>
<div style="position: fixed; top: 0; height: 10vh; width: 100%;">
<div style="float: left; width: 100%; height: 10vh;">
`)
io.WriteString(w, UploadPage("media"))
io.WriteString(w, "</div>")
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, `<div class="img">error rendering %q: %v</div>`, m.Name, err)
continue
}
fmt.Fprintf(w, `<div class="img"><img width=%d height=%d src=%q /><br /><input type="text" readonly value=%q /><span class="copy">&#x1F4CB;</span></div>`, width, height, src, src)
}
}
io.WriteString(w, `<script>
document.querySelector('body').onclick = function (evt) {
if (evt.target.tagName === "INPUT" && evt.target.type === "text") {
evt.target.select();
}
if (evt.target.tagName === "SPAN" && evt.target.className === "copy") {
evt.target.previousSibling.select();
document.execCommand("copy");
}
}
document.querySelector('body').onmouseup = function (evt) {
if (evt.target.tagName === "INPUT" && evt.target.type === "text") {
return false;
}
}
</script></body><html>`)
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)]
}