mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-24 06:28:34 +01:00
Use aspect ratio when making thumbnails and fix rotation (#857)
This commit is contained in:
@@ -123,7 +123,9 @@ func run(cfg *config.Config) error {
|
|||||||
}
|
}
|
||||||
// Construct and validate the full storage path
|
// Construct and validate the full storage path
|
||||||
storageDir := filepath.Join(absBase, cfg.Storage.PrefixPath)
|
storageDir := filepath.Join(absBase, cfg.Storage.PrefixPath)
|
||||||
if !strings.HasPrefix(storageDir, absBase+string(os.PathSeparator)) && storageDir != absBase {
|
// Set windows paths to use forward slashes required by go-cloud
|
||||||
|
storageDir = strings.ReplaceAll(storageDir, "\\", "/")
|
||||||
|
if !strings.HasPrefix(storageDir, absBase+"/") && storageDir != absBase {
|
||||||
log.Fatal().
|
log.Fatal().
|
||||||
Str("path", storageDir).
|
Str("path", storageDir).
|
||||||
Msg("invalid storage path: you tried to use a prefix that is not a subdirectory of the base path")
|
Msg("invalid storage path: you tried to use a prefix that is not a subdirectory of the base path")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ require (
|
|||||||
entgo.io/ent v0.14.4
|
entgo.io/ent v0.14.4
|
||||||
github.com/ardanlabs/conf/v3 v3.8.0
|
github.com/ardanlabs/conf/v3 v3.8.0
|
||||||
github.com/containrrr/shoutrrr v0.8.0
|
github.com/containrrr/shoutrrr v0.8.0
|
||||||
|
github.com/evanoberholster/imagemeta v0.3.1
|
||||||
github.com/gen2brain/avif v0.4.4
|
github.com/gen2brain/avif v0.4.4
|
||||||
github.com/gen2brain/heic v0.4.5
|
github.com/gen2brain/heic v0.4.5
|
||||||
github.com/gen2brain/jpegxl v0.4.5
|
github.com/gen2brain/jpegxl v0.4.5
|
||||||
@@ -139,7 +140,7 @@ require (
|
|||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
@@ -152,6 +153,7 @@ require (
|
|||||||
github.com/nats-io/nkeys v0.4.10 // indirect
|
github.com/nats-io/nkeys v0.4.10 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/philhofer/fwd v1.1.2 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
@@ -163,6 +165,7 @@ require (
|
|||||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||||
github.com/swaggo/files/v2 v2.0.0 // indirect
|
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||||
|
github.com/tinylib/msgp v1.1.8 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/yeqown/reedsolomon v1.0.0 // indirect
|
github.com/yeqown/reedsolomon v1.0.0 // indirect
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJP
|
|||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||||
|
github.com/evanoberholster/imagemeta v0.3.1 h1:E4GUjXcvlVMjP9joN25+bBNf3Al3MTTfMqCrDOCW+LE=
|
||||||
|
github.com/evanoberholster/imagemeta v0.3.1/go.mod h1:V0vtDJmjTqvwAYO8r+u33NRVIMXQb0qSqEfImoKEiXM=
|
||||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
@@ -323,8 +325,8 @@ github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKu
|
|||||||
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
|
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -350,8 +352,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
|||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||||
@@ -375,12 +375,12 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm
|
|||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/olahol/melody v1.2.1 h1:xdwRkzHxf+B0w4TKbGpUSSkV516ZucQZJIWLztOWICQ=
|
github.com/olahol/melody v1.2.1 h1:xdwRkzHxf+B0w4TKbGpUSSkV516ZucQZJIWLztOWICQ=
|
||||||
github.com/olahol/melody v1.2.1/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
github.com/olahol/melody v1.2.1/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||||
|
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||||
|
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
@@ -417,10 +417,6 @@ github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQU
|
|||||||
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
|
||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -440,6 +436,8 @@ github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
|||||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||||
|
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||||
|
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
@@ -515,6 +513,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
|
|||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
@@ -530,6 +529,7 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
@@ -558,9 +558,11 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -571,6 +573,7 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
|||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
@@ -578,6 +581,7 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
|||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
@@ -593,6 +597,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/evanoberholster/imagemeta"
|
||||||
|
"github.com/gen2brain/avif"
|
||||||
|
"github.com/gen2brain/heic"
|
||||||
|
"github.com/gen2brain/jpegxl"
|
||||||
|
"github.com/gen2brain/webp"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
|
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
|
||||||
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
|
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
|
||||||
"github.com/sysadminsmedia/homebox/backend/pkgs/utils"
|
"github.com/sysadminsmedia/homebox/backend/pkgs/utils"
|
||||||
"github.com/zeebo/blake3"
|
"github.com/zeebo/blake3"
|
||||||
|
|
||||||
"github.com/gen2brain/avif"
|
|
||||||
"github.com/gen2brain/heic"
|
|
||||||
"github.com/gen2brain/jpegxl"
|
|
||||||
"github.com/gen2brain/webp"
|
|
||||||
"golang.org/x/image/draw"
|
"golang.org/x/image/draw"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
@@ -502,10 +502,13 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
contentType := http.DetectContentType(contentBytes[:min(512, len(contentBytes))])
|
contentType := http.DetectContentType(contentBytes[:min(512, len(contentBytes))])
|
||||||
|
|
||||||
if contentType == "application/octet-stream" {
|
if contentType == "application/octet-stream" {
|
||||||
if strings.HasSuffix(title, ".heic") || strings.HasSuffix(title, ".heif") {
|
switch {
|
||||||
|
case strings.HasSuffix(title, ".heic") || strings.HasSuffix(title, ".heif"):
|
||||||
contentType = "image/heic"
|
contentType = "image/heic"
|
||||||
} else if strings.HasSuffix(title, ".avif") {
|
case strings.HasSuffix(title, ".avif"):
|
||||||
contentType = "image/avif"
|
contentType = "image/avif"
|
||||||
|
case strings.HasSuffix(title, ".jxl"):
|
||||||
|
contentType = "image/jxl"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,10 +525,18 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, r.thumbnail.Width, r.thumbnail.Height))
|
log.Debug().Msg("reading original file orientation")
|
||||||
draw.ApproxBiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Over, nil)
|
imageMeta, err := imagemeta.Decode(bytes.NewReader(contentBytes))
|
||||||
buf := new(bytes.Buffer)
|
if err != nil {
|
||||||
err = webp.Encode(buf, dst, webp.Options{Quality: 80, Lossless: false})
|
log.Err(err).Msg("failed to decode original file content")
|
||||||
|
err := tx.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
orientation := uint16(imageMeta.Orientation)
|
||||||
|
thumbnailPath, err := r.processThumbnailFromImage(ctx, groupId, img, title, orientation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := tx.Rollback()
|
err := tx.Rollback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -533,22 +544,7 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contentBytes := buf.Bytes()
|
att.SetPath(thumbnailPath)
|
||||||
log.Debug().Msg("uploading thumbnail file")
|
|
||||||
thumbnailFile, err := r.UploadFile(ctx, tx.Group.GetX(ctx, groupId), ItemCreateAttachment{
|
|
||||||
Title: fmt.Sprintf("%s-thumb", title),
|
|
||||||
Content: bytes.NewReader(contentBytes),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("failed to upload thumbnail file")
|
|
||||||
err := tx.Rollback()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug().Msg("setting thumbnail file path in attachment")
|
|
||||||
att.SetPath(thumbnailFile)
|
|
||||||
case contentType == "image/webp":
|
case contentType == "image/webp":
|
||||||
log.Debug().Msg("creating thumbnail for webp file")
|
log.Debug().Msg("creating thumbnail for webp file")
|
||||||
img, err := webp.Decode(bytes.NewReader(contentBytes))
|
img, err := webp.Decode(bytes.NewReader(contentBytes))
|
||||||
@@ -560,10 +556,18 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, r.thumbnail.Width, r.thumbnail.Height))
|
log.Debug().Msg("reading original file orientation")
|
||||||
draw.ApproxBiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Over, nil)
|
imageMeta, err := imagemeta.Decode(bytes.NewReader(contentBytes))
|
||||||
buf := new(bytes.Buffer)
|
if err != nil {
|
||||||
err = webp.Encode(buf, dst, webp.Options{Quality: 80, Lossless: false})
|
log.Err(err).Msg("failed to decode original file content")
|
||||||
|
err := tx.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
orientation := uint16(imageMeta.Orientation)
|
||||||
|
thumbnailPath, err := r.processThumbnailFromImage(ctx, groupId, img, title, orientation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := tx.Rollback()
|
err := tx.Rollback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -571,22 +575,7 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contentBytes := buf.Bytes()
|
att.SetPath(thumbnailPath)
|
||||||
log.Debug().Msg("uploading thumbnail file")
|
|
||||||
thumbnailFile, err := r.UploadFile(ctx, tx.Group.GetX(ctx, groupId), ItemCreateAttachment{
|
|
||||||
Title: fmt.Sprintf("%s-thumb", title),
|
|
||||||
Content: bytes.NewReader(contentBytes),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("failed to upload thumbnail file")
|
|
||||||
err := tx.Rollback()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug().Msg("setting thumbnail file path in attachment")
|
|
||||||
att.SetPath(thumbnailFile)
|
|
||||||
case contentType == "image/avif":
|
case contentType == "image/avif":
|
||||||
log.Debug().Msg("creating thumbnail for avif file")
|
log.Debug().Msg("creating thumbnail for avif file")
|
||||||
img, err := avif.Decode(bytes.NewReader(contentBytes))
|
img, err := avif.Decode(bytes.NewReader(contentBytes))
|
||||||
@@ -598,10 +587,7 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, r.thumbnail.Width, r.thumbnail.Height))
|
thumbnailPath, err := r.processThumbnailFromImage(ctx, groupId, img, title, uint16(1))
|
||||||
draw.ApproxBiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Over, nil)
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err = webp.Encode(buf, dst, webp.Options{Quality: 80, Lossless: false})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := tx.Rollback()
|
err := tx.Rollback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -609,22 +595,7 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contentBytes := buf.Bytes()
|
att.SetPath(thumbnailPath)
|
||||||
log.Debug().Msg("uploading thumbnail file")
|
|
||||||
thumbnailFile, err := r.UploadFile(ctx, tx.Group.GetX(ctx, groupId), ItemCreateAttachment{
|
|
||||||
Title: fmt.Sprintf("%s-thumb", title),
|
|
||||||
Content: bytes.NewReader(contentBytes),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("failed to upload thumbnail file")
|
|
||||||
err := tx.Rollback()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug().Msg("setting thumbnail file path in attachment")
|
|
||||||
att.SetPath(thumbnailFile)
|
|
||||||
case contentType == "image/heic" || contentType == "image/heif":
|
case contentType == "image/heic" || contentType == "image/heif":
|
||||||
log.Debug().Msg("creating thumbnail for heic file")
|
log.Debug().Msg("creating thumbnail for heic file")
|
||||||
img, err := heic.Decode(bytes.NewReader(contentBytes))
|
img, err := heic.Decode(bytes.NewReader(contentBytes))
|
||||||
@@ -636,10 +607,18 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, r.thumbnail.Width, r.thumbnail.Height))
|
log.Debug().Msg("reading original file orientation")
|
||||||
draw.ApproxBiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Over, nil)
|
imageMeta, err := imagemeta.Decode(bytes.NewReader(contentBytes))
|
||||||
buf := new(bytes.Buffer)
|
if err != nil {
|
||||||
err = webp.Encode(buf, dst, webp.Options{Quality: 80, Lossless: false})
|
log.Err(err).Msg("failed to decode original file content")
|
||||||
|
err := tx.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
orientation := uint16(imageMeta.Orientation)
|
||||||
|
thumbnailPath, err := r.processThumbnailFromImage(ctx, groupId, img, title, orientation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := tx.Rollback()
|
err := tx.Rollback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -647,22 +626,7 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contentBytes := buf.Bytes()
|
att.SetPath(thumbnailPath)
|
||||||
log.Debug().Msg("uploading thumbnail file")
|
|
||||||
thumbnailFile, err := r.UploadFile(ctx, tx.Group.GetX(ctx, groupId), ItemCreateAttachment{
|
|
||||||
Title: fmt.Sprintf("%s-thumb", title),
|
|
||||||
Content: bytes.NewReader(contentBytes),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("failed to upload thumbnail file")
|
|
||||||
err := tx.Rollback()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug().Msg("setting thumbnail file path in attachment")
|
|
||||||
att.SetPath(thumbnailFile)
|
|
||||||
case contentType == "image/jxl":
|
case contentType == "image/jxl":
|
||||||
log.Debug().Msg("creating thumbnail for jpegxl file")
|
log.Debug().Msg("creating thumbnail for jpegxl file")
|
||||||
img, err := jpegxl.Decode(bytes.NewReader(contentBytes))
|
img, err := jpegxl.Decode(bytes.NewReader(contentBytes))
|
||||||
@@ -674,10 +638,7 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, r.thumbnail.Width, r.thumbnail.Height))
|
thumbnailPath, err := r.processThumbnailFromImage(ctx, groupId, img, title, uint16(1))
|
||||||
draw.ApproxBiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Over, nil)
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err = webp.Encode(buf, dst, webp.Options{Quality: 80, Lossless: false})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := tx.Rollback()
|
err := tx.Rollback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -685,22 +646,7 @@ func (r *AttachmentRepo) CreateThumbnail(ctx context.Context, groupId, attachmen
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contentBytes := buf.Bytes()
|
att.SetPath(thumbnailPath)
|
||||||
log.Debug().Msg("uploading thumbnail file")
|
|
||||||
thumbnailFile, err := r.UploadFile(ctx, tx.Group.GetX(ctx, groupId), ItemCreateAttachment{
|
|
||||||
Title: fmt.Sprintf("%s-thumb", title),
|
|
||||||
Content: bytes.NewReader(contentBytes),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("failed to upload thumbnail file")
|
|
||||||
err := tx.Rollback()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug().Msg("setting thumbnail file path in attachment")
|
|
||||||
att.SetPath(thumbnailFile)
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("file type %s is not supported for thumbnail creation or document thumnails disabled", title)
|
return fmt.Errorf("file type %s is not supported for thumbnail creation or document thumnails disabled", title)
|
||||||
}
|
}
|
||||||
@@ -831,3 +777,73 @@ func isImageFile(mimetype string) bool {
|
|||||||
// Check file extension for image types
|
// Check file extension for image types
|
||||||
return strings.Contains(mimetype, "image/jpeg") || strings.Contains(mimetype, "image/png") || strings.Contains(mimetype, "image/gif")
|
return strings.Contains(mimetype, "image/jpeg") || strings.Contains(mimetype, "image/png") || strings.Contains(mimetype, "image/gif")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculateThumbnailDimensions calculates new dimensions that preserve aspect ratio
|
||||||
|
// while fitting within the configured maximum width and height
|
||||||
|
func calculateThumbnailDimensions(origWidth, origHeight, maxWidth, maxHeight int) (int, int) {
|
||||||
|
if origWidth <= maxWidth && origHeight <= maxHeight {
|
||||||
|
return origWidth, origHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate scaling factors for both dimensions
|
||||||
|
scaleX := float64(maxWidth) / float64(origWidth)
|
||||||
|
scaleY := float64(maxHeight) / float64(origHeight)
|
||||||
|
|
||||||
|
// Use the smaller scaling factor to ensure both dimensions fit
|
||||||
|
scale := scaleX
|
||||||
|
if scaleY < scaleX {
|
||||||
|
scale = scaleY
|
||||||
|
}
|
||||||
|
|
||||||
|
newWidth := int(float64(origWidth) * scale)
|
||||||
|
newHeight := int(float64(origHeight) * scale)
|
||||||
|
|
||||||
|
// Ensure we don't get zero dimensions
|
||||||
|
if newWidth < 1 {
|
||||||
|
newWidth = 1
|
||||||
|
}
|
||||||
|
if newHeight < 1 {
|
||||||
|
newHeight = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return newWidth, newHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// processThumbnailFromImage handles the common thumbnail processing logic after image decoding
|
||||||
|
// Returns the thumbnail file path or an error
|
||||||
|
func (r *AttachmentRepo) processThumbnailFromImage(ctx context.Context, groupId uuid.UUID, img image.Image, title string, orientation uint16) (string, error) {
|
||||||
|
bounds := img.Bounds()
|
||||||
|
// Apply EXIF orientation if needed
|
||||||
|
if orientation > 1 {
|
||||||
|
img = utils.ApplyOrientation(img, orientation)
|
||||||
|
bounds = img.Bounds()
|
||||||
|
}
|
||||||
|
newWidth, newHeight := calculateThumbnailDimensions(bounds.Dx(), bounds.Dy(), r.thumbnail.Width, r.thumbnail.Height)
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
|
||||||
|
draw.ApproxBiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Over, nil)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err := webp.Encode(buf, dst, webp.Options{Quality: 80, Lossless: false})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
contentBytes := buf.Bytes()
|
||||||
|
log.Debug().Msg("uploading thumbnail file")
|
||||||
|
|
||||||
|
// Get the group for uploading the thumbnail
|
||||||
|
group, err := r.db.Group.Get(ctx, groupId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnailFile, err := r.UploadFile(ctx, group, ItemCreateAttachment{
|
||||||
|
Title: fmt.Sprintf("%s-thumb", title),
|
||||||
|
Content: bytes.NewReader(contentBytes),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("failed to upload thumbnail file")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbnailFile, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func TestAttachmentRepo_Create(t *testing.T) {
|
|||||||
ids := []uuid.UUID{item.ID}
|
ids := []uuid.UUID{item.ID}
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
_ = tRepos.Attachments.Delete(context.Background(), id)
|
_ = tRepos.Attachments.Delete(context.Background(), tGroup.ID, item.ID, id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ func TestAttachmentRepo_Create(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, tt.want.Type, got.Type)
|
assert.Equal(t, tt.want.Type, got.Type)
|
||||||
|
|
||||||
withItems, err := tRepos.Attachments.Get(tt.args.ctx, got.ID)
|
withItems, err := tRepos.Attachments.Get(tt.args.ctx, tGroup.ID, got.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, tt.args.itemID, withItems.Edges.Item.ID)
|
assert.Equal(t, tt.args.itemID, withItems.Edges.Item.ID)
|
||||||
|
|
||||||
@@ -86,17 +86,17 @@ func useAttachments(t *testing.T, n int) []*ent.Attachment {
|
|||||||
ids := make([]uuid.UUID, 0, n)
|
ids := make([]uuid.UUID, 0, n)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
_ = tRepos.Attachments.Delete(context.Background(), id)
|
_ = tRepos.Attachments.Delete(context.Background(), tGroup.ID, item.ID, id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
attachments := make([]*ent.Attachment, n)
|
attachments := make([]*ent.Attachment, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
attachment, err := tRepos.Attachments.Create(context.Background(), item.ID, ItemCreateAttachment{Title: "Test", Content: strings.NewReader("Test String")}, attachment.TypePhoto, true)
|
attach, err := tRepos.Attachments.Create(context.Background(), item.ID, ItemCreateAttachment{Title: "Test", Content: strings.NewReader("Test String")}, attachment.TypePhoto, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
attachments[i] = attachment
|
attachments[i] = attach
|
||||||
|
|
||||||
ids = append(ids, attachment.ID)
|
ids = append(ids, attach.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return attachments
|
return attachments
|
||||||
@@ -107,13 +107,13 @@ func TestAttachmentRepo_Update(t *testing.T) {
|
|||||||
|
|
||||||
for _, typ := range []attachment.Type{"photo", "manual", "warranty", "attachment"} {
|
for _, typ := range []attachment.Type{"photo", "manual", "warranty", "attachment"} {
|
||||||
t.Run(string(typ), func(t *testing.T) {
|
t.Run(string(typ), func(t *testing.T) {
|
||||||
_, err := tRepos.Attachments.Update(context.Background(), entity.ID, &ItemAttachmentUpdate{
|
_, err := tRepos.Attachments.Update(context.Background(), tGroup.ID, entity.ID, &ItemAttachmentUpdate{
|
||||||
Type: string(typ),
|
Type: string(typ),
|
||||||
})
|
})
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
updated, err := tRepos.Attachments.Get(context.Background(), entity.ID)
|
updated, err := tRepos.Attachments.Get(context.Background(), tGroup.ID, entity.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, typ, updated.Type)
|
assert.Equal(t, typ, updated.Type)
|
||||||
})
|
})
|
||||||
@@ -122,11 +122,12 @@ func TestAttachmentRepo_Update(t *testing.T) {
|
|||||||
|
|
||||||
func TestAttachmentRepo_Delete(t *testing.T) {
|
func TestAttachmentRepo_Delete(t *testing.T) {
|
||||||
entity := useAttachments(t, 1)[0]
|
entity := useAttachments(t, 1)[0]
|
||||||
|
item := useItems(t, 1)[0]
|
||||||
|
|
||||||
err := tRepos.Attachments.Delete(context.Background(), entity.ID)
|
err := tRepos.Attachments.Delete(context.Background(), tGroup.ID, item.ID, entity.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = tRepos.Attachments.Get(context.Background(), entity.ID)
|
_, err = tRepos.Attachments.Get(context.Background(), tGroup.ID, entity.ID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,13 +136,13 @@ func TestAttachmentRepo_EnsureSinglePrimaryAttachment(t *testing.T) {
|
|||||||
attachments := useAttachments(t, 2)
|
attachments := useAttachments(t, 2)
|
||||||
|
|
||||||
setAndVerifyPrimary := func(primaryAttachmentID, nonPrimaryAttachmentID uuid.UUID) {
|
setAndVerifyPrimary := func(primaryAttachmentID, nonPrimaryAttachmentID uuid.UUID) {
|
||||||
primaryAttachment, err := tRepos.Attachments.Update(ctx, primaryAttachmentID, &ItemAttachmentUpdate{
|
primaryAttachment, err := tRepos.Attachments.Update(ctx, tGroup.ID, primaryAttachmentID, &ItemAttachmentUpdate{
|
||||||
Type: attachment.TypePhoto.String(),
|
Type: attachment.TypePhoto.String(),
|
||||||
Primary: true,
|
Primary: true,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
nonPrimaryAttachment, err := tRepos.Attachments.Get(ctx, nonPrimaryAttachmentID)
|
nonPrimaryAttachment, err := tRepos.Attachments.Get(ctx, tGroup.ID, nonPrimaryAttachmentID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.True(t, primaryAttachment.Primary)
|
assert.True(t, primaryAttachment.Primary)
|
||||||
|
|||||||
86
backend/pkgs/utils/image.go
Normal file
86
backend/pkgs/utils/image.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
|
||||||
|
// flipHorizontal will flip the image horizontally. There is a limit of 10000 pixels in either dimension to prevent excessive memory usage.
|
||||||
|
func flipHorizontal(img image.Image) image.Image {
|
||||||
|
b := img.Bounds()
|
||||||
|
if b.Dx() > 10000 || b.Dy() > 10000 {
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
dst := image.NewRGBA(b)
|
||||||
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
|
dst.Set(b.Max.X-1-(x-b.Min.X), y, img.At(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// flipVertical will flip the image vertically. There is a limit of 10000 pixels in either dimension to prevent excessive memory usage.
|
||||||
|
func flipVertical(img image.Image) image.Image {
|
||||||
|
b := img.Bounds()
|
||||||
|
if b.Dx() > 10000 || b.Dy() > 10000 {
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
dst := image.NewRGBA(b)
|
||||||
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
|
dst.Set(x, b.Max.Y-1-(y-b.Min.Y), img.At(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate90 will rotate the image 90 degrees clockwise. There is a limit of 10000 pixels in either dimension to prevent excessive memory usage.
|
||||||
|
func rotate90(img image.Image) image.Image {
|
||||||
|
b := img.Bounds()
|
||||||
|
if b.Dx() > 10000 || b.Dy() > 10000 {
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, b.Dy(), b.Dx()))
|
||||||
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
|
dst.Set(b.Max.Y-1-y, x, img.At(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func rotate180(img image.Image) image.Image {
|
||||||
|
return rotate90(rotate90(img))
|
||||||
|
}
|
||||||
|
|
||||||
|
func rotate270(img image.Image) image.Image {
|
||||||
|
return rotate90(rotate180(img))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies EXIF orientation using only stdlib
|
||||||
|
func ApplyOrientation(img image.Image, orientation uint16) image.Image {
|
||||||
|
if img == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if orientation < 1 || orientation > 8 {
|
||||||
|
return img // No orientation or invalid orientation
|
||||||
|
}
|
||||||
|
switch orientation {
|
||||||
|
case 1:
|
||||||
|
return img // No rotation needed
|
||||||
|
case 2:
|
||||||
|
return flipHorizontal(img)
|
||||||
|
case 3:
|
||||||
|
return rotate180(img)
|
||||||
|
case 4:
|
||||||
|
return flipVertical(img)
|
||||||
|
case 5:
|
||||||
|
return rotate90(flipHorizontal(img))
|
||||||
|
case 6:
|
||||||
|
return rotate90(img)
|
||||||
|
case 7:
|
||||||
|
return rotate270(flipHorizontal(img))
|
||||||
|
case 8:
|
||||||
|
return rotate270(img)
|
||||||
|
default:
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user