commit 2b2639287395834ec43442926a592f6b4150342f Author: Ashhhleyyy Date: Thu Jun 15 19:32:03 2023 +0100 feat: initial commit diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a963ecc --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +/result +/.direnv +/out +*.nfo diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..86d4ade --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,789 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "litefin" +version = "0.1.0" +dependencies = [ + "clap", + "color-eyre", + "maud", + "serde", + "serde_json", + "url", + "walkdir", + "yaserde", + "yaserde_derive", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "maud" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0bab19cef8a7fe1c18a43e881793bfc9d4ea984befec3ae5bd0415abf3ecf00" +dependencies = [ + "itoa", + "maud_macros", +] + +[[package]] +name = "maud_macros" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be95d66c3024ffce639216058e5bae17a83ecaf266ffc6e4d060ad447c9eed2" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[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 = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +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 = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "xml-rs" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1690519550bfa95525229b9ca2350c63043a4857b3b0013811b2ccf4a2420b01" + +[[package]] +name = "yaserde" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf52af554a50b866aaad63d7eabd6fca298db3dfe49afd50b7ba5a33dfa0582" +dependencies = [ + "log", + "xml-rs", +] + +[[package]] +name = "yaserde_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab8bd5c76eebb8380b26833d30abddbdd885b00dd06178412e0d51d5bfc221f" +dependencies = [ + "heck", + "log", + "proc-macro2", + "quote", + "syn 1.0.109", + "xml-rs", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1a30883 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "litefin" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.3.0", features = ["derive"] } +color-eyre = "0.6.2" +maud = "0.25.0" +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" +url = "2.3.1" +walkdir = "2.3.3" +yaserde = "0.8.0" +yaserde_derive = "0.8.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..1287c3d --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# LiteFin + +Lightweight media player using Jellyfin as a base. + +## Setup + +Import your media library into Jellyfin, and ensure that the `Nfo` "Metadata saver" is enabled when importing; this makes Jellyfin write information about series into the source folder alongside the media files. + +To generate the static HTML pages, use `cargo run -- --base-url --rewrite-root /media/ --output ./out ` + +`` should be replaced with the browser-accessible URL of the ``. `--rewrite-root` is used to convert the image paths in the metadata files to the corresponding browser URL. + +Once the command has completed, you can copy the contents of the output directory to your webserver, to allow the library to easily be browsed. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d6ef021 --- /dev/null +++ b/flake.lock @@ -0,0 +1,165 @@ +{ + "nodes": { + "crane": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ], + "rust-overlay": "rust-overlay" + }, + "locked": { + "lastModified": 1680584903, + "narHash": "sha256-uraq+D3jcLzw/UVk0xMHcnfILfIMa0DLrtAEq2nNlxU=", + "owner": "ipetkov", + "repo": "crane", + "rev": "65d3f6a3970cd46bef5eedfd458300f72c56b3c5", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1678901627, + "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1681037374, + "narHash": "sha256-XL6X3VGbEFJZDUouv2xpKg2Aljzu/etPLv5e1FPt1q0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "033b9f258ca96a10e543d4442071f614dc3f8412", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1681064623, + "narHash": "sha256-UngFykv8KTrjxFeu4ZMvsOwFrxsa0A3ZPwyLhxb0Rrs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "da0b0bc6a5d699a8a9ffbf9e1b19e8642307062a", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay_2" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "crane", + "flake-utils" + ], + "nixpkgs": [ + "crane", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1680488274, + "narHash": "sha256-0vYMrZDdokVmPQQXtFpnqA2wEgCCUXf5a3dDuDVshn0=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "7ec2ff598a172c6e8584457167575b3a1a5d80d8", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "rust-overlay_2": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1681093076, + "narHash": "sha256-6uLZNeuP5jDDGlFkXgcoAxsJhTKy8yUTw25zdLHzdxE=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "45c2ed9dd1397526dad35fc867c43955d87f9f3f", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..da1d9aa --- /dev/null +++ b/flake.nix @@ -0,0 +1,90 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + crane.url = "github:ipetkov/crane"; + crane.inputs.nixpkgs.follows = "nixpkgs"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "flake-utils"; + }; + }; + }; + + outputs = { self, nixpkgs, crane, rust-overlay, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + }; + inherit (pkgs) lib; + + rust-toolchain = pkgs.rust-bin.stable.latest.default; + rust-dev-toolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" ]; + }; + + craneLib = (crane.mkLib pkgs).overrideToolchain rust-toolchain; + + cssFilter = path: _type: builtins.match ".*css$" path != null; + cssOrCargo = path: type: (cssFilter path type) || (craneLib.filterCargoSources path type); + src = lib.cleanSourceWith { + src = craneLib.path ./.; + filter = cssOrCargo; + }; + + buildInputs = [ + pkgs.bintools + ] ++ lib.optionals pkgs.stdenv.isDarwin [ + # Additional darwin specific inputs can be set here + pkgs.libiconv + ]; + + cargoArtifacts = craneLib.buildDepsOnly { + inherit src buildInputs; + }; + + # Build the actual crate itself, reusing the dependency + # artifacts from above. + litefin = craneLib.buildPackage { + inherit cargoArtifacts src buildInputs; + }; + in + { + checks = { + # Build the crate as part of `nix flake check` for convenience + inherit litefin; + + # Run clippy (and deny all warnings) on the crate source, + # again, resuing the dependency artifacts from above. + # + # Note that this is done as a separate derivation so that + # we can block the CI if there are issues here, but not + # prevent downstream consumers from building our crate by itself. + litefin-clippy = craneLib.cargoClippy { + inherit cargoArtifacts src buildInputs; + cargoClippyExtraArgs = "--all-targets -- --deny warnings"; + }; + + # Check formatting + litefin-fmt = craneLib.cargoFmt { + inherit src; + }; + }; + + packages.default = litefin; + + devShells.default = pkgs.mkShell { + inputsFrom = builtins.attrValues self.checks; + + # Extra inputs can be added here + nativeBuildInputs = with pkgs; [ + rust-dev-toolchain + cargo-watch + ]; + }; + }); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..86b3760 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,36 @@ +use std::path::PathBuf; + +use clap::Parser; +use color_eyre::Result; + +mod nfo; +mod templates; +mod util; +mod walker; + +#[derive(clap::Parser)] +#[command(version, about)] +pub struct Args { + /// Root media directory to walk + dir: PathBuf, + + /// Root directory that media files are contained in, used to rewrite paths in nfo files. + #[clap(short, long)] + rewrite_root: String, + + /// URL pointing to the root `dir` + #[clap(short, long)] + base_url: String, + + /// Path to write the generated HTML files to. + #[clap(short, long)] + output: PathBuf, +} + +fn main() -> Result<()> { + let args = Args::parse(); + + walker::walk_metadata(&args)?; + + Ok(()) +} diff --git a/src/nfo.rs b/src/nfo.rs new file mode 100644 index 0000000..79f086a --- /dev/null +++ b/src/nfo.rs @@ -0,0 +1,126 @@ +use yaserde_derive::YaDeserialize; + +#[derive(Clone)] +pub struct ItemMeta { + pub url: String, + pub ty: ContentType, + pub data: ItemData, + pub parent: Option, +} + +impl ItemMeta { + pub fn fanart_url(&self) -> Option<&str> { + if let Some(fanart) = &self.data.art.fanart { + Some(fanart) + } else if let Some(parent) = &self.parent { + if let Some(fanart) = &parent.art.fanart { + Some(fanart) + } else { + None + } + } else { + None + } + } +} + +#[derive(Clone, Debug)] +pub enum ContentType { + TvShow, + Season, + Episode, +} + +impl ContentType { + pub fn is_season(&self) -> bool { + match self { + ContentType::Season => true, + _ => false, + } + } + + pub fn is_episode(&self) -> bool { + match self { + ContentType::Episode => true, + _ => false, + } + } +} + +#[derive(Clone, Debug, Default, YaDeserialize)] +pub struct ItemData { + pub plot: Option, + pub outline: Option, + pub lockdata: bool, + #[yaserde(rename = "dateadded")] + pub date_added: String, + pub title: String, + #[yaserde(rename = "originaltitle")] + pub original_title: Option, + pub trailer: Vec, + pub rating: f64, + pub year: u32, + pub mpaa: Option, + #[yaserde(rename = "imdbid")] + pub imdb_id: Option, + #[yaserde(rename = "tmdbid")] + pub tmdb_id: Option, + #[yaserde(rename = "tvdbid")] + pub tvdb_id: String, + pub premiered: Option, + #[yaserde(rename = "releasedate")] + pub release_date: Option, + #[yaserde(rename = "enddate")] + pub end_date: Option, + pub runtime: u32, + #[yaserde(rename = "genre")] + pub genres: Vec, + pub studio: Option, + #[yaserde(rename = "tag")] + pub tags: Vec, + pub art: Art, + #[yaserde(rename = "actor")] + pub actors: Vec, + pub id: Option, + pub status: Option, + #[yaserde(rename = "seasonnumber")] + pub season_number: Option, + pub episode: Option, +} + +impl ItemData { + pub fn description(&self) -> &str { + if let Some(outline) = &self.outline { + outline + } else if let Some(plot) = &self.plot { + plot + } else { + "" + } + } +} + +#[derive(Clone, Debug, Default, YaDeserialize)] +#[yaserde(rename = "art")] +pub struct Art { + pub poster: Option, + pub fanart: Option, +} + +#[derive(Clone, Debug, Default, YaDeserialize)] +#[yaserde(rename = "actor")] +pub struct Actor { + pub name: String, + pub role: String, + #[yaserde(rename = "type")] + pub ty: String, + #[yaserde(rename = "sortorder")] + pub sort_order: i32, + pub thumb: Option, +} + +pub struct SubtitleTrack { + pub url: String, + pub label: String, + pub lang: String, +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..2bf9c42 --- /dev/null +++ b/src/style.css @@ -0,0 +1,118 @@ +/* AshCSS */ +:root { + --background: #13092b; + --background-transparent: #140a2bee; + --background-2: #090f2b; + --foreground: #ddd; + --foreground-bright: #fff; + --foreground-dim: #aaa; + --error: orange; + --accent: #f9027a; + --accent-dim: hsl(331, 50%, 49%); +} + +html, body { + font-family: 'Poppins', sans-serif; + margin: 0; + padding: 0; + background-color: var(--background); + box-sizing: border-box; +} + +* { + box-sizing: inherit; +} + +h1, h2, h3, h4, h5, h6 { + margin: 0; +} + +a { + color: white; +} + +.fadein { + transition: opacity 500ms ease; +} + +.poster-body { + min-width: 100%; + min-height: 100vh; + /* background-size: cover; + background-position: center; */ +} + +.poster-image { + width: 100%; + position: absolute; + top: 0; + left: 0; + max-height: 100vh; + object-fit: cover; +} + +.poster-details { + position: absolute; + width: 100%; + bottom: 0; + left: 0; + color: var(--foreground); + background: var(--background-transparent); + background: linear-gradient(0deg, var(--background-transparent) 50%, rgba(0,0,0,0) 100%); +} + +.poster-details-gradient { + width: 100%; + height: 256px; +} + +.poster-details-content { + display: flex; + flex-direction: column; + padding: 16px; + max-width: 100vw; + gap: 16px; +} + +.poster-details-content img { + /* display: inline-block; */ + height: 100%; + max-height: 24rem; +} + +.metadata { + display: flex; + flex-direction: row; + gap: 16px; + align-items: flex-end; +} + +.metadata-head { + max-width: 720px; +} + +.seasons { + display: flex; + flex-direction: row; + gap: 16px; + padding-left: 16px; + padding-right: 16px; + max-width: 100%; + overflow: auto; + white-space: nowrap; +} + +.season { + transform-origin: center; + transition: transform 300ms ease; + text-align: center; + margin-top: 1rem; +} + +.season:hover { + transform: translateY(-1rem) scale(1.05); +} + +.season img { + height: 12rem; +} diff --git a/src/templates.rs b/src/templates.rs new file mode 100644 index 0000000..007b2c9 --- /dev/null +++ b/src/templates.rs @@ -0,0 +1,201 @@ +use color_eyre::Result; + +use crate::{ + nfo::{ItemData, ItemMeta, SubtitleTrack}, + util::get_url, + Args, +}; + +fn base_template( + title: impl maud::Render, + body: impl maud::Render, + extra_head: Option, +) -> maud::Markup { + maud::html! { + (maud::DOCTYPE) + html { + head { + meta charset="utf-8"; + meta name="viewport" content="width=device-width, initial-scale=1.0"; + title { (title) } + link rel="stylesheet" href="https://cdn.ashhhleyyy.dev/file/ashhhleyyy-assets/css/addf63c63146a6d5.css"; + style { (include_str!("style.css")) } + @if let Some(extra_head) = extra_head { + (extra_head) + } + } + body { + (body) + } + } + } +} + +pub fn player( + title: &str, + video_url: &str, + poster: &str, + subtitles: &[SubtitleTrack], + args: &Args, +) -> Result { + Ok(base_template( + title, + maud::html! { + media-player + title=(title) + src=(video_url) + poster=(get_url(poster, args)?) + aspect-ratio="16/9" + style="height: 100vh" + crossorigin { + media-outlet { + media-poster alt=(title) {} + @for subtitle_track in subtitles { + track src=(get_url(&subtitle_track.url, args)?) + label=(subtitle_track.label) + srclang=(subtitle_track.lang) + kind="subtitles" + default; + } + } + media-community-skin {} + } + }, + Some(maud::html! { + link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vidstack/styles/defaults.min.css"; + link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vidstack/styles/community-skin/video.min.css"; + script type="module" src="https://cdn.jsdelivr.net/npm/vidstack/dist/cdn/dev.js" {} + }), + )) +} + +pub fn tv_show(details: &ItemMeta, args: &Args, children: &[ItemMeta]) -> Result { + let fanart_url = details.fanart_url().map(|s| get_url(s, args)).transpose()?; + let poster_url = details + .data + .art + .poster + .as_ref() + .map(|s| get_url(s, args)) + .transpose()?; + Ok(base_template( + &details.data.title, + maud::html! { + .poster-body { + @if let Some(fanart_url) = fanart_url { + (fade_image(&fanart_url, None, None, None, Some("poster-image"))) + // (fade_image("https://placehold.co/1920x1080", None, None, None, Some("poster-image"))) + } + .poster-details { + .poster-details-gradient {} + .poster-details-content { + .metadata { + @if let Some(poster_url) = poster_url { + (fade_image(&poster_url, None, None, None, None)) + // (fade_image("https://placehold.co/1364x2042", None, None, None, None)) + } + .metadata-head { + h1 { (details.data.title) } + p.description { (details.data.description()) } + } + } + .seasons { + @for child in children { + @if child.ty.is_season() { + @match season_card(&child.data, &child.url, args) { + Ok(s) => (s), + Err(_) => {}, + } + } @else if child.ty.is_episode() { + @match episode_card(&child.data, &child.url, args) { + Ok(s) => (s), + Err(_) => {}, + } + } + } + } + } + } + } + }, + None::, + )) +} + +fn season_card(info: &ItemData, url: &str, args: &Args) -> Result { + let poster_url = get_url(&info.art.poster.clone().unwrap(), args)?; + Ok(maud::html! { + .season { + a href=(url) { + (fade_image(&poster_url, None, None, None, None)) + // (fade_image("https://placehold.co/300x200", None, None, None, None)) + .h2 { (info.title) } + } + } + }) +} + +fn episode_card(info: &ItemData, url: &str, args: &Args) -> Result { + let poster_url = get_url(&info.art.poster.clone().unwrap(), args)?; + Ok(maud::html! { + .season { + a href=(url) { + (fade_image(&poster_url, None, None, None, None)) + // (fade_image("https://placehold.co/300x200", None, None, None, None)) + .h2 { (info.title) } + } + } + }) +} + +fn fade_image( + url: &str, + alt: Option<&str>, + width: Option, + height: Option, + class: Option<&str>, +) -> maud::Markup { + #[derive(serde::Serialize)] + struct ImageInfo<'a> { + url: &'a str, + alt: Option<&'a str>, + width: Option, + height: Option, + class: Option<&'a str>, + } + + let data = serde_json::to_string(&ImageInfo { + url, + alt, + width, + height, + class, + }) + .expect("failed to serialise"); + + maud::html! { + noscript { + img src=(url) loading="lazy" width=[width] height=[height] alt=[alt] class=[class]; + } + + script { + (maud::PreEscaped(format!(r#" + (function() {{ + const data = {data}; + const img = document.createElement('img'); + img.src = data.url; + if (data.alt) img.alt = data.alt; + if (data.width) img.width = data.width; + if (data.height) img.height = data.height; + if (data.class) img.className = data.class; + img.classList.add('fadein'); + img.style.opacity = 0; + img.onload = () => {{ + img.style.opacity = 1; + }}; + document.currentScript.parentElement.insertBefore(img, document.currentScript); + }})() + "#))) + } + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..3c79a7c --- /dev/null +++ b/src/util.rs @@ -0,0 +1,23 @@ +use color_eyre::Result; +use url::Url; + +use crate::Args; + +pub(crate) fn get_url(path: &str, args: &Args) -> Result { + let prefix = if args.rewrite_root.ends_with('/') { + args.rewrite_root.clone() + } else { + format!("{}/", args.rewrite_root) + }; + let path = match path.strip_prefix(&prefix) { + Some(path) => path, + None => path, + }; + let url = if args.base_url.ends_with('/') { + format!("{}{path}", args.base_url) + } else { + format!("{}/{path}", args.base_url) + }; + let url = Url::parse(&url)?; + Ok(url.to_string()) +} diff --git a/src/walker.rs b/src/walker.rs new file mode 100644 index 0000000..0f3c4a4 --- /dev/null +++ b/src/walker.rs @@ -0,0 +1,139 @@ +use std::{path::{Path, PathBuf}, fs::File, collections::HashMap}; + +use color_eyre::{Result, eyre::eyre}; + +use crate::{nfo::{ContentType, ItemMeta, ItemData}, Args, templates, util::get_url}; + +pub fn walk_metadata(args: &Args) -> Result<()> { + let mut path_cache = HashMap::::new(); + + let walk = walkdir::WalkDir::new(&args.dir) + .contents_first(true); + + for entry in walk { + let entry = entry?; + if !entry.file_type().is_dir() { continue; } + let path = entry.path(); + let read = std::fs::read_dir(path)?; + let mut entries = Vec::with_capacity(read.size_hint().0); + for e in read { + entries.push(e?.path()); + } + let content_type = determine_contents(path, &entries); + if let Some(content_type) = content_type { + let info = load_contents(path, content_type, args)?; + let output_dir = path.strip_prefix(&args.dir)?; + let output_dir = args.output.join(output_dir); + std::fs::create_dir_all(&output_dir)?; + let output_path = output_dir.join("index.html"); + match info.ty { + ContentType::TvShow => { + let children = find_tv_show_children(&path_cache, &entries); + let rendered = templates::tv_show(&info, args, &children)?; + std::fs::write(output_path, rendered.0)?; + }, + ContentType::Season => { + let mut children = find_season_children(&path_cache, &entries, &info.data, args)?; + + for child in &mut children { + let rendered = templates::player( + &child.data.title, + &child.url, + child.data.art.poster.as_ref().unwrap(), + &[], + args, + )?; + let filename = format!("Episode {}.html", child.data.episode.unwrap()); + std::fs::write(output_dir.join(&filename), rendered.0)?; + child.url = filename; + } + + let rendered = templates::tv_show(&info, args, &children)?; + std::fs::write(output_path, rendered.0)?; + }, + ContentType::Episode => unreachable!(), + } + println!("{path:?}: {:?}", info.ty); + path_cache.insert(path.to_owned(), info); + } else { + println!("{path:?}: no content type"); + } + } + + Ok(()) +} + +fn find_tv_show_children(path_cache: &HashMap, entries: &[PathBuf]) -> Vec { + let mut children = vec![]; + for entry in entries { + if let Some(info) = path_cache.get(entry) { + let mut info = info.clone(); + info.url = entry.file_name().unwrap().to_string_lossy().to_string(); + children.push(info); + } + } + children.sort_by_key(|c| c.data.season_number); + children +} + +fn find_season_children(_path_cache: &HashMap, entries: &[PathBuf], parent: &ItemData, args: &Args) -> Result> { + let mut children = vec![]; + for entry in entries { + if let (Some(stem), Some(ext)) = (entry.file_stem(), entry.extension()) { + if let (Some(stem), Some(ext)) = (stem.to_str(), ext.to_str()) { + if stem.contains("Episode") && ext == "nfo" { + let data = read_data(entry)?; + let video = entry.with_extension("mp4"); + let url = get_url(&video.to_string_lossy().replace("/stuff/ash/Series", "/media/Shows"), args)?; + children.push(ItemMeta { + url, + ty: ContentType::Episode, + data, + parent: Some(parent.clone()), + }); + } + } + } + } + children.sort_by_key(|c| c.data.episode); + Ok(children) +} + +fn read_data(path: &Path) -> Result { + let mut f = File::open(path)?; + let data: ItemData = yaserde::de::from_reader(&mut f).map_err(|e| eyre!("failed to parse directory meta: {e}"))?; + Ok(data) +} + +fn load_contents(path: &Path, content_type: ContentType, args: &Args) -> Result { + let meta_file = match content_type { + ContentType::TvShow => "tvshow.nfo", + ContentType::Season => "season.nfo", + ContentType::Episode => unreachable!(), + }; + let data = read_data(&path.join(meta_file))?; + let url = get_url(&path.to_string_lossy().to_owned(), args)?; + let parent = match content_type { + ContentType::TvShow => None, + ContentType::Season => Some(read_data(&path.parent().unwrap().join("tvshow.nfo"))?), + ContentType::Episode => unreachable!(), + }; + Ok(ItemMeta { + ty: content_type, + data, + url, + parent, + }) +} + +fn determine_contents(_path: &Path, entries: &[PathBuf]) -> Option { + for entry in entries { + if entry.ends_with("tvshow.nfo") { + return Some(ContentType::TvShow); + } + if entry.ends_with("season.nfo") { + return Some(ContentType::Season); + } + } + None +}