feat: thumbnail generator plugin with configurable size/format
- ThumbnailGeneratorPort in domain (bytes + config → resized bytes) - adapters-thumbnail: ImageThumbnailGenerator using image crate - ThumbnailGeneratorPlugin reads width/height/format/profile from step config - PostgresDerivativeRepository + 012_derivatives migration - Seeded in extract_metadata pipeline as step 2 (300x300 webp) - Standalone generate_derivative pipeline for on-demand use
This commit is contained in:
665
Cargo.lock
generated
665
Cargo.lock
generated
@@ -68,6 +68,21 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adapters-thumbnail"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"domain",
|
||||||
|
"image",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -77,6 +92,24 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aligned"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
|
||||||
|
dependencies = [
|
||||||
|
"as-slice",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aligned-vec"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b"
|
||||||
|
dependencies = [
|
||||||
|
"equator",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -137,6 +170,38 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arg_enum_proc_macro"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "as-slice"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
|
||||||
|
dependencies = [
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-nats"
|
name = "async-nats"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
@@ -205,6 +270,49 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "av-scenechange"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
|
||||||
|
dependencies = [
|
||||||
|
"aligned",
|
||||||
|
"anyhow",
|
||||||
|
"arg_enum_proc_macro",
|
||||||
|
"arrayvec",
|
||||||
|
"log",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
"pastey",
|
||||||
|
"rayon",
|
||||||
|
"thiserror",
|
||||||
|
"v_frame",
|
||||||
|
"y4m",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "av1-grain"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"arrayvec",
|
||||||
|
"log",
|
||||||
|
"nom 8.0.0",
|
||||||
|
"num-rational",
|
||||||
|
"v_frame",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "avif-serialize"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -295,6 +403,12 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit_field"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.11.1"
|
version = "2.11.1"
|
||||||
@@ -304,6 +418,15 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitstream-io"
|
||||||
|
version = "4.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f"
|
||||||
|
dependencies = [
|
||||||
|
"no_std_io2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@@ -346,18 +469,36 @@ dependencies = [
|
|||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "built"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.1"
|
version = "3.19.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -374,6 +515,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
|
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -424,6 +567,12 @@ dependencies = [
|
|||||||
"inout",
|
"inout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -488,6 +637,34 @@ version = "2.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
|
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-queue"
|
name = "crossbeam-queue"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
@@ -503,6 +680,12 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@@ -649,6 +832,26 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equator"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
|
||||||
|
dependencies = [
|
||||||
|
"equator-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equator-macro"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -701,6 +904,36 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exr"
|
||||||
|
version = "1.74.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
|
||||||
|
dependencies = [
|
||||||
|
"bit_field",
|
||||||
|
"half",
|
||||||
|
"lebe",
|
||||||
|
"miniz_oxide",
|
||||||
|
"rayon-core",
|
||||||
|
"smallvec",
|
||||||
|
"zune-inflate",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fax"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fiat-crypto"
|
name = "fiat-crypto"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
@@ -713,6 +946,16 @@ version = "0.1.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
|
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@@ -906,6 +1149,16 @@ dependencies = [
|
|||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gif"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
|
||||||
|
dependencies = [
|
||||||
|
"color_quant",
|
||||||
|
"weezl",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
@@ -925,6 +1178,17 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -1234,6 +1498,46 @@ dependencies = [
|
|||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.25.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder-lite",
|
||||||
|
"color_quant",
|
||||||
|
"exr",
|
||||||
|
"gif",
|
||||||
|
"image-webp",
|
||||||
|
"moxcms",
|
||||||
|
"num-traits",
|
||||||
|
"png",
|
||||||
|
"qoi",
|
||||||
|
"ravif",
|
||||||
|
"rayon",
|
||||||
|
"rgb",
|
||||||
|
"tiff",
|
||||||
|
"zune-core",
|
||||||
|
"zune-jpeg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image-webp"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder-lite",
|
||||||
|
"quick-error",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imgref"
|
||||||
|
version = "1.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.14.0"
|
version = "2.14.0"
|
||||||
@@ -1255,6 +1559,17 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "interpolate_name"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.12.0"
|
version = "2.12.0"
|
||||||
@@ -1268,7 +1583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b5090db9c6a716d1f4eeb729957e889e9c28156061c825cbccd44950cf0f3c66"
|
checksum = "b5090db9c6a716d1f4eeb729957e889e9c28156061c825cbccd44950cf0f3c66"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"geo-types",
|
"geo-types",
|
||||||
"nom",
|
"nom 7.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1280,12 +1595,31 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.83"
|
version = "0.3.83"
|
||||||
@@ -1326,12 +1660,28 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lebe"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.178"
|
version = "0.2.178"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libfuzzer-sys"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@@ -1381,6 +1731,15 @@ version = "0.4.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loop9"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
|
||||||
|
dependencies = [
|
||||||
|
"imgref",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru-slab"
|
name = "lru-slab"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -1402,6 +1761,16 @@ version = "0.8.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maybe-rayon"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"rayon",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@@ -1430,6 +1799,16 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -1441,6 +1820,16 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moxcms"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"pxfm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "multer"
|
name = "multer"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
@@ -1458,6 +1847,12 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "new_debug_unreachable"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nkeys"
|
name = "nkeys"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
@@ -1473,6 +1868,15 @@ dependencies = [
|
|||||||
"signatory",
|
"signatory",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "no_std_io2"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@@ -1483,6 +1887,15 @@ dependencies = [
|
|||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom-exif"
|
name = "nom-exif"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -1492,13 +1905,19 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"iso6709parse",
|
"iso6709parse",
|
||||||
"nom",
|
"nom 7.1.3",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "noop_proc_macro"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
@@ -1549,6 +1968,17 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
|
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-derive"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.46"
|
version = "0.1.46"
|
||||||
@@ -1569,6 +1999,17 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -1592,7 +2033,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"humantime",
|
"humantime",
|
||||||
"hyper",
|
"hyper",
|
||||||
"itertools",
|
"itertools 0.13.0",
|
||||||
"md-5",
|
"md-5",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
@@ -1651,6 +2092,18 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pastey"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem"
|
name = "pem"
|
||||||
version = "3.0.6"
|
version = "3.0.6"
|
||||||
@@ -1735,6 +2188,19 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
@@ -1804,6 +2270,46 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "profiling"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
|
||||||
|
dependencies = [
|
||||||
|
"profiling-procmacros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "profiling-procmacros"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pxfm"
|
||||||
|
version = "0.1.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "qoi"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.37.5"
|
version = "0.37.5"
|
||||||
@@ -1966,6 +2472,76 @@ version = "0.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rav1e"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b"
|
||||||
|
dependencies = [
|
||||||
|
"aligned-vec",
|
||||||
|
"arbitrary",
|
||||||
|
"arg_enum_proc_macro",
|
||||||
|
"arrayvec",
|
||||||
|
"av-scenechange",
|
||||||
|
"av1-grain",
|
||||||
|
"bitstream-io",
|
||||||
|
"built",
|
||||||
|
"cfg-if",
|
||||||
|
"interpolate_name",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"libc",
|
||||||
|
"libfuzzer-sys",
|
||||||
|
"log",
|
||||||
|
"maybe-rayon",
|
||||||
|
"new_debug_unreachable",
|
||||||
|
"noop_proc_macro",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
"profiling",
|
||||||
|
"rand 0.9.4",
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"simd_helpers",
|
||||||
|
"thiserror",
|
||||||
|
"v_frame",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ravif"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45"
|
||||||
|
dependencies = [
|
||||||
|
"avif-serialize",
|
||||||
|
"imgref",
|
||||||
|
"loop9",
|
||||||
|
"quick-error",
|
||||||
|
"rav1e",
|
||||||
|
"rayon",
|
||||||
|
"rgb",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
@@ -2055,6 +2631,12 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rgb"
|
||||||
|
version = "0.8.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
@@ -2370,6 +2952,21 @@ dependencies = [
|
|||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd_helpers"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simple_asn1"
|
name = "simple_asn1"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
@@ -2726,6 +3323,20 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiff"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
||||||
|
dependencies = [
|
||||||
|
"fax",
|
||||||
|
"flate2",
|
||||||
|
"half",
|
||||||
|
"quick-error",
|
||||||
|
"weezl",
|
||||||
|
"zune-jpeg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.47"
|
version = "0.3.47"
|
||||||
@@ -3100,6 +3711,17 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "v_frame"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2"
|
||||||
|
dependencies = [
|
||||||
|
"aligned-vec",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -3310,6 +3932,12 @@ dependencies = [
|
|||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "weezl"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whoami"
|
name = "whoami"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
@@ -3638,6 +4266,7 @@ dependencies = [
|
|||||||
"adapters-nats",
|
"adapters-nats",
|
||||||
"adapters-postgres",
|
"adapters-postgres",
|
||||||
"adapters-storage",
|
"adapters-storage",
|
||||||
|
"adapters-thumbnail",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"application",
|
"application",
|
||||||
"async-nats",
|
"async-nats",
|
||||||
@@ -3657,6 +4286,12 @@ version = "0.6.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "y4m"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -3765,3 +4400,27 @@ name = "zmij"
|
|||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-inflate"
|
||||||
|
version = "0.2.54"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-jpeg"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||||
|
dependencies = [
|
||||||
|
"zune-core",
|
||||||
|
]
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ members = [
|
|||||||
"crates/adapters/event-transport",
|
"crates/adapters/event-transport",
|
||||||
"crates/adapters/nats",
|
"crates/adapters/nats",
|
||||||
"crates/adapters/exif",
|
"crates/adapters/exif",
|
||||||
|
"crates/adapters/thumbnail",
|
||||||
"crates/presentation",
|
"crates/presentation",
|
||||||
"crates/bootstrap",
|
"crates/bootstrap",
|
||||||
"crates/worker",
|
"crates/worker",
|
||||||
@@ -47,7 +48,8 @@ adapters-storage = { path = "crates/adapters/storage" }
|
|||||||
event-payload = { path = "crates/adapters/event-payload" }
|
event-payload = { path = "crates/adapters/event-payload" }
|
||||||
event-transport = { path = "crates/adapters/event-transport" }
|
event-transport = { path = "crates/adapters/event-transport" }
|
||||||
adapters-nats = { path = "crates/adapters/nats" }
|
adapters-nats = { path = "crates/adapters/nats" }
|
||||||
adapters-exif = { path = "crates/adapters/exif" }
|
adapters-exif = { path = "crates/adapters/exif" }
|
||||||
|
adapters-thumbnail = { path = "crates/adapters/thumbnail" }
|
||||||
async-nats = "0.48"
|
async-nats = "0.48"
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
presentation = { path = "crates/presentation" }
|
presentation = { path = "crates/presentation" }
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
-- Default plugins matching worker's InMemoryPluginRegistry
|
-- Default plugins matching worker's InMemoryPluginRegistry
|
||||||
INSERT INTO plugins (plugin_id, name, plugin_type, is_enabled, configuration)
|
INSERT INTO plugins (plugin_id, name, plugin_type, is_enabled, configuration)
|
||||||
VALUES
|
VALUES
|
||||||
('a0000000-0000-4000-8000-000000000001', 'metadata_extractor', 'media_processor', true, '{}'),
|
('a0000000-0000-4000-8000-000000000001', 'metadata_extractor', 'media_processor', true, '{}'),
|
||||||
('a0000000-0000-4000-8000-000000000002', 'sidecar_sync', 'sidecar_writer', true, '{}'),
|
('a0000000-0000-4000-8000-000000000002', 'sidecar_sync', 'sidecar_writer', true, '{}'),
|
||||||
('a0000000-0000-4000-8000-000000000003', 'no_op', 'scheduled_task', true, '{}')
|
('a0000000-0000-4000-8000-000000000003', 'no_op', 'scheduled_task', true, '{}'),
|
||||||
|
('a0000000-0000-4000-8000-000000000004', 'thumbnail_generator', 'media_processor', true, '{}')
|
||||||
ON CONFLICT (plugin_id) DO NOTHING;
|
ON CONFLICT (plugin_id) DO NOTHING;
|
||||||
|
|
||||||
-- Pipeline: extract_metadata → metadata_extractor
|
-- Pipeline: extract_metadata → metadata_extractor, then thumbnail_generator
|
||||||
INSERT INTO processing_pipelines (pipeline_id, trigger_event, steps)
|
INSERT INTO processing_pipelines (pipeline_id, trigger_event, steps)
|
||||||
VALUES (
|
VALUES (
|
||||||
'b0000000-0000-4000-8000-000000000001',
|
'b0000000-0000-4000-8000-000000000001',
|
||||||
'extract_metadata',
|
'extract_metadata',
|
||||||
'[{"plugin_id": "a0000000-0000-4000-8000-000000000001", "step_order": 0, "configuration": {}}]'
|
'[{"plugin_id": "a0000000-0000-4000-8000-000000000001", "step_order": 0, "configuration": {}},
|
||||||
|
{"plugin_id": "a0000000-0000-4000-8000-000000000004", "step_order": 1, "configuration": {"width": "300", "height": "300", "format": "webp", "profile": "ThumbnailSquare"}}]'
|
||||||
|
)
|
||||||
|
ON CONFLICT (pipeline_id) DO NOTHING;
|
||||||
|
|
||||||
|
-- Pipeline: generate_derivative (standalone, configurable per-step)
|
||||||
|
INSERT INTO processing_pipelines (pipeline_id, trigger_event, steps)
|
||||||
|
VALUES (
|
||||||
|
'b0000000-0000-4000-8000-000000000003',
|
||||||
|
'generate_derivative',
|
||||||
|
'[{"plugin_id": "a0000000-0000-4000-8000-000000000004", "step_order": 0, "configuration": {"width": "300", "height": "300", "format": "webp", "profile": "ThumbnailSquare"}}]'
|
||||||
)
|
)
|
||||||
ON CONFLICT (pipeline_id) DO NOTHING;
|
ON CONFLICT (pipeline_id) DO NOTHING;
|
||||||
|
|
||||||
|
|||||||
14
crates/adapters/postgres/migrations/012_derivatives.sql
Normal file
14
crates/adapters/postgres/migrations/012_derivatives.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
CREATE TABLE derivatives (
|
||||||
|
derivative_id UUID PRIMARY KEY,
|
||||||
|
parent_asset_id UUID NOT NULL REFERENCES assets(asset_id),
|
||||||
|
profile_type TEXT NOT NULL,
|
||||||
|
storage_path TEXT NOT NULL,
|
||||||
|
mime_type TEXT NOT NULL DEFAULT '',
|
||||||
|
file_size BIGINT NOT NULL DEFAULT 0,
|
||||||
|
width INTEGER NOT NULL DEFAULT 0,
|
||||||
|
height INTEGER NOT NULL DEFAULT 0,
|
||||||
|
generation_status TEXT NOT NULL DEFAULT 'pending'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_derivatives_parent ON derivatives(parent_asset_id);
|
||||||
|
CREATE INDEX idx_derivatives_parent_profile ON derivatives(parent_asset_id, profile_type);
|
||||||
@@ -3,11 +3,12 @@ use async_trait::async_trait;
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use domain::{
|
use domain::{
|
||||||
entities::{
|
entities::{
|
||||||
Asset, AssetMetadata, AssetType, DetectionMethod, DuplicateCandidate, DuplicateGroup,
|
Asset, AssetMetadata, AssetType, DerivativeAsset, DerivativeProfile, DetectionMethod,
|
||||||
DuplicateStatus, MetadataSource, SourceReference,
|
DuplicateCandidate, DuplicateGroup, DuplicateStatus, GenerationStatus, MetadataSource,
|
||||||
|
SourceReference,
|
||||||
},
|
},
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
ports::{AssetMetadataRepository, AssetRepository, DuplicateRepository},
|
ports::{AssetMetadataRepository, AssetRepository, DerivativeRepository, DuplicateRepository},
|
||||||
value_objects::{Checksum, DateTimeStamp, MetadataValue, StructuredData, SystemId},
|
value_objects::{Checksum, DateTimeStamp, MetadataValue, StructuredData, SystemId},
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@@ -452,3 +453,147 @@ impl DuplicateRepository for PostgresDuplicateRepository {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── DerivativeRepository ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct DerivativeRow {
|
||||||
|
derivative_id: Uuid,
|
||||||
|
parent_asset_id: Uuid,
|
||||||
|
profile_type: String,
|
||||||
|
storage_path: String,
|
||||||
|
mime_type: String,
|
||||||
|
file_size: i64,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
generation_status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn profile_from_str(s: &str) -> DerivativeProfile {
|
||||||
|
match s {
|
||||||
|
"thumbnail_square" => DerivativeProfile::ThumbnailSquare,
|
||||||
|
"thumbnail_large" => DerivativeProfile::ThumbnailLarge,
|
||||||
|
"web_optimized" => DerivativeProfile::WebOptimized,
|
||||||
|
"video_sd" => DerivativeProfile::VideoSd,
|
||||||
|
_ => DerivativeProfile::ThumbnailSquare,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn profile_to_str(p: &DerivativeProfile) -> &'static str {
|
||||||
|
match p {
|
||||||
|
DerivativeProfile::ThumbnailSquare => "thumbnail_square",
|
||||||
|
DerivativeProfile::ThumbnailLarge => "thumbnail_large",
|
||||||
|
DerivativeProfile::WebOptimized => "web_optimized",
|
||||||
|
DerivativeProfile::VideoSd => "video_sd",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_status_from_str(s: &str) -> GenerationStatus {
|
||||||
|
match s {
|
||||||
|
"pending" => GenerationStatus::Pending,
|
||||||
|
"ready" => GenerationStatus::Ready,
|
||||||
|
"failed" => GenerationStatus::Failed,
|
||||||
|
_ => GenerationStatus::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_status_to_str(s: &GenerationStatus) -> &'static str {
|
||||||
|
match s {
|
||||||
|
GenerationStatus::Pending => "pending",
|
||||||
|
GenerationStatus::Ready => "ready",
|
||||||
|
GenerationStatus::Failed => "failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DerivativeRow> for DerivativeAsset {
|
||||||
|
fn from(r: DerivativeRow) -> Self {
|
||||||
|
Self {
|
||||||
|
derivative_id: SystemId::from_uuid(r.derivative_id),
|
||||||
|
parent_asset_id: SystemId::from_uuid(r.parent_asset_id),
|
||||||
|
profile_type: profile_from_str(&r.profile_type),
|
||||||
|
storage_path: r.storage_path,
|
||||||
|
mime_type: r.mime_type,
|
||||||
|
file_size: r.file_size as u64,
|
||||||
|
dimensions: (r.width as u32, r.height as u32),
|
||||||
|
generation_status: gen_status_from_str(&r.generation_status),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pg_repo!(PostgresDerivativeRepository);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl DerivativeRepository for PostgresDerivativeRepository {
|
||||||
|
async fn find_by_asset(
|
||||||
|
&self,
|
||||||
|
asset_id: &SystemId,
|
||||||
|
) -> Result<Vec<DerivativeAsset>, DomainError> {
|
||||||
|
let rows = sqlx::query_as::<_, DerivativeRow>(
|
||||||
|
"SELECT derivative_id, parent_asset_id, profile_type, storage_path,
|
||||||
|
mime_type, file_size, width, height, generation_status
|
||||||
|
FROM derivatives WHERE parent_asset_id = $1",
|
||||||
|
)
|
||||||
|
.bind(*asset_id.as_uuid())
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_pg()?;
|
||||||
|
|
||||||
|
Ok(rows.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_by_asset_and_profile(
|
||||||
|
&self,
|
||||||
|
asset_id: &SystemId,
|
||||||
|
profile: DerivativeProfile,
|
||||||
|
) -> Result<Option<DerivativeAsset>, DomainError> {
|
||||||
|
let row = sqlx::query_as::<_, DerivativeRow>(
|
||||||
|
"SELECT derivative_id, parent_asset_id, profile_type, storage_path,
|
||||||
|
mime_type, file_size, width, height, generation_status
|
||||||
|
FROM derivatives WHERE parent_asset_id = $1 AND profile_type = $2",
|
||||||
|
)
|
||||||
|
.bind(*asset_id.as_uuid())
|
||||||
|
.bind(profile_to_str(&profile))
|
||||||
|
.fetch_optional(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_pg()?;
|
||||||
|
|
||||||
|
Ok(row.map(Into::into))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save(&self, d: &DerivativeAsset) -> Result<(), DomainError> {
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO derivatives (derivative_id, parent_asset_id, profile_type, storage_path,
|
||||||
|
mime_type, file_size, width, height, generation_status)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
|
ON CONFLICT (derivative_id) DO UPDATE SET
|
||||||
|
storage_path = EXCLUDED.storage_path,
|
||||||
|
mime_type = EXCLUDED.mime_type,
|
||||||
|
file_size = EXCLUDED.file_size,
|
||||||
|
width = EXCLUDED.width,
|
||||||
|
height = EXCLUDED.height,
|
||||||
|
generation_status = EXCLUDED.generation_status",
|
||||||
|
)
|
||||||
|
.bind(*d.derivative_id.as_uuid())
|
||||||
|
.bind(*d.parent_asset_id.as_uuid())
|
||||||
|
.bind(profile_to_str(&d.profile_type))
|
||||||
|
.bind(&d.storage_path)
|
||||||
|
.bind(&d.mime_type)
|
||||||
|
.bind(d.file_size as i64)
|
||||||
|
.bind(d.dimensions.0 as i32)
|
||||||
|
.bind(d.dimensions.1 as i32)
|
||||||
|
.bind(gen_status_to_str(&d.generation_status))
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_pg()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete(&self, id: &SystemId) -> Result<(), DomainError> {
|
||||||
|
sqlx::query("DELETE FROM derivatives WHERE derivative_id = $1")
|
||||||
|
.bind(*id.as_uuid())
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_pg()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
9
crates/adapters/thumbnail/Cargo.toml
Normal file
9
crates/adapters/thumbnail/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "adapters-thumbnail"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
domain = { workspace = true }
|
||||||
|
bytes = { workspace = true }
|
||||||
|
image = "0.25"
|
||||||
57
crates/adapters/thumbnail/src/lib.rs
Normal file
57
crates/adapters/thumbnail/src/lib.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
use bytes::Bytes;
|
||||||
|
use domain::{
|
||||||
|
errors::DomainError,
|
||||||
|
ports::{ThumbnailGeneratorPort, ThumbnailOutput},
|
||||||
|
};
|
||||||
|
use image::{DynamicImage, ImageFormat, load_from_memory};
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
pub struct ImageThumbnailGenerator;
|
||||||
|
|
||||||
|
impl ThumbnailGeneratorPort for ImageThumbnailGenerator {
|
||||||
|
fn generate(
|
||||||
|
&self,
|
||||||
|
source: &Bytes,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
format: &str,
|
||||||
|
) -> Result<ThumbnailOutput, DomainError> {
|
||||||
|
let img = load_from_memory(source)
|
||||||
|
.map_err(|e| DomainError::Internal(format!("failed to decode image: {e}")))?;
|
||||||
|
|
||||||
|
let thumb = img.thumbnail(width, height);
|
||||||
|
let (img_format, mime) = parse_format(format)?;
|
||||||
|
|
||||||
|
let encoded = encode(&thumb, img_format)?;
|
||||||
|
let actual_width = thumb.width();
|
||||||
|
let actual_height = thumb.height();
|
||||||
|
|
||||||
|
Ok(ThumbnailOutput {
|
||||||
|
bytes: Bytes::from(encoded),
|
||||||
|
width: actual_width,
|
||||||
|
height: actual_height,
|
||||||
|
mime_type: mime.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_format(s: &str) -> Result<(ImageFormat, &'static str), DomainError> {
|
||||||
|
match s {
|
||||||
|
"jpeg" | "jpg" => Ok((ImageFormat::Jpeg, "image/jpeg")),
|
||||||
|
"webp" => Ok((ImageFormat::WebP, "image/webp")),
|
||||||
|
"png" => Ok((ImageFormat::Png, "image/png")),
|
||||||
|
other => Err(DomainError::Validation(format!(
|
||||||
|
"unsupported thumbnail format: {other}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(img: &DynamicImage, format: ImageFormat) -> Result<Vec<u8>, DomainError> {
|
||||||
|
let mut buf = Cursor::new(Vec::new());
|
||||||
|
img.write_to(&mut buf, format)
|
||||||
|
.map_err(|e| DomainError::Internal(format!("failed to encode thumbnail: {e}")))?;
|
||||||
|
Ok(buf.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
66
crates/adapters/thumbnail/src/tests.rs
Normal file
66
crates/adapters/thumbnail/src/tests.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
use crate::ImageThumbnailGenerator;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use domain::ports::ThumbnailGeneratorPort as _;
|
||||||
|
|
||||||
|
fn make_test_png() -> Bytes {
|
||||||
|
use image::{ImageFormat, RgbImage};
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
let img = RgbImage::new(100, 200);
|
||||||
|
let mut buf = Cursor::new(Vec::new());
|
||||||
|
img.write_to(&mut buf, ImageFormat::Png).unwrap();
|
||||||
|
Bytes::from(buf.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generates_jpeg_thumbnail() {
|
||||||
|
let generator = ImageThumbnailGenerator;
|
||||||
|
let source = make_test_png();
|
||||||
|
|
||||||
|
let out = generator.generate(&source, 50, 50, "jpeg").unwrap();
|
||||||
|
|
||||||
|
assert!(out.width <= 50);
|
||||||
|
assert!(out.height <= 50);
|
||||||
|
assert_eq!(out.mime_type, "image/jpeg");
|
||||||
|
assert!(!out.bytes.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generates_webp_thumbnail() {
|
||||||
|
let generator = ImageThumbnailGenerator;
|
||||||
|
let source = make_test_png();
|
||||||
|
|
||||||
|
let out = generator.generate(&source, 30, 30, "webp").unwrap();
|
||||||
|
|
||||||
|
assert!(out.width <= 30);
|
||||||
|
assert!(out.height <= 30);
|
||||||
|
assert_eq!(out.mime_type, "image/webp");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preserves_aspect_ratio() {
|
||||||
|
let generator = ImageThumbnailGenerator;
|
||||||
|
let source = make_test_png(); // 100x200
|
||||||
|
|
||||||
|
let out = generator.generate(&source, 50, 50, "png").unwrap();
|
||||||
|
|
||||||
|
// 100x200 → fits in 50x50 → 25x50
|
||||||
|
assert_eq!(out.width, 25);
|
||||||
|
assert_eq!(out.height, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_unsupported_format() {
|
||||||
|
let generator = ImageThumbnailGenerator;
|
||||||
|
let source = make_test_png();
|
||||||
|
|
||||||
|
let result = generator.generate(&source, 50, 50, "bmp");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_garbage_input() {
|
||||||
|
let generator = ImageThumbnailGenerator;
|
||||||
|
let result = generator.generate(&Bytes::from_static(b"not an image"), 50, 50, "jpeg");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
@@ -81,3 +81,22 @@ pub trait DuplicateRepository: Send + Sync {
|
|||||||
pub trait MetadataExtractorPort: Send + Sync {
|
pub trait MetadataExtractorPort: Send + Sync {
|
||||||
fn extract(&self, bytes: &Bytes) -> Result<StructuredData, DomainError>;
|
fn extract(&self, bytes: &Bytes) -> Result<StructuredData, DomainError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- ThumbnailGeneratorPort ---
|
||||||
|
|
||||||
|
pub struct ThumbnailOutput {
|
||||||
|
pub bytes: Bytes,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub mime_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ThumbnailGeneratorPort: Send + Sync {
|
||||||
|
fn generate(
|
||||||
|
&self,
|
||||||
|
source: &Bytes,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
format: &str,
|
||||||
|
) -> Result<ThumbnailOutput, DomainError>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ adapters-storage = { workspace = true }
|
|||||||
adapters-nats = { workspace = true }
|
adapters-nats = { workspace = true }
|
||||||
event-transport = { workspace = true }
|
event-transport = { workspace = true }
|
||||||
adapters-exif = { workspace = true }
|
adapters-exif = { workspace = true }
|
||||||
|
adapters-thumbnail = { workspace = true }
|
||||||
async-nats = { workspace = true }
|
async-nats = { workspace = true }
|
||||||
|
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use adapters_postgres::{
|
use adapters_postgres::{
|
||||||
PostgresAssetMetadataRepository, PostgresAssetRepository, PostgresJobBatchRepository,
|
PostgresAssetMetadataRepository, PostgresAssetRepository, PostgresDerivativeRepository,
|
||||||
PostgresJobRepository, PostgresPipelineRepository, PostgresPluginRepository,
|
PostgresJobBatchRepository, PostgresJobRepository, PostgresPipelineRepository,
|
||||||
PostgresSidecarRepository,
|
PostgresPluginRepository, PostgresSidecarRepository,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ pub struct Repos {
|
|||||||
pub plugin: Arc<PostgresPluginRepository>,
|
pub plugin: Arc<PostgresPluginRepository>,
|
||||||
pub asset: Arc<PostgresAssetRepository>,
|
pub asset: Arc<PostgresAssetRepository>,
|
||||||
pub metadata: Arc<PostgresAssetMetadataRepository>,
|
pub metadata: Arc<PostgresAssetMetadataRepository>,
|
||||||
|
pub derivative: Arc<PostgresDerivativeRepository>,
|
||||||
pub sidecar: Arc<PostgresSidecarRepository>,
|
pub sidecar: Arc<PostgresSidecarRepository>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ impl Repos {
|
|||||||
plugin: Arc::new(PostgresPluginRepository::new(pool.clone())),
|
plugin: Arc::new(PostgresPluginRepository::new(pool.clone())),
|
||||||
asset: Arc::new(PostgresAssetRepository::new(pool.clone())),
|
asset: Arc::new(PostgresAssetRepository::new(pool.clone())),
|
||||||
metadata: Arc::new(PostgresAssetMetadataRepository::new(pool.clone())),
|
metadata: Arc::new(PostgresAssetMetadataRepository::new(pool.clone())),
|
||||||
|
derivative: Arc::new(PostgresDerivativeRepository::new(pool.clone())),
|
||||||
sidecar: Arc::new(PostgresSidecarRepository::new(pool)),
|
sidecar: Arc::new(PostgresSidecarRepository::new(pool)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use crate::plugin_registry::InMemoryPluginRegistry;
|
use crate::plugin_registry::InMemoryPluginRegistry;
|
||||||
use crate::plugins::{MetadataExtractorPlugin, NoOpPlugin, SidecarSyncPlugin};
|
use crate::plugins::{
|
||||||
use domain::ports::{MetadataExtractorPort, SidecarWriterPort};
|
MetadataExtractorPlugin, NoOpPlugin, SidecarSyncPlugin, ThumbnailGeneratorPlugin,
|
||||||
|
};
|
||||||
|
use domain::ports::{MetadataExtractorPort, SidecarWriterPort, ThumbnailGeneratorPort};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::Repos;
|
use super::Repos;
|
||||||
@@ -10,16 +12,23 @@ pub fn build_plugin_registry(
|
|||||||
file_storage: Arc<dyn domain::ports::FileStoragePort>,
|
file_storage: Arc<dyn domain::ports::FileStoragePort>,
|
||||||
sidecar_writer: Arc<dyn SidecarWriterPort>,
|
sidecar_writer: Arc<dyn SidecarWriterPort>,
|
||||||
extractor: Arc<dyn MetadataExtractorPort>,
|
extractor: Arc<dyn MetadataExtractorPort>,
|
||||||
|
thumbnail_gen: Arc<dyn ThumbnailGeneratorPort>,
|
||||||
) -> InMemoryPluginRegistry {
|
) -> InMemoryPluginRegistry {
|
||||||
let mut registry = InMemoryPluginRegistry::new();
|
let mut registry = InMemoryPluginRegistry::new();
|
||||||
|
|
||||||
registry.register(Arc::new(NoOpPlugin));
|
registry.register(Arc::new(NoOpPlugin));
|
||||||
registry.register(Arc::new(MetadataExtractorPlugin::new(
|
registry.register(Arc::new(MetadataExtractorPlugin::new(
|
||||||
repos.asset.clone(),
|
repos.asset.clone(),
|
||||||
file_storage,
|
file_storage.clone(),
|
||||||
repos.metadata.clone(),
|
repos.metadata.clone(),
|
||||||
extractor,
|
extractor,
|
||||||
)));
|
)));
|
||||||
|
registry.register(Arc::new(ThumbnailGeneratorPlugin::new(
|
||||||
|
repos.asset.clone(),
|
||||||
|
file_storage,
|
||||||
|
repos.derivative.clone(),
|
||||||
|
thumbnail_gen,
|
||||||
|
)));
|
||||||
|
|
||||||
let export_handler = Arc::new(application::sidecar::ExportSidecarHandler::new(
|
let export_handler = Arc::new(application::sidecar::ExportSidecarHandler::new(
|
||||||
repos.metadata.clone(),
|
repos.metadata.clone(),
|
||||||
|
|||||||
@@ -53,11 +53,14 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let extractor: Arc<dyn domain::ports::MetadataExtractorPort> =
|
let extractor: Arc<dyn domain::ports::MetadataExtractorPort> =
|
||||||
Arc::new(adapters_exif::NomExifExtractor);
|
Arc::new(adapters_exif::NomExifExtractor);
|
||||||
|
let thumbnail_gen: Arc<dyn domain::ports::ThumbnailGeneratorPort> =
|
||||||
|
Arc::new(adapters_thumbnail::ImageThumbnailGenerator);
|
||||||
let registry = Arc::new(build_plugin_registry(
|
let registry = Arc::new(build_plugin_registry(
|
||||||
&repos,
|
&repos,
|
||||||
file_storage,
|
file_storage,
|
||||||
sidecar_writer,
|
sidecar_writer,
|
||||||
extractor,
|
extractor,
|
||||||
|
thumbnail_gen,
|
||||||
));
|
));
|
||||||
let process_next = Arc::new(build_process_next_handler(
|
let process_next = Arc::new(build_process_next_handler(
|
||||||
&repos,
|
&repos,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
pub mod metadata_extractor;
|
pub mod metadata_extractor;
|
||||||
pub mod no_op;
|
pub mod no_op;
|
||||||
pub mod sidecar_sync;
|
pub mod sidecar_sync;
|
||||||
|
pub mod thumbnail_generator;
|
||||||
|
|
||||||
pub use metadata_extractor::MetadataExtractorPlugin;
|
pub use metadata_extractor::MetadataExtractorPlugin;
|
||||||
pub use no_op::NoOpPlugin;
|
pub use no_op::NoOpPlugin;
|
||||||
pub use sidecar_sync::SidecarSyncPlugin;
|
pub use sidecar_sync::SidecarSyncPlugin;
|
||||||
|
pub use thumbnail_generator::ThumbnailGeneratorPlugin;
|
||||||
|
|||||||
138
crates/worker/src/plugins/thumbnail_generator.rs
Normal file
138
crates/worker/src/plugins/thumbnail_generator.rs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use domain::{
|
||||||
|
entities::{DerivativeAsset, DerivativeProfile},
|
||||||
|
errors::DomainError,
|
||||||
|
ports::{
|
||||||
|
AssetRepository, DerivativeRepository, FileStoragePort, PluginExecutor,
|
||||||
|
ThumbnailGeneratorPort,
|
||||||
|
},
|
||||||
|
value_objects::{MetadataValue, StructuredData, SystemId},
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
pub struct ThumbnailGeneratorPlugin {
|
||||||
|
asset_repo: Arc<dyn AssetRepository>,
|
||||||
|
file_storage: Arc<dyn FileStoragePort>,
|
||||||
|
derivative_repo: Arc<dyn DerivativeRepository>,
|
||||||
|
thumbnail_gen: Arc<dyn ThumbnailGeneratorPort>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThumbnailGeneratorPlugin {
|
||||||
|
pub fn new(
|
||||||
|
asset_repo: Arc<dyn AssetRepository>,
|
||||||
|
file_storage: Arc<dyn FileStoragePort>,
|
||||||
|
derivative_repo: Arc<dyn DerivativeRepository>,
|
||||||
|
thumbnail_gen: Arc<dyn ThumbnailGeneratorPort>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
asset_repo,
|
||||||
|
file_storage,
|
||||||
|
derivative_repo,
|
||||||
|
thumbnail_gen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_profile(s: &str) -> DerivativeProfile {
|
||||||
|
match s {
|
||||||
|
"ThumbnailLarge" => DerivativeProfile::ThumbnailLarge,
|
||||||
|
"WebOptimized" => DerivativeProfile::WebOptimized,
|
||||||
|
"VideoSd" => DerivativeProfile::VideoSd,
|
||||||
|
_ => DerivativeProfile::ThumbnailSquare,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_extension(format: &str) -> &str {
|
||||||
|
match format {
|
||||||
|
"jpeg" | "jpg" => "jpg",
|
||||||
|
"png" => "png",
|
||||||
|
_ => "webp",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl PluginExecutor for ThumbnailGeneratorPlugin {
|
||||||
|
fn plugin_name(&self) -> &str {
|
||||||
|
"thumbnail_generator"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
asset_id: Option<SystemId>,
|
||||||
|
_payload: &StructuredData,
|
||||||
|
config: &StructuredData,
|
||||||
|
) -> Result<StructuredData, DomainError> {
|
||||||
|
let asset_id = asset_id.ok_or_else(|| {
|
||||||
|
DomainError::Validation("thumbnail_generator requires asset_id".into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let width = config
|
||||||
|
.get_string("width")
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
.unwrap_or(300u32);
|
||||||
|
let height = config
|
||||||
|
.get_string("height")
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
.unwrap_or(300u32);
|
||||||
|
let format = config.get_string("format").unwrap_or("webp");
|
||||||
|
let profile = config
|
||||||
|
.get_string("profile")
|
||||||
|
.map(parse_profile)
|
||||||
|
.unwrap_or(DerivativeProfile::ThumbnailSquare);
|
||||||
|
|
||||||
|
let asset = self
|
||||||
|
.asset_repo
|
||||||
|
.find_by_id(&asset_id)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| DomainError::NotFound(format!("Asset {} not found", asset_id)))?;
|
||||||
|
|
||||||
|
if !asset.mime_type.starts_with("image/") {
|
||||||
|
return Ok(StructuredData::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_bytes = self
|
||||||
|
.file_storage
|
||||||
|
.read_file(&asset.source_reference.relative_path)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let output = self
|
||||||
|
.thumbnail_gen
|
||||||
|
.generate(&source_bytes, width, height, format)?;
|
||||||
|
|
||||||
|
let ext = format_extension(format);
|
||||||
|
let storage_path = format!("derivatives/{asset_id}_{profile:?}.{ext}");
|
||||||
|
|
||||||
|
let byte_len = output.bytes.len() as u64;
|
||||||
|
self.file_storage
|
||||||
|
.store_file(&storage_path, output.bytes)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut derivative = match self
|
||||||
|
.derivative_repo
|
||||||
|
.find_by_asset_and_profile(&asset_id, profile)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Some(d) => d,
|
||||||
|
None => DerivativeAsset::new_pending(asset_id, profile, &storage_path),
|
||||||
|
};
|
||||||
|
|
||||||
|
derivative.storage_path = storage_path.clone();
|
||||||
|
derivative.mark_ready(&output.mime_type, byte_len, (output.width, output.height));
|
||||||
|
self.derivative_repo.save(&derivative).await?;
|
||||||
|
|
||||||
|
let mut result = StructuredData::new();
|
||||||
|
result.insert("thumbnail_path", MetadataValue::String(storage_path));
|
||||||
|
result.insert(
|
||||||
|
"thumbnail_width",
|
||||||
|
MetadataValue::Integer(output.width as i64),
|
||||||
|
);
|
||||||
|
result.insert(
|
||||||
|
"thumbnail_height",
|
||||||
|
MetadataValue::Integer(output.height as i64),
|
||||||
|
);
|
||||||
|
|
||||||
|
info!(asset_id = %asset_id, w = output.width, h = output.height, "thumbnail generated");
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user