Skip to content
Snippets Groups Projects
Verified Commit 055d850a 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 #2811 failed
Showing with 1422 additions and 179 deletions
/.idea/
/vendor/
/node_modules/
/cli/
/assets/css
/imghost
......@@ -16,3 +16,12 @@ frontend:
- 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.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
RUN apk --no-cache add \
--virtual .build-deps \
alpine-sdk \
cmake \
sudo \
libssh2 libssh2-dev \
git \
dep \
bash \
curl \
imagemagick \
imagemagick-dev
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 ./backend
FROM alpine:3.15
RUN apk --no-cache add imagemagick
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 /
......
FROM golang:1.17-alpine3.15 AS go_builder
RUN apk --no-cache add \
--virtual .build-deps \
alpine-sdk \
cmake \
sudo \
libssh2 libssh2-dev \
git \
dep \
bash \
curl \
imagemagick \
imagemagick-dev
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 ./frontend
FROM node:alpine as asset_builder
RUN apk --no-cache add \
--virtual .build-deps \
alpine-sdk \
cmake \
libssh2 libssh2-dev \
git \
dep \
bash \
curl \
build-base \
python3
WORKDIR /app
COPY frontend/package* /app/
......@@ -35,10 +23,10 @@ COPY frontend/assets /app/assets
RUN npm run build
FROM alpine:3.15
RUN apk --no-cache add imagemagick
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/app /
COPY --from=go_builder /go/src/app /
COPY frontend/templates /templates
COPY --from=asset_builder /app/assets /assets
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 (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"gopkg.in/gographics/imagick.v3/imagick"
"gopkg.in/gographics/imagick.v2/imagick"
"log"
"net/http"
"os"
......@@ -55,6 +55,7 @@ func main() {
runner := shared.Runner{}
runner.RunParallel(func() {
log.Printf("starting metrics server")
if err := metrics.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("error in metrics server: %s", err.Error())
}
......@@ -62,6 +63,7 @@ func main() {
srv.Shutdown()
})
runner.RunParallel(func() {
log.Printf("starting asynq server")
if err := srv.Run(mux); err != nil {
log.Printf("error in asynq server: %s", err.Error())
}
......
......@@ -7,6 +7,7 @@ import (
"git.kuschku.de/justjanne/imghost/shared"
"github.com/hibiken/asynq"
"github.com/prometheus/client_golang/prometheus"
"log"
"os"
"path/filepath"
"strings"
......@@ -21,12 +22,15 @@ func trackTimeSince(counter prometheus.Counter, start time.Time) time.Time {
func ProcessImageHandler(config *shared.Config) asynq.HandlerFunc {
return func(ctx context.Context, t *asynq.Task) error {
log.Printf("received image resize task")
task := shared.ImageTaskPayload{}
if err := json.Unmarshal(t.Payload(), &task); err != nil {
return err
}
log.Printf("starting image resize task %s", 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))
errorMessages := make([]string, len(errors))
......@@ -35,6 +39,14 @@ func ProcessImageHandler(config *shared.Config) asynq.HandlerFunc {
}
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(
"errors occured while processing task %s (%s): %s",
t.Type(),
......@@ -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
}
}
......@@ -2,9 +2,11 @@ package main
import (
"fmt"
"git.kuschku.de/justjanne/imghost/imgconv"
"git.kuschku.de/justjanne/imghost/shared"
"github.com/justjanne/imgconv"
"gopkg.in/gographics/imagick.v3/imagick"
"gopkg.in/gographics/imagick.v2/imagick"
"log"
"os"
"path/filepath"
"time"
)
......@@ -12,37 +14,52 @@ import (
func ResizeImage(config *shared.Config, imageId string) []error {
var err error
log.Printf("creating magick wand for %s", imageId)
wand := imagick.NewMagickWand()
defer wand.Destroy()
startRead := time.Now().UTC()
log.Printf("reading image for %s", imageId)
if err = wand.ReadImage(filepath.Join(config.SourceFolder, imageId)); err != nil {
return []error{err}
}
log.Printf("importing image for %s", imageId)
var originalImage imgconv.ImageHandle
if originalImage, err = imgconv.NewImage(wand); err != nil {
return []error{err}
}
trackTimeSince(imageProcessDurationRead, startRead)
log.Printf("launching resize goroutines for %s", imageId)
return runMany(len(config.Sizes), func(index int) error {
definition := config.Sizes[index]
path := filepath.Join(config.TargetFolder, fmt.Sprintf("%s%s", imageId, definition.Suffix))
startClone := time.Now().UTC()
log.Printf("cloning image for %s in %v", imageId, definition)
image := originalImage.CloneImage()
startCrop := trackTimeSince(imageProcessDurationClone, startClone)
log.Printf("cropping image for %s in %v", imageId, definition)
if err := image.Crop(definition.Size); err != nil {
return err
}
startResize := trackTimeSince(imageProcessDurationCrop, startCrop)
log.Printf("resizing image for %s in %v", imageId, definition)
if err := image.Resize(definition.Size); err != nil {
return err
}
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
}
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)
log.Printf("done with image for %s in %v", imageId, definition)
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() {
context.Background(),
&config,
asynq.NewClient(config.AsynqOpts()),
asynq.NewInspector(config.AsynqOpts()),
config.UploadTimeoutDuration(),
db,
http.FileServer(http.Dir(config.TargetFolder)),
......
......@@ -13,6 +13,7 @@ type ImageDetailData struct {
User UserInfo
Image shared.Image
IsMine bool
BaseUrl string
}
func pageImageDetail(ctx PageContext) http.Handler {
......@@ -86,6 +87,7 @@ func pageImageDetail(ctx PageContext) http.Handler {
user,
info,
owner == user.Id,
ctx.Config.BaseUrl,
}); err != nil {
formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
return
......
......@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"git.kuschku.de/justjanne/imghost/shared"
"github.com/hibiken/asynq"
"io"
"mime/multipart"
"net/http"
......@@ -14,6 +15,11 @@ import (
"time"
)
type UploadData struct {
BaseUrl string
User UserInfo
}
func detectMimeType(path string) (string, error) {
file, err := os.Open(path)
if err != nil {
......@@ -100,19 +106,21 @@ func pageUpload(ctx PageContext) http.Handler {
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)
fmt.Printf("Submitted task %s at %d\n", image.Id, time.Now().Unix())
if err != nil {
formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
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 {
formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
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")
return
}
......@@ -128,7 +136,8 @@ func pageUpload(ctx PageContext) http.Handler {
return
} else {
user := parseUser(r)
if err := formatTemplate(w, "upload.html", IndexData{
if err := formatTemplate(w, "upload.html", UploadData{
ctx.Config.BaseUrl,
user,
}); err != nil {
formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
......
......@@ -38,14 +38,14 @@
<div class="url">
<p>Detail Page</p>
<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>
</div>
</div>
<div class="url">
<p>Direct Link</p>
<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>
</div>
</div>
......
......@@ -5,7 +5,7 @@
{{range .Images}}
<a class="image" href="/i/{{.Id}}">
<div class="image-container">
<img src="https://i.k8r.eu/{{.Id}}t.png">
<img src="/{{.Id}}t.png">
</div>
<div class="info">
<p class="title">
......
{{- /*gotype: git.kuschku.de/justjanne/imghost.UploadData*/ -}}
{{define "title"}}Upload | ik8r{{end}}
{{define "content"}}
<div class="page upload">
......@@ -15,7 +16,7 @@
<div class="url">
<p>Album</p>
<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>
</div>
</div>
......
......@@ -32,7 +32,8 @@ func (info UserInfo) HasRole(role string) bool {
type PageContext struct {
Context context.Context
Config *shared.Config
Async *asynq.Client
AsynqClient *asynq.Client
AsynqInspector *asynq.Inspector
UploadTimeout time.Duration
Database *sql.DB
Images http.Handler
......@@ -106,27 +107,41 @@ func formatTemplate(w http.ResponseWriter, templateName string, data interface{}
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)
for total < timeout && info.State != asynq.TaskStateCompleted {
for info.State == asynq.TaskStateScheduled {
info, err := ctx.AsynqInspector.GetTaskInfo(info.Queue, info.ID)
if err != nil {
return nil, err
}
// Wait for it being scheduled
for total < timeout && info.State == asynq.TaskStateScheduled {
duration := info.NextProcessAt.Sub(time.Now())
total += duration
if total < timeout {
time.Sleep(duration)
}
info, err = ctx.AsynqInspector.GetTaskInfo(info.Queue, info.ID)
if err != nil {
return nil, err
}
}
if info.State != asynq.TaskStateCompleted {
duration := time.Duration(1 * time.Second)
// Wait for it being completed
for total < timeout && info.State != asynq.TaskStateArchived && info.State != asynq.TaskStateCompleted {
duration := 1 * time.Second
total += duration
if total < timeout {
time.Sleep(duration)
}
info, err = ctx.AsynqInspector.GetTaskInfo(info.Queue, info.ID)
if err != nil {
return nil, err
}
}
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 {
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
go 1.15
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/justjanne/imgconv v1.4.1
github.com/lib/pq v1.10.4
github.com/prometheus/client_golang v1.11.1
github.com/spf13/cast v1.5.0 // indirect
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6
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
gopkg.in/gographics/imagick.v2 v2.4.0
gopkg.in/yaml.v2 v2.3.0
)
......@@ -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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
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.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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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
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/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.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
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
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.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.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-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/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=
......@@ -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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
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.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.3.0/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/
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.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.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/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.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/go.mod h1:K70jPVx+CAmmQrXot7Dru0D52EO7ob4BIun3ri5z1Qw=
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/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.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
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/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.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/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.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/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.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/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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
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-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.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.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.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.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.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.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
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/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.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
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.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.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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.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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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
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-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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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
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-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-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-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-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-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-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.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.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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
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.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/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/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/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/gographics/imagick.v3 v3.4.0 h1:kSnbsXOWofo81VJEn/Hw8w3qqoOrfTyWwjAQzSdtPlg=
gopkg.in/gographics/imagick.v3 v3.4.0/go.mod h1:+Q9nyA2xRZXrDyTtJ/eko+8V/5E7bWYs08ndkZp8UmA=
gopkg.in/gographics/imagick.v2 v2.4.0 h1:E1KeUJAk9TUdhClfkvWPjJbDfXe1R946J6vgBtPVP4Q=
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/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
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.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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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
}
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment