From 1f33bec28ce9f55708736253258e1dd2240b5860 Mon Sep 17 00:00:00 2001 From: Milan Nikolic Date: Sat, 2 Nov 2024 23:01:32 +0100 Subject: [PATCH] Use bild library --- cbconvert.go | 55 +++++------------- cbconvert_func.go | 39 ------------- cbconvert_image.go | 135 +++++++++++++++++++++++++++++++++++++++++++++ go.mod | 16 +++--- go.sum | 26 +++++---- 5 files changed, 172 insertions(+), 99 deletions(-) create mode 100644 cbconvert_image.go diff --git a/cbconvert.go b/cbconvert.go index 240731d..cd47fb2 100644 --- a/cbconvert.go +++ b/cbconvert.go @@ -24,7 +24,6 @@ import ( "github.com/gen2brain/webp" "golang.org/x/image/tiff" - "github.com/disintegration/imaging" pngstructure "github.com/dsoprea/go-png-image-structure" "github.com/dustin/go-humanize" "github.com/fvbommel/sortorder" @@ -33,34 +32,6 @@ import ( "golang.org/x/sync/errgroup" ) -// Resample filters. -const ( - // NearestNeighbor is the fastest resampling filter, no antialiasing. - NearestNeighbor int = iota - // Box filter (averaging pixels). - Box - // Linear is the bilinear filter, smooth and reasonably fast. - Linear - // MitchellNetravali is a smooth bicubic filter. - MitchellNetravali - // CatmullRom is a sharp bicubic filter. - CatmullRom - // Gaussian is a blurring filter that uses gaussian function, useful for noise removal. - Gaussian - // Lanczos is a high-quality resampling filter, it's slower than cubic filters. - Lanczos -) - -var filters = map[int]imaging.ResampleFilter{ - NearestNeighbor: imaging.NearestNeighbor, - Box: imaging.Box, - Linear: imaging.Linear, - MitchellNetravali: imaging.MitchellNetravali, - CatmullRom: imaging.CatmullRom, - Gaussian: imaging.Gaussian, - Lanczos: imaging.Lanczos, -} - // Options type. type Options struct { // Image format, valid values are jpeg, png, tiff, bmp, webp, avif, jxl @@ -480,29 +451,29 @@ func (c *Converter) imageTransform(img image.Image) image.Image { if c.Opts.Width > 0 || c.Opts.Height > 0 { if c.Opts.Fit { - i = imaging.Fit(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) + i = fit(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) } else { - i = imaging.Resize(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) + i = resize(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) } } if c.Opts.Rotate > 0 { switch c.Opts.Rotate { case 90: - i = imaging.Rotate90(i) + i = rotate(i, 90) case 180: - i = imaging.Rotate180(i) + i = rotate(i, 180) case 270: - i = imaging.Rotate270(i) + i = rotate(i, 270) } } if c.Opts.Brightness != 0 { - i = imaging.AdjustBrightness(i, float64(c.Opts.Brightness)) + i = brightness(i, float64(c.Opts.Brightness)) } if c.Opts.Contrast != 0 { - i = imaging.AdjustContrast(i, float64(c.Opts.Contrast)) + i = contrast(i, float64(c.Opts.Contrast)) } if c.Opts.Grayscale { @@ -811,9 +782,9 @@ func (c *Converter) Cover(fileName string, fileInfo os.FileInfo) error { if c.Opts.Width > 0 || c.Opts.Height > 0 { if c.Opts.Fit { - cover = imaging.Fit(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) + cover = fit(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) } else { - cover = imaging.Resize(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) + cover = resize(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) } } @@ -853,12 +824,12 @@ func (c *Converter) Thumbnail(fileName string, fileInfo os.FileInfo) error { if c.Opts.Width > 0 || c.Opts.Height > 0 { if c.Opts.Fit { - cover = imaging.Fit(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) + cover = fit(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) } else { - cover = imaging.Resize(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) + cover = resize(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter]) } } else { - cover = imaging.Resize(cover, 256, 0, filters[c.Opts.Filter]) + cover = resize(cover, 256, 0, filters[c.Opts.Filter]) } var buf bytes.Buffer @@ -1019,7 +990,7 @@ func (c *Converter) Preview(fileName string, fileInfo os.FileInfo, width, height } if width != 0 && height != 0 { - dec = imaging.Fit(dec, width, height, filters[c.Opts.Filter]) + dec = fit(dec, width, height, filters[c.Opts.Filter]) } img.Image = dec diff --git a/cbconvert_func.go b/cbconvert_func.go index f8676c3..22fcc15 100644 --- a/cbconvert_func.go +++ b/cbconvert_func.go @@ -2,41 +2,12 @@ package cbconvert import ( "fmt" - "image" - "image/color" - "image/draw" "io" "os" "path/filepath" "strings" ) -// imageToRGBA converts an image.Image to *image.RGBA. -func imageToRGBA(src image.Image) *image.RGBA { - if dst, ok := src.(*image.RGBA); ok { - return dst - } - - b := src.Bounds() - dst := image.NewRGBA(b) - draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src) - - return dst -} - -// imageToGray converts an image.Image to *image.Gray. -func imageToGray(src image.Image) *image.Gray { - if dst, ok := src.(*image.Gray); ok { - return dst - } - - b := src.Bounds() - dst := image.NewGray(b) - draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src) - - return dst -} - // imagesFromPath returns list of found image files for given directory. func imagesFromPath(path string) ([]string, error) { var images []string @@ -147,16 +118,6 @@ func isSize(a, b int64) bool { return true } -// isGrayScale checks if image is grayscale. -func isGrayScale(img image.Image) bool { - model := img.ColorModel() - if model == color.GrayModel || model == color.Gray16Model { - return true - } - - return false -} - // baseNoExt returns base name without extension. func baseNoExt(filename string) string { return strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename)) diff --git a/cbconvert_image.go b/cbconvert_image.go new file mode 100644 index 0000000..fb64713 --- /dev/null +++ b/cbconvert_image.go @@ -0,0 +1,135 @@ +package cbconvert + +import ( + "image" + "image/color" + "image/draw" + "math" + + "github.com/anthonynsimon/bild/adjust" + "github.com/anthonynsimon/bild/transform" +) + +// Resample filters. +const ( + // NearestNeighbor is the fastest resampling filter, no antialiasing. + NearestNeighbor int = iota + // Box filter (averaging pixels). + Box + // Linear is the bilinear filter, smooth and reasonably fast. + Linear + // MitchellNetravali is a smooth bicubic filter. + MitchellNetravali + // CatmullRom is a sharp bicubic filter. + CatmullRom + // Gaussian is a blurring filter that uses gaussian function, useful for noise removal. + Gaussian + // Lanczos is a high-quality resampling filter, it's slower than cubic filters. + Lanczos +) + +var filters = map[int]transform.ResampleFilter{ + NearestNeighbor: transform.NearestNeighbor, + Box: transform.Box, + Linear: transform.Linear, + MitchellNetravali: transform.MitchellNetravali, + CatmullRom: transform.CatmullRom, + Gaussian: transform.Gaussian, + Lanczos: transform.Lanczos, +} + +func resize(img image.Image, width, height int, filter transform.ResampleFilter) *image.RGBA { + dstW, dstH := width, height + + srcW := img.Bounds().Dx() + srcH := img.Bounds().Dy() + + if dstW == 0 { + tmpW := float64(dstH) * float64(srcW) / float64(srcH) + dstW = int(math.Max(1.0, math.Floor(tmpW+0.5))) + } + if dstH == 0 { + tmpH := float64(dstW) * float64(srcH) / float64(srcW) + dstH = int(math.Max(1.0, math.Floor(tmpH+0.5))) + } + + if srcW == dstW && srcH == dstH { + return imageToRGBA(img) + } + + return transform.Resize(img, dstW, dstH, filter) +} + +func fit(img image.Image, width, height int, filter transform.ResampleFilter) *image.RGBA { + maxW, maxH := width, height + + b := img.Bounds() + srcW := b.Dx() + srcH := b.Dy() + + if srcW <= maxW && srcH <= maxH { + return imageToRGBA(img) + } + + srcAspectRatio := float64(srcW) / float64(srcH) + maxAspectRatio := float64(maxW) / float64(maxH) + + var dstW, dstH int + if srcAspectRatio > maxAspectRatio { + dstW = maxW + dstH = int(float64(dstW) / srcAspectRatio) + } else { + dstH = maxH + dstW = int(float64(dstH) * srcAspectRatio) + } + + return resize(img, dstW, dstH, filter) +} + +func rotate(img image.Image, angle float64) *image.RGBA { + return transform.Rotate(img, angle, &transform.RotationOptions{ResizeBounds: true, Pivot: &image.Point{}}) +} + +func brightness(img image.Image, change float64) *image.RGBA { + return adjust.Brightness(img, change/100) +} + +func contrast(img image.Image, change float64) *image.RGBA { + return adjust.Contrast(img, change/100) +} + +// imageToRGBA converts an image.Image to *image.RGBA. +func imageToRGBA(src image.Image) *image.RGBA { + if dst, ok := src.(*image.RGBA); ok { + return dst + } + + b := src.Bounds() + dst := image.NewRGBA(b) + draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src) + + return dst +} + +// imageToGray converts an image.Image to *image.Gray. +func imageToGray(src image.Image) *image.Gray { + if dst, ok := src.(*image.Gray); ok { + return dst + } + + b := src.Bounds() + dst := image.NewGray(b) + draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src) + + return dst +} + +// isGrayScale checks if image is grayscale. +func isGrayScale(img image.Image) bool { + model := img.ColorModel() + if model == color.GrayModel || model == color.Gray16Model { + return true + } + + return false +} diff --git a/go.mod b/go.mod index acd01c6..a616f4f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gen2brain/cbconvert go 1.23 require ( - github.com/disintegration/imaging v1.6.2 + github.com/anthonynsimon/bild v0.14.0 github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d github.com/dustin/go-humanize v1.0.1 github.com/fvbommel/sortorder v1.1.0 @@ -17,14 +17,14 @@ require ( ) require ( - github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4 // indirect - github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d // indirect - github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf // indirect + github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d // indirect + github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect + github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 // indirect github.com/ebitengine/purego v0.8.1 // indirect - github.com/go-errors/errors v1.1.1 // indirect - github.com/golang/geo v0.0.0-20200319012246-673a6f80352d // indirect + github.com/go-errors/errors v1.5.1 // indirect + github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect github.com/jupiterrider/ffi v0.2.1 // indirect github.com/tetratelabs/wazero v1.8.1 // indirect - golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + golang.org/x/net v0.30.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 6142e78..8e88bd2 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,20 @@ -github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= -github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/anthonynsimon/bild v0.14.0 h1:IFRkmKdNdqmexXHfEU7rPlAmdUZ8BDZEGtGHDnGWync= +github.com/anthonynsimon/bild v0.14.0/go.mod h1:hcvEAyBjTW69qkKJTfpcDQ83sSZHxwOunsseDfeQhUs= github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= -github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4 h1:Mg7pY7kxDQD2Bkvr1N+XW4BESSIQ7tTTR7Vv+Gi2CsM= github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4/go.mod h1:9EXlPeHfblFFnwu5UOqmP2eoZfJyAZ2Ri/Vki33ajO0= +github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d h1:yeH8wrJa3+8uKKDAdURHUK1ds2UvKhMqX2MiOdVeKPs= +github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d/go.mod h1:oKrjk2kb3rAR5NbtSTLUMvMSbc+k8ZosI3MaVH47noc= github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8= github.com/dsoprea/go-exif/v3 v3.0.0-20210512043655-120bcdb2a55e/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= -github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d h1:F/7L5wr/fP/SKeO5HuMlNEX9Ipyx2MbH2rV9G4zJRpk= github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= +github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg= +github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d h1:8+qI8ant/vZkNSsbwSjIR6XJfWcDVTg/qx/3pRUUZNA= github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d/go.mod h1:yTR3tKgyk20phAFg6IE9ulMA5NjEDD2wyx+okRFLVtw= -github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU= github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= +github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 h1:/py11NlxDaOxkT9OKN+gXgT+QOH5xj1ZRoyusfRIlo4= +github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349/go.mod h1:KVK+/Hul09ujXAGq+42UBgCTnXkiJZRnLYdURGjQUwo= github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -31,26 +34,28 @@ github.com/gen2brain/webp v0.5.0 h1:nn3o0BtKltoFKX9rlDZG/Y/aWqNzUZVyXdB815yVNfU= github.com/gen2brain/webp v0.5.0/go.mod h1:Nb3xO5sy6MeUAHhru9H3GT7nlOQO5dKRNNlE92CZrJw= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= -github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg= github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc= github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I= +github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jupiterrider/ffi v0.2.1 h1:08GJVDqz4eoQq7cKT1T0kwb9MB58XEAGjgxDvz80yBs= github.com/jupiterrider/ffi v0.2.1/go.mod h1:tJ7Q8p/3blFjdWt5qJU4W5oDE0xloImvrViE+0td0Rk= github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550= github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -59,5 +64,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.7/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=