diff --git a/Makefile b/Makefile
index 12b9f0834fd93f385735da38d444435d4a92d5c9..c52dd2ad82b0b44b65e2c37ae1b09bdcfc539197 100644
--- a/Makefile
+++ b/Makefile
@@ -10,12 +10,11 @@ PROTOFILES=	$(filter-out $(PROTODIR)/status/status.proto, $(ALLPROTO))
 PROTOGOFILES=$(PROTOFILES:.proto=.pb.go)
 PROTOGOGRPCFILES=$(PROTOFILES:.proto=_grpc.pb.go)
 
-PKGDIR=pkg
-ACCESSLOGGING=$(shell find $(PKGDIR) -name "logging_middleware.go" -type f)
-ERRORLOGGING=$(shell find $(PKGDIR) -name "error_logging_middleware.go" -type f)
-SERVICETELEMETRY=$(shell find $(PKGDIR) -name "telemetry_middleware.go" -type f)
-SERVICEMIDDLEWARE=$(shell find $(PKGDIR) -name "middleware.go" -type f)
-SERVICERECOVERING=$(shell find $(PKGDIR) -name "recovering_middleware.go" -type f)
+ACCESSLOGGING=$(shell find -name "logging_middleware.go" -type f)
+ERRORLOGGING=$(shell find -name "error_logging_middleware.go" -type f)
+SERVICETELEMETRY=$(shell find -name "telemetry_middleware.go" -type f)
+SERVICEMIDDLEWARE=$(shell find -name "middleware.go" -type f)
+SERVICERECOVERING=$(shell find -name "recovering_middleware.go" -type f)
 
 # Генерация grpc-клиентов для go
 proto: protoc-check protoc-gen-go-check $(PROTOGOFILES)
diff --git a/go.mod b/go.mod
index 97b1f97a8ab58876558eae52daf0d0f0b55a842a..86d2b2e66603975e24e4f78b4650a3100b17ece5 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.21
 require (
 	github.com/antonmedv/expr v1.9.0
 	github.com/avast/retry-go/v4 v4.5.1
+	github.com/bep/gowebp v0.2.0
 	github.com/go-kit/kit v0.13.0
 	github.com/gosimple/slug v1.13.1
 	github.com/hashicorp/go-multierror v1.1.1
@@ -20,6 +21,7 @@ require (
 	go.opentelemetry.io/otel/trace v1.20.0
 	go.uber.org/zap v1.26.0
 	golang.org/x/crypto v0.15.0
+	golang.org/x/image v0.14.0
 	golang.org/x/net v0.18.0
 	golang.org/x/oauth2 v0.14.0
 	google.golang.org/grpc v1.59.0
diff --git a/go.sum b/go.sum
index ce3bedfbd85e1756001cb5a5a06a2635226d9e8d..d11a9be6ed599fe2754ece384aef33f07f3ec19d 100644
--- a/go.sum
+++ b/go.sum
@@ -5,10 +5,10 @@ cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2Aawl
 github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 github.com/antonmedv/expr v1.9.0 h1:j4HI3NHEdgDnN9p6oI6Ndr0G5QryMY0FNxT4ONrFDGU=
 github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8=
-github.com/avast/retry-go/v4 v4.5.0 h1:QoRAZZ90cj5oni2Lsgl2GW8mNTnUCnmpx/iKpwVisHg=
-github.com/avast/retry-go/v4 v4.5.0/go.mod h1:7hLEXp0oku2Nir2xBAsg0PTphp9z71bN5Aq1fboC3+I=
 github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o=
 github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc=
+github.com/bep/gowebp v0.2.0 h1:ZVfK8i9PpZqKHEmthQSt3qCnnHycbLzBPEsVtk2ch2Q=
+github.com/bep/gowebp v0.2.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
 github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/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=
@@ -52,8 +52,6 @@ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
-github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
-github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA=
 github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
 github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
@@ -111,16 +109,10 @@ github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 go.mongodb.org/mongo-driver v1.13.0 h1:67DgFFjYOCMWdtTEmKFpV3ffWlFnh+CYZ8ZS/tXWUfY=
 go.mongodb.org/mongo-driver v1.13.0/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
-go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
-go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
 go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc=
 go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs=
-go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
-go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
 go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA=
 go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM=
-go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
-go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
 go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ=
 go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU=
 go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
@@ -133,22 +125,19 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
 golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
+golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
+golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
 golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
-golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
-golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
 golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
 golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git a/images/convert/convert.go b/images/convert/convert.go
new file mode 100644
index 0000000000000000000000000000000000000000..b704055dd7399eb4629d1804cd51574befff49c6
--- /dev/null
+++ b/images/convert/convert.go
@@ -0,0 +1,73 @@
+package convert
+
+import (
+	"image"
+	"io"
+	"os"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+)
+
+type (
+	EncodeFunc func(w io.Writer, img image.Image) error
+	Format     string
+)
+
+func (f Format) String() string {
+	return string(f)
+}
+
+var (
+	defaultFormatEncoderRegistry = make(map[Format]EncodeFunc)
+	formatExtensions             = make(map[string]Format)
+)
+
+func RegisterFormatEncoder(format Format, fn EncodeFunc, extensions ...string) {
+	defaultFormatEncoderRegistry[format] = fn
+	formatExtensions[format.String()] = format
+	for _, ext := range extensions {
+		formatExtensions[strings.TrimPrefix(strings.ToLower(ext), ".")] = format
+	}
+}
+
+func Encode(w io.Writer, format Format, img image.Image) error {
+	encoder, ok := defaultFormatEncoderRegistry[format]
+	if !ok {
+		return errors.Errorf("unknown format: %s", format)
+	}
+	err := encoder(w, img)
+	if err != nil {
+		return errors.Wrap(err, "encode image")
+	}
+	return nil
+}
+
+func Decode(r io.Reader) (image.Image, string, error) {
+	img, ext, err := image.Decode(r)
+	if err != nil {
+		return nil, "", errors.Wrap(err, "decode image")
+	}
+	return img, ext, nil
+}
+
+func Open(filename string) (image.Image, string, error) {
+	file, err := os.Open(filename)
+	if err != nil {
+		return nil, "", errors.Wrap(err, "open file")
+	}
+	defer file.Close()
+	img, ext, err := Decode(file)
+	if err != nil {
+		return nil, "", errors.Wrap(err, "decode file")
+	}
+	return img, ext, nil
+}
+
+func FormatFromExtension(ext string) (Format, error) {
+	ext = strings.TrimPrefix(strings.ToLower(ext), ".")
+	if f, ok := formatExtensions[ext]; ok {
+		return f, nil
+	}
+	return "", errors.Errorf("unsupported format")
+}
diff --git a/images/convert/convert_test.go b/images/convert/convert_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b7a759eac8dba586d985806bb587975e07a93403
--- /dev/null
+++ b/images/convert/convert_test.go
@@ -0,0 +1,170 @@
+package convert
+
+import (
+	"bytes"
+	"image"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestOpen(t *testing.T) {
+	_, ext, err := Open("testdata/1.jpeg")
+	if err != nil {
+		return
+	}
+	require.NoError(t, err)
+	require.Equal(t, "jpeg", ext)
+}
+
+func TestFormatFromExtension(t *testing.T) {
+	var tests = []struct {
+		name    string
+		input   string
+		output  Format
+		wantErr bool
+	}{
+		{
+			name:    "correct jpeg",
+			input:   "jpg",
+			output:  JPEG,
+			wantErr: false,
+		},
+		{
+			name:    "correct png",
+			input:   "png",
+			output:  PNG,
+			wantErr: false,
+		},
+		{
+			name:    "incorrect any",
+			input:   "any",
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			format, err := FormatFromExtension(tt.input)
+			if tt.wantErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+				require.Equal(t, tt.output, format)
+			}
+		})
+	}
+}
+
+func TestEncode(t *testing.T) {
+	var tests = []struct {
+		name    string
+		input   Format
+		wantErr bool
+	}{
+		{
+			name:    "unknown format",
+			input:   "go",
+			wantErr: true,
+		},
+		{
+			name:    "jpeg format",
+			input:   JPEG,
+			wantErr: false,
+		},
+		{
+			name:    "png format",
+			input:   PNG,
+			wantErr: false,
+		},
+		{
+			name:    "gif format",
+			input:   GIF,
+			wantErr: false,
+		},
+		{
+			name:    "tiff format",
+			input:   TIFF,
+			wantErr: false,
+		},
+		{
+			name:    "bmp format",
+			input:   BMP,
+			wantErr: false,
+		},
+	}
+	img := image.NewRGBA(image.Rect(0, 0, 10, 10))
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			buf := new(bytes.Buffer)
+			err := Encode(buf, tt.input, img)
+			if tt.wantErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestDecode(t *testing.T) {
+	var tests = []struct {
+		name    string
+		input   string
+		output  string
+		wantErr bool
+	}{
+		{
+			name:    "unknown format",
+			input:   "testdata/1.go",
+			wantErr: true,
+		},
+		{
+			name:    "jpeg format",
+			input:   "testdata/1.jpeg",
+			output:  "jpeg",
+			wantErr: false,
+		},
+		{
+			name:    "png format",
+			input:   "testdata/1.png",
+			output:  "png",
+			wantErr: false,
+		},
+		{
+			name:    "gif format",
+			input:   "testdata/1.gif",
+			output:  "gif",
+			wantErr: false,
+		},
+		{
+			name:    "tiff format",
+			input:   "testdata/1.tiff",
+			output:  "tiff",
+			wantErr: false,
+		},
+		{
+			name:    "bmp format",
+			input:   "testdata/1.bmp",
+			output:  "bmp",
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			file, err := os.Open(tt.input)
+			if err != nil {
+				t.Fatal(err)
+			}
+			defer file.Close()
+			_, ext, err := Decode(file)
+			if tt.wantErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+				require.Equal(t, tt.output, ext)
+			}
+		})
+	}
+}
diff --git a/images/convert/default_format.go b/images/convert/default_format.go
new file mode 100644
index 0000000000000000000000000000000000000000..7e680d33432480ca06037f0038f83c28a9dea1c0
--- /dev/null
+++ b/images/convert/default_format.go
@@ -0,0 +1,28 @@
+package convert
+
+import (
+	"image"
+	"image/gif"
+	"image/jpeg"
+	"image/png"
+	"io"
+
+	"golang.org/x/image/bmp"
+	"golang.org/x/image/tiff"
+)
+
+const (
+	JPEG Format = "jpeg"
+	PNG  Format = "png"
+	GIF  Format = "gif"
+	TIFF Format = "tiff"
+	BMP  Format = "bmp"
+)
+
+func init() {
+	RegisterFormatEncoder(JPEG, func(w io.Writer, img image.Image) error { return jpeg.Encode(w, img, nil) }, "jpg")
+	RegisterFormatEncoder(PNG, png.Encode)
+	RegisterFormatEncoder(GIF, func(w io.Writer, img image.Image) error { return gif.Encode(w, img, nil) })
+	RegisterFormatEncoder(TIFF, func(w io.Writer, img image.Image) error { return tiff.Encode(w, img, nil) }, "tif")
+	RegisterFormatEncoder(BMP, bmp.Encode)
+}
diff --git a/images/convert/testdata/1.bmp b/images/convert/testdata/1.bmp
new file mode 100644
index 0000000000000000000000000000000000000000..1a4a8d81288b72efbe3ccfc90887b86f897b137e
Binary files /dev/null and b/images/convert/testdata/1.bmp differ
diff --git a/images/convert/testdata/1.gif b/images/convert/testdata/1.gif
new file mode 100644
index 0000000000000000000000000000000000000000..73889f7e555aaf01ceb121d2b700a1b6ffd6428c
Binary files /dev/null and b/images/convert/testdata/1.gif differ
diff --git a/images/convert/testdata/1.go b/images/convert/testdata/1.go
new file mode 100644
index 0000000000000000000000000000000000000000..c8eb7f369645cb9cf399939b2297f06f937daace
--- /dev/null
+++ b/images/convert/testdata/1.go
@@ -0,0 +1,3 @@
+package testdata
+
+// hi
diff --git a/images/convert/testdata/1.jpeg b/images/convert/testdata/1.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..8348e38780de3332ba2e1d39fdb81fd8bba92389
Binary files /dev/null and b/images/convert/testdata/1.jpeg differ
diff --git a/images/convert/testdata/1.png b/images/convert/testdata/1.png
new file mode 100644
index 0000000000000000000000000000000000000000..d047fa256d11a29ce71b27ae123a22a1870f084d
Binary files /dev/null and b/images/convert/testdata/1.png differ
diff --git a/images/convert/testdata/1.tiff b/images/convert/testdata/1.tiff
new file mode 100644
index 0000000000000000000000000000000000000000..ca37358818808f0da0a11a3a9396e16035a04652
Binary files /dev/null and b/images/convert/testdata/1.tiff differ
diff --git a/images/convert/testdata/1.webp b/images/convert/testdata/1.webp
new file mode 100644
index 0000000000000000000000000000000000000000..122741b605f3121d393829ffb5b7a0924db13c86
Binary files /dev/null and b/images/convert/testdata/1.webp differ
diff --git a/images/convert/webp.go b/images/convert/webp.go
new file mode 100644
index 0000000000000000000000000000000000000000..d9f5a892247316fc46581d93a727e5a2646c604e
--- /dev/null
+++ b/images/convert/webp.go
@@ -0,0 +1,23 @@
+//go:build webp
+
+package convert
+
+import (
+	"image"
+	"io"
+
+	"github.com/bep/gowebp/libwebp"
+	"github.com/bep/gowebp/libwebp/webpoptions"
+
+	// Нужно включать в сборку для вызова регистрации декодера в стандартном
+	// пакете "images" для декодирования файлов webp.
+	_ "golang.org/x/image/webp"
+)
+
+const (
+	WEBP Format = "webp"
+)
+
+func init() {
+	RegisterFormatEncoder(WEBP, func(w io.Writer, img image.Image) error { return libwebp.Encode(w, img, webpoptions.EncodingOptions{}) })
+}
diff --git a/images/convert/webp_test.go b/images/convert/webp_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..59518ca7384f90332e2e5ed3f95edf295247c904
--- /dev/null
+++ b/images/convert/webp_test.go
@@ -0,0 +1,30 @@
+//go:build webp
+
+package convert
+
+import (
+	"bytes"
+	"image"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestEncodeWebP(t *testing.T) {
+	img := image.NewRGBA(image.Rect(0, 0, 10, 10))
+	buf := new(bytes.Buffer)
+	err := Encode(buf, WEBP, img)
+	require.NoError(t, err)
+}
+
+func TestDecodeWebP(t *testing.T) {
+	file, err := os.Open("testdata/1.webp")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer file.Close()
+	_, ext, err := Decode(file)
+	require.NoError(t, err)
+	require.Equal(t, "webp", ext)
+}
diff --git a/pkg/images/middleware/error_logging_middleware.go b/images/middleware/error_logging_middleware.go
similarity index 77%
rename from pkg/images/middleware/error_logging_middleware.go
rename to images/middleware/error_logging_middleware.go
index 749db73b2ed5682c9725c920ae4b850eff5e67e1..d0e36f879dfcafb2c037722ab171eb79240ef95c 100644
--- a/pkg/images/middleware/error_logging_middleware.go
+++ b/images/middleware/error_logging_middleware.go
@@ -1,16 +1,16 @@
 // Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/error_log
+// template: ../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
 package middleware
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/images -i Images -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/images -i Images -t ../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
 	"context"
 
+	"git.perx.ru/perxis/perxis-go/images"
 	"git.perx.ru/perxis/perxis-go/pkg/files"
-	"git.perx.ru/perxis/perxis-go/pkg/images"
 	"go.uber.org/zap"
 )
 
diff --git a/pkg/images/middleware/logging_middleware.go b/images/middleware/logging_middleware.go
similarity index 85%
rename from pkg/images/middleware/logging_middleware.go
rename to images/middleware/logging_middleware.go
index fb7d0af4faecfde621e295b0da8e6f2e098e56a1..a69a56ee6a236aab96b1d22340691a63e36fdc01 100644
--- a/pkg/images/middleware/logging_middleware.go
+++ b/images/middleware/logging_middleware.go
@@ -1,19 +1,19 @@
 // Code generated by gowrap. DO NOT EDIT.
-// template: ../../../assets/templates/middleware/access_log
+// template: ../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
 package middleware
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/images -i Images -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/images -i Images -t ../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
 	"context"
 	"fmt"
 	"time"
 
+	"git.perx.ru/perxis/perxis-go/images"
 	"git.perx.ru/perxis/perxis-go/pkg/auth"
 	"git.perx.ru/perxis/perxis-go/pkg/files"
-	"git.perx.ru/perxis/perxis-go/pkg/images"
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
 )
diff --git a/pkg/images/middleware/middleware.go b/images/middleware/middleware.go
similarity index 74%
rename from pkg/images/middleware/middleware.go
rename to images/middleware/middleware.go
index d4717a0db627b3f11abfc569cd1c79e30300b93f..e29bdeb8c6e7a5e2d6b298d01069f4566481e80a 100644
--- a/pkg/images/middleware/middleware.go
+++ b/images/middleware/middleware.go
@@ -4,10 +4,10 @@
 
 package middleware
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/images -i Images -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/images -i Images -t ../../assets/templates/middleware/middleware -o middleware.go -l ""
 
 import (
-	"git.perx.ru/perxis/perxis-go/pkg/images"
+	"git.perx.ru/perxis/perxis-go/images"
 	"go.uber.org/zap"
 )
 
diff --git a/pkg/images/middleware/recovering_middleware.go b/images/middleware/recovering_middleware.go
similarity index 83%
rename from pkg/images/middleware/recovering_middleware.go
rename to images/middleware/recovering_middleware.go
index 4fe31525e0b587e94a6cb8259ae551b38fd4f450..9ad61603a2584b58f4635c9ee8296f67f047b7a5 100644
--- a/pkg/images/middleware/recovering_middleware.go
+++ b/images/middleware/recovering_middleware.go
@@ -4,14 +4,14 @@
 
 package middleware
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/images -i Images -t ../../../assets/templates/middleware/recovery -o recovering_middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/images -i Images -t ../../assets/templates/middleware/recovery -o recovering_middleware.go -l ""
 
 import (
 	"context"
 	"fmt"
 
+	"git.perx.ru/perxis/perxis-go/images"
 	"git.perx.ru/perxis/perxis-go/pkg/files"
-	"git.perx.ru/perxis/perxis-go/pkg/images"
 	"go.uber.org/zap"
 )
 
diff --git a/pkg/images/middleware/telemetry_middleware.go b/images/middleware/telemetry_middleware.go
similarity index 89%
rename from pkg/images/middleware/telemetry_middleware.go
rename to images/middleware/telemetry_middleware.go
index b3cb8bef156613220dac2d1ad01a56bc4bba3723..2c0aac8eeaa3507d16ee852fd47b4a46569bc827 100644
--- a/pkg/images/middleware/telemetry_middleware.go
+++ b/images/middleware/telemetry_middleware.go
@@ -4,13 +4,13 @@
 
 package middleware
 
-//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/images -i Images -t ../../../assets/templates/middleware/telemetry -o telemetry_middleware.go -l ""
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/images -i Images -t ../../assets/templates/middleware/telemetry -o telemetry_middleware.go -l ""
 
 import (
 	"context"
 
+	"git.perx.ru/perxis/perxis-go/images"
 	"git.perx.ru/perxis/perxis-go/pkg/files"
-	"git.perx.ru/perxis/perxis-go/pkg/images"
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/trace"
diff --git a/pkg/images/mocks/Images.go b/images/mocks/Images.go
similarity index 74%
rename from pkg/images/mocks/Images.go
rename to images/mocks/Images.go
index baf57ae6c35baeb19c45ba6046833ba0d41868f9..0797d930363b79877509b71c6e010004ff156eae 100644
--- a/pkg/images/mocks/Images.go
+++ b/images/mocks/Images.go
@@ -1,12 +1,12 @@
-// Code generated by mockery v2.15.0. DO NOT EDIT.
+// Code generated by mockery v2.38.0. DO NOT EDIT.
 
 package mocks
 
 import (
 	context "context"
 
+	images "git.perx.ru/perxis/perxis-go/images"
 	files "git.perx.ru/perxis/perxis-go/pkg/files"
-	images "git.perx.ru/perxis/perxis-go/pkg/images"
 
 	mock "github.com/stretchr/testify/mock"
 )
@@ -20,7 +20,15 @@ type Images struct {
 func (_m *Images) Get(ctx context.Context, source *files.File, opts *images.GetOptions) (*files.File, error) {
 	ret := _m.Called(ctx, source, opts)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Get")
+	}
+
 	var r0 *files.File
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *files.File, *images.GetOptions) (*files.File, error)); ok {
+		return rf(ctx, source, opts)
+	}
 	if rf, ok := ret.Get(0).(func(context.Context, *files.File, *images.GetOptions) *files.File); ok {
 		r0 = rf(ctx, source, opts)
 	} else {
@@ -29,7 +37,6 @@ func (_m *Images) Get(ctx context.Context, source *files.File, opts *images.GetO
 		}
 	}
 
-	var r1 error
 	if rf, ok := ret.Get(1).(func(context.Context, *files.File, *images.GetOptions) error); ok {
 		r1 = rf(ctx, source, opts)
 	} else {
@@ -39,13 +46,12 @@ func (_m *Images) Get(ctx context.Context, source *files.File, opts *images.GetO
 	return r0, r1
 }
 
-type mockConstructorTestingTNewImages interface {
+// NewImages creates a new instance of Images. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewImages(t interface {
 	mock.TestingT
 	Cleanup(func())
-}
-
-// NewImages creates a new instance of Images. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-func NewImages(t mockConstructorTestingTNewImages) *Images {
+}) *Images {
 	mock := &Images{}
 	mock.Mock.Test(t)
 
diff --git a/pkg/images/mocks/Middleware.go b/images/mocks/Middleware.go
similarity index 75%
rename from pkg/images/mocks/Middleware.go
rename to images/mocks/Middleware.go
index 43ba1dd2b86fd43237c6e83c24a13928a8f8c2d3..5f4292c718008b2814d46b2cf5ac2d59016ca225 100644
--- a/pkg/images/mocks/Middleware.go
+++ b/images/mocks/Middleware.go
@@ -1,9 +1,9 @@
-// Code generated by mockery v2.15.0. DO NOT EDIT.
+// Code generated by mockery v2.38.0. DO NOT EDIT.
 
 package mocks
 
 import (
-	images "git.perx.ru/perxis/perxis-go/pkg/images"
+	images "git.perx.ru/perxis/perxis-go/images"
 
 	mock "github.com/stretchr/testify/mock"
 )
@@ -17,6 +17,10 @@ type Middleware struct {
 func (_m *Middleware) Execute(_a0 images.Images) images.Images {
 	ret := _m.Called(_a0)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Execute")
+	}
+
 	var r0 images.Images
 	if rf, ok := ret.Get(0).(func(images.Images) images.Images); ok {
 		r0 = rf(_a0)
@@ -29,13 +33,12 @@ func (_m *Middleware) Execute(_a0 images.Images) images.Images {
 	return r0
 }
 
-type mockConstructorTestingTNewMiddleware interface {
+// NewMiddleware creates a new instance of Middleware. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMiddleware(t interface {
 	mock.TestingT
 	Cleanup(func())
-}
-
-// NewMiddleware creates a new instance of Middleware. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-func NewMiddleware(t mockConstructorTestingTNewMiddleware) *Middleware {
+}) *Middleware {
 	mock := &Middleware{}
 	mock.Mock.Test(t)
 
diff --git a/pkg/images/service.go b/images/service.go
similarity index 100%
rename from pkg/images/service.go
rename to images/service.go
diff --git a/pkg/images/transport/client.microgen.go b/images/transport/client.microgen.go
similarity index 93%
rename from pkg/images/transport/client.microgen.go
rename to images/transport/client.microgen.go
index 9994b2c34e5234a20ae8d13846e3b5ef6fe41668..d8b9342e08a9770c17cbc81ead84eddf57dd16fa 100644
--- a/pkg/images/transport/client.microgen.go
+++ b/images/transport/client.microgen.go
@@ -6,8 +6,8 @@ import (
 	"context"
 	"errors"
 
+	images "git.perx.ru/perxis/perxis-go/images"
 	files "git.perx.ru/perxis/perxis-go/pkg/files"
-	images "git.perx.ru/perxis/perxis-go/pkg/images"
 	codes "google.golang.org/grpc/codes"
 	status "google.golang.org/grpc/status"
 )
diff --git a/pkg/images/transport/endpoints.microgen.go b/images/transport/endpoints.microgen.go
similarity index 100%
rename from pkg/images/transport/endpoints.microgen.go
rename to images/transport/endpoints.microgen.go
diff --git a/pkg/images/transport/exchanges.microgen.go b/images/transport/exchanges.microgen.go
similarity index 86%
rename from pkg/images/transport/exchanges.microgen.go
rename to images/transport/exchanges.microgen.go
index 9b9992a01ee5d21e24a69a88731de706e35a846e..71d2cf729e20f354e4860963c78e70cc24e198e2 100644
--- a/pkg/images/transport/exchanges.microgen.go
+++ b/images/transport/exchanges.microgen.go
@@ -3,8 +3,8 @@
 package transport
 
 import (
+	images "git.perx.ru/perxis/perxis-go/images"
 	files "git.perx.ru/perxis/perxis-go/pkg/files"
-	images "git.perx.ru/perxis/perxis-go/pkg/images"
 )
 
 type (
diff --git a/pkg/images/transport/grpc/client.microgen.go b/images/transport/grpc/client.microgen.go
similarity index 89%
rename from pkg/images/transport/grpc/client.microgen.go
rename to images/transport/grpc/client.microgen.go
index cbafae5f0c308d46b4b8b9430ff1c798943fdc5e..f613e655bb0af499c9c7c525526b6007b3c1926d 100644
--- a/pkg/images/transport/grpc/client.microgen.go
+++ b/images/transport/grpc/client.microgen.go
@@ -3,7 +3,7 @@
 package transportgrpc
 
 import (
-	transport "git.perx.ru/perxis/perxis-go/pkg/images/transport"
+	transport "git.perx.ru/perxis/perxis-go/images/transport"
 	pb "git.perx.ru/perxis/perxis-go/proto/images"
 	grpckit "github.com/go-kit/kit/transport/grpc"
 	grpc "google.golang.org/grpc"
diff --git a/pkg/images/transport/grpc/protobuf_endpoint_converters.microgen.go b/images/transport/grpc/protobuf_endpoint_converters.microgen.go
similarity index 96%
rename from pkg/images/transport/grpc/protobuf_endpoint_converters.microgen.go
rename to images/transport/grpc/protobuf_endpoint_converters.microgen.go
index e1800e1747d2d03335586a358e3139154b3ae0f0..d206e2f4e94445f7b1ade13f23c296eaf68bcf59 100644
--- a/pkg/images/transport/grpc/protobuf_endpoint_converters.microgen.go
+++ b/images/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -7,7 +7,7 @@ import (
 	"context"
 	"errors"
 
-	transport "git.perx.ru/perxis/perxis-go/pkg/images/transport"
+	transport "git.perx.ru/perxis/perxis-go/images/transport"
 	pb "git.perx.ru/perxis/perxis-go/proto/images"
 )
 
diff --git a/pkg/images/transport/grpc/protobuf_type_converters.microgen.go b/images/transport/grpc/protobuf_type_converters.microgen.go
similarity index 91%
rename from pkg/images/transport/grpc/protobuf_type_converters.microgen.go
rename to images/transport/grpc/protobuf_type_converters.microgen.go
index ff8c72337ca3dffda4433a785163c141e37967d7..48a13510667582f7858cbc04ff517598eb17772d 100644
--- a/pkg/images/transport/grpc/protobuf_type_converters.microgen.go
+++ b/images/transport/grpc/protobuf_type_converters.microgen.go
@@ -5,8 +5,8 @@
 package transportgrpc
 
 import (
+	service "git.perx.ru/perxis/perxis-go/images"
 	file "git.perx.ru/perxis/perxis-go/pkg/files"
-	service "git.perx.ru/perxis/perxis-go/pkg/images"
 	pbfile "git.perx.ru/perxis/perxis-go/proto/files"
 	pbimage "git.perx.ru/perxis/perxis-go/proto/images"
 )
@@ -74,9 +74,9 @@ func ProtoToPtrGetOptions(protoOpts *pbimage.GetRequest_GetOptions) (*service.Ge
 }
 
 //func PtrFilesFileToProto(source *files.File) (*pbimage.File, error) {
-//	panic("function not provided") // TODO: provide converter
+//	panic("function not provided") // TODO: provide convert
 //}
 //
 //func ProtoToPtrFilesFile(protoSource *images.File) (*files.File, error) {
-//	panic("function not provided") // TODO: provide converter
+//	panic("function not provided") // TODO: provide convert
 //}
diff --git a/pkg/images/transport/grpc/server.microgen.go b/images/transport/grpc/server.microgen.go
similarity index 92%
rename from pkg/images/transport/grpc/server.microgen.go
rename to images/transport/grpc/server.microgen.go
index 47e04dc3e59277209f7c6ff3b4028a8cf079d9e0..19d48fcd57b1a210c3864e26cf9d21871b7ce3d9 100644
--- a/pkg/images/transport/grpc/server.microgen.go
+++ b/images/transport/grpc/server.microgen.go
@@ -4,7 +4,7 @@
 package transportgrpc
 
 import (
-	transport "git.perx.ru/perxis/perxis-go/pkg/images/transport"
+	transport "git.perx.ru/perxis/perxis-go/images/transport"
 	pb "git.perx.ru/perxis/perxis-go/proto/images"
 	grpc "github.com/go-kit/kit/transport/grpc"
 	context "golang.org/x/net/context"
diff --git a/pkg/images/transport/server.microgen.go b/images/transport/server.microgen.go
similarity index 91%
rename from pkg/images/transport/server.microgen.go
rename to images/transport/server.microgen.go
index 26105401a1f079c3837d311ccf7da233c2deb9a7..de0d7e160da27151d585256b3a3becc38d7ffffe 100644
--- a/pkg/images/transport/server.microgen.go
+++ b/images/transport/server.microgen.go
@@ -5,7 +5,7 @@ package transport
 import (
 	"context"
 
-	images "git.perx.ru/perxis/perxis-go/pkg/images"
+	images "git.perx.ru/perxis/perxis-go/images"
 	endpoint "github.com/go-kit/kit/endpoint"
 )