vid_dup_finder_common-0.3.0/.cargo_vcs_info.json0000644000000001630000000000100153650ustar { "git": { "sha1": "32555b72c5b95fa762120ea135ee246118a39f2c" }, "path_in_vcs": "vid_dup_finder_common" }vid_dup_finder_common-0.3.0/.gitignore000064400000000000000000000000231046102023000161400ustar 00000000000000/target Cargo.lock vid_dup_finder_common-0.3.0/Cargo.lock0000644000001026220000000000100133430ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "ab_glyph" version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", ] [[package]] name = "ab_glyph_rasterizer" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aligned-vec" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "anyhow" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[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 = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "av1-grain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" dependencies = [ "anyhow", "arrayvec", "log", "nom", "num-rational", "v_frame", ] [[package]] name = "avif-serialize" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" dependencies = [ "arrayvec", ] [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitstream-io" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" [[package]] name = "built" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" [[package]] name = "byteorder-lite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cc" version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", "shlex", ] [[package]] name = "cfg-expr" version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 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]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "document-features" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" dependencies = [ "litrs", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "exr" version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", "half", "lebe", "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", ] [[package]] name = "fast_image_resize" version = "5.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e146c782f75f50995dae9ecf9ec189fc9d0d2906318cc6826ea9451717fe52ec" dependencies = [ "bytemuck", "cfg-if", "document-features", "image", "num-traits", "thiserror", ] [[package]] name = "fdeflate" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gif" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" dependencies = [ "color_quant", "weezl", ] [[package]] name = "half" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "image" version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", "color_quant", "exr", "gif", "image-webp", "num-traits", "png", "qoi", "ravif", "rayon", "rgb", "tiff", "zune-core", "zune-jpeg", ] [[package]] name = "image-webp" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" dependencies = [ "byteorder-lite", "quick-error", ] [[package]] name = "imageproc" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2393fb7808960751a52e8a154f67e7dd3f8a2ef9bd80d1553078a7b4e8ed3f0d" dependencies = [ "ab_glyph", "approx", "getrandom 0.2.15", "image", "itertools 0.12.1", "nalgebra", "num", "rand 0.8.5", "rand_distr", "rayon", ] [[package]] name = "imgref" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", ] [[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]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "jobserver" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ "getrandom 0.3.2", "libc", ] [[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lebe" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libfuzzer-sys" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" dependencies = [ "arbitrary", "cc", ] [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "litrs" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loop9" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" dependencies = [ "imgref", ] [[package]] name = "matrixmultiply" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" dependencies = [ "autocfg", "rawpointer", ] [[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]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "nalgebra" version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" dependencies = [ "approx", "matrixmultiply", "num-complex", "num-rational", "num-traits", "simba", "typenum", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "noop_proc_macro" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "num" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", "num-integer", "num-iter", "num-rational", "num-traits", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-complex" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] [[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]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "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]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "owned_ttf_parser" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" dependencies = [ "ttf-parser", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", "syn", ] [[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]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", "zerocopy", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.4", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core 0.9.3", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.15", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.2", ] [[package]] name = "rand_distr" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", "rand 0.8.5", ] [[package]] name = "rav1e" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" dependencies = [ "arbitrary", "arg_enum_proc_macro", "arrayvec", "av1-grain", "bitstream-io", "built", "cfg-if", "interpolate_name", "itertools 0.12.1", "libc", "libfuzzer-sys", "log", "maybe-rayon", "new_debug_unreachable", "noop_proc_macro", "num-derive", "num-traits", "once_cell", "paste", "profiling", "rand 0.8.5", "rand_chacha 0.3.1", "simd_helpers", "system-deps", "thiserror", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", "rayon", "rgb", ] [[package]] name = "rawpointer" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "rgb" version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "safe_arch" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" dependencies = [ "bytemuck", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simba" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" dependencies = [ "approx", "num-complex", "num-traits", "paste", "wide", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simd_helpers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" dependencies = [ "quote", ] [[package]] name = "smallvec" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "syn" version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tiff" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" dependencies = [ "flate2", "jpeg-decoder", "weezl", ] [[package]] name = "toml" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "ttf-parser" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "v_frame" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" dependencies = [ "aligned-vec", "num-traits", "wasm-bindgen", ] [[package]] name = "version-compare" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "vid_dup_finder_common" version = "0.3.0" dependencies = [ "fast_image_resize", "image", "imageproc", "itertools 0.14.0", "rand 0.9.0", "winapi", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "weezl" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wide" version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" dependencies = [ "bytemuck", "safe_arch", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winnow" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.0", ] [[package]] name = "zerocopy" version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zune-core" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[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.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" dependencies = [ "zune-core", ] vid_dup_finder_common-0.3.0/Cargo.toml0000644000000023460000000000100133700ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "vid_dup_finder_common" version = "0.3.0" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Common utilities for vid_dup_finder_lib and vid_dup_finder." readme = false license = "MIT OR Apache-2.0" repository = "https://github.com/Farmadupe/vid_dup_finder_lib" [lib] name = "vid_dup_finder_common" crate-type = ["lib"] path = "src/lib.rs" [dependencies.fast_image_resize] version = "5.1" features = ["image"] [dependencies.image] version = "0.25" default-features = false [dependencies.imageproc] version = "0.25" [dependencies.itertools] version = "0.14" [dependencies.rand] version = "0.9" [target."cfg(windows)".dependencies.winapi] version = "0.3" features = ["winbase"] vid_dup_finder_common-0.3.0/Cargo.toml.orig000064400000000000000000000011301046102023000170370ustar 00000000000000[package] description = "Common utilities for vid_dup_finder_lib and vid_dup_finder." edition = "2021" license = "MIT OR Apache-2.0" name = "vid_dup_finder_common" repository = "https://github.com/Farmadupe/vid_dup_finder_lib" version = "0.3.0" [lib] crate-type = ["lib"] name = "vid_dup_finder_common" path = "src/lib.rs" [dependencies] image = { version = "0.25", default-features = false } fast_image_resize = { version = "5.1", features= ["image"]} imageproc = "0.25" itertools = "0.14" rand = "0.9" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winbase"] } vid_dup_finder_common-0.3.0/src/compositing.rs000064400000000000000000000074641046102023000176600ustar 00000000000000use std::borrow::Borrow; use image::{GenericImage, GenericImageView, RgbImage}; /// Given a two-dimensional array of images, arrange them all in a grid. /// All images must share the same dimensions. /// Returns None if there are no images. /// /// Panics if the images are not all the same dimensions. #[must_use] pub fn grid_images_rgb(images: &[&[RgbImage]]) -> Option { //Check that all image dimensions are equal to the dimensions of the first //image. let mut all_img_dimensions = images .iter() .flat_map(|row| row.iter().map(RgbImage::dimensions)); let (img_x, img_y) = all_img_dimensions.next()?; assert!(all_img_dimensions .all(|(curr_img_x, curr_img_y)| curr_img_x == img_x && curr_img_y == img_y)); //work out how wide and how deep the output buffer needs to be let grid_num_x = images .iter() .map(|i| u32::try_from(i.len()).expect("unreachable")) .max()?; let grid_num_y = u32::try_from(images.len()).expect("unreachable"); //create the output buffer and fill it. let mut grid_buf = RgbImage::new(grid_num_x * img_x, grid_num_y * img_y); for (col_no, row_imgs) in images.iter().enumerate() { for (row_no, img) in row_imgs.iter().enumerate() { let x_coord = u32::try_from(row_no).expect("unreachable") * img_x; let y_coord = u32::try_from(col_no).expect("unreachable") * img_y; grid_buf .copy_from(img as &RgbImage, x_coord, y_coord) .expect("unreachable due to above assertion about image dimensions"); } } Some(grid_buf) } ///Arrange a sequence of images side by side in a row. ///The images must all be the same size. /// /// Returns None if there are no images /// Panics if the images are not all the same size pub fn row_images<'a, ExactIter, View, Pixel, Subpix>( images: ExactIter, ) -> Option>> where ExactIter: ExactSizeIterator, View: GenericImageView + 'a, Pixel: image::Pixel, { type RetBuf = image::ImageBuffer>; //get the number of images and their size. If dimensions is None //then there are no images, so return None as there is no work to do. let mut images = images.map(|x| x.borrow()).peekable(); let (img_x, img_y) = images.peek().map(|x| x.dimensions())?; let len = u32::try_from(images.len()).expect("unreachable"); let mut ret = RetBuf::new(len * img_x, img_y); for (col_no, img) in images.enumerate() { let x_coord = u32::try_from(col_no).expect("unreachable") * img_x; ret.copy_from(img, x_coord, 0).unwrap(); } Some(ret) } ///Arrange a sequence of images top to bottom. ///The images must all be the same size. /// /// Returns None if there are no images /// Panics if the images are not all the same size pub fn stack_images<'a, ExactIter, View, Pixel, Subpix>( images: ExactIter, ) -> Option>> where ExactIter: ExactSizeIterator, View: GenericImageView + 'a, Pixel: image::Pixel, { type RetBuf = image::ImageBuffer>; //get the number of images and their size. If dimensions is None //then there are no images, so return None as there is no work to do. let mut images = images.map(|img| img.borrow()).peekable(); let (img_x, img_y) = images.peek().map(|img| img.dimensions())?; let len = u32::try_from(images.len()).expect("unreachable"); let mut ret = RetBuf::new(img_x, len * img_y); for (row_no, img) in images.enumerate() { let y_coord = u32::try_from(row_no).expect("unreachable") * img_y; ret.copy_from(img, 0, y_coord).unwrap(); } Some(ret) } vid_dup_finder_common-0.3.0/src/crop.rs000064400000000000000000000241151046102023000162600ustar 00000000000000// use image::flat::SampleLayout; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Crop { pub orig_res: (u32, u32), pub left: u32, pub right: u32, pub top: u32, pub bottom: u32, } impl Crop { #[must_use] pub fn from_edge_offsets( orig_res: (u32, u32), left: u32, right: u32, top: u32, bottom: u32, ) -> Self { assert!((left + right) < orig_res.0); assert!((top + bottom) < orig_res.1); Self { orig_res, left, right, top, bottom, } } pub fn from_topleft_and_dims( (orig_width, orig_height): (u32, u32), x: u32, y: u32, width: u32, height: u32, ) -> Self { let left = x; let right = orig_width - width - x; let top = y; let bottom = orig_height - height - y; Self { orig_res: (orig_width, orig_height), left, right, top, bottom, } } #[must_use] pub fn union(&self, other: &Self) -> Self { use std::cmp::min; if self.orig_res != other.orig_res { //assert!(self.orig_res == other.orig_res); } let ret = Self::from_edge_offsets( self.orig_res, min(self.left, other.left), min(self.right, other.right), min(self.top, other.top), min(self.bottom, other.bottom), ); ret } #[must_use] pub fn biggest_crop(&self, other: &Self) -> Self { //println!("biggest_crop: Self: {self:#?}; Other: #{other:#?}"); assert!(self.orig_res == other.orig_res); let t_w = self.right.abs_diff(self.left); let t_h = self.bottom.abs_diff(self.top); let t_dim = t_w * t_h; let o_w = other.right.abs_diff(other.left); let o_h = other.bottom.abs_diff(other.top); let o_dim = o_w * o_h; if t_dim < o_dim { *self } else { *other } } #[must_use] pub fn as_view_args(&self) -> (u32, u32, u32, u32) { let (orig_width, orig_height) = self.orig_res; let coord_x = self.left; let coord_y = self.top; // let width = orig_width - (self.left + self.right); // let height = orig_height - (self.top + self.bottom); let width = orig_width.checked_sub(self.left + self.right).unwrap(); let height = orig_height.checked_sub(self.top + self.bottom).unwrap(); (coord_x, coord_y, width, height) } pub fn width(&self) -> u32 { self.orig_res.0 - (self.left + self.right) } pub fn height(&self) -> u32 { self.orig_res.1 - (self.top + self.bottom) } pub fn area(&self) -> u32 { self.width() * self.height() } pub fn aspect_ratio(&self) -> f64 { f64::from(self.width()) / f64::from(self.height()) } pub fn enumerate_coords(&self) -> impl Iterator { let (orig_x, orig_y) = self.orig_res; let first_x_pix = self.left; let last_x_pix = orig_x - self.right; let xs = first_x_pix..last_x_pix; let first_y_pix = self.top; let last_y_pix = orig_y - self.bottom; let ys = first_y_pix..last_y_pix; xs.flat_map(move |x| ys.clone().map(move |y| (x, y))) } pub fn enumerate_coords_excluded(&self) -> impl Iterator { let (orig_x, orig_y) = self.orig_res; let x0 = 0; let x1 = self.left; let x2 = orig_x - self.right; let x3 = orig_x; let y0 = 0; let y1 = self.top; let y2 = orig_y - self.bottom; let y3 = orig_y; //clockwise starting at topleft (tl) let tl = (x0..x1).flat_map(move |x| (y0..y1).map(move |y| (x, y))); let tm = (x1..x2).flat_map(move |x| (y0..y1).map(move |y| (x, y))); let tr = (x2..x3).flat_map(move |x| (y0..y1).map(move |y| (x, y))); let mr = (x2..x3).flat_map(move |x| (y1..y2).map(move |y| (x, y))); let bl = (x0..x1).flat_map(move |x| (y2..y3).map(move |y| (x, y))); let bm = (x1..x2).flat_map(move |x| (y2..y3).map(move |y| (x, y))); let br = (x2..x3).flat_map(move |x| (y2..y3).map(move |y| (x, y))); let ml = (x0..x1).flat_map(move |x| (y1..y2).map(move |y| (x, y))); tl.chain(tm.chain(tr.chain(mr.chain(bl.chain(bm.chain(br.chain(ml))))))) } pub fn eroded(self) -> Option { let mut ret = self; ret.left += 1; ret.right += 1; ret.top += 1; ret.bottom += 1; if ret.left + ret.right >= ret.orig_res.0 { return None; } if ret.top + ret.bottom >= ret.orig_res.1 { return None; } Some(ret) } pub fn is_uncropped(&self) -> bool { (self.left == 0) && (self.right == 0) && (self.top == 0) && (self.bottom == 0) } } //pub fn as_cropped(layout: SampleLayout, crop: Crop) -> SampleLayout {} impl Default for Crop { //an arbitrary 'enormous' crop suitable for initializing a fold/reduce fn default() -> Self { Self { orig_res: (u32::MAX, u32::MAX), left: u32::MAX / 8, right: u32::MAX / 8, top: u32::MAX / 8, bottom: u32::MAX / 8, } } } #[cfg(test)] mod test { use itertools::Itertools; use super::*; #[test] fn test_as_view_args_nocrop() { let crop = Crop::from_edge_offsets((100, 100), 0, 0, 0, 0); let exp: (u32, u32, u32, u32) = (0, 0, 100, 100); let act = crop.as_view_args(); assert!(act == exp); } #[test] fn test_as_view_args_1pix_left() { let crop = Crop::from_edge_offsets((100, 100), 1, 0, 0, 0); let exp: (u32, u32, u32, u32) = (1, 0, 99, 100); let act = crop.as_view_args(); assert!(act == exp); } #[test] fn test_as_view_args_1pix_right() { let crop = Crop::from_edge_offsets((100, 100), 0, 1, 0, 0); let exp: (u32, u32, u32, u32) = (0, 0, 99, 100); let act = crop.as_view_args(); assert!(act == exp); } #[test] fn test_as_view_args_1pix_top() { let crop = Crop::from_edge_offsets((100, 100), 0, 0, 1, 0); let exp: (u32, u32, u32, u32) = (0, 1, 100, 99); let act = crop.as_view_args(); assert!(act == exp); } #[test] fn test_as_view_args_1pix_bot() { let crop = Crop::from_edge_offsets((100, 100), 0, 0, 0, 1); let exp: (u32, u32, u32, u32) = (0, 0, 100, 99); let act = crop.as_view_args(); assert!(act == exp); } #[test] fn test_as_view_args_four_values() { let crop = Crop::from_edge_offsets((100, 100), 25, 25, 25, 25); let exp: (u32, u32, u32, u32) = (25, 25, 50, 50); let act = crop.as_view_args(); assert!(act == exp); } #[test] fn test_as_view_args_four_more() { let crop = Crop::from_edge_offsets((768, 432), 96, 96, 0, 0); let exp: (u32, u32, u32, u32) = (96, 0, 576, 432); let act = crop.as_view_args(); assert!(act == exp); } // #[test] // #[should_panic] // fn test_as_view_args_noneleft() { // let _ = Crop::from_edge_offsets((100, 100), 50, 50, 50, 50); // } #[test] fn test_from_offset_and_dims() { let crop = Crop::from_topleft_and_dims((100, 100), 11, 12, 13, 14); assert!(crop.as_view_args() == (11, 12, 13, 14)); } #[test] fn test_enumerate_coords_nocrop() { let crop = Crop::from_edge_offsets((3, 3), 0, 0, 0, 0); assert!(crop.enumerate_coords().count() == 9); assert!(crop.enumerate_coords_excluded().count() == 0); } #[test] fn test_enumerate_coords_1pixinthemiddle() { let crop = Crop::from_edge_offsets((3, 3), 1, 1, 1, 1); //test included { let exp = vec![(1, 1)]; let act = crop.enumerate_coords().collect::>(); assert_eq!(exp, act); } //test exluced { #[rustfmt::skip] let exp = vec![ (0, 0), (1, 0), (2, 0), (0, 1), (2, 1), (0, 2), (1, 2), (2, 2), ].into_iter().sorted().collect::>(); let act = crop .enumerate_coords_excluded() .sorted() .collect::>(); assert!(exp == act); } } #[test] fn test_enumerate_coords_1pixinthetop() { let crop = Crop::from_edge_offsets((3, 3), 1, 1, 0, 2); //test included { let exp = vec![(1, 0)]; let act = crop.enumerate_coords().collect::>(); assert_eq!(exp, act); } //test exluced { #[rustfmt::skip] let exp = vec![ (0, 0), (2, 0), (0, 1), (1, 1), (2, 1), (0, 2), (1, 2), (2, 2), ].into_iter().sorted().collect::>(); let act = crop .enumerate_coords_excluded() .sorted() .collect::>(); assert_eq!(exp, act); } } #[test] fn test_enumerate_coords_1pixintheright() { let crop = Crop::from_edge_offsets((3, 3), 2, 0, 2, 0); //(also test alternate constructor) let other_crop = Crop::from_topleft_and_dims((3, 3), 2, 2, 1, 1); assert_eq!(crop, other_crop); //test included { let exp = vec![(2, 2)]; let act = crop.enumerate_coords().collect::>(); assert_eq!(exp, act); } //test exluced { #[rustfmt::skip] let exp = vec![ (0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1), (0, 2), (1, 2), ].into_iter().sorted().collect::>(); let act = crop .enumerate_coords_excluded() .sorted() .collect::>(); assert_eq!(exp, act); } } } vid_dup_finder_common-0.3.0/src/lib.rs000064400000000000000000000013201046102023000160540ustar 00000000000000#![allow(clippy::let_and_return)] #![deny(clippy::print_stdout)] #![deny(clippy::print_stderr)] #![deny(clippy::dbg_macro)] // #![warn(clippy::unnecessary_cast)] // #![warn(clippy::cast_lossless)] // #![warn(clippy::cast_possible_truncation)] // #![warn(clippy::cast_possible_wrap)] // #![warn(clippy::cast_precision_loss)] // #![warn(clippy::cast_sign_loss)] pub mod compositing; mod crop; pub mod motioncrop; pub mod resize_gray; pub mod resize_rgb; pub mod video_frames_gray; pub mod video_frames_rgb; pub use compositing::grid_images_rgb; pub use compositing::row_images; pub use crop::Crop; pub use resize_gray::crop_resize_buf; pub use video_frames_gray::VideoFramesGray; pub use video_frames_rgb::FrameSeqRgb; vid_dup_finder_common-0.3.0/src/motioncrop/autocrop_frames.rs000064400000000000000000000246701046102023000227050ustar 00000000000000use std::ops::Deref; use imageproc::contrast::stretch_contrast_mut; use itertools::Itertools; use image::{buffer::ConvertBuffer, GenericImageView, GrayImage, Luma, RgbImage}; use super::{ darkest_frame::DarkestFrame, frame_change::FrameChange, utils::{colourize_regions, maskize_regions, regionize_image, tint_cropped_area, RgbChan}, }; use crate::{ crop::Crop, motioncrop::utils::clear_out_cropped_area, video_frames_gray::{LetterboxColour, VdfFrameExt}, }; #[derive(Debug, Clone)] pub struct MotiondetectCrop { _crop: (), // darkest_frame: DarkestFrame, // movement_intensity: FrameChange, // guessed_regions: Vec, // frames_added: usize, } // struct CropAndMaskedoutFrames { // crop: Option, // masked_out_frames: Vec, // } impl MotiondetectCrop { #[allow(clippy::new_without_default)] #[must_use] pub fn from_frames( frames: impl IntoIterator>, ) -> Option { //for now, we need a mutable copy of all of the frames to do this crop :( let mut frames = frames .into_iter() .map(|f| f.deref().clone()) .collect::>(); if frames.len() < 2 { return None; } let mut min_pix: u8 = 255; let mut max_pix: u8 = 0; #[allow(unused_variables)] frames.iter().enumerate().for_each(|(i, frame)| { let mut frame_min_pix: u8 = 255; let mut frame_max_pix: u8 = 0; for Luma([pix]) in frame.pixels() { if *pix < frame_min_pix { frame_min_pix = *pix; } if *pix > frame_max_pix { frame_max_pix = *pix; } } // eprintln!("frame {i}, min: {frame_min_pix}, max: {frame_max_pix}"); // if let Some(debug_dir) = debug_img_dir() { // if frame_max_pix == 255 { // let mut annotated: RgbImage = frame.clone().convert(); // for Rgb([ref mut r, ref mut g, ref mut b]) in annotated.pixels_mut() { // if *r == 255 && *g == 255 && *b == 255 { // *g = 0; // *b = 0; // } // } // annotated // .save(Path::new(&debug_dir).join(format!("{i}.png"))) // .unwrap(); // } // } min_pix = min_pix.min(frame_min_pix); max_pix = max_pix.max(frame_max_pix); }); if debug_img_dir().is_some() { let (modal_pix, modal_count) = { let mut acc = [0usize; 256]; let each_pix = frames.iter().flat_map(|frame| frame.pixels()); for Luma([inty]) in each_pix { acc[*inty as usize] += 1; } acc.into_iter().enumerate().max_by_key(|&(_, item)| item) } .unwrap(); let num_pix = (frames[0].width() * frames[0].height()) * frames.len() as u32; let modal_proportion = modal_count as f64 / num_pix as f64; #[allow(clippy::print_stderr)] let () = eprintln!("minmax_inty: ({min_pix:?}, {max_pix:?}) modal pix: {modal_pix:?} modal pix proportion: {:.0}%", modal_proportion * 100.0); } #[allow(clippy::collapsible_if)] if max_pix != 255 && min_pix != 0 { if min_pix < max_pix { for frame in &mut frames { stretch_contrast_mut(frame, min_pix, max_pix, 0, 255); } } } //check that all frames are the same size for (f1, f2) in frames.iter().tuple_windows::<(_, _)>() { if f1.dimensions() != f2.dimensions() { return None; } } //get the letterbox crop let letterbox_crop = frames .iter() .fold(None, |acc, frame| { let this_frame_letterbox = frame.letterbox_crop(LetterboxColour::AnyColour(16)); match acc { None => Some(this_frame_letterbox), Some(c) => Some(this_frame_letterbox.union(&c)), } }) .expect("should always be at least 1 frame by this point"); //whiten out the letterbox for frame in frames.iter_mut() { for (x, y) in letterbox_crop.enumerate_coords_excluded() { frame.put_pixel(x, y, Luma([255])); } } let crop_1 = Self::from_frames_one(&frames); let first_frame = frames[0].clone(); let crop_2 = match crop_1 { None => None, Some(crop_1) => { for (i, frame) in frames.iter_mut().enumerate() { if i == 1 { if let Some(debug_dir) = debug_img_dir() { frame.save(format!("{debug_dir}/{i}_a.png")).unwrap(); } } clear_out_cropped_area(frame, crop_1); if i == 1 { if let Some(debug_dir) = debug_img_dir() { frame.save(format!("{debug_dir}/{i}_b.png")).unwrap(); } } } Self::from_frames_one(&frames) } }; let crops = [crop_1, crop_2].into_iter().flatten().collect::>(); if crops.is_empty() { return Some(letterbox_crop); } let filtered_crops = crops.iter().copied(); //reject implausible aspect ratio let worst_aspect_ratio: f64 = 3.0; let filtered_crops = filtered_crops.filter(|crop| { let width = f64::from(crop.width()); let height = f64::from(crop.height()); let aspect_ratio = if width > height { width / height } else { height / width }; aspect_ratio <= worst_aspect_ratio }); //reject crops that are too small let largest_area = f64::from(crops.iter().map(Crop::area).max()?); let filtered_crops = filtered_crops.filter(|crop| f64::from(crop.area()) > largest_area * 0.8); //select topmost if there are several candidates let ret = filtered_crops.min_by_key(|crop| crop.top); //if we didn't actually detect anything, just return the letterbox let ret = ret.unwrap_or(letterbox_crop); if let Some(debug_dir) = debug_img_dir() { let mut first_frame: RgbImage = first_frame.convert(); for crop in crops.iter().copied() { let chan = if crop == ret { RgbChan::Red } else { RgbChan::Blue }; first_frame = tint_cropped_area(&first_frame, crop, chan); } first_frame .save(format!("{debug_dir}/combined.png")) .unwrap(); } Some(ret) } #[allow(clippy::new_without_default)] #[must_use] fn from_frames_one(frames: &[GrayImage]) -> Option { // dbg!(letterbox_crop, letterbox_crop.as_view_args()); //quick heuristic to see if there are lots of white/black pixels left // if false { // let bg_pix_proportion = { // let total_pix = frames.len() * width as usize * height as usize; // let bg_pix = frames // .iter() // .flat_map(|f| f.pixels()) // .filter(|Luma([pix])| (*pix > 230 || *pix < 5) && *pix != 254) // .count(); // let val = bg_pix as f64 / total_pix as f64; // // let s = format!("total_pix: {total_pix}, bg_pix: {bg_pix}, proportion: {val}"); // // dbg!(s); // val // }; // if bg_pix_proportion < MIN_SATURATED_PIX { // return None; // } // } let mut darkest_frame = DarkestFrame::try_from_seq(frames)?; let mut movement_intensity = FrameChange::try_from_iter(frames.iter().tuple_windows::<(_, _)>())?; let largest_motion_region = movement_intensity.largest_region_with_motion(); let retained_region_mask = darkest_frame.largest_dark_region_with_motion(largest_motion_region)?; let subimage = super::utils::view_mask(&retained_region_mask)?; let ret = { let (x, y) = subimage.offsets(); let (orig_width, orig_height) = frames.first().unwrap().dimensions(); Crop::from_topleft_and_dims( (orig_width, orig_height), x, y, subimage.width(), subimage.height(), ) }; if let Some(debug_dir) = debug_img_dir() { let rand = rand::random::(); std::fs::create_dir_all(&debug_dir).ok(); colourize_regions(®ionize_image(darkest_frame.inner()).0) .save(format!("{debug_dir}/{rand}darkest_frame.png")) .unwrap(); colourize_regions(®ionize_image(largest_motion_region).0) .save(format!("{debug_dir}/{rand}largest_motion_region.png")) .unwrap(); maskize_regions(&movement_intensity.largest_area().unwrap()) .save(format!("{debug_dir}/{rand}movement_intensity_largest.png")) .unwrap(); retained_region_mask .save(format!("{debug_dir}/{rand}retained_region.png")) .unwrap(); let (a, b, c, d) = ret.as_view_args(); let check_it_worked = frames[0].view(a, b, c, d); check_it_worked .to_image() .save(format!("{debug_dir}/{rand}_check_final.png")) .unwrap(); subimage .to_image() .save(format!("{debug_dir}/{rand}check_pre_subimage.png")) .unwrap(); } if ret.is_uncropped() { Some(ret) } else if let Some(eroded) = Some(ret).and_then(Crop::eroded).and_then(Crop::eroded) { Some(eroded) } else { Some(ret) } } } fn debug_img_dir() -> Option { std::env::var("AUTOCROP_DEBUG_IMG_DIR").ok() } vid_dup_finder_common-0.3.0/src/motioncrop/darkest_frame.rs000064400000000000000000000065751046102023000223270ustar 00000000000000use image::{GrayImage, Luma}; use imageproc::distance_transform::Norm; use crate::{ motioncrop::utils::{ boolean_and, maskize_regions, regionize_image, regions_in_mask, retain_regions, }, Crop, }; /// Given a sequence of frames from a single video, keeps track of the darkest /// pixel in each frame. #[derive(Debug, Clone)] pub struct DarkestFrame { frame: GrayImage, processed_frame: Option, } impl DarkestFrame { pub fn try_from_seq(seq: &[GrayImage]) -> Option { let mut ret: Option = None; for frame in seq { match ret.as_mut() { Some(ref mut ret) => ret.add_frame(frame), None => ret = Some(Self::new(frame)), } } ret } pub fn new(first_frame: &GrayImage) -> Self { let (x, y) = first_frame.dimensions(); let mut ret = Self { frame: GrayImage::from_pixel(x, y, Luma([255])), processed_frame: None, }; ret.add_frame(first_frame); ret } pub fn add_frame(&mut self, frame: &GrayImage) { // Update the darkest pix image for (Luma([src_pix]), Luma([dark_pix])) in frame.pixels().zip(self.frame.pixels_mut()) { *dark_pix = *src_pix.min(dark_pix); } self.processed_frame = None; } fn postprocess(&mut self) { if self.processed_frame.is_some() { return; } let min_white = 210; //clamp all pixels above min_white to 255. let mut tmp = self.frame.clone(); for Luma([pix]) in tmp.pixels_mut() { if *pix >= min_white { *pix = 0 } else { *pix = 255 } } let bin = imageproc::contrast::ThresholdType::Binary; imageproc::contrast::threshold_mut(&mut tmp, min_white - 1, bin); self.processed_frame = Some(tmp) } pub fn mask_out_area(&mut self, area: &Crop) { for (x, y) in area.enumerate_coords() { //dbg!(x, y); self.frame.put_pixel(x, y, Luma([255])) } self.processed_frame = None; } pub fn inner(&mut self) -> &GrayImage { self.postprocess(); self.processed_frame.as_ref().unwrap() } pub fn largest_dark_region_with_motion( &mut self, motion_frame: &GrayImage, ) -> Option { self.postprocess(); let pp_frame = self.processed_frame.as_ref().unwrap(); //open can destroy small images let erode_thr = (pp_frame.height() / 10).min(10); let erode_thr = u8::try_from(erode_thr).unwrap(); let pp_frame = if pp_frame.height() > 100 { imageproc::morphology::open(pp_frame, Norm::LInf, erode_thr) } else { pp_frame.clone() }; let anded_image = boolean_and(&pp_frame, motion_frame); let (pp_regions, _num_regions) = regionize_image(&pp_frame); let preserved_regions_idxs = regions_in_mask(&pp_regions, &anded_image); let preserved_regions = retain_regions(&pp_regions, &preserved_regions_idxs); let largest_region_idx = super::utils::largest_region(&preserved_regions)?; let largest_region = retain_regions(&preserved_regions, &[largest_region_idx]); let largest_region_masked = maskize_regions(&largest_region); Some(largest_region_masked) } } vid_dup_finder_common-0.3.0/src/motioncrop/frame_change.rs000064400000000000000000000106471046102023000221120ustar 00000000000000type GrayImageU16 = ImageBuffer, Vec>; use image::{buffer::ConvertBuffer, GrayImage, ImageBuffer, Luma}; use imageproc::definitions::Image; use super::utils::{largest_region, regionize_image, retain_regions}; #[derive(Debug, Clone)] pub struct FrameChange { sum_frame: GrayImageU16, sum_frame_u8: Option, } impl FrameChange { pub fn try_from_iter<'a>( iter: impl IntoIterator, ) -> Option { let mut ret: Option = None; for x in iter { match ret.as_mut() { Some(ref mut ret) => ret.add_frame(x), None => ret = Some(Self::new(x)), } } ret } pub fn new((frame_a, frame_b): (&GrayImage, &GrayImage)) -> Self { let width = frame_a.width(); let height = frame_b.height(); let mut ret = Self { sum_frame: GrayImageU16::new(width, height), sum_frame_u8: None, }; ret.update_sum_frame(frame_a, frame_b); ret } //updates the movement intensity frame with the differences between a pair of images. fn update_sum_frame(&mut self, frame_a: &GrayImage, frame_b: &GrayImage) { //tweakable to ignore small differences (recording noise, compression noise etc) let thresh = 8; //NOTE: Hot code. This zip().zip() was the only way I could get the loop to vectorize for (&Luma([ref a_pix]), (&Luma([ref b_pix]), &mut Luma([ref mut sum_pix]))) in frame_a .pixels() .zip(frame_b.pixels().zip(self.sum_frame.pixels_mut())) { let diff = u16::from(a_pix.abs_diff(*b_pix)); let diff = if diff >= thresh { diff } else { 0 }; *sum_pix += diff; } self.sum_frame_u8 = None; } pub fn add_frame(&mut self, (frame_a, frame_b): (&GrayImage, &GrayImage)) { //check that the new frame is the same size as all previous frames. //Diff diff the two frames if so. assert!(frame_a.dimensions() == frame_b.dimensions()); self.update_sum_frame(frame_a, frame_b); } fn postprocess(&mut self) { use imageproc::distance_transform::Norm::LInf; if self.sum_frame_u8.is_some() { return; } //after the last image has been inserted, we blur the sum_frame, and then //normalize it to fit into the full range. let mut tmp = self.sum_frame.clone().convert(); normalize_u16(&mut tmp); let tmp = tmp.convert(); let mut tmp = image::imageops::blur(&tmp, 2.0); let bin = imageproc::contrast::ThresholdType::Binary; imageproc::contrast::threshold_mut(&mut tmp, 20, bin); let tmp = imageproc::morphology::close(&tmp, LInf, 5); self.sum_frame_u8 = Some(tmp); } // pub fn inner_unblurred(&mut self) -> GrayImage { // assert!(!self.processed); // let mut temp_frame = self.sum_frame.clone(); // normalize_u16(&mut temp_frame); // temp_frame.convert() // } pub fn largest_region_with_motion(&mut self) -> &GrayImage { self.postprocess(); self.sum_frame_u8.as_ref().unwrap() } pub fn largest_area(&mut self) -> Option>> { self.postprocess(); let img = self.sum_frame_u8.as_ref()?; let (regions, _num_regions) = regionize_image(img); let biggest_region_idx = largest_region(®ions)?; let biggest_region = retain_regions(®ions, &[biggest_region_idx]); Some(biggest_region) } } fn normalize_u16(frame: &mut GrayImageU16) { fn brightest_pix_u16(img: &GrayImageU16) -> Luma { img.pixels() .copied() .reduce(|Luma([acc]), Luma([pix])| Luma([acc.max(pix)])) .unwrap() } fn darkest_pix_u16(img: &GrayImageU16) -> Luma { img.pixels() .copied() .reduce(|Luma([acc]), Luma([pix])| Luma([acc.min(pix)])) .unwrap() } let Luma([max]) = brightest_pix_u16(frame); let Luma([min]) = darkest_pix_u16(frame); let scaling_factor = f64::from(u16::MAX) / f64::from(min.abs_diff(max)); for &mut Luma([ref mut pix]) in frame.pixels_mut() { let new_val = f64::from(*pix - min) * scaling_factor; let new_val = new_val.clamp(f64::from(u16::MIN), f64::from(u16::MAX)); let new_val = new_val as u16; *pix = new_val; } } vid_dup_finder_common-0.3.0/src/motioncrop/mod.rs000064400000000000000000000004131046102023000202600ustar 00000000000000pub mod autocrop_frames; pub mod darkest_frame; pub mod frame_change; mod utils; #[cfg(test)] mod test; ///////development tweakables //proportion of all-white/all black pix needed in video before attemptign motion detect crop //const MIN_SATURATED_PIX: f64 = 0.1; vid_dup_finder_common-0.3.0/src/motioncrop/test.rs000064400000000000000000000134401046102023000204640ustar 00000000000000use std::rc::Rc; use crate::Crop; use image::GrayImage; use super::autocrop_frames::MotiondetectCrop; //test that if there is nothing to crop due to static image, then nothing is returned #[test] fn test_nocrop() { #[rustfmt::skip] let pixen = [ vec![ 255, 255, 255, 255, 255, 255, 255, 255, 255, ], vec![ 255, 255, 255, 255, 255, 255, 255, 255, 255, ] ]; let imgs = util_generate_frames(3, 3, pixen); let exp = Some(Crop::from_edge_offsets((3, 3), 0, 0, 0, 0)); let act = MotiondetectCrop::from_frames(imgs); assert_eq!(exp, act); } //test that if there is no motion, but there is a letterbox, then //the letterbox is removed. #[test] fn test_letterbox_static() { #[rustfmt::skip] let pixen = [ vec![ 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], vec![ 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ] ]; let imgs = util_generate_frames(5, 6, pixen); let exp = Some(Crop::from_edge_offsets((5, 6), 1, 1, 1, 2)); let act = MotiondetectCrop::from_frames(imgs); assert_eq!(exp, act); } #[test] fn test_2pixsquareinthemiddle() { #[rustfmt::skip] let pixen = [ vec![ 255, 220, 220, 255, 220, 80, 80, 220, 220, 80, 80, 220, 255, 255, 255, 255, ], vec![ 255, 220, 220, 255, 220, 27, 27, 220, 220, 27, 27, 220, 255, 255, 255, 255, ] ]; let imgs = util_generate_frames(4, 4, pixen); let exp = Some(Crop::from_edge_offsets((4, 4), 1, 1, 1, 1)); let act = MotiondetectCrop::from_frames(imgs); assert_eq!(exp, act); } #[test] fn test_prefer_bigger_region() { #[rustfmt::skip] let pixen = [ vec![ 255, 220, 220, 255, 220, 80, 255, 220, 220, 255, 255, 220, 255, 255, 255, 255, 255, 220, 220, 255, 220, 80, 80, 220, 220, 80, 80, 220, 255, 255, 255, 255, ], vec![ 255, 220, 220, 255, 220, 20, 255, 220, 220, 255, 255, 220, 255, 255, 255, 255, 255, 220, 220, 255, 220, 20, 20, 220, 220, 20, 20, 220, 255, 255, 255, 255, ] ]; let imgs = util_generate_frames(4, 8, pixen); let exp = Some(Crop::from_edge_offsets((4, 8), 1, 1, 5, 1)); let act = MotiondetectCrop::from_frames(imgs); assert_eq!(exp, act); } #[test] fn test_prefer_upper_region() { #[rustfmt::skip] let pixen = [ vec![ 255, 220, 220, 255, 220, 80, 80, 220, 220, 255, 80, 220, 255, 255, 255, 255, 255, 220, 220, 255, 220, 80, 80, 220, 220, 80, 80, 220, 255, 255, 255, 255, ], vec![ 255, 220, 220, 255, 220, 20, 255, 220, 220, 20, 255, 220, 255, 255, 255, 255, 255, 220, 220, 255, 220, 20, 20, 220, 220, 20, 20, 220, 255, 255, 255, 255, ] ]; let imgs = util_generate_frames(4, 8, pixen); let exp = Some(Crop::from_edge_offsets((4, 8), 1, 1, 1, 5)); let act = MotiondetectCrop::from_frames(imgs); assert_eq!(exp, act); } #[test] fn test_detect_topleft() { #[rustfmt::skip] let pixen = [ vec![ 80 , 220, 220, 255, 220, 255, 255, 220, 220, 255, 255, 220, 255, 255, 255, 255, 255, 220, 220, 255, 220, 255, 255, 220, 220, 255, 255, 220, 255, 220, 255, 255, ], vec![ 20, 220, 220, 255, 220, 255, 255, 220, 220, 255, 255, 220, 255, 255, 255, 255, 255, 220, 220, 255, 220, 255, 255, 220, 220, 255, 255, 220, 255, 255, 255, 255, ] ]; let imgs = util_generate_frames(4, 8, pixen); let exp = Some(Crop::from_edge_offsets((4, 8), 0, 3, 0, 7)); let act = MotiondetectCrop::from_frames(imgs); assert_eq!(exp, act); } #[test] fn test_detect_botright() { #[rustfmt::skip] let pixen = [ vec![ 255, 220, 220, 255, 220, 255, 255, 220, 220, 255, 255, 220, 255, 255, 255, 255, 255, 220, 220, 255, 220, 255, 255, 220, 220, 255, 20, 20, 255, 255, 20, 20, ], vec![ 255, 220, 220, 255, 220, 255, 255, 220, 220, 255, 255, 220, 255, 255, 255, 255, 255, 220, 220, 255, 220, 255, 255, 220, 220, 255, 40, 20, 255, 255, 20, 40, ] ]; let imgs = util_generate_frames(4, 8, pixen); let exp = Some(Crop::from_edge_offsets((4, 8), 2, 0, 6, 0)); let act = MotiondetectCrop::from_frames(imgs); assert_eq!(exp, act); } //takes a series of vectors describing an image, and turns them into a sequence of images for running //the autocrop algorithm over fn util_generate_frames( x: u32, y: u32, pixen: impl IntoIterator>, ) -> Vec> { let raw = pixen .into_iter() .map(|pixen| GrayImage::from_vec(x, y, pixen).unwrap()) .map(Rc::new) .collect::>(); raw.iter().cloned().cycle().take(2).collect() } vid_dup_finder_common-0.3.0/src/motioncrop/utils.rs000064400000000000000000000127661046102023000206570ustar 00000000000000use image::{GenericImageView, GrayImage, Luma, Rgb, RgbImage}; use imageproc::definitions::Image; use itertools::Itertools; use crate::Crop; pub(super) fn regionize_image(img: &GrayImage) -> (Image>, usize) { use imageproc::region_labelling::Connectivity::Eight; let fg = imageproc::region_labelling::connected_components(img, Eight, image::Luma([0])); let num_regions = fg.pixels().unique().count() - 1; (fg, num_regions) } pub(super) fn maskize_regions(regions: &Image>) -> GrayImage { let mut ret = GrayImage::new(regions.width(), regions.height()); for (&mut Luma([ref mut ret_pix]), &Luma([ref region_pix])) in ret.pixels_mut().zip(regions.pixels()) { if *region_pix != 0 { *ret_pix = 255; } else { *ret_pix = 0; } } ret } pub(super) fn regions_in_mask(img: &Image>, mask: &GrayImage) -> Vec { assert!(img.dimensions() == mask.dimensions()); let mut ret = vec![]; for (Luma([img_pix]), Luma([mask_pix])) in img.pixels().zip(mask.pixels()) { if *mask_pix == 255 && !ret.contains(img_pix) { ret.push(*img_pix); } } ret } pub(super) fn retain_regions(img: &Image>, regions: &[u32]) -> Image> { let mut ret = img.clone(); for &mut Luma([ref mut pix]) in ret.pixels_mut() { if !regions.contains(pix) { *pix = 0; } } ret } pub(super) fn largest_region(img: &Image>) -> Option { let mut acc: Vec = vec![]; for Luma([pix]) in img.pixels() { if *pix == 0 { continue; } acc.resize_with((*pix as usize + 1).max(acc.len()), || 0); acc[*pix as usize] += 1; } acc.iter() .enumerate() .max_by(|(_, a), (_, b)| a.cmp(b)) .map(|(index, _)| index as u32) } pub fn view_mask(img: &GrayImage) -> Option> { let masked_pixels = img .enumerate_pixels() .filter(|(_x, _y, Luma([pix]))| *pix == 255); let (Some(min_x), Some(min_y), Some(max_x), Some(max_y)) = masked_pixels.fold( (None, None, None, None), |(acc_min_x, acc_min_y, acc_max_x, acc_max_y), (x, y, Luma([_pix]))| { //println!("{y}"); ( acc_min_x.map_or(Some(x), |a| Some(x.min(a))), acc_min_y.map_or(Some(y), |a| Some(y.min(a))), acc_max_x.map_or(Some(x), |a| Some(x.max(a))), acc_max_y.map_or(Some(y), |a| Some(y.max(a))), ) }, ) else { return None; }; //dbg!((min_x, min_y, max_x, max_y)); let width = (max_x - min_x) + 1; let height = (max_y - min_y) + 1; let ret = img.view(min_x, min_y, width, height); Some(ret) } // pub(super) fn view_letterbox(img: &mut GrayImage, crop: Crop) -> image::SubImage<&mut GrayImage> { // let (x, y, width, height) = crop.as_view_args(); // img.sub_image(x, y, width, height) // } pub(super) fn boolean_and(img_1: &GrayImage, img_2: &GrayImage) -> GrayImage { assert!(img_1.dimensions() == img_2.dimensions()); let mut ret = GrayImage::new(img_1.width(), img_1.height()); for ((&mut Luma([ref mut ret_pix]), &Luma([ref img_1_pix])), &Luma([ref img_2_pix])) in ret.pixels_mut().zip(img_1.pixels()).zip(img_2.pixels()) { if *img_1_pix == 255 && *img_2_pix == 255 { *ret_pix = 255; } else { *ret_pix = 0; } } ret } //////////////////////////////////////////////////////////////////////////////////// pub(super) fn clear_out_cropped_area(img: &mut GrayImage, crop: Crop) { for (x, y) in crop.enumerate_coords() { img.put_pixel(x, y, Luma([255])); } } #[allow(dead_code)] pub(super) enum RgbChan { Red, Green, Blue, } pub(super) fn tint_cropped_area(img: &RgbImage, crop: Crop, chan: RgbChan) -> RgbImage { let mut ret = img.clone(); let (x, y, width, height) = crop.as_view_args(); let view = img.view(x, y, width, height); for (x, y, _pix) in view.pixels() { let &mut Rgb([ref mut r, ref mut g, ref mut b]) = ret.get_pixel_mut(view.offsets().0 + x, view.offsets().1 + y); match chan { RgbChan::Red => *r = 255, RgbChan::Green => *g = 255, RgbChan::Blue => *b = 255, } } ret } pub(super) fn colourize_regions(img: &Image>) -> RgbImage { let colours = [ Rgb::([0, 0, 255]), Rgb::([255, 0, 255]), Rgb::([128, 128, 128]), Rgb::([0, 128, 0]), Rgb::([0, 255, 0]), Rgb::([128, 0, 0]), Rgb::([0, 0, 128]), Rgb::([128, 128, 0]), Rgb::([128, 0, 128]), Rgb::([255, 0, 0]), Rgb::([192, 192, 192]), Rgb::([0, 128, 128]), Rgb::([255, 255, 0]), ]; let mut ret = RgbImage::new(img.width(), img.height()); for (Luma([region_pix]), ret_pix) in img.pixels().zip(ret.pixels_mut()) { if *region_pix != 0 { *ret_pix = *colours.get(*region_pix as usize % colours.len()).unwrap(); } } ret } // pub(super) fn into_gray_image(img: impl Deref) -> GrayImage // where // V: GenericImageView>, // { // let mut ret = GrayImage::new(img.width(), img.height()); // for (Luma([ref mut dst_pix]), (_x, _y, Luma([src_pix]))) in ret.pixels_mut().zip(img.pixels()) { // *dst_pix = src_pix; // } // ret // } vid_dup_finder_common-0.3.0/src/resize_gray.rs000064400000000000000000000031771046102023000176450ustar 00000000000000use std::{borrow::Borrow, num::NonZeroU32, ops::Deref}; use fast_image_resize::images::ImageRef; use image::{DynamicImage, GrayImage, ImageBuffer, Luma}; use crate::Crop; use fast_image_resize::{self as fr, Resizer}; #[must_use] pub fn crop_resize_buf( src_frame: I, new_width: NonZeroU32, new_height: NonZeroU32, crop: Crop, ) -> GrayImage where I: Borrow, C>>, C: Deref, { let src_frame = src_frame.borrow(); let src_ref = ImageRef::new( src_frame.width(), src_frame.height(), src_frame.deref(), fast_image_resize::PixelType::U8, ) .unwrap(); let mut dst_image = DynamicImage::ImageLuma8(GrayImage::new(new_width.into(), new_height.into())); let mut resizer = Resizer::new(); let (left, top, width, height) = crop.as_view_args(); resizer .resize( &src_ref, &mut dst_image, Some(&fr::ResizeOptions::new().crop( left as f64, top as f64, width as f64, height as f64, )), ) .unwrap(); let DynamicImage::ImageLuma8(dst_image) = dst_image else { unreachable!() }; dst_image } #[must_use] pub fn resize_frame(frame: I, new_width: NonZeroU32, new_height: NonZeroU32) -> GrayImage where I: Borrow, C>>, C: Deref, C: AsRef<[u8]>, { let frame = frame.borrow(); crop_resize_buf( frame, new_width, new_height, Crop::from_edge_offsets(frame.dimensions(), 0, 0, 0, 0), ) } vid_dup_finder_common-0.3.0/src/resize_rgb.rs000064400000000000000000000013211046102023000174420ustar 00000000000000use fast_image_resize::{images::ImageRef, Resizer}; use image::{DynamicImage, RgbImage}; use std::{num::NonZeroU32, ops::Deref}; #[must_use] pub fn resize_img_rgb(frame: &RgbImage, new_width: NonZeroU32, new_height: NonZeroU32) -> RgbImage { let frame = ImageRef::new( frame.width(), frame.height(), frame.deref(), fast_image_resize::PixelType::U8x3, ) .unwrap(); let mut dst_image = DynamicImage::ImageRgb8(RgbImage::new(new_width.into(), new_height.into())); let mut resizer = Resizer::new(); resizer.resize(&frame, &mut dst_image, None).unwrap(); let DynamicImage::ImageRgb8(dst_image) = dst_image else { unreachable!() }; dst_image } vid_dup_finder_common-0.3.0/src/video_frames_gray.rs000064400000000000000000000315161046102023000210050ustar 00000000000000use image::{GenericImageView, GrayImage, Luma, SubImage}; use crate::{crop::Crop, motioncrop::autocrop_frames::MotiondetectCrop}; #[derive(Copy, Clone)] pub enum LetterboxColour { BlackWhite(u8), AnyColour(u8), } pub struct VideoFramesGray { frames: Vec, } impl VideoFramesGray { pub fn from_images(images: impl IntoIterator) -> Option { let img_vec = images.into_iter().collect::>(); if img_vec.is_empty() { return None; } Some(Self { frames: img_vec }) } #[must_use] pub fn into_inner(self) -> Vec { self.frames } } pub trait VdfFrameExt { type Item: image::GenericImageView>; fn frame(&self) -> &Self::Item; #[must_use] //detect the letterbox of a single video frame fn letterbox_crop(&self, colour: LetterboxColour) -> Crop { let frame = self.frame(); enum Side { Left, Right, Top, Bottom, } use Side::*; let (width, height) = frame.dimensions(); let measure_side = |side: Side| -> u32 { //get the window of pixels representing the next row/column to be checked let pixel_window = |idx: u32| { #[rustfmt::skip] let ret = match side { // x y width height Left => frame.view(idx, 0, 1, height), Right => frame.view(width - idx - 1, 0, 1, height), Top => frame.view(0, idx, width, 1), Bottom => frame.view(0, height - idx - 1, width, 1), }; ret }; let is_letterbox = |strip: &SubImage<&Self::Item>| { use LetterboxColour::*; let min_proportion: f64 = 0.9; let matching_pixels = match colour { BlackWhite(tol) => strip .pixels() .filter(|(_x, _y, Luma([l]))| { let black_enough = *l <= tol; let white_enough = *l >= (u8::MAX - tol); black_enough || white_enough }) .count(), AnyColour(tol) => { let mut mode_acc = [0usize; u8::MAX as usize + 1]; for (_x, _y, image::Luma::([l])) in strip.pixels() { mode_acc[l as usize] += 1; } let mode = mode_acc .iter() .enumerate() .max_by_key(|(_i, sum)| *sum) .map(|(i, _sum)| i as u8) .unwrap(); let count = strip .pixels() .filter(|(_x, _y, Luma([pix]))| mode.abs_diff(*pix) <= tol) .count(); count } }; let proportion = matching_pixels as f64 / (strip.dimensions().0 * strip.dimensions().1) as f64; proportion > min_proportion }; let pix_range = match side { Left | Right => 0..width, Top | Bottom => 0..height, }; pix_range .map(pixel_window) .take_while(|x| is_letterbox(x)) .count() as u32 }; let l = measure_side(Left); let r = measure_side(Right); let t = measure_side(Top); let b = measure_side(Bottom); //sanity check -- make sure there is at least 1 pix in the horz and vert dimension let remaining_horz = (width as i32) - (l as i32) - (r as i32); let remaining_vert = (height as i32) - (t as i32) - (b as i32); if (remaining_horz >= 1) && (remaining_vert >= 1) { Crop::from_edge_offsets((width, height), l, r, t, b) } else { Crop::from_edge_offsets((width, height), 0, 0, 0, 0) } } fn cropped(&self, crop: Crop) -> image::SubImage<&Self::Item> { let (x, y, w, h) = crop.as_view_args(); assert!(self.frame().dimensions() == crop.orig_res); self.frame().view(x, y, w, h) } } impl VdfFrameExt for T where T: GenericImageView>, { type Item = T; fn frame(&self) -> &Self::Item { self } } pub trait VdfFrameSeqExt { fn frames(&self) -> &[GrayImage]; fn letterbox_crop(&self) -> Crop { use LetterboxColour::*; let cfg: LetterboxColour = AnyColour(16); let crop = self .frames() .iter() .map(|frame| frame.letterbox_crop(cfg)) .reduce(|x, y| x.union(&y)) .unwrap(); crop } fn motiondetect_crop(&self) -> Crop { if let Some(ret) = MotiondetectCrop::from_frames(self.frames()) { ret } else { self.letterbox_crop() } } } impl VdfFrameSeqExt for VideoFramesGray { fn frames(&self) -> &[GrayImage] { self.frames.as_slice() } } #[derive(Clone)] pub struct RgbImageAsGray(pub image::RgbImage); impl image::GenericImageView for RgbImageAsGray { type Pixel = image::Luma; fn dimensions(&self) -> (u32, u32) { self.0.dimensions() } fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel { use image::Pixel; self.0.get_pixel(x, y).to_luma() } } pub fn cropdetect_none(frames: &[GrayImage]) -> Option { let dimensions = frames.iter().next().map(|f| f.dimensions())?; Some(Crop::from_edge_offsets(dimensions, 0, 0, 0, 0)) } pub fn cropdetect_letterbox(frames: &[GrayImage]) -> Option { // we don't need all of the frames to detect the crop. (this isn't a huge speedup because gstreamer still // decodes every frame) let frames = frames.iter().step_by(8).take(8); let ret = frames .map(|f| f.letterbox_crop(LetterboxColour::AnyColour(16))) .reduce(|a, b| a.union(&b))?; Some(ret) } pub fn cropdetect_motion(frames: &[GrayImage]) -> Option { MotiondetectCrop::from_frames(frames) } #[cfg(test)] mod test { use image::GrayImage; use crate::Crop; use super::VdfFrameExt; //Should find no crop, as the letterboxes will converge #[test] fn test_letterbox_crop_white_img_finds_no_crop() { #[rustfmt::skip] let pixs = vec![ 255, 255, 255, 255, 255, 255, 255, 255, 255, ]; let img = GrayImage::from_vec(3, 3, pixs).unwrap(); //test black/white { let exp = Crop::from_edge_offsets((3, 3), 0, 0, 0, 0); let act = img.letterbox_crop(super::LetterboxColour::BlackWhite(1)); assert_eq!(exp, act); } //test anycolour { let exp = Crop::from_edge_offsets((3, 3), 0, 0, 0, 0); let act = img.letterbox_crop(super::LetterboxColour::AnyColour(1)); assert_eq!(exp, act); } } //Should find no crop, as the letterboxes will converge #[test] fn test_letterbox_crop_black_img_finds_no_crop() { #[rustfmt::skip] let pixs = vec![ 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; let img = GrayImage::from_vec(3, 3, pixs).unwrap(); //test black/white { let exp = Crop::from_edge_offsets((3, 3), 0, 0, 0, 0); let act = img.letterbox_crop(super::LetterboxColour::BlackWhite(1)); assert_eq!(exp, act); } //test anycolour { let exp = Crop::from_edge_offsets((3, 3), 0, 0, 0, 0); let act = img.letterbox_crop(super::LetterboxColour::AnyColour(1)); assert_eq!(exp, act); } } #[test] fn test_letterbox_crop_any_colour_gray() { #[rustfmt::skip] let pixs = vec![ 127, 127, 127, 127, 0, 127, 127, 127, 127, ]; let img = GrayImage::from_vec(3, 3, pixs).unwrap(); //test black/white { let exp = Crop::from_edge_offsets((3, 3), 0, 0, 0, 0); let act = img.letterbox_crop(super::LetterboxColour::BlackWhite(1)); assert_eq!(exp, act); } //test anycolour { let exp = Crop::from_edge_offsets((3, 3), 1, 1, 1, 1); let act = img.letterbox_crop(super::LetterboxColour::AnyColour(1)); assert_eq!(exp, act); } } #[test] fn test_letterbox_crop_any_threshold() { #[rustfmt::skip] let pixs = vec![ 120, 130, 120, 130, 0, 130, 120, 130, 120, ]; let img = GrayImage::from_vec(3, 3, pixs).unwrap(); //just under, should find no crop { let exp = Crop::from_edge_offsets((3, 3), 0, 0, 0, 0); let act = img.letterbox_crop(super::LetterboxColour::AnyColour(9)); assert_eq!(exp, act); } { let exp = Crop::from_edge_offsets((3, 3), 1, 1, 1, 1); let act = img.letterbox_crop(super::LetterboxColour::AnyColour(10)); assert_eq!(exp, act); } } #[test] fn test_letterbox_crop_onepix() { #[rustfmt::skip] let pixs = vec![ 0, 0, 0, 0, 127, 0, 0, 0, 0, ]; let img = GrayImage::from_vec(3, 3, pixs).unwrap(); //test black/white { let exp = Crop::from_edge_offsets((3, 3), 1, 1, 1, 1); let act = img.letterbox_crop(super::LetterboxColour::BlackWhite(10)); assert_eq!(exp, act); } //test anycolour { let exp = Crop::from_edge_offsets((3, 3), 1, 1, 1, 1); let act = img.letterbox_crop(super::LetterboxColour::AnyColour(1)); assert_eq!(exp, act); } } #[test] fn test_letterbox_crop_topcorner() { #[rustfmt::skip] let pixs = vec![ 127, 0, 0, 0, 0, 0, 0, 0, 0, ]; let img = GrayImage::from_vec(3, 3, pixs).unwrap(); //test black/white { let exp = Crop::from_edge_offsets((3, 3), 0, 2, 0, 2); let act = img.letterbox_crop(super::LetterboxColour::BlackWhite(10)); assert_eq!(exp, act); } //test anycolour { let exp = Crop::from_edge_offsets((3, 3), 0, 2, 0, 2); let act = img.letterbox_crop(super::LetterboxColour::AnyColour(1)); assert_eq!(exp, act); } } #[test] fn test_letterbox_crop_rightedge() { #[rustfmt::skip] let pixs = vec![ 0, 0, 200, 0, 0, 120, 0, 0, 100, ]; let img = GrayImage::from_vec(3, 3, pixs).unwrap(); //test black/white { let exp = Crop::from_edge_offsets((3, 3), 2, 0, 0, 0); let act = img.letterbox_crop(super::LetterboxColour::BlackWhite(10)); assert_eq!(exp, act); } //test anycolour { let exp = Crop::from_edge_offsets((3, 3), 2, 0, 0, 0); let act = img.letterbox_crop(super::LetterboxColour::AnyColour(1)); assert_eq!(exp, act); } } #[test] fn test_letterbox_crop_bottom_right_2pix() { #[rustfmt::skip] let pixs = vec![ 0, 0, 0, 0, 127, 0, 0, 0, 127, ]; let img = GrayImage::from_vec(3, 3, pixs).unwrap(); //test black/white { let exp = Crop::from_edge_offsets((3, 3), 1, 0, 1, 0); let act = img.letterbox_crop(super::LetterboxColour::BlackWhite(10)); assert_eq!(exp, act); } //test anycolour { let exp = Crop::from_edge_offsets((3, 3), 1, 0, 1, 0); let act = img.letterbox_crop(super::LetterboxColour::AnyColour(1)); assert_eq!(exp, act); } } #[test] fn test_letterbox_crop_2pix_bottom() { #[rustfmt::skip] let pixs = vec![ 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; let img = GrayImage::from_vec(5, 6, pixs).unwrap(); let exp = Crop::from_edge_offsets((5, 6), 1, 1, 1, 2); let act = img.letterbox_crop(super::LetterboxColour::AnyColour(1)); assert_eq!(exp, act) } } vid_dup_finder_common-0.3.0/src/video_frames_rgb.rs000064400000000000000000000025701046102023000206130ustar 00000000000000use std::num::NonZeroU32; use image::{GenericImageView, RgbImage}; use crate::{resize_rgb::resize_img_rgb, video_frames_gray::RgbImageAsGray, Crop}; pub struct FrameSeqRgb { frames: Vec, } impl FrameSeqRgb { pub fn from_images(images: impl IntoIterator) -> Option { let img_vec = images.into_iter().map(RgbImageAsGray).collect::>(); if img_vec.is_empty() { return None; } Some(Self { frames: img_vec }) } #[must_use] pub fn into_inner(self) -> Vec { self.frames.into_iter().map(|x| x.0).collect::>() } #[must_use] pub fn crop(&self, crop: Crop) -> Self { let new_frames = self .frames .iter() .map(|img| { let (x, y, w, h) = crop.as_view_args(); img.0.view(x, y, w, h).to_image() }) .map(RgbImageAsGray) .collect(); Self { frames: new_frames } } #[must_use] pub fn resize(&self, new_width: NonZeroU32, new_height: NonZeroU32) -> Self { let resized_frames = self .frames .iter() .map(|frame| resize_img_rgb(&frame.0, new_width, new_height)) .map(RgbImageAsGray) .collect(); Self { frames: resized_frames, } } }