diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 00cf1ef..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 6990990..e34c6e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,30 @@ +# Rust build artifacts /target +/debug +/release + +# Test files *.mp4 +*.avi +*.mkv +*.mov + +# Package and deployment artifacts +/package/ +*.tar.gz +*.tar +*.zip + +# OS files +.DS_Store +Thumbs.db + +# Editor files +*.swp +*.swo +*~ +.vscode/ +.idea/ + +# Cache files +.cache/ diff --git a/livestream-archiver-clean.tar.gz b/livestream-archiver-clean.tar.gz deleted file mode 100644 index fe41348..0000000 Binary files a/livestream-archiver-clean.tar.gz and /dev/null differ diff --git a/package/Cargo.lock b/package/Cargo.lock deleted file mode 100644 index fde4950..0000000 --- a/package/Cargo.lock +++ /dev/null @@ -1,748 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytes" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" - -[[package]] -name = "cc" -version = "1.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets 0.52.6", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "crossbeam-channel" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "filetime" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.59.0", -] - -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "inotify" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" -dependencies = [ - "bitflags 1.3.2", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "kqueue" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - -[[package]] -name = "libc" -version = "0.2.169" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.6.0", - "libc", - "redox_syscall", -] - -[[package]] -name = "livestream_archiver" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "notify", - "tokio", -] - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "miniz_oxide" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.48.0", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "notify" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" -dependencies = [ - "bitflags 2.6.0", - "crossbeam-channel", - "filetime", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio 0.8.11", - "walkdir", - "windows-sys 0.48.0", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags 2.6.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "syn" -version = "2.0.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tokio" -version = "1.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio 1.0.3", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[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 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[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 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "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_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_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_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_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[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_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_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_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" diff --git a/package/Cargo.toml b/package/Cargo.toml deleted file mode 100644 index 9e22573..0000000 --- a/package/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "livestream_archiver" -version = "0.1.0" -edition = "2024" - -[dependencies] -tokio = { version = "1.36", features = ["full"] } -anyhow = "1.0" -notify = "6.1" -chrono = "0.4" - - -# We don't need regex or other conversion-related deps -# since we're just copying and renaming files diff --git a/package/DEPLOY.md b/package/DEPLOY.md deleted file mode 100644 index f9f3bcd..0000000 --- a/package/DEPLOY.md +++ /dev/null @@ -1,104 +0,0 @@ -# Deployment Instructions - -## Prerequisites - -Before deploying, ensure: - -1. **SSH Key Setup**: You have passwordless SSH access to the server: - ```bash - ssh -p 8443 rockvilleav@remote.rockvilletollandsda.church - ``` - -2. **Mac SSH Setup**: The server can SSH to your Mac on port 8443: - ```bash - # Test from the server: - ssh -p 8443 benjaminslingo@macbook-pro.slingoapps.dev - ``` - -3. **Directory on Mac**: Create the destination directory: - ```bash - mkdir -p ~/rtsda/livestreams - ``` - -## Deployment - -Simply run the deployment script: - -```bash -./deploy.sh -``` - -This will: -- Build the release binary -- Package all necessary files -- Copy to the server -- Install as a systemd service -- Start the service -- Show service status - -## Manual Deployment (if script fails) - -1. **Build locally**: - ```bash - cargo build --release - ``` - -2. **Copy files to server**: - ```bash - scp -P 8443 target/release/livestream_archiver rockvilleav@remote.rockvilletollandsda.church:/tmp/ - scp -P 8443 livestream-archiver.service rockvilleav@remote.rockvilletollandsda.church:/tmp/ - ``` - -3. **Install on server**: - ```bash - ssh -p 8443 rockvilleav@remote.rockvilletollandsda.church - - # Create directories - sudo mkdir -p /home/rockvilleav/livestream-archiver - sudo mkdir -p /home/rockvilleav/Sync/Livestreams - sudo mkdir -p /media/archive/jellyfin/livestreams - - # Move binary - sudo mv /tmp/livestream_archiver /home/rockvilleav/livestream-archiver/ - sudo chmod +x /home/rockvilleav/livestream-archiver/livestream_archiver - - # Install service - sudo mv /tmp/livestream-archiver.service /etc/systemd/system/ - sudo systemctl daemon-reload - sudo systemctl enable livestream-archiver - sudo systemctl start livestream-archiver - - # Set permissions - sudo chown -R rockvilleav:rockvilleav /home/rockvilleav/livestream-archiver - sudo chown -R rockvilleav:rockvilleav /home/rockvilleav/Sync/Livestreams - ``` - -## Management Commands - -```bash -# Check status -sudo systemctl status livestream-archiver - -# View logs -sudo journalctl -u livestream-archiver -f - -# Restart service -sudo systemctl restart livestream-archiver - -# Stop service -sudo systemctl stop livestream-archiver -``` - -## Testing - -1. **Place a test file** in `/home/rockvilleav/Sync/Livestreams/` (name it like `2024-07-21_10-30-00.mp4`) -2. **Check logs** to see if it detects and processes the file -3. **Verify sync** to your Mac at `~/rtsda/livestreams/` -4. **Check Jellyfin** output at `/media/archive/jellyfin/livestreams/` - -## Troubleshooting - -- **SSH connection issues**: Verify port 8443 is open and SSH keys are set up -- **Permission errors**: Ensure directories have correct ownership (`rockvilleav:rockvilleav`) -- **rsync failures**: Check network connectivity and SSH key authentication -- **Service won't start**: Check logs with `journalctl -u livestream-archiver` \ No newline at end of file diff --git a/package/README.md b/package/README.md deleted file mode 100644 index d7d42eb..0000000 --- a/package/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Livestream Archiver - -A Rust application that monitors a directory for livestream recordings, processes them, and syncs them to a PC. - -## Features - -- **File Detection**: Monitors `/home/rockvilleav/Sync/Livestreams` for new MP4 files -- **Readiness Check**: Waits for files to be completely written before processing -- **PC Sync**: Uses rsync to send files to your PC immediately after detection -- **Caching & Retry**: Caches failed syncs and retries them on subsequent runs -- **Processing**: Converts files to AV1 using QSV hardware acceleration -- **Organization**: Creates date-based directory structure in Jellyfin format -- **Cleanup**: Deletes original files after successful processing and sync - -## Configuration - -Set the `PC_SYNC_TARGET` environment variable to configure where files are synced: - -```bash -export PC_SYNC_TARGET="user@192.168.1.100:/path/to/destination/" -``` - -If not set, defaults to: `user@192.168.1.100:/path/to/destination/` - -## Usage - -```bash -cargo run -``` - -The application will: -1. Check for existing unprocessed files -2. Start monitoring for new files -3. For each detected file: - - Wait for it to be ready (stable size/modification time) - - Sync to PC using rsync - - Convert to AV1 format - - Create NFO metadata file - - Delete original file - -## Dependencies - -- `rsync` must be installed and accessible in PATH -- `ffmpeg` with QSV support for hardware acceleration -- SSH key authentication should be set up for passwordless rsync - -## Directory Structure - -Output files are organized as: -``` -/media/archive/jellyfin/livestreams/ -├── 2024/ -│ ├── 01-January/ -│ │ ├── Divine Worship Service - RTSDA | January 01 2024.mp4 -│ │ ├── Divine Worship Service - RTSDA | January 01 2024.nfo -│ │ └── ... -│ └── ... -└── ... -``` \ No newline at end of file diff --git a/package/livestream-archiver-clean.tar.gz b/package/livestream-archiver-clean.tar.gz deleted file mode 100644 index af62bd7..0000000 Binary files a/package/livestream-archiver-clean.tar.gz and /dev/null differ diff --git a/package/livestream-archiver.service b/package/livestream-archiver.service deleted file mode 100644 index 9552b0a..0000000 --- a/package/livestream-archiver.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=Livestream Archiver Service -After=network.target - -[Service] -Type=simple -User=rockvilleav -Group=rockvilleav -WorkingDirectory=/home/rockvilleav/livestream-archiver -ExecStart=/home/rockvilleav/livestream-archiver/target/release/livestream_archiver -Environment=PC_SYNC_TARGET=benjaminslingo@macbook-pro.slingoapps.dev:~/rtsda/livestreams/ -Restart=always -RestartSec=10 - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/package/src/main.rs b/package/src/main.rs deleted file mode 100644 index c299f77..0000000 --- a/package/src/main.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::path::PathBuf; -use anyhow::Result; -use notify::{Watcher, RecursiveMode, Event, EventKind}; -use tokio::sync::mpsc; -use std::collections::HashSet; -use std::sync::{Arc, Mutex}; - -mod services; -use services::livestream_archiver::LivestreamArchiver; - -#[tokio::main] -async fn main() -> Result<()> { - let watch_path = PathBuf::from("/home/rockvilleav/Sync/Livestreams"); - let output_path = PathBuf::from("/media/archive/jellyfin/livestreams"); - - // Ensure directories exist - if !watch_path.exists() { - std::fs::create_dir_all(&watch_path)?; - } - if !output_path.exists() { - std::fs::create_dir_all(&output_path)?; - } - - println!("Starting livestream archiver service..."); - println!("Watching directory: {}", watch_path.display()); - println!("Output directory: {}", output_path.display()); - - // Configure PC sync target (replace with your actual PC address and path) - let pc_sync_target = std::env::var("PC_SYNC_TARGET") - .unwrap_or_else(|_| "benjaminslingo@macbook-pro.slingoapps.dev:~/rtsda/livestreams/".to_string()); - - let archiver = Arc::new(Mutex::new( - LivestreamArchiver::with_pc_sync(output_path.clone(), pc_sync_target) - )); - let processed_files = Arc::new(Mutex::new(HashSet::new())); - - // Process existing files first - println!("Checking for existing files..."); - if let Ok(entries) = std::fs::read_dir(&watch_path) { - for entry in entries { - if let Ok(entry) = entry { - let path = entry.path(); - // Only process .mp4 files - if path.extension().and_then(|ext| ext.to_str()) == Some("mp4") { - // Extract date from filename to check if output exists - if let Some(filename) = path.file_name().and_then(|f| f.to_str()) { - let archiver_guard = archiver.lock().unwrap(); - if let Ok(date) = archiver_guard.extract_date_from_filename(filename).await { - // Check if either Divine Worship or Afternoon Program exists for this date - let year_dir = archiver_guard.get_output_path().join(date.format("%Y").to_string()); - let month_dir = year_dir.join(format!("{}-{}", - date.format("%m"), - date.format("%B") - )); - - let divine_worship_file = month_dir.join(format!( - "Divine Worship Service - RTSDA | {}.mp4", - date.format("%B %d %Y") - )); - let afternoon_program_file = month_dir.join(format!( - "Afternoon Program - RTSDA | {}.mp4", - date.format("%B %d %Y") - )); - - if !divine_worship_file.exists() && !afternoon_program_file.exists() { - println!("Found unprocessed file: {}", path.display()); - drop(archiver_guard); // Release lock before async operation - let mut archiver_mut = archiver.lock().unwrap(); - if let Err(e) = archiver_mut.process_file(path).await { - eprintln!("Error processing existing file: {}", e); - } - } else { - println!("Skipping already processed file: {}", path.display()); - } - } - } - } - } - } - } - - // Set up file watcher for new files - let (tx, mut rx) = mpsc::channel(100); - - let mut watcher = notify::recommended_watcher(move |res: Result| { - let tx = tx.clone(); - match res { - Ok(event) => { - println!("Received event: {:?}", event); - if let Err(e) = tx.blocking_send(event) { - eprintln!("Error sending event: {}", e); - } - } - Err(e) => eprintln!("Watch error: {}", e), - } - })?; - - watcher.watch(&watch_path, RecursiveMode::NonRecursive)?; - - while let Some(event) = rx.recv().await { - println!("Processing event: {:?}", event); - - match event.kind { - EventKind::Create(_) | EventKind::Modify(_) => { - for path in event.paths { - if let Ok(canonical_path) = std::fs::canonicalize(&path) { - let path_str = canonical_path.to_string_lossy().to_string(); - let processed = processed_files.lock().unwrap(); - - if !processed.contains(&path_str) { - println!("Processing file: {}", path_str); - drop(processed); // Release processed files lock - let mut archiver_mut = archiver.lock().unwrap(); - if let Err(e) = archiver_mut.process_file(path).await { - eprintln!("Error processing file: {}", e); - } else { - let mut processed = processed_files.lock().unwrap(); - processed.insert(path_str); - if processed.len() > 1000 { - processed.clear(); - } - } - } else { - println!("Skipping already processed file: {}", path_str); - } - } - } - }, - _ => println!("Ignoring event: {:?}", event), - } - } - - Ok(()) -} diff --git a/package/src/services/livestream_archiver.rs b/package/src/services/livestream_archiver.rs deleted file mode 100644 index 6459fba..0000000 --- a/package/src/services/livestream_archiver.rs +++ /dev/null @@ -1,313 +0,0 @@ -use std::path::PathBuf; -use anyhow::{Result, anyhow}; -use chrono::NaiveDateTime; -use tokio::process::Command; -use tokio::time::Duration; -use std::collections::VecDeque; - -pub struct LivestreamArchiver { - output_path: PathBuf, - pc_sync_target: Option, - sync_cache: VecDeque, -} - -impl LivestreamArchiver { - pub fn new(output_path: PathBuf) -> Self { - LivestreamArchiver { - output_path, - pc_sync_target: None, - sync_cache: VecDeque::new(), - } - } - - pub fn with_pc_sync(output_path: PathBuf, pc_sync_target: String) -> Self { - LivestreamArchiver { - output_path, - pc_sync_target: Some(pc_sync_target), - sync_cache: VecDeque::new(), - } - } - - pub fn get_output_path(&self) -> &PathBuf { - &self.output_path - } - - async fn sync_to_pc(&mut self, file_path: &PathBuf) -> Result<()> { - if let Some(target) = &self.pc_sync_target { - println!("Syncing {} to PC at {}", file_path.display(), target); - - let status = Command::new("rsync") - .arg("-avz") - .arg("--progress") - .arg("-e") - .arg("ssh -p 8443") - .arg(file_path) - .arg(target) - .status() - .await?; - - if status.success() { - println!("Successfully synced {} to PC", file_path.display()); - Ok(()) - } else { - println!("Failed to sync {} to PC, adding to cache", file_path.display()); - self.sync_cache.push_back(file_path.clone()); - Err(anyhow!("Rsync failed")) - } - } else { - println!("No PC sync target configured, skipping sync"); - Ok(()) - } - } - - async fn retry_cached_syncs(&mut self) -> Result<()> { - if let Some(target) = &self.pc_sync_target { - let mut successful_syncs = Vec::new(); - - for (index, file_path) in self.sync_cache.iter().enumerate() { - println!("Retrying sync for cached file: {}", file_path.display()); - - let status = Command::new("rsync") - .arg("-avz") - .arg("--progress") - .arg("-e") - .arg("ssh -p 8443") - .arg(file_path) - .arg(target) - .status() - .await?; - - if status.success() { - println!("Successfully synced cached file: {}", file_path.display()); - successful_syncs.push(index); - } else { - println!("Still failed to sync: {}", file_path.display()); - } - } - - // Remove successfully synced files from cache (in reverse order to maintain indices) - for &index in successful_syncs.iter().rev() { - self.sync_cache.remove(index); - } - } - Ok(()) - } - - async fn wait_for_file_ready(&self, path: &PathBuf) -> Result<()> { - println!("Waiting for file to be ready: {}", path.display()); - - // Initial delay - let OBS get started - tokio::time::sleep(Duration::from_secs(10)).await; - - let mut last_size = 0; - let mut stable_count = 0; - let mut last_modified = std::time::SystemTime::now(); - let required_stable_checks = 15; // Must be stable for 30 seconds - - // Check for up to 4 hours (14400 seconds / 2 second interval = 7200 iterations) - for i in 0..7200 { - match tokio::fs::metadata(path).await { - Ok(metadata) => { - let current_size = metadata.len(); - let current_modified = metadata.modified()?; - - println!("Check {}: Size = {} bytes, Last Modified: {:?}", i, current_size, current_modified); - - if current_size > 0 { - if current_size == last_size { - // Also check if file hasn't been modified recently - if current_modified == last_modified { - stable_count += 1; - println!("Size and modification time stable for {} checks", stable_count); - - if stable_count >= required_stable_checks { - println!("File appears complete - size and modification time stable for 30 seconds"); - // Extra 30 second buffer after stability to be sure - tokio::time::sleep(Duration::from_secs(30)).await; - return Ok(()); - } - } else { - println!("File still being modified"); - stable_count = 0; - } - } else { - println!("Size changed: {} -> {}", last_size, current_size); - stable_count = 0; - } - - last_size = current_size; - last_modified = current_modified; - } - }, - Err(e) => { - println!("Error checking file: {}", e); - return Err(anyhow!("Failed to check file metadata: {}", e)); - } - } - tokio::time::sleep(Duration::from_secs(2)).await; - } - - // If we reach here, it timed out after 4 hours - something is wrong - println!("Timeout after 4 hours - file is still being written?"); - Err(anyhow!("Timeout after 4 hours waiting for file to stabilize")) - } - - pub async fn extract_date_from_filename(&self, filename: &str) -> Result { - // Example filename: "2024-12-27_18-42-36.mp4" - let date_time_str = filename - .strip_suffix(".mp4") - .ok_or_else(|| anyhow!("Invalid filename format"))?; - - // Parse the full date and time - let date = NaiveDateTime::parse_from_str(date_time_str, "%Y-%m-%d_%H-%M-%S")?; - Ok(date) - } - - pub async fn process_file(&mut self, path: PathBuf) -> Result<()> { - // Only process .mp4 files - if path.extension().and_then(|ext| ext.to_str()) != Some("mp4") { - return Err(anyhow!("Ignoring non-MP4 file")); - } - - println!("Processing livestream recording: {}", path.display()); - - // Wait for file to be fully copied - self.wait_for_file_ready(&path).await?; - - // Try to retry any cached syncs first - if let Err(e) = self.retry_cached_syncs().await { - println!("Warning: Failed to retry cached syncs: {}", e); - } - - // Sync the file to PC immediately after detection and readiness check - if let Err(e) = self.sync_to_pc(&path).await { - println!("Warning: Failed to sync file to PC: {}", e); - } - - // Get the filename - let filename = path.file_name() - .ok_or_else(|| anyhow!("Invalid filename"))? - .to_str() - .ok_or_else(|| anyhow!("Invalid UTF-8 in filename"))?; - - // Extract date from filename - let date = self.extract_date_from_filename(filename).await?; - - // Create date-based directory structure - let year_dir = self.output_path.join(date.format("%Y").to_string()); - let month_dir = year_dir.join(format!("{}-{}", - date.format("%m"), // numeric month (12) - date.format("%B") // full month name (December) - )); - - // Create directories if they don't exist - tokio::fs::create_dir_all(&month_dir).await?; - - // Check for existing files - let divine_worship_file = month_dir.join(format!( - "Divine Worship Service - RTSDA | {}.mp4", - date.format("%B %d %Y") - )); - let afternoon_program_file = month_dir.join(format!( - "Afternoon Program - RTSDA | {}.mp4", - date.format("%B %d %Y") - )); - - // Determine which filename to use - let (base_filename, nfo_title, nfo_tag) = if !divine_worship_file.exists() { - ( - format!("Divine Worship Service - RTSDA | {}", date.format("%B %d %Y")), - format!("Divine Worship Service - RTSDA | {}", date.format("%B %-d %Y")), - "Divine Worship Service" - ) - } else if !afternoon_program_file.exists() { - ( - format!("Afternoon Program - RTSDA | {}", date.format("%B %d %Y")), - format!("Afternoon Program - RTSDA | {}", date.format("%B %-d %Y")), - "Afternoon Program" - ) - } else { - // Both exist, add suffix to Afternoon Program - let mut suffix = 1; - let mut test_file = month_dir.join(format!( - "Afternoon Program - RTSDA | {} ({}).mp4", - date.format("%B %d %Y"), - suffix - )); - while test_file.exists() { - suffix += 1; - test_file = month_dir.join(format!( - "Afternoon Program - RTSDA | {} ({}).mp4", - date.format("%B %d %Y"), - suffix - )); - } - ( - format!("Afternoon Program - RTSDA | {} ({})", date.format("%B %d %Y"), suffix), - format!("Afternoon Program - RTSDA | {} ({})", date.format("%B %-d %Y"), suffix), - "Afternoon Program" - ) - }; - - let output_file = month_dir.join(format!("{}.mp4", base_filename)); - - println!("Converting to AV1 and saving to: {}", output_file.display()); - - // Build ffmpeg command for AV1 conversion using QSV - let status = Command::new("ffmpeg") - .arg("-init_hw_device").arg("qsv=hw") - .arg("-filter_hw_device").arg("hw") - .arg("-hwaccel").arg("qsv") - .arg("-hwaccel_output_format").arg("qsv") - .arg("-i").arg(&path) - .arg("-c:v").arg("av1_qsv") - .arg("-preset").arg("4") - .arg("-b:v").arg("6M") - .arg("-maxrate").arg("12M") - .arg("-bufsize").arg("24M") - .arg("-c:a").arg("copy") - .arg("-n") // Never overwrite existing files - .arg(&output_file) - .status() - .await?; - - if !status.success() { - return Err(anyhow!("FFmpeg conversion failed")); - } - - // Create NFO file - println!("Creating NFO file..."); - let nfo_content = format!(r#" - - {} - LiveStreams - {} - {} - {} - {} - {} - {} -"#, - nfo_title, - date.format("%Y").to_string(), - date.format("%m%d").to_string(), - date.format("%Y-%m-%d"), - date.format("%Y"), - date.format("%m%d"), - nfo_tag - ); - - let nfo_path = output_file.with_extension("nfo"); - tokio::fs::write(nfo_path, nfo_content).await?; - - println!("Successfully converted {} to AV1 and created NFO", path.display()); - - // Delete original file after successful processing and sync - match tokio::fs::remove_file(&path).await { - Ok(_) => println!("Successfully deleted original file: {}", path.display()), - Err(e) => println!("Warning: Failed to delete original file {}: {}", path.display(), e), - } - - Ok(()) - } -} diff --git a/package/src/services/mod.rs b/package/src/services/mod.rs deleted file mode 100644 index ee0140f..0000000 --- a/package/src/services/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod livestream_archiver; diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index 60132f2..0000000 Binary files a/src/.DS_Store and /dev/null differ