diff --git a/.env.example b/.env.example index 34e2330..5fa3d70 100644 --- a/.env.example +++ b/.env.example @@ -1,22 +1,32 @@ # Synology NAS -NAS_FQDN="https://your-nas.example.com" -NAS_USER="your_username" -NAS_PASS="your_password" -NAS_FILE="/Team Folder/path/to/file.osheet" +NAS_FQDN="https://nas.company.local:5001" +NAS_USER="DOMAIN\\service_account" +NAS_PASS="strongpassword" +NAS_FILE="/Team Folder/Anonsy/sport.osheet" -# Logging (optional, defaults to info) +# Logging RUST_LOG="info" -# Web Server (optional, defaults to 3000) +# Web Server WEB_PORT="3000" # Nexrender -NEXRENDER_API_URL="http://nexrender-server:3050/api/v1/jobs" -OUTPUT_FOLDER="/path/to/output" +NEXRENDER_API_URL="http://render-01:3050/api/v1/jobs" +OUTPUT_FOLDER="/mnt/nexrender/output" # After Effects Templates -TEMPLATE_DOUBLE_SRC="file:///path/to/double_team_template.aep" -TEMPLATE_SINGLE_SRC="file:///path/to/single_team_template.aep" +TEMPLATE_DOUBLE_SRC="file:///mnt/templates/double_team.aepx" +TEMPLATE_SINGLE_SRC="file:///mnt/templates/single_team.aepx" TEMPLATE_COMPOSITION="main" TEMPLATE_OUTPUT_MODULE="h264" -TEMPLATE_OUTPUT_EXT="mp4" \ No newline at end of file +TEMPLATE_OUTPUT_EXT="mp4" + +# SMB Upload for approved videos (optional, only needed for web mode) +# Format: smb://server/share/optional/folder +SMB_UPLOAD_URL="smb://storage.company.local/approved_videos" +SMB_USERNAME="DOMAIN\\service_account" +SMB_PASSWORD="strongpassword" +# Optional: SMB_WORKGROUP="DOMAIN" + +# Database (optional, default sqlite:ae_anons.db) +DATABASE_URL="sqlite:ae_anons.db" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 224f194..112692a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ae_anons" -version = "0.2.6" +version = "0.3.0" dependencies = [ "anyhow", "askama", @@ -23,19 +23,60 @@ dependencies = [ "env_logger", "futures", "log", + "mime_guess", "openssl", "regex", "reqwest", "serde", "serde_json", + "smb", + "sqlx", "thiserror", "tokio", + "tokio-stream", + "tokio-util", "tower", "tower-http", + "url", "urlencoding", "winres", ] +[[package]] +name = "aead" +version = "0.6.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a578e7d4edaef88aeb9cdd81556f4a62266ce26601317c006a79e8bc58b5af" +dependencies = [ + "crypto-common 0.2.0-rc.8", + "inout", +] + +[[package]] +name = "aes" +version = "0.9.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9e1c818b25efb32214df89b0ec22f01aa397aaeb718d1022bf0635a3bfd1a8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.11.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f5c07f414d7dc0755870f84c7900425360288d24e0eae4836f9dee19a30fa5f" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -45,6 +86,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -111,13 +158,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] -name = "arbitrary" -version = "1.4.2" +name = "array-init" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" [[package]] name = "askama" @@ -146,7 +190,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn", + "syn 2.0.117", ] [[package]] @@ -171,6 +215,54 @@ dependencies = [ "winnow", ] +[[package]] +name = "async-dnssd" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d49ffe175ab45bbfd74b548313d9d7cdfff27161a94b007b52eeeb5f9aaa15e" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-util", + "libc", + "log", + "pin-utils", + "pkg-config", + "tokio", + "winapi", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atoi_simd" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad17c7c205c2c28b527b9845eeb91cf1b4d008b438f98ce0e628227a822758e" +dependencies = [ + "debug_unsafe", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -190,6 +282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", + "base64", "bytes", "form_urlencoded", "futures-util", @@ -208,8 +301,10 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1 0.10.6", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", @@ -235,12 +330,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "basic-toml" version = "0.1.10" @@ -250,11 +357,71 @@ dependencies = [ "serde", ] +[[package]] +name = "binrw" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53195f985e88ab94d1cc87e80049dd2929fd39e4a772c5ae96a7e5c4aad3642" +dependencies = [ + "array-init", + "binrw_derive", + "bytemuck", +] + +[[package]] +name = "binrw_derive" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5910da05ee556b789032c8ff5a61fb99239580aa3fd0bfaa8f4d094b2aee00ad" +dependencies = [ + "either", + "owo-colors", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96eb4cdd6cf1b31d671e9efe75c5d1ec614776856cefbe109ca373554a6d514f" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] [[package]] name = "bumpalo" @@ -262,6 +429,12 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" @@ -276,19 +449,30 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "calamine" -version = "0.26.1" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138646b9af2c5d7f1804ea4bf93afc597737d2bd4f7341d67c48b03316976eb1" +checksum = "20ae05a4e39297eecf9a994210d27501318c37a9318201f8e11050add82bb6f0" dependencies = [ + "atoi_simd", "byteorder", "codepage", "encoding_rs", + "fast-float2", "log", "quick-xml", "serde", "zip", ] +[[package]] +name = "cbc" +version = "0.2.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dbf9e5b071e9de872e32b73f485e8f644ff47c7011d95476733e7482ee3e5c3" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.60" @@ -299,12 +483,41 @@ dependencies = [ "shlex", ] +[[package]] +name = "ccm" +version = "0.6.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ad0c3a709bfe243b5585554f3d697dc015d04ebad7598fd4d650bc2d7591a3" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f895fb33c1ad22da4bc79d37c0bddff8aee2ba4575705345eb73b8ffbc386074" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.0-rc-3", +] + [[package]] name = "chrono" version = "0.4.44" @@ -314,10 +527,22 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] +[[package]] +name = "cipher" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98d708bac5451350d56398433b19a7889022fa9187df1a769c0edbc3b2c03167" +dependencies = [ + "block-buffer 0.11.0", + "crypto-common 0.2.0-rc.8", + "inout", +] + [[package]] name = "clap" version = "4.6.1" @@ -349,7 +574,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -358,6 +583,23 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cmac" +version = "0.8.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f22bad4cbf035f087384fa49d3c5d105af29801fe3d04831a737a982d67cd0" +dependencies = [ + "cipher", + "dbl", + "digest 0.11.0-rc.5", +] + +[[package]] +name = "cmov" +version = "0.5.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5417da527aa9bf6a1e10a781231effd1edd3ee82f27d5f8529ac9b279babce96" + [[package]] name = "codepage" version = "0.1.2" @@ -373,6 +615,27 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "core-foundation" version = "0.9.4" @@ -399,6 +662,30 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" @@ -408,6 +695,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -415,14 +711,192 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "derive_arbitrary" -version = "1.4.2" +name = "crypto-bigint" +version = "0.7.0-rc.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +checksum = "37387ceb32048ff590f2cbd24d8b05fffe63c3f69a5cfa089d4f722ca4385a19" +dependencies = [ + "ctutils", + "getrandom 0.4.0-rc.0", + "hybrid-array", + "num-traits", + "rand_core 0.10.0-rc-3", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.0-rc.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6165b8029cdc3e765b74d3548f85999ee799d5124877ce45c2c85ca78e4d4aa" +dependencies = [ + "getrandom 0.4.0-rc.0", + "hybrid-array", + "rand_core 0.10.0-rc-3", +] + +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-primes" +version = "0.7.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79c98a281f9441200b24e3151407a629bfbe720399186e50516da939195e482" +dependencies = [ + "crypto-bigint", + "libm", + "rand_core 0.10.0-rc-3", +] + +[[package]] +name = "ctr" +version = "0.10.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0ec605a95e78815a4c4b8040217d56d5a1ab37043851ee9e7e65b89afa00e3" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758e5ed90be3c8abff7f9a6f37ab7f6d8c59c2210d448b81f3f508134aec84e4" +dependencies = [ + "cmov", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ae8b2fe5e4995d7fd08a7604e794dc569a65ed19659f5939d529813ed816d38" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.11.0-rc.5", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "dbl" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d7a944e61df464668c5f51f56cc667396a8821434273112948ea0b66e405d7" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "debug_unsafe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eed2c4702fa172d1ce21078faa7c5203e69f5394d48cc436d25928394a867a2" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468 0.7.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653" +dependencies = [ + "const-oid 0.10.2", + "pem-rfc7468 1.0.0", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "des" +version = "0.9.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f51594a70805988feb1c85495ddec0c2052e4fbe59d9c0bb7f94bfc164f4f90" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf9423bafb058e4142194330c52273c343f8a5beb7176d052f0e73b17dd35b9" +dependencies = [ + "block-buffer 0.11.0", + "const-oid 0.10.2", + "crypto-common 0.2.0-rc.8", + "subtle", ] [[package]] @@ -433,7 +907,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -442,6 +916,83 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "ecdsa" +version = "0.17.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "569a1f3377df19ab839b2811061095ff7d9fb7ea3c0e500b7a4724343cf6ee3d" +dependencies = [ + "der 0.8.0-rc.10", + "digest 0.11.0-rc.5", + "elliptic-curve", + "rfc6979", + "signature 3.0.0-rc.6", + "spki 0.8.0-rc.4", + "zeroize", +] + +[[package]] +name = "ed25519" +version = "3.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "594435fe09e345ee388e4e8422072ff7dfeca8729389fbd997b3f5504c44cd47" +dependencies = [ + "pkcs8 0.11.0-rc.8", + "signature 3.0.0-rc.6", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b9f613e0c236c699bf70d39f825594d9b03aadfd8dd856ea40685f782a4ef2" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.10.0-rc-3", + "sha2 0.11.0-rc.3", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.14.0-rc.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bfae4ab886ff791e2119cc79402281e35408f22b6b7322acef371d01061054b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.11.0-rc.5", + "getrandom 0.4.0-rc.0", + "hkdf 0.13.0-rc.3", + "hybrid-array", + "once_cell", + "pem-rfc7468 1.0.0", + "pkcs8 0.11.0-rc.8", + "rand_core 0.10.0-rc-3", + "rustcrypto-ff", + "rustcrypto-group", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -490,12 +1041,56 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + [[package]] name = "fastrand" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "ff" +version = "0.14.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d42dd26f5790eda47c1a2158ea4120e32c35ddc9a7743c98a292accc01b54ef3" +dependencies = [ + "rand_core 0.9.5", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -508,8 +1103,19 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ - "crc32fast", "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", ] [[package]] @@ -518,6 +1124,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -584,6 +1196,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.32" @@ -598,7 +1221,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -630,6 +1253,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -637,8 +1270,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -648,9 +1283,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b99f0d993a2b9b97b9a201193aa8ad21305cde06a3be9a7e1f8f4201e5cc27e" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core 0.10.0-rc-3", + "wasip2", +] + +[[package]] +name = "ghash" +version = "0.6.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333de57ed9494a40df4bbb866752b100819dde0d18f2264c48f5a08a85fe673d" +dependencies = [ + "polyval", +] + +[[package]] +name = "group" +version = "0.14.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff6a0b2dd4b981b1ae9e3e6830ab146771f3660d31d57bafd9018805a91b0f1" +dependencies = [ + "ff", + "rand_core 0.9.5", + "subtle", ] [[package]] @@ -672,18 +1342,89 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hkdf" +version = "0.13.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfbb4225acf2b5cc4e12d384672cd6d1f0cb980ff5859ffcf144db25b593a24d" +dependencies = [ + "hmac 0.13.0-rc.3", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c597ac7d6cc8143e30e83ef70915e7f883b18d8bec2e2b2bce47f5bbb06d57" +dependencies = [ + "digest 0.11.0-rc.5", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "http" version = "1.4.0" @@ -735,6 +1476,17 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "subtle", + "typenum", + "zeroize", +] + [[package]] name = "hyper" version = "1.9.0" @@ -767,6 +1519,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", + "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", @@ -947,7 +1700,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.0", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding", + "hybrid-array", ] [[package]] @@ -999,7 +1762,7 @@ checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1014,12 +1777,68 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kbkdf" +version = "0.1.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abcf51a2ffe5a3317b0da773f988ea38211a530d87c6dfcf76634629fca1bc6" +dependencies = [ + "digest 0.11.0-rc.5", +] + +[[package]] +name = "keccak" +version = "0.2.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d546793a04a1d3049bd192856f804cfe96356e2cf36b54b4e575155babe9f41" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + [[package]] name = "libc" version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.4", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -1047,12 +1866,64 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4_flex" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" + [[package]] name = "matchit" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "md-5" +version = "0.11.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9ec86664728010f574d67ef01aec964e6f1299241a3402857c1a8a390a62478" +dependencies = [ + "cfg-if", + "digest 0.11.0-rc.5", +] + +[[package]] +name = "md4" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da5ac363534dce5fabf69949225e174fbf111a498bf0ff794c8ea1fba9f3dda" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.8.0" @@ -1096,6 +1967,27 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "native-tls" version = "0.2.18" @@ -1113,6 +2005,59 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.6", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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-traits" version = "0.2.19" @@ -1120,6 +2065,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "oid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2" +dependencies = [ + "serde", ] [[package]] @@ -1140,7 +2095,7 @@ version = "0.10.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -1157,7 +2112,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1188,6 +2143,59 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "p256" +version = "0.14.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caab26e75ab3d0790a0f29df73f006308ada2e1fbbfcbab03e92346adc43dd9" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2 0.11.0-rc.3", +] + +[[package]] +name = "p384" +version = "0.14.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30732b26c446549117425e4e905456494ff6c87da40216fd6c71ccc7c3976080" +dependencies = [ + "ecdsa", + "elliptic-curve", + "fiat-crypto", + "primefield", + "primeorder", + "sha2 0.11.0-rc.3", +] + +[[package]] +name = "p521" +version = "0.14.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd4780ae0e4fc6a0a722123508ee69e88d45f94bdff67ef25528f659dda9dbd" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2 0.11.0-rc.3", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1206,29 +2214,250 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pbkdf2" +version = "0.13.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3fc18bb4460ac250ba6b75dfa7cf9d0b2273e3e623f660bd6ce2c3e902342e" +dependencies = [ + "digest 0.11.0-rc.5", + "hmac 0.13.0-rc.3", + "sha1 0.11.0-rc.3", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "picky" +version = "7.0.0-rc.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4d077505451145769907bee30661be7182a24c75e97f040ccaf5848fd5de928" +dependencies = [ + "base64", + "block-buffer 0.11.0", + "block-padding", + "crypto-bigint", + "crypto-common 0.2.0-rc.8", + "crypto-primes", + "curve25519-dalek", + "der 0.8.0-rc.10", + "digest 0.11.0-rc.5", + "ecdsa", + "ed25519", + "ed25519-dalek", + "elliptic-curve", + "ff", + "group", + "hex", + "hkdf 0.13.0-rc.3", + "inout", + "keccak", + "md-5 0.11.0-rc.2", + "p256", + "p384", + "p521", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "pkcs1 0.8.0-rc.4", + "pkcs8 0.11.0-rc.8", + "primefield", + "primeorder", + "rand 0.10.0-rc.6", + "rand_core 0.10.0-rc-3", + "rfc6979", + "rsa 0.10.0-rc.12", + "sec1", + "serde", + "sha1 0.11.0-rc.3", + "sha2 0.11.0-rc.3", + "sha3", + "signature 3.0.0-rc.6", + "spki 0.8.0-rc.4", + "thiserror", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "picky-asn1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff038f9360b934342fb3c0a1d6e82c438a2624b51c3c6e3e6d7cf252b6f3ee3" +dependencies = [ + "oid", + "serde", + "serde_bytes", + "time", + "zeroize", +] + +[[package]] +name = "picky-asn1-der" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09561f7aa88cd6fae98edaf7dca3d8520da5b86a92839f2109a928af12a55c41" +dependencies = [ + "picky-asn1", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-x509" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cacf27a75aa1b95e8b1726569fcd693a19d3445aecbcd0d20120e7f82a3cb3b" +dependencies = [ + "base64", + "crypto-bigint", + "oid", + "picky-asn1", + "picky-asn1-der", + "serde", + "widestring", + "zeroize", +] + +[[package]] +name = "picky-krb" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1647012bc081792da896ec538040c34aac79ce6b11cfdd68029b3baa06b0453f" +dependencies = [ + "aes", + "block-buffer 0.11.0", + "block-padding", + "byteorder", + "cbc", + "cipher", + "crypto-bigint", + "crypto-common 0.2.0-rc.8", + "des", + "digest 0.11.0-rc.5", + "hmac 0.13.0-rc.3", + "inout", + "oid", + "pbkdf2", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "rand 0.10.0-rc.6", + "serde", + "sha1 0.11.0-rc.3", + "thiserror", + "uuid", +] + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", +] + +[[package]] +name = "pkcs1" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" +dependencies = [ + "der 0.8.0-rc.10", + "spki 0.8.0-rc.4", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0-rc.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77089aec8290d0b7bb01b671b091095cf1937670725af4fd73d47249f03b12c0" +dependencies = [ + "der 0.8.0-rc.10", + "spki 0.8.0-rc.4", +] + [[package]] name = "pkg-config" version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polyval" +version = "0.7.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad60831c19edda4b20878a676595c357e93a9b4e6dca2ba98d75b01066b317b" +dependencies = [ + "cfg-if", + "cpufeatures", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -1253,6 +2482,43 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primefield" +version = "0.14.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b2bd4ddf14d08c2bc8d9cceaf362f28c146b0737d58c7fee6534b99e19a3ee" +dependencies = [ + "crypto-bigint", + "rand_core 0.10.0-rc-3", + "rustcrypto-ff", + "subtle", + "zeroize", +] + +[[package]] +name = "primeorder" +version = "0.14.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e56388fad6b8c7576e6987fd0c8c7f3bf94d73d74ae794edaac3e420f9cabfe" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1264,14 +2530,69 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "encoding_rs", "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -1287,13 +2608,98 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bccc05ac8fad6ee391f3cc6725171817eed960345e2fb42ad229d486c1ca2d98" +dependencies = [ + "chacha20", + "getrandom 0.4.0-rc.0", + "rand_core 0.10.0-rc-3", +] + +[[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.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.0-rc-3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66ee92bc15280519ef199a274fe0cafff4245d31bc39aaa31c011ad56cb1f05" + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +dependencies = [ + "bitflags 2.11.0", ] [[package]] @@ -1334,6 +2740,7 @@ dependencies = [ "base64", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2", @@ -1351,6 +2758,9 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -1358,6 +2768,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -1369,6 +2780,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8e2323084c987a72875b2fd682b7307d5cf14d47e3875bb5e89948e8809d4" +dependencies = [ + "hmac 0.13.0-rc.3", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -1383,19 +2804,87 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid 0.9.6", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1 0.7.5", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "signature 2.2.0", + "spki 0.7.3", + "subtle", + "zeroize", +] + +[[package]] +name = "rsa" +version = "0.10.0-rc.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2b1eacbc34fbaf77f6f1db1385518446008d49b9f9f59dc9d1340fce4ca9e" +dependencies = [ + "const-oid 0.10.2", + "crypto-bigint", + "crypto-primes", + "digest 0.11.0-rc.5", + "pkcs1 0.8.0-rc.4", + "pkcs8 0.11.0-rc.8", + "rand_core 0.10.0-rc-3", + "signature 3.0.0-rc.6", + "spki 0.8.0-rc.4", + "zeroize", +] + [[package]] name = "rustc-hash" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustcrypto-ff" +version = "0.14.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9cd37111549306f79b09aa2618e15b1e8241b7178c286821e3dd71579db4db" +dependencies = [ + "rand_core 0.10.0-rc-3", + "subtle", +] + +[[package]] +name = "rustcrypto-group" +version = "0.14.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e394cd734b5f97dfc3484fa42aad7acd912961c2bcd96c99aa05b3d6cab7cafd" +dependencies = [ + "rand_core 0.10.0-rc-3", + "rustcrypto-ff", + "subtle", +] + [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -1408,19 +2897,34 @@ version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ + "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] @@ -1462,13 +2966,27 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.8.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2568531a8ace88b848310caa98fb2115b151ef924d54aa523e659c21b9d32d71" +dependencies = [ + "base16ct", + "ctutils", + "der 0.8.0-rc.10", + "hybrid-array", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -1485,6 +3003,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -1495,6 +3019,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -1512,7 +3046,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1551,6 +3085,70 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa1ae819b9870cadc959a052363de870944a1646932d274a4e270f64bf79e5ef" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-rc.5", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-rc.5", +] + +[[package]] +name = "sha3" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2103ca0e6f4e9505eae906de5e5883e06fc3b2232fb5d6914890c7bbcb62f478" +dependencies = [ + "digest 0.11.0-rc.5", + "keccak", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1567,6 +3165,26 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "3.0.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a96996ccff7dfa16f052bd995b4cecc72af22c35138738dc029f0ead6608d" +dependencies = [ + "digest 0.11.0-rc.5", + "rand_core 0.10.0-rc-3", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -1584,6 +3202,149 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "smb" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502b7620c9f63f2aabccfeb6fe454d0ea3e86731ff76f961126d9a249f06edf4" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "binrw", + "ccm", + "cmac", + "crypto-common 0.2.0-rc.8", + "futures", + "futures-core", + "futures-util", + "hmac 0.13.0-rc.3", + "kbkdf", + "log", + "lz4_flex", + "maybe-async", + "modular-bitfield", + "pastey", + "rand 0.8.6", + "sha2 0.11.0-rc.3", + "smb-dtyp", + "smb-fscc", + "smb-msg", + "smb-rpc", + "smb-transport", + "sspi", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "url", +] + +[[package]] +name = "smb-dtyp" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c53a2e0e811782d8ddee78553db367fe301832ed89461de9a2bd9c7596eba6f" +dependencies = [ + "binrw", + "modular-bitfield", + "pastey", + "rand 0.8.6", + "smb-dtyp-derive", + "time", +] + +[[package]] +name = "smb-dtyp-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907882c54c427b7b371121755096094d7961216fe45adb088ed8863a49b3f171" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "smb-fscc" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2b9929267ccfe0338eb060448ff199d1393f37046e1fc37f65344c64887b9da" +dependencies = [ + "binrw", + "modular-bitfield", + "pastey", + "smb-dtyp", + "thiserror", + "time", +] + +[[package]] +name = "smb-msg" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e31d6e0903fd4bdce3e27451642ee76a36d1cd90375b8543cdf8c8f696f5b" +dependencies = [ + "binrw", + "modular-bitfield", + "pastey", + "smb-dtyp", + "smb-fscc", + "smb-msg-derive", + "thiserror", + "time", +] + +[[package]] +name = "smb-msg-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46df670deb62a5b52ba7f1de0f5d08a0fd6123ab6f857ed1b12b80859454e917" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "smb-rpc" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6fc5bf5ff09109617813740d82e4dfa275c84388d4139d583a99681c7a980c5" +dependencies = [ + "binrw", + "maybe-async", + "modular-bitfield", + "pastey", + "smb-dtyp", + "thiserror", + "time", +] + +[[package]] +name = "smb-transport" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad8a8257c0cd01b1c86782116ad97e1028295b5421db3463423f26aee1899fe" +dependencies = [ + "binrw", + "futures-core", + "futures-util", + "log", + "maybe-async", + "modular-bitfield", + "pastey", + "smb-dtyp", + "thiserror", + "time", + "tokio", + "tokio-util", +] [[package]] name = "socket2" @@ -1595,12 +3356,313 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" +dependencies = [ + "base64ct", + "der 0.8.0-rc.10", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "log", + "memchr", + "native-tls", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2 0.10.9", + "smallvec", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.117", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.9", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.117", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64", + "bitflags 2.11.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf 0.12.4", + "hmac 0.12.1", + "itoa", + "log", + "md-5 0.10.6", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.6", + "rsa 0.9.10", + "serde", + "sha1 0.10.6", + "sha2 0.10.9", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64", + "bitflags 2.11.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf 0.12.4", + "hmac 0.12.1", + "home", + "itoa", + "log", + "md-5 0.10.6", + "memchr", + "once_cell", + "rand 0.8.6", + "serde", + "serde_json", + "sha2 0.10.9", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror", + "tracing", + "url", +] + +[[package]] +name = "sspi" +version = "0.18.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed99306222fde1e7fefb2f09e32715897c4643f00a52009534b72cc3e0eef36b" +dependencies = [ + "async-dnssd", + "async-recursion", + "bitflags 2.11.0", + "block-buffer 0.11.0", + "byteorder", + "cfg-if", + "crypto-bigint", + "crypto-common 0.2.0-rc.8", + "crypto-mac", + "crypto-primes", + "curve25519-dalek", + "der 0.8.0-rc.10", + "digest 0.11.0-rc.5", + "ed25519-dalek", + "ff", + "futures", + "getrandom 0.3.4", + "group", + "hmac 0.13.0-rc.3", + "md-5 0.11.0-rc.2", + "md4", + "num-derive", + "num-traits", + "oid", + "p256", + "p384", + "p521", + "pem-rfc7468 1.0.0", + "picky", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "picky-krb", + "pkcs1 0.8.0-rc.4", + "pkcs8 0.11.0-rc.8", + "primefield", + "primeorder", + "rand 0.10.0-rc.6", + "reqwest", + "rsa 0.10.0-rc.12", + "rustls", + "serde", + "sha1 0.11.0-rc.3", + "sha2 0.11.0-rc.3", + "signature 3.0.0-rc.6", + "spki 0.8.0-rc.4", + "time", + "tokio", + "tracing", + "url", + "uuid", + "windows", + "windows-registry", + "zeroize", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1613,6 +3675,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -1641,7 +3714,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1650,7 +3723,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -1695,7 +3768,38 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "js-sys", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -1709,10 +3813,25 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.52.0" +name = "tinyvec" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -1733,7 +3852,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1756,6 +3875,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -1800,7 +3942,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags", + "bitflags 2.11.0", "bytes", "futures-core", "futures-util", @@ -1842,9 +3984,21 @@ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -1860,18 +4014,77 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.4", + "sha1 0.10.6", + "thiserror", +] + +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicase" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "universal-hash" +version = "0.6.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0386f227888b17b65d3e38219a7d41185035471300855c285667811907bb1677" +dependencies = [ + "crypto-common 0.2.0-rc.8", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -1908,12 +4121,30 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "want" version = "0.3.1" @@ -1938,6 +4169,12 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.118" @@ -1980,7 +4217,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -2016,6 +4253,75 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[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 = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -2029,6 +4335,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -2037,7 +4354,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2048,7 +4365,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2057,6 +4374,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + [[package]] name = "windows-registry" version = "0.6.1" @@ -2086,13 +4413,31 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", ] [[package]] @@ -2104,70 +4449,201 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "1.0.1" @@ -2198,6 +4674,17 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "x25519-dalek" +version = "3.0.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5887899407ca8fb861126d509bb08465c14a9c60fad1f24c59ed59630a45586" +dependencies = [ + "curve25519-dalek", + "rand_core 0.10.0-rc-3", + "zeroize", +] + [[package]] name = "yoke" version = "0.8.2" @@ -2217,10 +4704,30 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zerofrom" version = "0.1.7" @@ -2238,7 +4745,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -2247,6 +4754,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "zerotrie" @@ -2278,26 +4799,29 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "zip" -version = "2.4.2" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" dependencies = [ - "arbitrary", "crc32fast", - "crossbeam-utils", - "displaydoc", "flate2", "indexmap", "memchr", - "thiserror", + "typed-path", "zopfli", ] +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index 1c9a28e..7d88329 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ae_anons" -version = "0.2.6" +version = "0.3.0" edition = "2021" license = "MIT" authors = ["Alexey Barabanov "] @@ -11,31 +11,41 @@ keywords = ["nexrender", "after-effects", "synology", "automation"] categories = ["command-line-utilities", "multimedia"] [dependencies] -reqwest = { version = "0.12", features = ["json", "multipart", "stream"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "2.0" -urlencoding = "2.1" -tokio = { version = "1.0", features = ["full"] } -dotenv = "0.15" -calamine = "0.26" -chrono = "0.4" -regex = "1.11" -futures = "0.3" -anyhow = "1.0" -log = "0.4" -env_logger = "0.11" -bytes = "1.9" +reqwest = { version = "0.12.5", features = ["json", "multipart", "stream"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.140" +thiserror = "2.0.12" +urlencoding = "2.1.3" +tokio = { version = "1.52.1", features = ["full", "sync"] } +dotenv = "0.15.0" +calamine = "0.34" +chrono = { version = "0.4.41", features = ["serde"] } +regex = "1.11.1" +futures = "0.3.31" +anyhow = "1.0.98" +log = "0.4.27" +env_logger = "0.11.8" +bytes = "1.10.1" +sqlx = { version = "0.8.5", features = [ + "runtime-tokio-native-tls", + "sqlite", + "chrono", +] } +tokio-stream = "0.1.17" +mime_guess = "2.0.5" +tokio-util = "0.7.14" +url = "2.5.0" +smb = "0.11.1" -# Web server (updated to latest stable versions) -axum = "0.8.9" -tower = "0.5" -tower-http = { version = "0.6", features = ["fs", "trace"] } -askama = "0.15" -clap = { version = "4.5", features = ["derive"] } +# Web server (with WebSocket support) +axum = { version = "0.8.9", features = ["ws"] } +tower = "0.5.2" +tower-http = { version = "0.6.4", features = ["fs", "trace"] } +askama = "0.15.4" +clap = { version = "4.5.38", features = ["derive"] } [dev-dependencies] -tokio = { version = "1.0", features = ["full", "rt-multi-thread"] } +tokio = { version = "1.52.1", features = ["full", "rt-multi-thread"] } [target.x86_64-unknown-linux-gnu.dependencies] openssl = { version = "0.10", features = ["vendored"] } @@ -53,4 +63,4 @@ strip = true winres = "0.1" [build-dependencies] -winres = "0.1" \ No newline at end of file +winres = "0.1" diff --git a/README.md b/README.md index 0e8f255..e419ba8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AE Anons - Автоматизированный генератор спортивных анонсов в After Effects +# AE Anons - Автономный генератор спортивных анонсов в After Effects

@@ -10,23 +10,22 @@ [![Rust](https://img.shields.io/badge/Rust-1.70%2B-orange.svg)](https://www.rust-lang.org/) [![Status](https://img.shields.io/badge/status-production-green.svg)](https://git.tvstart.ru/lexx/AE_Anons) -Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office. +**AE Anons** — полностью автономная (standalone) система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender. Все данные берутся из электронных таблиц Synology Office, рендеринг выполняется локально, управление заданиями происходит через встроенный веб-интерфейс с мгновенными обновлениями. Система не требует доступа в интернет — все ресурсы (иконки, стили, шрифты) встроены в бинарный файл. -## Новое в версии v0.2.6 +## Новое в версии v0.3.0 -- Сортировка заданий по дате и времени события (самые ранние — первыми) -- Сортировка по варианту (оригинал → today → tomorrow) -- Последовательная отправка для гарантированного порядка в очереди -- Исправлена нормализация дефисов (2 и более дефиса подряд → 1) -- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS) -- Добавлена поддержка кеширования ассетов (cache: true/false) -- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются +- **WebSocket** — мгновенные обновления списка заданий (без устаревшего polling каждые 60 секунд) +- **SQLite** — хранение истории заданий и статуса апрува +- **Просмотр видео** прямо в веб-интерфейсе (по ссылке на готовый файл) +- **Апрув заданий** с копированием видео на SMB-шару (без монтирования, с поддержкой Active Directory) +- **Уведомления в реальном времени** о старте/завершении генерации, ошибках +- **Улучшенный веб-интерфейс** с кнопками апрува и иконками превью +- **Оптимизация памяти** — строки таблицы обрабатываются потоково, неиспользуемые логотипы и ассеты удаляются после генерации. Пиковое потребление памяти достигается только в момент загрузки и парсинга Excel, после чего память освобождается. Это позволяет работать с таблицами большого объёма (десятки тысяч строк) и обеспечивает стабильную работу в режиме долгоживущего сервиса. +- Полная автономность — все статические ресурсы (HTML, CSS, Font Awesome, логотип, favicon) встроены в бинарный файл. ## Обзор -AE Anons — это CLI-утилита и веб-сервер на Rust, которая выступает как **интеллектуальный генератор заданий** -для [Nexrender](https://github.com/inlife/nexrender) — опенсорсного оркестратора рендеринга -After Effects (лицензия MIT). +AE Anons — это CLI-утилита и веб-сервер на Rust, которая выступает как **интеллектуальный генератор заданий** для [Nexrender](https://github.com/inlife/nexrender) — опенсорсного оркестратора рендеринга After Effects. ### Режимы работы @@ -37,154 +36,147 @@ After Effects (лицензия MIT). - Мониторинг завершения и выход 2. **Веб-сервер** (`--web`): - - Запуск веб-интерфейса на порту `:3000` - - Управление заданиями через браузер - - Автообновление статуса каждую минуту + - Запуск веб-интерфейса на порту `:3000` (настраивается) + - Управление заданиями через браузер с WebSocket-обновлениями + - Просмотр видео и апрув готовых роликов ## Особенности -- **Интеграция с Synology**: Аутентификация и загрузка файлов с NAS Synology -- **Экспорт офисных таблиц**: Автоматическое преобразование файлов .osheet в формат Excel -- **Гибкий парсинг данных**: Динамический парсинг листов с обнаружением заголовков -- **Множественная генерация вариантов**: Создание "Сегодня", "Завтра" и датированных версий -- **Умное управление логотипами**: Автоматическое разрешение и масштабирование логотипов -- **Веб-интерфейс**: Удобное управление и мониторинг заданий -- **REST API**: Программный доступ к управлению заданиями -- **Профессиональное логирование**: Структурированный журнал с настройкой уровня +- **Полная автономность** — не требует выхода в интернет, все ресурсы встроены в бинарник +- **Интеграция с Synology** — аутентификация и загрузка файлов с NAS Synology (локальные и AD-учётные записи) +- **Экспорт офисных таблиц** — автоматическое преобразование `.osheet` в Excel +- **Гибкий парсинг данных** — динамический парсинг листов с обнаружением заголовков +- **Множественная генерация вариантов** — "Сегодня", "Завтра" и датированные версии +- **Умное управление логотипами** — автоматическое разрешение и масштабирование +- **Веб-интерфейс с WebSocket** — мгновенные обновления, просмотр видео, апрув +- **REST API** — программное управление заданиями +- **SQLite** — хранение истории и статуса апрува +- **Поддержка SMB** — копирование утверждённых видео на сетевую шару (с AD-аутентификацией) +- **Профессиональное логирование** — структурированный журнал с настройкой уровня +- **Эффективная работа с памятью** — потоковая обработка строк, своевременное удаление неиспользуемых данных (логотипы, ассеты) позволяет обрабатывать таблицы с десятками тысяч строк без утечек памяти ## Предварительные требования - Rust 1.70 или выше -- Доступ к NAS Synology с пакетами File Station и Office -- Экземпляр _server_ и _worker_(не менее одного) Nexrender -- Шаблоны After Effects (формат Adobe After Effects 2024 .aepx) +- Доступ к NAS Synology с пакетами File Station и Office (локальный или доменный пользователь) +- Экземпляр _server_ и _worker_ (не менее одного) Nexrender +- Шаблоны After Effects (формат .aep или .aepx, совместимые с версией AE на worker) +- (Для апрува) Доступ к SMB-шаре с правами на запись ## Установка -### 1. Клонирование репозитория - -```bash -git clone https://git.tvstart.ru/lexx/AE_Anons.git -cd ae_anons -``` - -### 2. Сборка проекта - -```bash -cargo build --release -``` - -### 3. Настройка окружения - -Скопируйте пример конфигурации и заполните своими данными: - -```bash -cp .env.example .env -``` - -Затем отредактируйте файл .env: - -См. раздел Конфигурация для подробной информации. - -## Использование - -### Однократная обработка - -```bash -# Запуск с настройками по умолчанию -./target/release/ae_anons - -# Явно указать однократный режим -./target/release/ae_anons --once - -# С отладочным логированием -RUST_LOG=debug ./target/release/ae_anons -``` - -### Веб-сервер - -```bash -# Запуск веб-интерфейса на порту по умолчанию (3000) -./target/release/ae_anons --web - -# Или с указанием другого порта через .env файл -# WEB_PORT=8080 ./target/release/ae_anons --web - -``` - -## Веб-интерфейс - -После запуска веб-сервера откройте браузер: - -- Главная страница: - -- API эндпоинты: - - `GET /api/jobs` - список всех заданий - - `POST /api/generate` - запуск генерации - - `POST /api/cleanup` - очистка завершённых - - `POST /api/jobs/stop-all` - остановка активных - - `GET /api/status` - статус сервера - -## Уровни логирования - -Управляйте детализацией вывода через переменную окружения `RUST_LOG`: - -- **error** - Только критические ошибки -- **warn** - Предупреждения и ошибки -- **info** - Общие операционные сообщения (по умолчанию) -- **debug** - Детальная информация о процессе обработки -- **trace** - Полное отладочное логирование с деталями API вызовов - -```bash -RUST_LOG=debug cargo run -``` + git clone https://git.example.com/team/ae_anons.git + cd ae_anons + cargo build --release + cp .env.example .env + # отредактируйте .env согласно вашей инфраструктуре ## Конфигурация -### Переменные окружения +Все настройки задаются через переменные окружения в файле `.env`. -| Переменная | Обязательна | Описание | -|-------------------------|-------------|------------------------------------------------------| -| `NAS_FQDN` | Да | URL NAS Synology с протоколом и портом | -| `NAS_USER` | Да | Имя пользователя учетной записи Synology | -| `NAS_PASS` | Да | Пароль учетной записи Synology | -| `NAS_FILE` | Да | Полный путь к файлу .osheet на NAS | -| `NEXRENDER_API_URL` | Да | Конечная точка API сервера Nexrender | -| `OUTPUT_FOLDER` | Да | Сетевой путь для рендеренных видео | -| `TEMPLATE_DOUBLE_SRC` | Да | Путь к AEP-шаблону для двух команд | -| `TEMPLATE_SINGLE_SRC` | Да | Путь к AEP-шаблону для одной команды | -| `TEMPLATE_COMPOSITION` | Да | Имя композиции в проекте AE (например, `main`) | -| `TEMPLATE_OUTPUT_MODULE`| Да | Имя модуля вывода в AE (например, `h264`) | -| `TEMPLATE_OUTPUT_EXT` | Да | Расширение выходного файла (например, `mp4`) | -| `WEB_PORT` | Нет | Порт для веб-сервера (по умолчанию: 3000) | -| `RUST_LOG` | Нет | Уровень детализации логирования (по умолчанию: info) | +### Основные переменные + +| Переменная | Обязательна | Описание | +|-------------------------|-------------|---------------------------------------------------------------------------------| +| `NAS_FQDN` | Да | URL Synology NAS (*) | +| `NAS_USER` | Да | Имя пользователя (локального или доменного, например `DOMAIN\username`) | +| `NAS_PASS` | Да | Пароль | +| `NAS_FILE` | Да | Путь к `.osheet` файлу на NAS (например, `/Team Folder/schedule.osheet`) | +| `NEXRENDER_API_URL` | Да | API Nexrender (например, `http://nexrender:3050/api/v1/jobs`) | +| `OUTPUT_FOLDER` | Да | Папка, куда Nexrender сохраняет готовые видео (локальный путь) | +| `TEMPLATE_DOUBLE_SRC` | Да | Путь к AEP-шаблону для двух команд (например, `file:///templates/double.aepx`) | +| `TEMPLATE_SINGLE_SRC` | Да | Путь к AEP-шаблону для одной команды | +| `TEMPLATE_COMPOSITION` | Да | Имя композиции в проекте AE | +| `TEMPLATE_OUTPUT_MODULE`| Да | Имя модуля вывода в AE | +| `TEMPLATE_OUTPUT_EXT` | Да | Расширение выходного файла (например, `mp4`) | +| `WEB_PORT` | Нет | Порт веб-сервера (по умолчанию 3000) | +| `RUST_LOG` | Нет | Уровень логирования (info, debug, trace) | +| `SMB_UPLOAD_URL` | Да(**) | SMB-URL для апрува (например, `smb://storage/approved`) | +| `SMB_USERNAME` | Да(**) | Имя пользователя для SMB (локальный или `DOMAIN\user`) | +| `SMB_PASSWORD` | Да(**) | Пароль для SMB | +| `DATABASE_URL` | Нет | Путь к SQLite БД (по умолчанию `sqlite:ae_anons.db`) | + +> (*) — для `NAS_FQDN` можно указывать протокол `http://` или `https://`. Безопаснее `https`, хотя для локальной сети допустим и `http`. Если NAS доступен по стандартному HTTPS-порту 443, протокол можно опустить (например, `nas.company.local`). Иначе указывайте полный URL с портом: `https://nas.company.local:5001` или `http://nas.local:5000`. +> +> (**) — обязательно, если используется функция апрува (веб-режим). В режиме `--once` апрув не требуется. ### Пример файла `.env` -```env -# Synology NAS -NAS_FQDN="https://your-nas.example.com" -NAS_USER="your_username" -NAS_PASS="your_password" -NAS_FILE="/Team Folder/path/to/file.osheet" + # Synology NAS + NAS_FQDN="https://nas.company.local:5001" + NAS_USER="DOMAIN\\service_account" + NAS_PASS="strongpassword" + NAS_FILE="/Team Folder/Anonsy/sport.osheet" -# Logging -RUST_LOG="info" + # Nexrender + NEXRENDER_API_URL="http://render-01:3050/api/v1/jobs" + OUTPUT_FOLDER="/mnt/nexrender/output" -# Web Server -WEB_PORT="3000" + # After Effects Templates + TEMPLATE_DOUBLE_SRC="file:///mnt/templates/double_team.aepx" + TEMPLATE_SINGLE_SRC="file:///mnt/templates/single_team.aepx" + TEMPLATE_COMPOSITION="main" + TEMPLATE_OUTPUT_MODULE="h264" + TEMPLATE_OUTPUT_EXT="mp4" -# Nexrender -NEXRENDER_API_URL="http://nexrender-server:3050/api/v1/jobs" -OUTPUT_FOLDER="/path/to/output" + # Web server + WEB_PORT="3000" + RUST_LOG="info" -# After Effects Templates -TEMPLATE_DOUBLE_SRC="file:///path/to/double_team_template.aepx" -TEMPLATE_SINGLE_SRC="file:///path/to/single_team_template.aepx" -TEMPLATE_COMPOSITION="main" -TEMPLATE_OUTPUT_MODULE="h264" -TEMPLATE_OUTPUT_EXT="mp4" -``` + # SMB для апрува (AD-совместимо) + SMB_UPLOAD_URL="smb://storage.company.local/approved_videos" + SMB_USERNAME="DOMAIN\\service_account" + SMB_PASSWORD="strongpassword" + + # Database + DATABASE_URL="sqlite:/var/lib/ae_anons/ae_anons.db" + +> **Примечание об аутентификации:** +> Synology API и SMB-библиотека `smb` поддерживают как локальных пользователей NAS, так и доменных (Active Directory). Для доменных пользователей используйте формат `DOMAIN\username` (обратный слеш необходимо экранировать в `.env` как `\\`). Проверено на Synology DSM 7.x и Samba AD. + +## Использование + +### Однократная обработка (cron или ручной запуск) + + ./target/release/ae_anons --once + +### Веб-сервер (интерактивный режим) + + ./target/release/ae_anons --web + +После запуска откройте браузер: `http://localhost:3000` + +## Веб-интерфейс + +- **Главная страница** — таблица всех заданий с полями: UID, имя файла, статус, превью (иконка видео), кнопка апрува, дата создания. +- **Обновления в реальном времени** через WebSocket — статусы заданий меняются мгновенно без перезагрузки страницы. +- **Кнопка «Generate»** — запускает парсинг таблицы Synology и создание новых заданий. Во время генерации кнопка блокируется, при завершении приходит уведомление. +- **Кнопка «Cleanup»** — удаляет из Nexrender задания со статусом finished/error. +- **Кнопка «Stop all»** — останавливает все активные (queued/started/processing) задания. +- **Просмотр видео** — клик по иконке видео открывает готовый файл в новой вкладке (поддерживается любой браузерный просмотр MP4). +- **Апрув** — клик по ✅ копирует видео на SMB-шару (путь из `SMB_UPLOAD_URL`) и помечает задание как approved. Повторный апрув невозможен. + +### API эндпоинты + +- `GET /api/jobs` — список всех заданий (из SQLite) +- `POST /api/generate` — запуск генерации +- `POST /api/cleanup` — очистка завершённых +- `POST /api/jobs/stop-all` — остановка активных +- `GET /api/status` — статус сервера +- `GET /api/video/:uid` — просмотр видеофайла +- `POST /api/approve/:uid` — апрув (копирование на SMB) +- `WS /ws` — WebSocket для получения событий (`JobUpdated`, `GenerationStarted`, `GenerationFinished`, `Error`) + +## Уровни логирования + +`RUST_LOG=debug ./ae_anons --web` + +- **error** — только критические ошибки +- **warn** — предупреждения +- **info** — стандартная информация (по умолчанию) +- **debug** — детали API-вызовов, парсинга, SMB-операций +- **trace** — максимальная детализация ## Структура электронной таблицы @@ -243,12 +235,7 @@ TEMPLATE_OUTPUT_EXT="mp4" Имена команд поддерживают два типа разделителей хэш #: 1. **Первый хеш**: Уникальный идентификатор для команд с одинаковыми названиями в одном виде спорта, или одинаковым названием в разных видах спорта. -Примеры: - -- `Динамо#Футбол`, `Динамо#Волейбол` - Одно название разный вид спорта -- `Спартак#ЖенскийФ`, `Спартак#МужскойФ` Одно название _разный_ вид спорта - -1. **Второй хеш**: Целевой размер в пикселях для масштабирования логотипа +2. **Второй хеш**: Целевой размер в пикселях для масштабирования логотипа Формат: `DisplayName#UniqueID#TargetSize` @@ -258,422 +245,70 @@ TEMPLATE_OUTPUT_EXT="mp4" - `Сомбатей##400` - только целевой размер (обратите внимание на двойной хеш) - `Спортинг#ll#550` - оба идентификатор и целевой размер -**Важно**: Обрабатывается **только второй хеш** для масштабирования логотипа. Если уникальный идентификатор не требуется, используйте двойной хеш ## перед целевым размером. - ## Выходные файлы -### Рендеренные видео +Рендеренные видео сохраняются в `OUTPUT_FOLDER` по шаблону: +`YYYYMMDD_Sport_League_TeamA_TeamB_Channel[_Variant].mp4` -Рендерные видео сохраняются в `OUTPUT_FOLDER` по следующему шаблону именования: +При апруве файл копируется в SMB-шару с тем же именем. -```shell -YYYYMMDD_Sport_League_TeamA_TeamB_Channel[_Variant].mp4 -``` +## Рабочий процесс (веб-режим) -Имена файлов транслитерируются в латиницу. -**Примеры:** - -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH.mp4` -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH_Today.mp4` -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH_Tomorrow.mp4` - -## Рабочий процесс - -1. **Аутентификация**: Подключение к NAS Synology с использованием предоставленных учетных данных -2. **Получение файла**: Поиск и экспорт указанного файла .osheet в Excel -3. **Парсинг данных**: Чтение всех листов и структурирование данных -4. **Разрешение ресурсов**: Сопоставление видов спорта, команд и каналов с визуальными элементами -5. **Генерация заданий**: Создание заданий Nexrender для каждой активной строки с вариантами -6. **Очистка**: Удаление завершенных/неудачных заданий из предыдущих запусков -7. **Отправка**: Отправка заданий на API Nexrender -8. **Мониторинг**: Слежение за прогрессом выполнения до завершения -9. **Завершение сеанса**: Закрытие сессии Synology +1. Пользователь заполняет таблицу Synology Office. +2. Нажимает «Generate» в веб-интерфейсе. +3. Сервер аутентифицируется на NAS, скачивает и парсит Excel. +4. Генерирует задания Nexrender, сохраняет их в SQLite и отправляет в Nexrender. +5. WebSocket уведомляет клиент о новых заданиях. +6. Nexrender рендерит видео, статус задания обновляется (периодический опрос или webhook). +7. Когда видео готово, пользователь видит иконку превью, может посмотреть видео и нажать апрув. +8. При апруве видео копируется на SMB-шару, задание помечается как approved. ## Требования к шаблонам After Effects -Шаблоны должны быть предварительно настроены на узлах рендеринга со следующими именами слоёв. - -### Файлы шаблонов - -Настраиваются через переменные окружения: - -| Переменная | Назначение | Пример значения | -|-------------------------|-------------------------------------|------------------------------------| -| `TEMPLATE_DOUBLE_SRC` | Шаблон для матчей с двумя командами | `file:///path/to/double.aepx` | -| `TEMPLATE_SINGLE_SRC` | Шаблон для анонсов с одной командой | `file:///path/to/single.aepx` | -| `TEMPLATE_COMPOSITION` | Имя главной композиции | `main` | -| `TEMPLATE_OUTPUT_MODULE`| Имя модуля вывода | `h264` | -| `TEMPLATE_OUTPUT_EXT` | Расширение выходного файла | `mp4` | - -### Обязательные слои в шаблонах - -#### Текстовые слои - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|-----------|:-------------:|:-------------:|-------------------------------------------------| -| `DATA` | ✅ | ✅ | Отображение даты (автоматически подстраивается) | -| `TIME_H` | ✅ | ❌ | Часы (отдельный слой) | -| `TIME_M` | ✅ | ❌ | Минуты (отдельный слой) | -| `TIME` | ❌ | ✅ | Полное время (единый слой) | -| `LEAGUE` | ✅ | ✅ | Название лиги/турнира | -| `SPORT` | ✅ | ✅ | Категория спорта | -| `TEAMS` | ✅ | ✅ | Скомбинированные имена команд | - -#### Слои с изображениями - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|----------------|:-------------:|:-------------:|-----------------------------| -| `TEAM_A_LOGO` | ✅ | ✅ | Логотип команды A | -| `TEAM_B_LOGO` | ✅ | ❌ | Логотип команды B | -| `CHANELL` | ✅ | ✅ | Логотип канала (две буквы L)| - -#### Видео слои - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|-----------|:-------------:|:-------------:|----------------------------| -| `TOP` | ✅ | ✅ | Наложение видео оформления | - -### Особенности шаблонов - -#### Шаблон DOUBLE (две команды) - -Используется когда в строке Excel заполнены оба поля `TEAM A` и `TEAM B`. - -Обязательные слои: - -- Все текстовые слои с `TIME_H` и `TIME_M` вместо `TIME` -- Оба логотипа команд: `TEAM_A_LOGO` и `TEAM_B_LOGO` - -#### Шаблон SINGLE (одна команда) - -Используется когда заполнено только одно поле команды. - -Обязательные слои: - -- Текстовый слой `TIME` вместо `TIME_H` и `TIME_M` -- Только логотип `TEAM_A_LOGO` - -### Автоматические корректировки - -AE Anons автоматически применяет следующие настройки к слоям: - -#### Слой DATA - -В зависимости от отображаемого текста и типа шаблона: - -| Текст | Шаблон | Font Size | Anchor Point | -|------------|----------|-----------|--------------| -| "сегодня" | DOUBLE | 105 | [0, 5] | -| "завтра" | DOUBLE | 115 | [0, 25] | -| дата (<6) | DOUBLE | 120 | [0, 20] | - -#### Слои TIME_H, TIME_M, TIME - -Корректировка Anchor Point в зависимости от длины текста: - -| Условие | Anchor Point | -|-------------------|--------------| -| 1 символ | [60, 0] | -| 2 символа, <20 | [20, 0] | - -#### Слой LEAGUE - -Если длина текста превышает 16 символов, размер шрифта уменьшается до 73. - -#### Слой TEAMS - -Если суммарная длина имён команд ≥ 32 символов, размер шрифта уменьшается до 55. - -#### Логотипы (TEAM_A_LOGO, TEAM_B_LOGO) - -Если в имени команды указан целевой размер через `#` (например, `Спортинг##550`), применяется выражение масштабирования: - -```javascript -if (width > height) { - max_size = width; -} else { - max_size = height; -} -var real_size = 550 / max_size * 100; -[real_size, real_size] -``` - -### Пример структуры слоёв в After Effects - -Ниже представлен рекомендуемый порядок слоёв в композиции. Порядок важен для правильного наложения элементов. - -```text -📁 main (композиция) -├── 🎬 TOP (видео слой) # Видео-оверлей (обязательный) -├── 📝 TEAMS (текстовый слой) # Имена команд (обязательный) -├── 📝 LEAGUE (текстовый слой) # Название лиги (обязательный) -├── 📝 SPORT (текстовый слой) # Вид спорта (обязательный) -├── 📝 DATA (текстовый слой) # Дата (обязательный) -├── 📝 TIME (текстовый слой) # Время (только SINGLE) -├── 📝 TIME_H (текстовый слой) # Часы (только DOUBLE) -├── 📝 TIME_M (текстовый слой) # Минуты (только DOUBLE) -├── 🖼️ CHANELL (слой изображения) # Логотип канала (обязательный) -├── 🖼️ TEAM_A_LOGO (слой изображения) # Логотип команды A (обязательный) -├── 🖼️ TEAM_B_LOGO (слой изображения) # Логотип команды B (только DOUBLE) -└── 🖼️ BOTTOM (слой изображения) # Фоновый слой (опционально) -``` - -### Формат проекта и совместимость с After Effects - -**Поддерживаемые форматы проектов:** - -- `.aep` — стандартный бинарный формат After Effects (рекомендуется) -- `.aepx` — XML-формат проекта (поддерживается с AE CC 2015) - -**Совместимость версий After Effects:** - -| Версия After Effects | Поддерживаемые форматы | Особенности | -|----------------------|------------------------|-------------------------------------------------| -| CS 5.5 | `.aep` | Базовая поддержка | -| CC / CC 2014 | `.aep` | Полная поддержка | -| CC 2015 - CC 2019 | `.aep`, `.aepx` | Добавлена поддержка XML формата `.aepx` | -| CC 2020 - CC 2022 | `.aep`, `.aepx` | Рекомендуется использовать `.aep` | -| CC 2023 и новее | `.aep`, `.aepx` | **Требуется настройка Output Module** (см. ниже)| - -> **⚠️ Важно для After Effects 2023+:** -> -> В версиях After Effects 2023 и новее критически важно настроить **Output Module** в шаблоне проекта. Бинарный файл рендеринга (`aerender`) не будет обрабатывать композицию без явно указанного модуля вывода, даже если в проекте используется модуль по умолчанию. -> -> AE Anons автоматически решает эту проблему, используя параметры `TEMPLATE_OUTPUT_MODULE` и `TEMPLATE_OUTPUT_EXT` из `.env` файла. Убедитесь, что эти значения соответствуют настройкам вашего шаблона. - -### Настройки композиции - -| Параметр | Переменная окружения | Пример значения | Описание | -|---------------------------|--------------------------|-----------------|---------------------------------------------| -| Имя композиции | `TEMPLATE_COMPOSITION` | `main` | Имя главной композиции в проекте AE | -| Выходной модуль | `TEMPLATE_OUTPUT_MODULE` | `h264` | Имя модуля вывода в AE | -| Расширение выходного файла| `TEMPLATE_OUTPUT_EXT` | `mp4` | Расширение выходного файла | - -**Рекомендации по выбору формата:** - -1. Используйте `.aep` для максимальной совместимости со всеми версиями AE -2. Версия After Effects на worker-машине должна быть не ниже версии, в которой создан проект -3. Для AE 2023+ убедитесь, что в шаблоне настроен Output Module с именем, указанным в `TEMPLATE_OUTPUT_MODULE` - -**Источники:** - -- [Nexrender - Tested with After Effects versions](https://github.com/inlife/nexrender#tested-with) -- [Adobe Aerender documentation](https://helpx.adobe.com/after-effects/using/automated-rendering-network-rendering.html) +Не изменились — все слои `DATA`, `TIME_H`, `TIME_M`, `LEAGUE`, `SPORT`, `TEAMS`, `TEAM_A_LOGO`, `TEAM_B_LOGO`, `CHANELL`, `TOP` должны присутствовать согласно типу шаблона (DOUBLE/SINGLE). Поддержка масштабирования логотипов через выражения After Effects — осталась. ## Устранение неисправностей -### Частые проблемы +### WebSocket не работает -#### Ошибка подключения к NAS +- Проверьте, что браузер поддерживает WebSocket (все современные поддерживают). +- При использовании прокси (nginx) необходимо настроить Upgrade заголовки. -- Проверьте, что `NAS_FQDN` включает протокол и порт (например, `https://nas.example.com:5001`) -- Проверьте сетевую связность с NAS -- Убедитесь, что сервисы File Station и Office включены +### Ошибка аутентификации на NAS для доменного пользователя -#### Файл не найден +- Убедитесь, что в `.env` указано `DOMAIN\\username` (двойной обратный слеш). +- Проверьте, что NAS настроен на приём доменных учётных записей (DSM → Домен/LDAP). -- Убедитесь, что путь в `NAS_FILE` точно соответствует пути в Synology Drive -- Путь должен начинаться с `/Team Folder/` для рабочих папок -- Проверьте права доступа к файлу для учетной записи сервиса +### Ошибка SMB подключения -#### Ошибка отправки задания Nexrender +- Проверьте доступность шары: `smbclient -U DOMAIN/username -L //storage/` +- Убедитесь, что в URL используется `smb://` протокол, путь без лишних слешей. +- Время на сервере и клиенте должно быть синхронизировано (SMB требует этого). -- Подтвердите доступность сервера Nexrender -- Убедитесь, что `NEXRENDER_API_URL` правильный -- Проверьте существование файлов шаблонов на узлах рендеринга +### БД SQLite блокирована -### Режим отладки +- При конкурентных запросах SQLite может выдавать `database is locked`. Для веб-сервера с одним процессом это маловероятно. Если возникает — используйте `sqlite://:memory:` для тестов или настройте WAL-режим. -Включите подробное логирование для детального анализа: +### Высокое потребление памяти -```bash -RUST_LOG=debug ./target/release/ae_anons -``` - -Это выведет: - -- Детали API запросов/ответов -- Информацию о парсинге листов -- Детали создания заданий -- Процесс разрешения ресурсов - -## Возможные ограничения по производительности - -- **Большие таблицы**: Ограничение обработки до 10,000 строк на лист -- **Задержка сети**: Загрузка файлов с NAS может занимать время для больших файлов -- **Параллельные задания**: Nexrender управляет очередью заданий внутренне - -## Примечания по безопасности - -- Храните учетные данные только в файле `.env` (исключен из git) -- Используйте специальные аккаунты с минимально необходимыми правами -- Сессии Synology завершаются после выполнения -- HTTPS рекомендуется для подключений NAS в продакшене +- Система спроектирована так, чтобы минимизировать удержание данных. Если вы наблюдаете рост памяти, убедитесь, что вы используете последнюю версию. При работе с очень большими таблицами (сотни тысяч строк) может потребоваться увеличить лимит строк в `processor.rs` (константа 10000). ## Разработка -### Структура кода +Все статические ресурсы (HTML, CSS, шрифты Font Awesome, логотип, favicon) встроены в бинарник с помощью `include_str!` и `include_bytes!`. Для разработки можно редактировать файлы в `src/static/`, но при сборке они компилируются внутрь исполняемого файла. -```shell -ae_anons/ -├── .gitignore -├── .env.example -├── Cargo.toml -├── LICENSE -├── README.md -├── assets/ -│ └── logo.png -└── src/ - ├── main.rs # Точка входа, CLI - ├── config.rs # Конфигурация из .env - ├── nexrender.rs # Модели заданий Nexrender - ├── synology.rs # Клиент Synology API - ├── processor.rs # Логика обработки - ├── web.rs # Веб-сервер и API - └── static/ - ├── index.html # Веб-интерфейс - └── style.css # Стили -``` +### Сборка для production -## Зависимости + cargo build --release + strip target/release/ae_anons # уменьшает размер -### Основные зависимости - -| Crate | Версия | Назначение | -|--------------------|---------|-----------------------------------------| -| reqwest | 0.12 | HTTP клиент для коммуникации с API | -| serde / serde_json | 1.0 | Сериализация и десериализация JSON | -| calamine | 0.26 | Парсинг файлов Excel (.xlsx, .xls) | -| chrono | 0.4 | Обработка дат и времени | -| tokio | 1.0 | Асинхронная среда выполнения | -| dotenv | 0.15 | Загрузка конфигурации из .env файла | -| log / env_logger | 0.4/0.11| Система логирования с уровнями | -| thiserror | 2.0 | Эргономичные определения типов ошибок | -| anyhow | 1.0 | Упрощённая обработка ошибок | -| regex | 1.11 | Регулярные выражения | -| urlencoding | 2.1 | Кодирование URL для API запросов | -| bytes | 1.9 | Работа с байтовыми данными | -| futures | 0.3 | Асинхронные примитивы | - -### Веб-сервер и CLI - -| Crate | Версия | Назначение | -|--------------------|--------|-----------------------------------------| -| axum | 0.8 | Веб-фреймворк для REST API | -| tower | 0.5 | Промежуточное ПО для веб-сервера | -| tower-http | 0.6 |HTTP утилиты (CORS, статика, трассировка)| -| askama | 0.15 | Шаблонизация (опционально) | -| clap | 4.5 | Парсинг аргументов командной строки | - -### Платформозависимые зависимости - -| Crate | Версия | Платформа | Назначение | -|--------------------|--------|-----------|----------------------------------| -| openssl | 0.10 | Linux | Криптография для HTTPS (vendored)| - -## 🙏 Благодарности - -### Nexrender - -Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)** -([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы -для автоматизации After Effects. - -### Font Awesome Free 6.4.0 - -Веб-интерфейс использует иконки и шрифты **Font Awesome Free**: - -- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) -- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL) - -© 2023 Fonticons, Inc. — +Итоговый бинарник можно переносить на любой Linux-сервер без дополнительных зависимостей (кроме libc и openssl, если не используется статическая сборка). ## Лицензия -**AE Anons** — [License](LICENSE) -**Nexrender** — [MIT License](https://github.com/inlife/nexrender/blob/master/LICENSE) - -Обе лицензии MIT обеспечивают полную свободу использования и модификации кода. - -**Разрешается:** - -- ✅ Использовать в коммерческих целях -- ✅ Изменять исходный код -- ✅ Распространять копии -- ✅ Использовать приватно - -**Требуется:** - -- Сохранять копирайт и текст лицензии - -## Поддержка - -Для вопросов и запросов функций обращайтесь: - -- -- [README.md](https://git.tvstart.ru/lexx/AE_Anons) - -## История изменений - -### v0.2.6 (текущая) - -- Сортировка заданий по дате и времени события (самые ранние — первыми) -- Сортировка по варианту (оригинал → today → tomorrow) -- Последовательная отправка для гарантированного порядка в очереди -- Исправлена нормализация дефисов (2 и более дефиса подряд → 1) -- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS) -- Добавлена поддержка кеширования ассетов (cache: true/false) -- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются - -### v0.2.5 - -- Оптимизация памяти: очистка неиспользуемых логотипов -- Групповая отправка заданий по 3 для сохранения порядка -- Добавлен UID в логи отправки - -### v0.2.4 - -- Полностью автономный режим (встроенный Font Awesome) -- Добавлен favicon -- Все статические ресурсы встроены в бинарник - -### v0.2.3 - -- Веб-интерфейс с настраиваемым портом (WEB_PORT) -- Тёмная/светлая тема с автоопределением ОС -- Сортировка по всем колонкам в интерфейсе -- Фильтрация по имени файла/UID -- Остановка всех активных заданий -- Автообновление каждые 60 секунд -- Улучшенное debug-логирование -- Исправлена обработка UNC/SMB путей - -### v0.2.2 - -- Исправлено отображение имён выходных файлов в веб-интерфейсе -- Улучшена цветовая схема -- Добавлена сортировка по всем колонкам -- Вынесены стили в отдельный CSS файл - -### v0.2.0 - -- Добавлен веб-интерфейс для управления заданиями -- Реализован REST API -- Добавлена поддержка тёмной/светлой темы -- Автообновление статуса заданий -- Возможность остановки всех активных заданий - -### v0.1.1 - -- Оптимизирована работа с памятью -- Убрана функция создания .json - -### v0.1.0 - -- Первый выпуск -- Интеграция с Synology Office -- Базовая генерация заданий Nexrender -- Парсинг Excel с динамическим обнаружением листов +**AE Anons** — MIT +**Nexrender** — MIT +**Font Awesome Free** — CC BY 4.0 (иконки) и SIL OFL 1.1 (шрифты) --- diff --git a/ae_anons-v0.2.6-all.tar.gz b/ae_anons-v0.2.6-all.tar.gz deleted file mode 100644 index e674f9d..0000000 Binary files a/ae_anons-v0.2.6-all.tar.gz and /dev/null differ diff --git a/releases/ae_anons-v0.2.6-linux-gnu/.env.example b/releases/ae_anons-v0.2.6-linux-gnu/.env.example deleted file mode 100644 index 34e2330..0000000 --- a/releases/ae_anons-v0.2.6-linux-gnu/.env.example +++ /dev/null @@ -1,22 +0,0 @@ -# Synology NAS -NAS_FQDN="https://your-nas.example.com" -NAS_USER="your_username" -NAS_PASS="your_password" -NAS_FILE="/Team Folder/path/to/file.osheet" - -# Logging (optional, defaults to info) -RUST_LOG="info" - -# Web Server (optional, defaults to 3000) -WEB_PORT="3000" - -# Nexrender -NEXRENDER_API_URL="http://nexrender-server:3050/api/v1/jobs" -OUTPUT_FOLDER="/path/to/output" - -# After Effects Templates -TEMPLATE_DOUBLE_SRC="file:///path/to/double_team_template.aep" -TEMPLATE_SINGLE_SRC="file:///path/to/single_team_template.aep" -TEMPLATE_COMPOSITION="main" -TEMPLATE_OUTPUT_MODULE="h264" -TEMPLATE_OUTPUT_EXT="mp4" \ No newline at end of file diff --git a/releases/ae_anons-v0.2.6-linux-gnu/LICENSE b/releases/ae_anons-v0.2.6-linux-gnu/LICENSE deleted file mode 100644 index f6da942..0000000 --- a/releases/ae_anons-v0.2.6-linux-gnu/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -# MIT License - -Copyright (c) 2026 [Your Name or Company] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/releases/ae_anons-v0.2.6-linux-gnu/README.md b/releases/ae_anons-v0.2.6-linux-gnu/README.md deleted file mode 100644 index 0e8f255..0000000 --- a/releases/ae_anons-v0.2.6-linux-gnu/README.md +++ /dev/null @@ -1,680 +0,0 @@ -# AE Anons - Автоматизированный генератор спортивных анонсов в After Effects - - -

- AE Anons Logo -

- - -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Rust](https://img.shields.io/badge/Rust-1.70%2B-orange.svg)](https://www.rust-lang.org/) -[![Status](https://img.shields.io/badge/status-production-green.svg)](https://git.tvstart.ru/lexx/AE_Anons) - -Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office. - -## Новое в версии v0.2.6 - -- Сортировка заданий по дате и времени события (самые ранние — первыми) -- Сортировка по варианту (оригинал → today → tomorrow) -- Последовательная отправка для гарантированного порядка в очереди -- Исправлена нормализация дефисов (2 и более дефиса подряд → 1) -- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS) -- Добавлена поддержка кеширования ассетов (cache: true/false) -- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются - -## Обзор - -AE Anons — это CLI-утилита и веб-сервер на Rust, которая выступает как **интеллектуальный генератор заданий** -для [Nexrender](https://github.com/inlife/nexrender) — опенсорсного оркестратора рендеринга -After Effects (лицензия MIT). - -### Режимы работы - -1. **Однократная обработка** (`--once` или без флагов): - - Подключение к NAS Synology - - Парсинг Excel данных - - Генерация и отправка заданий Nexrender - - Мониторинг завершения и выход - -2. **Веб-сервер** (`--web`): - - Запуск веб-интерфейса на порту `:3000` - - Управление заданиями через браузер - - Автообновление статуса каждую минуту - -## Особенности - -- **Интеграция с Synology**: Аутентификация и загрузка файлов с NAS Synology -- **Экспорт офисных таблиц**: Автоматическое преобразование файлов .osheet в формат Excel -- **Гибкий парсинг данных**: Динамический парсинг листов с обнаружением заголовков -- **Множественная генерация вариантов**: Создание "Сегодня", "Завтра" и датированных версий -- **Умное управление логотипами**: Автоматическое разрешение и масштабирование логотипов -- **Веб-интерфейс**: Удобное управление и мониторинг заданий -- **REST API**: Программный доступ к управлению заданиями -- **Профессиональное логирование**: Структурированный журнал с настройкой уровня - -## Предварительные требования - -- Rust 1.70 или выше -- Доступ к NAS Synology с пакетами File Station и Office -- Экземпляр _server_ и _worker_(не менее одного) Nexrender -- Шаблоны After Effects (формат Adobe After Effects 2024 .aepx) - -## Установка - -### 1. Клонирование репозитория - -```bash -git clone https://git.tvstart.ru/lexx/AE_Anons.git -cd ae_anons -``` - -### 2. Сборка проекта - -```bash -cargo build --release -``` - -### 3. Настройка окружения - -Скопируйте пример конфигурации и заполните своими данными: - -```bash -cp .env.example .env -``` - -Затем отредактируйте файл .env: - -См. раздел Конфигурация для подробной информации. - -## Использование - -### Однократная обработка - -```bash -# Запуск с настройками по умолчанию -./target/release/ae_anons - -# Явно указать однократный режим -./target/release/ae_anons --once - -# С отладочным логированием -RUST_LOG=debug ./target/release/ae_anons -``` - -### Веб-сервер - -```bash -# Запуск веб-интерфейса на порту по умолчанию (3000) -./target/release/ae_anons --web - -# Или с указанием другого порта через .env файл -# WEB_PORT=8080 ./target/release/ae_anons --web - -``` - -## Веб-интерфейс - -После запуска веб-сервера откройте браузер: - -- Главная страница: - -- API эндпоинты: - - `GET /api/jobs` - список всех заданий - - `POST /api/generate` - запуск генерации - - `POST /api/cleanup` - очистка завершённых - - `POST /api/jobs/stop-all` - остановка активных - - `GET /api/status` - статус сервера - -## Уровни логирования - -Управляйте детализацией вывода через переменную окружения `RUST_LOG`: - -- **error** - Только критические ошибки -- **warn** - Предупреждения и ошибки -- **info** - Общие операционные сообщения (по умолчанию) -- **debug** - Детальная информация о процессе обработки -- **trace** - Полное отладочное логирование с деталями API вызовов - -```bash -RUST_LOG=debug cargo run -``` - -## Конфигурация - -### Переменные окружения - -| Переменная | Обязательна | Описание | -|-------------------------|-------------|------------------------------------------------------| -| `NAS_FQDN` | Да | URL NAS Synology с протоколом и портом | -| `NAS_USER` | Да | Имя пользователя учетной записи Synology | -| `NAS_PASS` | Да | Пароль учетной записи Synology | -| `NAS_FILE` | Да | Полный путь к файлу .osheet на NAS | -| `NEXRENDER_API_URL` | Да | Конечная точка API сервера Nexrender | -| `OUTPUT_FOLDER` | Да | Сетевой путь для рендеренных видео | -| `TEMPLATE_DOUBLE_SRC` | Да | Путь к AEP-шаблону для двух команд | -| `TEMPLATE_SINGLE_SRC` | Да | Путь к AEP-шаблону для одной команды | -| `TEMPLATE_COMPOSITION` | Да | Имя композиции в проекте AE (например, `main`) | -| `TEMPLATE_OUTPUT_MODULE`| Да | Имя модуля вывода в AE (например, `h264`) | -| `TEMPLATE_OUTPUT_EXT` | Да | Расширение выходного файла (например, `mp4`) | -| `WEB_PORT` | Нет | Порт для веб-сервера (по умолчанию: 3000) | -| `RUST_LOG` | Нет | Уровень детализации логирования (по умолчанию: info) | - -### Пример файла `.env` - -```env -# Synology NAS -NAS_FQDN="https://your-nas.example.com" -NAS_USER="your_username" -NAS_PASS="your_password" -NAS_FILE="/Team Folder/path/to/file.osheet" - -# Logging -RUST_LOG="info" - -# Web Server -WEB_PORT="3000" - -# Nexrender -NEXRENDER_API_URL="http://nexrender-server:3050/api/v1/jobs" -OUTPUT_FOLDER="/path/to/output" - -# After Effects Templates -TEMPLATE_DOUBLE_SRC="file:///path/to/double_team_template.aepx" -TEMPLATE_SINGLE_SRC="file:///path/to/single_team_template.aepx" -TEMPLATE_COMPOSITION="main" -TEMPLATE_OUTPUT_MODULE="h264" -TEMPLATE_OUTPUT_EXT="mp4" -``` - -## Структура электронной таблицы - -Файл Excel должен содержать следующие листы со специфичными структурами: - -### Лист "SPORT" - -Связывает названия видов спорта с соответствующими видео пакетами. - -| SPORT | LINK | -|----------------|---------------------------------------------| -| Без оформления | \\server\share\path\to\null.mov | -| Футбол | \\server\share\path\to\football_pack.mov | -| Волейбол | \\server\share\path\to\volleyball_pack.mov | - -### Лист "TEAMS" - -Сопоставляет имена команд с их видами спорта и логотипами. Колонка SPORT заполняется из выпадающего списка (заполнена из листа SPORT). - -Если несколько команд в одном виде спорта имеют одинаковые названия, но разные логотипы, используется разделитель хеш # для уникальной идентификации. - -| TEAM | SPORT | LINK | -|-------------------|-----------|---------------------------------------------------| -| Галатасарай#Turki | Футбол | \\server\share\path\to\galatasaray_football.png | -| Галатасарай | Волейбол | \\server\share\path\to\galatasaray_volleyball.png | -| Галатасарай#UCL | Волейбол | \\server\share\path\to\galatasaray_ucl.png | -| Сомбатей##400 | Футбол | \\server\share\path\to\szombathely.png | - -### Лист "CHANELL" - -Сопоставляет названия каналов с их логотипами. Поля заполняются вручную без выпадающих списков. - -| CHANELL | LINK | -|---------|-----------------------------------------| -| КАНАЛ | \\server\share\path\to\channel_logo.png | -| TRIUMPH | \\server\share\path\to\triumph_logo.png | - -### Лист "Start" - -Основной источник данных для генерации анонсов. Большинство полей заполняются из выпадающих списков, основанных на других листах. - -Обязательные колонки: DATA, TIME, CHANELL, SPORT, LEAGUE, TEAM A, TEAM B - -**Примечания:** - -- Колонка LEAGUE обязательна для заполнения, но не имеет выпадающего списка. Проверка орфографии и опечаток отсутствует! -- Колонка DATA должна содержать дату в формате `ДД.ММ.ГГГГ` (например, `28.2.2026`). - -| STATE | TRIPPLE | DATA | TIME | SPORT | LEAGUE | CHANELL | TEAM A | TEAM B | -|-------|---------|-----------|-------|---------|---------------------------|---------|-----------------|------------------| -| TRUE | TRUE | 27.2.2026 | 23:40 | Футбол | Чемпионат Португалии | START | Спортинг#ll#550 | Эшторил#ll#550 | -| FALSE | TRUE | 28.2.2026 | 12:55 | Волейбол| Чемпионат Турции. Женщины | TRIUMPH | Бешикташ | Галатасарай#Turki| - -### Формат имен команд - -Имена команд поддерживают два типа разделителей хэш #: - -1. **Первый хеш**: Уникальный идентификатор для команд с одинаковыми названиями в одном виде спорта, или одинаковым названием в разных видах спорта. -Примеры: - -- `Динамо#Футбол`, `Динамо#Волейбол` - Одно название разный вид спорта -- `Спартак#ЖенскийФ`, `Спартак#МужскойФ` Одно название _разный_ вид спорта - -1. **Второй хеш**: Целевой размер в пикселях для масштабирования логотипа - -Формат: `DisplayName#UniqueID#TargetSize` - -Примеры: - -- `Галатасарай#Turki` - только уникальный идентификатор -- `Сомбатей##400` - только целевой размер (обратите внимание на двойной хеш) -- `Спортинг#ll#550` - оба идентификатор и целевой размер - -**Важно**: Обрабатывается **только второй хеш** для масштабирования логотипа. Если уникальный идентификатор не требуется, используйте двойной хеш ## перед целевым размером. - -## Выходные файлы - -### Рендеренные видео - -Рендерные видео сохраняются в `OUTPUT_FOLDER` по следующему шаблону именования: - -```shell -YYYYMMDD_Sport_League_TeamA_TeamB_Channel[_Variant].mp4 -``` - -Имена файлов транслитерируются в латиницу. -**Примеры:** - -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH.mp4` -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH_Today.mp4` -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH_Tomorrow.mp4` - -## Рабочий процесс - -1. **Аутентификация**: Подключение к NAS Synology с использованием предоставленных учетных данных -2. **Получение файла**: Поиск и экспорт указанного файла .osheet в Excel -3. **Парсинг данных**: Чтение всех листов и структурирование данных -4. **Разрешение ресурсов**: Сопоставление видов спорта, команд и каналов с визуальными элементами -5. **Генерация заданий**: Создание заданий Nexrender для каждой активной строки с вариантами -6. **Очистка**: Удаление завершенных/неудачных заданий из предыдущих запусков -7. **Отправка**: Отправка заданий на API Nexrender -8. **Мониторинг**: Слежение за прогрессом выполнения до завершения -9. **Завершение сеанса**: Закрытие сессии Synology - -## Требования к шаблонам After Effects - -Шаблоны должны быть предварительно настроены на узлах рендеринга со следующими именами слоёв. - -### Файлы шаблонов - -Настраиваются через переменные окружения: - -| Переменная | Назначение | Пример значения | -|-------------------------|-------------------------------------|------------------------------------| -| `TEMPLATE_DOUBLE_SRC` | Шаблон для матчей с двумя командами | `file:///path/to/double.aepx` | -| `TEMPLATE_SINGLE_SRC` | Шаблон для анонсов с одной командой | `file:///path/to/single.aepx` | -| `TEMPLATE_COMPOSITION` | Имя главной композиции | `main` | -| `TEMPLATE_OUTPUT_MODULE`| Имя модуля вывода | `h264` | -| `TEMPLATE_OUTPUT_EXT` | Расширение выходного файла | `mp4` | - -### Обязательные слои в шаблонах - -#### Текстовые слои - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|-----------|:-------------:|:-------------:|-------------------------------------------------| -| `DATA` | ✅ | ✅ | Отображение даты (автоматически подстраивается) | -| `TIME_H` | ✅ | ❌ | Часы (отдельный слой) | -| `TIME_M` | ✅ | ❌ | Минуты (отдельный слой) | -| `TIME` | ❌ | ✅ | Полное время (единый слой) | -| `LEAGUE` | ✅ | ✅ | Название лиги/турнира | -| `SPORT` | ✅ | ✅ | Категория спорта | -| `TEAMS` | ✅ | ✅ | Скомбинированные имена команд | - -#### Слои с изображениями - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|----------------|:-------------:|:-------------:|-----------------------------| -| `TEAM_A_LOGO` | ✅ | ✅ | Логотип команды A | -| `TEAM_B_LOGO` | ✅ | ❌ | Логотип команды B | -| `CHANELL` | ✅ | ✅ | Логотип канала (две буквы L)| - -#### Видео слои - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|-----------|:-------------:|:-------------:|----------------------------| -| `TOP` | ✅ | ✅ | Наложение видео оформления | - -### Особенности шаблонов - -#### Шаблон DOUBLE (две команды) - -Используется когда в строке Excel заполнены оба поля `TEAM A` и `TEAM B`. - -Обязательные слои: - -- Все текстовые слои с `TIME_H` и `TIME_M` вместо `TIME` -- Оба логотипа команд: `TEAM_A_LOGO` и `TEAM_B_LOGO` - -#### Шаблон SINGLE (одна команда) - -Используется когда заполнено только одно поле команды. - -Обязательные слои: - -- Текстовый слой `TIME` вместо `TIME_H` и `TIME_M` -- Только логотип `TEAM_A_LOGO` - -### Автоматические корректировки - -AE Anons автоматически применяет следующие настройки к слоям: - -#### Слой DATA - -В зависимости от отображаемого текста и типа шаблона: - -| Текст | Шаблон | Font Size | Anchor Point | -|------------|----------|-----------|--------------| -| "сегодня" | DOUBLE | 105 | [0, 5] | -| "завтра" | DOUBLE | 115 | [0, 25] | -| дата (<6) | DOUBLE | 120 | [0, 20] | - -#### Слои TIME_H, TIME_M, TIME - -Корректировка Anchor Point в зависимости от длины текста: - -| Условие | Anchor Point | -|-------------------|--------------| -| 1 символ | [60, 0] | -| 2 символа, <20 | [20, 0] | - -#### Слой LEAGUE - -Если длина текста превышает 16 символов, размер шрифта уменьшается до 73. - -#### Слой TEAMS - -Если суммарная длина имён команд ≥ 32 символов, размер шрифта уменьшается до 55. - -#### Логотипы (TEAM_A_LOGO, TEAM_B_LOGO) - -Если в имени команды указан целевой размер через `#` (например, `Спортинг##550`), применяется выражение масштабирования: - -```javascript -if (width > height) { - max_size = width; -} else { - max_size = height; -} -var real_size = 550 / max_size * 100; -[real_size, real_size] -``` - -### Пример структуры слоёв в After Effects - -Ниже представлен рекомендуемый порядок слоёв в композиции. Порядок важен для правильного наложения элементов. - -```text -📁 main (композиция) -├── 🎬 TOP (видео слой) # Видео-оверлей (обязательный) -├── 📝 TEAMS (текстовый слой) # Имена команд (обязательный) -├── 📝 LEAGUE (текстовый слой) # Название лиги (обязательный) -├── 📝 SPORT (текстовый слой) # Вид спорта (обязательный) -├── 📝 DATA (текстовый слой) # Дата (обязательный) -├── 📝 TIME (текстовый слой) # Время (только SINGLE) -├── 📝 TIME_H (текстовый слой) # Часы (только DOUBLE) -├── 📝 TIME_M (текстовый слой) # Минуты (только DOUBLE) -├── 🖼️ CHANELL (слой изображения) # Логотип канала (обязательный) -├── 🖼️ TEAM_A_LOGO (слой изображения) # Логотип команды A (обязательный) -├── 🖼️ TEAM_B_LOGO (слой изображения) # Логотип команды B (только DOUBLE) -└── 🖼️ BOTTOM (слой изображения) # Фоновый слой (опционально) -``` - -### Формат проекта и совместимость с After Effects - -**Поддерживаемые форматы проектов:** - -- `.aep` — стандартный бинарный формат After Effects (рекомендуется) -- `.aepx` — XML-формат проекта (поддерживается с AE CC 2015) - -**Совместимость версий After Effects:** - -| Версия After Effects | Поддерживаемые форматы | Особенности | -|----------------------|------------------------|-------------------------------------------------| -| CS 5.5 | `.aep` | Базовая поддержка | -| CC / CC 2014 | `.aep` | Полная поддержка | -| CC 2015 - CC 2019 | `.aep`, `.aepx` | Добавлена поддержка XML формата `.aepx` | -| CC 2020 - CC 2022 | `.aep`, `.aepx` | Рекомендуется использовать `.aep` | -| CC 2023 и новее | `.aep`, `.aepx` | **Требуется настройка Output Module** (см. ниже)| - -> **⚠️ Важно для After Effects 2023+:** -> -> В версиях After Effects 2023 и новее критически важно настроить **Output Module** в шаблоне проекта. Бинарный файл рендеринга (`aerender`) не будет обрабатывать композицию без явно указанного модуля вывода, даже если в проекте используется модуль по умолчанию. -> -> AE Anons автоматически решает эту проблему, используя параметры `TEMPLATE_OUTPUT_MODULE` и `TEMPLATE_OUTPUT_EXT` из `.env` файла. Убедитесь, что эти значения соответствуют настройкам вашего шаблона. - -### Настройки композиции - -| Параметр | Переменная окружения | Пример значения | Описание | -|---------------------------|--------------------------|-----------------|---------------------------------------------| -| Имя композиции | `TEMPLATE_COMPOSITION` | `main` | Имя главной композиции в проекте AE | -| Выходной модуль | `TEMPLATE_OUTPUT_MODULE` | `h264` | Имя модуля вывода в AE | -| Расширение выходного файла| `TEMPLATE_OUTPUT_EXT` | `mp4` | Расширение выходного файла | - -**Рекомендации по выбору формата:** - -1. Используйте `.aep` для максимальной совместимости со всеми версиями AE -2. Версия After Effects на worker-машине должна быть не ниже версии, в которой создан проект -3. Для AE 2023+ убедитесь, что в шаблоне настроен Output Module с именем, указанным в `TEMPLATE_OUTPUT_MODULE` - -**Источники:** - -- [Nexrender - Tested with After Effects versions](https://github.com/inlife/nexrender#tested-with) -- [Adobe Aerender documentation](https://helpx.adobe.com/after-effects/using/automated-rendering-network-rendering.html) - -## Устранение неисправностей - -### Частые проблемы - -#### Ошибка подключения к NAS - -- Проверьте, что `NAS_FQDN` включает протокол и порт (например, `https://nas.example.com:5001`) -- Проверьте сетевую связность с NAS -- Убедитесь, что сервисы File Station и Office включены - -#### Файл не найден - -- Убедитесь, что путь в `NAS_FILE` точно соответствует пути в Synology Drive -- Путь должен начинаться с `/Team Folder/` для рабочих папок -- Проверьте права доступа к файлу для учетной записи сервиса - -#### Ошибка отправки задания Nexrender - -- Подтвердите доступность сервера Nexrender -- Убедитесь, что `NEXRENDER_API_URL` правильный -- Проверьте существование файлов шаблонов на узлах рендеринга - -### Режим отладки - -Включите подробное логирование для детального анализа: - -```bash -RUST_LOG=debug ./target/release/ae_anons -``` - -Это выведет: - -- Детали API запросов/ответов -- Информацию о парсинге листов -- Детали создания заданий -- Процесс разрешения ресурсов - -## Возможные ограничения по производительности - -- **Большие таблицы**: Ограничение обработки до 10,000 строк на лист -- **Задержка сети**: Загрузка файлов с NAS может занимать время для больших файлов -- **Параллельные задания**: Nexrender управляет очередью заданий внутренне - -## Примечания по безопасности - -- Храните учетные данные только в файле `.env` (исключен из git) -- Используйте специальные аккаунты с минимально необходимыми правами -- Сессии Synology завершаются после выполнения -- HTTPS рекомендуется для подключений NAS в продакшене - -## Разработка - -### Структура кода - -```shell -ae_anons/ -├── .gitignore -├── .env.example -├── Cargo.toml -├── LICENSE -├── README.md -├── assets/ -│ └── logo.png -└── src/ - ├── main.rs # Точка входа, CLI - ├── config.rs # Конфигурация из .env - ├── nexrender.rs # Модели заданий Nexrender - ├── synology.rs # Клиент Synology API - ├── processor.rs # Логика обработки - ├── web.rs # Веб-сервер и API - └── static/ - ├── index.html # Веб-интерфейс - └── style.css # Стили -``` - -## Зависимости - -### Основные зависимости - -| Crate | Версия | Назначение | -|--------------------|---------|-----------------------------------------| -| reqwest | 0.12 | HTTP клиент для коммуникации с API | -| serde / serde_json | 1.0 | Сериализация и десериализация JSON | -| calamine | 0.26 | Парсинг файлов Excel (.xlsx, .xls) | -| chrono | 0.4 | Обработка дат и времени | -| tokio | 1.0 | Асинхронная среда выполнения | -| dotenv | 0.15 | Загрузка конфигурации из .env файла | -| log / env_logger | 0.4/0.11| Система логирования с уровнями | -| thiserror | 2.0 | Эргономичные определения типов ошибок | -| anyhow | 1.0 | Упрощённая обработка ошибок | -| regex | 1.11 | Регулярные выражения | -| urlencoding | 2.1 | Кодирование URL для API запросов | -| bytes | 1.9 | Работа с байтовыми данными | -| futures | 0.3 | Асинхронные примитивы | - -### Веб-сервер и CLI - -| Crate | Версия | Назначение | -|--------------------|--------|-----------------------------------------| -| axum | 0.8 | Веб-фреймворк для REST API | -| tower | 0.5 | Промежуточное ПО для веб-сервера | -| tower-http | 0.6 |HTTP утилиты (CORS, статика, трассировка)| -| askama | 0.15 | Шаблонизация (опционально) | -| clap | 4.5 | Парсинг аргументов командной строки | - -### Платформозависимые зависимости - -| Crate | Версия | Платформа | Назначение | -|--------------------|--------|-----------|----------------------------------| -| openssl | 0.10 | Linux | Криптография для HTTPS (vendored)| - -## 🙏 Благодарности - -### Nexrender - -Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)** -([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы -для автоматизации After Effects. - -### Font Awesome Free 6.4.0 - -Веб-интерфейс использует иконки и шрифты **Font Awesome Free**: - -- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) -- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL) - -© 2023 Fonticons, Inc. — - -## Лицензия - -**AE Anons** — [License](LICENSE) -**Nexrender** — [MIT License](https://github.com/inlife/nexrender/blob/master/LICENSE) - -Обе лицензии MIT обеспечивают полную свободу использования и модификации кода. - -**Разрешается:** - -- ✅ Использовать в коммерческих целях -- ✅ Изменять исходный код -- ✅ Распространять копии -- ✅ Использовать приватно - -**Требуется:** - -- Сохранять копирайт и текст лицензии - -## Поддержка - -Для вопросов и запросов функций обращайтесь: - -- -- [README.md](https://git.tvstart.ru/lexx/AE_Anons) - -## История изменений - -### v0.2.6 (текущая) - -- Сортировка заданий по дате и времени события (самые ранние — первыми) -- Сортировка по варианту (оригинал → today → tomorrow) -- Последовательная отправка для гарантированного порядка в очереди -- Исправлена нормализация дефисов (2 и более дефиса подряд → 1) -- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS) -- Добавлена поддержка кеширования ассетов (cache: true/false) -- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются - -### v0.2.5 - -- Оптимизация памяти: очистка неиспользуемых логотипов -- Групповая отправка заданий по 3 для сохранения порядка -- Добавлен UID в логи отправки - -### v0.2.4 - -- Полностью автономный режим (встроенный Font Awesome) -- Добавлен favicon -- Все статические ресурсы встроены в бинарник - -### v0.2.3 - -- Веб-интерфейс с настраиваемым портом (WEB_PORT) -- Тёмная/светлая тема с автоопределением ОС -- Сортировка по всем колонкам в интерфейсе -- Фильтрация по имени файла/UID -- Остановка всех активных заданий -- Автообновление каждые 60 секунд -- Улучшенное debug-логирование -- Исправлена обработка UNC/SMB путей - -### v0.2.2 - -- Исправлено отображение имён выходных файлов в веб-интерфейсе -- Улучшена цветовая схема -- Добавлена сортировка по всем колонкам -- Вынесены стили в отдельный CSS файл - -### v0.2.0 - -- Добавлен веб-интерфейс для управления заданиями -- Реализован REST API -- Добавлена поддержка тёмной/светлой темы -- Автообновление статуса заданий -- Возможность остановки всех активных заданий - -### v0.1.1 - -- Оптимизирована работа с памятью -- Убрана функция создания .json - -### v0.1.0 - -- Первый выпуск -- Интеграция с Synology Office -- Базовая генерация заданий Nexrender -- Парсинг Excel с динамическим обнаружением листов - ---- - -Made with 🦀 Rust and ☕ coffee diff --git a/releases/ae_anons-v0.2.6-linux-gnu/ae_anons b/releases/ae_anons-v0.2.6-linux-gnu/ae_anons deleted file mode 100755 index 6bf5ccb..0000000 Binary files a/releases/ae_anons-v0.2.6-linux-gnu/ae_anons and /dev/null differ diff --git a/releases/ae_anons-v0.2.6-linux-musl/.env.example b/releases/ae_anons-v0.2.6-linux-musl/.env.example deleted file mode 100644 index 34e2330..0000000 --- a/releases/ae_anons-v0.2.6-linux-musl/.env.example +++ /dev/null @@ -1,22 +0,0 @@ -# Synology NAS -NAS_FQDN="https://your-nas.example.com" -NAS_USER="your_username" -NAS_PASS="your_password" -NAS_FILE="/Team Folder/path/to/file.osheet" - -# Logging (optional, defaults to info) -RUST_LOG="info" - -# Web Server (optional, defaults to 3000) -WEB_PORT="3000" - -# Nexrender -NEXRENDER_API_URL="http://nexrender-server:3050/api/v1/jobs" -OUTPUT_FOLDER="/path/to/output" - -# After Effects Templates -TEMPLATE_DOUBLE_SRC="file:///path/to/double_team_template.aep" -TEMPLATE_SINGLE_SRC="file:///path/to/single_team_template.aep" -TEMPLATE_COMPOSITION="main" -TEMPLATE_OUTPUT_MODULE="h264" -TEMPLATE_OUTPUT_EXT="mp4" \ No newline at end of file diff --git a/releases/ae_anons-v0.2.6-linux-musl/LICENSE b/releases/ae_anons-v0.2.6-linux-musl/LICENSE deleted file mode 100644 index f6da942..0000000 --- a/releases/ae_anons-v0.2.6-linux-musl/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -# MIT License - -Copyright (c) 2026 [Your Name or Company] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/releases/ae_anons-v0.2.6-linux-musl/README.md b/releases/ae_anons-v0.2.6-linux-musl/README.md deleted file mode 100644 index 0e8f255..0000000 --- a/releases/ae_anons-v0.2.6-linux-musl/README.md +++ /dev/null @@ -1,680 +0,0 @@ -# AE Anons - Автоматизированный генератор спортивных анонсов в After Effects - - -

- AE Anons Logo -

- - -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Rust](https://img.shields.io/badge/Rust-1.70%2B-orange.svg)](https://www.rust-lang.org/) -[![Status](https://img.shields.io/badge/status-production-green.svg)](https://git.tvstart.ru/lexx/AE_Anons) - -Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office. - -## Новое в версии v0.2.6 - -- Сортировка заданий по дате и времени события (самые ранние — первыми) -- Сортировка по варианту (оригинал → today → tomorrow) -- Последовательная отправка для гарантированного порядка в очереди -- Исправлена нормализация дефисов (2 и более дефиса подряд → 1) -- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS) -- Добавлена поддержка кеширования ассетов (cache: true/false) -- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются - -## Обзор - -AE Anons — это CLI-утилита и веб-сервер на Rust, которая выступает как **интеллектуальный генератор заданий** -для [Nexrender](https://github.com/inlife/nexrender) — опенсорсного оркестратора рендеринга -After Effects (лицензия MIT). - -### Режимы работы - -1. **Однократная обработка** (`--once` или без флагов): - - Подключение к NAS Synology - - Парсинг Excel данных - - Генерация и отправка заданий Nexrender - - Мониторинг завершения и выход - -2. **Веб-сервер** (`--web`): - - Запуск веб-интерфейса на порту `:3000` - - Управление заданиями через браузер - - Автообновление статуса каждую минуту - -## Особенности - -- **Интеграция с Synology**: Аутентификация и загрузка файлов с NAS Synology -- **Экспорт офисных таблиц**: Автоматическое преобразование файлов .osheet в формат Excel -- **Гибкий парсинг данных**: Динамический парсинг листов с обнаружением заголовков -- **Множественная генерация вариантов**: Создание "Сегодня", "Завтра" и датированных версий -- **Умное управление логотипами**: Автоматическое разрешение и масштабирование логотипов -- **Веб-интерфейс**: Удобное управление и мониторинг заданий -- **REST API**: Программный доступ к управлению заданиями -- **Профессиональное логирование**: Структурированный журнал с настройкой уровня - -## Предварительные требования - -- Rust 1.70 или выше -- Доступ к NAS Synology с пакетами File Station и Office -- Экземпляр _server_ и _worker_(не менее одного) Nexrender -- Шаблоны After Effects (формат Adobe After Effects 2024 .aepx) - -## Установка - -### 1. Клонирование репозитория - -```bash -git clone https://git.tvstart.ru/lexx/AE_Anons.git -cd ae_anons -``` - -### 2. Сборка проекта - -```bash -cargo build --release -``` - -### 3. Настройка окружения - -Скопируйте пример конфигурации и заполните своими данными: - -```bash -cp .env.example .env -``` - -Затем отредактируйте файл .env: - -См. раздел Конфигурация для подробной информации. - -## Использование - -### Однократная обработка - -```bash -# Запуск с настройками по умолчанию -./target/release/ae_anons - -# Явно указать однократный режим -./target/release/ae_anons --once - -# С отладочным логированием -RUST_LOG=debug ./target/release/ae_anons -``` - -### Веб-сервер - -```bash -# Запуск веб-интерфейса на порту по умолчанию (3000) -./target/release/ae_anons --web - -# Или с указанием другого порта через .env файл -# WEB_PORT=8080 ./target/release/ae_anons --web - -``` - -## Веб-интерфейс - -После запуска веб-сервера откройте браузер: - -- Главная страница: - -- API эндпоинты: - - `GET /api/jobs` - список всех заданий - - `POST /api/generate` - запуск генерации - - `POST /api/cleanup` - очистка завершённых - - `POST /api/jobs/stop-all` - остановка активных - - `GET /api/status` - статус сервера - -## Уровни логирования - -Управляйте детализацией вывода через переменную окружения `RUST_LOG`: - -- **error** - Только критические ошибки -- **warn** - Предупреждения и ошибки -- **info** - Общие операционные сообщения (по умолчанию) -- **debug** - Детальная информация о процессе обработки -- **trace** - Полное отладочное логирование с деталями API вызовов - -```bash -RUST_LOG=debug cargo run -``` - -## Конфигурация - -### Переменные окружения - -| Переменная | Обязательна | Описание | -|-------------------------|-------------|------------------------------------------------------| -| `NAS_FQDN` | Да | URL NAS Synology с протоколом и портом | -| `NAS_USER` | Да | Имя пользователя учетной записи Synology | -| `NAS_PASS` | Да | Пароль учетной записи Synology | -| `NAS_FILE` | Да | Полный путь к файлу .osheet на NAS | -| `NEXRENDER_API_URL` | Да | Конечная точка API сервера Nexrender | -| `OUTPUT_FOLDER` | Да | Сетевой путь для рендеренных видео | -| `TEMPLATE_DOUBLE_SRC` | Да | Путь к AEP-шаблону для двух команд | -| `TEMPLATE_SINGLE_SRC` | Да | Путь к AEP-шаблону для одной команды | -| `TEMPLATE_COMPOSITION` | Да | Имя композиции в проекте AE (например, `main`) | -| `TEMPLATE_OUTPUT_MODULE`| Да | Имя модуля вывода в AE (например, `h264`) | -| `TEMPLATE_OUTPUT_EXT` | Да | Расширение выходного файла (например, `mp4`) | -| `WEB_PORT` | Нет | Порт для веб-сервера (по умолчанию: 3000) | -| `RUST_LOG` | Нет | Уровень детализации логирования (по умолчанию: info) | - -### Пример файла `.env` - -```env -# Synology NAS -NAS_FQDN="https://your-nas.example.com" -NAS_USER="your_username" -NAS_PASS="your_password" -NAS_FILE="/Team Folder/path/to/file.osheet" - -# Logging -RUST_LOG="info" - -# Web Server -WEB_PORT="3000" - -# Nexrender -NEXRENDER_API_URL="http://nexrender-server:3050/api/v1/jobs" -OUTPUT_FOLDER="/path/to/output" - -# After Effects Templates -TEMPLATE_DOUBLE_SRC="file:///path/to/double_team_template.aepx" -TEMPLATE_SINGLE_SRC="file:///path/to/single_team_template.aepx" -TEMPLATE_COMPOSITION="main" -TEMPLATE_OUTPUT_MODULE="h264" -TEMPLATE_OUTPUT_EXT="mp4" -``` - -## Структура электронной таблицы - -Файл Excel должен содержать следующие листы со специфичными структурами: - -### Лист "SPORT" - -Связывает названия видов спорта с соответствующими видео пакетами. - -| SPORT | LINK | -|----------------|---------------------------------------------| -| Без оформления | \\server\share\path\to\null.mov | -| Футбол | \\server\share\path\to\football_pack.mov | -| Волейбол | \\server\share\path\to\volleyball_pack.mov | - -### Лист "TEAMS" - -Сопоставляет имена команд с их видами спорта и логотипами. Колонка SPORT заполняется из выпадающего списка (заполнена из листа SPORT). - -Если несколько команд в одном виде спорта имеют одинаковые названия, но разные логотипы, используется разделитель хеш # для уникальной идентификации. - -| TEAM | SPORT | LINK | -|-------------------|-----------|---------------------------------------------------| -| Галатасарай#Turki | Футбол | \\server\share\path\to\galatasaray_football.png | -| Галатасарай | Волейбол | \\server\share\path\to\galatasaray_volleyball.png | -| Галатасарай#UCL | Волейбол | \\server\share\path\to\galatasaray_ucl.png | -| Сомбатей##400 | Футбол | \\server\share\path\to\szombathely.png | - -### Лист "CHANELL" - -Сопоставляет названия каналов с их логотипами. Поля заполняются вручную без выпадающих списков. - -| CHANELL | LINK | -|---------|-----------------------------------------| -| КАНАЛ | \\server\share\path\to\channel_logo.png | -| TRIUMPH | \\server\share\path\to\triumph_logo.png | - -### Лист "Start" - -Основной источник данных для генерации анонсов. Большинство полей заполняются из выпадающих списков, основанных на других листах. - -Обязательные колонки: DATA, TIME, CHANELL, SPORT, LEAGUE, TEAM A, TEAM B - -**Примечания:** - -- Колонка LEAGUE обязательна для заполнения, но не имеет выпадающего списка. Проверка орфографии и опечаток отсутствует! -- Колонка DATA должна содержать дату в формате `ДД.ММ.ГГГГ` (например, `28.2.2026`). - -| STATE | TRIPPLE | DATA | TIME | SPORT | LEAGUE | CHANELL | TEAM A | TEAM B | -|-------|---------|-----------|-------|---------|---------------------------|---------|-----------------|------------------| -| TRUE | TRUE | 27.2.2026 | 23:40 | Футбол | Чемпионат Португалии | START | Спортинг#ll#550 | Эшторил#ll#550 | -| FALSE | TRUE | 28.2.2026 | 12:55 | Волейбол| Чемпионат Турции. Женщины | TRIUMPH | Бешикташ | Галатасарай#Turki| - -### Формат имен команд - -Имена команд поддерживают два типа разделителей хэш #: - -1. **Первый хеш**: Уникальный идентификатор для команд с одинаковыми названиями в одном виде спорта, или одинаковым названием в разных видах спорта. -Примеры: - -- `Динамо#Футбол`, `Динамо#Волейбол` - Одно название разный вид спорта -- `Спартак#ЖенскийФ`, `Спартак#МужскойФ` Одно название _разный_ вид спорта - -1. **Второй хеш**: Целевой размер в пикселях для масштабирования логотипа - -Формат: `DisplayName#UniqueID#TargetSize` - -Примеры: - -- `Галатасарай#Turki` - только уникальный идентификатор -- `Сомбатей##400` - только целевой размер (обратите внимание на двойной хеш) -- `Спортинг#ll#550` - оба идентификатор и целевой размер - -**Важно**: Обрабатывается **только второй хеш** для масштабирования логотипа. Если уникальный идентификатор не требуется, используйте двойной хеш ## перед целевым размером. - -## Выходные файлы - -### Рендеренные видео - -Рендерные видео сохраняются в `OUTPUT_FOLDER` по следующему шаблону именования: - -```shell -YYYYMMDD_Sport_League_TeamA_TeamB_Channel[_Variant].mp4 -``` - -Имена файлов транслитерируются в латиницу. -**Примеры:** - -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH.mp4` -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH_Today.mp4` -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH_Tomorrow.mp4` - -## Рабочий процесс - -1. **Аутентификация**: Подключение к NAS Synology с использованием предоставленных учетных данных -2. **Получение файла**: Поиск и экспорт указанного файла .osheet в Excel -3. **Парсинг данных**: Чтение всех листов и структурирование данных -4. **Разрешение ресурсов**: Сопоставление видов спорта, команд и каналов с визуальными элементами -5. **Генерация заданий**: Создание заданий Nexrender для каждой активной строки с вариантами -6. **Очистка**: Удаление завершенных/неудачных заданий из предыдущих запусков -7. **Отправка**: Отправка заданий на API Nexrender -8. **Мониторинг**: Слежение за прогрессом выполнения до завершения -9. **Завершение сеанса**: Закрытие сессии Synology - -## Требования к шаблонам After Effects - -Шаблоны должны быть предварительно настроены на узлах рендеринга со следующими именами слоёв. - -### Файлы шаблонов - -Настраиваются через переменные окружения: - -| Переменная | Назначение | Пример значения | -|-------------------------|-------------------------------------|------------------------------------| -| `TEMPLATE_DOUBLE_SRC` | Шаблон для матчей с двумя командами | `file:///path/to/double.aepx` | -| `TEMPLATE_SINGLE_SRC` | Шаблон для анонсов с одной командой | `file:///path/to/single.aepx` | -| `TEMPLATE_COMPOSITION` | Имя главной композиции | `main` | -| `TEMPLATE_OUTPUT_MODULE`| Имя модуля вывода | `h264` | -| `TEMPLATE_OUTPUT_EXT` | Расширение выходного файла | `mp4` | - -### Обязательные слои в шаблонах - -#### Текстовые слои - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|-----------|:-------------:|:-------------:|-------------------------------------------------| -| `DATA` | ✅ | ✅ | Отображение даты (автоматически подстраивается) | -| `TIME_H` | ✅ | ❌ | Часы (отдельный слой) | -| `TIME_M` | ✅ | ❌ | Минуты (отдельный слой) | -| `TIME` | ❌ | ✅ | Полное время (единый слой) | -| `LEAGUE` | ✅ | ✅ | Название лиги/турнира | -| `SPORT` | ✅ | ✅ | Категория спорта | -| `TEAMS` | ✅ | ✅ | Скомбинированные имена команд | - -#### Слои с изображениями - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|----------------|:-------------:|:-------------:|-----------------------------| -| `TEAM_A_LOGO` | ✅ | ✅ | Логотип команды A | -| `TEAM_B_LOGO` | ✅ | ❌ | Логотип команды B | -| `CHANELL` | ✅ | ✅ | Логотип канала (две буквы L)| - -#### Видео слои - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|-----------|:-------------:|:-------------:|----------------------------| -| `TOP` | ✅ | ✅ | Наложение видео оформления | - -### Особенности шаблонов - -#### Шаблон DOUBLE (две команды) - -Используется когда в строке Excel заполнены оба поля `TEAM A` и `TEAM B`. - -Обязательные слои: - -- Все текстовые слои с `TIME_H` и `TIME_M` вместо `TIME` -- Оба логотипа команд: `TEAM_A_LOGO` и `TEAM_B_LOGO` - -#### Шаблон SINGLE (одна команда) - -Используется когда заполнено только одно поле команды. - -Обязательные слои: - -- Текстовый слой `TIME` вместо `TIME_H` и `TIME_M` -- Только логотип `TEAM_A_LOGO` - -### Автоматические корректировки - -AE Anons автоматически применяет следующие настройки к слоям: - -#### Слой DATA - -В зависимости от отображаемого текста и типа шаблона: - -| Текст | Шаблон | Font Size | Anchor Point | -|------------|----------|-----------|--------------| -| "сегодня" | DOUBLE | 105 | [0, 5] | -| "завтра" | DOUBLE | 115 | [0, 25] | -| дата (<6) | DOUBLE | 120 | [0, 20] | - -#### Слои TIME_H, TIME_M, TIME - -Корректировка Anchor Point в зависимости от длины текста: - -| Условие | Anchor Point | -|-------------------|--------------| -| 1 символ | [60, 0] | -| 2 символа, <20 | [20, 0] | - -#### Слой LEAGUE - -Если длина текста превышает 16 символов, размер шрифта уменьшается до 73. - -#### Слой TEAMS - -Если суммарная длина имён команд ≥ 32 символов, размер шрифта уменьшается до 55. - -#### Логотипы (TEAM_A_LOGO, TEAM_B_LOGO) - -Если в имени команды указан целевой размер через `#` (например, `Спортинг##550`), применяется выражение масштабирования: - -```javascript -if (width > height) { - max_size = width; -} else { - max_size = height; -} -var real_size = 550 / max_size * 100; -[real_size, real_size] -``` - -### Пример структуры слоёв в After Effects - -Ниже представлен рекомендуемый порядок слоёв в композиции. Порядок важен для правильного наложения элементов. - -```text -📁 main (композиция) -├── 🎬 TOP (видео слой) # Видео-оверлей (обязательный) -├── 📝 TEAMS (текстовый слой) # Имена команд (обязательный) -├── 📝 LEAGUE (текстовый слой) # Название лиги (обязательный) -├── 📝 SPORT (текстовый слой) # Вид спорта (обязательный) -├── 📝 DATA (текстовый слой) # Дата (обязательный) -├── 📝 TIME (текстовый слой) # Время (только SINGLE) -├── 📝 TIME_H (текстовый слой) # Часы (только DOUBLE) -├── 📝 TIME_M (текстовый слой) # Минуты (только DOUBLE) -├── 🖼️ CHANELL (слой изображения) # Логотип канала (обязательный) -├── 🖼️ TEAM_A_LOGO (слой изображения) # Логотип команды A (обязательный) -├── 🖼️ TEAM_B_LOGO (слой изображения) # Логотип команды B (только DOUBLE) -└── 🖼️ BOTTOM (слой изображения) # Фоновый слой (опционально) -``` - -### Формат проекта и совместимость с After Effects - -**Поддерживаемые форматы проектов:** - -- `.aep` — стандартный бинарный формат After Effects (рекомендуется) -- `.aepx` — XML-формат проекта (поддерживается с AE CC 2015) - -**Совместимость версий After Effects:** - -| Версия After Effects | Поддерживаемые форматы | Особенности | -|----------------------|------------------------|-------------------------------------------------| -| CS 5.5 | `.aep` | Базовая поддержка | -| CC / CC 2014 | `.aep` | Полная поддержка | -| CC 2015 - CC 2019 | `.aep`, `.aepx` | Добавлена поддержка XML формата `.aepx` | -| CC 2020 - CC 2022 | `.aep`, `.aepx` | Рекомендуется использовать `.aep` | -| CC 2023 и новее | `.aep`, `.aepx` | **Требуется настройка Output Module** (см. ниже)| - -> **⚠️ Важно для After Effects 2023+:** -> -> В версиях After Effects 2023 и новее критически важно настроить **Output Module** в шаблоне проекта. Бинарный файл рендеринга (`aerender`) не будет обрабатывать композицию без явно указанного модуля вывода, даже если в проекте используется модуль по умолчанию. -> -> AE Anons автоматически решает эту проблему, используя параметры `TEMPLATE_OUTPUT_MODULE` и `TEMPLATE_OUTPUT_EXT` из `.env` файла. Убедитесь, что эти значения соответствуют настройкам вашего шаблона. - -### Настройки композиции - -| Параметр | Переменная окружения | Пример значения | Описание | -|---------------------------|--------------------------|-----------------|---------------------------------------------| -| Имя композиции | `TEMPLATE_COMPOSITION` | `main` | Имя главной композиции в проекте AE | -| Выходной модуль | `TEMPLATE_OUTPUT_MODULE` | `h264` | Имя модуля вывода в AE | -| Расширение выходного файла| `TEMPLATE_OUTPUT_EXT` | `mp4` | Расширение выходного файла | - -**Рекомендации по выбору формата:** - -1. Используйте `.aep` для максимальной совместимости со всеми версиями AE -2. Версия After Effects на worker-машине должна быть не ниже версии, в которой создан проект -3. Для AE 2023+ убедитесь, что в шаблоне настроен Output Module с именем, указанным в `TEMPLATE_OUTPUT_MODULE` - -**Источники:** - -- [Nexrender - Tested with After Effects versions](https://github.com/inlife/nexrender#tested-with) -- [Adobe Aerender documentation](https://helpx.adobe.com/after-effects/using/automated-rendering-network-rendering.html) - -## Устранение неисправностей - -### Частые проблемы - -#### Ошибка подключения к NAS - -- Проверьте, что `NAS_FQDN` включает протокол и порт (например, `https://nas.example.com:5001`) -- Проверьте сетевую связность с NAS -- Убедитесь, что сервисы File Station и Office включены - -#### Файл не найден - -- Убедитесь, что путь в `NAS_FILE` точно соответствует пути в Synology Drive -- Путь должен начинаться с `/Team Folder/` для рабочих папок -- Проверьте права доступа к файлу для учетной записи сервиса - -#### Ошибка отправки задания Nexrender - -- Подтвердите доступность сервера Nexrender -- Убедитесь, что `NEXRENDER_API_URL` правильный -- Проверьте существование файлов шаблонов на узлах рендеринга - -### Режим отладки - -Включите подробное логирование для детального анализа: - -```bash -RUST_LOG=debug ./target/release/ae_anons -``` - -Это выведет: - -- Детали API запросов/ответов -- Информацию о парсинге листов -- Детали создания заданий -- Процесс разрешения ресурсов - -## Возможные ограничения по производительности - -- **Большие таблицы**: Ограничение обработки до 10,000 строк на лист -- **Задержка сети**: Загрузка файлов с NAS может занимать время для больших файлов -- **Параллельные задания**: Nexrender управляет очередью заданий внутренне - -## Примечания по безопасности - -- Храните учетные данные только в файле `.env` (исключен из git) -- Используйте специальные аккаунты с минимально необходимыми правами -- Сессии Synology завершаются после выполнения -- HTTPS рекомендуется для подключений NAS в продакшене - -## Разработка - -### Структура кода - -```shell -ae_anons/ -├── .gitignore -├── .env.example -├── Cargo.toml -├── LICENSE -├── README.md -├── assets/ -│ └── logo.png -└── src/ - ├── main.rs # Точка входа, CLI - ├── config.rs # Конфигурация из .env - ├── nexrender.rs # Модели заданий Nexrender - ├── synology.rs # Клиент Synology API - ├── processor.rs # Логика обработки - ├── web.rs # Веб-сервер и API - └── static/ - ├── index.html # Веб-интерфейс - └── style.css # Стили -``` - -## Зависимости - -### Основные зависимости - -| Crate | Версия | Назначение | -|--------------------|---------|-----------------------------------------| -| reqwest | 0.12 | HTTP клиент для коммуникации с API | -| serde / serde_json | 1.0 | Сериализация и десериализация JSON | -| calamine | 0.26 | Парсинг файлов Excel (.xlsx, .xls) | -| chrono | 0.4 | Обработка дат и времени | -| tokio | 1.0 | Асинхронная среда выполнения | -| dotenv | 0.15 | Загрузка конфигурации из .env файла | -| log / env_logger | 0.4/0.11| Система логирования с уровнями | -| thiserror | 2.0 | Эргономичные определения типов ошибок | -| anyhow | 1.0 | Упрощённая обработка ошибок | -| regex | 1.11 | Регулярные выражения | -| urlencoding | 2.1 | Кодирование URL для API запросов | -| bytes | 1.9 | Работа с байтовыми данными | -| futures | 0.3 | Асинхронные примитивы | - -### Веб-сервер и CLI - -| Crate | Версия | Назначение | -|--------------------|--------|-----------------------------------------| -| axum | 0.8 | Веб-фреймворк для REST API | -| tower | 0.5 | Промежуточное ПО для веб-сервера | -| tower-http | 0.6 |HTTP утилиты (CORS, статика, трассировка)| -| askama | 0.15 | Шаблонизация (опционально) | -| clap | 4.5 | Парсинг аргументов командной строки | - -### Платформозависимые зависимости - -| Crate | Версия | Платформа | Назначение | -|--------------------|--------|-----------|----------------------------------| -| openssl | 0.10 | Linux | Криптография для HTTPS (vendored)| - -## 🙏 Благодарности - -### Nexrender - -Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)** -([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы -для автоматизации After Effects. - -### Font Awesome Free 6.4.0 - -Веб-интерфейс использует иконки и шрифты **Font Awesome Free**: - -- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) -- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL) - -© 2023 Fonticons, Inc. — - -## Лицензия - -**AE Anons** — [License](LICENSE) -**Nexrender** — [MIT License](https://github.com/inlife/nexrender/blob/master/LICENSE) - -Обе лицензии MIT обеспечивают полную свободу использования и модификации кода. - -**Разрешается:** - -- ✅ Использовать в коммерческих целях -- ✅ Изменять исходный код -- ✅ Распространять копии -- ✅ Использовать приватно - -**Требуется:** - -- Сохранять копирайт и текст лицензии - -## Поддержка - -Для вопросов и запросов функций обращайтесь: - -- -- [README.md](https://git.tvstart.ru/lexx/AE_Anons) - -## История изменений - -### v0.2.6 (текущая) - -- Сортировка заданий по дате и времени события (самые ранние — первыми) -- Сортировка по варианту (оригинал → today → tomorrow) -- Последовательная отправка для гарантированного порядка в очереди -- Исправлена нормализация дефисов (2 и более дефиса подряд → 1) -- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS) -- Добавлена поддержка кеширования ассетов (cache: true/false) -- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются - -### v0.2.5 - -- Оптимизация памяти: очистка неиспользуемых логотипов -- Групповая отправка заданий по 3 для сохранения порядка -- Добавлен UID в логи отправки - -### v0.2.4 - -- Полностью автономный режим (встроенный Font Awesome) -- Добавлен favicon -- Все статические ресурсы встроены в бинарник - -### v0.2.3 - -- Веб-интерфейс с настраиваемым портом (WEB_PORT) -- Тёмная/светлая тема с автоопределением ОС -- Сортировка по всем колонкам в интерфейсе -- Фильтрация по имени файла/UID -- Остановка всех активных заданий -- Автообновление каждые 60 секунд -- Улучшенное debug-логирование -- Исправлена обработка UNC/SMB путей - -### v0.2.2 - -- Исправлено отображение имён выходных файлов в веб-интерфейсе -- Улучшена цветовая схема -- Добавлена сортировка по всем колонкам -- Вынесены стили в отдельный CSS файл - -### v0.2.0 - -- Добавлен веб-интерфейс для управления заданиями -- Реализован REST API -- Добавлена поддержка тёмной/светлой темы -- Автообновление статуса заданий -- Возможность остановки всех активных заданий - -### v0.1.1 - -- Оптимизирована работа с памятью -- Убрана функция создания .json - -### v0.1.0 - -- Первый выпуск -- Интеграция с Synology Office -- Базовая генерация заданий Nexrender -- Парсинг Excel с динамическим обнаружением листов - ---- - -Made with 🦀 Rust and ☕ coffee diff --git a/releases/ae_anons-v0.2.6-linux-musl/ae_anons b/releases/ae_anons-v0.2.6-linux-musl/ae_anons deleted file mode 100755 index 5cf6b26..0000000 Binary files a/releases/ae_anons-v0.2.6-linux-musl/ae_anons and /dev/null differ diff --git a/releases/ae_anons-v0.2.6-macos-arm64/.env.example b/releases/ae_anons-v0.2.6-macos-arm64/.env.example deleted file mode 100644 index 34e2330..0000000 --- a/releases/ae_anons-v0.2.6-macos-arm64/.env.example +++ /dev/null @@ -1,22 +0,0 @@ -# Synology NAS -NAS_FQDN="https://your-nas.example.com" -NAS_USER="your_username" -NAS_PASS="your_password" -NAS_FILE="/Team Folder/path/to/file.osheet" - -# Logging (optional, defaults to info) -RUST_LOG="info" - -# Web Server (optional, defaults to 3000) -WEB_PORT="3000" - -# Nexrender -NEXRENDER_API_URL="http://nexrender-server:3050/api/v1/jobs" -OUTPUT_FOLDER="/path/to/output" - -# After Effects Templates -TEMPLATE_DOUBLE_SRC="file:///path/to/double_team_template.aep" -TEMPLATE_SINGLE_SRC="file:///path/to/single_team_template.aep" -TEMPLATE_COMPOSITION="main" -TEMPLATE_OUTPUT_MODULE="h264" -TEMPLATE_OUTPUT_EXT="mp4" \ No newline at end of file diff --git a/releases/ae_anons-v0.2.6-macos-arm64/LICENSE b/releases/ae_anons-v0.2.6-macos-arm64/LICENSE deleted file mode 100644 index f6da942..0000000 --- a/releases/ae_anons-v0.2.6-macos-arm64/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -# MIT License - -Copyright (c) 2026 [Your Name or Company] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/releases/ae_anons-v0.2.6-macos-arm64/README.md b/releases/ae_anons-v0.2.6-macos-arm64/README.md deleted file mode 100644 index 0e8f255..0000000 --- a/releases/ae_anons-v0.2.6-macos-arm64/README.md +++ /dev/null @@ -1,680 +0,0 @@ -# AE Anons - Автоматизированный генератор спортивных анонсов в After Effects - - -

- AE Anons Logo -

- - -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Rust](https://img.shields.io/badge/Rust-1.70%2B-orange.svg)](https://www.rust-lang.org/) -[![Status](https://img.shields.io/badge/status-production-green.svg)](https://git.tvstart.ru/lexx/AE_Anons) - -Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office. - -## Новое в версии v0.2.6 - -- Сортировка заданий по дате и времени события (самые ранние — первыми) -- Сортировка по варианту (оригинал → today → tomorrow) -- Последовательная отправка для гарантированного порядка в очереди -- Исправлена нормализация дефисов (2 и более дефиса подряд → 1) -- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS) -- Добавлена поддержка кеширования ассетов (cache: true/false) -- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются - -## Обзор - -AE Anons — это CLI-утилита и веб-сервер на Rust, которая выступает как **интеллектуальный генератор заданий** -для [Nexrender](https://github.com/inlife/nexrender) — опенсорсного оркестратора рендеринга -After Effects (лицензия MIT). - -### Режимы работы - -1. **Однократная обработка** (`--once` или без флагов): - - Подключение к NAS Synology - - Парсинг Excel данных - - Генерация и отправка заданий Nexrender - - Мониторинг завершения и выход - -2. **Веб-сервер** (`--web`): - - Запуск веб-интерфейса на порту `:3000` - - Управление заданиями через браузер - - Автообновление статуса каждую минуту - -## Особенности - -- **Интеграция с Synology**: Аутентификация и загрузка файлов с NAS Synology -- **Экспорт офисных таблиц**: Автоматическое преобразование файлов .osheet в формат Excel -- **Гибкий парсинг данных**: Динамический парсинг листов с обнаружением заголовков -- **Множественная генерация вариантов**: Создание "Сегодня", "Завтра" и датированных версий -- **Умное управление логотипами**: Автоматическое разрешение и масштабирование логотипов -- **Веб-интерфейс**: Удобное управление и мониторинг заданий -- **REST API**: Программный доступ к управлению заданиями -- **Профессиональное логирование**: Структурированный журнал с настройкой уровня - -## Предварительные требования - -- Rust 1.70 или выше -- Доступ к NAS Synology с пакетами File Station и Office -- Экземпляр _server_ и _worker_(не менее одного) Nexrender -- Шаблоны After Effects (формат Adobe After Effects 2024 .aepx) - -## Установка - -### 1. Клонирование репозитория - -```bash -git clone https://git.tvstart.ru/lexx/AE_Anons.git -cd ae_anons -``` - -### 2. Сборка проекта - -```bash -cargo build --release -``` - -### 3. Настройка окружения - -Скопируйте пример конфигурации и заполните своими данными: - -```bash -cp .env.example .env -``` - -Затем отредактируйте файл .env: - -См. раздел Конфигурация для подробной информации. - -## Использование - -### Однократная обработка - -```bash -# Запуск с настройками по умолчанию -./target/release/ae_anons - -# Явно указать однократный режим -./target/release/ae_anons --once - -# С отладочным логированием -RUST_LOG=debug ./target/release/ae_anons -``` - -### Веб-сервер - -```bash -# Запуск веб-интерфейса на порту по умолчанию (3000) -./target/release/ae_anons --web - -# Или с указанием другого порта через .env файл -# WEB_PORT=8080 ./target/release/ae_anons --web - -``` - -## Веб-интерфейс - -После запуска веб-сервера откройте браузер: - -- Главная страница: - -- API эндпоинты: - - `GET /api/jobs` - список всех заданий - - `POST /api/generate` - запуск генерации - - `POST /api/cleanup` - очистка завершённых - - `POST /api/jobs/stop-all` - остановка активных - - `GET /api/status` - статус сервера - -## Уровни логирования - -Управляйте детализацией вывода через переменную окружения `RUST_LOG`: - -- **error** - Только критические ошибки -- **warn** - Предупреждения и ошибки -- **info** - Общие операционные сообщения (по умолчанию) -- **debug** - Детальная информация о процессе обработки -- **trace** - Полное отладочное логирование с деталями API вызовов - -```bash -RUST_LOG=debug cargo run -``` - -## Конфигурация - -### Переменные окружения - -| Переменная | Обязательна | Описание | -|-------------------------|-------------|------------------------------------------------------| -| `NAS_FQDN` | Да | URL NAS Synology с протоколом и портом | -| `NAS_USER` | Да | Имя пользователя учетной записи Synology | -| `NAS_PASS` | Да | Пароль учетной записи Synology | -| `NAS_FILE` | Да | Полный путь к файлу .osheet на NAS | -| `NEXRENDER_API_URL` | Да | Конечная точка API сервера Nexrender | -| `OUTPUT_FOLDER` | Да | Сетевой путь для рендеренных видео | -| `TEMPLATE_DOUBLE_SRC` | Да | Путь к AEP-шаблону для двух команд | -| `TEMPLATE_SINGLE_SRC` | Да | Путь к AEP-шаблону для одной команды | -| `TEMPLATE_COMPOSITION` | Да | Имя композиции в проекте AE (например, `main`) | -| `TEMPLATE_OUTPUT_MODULE`| Да | Имя модуля вывода в AE (например, `h264`) | -| `TEMPLATE_OUTPUT_EXT` | Да | Расширение выходного файла (например, `mp4`) | -| `WEB_PORT` | Нет | Порт для веб-сервера (по умолчанию: 3000) | -| `RUST_LOG` | Нет | Уровень детализации логирования (по умолчанию: info) | - -### Пример файла `.env` - -```env -# Synology NAS -NAS_FQDN="https://your-nas.example.com" -NAS_USER="your_username" -NAS_PASS="your_password" -NAS_FILE="/Team Folder/path/to/file.osheet" - -# Logging -RUST_LOG="info" - -# Web Server -WEB_PORT="3000" - -# Nexrender -NEXRENDER_API_URL="http://nexrender-server:3050/api/v1/jobs" -OUTPUT_FOLDER="/path/to/output" - -# After Effects Templates -TEMPLATE_DOUBLE_SRC="file:///path/to/double_team_template.aepx" -TEMPLATE_SINGLE_SRC="file:///path/to/single_team_template.aepx" -TEMPLATE_COMPOSITION="main" -TEMPLATE_OUTPUT_MODULE="h264" -TEMPLATE_OUTPUT_EXT="mp4" -``` - -## Структура электронной таблицы - -Файл Excel должен содержать следующие листы со специфичными структурами: - -### Лист "SPORT" - -Связывает названия видов спорта с соответствующими видео пакетами. - -| SPORT | LINK | -|----------------|---------------------------------------------| -| Без оформления | \\server\share\path\to\null.mov | -| Футбол | \\server\share\path\to\football_pack.mov | -| Волейбол | \\server\share\path\to\volleyball_pack.mov | - -### Лист "TEAMS" - -Сопоставляет имена команд с их видами спорта и логотипами. Колонка SPORT заполняется из выпадающего списка (заполнена из листа SPORT). - -Если несколько команд в одном виде спорта имеют одинаковые названия, но разные логотипы, используется разделитель хеш # для уникальной идентификации. - -| TEAM | SPORT | LINK | -|-------------------|-----------|---------------------------------------------------| -| Галатасарай#Turki | Футбол | \\server\share\path\to\galatasaray_football.png | -| Галатасарай | Волейбол | \\server\share\path\to\galatasaray_volleyball.png | -| Галатасарай#UCL | Волейбол | \\server\share\path\to\galatasaray_ucl.png | -| Сомбатей##400 | Футбол | \\server\share\path\to\szombathely.png | - -### Лист "CHANELL" - -Сопоставляет названия каналов с их логотипами. Поля заполняются вручную без выпадающих списков. - -| CHANELL | LINK | -|---------|-----------------------------------------| -| КАНАЛ | \\server\share\path\to\channel_logo.png | -| TRIUMPH | \\server\share\path\to\triumph_logo.png | - -### Лист "Start" - -Основной источник данных для генерации анонсов. Большинство полей заполняются из выпадающих списков, основанных на других листах. - -Обязательные колонки: DATA, TIME, CHANELL, SPORT, LEAGUE, TEAM A, TEAM B - -**Примечания:** - -- Колонка LEAGUE обязательна для заполнения, но не имеет выпадающего списка. Проверка орфографии и опечаток отсутствует! -- Колонка DATA должна содержать дату в формате `ДД.ММ.ГГГГ` (например, `28.2.2026`). - -| STATE | TRIPPLE | DATA | TIME | SPORT | LEAGUE | CHANELL | TEAM A | TEAM B | -|-------|---------|-----------|-------|---------|---------------------------|---------|-----------------|------------------| -| TRUE | TRUE | 27.2.2026 | 23:40 | Футбол | Чемпионат Португалии | START | Спортинг#ll#550 | Эшторил#ll#550 | -| FALSE | TRUE | 28.2.2026 | 12:55 | Волейбол| Чемпионат Турции. Женщины | TRIUMPH | Бешикташ | Галатасарай#Turki| - -### Формат имен команд - -Имена команд поддерживают два типа разделителей хэш #: - -1. **Первый хеш**: Уникальный идентификатор для команд с одинаковыми названиями в одном виде спорта, или одинаковым названием в разных видах спорта. -Примеры: - -- `Динамо#Футбол`, `Динамо#Волейбол` - Одно название разный вид спорта -- `Спартак#ЖенскийФ`, `Спартак#МужскойФ` Одно название _разный_ вид спорта - -1. **Второй хеш**: Целевой размер в пикселях для масштабирования логотипа - -Формат: `DisplayName#UniqueID#TargetSize` - -Примеры: - -- `Галатасарай#Turki` - только уникальный идентификатор -- `Сомбатей##400` - только целевой размер (обратите внимание на двойной хеш) -- `Спортинг#ll#550` - оба идентификатор и целевой размер - -**Важно**: Обрабатывается **только второй хеш** для масштабирования логотипа. Если уникальный идентификатор не требуется, используйте двойной хеш ## перед целевым размером. - -## Выходные файлы - -### Рендеренные видео - -Рендерные видео сохраняются в `OUTPUT_FOLDER` по следующему шаблону именования: - -```shell -YYYYMMDD_Sport_League_TeamA_TeamB_Channel[_Variant].mp4 -``` - -Имена файлов транслитерируются в латиницу. -**Примеры:** - -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH.mp4` -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH_Today.mp4` -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH_Tomorrow.mp4` - -## Рабочий процесс - -1. **Аутентификация**: Подключение к NAS Synology с использованием предоставленных учетных данных -2. **Получение файла**: Поиск и экспорт указанного файла .osheet в Excel -3. **Парсинг данных**: Чтение всех листов и структурирование данных -4. **Разрешение ресурсов**: Сопоставление видов спорта, команд и каналов с визуальными элементами -5. **Генерация заданий**: Создание заданий Nexrender для каждой активной строки с вариантами -6. **Очистка**: Удаление завершенных/неудачных заданий из предыдущих запусков -7. **Отправка**: Отправка заданий на API Nexrender -8. **Мониторинг**: Слежение за прогрессом выполнения до завершения -9. **Завершение сеанса**: Закрытие сессии Synology - -## Требования к шаблонам After Effects - -Шаблоны должны быть предварительно настроены на узлах рендеринга со следующими именами слоёв. - -### Файлы шаблонов - -Настраиваются через переменные окружения: - -| Переменная | Назначение | Пример значения | -|-------------------------|-------------------------------------|------------------------------------| -| `TEMPLATE_DOUBLE_SRC` | Шаблон для матчей с двумя командами | `file:///path/to/double.aepx` | -| `TEMPLATE_SINGLE_SRC` | Шаблон для анонсов с одной командой | `file:///path/to/single.aepx` | -| `TEMPLATE_COMPOSITION` | Имя главной композиции | `main` | -| `TEMPLATE_OUTPUT_MODULE`| Имя модуля вывода | `h264` | -| `TEMPLATE_OUTPUT_EXT` | Расширение выходного файла | `mp4` | - -### Обязательные слои в шаблонах - -#### Текстовые слои - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|-----------|:-------------:|:-------------:|-------------------------------------------------| -| `DATA` | ✅ | ✅ | Отображение даты (автоматически подстраивается) | -| `TIME_H` | ✅ | ❌ | Часы (отдельный слой) | -| `TIME_M` | ✅ | ❌ | Минуты (отдельный слой) | -| `TIME` | ❌ | ✅ | Полное время (единый слой) | -| `LEAGUE` | ✅ | ✅ | Название лиги/турнира | -| `SPORT` | ✅ | ✅ | Категория спорта | -| `TEAMS` | ✅ | ✅ | Скомбинированные имена команд | - -#### Слои с изображениями - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|----------------|:-------------:|:-------------:|-----------------------------| -| `TEAM_A_LOGO` | ✅ | ✅ | Логотип команды A | -| `TEAM_B_LOGO` | ✅ | ❌ | Логотип команды B | -| `CHANELL` | ✅ | ✅ | Логотип канала (две буквы L)| - -#### Видео слои - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|-----------|:-------------:|:-------------:|----------------------------| -| `TOP` | ✅ | ✅ | Наложение видео оформления | - -### Особенности шаблонов - -#### Шаблон DOUBLE (две команды) - -Используется когда в строке Excel заполнены оба поля `TEAM A` и `TEAM B`. - -Обязательные слои: - -- Все текстовые слои с `TIME_H` и `TIME_M` вместо `TIME` -- Оба логотипа команд: `TEAM_A_LOGO` и `TEAM_B_LOGO` - -#### Шаблон SINGLE (одна команда) - -Используется когда заполнено только одно поле команды. - -Обязательные слои: - -- Текстовый слой `TIME` вместо `TIME_H` и `TIME_M` -- Только логотип `TEAM_A_LOGO` - -### Автоматические корректировки - -AE Anons автоматически применяет следующие настройки к слоям: - -#### Слой DATA - -В зависимости от отображаемого текста и типа шаблона: - -| Текст | Шаблон | Font Size | Anchor Point | -|------------|----------|-----------|--------------| -| "сегодня" | DOUBLE | 105 | [0, 5] | -| "завтра" | DOUBLE | 115 | [0, 25] | -| дата (<6) | DOUBLE | 120 | [0, 20] | - -#### Слои TIME_H, TIME_M, TIME - -Корректировка Anchor Point в зависимости от длины текста: - -| Условие | Anchor Point | -|-------------------|--------------| -| 1 символ | [60, 0] | -| 2 символа, <20 | [20, 0] | - -#### Слой LEAGUE - -Если длина текста превышает 16 символов, размер шрифта уменьшается до 73. - -#### Слой TEAMS - -Если суммарная длина имён команд ≥ 32 символов, размер шрифта уменьшается до 55. - -#### Логотипы (TEAM_A_LOGO, TEAM_B_LOGO) - -Если в имени команды указан целевой размер через `#` (например, `Спортинг##550`), применяется выражение масштабирования: - -```javascript -if (width > height) { - max_size = width; -} else { - max_size = height; -} -var real_size = 550 / max_size * 100; -[real_size, real_size] -``` - -### Пример структуры слоёв в After Effects - -Ниже представлен рекомендуемый порядок слоёв в композиции. Порядок важен для правильного наложения элементов. - -```text -📁 main (композиция) -├── 🎬 TOP (видео слой) # Видео-оверлей (обязательный) -├── 📝 TEAMS (текстовый слой) # Имена команд (обязательный) -├── 📝 LEAGUE (текстовый слой) # Название лиги (обязательный) -├── 📝 SPORT (текстовый слой) # Вид спорта (обязательный) -├── 📝 DATA (текстовый слой) # Дата (обязательный) -├── 📝 TIME (текстовый слой) # Время (только SINGLE) -├── 📝 TIME_H (текстовый слой) # Часы (только DOUBLE) -├── 📝 TIME_M (текстовый слой) # Минуты (только DOUBLE) -├── 🖼️ CHANELL (слой изображения) # Логотип канала (обязательный) -├── 🖼️ TEAM_A_LOGO (слой изображения) # Логотип команды A (обязательный) -├── 🖼️ TEAM_B_LOGO (слой изображения) # Логотип команды B (только DOUBLE) -└── 🖼️ BOTTOM (слой изображения) # Фоновый слой (опционально) -``` - -### Формат проекта и совместимость с After Effects - -**Поддерживаемые форматы проектов:** - -- `.aep` — стандартный бинарный формат After Effects (рекомендуется) -- `.aepx` — XML-формат проекта (поддерживается с AE CC 2015) - -**Совместимость версий After Effects:** - -| Версия After Effects | Поддерживаемые форматы | Особенности | -|----------------------|------------------------|-------------------------------------------------| -| CS 5.5 | `.aep` | Базовая поддержка | -| CC / CC 2014 | `.aep` | Полная поддержка | -| CC 2015 - CC 2019 | `.aep`, `.aepx` | Добавлена поддержка XML формата `.aepx` | -| CC 2020 - CC 2022 | `.aep`, `.aepx` | Рекомендуется использовать `.aep` | -| CC 2023 и новее | `.aep`, `.aepx` | **Требуется настройка Output Module** (см. ниже)| - -> **⚠️ Важно для After Effects 2023+:** -> -> В версиях After Effects 2023 и новее критически важно настроить **Output Module** в шаблоне проекта. Бинарный файл рендеринга (`aerender`) не будет обрабатывать композицию без явно указанного модуля вывода, даже если в проекте используется модуль по умолчанию. -> -> AE Anons автоматически решает эту проблему, используя параметры `TEMPLATE_OUTPUT_MODULE` и `TEMPLATE_OUTPUT_EXT` из `.env` файла. Убедитесь, что эти значения соответствуют настройкам вашего шаблона. - -### Настройки композиции - -| Параметр | Переменная окружения | Пример значения | Описание | -|---------------------------|--------------------------|-----------------|---------------------------------------------| -| Имя композиции | `TEMPLATE_COMPOSITION` | `main` | Имя главной композиции в проекте AE | -| Выходной модуль | `TEMPLATE_OUTPUT_MODULE` | `h264` | Имя модуля вывода в AE | -| Расширение выходного файла| `TEMPLATE_OUTPUT_EXT` | `mp4` | Расширение выходного файла | - -**Рекомендации по выбору формата:** - -1. Используйте `.aep` для максимальной совместимости со всеми версиями AE -2. Версия After Effects на worker-машине должна быть не ниже версии, в которой создан проект -3. Для AE 2023+ убедитесь, что в шаблоне настроен Output Module с именем, указанным в `TEMPLATE_OUTPUT_MODULE` - -**Источники:** - -- [Nexrender - Tested with After Effects versions](https://github.com/inlife/nexrender#tested-with) -- [Adobe Aerender documentation](https://helpx.adobe.com/after-effects/using/automated-rendering-network-rendering.html) - -## Устранение неисправностей - -### Частые проблемы - -#### Ошибка подключения к NAS - -- Проверьте, что `NAS_FQDN` включает протокол и порт (например, `https://nas.example.com:5001`) -- Проверьте сетевую связность с NAS -- Убедитесь, что сервисы File Station и Office включены - -#### Файл не найден - -- Убедитесь, что путь в `NAS_FILE` точно соответствует пути в Synology Drive -- Путь должен начинаться с `/Team Folder/` для рабочих папок -- Проверьте права доступа к файлу для учетной записи сервиса - -#### Ошибка отправки задания Nexrender - -- Подтвердите доступность сервера Nexrender -- Убедитесь, что `NEXRENDER_API_URL` правильный -- Проверьте существование файлов шаблонов на узлах рендеринга - -### Режим отладки - -Включите подробное логирование для детального анализа: - -```bash -RUST_LOG=debug ./target/release/ae_anons -``` - -Это выведет: - -- Детали API запросов/ответов -- Информацию о парсинге листов -- Детали создания заданий -- Процесс разрешения ресурсов - -## Возможные ограничения по производительности - -- **Большие таблицы**: Ограничение обработки до 10,000 строк на лист -- **Задержка сети**: Загрузка файлов с NAS может занимать время для больших файлов -- **Параллельные задания**: Nexrender управляет очередью заданий внутренне - -## Примечания по безопасности - -- Храните учетные данные только в файле `.env` (исключен из git) -- Используйте специальные аккаунты с минимально необходимыми правами -- Сессии Synology завершаются после выполнения -- HTTPS рекомендуется для подключений NAS в продакшене - -## Разработка - -### Структура кода - -```shell -ae_anons/ -├── .gitignore -├── .env.example -├── Cargo.toml -├── LICENSE -├── README.md -├── assets/ -│ └── logo.png -└── src/ - ├── main.rs # Точка входа, CLI - ├── config.rs # Конфигурация из .env - ├── nexrender.rs # Модели заданий Nexrender - ├── synology.rs # Клиент Synology API - ├── processor.rs # Логика обработки - ├── web.rs # Веб-сервер и API - └── static/ - ├── index.html # Веб-интерфейс - └── style.css # Стили -``` - -## Зависимости - -### Основные зависимости - -| Crate | Версия | Назначение | -|--------------------|---------|-----------------------------------------| -| reqwest | 0.12 | HTTP клиент для коммуникации с API | -| serde / serde_json | 1.0 | Сериализация и десериализация JSON | -| calamine | 0.26 | Парсинг файлов Excel (.xlsx, .xls) | -| chrono | 0.4 | Обработка дат и времени | -| tokio | 1.0 | Асинхронная среда выполнения | -| dotenv | 0.15 | Загрузка конфигурации из .env файла | -| log / env_logger | 0.4/0.11| Система логирования с уровнями | -| thiserror | 2.0 | Эргономичные определения типов ошибок | -| anyhow | 1.0 | Упрощённая обработка ошибок | -| regex | 1.11 | Регулярные выражения | -| urlencoding | 2.1 | Кодирование URL для API запросов | -| bytes | 1.9 | Работа с байтовыми данными | -| futures | 0.3 | Асинхронные примитивы | - -### Веб-сервер и CLI - -| Crate | Версия | Назначение | -|--------------------|--------|-----------------------------------------| -| axum | 0.8 | Веб-фреймворк для REST API | -| tower | 0.5 | Промежуточное ПО для веб-сервера | -| tower-http | 0.6 |HTTP утилиты (CORS, статика, трассировка)| -| askama | 0.15 | Шаблонизация (опционально) | -| clap | 4.5 | Парсинг аргументов командной строки | - -### Платформозависимые зависимости - -| Crate | Версия | Платформа | Назначение | -|--------------------|--------|-----------|----------------------------------| -| openssl | 0.10 | Linux | Криптография для HTTPS (vendored)| - -## 🙏 Благодарности - -### Nexrender - -Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)** -([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы -для автоматизации After Effects. - -### Font Awesome Free 6.4.0 - -Веб-интерфейс использует иконки и шрифты **Font Awesome Free**: - -- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) -- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL) - -© 2023 Fonticons, Inc. — - -## Лицензия - -**AE Anons** — [License](LICENSE) -**Nexrender** — [MIT License](https://github.com/inlife/nexrender/blob/master/LICENSE) - -Обе лицензии MIT обеспечивают полную свободу использования и модификации кода. - -**Разрешается:** - -- ✅ Использовать в коммерческих целях -- ✅ Изменять исходный код -- ✅ Распространять копии -- ✅ Использовать приватно - -**Требуется:** - -- Сохранять копирайт и текст лицензии - -## Поддержка - -Для вопросов и запросов функций обращайтесь: - -- -- [README.md](https://git.tvstart.ru/lexx/AE_Anons) - -## История изменений - -### v0.2.6 (текущая) - -- Сортировка заданий по дате и времени события (самые ранние — первыми) -- Сортировка по варианту (оригинал → today → tomorrow) -- Последовательная отправка для гарантированного порядка в очереди -- Исправлена нормализация дефисов (2 и более дефиса подряд → 1) -- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS) -- Добавлена поддержка кеширования ассетов (cache: true/false) -- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются - -### v0.2.5 - -- Оптимизация памяти: очистка неиспользуемых логотипов -- Групповая отправка заданий по 3 для сохранения порядка -- Добавлен UID в логи отправки - -### v0.2.4 - -- Полностью автономный режим (встроенный Font Awesome) -- Добавлен favicon -- Все статические ресурсы встроены в бинарник - -### v0.2.3 - -- Веб-интерфейс с настраиваемым портом (WEB_PORT) -- Тёмная/светлая тема с автоопределением ОС -- Сортировка по всем колонкам в интерфейсе -- Фильтрация по имени файла/UID -- Остановка всех активных заданий -- Автообновление каждые 60 секунд -- Улучшенное debug-логирование -- Исправлена обработка UNC/SMB путей - -### v0.2.2 - -- Исправлено отображение имён выходных файлов в веб-интерфейсе -- Улучшена цветовая схема -- Добавлена сортировка по всем колонкам -- Вынесены стили в отдельный CSS файл - -### v0.2.0 - -- Добавлен веб-интерфейс для управления заданиями -- Реализован REST API -- Добавлена поддержка тёмной/светлой темы -- Автообновление статуса заданий -- Возможность остановки всех активных заданий - -### v0.1.1 - -- Оптимизирована работа с памятью -- Убрана функция создания .json - -### v0.1.0 - -- Первый выпуск -- Интеграция с Synology Office -- Базовая генерация заданий Nexrender -- Парсинг Excel с динамическим обнаружением листов - ---- - -Made with 🦀 Rust and ☕ coffee diff --git a/releases/ae_anons-v0.2.6-macos-arm64/ae_anons b/releases/ae_anons-v0.2.6-macos-arm64/ae_anons deleted file mode 100755 index ddc2b4c..0000000 Binary files a/releases/ae_anons-v0.2.6-macos-arm64/ae_anons and /dev/null differ diff --git a/releases/ae_anons-v0.2.6-windows-x64/.env.example b/releases/ae_anons-v0.2.6-windows-x64/.env.example deleted file mode 100644 index 34e2330..0000000 --- a/releases/ae_anons-v0.2.6-windows-x64/.env.example +++ /dev/null @@ -1,22 +0,0 @@ -# Synology NAS -NAS_FQDN="https://your-nas.example.com" -NAS_USER="your_username" -NAS_PASS="your_password" -NAS_FILE="/Team Folder/path/to/file.osheet" - -# Logging (optional, defaults to info) -RUST_LOG="info" - -# Web Server (optional, defaults to 3000) -WEB_PORT="3000" - -# Nexrender -NEXRENDER_API_URL="http://nexrender-server:3050/api/v1/jobs" -OUTPUT_FOLDER="/path/to/output" - -# After Effects Templates -TEMPLATE_DOUBLE_SRC="file:///path/to/double_team_template.aep" -TEMPLATE_SINGLE_SRC="file:///path/to/single_team_template.aep" -TEMPLATE_COMPOSITION="main" -TEMPLATE_OUTPUT_MODULE="h264" -TEMPLATE_OUTPUT_EXT="mp4" \ No newline at end of file diff --git a/releases/ae_anons-v0.2.6-windows-x64/LICENSE b/releases/ae_anons-v0.2.6-windows-x64/LICENSE deleted file mode 100644 index f6da942..0000000 --- a/releases/ae_anons-v0.2.6-windows-x64/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -# MIT License - -Copyright (c) 2026 [Your Name or Company] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/releases/ae_anons-v0.2.6-windows-x64/README.md b/releases/ae_anons-v0.2.6-windows-x64/README.md deleted file mode 100644 index 0e8f255..0000000 --- a/releases/ae_anons-v0.2.6-windows-x64/README.md +++ /dev/null @@ -1,680 +0,0 @@ -# AE Anons - Автоматизированный генератор спортивных анонсов в After Effects - - -

- AE Anons Logo -

- - -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Rust](https://img.shields.io/badge/Rust-1.70%2B-orange.svg)](https://www.rust-lang.org/) -[![Status](https://img.shields.io/badge/status-production-green.svg)](https://git.tvstart.ru/lexx/AE_Anons) - -Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office. - -## Новое в версии v0.2.6 - -- Сортировка заданий по дате и времени события (самые ранние — первыми) -- Сортировка по варианту (оригинал → today → tomorrow) -- Последовательная отправка для гарантированного порядка в очереди -- Исправлена нормализация дефисов (2 и более дефиса подряд → 1) -- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS) -- Добавлена поддержка кеширования ассетов (cache: true/false) -- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются - -## Обзор - -AE Anons — это CLI-утилита и веб-сервер на Rust, которая выступает как **интеллектуальный генератор заданий** -для [Nexrender](https://github.com/inlife/nexrender) — опенсорсного оркестратора рендеринга -After Effects (лицензия MIT). - -### Режимы работы - -1. **Однократная обработка** (`--once` или без флагов): - - Подключение к NAS Synology - - Парсинг Excel данных - - Генерация и отправка заданий Nexrender - - Мониторинг завершения и выход - -2. **Веб-сервер** (`--web`): - - Запуск веб-интерфейса на порту `:3000` - - Управление заданиями через браузер - - Автообновление статуса каждую минуту - -## Особенности - -- **Интеграция с Synology**: Аутентификация и загрузка файлов с NAS Synology -- **Экспорт офисных таблиц**: Автоматическое преобразование файлов .osheet в формат Excel -- **Гибкий парсинг данных**: Динамический парсинг листов с обнаружением заголовков -- **Множественная генерация вариантов**: Создание "Сегодня", "Завтра" и датированных версий -- **Умное управление логотипами**: Автоматическое разрешение и масштабирование логотипов -- **Веб-интерфейс**: Удобное управление и мониторинг заданий -- **REST API**: Программный доступ к управлению заданиями -- **Профессиональное логирование**: Структурированный журнал с настройкой уровня - -## Предварительные требования - -- Rust 1.70 или выше -- Доступ к NAS Synology с пакетами File Station и Office -- Экземпляр _server_ и _worker_(не менее одного) Nexrender -- Шаблоны After Effects (формат Adobe After Effects 2024 .aepx) - -## Установка - -### 1. Клонирование репозитория - -```bash -git clone https://git.tvstart.ru/lexx/AE_Anons.git -cd ae_anons -``` - -### 2. Сборка проекта - -```bash -cargo build --release -``` - -### 3. Настройка окружения - -Скопируйте пример конфигурации и заполните своими данными: - -```bash -cp .env.example .env -``` - -Затем отредактируйте файл .env: - -См. раздел Конфигурация для подробной информации. - -## Использование - -### Однократная обработка - -```bash -# Запуск с настройками по умолчанию -./target/release/ae_anons - -# Явно указать однократный режим -./target/release/ae_anons --once - -# С отладочным логированием -RUST_LOG=debug ./target/release/ae_anons -``` - -### Веб-сервер - -```bash -# Запуск веб-интерфейса на порту по умолчанию (3000) -./target/release/ae_anons --web - -# Или с указанием другого порта через .env файл -# WEB_PORT=8080 ./target/release/ae_anons --web - -``` - -## Веб-интерфейс - -После запуска веб-сервера откройте браузер: - -- Главная страница: - -- API эндпоинты: - - `GET /api/jobs` - список всех заданий - - `POST /api/generate` - запуск генерации - - `POST /api/cleanup` - очистка завершённых - - `POST /api/jobs/stop-all` - остановка активных - - `GET /api/status` - статус сервера - -## Уровни логирования - -Управляйте детализацией вывода через переменную окружения `RUST_LOG`: - -- **error** - Только критические ошибки -- **warn** - Предупреждения и ошибки -- **info** - Общие операционные сообщения (по умолчанию) -- **debug** - Детальная информация о процессе обработки -- **trace** - Полное отладочное логирование с деталями API вызовов - -```bash -RUST_LOG=debug cargo run -``` - -## Конфигурация - -### Переменные окружения - -| Переменная | Обязательна | Описание | -|-------------------------|-------------|------------------------------------------------------| -| `NAS_FQDN` | Да | URL NAS Synology с протоколом и портом | -| `NAS_USER` | Да | Имя пользователя учетной записи Synology | -| `NAS_PASS` | Да | Пароль учетной записи Synology | -| `NAS_FILE` | Да | Полный путь к файлу .osheet на NAS | -| `NEXRENDER_API_URL` | Да | Конечная точка API сервера Nexrender | -| `OUTPUT_FOLDER` | Да | Сетевой путь для рендеренных видео | -| `TEMPLATE_DOUBLE_SRC` | Да | Путь к AEP-шаблону для двух команд | -| `TEMPLATE_SINGLE_SRC` | Да | Путь к AEP-шаблону для одной команды | -| `TEMPLATE_COMPOSITION` | Да | Имя композиции в проекте AE (например, `main`) | -| `TEMPLATE_OUTPUT_MODULE`| Да | Имя модуля вывода в AE (например, `h264`) | -| `TEMPLATE_OUTPUT_EXT` | Да | Расширение выходного файла (например, `mp4`) | -| `WEB_PORT` | Нет | Порт для веб-сервера (по умолчанию: 3000) | -| `RUST_LOG` | Нет | Уровень детализации логирования (по умолчанию: info) | - -### Пример файла `.env` - -```env -# Synology NAS -NAS_FQDN="https://your-nas.example.com" -NAS_USER="your_username" -NAS_PASS="your_password" -NAS_FILE="/Team Folder/path/to/file.osheet" - -# Logging -RUST_LOG="info" - -# Web Server -WEB_PORT="3000" - -# Nexrender -NEXRENDER_API_URL="http://nexrender-server:3050/api/v1/jobs" -OUTPUT_FOLDER="/path/to/output" - -# After Effects Templates -TEMPLATE_DOUBLE_SRC="file:///path/to/double_team_template.aepx" -TEMPLATE_SINGLE_SRC="file:///path/to/single_team_template.aepx" -TEMPLATE_COMPOSITION="main" -TEMPLATE_OUTPUT_MODULE="h264" -TEMPLATE_OUTPUT_EXT="mp4" -``` - -## Структура электронной таблицы - -Файл Excel должен содержать следующие листы со специфичными структурами: - -### Лист "SPORT" - -Связывает названия видов спорта с соответствующими видео пакетами. - -| SPORT | LINK | -|----------------|---------------------------------------------| -| Без оформления | \\server\share\path\to\null.mov | -| Футбол | \\server\share\path\to\football_pack.mov | -| Волейбол | \\server\share\path\to\volleyball_pack.mov | - -### Лист "TEAMS" - -Сопоставляет имена команд с их видами спорта и логотипами. Колонка SPORT заполняется из выпадающего списка (заполнена из листа SPORT). - -Если несколько команд в одном виде спорта имеют одинаковые названия, но разные логотипы, используется разделитель хеш # для уникальной идентификации. - -| TEAM | SPORT | LINK | -|-------------------|-----------|---------------------------------------------------| -| Галатасарай#Turki | Футбол | \\server\share\path\to\galatasaray_football.png | -| Галатасарай | Волейбол | \\server\share\path\to\galatasaray_volleyball.png | -| Галатасарай#UCL | Волейбол | \\server\share\path\to\galatasaray_ucl.png | -| Сомбатей##400 | Футбол | \\server\share\path\to\szombathely.png | - -### Лист "CHANELL" - -Сопоставляет названия каналов с их логотипами. Поля заполняются вручную без выпадающих списков. - -| CHANELL | LINK | -|---------|-----------------------------------------| -| КАНАЛ | \\server\share\path\to\channel_logo.png | -| TRIUMPH | \\server\share\path\to\triumph_logo.png | - -### Лист "Start" - -Основной источник данных для генерации анонсов. Большинство полей заполняются из выпадающих списков, основанных на других листах. - -Обязательные колонки: DATA, TIME, CHANELL, SPORT, LEAGUE, TEAM A, TEAM B - -**Примечания:** - -- Колонка LEAGUE обязательна для заполнения, но не имеет выпадающего списка. Проверка орфографии и опечаток отсутствует! -- Колонка DATA должна содержать дату в формате `ДД.ММ.ГГГГ` (например, `28.2.2026`). - -| STATE | TRIPPLE | DATA | TIME | SPORT | LEAGUE | CHANELL | TEAM A | TEAM B | -|-------|---------|-----------|-------|---------|---------------------------|---------|-----------------|------------------| -| TRUE | TRUE | 27.2.2026 | 23:40 | Футбол | Чемпионат Португалии | START | Спортинг#ll#550 | Эшторил#ll#550 | -| FALSE | TRUE | 28.2.2026 | 12:55 | Волейбол| Чемпионат Турции. Женщины | TRIUMPH | Бешикташ | Галатасарай#Turki| - -### Формат имен команд - -Имена команд поддерживают два типа разделителей хэш #: - -1. **Первый хеш**: Уникальный идентификатор для команд с одинаковыми названиями в одном виде спорта, или одинаковым названием в разных видах спорта. -Примеры: - -- `Динамо#Футбол`, `Динамо#Волейбол` - Одно название разный вид спорта -- `Спартак#ЖенскийФ`, `Спартак#МужскойФ` Одно название _разный_ вид спорта - -1. **Второй хеш**: Целевой размер в пикселях для масштабирования логотипа - -Формат: `DisplayName#UniqueID#TargetSize` - -Примеры: - -- `Галатасарай#Turki` - только уникальный идентификатор -- `Сомбатей##400` - только целевой размер (обратите внимание на двойной хеш) -- `Спортинг#ll#550` - оба идентификатор и целевой размер - -**Важно**: Обрабатывается **только второй хеш** для масштабирования логотипа. Если уникальный идентификатор не требуется, используйте двойной хеш ## перед целевым размером. - -## Выходные файлы - -### Рендеренные видео - -Рендерные видео сохраняются в `OUTPUT_FOLDER` по следующему шаблону именования: - -```shell -YYYYMMDD_Sport_League_TeamA_TeamB_Channel[_Variant].mp4 -``` - -Имена файлов транслитерируются в латиницу. -**Примеры:** - -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH.mp4` -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH_Today.mp4` -- `20260228_Volleyball_Championship-Turkey-Women_Beşiktaş_Galatasaray_TRIUMPH_Tomorrow.mp4` - -## Рабочий процесс - -1. **Аутентификация**: Подключение к NAS Synology с использованием предоставленных учетных данных -2. **Получение файла**: Поиск и экспорт указанного файла .osheet в Excel -3. **Парсинг данных**: Чтение всех листов и структурирование данных -4. **Разрешение ресурсов**: Сопоставление видов спорта, команд и каналов с визуальными элементами -5. **Генерация заданий**: Создание заданий Nexrender для каждой активной строки с вариантами -6. **Очистка**: Удаление завершенных/неудачных заданий из предыдущих запусков -7. **Отправка**: Отправка заданий на API Nexrender -8. **Мониторинг**: Слежение за прогрессом выполнения до завершения -9. **Завершение сеанса**: Закрытие сессии Synology - -## Требования к шаблонам After Effects - -Шаблоны должны быть предварительно настроены на узлах рендеринга со следующими именами слоёв. - -### Файлы шаблонов - -Настраиваются через переменные окружения: - -| Переменная | Назначение | Пример значения | -|-------------------------|-------------------------------------|------------------------------------| -| `TEMPLATE_DOUBLE_SRC` | Шаблон для матчей с двумя командами | `file:///path/to/double.aepx` | -| `TEMPLATE_SINGLE_SRC` | Шаблон для анонсов с одной командой | `file:///path/to/single.aepx` | -| `TEMPLATE_COMPOSITION` | Имя главной композиции | `main` | -| `TEMPLATE_OUTPUT_MODULE`| Имя модуля вывода | `h264` | -| `TEMPLATE_OUTPUT_EXT` | Расширение выходного файла | `mp4` | - -### Обязательные слои в шаблонах - -#### Текстовые слои - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|-----------|:-------------:|:-------------:|-------------------------------------------------| -| `DATA` | ✅ | ✅ | Отображение даты (автоматически подстраивается) | -| `TIME_H` | ✅ | ❌ | Часы (отдельный слой) | -| `TIME_M` | ✅ | ❌ | Минуты (отдельный слой) | -| `TIME` | ❌ | ✅ | Полное время (единый слой) | -| `LEAGUE` | ✅ | ✅ | Название лиги/турнира | -| `SPORT` | ✅ | ✅ | Категория спорта | -| `TEAMS` | ✅ | ✅ | Скомбинированные имена команд | - -#### Слои с изображениями - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|----------------|:-------------:|:-------------:|-----------------------------| -| `TEAM_A_LOGO` | ✅ | ✅ | Логотип команды A | -| `TEAM_B_LOGO` | ✅ | ❌ | Логотип команды B | -| `CHANELL` | ✅ | ✅ | Логотип канала (две буквы L)| - -#### Видео слои - -| Имя слоя | Шаблон DOUBLE | Шаблон SINGLE | Описание | -|-----------|:-------------:|:-------------:|----------------------------| -| `TOP` | ✅ | ✅ | Наложение видео оформления | - -### Особенности шаблонов - -#### Шаблон DOUBLE (две команды) - -Используется когда в строке Excel заполнены оба поля `TEAM A` и `TEAM B`. - -Обязательные слои: - -- Все текстовые слои с `TIME_H` и `TIME_M` вместо `TIME` -- Оба логотипа команд: `TEAM_A_LOGO` и `TEAM_B_LOGO` - -#### Шаблон SINGLE (одна команда) - -Используется когда заполнено только одно поле команды. - -Обязательные слои: - -- Текстовый слой `TIME` вместо `TIME_H` и `TIME_M` -- Только логотип `TEAM_A_LOGO` - -### Автоматические корректировки - -AE Anons автоматически применяет следующие настройки к слоям: - -#### Слой DATA - -В зависимости от отображаемого текста и типа шаблона: - -| Текст | Шаблон | Font Size | Anchor Point | -|------------|----------|-----------|--------------| -| "сегодня" | DOUBLE | 105 | [0, 5] | -| "завтра" | DOUBLE | 115 | [0, 25] | -| дата (<6) | DOUBLE | 120 | [0, 20] | - -#### Слои TIME_H, TIME_M, TIME - -Корректировка Anchor Point в зависимости от длины текста: - -| Условие | Anchor Point | -|-------------------|--------------| -| 1 символ | [60, 0] | -| 2 символа, <20 | [20, 0] | - -#### Слой LEAGUE - -Если длина текста превышает 16 символов, размер шрифта уменьшается до 73. - -#### Слой TEAMS - -Если суммарная длина имён команд ≥ 32 символов, размер шрифта уменьшается до 55. - -#### Логотипы (TEAM_A_LOGO, TEAM_B_LOGO) - -Если в имени команды указан целевой размер через `#` (например, `Спортинг##550`), применяется выражение масштабирования: - -```javascript -if (width > height) { - max_size = width; -} else { - max_size = height; -} -var real_size = 550 / max_size * 100; -[real_size, real_size] -``` - -### Пример структуры слоёв в After Effects - -Ниже представлен рекомендуемый порядок слоёв в композиции. Порядок важен для правильного наложения элементов. - -```text -📁 main (композиция) -├── 🎬 TOP (видео слой) # Видео-оверлей (обязательный) -├── 📝 TEAMS (текстовый слой) # Имена команд (обязательный) -├── 📝 LEAGUE (текстовый слой) # Название лиги (обязательный) -├── 📝 SPORT (текстовый слой) # Вид спорта (обязательный) -├── 📝 DATA (текстовый слой) # Дата (обязательный) -├── 📝 TIME (текстовый слой) # Время (только SINGLE) -├── 📝 TIME_H (текстовый слой) # Часы (только DOUBLE) -├── 📝 TIME_M (текстовый слой) # Минуты (только DOUBLE) -├── 🖼️ CHANELL (слой изображения) # Логотип канала (обязательный) -├── 🖼️ TEAM_A_LOGO (слой изображения) # Логотип команды A (обязательный) -├── 🖼️ TEAM_B_LOGO (слой изображения) # Логотип команды B (только DOUBLE) -└── 🖼️ BOTTOM (слой изображения) # Фоновый слой (опционально) -``` - -### Формат проекта и совместимость с After Effects - -**Поддерживаемые форматы проектов:** - -- `.aep` — стандартный бинарный формат After Effects (рекомендуется) -- `.aepx` — XML-формат проекта (поддерживается с AE CC 2015) - -**Совместимость версий After Effects:** - -| Версия After Effects | Поддерживаемые форматы | Особенности | -|----------------------|------------------------|-------------------------------------------------| -| CS 5.5 | `.aep` | Базовая поддержка | -| CC / CC 2014 | `.aep` | Полная поддержка | -| CC 2015 - CC 2019 | `.aep`, `.aepx` | Добавлена поддержка XML формата `.aepx` | -| CC 2020 - CC 2022 | `.aep`, `.aepx` | Рекомендуется использовать `.aep` | -| CC 2023 и новее | `.aep`, `.aepx` | **Требуется настройка Output Module** (см. ниже)| - -> **⚠️ Важно для After Effects 2023+:** -> -> В версиях After Effects 2023 и новее критически важно настроить **Output Module** в шаблоне проекта. Бинарный файл рендеринга (`aerender`) не будет обрабатывать композицию без явно указанного модуля вывода, даже если в проекте используется модуль по умолчанию. -> -> AE Anons автоматически решает эту проблему, используя параметры `TEMPLATE_OUTPUT_MODULE` и `TEMPLATE_OUTPUT_EXT` из `.env` файла. Убедитесь, что эти значения соответствуют настройкам вашего шаблона. - -### Настройки композиции - -| Параметр | Переменная окружения | Пример значения | Описание | -|---------------------------|--------------------------|-----------------|---------------------------------------------| -| Имя композиции | `TEMPLATE_COMPOSITION` | `main` | Имя главной композиции в проекте AE | -| Выходной модуль | `TEMPLATE_OUTPUT_MODULE` | `h264` | Имя модуля вывода в AE | -| Расширение выходного файла| `TEMPLATE_OUTPUT_EXT` | `mp4` | Расширение выходного файла | - -**Рекомендации по выбору формата:** - -1. Используйте `.aep` для максимальной совместимости со всеми версиями AE -2. Версия After Effects на worker-машине должна быть не ниже версии, в которой создан проект -3. Для AE 2023+ убедитесь, что в шаблоне настроен Output Module с именем, указанным в `TEMPLATE_OUTPUT_MODULE` - -**Источники:** - -- [Nexrender - Tested with After Effects versions](https://github.com/inlife/nexrender#tested-with) -- [Adobe Aerender documentation](https://helpx.adobe.com/after-effects/using/automated-rendering-network-rendering.html) - -## Устранение неисправностей - -### Частые проблемы - -#### Ошибка подключения к NAS - -- Проверьте, что `NAS_FQDN` включает протокол и порт (например, `https://nas.example.com:5001`) -- Проверьте сетевую связность с NAS -- Убедитесь, что сервисы File Station и Office включены - -#### Файл не найден - -- Убедитесь, что путь в `NAS_FILE` точно соответствует пути в Synology Drive -- Путь должен начинаться с `/Team Folder/` для рабочих папок -- Проверьте права доступа к файлу для учетной записи сервиса - -#### Ошибка отправки задания Nexrender - -- Подтвердите доступность сервера Nexrender -- Убедитесь, что `NEXRENDER_API_URL` правильный -- Проверьте существование файлов шаблонов на узлах рендеринга - -### Режим отладки - -Включите подробное логирование для детального анализа: - -```bash -RUST_LOG=debug ./target/release/ae_anons -``` - -Это выведет: - -- Детали API запросов/ответов -- Информацию о парсинге листов -- Детали создания заданий -- Процесс разрешения ресурсов - -## Возможные ограничения по производительности - -- **Большие таблицы**: Ограничение обработки до 10,000 строк на лист -- **Задержка сети**: Загрузка файлов с NAS может занимать время для больших файлов -- **Параллельные задания**: Nexrender управляет очередью заданий внутренне - -## Примечания по безопасности - -- Храните учетные данные только в файле `.env` (исключен из git) -- Используйте специальные аккаунты с минимально необходимыми правами -- Сессии Synology завершаются после выполнения -- HTTPS рекомендуется для подключений NAS в продакшене - -## Разработка - -### Структура кода - -```shell -ae_anons/ -├── .gitignore -├── .env.example -├── Cargo.toml -├── LICENSE -├── README.md -├── assets/ -│ └── logo.png -└── src/ - ├── main.rs # Точка входа, CLI - ├── config.rs # Конфигурация из .env - ├── nexrender.rs # Модели заданий Nexrender - ├── synology.rs # Клиент Synology API - ├── processor.rs # Логика обработки - ├── web.rs # Веб-сервер и API - └── static/ - ├── index.html # Веб-интерфейс - └── style.css # Стили -``` - -## Зависимости - -### Основные зависимости - -| Crate | Версия | Назначение | -|--------------------|---------|-----------------------------------------| -| reqwest | 0.12 | HTTP клиент для коммуникации с API | -| serde / serde_json | 1.0 | Сериализация и десериализация JSON | -| calamine | 0.26 | Парсинг файлов Excel (.xlsx, .xls) | -| chrono | 0.4 | Обработка дат и времени | -| tokio | 1.0 | Асинхронная среда выполнения | -| dotenv | 0.15 | Загрузка конфигурации из .env файла | -| log / env_logger | 0.4/0.11| Система логирования с уровнями | -| thiserror | 2.0 | Эргономичные определения типов ошибок | -| anyhow | 1.0 | Упрощённая обработка ошибок | -| regex | 1.11 | Регулярные выражения | -| urlencoding | 2.1 | Кодирование URL для API запросов | -| bytes | 1.9 | Работа с байтовыми данными | -| futures | 0.3 | Асинхронные примитивы | - -### Веб-сервер и CLI - -| Crate | Версия | Назначение | -|--------------------|--------|-----------------------------------------| -| axum | 0.8 | Веб-фреймворк для REST API | -| tower | 0.5 | Промежуточное ПО для веб-сервера | -| tower-http | 0.6 |HTTP утилиты (CORS, статика, трассировка)| -| askama | 0.15 | Шаблонизация (опционально) | -| clap | 4.5 | Парсинг аргументов командной строки | - -### Платформозависимые зависимости - -| Crate | Версия | Платформа | Назначение | -|--------------------|--------|-----------|----------------------------------| -| openssl | 0.10 | Linux | Криптография для HTTPS (vendored)| - -## 🙏 Благодарности - -### Nexrender - -Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)** -([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы -для автоматизации After Effects. - -### Font Awesome Free 6.4.0 - -Веб-интерфейс использует иконки и шрифты **Font Awesome Free**: - -- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) -- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL) - -© 2023 Fonticons, Inc. — - -## Лицензия - -**AE Anons** — [License](LICENSE) -**Nexrender** — [MIT License](https://github.com/inlife/nexrender/blob/master/LICENSE) - -Обе лицензии MIT обеспечивают полную свободу использования и модификации кода. - -**Разрешается:** - -- ✅ Использовать в коммерческих целях -- ✅ Изменять исходный код -- ✅ Распространять копии -- ✅ Использовать приватно - -**Требуется:** - -- Сохранять копирайт и текст лицензии - -## Поддержка - -Для вопросов и запросов функций обращайтесь: - -- -- [README.md](https://git.tvstart.ru/lexx/AE_Anons) - -## История изменений - -### v0.2.6 (текущая) - -- Сортировка заданий по дате и времени события (самые ранние — первыми) -- Сортировка по варианту (оригинал → today → tomorrow) -- Последовательная отправка для гарантированного порядка в очереди -- Исправлена нормализация дефисов (2 и более дефиса подряд → 1) -- Исправлен SINGLE шаблон (убран несуществующий слой TEAMS) -- Добавлена поддержка кеширования ассетов (cache: true/false) -- Логотипы команд не кешируются, логотипы каналов и видео-паки кешируются - -### v0.2.5 - -- Оптимизация памяти: очистка неиспользуемых логотипов -- Групповая отправка заданий по 3 для сохранения порядка -- Добавлен UID в логи отправки - -### v0.2.4 - -- Полностью автономный режим (встроенный Font Awesome) -- Добавлен favicon -- Все статические ресурсы встроены в бинарник - -### v0.2.3 - -- Веб-интерфейс с настраиваемым портом (WEB_PORT) -- Тёмная/светлая тема с автоопределением ОС -- Сортировка по всем колонкам в интерфейсе -- Фильтрация по имени файла/UID -- Остановка всех активных заданий -- Автообновление каждые 60 секунд -- Улучшенное debug-логирование -- Исправлена обработка UNC/SMB путей - -### v0.2.2 - -- Исправлено отображение имён выходных файлов в веб-интерфейсе -- Улучшена цветовая схема -- Добавлена сортировка по всем колонкам -- Вынесены стили в отдельный CSS файл - -### v0.2.0 - -- Добавлен веб-интерфейс для управления заданиями -- Реализован REST API -- Добавлена поддержка тёмной/светлой темы -- Автообновление статуса заданий -- Возможность остановки всех активных заданий - -### v0.1.1 - -- Оптимизирована работа с памятью -- Убрана функция создания .json - -### v0.1.0 - -- Первый выпуск -- Интеграция с Synology Office -- Базовая генерация заданий Nexrender -- Парсинг Excel с динамическим обнаружением листов - ---- - -Made with 🦀 Rust and ☕ coffee diff --git a/releases/ae_anons-v0.2.6-windows-x64/ae_anons.exe b/releases/ae_anons-v0.2.6-windows-x64/ae_anons.exe deleted file mode 100755 index 810d82e..0000000 Binary files a/releases/ae_anons-v0.2.6-windows-x64/ae_anons.exe and /dev/null differ diff --git a/src/config.rs b/src/config.rs index 1e9ed38..d3cf395 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,18 +11,31 @@ pub struct Config { // Nexrender pub nexrender_api_url: String, pub output_folder: String, - // Templates (all required) + // Templates pub template_double_src: String, pub template_single_src: String, pub template_composition: String, pub template_output_module: String, pub template_output_ext: String, - //web_serwer + // Web pub web_port: u16, + // SMB for approved videos (optional) + pub smb_upload_url: Option, + pub smb_username: Option, + pub smb_password: Option, + pub smb_workgroup: Option, + // SMB source folder (where videos are stored locally before upload) + pub smb_source_folder: String, + // Database + pub database_url: String, } impl Config { pub fn from_env() -> Result { + let output_folder = env::var("OUTPUT_FOLDER").context("OUTPUT_FOLDER not set")?; + let smb_source_folder = + env::var("SMB_SOURCE_FOLDER").unwrap_or_else(|_| output_folder.clone()); + Ok(Self { nas_fqdn: env::var("NAS_FQDN").context("NAS_FQDN not set")?, nas_user: env::var("NAS_USER").context("NAS_USER not set")?, @@ -30,7 +43,7 @@ impl Config { nas_file: env::var("NAS_FILE").context("NAS_FILE not set")?, nexrender_api_url: env::var("NEXRENDER_API_URL") .context("NEXRENDER_API_URL not set")?, - output_folder: env::var("OUTPUT_FOLDER").context("OUTPUT_FOLDER not set")?, + output_folder, template_double_src: env::var("TEMPLATE_DOUBLE_SRC") .context("TEMPLATE_DOUBLE_SRC not set")?, template_single_src: env::var("TEMPLATE_SINGLE_SRC") @@ -45,6 +58,13 @@ impl Config { .unwrap_or_else(|_| "3000".to_string()) .parse() .context("Invalid WEB_PORT")?, + smb_upload_url: env::var("SMB_UPLOAD_URL").ok(), + smb_username: env::var("SMB_USERNAME").ok(), + smb_password: env::var("SMB_PASSWORD").ok(), + smb_workgroup: env::var("SMB_WORKGROUP").ok(), + smb_source_folder, + database_url: env::var("DATABASE_URL") + .unwrap_or_else(|_| "sqlite:ae_anons.db".to_string()), }) } } diff --git a/src/main.rs b/src/main.rs index cb6faf7..049113f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod config; mod nexrender; mod processor; +mod storage; mod synology; mod web; @@ -13,40 +14,31 @@ use log::info; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { - /// Run web server instead of one-time processing #[arg(short, long)] web: bool, - - /// Run one-time processing (default if no flags) #[arg(short, long)] once: bool, } #[tokio::main] async fn main() -> Result<()> { - // Загружаем .env до инициализации логгера dotenv().ok(); - - // Инициализируем логгер env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let cli = Cli::parse(); let config = Config::from_env()?; if cli.web { - info!( - "Starting AE Anons web server v{}", - env!("CARGO_PKG_VERSION") - ); + info!("Starting AE Anons web server v{}", env!("CARGO_PKG_VERSION")); web::run_web_server(config).await?; } else { - info!( - "Starting AE Anons processor v{} (one-time mode)", - env!("CARGO_PKG_VERSION") - ); - let submitted = processor::process_spreadsheet(&config).await?; + info!("Starting AE Anons processor v{} (one-time mode)", env!("CARGO_PKG_VERSION")); + // For one-time mode we don't need SMB, so we create dummy db and channel + let (dummy_ws_tx, _) = tokio::sync::broadcast::channel(1); + let dummy_db = sqlx::SqlitePool::connect("sqlite::memory:").await?; + let submitted = processor::process_spreadsheet(&config, &dummy_db, dummy_ws_tx).await?; info!("Submitted {} jobs. Exiting.", submitted.len()); } Ok(()) -} +} \ No newline at end of file diff --git a/src/processor.rs b/src/processor.rs index 7b6c07b..ce526ea 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -1,15 +1,18 @@ use crate::config::Config; use crate::nexrender::{JobData, LogoRegistry}; use crate::synology::SynologyClient; +use crate::storage::{self, JobRecord}; +use crate::web::WsEvent; use anyhow::{anyhow, Result}; use calamine::{Data, Reader, Xlsx}; -use chrono::{Duration, NaiveDate}; -use log::{debug, error, info, log_enabled, Level}; +use chrono::{Duration, NaiveDate, Utc}; +use log::{debug, error, info}; use reqwest::Client; use serde_json::Value; use std::collections::{HashMap, HashSet}; use std::io::Cursor; use std::path::Path; +use tokio::sync::broadcast; #[derive(Debug, Clone)] pub struct SheetData { @@ -29,8 +32,7 @@ impl ExcelWorkbook { } } -/// Основная функция обработки: скачивает Excel, генерирует задания, отправляет в Nexrender -pub async fn process_spreadsheet(config: &Config) -> Result> { +pub async fn process_spreadsheet(config: &Config, db: &sqlx::SqlitePool, ws_tx: broadcast::Sender) -> Result> { let mut client = SynologyClient::new(&config.nas_fqdn); client.login(&config.nas_user, &config.nas_pass).await?; info!("Successfully authenticated with Synology NAS"); @@ -38,11 +40,10 @@ pub async fn process_spreadsheet(config: &Config) -> Result = data_matrix[0] .iter() .enumerate() @@ -192,7 +191,7 @@ fn parse_sheet_dynamic_optimized( None } }) - .take(10000) // Ограничение для безопасности + .take(10000) .collect(); Ok(SheetData { @@ -274,7 +273,6 @@ fn display_workbook_structure(workbook: &ExcelWorkbook) { } } -// Вспомогательная функция для поиска значения по части ключа fn get_cell_fuzzy(row: &HashMap, key_part: &str) -> Option { if let Some(val) = row.get(key_part) { if !val.is_empty() { @@ -292,73 +290,41 @@ fn get_cell_fuzzy(row: &HashMap, key_part: &str) -> Option, ) -> Result> { info!("Preparing Nexrender jobs..."); - // ========== ЗАГРУЖАЕМ SPORT PACKS ========== + // Load SPORT packs let sport_sheet = workbook .get_sheet("SPORT") .ok_or_else(|| anyhow!("Sheet 'SPORT' not found"))?; - let packs: HashMap = sport_sheet .rows .iter() .filter_map(|row| Some((row.get("SPORT")?.clone(), row.get("LINK")?.clone()))) .collect(); info!("Loaded {} sport packs", packs.len()); - debug!("Processed SPORT sheet ({} rows)", sport_sheet.rows.len()); - // ========== ЗАГРУЖАЕМ TEAM LOGOS ========== + // Load TEAM logos let teams_sheet = workbook .get_sheet("TEAMS") .ok_or_else(|| anyhow!("Sheet 'TEAMS' not found"))?; - - debug!("TEAMS headers: {:?}", teams_sheet.headers); - if log_enabled!(Level::Debug) { - for (i, row) in teams_sheet.rows.iter().take(3).enumerate() { - debug!("TEAMS row {}: {:?}", i, row); - } - } - let mut logos = LogoRegistry::with_capacity(teams_sheet.rows.len()); for row in &teams_sheet.rows { let team = get_cell_fuzzy(row, "TEAM"); let sport = get_cell_fuzzy(row, "SPORT"); let link = get_cell_fuzzy(row, "LINK"); - if let (Some(team), Some(sport), Some(link)) = (team, sport, link) { logos.insert(team.clone(), sport.clone(), link.clone()); } } - let total_teams = teams_sheet.rows.len(); - info!("Loaded {} team logos", total_teams); + info!("Loaded {} team logos", teams_sheet.rows.len()); - // Дебаг: выводим статистику по TEAMS - if log_enabled!(Level::Debug) { - let sports: HashSet<_> = teams_sheet - .rows - .iter() - .filter_map(|r| r.get("SPORT")) - .collect(); - debug!("Sports found in TEAMS sheet: {:?}", sports); - - for sport in sports.iter().take(5) { - let examples: Vec<_> = teams_sheet - .rows - .iter() - .filter(|r| r.get("SPORT") == Some(*sport)) - .take(3) - .filter_map(|r| r.get("TEAM")) - .collect(); - debug!(" TEAMS examples for '{}': {:?}", sport, examples); - } - } - - // ========== ЗАГРУЖАЕМ CHANNEL LOGOS ========== + // Load CHANNEL logos let channel_sheet = workbook .get_sheet("CHANELL") .ok_or_else(|| anyhow!("Sheet 'CHANELL' not found"))?; - let channels: HashMap = channel_sheet .rows .iter() @@ -366,95 +332,42 @@ async fn generate_and_submit_jobs( .collect(); info!("Loaded {} channel logos", channels.len()); - if log_enabled!(Level::Debug) { - debug!("Channels loaded: {:?}", channels.keys().collect::>()); - } - - // ========== ОБРАБАТЫВАЕМ START (только активные строки) ========== + // Process START sheet let start_sheet = workbook .get_sheet("Start") .ok_or_else(|| anyhow!("Sheet 'Start' not found"))?; - - let total_start_rows = start_sheet.rows.len(); - - // Сразу фильтруем только строки с STATE = "FALSE" let active_rows: Vec<(usize, &HashMap)> = start_sheet .rows .iter() .enumerate() .filter(|(_, row)| row.get("STATE").map(|s| s.as_str()) == Some("FALSE")) .collect(); + info!("Found {} active rows (STATE='FALSE') out of {} total", active_rows.len(), start_sheet.rows.len()); - info!( - "Found {} active rows (STATE='FALSE') out of {} total", - active_rows.len(), - total_start_rows - ); - - // Собираем использованные команды для очистки logos let mut used_teams: HashSet<(String, String)> = HashSet::new(); let mut jobs: Vec = Vec::with_capacity(active_rows.len() * 3); - // Для дебага - let mut missing_teams: HashSet = HashSet::new(); - let mut missing_sports: HashSet = HashSet::new(); - for (idx, row) in active_rows { let team_a = row.get("TEAM A").cloned().unwrap_or_default(); let team_b = row.get("TEAM B").cloned().unwrap_or_default(); let sport = row.get("SPORT").cloned().unwrap_or_default(); - - // Запоминаем использованные команды if !team_a.is_empty() { used_teams.insert((team_a.clone(), sport.clone())); } if !team_b.is_empty() { used_teams.insert((team_b.clone(), sport.clone())); } - - if log_enabled!(Level::Debug) { - let logo_a = logos.find(&team_a, &sport); - let logo_b = logos.find(&team_b, &sport); - - if !team_a.is_empty() && logo_a.is_none() { - missing_teams.insert(team_a.clone()); - missing_sports.insert(sport.clone()); - } - if !team_b.is_empty() && logo_b.is_none() { - missing_teams.insert(team_b.clone()); - missing_sports.insert(sport.clone()); - } - } - if let Some(job) = JobData::from_row(row, idx, &packs, &logos, &channels) { jobs.extend(job.create_variants()); } } - // Очищаем logos от неиспользуемых команд + // Cleanup unused logos let logos_before = logos.len(); logos.retain(|team, sport| used_teams.contains(&(team.to_string(), sport.to_string()))); - info!( - "Retained {} used team logos (cleaned up {} unused)", - logos.len(), - logos_before - logos.len() - ); + info!("Retained {} used team logos (cleaned up {} unused)", logos.len(), logos_before - logos.len()); - // Дебаг: выводим сводку по отсутствующим логотипам - if log_enabled!(Level::Debug) && !missing_teams.is_empty() { - debug!("=== MISSING LOGOS SUMMARY ==="); - debug!("Total unique teams missing logos: {}", missing_teams.len()); - debug!("Sports involved: {:?}", missing_sports); - debug!( - "Missing teams (first 20): {:?}", - missing_teams.iter().take(20).collect::>() - ); - debug!("=============================="); - } - - info!("Generated {} total jobs (including variants)", jobs.len()); - - // ========== СОРТИРОВКА ПО ДАТЕ И ВРЕМЕНИ ========== + // Sort jobs by date and time jobs.sort_by(|a, b| match a.sort_date.cmp(&b.sort_date) { std::cmp::Ordering::Equal => match a.sort_time.cmp(&b.sort_time) { std::cmp::Ordering::Equal => a.variant_order.cmp(&b.variant_order), @@ -462,44 +375,20 @@ async fn generate_and_submit_jobs( }, other => other, }); - info!("Jobs sorted by date and time (earliest first)"); - if log_enabled!(Level::Debug) { - for (i, job) in jobs.iter().take(10).enumerate() { - debug!( - " {}: {} {} - {} (variant: {})", - i + 1, - job.sort_date, - job.sort_time, - job.outfile_name, - job.variant_order - ); - } - } - if jobs.is_empty() { info!("No jobs with STATE='FALSE' found"); return Ok(Vec::new()); } - // ========== ОТПРАВКА ЗАДАНИЙ (ПОСЛЕДОВАТЕЛЬНО) ========== cleanup_finished_jobs(&config.nexrender_api_url).await?; - let http_client = Client::new(); let mut submitted_jobs: Vec<(String, String)> = Vec::with_capacity(jobs.len()); for job in jobs { let nexrender_job = job.to_nexrender_job(config); info!("Submitting job: {}", job.outfile_name); - - if log_enabled!(Level::Debug) { - debug!( - "Job details: sport='{}', league='{}', channel='{}', team_a='{}', team_b='{}'", - job.sport, job.league, job.channel, job.team_a, job.team_b - ); - } - let response = http_client .post(&config.nexrender_api_url) .json(&nexrender_job) @@ -511,6 +400,22 @@ async fn generate_and_submit_jobs( if let Some(uid) = result.get("uid").and_then(|u| u.as_str()) { info!("Job submitted: {} (UID: {})", job.outfile_name, uid); submitted_jobs.push((uid.to_string(), job.outfile_name.clone())); + + // Save to database + let record = JobRecord { + uid: uid.to_string(), + filename: job.outfile_name.clone(), + status: "queued".to_string(), + output_path: format!("{}/{}.{}", config.output_folder, job.outfile_name, config.template_output_ext), + approved: false, + created_at: Utc::now(), + updated_at: Utc::now(), + }; + if let Err(e) = storage::upsert_job(db, &record).await { + error!("Failed to save job to DB: {}", e); + } else { + let _ = ws_tx.send(WsEvent::JobUpdated(record)); + } } } else { let status = response.status(); @@ -519,18 +424,13 @@ async fn generate_and_submit_jobs( } } - info!( - "Successfully submitted {} jobs to Nexrender", - submitted_jobs.len() - ); - + info!("Successfully submitted {} jobs to Nexrender", submitted_jobs.len()); Ok(submitted_jobs) } pub async fn cleanup_finished_jobs(api_url: &str) -> Result<()> { let client = Client::new(); let response = client.get(api_url).send().await?; - if response.status().is_success() { let jobs: Vec = response.json().await?; for job in jobs { @@ -557,4 +457,4 @@ pub async fn fetch_all_jobs(api_url: &str) -> Result> { } else { Ok(Vec::new()) } -} +} \ No newline at end of file diff --git a/src/static/index.html b/src/static/index.html index bde6f70..28cd0bc 100644 --- a/src/static/index.html +++ b/src/static/index.html @@ -2,108 +2,56 @@ - + - AE Anons - - - - - - - + AE Anons — Управление заданиями + +
-
-
-
- - -
+
+

AE Anons

-
-
- -
- - - - -
- - Ready -
+
- -
- - -
-
- - -
-
- - Auto-refresh: 60s - 60s -
-
- - -
+
- - - - - + + + + + + - + - +
- Output File - - State - - Created - - Updated - - UID - UID Файл Статус ПревьюАпрувСоздан
-
- -

Loading jobs...

-
-
Загрузка заданий...
@@ -111,384 +59,243 @@
diff --git a/src/static/style.css b/src/static/style.css index 3b3e2e4..970ef36 100644 --- a/src/static/style.css +++ b/src/static/style.css @@ -1,555 +1,273 @@ -/* ======================================== - AE Anons - Nexrender Job Manager Styles - ======================================== */ - -/* CSS Variables - Light Theme (default) */ :root { - --bg-primary: #ffffff; - --bg-secondary: #fafafa; - --bg-tertiary: #f3f0f7; - --text-primary: #1a1a1a; - --text-secondary: #6b4f7c; - --border-color: #e0d4e8; - --accent-primary: #7c3aed; - --accent-secondary: #c2410c; - --accent-success: #10b981; - --accent-warning: #ea580c; - --accent-danger: #ef4444; - --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); - --header-bg: #7c3aed; - --header-text: #ffffff; + --bg-body: #f5f7fa; + --bg-container: #ffffff; + --text-primary: #1e2a3a; + --text-secondary: #4a5568; + --border-color: #e2e8f0; + --table-header-bg: #f8fafc; + --btn-primary-bg: #3b82f6; + --btn-primary-hover: #2563eb; + --btn-secondary-bg: #64748b; + --btn-secondary-hover: #475569; + --btn-danger-bg: #ef4444; + --btn-danger-hover: #dc2626; + --status-info: #3b82f6; + --status-success: #10b981; + --status-error: #ef4444; + --logo-brightness: 1; } -/* Dark Theme */ [data-theme="dark"] { - --bg-primary: #1a1025; - --bg-secondary: #251a30; - --bg-tertiary: #352545; - --text-primary: #f0e6ff; - --text-secondary: #c4a6d9; - --border-color: #4a3560; - --accent-primary: #a78bfa; - --accent-secondary: #f59e0b; - --accent-success: #34d399; - --accent-warning: #fbbf24; - --accent-danger: #f87171; - --header-bg: #a78bfa; - --header-text: #1a1025; + --bg-body: #0f172a; + --bg-container: #1e293b; + --text-primary: #f1f5f9; + --text-secondary: #cbd5e1; + --border-color: #334155; + --table-header-bg: #0f172a; + --btn-primary-bg: #2563eb; + --btn-primary-hover: #3b82f6; + --btn-secondary-bg: #475569; + --btn-secondary-hover: #64748b; + --btn-danger-bg: #dc2626; + --btn-danger-hover: #ef4444; + --logo-brightness: 0.9; } -/* Reset & Base */ * { - margin: 0; - padding: 0; box-sizing: border-box; - transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease; } body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - background: var(--bg-primary); + margin: 0; + padding: 20px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + background-color: var(--bg-body); color: var(--text-primary); - line-height: 1.6; - min-height: 100vh; + transition: background-color 0.3s, color 0.2s; } .container { - max-width: 100%; + max-width: 1400px; margin: 0 auto; - padding: 24px; + background-color: var(--bg-container); + border-radius: 16px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + overflow: hidden; } -/* Header */ .header { display: flex; - align-items: center; justify-content: space-between; - margin-bottom: 24px; - flex-wrap: wrap; - gap: 16px; + align-items: center; + padding: 1rem 2rem; + border-bottom: 1px solid var(--border-color); } -.header-left { +.logo-area { display: flex; align-items: center; - gap: 16px; -} - -.logo-container { - width: 48px; - height: 48px; - border-radius: 12px; - background: var(--accent-primary); - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - box-shadow: var(--shadow-md); + gap: 1rem; } .logo { - width: 100%; - height: 100%; - object-fit: cover; + width: 150px; + /* уменьшенный размер */ + height: auto; + filter: brightness(var(--logo-brightness)); } -.logo-placeholder { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - color: white; - font-size: 24px; - font-weight: bold; -} - -.header h1 { - font-size: 28px; +.logo-area h1 { + margin: 0; + font-size: 1.8rem; font-weight: 600; - color: var(--accent-primary); - letter-spacing: -0.5px; } -.header-controls { - display: flex; - align-items: center; - gap: 12px; -} - -.theme-toggle { - background: var(--bg-secondary); - border: 2px solid var(--accent-primary); - border-radius: 24px; - padding: 8px 16px; - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - color: var(--text-primary); - font-size: 14px; - box-shadow: var(--shadow-sm); -} - -.theme-toggle:hover { - box-shadow: var(--shadow-md); -} - -/* Action Bar */ -.action-bar { - background: var(--bg-secondary); - border-radius: 12px; - padding: 20px; - margin-bottom: 24px; - box-shadow: var(--shadow-md); - display: flex; - align-items: center; - gap: 12px; - flex-wrap: wrap; +.theme-toggle-btn { + background: none; border: 1px solid var(--border-color); + border-radius: 50%; + width: 40px; + height: 40px; + cursor: pointer; + font-size: 1.2rem; + color: var(--text-primary); + transition: all 0.2s; +} + +.theme-toggle-btn:hover { + background-color: var(--border-color); +} + +.controls { + padding: 1rem 2rem; + display: flex; + gap: 1rem; + align-items: center; + flex-wrap: wrap; + border-bottom: 1px solid var(--border-color); } -/* Buttons */ .btn { - padding: 10px 20px; + padding: 0.5rem 1rem; border: none; border-radius: 8px; - font-size: 14px; - font-weight: 500; + font-size: 0.9rem; cursor: pointer; + transition: background-color 0.2s, transform 0.1s; + font-weight: 500; display: inline-flex; align-items: center; - gap: 8px; - transition: all 0.2s ease; - background: var(--bg-tertiary); - color: var(--text-primary); - border: 1px solid var(--border-color); - position: relative; - overflow: hidden; -} - -.btn::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(255, 255, 255, 0.1); - opacity: 0; - transition: opacity 0.2s; -} - -.btn:hover::before { - opacity: 1; -} - -.btn:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-md); + gap: 0.5rem; } .btn i { - font-size: 14px; + font-size: 1rem; } -/* Generate - Purple */ .btn-primary { - background: linear-gradient(135deg, #7c3aed 0%, #8b5cf6 100%); + background-color: var(--btn-primary-bg); color: white; - border: none; - box-shadow: 0 2px 8px rgba(124, 58, 237, 0.3); -} - -[data-theme="dark"] .btn-primary { - background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%); - color: #1a1025; - box-shadow: 0 2px 8px rgba(139, 92, 246, 0.4); } .btn-primary:hover { - box-shadow: 0 4px 12px rgba(124, 58, 237, 0.4); + background-color: var(--btn-primary-hover); } -/* Stop - Orange */ -.btn-warning { - background: linear-gradient(135deg, #ea580c 0%, #f97316 100%); +.btn-secondary { + background-color: var(--btn-secondary-bg); color: white; - border: none; - box-shadow: 0 2px 8px rgba(234, 88, 12, 0.3); } -[data-theme="dark"] .btn-warning { - background: linear-gradient(135deg, #f97316 0%, #fb923c 100%); - color: #1a1025; - box-shadow: 0 2px 8px rgba(249, 115, 22, 0.4); +.btn-secondary:hover { + background-color: var(--btn-secondary-hover); } -.btn-warning:hover { - box-shadow: 0 4px 12px rgba(234, 88, 12, 0.4); -} - -/* Cleanup - Plum */ .btn-danger { - background: linear-gradient(135deg, #9d174d 0%, #be185d 100%); + background-color: var(--btn-danger-bg); color: white; - border: none; - box-shadow: 0 2px 8px rgba(157, 23, 77, 0.3); -} - -[data-theme="dark"] .btn-danger { - background: linear-gradient(135deg, #be185d 0%, #db2777 100%); - color: #f0e6ff; - box-shadow: 0 2px 8px rgba(190, 24, 93, 0.4); } .btn-danger:hover { - box-shadow: 0 4px 12px rgba(157, 23, 77, 0.4); + background-color: var(--btn-danger-hover); } -/* Refresh - Outline */ -.btn-outline { - background: transparent; - border: 2px solid #7c3aed; - color: #7c3aed; - box-shadow: none; +.btn:active { + transform: scale(0.97); } -[data-theme="dark"] .btn-outline { - border-color: #a78bfa; - color: #a78bfa; -} - -.btn-outline:hover { - background: #7c3aed; - color: white; - border-color: #7c3aed; -} - -[data-theme="dark"] .btn-outline:hover { - background: #a78bfa; - color: #1a1025; - border-color: #a78bfa; -} - -/* Status Message */ -.status-message { +.status-msg { margin-left: auto; - padding: 8px 16px; - background: var(--bg-tertiary); + font-size: 0.85rem; + padding: 0.25rem 0.75rem; border-radius: 20px; - font-size: 14px; - color: var(--text-secondary); - display: flex; - align-items: center; - gap: 8px; } -/* Stats Cards */ -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 16px; - margin-bottom: 24px; +.status-msg.info { + background-color: var(--status-info); + color: white; } -.stat-card { - background: var(--bg-secondary); - border-radius: 12px; - padding: 20px; - box-shadow: var(--shadow-sm); - border: 1px solid var(--border-color); +.status-msg.success { + background-color: var(--status-success); + color: white; } -.stat-card h3 { - font-size: 14px; - color: var(--text-secondary); - margin-bottom: 8px; - font-weight: 500; +.status-msg.error { + background-color: var(--status-error); + color: white; } -.stat-card .value { - font-size: 32px; - font-weight: 700; - color: var(--accent-primary); -} - -/* Filter Bar */ -.filter-bar { - margin-bottom: 20px; - display: flex; - gap: 12px; - align-items: center; -} - -.search-wrapper { - flex: 1; - position: relative; -} - -.search-wrapper i { - position: absolute; - left: 16px; - top: 50%; - transform: translateY(-50%); - color: var(--text-secondary); -} - -.search-input { - width: 100%; - padding: 12px 16px 12px 44px; - background: var(--bg-secondary); - border: 2px solid var(--border-color); - border-radius: 24px; - color: var(--text-primary); - font-size: 14px; -} - -.search-input:focus { - outline: none; - border-color: var(--accent-primary); - box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1); -} - -.auto-refresh-badge { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 16px; - background: var(--bg-tertiary); - border-radius: 20px; - font-size: 13px; - color: var(--text-secondary); - border: 1px solid var(--border-color); -} - -/* Table */ -.table-container { - background: var(--bg-secondary); - border-radius: 12px; - box-shadow: var(--shadow-lg); +.table-wrapper { overflow-x: auto; - border: 1px solid var(--border-color); + padding: 0 2rem 2rem 2rem; } table { width: 100%; border-collapse: collapse; - table-layout: auto; - min-width: 800px; + font-size: 0.9rem; +} + +th, +td { + padding: 0.75rem 1rem; + text-align: left; + border-bottom: 1px solid var(--border-color); } th { - text-align: left; - padding: 16px; - background: var(--header-bg); - color: var(--header-text); + background-color: var(--table-header-bg); font-weight: 600; - font-size: 13px; - text-transform: uppercase; - letter-spacing: 0.5px; - white-space: nowrap; cursor: pointer; user-select: none; - position: relative; -} - -th:hover { - opacity: 0.9; } th i { - margin-left: 8px; - font-size: 12px; - opacity: 0.7; -} - -th.sorted-asc i, -th.sorted-desc i { - opacity: 1; -} - -td { - padding: 12px 16px; - border-bottom: 1px solid var(--border-color); - color: var(--text-primary); - vertical-align: middle; -} - -tr:last-child td { - border-bottom: none; + margin-left: 0.5rem; + opacity: 0.5; } tr:hover { - background: var(--bg-tertiary); + background-color: rgba(0, 0, 0, 0.02); } -/* Job Filename */ -.job-filename { - font-family: 'Monaco', 'Menlo', 'Cascadia Code', 'Consolas', monospace; - font-size: 13px; - font-weight: 500; - line-height: 1.5; - word-break: break-word; - white-space: normal; - max-width: 100%; - color: var(--text-primary); +[data-theme="dark"] tr:hover { + background-color: rgba(255, 255, 255, 0.05); } -/* Badges */ -.badge { - padding: 4px 12px; - border-radius: 20px; - font-size: 12px; - font-weight: 600; - display: inline-block; - white-space: nowrap; -} - -.badge-finished { - background: rgba(16, 185, 129, 0.15); - color: var(--accent-success); - border: 1px solid rgba(16, 185, 129, 0.3); -} - -.badge-started, -.badge-processing { - background: rgba(124, 58, 237, 0.15); - color: var(--accent-primary); - border: 1px solid rgba(124, 58, 237, 0.3); -} - -.badge-queued { - background: rgba(234, 88, 12, 0.15); - color: var(--accent-warning); - border: 1px solid rgba(234, 88, 12, 0.3); -} - -.badge-error { - background: rgba(239, 68, 68, 0.15); - color: var(--accent-danger); - border: 1px solid rgba(239, 68, 68, 0.3); -} - -.badge-pending { - background: rgba(245, 158, 11, 0.15); - color: var(--accent-secondary); - border: 1px solid rgba(245, 158, 11, 0.3); -} - -/* Date & Time */ -.datetime { - font-family: 'Monaco', 'Menlo', monospace; - font-size: 12px; - color: var(--text-secondary); - white-space: nowrap; -} - -.uid { - font-family: 'Monaco', 'Menlo', monospace; - font-size: 11px; - color: var(--text-secondary); - white-space: nowrap; -} - -/* Empty State */ -.empty-state { +.loading { text-align: center; - padding: 60px 20px; + padding: 2rem; color: var(--text-secondary); } -.empty-state i { - font-size: 48px; - margin-bottom: 16px; +.approve-btn { + background: none; + border: none; + font-size: 1.2rem; + cursor: pointer; + padding: 0.25rem 0.5rem; + transition: transform 0.1s; +} + +.approve-btn:not(:disabled):hover { + transform: scale(1.1); +} + +.approve-btn:disabled { opacity: 0.5; - color: var(--accent-primary); + cursor: default; } -/* Loading Spinner */ -.spinner { - width: 20px; - height: 20px; - border: 3px solid var(--border-color); - border-top-color: var(--accent-primary); - border-radius: 50%; - animation: spin 1s linear infinite; - display: inline-block; +.preview a { + color: var(--text-primary); + text-decoration: none; + font-size: 1.2rem; } -@keyframes spin { - to { - transform: rotate(360deg); - } +.preview a:hover { + opacity: 0.7; } -/* Responsive */ @media (max-width: 768px) { - .container { - padding: 16px; + + .header, + .controls, + .table-wrapper { + padding-left: 1rem; + padding-right: 1rem; } - .header { - flex-direction: column; - align-items: flex-start; + .logo { + width: 100px; } - .header-left { - flex-wrap: wrap; + .logo-area h1 { + font-size: 1.2rem; } - .header h1 { - font-size: 20px; - } - - .action-bar { - flex-direction: column; - align-items: stretch; - } - - .status-message { - margin-left: 0; + th, + td { + padding: 0.5rem; } } \ No newline at end of file diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 0000000..1fe6ebd --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,132 @@ +use anyhow::Result; +use chrono::{DateTime, Utc}; +use serde::Serialize; +use sqlx::{Row, SqlitePool}; + +#[derive(Debug, Clone, Serialize)] +pub struct JobRecord { + pub uid: String, + pub filename: String, + pub status: String, + pub output_path: String, + pub approved: bool, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +pub async fn init_db(database_url: &str) -> Result { + match SqlitePool::connect(database_url).await { + Ok(pool) => { + // Создаём таблицы + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS jobs ( + uid TEXT PRIMARY KEY, + filename TEXT NOT NULL, + status TEXT NOT NULL, + output_path TEXT NOT NULL, + approved BOOLEAN NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL + ) + "#, + ) + .execute(&pool) + .await?; + Ok(pool) + } + Err(e) => { + log::warn!("Failed to open database file '{}': {}", database_url, e); + log::warn!("Falling back to in-memory database. Data will not be persisted!"); + let pool = SqlitePool::connect("sqlite::memory:").await?; + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS jobs ( + uid TEXT PRIMARY KEY, + filename TEXT NOT NULL, + status TEXT NOT NULL, + output_path TEXT NOT NULL, + approved BOOLEAN NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL + ) + "#, + ) + .execute(&pool) + .await?; + Ok(pool) + } + } +} + +pub async fn upsert_job(pool: &SqlitePool, record: &JobRecord) -> Result<()> { + sqlx::query( + r#" + INSERT INTO jobs (uid, filename, status, output_path, approved, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(uid) DO UPDATE SET + status = excluded.status, + output_path = excluded.output_path, + approved = excluded.approved, + updated_at = excluded.updated_at + "#, + ) + .bind(&record.uid) + .bind(&record.filename) + .bind(&record.status) + .bind(&record.output_path) + .bind(record.approved) + .bind(record.created_at) + .bind(record.updated_at) + .execute(pool) + .await?; + Ok(()) +} + +pub async fn get_all_jobs(pool: &SqlitePool) -> Result> { + let rows = sqlx::query("SELECT * FROM jobs ORDER BY created_at DESC") + .fetch_all(pool) + .await?; + let mut jobs = Vec::new(); + for row in rows { + jobs.push(JobRecord { + uid: row.get("uid"), + filename: row.get("filename"), + status: row.get("status"), + output_path: row.get("output_path"), + approved: row.get("approved"), + created_at: row.get("created_at"), + updated_at: row.get("updated_at"), + }); + } + Ok(jobs) +} + +pub async fn get_job(pool: &SqlitePool, uid: &str) -> Result> { + let row = sqlx::query("SELECT * FROM jobs WHERE uid = ?") + .bind(uid) + .fetch_optional(pool) + .await?; + if let Some(row) = row { + Ok(Some(JobRecord { + uid: row.get("uid"), + filename: row.get("filename"), + status: row.get("status"), + output_path: row.get("output_path"), + approved: row.get("approved"), + created_at: row.get("created_at"), + updated_at: row.get("updated_at"), + })) + } else { + Ok(None) + } +} + +pub async fn approve_job(pool: &SqlitePool, uid: &str) -> Result { + let result = sqlx::query("UPDATE jobs SET approved = 1, updated_at = ? WHERE uid = ?") + .bind(Utc::now()) + .bind(uid) + .execute(pool) + .await?; + Ok(result.rows_affected() > 0) +} diff --git a/src/web.rs b/src/web.rs index c02fe9c..a522226 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,32 +1,430 @@ use crate::config::Config; -use crate::processor::{cleanup_finished_jobs, fetch_all_jobs, process_spreadsheet}; +use crate::processor::{fetch_all_jobs, process_spreadsheet}; +use crate::storage::{self, JobRecord}; use axum::{ - extract::State, + extract::{Path, State, WebSocketUpgrade}, http::{header::CONTENT_TYPE, StatusCode}, - response::{Html, IntoResponse, Json}, + response::{Html, IntoResponse, Json, Response}, routing::{get, post}, Router, }; use serde::Serialize; +use serde_json::json; +use smb::{Client, ClientConfig, CreateOptions, FileAttributes, FileCreateArgs, UncPath, WriteAt}; +use sqlx::SqlitePool; use std::net::SocketAddr; +use std::str::FromStr; use std::sync::Arc; -use tokio::sync::Mutex; -use tower_http::services::ServeDir; +use tokio::sync::broadcast; +use tokio_util::io::ReaderStream; use tower_http::trace::TraceLayer; +use url::Url; #[derive(Clone)] pub struct AppState { pub config: Config, - pub last_generation: Arc>>>, + pub db: SqlitePool, + pub ws_tx: broadcast::Sender, + pub last_generation: Arc>>>, } -#[derive(Serialize)] -pub struct JobInfo { - pub uid: String, - pub outfile_name: String, - pub state: String, - pub created_at: Option, - pub updated_at: Option, +#[derive(Serialize, Clone)] +#[serde(tag = "type")] +pub enum WsEvent { + JobUpdated(JobRecord), + GenerationStarted, + GenerationFinished { count: usize }, + Error { message: String }, +} + +pub async fn run_web_server(config: Config) -> anyhow::Result<()> { + let web_port = config.web_port; + let db = storage::init_db(&config.database_url).await?; + let (ws_tx, _) = broadcast::channel(100); + + let state = AppState { + config, + db, + ws_tx, + last_generation: Arc::new(tokio::sync::Mutex::new(None)), + }; + + let state_clone_for_sync = state.clone(); + tokio::spawn(async move { + sync_jobs_status(state_clone_for_sync).await; + }); + + let app = Router::new() + .route("/", get(index_page)) + .route("/favicon.ico", get(favicon)) + .route("/static/style.css", get(style_css)) + .route("/static/fontawesome/all.min.css", get(fontawesome_css)) + .route("/assets/logo.png", get(logo_png)) + .route("/api/jobs", get(list_jobs)) + .route("/api/generate", post(generate_jobs)) + .route("/api/cleanup", post(cleanup_jobs)) + .route("/api/status", get(get_status)) + .route("/api/jobs/stop-all", post(stop_all_jobs)) + .route("/api/video/{uid}", get(serve_video)) + .route("/api/approve/{uid}", post(approve_job)) + .route("/ws", get(ws_handler)) + .layer(TraceLayer::new_for_http()) + .with_state(state); + + let addr: SocketAddr = format!("0.0.0.0:{}", web_port).parse()?; + log::info!("Web server listening on http://{}", addr); + + let listener = tokio::net::TcpListener::bind(addr).await?; + axum::serve(listener, app).await?; + Ok(()) +} + +async fn sync_jobs_status(state: AppState) { + let mut interval = tokio::time::interval(std::time::Duration::from_secs(5)); + loop { + interval.tick().await; + if let Ok(jobs_json) = fetch_all_jobs(&state.config.nexrender_api_url).await { + for job_json in jobs_json { + if let Some(job) = JobInfo::from_nexrender_json(&job_json) { + // Получаем текущую запись из БД + if let Ok(Some(mut record)) = storage::get_job(&state.db, &job.uid).await { + if record.status != job.state { + // Статус изменился – обновляем БД и шлём событие + record.status = job.state.clone(); + record.updated_at = chrono::Utc::now(); + if let Err(e) = storage::upsert_job(&state.db, &record).await { + log::error!("Failed to update job status in DB: {}", e); + } else { + let _ = state.ws_tx.send(WsEvent::JobUpdated(record)); + } + } + } + } + } + } + } +} + +async fn index_page() -> Html<&'static str> { + Html(include_str!("static/index.html")) +} + +async fn style_css() -> impl IntoResponse { + ( + [(CONTENT_TYPE, "text/css")], + include_str!("static/style.css"), + ) +} + +async fn fontawesome_css() -> impl IntoResponse { + ( + [(CONTENT_TYPE, "text/css")], + include_str!("static/fontawesome/all.min.css"), + ) +} + +async fn logo_png() -> impl IntoResponse { + ( + [(CONTENT_TYPE, "image/png")], + include_bytes!("../assets/logo.png").as_slice(), + ) +} + +async fn favicon() -> impl IntoResponse { + ( + [(CONTENT_TYPE, "image/x-icon")], + include_bytes!("static/favicon.ico").as_slice(), + ) +} + +async fn list_jobs(State(state): State) -> Result>, AppError> { + let jobs = storage::get_all_jobs(&state.db) + .await + .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; + Ok(Json(jobs)) +} + +async fn generate_jobs(State(state): State) -> Result { + let mut last_gen = state.last_generation.lock().await; + if let Some(last) = *last_gen { + let elapsed = chrono::Local::now().signed_duration_since(last); + if elapsed.num_seconds() < 5 { + return Err(AppError( + StatusCode::TOO_MANY_REQUESTS, + "Generation already in progress or too recent".to_string(), + )); + } + } + *last_gen = Some(chrono::Local::now()); + drop(last_gen); + + let state_clone = state.clone(); + tokio::spawn(async move { + let _ = state_clone.ws_tx.send(WsEvent::GenerationStarted); + match process_spreadsheet( + &state_clone.config, + &state_clone.db, + state_clone.ws_tx.clone(), + ) + .await + { + Ok(submitted) => { + log::info!("Generation completed, {} jobs submitted", submitted.len()); + let _ = state_clone.ws_tx.send(WsEvent::GenerationFinished { + count: submitted.len(), + }); + } + Err(e) => { + log::error!("Generation failed: {}", e); + let _ = state_clone.ws_tx.send(WsEvent::Error { + message: e.to_string(), + }); + } + } + *state_clone.last_generation.lock().await = None; + }); + + Ok((StatusCode::ACCEPTED, "Job generation started")) +} + +async fn cleanup_jobs(State(state): State) -> Result { + let client = reqwest::Client::new(); + let api_url = &state.config.nexrender_api_url; + let jobs = fetch_all_jobs(api_url) + .await + .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; + let mut removed = 0; + for job in jobs { + if let (Some(uid), Some(status)) = ( + job.get("uid").and_then(|u| u.as_str()), + job.get("state").and_then(|s| s.as_str()), + ) { + if status == "finished" || status == "error" { + let _ = client.delete(&format!("{}/{}", api_url, uid)).send().await; + // Also delete from local DB + let _ = sqlx::query("DELETE FROM jobs WHERE uid = ?") + .bind(uid) + .execute(&state.db) + .await; + removed += 1; + log::info!("Cleaned up job: {}", uid); + } + } + } + Ok((StatusCode::OK, format!("Cleaned up {} jobs", removed))) +} +async fn get_status(State(state): State) -> Result, AppError> { + let last_gen = *state.last_generation.lock().await; + let status = json!({ + "last_generation": last_gen.map(|dt| dt.to_rfc3339()), + "nexrender_api": state.config.nexrender_api_url, + }); + Ok(Json(status)) +} + +async fn stop_all_jobs(State(state): State) -> Result { + use reqwest::Client; + + let client = Client::new(); + let api_url = &state.config.nexrender_api_url; + + let jobs = fetch_all_jobs(api_url) + .await + .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; + + let mut stopped = 0; + for job in jobs { + let state_str = job.get("state").and_then(|s| s.as_str()).unwrap_or(""); + if state_str == "queued" || state_str == "started" || state_str == "processing" { + if let Some(uid) = job.get("uid").and_then(|u| u.as_str()) { + let _ = client.delete(&format!("{}/{}", api_url, uid)).send().await; + stopped += 1; + log::info!("Stopped job: {}", uid); + } + } + } + + log::info!("Stopped {} active jobs", stopped); + Ok((StatusCode::OK, format!("Stopped {} jobs", stopped))) +} + +async fn serve_video( + Path(uid): Path, + State(state): State, +) -> Result { + let record = storage::get_job(&state.db, &uid) + .await + .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? + .ok_or_else(|| AppError(StatusCode::NOT_FOUND, "Job not found".to_string()))?; + + let path = std::path::Path::new(&record.output_path); + if !path.exists() { + return Err(AppError( + StatusCode::NOT_FOUND, + "File not found".to_string(), + )); + } + + let mime = mime_guess::from_path(path).first_or_octet_stream(); + let file = tokio::fs::File::open(path) + .await + .map_err(|_| AppError(StatusCode::NOT_FOUND, "Cannot open file".to_string()))?; + let stream = ReaderStream::new(file); + let body = axum::body::Body::from_stream(stream); + Ok(Response::builder() + .header(CONTENT_TYPE, mime.as_ref()) + .body(body) + .unwrap()) +} + +async fn approve_job( + Path(uid): Path, + State(state): State, +) -> Result, AppError> { + let mut record = storage::get_job(&state.db, &uid) + .await + .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? + .ok_or_else(|| AppError(StatusCode::NOT_FOUND, "Job not found".to_string()))?; + + if record.approved { + return Ok(Json(json!({"status": "already_approved"}))); + } + + let smb_url = state.config.smb_upload_url.as_ref().ok_or_else(|| { + AppError( + StatusCode::BAD_REQUEST, + "SMB_UPLOAD_URL not configured".to_string(), + ) + })?; + let smb_user = state.config.smb_username.as_ref().ok_or_else(|| { + AppError( + StatusCode::BAD_REQUEST, + "SMB_USERNAME not configured".to_string(), + ) + })?; + let smb_pass = state.config.smb_password.as_ref().ok_or_else(|| { + AppError( + StatusCode::BAD_REQUEST, + "SMB_PASSWORD not configured".to_string(), + ) + })?; + let _smb_workgroup = state.config.smb_workgroup.clone().unwrap_or_default(); + + // *** ИСПРАВЛЕНИЕ ЗДЕСЬ *** + let src_path = std::path::Path::new(&state.config.smb_source_folder).join(&record.filename); + if !src_path.exists() { + return Err(AppError( + StatusCode::NOT_FOUND, + format!("Source file not found: {}", src_path.display()), + )); + } + + let url = Url::parse(smb_url) + .map_err(|e| AppError(StatusCode::BAD_REQUEST, format!("Invalid SMB URL: {}", e)))?; + let server = url + .host_str() + .ok_or_else(|| AppError(StatusCode::BAD_REQUEST, "No host in SMB URL".to_string()))?; + let share = url + .path() + .trim_start_matches('/') + .split('/') + .next() + .unwrap_or(""); + if share.is_empty() { + return Err(AppError( + StatusCode::BAD_REQUEST, + "No share in SMB URL".to_string(), + )); + } + + let client = Client::new(ClientConfig::default()); + let target_path = UncPath::from_str(&format!("\\\\{}\\{}", server, share)) + .map_err(|e| AppError(StatusCode::BAD_REQUEST, format!("Invalid UNC path: {}", e)))?; + client + .share_connect(&target_path, smb_user, smb_pass.clone()) + .await + .map_err(|e| { + AppError( + StatusCode::INTERNAL_SERVER_ERROR, + format!("SMB connect failed: {}", e), + ) + })?; + + let data = tokio::fs::read(&src_path).await.map_err(|e| { + AppError( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Read source failed: {}", e), + ) + })?; + + let remote_path = url.path().trim_start_matches('/'); + let remote_filename = if remote_path.is_empty() { + record.filename.clone() + } else { + format!("{}/{}", remote_path, record.filename) + }; + + let file_to_open = target_path.with_path(&remote_filename); + let file_open_args = + FileCreateArgs::make_overwrite(FileAttributes::default(), CreateOptions::default()); + let resource = client + .create_file(&file_to_open, &file_open_args) + .await + .map_err(|e| { + AppError( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Create file failed: {}", e), + ) + })?; + let remote_file = resource.unwrap_file(); + + remote_file.write_at(&data, 0).await.map_err(|e| { + AppError( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Write to SMB failed: {}", e), + ) + })?; + remote_file.close().await.map_err(|e| { + AppError( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Close file failed: {}", e), + ) + })?; + + storage::approve_job(&state.db, &uid) + .await + .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; + + record.approved = true; + let _ = state.ws_tx.send(WsEvent::JobUpdated(record)); + + Ok(Json(json!({"status": "approved"}))) +} + +async fn ws_handler(ws: WebSocketUpgrade, State(state): State) -> Response { + ws.on_upgrade(|socket| handle_socket(socket, state)) +} + +async fn handle_socket(mut socket: axum::extract::ws::WebSocket, state: AppState) { + let mut rx = state.ws_tx.subscribe(); + while let Ok(event) = rx.recv().await { + if let Ok(text) = serde_json::to_string(&event) { + if socket + .send(axum::extract::ws::Message::Text(text.into())) + .await + .is_err() + { + break; + } + } + } +} + +#[derive(Debug, Clone)] +struct JobInfo { + uid: String, + outfile_name: String, + state: String, } impl JobInfo { @@ -65,174 +463,14 @@ impl JobInfo { uid, outfile_name, state, - created_at: job - .get("createdAt") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()), - updated_at: job - .get("updatedAt") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()), }) } } -pub async fn run_web_server(config: Config) -> anyhow::Result<()> { - let web_port = config.web_port; - let state = AppState { - config, - last_generation: Arc::new(Mutex::new(None)), - }; - - let app = Router::new() - .route("/", get(index_page)) - .route("/favicon.ico", get(favicon)) - .route("/static/style.css", get(style_css)) - .route("/static/fontawesome/all.min.css", get(fontawesome_css)) - .route("/assets/logo.png", get(logo_png)) - .route("/api/jobs", get(list_jobs)) - .route("/api/generate", post(generate_jobs)) - .route("/api/cleanup", post(cleanup_jobs)) - .route("/api/status", get(get_status)) - .route("/api/jobs/stop-all", post(stop_all_jobs)) - .nest_service( - "/static/fontawesome/webfonts", - ServeDir::new("src/static/fontawesome/webfonts"), - ) - .layer(TraceLayer::new_for_http()) - .with_state(state); - - let addr: SocketAddr = format!("0.0.0.0:{}", web_port).parse()?; - log::info!("Web server listening on http://{}", addr); - - let listener = tokio::net::TcpListener::bind(addr).await?; - axum::serve(listener, app).await?; - Ok(()) -} - -async fn index_page() -> Html<&'static str> { - Html(include_str!("static/index.html")) -} - -async fn style_css() -> impl IntoResponse { - ( - [(CONTENT_TYPE, "text/css")], - include_str!("static/style.css"), - ) -} - -async fn fontawesome_css() -> impl IntoResponse { - ( - [(CONTENT_TYPE, "text/css")], - include_str!("static/fontawesome/all.min.css"), - ) -} - -async fn logo_png() -> impl IntoResponse { - ( - [(CONTENT_TYPE, "image/png")], - include_bytes!("../assets/logo.png").as_slice(), - ) -} - -async fn favicon() -> impl IntoResponse { - ( - [(CONTENT_TYPE, "image/x-icon")], - include_bytes!("static/favicon.ico").as_slice(), - ) -} - -async fn list_jobs(State(state): State) -> Result>, AppError> { - let jobs_json = fetch_all_jobs(&state.config.nexrender_api_url) - .await - .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; - - let jobs: Vec = jobs_json - .iter() - .filter_map(JobInfo::from_nexrender_json) - .collect(); - - Ok(Json(jobs)) -} - -async fn generate_jobs(State(state): State) -> Result { - let mut last_gen = state.last_generation.lock().await; - if let Some(last) = *last_gen { - let elapsed = chrono::Local::now().signed_duration_since(last); - if elapsed.num_seconds() < 5 { - return Err(AppError( - StatusCode::TOO_MANY_REQUESTS, - "Generation already in progress or too recent".to_string(), - )); - } - } - *last_gen = Some(chrono::Local::now()); - drop(last_gen); - - let config = state.config.clone(); - let last_gen_clone = state.last_generation.clone(); - - tokio::spawn(async move { - match process_spreadsheet(&config).await { - Ok(submitted) => { - log::info!("Generation completed, {} jobs submitted", submitted.len()); - } - Err(e) => { - log::error!("Generation failed: {}", e); - } - } - *last_gen_clone.lock().await = None; - }); - - Ok((StatusCode::ACCEPTED, "Job generation started")) -} - -async fn cleanup_jobs(State(state): State) -> Result { - cleanup_finished_jobs(&state.config.nexrender_api_url) - .await - .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; - Ok((StatusCode::OK, "Cleanup completed")) -} - -async fn get_status(State(state): State) -> Result, AppError> { - let last_gen = *state.last_generation.lock().await; - let status = serde_json::json!({ - "last_generation": last_gen.map(|dt| dt.to_rfc3339()), - "nexrender_api": state.config.nexrender_api_url, - }); - Ok(Json(status)) -} - -async fn stop_all_jobs(State(state): State) -> Result { - use reqwest::Client; - - let client = Client::new(); - let api_url = &state.config.nexrender_api_url; - - let jobs = fetch_all_jobs(api_url) - .await - .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; - - let mut stopped = 0; - for job in jobs { - let state = job.get("state").and_then(|s| s.as_str()).unwrap_or(""); - if state == "queued" || state == "started" || state == "processing" { - if let Some(uid) = job.get("uid").and_then(|u| u.as_str()) { - let _ = client.delete(&format!("{}/{}", api_url, uid)).send().await; - stopped += 1; - log::info!("Stopped job: {}", uid); - } - } - } - - log::info!("Stopped {} active jobs", stopped); - Ok((StatusCode::OK, format!("Stopped {} jobs", stopped))) -} - struct AppError(StatusCode, String); impl IntoResponse for AppError { - fn into_response(self) -> axum::response::Response { + fn into_response(self) -> Response { (self.0, self.1).into_response() } }