From bc6fdec360415ef8d298d5a7f10041cb3c947c03 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 5 Apr 2023 22:24:24 +0900 Subject: [PATCH 01/46] Create a transcoder crate --- shell.nix | 26 +- transcoder/.gitignore | 1 + transcoder/Cargo.lock | 1435 +++++++++++++++++++++++++++++++++++++++ transcoder/Cargo.toml | 7 + transcoder/rustfmt.toml | 1 + transcoder/src/main.rs | 12 + 6 files changed, 1472 insertions(+), 10 deletions(-) create mode 100644 transcoder/.gitignore create mode 100644 transcoder/Cargo.lock create mode 100644 transcoder/Cargo.toml create mode 100644 transcoder/rustfmt.toml create mode 100644 transcoder/src/main.rs diff --git a/shell.nix b/shell.nix index b77d93d3..59564d8b 100644 --- a/shell.nix +++ b/shell.nix @@ -1,4 +1,5 @@ {pkgs ? import {}}: let + pwd = ./.; venvDir = "./scanner/.venv"; pythonPkgs = ./scanner/requirements.txt; in @@ -13,18 +14,23 @@ in ]) python3 python3Packages.pip + cargo + rustfmt + rustc ]; + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; + shellHook = '' - # Install python modules - SOURCE_DATE_EPOCH=$(date +%s) - if [ ! -d "${venvDir}" ]; then - ${pkgs.python3}/bin/python3 -m venv ${venvDir} - source ${venvDir}/bin/activate - export PIP_DISABLE_PIP_VERSION_CHECK=1 - pip install -r ${pythonPkgs} >&2 - else - source ${venvDir}/bin/activate - fi + # Install python modules + SOURCE_DATE_EPOCH=$(date +%s) + if [ ! -d "${venvDir}" ]; then + ${pkgs.python3}/bin/python3 -m venv ${pwd}/${venvDir} + source ${venvDir}/bin/activate + export PIP_DISABLE_PIP_VERSION_CHECK=1 + pip install -r ${pythonPkgs} >&2 + else + source ${venvDir}/bin/activate + fi ''; } diff --git a/transcoder/.gitignore b/transcoder/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/transcoder/.gitignore @@ -0,0 +1 @@ +/target diff --git a/transcoder/Cargo.lock b/transcoder/Cargo.lock new file mode 100644 index 00000000..97e8d65b --- /dev/null +++ b/transcoder/Cargo.lock @@ -0,0 +1,1435 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "async-stream" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[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 = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "devise" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" +dependencies = [ + "devise_core", + "quote", +] + +[[package]] +name = "devise_core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" +dependencies = [ + "bitflags 2.0.2", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + +[[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 = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "figment" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generator" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "h2" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] + +[[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.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" + +[[package]] +name = "linux-raw-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin", + "tokio", + "tokio-util", + "version_check", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pear" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.13", +] + +[[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 = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", + "version_check", + "yansi", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "ref-cast" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "rocket" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +dependencies = [ + "async-stream", + "async-trait", + "atomic", + "binascii", + "bytes", + "either", + "figment", + "futures", + "indexmap", + "is-terminal", + "log", + "memchr", + "multer", + "num_cpus", + "parking_lot", + "pin-project-lite", + "rand", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "state", + "tempfile", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" +dependencies = [ + "devise", + "glob", + "indexmap", + "proc-macro2", + "quote", + "rocket_http", + "syn 2.0.13", + "unicode-xid", +] + +[[package]] +name = "rocket_http" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" +dependencies = [ + "cookie", + "either", + "futures", + "http", + "hyper", + "indexmap", + "log", + "memchr", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec", + "stable-pattern", + "state", + "time", + "tokio", + "uncased", +] + +[[package]] +name = "rustix" +version = "0.37.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +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 = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[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 = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.45.0", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "tokio-stream" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[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-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "transcoder" +version = "0.1.0" +dependencies = [ + "rocket", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "ubyte" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" +dependencies = [ + "serde", +] + +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[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 = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[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.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/transcoder/Cargo.toml b/transcoder/Cargo.toml new file mode 100644 index 00000000..44d25f7b --- /dev/null +++ b/transcoder/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "transcoder" +version = "0.1.0" +edition = "2021" + +[dependencies] +rocket = "=0.5.0-rc.3" diff --git a/transcoder/rustfmt.toml b/transcoder/rustfmt.toml new file mode 100644 index 00000000..218e2032 --- /dev/null +++ b/transcoder/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs new file mode 100644 index 00000000..b25b3c43 --- /dev/null +++ b/transcoder/src/main.rs @@ -0,0 +1,12 @@ +#[macro_use] +extern crate rocket; + +#[get("/")] +fn index() -> &'static str { + "Hello, world!" +} + +#[launch] +fn rocket() -> _ { + rocket::build().mount("/", routes![index]) +} From d106988fd7c0d0a3bde10c9cbac82fc72972985a Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 6 Apr 2023 01:46:25 +0900 Subject: [PATCH 02/46] Add transcoder's dockerfile --- .github/workflows/coding-style.yml | 16 ++++++++++++++++ .github/workflows/docker.yml | 3 +++ back/Dockerfile | 2 +- back/Dockerfile.dev | 2 +- docker-compose.dev.yml | 11 +++++++++++ docker-compose.prod.yml | 6 ++++++ docker-compose.yml | 6 ++++++ front/Dockerfile.dev | 4 ++-- shell.nix | 1 + transcoder/Dockerfile | 20 ++++++++++++++++++++ transcoder/Dockerfile.dev | 14 ++++++++++++++ 11 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 transcoder/Dockerfile create mode 100644 transcoder/Dockerfile.dev diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index e27da2f0..cc6bc419 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -41,6 +41,7 @@ jobs: - name: Lint run: yarn lint + scanner: name: "Lint scanner" runs-on: ubuntu-latest @@ -54,3 +55,18 @@ jobs: run: | pip install black-with-tabs black . --check + + transcoder: + name: "Lint transcoder" + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./transcoder + steps: + - uses: actions/checkout@v1 + + - uses: dtolnay/rust-toolchain@stable + + - name: Run cargo fmt + run: | + cargo fmt --check diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3da3af3b..32cbe682 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -24,6 +24,9 @@ jobs: - context: ./scanner label: scanner image: zoriya/kyoo_scanner + - context: ./transcoder + label: transcoder + image: zoriya/kyoo_transcoder name: Build ${{matrix.label}} steps: - uses: actions/checkout@v2 diff --git a/back/Dockerfile b/back/Dockerfile index 1a3818d2..9c88a9b3 100644 --- a/back/Dockerfile +++ b/back/Dockerfile @@ -29,6 +29,6 @@ COPY --from=transcoder /transcoder/libtranscoder.so /app WORKDIR /kyoo EXPOSE 5000 -HEALTHCHECK CMD curl --fail http://localhost:5000/health || exit +HEALTHCHECK --interval=5s CMD curl --fail http://localhost:5000/health || exit CMD /app/Kyoo.Host diff --git a/back/Dockerfile.dev b/back/Dockerfile.dev index 373dfa5b..a0aac4ec 100644 --- a/back/Dockerfile.dev +++ b/back/Dockerfile.dev @@ -25,6 +25,6 @@ COPY --from=transcoder /transcoder/libtranscoder.so /app/out/bin/Kyoo.Host/Debug WORKDIR /kyoo EXPOSE 5000 ENV DOTNET_USE_POLLING_FILE_WATCHER 1 -HEALTHCHECK CMD curl --fail http://localhost:5000/health || exit +HEALTHCHECK --interval=5s CMD curl --fail http://localhost:5000/health || exit CMD dotnet watch run --no-restore --project /app/src/Kyoo.Host diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 215bef95..80777bb7 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -50,6 +50,17 @@ services: volumes: - ${LIBRARY_ROOT}:/video + transcoder: + build: + context: ./transcoder + dockerfile: Dockerfile.dev + ports: + - "7666:7666" + restart: on-failure + volumes: + - ./transcoder:/app + - ${LIBRARY_ROOT}:/video + ingress: image: nginx restart: on-failure diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 861448f9..0bd3589e 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -34,6 +34,12 @@ services: volumes: - ${LIBRARY_ROOT}:/video + transcoder: + image: zoriya/kyoo_transcoder:edge + restart: on-failure + volumes: + - ${LIBRARY_ROOT}:/video + ingress: image: nginx restart: on-failure diff --git a/docker-compose.yml b/docker-compose.yml index 3402bfca..87998ea3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,12 @@ services: volumes: - ${LIBRARY_ROOT}:/video + transcoder: + build: ./transcoder + restart: on-failure + volumes: + - ${LIBRARY_ROOT}:/video + ingress: image: nginx restart: on-failure diff --git a/front/Dockerfile.dev b/front/Dockerfile.dev index b1e58e67..6c22cbbd 100644 --- a/front/Dockerfile.dev +++ b/front/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM node:16-alpine AS builder +FROM node:16-alpine RUN apk add git bash WORKDIR /app COPY .yarn ./.yarn @@ -14,4 +14,4 @@ RUN yarn --immutable ENV NEXT_TELEMETRY_DISABLED 1 EXPOSE 3000 EXPOSE 19000 -CMD ["yarn", "dev"] +CMD yarn dev diff --git a/shell.nix b/shell.nix index 59564d8b..e2433ae0 100644 --- a/shell.nix +++ b/shell.nix @@ -15,6 +15,7 @@ in python3 python3Packages.pip cargo + cargo-watch rustfmt rustc ]; diff --git a/transcoder/Dockerfile b/transcoder/Dockerfile new file mode 100644 index 00000000..231c92d0 --- /dev/null +++ b/transcoder/Dockerfile @@ -0,0 +1,20 @@ +FROM rust as builder +WORKDIR /app + +# FIX: see https://github.com/rust-lang/cargo/issues/2644 +RUN mkdir src/ && touch src/lib.rs +COPY Cargo.toml Cargo.lock ./ +RUN cargo build +RUN rm src/lib.rs + +COPY src src +RUN cargo install --path . + +FROM debian:bullseye-slim +#RUN apt-get update && apt-get install -y extra-runtime-dependencies && rm -rf /var/lib/apt/lists/* +COPY --from=builder /usr/local/cargo/bin/transcoder ./transcoder + +ENV ROCKET_ADDRESS=0.0.0.0 +ENV ROCKET_PORT=7666 +EXPOSE 7666 +CMD ./transcoder diff --git a/transcoder/Dockerfile.dev b/transcoder/Dockerfile.dev new file mode 100644 index 00000000..4f677734 --- /dev/null +++ b/transcoder/Dockerfile.dev @@ -0,0 +1,14 @@ +FROM rust +RUN cargo install cargo-watch +WORKDIR /app + +# FIX: see https://github.com/rust-lang/cargo/issues/2644 +RUN mkdir src/ && touch src/lib.rs +COPY Cargo.toml Cargo.lock ./ +RUN cargo build +RUN rm src/lib.rs + +ENV ROCKET_ADDRESS=0.0.0.0 +ENV ROCKET_PORT=7666 +EXPOSE 7666 +CMD cargo watch -x run From 5543bc4c9d5af6cb89148ca95566a834e7c20cfb Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 6 Apr 2023 16:24:37 +0900 Subject: [PATCH 03/46] Migrate to actix --- shell.nix | 3 +- transcoder/.dockerignore | 1 + transcoder/Cargo.lock | 1230 +++++++++++++++---------------------- transcoder/Cargo.toml | 3 +- transcoder/Dockerfile | 2 - transcoder/Dockerfile.dev | 2 - transcoder/src/main.rs | 24 +- transcoder/src/paths.rs | 4 + 8 files changed, 535 insertions(+), 734 deletions(-) create mode 100644 transcoder/.dockerignore create mode 100644 transcoder/src/paths.rs diff --git a/shell.nix b/shell.nix index e2433ae0..cc9579c7 100644 --- a/shell.nix +++ b/shell.nix @@ -1,5 +1,4 @@ {pkgs ? import {}}: let - pwd = ./.; venvDir = "./scanner/.venv"; pythonPkgs = ./scanner/requirements.txt; in @@ -26,7 +25,7 @@ in # Install python modules SOURCE_DATE_EPOCH=$(date +%s) if [ ! -d "${venvDir}" ]; then - ${pkgs.python3}/bin/python3 -m venv ${pwd}/${venvDir} + ${pkgs.python3}/bin/python3 -m venv ${toString ./.}/${venvDir} source ${venvDir}/bin/activate export PIP_DISABLE_PIP_VERSION_CHECK=1 pip install -r ${pythonPkgs} >&2 diff --git a/transcoder/.dockerignore b/transcoder/.dockerignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/transcoder/.dockerignore @@ -0,0 +1 @@ +target/ diff --git a/transcoder/Cargo.lock b/transcoder/Cargo.lock index 97e8d65b..e2525dd1 100644 --- a/transcoder/Cargo.lock +++ b/transcoder/Cargo.lock @@ -3,47 +3,268 @@ version = 3 [[package]] -name = "async-stream" -version = "0.3.4" +name = "actix-codec" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" +checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" dependencies = [ - "async-stream-impl", + "bitflags", + "bytes", "futures-core", + "futures-sink", + "log", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-files" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d832782fac6ca7369a70c9ee9a20554623c5e51c76e190ad151780ebea1cf689" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "askama_escape", + "bitflags", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", "pin-project-lite", ] [[package]] -name = "async-stream-impl" -version = "0.3.4" +name = "actix-http" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" +checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash 0.8.3", + "base64", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", ] [[package]] -name = "async-trait" -version = "0.1.68" +name = "actix-macros" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" dependencies = [ - "proc-macro2", "quote", - "syn 2.0.13", + "syn", ] [[package]] -name = "atomic" +name = "actix-router" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" dependencies = [ - "autocfg", + "bytestring", + "http", + "regex", + "serde", + "tracing", ] +[[package]] +name = "actix-rt" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "num_cpus", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash 0.7.6", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + [[package]] name = "autocfg" version = "1.1.0" @@ -51,10 +272,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "binascii" -version = "0.1.4" +name = "base64" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "bitflags" @@ -63,10 +284,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "bitflags" -version = "2.0.2" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] [[package]] name = "bytes" @@ -74,11 +319,23 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "bytestring" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +dependencies = [ + "bytes", +] + [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -87,10 +344,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "cookie" -version = "0.17.0" +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", "time", @@ -98,43 +361,55 @@ dependencies = [ ] [[package]] -name = "devise" -version = "0.4.1" +name = "cpufeatures" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" dependencies = [ - "devise_codegen", - "devise_core", + "libc", ] [[package]] -name = "devise_codegen" -version = "0.4.1" +name = "crc32fast" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "devise_core", - "quote", + "cfg-if", ] [[package]] -name = "devise_core" -version = "0.4.1" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "bitflags 2.0.2", + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", "proc-macro2", - "proc-macro2-diagnostics", "quote", - "syn 2.0.13", + "rustc_version", + "syn", ] [[package]] -name = "either" -version = "1.8.1" +name = "digest" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] [[package]] name = "encoding_rs" @@ -146,47 +421,13 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.3.0" +name = "flate2" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.45.0", -] - -[[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 = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "figment" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" -dependencies = [ - "atomic", - "pear", - "serde", - "toml", - "uncased", - "version_check", + "crc32fast", + "miniz_oxide", ] [[package]] @@ -196,27 +437,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "futures" -version = "0.3.28" +name = "form_urlencoded" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", + "percent-encoding", ] [[package]] @@ -225,12 +451,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - [[package]] name = "futures-sink" version = "0.3.28" @@ -249,28 +469,20 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ - "futures-channel", "futures-core", - "futures-io", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", - "slab", ] [[package]] -name = "generator" -version = "0.7.3" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows", + "typenum", + "version_check", ] [[package]] @@ -284,12 +496,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "h2" version = "0.3.16" @@ -324,12 +530,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - [[package]] name = "http" version = "0.2.9" @@ -342,15 +542,10 @@ dependencies = [ ] [[package]] -name = "http-body" -version = "0.4.5" +name = "http-range" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" [[package]] name = "httparse" @@ -365,27 +560,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] -name = "hyper" -version = "0.14.25" +name = "idna" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -396,45 +577,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", - "serde", -] - -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "is-terminal" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.45.0", ] [[package]] @@ -444,10 +586,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] -name = "lazy_static" -version = "1.4.0" +name = "jobserver" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "libc" @@ -456,10 +607,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] -name = "linux-raw-sys" -version = "0.3.1" +name = "local-channel" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" [[package]] name = "lock_api" @@ -480,30 +643,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - [[package]] name = "memchr" version = "2.5.0" @@ -516,6 +655,25 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.6" @@ -525,37 +683,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys 0.45.0", -] - -[[package]] -name = "multer" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", - "memchr", - "mime", - "spin", - "tokio", - "tokio-util", - "version_check", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", + "windows-sys", ] [[package]] @@ -564,7 +692,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] @@ -574,12 +702,6 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "parking_lot" version = "0.12.1" @@ -598,33 +720,16 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] -name = "pear" -version = "0.2.4" +name = "paste" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.13", -] +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "percent-encoding" @@ -644,6 +749,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -659,19 +770,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", - "version_check", - "yansi", -] - [[package]] name = "quote" version = "1.0.26" @@ -717,36 +815,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "ref-cast" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", + "bitflags", ] [[package]] @@ -755,15 +824,8 @@ version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -774,143 +836,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "rocket" -version = "0.5.0-rc.3" +name = "rustc_version" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "async-stream", - "async-trait", - "atomic", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap", - "is-terminal", - "log", - "memchr", - "multer", - "num_cpus", - "parking_lot", - "pin-project-lite", - "rand", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde", - "state", - "tempfile", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi", + "semver", ] -[[package]] -name = "rocket_codegen" -version = "0.5.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" -dependencies = [ - "devise", - "glob", - "indexmap", - "proc-macro2", - "quote", - "rocket_http", - "syn 2.0.13", - "unicode-xid", -] - -[[package]] -name = "rocket_http" -version = "0.5.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" -dependencies = [ - "cookie", - "either", - "futures", - "http", - "hyper", - "indexmap", - "log", - "memchr", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", - "serde", - "smallvec", - "stable-pattern", - "state", - "time", - "tokio", - "uncased", -] - -[[package]] -name = "rustix" -version = "0.37.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.45.0", -] - -[[package]] -name = "rustversion" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" - [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.159" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", -] [[package]] name = "serde_json" @@ -924,12 +880,26 @@ dependencies = [ ] [[package]] -name = "sharded-slab" -version = "0.1.4" +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ - "lazy_static", + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", ] [[package]] @@ -966,30 +936,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - -[[package]] -name = "state" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" -dependencies = [ - "loom", -] - [[package]] name = "syn" version = "1.0.109" @@ -1001,40 +947,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "syn" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.45.0", -] - -[[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 = "time" version = "0.3.20" @@ -1062,6 +974,21 @@ dependencies = [ "time-core", ] +[[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 = "tokio" version = "1.27.0" @@ -1072,34 +999,11 @@ dependencies = [ "bytes", "libc", "mio", - "num_cpus", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", - "tokio-macros", - "windows-sys 0.45.0", -] - -[[package]] -name = "tokio-macros" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", -] - -[[package]] -name = "tokio-stream" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", + "windows-sys", ] [[package]] @@ -1116,21 +1020,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - [[package]] name = "tracing" version = "0.1.37" @@ -1138,22 +1027,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "tracing-core" version = "0.1.30" @@ -1161,70 +1039,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", ] [[package]] name = "transcoder" version = "0.1.0" dependencies = [ - "rocket", + "actix-files", + "actix-web", ] [[package]] -name = "try-lock" -version = "0.2.4" +name = "typenum" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] -name = "ubyte" -version = "0.10.3" +name = "unicase" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "serde", -] - -[[package]] -name = "uncased" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" -dependencies = [ - "serde", "version_check", ] +[[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" @@ -1232,16 +1077,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] -name = "unicode-xid" -version = "0.2.4" +name = "unicode-normalization" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] [[package]] -name = "valuable" -version = "0.1.0" +name = "url" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] [[package]] name = "version_check" @@ -1249,16 +1102,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1287,31 +1130,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.42.2", -] - -[[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.0", + "windows-targets", ] [[package]] @@ -1320,28 +1145,13 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "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]] @@ -1350,72 +1160,36 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[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.42.2" @@ -1423,13 +1197,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" +name = "zstd" +version = "0.12.3+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +dependencies = [ + "zstd-safe", +] [[package]] -name = "yansi" -version = "0.5.1" +name = "zstd-safe" +version = "6.0.5+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/transcoder/Cargo.toml b/transcoder/Cargo.toml index 44d25f7b..0842ad02 100644 --- a/transcoder/Cargo.toml +++ b/transcoder/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -rocket = "=0.5.0-rc.3" +actix-web = "4" +actix-files = "0.6.2" diff --git a/transcoder/Dockerfile b/transcoder/Dockerfile index 231c92d0..4ca2d641 100644 --- a/transcoder/Dockerfile +++ b/transcoder/Dockerfile @@ -14,7 +14,5 @@ FROM debian:bullseye-slim #RUN apt-get update && apt-get install -y extra-runtime-dependencies && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/local/cargo/bin/transcoder ./transcoder -ENV ROCKET_ADDRESS=0.0.0.0 -ENV ROCKET_PORT=7666 EXPOSE 7666 CMD ./transcoder diff --git a/transcoder/Dockerfile.dev b/transcoder/Dockerfile.dev index 4f677734..b33f646a 100644 --- a/transcoder/Dockerfile.dev +++ b/transcoder/Dockerfile.dev @@ -8,7 +8,5 @@ COPY Cargo.toml Cargo.lock ./ RUN cargo build RUN rm src/lib.rs -ENV ROCKET_ADDRESS=0.0.0.0 -ENV ROCKET_PORT=7666 EXPOSE 7666 CMD cargo watch -x run diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index b25b3c43..322f02ea 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -1,12 +1,20 @@ -#[macro_use] -extern crate rocket; +use actix_files::NamedFile; +use actix_web::{get, web, App, HttpServer, Result}; +mod paths; -#[get("/")] -fn index() -> &'static str { - "Hello, world!" +#[get("/movie/direct/{slug}")] +async fn index(query: web::Path) -> Result { + let slug = query.into_inner(); + let path = paths::get_movie_path(slug); + + Ok(NamedFile::open_async(path).await?) + // .map(|f| (infer_content_type(f), f)) } -#[launch] -fn rocket() -> _ { - rocket::build().mount("/", routes![index]) +#[actix_web::main] +async fn main() -> std::io::Result<()> { + HttpServer::new(|| App::new().service(index)) + .bind(("0.0.0.0", 7666))? + .run() + .await } diff --git a/transcoder/src/paths.rs b/transcoder/src/paths.rs new file mode 100644 index 00000000..fd1f11f2 --- /dev/null +++ b/transcoder/src/paths.rs @@ -0,0 +1,4 @@ +pub fn get_movie_path(_slug: String) -> String { + // TODO: Implement this method to fetch the path from the API. + String::from("/video/test.mkv") +} From 33d212bd84daa733b3baa4c246966ebf6f930062 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 9 Apr 2023 01:14:55 +0900 Subject: [PATCH 04/46] Add transcode cmd paramters --- transcoder/Cargo.lock | 34 ++++++++++++-- transcoder/Cargo.toml | 3 ++ transcoder/src/main.rs | 32 ++++++++++--- transcoder/src/transcode.rs | 94 +++++++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 transcoder/src/transcode.rs diff --git a/transcoder/Cargo.lock b/transcoder/Cargo.lock index e2525dd1..bd751f79 100644 --- a/transcoder/Cargo.lock +++ b/transcoder/Cargo.lock @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -203,7 +203,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -398,7 +398,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 1.0.109", ] [[package]] @@ -867,6 +867,20 @@ name = "serde" version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] [[package]] name = "serde_json" @@ -947,6 +961,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "time" version = "0.3.20" @@ -1047,6 +1072,9 @@ version = "0.1.0" dependencies = [ "actix-files", "actix-web", + "rand", + "serde", + "tokio", ] [[package]] diff --git a/transcoder/Cargo.toml b/transcoder/Cargo.toml index 0842ad02..271a71aa 100644 --- a/transcoder/Cargo.toml +++ b/transcoder/Cargo.toml @@ -6,3 +6,6 @@ edition = "2021" [dependencies] actix-web = "4" actix-files = "0.6.2" +tokio = { version = "1.27.0", features = ["process"] } +serde = { version = "1.0.159", features = ["derive"] } +rand = "0.8.5" diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 322f02ea..1c9540b8 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -1,20 +1,40 @@ use actix_files::NamedFile; use actix_web::{get, web, App, HttpServer, Result}; + +use crate::transcode::{Quality, TranscoderState}; mod paths; +mod transcode; #[get("/movie/direct/{slug}")] -async fn index(query: web::Path) -> Result { +async fn get_movie_direct(query: web::Path) -> Result { let slug = query.into_inner(); let path = paths::get_movie_path(slug); Ok(NamedFile::open_async(path).await?) - // .map(|f| (infer_content_type(f), f)) +} + +#[get("/movie/{quality}/{slug}")] +async fn get_movie_auto( + query: web::Path<(String, String)>, + state: web::Data, +) -> Result { + let (quality, slug) = query.into_inner(); + let path = paths::get_movie_path(slug); + + Ok(NamedFile::open_async(path).await?) } #[actix_web::main] async fn main() -> std::io::Result<()> { - HttpServer::new(|| App::new().service(index)) - .bind(("0.0.0.0", 7666))? - .run() - .await + let state = web::Data::new(TranscoderState::new()); + + HttpServer::new(move || { + App::new() + .app_data(state.clone()) + .service(get_movie_direct) + .service(get_movie_auto) + }) + .bind(("0.0.0.0", 7666))? + .run() + .await } diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs new file mode 100644 index 00000000..3b3c07bd --- /dev/null +++ b/transcoder/src/transcode.rs @@ -0,0 +1,94 @@ +use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; +use serde::{Deserialize, Serialize}; +use std::process::{Command, Child}; +use std::{collections::HashMap, sync::Mutex}; + +pub struct TranscoderState { + running: Mutex>, +} + +impl TranscoderState { + pub fn new() -> TranscoderState { + Self { + running: Mutex::new(HashMap::new()), + } + } +} + +pub enum Quality { + P240, + P360, + P480, + P720, + P1080, + P1440, + P4k, + P8k, + Original, +} + +imhttps://sgsot3a.sic.shibaura-it.ac.jp/ + +fn get_transcode_video_quality_args(quality: Quality) -> Vec<&'static str> { + // superfast or ultrafast would produce a file extremly big so we prever veryfast. + let enc_base: Vec<&str> = vec![ + "-map", "0:v:0", "-c:v", "libx264", "-crf", "21", "-preset", "veryfast", + ]; + + match quality { + Quality::Original => vec![], + Quality::P240 => [enc_base, vec!["-vf", "scale=-1:240"]].concat(), + Quality::P360 => [enc_base, vec!["-vf", "scale=-1:360"]].concat(), + Quality::P480 => [enc_base, vec!["-vf", "scale=-1:480"]].concat(), + Quality::P720 => [enc_base, vec!["-vf", "scale=-1:720"]].concat(), + Quality::P1080 => [enc_base, vec!["-vf", "scale=-1:1080"]].concat(), + Quality::P1440 => [enc_base, vec!["-vf", "scale=-1:1440"]].concat(), + Quality::P4k => [enc_base, vec!["-vf", "scale=-1:2160"]].concat(), + Quality::P8k => [enc_base, vec!["-vf", "scale=-1:4320"]].concat(), + } +} + +// TODO: Add audios streams (and transcode them only when necesarry) +async fn start_transcode(path: &str, quality: Quality, start_time_sec: f32) -> (String, Child) { + // TODO: Use the out path below once cached segments can be reused. + // let out_dir = format!("/cache/{show_hash}/{quality}"); + let uuid: String = thread_rng() + .sample_iter(&Alphanumeric) + .take(30) + .map(char::from) + .collect(); + let out_dir = format!("/cache/{uuid}"); + + let segment_time = "10"; + let child = Command::new("ffmpeg") + .args(&["-ss", start_time_sec.to_string().as_str()]) + .args(&["-i", path]) + .args(&["-f", "segment"]) + .args(&["-segment_list_type", "m3u8"]) + // Keep all segments in the list (else only last X are presents, useful for livestreams) + .args(&["--segment_list_size", "0"]) + .args(&["-segment_time", segment_time]) + // Force segments to be exactly segment_time (only works when transcoding) + .args(&[ + "-force_key_frames", + format!("expr:gte(t,n_forced*{segment_time})").as_str(), + "-strict", + "-2", + "-segment_time_delta", + "0.1", + ]) + .args(get_transcode_video_quality_args(quality)) + .args(&[ + "-segment_list".to_string(), + format!("{out_dir}/stream.m3u8"), + format!("{out_dir}/segments-%02d.ts"), + ]) + .spawn() + .expect("ffmpeg failed to start"); + (uuid, child) +} + +pub async fn transcode(user_id: u32, path: &str, quality: Quality, start_time_sec: f32) { + +} From 64adc6392010c2737816fc908a018d898062d89e Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 11 Apr 2023 22:12:06 +0900 Subject: [PATCH 05/46] Make a clean transcoder state --- transcoder/Cargo.lock | 1 + transcoder/Cargo.toml | 1 + transcoder/src/error.rs | 33 +++++++++++++++ transcoder/src/main.rs | 28 +++++++++---- transcoder/src/transcode.rs | 81 ++++++++++++++++++++++++++++--------- transcoder/src/utils.rs | 38 +++++++++++++++++ 6 files changed, 156 insertions(+), 26 deletions(-) create mode 100644 transcoder/src/error.rs create mode 100644 transcoder/src/utils.rs diff --git a/transcoder/Cargo.lock b/transcoder/Cargo.lock index bd751f79..f7e404a1 100644 --- a/transcoder/Cargo.lock +++ b/transcoder/Cargo.lock @@ -1072,6 +1072,7 @@ version = "0.1.0" dependencies = [ "actix-files", "actix-web", + "derive_more", "rand", "serde", "tokio", diff --git a/transcoder/Cargo.toml b/transcoder/Cargo.toml index 271a71aa..531d8c76 100644 --- a/transcoder/Cargo.toml +++ b/transcoder/Cargo.toml @@ -9,3 +9,4 @@ actix-files = "0.6.2" tokio = { version = "1.27.0", features = ["process"] } serde = { version = "1.0.159", features = ["derive"] } rand = "0.8.5" +derive_more = "0.99.17" diff --git a/transcoder/src/error.rs b/transcoder/src/error.rs new file mode 100644 index 00000000..94b67137 --- /dev/null +++ b/transcoder/src/error.rs @@ -0,0 +1,33 @@ +use actix_web::{ + error, + http::{header::ContentType, StatusCode}, + HttpResponse, +}; +use derive_more::{Display, Error}; + +#[derive(Debug, Display, Error)] +pub enum ApiError { + #[display(fmt = "{}", error)] + BadRequest { error: String }, + #[display(fmt = "An internal error occurred. Please try again later.")] + InternalError, +} + +impl error::ResponseError for ApiError { + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()) + .insert_header(ContentType::json()) + .body(format!( + "{{ \"status\": \"{status}\", \"error\": \"{err}\" }}", + status = self.status_code(), + err = self.to_string() + )) + } + + fn status_code(&self) -> StatusCode { + match *self { + ApiError::BadRequest { error } => StatusCode::BAD_REQUEST, + ApiError::InternalError => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 1c9540b8..c6afb090 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -1,9 +1,14 @@ -use actix_files::NamedFile; -use actix_web::{get, web, App, HttpServer, Result}; +use std::str::FromStr; -use crate::transcode::{Quality, TranscoderState}; +use actix_files::NamedFile; +use actix_web::{get, web, App, HttpRequest, HttpServer, Result}; +use error::ApiError; + +use crate::transcode::{Quality, Transcoder}; +mod error; mod paths; mod transcode; +mod utils; #[get("/movie/direct/{slug}")] async fn get_movie_direct(query: web::Path) -> Result { @@ -13,20 +18,27 @@ async fn get_movie_direct(query: web::Path) -> Result { Ok(NamedFile::open_async(path).await?) } -#[get("/movie/{quality}/{slug}")] +#[get("/movie/{quality}/{slug}/master.m3u8")] async fn get_movie_auto( + req: HttpRequest, query: web::Path<(String, String)>, - state: web::Data, -) -> Result { + transcoder: web::Data, +) -> Result { let (quality, slug) = query.into_inner(); + let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { + error: "Invalid quality".to_string(), + })?; + let client_id = req.headers().get("x-client-id") + .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), })?; + let path = paths::get_movie_path(slug); - Ok(NamedFile::open_async(path).await?) + todo!() } #[actix_web::main] async fn main() -> std::io::Result<()> { - let state = web::Data::new(TranscoderState::new()); + let state = web::Data::new(Transcoder::new()); HttpServer::new(move || { App::new() diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 3b3c07bd..453d58a2 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -1,21 +1,12 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use serde::{Deserialize, Serialize}; -use std::process::{Command, Child}; +use std::process::{Child, Command}; +use std::str::FromStr; use std::{collections::HashMap, sync::Mutex}; -pub struct TranscoderState { - running: Mutex>, -} - -impl TranscoderState { - pub fn new() -> TranscoderState { - Self { - running: Mutex::new(HashMap::new()), - } - } -} +use crate::utils::Signalable; +#[derive(PartialEq, Eq)] pub enum Quality { P240, P360, @@ -28,9 +19,29 @@ pub enum Quality { Original, } -imhttps://sgsot3a.sic.shibaura-it.ac.jp/ +#[derive(Debug, PartialEq, Eq)] +pub struct InvalidValueError; -fn get_transcode_video_quality_args(quality: Quality) -> Vec<&'static str> { +impl FromStr for Quality { + type Err = InvalidValueError; + + fn from_str(s: &str) -> Result { + match s { + "240p" => Ok(Quality::P240), + "360p" => Ok(Quality::P360), + "480p" => Ok(Quality::P480), + "720p" => Ok(Quality::P720), + "1080p" => Ok(Quality::P1080), + "1440p" => Ok(Quality::P1440), + "4k" => Ok(Quality::P4k), + "8k" => Ok(Quality::P8k), + "original" => Ok(Quality::Original), + _ => Err(InvalidValueError), + } + } +} + +fn get_transcode_video_quality_args(quality: &Quality) -> Vec<&'static str> { // superfast or ultrafast would produce a file extremly big so we prever veryfast. let enc_base: Vec<&str> = vec![ "-map", "0:v:0", "-c:v", "libx264", "-crf", "21", "-preset", "veryfast", @@ -50,7 +61,7 @@ fn get_transcode_video_quality_args(quality: Quality) -> Vec<&'static str> { } // TODO: Add audios streams (and transcode them only when necesarry) -async fn start_transcode(path: &str, quality: Quality, start_time_sec: f32) -> (String, Child) { +async fn start_transcode(path: &str, quality: &Quality, start_time_sec: f32) -> (String, Child) { // TODO: Use the out path below once cached segments can be reused. // let out_dir = format!("/cache/{show_hash}/{quality}"); let uuid: String = thread_rng() @@ -89,6 +100,40 @@ async fn start_transcode(path: &str, quality: Quality, start_time_sec: f32) -> ( (uuid, child) } -pub async fn transcode(user_id: u32, path: &str, quality: Quality, start_time_sec: f32) { - +struct TranscodeInfo { + show: (String, Quality) + job: Child + uuid: String +} + +pub struct Transcoder { + running: Mutex>, +} + +impl Transcoder { + pub fn new() -> Transcoder { + Self { + running: Mutex::new(HashMap::new()), + } + } + + pub async fn transcode( + &mut self, + client_id: String, + path: String, + quality: Quality, + start_time_sec: f32, + ) { + // TODO: If the stream is not yet up to start_time (and is far), kill it and restart one at the right time. + // TODO: Clear cache at startup/every X time without use. + // TODO: cache transcoded output for a show/quality and reuse it for every future requests. + if let Some(TranscodeInfo{show: (old_path, old_qual), job, uuid}) = self.running.lock().unwrap().get_mut(&client_id) { + if path == *old_path && quality != *old_qual { + job.interrupt(); + } + } + + let (uuid, job) = start_transcode(&path, &quality, start_time_sec).await; + self.running.lock().unwrap().insert(client_id, TranscodeInfo { show: (path, quality), job, uuid}); + } } diff --git a/transcoder/src/utils.rs b/transcoder/src/utils.rs new file mode 100644 index 00000000..5414e693 --- /dev/null +++ b/transcoder/src/utils.rs @@ -0,0 +1,38 @@ +use std::{io, process::Child}; + +extern "C" { + fn kill(pid: i32, sig: i32) -> i32; +} + +/// Signal the process `pid` +fn signal(pid: i32, signal: i32) -> io::Result<()> { + let ret = unsafe { kill(pid, signal) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +pub trait Signalable { + /// Signal the thing + fn signal(&mut self, signal: i32) -> io::Result<()>; + + /// Send SIGINT + fn interrupt(&mut self) -> io::Result<()> { + self.signal(2) + } +} + +impl Signalable for Child { + fn signal(&mut self, signal: i32) -> io::Result<()> { + if self.try_wait()?.is_some() { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid argument: can't signal an exited process", + )) + } else { + crate::utils::signal(self.id() as i32, signal) + } + } +} From 2939ea0787a3bdd2cc09bca06c1baaa4a76d570b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 13 Apr 2023 18:09:12 +0900 Subject: [PATCH 06/46] Allow the transcoder to be run --- .env.example | 1 + docker-compose.dev.yml | 1 + docker-compose.prod.yml | 1 + docker-compose.yml | 1 + transcoder/Dockerfile | 4 +- transcoder/Dockerfile.dev | 3 +- transcoder/src/error.rs | 4 +- transcoder/src/main.rs | 13 ++++-- transcoder/src/transcode.rs | 89 +++++++++++++++++++++++++++++-------- transcoder/src/utils.rs | 8 ++-- 10 files changed, 94 insertions(+), 31 deletions(-) diff --git a/.env.example b/.env.example index 123a0388..7dc4f780 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ # Useful config options LIBRARY_ROOT=/video +CACHE_ROOT=/tmp/kyoo_cache LIBRARY_LANGUAGES=en # The following two values should be set to a random sequence of characters. diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 80777bb7..e27b7d65 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -60,6 +60,7 @@ services: volumes: - ./transcoder:/app - ${LIBRARY_ROOT}:/video + - ${CACHE_ROOT}:/cache ingress: image: nginx diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 0bd3589e..74b42dc8 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -39,6 +39,7 @@ services: restart: on-failure volumes: - ${LIBRARY_ROOT}:/video + - ${CACHE_ROOT}:/cache ingress: image: nginx diff --git a/docker-compose.yml b/docker-compose.yml index 87998ea3..b5d9fafa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,7 @@ services: restart: on-failure volumes: - ${LIBRARY_ROOT}:/video + - ${CACHE_ROOT}:/cache ingress: image: nginx diff --git a/transcoder/Dockerfile b/transcoder/Dockerfile index 4ca2d641..eba3c847 100644 --- a/transcoder/Dockerfile +++ b/transcoder/Dockerfile @@ -10,8 +10,8 @@ RUN rm src/lib.rs COPY src src RUN cargo install --path . -FROM debian:bullseye-slim -#RUN apt-get update && apt-get install -y extra-runtime-dependencies && rm -rf /var/lib/apt/lists/* +FROM alpine +RUN apk add --no-cache ffmpeg COPY --from=builder /usr/local/cargo/bin/transcoder ./transcoder EXPOSE 7666 diff --git a/transcoder/Dockerfile.dev b/transcoder/Dockerfile.dev index b33f646a..1d77ee35 100644 --- a/transcoder/Dockerfile.dev +++ b/transcoder/Dockerfile.dev @@ -1,4 +1,5 @@ -FROM rust +FROM rust:alpine +RUN apk add --no-cache musl-dev ffmpeg RUN cargo install cargo-watch WORKDIR /app diff --git a/transcoder/src/error.rs b/transcoder/src/error.rs index 94b67137..6025dd3f 100644 --- a/transcoder/src/error.rs +++ b/transcoder/src/error.rs @@ -25,8 +25,8 @@ impl error::ResponseError for ApiError { } fn status_code(&self) -> StatusCode { - match *self { - ApiError::BadRequest { error } => StatusCode::BAD_REQUEST, + match self { + ApiError::BadRequest { error: _ } => StatusCode::BAD_REQUEST, ApiError::InternalError => StatusCode::INTERNAL_SERVER_ERROR, } } diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index c6afb090..9813d530 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -23,19 +23,24 @@ async fn get_movie_auto( req: HttpRequest, query: web::Path<(String, String)>, transcoder: web::Data, -) -> Result { +) -> Result { let (quality, slug) = query.into_inner(); let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { error: "Invalid quality".to_string(), })?; let client_id = req.headers().get("x-client-id") - .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), })?; + .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), })? + .to_str().unwrap(); let path = paths::get_movie_path(slug); - - todo!() + // TODO: Handle start_time that is not 0 + transcoder + .transcode(client_id.to_string(), path, quality, 0) + .await + .map_err(|_| ApiError::InternalError) } + #[actix_web::main] async fn main() -> std::io::Result<()> { let state = web::Data::new(Transcoder::new()); diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 453d58a2..e326de8c 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -1,8 +1,12 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use std::process::{Child, Command}; +use std::process::Stdio; use std::str::FromStr; +use std::sync::atomic::AtomicI32; +use std::sync::Arc; use std::{collections::HashMap, sync::Mutex}; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::{Child, Command}; use crate::utils::Signalable; @@ -61,7 +65,7 @@ fn get_transcode_video_quality_args(quality: &Quality) -> Vec<&'static str> { } // TODO: Add audios streams (and transcode them only when necesarry) -async fn start_transcode(path: &str, quality: &Quality, start_time_sec: f32) -> (String, Child) { +async fn start_transcode(path: String, quality: Quality, start_time: i32) -> TranscodeInfo { // TODO: Use the out path below once cached segments can be reused. // let out_dir = format!("/cache/{show_hash}/{quality}"); let uuid: String = thread_rng() @@ -70,15 +74,19 @@ async fn start_transcode(path: &str, quality: &Quality, start_time_sec: f32) -> .map(char::from) .collect(); let out_dir = format!("/cache/{uuid}"); + std::fs::create_dir(&out_dir).expect("Could not create cache directory"); let segment_time = "10"; - let child = Command::new("ffmpeg") - .args(&["-ss", start_time_sec.to_string().as_str()]) - .args(&["-i", path]) + let mut child = Command::new("ffmpeg") + .args(&["-progress", "pipe:1"]) + .args(&["-ss", start_time.to_string().as_str()]) + .args(&["-i", path.as_str()]) .args(&["-f", "segment"]) .args(&["-segment_list_type", "m3u8"]) + // Disable the .tmp file to serve it instantly to the client. + .args(&["-hls_flags", "temp_files"]) // Keep all segments in the list (else only last X are presents, useful for livestreams) - .args(&["--segment_list_size", "0"]) + .args(&["-segment_list_size", "0"]) .args(&["-segment_time", segment_time]) // Force segments to be exactly segment_time (only works when transcoding) .args(&[ @@ -89,21 +97,53 @@ async fn start_transcode(path: &str, quality: &Quality, start_time_sec: f32) -> "-segment_time_delta", "0.1", ]) - .args(get_transcode_video_quality_args(quality)) + .args(get_transcode_video_quality_args(&quality)) .args(&[ "-segment_list".to_string(), format!("{out_dir}/stream.m3u8"), format!("{out_dir}/segments-%02d.ts"), ]) + .stdout(Stdio::piped()) .spawn() .expect("ffmpeg failed to start"); - (uuid, child) + + let stdout = child.stdout.take().unwrap(); + let info = TranscodeInfo { + show: (path, quality), + job: child, + uuid, + start_time, + ready_time: Arc::new(AtomicI32::new(0)), + }; + let ready_time = Arc::clone(&info.ready_time); + + tokio::spawn(async move { + let mut reader = BufReader::new(stdout).lines(); + while let Some(line) = reader.next_line().await.unwrap() { + if let Some((key, value)) = line.find(':').map(|i| line.split_at(i)) { + if key == "out_time_ms" { + ready_time.store( + value.parse::().unwrap() / 1000, + std::sync::atomic::Ordering::Relaxed, + ); + } + // TODO: maybe store speed too. + } + } + }); + + // TODO: Wait for 1.5 * segment time after start_time to be ready. + return info; } struct TranscodeInfo { - show: (String, Quality) - job: Child - uuid: String + show: (String, Quality), + // TODO: Store if the process as ended (probably Option for the job) + job: Child, + uuid: String, + #[allow(dead_code)] + start_time: i32, + ready_time: Arc, } pub struct Transcoder { @@ -118,22 +158,33 @@ impl Transcoder { } pub async fn transcode( - &mut self, + &self, client_id: String, path: String, quality: Quality, - start_time_sec: f32, - ) { + start_time: i32, + ) -> Result { // TODO: If the stream is not yet up to start_time (and is far), kill it and restart one at the right time. // TODO: Clear cache at startup/every X time without use. // TODO: cache transcoded output for a show/quality and reuse it for every future requests. - if let Some(TranscodeInfo{show: (old_path, old_qual), job, uuid}) = self.running.lock().unwrap().get_mut(&client_id) { - if path == *old_path && quality != *old_qual { - job.interrupt(); + if let Some(TranscodeInfo { + show: (old_path, old_qual), + job, + uuid, + .. + }) = self.running.lock().unwrap().get_mut(&client_id) + { + if path != *old_path || quality != *old_qual { + job.interrupt()?; + } else { + let path = format!("/cache/{uuid}/stream.m3u8", uuid = &uuid); + return std::fs::read_to_string(path); } } - let (uuid, job) = start_transcode(&path, &quality, start_time_sec).await; - self.running.lock().unwrap().insert(client_id, TranscodeInfo { show: (path, quality), job, uuid}); + let info = start_transcode(path, quality, start_time).await; + let path = format!("/cache/{uuid}/stream.m3u8", uuid = &info.uuid); + self.running.lock().unwrap().insert(client_id, info); + std::fs::read_to_string(path) } } diff --git a/transcoder/src/utils.rs b/transcoder/src/utils.rs index 5414e693..c883ed58 100644 --- a/transcoder/src/utils.rs +++ b/transcoder/src/utils.rs @@ -1,4 +1,4 @@ -use std::{io, process::Child}; +use tokio::{io, process::Child}; extern "C" { fn kill(pid: i32, sig: i32) -> i32; @@ -26,13 +26,15 @@ pub trait Signalable { impl Signalable for Child { fn signal(&mut self, signal: i32) -> io::Result<()> { - if self.try_wait()?.is_some() { + let id = self.id(); + + if self.try_wait()?.is_some() || id.is_none() { Err(io::Error::new( io::ErrorKind::InvalidInput, "invalid argument: can't signal an exited process", )) } else { - crate::utils::signal(self.id() as i32, signal) + crate::utils::signal(id.unwrap() as i32, signal) } } } From c6edf4e2cb53ab3c8bcc1b6ab2c71996c856abf9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 15 Apr 2023 15:35:32 +0900 Subject: [PATCH 07/46] Add segments route --- transcoder/src/main.rs | 33 +++++++++++++++++++++++--- transcoder/src/transcode.rs | 46 +++++++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 9813d530..24c7ab39 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -18,8 +18,8 @@ async fn get_movie_direct(query: web::Path) -> Result { Ok(NamedFile::open_async(path).await?) } -#[get("/movie/{quality}/{slug}/master.m3u8")] -async fn get_movie_auto( +#[get("/movie/{quality}/{slug}/index.m3u8")] +async fn transcode_movie( req: HttpRequest, query: web::Path<(String, String)>, transcoder: web::Data, @@ -40,6 +40,32 @@ async fn get_movie_auto( .map_err(|_| ApiError::InternalError) } +#[get("/movie/{quality}/{slug}/segments/{chunk}")] +async fn get_movie_chunk( + req: HttpRequest, + query: web::Path<(String, String, u32)>, + transcoder: web::Data, +) -> Result { + let (quality, slug, chunk) = query.into_inner(); + let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { + error: "Invalid quality".to_string(), + })?; + let client_id = req.headers().get("x-client-id") + .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), })? + .to_str().unwrap(); + + let path = paths::get_movie_path(slug); + // TODO: Handle start_time that is not 0 + transcoder + .get_segment(client_id.to_string(), path, quality, chunk) + .await + .map_err(|_| ApiError::InternalError) + .and_then(|path| { + NamedFile::open(path).map_err(|_| ApiError::BadRequest { + error: "Invalid segment number.".to_string(), + }) + }) +} #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -49,7 +75,8 @@ async fn main() -> std::io::Result<()> { App::new() .app_data(state.clone()) .service(get_movie_direct) - .service(get_movie_auto) + .service(transcode_movie) + .service(get_movie_chunk) }) .bind(("0.0.0.0", 7666))? .run() diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index e326de8c..e40a3a9d 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -1,10 +1,11 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; +use std::path::PathBuf; use std::process::Stdio; use std::str::FromStr; use std::sync::atomic::AtomicI32; -use std::sync::Arc; -use std::{collections::HashMap, sync::Mutex}; +use std::sync::{Arc, RwLock}; +use std::collections::HashMap; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::{Child, Command}; @@ -136,6 +137,14 @@ async fn start_transcode(path: String, quality: Quality, start_time: i32) -> Tra return info; } +fn get_cache_path(info: &TranscodeInfo) -> PathBuf { + return get_cache_path_from_uuid(&info.uuid); +} + +fn get_cache_path_from_uuid(uuid: &String) -> PathBuf { + return PathBuf::from(format!("/cache/{uuid}/stream.m3u8", uuid = &uuid)); +} + struct TranscodeInfo { show: (String, Quality), // TODO: Store if the process as ended (probably Option for the job) @@ -147,13 +156,13 @@ struct TranscodeInfo { } pub struct Transcoder { - running: Mutex>, + running: RwLock>, } impl Transcoder { pub fn new() -> Transcoder { Self { - running: Mutex::new(HashMap::new()), + running: RwLock::new(HashMap::new()), } } @@ -172,19 +181,38 @@ impl Transcoder { job, uuid, .. - }) = self.running.lock().unwrap().get_mut(&client_id) + }) = self.running.write().unwrap().get_mut(&client_id) { if path != *old_path || quality != *old_qual { job.interrupt()?; } else { - let path = format!("/cache/{uuid}/stream.m3u8", uuid = &uuid); - return std::fs::read_to_string(path); + return std::fs::read_to_string(get_cache_path_from_uuid(uuid)); } } let info = start_transcode(path, quality, start_time).await; - let path = format!("/cache/{uuid}/stream.m3u8", uuid = &info.uuid); - self.running.lock().unwrap().insert(client_id, info); + let path = get_cache_path(&info); + self.running.write().unwrap().insert(client_id, info); std::fs::read_to_string(path) } + + pub async fn get_segment( + &self, + client_id: String, + _path: String, + _quality: Quality, + chunk: u32, + ) -> Result { + let hashmap = self.running.read().unwrap(); + let info = hashmap.get(&client_id).ok_or(SegmentError::NoTranscode)?; + + // TODO: Check if ready_time is far enough for this fragment to exist. + let mut path = get_cache_path(&info); + path.push(format!("segments-{0:02}.ts", chunk)); + Ok(path) + } +} + +pub enum SegmentError { + NoTranscode, } From e7ace4d4975f56008153c2180db7cb15dd53c264 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 15 Apr 2023 21:59:51 +0900 Subject: [PATCH 08/46] Add errors messages and fix segments --- transcoder/src/main.rs | 24 ++++++++++++++---------- transcoder/src/transcode.rs | 28 ++++++++++++++++------------ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 24c7ab39..818322a1 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -10,6 +10,12 @@ mod paths; mod transcode; mod utils; +fn get_client_id(req: HttpRequest) -> Result { + req.headers().get("x-client-id") + .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), }) + .map(|x| x.to_str().unwrap().to_string()) +} + #[get("/movie/direct/{slug}")] async fn get_movie_direct(query: web::Path) -> Result { let slug = query.into_inner(); @@ -28,19 +34,17 @@ async fn transcode_movie( let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { error: "Invalid quality".to_string(), })?; - let client_id = req.headers().get("x-client-id") - .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), })? - .to_str().unwrap(); + let client_id = get_client_id(req)?; let path = paths::get_movie_path(slug); // TODO: Handle start_time that is not 0 transcoder - .transcode(client_id.to_string(), path, quality, 0) + .transcode(client_id, path, quality, 0) .await .map_err(|_| ApiError::InternalError) } -#[get("/movie/{quality}/{slug}/segments/{chunk}")] +#[get("/movie/{quality}/{slug}/segments-{chunk}.ts")] async fn get_movie_chunk( req: HttpRequest, query: web::Path<(String, String, u32)>, @@ -50,16 +54,16 @@ async fn get_movie_chunk( let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { error: "Invalid quality".to_string(), })?; - let client_id = req.headers().get("x-client-id") - .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), })? - .to_str().unwrap(); + let client_id = get_client_id(req)?; let path = paths::get_movie_path(slug); // TODO: Handle start_time that is not 0 transcoder - .get_segment(client_id.to_string(), path, quality, chunk) + .get_segment(client_id, path, quality, chunk) .await - .map_err(|_| ApiError::InternalError) + .map_err(|_| ApiError::BadRequest { + error: "No transcode started for the selected show/quality.".to_string(), + }) .and_then(|path| { NamedFile::open(path).map_err(|_| ApiError::BadRequest { error: "Invalid segment number.".to_string(), diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index e40a3a9d..da11a4dd 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -1,11 +1,11 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; +use std::collections::HashMap; use std::path::PathBuf; use std::process::Stdio; use std::str::FromStr; use std::sync::atomic::AtomicI32; use std::sync::{Arc, RwLock}; -use std::collections::HashMap; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::{Child, Command}; @@ -54,14 +54,14 @@ fn get_transcode_video_quality_args(quality: &Quality) -> Vec<&'static str> { match quality { Quality::Original => vec![], - Quality::P240 => [enc_base, vec!["-vf", "scale=-1:240"]].concat(), - Quality::P360 => [enc_base, vec!["-vf", "scale=-1:360"]].concat(), - Quality::P480 => [enc_base, vec!["-vf", "scale=-1:480"]].concat(), - Quality::P720 => [enc_base, vec!["-vf", "scale=-1:720"]].concat(), - Quality::P1080 => [enc_base, vec!["-vf", "scale=-1:1080"]].concat(), - Quality::P1440 => [enc_base, vec!["-vf", "scale=-1:1440"]].concat(), - Quality::P4k => [enc_base, vec!["-vf", "scale=-1:2160"]].concat(), - Quality::P8k => [enc_base, vec!["-vf", "scale=-1:4320"]].concat(), + Quality::P240 => [enc_base, vec!["-vf", "scale=-2:240"]].concat(), + Quality::P360 => [enc_base, vec!["-vf", "scale=-2:360"]].concat(), + Quality::P480 => [enc_base, vec!["-vf", "scale=-2:480"]].concat(), + Quality::P720 => [enc_base, vec!["-vf", "scale=-2:720"]].concat(), + Quality::P1080 => [enc_base, vec!["-vf", "scale=-2:1080"]].concat(), + Quality::P1440 => [enc_base, vec!["-vf", "scale=-2:1440"]].concat(), + Quality::P4k => [enc_base, vec!["-vf", "scale=-2:2160"]].concat(), + Quality::P8k => [enc_base, vec!["-vf", "scale=-2:4320"]].concat(), } } @@ -142,7 +142,7 @@ fn get_cache_path(info: &TranscodeInfo) -> PathBuf { } fn get_cache_path_from_uuid(uuid: &String) -> PathBuf { - return PathBuf::from(format!("/cache/{uuid}/stream.m3u8", uuid = &uuid)); + return PathBuf::from(format!("/cache/{uuid}/", uuid = &uuid)); } struct TranscodeInfo { @@ -186,16 +186,20 @@ impl Transcoder { if path != *old_path || quality != *old_qual { job.interrupt()?; } else { - return std::fs::read_to_string(get_cache_path_from_uuid(uuid)); + let mut path = get_cache_path_from_uuid(uuid); + path.push("stream.m3u8"); + return std::fs::read_to_string(path); } } let info = start_transcode(path, quality, start_time).await; - let path = get_cache_path(&info); + let mut path = get_cache_path(&info); + path.push("stream.m3u8"); self.running.write().unwrap().insert(client_id, info); std::fs::read_to_string(path) } + // TODO: Use path/quality instead of client_id pub async fn get_segment( &self, client_id: String, From 67e4764a72e9521a46617426938b6fa864b45531 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 18 Apr 2023 01:27:22 +0900 Subject: [PATCH 09/46] Wait for the first segment to exist before sending the m3u8 --- transcoder/src/main.rs | 5 ++- transcoder/src/transcode.rs | 64 ++++++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 818322a1..ea153b21 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -41,7 +41,10 @@ async fn transcode_movie( transcoder .transcode(client_id, path, quality, 0) .await - .map_err(|_| ApiError::InternalError) + .map_err(|e| { + eprintln!("Unhandled error occured while transcoding: {}", e); + ApiError::InternalError + }) } #[get("/movie/{quality}/{slug}/segments-{chunk}.ts")] diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index da11a4dd..dca6fe52 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -4,10 +4,10 @@ use std::collections::HashMap; use std::path::PathBuf; use std::process::Stdio; use std::str::FromStr; -use std::sync::atomic::AtomicI32; -use std::sync::{Arc, RwLock}; +use std::sync::RwLock; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::{Child, Command}; +use tokio::sync::watch::{self, Receiver}; use crate::utils::Signalable; @@ -66,7 +66,7 @@ fn get_transcode_video_quality_args(quality: &Quality) -> Vec<&'static str> { } // TODO: Add audios streams (and transcode them only when necesarry) -async fn start_transcode(path: String, quality: Quality, start_time: i32) -> TranscodeInfo { +async fn start_transcode(path: String, quality: Quality, start_time: u32) -> TranscodeInfo { // TODO: Use the out path below once cached segments can be reused. // let out_dir = format!("/cache/{show_hash}/{quality}"); let uuid: String = thread_rng() @@ -77,18 +77,19 @@ async fn start_transcode(path: String, quality: Quality, start_time: i32) -> Tra let out_dir = format!("/cache/{uuid}"); std::fs::create_dir(&out_dir).expect("Could not create cache directory"); - let segment_time = "10"; + let segment_time: u32 = 10; let mut child = Command::new("ffmpeg") .args(&["-progress", "pipe:1"]) + .arg("-nostats") .args(&["-ss", start_time.to_string().as_str()]) .args(&["-i", path.as_str()]) .args(&["-f", "segment"]) .args(&["-segment_list_type", "m3u8"]) - // Disable the .tmp file to serve it instantly to the client. + // Use a .tmp file for segments (.ts files) .args(&["-hls_flags", "temp_files"]) // Keep all segments in the list (else only last X are presents, useful for livestreams) .args(&["-segment_list_size", "0"]) - .args(&["-segment_time", segment_time]) + .args(&["-segment_time", segment_time.to_string().as_str()]) // Force segments to be exactly segment_time (only works when transcoding) .args(&[ "-force_key_frames", @@ -104,37 +105,46 @@ async fn start_transcode(path: String, quality: Quality, start_time: i32) -> Tra format!("{out_dir}/stream.m3u8"), format!("{out_dir}/segments-%02d.ts"), ]) + // TODO: Figure out which flag would be the best. + .args(&["-segment_list_flags", "live"]) + // .args(&["-segment_list_flags", "cache"]) .stdout(Stdio::piped()) .spawn() .expect("ffmpeg failed to start"); let stdout = child.stdout.take().unwrap(); - let info = TranscodeInfo { - show: (path, quality), - job: child, - uuid, - start_time, - ready_time: Arc::new(AtomicI32::new(0)), - }; - let ready_time = Arc::clone(&info.ready_time); + let (tx, mut rx) = watch::channel(0u32); tokio::spawn(async move { let mut reader = BufReader::new(stdout).lines(); while let Some(line) = reader.next_line().await.unwrap() { - if let Some((key, value)) = line.find(':').map(|i| line.split_at(i)) { - if key == "out_time_ms" { - ready_time.store( - value.parse::().unwrap() / 1000, - std::sync::atomic::Ordering::Relaxed, - ); + if let Some((key, value)) = line.find('=').map(|i| line.split_at(i)) { + let value = &value[1..]; + // Can't use ms since ms and us are both set to us /shrug + if key == "out_time_us" { + tx.send(value.parse::().unwrap() / 1_000_000).unwrap(); } // TODO: maybe store speed too. } } }); - // TODO: Wait for 1.5 * segment time after start_time to be ready. - return info; + // Wait for 1.5 * segment time after start_time to be ready. + loop { + rx.changed().await.unwrap(); + let ready_time = *rx.borrow(); + if ready_time >= (1.5 * segment_time as f32) as u32 + start_time { + break; + } + } + + TranscodeInfo { + show: (path, quality), + job: child, + uuid, + start_time, + ready_time: rx, + } } fn get_cache_path(info: &TranscodeInfo) -> PathBuf { @@ -151,8 +161,9 @@ struct TranscodeInfo { job: Child, uuid: String, #[allow(dead_code)] - start_time: i32, - ready_time: Arc, + start_time: u32, + #[allow(dead_code)] + ready_time: Receiver, } pub struct Transcoder { @@ -171,7 +182,7 @@ impl Transcoder { client_id: String, path: String, quality: Quality, - start_time: i32, + start_time: u32, ) -> Result { // TODO: If the stream is not yet up to start_time (and is far), kill it and restart one at the right time. // TODO: Clear cache at startup/every X time without use. @@ -184,7 +195,8 @@ impl Transcoder { }) = self.running.write().unwrap().get_mut(&client_id) { if path != *old_path || quality != *old_qual { - job.interrupt()?; + // If the job has already ended, interrupt returns an error but we don't care. + _ = job.interrupt(); } else { let mut path = get_cache_path_from_uuid(uuid); path.push("stream.m3u8"); From 4c523d56f52989aa5643cbf57636a39024c45d65 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 20 Apr 2023 11:19:26 +0900 Subject: [PATCH 10/46] Fetch paths from the api --- shell.nix | 2 + transcoder/Cargo.lock | 575 +++++++++++++++++++++++++++++++++++++- transcoder/Cargo.toml | 1 + transcoder/Dockerfile | 2 +- transcoder/Dockerfile.dev | 2 +- transcoder/src/error.rs | 3 + transcoder/src/main.rs | 12 +- transcoder/src/paths.rs | 19 +- 8 files changed, 595 insertions(+), 21 deletions(-) diff --git a/shell.nix b/shell.nix index cc9579c7..4cca2868 100644 --- a/shell.nix +++ b/shell.nix @@ -17,6 +17,8 @@ in cargo-watch rustfmt rustc + pkgconfig + openssl ]; RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; diff --git a/transcoder/Cargo.lock b/transcoder/Cargo.lock index f7e404a1..f222fcd3 100644 --- a/transcoder/Cargo.lock +++ b/transcoder/Cargo.lock @@ -313,6 +313,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + [[package]] name = "bytes" version = "1.4.0" @@ -360,6 +366,22 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "cpufeatures" version = "0.2.6" @@ -420,6 +442,36 @@ dependencies = [ "cfg-if", ] +[[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 0.48.0", +] + +[[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 = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "flate2" version = "1.0.25" @@ -436,6 +488,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -445,6 +512,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.28" @@ -530,6 +606,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "http" version = "0.2.9" @@ -541,6 +623,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "http-range" version = "0.1.5" @@ -559,6 +652,43 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "hyper" +version = "0.14.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "idna" version = "0.3.0" @@ -579,6 +709,32 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + [[package]] name = "itoa" version = "1.0.6" @@ -594,18 +750,39 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "language-tags" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[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.141" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +[[package]] +name = "linux-raw-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" + [[package]] name = "local-channel" version = "0.1.3" @@ -683,7 +860,25 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -692,7 +887,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -702,6 +897,50 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "openssl" +version = "0.10.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -720,9 +959,9 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -818,6 +1057,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.7.3" @@ -835,6 +1083,43 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "reqwest" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -844,18 +1129,64 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "722529a737f5a942fdbac3a46cee213053196737c5eaa3386d52e85b786f2659" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.17" @@ -972,6 +1303,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + [[package]] name = "time" version = "0.3.20" @@ -1028,7 +1372,17 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "socket2", - "windows-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] @@ -1045,6 +1399,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -1074,10 +1434,17 @@ dependencies = [ "actix-web", "derive_more", "rand", + "reqwest", "serde", "tokio", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typenum" version = "1.16.0" @@ -1125,18 +1492,110 @@ dependencies = [ "percent-encoding", ] +[[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[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.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1159,13 +1618,37 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[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.0", ] [[package]] @@ -1174,13 +1657,28 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 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", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -1189,42 +1687,93 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "zstd" version = "0.12.3+zstd.1.5.2" diff --git a/transcoder/Cargo.toml b/transcoder/Cargo.toml index 531d8c76..c0df8d39 100644 --- a/transcoder/Cargo.toml +++ b/transcoder/Cargo.toml @@ -10,3 +10,4 @@ tokio = { version = "1.27.0", features = ["process"] } serde = { version = "1.0.159", features = ["derive"] } rand = "0.8.5" derive_more = "0.99.17" +reqwest = { version = "0.11.16", features = ["json"] } diff --git a/transcoder/Dockerfile b/transcoder/Dockerfile index eba3c847..58fdde1a 100644 --- a/transcoder/Dockerfile +++ b/transcoder/Dockerfile @@ -11,7 +11,7 @@ COPY src src RUN cargo install --path . FROM alpine -RUN apk add --no-cache ffmpeg +RUN apk add --no-cache ffmpeg pkgconfig openssl-dev COPY --from=builder /usr/local/cargo/bin/transcoder ./transcoder EXPOSE 7666 diff --git a/transcoder/Dockerfile.dev b/transcoder/Dockerfile.dev index 1d77ee35..2538ba8d 100644 --- a/transcoder/Dockerfile.dev +++ b/transcoder/Dockerfile.dev @@ -1,5 +1,5 @@ FROM rust:alpine -RUN apk add --no-cache musl-dev ffmpeg +RUN apk add --no-cache musl-dev ffmpeg pkgconfig openssl-dev RUN cargo install cargo-watch WORKDIR /app diff --git a/transcoder/src/error.rs b/transcoder/src/error.rs index 6025dd3f..1c487d99 100644 --- a/transcoder/src/error.rs +++ b/transcoder/src/error.rs @@ -9,6 +9,8 @@ use derive_more::{Display, Error}; pub enum ApiError { #[display(fmt = "{}", error)] BadRequest { error: String }, + #[display(fmt = "Resource not found.")] + NotFound, #[display(fmt = "An internal error occurred. Please try again later.")] InternalError, } @@ -27,6 +29,7 @@ impl error::ResponseError for ApiError { fn status_code(&self) -> StatusCode { match self { ApiError::BadRequest { error: _ } => StatusCode::BAD_REQUEST, + ApiError::NotFound => StatusCode::NOT_FOUND, ApiError::InternalError => StatusCode::INTERNAL_SERVER_ERROR, } } diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index ea153b21..550ef175 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -19,7 +19,9 @@ fn get_client_id(req: HttpRequest) -> Result { #[get("/movie/direct/{slug}")] async fn get_movie_direct(query: web::Path) -> Result { let slug = query.into_inner(); - let path = paths::get_movie_path(slug); + let path = paths::get_movie_path(slug) + .await + .map_err(|_| ApiError::NotFound)?; Ok(NamedFile::open_async(path).await?) } @@ -36,7 +38,9 @@ async fn transcode_movie( })?; let client_id = get_client_id(req)?; - let path = paths::get_movie_path(slug); + let path = paths::get_movie_path(slug) + .await + .map_err(|_| ApiError::NotFound)?; // TODO: Handle start_time that is not 0 transcoder .transcode(client_id, path, quality, 0) @@ -59,7 +63,9 @@ async fn get_movie_chunk( })?; let client_id = get_client_id(req)?; - let path = paths::get_movie_path(slug); + let path = paths::get_movie_path(slug) + .await + .map_err(|_| ApiError::NotFound)?; // TODO: Handle start_time that is not 0 transcoder .get_segment(client_id, path, quality, chunk) diff --git a/transcoder/src/paths.rs b/transcoder/src/paths.rs index fd1f11f2..170b0aa2 100644 --- a/transcoder/src/paths.rs +++ b/transcoder/src/paths.rs @@ -1,4 +1,17 @@ -pub fn get_movie_path(_slug: String) -> String { - // TODO: Implement this method to fetch the path from the API. - String::from("/video/test.mkv") +use std::path::PathBuf; + +use serde::Deserialize; + +#[derive(Deserialize)] +struct Item { + path: String, +} + +pub async fn get_movie_path(_slug: String) -> Result { + // TODO: Implement this method to fetch the path from the API. + reqwest::get("{}/movie/{slug}") + .await? + .json::() + .await + .map(|x| x.path) } From 5cdcff0cd0c55be421f78d00b90580340846d348 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 21 Apr 2023 00:49:26 +0900 Subject: [PATCH 11/46] Add episodes to the transcoder --- .../Models/Resources/Episode.cs | 2 +- transcoder/Cargo.lock | 428 ++++-------------- transcoder/Cargo.toml | 2 +- transcoder/Dockerfile | 2 +- transcoder/Dockerfile.dev | 2 +- transcoder/src/main.rs | 39 +- transcoder/src/paths.rs | 11 +- 7 files changed, 126 insertions(+), 360 deletions(-) diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index bb60183f..ccf1f420 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -125,7 +125,7 @@ namespace Kyoo.Abstractions.Models /// /// The path of the video file for this episode. Any format supported by a is allowed. /// - [SerializeIgnore] public string Path { get; set; } + public string Path { get; set; } /// public Dictionary Images { get; set; } diff --git a/transcoder/Cargo.lock b/transcoder/Cargo.lock index f222fcd3..b8004086 100644 --- a/transcoder/Cargo.lock +++ b/transcoder/Cargo.lock @@ -366,22 +366,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - [[package]] name = "cpufeatures" version = "0.2.6" @@ -442,36 +426,6 @@ dependencies = [ "cfg-if", ] -[[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 0.48.0", -] - -[[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 = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "flate2" version = "1.0.25" @@ -488,21 +442,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.1.0" @@ -606,12 +545,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - [[package]] name = "http" version = "0.2.9" @@ -677,16 +610,16 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-rustls" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ - "bytes", + "http", "hyper", - "native-tls", + "rustls", "tokio", - "tokio-native-tls", + "tokio-rustls", ] [[package]] @@ -709,26 +642,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.7.2" @@ -765,24 +678,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" -[[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.141" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" -[[package]] -name = "linux-raw-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" - [[package]] name = "local-channel" version = "0.1.3" @@ -860,25 +761,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys 0.45.0", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "windows-sys", ] [[package]] @@ -887,7 +770,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] @@ -897,50 +780,6 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" -[[package]] -name = "openssl" -version = "0.10.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -959,9 +798,9 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -1057,15 +896,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags", -] - [[package]] name = "regex" version = "1.7.3" @@ -1098,28 +928,45 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", + "hyper-rustls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -1130,17 +977,24 @@ dependencies = [ ] [[package]] -name = "rustix" -version = "0.37.12" +name = "rustls" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722529a737f5a942fdbac3a46cee213053196737c5eaa3386d52e85b786f2659" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64", ] [[package]] @@ -1149,15 +1003,6 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -1165,26 +1010,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "security-framework" -version = "2.8.2" +name = "sct" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" -dependencies = [ - "core-foundation-sys", - "libc", + "ring", + "untrusted", ] [[package]] @@ -1281,6 +1113,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "syn" version = "1.0.109" @@ -1303,19 +1141,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tempfile" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.45.0", -] - [[package]] name = "time" version = "0.3.20" @@ -1372,17 +1197,18 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "socket2", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-rustls" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "native-tls", + "rustls", "tokio", + "webpki", ] [[package]] @@ -1481,6 +1307,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.3.1" @@ -1492,12 +1324,6 @@ dependencies = [ "percent-encoding", ] -[[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.4" @@ -1596,6 +1422,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1618,37 +1463,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.42.2", -] - -[[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.0", + "windows-targets", ] [[package]] @@ -1657,28 +1478,13 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "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]] @@ -1687,84 +1493,42 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - [[package]] name = "winreg" version = "0.10.1" diff --git a/transcoder/Cargo.toml b/transcoder/Cargo.toml index c0df8d39..4c236f8d 100644 --- a/transcoder/Cargo.toml +++ b/transcoder/Cargo.toml @@ -10,4 +10,4 @@ tokio = { version = "1.27.0", features = ["process"] } serde = { version = "1.0.159", features = ["derive"] } rand = "0.8.5" derive_more = "0.99.17" -reqwest = { version = "0.11.16", features = ["json"] } +reqwest = { version = "0.11.16", default_features = false, features = ["json", "rustls-tls"] } diff --git a/transcoder/Dockerfile b/transcoder/Dockerfile index 58fdde1a..eba3c847 100644 --- a/transcoder/Dockerfile +++ b/transcoder/Dockerfile @@ -11,7 +11,7 @@ COPY src src RUN cargo install --path . FROM alpine -RUN apk add --no-cache ffmpeg pkgconfig openssl-dev +RUN apk add --no-cache ffmpeg COPY --from=builder /usr/local/cargo/bin/transcoder ./transcoder EXPOSE 7666 diff --git a/transcoder/Dockerfile.dev b/transcoder/Dockerfile.dev index 2538ba8d..1d77ee35 100644 --- a/transcoder/Dockerfile.dev +++ b/transcoder/Dockerfile.dev @@ -1,5 +1,5 @@ FROM rust:alpine -RUN apk add --no-cache musl-dev ffmpeg pkgconfig openssl-dev +RUN apk add --no-cache musl-dev ffmpeg RUN cargo install cargo-watch WORKDIR /app diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 550ef175..afac84f3 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -16,29 +16,30 @@ fn get_client_id(req: HttpRequest) -> Result { .map(|x| x.to_str().unwrap().to_string()) } -#[get("/movie/direct/{slug}")] -async fn get_movie_direct(query: web::Path) -> Result { - let slug = query.into_inner(); - let path = paths::get_movie_path(slug) - .await - .map_err(|_| ApiError::NotFound)?; +#[get("/{resource}/direct/{slug}")] +async fn get_direct(query: web::Path<(String, String)>) -> Result { + let (resource, slug) = query.into_inner(); + let path = paths::get_path(resource, slug).await.map_err(|e| { + eprintln!("Unhandled error occured while getting the path: {}", e); + ApiError::NotFound + })?; Ok(NamedFile::open_async(path).await?) } -#[get("/movie/{quality}/{slug}/index.m3u8")] -async fn transcode_movie( +#[get("/{resource}/{quality}/{slug}/index.m3u8")] +async fn get_transcoded( req: HttpRequest, - query: web::Path<(String, String)>, + query: web::Path<(String, String, String)>, transcoder: web::Data, ) -> Result { - let (quality, slug) = query.into_inner(); + let (resource, quality, slug) = query.into_inner(); let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { error: "Invalid quality".to_string(), })?; let client_id = get_client_id(req)?; - let path = paths::get_movie_path(slug) + let path = paths::get_path(resource, slug) .await .map_err(|_| ApiError::NotFound)?; // TODO: Handle start_time that is not 0 @@ -51,19 +52,19 @@ async fn transcode_movie( }) } -#[get("/movie/{quality}/{slug}/segments-{chunk}.ts")] -async fn get_movie_chunk( +#[get("/{resource}/{quality}/{slug}/segments-{chunk}.ts")] +async fn get_chunk( req: HttpRequest, - query: web::Path<(String, String, u32)>, + query: web::Path<(String, String, String, u32)>, transcoder: web::Data, ) -> Result { - let (quality, slug, chunk) = query.into_inner(); + let (resource, quality, slug, chunk) = query.into_inner(); let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { error: "Invalid quality".to_string(), })?; let client_id = get_client_id(req)?; - let path = paths::get_movie_path(slug) + let path = paths::get_path(resource, slug) .await .map_err(|_| ApiError::NotFound)?; // TODO: Handle start_time that is not 0 @@ -87,9 +88,9 @@ async fn main() -> std::io::Result<()> { HttpServer::new(move || { App::new() .app_data(state.clone()) - .service(get_movie_direct) - .service(transcode_movie) - .service(get_movie_chunk) + .service(get_direct) + .service(get_transcoded) + .service(get_chunk) }) .bind(("0.0.0.0", 7666))? .run() diff --git a/transcoder/src/paths.rs b/transcoder/src/paths.rs index 170b0aa2..5f5558b1 100644 --- a/transcoder/src/paths.rs +++ b/transcoder/src/paths.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use serde::Deserialize; #[derive(Deserialize)] @@ -7,10 +5,13 @@ struct Item { path: String, } -pub async fn get_movie_path(_slug: String) -> Result { - // TODO: Implement this method to fetch the path from the API. - reqwest::get("{}/movie/{slug}") +pub async fn get_path(_resource: String, slug: String) -> Result { + let api_url = std::env::var("API_URL").unwrap_or("http://back:5000".to_string()); + + // TODO: The api create dummy episodes for movies right now so we hard code the /episode/ + reqwest::get(format!("{api_url}/episode/{slug}")) .await? + .error_for_status()? .json::() .await .map(|x| x.path) From f80289aef14d0aeaf5ac143db6cf4649e147c7f4 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 22 Apr 2023 16:00:08 +0900 Subject: [PATCH 12/46] Add identify route --- transcoder/src/identify.rs | 10 ++++++++++ transcoder/src/main.rs | 38 +++++++++++++++++++++++++++++++------- transcoder/src/paths.rs | 13 ++++++++++++- 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 transcoder/src/identify.rs diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs new file mode 100644 index 00000000..958da9b7 --- /dev/null +++ b/transcoder/src/identify.rs @@ -0,0 +1,10 @@ +use serde::Serialize; + +#[derive(Serialize)] +pub struct MediaInfo { + +} + +pub fn identify(path: String) -> Result { + todo!() +} diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index afac84f3..235e8fad 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -1,11 +1,19 @@ use std::str::FromStr; use actix_files::NamedFile; -use actix_web::{get, web, App, HttpRequest, HttpServer, Result}; +use actix_web::{ + get, + web::{self, Json}, + App, HttpRequest, HttpServer, Result, +}; use error::ApiError; -use crate::transcode::{Quality, Transcoder}; +use crate::{ + identify::{identify, MediaInfo}, + transcode::{Quality, Transcoder}, +}; mod error; +mod identify; mod paths; mod transcode; mod utils; @@ -16,7 +24,7 @@ fn get_client_id(req: HttpRequest) -> Result { .map(|x| x.to_str().unwrap().to_string()) } -#[get("/{resource}/direct/{slug}")] +#[get("/{resource}/{slug}/direct{extension}")] async fn get_direct(query: web::Path<(String, String)>) -> Result { let (resource, slug) = query.into_inner(); let path = paths::get_path(resource, slug).await.map_err(|e| { @@ -27,13 +35,13 @@ async fn get_direct(query: web::Path<(String, String)>) -> Result { Ok(NamedFile::open_async(path).await?) } -#[get("/{resource}/{quality}/{slug}/index.m3u8")] +#[get("/{resource}/{slug}/{quality}/index.m3u8")] async fn get_transcoded( req: HttpRequest, query: web::Path<(String, String, String)>, transcoder: web::Data, ) -> Result { - let (resource, quality, slug) = query.into_inner(); + let (resource, slug, quality) = query.into_inner(); let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { error: "Invalid quality".to_string(), })?; @@ -52,13 +60,13 @@ async fn get_transcoded( }) } -#[get("/{resource}/{quality}/{slug}/segments-{chunk}.ts")] +#[get("/{resource}/{slug}/{quality}/segments-{chunk}.ts")] async fn get_chunk( req: HttpRequest, query: web::Path<(String, String, String, u32)>, transcoder: web::Data, ) -> Result { - let (resource, quality, slug, chunk) = query.into_inner(); + let (resource, slug, quality, chunk) = query.into_inner(); let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { error: "Invalid quality".to_string(), })?; @@ -81,6 +89,21 @@ async fn get_chunk( }) } +#[get("/{resource}/{slug}/identify")] +async fn identify_resource( + query: web::Path<(String, String)>, +) -> Result, ApiError> { + let (resource, slug) = query.into_inner(); + let path = paths::get_path(resource, slug) + .await + .map_err(|_| ApiError::NotFound)?; + + identify(path).map(|info| Json(info)).map_err(|e| { + eprintln!("Unhandled error occured while transcoding: {}", e); + ApiError::InternalError + }) +} + #[actix_web::main] async fn main() -> std::io::Result<()> { let state = web::Data::new(Transcoder::new()); @@ -91,6 +114,7 @@ async fn main() -> std::io::Result<()> { .service(get_direct) .service(get_transcoded) .service(get_chunk) + .service(identify_resource) }) .bind(("0.0.0.0", 7666))? .run() diff --git a/transcoder/src/paths.rs b/transcoder/src/paths.rs index 5f5558b1..25853a39 100644 --- a/transcoder/src/paths.rs +++ b/transcoder/src/paths.rs @@ -7,9 +7,20 @@ struct Item { pub async fn get_path(_resource: String, slug: String) -> Result { let api_url = std::env::var("API_URL").unwrap_or("http://back:5000".to_string()); + let api_key = std::env::var("KYOO_APIKEYS") + .expect("Missing api keys.") + .split(',') + .next() + .unwrap() + .to_string(); + // TODO: Store the client somewhere gobal + let client = reqwest::Client::new(); // TODO: The api create dummy episodes for movies right now so we hard code the /episode/ - reqwest::get(format!("{api_url}/episode/{slug}")) + client + .get(format!("{api_url}/episode/{slug}")) + .header("X-API-KEY", api_key) + .send() .await? .error_for_status()? .json::() From ea52ce1c653e814cf21380015ab6800a43da9410 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 23 Apr 2023 17:09:27 +0900 Subject: [PATCH 13/46] Add identify types --- transcoder/src/identify.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index 958da9b7..199f0b20 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -2,7 +2,39 @@ use serde::Serialize; #[derive(Serialize)] pub struct MediaInfo { + container: String, + video_codec: String, + audios: Vec, + subtitles: Vec, + fonts: Vec, + chapters: Vec, +} +#[derive(Serialize)] +pub struct Track { + /// The index of this track on the media. + index: u32, + /// The title of the stream. + title: String, + /// The language of this stream (as a ISO-639-2 language code) + language: String, + /// The codec of this stream. + codec: String, + /// Is this stream the default one of it's type? + default: bool, + /// Is this stream tagged as forced? (useful only for subtitles) + forced: bool, +} + +#[derive(Serialize)] +pub struct Chapter { + /// The start time of the chapter (in second from the start of the episode). + start: f32, + /// The end time of the chapter (in second from the start of the episode). + end: f32, + /// The name of this chapter. This should be a human-readable name that could be presented to the user. + name: String + // TODO: add a type field for Opening, Credits... } pub fn identify(path: String) -> Result { From c96db3a3dc819830621414766807e842a29f19e8 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 25 Apr 2023 00:37:10 +0900 Subject: [PATCH 14/46] Add openapi spec for the transcoder --- transcoder/Cargo.lock | 58 ++++++++++++++++++++++++++++ transcoder/Cargo.toml | 1 + transcoder/src/identify.rs | 9 +++-- transcoder/src/main.rs | 79 +++++++++++++++++++++++++++++++++++++- 4 files changed, 141 insertions(+), 6 deletions(-) diff --git a/transcoder/Cargo.lock b/transcoder/Cargo.lock index b8004086..ae6510ff 100644 --- a/transcoder/Cargo.lock +++ b/transcoder/Cargo.lock @@ -640,6 +640,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] @@ -678,6 +679,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[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.141" @@ -839,6 +846,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[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.56" @@ -1263,6 +1294,7 @@ dependencies = [ "reqwest", "serde", "tokio", + "utoipa", ] [[package]] @@ -1324,6 +1356,32 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utoipa" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ae74ef183fae36d650f063ae7bde1cacbe1cd7e72b617cbe1e985551878b98" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea8ac818da7e746a63285594cce8a96f5e00ee31994e655bd827569cb8b137b" +dependencies = [ + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.13", +] + [[package]] name = "version_check" version = "0.9.4" diff --git a/transcoder/Cargo.toml b/transcoder/Cargo.toml index 4c236f8d..88d9c1d7 100644 --- a/transcoder/Cargo.toml +++ b/transcoder/Cargo.toml @@ -11,3 +11,4 @@ serde = { version = "1.0.159", features = ["derive"] } rand = "0.8.5" derive_more = "0.99.17" reqwest = { version = "0.11.16", default_features = false, features = ["json", "rustls-tls"] } +utoipa = { version = "3", features = ["actix_extras"] } diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index 199f0b20..65326fb7 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -1,6 +1,7 @@ use serde::Serialize; +use utoipa::ToSchema; -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct MediaInfo { container: String, video_codec: String, @@ -10,7 +11,7 @@ pub struct MediaInfo { chapters: Vec, } -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct Track { /// The index of this track on the media. index: u32, @@ -26,7 +27,7 @@ pub struct Track { forced: bool, } -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct Chapter { /// The start time of the chapter (in second from the start of the episode). start: f32, @@ -37,6 +38,6 @@ pub struct Chapter { // TODO: add a type field for Opening, Credits... } -pub fn identify(path: String) -> Result { +pub fn identify(_path: String) -> Result { todo!() } diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 235e8fad..e8df8f04 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -7,9 +7,10 @@ use actix_web::{ App, HttpRequest, HttpServer, Result, }; use error::ApiError; +use utoipa::OpenApi; use crate::{ - identify::{identify, MediaInfo}, + identify::{identify, MediaInfo, Track, Chapter}, transcode::{Quality, Transcoder}, }; mod error; @@ -24,7 +25,21 @@ fn get_client_id(req: HttpRequest) -> Result { .map(|x| x.to_str().unwrap().to_string()) } -#[get("/{resource}/{slug}/direct{extension}")] +/// Direct video +/// +/// Retrieve the raw video stream, in the same container as the one on the server. No transcoding or +/// transmuxing is done. +#[utoipa::path( + responses( + (status = 200, description = "The item is returned"), + (status = NOT_FOUND, description = "Invalid slug.") + ), + params( + ("resource" = String, Path, description = "Episode or movie"), + ("slug" = String, Path, description = "The slug of the movie/episode."), + ) +)] +#[get("/{resource}/{slug}/direct")] async fn get_direct(query: web::Path<(String, String)>) -> Result { let (resource, slug) = query.into_inner(); let path = paths::get_path(resource, slug).await.map_err(|e| { @@ -35,6 +50,23 @@ async fn get_direct(query: web::Path<(String, String)>) -> Result { Ok(NamedFile::open_async(path).await?) } +/// Transcode video +/// +/// Transcode the video to the selected quality. +/// This route can take a few seconds to respond since it will way for at least one segment to be +/// available. +#[utoipa::path( + responses( + (status = 200, description = "Get the m3u8 playlist."), + (status = NOT_FOUND, description = "Invalid slug.") + ), + params( + ("resource" = String, Path, description = "Episode or movie"), + ("slug" = String, Path, description = "The slug of the movie/episode."), + ("quality" = Quality, Path, description = "Specify the quality you want"), + ("x-client-id" = String, Header, description = "A unique identify for a player's instance. Used to cancel unused transcode"), + ) +)] #[get("/{resource}/{slug}/{quality}/index.m3u8")] async fn get_transcoded( req: HttpRequest, @@ -60,6 +92,22 @@ async fn get_transcoded( }) } +/// Get transmuxed chunk +/// +/// Retrieve a chunk of a transmuxed video. +#[utoipa::path( + responses( + (status = 200, description = "Get a hls chunk."), + (status = NOT_FOUND, description = "Invalid slug.") + ), + params( + ("resource" = String, Path, description = "Episode or movie"), + ("slug" = String, Path, description = "The slug of the movie/episode."), + ("quality" = Quality, Path, description = "Specify the quality you want"), + ("chunk" = u32, Path, description = "The number of the chunk"), + ("x-client-id" = String, Header, description = "A unique identify for a player's instance. Used to cancel unused transcode"), + ) +)] #[get("/{resource}/{slug}/{quality}/segments-{chunk}.ts")] async fn get_chunk( req: HttpRequest, @@ -89,6 +137,19 @@ async fn get_chunk( }) } +/// Identify +/// +/// Identify metadata about a file +#[utoipa::path( + responses( + (status = 200, description = "Ok", body = MediaInfo), + (status = NOT_FOUND, description = "Invalid slug.") + ), + params( + ("resource" = String, Path, description = "Episode or movie"), + ("slug" = String, Path, description = "The slug of the movie/episode."), + ) +)] #[get("/{resource}/{slug}/identify")] async fn identify_resource( query: web::Path<(String, String)>, @@ -104,6 +165,19 @@ async fn identify_resource( }) } +#[get("/openapi.json")] +async fn get_swagger() -> String { + #[derive(OpenApi)] + #[openapi( + info(description = "Transcoder's open api."), + paths(get_direct, get_transcoded, get_chunk, identify_resource), + components(schemas(MediaInfo, Track, Chapter)) + )] + struct ApiDoc; + + ApiDoc::openapi().to_pretty_json().unwrap() +} + #[actix_web::main] async fn main() -> std::io::Result<()> { let state = web::Data::new(Transcoder::new()); @@ -115,6 +189,7 @@ async fn main() -> std::io::Result<()> { .service(get_transcoded) .service(get_chunk) .service(identify_resource) + .service(get_swagger) }) .bind(("0.0.0.0", 7666))? .run() From 3ab76006afaeeb0d5e6536b8ffcf641700686559 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 25 Apr 2023 00:37:21 +0900 Subject: [PATCH 15/46] Create a proxy for the trasncoder --- back/src/Kyoo.Core/CoreModule.cs | 3 +- back/src/Kyoo.Core/Kyoo.Core.csproj | 1 + back/src/Kyoo.Core/Views/Watch/ProxyApi.cs | 48 +++++++ back/src/Kyoo.Core/Views/Watch/VideoApi.cs | 146 --------------------- docker-compose.dev.yml | 2 + docker-compose.prod.yml | 2 + docker-compose.yml | 2 + 7 files changed, 57 insertions(+), 147 deletions(-) create mode 100644 back/src/Kyoo.Core/Views/Watch/ProxyApi.cs delete mode 100644 back/src/Kyoo.Core/Views/Watch/VideoApi.cs diff --git a/back/src/Kyoo.Core/CoreModule.cs b/back/src/Kyoo.Core/CoreModule.cs index 5e808c9a..e1613c5c 100644 --- a/back/src/Kyoo.Core/CoreModule.cs +++ b/back/src/Kyoo.Core/CoreModule.cs @@ -16,9 +16,9 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Collections.Generic; using System.Linq; +using AspNetCore.Proxy; using Autofac; using Kyoo.Abstractions; using Kyoo.Abstractions.Controllers; @@ -113,6 +113,7 @@ namespace Kyoo.Core x.EnableForHttps = true; }); + services.AddProxies(); services.AddHttpClient(); } diff --git a/back/src/Kyoo.Core/Kyoo.Core.csproj b/back/src/Kyoo.Core/Kyoo.Core.csproj index 8f113039..7342f457 100644 --- a/back/src/Kyoo.Core/Kyoo.Core.csproj +++ b/back/src/Kyoo.Core/Kyoo.Core.csproj @@ -12,6 +12,7 @@ + diff --git a/back/src/Kyoo.Core/Views/Watch/ProxyApi.cs b/back/src/Kyoo.Core/Views/Watch/ProxyApi.cs new file mode 100644 index 00000000..207700a1 --- /dev/null +++ b/back/src/Kyoo.Core/Views/Watch/ProxyApi.cs @@ -0,0 +1,48 @@ +// Kyoo - A portable and vast media library solution. +// Copyright (c) Kyoo. +// +// See AUTHORS.md and LICENSE file in the project root for full license information. +// +// Kyoo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// Kyoo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kyoo. If not, see . + +using System.Threading.Tasks; +using AspNetCore.Proxy; +using Kyoo.Abstractions.Models.Permissions; +using Microsoft.AspNetCore.Mvc; + +namespace Kyoo.Core.Api +{ + /// + /// Proxy to other services + /// + [ApiController] + public class ProxyApi : Controller + { + /// + /// Transcoder proxy + /// + /// + /// Simply proxy requests to the transcoder + /// + /// The path of the transcoder. + /// The return value of the transcoder. + [Route("video/{**rest}")] + [Permission("video", Kind.Read)] + public Task Proxy(string rest) + { + // TODO: Use an env var to configure transcoder:7666. + return this.HttpProxyAsync($"http://transcoder:7666/{rest}"); + } + } +} diff --git a/back/src/Kyoo.Core/Views/Watch/VideoApi.cs b/back/src/Kyoo.Core/Views/Watch/VideoApi.cs deleted file mode 100644 index 40f8c881..00000000 --- a/back/src/Kyoo.Core/Views/Watch/VideoApi.cs +++ /dev/null @@ -1,146 +0,0 @@ -// Kyoo - A portable and vast media library solution. -// Copyright (c) Kyoo. -// -// See AUTHORS.md and LICENSE file in the project root for full license information. -// -// Kyoo is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// Kyoo is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Kyoo. If not, see . - -using System.IO; -using System.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Kyoo.Abstractions.Models.Attributes; -using Kyoo.Abstractions.Models.Permissions; -using Kyoo.Abstractions.Models.Utils; -using Kyoo.Core.Models.Options; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Options; -using static Kyoo.Abstractions.Models.Utils.Constants; - -namespace Kyoo.Core.Api -{ - /// - /// Get the video in a raw format or transcoded in the codec you want. - /// - [Route("videos")] - [Route("video", Order = AlternativeRoute)] - [ApiController] - [ApiDefinition("Videos", Group = WatchGroup)] - public class VideoApi : Controller - { - /// - /// The library manager used to modify or retrieve information in the data store. - /// - private readonly ILibraryManager _libraryManager; - - /// - /// The file system used to send video files. - /// - private readonly IFileSystem _files; - - /// - /// Create a new . - /// - /// The library manager used to retrieve episodes. - /// The file manager used to send video files. - public VideoApi(ILibraryManager libraryManager, - IFileSystem files) - { - _libraryManager = libraryManager; - _files = files; - } - - /// - /// - /// Disabling the cache prevent an issue on firefox that skip the last 30 seconds of HLS files - /// - public override void OnActionExecuted(ActionExecutedContext ctx) - { - base.OnActionExecuted(ctx); - ctx.HttpContext.Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate"); - ctx.HttpContext.Response.Headers.Add("Pragma", "no-cache"); - ctx.HttpContext.Response.Headers.Add("Expires", "0"); - } - - /// - /// Direct video - /// - /// - /// Retrieve the raw video stream, in the same container as the one on the server. No transcoding or - /// transmuxing is done. - /// - /// The identifier of the episode to retrieve. - /// The raw video stream - /// No episode exists for the given identifier. - // TODO enable the following line, this is disabled since the web app can't use bearers. [Permission("video", Kind.Read)] - [HttpGet("direct/{identifier:id}")] - [HttpGet("{identifier:id}", Order = AlternativeRoute)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task Direct(Identifier identifier) - { - Episode episode = await identifier.Match( - id => _libraryManager.GetOrDefault(id), - slug => _libraryManager.GetOrDefault(slug) - ); - return _files.FileResult(episode?.Path, true); - } - - /// - /// Transmux video - /// - /// - /// Change the container of the video to hls but don't re-encode the video or audio. This doesn't require mutch - /// resources from the server. - /// - /// The identifier of the episode to retrieve. - /// The transmuxed video stream - /// No episode exists for the given identifier. - [HttpGet("transmux/{identifier:id}/master.m3u8")] - [Permission("video", Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task Transmux(Identifier identifier) - { - Episode episode = await identifier.Match( - id => _libraryManager.GetOrDefault(id), - slug => _libraryManager.GetOrDefault(slug) - ); - return _files.Transmux(episode); - } - - /// - /// Transmuxed chunk - /// - /// - /// Retrieve a chunk of a transmuxed video. - /// - /// The identifier of the episode. - /// The identifier of the chunk to retrieve. - /// The options used to retrieve the path of the segments. - /// A transmuxed video chunk. - [HttpGet("transmux/{episodeLink}/segments/{chunk}", Order = AlternativeRoute)] - [Permission("video", Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - public IActionResult GetTransmuxedChunk(string episodeLink, string chunk, - [FromServices] IOptions options) - { - string path = Path.GetFullPath(Path.Combine(options.Value.TransmuxPath, episodeLink)); - path = Path.Combine(path, "segments", chunk); - return PhysicalFile(path, "video/MP2T"); - } - } -} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index e27b7d65..b510fa5b 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -57,6 +57,8 @@ services: ports: - "7666:7666" restart: on-failure + env_file: + - ./.env volumes: - ./transcoder:/app - ${LIBRARY_ROOT}:/video diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 74b42dc8..7e72a154 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -37,6 +37,8 @@ services: transcoder: image: zoriya/kyoo_transcoder:edge restart: on-failure + env_file: + - ./.env volumes: - ${LIBRARY_ROOT}:/video - ${CACHE_ROOT}:/cache diff --git a/docker-compose.yml b/docker-compose.yml index b5d9fafa..1885e9e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,6 +37,8 @@ services: transcoder: build: ./transcoder restart: on-failure + env_file: + - ./.env volumes: - ${LIBRARY_ROOT}:/video - ${CACHE_ROOT}:/cache From 3778b2148c3988f0dde6c7d1f4630cc333cc9b70 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 25 Apr 2023 22:00:52 +0900 Subject: [PATCH 16/46] Lint rust files --- transcoder/src/identify.rs | 3 +-- transcoder/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index 65326fb7..a9650f8e 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -34,8 +34,7 @@ pub struct Chapter { /// The end time of the chapter (in second from the start of the episode). end: f32, /// The name of this chapter. This should be a human-readable name that could be presented to the user. - name: String - // TODO: add a type field for Opening, Credits... + name: String, // TODO: add a type field for Opening, Credits... } pub fn identify(_path: String) -> Result { diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index e8df8f04..8692c271 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -10,7 +10,7 @@ use error::ApiError; use utoipa::OpenApi; use crate::{ - identify::{identify, MediaInfo, Track, Chapter}, + identify::{identify, Chapter, MediaInfo, Track}, transcode::{Quality, Transcoder}, }; mod error; From 9a125e0359688018351ba7b111b376ed0883ee75 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 27 Apr 2023 17:54:49 +0900 Subject: [PATCH 17/46] Add transcode quality on the front --- .../src/Kyoo.Abstractions/Models/WatchItem.cs | 18 +++- .../models/src/resources/watch-item.ts | 17 ++-- .../ui/src/player/components/hover.tsx | 5 +- .../src/player/components/right-buttons.tsx | 28 ++++-- front/packages/ui/src/player/index.tsx | 1 + front/packages/ui/src/player/state.tsx | 28 ++++-- front/packages/ui/src/player/video.tsx | 12 +++ front/packages/ui/src/player/video.web.tsx | 85 +++++++++---------- front/translations/en.json | 1 + front/translations/fr.json | 1 + 10 files changed, 129 insertions(+), 67 deletions(-) diff --git a/back/src/Kyoo.Abstractions/Models/WatchItem.cs b/back/src/Kyoo.Abstractions/Models/WatchItem.cs index 15d7ddf5..2a49d9ad 100644 --- a/back/src/Kyoo.Abstractions/Models/WatchItem.cs +++ b/back/src/Kyoo.Abstractions/Models/WatchItem.cs @@ -141,11 +141,23 @@ namespace Kyoo.Abstractions.Models /// public ICollection Chapters { get; set; } + string _Type => IsMovie ? "movie" : "episode"; + /// - public object Link => new + public object Link => new[] { - Direct = $"/video/direct/{Slug}", - Transmux = $"/video/transmux/{Slug}/master.m3u8", + new { Name = "Pristine", Link = $"/video/{_Type}/{Slug}/direct", Type = "direct" }, + new { Name = "Original", Link = $"/video/{_Type}/{Slug}/original/index.m3u8", Type = "transmux" }, + new { Name = "Auto", Link = $"/video/{_Type}/{Slug}/auto/index.m3u8", Type = "transcode-auto" }, + + new { Name = "8K", Link = $"/video/{_Type}/{Slug}/8k/index.m3u8", Type = "transcode", }, + new { Name = "4K", Link = $"/video/{_Type}/{Slug}/4k/index.m3u8", Type = "transcode" }, + new { Name = "1440p", Link = $"/video/{_Type}/{Slug}/1440p/index.m3u8", Type = "transcode" }, + new { Name = "1080p", Link = $"/video/{_Type}/{Slug}/1080p/index.m3u8", Type = "transcode" }, + new { Name = "720p", Link = $"/video/{_Type}/{Slug}/720p/index.m3u8", Type = "transcode" }, + new { Name = "480p", Link = $"/video/{_Type}/{Slug}/480p/index.m3u8", Type = "transcode" }, + new { Name = "360p", Link = $"/video/{_Type}/{Slug}/360p/index.m3u8", Type = "transcode" }, + new { Name = "240p", Link = $"/video/{_Type}/{Slug}/240p/index.m3u8", Type = "transcode" }, }; /// diff --git a/front/packages/models/src/resources/watch-item.ts b/front/packages/models/src/resources/watch-item.ts index e063f749..f5761fb4 100644 --- a/front/packages/models/src/resources/watch-item.ts +++ b/front/packages/models/src/resources/watch-item.ts @@ -129,7 +129,8 @@ const WatchMovieP = z.preprocess( */ releaseDate: zdate().nullable(), /** - * The container of the video file of this episode. Common containers are mp4, mkv, avi and so on. + * The container of the video file of this episode. Common containers are mp4, mkv, avi and so + * on. */ container: z.string(), /** @@ -155,10 +156,13 @@ const WatchMovieP = z.preprocess( /** * The links to the videos of this watch item. */ - link: z.object({ - direct: z.string().transform(imageFn), - transmux: z.string().transform(imageFn), - }), + link: z.array( + z.object({ + name: z.string(), + link: z.string().transform(imageFn), + type: z.enum(["direct", "transmux", "transcode-auto", "transcode"]) + }), + ), }), ); @@ -185,7 +189,8 @@ const WatchEpisodeP = WatchMovieP.and( */ episodeNumber: z.number().nullable(), /** - * The absolute number of this episode. It's an episode number that is not reset to 1 after a new season. + * The absolute number of this episode. It's an episode number that is not reset to 1 after a + * new season. */ absoluteNumber: z.number().nullable(), /** diff --git a/front/packages/ui/src/player/components/hover.tsx b/front/packages/ui/src/player/components/hover.tsx index e63e173b..97718cd4 100644 --- a/front/packages/ui/src/player/components/hover.tsx +++ b/front/packages/ui/src/player/components/hover.tsx @@ -33,7 +33,7 @@ import { tooltip, ts, } from "@kyoo/primitives"; -import { Chapter, Font, Track } from "@kyoo/models"; +import { Chapter, Font, Track, WatchItem } from "@kyoo/models"; import { useAtomValue, useSetAtom, useAtom } from "jotai"; import { View, ViewProps } from "react-native"; import { useTranslation } from "react-i18next"; @@ -51,6 +51,7 @@ export const Hover = ({ href, poster, chapters, + qualities, subtitles, fonts, previousSlug, @@ -66,6 +67,7 @@ export const Hover = ({ href?: string; poster?: string | null; chapters?: Chapter[]; + qualities?: WatchItem["link"] subtitles?: Track[]; fonts?: Font[]; previousSlug?: string | null; @@ -117,6 +119,7 @@ export const Hover = ({ diff --git a/front/packages/ui/src/player/components/right-buttons.tsx b/front/packages/ui/src/player/components/right-buttons.tsx index 0e7f9869..a5bf6fe7 100644 --- a/front/packages/ui/src/player/components/right-buttons.tsx +++ b/front/packages/ui/src/player/components/right-buttons.tsx @@ -18,7 +18,7 @@ * along with Kyoo. If not, see . */ -import { Font, Track } from "@kyoo/models"; +import { Font, Track, WatchItem } from "@kyoo/models"; import { IconButton, tooltip, Menu, ts } from "@kyoo/primitives"; import { useAtom, useSetAtom } from "jotai"; import { useEffect, useState } from "react"; @@ -27,21 +27,21 @@ import { useTranslation } from "react-i18next"; import ClosedCaption from "@material-symbols/svg-400/rounded/closed_caption-fill.svg"; import Fullscreen from "@material-symbols/svg-400/rounded/fullscreen-fill.svg"; import FullscreenExit from "@material-symbols/svg-400/rounded/fullscreen_exit-fill.svg"; +import SettingsIcon from "@material-symbols/svg-400/rounded/settings-fill.svg"; import { Stylable, useYoshiki } from "yoshiki/native"; -import { createParam } from "solito"; -import { fullscreenAtom, subtitleAtom } from "../state"; - -const { useParam } = createParam<{ subtitle?: string }>(); +import { fullscreenAtom, qualityAtom, subtitleAtom } from "../state"; export const RightButtons = ({ subtitles, fonts, + qualities, onMenuOpen, onMenuClose, ...props }: { subtitles?: Track[]; fonts?: Font[]; + qualities?: WatchItem["link"] onMenuOpen: () => void; onMenuClose: () => void; } & Stylable) => { @@ -49,6 +49,7 @@ export const RightButtons = ({ const { t } = useTranslation(); const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom); const setSubAtom = useSetAtom(subtitleAtom); + const [quality, setQuality] = useAtom(qualityAtom); const [selectedSubtitle, setSubtitle] = useState(undefined); useEffect(() => { @@ -87,6 +88,23 @@ export const RightButtons = ({ ))} )} + + {qualities?.map((x) => ( + setQuality(x.name)} + /> + ))} + {Platform.OS === "web" && ( ("Pristine"); export const bufferedAtom = atom(0); export const durationAtom = atom(undefined); @@ -63,8 +65,6 @@ const privateFullscreen = atom(false); export const subtitleAtom = atom(null); -const MemoVideo = memo(NativeVideo); - export const Video = memo(function _Video({ links, setError, @@ -78,6 +78,8 @@ export const Video = memo(function _Video({ const ref = useRef(null); const [isPlaying, setPlay] = useAtom(playAtom); const setLoad = useSetAtom(loadAtom); + const [source, setSource] = useState(null); + const [quality, setQuality] = useAtom(qualityAtom); const publicProgress = useAtomValue(publicProgressAtom); const setPrivateProgress = useSetAtom(privateProgressAtom); @@ -89,10 +91,11 @@ export const Video = memo(function _Video({ useLayoutEffect(() => { // Reset the state when a new video is loaded. + setSource(links?.find(x => x.name == quality) ?? null) setLoad(true); setPrivateProgress(0); setPlay(true); - }, [links, setLoad, setPrivateProgress, setPlay]); + }, [quality, links, setLoad, setPrivateProgress, setPlay]); const volume = useAtomValue(volumeAtom); const isMuted = useAtomValue(mutedAtom); @@ -109,13 +112,12 @@ export const Video = memo(function _Video({ const subtitle = useAtomValue(subtitleAtom); - if (!links) return null; + if (!source) return null; return ( - { + if (source.type === "direct") + setQuality(links?.find(x => x.type == "transmux")!.name!) + + // TODO: Replace transcode with transcode-auto when supported. + if (source.type === "transmux") + setQuality(links?.find(x => x.type == "transcode")!.name!) + + }} + // TODO: textTracks: external subtitles /> ); diff --git a/front/packages/ui/src/player/video.tsx b/front/packages/ui/src/player/video.tsx index 26f0f7b5..8b5645d9 100644 --- a/front/packages/ui/src/player/video.tsx +++ b/front/packages/ui/src/player/video.tsx @@ -18,7 +18,19 @@ * along with Kyoo. If not, see . */ +declare module "react-native-video" { + interface VideoProperties { + fonts?: Font[]; + onPlayPause: (isPlaying: boolean) => void; + onMediaUnsupported?: () => void; + } + export type VideoProps = Omit & { + source: { uri: string } & WatchItem["link"][0]; + }; +} + export * from "react-native-video"; +import { Font, WatchItem } from "@kyoo/models"; import Video from "react-native-video"; export default Video; diff --git a/front/packages/ui/src/player/video.web.tsx b/front/packages/ui/src/player/video.web.tsx index a9b966ee..19e473ca 100644 --- a/front/packages/ui/src/player/video.web.tsx +++ b/front/packages/ui/src/player/video.web.tsx @@ -18,7 +18,7 @@ * along with Kyoo. If not, see . */ -import { Font, Track } from "@kyoo/models"; +import { Font, getToken, Track } from "@kyoo/models"; import { forwardRef, RefObject, @@ -28,30 +28,23 @@ import { useRef, } from "react"; import { VideoProps } from "react-native-video"; -import { atom, useAtom, useAtomValue, useSetAtom } from "jotai"; +import { useAtomValue, useSetAtom } from "jotai"; import { useYoshiki } from "yoshiki"; import SubtitleOctopus from "libass-wasm"; import { playAtom, subtitleAtom } from "./state"; import Hls from "hls.js"; -declare module "react-native-video" { - interface VideoProperties { - fonts?: Font[]; - onPlayPause: (isPlaying: boolean) => void; - } - export type VideoProps = Omit & { - source: { uri?: string; transmux?: string }; - }; -} - -enum PlayMode { - Direct, - Transmux, -} - -const playModeAtom = atom(PlayMode.Direct); let hls: Hls | null = null; +function uuidv4(): string { + // @ts-ignore I have no clue how this works, thanks https://stackoverflow.com/questions/105034/how-do-i-create-a-guid-uuid + return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => + (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16), + ); +} + +let client_id = typeof window === "undefined" ? "ssr" : uuidv4(); + const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function _Video( { source, @@ -64,10 +57,12 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function onError, onEnd, onPlayPause, + onMediaUnsupported, fonts, }, forwaredRef, ) { + const { uri, type } = source; const ref = useRef(null); const { css } = useYoshiki(); @@ -94,28 +89,33 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function const subtitle = useAtomValue(subtitleAtom); useSubtitle(ref, subtitle, fonts); - const [playMode, setPlayMode] = useAtom(playModeAtom); - useEffect(() => { - setPlayMode(PlayMode.Direct); - }, [source.uri, setPlayMode]); - useLayoutEffect(() => { - const src = playMode === PlayMode.Direct ? source?.uri : source?.transmux; - - if (!ref?.current || !src) return; - if (playMode == PlayMode.Direct || ref.current.canPlayType("application/vnd.apple.mpegurl")) { - ref.current.src = src; - } else { - if (hls === null) hls = new Hls(); - hls.loadSource(src); - hls.attachMedia(ref.current); - hls.on(Hls.Events.MANIFEST_LOADED, async () => { - try { - await ref.current?.play(); - } catch {} - }); - } - }, [playMode, source?.uri, source?.transmux]); + (async () => { + if (!ref?.current || !uri || !type) return; + // TODO: Use hls.js even for safari or handle XHR requests with tokens,auto... + if (type === "direct" || ref.current.canPlayType("application/vnd.apple.mpegurl")) { + ref.current.src = uri; + } else { + if (hls === null) { + const token = await getToken(); + hls = new Hls({ + xhrSetup: (xhr) => { + if (token) xhr.setRequestHeader("Authorization", `Bearer: {token}`); + xhr.setRequestHeader("X-CLIENT-ID", client_id); + }, + }); + } + hls.loadSource(uri); + hls.attachMedia(ref.current); + // TODO: Enable custom XHR for tokens + hls.on(Hls.Events.MANIFEST_LOADED, async () => { + try { + await ref.current?.play(); + } catch {} + }); + } + })(); + }, [uri, type]); const setPlay = useSetAtom(playAtom); useEffect(() => { @@ -147,11 +147,8 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function }); }} onError={() => { - if ( - ref?.current?.error?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED && - playMode !== PlayMode.Transmux - ) - setPlayMode(PlayMode.Transmux); + if (ref?.current?.error?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED) + onMediaUnsupported?.call(undefined); else { onError?.call(null, { error: { "": "", errorString: ref.current?.error?.message ?? "Unknown error" }, diff --git a/front/translations/en.json b/front/translations/en.json index 6d322b56..a4be8051 100644 --- a/front/translations/en.json +++ b/front/translations/en.json @@ -43,6 +43,7 @@ "pause": "Pause", "mute": "Toggle mute", "volume": "Volume", + "quality": "Quality", "subtitles": "Subtitles", "subtitle-none": "None", "fullscreen": "Fullscreen" diff --git a/front/translations/fr.json b/front/translations/fr.json index a3cefc2c..50372024 100644 --- a/front/translations/fr.json +++ b/front/translations/fr.json @@ -43,6 +43,7 @@ "pause": "Pause", "mute": "Muet", "volume": "Volume", + "quality": "Qualité", "subtitles": "Sous titres", "subtitle-none": "Aucun", "fullscreen": "Plein-écran" From 98f6fda99fdd17b3df4900f810d35ab24bd513f0 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 27 Apr 2023 17:55:30 +0900 Subject: [PATCH 18/46] Add a video field to the identify --- transcoder/src/identify.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index a9650f8e..aa46915e 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -1,16 +1,30 @@ use serde::Serialize; use utoipa::ToSchema; +use crate::transcode::Quality; + #[derive(Serialize, ToSchema)] pub struct MediaInfo { + /// The length of the media in seconds. + length: f32, container: String, - video_codec: String, + video: VideoTrack, audios: Vec, subtitles: Vec, fonts: Vec, chapters: Vec, } +#[derive(Serialize, ToSchema)] +pub struct VideoTrack { + /// The codec of this stream. + codec: String, + /// The language of this stream (as a ISO-639-2 language code) + language: String, + /// The max quality of this video track. + quality: Quality, +} + #[derive(Serialize, ToSchema)] pub struct Track { /// The index of this track on the media. From 4e4a90b2d270b794161698db624f1a4d822eba09 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 27 Apr 2023 17:56:42 +0900 Subject: [PATCH 19/46] Switch to the hls encoder instead of segments --- transcoder/src/transcode.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index dca6fe52..d2495775 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -1,5 +1,6 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; +use serde::Serialize; use std::collections::HashMap; use std::path::PathBuf; use std::process::Stdio; @@ -11,7 +12,7 @@ use tokio::sync::watch::{self, Receiver}; use crate::utils::Signalable; -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Serialize)] pub enum Quality { P240, P360, @@ -78,18 +79,18 @@ async fn start_transcode(path: String, quality: Quality, start_time: u32) -> Tra std::fs::create_dir(&out_dir).expect("Could not create cache directory"); let segment_time: u32 = 10; - let mut child = Command::new("ffmpeg") - .args(&["-progress", "pipe:1"]) + let mut cmd = Command::new("ffmpeg"); + cmd.args(&["-progress", "pipe:1"]) .arg("-nostats") .args(&["-ss", start_time.to_string().as_str()]) .args(&["-i", path.as_str()]) - .args(&["-f", "segment"]) - .args(&["-segment_list_type", "m3u8"]) + .args(&["-f", "hls"]) // Use a .tmp file for segments (.ts files) - .args(&["-hls_flags", "temp_files"]) + .args(&["-hls_flags", "temp_file"]) + .args(&["-hls_allow_cache", "1"]) // Keep all segments in the list (else only last X are presents, useful for livestreams) - .args(&["-segment_list_size", "0"]) - .args(&["-segment_time", segment_time.to_string().as_str()]) + .args(&["-hls_list_size", "0"]) + .args(&["-hls_time", segment_time.to_string().as_str()]) // Force segments to be exactly segment_time (only works when transcoding) .args(&[ "-force_key_frames", @@ -101,16 +102,13 @@ async fn start_transcode(path: String, quality: Quality, start_time: u32) -> Tra ]) .args(get_transcode_video_quality_args(&quality)) .args(&[ - "-segment_list".to_string(), - format!("{out_dir}/stream.m3u8"), + "-hls_segment_filename".to_string(), format!("{out_dir}/segments-%02d.ts"), + format!("{out_dir}/stream.m3u8"), ]) - // TODO: Figure out which flag would be the best. - .args(&["-segment_list_flags", "live"]) - // .args(&["-segment_list_flags", "cache"]) - .stdout(Stdio::piped()) - .spawn() - .expect("ffmpeg failed to start"); + .stdout(Stdio::piped()); + println!("Starting a transcode with the command: {:?}", cmd); + let mut child = cmd.spawn().expect("ffmpeg failed to start"); let stdout = child.stdout.take().unwrap(); let (tx, mut rx) = watch::channel(0u32); From f76ce5dae10bff36a7db4c06576672b140089ef1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 28 Apr 2023 15:02:19 +0900 Subject: [PATCH 20/46] Fix transcode upscale --- transcoder/src/transcode.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index d2495775..0f4a7f1b 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -55,14 +55,14 @@ fn get_transcode_video_quality_args(quality: &Quality) -> Vec<&'static str> { match quality { Quality::Original => vec![], - Quality::P240 => [enc_base, vec!["-vf", "scale=-2:240"]].concat(), - Quality::P360 => [enc_base, vec!["-vf", "scale=-2:360"]].concat(), - Quality::P480 => [enc_base, vec!["-vf", "scale=-2:480"]].concat(), - Quality::P720 => [enc_base, vec!["-vf", "scale=-2:720"]].concat(), - Quality::P1080 => [enc_base, vec!["-vf", "scale=-2:1080"]].concat(), - Quality::P1440 => [enc_base, vec!["-vf", "scale=-2:1440"]].concat(), - Quality::P4k => [enc_base, vec!["-vf", "scale=-2:2160"]].concat(), - Quality::P8k => [enc_base, vec!["-vf", "scale=-2:4320"]].concat(), + Quality::P240 => [enc_base, vec!["-vf", "scale=-2:'min(240,ih)'"]].concat(), + Quality::P360 => [enc_base, vec!["-vf", "scale=-2:'min(360,ih)'"]].concat(), + Quality::P480 => [enc_base, vec!["-vf", "scale=-2:'min(480,ih)'"]].concat(), + Quality::P720 => [enc_base, vec!["-vf", "scale=-2:'min(720,ih)'"]].concat(), + Quality::P1080 => [enc_base, vec!["-vf", "scale=-2:'min(1080,ih)'"]].concat(), + Quality::P1440 => [enc_base, vec!["-vf", "scale=-2:'min(1440,ih)'"]].concat(), + Quality::P4k => [enc_base, vec!["-vf", "scale=-2:'min(2160,ih)'"]].concat(), + Quality::P8k => [enc_base, vec!["-vf", "scale=-2:'min(4320,ih)'"]].concat(), } } From ca4e9e005244edfed5aad7f27987c4246d4e060b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 29 Apr 2023 19:21:59 +0900 Subject: [PATCH 21/46] Add bandwidth flags to ffmpeg transcodes --- transcoder/src/transcode.rs | 59 ++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 0f4a7f1b..dfd82796 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -53,16 +53,59 @@ fn get_transcode_video_quality_args(quality: &Quality) -> Vec<&'static str> { "-map", "0:v:0", "-c:v", "libx264", "-crf", "21", "-preset", "veryfast", ]; + // I'm not entierly sure about the values for bitrates. Double checking would be nice. + // Even less sure but bufsize are 5x the avergae bitrate since the average bitrate is only + // useful for hls segments. match quality { Quality::Original => vec![], - Quality::P240 => [enc_base, vec!["-vf", "scale=-2:'min(240,ih)'"]].concat(), - Quality::P360 => [enc_base, vec!["-vf", "scale=-2:'min(360,ih)'"]].concat(), - Quality::P480 => [enc_base, vec!["-vf", "scale=-2:'min(480,ih)'"]].concat(), - Quality::P720 => [enc_base, vec!["-vf", "scale=-2:'min(720,ih)'"]].concat(), - Quality::P1080 => [enc_base, vec!["-vf", "scale=-2:'min(1080,ih)'"]].concat(), - Quality::P1440 => [enc_base, vec!["-vf", "scale=-2:'min(1440,ih)'"]].concat(), - Quality::P4k => [enc_base, vec!["-vf", "scale=-2:'min(2160,ih)'"]].concat(), - Quality::P8k => [enc_base, vec!["-vf", "scale=-2:'min(4320,ih)'"]].concat(), + Quality::P240 => [ + enc_base, + vec!["-vf", "scale=-2:'min(240,ih)'"], + vec!["-b:v", "400k", "-bufsize", "2000k", "-maxrate", "700k"], + ] + .concat(), + Quality::P360 => [ + enc_base, + vec!["-vf", "scale=-2:'min(360,ih)'"], + vec!["-b:v", "800k", "-bufsize", "4000k", "-maxrate", "1400"], + ] + .concat(), + Quality::P480 => [ + enc_base, + vec!["-vf", "scale=-2:'min(480,ih)'"], + vec!["-b:v", "1200k", "-bufsize", "6000k", "-maxrate", "2100k"], + ] + .concat(), + Quality::P720 => [ + enc_base, + vec!["-vf", "scale=-2:'min(720,ih)'"], + vec!["-b:v", "2400k", "-bufsize", "12000k", "-maxrate", "4000k"], + ] + .concat(), + Quality::P1080 => [ + enc_base, + vec!["-vf", "scale=-2:'min(1080,ih)'"], + vec!["-b:v", "4800k", "-bufsize", "24000k", "-maxrate", "6000k"], + ] + .concat(), + Quality::P1440 => [ + enc_base, + vec!["-vf", "scale=-2:'min(1440,ih)'"], + vec!["-b:v", "9600k", "-bufsize", "48000k", "-maxrate", "12000k"], + ] + .concat(), + Quality::P4k => [ + enc_base, + vec!["-vf", "scale=-2:'min(2160,ih)'"], + vec!["-b:v", "16000k", "-bufsize", "80000k", "-maxrate", "28000k"], + ] + .concat(), + Quality::P8k => [ + enc_base, + vec!["-vf", "scale=-2:'min(4320,ih)'"], + vec!["-b:v", "28000k", "-bufsize", "140000k", "-maxrate", "40000k"], + ] + .concat(), } } From 6e39690d7a61dfe5dce6f4f08e224801e66f3913 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 29 Apr 2023 19:22:13 +0900 Subject: [PATCH 22/46] Create a master playlist --- .../src/Kyoo.Abstractions/Models/WatchItem.cs | 2 +- transcoder/src/main.rs | 36 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/back/src/Kyoo.Abstractions/Models/WatchItem.cs b/back/src/Kyoo.Abstractions/Models/WatchItem.cs index 2a49d9ad..9a5d49fd 100644 --- a/back/src/Kyoo.Abstractions/Models/WatchItem.cs +++ b/back/src/Kyoo.Abstractions/Models/WatchItem.cs @@ -148,7 +148,7 @@ namespace Kyoo.Abstractions.Models { new { Name = "Pristine", Link = $"/video/{_Type}/{Slug}/direct", Type = "direct" }, new { Name = "Original", Link = $"/video/{_Type}/{Slug}/original/index.m3u8", Type = "transmux" }, - new { Name = "Auto", Link = $"/video/{_Type}/{Slug}/auto/index.m3u8", Type = "transcode-auto" }, + new { Name = "Auto", Link = $"/video/{_Type}/{Slug}/master.m3u8", Type = "transcode-auto" }, new { Name = "8K", Link = $"/video/{_Type}/{Slug}/8k/index.m3u8", Type = "transcode", }, new { Name = "4K", Link = $"/video/{_Type}/{Slug}/4k/index.m3u8", Type = "transcode" }, diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 8692c271..5b83a2ed 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{str::FromStr, iter::Map, collections::HashMap}; use actix_files::NamedFile; use actix_web::{ @@ -50,6 +50,37 @@ async fn get_direct(query: web::Path<(String, String)>) -> Result { Ok(NamedFile::open_async(path).await?) } +/// Get master playlist +/// +/// Get a master playlist containing all possible video qualities and audios available for this resource. +/// Note that the direct stream is missing (since the direct is not an hls stream) and +/// subtitles/fonts are not included to support more codecs than just webvtt. +#[utoipa::path( + responses( + (status = 200, description = "Get the m3u8 master playlist."), + (status = NOT_FOUND, description = "Invalid slug.") + ), + params( + ("resource" = String, Path, description = "Episode or movie"), + ("slug" = String, Path, description = "The slug of the movie/episode."), + ) +)] +#[get("/{resource}/{slug}/master.m3u8")] +async fn get_master( + query: web::Path<(String, String)>, +) -> Result { + let (_resource, _slug) = query.into_inner(); + + // TODO: Fetch kyoo to retrieve the max quality (as well as the list of audio streams) + // TODO: Put resolutions based on the aspect ratio and do not assume 16/9 + Ok(String::from(r#"#EXTM3U +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=400000,BANDWIDTH=700000,RESOLUTION=426x240,CODECS="avc1.640028" +./240p/index.m3u8 +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2400000,BANDWIDTH=4000000,RESOLUTION=1080x720,CODECS="avc1.640028" +./720p/index.m3u8 +"#)) +} + /// Transcode video /// /// Transcode the video to the selected quality. @@ -170,7 +201,7 @@ async fn get_swagger() -> String { #[derive(OpenApi)] #[openapi( info(description = "Transcoder's open api."), - paths(get_direct, get_transcoded, get_chunk, identify_resource), + paths(get_direct, get_transcoded, get_master, get_chunk, identify_resource), components(schemas(MediaInfo, Track, Chapter)) )] struct ApiDoc; @@ -187,6 +218,7 @@ async fn main() -> std::io::Result<()> { .app_data(state.clone()) .service(get_direct) .service(get_transcoded) + .service(get_master) .service(get_chunk) .service(identify_resource) .service(get_swagger) From 5ee0a0044ae7cd1cb47a36f215f820a25de065a1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 29 Apr 2023 23:27:03 +0900 Subject: [PATCH 23/46] Add all metadata to quality variants in the master playlist --- .../src/player/components/right-buttons.tsx | 2 +- transcoder/src/identify.rs | 11 +- transcoder/src/main.rs | 16 +- transcoder/src/transcode.rs | 208 ++++++++++++------ 4 files changed, 153 insertions(+), 84 deletions(-) diff --git a/front/packages/ui/src/player/components/right-buttons.tsx b/front/packages/ui/src/player/components/right-buttons.tsx index a5bf6fe7..fdd9f8e1 100644 --- a/front/packages/ui/src/player/components/right-buttons.tsx +++ b/front/packages/ui/src/player/components/right-buttons.tsx @@ -41,7 +41,7 @@ export const RightButtons = ({ }: { subtitles?: Track[]; fonts?: Font[]; - qualities?: WatchItem["link"] + qualities?: WatchItem["link"]; onMenuOpen: () => void; onMenuClose: () => void; } & Stylable) => { diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index aa46915e..00ddd1a4 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -17,12 +17,21 @@ pub struct MediaInfo { #[derive(Serialize, ToSchema)] pub struct VideoTrack { - /// The codec of this stream. + /// The codec of this stream (defined as the RFC 6381). codec: String, /// The language of this stream (as a ISO-639-2 language code) language: String, /// The max quality of this video track. quality: Quality, + /// The width of the video stream + width: u32, + /// The height of the video stream + height: u32, + /// The average bitrate of the video in bytes/s + average_bitrate: u32, + // TODO: Figure out if this is doable + /// The max bitrate of the video in bytes/s + max_bitrate: u32, } #[derive(Serialize, ToSchema)] diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 5b83a2ed..ee8f6209 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, iter::Map, collections::HashMap}; +use std::str::FromStr; use actix_files::NamedFile; use actix_web::{ @@ -68,17 +68,10 @@ async fn get_direct(query: web::Path<(String, String)>) -> Result { #[get("/{resource}/{slug}/master.m3u8")] async fn get_master( query: web::Path<(String, String)>, + transcoder: web::Data, ) -> Result { - let (_resource, _slug) = query.into_inner(); - - // TODO: Fetch kyoo to retrieve the max quality (as well as the list of audio streams) - // TODO: Put resolutions based on the aspect ratio and do not assume 16/9 - Ok(String::from(r#"#EXTM3U -#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=400000,BANDWIDTH=700000,RESOLUTION=426x240,CODECS="avc1.640028" -./240p/index.m3u8 -#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2400000,BANDWIDTH=4000000,RESOLUTION=1080x720,CODECS="avc1.640028" -./720p/index.m3u8 -"#)) + let (resource, slug) = query.into_inner(); + Ok(transcoder.build_master(resource, slug).await) } /// Transcode video @@ -227,3 +220,4 @@ async fn main() -> std::io::Result<()> { .run() .await } + diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index dfd82796..9b2c3b38 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -1,9 +1,11 @@ +use derive_more::Display; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use serde::Serialize; use std::collections::HashMap; use std::path::PathBuf; use std::process::Stdio; +use std::slice::Iter; use std::str::FromStr; use std::sync::RwLock; use tokio::io::{AsyncBufReadExt, BufReader}; @@ -12,19 +14,89 @@ use tokio::sync::watch::{self, Receiver}; use crate::utils::Signalable; -#[derive(PartialEq, Eq, Serialize)] +#[derive(PartialEq, Eq, Serialize, Display)] pub enum Quality { + #[display(fmt = "240p")] P240, + #[display(fmt = "360p")] P360, + #[display(fmt = "480p")] P480, + #[display(fmt = "720p")] P720, + #[display(fmt = "1080p")] P1080, + #[display(fmt = "1440p")] P1440, + #[display(fmt = "4k")] P4k, + #[display(fmt = "8k")] P8k, + #[display(fmt = "original")] Original, } +impl Quality { + fn iter() -> Iter<'static, Quality> { + static QUALITIES: [Quality; 8] = [ + Quality::P240, + Quality::P360, + Quality::P480, + Quality::P720, + Quality::P1080, + Quality::P1440, + Quality::P4k, + Quality::P8k, + // Purposfully removing Original from this list (since it require special treatments + // anyways) + ]; + QUALITIES.iter() + } + + fn height(&self) -> u32 { + match self { + Self::P240 => 240, + Self::P360 => 360, + Self::P480 => 480, + Self::P720 => 720, + Self::P1080 => 1080, + Self::P1440 => 1440, + Self::P4k => 2160, + Self::P8k => 4320, + Self::Original => panic!("Original quality must be handled specially"), + } + } + + // I'm not entierly sure about the values for bitrates. Double checking would be nice. + fn average_bitrate(&self) -> u32 { + match self { + Self::P240 => 400_000, + Self::P360 => 800_000, + Self::P480 => 1200_000, + Self::P720 => 2400_000, + Self::P1080 => 4800_000, + Self::P1440 => 9600_000, + Self::P4k => 16_000_000, + Self::P8k => 28_000_000, + Self::Original => panic!("Original quality must be handled specially"), + } + } + + fn max_bitrate(&self) -> u32 { + match self { + Self::P240 => 700_000, + Self::P360 => 1400_000, + Self::P480 => 2100_000, + Self::P720 => 4000_000, + Self::P1080 => 8000_000, + Self::P1440 => 12_000_000, + Self::P4k => 28_000_000, + Self::P8k => 40_000_000, + Self::Original => panic!("Original quality must be handled specially"), + } + } +} + #[derive(Debug, PartialEq, Eq)] pub struct InvalidValueError; @@ -47,66 +119,41 @@ impl FromStr for Quality { } } -fn get_transcode_video_quality_args(quality: &Quality) -> Vec<&'static str> { - // superfast or ultrafast would produce a file extremly big so we prever veryfast. - let enc_base: Vec<&str> = vec![ - "-map", "0:v:0", "-c:v", "libx264", "-crf", "21", "-preset", "veryfast", - ]; - - // I'm not entierly sure about the values for bitrates. Double checking would be nice. - // Even less sure but bufsize are 5x the avergae bitrate since the average bitrate is only - // useful for hls segments. - match quality { - Quality::Original => vec![], - Quality::P240 => [ - enc_base, - vec!["-vf", "scale=-2:'min(240,ih)'"], - vec!["-b:v", "400k", "-bufsize", "2000k", "-maxrate", "700k"], - ] - .concat(), - Quality::P360 => [ - enc_base, - vec!["-vf", "scale=-2:'min(360,ih)'"], - vec!["-b:v", "800k", "-bufsize", "4000k", "-maxrate", "1400"], - ] - .concat(), - Quality::P480 => [ - enc_base, - vec!["-vf", "scale=-2:'min(480,ih)'"], - vec!["-b:v", "1200k", "-bufsize", "6000k", "-maxrate", "2100k"], - ] - .concat(), - Quality::P720 => [ - enc_base, - vec!["-vf", "scale=-2:'min(720,ih)'"], - vec!["-b:v", "2400k", "-bufsize", "12000k", "-maxrate", "4000k"], - ] - .concat(), - Quality::P1080 => [ - enc_base, - vec!["-vf", "scale=-2:'min(1080,ih)'"], - vec!["-b:v", "4800k", "-bufsize", "24000k", "-maxrate", "6000k"], - ] - .concat(), - Quality::P1440 => [ - enc_base, - vec!["-vf", "scale=-2:'min(1440,ih)'"], - vec!["-b:v", "9600k", "-bufsize", "48000k", "-maxrate", "12000k"], - ] - .concat(), - Quality::P4k => [ - enc_base, - vec!["-vf", "scale=-2:'min(2160,ih)'"], - vec!["-b:v", "16000k", "-bufsize", "80000k", "-maxrate", "28000k"], - ] - .concat(), - Quality::P8k => [ - enc_base, - vec!["-vf", "scale=-2:'min(4320,ih)'"], - vec!["-b:v", "28000k", "-bufsize", "140000k", "-maxrate", "40000k"], - ] - .concat(), +fn get_transcode_video_quality_args(quality: &Quality, segment_time: u32) -> Vec { + if *quality == Quality::Original { + return vec!["-map", "0:v:0", "-c:v", "copy"] + .iter() + .map(|a| a.to_string()) + .collect(); } + vec![ + // superfast or ultrafast would produce a file extremly big so we prever veryfast. + vec![ + "-map", "0:v:0", "-c:v", "libx264", "-crf", "21", "-preset", "veryfast", + ], + vec![ + "-vf", + format!("scale=-2:'min({height},ih)'", height = quality.height()).as_str(), + ], + // Even less sure but bufsize are 5x the avergae bitrate since the average bitrate is only + // useful for hls segments. + vec!["-bufsize", (quality.max_bitrate() * 5).to_string().as_str()], + vec!["-b:v", quality.average_bitrate().to_string().as_str()], + vec!["-maxrate", quality.max_bitrate().to_string().as_str()], + // Force segments to be exactly segment_time (only works when transcoding) + vec![ + "-force_key_frames", + format!("expr:gte(t,n_forced*{segment_time})").as_str(), + "-strict", + "-2", + "-segment_time_delta", + "0.1", + ], + ] + .concat() + .iter() + .map(|arg| arg.to_string()) + .collect() } // TODO: Add audios streams (and transcode them only when necesarry) @@ -130,20 +177,12 @@ async fn start_transcode(path: String, quality: Quality, start_time: u32) -> Tra .args(&["-f", "hls"]) // Use a .tmp file for segments (.ts files) .args(&["-hls_flags", "temp_file"]) - .args(&["-hls_allow_cache", "1"]) + // Cache can't be allowed since switching quality means starting a new encode for now. + // .args(&["-hls_allow_cache", "1"]) // Keep all segments in the list (else only last X are presents, useful for livestreams) .args(&["-hls_list_size", "0"]) .args(&["-hls_time", segment_time.to_string().as_str()]) - // Force segments to be exactly segment_time (only works when transcoding) - .args(&[ - "-force_key_frames", - format!("expr:gte(t,n_forced*{segment_time})").as_str(), - "-strict", - "-2", - "-segment_time_delta", - "0.1", - ]) - .args(get_transcode_video_quality_args(&quality)) + .args(get_transcode_video_quality_args(&quality, segment_time)) .args(&[ "-hls_segment_filename".to_string(), format!("{out_dir}/segments-%02d.ts"), @@ -218,6 +257,33 @@ impl Transcoder { } } + pub async fn build_master(&self, _resource: String, _slug: String) -> String { + let mut master = String::from("#EXTM3U\n"); + // TODO: Add transmux (original quality) in this master playlist. + // Transmux should be the first variant since it's used to test bandwidth + // and serve as a hint for preffered variant for clients. + + // TODO: Fetch kyoo to retrieve the max quality and the aspect_ratio + let aspect_ratio = 16.0 / 9.0; + for quality in Quality::iter() { + master.push_str("#EXT-X-STREAM-INF:"); + master.push_str(format!("AVERAGE-BANDWIDTH={},", quality.average_bitrate()).as_str()); + master.push_str(format!("BANDWIDTH={},", quality.max_bitrate()).as_str()); + master.push_str( + format!( + "RESOLUTION={}x{},", + (aspect_ratio * quality.height() as f32).round() as u32, + quality.height() + ) + .as_str(), + ); + master.push_str("CODECS=\"avc1.640028\"\n"); + master.push_str(format!("./{}/index.m3u8\n", quality).as_str()); + } + // TODO: Add audio streams + master + } + pub async fn transcode( &self, client_id: String, From 0b2d8a7e2ec34e66a35925cde5f9f8d24a382896 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 30 Apr 2023 23:21:41 +0900 Subject: [PATCH 24/46] Add routes for audio streams --- transcoder/src/audio.rs | 77 ++++++++++++++++++++++++++ transcoder/src/main.rs | 118 ++++++---------------------------------- transcoder/src/utils.rs | 10 ++++ transcoder/src/video.rs | 96 ++++++++++++++++++++++++++++++++ 4 files changed, 201 insertions(+), 100 deletions(-) create mode 100644 transcoder/src/audio.rs create mode 100644 transcoder/src/video.rs diff --git a/transcoder/src/audio.rs b/transcoder/src/audio.rs new file mode 100644 index 00000000..4a772759 --- /dev/null +++ b/transcoder/src/audio.rs @@ -0,0 +1,77 @@ +use crate::{ + error::ApiError, + paths, + transcode::Transcoder, +}; +use actix_files::NamedFile; +use actix_web::{get, web, Result}; + +/// Transcode audio +/// +/// Get the selected audio +/// This route can take a few seconds to respond since it will way for at least one segment to be +/// available. +#[utoipa::path( + responses( + (status = 200, description = "Get the m3u8 playlist."), + (status = NOT_FOUND, description = "Invalid slug.") + ), + params( + ("resource" = String, Path, description = "Episode or movie"), + ("slug" = String, Path, description = "The slug of the movie/episode."), + ("audio" = u32, Path, description = "Specify the audio stream you want. For mappings, refer to the audios fields of the /watch response."), + ) +)] +#[get("/audio/{resource}/{slug}/{audio}/index.m3u8")] +async fn get_audio_transcoded( + query: web::Path<(String, String, u32)>, + transcoder: web::Data, +) -> Result { + let (resource, slug, audio) = query.into_inner(); + let path = paths::get_path(resource, slug) + .await + .map_err(|_| ApiError::NotFound)?; + + transcoder + .get_transcoded_audio(path, audio) + .await + .map_err(|_| ApiError::InternalError) +} + +/// Get audio chunk +/// +/// Retrieve a chunk of a transcoded audio. +#[utoipa::path( + responses( + (status = 200, description = "Get a hls chunk."), + (status = NOT_FOUND, description = "Invalid slug.") + ), + params( + ("resource" = String, Path, description = "Episode or movie"), + ("slug" = String, Path, description = "The slug of the movie/episode."), + ("audio" = u32, Path, description = "Specify the audio you want"), + ("chunk" = u32, Path, description = "The number of the chunk"), + ) +)] +#[get("/audio/{resource}/{slug}/{audio}/segments-{chunk}.ts")] +async fn get_audio_chunk( + query: web::Path<(String, String, u32, u32)>, + transcoder: web::Data, +) -> Result { + let (resource, slug, audio, chunk) = query.into_inner(); + let path = paths::get_path(resource, slug) + .await + .map_err(|_| ApiError::NotFound)?; + + transcoder + .get_audio_segment(path, audio, chunk) + .await + .map_err(|_| ApiError::BadRequest { + error: "No transcode started for the selected show/audio.".to_string(), + }) + .and_then(|path| { + NamedFile::open(path).map_err(|_| ApiError::BadRequest { + error: "Invalid segment number.".to_string(), + }) + }) +} diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index ee8f6209..b2b92dd9 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -1,29 +1,25 @@ -use std::str::FromStr; - use actix_files::NamedFile; use actix_web::{ get, web::{self, Json}, - App, HttpRequest, HttpServer, Result, + App, HttpServer, Result, }; use error::ApiError; use utoipa::OpenApi; use crate::{ + audio::*, identify::{identify, Chapter, MediaInfo, Track}, - transcode::{Quality, Transcoder}, + transcode::Transcoder, + video::*, }; +mod audio; mod error; mod identify; mod paths; mod transcode; mod utils; - -fn get_client_id(req: HttpRequest) -> Result { - req.headers().get("x-client-id") - .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), }) - .map(|x| x.to_str().unwrap().to_string()) -} +mod video; /// Direct video /// @@ -74,93 +70,6 @@ async fn get_master( Ok(transcoder.build_master(resource, slug).await) } -/// Transcode video -/// -/// Transcode the video to the selected quality. -/// This route can take a few seconds to respond since it will way for at least one segment to be -/// available. -#[utoipa::path( - responses( - (status = 200, description = "Get the m3u8 playlist."), - (status = NOT_FOUND, description = "Invalid slug.") - ), - params( - ("resource" = String, Path, description = "Episode or movie"), - ("slug" = String, Path, description = "The slug of the movie/episode."), - ("quality" = Quality, Path, description = "Specify the quality you want"), - ("x-client-id" = String, Header, description = "A unique identify for a player's instance. Used to cancel unused transcode"), - ) -)] -#[get("/{resource}/{slug}/{quality}/index.m3u8")] -async fn get_transcoded( - req: HttpRequest, - query: web::Path<(String, String, String)>, - transcoder: web::Data, -) -> Result { - let (resource, slug, quality) = query.into_inner(); - let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { - error: "Invalid quality".to_string(), - })?; - let client_id = get_client_id(req)?; - - let path = paths::get_path(resource, slug) - .await - .map_err(|_| ApiError::NotFound)?; - // TODO: Handle start_time that is not 0 - transcoder - .transcode(client_id, path, quality, 0) - .await - .map_err(|e| { - eprintln!("Unhandled error occured while transcoding: {}", e); - ApiError::InternalError - }) -} - -/// Get transmuxed chunk -/// -/// Retrieve a chunk of a transmuxed video. -#[utoipa::path( - responses( - (status = 200, description = "Get a hls chunk."), - (status = NOT_FOUND, description = "Invalid slug.") - ), - params( - ("resource" = String, Path, description = "Episode or movie"), - ("slug" = String, Path, description = "The slug of the movie/episode."), - ("quality" = Quality, Path, description = "Specify the quality you want"), - ("chunk" = u32, Path, description = "The number of the chunk"), - ("x-client-id" = String, Header, description = "A unique identify for a player's instance. Used to cancel unused transcode"), - ) -)] -#[get("/{resource}/{slug}/{quality}/segments-{chunk}.ts")] -async fn get_chunk( - req: HttpRequest, - query: web::Path<(String, String, String, u32)>, - transcoder: web::Data, -) -> Result { - let (resource, slug, quality, chunk) = query.into_inner(); - let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { - error: "Invalid quality".to_string(), - })?; - let client_id = get_client_id(req)?; - - let path = paths::get_path(resource, slug) - .await - .map_err(|_| ApiError::NotFound)?; - // TODO: Handle start_time that is not 0 - transcoder - .get_segment(client_id, path, quality, chunk) - .await - .map_err(|_| ApiError::BadRequest { - error: "No transcode started for the selected show/quality.".to_string(), - }) - .and_then(|path| { - NamedFile::open(path).map_err(|_| ApiError::BadRequest { - error: "Invalid segment number.".to_string(), - }) - }) -} - /// Identify /// /// Identify metadata about a file @@ -194,7 +103,15 @@ async fn get_swagger() -> String { #[derive(OpenApi)] #[openapi( info(description = "Transcoder's open api."), - paths(get_direct, get_transcoded, get_master, get_chunk, identify_resource), + paths( + get_direct, + get_master, + get_transcoded, + get_chunk, + get_audio_transcoded, + get_audio_chunk, + identify_resource + ), components(schemas(MediaInfo, Track, Chapter)) )] struct ApiDoc; @@ -210,9 +127,11 @@ async fn main() -> std::io::Result<()> { App::new() .app_data(state.clone()) .service(get_direct) - .service(get_transcoded) .service(get_master) + .service(get_transcoded) .service(get_chunk) + .service(get_audio_transcoded) + .service(get_audio_chunk) .service(identify_resource) .service(get_swagger) }) @@ -220,4 +139,3 @@ async fn main() -> std::io::Result<()> { .run() .await } - diff --git a/transcoder/src/utils.rs b/transcoder/src/utils.rs index c883ed58..9b254bf4 100644 --- a/transcoder/src/utils.rs +++ b/transcoder/src/utils.rs @@ -1,5 +1,8 @@ +use actix_web::HttpRequest; use tokio::{io, process::Child}; +use crate::error::ApiError; + extern "C" { fn kill(pid: i32, sig: i32) -> i32; } @@ -38,3 +41,10 @@ impl Signalable for Child { } } } + +pub fn get_client_id(req: HttpRequest) -> Result { + req.headers().get("x-client-id") + .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), }) + .map(|x| x.to_str().unwrap().to_string()) +} + diff --git a/transcoder/src/video.rs b/transcoder/src/video.rs new file mode 100644 index 00000000..f6a1c970 --- /dev/null +++ b/transcoder/src/video.rs @@ -0,0 +1,96 @@ +use std::str::FromStr; + +use crate::{ + error::ApiError, + transcode::{Quality, Transcoder}, + utils::get_client_id, paths, +}; +use actix_files::NamedFile; +use actix_web::{get, web, HttpRequest, Result}; + +/// Transcode video +/// +/// Transcode the video to the selected quality. +/// This route can take a few seconds to respond since it will way for at least one segment to be +/// available. +#[utoipa::path( + responses( + (status = 200, description = "Get the m3u8 playlist."), + (status = NOT_FOUND, description = "Invalid slug.") + ), + params( + ("resource" = String, Path, description = "Episode or movie"), + ("slug" = String, Path, description = "The slug of the movie/episode."), + ("quality" = Quality, Path, description = "Specify the quality you want"), + ("x-client-id" = String, Header, description = "A unique identify for a player's instance. Used to cancel unused transcode"), + ) +)] +#[get("/{resource}/{slug}/{quality}/index.m3u8")] +async fn get_transcoded( + req: HttpRequest, + query: web::Path<(String, String, String)>, + transcoder: web::Data, +) -> Result { + let (resource, slug, quality) = query.into_inner(); + let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { + error: "Invalid quality".to_string(), + })?; + let client_id = get_client_id(req)?; + + let path = paths::get_path(resource, slug) + .await + .map_err(|_| ApiError::NotFound)?; + // TODO: Handle start_time that is not 0 + transcoder + .transcode(client_id, path, quality, 0) + .await + .map_err(|e| { + eprintln!("Unhandled error occured while transcoding: {}", e); + ApiError::InternalError + }) +} + +/// Get transmuxed chunk +/// +/// Retrieve a chunk of a transmuxed video. +#[utoipa::path( + responses( + (status = 200, description = "Get a hls chunk."), + (status = NOT_FOUND, description = "Invalid slug.") + ), + params( + ("resource" = String, Path, description = "Episode or movie"), + ("slug" = String, Path, description = "The slug of the movie/episode."), + ("quality" = Quality, Path, description = "Specify the quality you want"), + ("chunk" = u32, Path, description = "The number of the chunk"), + ("x-client-id" = String, Header, description = "A unique identify for a player's instance. Used to cancel unused transcode"), + ) +)] +#[get("/{resource}/{slug}/{quality}/segments-{chunk}.ts")] +async fn get_chunk( + req: HttpRequest, + query: web::Path<(String, String, String, u32)>, + transcoder: web::Data, +) -> Result { + let (resource, slug, quality, chunk) = query.into_inner(); + let quality = Quality::from_str(quality.as_str()).map_err(|_| ApiError::BadRequest { + error: "Invalid quality".to_string(), + })?; + let client_id = get_client_id(req)?; + + let path = paths::get_path(resource, slug) + .await + .map_err(|_| ApiError::NotFound)?; + // TODO: Handle start_time that is not 0 + transcoder + .get_segment(client_id, path, quality, chunk) + .await + .map_err(|_| ApiError::BadRequest { + error: "No transcode started for the selected show/quality.".to_string(), + }) + .and_then(|path| { + NamedFile::open(path).map_err(|_| ApiError::BadRequest { + error: "Invalid segment number.".to_string(), + }) + }) +} From a5fc5b3753d376b688d3c67bc34a6c7dfe369bae Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 1 May 2023 00:53:58 +0900 Subject: [PATCH 25/46] Add audio transcode functions --- transcoder/src/transcode.rs | 83 +++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 9b2c3b38..163acd66 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -3,6 +3,8 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use serde::Serialize; use std::collections::HashMap; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hasher, Hash}; use std::path::PathBuf; use std::process::Stdio; use std::slice::Iter; @@ -10,10 +12,12 @@ use std::str::FromStr; use std::sync::RwLock; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::{Child, Command}; -use tokio::sync::watch::{self, Receiver}; +use tokio::sync::watch; use crate::utils::Signalable; +const SEGMENT_TIME: u32 = 10; + #[derive(PartialEq, Eq, Serialize, Display)] pub enum Quality { #[display(fmt = "240p")] @@ -119,6 +123,18 @@ impl FromStr for Quality { } } +fn get_transcode_audio_args(audio_idx: u32) -> Vec { + // TODO: Support multy audio qualities. + return vec![ + "-map".to_string(), + format!("0:a:{}", audio_idx), + "-c:a".to_string(), + "aac".to_string(), + "-b:a".to_string(), + "128k".to_string(), + ]; +} + fn get_transcode_video_quality_args(quality: &Quality, segment_time: u32) -> Vec { if *quality == Quality::Original { return vec!["-map", "0:v:0", "-c:v", "copy"] @@ -156,8 +172,23 @@ fn get_transcode_video_quality_args(quality: &Quality, segment_time: u32) -> Vec .collect() } -// TODO: Add audios streams (and transcode them only when necesarry) -async fn start_transcode(path: String, quality: Quality, start_time: u32) -> TranscodeInfo { +async fn transcode_audio(path: String, audio: u32) -> TranscodeInfo { + let mut hasher = DefaultHasher::new(); + path.hash(&mut hasher); + audio.hash(&mut hasher); + let hash = hasher.finish(); + + let child = start_transcode( + &path, + &format!("/cache/{hash}"), + get_transcode_audio_args(audio), + 0, + ) + .await; + todo!() +} + +async fn transcode_video(path: String, quality: Quality, start_time: u32) -> TranscodeInfo { // TODO: Use the out path below once cached segments can be reused. // let out_dir = format!("/cache/{show_hash}/{quality}"); let uuid: String = thread_rng() @@ -168,7 +199,26 @@ async fn start_transcode(path: String, quality: Quality, start_time: u32) -> Tra let out_dir = format!("/cache/{uuid}"); std::fs::create_dir(&out_dir).expect("Could not create cache directory"); - let segment_time: u32 = 10; + let child = start_transcode( + &path, + &out_dir, + get_transcode_video_quality_args(&quality, SEGMENT_TIME), + start_time, + ) + .await; + TranscodeInfo { + show: (path, quality), + job: child, + uuid, + } +} + +async fn start_transcode( + path: &String, + out_dir: &String, + encode_args: Vec, + start_time: u32, +) -> Child { let mut cmd = Command::new("ffmpeg"); cmd.args(&["-progress", "pipe:1"]) .arg("-nostats") @@ -181,8 +231,8 @@ async fn start_transcode(path: String, quality: Quality, start_time: u32) -> Tra // .args(&["-hls_allow_cache", "1"]) // Keep all segments in the list (else only last X are presents, useful for livestreams) .args(&["-hls_list_size", "0"]) - .args(&["-hls_time", segment_time.to_string().as_str()]) - .args(get_transcode_video_quality_args(&quality, segment_time)) + .args(&["-hls_time", SEGMENT_TIME.to_string().as_str()]) + .args(&encode_args) .args(&[ "-hls_segment_filename".to_string(), format!("{out_dir}/segments-%02d.ts"), @@ -213,18 +263,10 @@ async fn start_transcode(path: String, quality: Quality, start_time: u32) -> Tra loop { rx.changed().await.unwrap(); let ready_time = *rx.borrow(); - if ready_time >= (1.5 * segment_time as f32) as u32 + start_time { - break; + if ready_time >= (1.5 * SEGMENT_TIME as f32) as u32 + start_time { + return child; } } - - TranscodeInfo { - show: (path, quality), - job: child, - uuid, - start_time, - ready_time: rx, - } } fn get_cache_path(info: &TranscodeInfo) -> PathBuf { @@ -235,15 +277,10 @@ fn get_cache_path_from_uuid(uuid: &String) -> PathBuf { return PathBuf::from(format!("/cache/{uuid}/", uuid = &uuid)); } -struct TranscodeInfo { +pub struct TranscodeInfo { show: (String, Quality), - // TODO: Store if the process as ended (probably Option for the job) job: Child, uuid: String, - #[allow(dead_code)] - start_time: u32, - #[allow(dead_code)] - ready_time: Receiver, } pub struct Transcoder { @@ -311,7 +348,7 @@ impl Transcoder { } } - let info = start_transcode(path, quality, start_time).await; + let info = transcode_video(path, quality, start_time).await; let mut path = get_cache_path(&info); path.push("stream.m3u8"); self.running.write().unwrap().insert(client_id, info); From 47c7617d242ff223e1dc2a954da81bf94edd73e1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 1 May 2023 01:18:03 +0900 Subject: [PATCH 26/46] Split transcode commands and state --- transcoder/src/audio.rs | 2 +- transcoder/src/main.rs | 3 +- transcoder/src/state.rs | 99 ++++++++++++++++++++++++++++++ transcoder/src/transcode.rs | 116 +++--------------------------------- transcoder/src/video.rs | 6 +- 5 files changed, 112 insertions(+), 114 deletions(-) create mode 100644 transcoder/src/state.rs diff --git a/transcoder/src/audio.rs b/transcoder/src/audio.rs index 4a772759..2251a802 100644 --- a/transcoder/src/audio.rs +++ b/transcoder/src/audio.rs @@ -1,7 +1,7 @@ use crate::{ error::ApiError, paths, - transcode::Transcoder, + state::Transcoder, }; use actix_files::NamedFile; use actix_web::{get, web, Result}; diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index b2b92dd9..e841d629 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -10,13 +10,14 @@ use utoipa::OpenApi; use crate::{ audio::*, identify::{identify, Chapter, MediaInfo, Track}, - transcode::Transcoder, + state::Transcoder, video::*, }; mod audio; mod error; mod identify; mod paths; +mod state; mod transcode; mod utils; mod video; diff --git a/transcoder/src/state.rs b/transcoder/src/state.rs new file mode 100644 index 00000000..b6dadb62 --- /dev/null +++ b/transcoder/src/state.rs @@ -0,0 +1,99 @@ +use crate::transcode::*; +use crate::utils::Signalable; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::RwLock; + +pub struct Transcoder { + running: RwLock>, +} + +impl Transcoder { + pub fn new() -> Transcoder { + Self { + running: RwLock::new(HashMap::new()), + } + } + + pub async fn build_master(&self, _resource: String, _slug: String) -> String { + let mut master = String::from("#EXTM3U\n"); + // TODO: Add transmux (original quality) in this master playlist. + // Transmux should be the first variant since it's used to test bandwidth + // and serve as a hint for preffered variant for clients. + + // TODO: Fetch kyoo to retrieve the max quality and the aspect_ratio + let aspect_ratio = 16.0 / 9.0; + for quality in Quality::iter() { + master.push_str("#EXT-X-STREAM-INF:"); + master.push_str(format!("AVERAGE-BANDWIDTH={},", quality.average_bitrate()).as_str()); + master.push_str(format!("BANDWIDTH={},", quality.max_bitrate()).as_str()); + master.push_str( + format!( + "RESOLUTION={}x{},", + (aspect_ratio * quality.height() as f32).round() as u32, + quality.height() + ) + .as_str(), + ); + master.push_str("CODECS=\"avc1.640028\"\n"); + master.push_str(format!("./{}/index.m3u8\n", quality).as_str()); + } + // TODO: Add audio streams + master + } + + pub async fn transcode( + &self, + client_id: String, + path: String, + quality: Quality, + start_time: u32, + ) -> Result { + // TODO: If the stream is not yet up to start_time (and is far), kill it and restart one at the right time. + // TODO: Clear cache at startup/every X time without use. + // TODO: cache transcoded output for a show/quality and reuse it for every future requests. + if let Some(TranscodeInfo { + show: (old_path, old_qual), + job, + uuid, + .. + }) = self.running.write().unwrap().get_mut(&client_id) + { + if path != *old_path || quality != *old_qual { + // If the job has already ended, interrupt returns an error but we don't care. + _ = job.interrupt(); + } else { + let mut path = get_cache_path_from_uuid(uuid); + path.push("stream.m3u8"); + return std::fs::read_to_string(path); + } + } + + let info = transcode_video(path, quality, start_time).await; + let mut path = get_cache_path(&info); + path.push("stream.m3u8"); + self.running.write().unwrap().insert(client_id, info); + std::fs::read_to_string(path) + } + + // TODO: Use path/quality instead of client_id + pub async fn get_segment( + &self, + client_id: String, + _path: String, + _quality: Quality, + chunk: u32, + ) -> Result { + let hashmap = self.running.read().unwrap(); + let info = hashmap.get(&client_id).ok_or(SegmentError::NoTranscode)?; + + // TODO: Check if ready_time is far enough for this fragment to exist. + let mut path = get_cache_path(&info); + path.push(format!("segments-{0:02}.ts", chunk)); + Ok(path) + } +} + +pub enum SegmentError { + NoTranscode, +} diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 163acd66..2e692dd1 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -2,20 +2,16 @@ use derive_more::Display; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use serde::Serialize; -use std::collections::HashMap; use std::collections::hash_map::DefaultHasher; -use std::hash::{Hasher, Hash}; +use std::hash::{Hash, Hasher}; use std::path::PathBuf; use std::process::Stdio; use std::slice::Iter; use std::str::FromStr; -use std::sync::RwLock; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::{Child, Command}; use tokio::sync::watch; -use crate::utils::Signalable; - const SEGMENT_TIME: u32 = 10; #[derive(PartialEq, Eq, Serialize, Display)] @@ -41,7 +37,7 @@ pub enum Quality { } impl Quality { - fn iter() -> Iter<'static, Quality> { + pub fn iter() -> Iter<'static, Quality> { static QUALITIES: [Quality; 8] = [ Quality::P240, Quality::P360, @@ -57,7 +53,7 @@ impl Quality { QUALITIES.iter() } - fn height(&self) -> u32 { + pub fn height(&self) -> u32 { match self { Self::P240 => 240, Self::P360 => 360, @@ -72,7 +68,7 @@ impl Quality { } // I'm not entierly sure about the values for bitrates. Double checking would be nice. - fn average_bitrate(&self) -> u32 { + pub fn average_bitrate(&self) -> u32 { match self { Self::P240 => 400_000, Self::P360 => 800_000, @@ -86,7 +82,7 @@ impl Quality { } } - fn max_bitrate(&self) -> u32 { + pub fn max_bitrate(&self) -> u32 { match self { Self::P240 => 700_000, Self::P360 => 1400_000, @@ -172,7 +168,7 @@ fn get_transcode_video_quality_args(quality: &Quality, segment_time: u32) -> Vec .collect() } -async fn transcode_audio(path: String, audio: u32) -> TranscodeInfo { +pub async fn transcode_audio(path: String, audio: u32) -> TranscodeInfo { let mut hasher = DefaultHasher::new(); path.hash(&mut hasher); audio.hash(&mut hasher); @@ -188,7 +184,7 @@ async fn transcode_audio(path: String, audio: u32) -> TranscodeInfo { todo!() } -async fn transcode_video(path: String, quality: Quality, start_time: u32) -> TranscodeInfo { +pub async fn transcode_video(path: String, quality: Quality, start_time: u32) -> TranscodeInfo { // TODO: Use the out path below once cached segments can be reused. // let out_dir = format!("/cache/{show_hash}/{quality}"); let uuid: String = thread_rng() @@ -269,11 +265,11 @@ async fn start_transcode( } } -fn get_cache_path(info: &TranscodeInfo) -> PathBuf { +pub fn get_cache_path(info: &TranscodeInfo) -> PathBuf { return get_cache_path_from_uuid(&info.uuid); } -fn get_cache_path_from_uuid(uuid: &String) -> PathBuf { +pub fn get_cache_path_from_uuid(uuid: &String) -> PathBuf { return PathBuf::from(format!("/cache/{uuid}/", uuid = &uuid)); } @@ -282,97 +278,3 @@ pub struct TranscodeInfo { job: Child, uuid: String, } - -pub struct Transcoder { - running: RwLock>, -} - -impl Transcoder { - pub fn new() -> Transcoder { - Self { - running: RwLock::new(HashMap::new()), - } - } - - pub async fn build_master(&self, _resource: String, _slug: String) -> String { - let mut master = String::from("#EXTM3U\n"); - // TODO: Add transmux (original quality) in this master playlist. - // Transmux should be the first variant since it's used to test bandwidth - // and serve as a hint for preffered variant for clients. - - // TODO: Fetch kyoo to retrieve the max quality and the aspect_ratio - let aspect_ratio = 16.0 / 9.0; - for quality in Quality::iter() { - master.push_str("#EXT-X-STREAM-INF:"); - master.push_str(format!("AVERAGE-BANDWIDTH={},", quality.average_bitrate()).as_str()); - master.push_str(format!("BANDWIDTH={},", quality.max_bitrate()).as_str()); - master.push_str( - format!( - "RESOLUTION={}x{},", - (aspect_ratio * quality.height() as f32).round() as u32, - quality.height() - ) - .as_str(), - ); - master.push_str("CODECS=\"avc1.640028\"\n"); - master.push_str(format!("./{}/index.m3u8\n", quality).as_str()); - } - // TODO: Add audio streams - master - } - - pub async fn transcode( - &self, - client_id: String, - path: String, - quality: Quality, - start_time: u32, - ) -> Result { - // TODO: If the stream is not yet up to start_time (and is far), kill it and restart one at the right time. - // TODO: Clear cache at startup/every X time without use. - // TODO: cache transcoded output for a show/quality and reuse it for every future requests. - if let Some(TranscodeInfo { - show: (old_path, old_qual), - job, - uuid, - .. - }) = self.running.write().unwrap().get_mut(&client_id) - { - if path != *old_path || quality != *old_qual { - // If the job has already ended, interrupt returns an error but we don't care. - _ = job.interrupt(); - } else { - let mut path = get_cache_path_from_uuid(uuid); - path.push("stream.m3u8"); - return std::fs::read_to_string(path); - } - } - - let info = transcode_video(path, quality, start_time).await; - let mut path = get_cache_path(&info); - path.push("stream.m3u8"); - self.running.write().unwrap().insert(client_id, info); - std::fs::read_to_string(path) - } - - // TODO: Use path/quality instead of client_id - pub async fn get_segment( - &self, - client_id: String, - _path: String, - _quality: Quality, - chunk: u32, - ) -> Result { - let hashmap = self.running.read().unwrap(); - let info = hashmap.get(&client_id).ok_or(SegmentError::NoTranscode)?; - - // TODO: Check if ready_time is far enough for this fragment to exist. - let mut path = get_cache_path(&info); - path.push(format!("segments-{0:02}.ts", chunk)); - Ok(path) - } -} - -pub enum SegmentError { - NoTranscode, -} diff --git a/transcoder/src/video.rs b/transcoder/src/video.rs index f6a1c970..e088b981 100644 --- a/transcoder/src/video.rs +++ b/transcoder/src/video.rs @@ -1,10 +1,6 @@ use std::str::FromStr; -use crate::{ - error::ApiError, - transcode::{Quality, Transcoder}, - utils::get_client_id, paths, -}; +use crate::{error::ApiError, paths, state::Transcoder, transcode::Quality, utils::get_client_id}; use actix_files::NamedFile; use actix_web::{get, web, HttpRequest, Result}; From 8ba80e93e32e4a098980232d30a2887cb0e23ccc Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 2 May 2023 02:14:08 +0900 Subject: [PATCH 27/46] Add audio transcoding --- transcoder/src/audio.rs | 6 ++-- transcoder/src/state.rs | 59 +++++++++++++++++++++++++++++++++++-- transcoder/src/transcode.rs | 35 ++++++++++++---------- transcoder/src/utils.rs | 1 + 4 files changed, 79 insertions(+), 22 deletions(-) diff --git a/transcoder/src/audio.rs b/transcoder/src/audio.rs index 2251a802..a8dcc070 100644 --- a/transcoder/src/audio.rs +++ b/transcoder/src/audio.rs @@ -22,7 +22,7 @@ use actix_web::{get, web, Result}; ("audio" = u32, Path, description = "Specify the audio stream you want. For mappings, refer to the audios fields of the /watch response."), ) )] -#[get("/audio/{resource}/{slug}/{audio}/index.m3u8")] +#[get("/{resource}/{slug}/audio/{audio}/index.m3u8")] async fn get_audio_transcoded( query: web::Path<(String, String, u32)>, transcoder: web::Data, @@ -33,7 +33,7 @@ async fn get_audio_transcoded( .map_err(|_| ApiError::NotFound)?; transcoder - .get_transcoded_audio(path, audio) + .transcode_audio(path, audio) .await .map_err(|_| ApiError::InternalError) } @@ -53,7 +53,7 @@ async fn get_audio_transcoded( ("chunk" = u32, Path, description = "The number of the chunk"), ) )] -#[get("/audio/{resource}/{slug}/{audio}/segments-{chunk}.ts")] +#[get("/{resource}/{slug}/audio/{audio}/segments-{chunk}.ts")] async fn get_audio_chunk( query: web::Path<(String, String, u32, u32)>, transcoder: web::Data, diff --git a/transcoder/src/state.rs b/transcoder/src/state.rs index b6dadb62..ff2d18b9 100644 --- a/transcoder/src/state.rs +++ b/transcoder/src/state.rs @@ -6,12 +6,14 @@ use std::sync::RwLock; pub struct Transcoder { running: RwLock>, + audio_jobs: RwLock>, } impl Transcoder { pub fn new() -> Transcoder { Self { running: RwLock::new(HashMap::new()), + audio_jobs: RwLock::new(Vec::new()), } } @@ -24,6 +26,7 @@ impl Transcoder { // TODO: Fetch kyoo to retrieve the max quality and the aspect_ratio let aspect_ratio = 16.0 / 9.0; for quality in Quality::iter() { + // Doc: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/creating_a_multivariant_playlist master.push_str("#EXT-X-STREAM-INF:"); master.push_str(format!("AVERAGE-BANDWIDTH={},", quality.average_bitrate()).as_str()); master.push_str(format!("BANDWIDTH={},", quality.max_bitrate()).as_str()); @@ -35,10 +38,26 @@ impl Transcoder { ) .as_str(), ); - master.push_str("CODECS=\"avc1.640028\"\n"); + master.push_str("CODECS=\"avc1.640028\","); + // With multiple audio qualities, maybe switch qualities depending on the video quality. + master.push_str("AUDIO=\"audio\"\n"); master.push_str(format!("./{}/index.m3u8\n", quality).as_str()); } - // TODO: Add audio streams + // TODO: Fetch audio stream list/metadata from kyoo. + for audio in vec![0] { + // Doc: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/adding_alternate_media_to_a_playlist + master.push_str("#EXT-X-MEDIA:TYPE=AUDIO,"); + // The group-id allows to distinguish multiple qualities from multiple variants. + // We could create another quality set and use group-ids hiqual and lowqual. + master.push_str("GROUP-ID=\"audio\","); + // master.push_str(format!("LANGUAGE=\"{}\",", "eng").as_str()); + master.push_str(format!("NAME=\"{}\",", "Default").as_str()); + // TODO: Support aac5.1 (and specify the number of channel bellow) + // master.push_str(format!("CHANNELS=\"{}\",", 2).as_str()); + master.push_str("DEFAULT=YES,"); + master.push_str(format!("URI=\"./audio/{}/index.m3u8\"\n", audio).as_str()); + } + master } @@ -87,11 +106,45 @@ impl Transcoder { let hashmap = self.running.read().unwrap(); let info = hashmap.get(&client_id).ok_or(SegmentError::NoTranscode)?; - // TODO: Check if ready_time is far enough for this fragment to exist. + // If the segment is in the playlist file, it is available so we don't need to check that. let mut path = get_cache_path(&info); path.push(format!("segments-{0:02}.ts", chunk)); Ok(path) } + + pub async fn transcode_audio( + &self, + path: String, + audio: u32, + ) -> Result { + let mut stream = PathBuf::from(get_audio_path(&path, audio)); + stream.push("stream.m3u8"); + + if !self + .audio_jobs + .read() + .unwrap() + .contains(&(path.clone(), audio)) + { + // TODO: If two concurrent requests for the same audio came, the first one will + // initialize the transcode and wait for the second segment while the second will use + // the same transcode but not wait and retrieve a potentially invalid playlist file. + self.audio_jobs.write().unwrap().push((path.clone(), audio)); + transcode_audio(path, audio).await; + } + std::fs::read_to_string(stream) + } + + pub async fn get_audio_segment( + &self, + path: String, + audio: u32, + chunk: u32, + ) -> Result { + let mut path = PathBuf::from(get_audio_path(&path, audio)); + path.push(format!("segments-{0:02}.ts", chunk)); + Ok(path) + } } pub enum SegmentError { diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 2e692dd1..13fc3532 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -168,20 +168,14 @@ fn get_transcode_video_quality_args(quality: &Quality, segment_time: u32) -> Vec .collect() } -pub async fn transcode_audio(path: String, audio: u32) -> TranscodeInfo { - let mut hasher = DefaultHasher::new(); - path.hash(&mut hasher); - audio.hash(&mut hasher); - let hash = hasher.finish(); - - let child = start_transcode( +pub async fn transcode_audio(path: String, audio: u32) { + start_transcode( &path, - &format!("/cache/{hash}"), + &get_audio_path(&path, audio), get_transcode_audio_args(audio), 0, ) .await; - todo!() } pub async fn transcode_video(path: String, quality: Quality, start_time: u32) -> TranscodeInfo { @@ -193,7 +187,6 @@ pub async fn transcode_video(path: String, quality: Quality, start_time: u32) -> .map(char::from) .collect(); let out_dir = format!("/cache/{uuid}"); - std::fs::create_dir(&out_dir).expect("Could not create cache directory"); let child = start_transcode( &path, @@ -215,6 +208,8 @@ async fn start_transcode( encode_args: Vec, start_time: u32, ) -> Child { + std::fs::create_dir(&out_dir).expect("Could not create cache directory"); + let mut cmd = Command::new("ffmpeg"); cmd.args(&["-progress", "pipe:1"]) .arg("-nostats") @@ -248,16 +243,16 @@ async fn start_transcode( let value = &value[1..]; // Can't use ms since ms and us are both set to us /shrug if key == "out_time_us" { - tx.send(value.parse::().unwrap() / 1_000_000).unwrap(); + let _ = tx.send(value.parse::().unwrap() / 1_000_000); } - // TODO: maybe store speed too. } } }); // Wait for 1.5 * segment time after start_time to be ready. loop { - rx.changed().await.unwrap(); + // TODO: Create a better error handling for here. + rx.changed().await.expect("Invalid audio index."); let ready_time = *rx.borrow(); if ready_time >= (1.5 * SEGMENT_TIME as f32) as u32 + start_time { return child; @@ -265,6 +260,14 @@ async fn start_transcode( } } +pub fn get_audio_path(path: &String, audio: u32) -> String { + let mut hasher = DefaultHasher::new(); + path.hash(&mut hasher); + audio.hash(&mut hasher); + let hash = hasher.finish(); + format!("/cache/{hash:x}") +} + pub fn get_cache_path(info: &TranscodeInfo) -> PathBuf { return get_cache_path_from_uuid(&info.uuid); } @@ -274,7 +277,7 @@ pub fn get_cache_path_from_uuid(uuid: &String) -> PathBuf { } pub struct TranscodeInfo { - show: (String, Quality), - job: Child, - uuid: String, + pub show: (String, Quality), + pub job: Child, + pub uuid: String, } diff --git a/transcoder/src/utils.rs b/transcoder/src/utils.rs index 9b254bf4..a35a87db 100644 --- a/transcoder/src/utils.rs +++ b/transcoder/src/utils.rs @@ -43,6 +43,7 @@ impl Signalable for Child { } pub fn get_client_id(req: HttpRequest) -> Result { + // return Ok(String::from("1234")); req.headers().get("x-client-id") .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), }) .map(|x| x.to_str().unwrap().to_string()) From 95133deeb05d417c4ee1681ae1022e297aad0cb5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 5 May 2023 01:49:20 +0900 Subject: [PATCH 28/46] Add audio menu and rework qualities menu --- .../src/Kyoo.Abstractions/Models/WatchItem.cs | 19 +-- front/packages/models/src/login.ts | 1 - .../models/src/resources/watch-item.ts | 11 +- front/packages/primitives/src/menu.tsx | 2 +- .../src/player/components/right-buttons.tsx | 28 ++-- front/packages/ui/src/player/state.tsx | 29 ++-- front/packages/ui/src/player/video.tsx | 17 ++- front/packages/ui/src/player/video.web.tsx | 126 +++++++++++++++--- front/translations/en.json | 5 +- front/translations/fr.json | 5 +- 10 files changed, 166 insertions(+), 77 deletions(-) diff --git a/back/src/Kyoo.Abstractions/Models/WatchItem.cs b/back/src/Kyoo.Abstractions/Models/WatchItem.cs index 9a5d49fd..13e61ae0 100644 --- a/back/src/Kyoo.Abstractions/Models/WatchItem.cs +++ b/back/src/Kyoo.Abstractions/Models/WatchItem.cs @@ -141,23 +141,14 @@ namespace Kyoo.Abstractions.Models /// public ICollection Chapters { get; set; } - string _Type => IsMovie ? "movie" : "episode"; + [SerializeIgnore] + private string _Type => IsMovie ? "movie" : "episode"; /// - public object Link => new[] + public object Link => new { - new { Name = "Pristine", Link = $"/video/{_Type}/{Slug}/direct", Type = "direct" }, - new { Name = "Original", Link = $"/video/{_Type}/{Slug}/original/index.m3u8", Type = "transmux" }, - new { Name = "Auto", Link = $"/video/{_Type}/{Slug}/master.m3u8", Type = "transcode-auto" }, - - new { Name = "8K", Link = $"/video/{_Type}/{Slug}/8k/index.m3u8", Type = "transcode", }, - new { Name = "4K", Link = $"/video/{_Type}/{Slug}/4k/index.m3u8", Type = "transcode" }, - new { Name = "1440p", Link = $"/video/{_Type}/{Slug}/1440p/index.m3u8", Type = "transcode" }, - new { Name = "1080p", Link = $"/video/{_Type}/{Slug}/1080p/index.m3u8", Type = "transcode" }, - new { Name = "720p", Link = $"/video/{_Type}/{Slug}/720p/index.m3u8", Type = "transcode" }, - new { Name = "480p", Link = $"/video/{_Type}/{Slug}/480p/index.m3u8", Type = "transcode" }, - new { Name = "360p", Link = $"/video/{_Type}/{Slug}/360p/index.m3u8", Type = "transcode" }, - new { Name = "240p", Link = $"/video/{_Type}/{Slug}/240p/index.m3u8", Type = "transcode" }, + Direct = $"/video/{_Type}/{Slug}/direct", + Hls = $"/video/{_Type}/{Slug}/master.m3u8", }; /// diff --git a/front/packages/models/src/login.ts b/front/packages/models/src/login.ts index 513170c2..6ade564a 100644 --- a/front/packages/models/src/login.ts +++ b/front/packages/models/src/login.ts @@ -78,7 +78,6 @@ export const getTokenWJ = async (cookies?: string): Promise<[string, Token] | [n export const getToken = async (cookies?: string): Promise => (await getTokenWJ(cookies))[0] - export const logout = async () =>{ deleteSecureItem("auth") } diff --git a/front/packages/models/src/resources/watch-item.ts b/front/packages/models/src/resources/watch-item.ts index f5761fb4..76995234 100644 --- a/front/packages/models/src/resources/watch-item.ts +++ b/front/packages/models/src/resources/watch-item.ts @@ -156,13 +156,10 @@ const WatchMovieP = z.preprocess( /** * The links to the videos of this watch item. */ - link: z.array( - z.object({ - name: z.string(), - link: z.string().transform(imageFn), - type: z.enum(["direct", "transmux", "transcode-auto", "transcode"]) - }), - ), + link: z.object({ + direct: z.string().transform(imageFn), + hls: z.string().transform(imageFn), + }), }), ); diff --git a/front/packages/primitives/src/menu.tsx b/front/packages/primitives/src/menu.tsx index ffc52b86..a1c4b4ba 100644 --- a/front/packages/primitives/src/menu.tsx +++ b/front/packages/primitives/src/menu.tsx @@ -43,7 +43,7 @@ const Menu = ({ ...props }: { Trigger: ComponentType; - children: ReactNode | ReactNode[] | null; + children?: ReactNode | ReactNode[] | null; onMenuOpen?: () => void; onMenuClose?: () => void; } & Omit) => { diff --git a/front/packages/ui/src/player/components/right-buttons.tsx b/front/packages/ui/src/player/components/right-buttons.tsx index fdd9f8e1..e64a7208 100644 --- a/front/packages/ui/src/player/components/right-buttons.tsx +++ b/front/packages/ui/src/player/components/right-buttons.tsx @@ -28,8 +28,10 @@ import ClosedCaption from "@material-symbols/svg-400/rounded/closed_caption-fill import Fullscreen from "@material-symbols/svg-400/rounded/fullscreen-fill.svg"; import FullscreenExit from "@material-symbols/svg-400/rounded/fullscreen_exit-fill.svg"; import SettingsIcon from "@material-symbols/svg-400/rounded/settings-fill.svg"; +import MusicNote from "@material-symbols/svg-400/rounded/music_note-fill.svg"; import { Stylable, useYoshiki } from "yoshiki/native"; -import { fullscreenAtom, qualityAtom, subtitleAtom } from "../state"; +import { fullscreenAtom, subtitleAtom } from "../state"; +import { AudiosMenu, QualitiesMenu } from "../video"; export const RightButtons = ({ subtitles, @@ -49,7 +51,6 @@ export const RightButtons = ({ const { t } = useTranslation(); const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom); const setSubAtom = useSetAtom(subtitleAtom); - const [quality, setQuality] = useAtom(qualityAtom); const [selectedSubtitle, setSubtitle] = useState(undefined); useEffect(() => { @@ -64,7 +65,7 @@ export const RightButtons = ({ return ( - {subtitles && ( + {subtitles && subtitles.length && ( )} - + - {qualities?.map((x) => ( - setQuality(x.name)} - /> - ))} - + /> {Platform.OS === "web" && ( ("Pristine"); +export enum PlayMode { + Direct, + Hls, +} +export const playModeAtom = atom(PlayMode.Direct); export const bufferedAtom = atom(0); export const durationAtom = atom(undefined); @@ -78,8 +83,8 @@ export const Video = memo(function _Video({ const ref = useRef(null); const [isPlaying, setPlay] = useAtom(playAtom); const setLoad = useSetAtom(loadAtom); - const [source, setSource] = useState(null); - const [quality, setQuality] = useAtom(qualityAtom); + const [source, setSource] = useState(null); + const [mode, setPlayMode] = useAtom(playModeAtom); const publicProgress = useAtomValue(publicProgressAtom); const setPrivateProgress = useSetAtom(privateProgressAtom); @@ -91,11 +96,11 @@ export const Video = memo(function _Video({ useLayoutEffect(() => { // Reset the state when a new video is loaded. - setSource(links?.find(x => x.name == quality) ?? null) + setSource((mode === PlayMode.Direct ? links?.direct : links?.hls) ?? null); setLoad(true); setPrivateProgress(0); setPlay(true); - }, [quality, links, setLoad, setPrivateProgress, setPlay]); + }, [mode, links, setLoad, setPrivateProgress, setPlay]); const volume = useAtomValue(volumeAtom); const isMuted = useAtomValue(mutedAtom); @@ -112,12 +117,12 @@ export const Video = memo(function _Video({ const subtitle = useAtomValue(subtitleAtom); - if (!source) return null; + if (!source || !links) return null; return ( { - if (source.type === "direct") - setQuality(links?.find(x => x.type == "transmux")!.name!) - - // TODO: Replace transcode with transcode-auto when supported. - if (source.type === "transmux") - setQuality(links?.find(x => x.type == "transcode")!.name!) - + if (mode == PlayMode.Direct) + setPlayMode(PlayMode.Hls); }} - // TODO: textTracks: external subtitles /> ); diff --git a/front/packages/ui/src/player/video.tsx b/front/packages/ui/src/player/video.tsx index 8b5645d9..8094c4bb 100644 --- a/front/packages/ui/src/player/video.tsx +++ b/front/packages/ui/src/player/video.tsx @@ -25,12 +25,25 @@ declare module "react-native-video" { onMediaUnsupported?: () => void; } export type VideoProps = Omit & { - source: { uri: string } & WatchItem["link"][0]; + source: { uri: string; hls: string }; }; } export * from "react-native-video"; -import { Font, WatchItem } from "@kyoo/models"; +import { Font } from "@kyoo/models"; +import { IconButton, Menu } from "@kyoo/primitives"; +import { ComponentProps } from "react"; import Video from "react-native-video"; export default Video; + +// TODO: Implement those for mobile. + +type CustomMenu = ComponentProps>>; +export const AudiosMenu = (props: CustomMenu) => { + return ; +}; + +export const QualitiesMenu = (props: CustomMenu) => { + return ; +}; diff --git a/front/packages/ui/src/player/video.web.tsx b/front/packages/ui/src/player/video.web.tsx index 19e473ca..5be9e03e 100644 --- a/front/packages/ui/src/player/video.web.tsx +++ b/front/packages/ui/src/player/video.web.tsx @@ -26,15 +26,19 @@ import { useImperativeHandle, useLayoutEffect, useRef, + useReducer, + ComponentProps, } from "react"; import { VideoProps } from "react-native-video"; -import { useAtomValue, useSetAtom } from "jotai"; +import { useAtomValue, useSetAtom, useAtom } from "jotai"; import { useYoshiki } from "yoshiki"; import SubtitleOctopus from "libass-wasm"; -import { playAtom, subtitleAtom } from "./state"; +import { playAtom, PlayMode, playModeAtom, subtitleAtom } from "./state"; import Hls from "hls.js"; +import { useTranslation } from "react-i18next"; +import { Menu } from "@kyoo/primitives"; -let hls: Hls | null = null; +let hls: Hls = null!; function uuidv4(): string { // @ts-ignore I have no clue how this works, thanks https://stackoverflow.com/questions/105034/how-do-i-create-a-guid-uuid @@ -45,6 +49,17 @@ function uuidv4(): string { let client_id = typeof window === "undefined" ? "ssr" : uuidv4(); +const initHls = async () => { + if (hls !== null) return; + const token = await getToken(); + hls = new Hls({ + xhrSetup: (xhr) => { + if (token) xhr.setRequestHeader("Authorization", `Bearer: {token}`); + xhr.setRequestHeader("X-CLIENT-ID", client_id); + }, + }); +}; + const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function _Video( { source, @@ -62,8 +77,8 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function }, forwaredRef, ) { - const { uri, type } = source; const ref = useRef(null); + const oldHls = useRef(null); const { css } = useYoshiki(); useImperativeHandle( @@ -78,7 +93,7 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function useEffect(() => { if (paused) ref.current?.pause(); - else ref.current?.play().catch(() => {}); + else ref.current?.play().catch(() => { }); }, [paused]); useEffect(() => { if (!ref.current || !volume) return; @@ -89,33 +104,39 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function const subtitle = useAtomValue(subtitleAtom); useSubtitle(ref, subtitle, fonts); + useLayoutEffect(() => { (async () => { - if (!ref?.current || !uri || !type) return; - // TODO: Use hls.js even for safari or handle XHR requests with tokens,auto... - if (type === "direct" || ref.current.canPlayType("application/vnd.apple.mpegurl")) { - ref.current.src = uri; + await initHls(); + // Still load the hls source to list available qualities. + // Note: This may ask the server to transmux the audio/video by loading the index.m3u8 + hls.loadSource(source.hls); + })(); + }, [source.hls]); + useLayoutEffect(() => { + (async () => { + if (!ref?.current || !source.uri) return; + await initHls(); + if (oldHls.current !== source.hls) { + // Still load the hls source to list available qualities. + // Note: This may ask the server to transmux the audio/video by loading the index.m3u8 + hls.loadSource(source.hls); + oldHls.current = source.hls; + } + if (!source.uri.endsWith(".m3u8")) { + hls.detachMedia(); + ref.current.src = source.uri; } else { - if (hls === null) { - const token = await getToken(); - hls = new Hls({ - xhrSetup: (xhr) => { - if (token) xhr.setRequestHeader("Authorization", `Bearer: {token}`); - xhr.setRequestHeader("X-CLIENT-ID", client_id); - }, - }); - } - hls.loadSource(uri); hls.attachMedia(ref.current); // TODO: Enable custom XHR for tokens hls.on(Hls.Events.MANIFEST_LOADED, async () => { try { await ref.current?.play(); - } catch {} + } catch { } }); } })(); - }, [uri, type]); + }, [source.uri, source.hls]); const setPlay = useSetAtom(playAtom); useEffect(() => { @@ -221,3 +242,66 @@ const useSubtitle = (player: RefObject, value: Track | null, f } }, [player, value, fonts]); }; + +export const AudiosMenu = (props: ComponentProps) => { + if (!hls || hls.audioTracks.length < 2) return null; + return ( + + {hls.audioTracks.map((x, i) => ( + (hls!.audioTrack = i)} + /> + ))} + + ); +}; + +export const QualitiesMenu = (props: ComponentProps) => { + const { t } = useTranslation(); + const [mode, setPlayMode] = useAtom(playModeAtom); + const [_, rerender] = useReducer((x) => x + 1, 0); + + useEffect(() => { + if (!hls) return; + hls.on(Hls.Events.LEVEL_SWITCHED, rerender); + return () => hls!.off(Hls.Events.LEVEL_SWITCHED, rerender); + }); + + return ( + + setPlayMode(PlayMode.Direct)} + /> + = 0 + ? `${t("player.auto")} (${hls.levels[hls.currentLevel].height}p)` + : t("player.auto") + } + selected={hls?.autoLevelEnabled && mode === PlayMode.Hls} + onSelect={() => { + setPlayMode(PlayMode.Hls); + if (hls) hls.nextLevel = -1; + }} + /> + {hls?.levels + .map((x, i) => ( + { + setPlayMode(PlayMode.Hls); + hls!.nextLevel = i; + }} + /> + )) + .reverse()} + + ); +}; diff --git a/front/translations/en.json b/front/translations/en.json index a4be8051..b5c470c6 100644 --- a/front/translations/en.json +++ b/front/translations/en.json @@ -44,9 +44,12 @@ "mute": "Toggle mute", "volume": "Volume", "quality": "Quality", + "audios": "Audio", "subtitles": "Subtitles", "subtitle-none": "None", - "fullscreen": "Fullscreen" + "fullscreen": "Fullscreen", + "direct": "Pristine", + "auto": "Auto" }, "search": { "empty": "No result found. Try a different query." diff --git a/front/translations/fr.json b/front/translations/fr.json index 50372024..6d5aa62d 100644 --- a/front/translations/fr.json +++ b/front/translations/fr.json @@ -44,9 +44,12 @@ "mute": "Muet", "volume": "Volume", "quality": "Qualité", + "audios": "Audio", "subtitles": "Sous titres", "subtitle-none": "Aucun", - "fullscreen": "Plein-écran" + "fullscreen": "Plein-écran", + "direct": "Pristine", + "auto": "Auto" }, "search": { "empty": "Aucun résultat trouvé. Essayer avec une autre recherche." From 954909fecdeaea2f6d63bf82f6a7dc5d740f4f74 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 5 May 2023 01:56:49 +0900 Subject: [PATCH 29/46] Format code --- transcoder/src/audio.rs | 6 +----- transcoder/src/utils.rs | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/transcoder/src/audio.rs b/transcoder/src/audio.rs index a8dcc070..6052f8a1 100644 --- a/transcoder/src/audio.rs +++ b/transcoder/src/audio.rs @@ -1,8 +1,4 @@ -use crate::{ - error::ApiError, - paths, - state::Transcoder, -}; +use crate::{error::ApiError, paths, state::Transcoder}; use actix_files::NamedFile; use actix_web::{get, web, Result}; diff --git a/transcoder/src/utils.rs b/transcoder/src/utils.rs index a35a87db..5e27aed8 100644 --- a/transcoder/src/utils.rs +++ b/transcoder/src/utils.rs @@ -48,4 +48,3 @@ pub fn get_client_id(req: HttpRequest) -> Result { .ok_or(ApiError::BadRequest { error: String::from("Missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)."), }) .map(|x| x.to_str().unwrap().to_string()) } - From f42eaeb953f4ee7fea9bb3dfe5dea0262771ec9c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 5 May 2023 02:08:28 +0900 Subject: [PATCH 30/46] Fix duplicated code issue --- front/packages/ui/src/player/video.web.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/front/packages/ui/src/player/video.web.tsx b/front/packages/ui/src/player/video.web.tsx index 5be9e03e..26c7cfe0 100644 --- a/front/packages/ui/src/player/video.web.tsx +++ b/front/packages/ui/src/player/video.web.tsx @@ -105,14 +105,6 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function useSubtitle(ref, subtitle, fonts); - useLayoutEffect(() => { - (async () => { - await initHls(); - // Still load the hls source to list available qualities. - // Note: This may ask the server to transmux the audio/video by loading the index.m3u8 - hls.loadSource(source.hls); - })(); - }, [source.hls]); useLayoutEffect(() => { (async () => { if (!ref?.current || !source.uri) return; From f7f40be9569dda05676f0d718667c335ac793110 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 25 May 2023 15:30:30 +0900 Subject: [PATCH 31/46] Implement a naive identify invoking mediainfo --- transcoder/Cargo.lock | 7 ++++ transcoder/Cargo.toml | 1 + transcoder/src/identify.rs | 77 ++++++++++++++++++++++++++++++++----- transcoder/src/main.rs | 2 +- transcoder/src/transcode.rs | 9 ++++- 5 files changed, 85 insertions(+), 11 deletions(-) diff --git a/transcoder/Cargo.lock b/transcoder/Cargo.lock index ae6510ff..abec39ae 100644 --- a/transcoder/Cargo.lock +++ b/transcoder/Cargo.lock @@ -673,6 +673,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "language-tags" version = "0.3.2" @@ -1290,6 +1296,7 @@ dependencies = [ "actix-files", "actix-web", "derive_more", + "json", "rand", "reqwest", "serde", diff --git a/transcoder/Cargo.toml b/transcoder/Cargo.toml index 88d9c1d7..23d66708 100644 --- a/transcoder/Cargo.toml +++ b/transcoder/Cargo.toml @@ -12,3 +12,4 @@ rand = "0.8.5" derive_more = "0.99.17" reqwest = { version = "0.11.16", default_features = false, features = ["json", "rustls-tls"] } utoipa = { version = "3", features = ["actix_extras"] } +json = "0.12.4" diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index 00ddd1a4..db54ed8a 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -1,4 +1,6 @@ use serde::Serialize; +use std::str; +use tokio::process::Command; use utoipa::ToSchema; use crate::transcode::Quality; @@ -20,7 +22,7 @@ pub struct VideoTrack { /// The codec of this stream (defined as the RFC 6381). codec: String, /// The language of this stream (as a ISO-639-2 language code) - language: String, + language: Option, /// The max quality of this video track. quality: Quality, /// The width of the video stream @@ -28,10 +30,7 @@ pub struct VideoTrack { /// The height of the video stream height: u32, /// The average bitrate of the video in bytes/s - average_bitrate: u32, - // TODO: Figure out if this is doable - /// The max bitrate of the video in bytes/s - max_bitrate: u32, + bitrate: u32, } #[derive(Serialize, ToSchema)] @@ -39,9 +38,9 @@ pub struct Track { /// The index of this track on the media. index: u32, /// The title of the stream. - title: String, + title: Option, /// The language of this stream (as a ISO-639-2 language code) - language: String, + language: Option, /// The codec of this stream. codec: String, /// Is this stream the default one of it's type? @@ -60,6 +59,66 @@ pub struct Chapter { name: String, // TODO: add a type field for Opening, Credits... } -pub fn identify(_path: String) -> Result { - todo!() +pub async fn identify(path: String) -> Result { + let mediainfo = Command::new("mediainfo") + .arg("--Output=JSON") + .arg("--Language=raw") + .arg(path) + .output() + .await?; + assert!(mediainfo.status.success()); + let output = json::parse(str::from_utf8(mediainfo.stdout.as_slice()).unwrap()).unwrap(); + + let general = output["media"]["tracks"] + .members() + .find(|x| x["@type"] == "General") + .unwrap(); + + Ok(MediaInfo { + length: general["Duration"].as_f32().unwrap(), + container: general["Format"].as_str().unwrap().to_string(), + video: { + let v = output["media"]["tracks"] + .members() + .find(|x| x["@type"] == "Video") + .expect("File without video found. This is not supported"); + VideoTrack { + // This codec is not in the right format (does not include bitdepth...). + codec: v["Format"].as_str().unwrap().to_string(), + language: v["Language"].as_str().map(|x| x.to_string()), + quality: Quality::from_height(v["Height"].as_u32().unwrap()), + width: v["Width"].as_u32().unwrap(), + height: v["Height"].as_u32().unwrap(), + bitrate: v["BitRate"].as_u32().unwrap(), + } + }, + audios: output["media"]["tracks"] + .members() + .filter(|x| x["@type"] == "Audio") + .map(|a| Track { + index: a["StreamOrder"].as_u32().unwrap(), + title: a["Title"].as_str().map(|x| x.to_string()), + language: a["Language"].as_str().map(|x| x.to_string()), + // TODO: format is invalid. Channels count missing... + codec: a["Format"].as_str().unwrap().to_string(), + default: a["Default"] == "Yes", + forced: a["Forced"] == "No", + }) + .collect(), + subtitles: output["media"]["tracks"] + .members() + .filter(|x| x["@type"] == "Text") + .map(|a| Track { + index: a["StreamOrder"].as_u32().unwrap(), + title: a["Title"].as_str().map(|x| x.to_string()), + language: a["Language"].as_str().map(|x| x.to_string()), + // TODO: format is invalid. Channels count missing... + codec: a["Format"].as_str().unwrap().to_string(), + default: a["Default"] == "Yes", + forced: a["Forced"] == "No", + }) + .collect(), + fonts: vec![], + chapters: vec![], + }) } diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index e841d629..769a83d5 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -93,7 +93,7 @@ async fn identify_resource( .await .map_err(|_| ApiError::NotFound)?; - identify(path).map(|info| Json(info)).map_err(|e| { + identify(path).await.map(|info| Json(info)).map_err(|e| { eprintln!("Unhandled error occured while transcoding: {}", e); ApiError::InternalError }) diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 13fc3532..730d3331 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -14,7 +14,7 @@ use tokio::sync::watch; const SEGMENT_TIME: u32 = 10; -#[derive(PartialEq, Eq, Serialize, Display)] +#[derive(PartialEq, Eq, Serialize, Display, Clone, Copy)] pub enum Quality { #[display(fmt = "240p")] P240, @@ -95,6 +95,13 @@ impl Quality { Self::Original => panic!("Original quality must be handled specially"), } } + + pub fn from_height(height: u32) -> Self { + Self::iter() + .find(|x| x.height() <= height) + .unwrap_or(&Quality::P240) + .clone() + } } #[derive(Debug, PartialEq, Eq)] From 64d4ee9168443fc919e9cc3fda2bae47a2e191b1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 26 May 2023 00:36:52 +0900 Subject: [PATCH 32/46] Implement chapter detection --- transcoder/src/identify.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index db54ed8a..54e03cd6 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -119,6 +119,32 @@ pub async fn identify(path: String) -> Result { }) .collect(), fonts: vec![], - chapters: vec![], + chapters: output["media"]["tracks"] + .members() + .find(|x| x["@type"] == "Menu") + .map(|x| { + std::iter::zip(x["extra"].entries(), x["extra"].entries().skip(1)) + .map(|((start, name), (end, _))| Chapter { + start: time_to_seconds(start), + end: time_to_seconds(end), + name: name.as_str().unwrap().to_string(), + }) + .collect() + }) + .unwrap_or(vec![]), }) } + +fn time_to_seconds(time: &str) -> f32 { + let splited: Vec = time + .split('_') + .skip(1) + .map(|x| x.parse().unwrap()) + .collect(); + let hours = splited[0]; + let minutes = splited[1]; + let seconds = splited[2]; + let ms = splited[3]; + + (hours * 60. + minutes) * 60. + seconds + ms / 1000. +} From dbe85322eae8691ddd50b318e67f66284f4a8dc9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 26 May 2023 01:05:51 +0900 Subject: [PATCH 33/46] Fix mediainfo number parsing --- transcoder/Dockerfile | 2 +- transcoder/Dockerfile.dev | 2 +- transcoder/src/identify.rs | 35 +++++++++++++++++++++-------------- transcoder/src/main.rs | 2 +- transcoder/src/transcode.rs | 2 +- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/transcoder/Dockerfile b/transcoder/Dockerfile index eba3c847..39bbf803 100644 --- a/transcoder/Dockerfile +++ b/transcoder/Dockerfile @@ -11,7 +11,7 @@ COPY src src RUN cargo install --path . FROM alpine -RUN apk add --no-cache ffmpeg +RUN apk add --no-cache ffmpeg mediainfo COPY --from=builder /usr/local/cargo/bin/transcoder ./transcoder EXPOSE 7666 diff --git a/transcoder/Dockerfile.dev b/transcoder/Dockerfile.dev index 1d77ee35..c08026a0 100644 --- a/transcoder/Dockerfile.dev +++ b/transcoder/Dockerfile.dev @@ -1,5 +1,5 @@ FROM rust:alpine -RUN apk add --no-cache musl-dev ffmpeg +RUN apk add --no-cache musl-dev ffmpeg mediainfo RUN cargo install cargo-watch WORKDIR /app diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index 54e03cd6..dc83f3e3 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -1,5 +1,6 @@ +use json::JsonValue; use serde::Serialize; -use std::str; +use std::str::{self, FromStr}; use tokio::process::Command; use utoipa::ToSchema; @@ -65,20 +66,26 @@ pub async fn identify(path: String) -> Result { .arg("--Language=raw") .arg(path) .output() - .await?; + .await + .expect("Error running the mediainfo command"); assert!(mediainfo.status.success()); let output = json::parse(str::from_utf8(mediainfo.stdout.as_slice()).unwrap()).unwrap(); - let general = output["media"]["tracks"] + let general = output["media"]["track"] .members() .find(|x| x["@type"] == "General") .unwrap(); + fn parse(v: &JsonValue) -> Option { + v.as_str().and_then(|x| x.parse::().ok()) + } + + // TODO: Every number is wrapped by "" in mediainfo json's mode so the number parsing is wrong. Ok(MediaInfo { - length: general["Duration"].as_f32().unwrap(), + length: parse::(&general["Duration"]).unwrap(), container: general["Format"].as_str().unwrap().to_string(), video: { - let v = output["media"]["tracks"] + let v = output["media"]["track"] .members() .find(|x| x["@type"] == "Video") .expect("File without video found. This is not supported"); @@ -86,17 +93,17 @@ pub async fn identify(path: String) -> Result { // This codec is not in the right format (does not include bitdepth...). codec: v["Format"].as_str().unwrap().to_string(), language: v["Language"].as_str().map(|x| x.to_string()), - quality: Quality::from_height(v["Height"].as_u32().unwrap()), - width: v["Width"].as_u32().unwrap(), - height: v["Height"].as_u32().unwrap(), - bitrate: v["BitRate"].as_u32().unwrap(), + quality: Quality::from_height(parse::(&v["Height"]).unwrap()), + width: parse::(&v["Width"]).unwrap(), + height: parse::(&v["Height"]).unwrap(), + bitrate: parse::(&v["BitRate"]).unwrap(), } }, - audios: output["media"]["tracks"] + audios: output["media"]["track"] .members() .filter(|x| x["@type"] == "Audio") .map(|a| Track { - index: a["StreamOrder"].as_u32().unwrap(), + index: parse::(&a["StreamOrder"]).unwrap(), title: a["Title"].as_str().map(|x| x.to_string()), language: a["Language"].as_str().map(|x| x.to_string()), // TODO: format is invalid. Channels count missing... @@ -105,11 +112,11 @@ pub async fn identify(path: String) -> Result { forced: a["Forced"] == "No", }) .collect(), - subtitles: output["media"]["tracks"] + subtitles: output["media"]["track"] .members() .filter(|x| x["@type"] == "Text") .map(|a| Track { - index: a["StreamOrder"].as_u32().unwrap(), + index: parse::(&a["StreamOrder"]).unwrap(), title: a["Title"].as_str().map(|x| x.to_string()), language: a["Language"].as_str().map(|x| x.to_string()), // TODO: format is invalid. Channels count missing... @@ -119,7 +126,7 @@ pub async fn identify(path: String) -> Result { }) .collect(), fonts: vec![], - chapters: output["media"]["tracks"] + chapters: output["media"]["track"] .members() .find(|x| x["@type"] == "Menu") .map(|x| { diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 769a83d5..42f05aa6 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -94,7 +94,7 @@ async fn identify_resource( .map_err(|_| ApiError::NotFound)?; identify(path).await.map(|info| Json(info)).map_err(|e| { - eprintln!("Unhandled error occured while transcoding: {}", e); + eprintln!("Unhandled error occured while identifing the resource: {}", e); ApiError::InternalError }) } diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 730d3331..634a7f63 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -98,7 +98,7 @@ impl Quality { pub fn from_height(height: u32) -> Self { Self::iter() - .find(|x| x.height() <= height) + .find(|x| x.height() >= height) .unwrap_or(&Quality::P240) .clone() } From 63f7a75394cd178ca240d75c9466b214291f2ebe Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 28 May 2023 00:29:55 +0900 Subject: [PATCH 34/46] Add transmux to the hls playlist --- transcoder/src/identify.rs | 45 +++++++++++++++++----------------- transcoder/src/main.rs | 12 ++++++--- transcoder/src/state.rs | 50 ++++++++++++++++++++++++++------------ 3 files changed, 66 insertions(+), 41 deletions(-) diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index dc83f3e3..78ee8ec7 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -9,55 +9,55 @@ use crate::transcode::Quality; #[derive(Serialize, ToSchema)] pub struct MediaInfo { /// The length of the media in seconds. - length: f32, - container: String, - video: VideoTrack, - audios: Vec, - subtitles: Vec, - fonts: Vec, - chapters: Vec, + pub length: f32, + pub container: String, + pub video: VideoTrack, + pub audios: Vec, + pub subtitles: Vec, + pub fonts: Vec, + pub chapters: Vec, } #[derive(Serialize, ToSchema)] pub struct VideoTrack { /// The codec of this stream (defined as the RFC 6381). - codec: String, + pub codec: String, /// The language of this stream (as a ISO-639-2 language code) - language: Option, + pub language: Option, /// The max quality of this video track. - quality: Quality, + pub quality: Quality, /// The width of the video stream - width: u32, + pub width: u32, /// The height of the video stream - height: u32, + pub height: u32, /// The average bitrate of the video in bytes/s - bitrate: u32, + pub bitrate: u32, } #[derive(Serialize, ToSchema)] pub struct Track { /// The index of this track on the media. - index: u32, + pub index: u32, /// The title of the stream. - title: Option, + pub title: Option, /// The language of this stream (as a ISO-639-2 language code) - language: Option, + pub language: Option, /// The codec of this stream. - codec: String, + pub codec: String, /// Is this stream the default one of it's type? - default: bool, + pub default: bool, /// Is this stream tagged as forced? (useful only for subtitles) - forced: bool, + pub forced: bool, } #[derive(Serialize, ToSchema)] pub struct Chapter { /// The start time of the chapter (in second from the start of the episode). - start: f32, + pub start: f32, /// The end time of the chapter (in second from the start of the episode). - end: f32, + pub end: f32, /// The name of this chapter. This should be a human-readable name that could be presented to the user. - name: String, // TODO: add a type field for Opening, Credits... + pub name: String, // TODO: add a type field for Opening, Credits... } pub async fn identify(path: String) -> Result { @@ -80,7 +80,6 @@ pub async fn identify(path: String) -> Result { v.as_str().and_then(|x| x.parse::().ok()) } - // TODO: Every number is wrapped by "" in mediainfo json's mode so the number parsing is wrong. Ok(MediaInfo { length: parse::(&general["Duration"]).unwrap(), container: general["Format"].as_str().unwrap().to_string(), diff --git a/transcoder/src/main.rs b/transcoder/src/main.rs index 42f05aa6..949c7841 100644 --- a/transcoder/src/main.rs +++ b/transcoder/src/main.rs @@ -68,7 +68,10 @@ async fn get_master( transcoder: web::Data, ) -> Result { let (resource, slug) = query.into_inner(); - Ok(transcoder.build_master(resource, slug).await) + transcoder + .build_master(resource, slug) + .await + .ok_or(ApiError::InternalError) } /// Identify @@ -84,7 +87,7 @@ async fn get_master( ("slug" = String, Path, description = "The slug of the movie/episode."), ) )] -#[get("/{resource}/{slug}/identify")] +#[get("/{resource}/{slug}/info")] async fn identify_resource( query: web::Path<(String, String)>, ) -> Result, ApiError> { @@ -94,7 +97,10 @@ async fn identify_resource( .map_err(|_| ApiError::NotFound)?; identify(path).await.map(|info| Json(info)).map_err(|e| { - eprintln!("Unhandled error occured while identifing the resource: {}", e); + eprintln!( + "Unhandled error occured while identifing the resource: {}", + e + ); ApiError::InternalError }) } diff --git a/transcoder/src/state.rs b/transcoder/src/state.rs index ff2d18b9..2cdbc26b 100644 --- a/transcoder/src/state.rs +++ b/transcoder/src/state.rs @@ -1,5 +1,7 @@ -use crate::transcode::*; +use crate::identify::identify; +use crate::paths::get_path; use crate::utils::Signalable; +use crate::transcode::*; use std::collections::HashMap; use std::path::PathBuf; use std::sync::RwLock; @@ -17,15 +19,30 @@ impl Transcoder { } } - pub async fn build_master(&self, _resource: String, _slug: String) -> String { + pub async fn build_master(&self, resource: String, slug: String) -> Option { let mut master = String::from("#EXTM3U\n"); - // TODO: Add transmux (original quality) in this master playlist. - // Transmux should be the first variant since it's used to test bandwidth - // and serve as a hint for preffered variant for clients. + let path = get_path(resource, slug).await.ok()?; + let info = identify(path).await.ok()?; - // TODO: Fetch kyoo to retrieve the max quality and the aspect_ratio - let aspect_ratio = 16.0 / 9.0; - for quality in Quality::iter() { + // TODO: Only add this if transmuxing is possible. + if true { + // Doc: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/creating_a_multivariant_playlist + master.push_str("#EXT-X-STREAM-INF:"); + master.push_str(format!("AVERAGE-BANDWIDTH={},", info.video.bitrate).as_str()); + // Approximate a bit more because we can't know the maximum bandwidth. + master.push_str(format!("BANDWIDTH={},", (info.video.bitrate as f32 * 1.2) as u32).as_str()); + master.push_str( + format!("RESOLUTION={}x{},", info.video.width, info.video.height).as_str(), + ); + // TODO: Find codecs in the RFC 6381 format. + // master.push_str("CODECS=\"avc1.640028\","); + // TODO: With multiple audio qualities, maybe switch qualities depending on the video quality. + master.push_str("AUDIO=\"audio\"\n"); + master.push_str(format!("./{}/index.m3u8\n", Quality::Original).as_str()); + } + + let aspect_ratio = info.video.width as f32 / info.video.height as f32; + for quality in Quality::iter().filter(|x| x.height() <= info.video.quality.height()) { // Doc: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/creating_a_multivariant_playlist master.push_str("#EXT-X-STREAM-INF:"); master.push_str(format!("AVERAGE-BANDWIDTH={},", quality.average_bitrate()).as_str()); @@ -39,26 +56,29 @@ impl Transcoder { .as_str(), ); master.push_str("CODECS=\"avc1.640028\","); - // With multiple audio qualities, maybe switch qualities depending on the video quality. + // TODO: With multiple audio qualities, maybe switch qualities depending on the video quality. master.push_str("AUDIO=\"audio\"\n"); master.push_str(format!("./{}/index.m3u8\n", quality).as_str()); } - // TODO: Fetch audio stream list/metadata from kyoo. - for audio in vec![0] { + for audio in info.audios { // Doc: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/adding_alternate_media_to_a_playlist master.push_str("#EXT-X-MEDIA:TYPE=AUDIO,"); // The group-id allows to distinguish multiple qualities from multiple variants. // We could create another quality set and use group-ids hiqual and lowqual. master.push_str("GROUP-ID=\"audio\","); - // master.push_str(format!("LANGUAGE=\"{}\",", "eng").as_str()); - master.push_str(format!("NAME=\"{}\",", "Default").as_str()); + if let Some(language) = audio.language { + master.push_str(format!("LANGUAGE=\"{}\",", language).as_str()); + } + if let Some(title) = audio.title { + master.push_str(format!("NAME=\"{}\",", title).as_str()); + } // TODO: Support aac5.1 (and specify the number of channel bellow) // master.push_str(format!("CHANNELS=\"{}\",", 2).as_str()); master.push_str("DEFAULT=YES,"); - master.push_str(format!("URI=\"./audio/{}/index.m3u8\"\n", audio).as_str()); + master.push_str(format!("URI=\"./audio/{}/index.m3u8\"\n", audio.index).as_str()); } - master + Some(master) } pub async fn transcode( From 92b5e3394042c34aa332ab6760b73eddd9a3a10c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 30 May 2023 13:06:59 +0900 Subject: [PATCH 35/46] Fix ctranscoder dockerfiles --- back/Dockerfile | 2 +- back/Dockerfile.dev | 8 +++++--- back/src/Kyoo.Transcoder/tests/test_main.c | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/back/Dockerfile b/back/Dockerfile index 9c88a9b3..c4c5935a 100644 --- a/back/Dockerfile +++ b/back/Dockerfile @@ -1,4 +1,4 @@ -FROM gcc:latest as transcoder +FROM mcr.microsoft.com/dotnet/sdk:6.0 as transcoder RUN apt-get update && apt-get install -y cmake make libavutil-dev libavcodec-dev libavformat-dev WORKDIR /transcoder COPY src/Kyoo.Transcoder . diff --git a/back/Dockerfile.dev b/back/Dockerfile.dev index a0aac4ec..34882c9f 100644 --- a/back/Dockerfile.dev +++ b/back/Dockerfile.dev @@ -1,5 +1,6 @@ -FROM gcc:latest as transcoder -RUN apt-get update && apt-get install -y cmake make libavutil-dev libavcodec-dev libavformat-dev +FROM mcr.microsoft.com/dotnet/sdk:6.0 as transcoder +# Using the dotnet sdk as a base image to have the same versions of glibc/ffmpeg +RUN apt-get update && apt-get install -y gcc cmake make libavutil-dev libavcodec-dev libavformat-dev WORKDIR /transcoder COPY src/Kyoo.Transcoder . RUN cmake . && make -j @@ -20,11 +21,12 @@ COPY src/Kyoo.Swagger/Kyoo.Swagger.csproj src/Kyoo.Swagger/Kyoo.Swagger.csproj COPY tests/Kyoo.Tests/Kyoo.Tests.csproj tests/Kyoo.Tests/Kyoo.Tests.csproj RUN dotnet restore -COPY --from=transcoder /transcoder/libtranscoder.so /app/out/bin/Kyoo.Host/Debug/net6.0/libtranscoder.so +COPY --from=transcoder /transcoder/libtranscoder.so /lib/libtranscoder.so WORKDIR /kyoo EXPOSE 5000 ENV DOTNET_USE_POLLING_FILE_WATCHER 1 HEALTHCHECK --interval=5s CMD curl --fail http://localhost:5000/health || exit +# ENV LD_DEBUG libs CMD dotnet watch run --no-restore --project /app/src/Kyoo.Host diff --git a/back/src/Kyoo.Transcoder/tests/test_main.c b/back/src/Kyoo.Transcoder/tests/test_main.c index 653c3015..fba69376 100644 --- a/back/src/Kyoo.Transcoder/tests/test_main.c +++ b/back/src/Kyoo.Transcoder/tests/test_main.c @@ -69,4 +69,4 @@ int main(int argc, char **argv) %s info video_path - Test info prober\n\ %s transmux video_path m3u8_output_file - Test transmuxing\n", argv[0], argv[0]); return 0; -} \ No newline at end of file +} From e6a6131a144c69f2e482ffb7adb5f0eef7733498 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 30 May 2023 14:42:15 +0900 Subject: [PATCH 36/46] Fix time update on the web player --- front/packages/ui/src/player/components/right-buttons.tsx | 2 +- front/packages/ui/src/player/video.web.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/front/packages/ui/src/player/components/right-buttons.tsx b/front/packages/ui/src/player/components/right-buttons.tsx index e64a7208..b5fa5146 100644 --- a/front/packages/ui/src/player/components/right-buttons.tsx +++ b/front/packages/ui/src/player/components/right-buttons.tsx @@ -65,7 +65,7 @@ export const RightButtons = ({ return ( - {subtitles && subtitles.length && ( + {subtitles && subtitles.length > 0 && ( void }, VideoProps>(function if (!ref.current) return; onLoad?.call(null, { duration: ref.current.duration } as any); }} - onProgress={() => { + onTimeUpdate={() => { if (!ref.current) return; onProgress?.call(null, { currentTime: ref.current.currentTime, From 22d8ea8215d579bd17c60d37f4509c3abbff2731 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 30 May 2023 14:43:26 +0900 Subject: [PATCH 37/46] Fix audio index --- transcoder/src/identify.rs | 4 ++-- transcoder/src/transcode.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/transcoder/src/identify.rs b/transcoder/src/identify.rs index 78ee8ec7..d4778133 100644 --- a/transcoder/src/identify.rs +++ b/transcoder/src/identify.rs @@ -102,7 +102,7 @@ pub async fn identify(path: String) -> Result { .members() .filter(|x| x["@type"] == "Audio") .map(|a| Track { - index: parse::(&a["StreamOrder"]).unwrap(), + index: parse::(&a["StreamOrder"]).unwrap() - 1, title: a["Title"].as_str().map(|x| x.to_string()), language: a["Language"].as_str().map(|x| x.to_string()), // TODO: format is invalid. Channels count missing... @@ -115,7 +115,7 @@ pub async fn identify(path: String) -> Result { .members() .filter(|x| x["@type"] == "Text") .map(|a| Track { - index: parse::(&a["StreamOrder"]).unwrap(), + index: parse::(&a["StreamOrder"]).unwrap() - 1, title: a["Title"].as_str().map(|x| x.to_string()), language: a["Language"].as_str().map(|x| x.to_string()), // TODO: format is invalid. Channels count missing... diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 634a7f63..45cb4296 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -140,7 +140,7 @@ fn get_transcode_audio_args(audio_idx: u32) -> Vec { fn get_transcode_video_quality_args(quality: &Quality, segment_time: u32) -> Vec { if *quality == Quality::Original { - return vec!["-map", "0:v:0", "-c:v", "copy"] + return vec!["-map", "0:V:0", "-c:v", "copy"] .iter() .map(|a| a.to_string()) .collect(); @@ -215,7 +215,7 @@ async fn start_transcode( encode_args: Vec, start_time: u32, ) -> Child { - std::fs::create_dir(&out_dir).expect("Could not create cache directory"); + std::fs::create_dir_all(&out_dir).expect("Could not create cache directory"); let mut cmd = Command::new("ffmpeg"); cmd.args(&["-progress", "pipe:1"]) From cec8400145dcc8ac58a70af65766364490ff06a9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 30 May 2023 23:44:56 +0900 Subject: [PATCH 38/46] Disable the transcode with the same quality as the original tramsux --- front/packages/ui/src/player/video.web.tsx | 17 ++++++++++++----- front/translations/en.json | 1 + front/translations/fr.json | 1 + transcoder/src/audio.rs | 8 ++++---- transcoder/src/state.rs | 10 +++++++--- transcoder/src/transcode.rs | 5 +++-- 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/front/packages/ui/src/player/video.web.tsx b/front/packages/ui/src/player/video.web.tsx index 1dd1046f..754fc0c5 100644 --- a/front/packages/ui/src/player/video.web.tsx +++ b/front/packages/ui/src/player/video.web.tsx @@ -34,7 +34,7 @@ import { useAtomValue, useSetAtom, useAtom } from "jotai"; import { useYoshiki } from "yoshiki"; import SubtitleOctopus from "libass-wasm"; import { playAtom, PlayMode, playModeAtom, subtitleAtom } from "./state"; -import Hls from "hls.js"; +import Hls, { Level } from "hls.js"; import { useTranslation } from "react-i18next"; import { Menu } from "@kyoo/primitives"; @@ -58,6 +58,7 @@ const initHls = async () => { xhr.setRequestHeader("X-CLIENT-ID", client_id); }, }); + // hls.currentLevel = hls.startLevel; }; const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function _Video( @@ -262,6 +263,12 @@ export const QualitiesMenu = (props: ComponentProps) => { return () => hls!.off(Hls.Events.LEVEL_SWITCHED, rerender); }); + const levelName = (label: Level, auto?: boolean): string => { + const height = `${label.height}p` + if (auto) return height; + return label.uri.includes("original") ? `${t("player.transmux")} (${height})` : height; + } + return ( ) => { = 0 - ? `${t("player.auto")} (${hls.levels[hls.currentLevel].height}p)` + ? `${t("player.auto")} (${levelName(hls.levels[hls.currentLevel], true)})` : t("player.auto") } selected={hls?.autoLevelEnabled && mode === PlayMode.Hls} onSelect={() => { setPlayMode(PlayMode.Hls); - if (hls) hls.nextLevel = -1; + if (hls) hls.currentLevel = -1; }} /> {hls?.levels .map((x, i) => ( { setPlayMode(PlayMode.Hls); - hls!.nextLevel = i; + hls!.currentLevel = i; }} /> )) diff --git a/front/translations/en.json b/front/translations/en.json index b5c470c6..a3e4d298 100644 --- a/front/translations/en.json +++ b/front/translations/en.json @@ -49,6 +49,7 @@ "subtitle-none": "None", "fullscreen": "Fullscreen", "direct": "Pristine", + "transmux": "Original", "auto": "Auto" }, "search": { diff --git a/front/translations/fr.json b/front/translations/fr.json index 6d5aa62d..e33b041e 100644 --- a/front/translations/fr.json +++ b/front/translations/fr.json @@ -49,6 +49,7 @@ "subtitle-none": "Aucun", "fullscreen": "Plein-écran", "direct": "Pristine", + "transmux": "Original", "auto": "Auto" }, "search": { diff --git a/transcoder/src/audio.rs b/transcoder/src/audio.rs index 6052f8a1..f321cd7f 100644 --- a/transcoder/src/audio.rs +++ b/transcoder/src/audio.rs @@ -28,10 +28,10 @@ async fn get_audio_transcoded( .await .map_err(|_| ApiError::NotFound)?; - transcoder - .transcode_audio(path, audio) - .await - .map_err(|_| ApiError::InternalError) + transcoder.transcode_audio(path, audio).await.map_err(|e| { + eprintln!("Error while transcoding audio: {}", e); + ApiError::InternalError + }) } /// Get audio chunk diff --git a/transcoder/src/state.rs b/transcoder/src/state.rs index 2cdbc26b..6df4deec 100644 --- a/transcoder/src/state.rs +++ b/transcoder/src/state.rs @@ -1,7 +1,7 @@ use crate::identify::identify; use crate::paths::get_path; -use crate::utils::Signalable; use crate::transcode::*; +use crate::utils::Signalable; use std::collections::HashMap; use std::path::PathBuf; use std::sync::RwLock; @@ -30,7 +30,9 @@ impl Transcoder { master.push_str("#EXT-X-STREAM-INF:"); master.push_str(format!("AVERAGE-BANDWIDTH={},", info.video.bitrate).as_str()); // Approximate a bit more because we can't know the maximum bandwidth. - master.push_str(format!("BANDWIDTH={},", (info.video.bitrate as f32 * 1.2) as u32).as_str()); + master.push_str( + format!("BANDWIDTH={},", (info.video.bitrate as f32 * 1.2) as u32).as_str(), + ); master.push_str( format!("RESOLUTION={}x{},", info.video.width, info.video.height).as_str(), ); @@ -42,7 +44,9 @@ impl Transcoder { } let aspect_ratio = info.video.width as f32 / info.video.height as f32; - for quality in Quality::iter().filter(|x| x.height() <= info.video.quality.height()) { + // Do not include a quality with the same height as the original (simpler for automatic + // selection on the client side.) + for quality in Quality::iter().filter(|x| x.height() < info.video.quality.height()) { // Doc: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/creating_a_multivariant_playlist master.push_str("#EXT-X-STREAM-INF:"); master.push_str(format!("AVERAGE-BANDWIDTH={},", quality.average_bitrate()).as_str()); diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index 45cb4296..f00e112d 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -219,7 +219,7 @@ async fn start_transcode( let mut cmd = Command::new("ffmpeg"); cmd.args(&["-progress", "pipe:1"]) - .arg("-nostats") + .args(&["-nostats", "-hide_banner", "-loglevel", "warning"]) .args(&["-ss", start_time.to_string().as_str()]) .args(&["-i", path.as_str()]) .args(&["-f", "hls"]) @@ -250,7 +250,8 @@ async fn start_transcode( let value = &value[1..]; // Can't use ms since ms and us are both set to us /shrug if key == "out_time_us" { - let _ = tx.send(value.parse::().unwrap() / 1_000_000); + // Sometimes, the value is invalid (or negative), default to 0 in those cases + let _ = tx.send(value.parse::().unwrap_or(0) / 1_000_000); } } } From e8eb36284b4e6596d33d4dd2594e543166c5b1c0 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 1 Jun 2023 01:40:44 +0900 Subject: [PATCH 39/46] Fix transcoder ci build --- transcoder/Cargo.lock | 286 ++++++++++++++++++++++++++---------------- transcoder/Dockerfile | 3 +- 2 files changed, 181 insertions(+), 108 deletions(-) diff --git a/transcoder/Cargo.lock b/transcoder/Cargo.lock index abec39ae..d2dd0728 100644 --- a/transcoder/Cargo.lock +++ b/transcoder/Cargo.lock @@ -4,19 +4,19 @@ version = 3 [[package]] name = "actix-codec" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" +checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" dependencies = [ "bitflags", "bytes", "futures-core", "futures-sink", - "log", "memchr", "pin-project-lite", "tokio", "tokio-util", + "tracing", ] [[package]] @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -273,9 +273,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -315,9 +315,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytes" @@ -368,9 +368,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -409,9 +409,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", "miniz_oxide", @@ -502,9 +502,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -513,9 +513,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.16" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -587,9 +587,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -611,9 +611,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" dependencies = [ "http", "hyper", @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -693,9 +693,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.141" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "local-channel" @@ -727,12 +727,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "memchr" @@ -758,23 +755,23 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -789,9 +786,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" [[package]] name = "parking_lot" @@ -813,7 +810,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -842,9 +839,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "ppv-lite86" @@ -878,18 +875,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -935,9 +932,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" dependencies = [ "aho-corasick", "memchr", @@ -946,15 +943,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" -version = "0.11.16" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ "base64", "bytes", @@ -1015,14 +1012,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" dependencies = [ "log", "ring", + "rustls-webpki", "sct", - "webpki", ] [[package]] @@ -1034,6 +1031,16 @@ dependencies = [ "base64", ] +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.13" @@ -1064,29 +1071,29 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.159" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -1169,9 +1176,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -1180,9 +1187,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "itoa", "serde", @@ -1192,15 +1199,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -1222,9 +1229,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", @@ -1234,25 +1241,24 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "socket2", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" dependencies = [ "rustls", "tokio", - "webpki", ] [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -1282,9 +1288,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] @@ -1333,9 +1339,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -1386,7 +1392,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] @@ -1413,9 +1419,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1423,24 +1429,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -1450,9 +1456,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1460,28 +1466,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -1534,7 +1540,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[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.0", ] [[package]] @@ -1543,13 +1558,28 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 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", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -1558,42 +1588,84 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winreg" version = "0.10.1" diff --git a/transcoder/Dockerfile b/transcoder/Dockerfile index 39bbf803..b9c0aa4e 100644 --- a/transcoder/Dockerfile +++ b/transcoder/Dockerfile @@ -4,6 +4,7 @@ WORKDIR /app # FIX: see https://github.com/rust-lang/cargo/issues/2644 RUN mkdir src/ && touch src/lib.rs COPY Cargo.toml Cargo.lock ./ +ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse RUN cargo build RUN rm src/lib.rs @@ -11,7 +12,7 @@ COPY src src RUN cargo install --path . FROM alpine -RUN apk add --no-cache ffmpeg mediainfo +RUN apk add --no-cache ffmpeg COPY --from=builder /usr/local/cargo/bin/transcoder ./transcoder EXPOSE 7666 From 6f4936f6beafbec4ec3f8e4a885b786d740aa7f7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 1 Jun 2023 14:59:48 +0900 Subject: [PATCH 40/46] Fix transcoder docker --- docker-compose.prod.yml | 1 - docker-compose.yml | 1 - transcoder/Dockerfile | 5 +++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 7e72a154..618a3454 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -11,7 +11,6 @@ services: condition: service_healthy volumes: - kyoo:/kyoo - - ./cache:/kyoo/cached - ${LIBRARY_ROOT}:/video front: diff --git a/docker-compose.yml b/docker-compose.yml index 1885e9e5..fc1b6db5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,6 @@ services: condition: service_healthy volumes: - kyoo:/kyoo - - ./cache:/kyoo/cached - ${LIBRARY_ROOT}:/video front: diff --git a/transcoder/Dockerfile b/transcoder/Dockerfile index b9c0aa4e..7971f120 100644 --- a/transcoder/Dockerfile +++ b/transcoder/Dockerfile @@ -1,4 +1,5 @@ -FROM rust as builder +FROM rust:alpine as builder +RUN apk add --no-cache musl-dev WORKDIR /app # FIX: see https://github.com/rust-lang/cargo/issues/2644 @@ -12,7 +13,7 @@ COPY src src RUN cargo install --path . FROM alpine -RUN apk add --no-cache ffmpeg +RUN apk add --no-cache ffmpeg mediainfo musl-dev COPY --from=builder /usr/local/cargo/bin/transcoder ./transcoder EXPOSE 7666 From f49882fb0d8a9d4088a32d7fe3e850036fad6897 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 2 Jun 2023 18:25:51 +0900 Subject: [PATCH 41/46] Fix live hls issues --- front/packages/ui/src/player/state.tsx | 4 ++- front/packages/ui/src/player/video.web.tsx | 29 +++++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/front/packages/ui/src/player/state.tsx b/front/packages/ui/src/player/state.tsx index a350b5e0..838270d3 100644 --- a/front/packages/ui/src/player/state.tsx +++ b/front/packages/ui/src/player/state.tsx @@ -88,6 +88,7 @@ export const Video = memo(function _Video({ const publicProgress = useAtomValue(publicProgressAtom); const setPrivateProgress = useSetAtom(privateProgressAtom); + const setPublicProgress = useSetAtom(publicProgressAtom); const setBuffered = useSetAtom(bufferedAtom); const setDuration = useSetAtom(durationAtom); useEffect(() => { @@ -99,8 +100,9 @@ export const Video = memo(function _Video({ setSource((mode === PlayMode.Direct ? links?.direct : links?.hls) ?? null); setLoad(true); setPrivateProgress(0); + setPublicProgress(0); setPlay(true); - }, [mode, links, setLoad, setPrivateProgress, setPlay]); + }, [mode, links, setLoad, setPrivateProgress, setPublicProgress, setPlay]); const volume = useAtomValue(volumeAtom); const isMuted = useAtomValue(mutedAtom); diff --git a/front/packages/ui/src/player/video.web.tsx b/front/packages/ui/src/player/video.web.tsx index 754fc0c5..3130c2ba 100644 --- a/front/packages/ui/src/player/video.web.tsx +++ b/front/packages/ui/src/player/video.web.tsx @@ -38,7 +38,7 @@ import Hls, { Level } from "hls.js"; import { useTranslation } from "react-i18next"; import { Menu } from "@kyoo/primitives"; -let hls: Hls = null!; +let hls: Hls | null = null; function uuidv4(): string { // @ts-ignore I have no clue how this works, thanks https://stackoverflow.com/questions/105034/how-do-i-create-a-guid-uuid @@ -49,16 +49,20 @@ function uuidv4(): string { let client_id = typeof window === "undefined" ? "ssr" : uuidv4(); -const initHls = async () => { - if (hls !== null) return; +const initHls = async (): Promise => { + if (hls !== null) return hls; const token = await getToken(); hls = new Hls({ xhrSetup: (xhr) => { if (token) xhr.setRequestHeader("Authorization", `Bearer: {token}`); xhr.setRequestHeader("X-CLIENT-ID", client_id); }, + autoStartLoad: false, + // debug: true, + startPosition: 0, }); // hls.currentLevel = hls.startLevel; + return hls; }; const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function _Video( @@ -109,8 +113,12 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function useLayoutEffect(() => { (async () => { if (!ref?.current || !source.uri) return; - await initHls(); - if (oldHls.current !== source.hls) { + if (!hls || oldHls.current !== source.hls) { + // Reinit the hls player when we change track. + if (hls) + hls.destroy(); + hls = null; + hls = await initHls(); // Still load the hls source to list available qualities. // Note: This may ask the server to transmux the audio/video by loading the index.m3u8 hls.loadSource(source.hls); @@ -121,14 +129,23 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function ref.current.src = source.uri; } else { hls.attachMedia(ref.current); - // TODO: Enable custom XHR for tokens + hls.startLoad(0); hls.on(Hls.Events.MANIFEST_LOADED, async () => { try { await ref.current?.play(); } catch { } }); + hls.on(Hls.Events.ERROR, (_, d) => { + console.log("Hls error", d); + if (!d.fatal) return; + onError?.call(null, { + error: { "": "", errorString: d.reason ?? d.err?.message ?? "Unknown hls error" }, + }); + }); } })(); + // onError changes should not restart the playback. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [source.uri, source.hls]); const setPlay = useSetAtom(playAtom); From 735edf15290f69026838c2921dee1107e1d4853b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 2 Jun 2023 18:26:03 +0900 Subject: [PATCH 42/46] Fix multi channels audio issues --- transcoder/src/transcode.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/transcoder/src/transcode.rs b/transcoder/src/transcode.rs index f00e112d..7db5f866 100644 --- a/transcoder/src/transcode.rs +++ b/transcoder/src/transcode.rs @@ -127,12 +127,15 @@ impl FromStr for Quality { } fn get_transcode_audio_args(audio_idx: u32) -> Vec { - // TODO: Support multy audio qualities. + // TODO: Support multi audio qualities. return vec![ "-map".to_string(), format!("0:a:{}", audio_idx), "-c:a".to_string(), "aac".to_string(), + // TODO: Support 5.1 audio streams. + "-ac".to_string(), + "2".to_string(), "-b:a".to_string(), "128k".to_string(), ]; @@ -226,7 +229,7 @@ async fn start_transcode( // Use a .tmp file for segments (.ts files) .args(&["-hls_flags", "temp_file"]) // Cache can't be allowed since switching quality means starting a new encode for now. - // .args(&["-hls_allow_cache", "1"]) + .args(&["-hls_allow_cache", "1"]) // Keep all segments in the list (else only last X are presents, useful for livestreams) .args(&["-hls_list_size", "0"]) .args(&["-hls_time", SEGMENT_TIME.to_string().as_str()]) From 3f928ad5076c016d07da011ca2cfebea3898c05b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 4 Jun 2023 13:51:25 +0900 Subject: [PATCH 43/46] Fix firefox play/pause loop --- front/packages/ui/src/player/video.web.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/front/packages/ui/src/player/video.web.tsx b/front/packages/ui/src/player/video.web.tsx index 3130c2ba..6705905b 100644 --- a/front/packages/ui/src/player/video.web.tsx +++ b/front/packages/ui/src/player/video.web.tsx @@ -97,6 +97,7 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function ); useEffect(() => { + if (!ref.current || paused === ref.current.paused) return; if (paused) ref.current?.pause(); else ref.current?.play().catch(() => { }); }, [paused]); @@ -186,8 +187,9 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function }); } }} - onPlay={() => onPlayPause?.call(null, true)} - onPause={() => onPlayPause?.call(null, false)} + // BUG: If this is enabled, switching to fullscreen or opening a menu make a play/pause loop until firefox crash. + // onPlay={() => onPlayPause?.call(null, true)} + // onPause={() => onPlayPause?.call(null, false)} onEnded={onEnd} {...css({ width: "100%", height: "100%" })} /> From 6a4c2c6aea377a90f817dc9e1e2d8f636686b49a Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 5 Jun 2023 15:41:15 +0900 Subject: [PATCH 44/46] Switch to the pointer event api instead of the pressable api on the player to support firefox android --- .../ui/src/player/components/hover.tsx | 6 ++- front/packages/ui/src/player/index.tsx | 52 +++++++++---------- front/packages/ui/src/player/video.web.tsx | 2 +- 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/front/packages/ui/src/player/components/hover.tsx b/front/packages/ui/src/player/components/hover.tsx index 97718cd4..3c92dba7 100644 --- a/front/packages/ui/src/player/components/hover.tsx +++ b/front/packages/ui/src/player/components/hover.tsx @@ -35,7 +35,7 @@ import { } from "@kyoo/primitives"; import { Chapter, Font, Track, WatchItem } from "@kyoo/models"; import { useAtomValue, useSetAtom, useAtom } from "jotai"; -import { View, ViewProps } from "react-native"; +import { Platform, View, ViewProps } from "react-native"; import { useTranslation } from "react-i18next"; import { percent, rem, useYoshiki } from "yoshiki/native"; import { useRouter } from "solito/router"; @@ -87,7 +87,8 @@ export const Hover = ({ {...css( [ { - position: "absolute", + // Fixed is used because firefox android make the hover disapear under the navigation bar in absolute + position: Platform.OS === "web" ? "fixed" as any : "absolute", bottom: 0, left: 0, right: 0, @@ -106,6 +107,7 @@ export const Hover = ({ marginLeft: { xs: ts(0.5), sm: ts(3) }, flexDirection: "column", flexGrow: 1, + maxWidth: percent(100), })} >

diff --git a/front/packages/ui/src/player/index.tsx b/front/packages/ui/src/player/index.tsx index b264852f..7ab5325b 100644 --- a/front/packages/ui/src/player/index.tsx +++ b/front/packages/ui/src/player/index.tsx @@ -21,7 +21,7 @@ import { QueryIdentifier, QueryPage, WatchItem, WatchItemP, useFetch } from "@kyoo/models"; import { Head } from "@kyoo/primitives"; import { useState, useEffect, ComponentProps } from "react"; -import { Platform, Pressable, StyleSheet } from "react-native"; +import { Platform, Pressable, StyleSheet, View } from "react-native"; import { useTranslation } from "react-i18next"; import { useRouter } from "solito/router"; import { useAtom } from "jotai"; @@ -42,7 +42,7 @@ const mapData = ( data: WatchItem | undefined, previousSlug?: string, nextSlug?: string, -): Partial> => { +): Partial> & { isLoading: boolean } => { if (!data) return { isLoading: true }; return { isLoading: false, @@ -132,12 +132,12 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { data.isMovie ? data.name : data.showTitle + - " " + - episodeDisplayNumber({ - seasonNumber: data.seasonNumber, - episodeNumber: data.episodeNumber, - absoluteNumber: data.absoluteNumber, - }) + " " + + episodeDisplayNumber({ + seasonNumber: data.seasonNumber, + episodeNumber: data.episodeNumber, + absoluteNumber: data.absoluteNumber, + }) } description={data.overview} /> @@ -148,9 +148,9 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { next={next} previous={previous} /> - setMouseMoved(false)} + onPointerLeave={(e) => { if (e.nativeEvent.pointerType === "mouse") setMouseMoved(false) }} {...css({ flexGrow: 1, bg: "black", @@ -158,11 +158,9 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { cursor: displayControls ? "unset" : "none", })} > - { - // TODO: use onPress event to diferenciate touch and click on the web (requires react native web 0.19) - if (Platform.OS !== "web") { + { + if (e.nativeEvent.pointerType !== "mouse") { displayControls ? setMouseMoved(false) : show(); return; } @@ -178,13 +176,7 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { }, 400); setPlay(!isPlaying); }} - {...css([ - StyleSheet.absoluteFillObject, - { - // @ts-ignore Web only - cursor: "unset", - }, - ])} + {...css(StyleSheet.absoluteFillObject)} > + setHover(true)} - // @ts-ignore Web only types - onMouseLeave={() => setHover(false)} + onPointerEnter={(e) => { if (e.nativeEvent.pointerType === "mouse") setHover(true) }} + onPointerLeave={(e) => { if (e.nativeEvent.pointerType === "mouse") setHover(false) }} + onPointerDown={(e) => { + // also handle touch here because if we dont, the area where the hover should be will catch touches + // without openning the hover. + if (e.nativeEvent.pointerType !== "mouse") + displayControls ? setMouseMoved(false) : show(); + }} onMenuOpen={() => setMenuOpen(true)} onMenuClose={() => { // Disable hover since the menu overlay makes the mouseout unreliable. @@ -216,7 +212,7 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { }} show={displayControls} /> - + ); }; diff --git a/front/packages/ui/src/player/video.web.tsx b/front/packages/ui/src/player/video.web.tsx index 6705905b..215f728e 100644 --- a/front/packages/ui/src/player/video.web.tsx +++ b/front/packages/ui/src/player/video.web.tsx @@ -138,7 +138,7 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function }); hls.on(Hls.Events.ERROR, (_, d) => { console.log("Hls error", d); - if (!d.fatal) return; + if (!d.fatal || !hls?.media) return; onError?.call(null, { error: { "": "", errorString: d.reason ?? d.err?.message ?? "Unknown hls error" }, }); From 1d431ecad6909ca5eb256ebeddebb3520e3d3312 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 5 Jun 2023 23:07:34 +0900 Subject: [PATCH 45/46] Add error logs for fullscreen change on the front --- front/packages/ui/src/player/state.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/front/packages/ui/src/player/state.tsx b/front/packages/ui/src/player/state.tsx index 838270d3..993edca9 100644 --- a/front/packages/ui/src/player/state.tsx +++ b/front/packages/ui/src/player/state.tsx @@ -63,7 +63,9 @@ export const fullscreenAtom = atom( set(privateFullscreen, false); screen.orientation.unlock(); } - } catch {} + } catch(e) { + console.error(e); + } }, ); const privateFullscreen = atom(false); From ff163026c7cc9893711e83e3be9997fcc460c273 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 5 Jun 2023 23:25:29 +0900 Subject: [PATCH 46/46] Disable sonarcloud --- .github/workflows/analysis.yml | 89 ---------------------------------- 1 file changed, 89 deletions(-) delete mode 100644 .github/workflows/analysis.yml diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml deleted file mode 100644 index 0b30aa3c..00000000 --- a/.github/workflows/analysis.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: Analysis -on: - push: - branches: - - master - - next - pull_request: - - -jobs: - analysis: - name: Static Analysis - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - - name: Cache SonarCloud packages - uses: actions/cache@v1 - with: - path: ~/sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - - name: Cache SonarCloud scanner - id: cache-sonar-scanner - uses: actions/cache@v1 - with: - path: ~/.sonar/scanner - key: ${{ runner.os }}-sonar-scanner - restore-keys: ${{ runner.os }}-sonar-scanner - - - name: Install SonarCloud scanner - if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' - shell: bash - run: | - cd back - mkdir -p ~/.sonar/scanner - dotnet tool update dotnet-sonarscanner --tool-path ~/.sonar/scanner - - - name: Wait for tests to run (Push) - uses: lewagon/wait-on-check-action@master - if: github.event_name != 'pull_request' - with: - ref: ${{github.ref}} - check-name: "Back tests" - repo-token: ${{secrets.GITHUB_TOKEN}} - running-workflow-name: analysis - allowed-conclusions: success,skipped,cancelled,neutral,failure - - name: Wait for tests to run (PR) - uses: lewagon/wait-on-check-action@master - if: github.event_name == 'pull_request' - with: - ref: ${{github.event.pull_request.head.sha}} - check-name: "Back tests" - repo-token: ${{secrets.GITHUB_TOKEN}} - running-workflow-name: analysis - allowed-conclusions: success,skipped,cancelled,neutral,failure - - - name: Download coverage report - uses: dawidd6/action-download-artifact@v2 - with: - commit: ${{env.COMMIT_SHA}} - workflow: tests.yml - github_token: ${{secrets.GITHUB_TOKEN}} - - - name: Build and analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - shell: bash - run: | - cp -r results.xml/ coverage.xml/ back/ - cd back - find . -name 'coverage.opencover.xml' - dotnet build-server shutdown - - ~/.sonar/scanner/dotnet-sonarscanner begin \ - -k:"AnonymusRaccoon_Kyoo" \ - -o:"anonymus-raccoon" \ - -d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ - -d:sonar.host.url="https://sonarcloud.io" \ - -d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" \ - -d:sonar.cs.vstest.reportsPaths="**/TestOutputResults.xml" - - dotnet build --no-incremental '-p:SkipTranscoder=true' - - ~/.sonar/scanner/dotnet-sonarscanner end -d:sonar.login="${{ secrets.SONAR_TOKEN }}"