Skip to content
Snippets Groups Projects
Verified Commit 706a5039 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

fix: correct issues with image processing

parent ba7b214f
No related branches found
No related tags found
No related merge requests found
Pipeline #2810 canceled
Showing with 1426 additions and 184 deletions
/.idea/ /.idea/
/vendor/ /vendor/
/node_modules/
/cli/
/assets/css
/imghost
...@@ -16,3 +16,12 @@ frontend: ...@@ -16,3 +16,12 @@ frontend:
- mkdir -p /kaniko/.docker - mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile.frontend --destination $CI_REGISTRY_IMAGE:frontend-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA} --destination $CI_REGISTRY_IMAGE:frontend - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile.frontend --destination $CI_REGISTRY_IMAGE:frontend-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA} --destination $CI_REGISTRY_IMAGE:frontend
cli:
stage: build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [ "" ]
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile.cli --destination $CI_REGISTRY_IMAGE:cli-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA} --destination $CI_REGISTRY_IMAGE:cli
FROM golang:1.17-alpine3.15 AS go_builder FROM golang:1.17-alpine3.15 AS go_builder
RUN apk --no-cache add \ RUN apk --no-cache add \
--virtual .build-deps \ build-base \
alpine-sdk \ imagemagick6 \
cmake \ imagemagick6-dev
sudo \
libssh2 libssh2-dev \
git \
dep \
bash \
curl \
imagemagick \
imagemagick-dev
WORKDIR /go/src/app WORKDIR /go/src/app
COPY go.* ./ COPY go.* ./
ENV CGO_ENABLED=1
ENV CGO_CFLAGS_ALLOW=-Xpreprocessor
ENV GOPROXY=https://proxy.golang.org
RUN go mod download RUN go mod download
COPY . ./ COPY . ./
RUN go build -o app ./backend RUN go build -o app ./backend
FROM alpine:3.15 FROM alpine:3.15
RUN apk --no-cache add imagemagick RUN apk --no-cache add imagemagick6
RUN addgroup -g 1000 -S app && \ RUN addgroup -g 1000 -S app && \
adduser -u 1000 -G app -S app adduser -u 1000 -G app -S app
COPY --from=go_builder /go/src/app / COPY --from=go_builder /go/src/app /
......
FROM golang:1.17-alpine3.15 AS go_builder FROM golang:1.17-alpine3.15 AS go_builder
RUN apk --no-cache add \ RUN apk --no-cache add \
--virtual .build-deps \ build-base \
alpine-sdk \ imagemagick6 \
cmake \ imagemagick6-dev
sudo \
libssh2 libssh2-dev \
git \
dep \
bash \
curl \
imagemagick \
imagemagick-dev
WORKDIR /go/src/app WORKDIR /go/src/app
COPY go.* ./ COPY go.* ./
ENV CGO_ENABLED=1
ENV CGO_CFLAGS_ALLOW=-Xpreprocessor
ENV GOPROXY=https://proxy.golang.org
RUN go mod download RUN go mod download
COPY . ./ COPY . ./
RUN go build -o app ./frontend RUN go build -o app ./frontend
FROM node:alpine as asset_builder FROM node:alpine as asset_builder
RUN apk --no-cache add \ RUN apk --no-cache add \
--virtual .build-deps \ build-base \
alpine-sdk \
cmake \
libssh2 libssh2-dev \
git \
dep \
bash \
curl \
python3 python3
WORKDIR /app WORKDIR /app
COPY frontend/package* /app/ COPY frontend/package* /app/
...@@ -35,10 +23,10 @@ COPY frontend/assets /app/assets ...@@ -35,10 +23,10 @@ COPY frontend/assets /app/assets
RUN npm run build RUN npm run build
FROM alpine:3.15 FROM alpine:3.15
RUN apk --no-cache add imagemagick RUN apk --no-cache add imagemagick6
RUN addgroup -g 1000 -S app && \ RUN addgroup -g 1000 -S app && \
adduser -u 1000 -G app -S app adduser -u 1000 -G app -S app
COPY --from=go_builder /go/src/app/app / COPY --from=go_builder /go/src/app /
COPY frontend/templates /templates COPY frontend/templates /templates
COPY --from=asset_builder /app/assets /assets COPY --from=asset_builder /app/assets /assets
USER app USER app
......
FROM golang:1.17-alpine3.15 AS go_builder
RUN apk --no-cache add \
build-base \
imagemagick6 \
imagemagick6-dev
WORKDIR /go/src/app
COPY go.* ./
ENV CGO_ENABLED=1
ENV CGO_CFLAGS_ALLOW=-Xpreprocessor
ENV GOPROXY=https://proxy.golang.org
RUN go mod download
COPY . ./
RUN go build -o app ./cli
FROM alpine:3.15
RUN apk --no-cache add imagemagick6
RUN addgroup -g 1000 -S app && \
adduser -u 1000 -G app -S app
COPY --from=go_builder /go/src/app /
USER app
ENTRYPOINT ["/app"]
...@@ -7,7 +7,7 @@ import ( ...@@ -7,7 +7,7 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"gopkg.in/gographics/imagick.v3/imagick" "gopkg.in/gographics/imagick.v2/imagick"
"log" "log"
"net/http" "net/http"
"os" "os"
...@@ -55,6 +55,7 @@ func main() { ...@@ -55,6 +55,7 @@ func main() {
runner := shared.Runner{} runner := shared.Runner{}
runner.RunParallel(func() { runner.RunParallel(func() {
log.Printf("starting metrics server")
if err := metrics.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := metrics.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("error in metrics server: %s", err.Error()) log.Printf("error in metrics server: %s", err.Error())
} }
...@@ -62,6 +63,7 @@ func main() { ...@@ -62,6 +63,7 @@ func main() {
srv.Shutdown() srv.Shutdown()
}) })
runner.RunParallel(func() { runner.RunParallel(func() {
log.Printf("starting asynq server")
if err := srv.Run(mux); err != nil { if err := srv.Run(mux); err != nil {
log.Printf("error in asynq server: %s", err.Error()) log.Printf("error in asynq server: %s", err.Error())
} }
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"git.kuschku.de/justjanne/imghost/shared" "git.kuschku.de/justjanne/imghost/shared"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
...@@ -21,12 +22,15 @@ func trackTimeSince(counter prometheus.Counter, start time.Time) time.Time { ...@@ -21,12 +22,15 @@ func trackTimeSince(counter prometheus.Counter, start time.Time) time.Time {
func ProcessImageHandler(config *shared.Config) asynq.HandlerFunc { func ProcessImageHandler(config *shared.Config) asynq.HandlerFunc {
return func(ctx context.Context, t *asynq.Task) error { return func(ctx context.Context, t *asynq.Task) error {
log.Printf("received image resize task")
task := shared.ImageTaskPayload{} task := shared.ImageTaskPayload{}
if err := json.Unmarshal(t.Payload(), &task); err != nil { if err := json.Unmarshal(t.Payload(), &task); err != nil {
return err return err
} }
log.Printf("starting image resize task %s", task.ImageId)
errors := ResizeImage(config, task.ImageId) errors := ResizeImage(config, task.ImageId)
log.Printf("deleting cached image for image resize task %s", task.ImageId)
_ = os.Remove(filepath.Join(config.SourceFolder, task.ImageId)) _ = os.Remove(filepath.Join(config.SourceFolder, task.ImageId))
errorMessages := make([]string, len(errors)) errorMessages := make([]string, len(errors))
...@@ -35,6 +39,14 @@ func ProcessImageHandler(config *shared.Config) asynq.HandlerFunc { ...@@ -35,6 +39,14 @@ func ProcessImageHandler(config *shared.Config) asynq.HandlerFunc {
} }
if len(errors) != 0 { if len(errors) != 0 {
log.Printf("errors occured while processing image resize task %s: %s", task.ImageId, strings.Join(errorMessages, "\n"))
if err := json.NewEncoder(t.ResultWriter()).Encode(shared.Result{
Id: task.ImageId,
Success: true,
Errors: errorMessages,
}); err != nil {
return err
}
return fmt.Errorf( return fmt.Errorf(
"errors occured while processing task %s (%s): %s", "errors occured while processing task %s (%s): %s",
t.Type(), t.Type(),
...@@ -43,6 +55,13 @@ func ProcessImageHandler(config *shared.Config) asynq.HandlerFunc { ...@@ -43,6 +55,13 @@ func ProcessImageHandler(config *shared.Config) asynq.HandlerFunc {
) )
} }
if err := json.NewEncoder(t.ResultWriter()).Encode(shared.Result{
Id: task.ImageId,
Success: true,
Errors: []string{},
}); err != nil {
return err
}
return nil return nil
} }
} }
...@@ -2,9 +2,11 @@ package main ...@@ -2,9 +2,11 @@ package main
import ( import (
"fmt" "fmt"
"git.kuschku.de/justjanne/imghost/imgconv"
"git.kuschku.de/justjanne/imghost/shared" "git.kuschku.de/justjanne/imghost/shared"
"github.com/justjanne/imgconv" "gopkg.in/gographics/imagick.v2/imagick"
"gopkg.in/gographics/imagick.v3/imagick" "log"
"os"
"path/filepath" "path/filepath"
"time" "time"
) )
...@@ -12,37 +14,52 @@ import ( ...@@ -12,37 +14,52 @@ import (
func ResizeImage(config *shared.Config, imageId string) []error { func ResizeImage(config *shared.Config, imageId string) []error {
var err error var err error
log.Printf("creating magick wand for %s", imageId)
wand := imagick.NewMagickWand() wand := imagick.NewMagickWand()
defer wand.Destroy() defer wand.Destroy()
startRead := time.Now().UTC() startRead := time.Now().UTC()
log.Printf("reading image for %s", imageId)
if err = wand.ReadImage(filepath.Join(config.SourceFolder, imageId)); err != nil { if err = wand.ReadImage(filepath.Join(config.SourceFolder, imageId)); err != nil {
return []error{err} return []error{err}
} }
log.Printf("importing image for %s", imageId)
var originalImage imgconv.ImageHandle var originalImage imgconv.ImageHandle
if originalImage, err = imgconv.NewImage(wand); err != nil { if originalImage, err = imgconv.NewImage(wand); err != nil {
return []error{err} return []error{err}
} }
trackTimeSince(imageProcessDurationRead, startRead) trackTimeSince(imageProcessDurationRead, startRead)
log.Printf("launching resize goroutines for %s", imageId)
return runMany(len(config.Sizes), func(index int) error { return runMany(len(config.Sizes), func(index int) error {
definition := config.Sizes[index] definition := config.Sizes[index]
path := filepath.Join(config.TargetFolder, fmt.Sprintf("%s%s", imageId, definition.Suffix)) path := filepath.Join(config.TargetFolder, fmt.Sprintf("%s%s", imageId, definition.Suffix))
startClone := time.Now().UTC() startClone := time.Now().UTC()
log.Printf("cloning image for %s in %v", imageId, definition)
image := originalImage.CloneImage() image := originalImage.CloneImage()
startCrop := trackTimeSince(imageProcessDurationClone, startClone) startCrop := trackTimeSince(imageProcessDurationClone, startClone)
log.Printf("cropping image for %s in %v", imageId, definition)
if err := image.Crop(definition.Size); err != nil { if err := image.Crop(definition.Size); err != nil {
return err return err
} }
startResize := trackTimeSince(imageProcessDurationCrop, startCrop) startResize := trackTimeSince(imageProcessDurationCrop, startCrop)
log.Printf("resizing image for %s in %v", imageId, definition)
if err := image.Resize(definition.Size); err != nil { if err := image.Resize(definition.Size); err != nil {
return err return err
} }
startWrite := trackTimeSince(imageProcessDurationResize, startResize) startWrite := trackTimeSince(imageProcessDurationResize, startResize)
if err := image.Write(config.Quality, path); err != nil { log.Printf("opening image for %s in %v", imageId, definition)
target, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err return err
} }
log.Printf("writing image for %s in %v", imageId, definition)
if err := image.WriteImageFile(config.Quality, target); err != nil {
return err
}
log.Printf("tracking time for %s in %v", imageId, definition)
trackTimeSince(imageProcessDurationWrite, startWrite) trackTimeSince(imageProcessDurationWrite, startWrite)
log.Printf("done with image for %s in %v", imageId, definition)
return nil return nil
}) })
} }
package main
import (
"encoding/json"
"flag"
"git.kuschku.de/justjanne/imghost/imgconv"
"gopkg.in/gographics/imagick.v2/imagick"
"io/fs"
"io/ioutil"
"os"
)
type arguments struct {
Width *uint
Height *uint
Fit *string
Quality *uint
Source string
Target string
ExportMetadata *string
}
var args = arguments{
Width: flag.Uint(
"width",
0,
"Desired width of the image",
),
Height: flag.Uint(
"height",
0,
"Desired height of the image",
),
Fit: flag.String(
"fit",
"contain",
"Desired fit format for image. Allowed are cover and contain.",
),
Quality: flag.Uint(
"quality",
90,
"Desired quality of output image",
),
ExportMetadata: flag.String(
"export-metadata",
"",
"Export metadata as json",
),
}
func main() {
flag.Parse()
if flag.NArg() < 2 {
flag.Usage()
os.Exit(1)
}
imagick.Initialize()
defer imagick.Terminate()
source := flag.Arg(0)
target := flag.Arg(1)
data, err := convert(source, target, imgconv.Quality{
CompressionQuality: *args.Quality,
SamplingFactors: []float64{1.0, 1.0, 1.0, 1.0},
}, imgconv.Size{
Width: *args.Width,
Height: *args.Height,
Format: *args.Fit,
})
if err != nil {
panic(err)
}
if *args.ExportMetadata != "" {
marshalled, err := json.MarshalIndent(data, "", " ")
if err != nil {
panic(err)
}
if err := ioutil.WriteFile(*args.ExportMetadata, marshalled, fs.FileMode(644)); err != nil {
panic(err)
}
}
}
func convert(source string, target string, quality imgconv.Quality, size imgconv.Size) (*imgconv.Metadata, error) {
wand := imagick.NewMagickWand()
defer wand.Destroy()
var err error
if err = wand.ReadImage(source); err != nil {
return nil, err
}
var image imgconv.ImageHandle
if image, err = imgconv.NewImage(wand); err != nil {
return nil, err
}
data := image.ParseMetadata()
if err := image.Crop(size); err != nil {
return nil, err
}
if err := image.Resize(size); err != nil {
return nil, err
}
if err := image.Write(quality, target); err != nil {
return nil, err
}
return &data, nil
}
...@@ -29,6 +29,7 @@ func main() { ...@@ -29,6 +29,7 @@ func main() {
context.Background(), context.Background(),
&config, &config,
asynq.NewClient(config.AsynqOpts()), asynq.NewClient(config.AsynqOpts()),
asynq.NewInspector(config.AsynqOpts()),
config.UploadTimeoutDuration(), config.UploadTimeoutDuration(),
db, db,
http.FileServer(http.Dir(config.TargetFolder)), http.FileServer(http.Dir(config.TargetFolder)),
......
...@@ -13,6 +13,7 @@ type ImageDetailData struct { ...@@ -13,6 +13,7 @@ type ImageDetailData struct {
User UserInfo User UserInfo
Image shared.Image Image shared.Image
IsMine bool IsMine bool
BaseUrl string
} }
func pageImageDetail(ctx PageContext) http.Handler { func pageImageDetail(ctx PageContext) http.Handler {
...@@ -86,6 +87,7 @@ func pageImageDetail(ctx PageContext) http.Handler { ...@@ -86,6 +87,7 @@ func pageImageDetail(ctx PageContext) http.Handler {
user, user,
info, info,
owner == user.Id, owner == user.Id,
ctx.Config.BaseUrl,
}); err != nil { }); err != nil {
formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html") formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
return return
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"git.kuschku.de/justjanne/imghost/shared" "git.kuschku.de/justjanne/imghost/shared"
"github.com/hibiken/asynq"
"io" "io"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
...@@ -14,6 +15,11 @@ import ( ...@@ -14,6 +15,11 @@ import (
"time" "time"
) )
type UploadData struct {
BaseUrl string
User UserInfo
}
func detectMimeType(path string) (string, error) { func detectMimeType(path string) (string, error) {
file, err := os.Open(path) file, err := os.Open(path)
if err != nil { if err != nil {
...@@ -100,19 +106,21 @@ func pageUpload(ctx PageContext) http.Handler { ...@@ -100,19 +106,21 @@ func pageUpload(ctx PageContext) http.Handler {
return return
} }
fmt.Printf("Created task %s at %d\n", image.Id, time.Now().Unix()) fmt.Printf("created task %s at %d\n", image.Id, time.Now().Unix())
t, err := shared.NewImageResizeTask(image.Id) t, err := shared.NewImageResizeTask(image.Id)
fmt.Printf("Submitted task %s at %d\n", image.Id, time.Now().Unix())
if err != nil { if err != nil {
formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json") formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
return return
} }
info, err := ctx.Async.Enqueue(t) info, err := ctx.AsynqClient.Enqueue(t, asynq.Retention(ctx.UploadTimeout))
fmt.Printf("submitted task %s at %d\n", image.Id, time.Now().Unix())
if err != nil { if err != nil {
formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json") formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
return return
} }
if err := waitOnTask(info, ctx.UploadTimeout); err != nil { info, err = waitOnTask(ctx, info, ctx.UploadTimeout)
fmt.Printf("got result for task %s at %d\n", image.Id, time.Now().Unix())
if err != nil {
formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json") formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
return return
} }
...@@ -128,7 +136,8 @@ func pageUpload(ctx PageContext) http.Handler { ...@@ -128,7 +136,8 @@ func pageUpload(ctx PageContext) http.Handler {
return return
} else { } else {
user := parseUser(r) user := parseUser(r)
if err := formatTemplate(w, "upload.html", IndexData{ if err := formatTemplate(w, "upload.html", UploadData{
ctx.Config.BaseUrl,
user, user,
}); err != nil { }); err != nil {
formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html") formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
......
...@@ -38,14 +38,14 @@ ...@@ -38,14 +38,14 @@
<div class="url"> <div class="url">
<p>Detail Page</p> <p>Detail Page</p>
<div> <div>
<input id="url_full" type="text" value="https://i.k8r.eu/i/{{.Image.Id}}"> <input id="url_full" type="text" value="{{ .BaseUrl }}/i/{{.Image.Id}}">
<button class="copy" data-target="#url_full">Copy</button> <button class="copy" data-target="#url_full">Copy</button>
</div> </div>
</div> </div>
<div class="url"> <div class="url">
<p>Direct Link</p> <p>Direct Link</p>
<div> <div>
<input id="url_direct" type="text" value="https://i.k8r.eu/{{.Image.Id}}.png"> <input id="url_direct" type="text" value="{{ .BaseUrl }}/{{.Image.Id}}.png">
<button class="copy" data-target="#url_direct">Copy</button> <button class="copy" data-target="#url_direct">Copy</button>
</div> </div>
</div> </div>
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
{{range .Images}} {{range .Images}}
<a class="image" href="/i/{{.Id}}"> <a class="image" href="/i/{{.Id}}">
<div class="image-container"> <div class="image-container">
<img src="https://i.k8r.eu/{{.Id}}t.png"> <img src="/{{.Id}}t.png">
</div> </div>
<div class="info"> <div class="info">
<p class="title"> <p class="title">
......
{{- /*gotype: git.kuschku.de/justjanne/imghost.UploadData*/ -}}
{{define "title"}}Upload | ik8r{{end}} {{define "title"}}Upload | ik8r{{end}}
{{define "content"}} {{define "content"}}
<div class="page upload"> <div class="page upload">
...@@ -15,7 +16,7 @@ ...@@ -15,7 +16,7 @@
<div class="url"> <div class="url">
<p>Album</p> <p>Album</p>
<div> <div>
<input id="url_full" type="text" value="https://i.k8r.eu/a/ERROR"> <input id="url_full" type="text" value="{{ .BaseUrl }}/a/ERROR">
<button class="copy" data-target="#url_full">Copy</button> <button class="copy" data-target="#url_full">Copy</button>
</div> </div>
</div> </div>
......
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"html/template" "html/template"
"net/http" "net/http"
"strings"
"time" "time"
) )
...@@ -32,7 +31,8 @@ func (info UserInfo) HasRole(role string) bool { ...@@ -32,7 +31,8 @@ func (info UserInfo) HasRole(role string) bool {
type PageContext struct { type PageContext struct {
Context context.Context Context context.Context
Config *shared.Config Config *shared.Config
Async *asynq.Client AsynqClient *asynq.Client
AsynqInspector *asynq.Inspector
UploadTimeout time.Duration UploadTimeout time.Duration
Database *sql.DB Database *sql.DB
Images http.Handler Images http.Handler
...@@ -56,10 +56,10 @@ type Album struct { ...@@ -56,10 +56,10 @@ type Album struct {
func parseUser(r *http.Request) UserInfo { func parseUser(r *http.Request) UserInfo {
return UserInfo{ return UserInfo{
r.Header.Get("X-Auth-Subject"), "d41d9763-04a2-435a-b1eb-f636a81ef728",
r.Header.Get("X-Auth-Username"), "testuser",
r.Header.Get("X-Auth-Email"), "test@example.com",
strings.Split(r.Header.Get("X-Auth-Roles"), ","), []string{"imghost:user"},
} }
} }
...@@ -106,27 +106,41 @@ func formatTemplate(w http.ResponseWriter, templateName string, data interface{} ...@@ -106,27 +106,41 @@ func formatTemplate(w http.ResponseWriter, templateName string, data interface{}
return nil return nil
} }
func waitOnTask(info *asynq.TaskInfo, timeout time.Duration) error { func waitOnTask(ctx PageContext, info *asynq.TaskInfo, timeout time.Duration) (*asynq.TaskInfo, error) {
total := time.Duration(0) total := time.Duration(0)
for total < timeout && info.State != asynq.TaskStateCompleted { info, err := ctx.AsynqInspector.GetTaskInfo(info.Queue, info.ID)
for info.State == asynq.TaskStateScheduled { if err != nil {
return nil, err
}
// Wait for it being scheduled
for total < timeout && info.State == asynq.TaskStateScheduled {
duration := info.NextProcessAt.Sub(time.Now()) duration := info.NextProcessAt.Sub(time.Now())
total += duration total += duration
if total < timeout { if total < timeout {
time.Sleep(duration) time.Sleep(duration)
} }
info, err = ctx.AsynqInspector.GetTaskInfo(info.Queue, info.ID)
if err != nil {
return nil, err
}
} }
if info.State != asynq.TaskStateCompleted { // Wait for it being completed
duration := time.Duration(1 * time.Second) for total < timeout && info.State != asynq.TaskStateArchived && info.State != asynq.TaskStateCompleted {
duration := 1 * time.Second
total += duration total += duration
if total < timeout { if total < timeout {
time.Sleep(duration) time.Sleep(duration)
} }
info, err = ctx.AsynqInspector.GetTaskInfo(info.Queue, info.ID)
if err != nil {
return nil, err
} }
} }
if info.State == asynq.TaskStateCompleted { if info.State == asynq.TaskStateCompleted {
return nil return info, nil
} else if info.State == asynq.TaskStateArchived {
return info, fmt.Errorf("error executing task: %s (%s), has status %s", info.Type, info.ID, info.State)
} else { } else {
return fmt.Errorf("timed out waiting on task: %s (%s)", info.Type, info.ID) return info, fmt.Errorf("task timed out: %s (%s), has status %s", info.Type, info.ID, info.State)
} }
} }
...@@ -3,16 +3,9 @@ module git.kuschku.de/justjanne/imghost ...@@ -3,16 +3,9 @@ module git.kuschku.de/justjanne/imghost
go 1.15 go 1.15
require ( require (
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hibiken/asynq v0.23.0 github.com/hibiken/asynq v0.23.0
github.com/justjanne/imgconv v1.4.1
github.com/lib/pq v1.10.4 github.com/lib/pq v1.10.4
github.com/prometheus/client_golang v1.11.1 github.com/prometheus/client_golang v1.11.1
github.com/spf13/cast v1.5.0 // indirect gopkg.in/gographics/imagick.v2 v2.4.0
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 gopkg.in/yaml.v2 v2.3.0
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/gographics/imagick.v3 v3.4.0
gopkg.in/yaml.v2 v2.4.0
) )
...@@ -11,14 +11,9 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce ...@@ -11,14 +11,9 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
...@@ -26,8 +21,6 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r ...@@ -26,8 +21,6 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
...@@ -37,11 +30,9 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb ...@@ -37,11 +30,9 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-redis/redis/v8 v8.11.2 h1:WqlSpAwz8mxDSMCvbyz1Mkiqe0LE5OY4j3lgkvu1Ts0=
github.com/go-redis/redis/v8 v8.11.2/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M= github.com/go-redis/redis/v8 v8.11.2/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
...@@ -55,10 +46,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W ...@@ -55,10 +46,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
...@@ -66,36 +55,28 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ ...@@ -66,36 +55,28 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hibiken/asynq v0.23.0 h1:kmKkNFgqiXBatC8oz94Mer6uvKoGn4STlIVDV5wnKyE= github.com/hibiken/asynq v0.23.0 h1:kmKkNFgqiXBatC8oz94Mer6uvKoGn4STlIVDV5wnKyE=
github.com/hibiken/asynq v0.23.0/go.mod h1:K70jPVx+CAmmQrXot7Dru0D52EO7ob4BIun3ri5z1Qw= github.com/hibiken/asynq v0.23.0/go.mod h1:K70jPVx+CAmmQrXot7Dru0D52EO7ob4BIun3ri5z1Qw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/justjanne/imgconv v1.4.1 h1:byVibYVhfJZeQAaWwsU3EenvPOIf5OD2pnimc56IbS0=
github.com/justjanne/imgconv v1.4.1/go.mod h1:6uRn+br2dIv8K5G9iEVjAmBan0/GAW2++OEAVDuk0GE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
...@@ -106,23 +87,16 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN ...@@ -106,23 +87,16 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
...@@ -149,20 +123,16 @@ github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3x ...@@ -149,20 +123,16 @@ github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3x
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
...@@ -190,9 +160,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL ...@@ -190,9 +160,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
...@@ -212,29 +181,21 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w ...@@ -212,29 +181,21 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
...@@ -264,28 +225,23 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 ...@@ -264,28 +225,23 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gographics/imagick.v3 v3.4.0 h1:kSnbsXOWofo81VJEn/Hw8w3qqoOrfTyWwjAQzSdtPlg= gopkg.in/gographics/imagick.v2 v2.4.0 h1:E1KeUJAk9TUdhClfkvWPjJbDfXe1R946J6vgBtPVP4Q=
gopkg.in/gographics/imagick.v3 v3.4.0/go.mod h1:+Q9nyA2xRZXrDyTtJ/eko+8V/5E7bWYs08ndkZp8UmA= gopkg.in/gographics/imagick.v2 v2.4.0/go.mod h1:of4TbGX8yMcpgWkWFjha7FsOFr+NjOJ5O1qtKU27Yj0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
......
package imgconv
import (
"gopkg.in/gographics/imagick.v2/imagick"
"log"
"math"
"os"
"strings"
)
func NewImage(wand *imagick.MagickWand) (ImageHandle, error) {
meta := ImageHandle{
wand: wand,
depth: wand.GetImageDepth(),
}
if err := wand.AutoOrientImage(); err != nil {
return meta, err
}
if len(wand.GetImageProfiles("i*")) == 0 {
if err := wand.ProfileImage("icc", ProfileSRGB); err != nil {
return meta, err
}
}
for _, name := range wand.GetImageProfiles("*") {
meta.profiles = append(meta.profiles, ColorProfile{
data: []byte(wand.GetImageProfile(name)),
format: name,
})
}
if meta.depth < 16 {
if err := wand.SetImageDepth(16); err != nil {
return meta, err
}
}
if err := wand.ProfileImage("icc", ProfileACESLinear); err != nil {
return meta, err
}
return meta, nil
}
func (image *ImageHandle) CloneImage() ImageHandle {
return ImageHandle{
image.wand.Clone(),
image.depth,
image.profiles,
}
}
func (image *ImageHandle) ParseMetadata() Metadata {
return parseMetadata(image.wand)
}
func (image *ImageHandle) SanitizeMetadata() error {
var profiles []ColorProfile
for _, profile := range image.profiles {
if !strings.EqualFold("exif", profile.format) {
profiles = append(profiles, profile)
}
}
image.profiles = profiles
image.wand.RemoveImageProfile("exif")
if err := image.wand.SetOption("png:include-chunk", "bKGD,cHRM,iCCP"); err != nil {
return err
}
if err := image.wand.SetOption("png:exclude-chunk", "EXIF,iTXt,tEXt,zTXt,date"); err != nil {
return err
}
for _, key := range image.wand.GetImageProperties("png:*") {
if err := image.wand.DeleteImageProperty(key); err != nil {
return err
}
}
return nil
}
func (image *ImageHandle) Crop(size Size) error {
if size.Width == 0 || size.Height == 0 || size.Format != ImageFitCover {
return nil
}
currentWidth := image.wand.GetImageWidth()
currentHeight := image.wand.GetImageHeight()
currentAspectRatio := float64(currentWidth) / float64(currentHeight)
desiredAspectRatio := float64(size.Width) / float64(size.Height)
if currentAspectRatio == desiredAspectRatio {
return nil
}
var desiredWidth, desiredHeight uint
if desiredAspectRatio > currentAspectRatio {
desiredWidth = currentWidth
desiredHeight = uint(math.Round(float64(currentWidth) / desiredAspectRatio))
} else {
desiredHeight = currentHeight
desiredWidth = uint(math.Round(desiredAspectRatio * float64(currentHeight)))
}
offsetLeft := int((currentWidth - desiredWidth) / 2.0)
offsetTop := int((currentHeight - desiredHeight) / 2.0)
if err := image.wand.CropImage(desiredWidth, desiredHeight, offsetLeft, offsetTop); err != nil {
return err
}
return nil
}
func determineDesiredSize(width uint, height uint, size Size) (uint, uint) {
currentAspectRatio := float64(width) / float64(height)
var desiredWidth, desiredHeight uint
if size.Height != 0 && size.Width != 0 {
if size.Format == ImageFitCover {
var desiredAspectRatio = float64(size.Width) / float64(size.Height)
var croppedWidth, croppedHeight uint
if desiredAspectRatio > currentAspectRatio {
croppedWidth = width
croppedHeight = uint(math.Round(float64(width) / desiredAspectRatio))
} else {
croppedHeight = height
croppedWidth = uint(math.Round(desiredAspectRatio * float64(height)))
}
desiredHeight = uint(math.Min(float64(size.Height), float64(croppedHeight)))
desiredWidth = uint(math.Min(float64(size.Width), float64(croppedWidth)))
} else if currentAspectRatio > 1 {
desiredWidth = uint(math.Min(float64(size.Width), float64(width)))
desiredHeight = uint(math.Round(float64(desiredWidth) / currentAspectRatio))
} else {
desiredHeight = uint(math.Min(float64(size.Height), float64(height)))
desiredWidth = uint(math.Round(currentAspectRatio * float64(desiredHeight)))
}
} else if size.Height != 0 {
desiredHeight = uint(math.Min(float64(size.Height), float64(height)))
desiredWidth = uint(math.Round(currentAspectRatio * float64(desiredHeight)))
} else if size.Width != 0 {
desiredWidth = uint(math.Min(float64(size.Width), float64(width)))
desiredHeight = uint(math.Round(float64(desiredWidth) / currentAspectRatio))
} else {
desiredWidth = width
desiredHeight = height
}
return desiredWidth, desiredHeight
}
func (image *ImageHandle) Resize(size Size) error {
if size.Width == 0 && size.Height == 0 {
return nil
}
currentWidth := image.wand.GetImageWidth()
currentHeight := image.wand.GetImageHeight()
desiredWidth, desiredHeight := determineDesiredSize(currentWidth, currentHeight, size)
if desiredWidth != currentWidth || desiredHeight != currentHeight {
if err := image.wand.ResizeImage(desiredWidth, desiredHeight, imagick.FILTER_LANCZOS, 1); err != nil {
return err
}
}
return nil
}
func (image *ImageHandle) prepareWrite(quality Quality) error {
log.Printf("preparing image for writing at quality %v", quality)
for _, profile := range image.profiles {
log.Printf("setting color profile on image '%s' %d", profile.format, len(profile.data))
if err := image.wand.ProfileImage(profile.format, profile.data); err != nil {
return err
}
}
log.Printf("setting image depth on image %d", image.depth)
if err := image.wand.SetImageDepth(image.depth); err != nil {
return err
}
if quality.CompressionQuality != 0 {
log.Printf("setting compression quality on image %d", quality.CompressionQuality)
if err := image.wand.SetImageCompressionQuality(quality.CompressionQuality); err != nil {
return err
}
}
if len(quality.SamplingFactors) != 0 {
log.Printf("setting sampling factors on image %v", quality.SamplingFactors)
if err := image.wand.SetSamplingFactors(quality.SamplingFactors); err != nil {
return err
}
}
log.Printf("done preparing image for writing")
return nil
}
func (image *ImageHandle) Write(quality Quality, target string) error {
if err := image.prepareWrite(quality); err != nil {
return err
}
log.Printf("writing image %s", target)
if err := image.wand.WriteImage(target); err != nil {
return err
}
return nil
}
func (image *ImageHandle) WriteImageFile(quality Quality, target *os.File) error {
if err := image.prepareWrite(quality); err != nil {
return err
}
log.Printf("writing image %s", target.Name())
if err := image.wand.WriteImageFile(target); err != nil {
return err
}
return nil
}
package imgconv
import (
"encoding/json"
"fmt"
"gopkg.in/gographics/imagick.v2/imagick"
"strconv"
"strings"
"time"
)
type Metadata struct {
AspectRatio *Ratio `json:"aspectRatio,omitempty"`
Make string `json:"make,omitempty"`
Model string `json:"model,omitempty"`
LensMake string `json:"lensMake,omitempty"`
LensModel string `json:"lensModel,omitempty"`
Software string `json:"software,omitempty"`
Copyright string `json:"copyright,omitempty"`
Description string `json:"description,omitempty"`
CreatedAt *time.Time `json:"createdAt,omitempty"`
DigitizedAt *time.Time `json:"digitizedAt,omitempty"`
OriginalAt *time.Time `json:"originalAt,omitempty"`
DigitalZoom *Ratio `json:"digitalZoom,omitempty"`
Exposure *Ratio `json:"exposure,omitempty"`
ExposureMode ExposureMode `json:"exposureMode,omitempty"`
ExposureProgram ExposureProgram `json:"exposureProgram,omitempty"`
ShutterSpeed *Ratio `json:"shutterSpeed,omitempty"`
Aperture *Ratio `json:"aperture,omitempty"`
Brightness *Ratio `json:"brightness,omitempty"`
MaxAperture *Ratio `json:"maxAperture,omitempty"`
Flash *Flash `json:"flash,omitempty"`
FocalLength *Ratio `json:"focalLength,omitempty"`
FocalLengthFF *Ratio `json:"focalLengthFF,omitempty"`
IsoSpeedRating *int64 `json:"isoSpeedRating,omitempty"`
LightSource LightSource `json:"lightSource,omitempty"`
MeteringMode MeteringMode `json:"meteringMode,omitempty"`
WhiteBalance WhiteBalance `json:"whiteBalance,omitempty"`
SceneMode SceneMode `json:"scene,omitempty"`
ISO *int64 `json:"iso,omitempty"`
Orientation Orientation `json:"orientation,omitempty"`
Contrast ContrastMode `json:"contrast,omitempty"`
Sharpness SharpnessMode `json:"sharpness,omitempty"`
SubjectDistance *Ratio `json:"subjectDistance,omitempty"`
SubjectDistanceRange DistanceRange `json:"subjectDistanceRange,omitempty"`
FileSource FileSource `json:"source,omitempty"`
Saturation *int64 `json:"saturation,omitempty"`
SensorType SensorType `json:"sensor,omitempty"`
LensSpecification *LensSpecification `json:"lensSpecification,omitempty"`
Location *Location `json:"location,omitempty"`
Resolution *Resolution `json:"resolution,omitempty"`
}
func parseMetadata(wand *imagick.MagickWand) Metadata {
keys := exifKeyMap(wand)
get := func(key string) string {
originalKey, ok := keys[key]
if ok {
return wand.GetImageProperty(originalKey)
} else {
return ""
}
}
return Metadata{
AspectRatio: &Ratio{
int64(wand.GetImageWidth()),
int64(wand.GetImageHeight()),
},
Make: strings.TrimSpace(get("Make")),
Model: strings.TrimSpace(get("Model")),
LensMake: strings.TrimSpace(get("LensMake")),
LensModel: strings.TrimSpace(get("LensModel")),
Software: strings.TrimSpace(get("Software")),
Copyright: strings.TrimSpace(get("Copyright")),
Description: strings.TrimSpace(get("ImageDescription")),
CreatedAt: parseTime(get("DateTime"), get("SubSecTime")),
DigitizedAt: parseTime(get("DateTimeDigitized"), get("SubSecTimeDigitized")),
OriginalAt: parseTime(get("DateTimeOriginal"), get("SubSecTimeOriginal")),
DigitalZoom: parseRatio(get("DigitalZoomRatio")),
Exposure: parseRatio(get("ExposureBiasValue")),
ExposureMode: parseExposureMode(parseNumber(get("ExposureMode"))),
ExposureProgram: parseExposureProgram(parseNumber(get("ExposureProgram"))),
ShutterSpeed: parseShutterSpeed(parseRatio(get("ExposureTime")), parseRatio(get("ShutterSpeedValue"))),
Aperture: parseRatio(get("FNumber")),
Brightness: parseRatio(get("BrightnessValue")),
MaxAperture: parseRatio(get("MaxApertureValue")),
Flash: parseFlash(parseNumber(get("Flash")), get("FlashEnergy")),
FocalLength: parseRatio(get("FocalLength")),
FocalLengthFF: parseRatio(get("FocalLengthIn35mmFilm")),
ISO: parseNumber(get("PhotographicSensitivity")),
LightSource: parseLightSource(parseNumber(get("LightSource"))),
MeteringMode: parseMeteringMode(parseNumber(get("MeteringMode"))),
Orientation: parseOrientation(parseNumber(get("Orientation"))),
WhiteBalance: parseWhiteBalance(parseNumber(get("WhiteBalance"))),
SceneMode: parseSceneMode(parseNumber(get("SceneMode"))),
Contrast: parseContrastMode(parseNumber(get("Contrast"))),
Sharpness: parseSharpnessMode(parseNumber(get("Sharpness"))),
SubjectDistance: parseRatio(get("SubjectDistance")),
SubjectDistanceRange: parseDistanceRange(parseNumber(get("SubjectDistanceRange"))),
FileSource: parseFileSource(parseNumber(get("FileSource"))),
Saturation: parseNumber(get("Saturation")),
SensorType: parseSensorType(parseNumber(get("SensingMethod"))),
LensSpecification: parseLensSpecification(get("LensSpecification")),
Location: parseLocation(
parseCoordinate(get("GPSLatitude"), get("GPSLatitudeRef")),
parseCoordinate(get("GPSLongitude"), get("GPSLongitudeRef")),
),
Resolution: parseResolution(
parseNumber(get("ResolutionUnit")),
parseRatio(get("XResolution")),
parseRatio(get("YResolution")),
),
}
}
func exifKeyMap(wand *imagick.MagickWand) map[string]string {
metadata := make(map[string]string)
for _, key := range wand.GetImageProperties("exif:*") {
if strings.HasPrefix(key, "exif:thumbnail:") {
continue
}
trimmedKey := strings.TrimPrefix(key, "exif:")
metadata[trimmedKey] = key
}
return metadata
}
const ExifTime = "2006:01:02 15:04:05"
func parseTime(value string, subSec string) *time.Time {
result, err := time.Parse(ExifTime, value)
if err != nil {
return nil
} else {
microseconds := parseNumber(subSec)
if microseconds != nil {
result = result.Add(time.Duration(*microseconds) * time.Microsecond)
}
return &result
}
}
func parseNumber(value string) *int64 {
result, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return nil
} else {
return &result
}
}
func parseShutterSpeed(exposure *Ratio, shutterSpeed *Ratio) *Ratio {
if shutterSpeed != nil {
return &Ratio{
shutterSpeed.Denominator,
shutterSpeed.Numerator,
}
}
return exposure
}
type Flash struct {
Available bool `json:"available"`
Fired bool `json:"fired"`
StrobeDetection StrobeDetection `json:"strobeDetection"`
Mode FlashMode `json:"mode,omitempty"`
RedEyeReduction bool `json:"redEyeReduction"`
Strength *Ratio `json:"strength,omitempty"`
}
type StrobeDetection struct {
Available bool `json:"available"`
Detected bool `json:"detected"`
}
const (
maskFired = 0x0001
maskStrobeDetected = 0x0002
maskStrobeDetectionAvailable = 0x0004
maskMode = 0x0003
maskUnavailable = 0x0020
maskRedEye = 0x0040
)
func parseFlash(flash *int64, strength string) *Flash {
if flash == nil {
return nil
}
return &Flash{
Available: *flash&maskUnavailable == 0,
Fired: *flash&maskFired != 0,
StrobeDetection: StrobeDetection{
Available: *flash&maskStrobeDetectionAvailable != 0,
Detected: *flash&maskStrobeDetected != 0,
},
Mode: parseFlashMode((*flash >> 3) & maskMode),
RedEyeReduction: *flash&maskRedEye != 0,
Strength: parseRatio(strength),
}
}
type Resolution struct {
X Ratio `json:"x"`
Y Ratio `json:"y"`
}
func parseResolution(unit *int64, x *Ratio, y *Ratio) *Resolution {
if unit == nil {
defaultUnit := int64(2)
unit = &defaultUnit
}
if x == nil || y == nil {
return nil
}
return &Resolution{
X: Ratio{
x.Numerator,
x.Denominator * *unit,
}.reduce(),
Y: Ratio{
y.Numerator,
y.Denominator * *unit,
}.reduce(),
}
}
type LensSpecification struct {
WideFocalLength *Ratio `json:"wideFocalLength,omitempty"`
WideAperture *Ratio `json:"wideAperture,omitempty"`
TeleFocalLength *Ratio `json:"teleFocalLength,omitempty"`
TeleAperture *Ratio `json:"teleAperture,omitempty"`
}
func parseLensSpecification(value string) *LensSpecification {
split := strings.Split(value, ", ")
if len(split) != 4 {
return nil
}
return &LensSpecification{
WideFocalLength: parseRatio(split[0]),
TeleFocalLength: parseRatio(split[1]),
WideAperture: parseRatio(split[2]),
TeleAperture: parseRatio(split[3]),
}
}
type Location struct {
Longitude Coordinate `json:"longitude"`
Latitude Coordinate `json:"latitude"`
}
func parseLocation(longitude *Coordinate, latitude *Coordinate) *Location {
if longitude == nil || latitude == nil {
return nil
}
return &Location{*longitude, *latitude}
}
type Coordinate struct {
Degree Ratio `json:"degree"`
Minute Ratio `json:"minute"`
Second Ratio `json:"second"`
CardinalDirection CardinalDirection `json:"reference"`
}
func parseCoordinate(value string, reference string) *Coordinate {
parts := strings.Split(value, ", ")
if len(parts) != 3 {
return nil
}
degree := parseRatio(parts[0])
minute := parseRatio(parts[1])
second := parseRatio(parts[2])
direction := parseCardinalDirection(reference)
if degree == nil || minute == nil || second == nil || direction == "" {
return nil
}
return &Coordinate{
*degree,
*minute,
*second,
direction,
}
}
type CardinalDirection string
const (
CardinalDirectionNorth CardinalDirection = "N"
CardinalDirectionWest CardinalDirection = "W"
CardinalDirectionSouth CardinalDirection = "S"
CardinalDirectionEast CardinalDirection = "E"
)
func parseCardinalDirection(value string) CardinalDirection {
switch value {
case "N":
return CardinalDirectionNorth
case "W":
return CardinalDirectionWest
case "S":
return CardinalDirectionSouth
case "E":
return CardinalDirectionEast
default:
return ""
}
}
type Ratio struct {
Numerator int64 `json:"num"`
Denominator int64 `json:"den"`
}
func (ratio *Ratio) MarshalJSON() ([]byte, error) {
return json.Marshal(fmt.Sprintf("%d/%d", ratio.Numerator, ratio.Denominator))
}
func (ratio *Ratio) UnmarshalJSON(data []byte) error {
var raw string
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
split := strings.Split(raw, "/")
if len(split) != 2 {
fallback := parseNumber(raw)
if fallback == nil {
return fmt.Errorf("could not deserialize ratio: ratio is neither a ratio nor a plain number")
} else {
ratio.Numerator = *fallback
ratio.Denominator = 1
return nil
}
}
numerator := parseNumber(split[0])
if numerator == nil {
return fmt.Errorf("could not deserialize ratio: numerator is not a valid number")
}
denominator := parseNumber(split[1])
if denominator == nil {
return fmt.Errorf("could not deserialize ratio: denominator is not a valid number")
}
ratio.Numerator = *numerator
ratio.Denominator = *denominator
return nil
}
func (ratio Ratio) reduce() Ratio {
if ratio.Numerator > 0 && ratio.Denominator > 0 && ratio.Numerator%ratio.Denominator == 0 {
ratio.Numerator = ratio.Numerator / ratio.Denominator
ratio.Denominator = 1
} else if ratio.Numerator == 0 {
ratio.Numerator = 0
ratio.Denominator = 1
}
return ratio
}
func parseRatio(value string) *Ratio {
split := strings.Split(value, "/")
if len(split) != 2 {
return nil
}
numerator := parseNumber(split[0])
denominator := parseNumber(split[1])
if numerator == nil || denominator == nil {
return nil
}
result := Ratio{*numerator, *denominator}.reduce()
return &result
}
type SensorType string
const (
SensorTypeOther SensorType = "other"
SensorTypeSingleChipColorArea SensorType = "single_chip_color_area"
SensorTypeDualChipColorArea SensorType = "dual_chip_color_area"
SensorTypeTripleChipColorArea SensorType = "triple_chip_color_area"
SensorTypeColorSequentialArea SensorType = "color_sequential_area"
SensorTypeTrilinear SensorType = "trilinear"
SensorTypeColorSequentialLinear SensorType = "color_sequential_linear"
valueSensorTypeOther = 1
valueSensorTypeSingleChipColorArea = 2
valueSensorTypeDualChipColorArea = 3
valueSensorTypeTripleChipColorArea = 4
valueSensorTypeColorSequentialArea = 5
valueSensorTypeTrilinear = 7
valueSensorTypeColorSequentialLinear = 8
)
func parseSensorType(value *int64) SensorType {
if value == nil {
return ""
}
switch *value {
case valueSensorTypeOther:
return SensorTypeOther
case valueSensorTypeSingleChipColorArea:
return SensorTypeSingleChipColorArea
case valueSensorTypeDualChipColorArea:
return SensorTypeDualChipColorArea
case valueSensorTypeTripleChipColorArea:
return SensorTypeTripleChipColorArea
case valueSensorTypeColorSequentialArea:
return SensorTypeColorSequentialArea
case valueSensorTypeTrilinear:
return SensorTypeTrilinear
case valueSensorTypeColorSequentialLinear:
return SensorTypeColorSequentialLinear
default:
return ""
}
}
type FileSource string
const (
FileSourceOther FileSource = "other"
FileSourceTransmissiveScanner FileSource = "scanner_transmissive"
FileSourceReflectiveScanner FileSource = "scanner_reflective"
FileSourceDigitalCamera FileSource = "digital_camera"
valueFileSourceOther = 0
valueFileSourceTransmissiveScanner = 1
valueFileSourceReflectiveScanner = 2
valueFileSourceDigitalCamera = 3
)
func parseFileSource(value *int64) FileSource {
if value == nil {
return ""
}
switch *value {
case valueFileSourceOther:
return FileSourceOther
case valueFileSourceTransmissiveScanner:
return FileSourceTransmissiveScanner
case valueFileSourceReflectiveScanner:
return FileSourceReflectiveScanner
case valueFileSourceDigitalCamera:
return FileSourceDigitalCamera
default:
return ""
}
}
type Orientation string
const (
OrientationTopLeft Orientation = "top_left"
OrientationTopRight Orientation = "top_right"
OrientationBottomRight Orientation = "bottom_right"
OrientationBottomLeft Orientation = "bottom_left"
OrientationLeftTop Orientation = "left_top"
OrientationRightTop Orientation = "right_top"
OrientationRightBottom Orientation = "right_bottom"
OrientationLeftBottom Orientation = "left_bottom"
valueOrientationTopLeft = 1
valueOrientationTopRight = 2
valueOrientationBottomRight = 3
valueOrientationBottomLeft = 4
valueOrientationLeftTop = 5
valueOrientationRightTop = 6
valueOrientationRightBottom = 7
valueOrientationLeftBottom = 8
)
func parseOrientation(value *int64) Orientation {
if value == nil {
return ""
}
switch *value {
case valueOrientationTopLeft:
return OrientationTopLeft
case valueOrientationTopRight:
return OrientationTopRight
case valueOrientationBottomRight:
return OrientationBottomRight
case valueOrientationBottomLeft:
return OrientationBottomLeft
case valueOrientationLeftTop:
return OrientationLeftTop
case valueOrientationRightTop:
return OrientationRightTop
case valueOrientationRightBottom:
return OrientationRightBottom
case valueOrientationLeftBottom:
return OrientationLeftBottom
default:
return ""
}
}
type ContrastMode string
const (
ContrastModeNormal ContrastMode = "normal"
ContrastModeSoft ContrastMode = "soft"
ContrastModeHard ContrastMode = "hard"
valueContrastModeNormal = 0
valueContrastModeSoft = 1
valueContrastModeHard = 2
)
func parseContrastMode(value *int64) ContrastMode {
if value == nil {
return ""
}
switch *value {
case valueContrastModeNormal:
return ContrastModeNormal
case valueContrastModeSoft:
return ContrastModeSoft
case valueContrastModeHard:
return ContrastModeHard
default:
return ""
}
}
type DistanceRange string
const (
DistanceRangeMacro DistanceRange = "macro"
DistanceRangeClose DistanceRange = "close"
DistanceRangeDistant DistanceRange = "distant"
valueDistanceRangeMacro = 1
valueDistanceRangeClose = 2
valueDistanceRangeDistant = 3
)
func parseDistanceRange(value *int64) DistanceRange {
if value == nil {
return ""
}
switch *value {
case valueDistanceRangeMacro:
return DistanceRangeMacro
case valueDistanceRangeClose:
return DistanceRangeClose
case valueDistanceRangeDistant:
return DistanceRangeDistant
default:
return ""
}
}
type ExposureMode string
const (
ExposureModeAuto ExposureMode = "auto"
ExposureModeManual ExposureMode = "manual"
ExposureModeBracket ExposureMode = "bracket"
valueExposureModeAuto = 0
valueExposureModeManual = 1
valueExposureModeBracket = 2
)
func parseExposureMode(value *int64) ExposureMode {
if value == nil {
return ""
}
switch *value {
case valueExposureModeAuto:
return ExposureModeAuto
case valueExposureModeManual:
return ExposureModeManual
case valueExposureModeBracket:
return ExposureModeBracket
default:
return ""
}
}
type ExposureProgram string
const (
ExposureProgramManual ExposureProgram = "manual"
ExposureProgramNormal ExposureProgram = "normal"
ExposureProgramAperturePriority ExposureProgram = "aperture_priority"
ExposureProgramShutterPriority ExposureProgram = "shutter_priority"
ExposureProgramCreative ExposureProgram = "creative"
ExposureProgramAction ExposureProgram = "action"
ExposureProgramPortrait ExposureProgram = "portrait"
ExposureProgramLandscape ExposureProgram = "landscape"
valueExposureProgramManual = 1
valueExposureProgramNormal = 2
valueExposureProgramAperturePriority = 3
valueExposureProgramShutterPriority = 4
valueExposureProgramCreative = 5
valueExposureProgramAction = 6
valueExposureProgramPortrait = 7
valueExposureProgramLandscape = 8
)
func parseExposureProgram(value *int64) ExposureProgram {
if value == nil {
return ""
}
switch *value {
case valueExposureProgramManual:
return ExposureProgramManual
case valueExposureProgramNormal:
return ExposureProgramNormal
case valueExposureProgramAperturePriority:
return ExposureProgramAperturePriority
case valueExposureProgramShutterPriority:
return ExposureProgramShutterPriority
case valueExposureProgramCreative:
return ExposureProgramCreative
case valueExposureProgramAction:
return ExposureProgramAction
case valueExposureProgramPortrait:
return ExposureProgramPortrait
case valueExposureProgramLandscape:
return ExposureProgramLandscape
default:
return ""
}
}
type FlashMode string
const (
FlashModeAlwaysOn FlashMode = "always_on"
FlashModeAlwaysOff FlashMode = "always_off"
FlashModeAuto FlashMode = "auto"
valueFlashModeAlwaysOn = 1
valueFlashModeAlwaysOff = 2
valueFlashModeAuto = 3
)
func parseFlashMode(value int64) FlashMode {
switch value {
case valueFlashModeAlwaysOn:
return FlashModeAlwaysOn
case valueFlashModeAlwaysOff:
return FlashModeAlwaysOff
case valueFlashModeAuto:
return FlashModeAuto
default:
return ""
}
}
type LightSource string
const (
LightSourceDaylight LightSource = "daylight"
LightSourceFluorescent LightSource = "fluorescent"
LightSourceIncandescent LightSource = "incandescent"
LightSourceFlash LightSource = "flash"
LightSourceFineWeather LightSource = "weather_fine"
LightSourceCloudyWeather LightSource = "weather_cloudy"
LightSourceShade LightSource = "shade"
LightSource6400K LightSource = "6400K"
LightSource5000K LightSource = "5000K"
LightSource4200K LightSource = "4200K"
LightSource3450K LightSource = "3450K"
LightSourceStandardA LightSource = "standard_a"
LightSourceStandardB LightSource = "standard_b"
LightSourceStandardC LightSource = "standard_c"
LightSourceD55 LightSource = "D55"
LightSourceD65 LightSource = "D65"
LightSourceD75 LightSource = "D75"
LightSourceD50 LightSource = "D50"
LightSourceIsoStudio LightSource = "iso_studio"
valueLightSourceDaylight = 1
valueLightSourceFluorescent = 2
valueLightSourceIncandescent = 3
valueLightSourceFlash = 4
valueLightSourceFineWeather = 9
valueLightSourceCloudyWeather = 10
valueLightSourceShade = 11
valueLightSource6400K = 12
valueLightSource5000K = 13
valueLightSource4200K = 14
valueLightSource3450K = 15
valueLightSourceStandardA = 17
valueLightSourceStandardB = 18
valueLightSourceStandardC = 19
valueLightSourceD55 = 20
valueLightSourceD65 = 21
valueLightSourceD75 = 22
valueLightSourceD50 = 23
valueLightSourceIsoStudio = 24
)
func parseLightSource(value *int64) LightSource {
if value == nil {
return ""
}
switch *value {
case valueLightSourceDaylight:
return LightSourceDaylight
case valueLightSourceFluorescent:
return LightSourceFluorescent
case valueLightSourceIncandescent:
return LightSourceIncandescent
case valueLightSourceFlash:
return LightSourceFlash
case valueLightSourceFineWeather:
return LightSourceFineWeather
case valueLightSourceCloudyWeather:
return LightSourceCloudyWeather
case valueLightSourceShade:
return LightSourceShade
case valueLightSource6400K:
return LightSource6400K
case valueLightSource5000K:
return LightSource5000K
case valueLightSource4200K:
return LightSource4200K
case valueLightSource3450K:
return LightSource3450K
case valueLightSourceStandardA:
return LightSourceStandardA
case valueLightSourceStandardB:
return LightSourceStandardB
case valueLightSourceStandardC:
return LightSourceStandardC
case valueLightSourceD55:
return LightSourceD55
case valueLightSourceD65:
return LightSourceD65
case valueLightSourceD75:
return LightSourceD75
case valueLightSourceD50:
return LightSourceD50
case valueLightSourceIsoStudio:
return LightSourceIsoStudio
default:
return ""
}
}
type MeteringMode string
const (
MeteringModeAverage MeteringMode = "average"
MeteringModeCenterWeightedAverage MeteringMode = "center_weighted_average"
MeteringModeSpot MeteringMode = "spot"
MeteringModeMultiSpot MeteringMode = "multi_sport"
MeteringModePattern MeteringMode = "pattern"
MeteringModePartial MeteringMode = "partial"
valueMeteringModeAverage = 1
valueMeteringModeCenterWeightedAverage = 2
valueMeteringModeSpot = 3
valueMeteringModeMultiSpot = 4
valueMeteringModePattern = 5
valueMeteringModePartial = 6
)
func parseMeteringMode(value *int64) MeteringMode {
if value == nil {
return ""
}
switch *value {
case valueMeteringModeAverage:
return MeteringModeAverage
case valueMeteringModeCenterWeightedAverage:
return MeteringModeCenterWeightedAverage
case valueMeteringModeSpot:
return MeteringModeSpot
case valueMeteringModeMultiSpot:
return MeteringModeMultiSpot
case valueMeteringModePattern:
return MeteringModePattern
case valueMeteringModePartial:
return MeteringModePartial
default:
return ""
}
}
type SceneMode string
const (
SceneModeStandard SceneMode = "standard"
SceneModeLandscape SceneMode = "landscape"
SceneModePortrait SceneMode = "portrait"
SceneModeNightScene SceneMode = "night"
valueSceneModeStandard = 0
valueSceneModeLandscape = 1
valueSceneModePortrait = 2
valueSceneModeNightScene = 3
)
func parseSceneMode(value *int64) SceneMode {
if value == nil {
return ""
}
switch *value {
case valueSceneModeStandard:
return SceneModeStandard
case valueSceneModeLandscape:
return SceneModeLandscape
case valueSceneModePortrait:
return SceneModePortrait
case valueSceneModeNightScene:
return SceneModeNightScene
default:
return ""
}
}
type SharpnessMode string
const (
SharpnessModeNormal SharpnessMode = "normal"
SharpnessModeSoft SharpnessMode = "soft"
SharpnessModeHard SharpnessMode = "hard"
valueSharpnessModeNormal = 0
valueSharpnessModeSoft = 1
valueSharpnessModeHard = 2
)
func parseSharpnessMode(value *int64) SharpnessMode {
if value == nil {
return ""
}
switch *value {
case valueSharpnessModeNormal:
return SharpnessModeNormal
case valueSharpnessModeSoft:
return SharpnessModeSoft
case valueSharpnessModeHard:
return SharpnessModeHard
default:
return ""
}
}
type WhiteBalance string
const (
WhiteBalanceAuto WhiteBalance = "auto"
WhiteBalanceManual WhiteBalance = "manual"
valueWhiteBalanceAuto = 0
valueWhiteBalanceManual = 1
)
func parseWhiteBalance(value *int64) WhiteBalance {
if value == nil {
return ""
}
switch *value {
case valueWhiteBalanceAuto:
return WhiteBalanceAuto
case valueWhiteBalanceManual:
return WhiteBalanceManual
default:
return ""
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment