diff --git a/Cargo.lock b/Cargo.lock index 06027c7..9bf74fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,7 +131,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2 0.5.5", + "socket2 0.5.6", "tokio", "tokio-uring", "tracing", @@ -210,7 +210,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.5.5", + "socket2 0.5.6", "time", "url", ] @@ -269,9 +269,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" dependencies = [ "anstyle", "anstyle-parse", @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "async-stream" @@ -340,7 +340,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -351,7 +351,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -478,11 +478,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blurhash-update" +version = "0.1.0" +source = "git+https://git.asonix.dog/asonix/blurhash-update#82f94e7f762ad8594b18ea9b238b5b276ade4afc" + [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "byteorder" @@ -507,12 +512,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "3286b845d0fccbdd15af433f61c5970e711987036cb468f437ff6badd70f4e24" [[package]] name = "cfg-if" @@ -522,21 +524,21 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] name = "clap" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", "clap_derive", @@ -544,9 +546,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstream", "anstyle", @@ -556,21 +558,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "color-eyre" @@ -697,9 +699,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -845,7 +847,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -857,7 +859,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -866,7 +868,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -882,9 +884,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "encoding_rs" @@ -1025,7 +1027,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -1113,7 +1115,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.2", + "indexmap 2.2.3", "slab", "tokio", "tokio-util", @@ -1126,20 +1128,14 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", +] [[package]] name = "hdrhistogram" @@ -1162,9 +1158,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" [[package]] name = "hex" @@ -1238,7 +1234,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -1328,9 +1324,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1387,9 +1383,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -1484,9 +1480,9 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "metrics" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b9e10a211c839210fd7f99954bda26e5f8e26ec686ad68da6a32df7c80e782" +checksum = "cd71d9db2e4287c3407fa04378b8c2ee570aebe0854431562cdd89ca091854f4" dependencies = [ "ahash", "portable-atomic", @@ -1494,13 +1490,13 @@ dependencies = [ [[package]] name = "metrics-exporter-prometheus" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a4c4718a371ddfb7806378f23617876eea8b82e5ff1324516bcd283249d9ea" +checksum = "9bf4e7146e30ad172c42c39b3246864bd2d3c6396780711a1baf749cfe423e21" dependencies = [ "base64", "hyper", - "indexmap 1.9.3", + "indexmap 2.2.3", "ipnet", "metrics", "metrics-util", @@ -1511,13 +1507,13 @@ dependencies = [ [[package]] name = "metrics-util" -version = "0.16.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2670b8badcc285d486261e2e9f1615b506baff91427b61bd336a472b65bbf5ed" +checksum = "ece71ab046dcf45604e573329966ec1db5ff4b81cfa170a924ff4c959ab5451a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.13.1", + "hashbrown 0.14.3", "metrics", "num_cpus", "quanta", @@ -1610,9 +1606,9 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -1650,7 +1646,7 @@ checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" dependencies = [ "futures-core", "futures-sink", - "indexmap 2.2.2", + "indexmap 2.2.3", "js-sys", "once_cell", "pin-project-lite", @@ -1844,6 +1840,7 @@ dependencies = [ "async-trait", "barrel", "base64", + "blurhash-update", "clap", "color-eyre", "config", @@ -1870,7 +1867,7 @@ dependencies = [ "reqwest-tracing", "rustls 0.22.2", "rustls-channel-resolver", - "rustls-pemfile 2.0.0", + "rustls-pemfile 2.1.0", "rusty-s3", "serde", "serde-tuple-vec-map", @@ -1917,7 +1914,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2049,7 +2046,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2194,7 +2191,7 @@ dependencies = [ "quote", "refinery-core", "regex", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2324,16 +2321,17 @@ checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2419,9 +2417,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" dependencies = [ "base64", "rustls-pki-types", @@ -2429,9 +2427,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a716eb65e3158e90e17cd93d855216e27bde02745ab842f2cab4a39dba1bacf" +checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" [[package]] name = "rustls-webpki" @@ -2481,9 +2479,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -2528,15 +2526,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -2552,20 +2550,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -2713,12 +2711,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2769,9 +2767,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "subtle" @@ -2792,9 +2790,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -2839,29 +2837,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -2927,7 +2925,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2 0.5.6", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -2951,7 +2949,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2974,7 +2972,7 @@ dependencies = [ "postgres-protocol", "postgres-types", "rand", - "socket2 0.5.5", + "socket2 0.5.6", "tokio", "tokio-util", "whoami", @@ -3058,9 +3056,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" dependencies = [ "serde", "serde_spanned", @@ -3079,11 +3077,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.3", "serde", "serde_spanned", "toml_datetime", @@ -3212,7 +3210,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -3330,9 +3328,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -3417,9 +3415,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3427,24 +3425,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", @@ -3454,9 +3452,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3464,22 +3462,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "wasm-streams" @@ -3496,9 +3494,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -3576,7 +3574,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -3594,7 +3592,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -3614,17 +3612,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", ] [[package]] @@ -3635,9 +3633,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" [[package]] name = "windows_aarch64_msvc" @@ -3647,9 +3645,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" [[package]] name = "windows_i686_gnu" @@ -3659,9 +3657,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" [[package]] name = "windows_i686_msvc" @@ -3671,9 +3669,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" [[package]] name = "windows_x86_64_gnu" @@ -3683,9 +3681,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" [[package]] name = "windows_x86_64_gnullvm" @@ -3695,9 +3693,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" [[package]] name = "windows_x86_64_msvc" @@ -3707,15 +3705,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" [[package]] name = "winnow" -version = "0.5.37" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cad8365489051ae9f054164e459304af2e7e9bb407c958076c8bf4aef52da5" +checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178" dependencies = [ "memchr", ] @@ -3775,7 +3773,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -3795,5 +3793,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] diff --git a/Cargo.toml b/Cargo.toml index 25e45c2..7068759 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ actix-web = { version = "4.0.0", default-features = false, features = ["rustls-0 async-trait = "0.1.51" barrel = { version = "0.7.0", features = ["pg"] } base64 = "0.21.0" +blurhash-update = { git = "https://git.asonix.dog/asonix/blurhash-update", version = "0.1.0" } clap = { version = "4.0.2", features = ["derive"] } color-eyre = "0.6" config = { version = "0.14.0", default-features = false, features = ["json", "ron", "toml", "yaml"] } diff --git a/src/blurhash.rs b/src/blurhash.rs new file mode 100644 index 0000000..3c22cf9 --- /dev/null +++ b/src/blurhash.rs @@ -0,0 +1,105 @@ +use std::ffi::{OsStr, OsString}; + +use tokio::io::AsyncReadExt; + +use crate::{ + details::Details, + error::{Error, UploadError}, + formats::ProcessableFormat, + magick::{MagickError, MAGICK_CONFIGURE_PATH, MAGICK_TEMPORARY_PATH}, + process::Process, + repo::Alias, + state::State, + store::Store, +}; + +pub(crate) async fn generate( + state: &State, + alias: &Alias, + original_details: &Details, +) -> Result +where + S: Store + 'static, +{ + let hash = state + .repo + .hash(alias) + .await? + .ok_or(UploadError::MissingIdentifier)?; + + let identifier = if original_details.is_video() { + crate::generate::ensure_motion_identifier(state, hash, original_details).await? + } else { + state + .repo + .identifier(hash) + .await? + .ok_or(UploadError::MissingIdentifier)? + }; + + let input_details = crate::ensure_details_identifier(state, &identifier).await?; + + let stream = state.store.to_stream(&identifier, None, None).await?; + + let blurhash = read_rgba_command( + state, + input_details + .internal_format() + .processable_format() + .expect("not a video"), + ) + .await? + .drive_with_stream(stream) + .with_stdout(|mut stdout| async move { + let mut encoder = blurhash_update::Encoder::auto(blurhash_update::ImageBounds { + width: input_details.width() as _, + height: input_details.height() as _, + }); + + let mut buf = [0u8; 1024 * 8]; + + loop { + let n = stdout.read(&mut buf).await?; + + if n == 0 { + break; + } + + encoder.update(&buf[..n]); + } + + Ok(encoder.finalize()) as std::io::Result + }) + .await??; + + Ok(blurhash) +} + +async fn read_rgba_command( + state: &State, + input_format: ProcessableFormat, +) -> Result { + let temporary_path = state + .tmp_dir + .tmp_folder() + .await + .map_err(MagickError::CreateTemporaryDirectory)?; + + let mut input_arg = OsString::from(input_format.magick_format()); + input_arg.push(":-"); + if input_format.coalesce() { + input_arg.push("[0]"); + } + + let args: [&OsStr; 3] = ["convert".as_ref(), &input_arg, "RGBA:-".as_ref()]; + + let envs = [ + (MAGICK_TEMPORARY_PATH, temporary_path.as_os_str()), + (MAGICK_CONFIGURE_PATH, state.policy_dir.as_os_str()), + ]; + + let process = Process::run("magick", &args, &envs, state.config.media.process_timeout)? + .add_extras(temporary_path); + + Ok(process) +} diff --git a/src/details.rs b/src/details.rs index 90a7333..f49240c 100644 --- a/src/details.rs +++ b/src/details.rs @@ -100,6 +100,14 @@ impl Details { )) } + pub(crate) fn width(&self) -> u16 { + self.inner.width + } + + pub(crate) fn height(&self) -> u16 { + self.inner.height + } + pub(crate) fn internal_format(&self) -> InternalFormat { self.inner.format } @@ -112,6 +120,10 @@ impl Details { self.inner.created_at.into() } + pub(crate) fn is_video(&self) -> bool { + matches!(self.inner.format, InternalFormat::Video(_)) + } + pub(crate) fn video_format(&self) -> Option { match self.inner.format { InternalFormat::Video(format) => Some(format), diff --git a/src/discover/exiftool.rs b/src/discover/exiftool.rs index 2549827..de0b7e3 100644 --- a/src/discover/exiftool.rs +++ b/src/discover/exiftool.rs @@ -1,5 +1,3 @@ - - use crate::{ bytes_stream::BytesStream, exiftool::ExifError, @@ -43,7 +41,7 @@ pub(super) async fn check_reorient( #[tracing::instrument(level = "trace", skip_all)] async fn needs_reorienting(input: BytesStream, timeout: u64) -> Result { let buf = Process::run("exiftool", &["-n", "-Orientation", "-"], &[], timeout)? - .bytes_stream_read(input) + .drive_with_async_read(input.into_reader()) .into_string() .await?; diff --git a/src/discover/ffmpeg.rs b/src/discover/ffmpeg.rs index f4d3d19..e9418be 100644 --- a/src/discover/ffmpeg.rs +++ b/src/discover/ffmpeg.rs @@ -14,7 +14,6 @@ use crate::{ state::State, }; - use super::Discovery; const MP4: &str = "mp4"; @@ -158,24 +157,6 @@ struct Flags { alpha: usize, } -#[tracing::instrument(skip_all)] -pub(super) async fn discover_bytes_stream( - state: &State, - bytes: BytesStream, -) -> Result, FfMpegError> { - discover_file(state, move |mut file| { - let bytes = bytes.clone(); - - async move { - file.write_from_stream(bytes.into_io_stream()) - .await - .map_err(FfMpegError::Write)?; - Ok(file) - } - }) - .await -} - async fn allows_alpha(pixel_format: &str, timeout: u64) -> Result { static ALPHA_PIXEL_FORMATS: OnceLock> = OnceLock::new(); @@ -191,45 +172,31 @@ async fn allows_alpha(pixel_format: &str, timeout: u64) -> Result(state: &State, f: F) -> Result, FfMpegError> -where - F: FnOnce(crate::file::File) -> Fut, - Fut: std::future::Future>, -{ - let input_file = state.tmp_dir.tmp_file(None); - crate::store::file_store::safe_create_parent(&input_file) - .await - .map_err(FfMpegError::CreateDir)?; - - let tmp_one = crate::file::File::create(&input_file) - .await - .map_err(FfMpegError::CreateFile)?; - let tmp_one = (f)(tmp_one).await?; - tmp_one.close().await.map_err(FfMpegError::CloseFile)?; - +pub(super) async fn discover_bytes_stream( + state: &State, + bytes: BytesStream, +) -> Result, FfMpegError> { let res = Process::run( "ffprobe", &[ - "-v".as_ref(), - "quiet".as_ref(), - "-count_frames".as_ref(), - "-show_entries".as_ref(), - "stream=width,height,nb_read_frames,codec_name,pix_fmt:format=format_name".as_ref(), - "-of".as_ref(), - "default=noprint_wrappers=1:nokey=1".as_ref(), - "-print_format".as_ref(), - "json".as_ref(), - input_file.as_os_str(), + "-v", + "quiet", + "-count_frames", + "-show_entries", + "stream=width,height,nb_read_frames,codec_name,pix_fmt:format=format_name", + "-of", + "default=noprint_wrappers=1:nokey=1", + "-print_format", + "json", + "-", ], &[], state.config.media.process_timeout, )? - .read() + .drive_with_async_read(bytes.into_reader()) .into_vec() .await; - input_file.cleanup().await.map_err(FfMpegError::Cleanup)?; - let output = res?; let output: FfMpegDiscovery = serde_json::from_slice(&output).map_err(FfMpegError::Json)?; diff --git a/src/discover/magick.rs b/src/discover/magick.rs index fb77356..e4a7f37 100644 --- a/src/discover/magick.rs +++ b/src/discover/magick.rs @@ -1,8 +1,6 @@ #[cfg(test)] mod tests; - - use crate::{ bytes_stream::BytesStream, discover::DiscoverError, @@ -50,39 +48,17 @@ pub(super) async fn confirm_bytes_stream( } } - discover_file(state, move |mut file| async move { - file.write_from_stream(bytes.into_io_stream()) - .await - .map_err(MagickError::Write)?; - - Ok(file) - }) - .await + discover(state, bytes).await } #[tracing::instrument(level = "debug", skip_all)] -async fn discover_file(state: &State, f: F) -> Result -where - F: FnOnce(crate::file::File) -> Fut, - Fut: std::future::Future>, -{ +async fn discover(state: &State, stream: BytesStream) -> Result { let temporary_path = state .tmp_dir .tmp_folder() .await .map_err(MagickError::CreateTemporaryDirectory)?; - let input_file = state.tmp_dir.tmp_file(None); - crate::store::file_store::safe_create_parent(&input_file) - .await - .map_err(MagickError::CreateDir)?; - - let tmp_one = crate::file::File::create(&input_file) - .await - .map_err(MagickError::CreateFile)?; - let tmp_one = (f)(tmp_one).await?; - tmp_one.close().await.map_err(MagickError::CloseFile)?; - let envs = [ (MAGICK_TEMPORARY_PATH, temporary_path.as_os_str()), (MAGICK_CONFIGURE_PATH, state.policy_dir.as_os_str()), @@ -91,19 +67,16 @@ where let res = Process::run( "magick", &[ - "convert".as_ref(), - // "-ping".as_ref(), // re-enable -ping after imagemagick fix - input_file.as_os_str(), - "JSON:".as_ref(), + "convert", // "-ping".as_ref(), // re-enable -ping after imagemagick fix + "-", "JSON:", ], &envs, state.config.media.process_timeout, )? - .read() + .drive_with_async_read(stream.into_reader()) .into_string() .await; - input_file.cleanup().await.map_err(MagickError::Cleanup)?; temporary_path .cleanup() .await diff --git a/src/exiftool.rs b/src/exiftool.rs index 027b53b..2db938d 100644 --- a/src/exiftool.rs +++ b/src/exiftool.rs @@ -1,9 +1,4 @@ -use crate::{ - bytes_stream::BytesStream, - error_code::ErrorCode, - process::{Process, ProcessError, ProcessRead}, -}; - +use crate::{error_code::ErrorCode, process::ProcessError}; #[derive(Debug, thiserror::Error)] pub(crate) enum ExifError { @@ -38,23 +33,3 @@ impl ExifError { } } } - -#[tracing::instrument(level = "trace", skip(input))] -pub(crate) async fn needs_reorienting(timeout: u64, input: BytesStream) -> Result { - let buf = Process::run("exiftool", &["-n", "-Orientation", "-"], &[], timeout)? - .bytes_stream_read(input) - .into_string() - .await?; - - Ok(!buf.is_empty()) -} - -#[tracing::instrument(level = "trace", skip(input))] -pub(crate) fn clear_metadata_bytes_read( - timeout: u64, - input: BytesStream, -) -> Result { - let process = Process::run("exiftool", &["-all=", "-", "-out", "-"], &[], timeout)?; - - Ok(process.bytes_stream_read(input)) -} diff --git a/src/generate.rs b/src/generate.rs index fdb209d..1f01191 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -117,20 +117,15 @@ async fn process( let stream = state.store.to_stream(&identifier, None, None).await?; - let bytes = crate::magick::process_image_stream_read( - state, - stream, - thumbnail_args, - input_format, - format, - quality, - ) - .await? - .into_bytes_stream() - .instrument(tracing::info_span!( - "Reading processed image to BytesStream" - )) - .await?; + let bytes = + crate::magick::process_image_command(state, thumbnail_args, input_format, format, quality) + .await? + .drive_with_stream(stream) + .into_bytes_stream() + .instrument(tracing::info_span!( + "Reading processed image to BytesStream" + )) + .await?; drop(permit); @@ -160,48 +155,37 @@ async fn process( Ok((details, identifier)) as Result<(Details, Arc), Error> } -#[tracing::instrument(skip_all)] -async fn input_identifier( +pub(crate) async fn ensure_motion_identifier( state: &State, - output_format: InputProcessableFormat, hash: Hash, original_details: &Details, ) -> Result, Error> where S: Store + 'static, { - let should_thumbnail = - if let Some(input_format) = original_details.internal_format().processable_format() { - let output_format = input_format.process_to(output_format); + if let Some(identifier) = state.repo.motion_identifier(hash.clone()).await? { + return Ok(identifier); + }; - input_format.should_thumbnail(output_format) - } else { - // video case - true - }; + let identifier = state + .repo + .identifier(hash.clone()) + .await? + .ok_or(UploadError::MissingIdentifier)?; - if should_thumbnail { - if let Some(identifier) = state.repo.motion_identifier(hash.clone()).await? { - return Ok(identifier); - }; - - let identifier = state - .repo - .identifier(hash.clone()) - .await? - .ok_or(UploadError::MissingIdentifier)?; - - let (reader, media_type) = if let Some(processable_format) = - original_details.internal_format().processable_format() - { + let (reader, media_type) = + if let Some(processable_format) = original_details.internal_format().processable_format() { let thumbnail_format = state.config.media.image.format.unwrap_or(ImageFormat::Webp); let stream = state.store.to_stream(&identifier, None, None).await?; - let reader = - magick::thumbnail(state, stream, processable_format, thumbnail_format).await?; + let process = + magick::thumbnail_command(state, processable_format, thumbnail_format).await?; - (reader, thumbnail_format.media_type()) + ( + process.drive_with_stream(stream), + thumbnail_format.media_type(), + ) } else { let thumbnail_format = match state.config.media.image.format { Some(ImageFormat::Webp | ImageFormat::Avif | ImageFormat::Jxl) => { @@ -224,16 +208,40 @@ where (reader, thumbnail_format.media_type()) }; - let motion_identifier = reader - .with_stdout(|stdout| async { state.store.save_async_read(stdout, media_type).await }) - .await??; + let motion_identifier = reader + .with_stdout(|stdout| async { state.store.save_async_read(stdout, media_type).await }) + .await??; - state - .repo - .relate_motion_identifier(hash, &motion_identifier) - .await?; + state + .repo + .relate_motion_identifier(hash, &motion_identifier) + .await?; - return Ok(motion_identifier); + Ok(motion_identifier) +} + +#[tracing::instrument(skip_all)] +async fn input_identifier( + state: &State, + output_format: InputProcessableFormat, + hash: Hash, + original_details: &Details, +) -> Result, Error> +where + S: Store + 'static, +{ + let should_thumbnail = + if let Some(input_format) = original_details.internal_format().processable_format() { + let output_format = input_format.process_to(output_format); + + input_format.should_thumbnail(output_format) + } else { + // video case + true + }; + + if should_thumbnail { + return ensure_motion_identifier(state, hash.clone(), original_details).await; } state diff --git a/src/generate/magick.rs b/src/generate/magick.rs index b753261..2796e0c 100644 --- a/src/generate/magick.rs +++ b/src/generate/magick.rs @@ -1,25 +1,17 @@ use std::ffi::OsStr; -use actix_web::web::Bytes; - use crate::{ formats::{ImageFormat, ProcessableFormat}, magick::{MagickError, MAGICK_CONFIGURE_PATH, MAGICK_TEMPORARY_PATH}, - process::{Process, ProcessRead}, + process::Process, state::State, - stream::LocalBoxStream, }; -async fn thumbnail_animation( +pub(super) async fn thumbnail_command( state: &State, input_format: ProcessableFormat, thumbnail_format: ImageFormat, - write_file: F, -) -> Result -where - F: FnOnce(crate::file::File) -> Fut, - Fut: std::future::Future>, -{ +) -> Result { let format = ProcessableFormat::Image(thumbnail_format); let quality = state.config.media.image.quality_for(thumbnail_format); @@ -29,22 +21,7 @@ where .await .map_err(MagickError::CreateTemporaryDirectory)?; - let input_file = state.tmp_dir.tmp_file(None); - crate::store::file_store::safe_create_parent(&input_file) - .await - .map_err(MagickError::CreateDir)?; - - let tmp_one = crate::file::File::create(&input_file) - .await - .map_err(MagickError::CreateFile)?; - let tmp_one = (write_file)(tmp_one).await?; - tmp_one.close().await.map_err(MagickError::CloseFile)?; - - let input_arg = [ - input_format.magick_format().as_ref(), - input_file.as_os_str(), - ] - .join(":".as_ref()); + let input_arg = format!("{}:-", input_format.magick_format()); let output_arg = format!("{}:-", format.magick_format()); let quality = quality.map(|q| q.to_string()); @@ -52,7 +29,7 @@ where let mut args: Vec<&OsStr> = Vec::with_capacity(len); args.push("convert".as_ref()); - args.push(&input_arg); + args.push(input_arg.as_ref()); if format.coalesce() { args.push("-coalesce".as_ref()); } @@ -66,31 +43,8 @@ where (MAGICK_CONFIGURE_PATH, state.policy_dir.as_os_str()), ]; - let reader = Process::run("magick", &args, &envs, state.config.media.process_timeout)? - .read() - .add_extras(input_file) + let process = Process::run("magick", &args, &envs, state.config.media.process_timeout)? .add_extras(temporary_path); - Ok(reader) -} - -pub(super) async fn thumbnail( - state: &State, - stream: LocalBoxStream<'static, std::io::Result>, - input_format: ProcessableFormat, - thumbnail_format: ImageFormat, -) -> Result { - thumbnail_animation( - state, - input_format, - thumbnail_format, - |mut tmp_file| async move { - tmp_file - .write_from_stream(stream) - .await - .map_err(MagickError::Write)?; - Ok(tmp_file) - }, - ) - .await + Ok(process) } diff --git a/src/ingest.rs b/src/ingest.rs index 8ab486d..e5aa8eb 100644 --- a/src/ingest.rs +++ b/src/ingest.rs @@ -69,15 +69,11 @@ where } }; - crate::magick::process_image_process_read( - state, - process_read, - magick_args, - format, - format, - quality, - ) - .await? + let process = + crate::magick::process_image_command(state, magick_args, format, format, quality) + .await?; + + process_read.pipe(process) } else { process_read } diff --git a/src/lib.rs b/src/lib.rs index 44bf807..c9d9a26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ mod backgrounded; +mod blurhash; mod bytes_stream; mod concurrent_processor; mod config; @@ -1277,6 +1278,44 @@ fn srv_head( builder } +async fn blurhash( + web::Query(alias_query): web::Query, + state: web::Data>, +) -> Result { + let alias = match alias_query { + AliasQuery::Alias { alias } => Serde::into_inner(alias), + AliasQuery::Proxy { proxy } => { + let alias = if let Some(alias) = state.repo.related(proxy.clone()).await? { + alias + } else if !state.config.server.read_only { + let stream = download_stream(proxy.as_str(), &state).await?; + + let (alias, _, _) = ingest_inline(stream, &state).await?; + + state.repo.relate_url(proxy, alias.clone()).await?; + + alias + } else { + return Err(UploadError::ReadOnly.into()); + }; + + if !state.config.server.read_only { + state.repo.accessed_alias(alias.clone()).await?; + } + + alias + } + }; + + let details = ensure_details(&state, &alias).await?; + let blurhash = blurhash::generate(&state, &alias, &details).await?; + + Ok(HttpResponse::Ok().json(serde_json::json!({ + "msg": "ok", + "blurhash": blurhash, + }))) +} + #[derive(serde::Serialize)] struct PruneResponse { complete: bool, @@ -1561,6 +1600,7 @@ fn configure_endpoints( .route(web::head().to(serve_head::)), ), ) + .service(web::resource("/blurhash").route(web::get().to(blurhash::))) .service( web::resource("/process.{ext}") .route(web::get().to(process::)) diff --git a/src/magick.rs b/src/magick.rs index cf4f433..a9c36cb 100644 --- a/src/magick.rs +++ b/src/magick.rs @@ -1,14 +1,11 @@ use std::{ffi::OsStr, ops::Deref, path::Path, sync::Arc}; -use actix_web::web::Bytes; - use crate::{ config::Media, error_code::ErrorCode, formats::ProcessableFormat, - process::{Process, ProcessError, ProcessRead}, + process::{Process, ProcessError}, state::State, - stream::LocalBoxStream, tmp_file::{TmpDir, TmpFolder}, }; @@ -23,21 +20,9 @@ pub(crate) enum MagickError { #[error("Invalid output format: {0}")] Json(String, #[source] serde_json::Error), - #[error("Error writing bytes")] - Write(#[source] std::io::Error), - - #[error("Error creating file")] - CreateFile(#[source] std::io::Error), - - #[error("Error creating directory")] - CreateDir(#[source] crate::store::file_store::FileError), - #[error("Error creating temporary directory")] CreateTemporaryDirectory(#[source] std::io::Error), - #[error("Error closing file")] - CloseFile(#[source] std::io::Error), - #[error("Error in metadata discovery")] Discover(#[source] crate::discover::DiscoverError), @@ -66,11 +51,7 @@ impl MagickError { Self::CommandFailed(_) => ErrorCode::COMMAND_FAILURE, Self::Process(e) => e.error_code(), Self::Json(_, _) - | Self::Write(_) - | Self::CreateFile(_) - | Self::CreateDir(_) | Self::CreateTemporaryDirectory(_) - | Self::CloseFile(_) | Self::Discover(_) | Self::Cleanup(_) | Self::Empty => ErrorCode::COMMAND_ERROR, @@ -86,40 +67,20 @@ impl MagickError { } } -async fn process_image( +pub(crate) async fn process_image_command( state: &State, process_args: Vec, input_format: ProcessableFormat, format: ProcessableFormat, quality: Option, - write_file: F, -) -> Result -where - F: FnOnce(crate::file::File) -> Fut, - Fut: std::future::Future>, -{ +) -> Result { let temporary_path = state .tmp_dir .tmp_folder() .await .map_err(MagickError::CreateTemporaryDirectory)?; - let input_file = state.tmp_dir.tmp_file(None); - crate::store::file_store::safe_create_parent(&input_file) - .await - .map_err(MagickError::CreateDir)?; - - let tmp_one = crate::file::File::create(&input_file) - .await - .map_err(MagickError::CreateFile)?; - let tmp_one = (write_file)(tmp_one).await?; - tmp_one.close().await.map_err(MagickError::CloseFile)?; - - let input_arg = [ - input_format.magick_format().as_ref(), - input_file.as_os_str(), - ] - .join(":".as_ref()); + let input_arg = format!("{}:-", input_format.magick_format()); let output_arg = format!("{}:-", format.magick_format()); let quality = quality.map(|q| q.to_string()); @@ -130,7 +91,7 @@ where let mut args: Vec<&OsStr> = Vec::with_capacity(len); args.push("convert".as_ref()); - args.push(&input_arg); + args.push(input_arg.as_ref()); if input_format.coalesce() { args.push("-coalesce".as_ref()); } @@ -145,67 +106,10 @@ where (MAGICK_CONFIGURE_PATH, state.policy_dir.as_os_str()), ]; - let reader = Process::run("magick", &args, &envs, state.config.media.process_timeout)? - .read() - .add_extras(input_file) + let process = Process::run("magick", &args, &envs, state.config.media.process_timeout)? .add_extras(temporary_path); - Ok(reader) -} - -pub(crate) async fn process_image_stream_read( - state: &State, - stream: LocalBoxStream<'static, std::io::Result>, - args: Vec, - input_format: ProcessableFormat, - format: ProcessableFormat, - quality: Option, -) -> Result { - process_image( - state, - args, - input_format, - format, - quality, - |mut tmp_file| async move { - tmp_file - .write_from_stream(stream) - .await - .map_err(MagickError::Write)?; - Ok(tmp_file) - }, - ) - .await -} - -pub(crate) async fn process_image_process_read( - state: &State, - process_read: ProcessRead, - args: Vec, - input_format: ProcessableFormat, - format: ProcessableFormat, - quality: Option, -) -> Result { - process_image( - state, - args, - input_format, - format, - quality, - |mut tmp_file| async move { - process_read - .with_stdout(|stdout| async { - tmp_file - .write_from_async_read(stdout) - .await - .map_err(MagickError::Write) - }) - .await??; - - Ok(tmp_file) - }, - ) - .await + Ok(process) } pub(crate) type ArcPolicyDir = Arc; @@ -280,12 +184,12 @@ fn generate_policy(media: &Media) -> String { - + - + diff --git a/src/process.rs b/src/process.rs index c512b4f..be49cc6 100644 --- a/src/process.rs +++ b/src/process.rs @@ -6,11 +6,13 @@ use std::{ time::{Duration, Instant}, }; +use futures_core::Stream; +use streem::IntoStreamer; use tokio::{ - io::AsyncReadExt, + io::{AsyncRead, AsyncReadExt, AsyncWriteExt}, process::{Child, ChildStdin, Command}, }; -use tokio_util::io::ReaderStream; +use tokio_util::{bytes::Bytes, io::ReaderStream}; use tracing::Instrument; use uuid::Uuid; @@ -65,6 +67,7 @@ pub(crate) struct Process { child: Child, guard: MetricsGuard, timeout: Duration, + extras: Box, id: Uuid, } @@ -202,11 +205,19 @@ impl Process { command, guard, timeout: Duration::from_secs(timeout), + extras: Box::new(()), id: Uuid::now_v7(), }) }) } + pub(crate) fn add_extras(self, extra: impl Extras + 'static) -> Self { + Self { + extras: Box::new((self.extras, extra)), + ..self + } + } + #[tracing::instrument(skip(self), fields(command = %self.command, id = %self.id))] pub(crate) async fn wait(self) -> Result<(), ProcessError> { let Process { @@ -214,11 +225,17 @@ impl Process { mut child, guard, timeout, + mut extras, id: _, } = self; let res = child.wait().with_timeout(timeout).await; + extras + .consume() + .await + .map_err(|e| ProcessError::Cleanup(command.clone(), e))?; + match res { Ok(Ok(status)) if status.success() => { guard.disarm(); @@ -234,10 +251,12 @@ impl Process { } } - pub(crate) fn bytes_stream_read(self, input: BytesStream) -> ProcessRead { - self.spawn_fn(move |mut stdin| { + pub(crate) fn drive_with_async_read(self, input: impl AsyncRead + 'static) -> ProcessRead { + self.drive(move |mut stdin| { async move { - match tokio::io::copy(&mut input.into_reader(), &mut stdin).await { + let mut input = std::pin::pin!(input); + + match tokio::io::copy(&mut input, &mut stdin).await { Ok(_) => Ok(()), // BrokenPipe means we finished reading from Stdout, so we don't need to write // to stdin. We'll still error out if the command failed so treat this as a @@ -249,14 +268,34 @@ impl Process { }) } + pub(crate) fn drive_with_stream(self, input: S) -> ProcessRead + where + S: Stream> + 'static, + { + self.drive(move |mut stdin| async move { + let stream = std::pin::pin!(input); + let mut stream = stream.into_streamer(); + + while let Some(mut bytes) = stream.try_next().await? { + match stdin.write_all_buf(&mut bytes).await { + Ok(()) => {} + Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => break, + Err(e) => return Err(e), + } + } + + Ok(()) + }) + } + pub(crate) fn read(self) -> ProcessRead { - self.spawn_fn(|_| async { Ok(()) }) + self.drive(|_| async { Ok(()) }) } #[allow(unknown_lints)] #[allow(clippy::let_with_type_underscore)] #[tracing::instrument(level = "trace", skip_all)] - fn spawn_fn(self, f: F) -> ProcessRead + fn drive(self, f: F) -> ProcessRead where F: FnOnce(ChildStdin) -> Fut + 'static, Fut: Future>, @@ -266,6 +305,7 @@ impl Process { mut child, guard, timeout, + extras, id, } = self; @@ -302,7 +342,7 @@ impl Process { handle, command, id, - extras: Box::new(()), + extras, } } } @@ -358,6 +398,58 @@ impl ProcessRead { .await? } + pub(crate) fn pipe(self, process: Process) -> ProcessRead { + let Process { + command, + mut child, + guard, + timeout, + extras, + id, + } = process; + + let mut stdin = child.stdin.take().expect("stdin exists"); + let stdout = child.stdout.take().expect("stdout exists"); + + let command2 = command.clone(); + let handle = Box::pin(async move { + self.with_stdout(move |mut stdout| async move { + let child_fut = async { + tokio::io::copy(&mut stdout, &mut stdin).await?; + drop(stdout); + drop(stdin); + + child.wait().await + }; + + match child_fut.with_timeout(timeout).await { + Ok(Ok(status)) if status.success() => { + guard.disarm(); + Ok(()) + } + Ok(Ok(status)) => Err(ProcessError::Status(command2, status)), + Ok(Err(e)) => Err(ProcessError::Other(command2, e)), + Err(_) => { + child + .kill() + .await + .map_err(|e| ProcessError::Other(command2.clone(), e))?; + Err(ProcessError::Timeout(command2)) + } + } + }) + .await? + }); + + ProcessRead { + reader: Box::pin(stdout), + handle, + command, + id, + extras, + } + } + pub(crate) async fn with_stdout( self, f: impl FnOnce(BoxRead<'static>) -> Fut, diff --git a/src/validate.rs b/src/validate.rs index 4358491..ef28c10 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -11,11 +11,10 @@ use crate::{ AnimationFormat, AnimationOutput, ImageInput, ImageOutput, InputFile, InputVideoFormat, InternalFormat, }, - process::ProcessRead, + process::{Process, ProcessRead}, state::State, }; - #[derive(Debug, thiserror::Error)] pub(crate) enum ValidationError { #[error("Too wide")] @@ -74,15 +73,23 @@ pub(crate) async fn validate_bytes_stream( match &input { InputFile::Image(input) => { - let (format, process_read) = process_image(state, bytes, *input, width, height).await?; + let (format, process) = + process_image_command(state, *input, bytes.len(), width, height).await?; - Ok((format, process_read)) + Ok((format, process.drive_with_async_read(bytes.into_reader()))) } InputFile::Animation(input) => { - let (format, process_read) = - process_animation(state, bytes, *input, width, height, frames.unwrap_or(1)).await?; + let (format, process) = process_animation_command( + state, + *input, + bytes.len(), + width, + height, + frames.unwrap_or(1), + ) + .await?; - Ok((format, process_read)) + Ok((format, process.drive_with_async_read(bytes.into_reader()))) } InputFile::Video(input) => { let (format, process_read) = @@ -93,14 +100,14 @@ pub(crate) async fn validate_bytes_stream( } } -#[tracing::instrument(skip(state, bytes), fields(len = bytes.len()))] -async fn process_image( +#[tracing::instrument(skip(state))] +async fn process_image_command( state: &State, - bytes: BytesStream, input: ImageInput, + length: usize, width: u16, height: u16, -) -> Result<(InternalFormat, ProcessRead), Error> { +) -> Result<(InternalFormat, Process), Error> { let validations = &state.config.media.image; if width > validations.max_width { @@ -112,7 +119,7 @@ async fn process_image( if u32::from(width) * u32::from(height) > validations.max_area { return Err(ValidationError::Area.into()); } - if bytes.len() > validations.max_file_size * MEGABYTES { + if length > validations.max_file_size * MEGABYTES { return Err(ValidationError::Filesize.into()); } @@ -121,15 +128,15 @@ async fn process_image( needs_transcode, } = input.build_output(validations.format); - let process_read = if needs_transcode { + let process = if needs_transcode { let quality = validations.quality_for(format); - magick::convert_image(state, input.format, format, quality, bytes).await? + magick::convert_image_command(state, input.format, format, quality).await? } else { - exiftool::clear_metadata_bytes_read(bytes, state.config.media.process_timeout)? + exiftool::clear_metadata_command(state.config.media.process_timeout)? }; - Ok((InternalFormat::Image(format), process_read)) + Ok((InternalFormat::Image(format), process)) } fn validate_animation( @@ -158,33 +165,33 @@ fn validate_animation( Ok(()) } -#[tracing::instrument(skip(state, bytes), fields(len = bytes.len()))] -async fn process_animation( +#[tracing::instrument(skip(state))] +async fn process_animation_command( state: &State, - bytes: BytesStream, input: AnimationFormat, + length: usize, width: u16, height: u16, frames: u32, -) -> Result<(InternalFormat, ProcessRead), Error> { +) -> Result<(InternalFormat, Process), Error> { let validations = &state.config.media.animation; - validate_animation(bytes.len(), width, height, frames, validations)?; + validate_animation(length, width, height, frames, validations)?; let AnimationOutput { format, needs_transcode, } = input.build_output(validations.format); - let process_read = if needs_transcode { + let process = if needs_transcode { let quality = validations.quality_for(format); - magick::convert_animation(state, input, format, quality, bytes).await? + magick::convert_animation_command(state, input, format, quality).await? } else { - exiftool::clear_metadata_bytes_read(bytes, state.config.media.process_timeout)? + exiftool::clear_metadata_command(state.config.media.process_timeout)? }; - Ok((InternalFormat::Animation(format), process_read)) + Ok((InternalFormat::Animation(format), process)) } fn validate_video( diff --git a/src/validate/exiftool.rs b/src/validate/exiftool.rs index 2705c11..b9e7199 100644 --- a/src/validate/exiftool.rs +++ b/src/validate/exiftool.rs @@ -1,18 +1,11 @@ - - -use crate::{ - bytes_stream::BytesStream, - exiftool::ExifError, - process::{Process, ProcessRead}, -}; +use crate::{exiftool::ExifError, process::Process}; #[tracing::instrument(level = "trace", skip_all)] -pub(crate) fn clear_metadata_bytes_read( - input: BytesStream, - timeout: u64, -) -> Result { - Ok( - Process::run("exiftool", &["-all=", "-", "-out", "-"], &[], timeout)? - .bytes_stream_read(input), - ) +pub(super) fn clear_metadata_command(timeout: u64) -> Result { + Ok(Process::run( + "exiftool", + &["-all=", "-", "-out", "-"], + &[], + timeout, + )?) } diff --git a/src/validate/ffmpeg.rs b/src/validate/ffmpeg.rs index b67ac80..8d5bd98 100644 --- a/src/validate/ffmpeg.rs +++ b/src/validate/ffmpeg.rs @@ -1,6 +1,5 @@ use std::{ffi::OsStr, sync::Arc}; - use uuid::Uuid; use crate::{ @@ -35,27 +34,38 @@ pub(super) async fn transcode_bytes( let output_file = tmp_dir.tmp_file(None); - let res = transcode_files( - input_file.as_os_str(), - input_format, - output_file.as_os_str(), - output_format, - crf, - timeout, - ) + let res = async { + let res = transcode_files( + input_file.as_os_str(), + input_format, + output_file.as_os_str(), + output_format, + crf, + timeout, + ) + .await; + + input_file.cleanup().await.map_err(FfMpegError::Cleanup)?; + res?; + + let tmp_two = crate::file::File::open(&output_file) + .await + .map_err(FfMpegError::OpenFile)?; + let stream = tmp_two + .read_to_stream(None, None) + .await + .map_err(FfMpegError::ReadFile)?; + Ok(tokio_util::io::StreamReader::new(stream)) + } .await; - input_file.cleanup().await.map_err(FfMpegError::Cleanup)?; - res?; - - let tmp_two = crate::file::File::open(&output_file) - .await - .map_err(FfMpegError::OpenFile)?; - let stream = tmp_two - .read_to_stream(None, None) - .await - .map_err(FfMpegError::ReadFile)?; - let reader = tokio_util::io::StreamReader::new(stream); + let reader = match res { + Ok(reader) => reader, + Err(e) => { + output_file.cleanup().await.map_err(FfMpegError::Cleanup)?; + return Err(e); + } + }; let process_read = ProcessRead::new( Box::pin(reader), diff --git a/src/validate/magick.rs b/src/validate/magick.rs index d6c1ab1..3e3b8e6 100644 --- a/src/validate/magick.rs +++ b/src/validate/magick.rs @@ -1,82 +1,60 @@ use std::ffi::OsStr; - - use crate::{ - bytes_stream::BytesStream, formats::{AnimationFormat, ImageFormat}, magick::{MagickError, MAGICK_CONFIGURE_PATH, MAGICK_TEMPORARY_PATH}, - process::{Process, ProcessRead}, + process::Process, state::State, }; -pub(super) async fn convert_image( +pub(super) async fn convert_image_command( state: &State, input: ImageFormat, output: ImageFormat, quality: Option, - bytes: BytesStream, -) -> Result { +) -> Result { convert( state, input.magick_format(), output.magick_format(), false, quality, - bytes, ) .await } -pub(super) async fn convert_animation( +pub(super) async fn convert_animation_command( state: &State, input: AnimationFormat, output: AnimationFormat, quality: Option, - bytes: BytesStream, -) -> Result { +) -> Result { convert( state, input.magick_format(), output.magick_format(), true, quality, - bytes, ) .await } async fn convert( state: &State, - input: &'static str, - output: &'static str, + input_format: &'static str, + output_format: &'static str, coalesce: bool, quality: Option, - bytes: BytesStream, -) -> Result { +) -> Result { let temporary_path = state .tmp_dir .tmp_folder() .await .map_err(MagickError::CreateTemporaryDirectory)?; - let input_file = state.tmp_dir.tmp_file(None); + let input_arg = format!("{input_format}:-"); - crate::store::file_store::safe_create_parent(&input_file) - .await - .map_err(MagickError::CreateDir)?; - - let mut tmp_one = crate::file::File::create(&input_file) - .await - .map_err(MagickError::CreateFile)?; - tmp_one - .write_from_stream(bytes.into_io_stream()) - .await - .map_err(MagickError::Write)?; - tmp_one.close().await.map_err(MagickError::CloseFile)?; - - let input_arg = [input.as_ref(), input_file.as_os_str()].join(":".as_ref()); - let output_arg = format!("{output}:-"); + let output_arg = format!("{output_format}:-"); let quality = quality.map(|q| q.to_string()); let mut args: Vec<&OsStr> = vec!["convert".as_ref()]; @@ -85,7 +63,11 @@ async fn convert( args.push("-coalesce".as_ref()); } - args.extend(["-strip".as_ref(), "-auto-orient".as_ref(), &input_arg] as [&OsStr; 3]); + args.extend([ + "-strip".as_ref(), + "-auto-orient".as_ref(), + input_arg.as_ref(), + ] as [&OsStr; 3]); if let Some(quality) = &quality { args.extend(["-quality".as_ref(), quality.as_ref()] as [&OsStr; 2]); @@ -98,9 +80,8 @@ async fn convert( (MAGICK_CONFIGURE_PATH, state.policy_dir.as_os_str()), ]; - let reader = Process::run("magick", &args, &envs, state.config.media.process_timeout)?.read(); + let process = Process::run("magick", &args, &envs, state.config.media.process_timeout)? + .add_extras(temporary_path); - let clean_reader = reader.add_extras(input_file).add_extras(temporary_path); - - Ok(clean_reader) + Ok(process) }