Working web render for public albums
parent
6070c64827
commit
787e025a1f
|
@ -69,6 +69,20 @@ func update(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
width, height, err := readsize(original)
|
||||
if err != nil {
|
||||
httphelpers.ErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
metadata, _ := client.GetPhotoMetadata(bucket, photo)
|
||||
metadata.Width = width
|
||||
metadata.Height = height
|
||||
err = client.PutPhotoMetadata(bucket, photo, metadata)
|
||||
if err != nil {
|
||||
httphelpers.ErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resizes := make([]resizeOperation, len(previewOptions))
|
||||
for i := range resizes {
|
||||
resizes[i] = resizeOperation{
|
||||
|
@ -138,6 +152,14 @@ func update(w http.ResponseWriter, req *http.Request) {
|
|||
w.Header().Add("X-Debug-Resize-Timings", fmt.Sprintf("%v", timings))
|
||||
}
|
||||
|
||||
func readsize(buf []byte) (int, int, error) {
|
||||
i, err := vips.NewImageFromBuffer(buf)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return i.Width(), i.Height(), nil
|
||||
}
|
||||
|
||||
func resize(buf []byte, height int, format vips.ImageType, quality int) ([]byte, error) {
|
||||
i, err := vips.NewImageFromBuffer(buf)
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
# Creating embedded data
|
||||
|
||||
```bash
|
||||
go get -u github.com/go-bindata/go-bindata/v3/...
|
||||
go generate
|
||||
```
|
||||
|
||||
<!-- vim: set conceallevel=2 et ts=2 sw=2: -->
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,45 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
lib "git.makerforce.io/photos/photos/pkg/bucket"
|
||||
)
|
||||
|
||||
var funcs = template.FuncMap{
|
||||
"ar": func(p Photo) float64 { return float64(p.Width) / float64(p.Height) },
|
||||
"mul": func(a, b float64) float64 { return a * b },
|
||||
"preview": preview,
|
||||
}
|
||||
|
||||
type IndexTemplateData struct {
|
||||
Assets string
|
||||
Metadata lib.BucketMetadata
|
||||
Photos []Photo
|
||||
}
|
||||
|
||||
type Photo struct {
|
||||
lib.Photo
|
||||
lib.PhotoMetadata
|
||||
}
|
||||
|
||||
var indexTemplate = mustTemplateAsset("index")
|
||||
|
||||
func mustTemplateAsset(name string) *template.Template {
|
||||
buf, err := Asset("view/" + name + ".tmpl")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return template.Must(
|
||||
template.New(name).Funcs(funcs).Parse(string(buf)),
|
||||
)
|
||||
}
|
||||
|
||||
func preview(p Photo, height int, format lib.PhotoFormat, quality int) template.Srcset {
|
||||
preview := p.GetPreview(lib.PreviewOption{
|
||||
Height: height,
|
||||
Format: format,
|
||||
Quality: quality,
|
||||
})
|
||||
return template.Srcset(preview.String())
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package main
|
||||
|
||||
//go:generate go-bindata -fs -prefix "../../web/" "../../web/..."
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.makerforce.io/photos/photos/internal/httphelpers"
|
||||
lib "git.makerforce.io/photos/photos/pkg/bucket"
|
||||
)
|
||||
|
||||
var client *lib.Client
|
||||
|
||||
var endpoint string
|
||||
|
||||
func main() {
|
||||
// Read configuration
|
||||
endpoint = os.Getenv("WEB_ENDPOINT")
|
||||
|
||||
// Setup bucket client
|
||||
var err error
|
||||
client, err = lib.NewClientFromEnv()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":8004",
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
http.Handle("/shared/", http.FileServer(AssetFile()))
|
||||
http.HandleFunc("/update", update)
|
||||
err = server.ListenAndServe()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func update(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
err := fmt.Errorf("%w: %v", httphelpers.ErrorMethodNotAllowed, req.Method)
|
||||
httphelpers.ErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
bucket := lib.Bucket(req.FormValue("bucket"))
|
||||
/*
|
||||
bucketMetadata, err := client.GetBucketMetadata(bucket)
|
||||
previewOptions := bucketMetadata.PreviewOptions
|
||||
*/
|
||||
|
||||
metadata, err := client.GetBucketMetadata(bucket)
|
||||
photos, err := client.GetPhotos(bucket)
|
||||
if err != nil {
|
||||
httphelpers.ErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
detailedPhotos := make([]Photo, 0)
|
||||
for _, photo := range photos {
|
||||
photoMetadata, err := client.GetPhotoMetadata(bucket, photo)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("%w: %v", err, photo)
|
||||
httphelpers.ErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
detailedPhotos = append(detailedPhotos, Photo{photo, photoMetadata})
|
||||
}
|
||||
|
||||
data := IndexTemplateData{
|
||||
Assets: endpoint + "/shared/",
|
||||
Metadata: metadata,
|
||||
Photos: detailedPhotos,
|
||||
}
|
||||
indexBuf := new(bytes.Buffer)
|
||||
err = indexTemplate.Execute(indexBuf, data)
|
||||
if err != nil {
|
||||
httphelpers.ErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = client.PutBucketWeb(bucket, indexBuf.Bytes())
|
||||
if err != nil {
|
||||
httphelpers.ErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
}
|
5
go.mod
5
go.mod
|
@ -4,6 +4,11 @@ go 1.14
|
|||
|
||||
require (
|
||||
github.com/davidbyttow/govips v0.0.0-20200412130214-cbefdd8c639a
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0
|
||||
github.com/go-bindata/go-bindata v1.0.0 // indirect
|
||||
github.com/go-bindata/go-bindata/v3 v3.1.3 // indirect
|
||||
github.com/miekg/dns v1.1.29
|
||||
github.com/minio/minio-go/v6 v6.0.55
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
|
||||
golang.org/x/tools v0.0.0-20200530233709-52effbd89c51 // indirect
|
||||
)
|
||||
|
|
27
go.sum
27
go.sum
|
@ -4,6 +4,12 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/davidbyttow/govips v0.0.0-20200412130214-cbefdd8c639a h1:g1X1c43wACmPtvFo+szBy/0coLkRwVJ0+i2f6YJITto=
|
||||
github.com/davidbyttow/govips v0.0.0-20200412130214-cbefdd8c639a/go.mod h1:a3qO525EPfJNYa0NXBcNtXzJvyQsJAxphEDa7OOHPBk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/go-bindata/go-bindata v1.0.0 h1:DZ34txDXWn1DyWa+vQf7V9ANc2ILTtrEjtlsdJRF26M=
|
||||
github.com/go-bindata/go-bindata v1.0.0/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-bindata/go-bindata/v3 v3.1.3 h1:F0nVttLC3ws0ojc7p60veTurcOm//D4QBODNM7EGrCI=
|
||||
github.com/go-bindata/go-bindata/v3 v3.1.3/go.mod h1:1/zrpXsLD8YDIbhZRqXzm1Ghc7NhEvIN9+Z6R5/xH4I=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
|
@ -11,6 +17,8 @@ github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGn
|
|||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/errcheck v1.2.0 h1:reN85Pxc5larApoH1keMBiu2GWtPqXQ1nc9gx+jOU+E=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
|
@ -35,12 +43,18 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||
|
@ -48,7 +62,10 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
|
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
|
@ -57,8 +74,18 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPT
|
|||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200530233709-52effbd89c51 h1:Wec8/IO8hAraBf0it7/dPQYOslIrgM938wZYNkLnOYc=
|
||||
golang.org/x/tools v0.0.0-20200530233709-52effbd89c51/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
|
|
@ -78,6 +78,42 @@ func (c *Client) PutBucketMetadata(b Bucket, m BucketMetadata) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) PutBucketWeb(b Bucket, buf []byte) error {
|
||||
err := b.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.PutObject(b.String(), "index.html", bytes.NewBuffer(buf), int64(len(buf)), minio.PutObjectOptions{
|
||||
ContentType: "text/html",
|
||||
StorageClass: "REDUCED_REDUNDANCY",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetPhotos(b Bucket) ([]Photo, error) {
|
||||
err := b.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := make([]Photo, 0)
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
objCh := c.ListObjectsV2(b.String(), photoPrefix, true, doneCh)
|
||||
for obj := range objCh {
|
||||
if obj.Err != nil {
|
||||
return nil, obj.Err
|
||||
}
|
||||
keys = append(keys, Photo(obj.Key))
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetPhoto(b Bucket, p Photo) ([]byte, error) {
|
||||
err := b.Validate()
|
||||
if err != nil {
|
||||
|
@ -96,6 +132,58 @@ func (c *Client) GetPhoto(b Bucket, p Photo) ([]byte, error) {
|
|||
return ioutil.ReadAll(o)
|
||||
}
|
||||
|
||||
func (c *Client) GetPhotoMetadata(b Bucket, p Photo) (PhotoMetadata, error) {
|
||||
err := b.Validate()
|
||||
if err != nil {
|
||||
return PhotoMetadata{}, err
|
||||
}
|
||||
err = p.Validate()
|
||||
if err != nil {
|
||||
return PhotoMetadata{}, err
|
||||
}
|
||||
|
||||
o, err := c.GetObject(b.String(), p.MetadataString(), minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
return PhotoMetadata{}, err
|
||||
}
|
||||
defer o.Close()
|
||||
buf, err := ioutil.ReadAll(o)
|
||||
if err != nil {
|
||||
return PhotoMetadata{}, err
|
||||
}
|
||||
|
||||
m := PhotoMetadata{}
|
||||
err = json.Unmarshal(buf, &m)
|
||||
if err != nil {
|
||||
return PhotoMetadata{}, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *Client) PutPhotoMetadata(b Bucket, p Photo, m PhotoMetadata) error {
|
||||
err := b.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf, err := json.MarshalIndent(m, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.PutObject(b.String(), p.MetadataString(), bytes.NewBuffer(buf), int64(len(buf)), minio.PutObjectOptions{
|
||||
ContentType: "application/json",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) PutPreview(b Bucket, p Preview, buf []byte) error {
|
||||
err := b.Validate()
|
||||
if err != nil {
|
||||
|
|
|
@ -26,3 +26,17 @@ func (p Photo) String() string {
|
|||
func (p Photo) Path() string {
|
||||
return "/" + string(p)
|
||||
}
|
||||
|
||||
const photoMetadataPrefix = "photometa/"
|
||||
|
||||
func (p Photo) MetadataString() string {
|
||||
objectBase := strings.Replace(string(p), photoPrefix, photoMetadataPrefix, 1)
|
||||
objectBase += ".metadata.json"
|
||||
return objectBase
|
||||
}
|
||||
|
||||
type PhotoMetadata struct {
|
||||
Title string `json:"title"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ func (p Photo) GetPreview(option PreviewOption) Preview {
|
|||
}
|
||||
|
||||
var defaultSizes = []int{640, 320, 160, 80}
|
||||
var defaultSizesThumb = []int{120, 60, 30}
|
||||
var defaultSizesThumb = []int{60, 30}
|
||||
var defaultFormats = []PhotoFormat{
|
||||
PhotoFormatWEBP,
|
||||
PhotoFormatJPEG,
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
local1:2020 {
|
||||
root * ./shared
|
||||
file_server browse
|
||||
header Access-Control-Allow-Origin "*"
|
||||
tls internal
|
||||
}
|
||||
|
||||
local2:2020 {
|
||||
root * ./view
|
||||
file_server browse
|
||||
tls internal
|
||||
}
|
|
@ -3,10 +3,10 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Photos</title>
|
||||
<link rel="stylesheet" href="https://local1:2020/css/base.css">
|
||||
<link rel="stylesheet" href="https://local1:2020/css/gallery.css">
|
||||
<link rel="stylesheet" href="https://local1:2020/css/enlarge.css">
|
||||
<title>{{ .Metadata.Title }}</title>
|
||||
<link rel="stylesheet" href="{{ .Assets }}css/base.css">
|
||||
<link rel="stylesheet" href="{{ .Assets }}css/gallery.css">
|
||||
<link rel="stylesheet" href="{{ .Assets }}css/enlarge.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
|
@ -16,65 +16,40 @@
|
|||
</ul>
|
||||
</nav>
|
||||
<header>
|
||||
<h1>James Birthday 2020</h1>
|
||||
<h1>{{ .Metadata.Title }}</h1>
|
||||
</header>
|
||||
<main class="gallery">
|
||||
{{ range (datasource "samples") }}
|
||||
<div class="gallery-item preloaded-thumbnail" data-ar="{{ div (index . 1) (index . 2) }}">
|
||||
<a href="sample/{{ index . 0 }}" data-enlarge="sample/{{ index . 0 }}">
|
||||
{{ range .Photos }}
|
||||
<div class="gallery-item preloaded-thumbnail" data-ar="{{ ar . }}">
|
||||
<a href="{{ . }}" data-enlarge="{{ . }}">
|
||||
<picture>
|
||||
<source srcset="
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_320.webp"}} 2x,
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_160.webp"}} 1x,
|
||||
" type="image/webp" media="(max-width: 900px)">
|
||||
<source srcset="
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_640.webp"}} 2x,
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_320.webp"}} 1x,
|
||||
" type="image/webp">
|
||||
<source srcset="
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_320.jpg"}} 2x,
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_160.jpg"}} 1x,
|
||||
" media="(max-width: 900px)">
|
||||
<source srcset="
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_640.jpg"}} 2x,
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_320.jpg"}} 1x,
|
||||
">
|
||||
<img src="sample/{{ index . 0 | replaceAll ".jpg" "_320.jpg"}}"
|
||||
<source srcset="{{ preview . 320 "image/webp" 70 }} 2x, {{ preview . 160 "image/webp" 70 }} 1x" type="image/webp" media="(max-width: 900px)">
|
||||
<source srcset="{{ preview . 640 "image/webp" 70 }} 2x, {{ preview . 320 "image/webp" 70 }} 1x" type="image/webp">
|
||||
<source srcset="{{ preview . 320 "image/jpeg" 70 }} 2x, {{ preview . 160 "image/jpeg" 70 }} 1x" type="image/jpeg" media="(max-width: 900px)">
|
||||
<source srcset="{{ preview . 640 "image/jpeg" 70 }} 2x, {{ preview . 320 "image/jpeg" 70 }} 1x" type="image/jpeg">
|
||||
<img src="{{ preview . 320 "image/jpeg" 70 }}"
|
||||
alt=""
|
||||
width="{{ div (index . 1) (index . 2) | mul 320 }}"
|
||||
width="{{ ar . | mul 320 }}"
|
||||
height="320"
|
||||
loading="lazy"
|
||||
data-enlarge-preload-for="sample/{{ index . 0 }}">
|
||||
data-enlarge-preload-for="{{ . }}">
|
||||
</picture>
|
||||
<picture class="preloaded-thumbnail-image">
|
||||
<source srcset="
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_pre60.webp"}} 2x,
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_pre30.webp"}} 1x,
|
||||
" type="image/webp" media="(max-width: 900px)">
|
||||
<source srcset="
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_pre120.webp"}} 2x,
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_pre60.webp"}} 1x,
|
||||
" type="image/webp">
|
||||
<source srcset="
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_pre60.jpg"}} 2x,
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_pre30.jpg"}} 1x,
|
||||
" media="(max-width: 900px)">
|
||||
<source srcset="
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_pre120.jpg"}} 2x,
|
||||
sample/{{ index . 0 | replaceAll ".jpg" "_pre60.jpg"}} 1x,
|
||||
">
|
||||
<img src="sample/{{ index . 0 | replaceAll ".jpg" "_pre30.jpg"}}"
|
||||
<source srcset="{{ preview . 30 "image/webp" 34 }} 1x" type="image/webp" media="(max-width: 900px)">
|
||||
<source srcset="{{ preview . 60 "image/webp" 34 }} 1x" type="image/webp">
|
||||
<source srcset="{{ preview . 30 "image/jpeg" 34 }} 1x" type="image/jpeg" media="(max-width: 900px)">
|
||||
<source srcset="{{ preview . 60 "image/jpeg" 34 }} 1x" type="image/jpeg">
|
||||
<img src="{{ preview . 30 "image/jpeg" 34 }}"
|
||||
alt=""
|
||||
width="{{ div (index . 1) (index . 2) | mul 320 }}"
|
||||
height="320"
|
||||
>
|
||||
width="{{ ar . | mul 320 }}"
|
||||
height="320">
|
||||
</picture>
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</main>
|
||||
<!-- This gallery works without JavaScript! -->
|
||||
<script type="module" src="https://local1:2020/js/view.js" async></script>
|
||||
<script type="module" src="{{ .Assets }}js/view.js" async></script>
|
||||
</body>
|
||||
</html>
|
||||
<!-- vim: set ft=html: -->
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
sizes=(640 320 160 80)
|
||||
presizes=(120 60 30)
|
||||
|
||||
rm samples.csv
|
||||
|
||||
echo "Getting file dimensions..."
|
||||
for f in sample/*unsplash.jpg; do
|
||||
gm identify -format %f,%w,%h $f >> samples.csv
|
||||
done
|
||||
|
||||
for size in ${presizes[*]}; do
|
||||
for f in sample/*unsplash.jpg; do
|
||||
echo "Compressing thumbnail $f at $size"
|
||||
if [ -f "${f%.jpg}_pre$size.jpg" ]; then
|
||||
continue
|
||||
fi
|
||||
gm convert $f -resize x$size -compress jpeg -quality 40 ${f%.jpg}_pre$size.jpg
|
||||
gm convert $f -resize x$size -compress webp -quality 35 ${f%.jpg}_pre$size.webp
|
||||
done
|
||||
done
|
||||
for size in ${sizes[*]}; do
|
||||
for f in sample/*unsplash.jpg; do
|
||||
echo "Compressing $f at $size"
|
||||
if [ -f "${f%.jpg}_$size.jpg" ]; then
|
||||
continue
|
||||
fi
|
||||
gm convert $f -resize x$size -compress jpeg -quality 75 ${f%.jpg}_$size.jpg
|
||||
gm convert $f -resize x$size -compress webp -quality 70 ${f%.jpg}_$size.webp
|
||||
done
|
||||
done
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
while true; do
|
||||
gotemplate -f index.tmpl -d samples.csv -o index.html 2>&1 > /dev/null
|
||||
date
|
||||
sleep 5
|
||||
done
|
||||
|
Loading…
Reference in New Issue