aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-20 11:16:25 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-20 15:39:32 +0200
commitf44711b1f494c0fe7a79df72645c926a5f892f68 (patch)
tree09d4bc813b961359be7ca23ee05a24532eeeea9b /pkgs
parentfeat(pkgs/yt): Start the rewrite (diff)
downloadnixos-config-f44711b1f494c0fe7a79df72645c926a5f892f68.zip
fix(pkgs/yt): Improve usability
`yt` has now reached feature-parity with the old `ytcc` based `yt` implementation.
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/by-name/yt/yt/.cargo/config.toml3
-rw-r--r--pkgs/by-name/yt/yt/Cargo.lock256
-rw-r--r--pkgs/by-name/yt/yt/Cargo.toml19
-rw-r--r--pkgs/by-name/yt/yt/flake.lock6
-rw-r--r--pkgs/by-name/yt/yt/flake.nix9
-rw-r--r--pkgs/by-name/yt/yt/libmpv2/libmpv2-sys/build.rs9
-rw-r--r--pkgs/by-name/yt/yt/libmpv2/src/lib.rs83
-rw-r--r--pkgs/by-name/yt/yt/libmpv2/src/mpv/events.rs2
-rwxr-xr-xpkgs/by-name/yt/yt/scripts/mkdb.sh5
-rw-r--r--pkgs/by-name/yt/yt/src/cache/mod.rs6
-rw-r--r--pkgs/by-name/yt/yt/src/cli.rs84
-rw-r--r--pkgs/by-name/yt/yt/src/constants.rs42
-rw-r--r--pkgs/by-name/yt/yt/src/download/mod.rs17
-rw-r--r--pkgs/by-name/yt/yt/src/main.rs19
-rw-r--r--pkgs/by-name/yt/yt/src/select/cmds.rs50
-rw-r--r--pkgs/by-name/yt/yt/src/select/mod.rs56
-rw-r--r--pkgs/by-name/yt/yt/src/select/selection_file/display.rs4
-rw-r--r--pkgs/by-name/yt/yt/src/select/selection_file/duration.rs52
-rw-r--r--pkgs/by-name/yt/yt/src/select/selection_file/mod.rs82
-rw-r--r--pkgs/by-name/yt/yt/src/storage/video_database/downloader.rs52
-rw-r--r--pkgs/by-name/yt/yt/src/storage/video_database/extractor_hash.rs113
-rw-r--r--pkgs/by-name/yt/yt/src/storage/video_database/getters.rs96
-rw-r--r--pkgs/by-name/yt/yt/src/storage/video_database/schema.sql10
-rw-r--r--pkgs/by-name/yt/yt/src/storage/video_database/setters.rs76
-rw-r--r--pkgs/by-name/yt/yt/src/update/mod.rs60
-rw-r--r--pkgs/by-name/yt/yt/src/watch/events.rs160
-rw-r--r--pkgs/by-name/yt/yt/src/watch/mod.rs80
-rw-r--r--pkgs/by-name/yt/yt/yt_dlp/src/lib.rs6
-rw-r--r--pkgs/by-name/yt/yt/yt_dlp/src/wrapper/info_json.rs3
29 files changed, 1049 insertions, 411 deletions
diff --git a/pkgs/by-name/yt/yt/.cargo/config.toml b/pkgs/by-name/yt/yt/.cargo/config.toml
index 6c88c6ba..e5151a56 100644
--- a/pkgs/by-name/yt/yt/.cargo/config.toml
+++ b/pkgs/by-name/yt/yt/.cargo/config.toml
@@ -2,5 +2,4 @@
PYO3_PYTHON = "/nix/store/7xzk119acyws2c4ysygdv66l0grxkr39-python3-3.11.9-env/bin/python3"
[build]
-# rustflags = ["-C", "link-args=-fuse-ld=mold,--no-rosegment"]
-rustflags = ["-Clink-arg=-fuse-ld=mold", "-Clink-arg=-Wl,--no-rosegment"]
+rustflags = ["-Clink-arg=-fuse-ld=mold", "-Ctarget-cpu=native"]
diff --git a/pkgs/by-name/yt/yt/Cargo.lock b/pkgs/by-name/yt/yt/Cargo.lock
index 1342e29b..c949c313 100644
--- a/pkgs/by-name/yt/yt/Cargo.lock
+++ b/pkgs/by-name/yt/yt/Cargo.lock
@@ -27,7 +27,7 @@ dependencies = [
"getrandom",
"once_cell",
"version_check",
- "zerocopy 0.7.35",
+ "zerocopy",
]
[[package]]
@@ -123,9 +123,9 @@ checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
[[package]]
name = "arrayvec"
-version = "0.7.4"
+version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "atoi"
@@ -188,7 +188,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
- "syn 2.0.72",
+ "syn 2.0.75",
"which",
]
@@ -243,15 +243,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.7.0"
+version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fca2be1d5c43812bae364ee3f30b3afcb7877cf59f4aeb94c66f313a41d2fac9"
+checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]]
name = "cc"
-version = "1.1.7"
+version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc"
+checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
+dependencies = [
+ "shlex",
+]
[[package]]
name = "cexpr"
@@ -304,9 +307,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.13"
+version = "4.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
+checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
dependencies = [
"clap_builder",
"clap_derive",
@@ -314,9 +317,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.13"
+version = "4.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
+checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
dependencies = [
"anstream",
"anstyle",
@@ -333,7 +336,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
]
[[package]]
@@ -362,15 +365,15 @@ checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
[[package]]
name = "core-foundation-sys"
-version = "0.8.6"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
-version = "0.2.12"
+version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
dependencies = [
"libc",
]
@@ -579,7 +582,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
]
[[package]]
@@ -686,6 +689,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
+name = "hermit-abi"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+
+[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -753,9 +762,9 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.3.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
+checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
dependencies = [
"equivalent",
"hashbrown",
@@ -769,11 +778,11 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "is-terminal"
-version = "0.4.12"
+version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
+checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
dependencies = [
- "hermit-abi",
+ "hermit-abi 0.4.0",
"libc",
"windows-sys 0.52.0",
]
@@ -786,9 +795,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
-version = "0.10.5"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
@@ -801,9 +810,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
-version = "0.3.69"
+version = "0.3.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
dependencies = [
"wasm-bindgen",
]
@@ -825,9 +834,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
-version = "0.2.155"
+version = "0.2.157"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86"
[[package]]
name = "libloading"
@@ -936,11 +945,11 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
+checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
dependencies = [
- "hermit-abi",
+ "hermit-abi 0.3.9",
"libc",
"wasi",
"windows-sys 0.52.0",
@@ -1005,9 +1014,9 @@ dependencies = [
[[package]]
name = "object"
-version = "0.36.2"
+version = "0.36.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e"
+checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
dependencies = [
"memchr",
]
@@ -1063,6 +1072,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
+name = "pest"
+version = "2.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95"
+dependencies = [
+ "memchr",
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.75",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f"
+dependencies = [
+ "once_cell",
+ "pest",
+ "sha2",
+]
+
+[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1109,11 +1163,11 @@ checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
[[package]]
name = "ppv-lite86"
-version = "0.2.18"
+version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
- "zerocopy 0.6.6",
+ "zerocopy",
]
[[package]]
@@ -1123,7 +1177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
dependencies = [
"proc-macro2",
- "syn 2.0.72",
+ "syn 2.0.75",
]
[[package]]
@@ -1182,7 +1236,7 @@ dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
]
[[package]]
@@ -1195,7 +1249,7 @@ dependencies = [
"proc-macro2",
"pyo3-build-config",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
]
[[package]]
@@ -1257,9 +1311,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.10.5"
+version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
@@ -1343,29 +1397,29 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
-version = "1.0.204"
+version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
+checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.204"
+version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
+checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
]
[[package]]
name = "serde_json"
-version = "1.0.121"
+version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609"
+checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
dependencies = [
"itoa",
"memchr",
@@ -1716,9 +1770,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.72"
+version = "2.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
+checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
dependencies = [
"proc-macro2",
"quote",
@@ -1733,14 +1787,15 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tempfile"
-version = "3.10.1"
+version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
+checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
dependencies = [
"cfg-if",
"fastrand",
+ "once_cell",
"rustix",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -1769,7 +1824,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
]
[[package]]
@@ -1799,9 +1854,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.39.2"
+version = "1.39.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
+checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
dependencies = [
"backtrace",
"bytes",
@@ -1822,7 +1877,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
]
[[package]]
@@ -1856,7 +1911,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
]
[[package]]
@@ -1869,12 +1924,28 @@ dependencies = [
]
[[package]]
+name = "trinitry"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f814008587cd653ef1f92f9caf321e86a6f53899ec118fd50eaed55974863a40"
+dependencies = [
+ "pest",
+ "pest_derive",
+]
+
+[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
+name = "ucd-trie"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
+
+[[package]]
name = "unicode-bidi"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1969,34 +2040,35 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
-version = "0.2.92"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
dependencies = [
"cfg-if",
+ "once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.92"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.92"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2004,22 +2076,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.92"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.92"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
[[package]]
name = "which"
@@ -2045,11 +2117,11 @@ dependencies = [
[[package]]
name = "winapi-util"
-version = "0.1.8"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -2080,6 +2152,15 @@ dependencies = [
]
[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2225,6 +2306,7 @@ dependencies = [
"stderrlog",
"tempfile",
"tokio",
+ "trinitry",
"url",
"xdg",
"yt_dlp",
@@ -2243,32 +2325,12 @@ dependencies = [
[[package]]
name = "zerocopy"
-version = "0.6.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6"
-dependencies = [
- "byteorder",
- "zerocopy-derive 0.6.6",
-]
-
-[[package]]
-name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
- "zerocopy-derive 0.7.35",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.6.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.72",
+ "byteorder",
+ "zerocopy-derive",
]
[[package]]
@@ -2279,7 +2341,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.72",
+ "syn 2.0.75",
]
[[package]]
diff --git a/pkgs/by-name/yt/yt/Cargo.toml b/pkgs/by-name/yt/yt/Cargo.toml
index d1e40cf0..78b9ab2c 100644
--- a/pkgs/by-name/yt/yt/Cargo.toml
+++ b/pkgs/by-name/yt/yt/Cargo.toml
@@ -10,20 +10,21 @@ anyhow = "1.0.86"
blake3 = "1.5.3"
chrono = { version = "0.4.38", features = ["now"] }
chrono-humanize = "0.2.3"
-clap = { version = "4.5.13", features = ["derive"] }
+clap = { version = "4.5.16", features = ["derive"] }
futures = "0.3.30"
log = "0.4.22"
-regex = "1.10.5"
-serde = { version = "1.0.204", features = ["derive"] }
-serde_json = "1.0.121"
+regex = "1.10.6"
+serde = { version = "1.0.208", features = ["derive"] }
+serde_json = "1.0.125"
sqlx = { version = "0.7.4", features = ["runtime-tokio", "sqlite"] }
stderrlog = "0.6.0"
-tempfile = "3.10.1"
-tokio = { version = "1.39.2", features = ["rt-multi-thread", "macros", "process"] }
+tempfile = "3.12.0"
+tokio = { version = "1.39.3", features = ["rt-multi-thread", "macros", "process", "time"] }
url = { version = "2.5.2", features = ["serde"] }
xdg = "2.5.2"
yt_dlp = { path = "./yt_dlp/" }
libmpv2 = { path = "./libmpv2" }
+trinitry = { version = "0.2.2" }
[[bin]]
name = "yt"
@@ -31,3 +32,9 @@ name = "yt"
[profile.profiling]
inherits = "release"
debug = true
+
+[profile.release]
+lto = true
+codegen-units = 1
+panic = "abort"
+split-debuginfo = "off"
diff --git a/pkgs/by-name/yt/yt/flake.lock b/pkgs/by-name/yt/yt/flake.lock
index cf99a35f..122b5b09 100644
--- a/pkgs/by-name/yt/yt/flake.lock
+++ b/pkgs/by-name/yt/yt/flake.lock
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1722421184,
- "narHash": "sha256-/DJBI6trCeVnasdjUo9pbnodCLZcFqnVZiLUfqLH4jA=",
+ "lastModified": 1723637854,
+ "narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "9f918d616c5321ad374ae6cb5ea89c9e04bf3e58",
+ "rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
"type": "github"
},
"original": {
diff --git a/pkgs/by-name/yt/yt/flake.nix b/pkgs/by-name/yt/yt/flake.nix
index ea9b0de4..5ced0fbd 100644
--- a/pkgs/by-name/yt/yt/flake.nix
+++ b/pkgs/by-name/yt/yt/flake.nix
@@ -25,10 +25,15 @@
];
in {
devShells.default = pkgs.mkShell {
- env = {
+ env = let
+ clang_version =
+ pkgs.lib.versions.major
+ pkgs.llvmPackages_latest.clang-unwrapped.version;
+ in {
FFMPEG_LOCATION = "${pkgs.lib.getExe pkgs.ffmpeg}";
LIBCLANG_PATH = "${pkgs.llvmPackages_latest.clang-unwrapped.lib}/lib/libclang.so";
- C_INCLUDE_PATH = "${pkgs.llvmPackages_latest.clang-unwrapped.lib}/lib/clang/18/include";
+ LIBCLANG_INCLUDE_PATH = "${pkgs.llvmPackages_latest.clang-unwrapped.lib}/lib/clang/${clang_version}/include";
+ C_INCLUDE_PATH = "${pkgs.glibc.dev}/include";
};
inherit buildInputs nativeBuildInputs;
diff --git a/pkgs/by-name/yt/yt/libmpv2/libmpv2-sys/build.rs b/pkgs/by-name/yt/yt/libmpv2/libmpv2-sys/build.rs
index 5361e198..1380b58d 100644
--- a/pkgs/by-name/yt/yt/libmpv2/libmpv2-sys/build.rs
+++ b/pkgs/by-name/yt/yt/libmpv2/libmpv2-sys/build.rs
@@ -12,7 +12,14 @@ fn main() {
.opaque_type("mpv_handle")
.opaque_type("mpv_render_context")
.enable_function_attribute_detection()
- .clang_arg("-fretain-comments-from-system-headers")
+ .clang_args(&[
+ "-fretain-comments-from-system-headers",
+ &format!(
+ "--include-directory={}",
+ env::var("LIBCLANG_INCLUDE_PATH").unwrap()
+ ),
+ "--verbose",
+ ])
.generate_comments(true)
.generate()
.expect("Unable to generate bindings");
diff --git a/pkgs/by-name/yt/yt/libmpv2/src/lib.rs b/pkgs/by-name/yt/yt/libmpv2/src/lib.rs
index 4b267e24..199d6ee9 100644
--- a/pkgs/by-name/yt/yt/libmpv2/src/lib.rs
+++ b/pkgs/by-name/yt/yt/libmpv2/src/lib.rs
@@ -19,6 +19,7 @@
#![allow(non_upper_case_globals)]
+use std::fmt::Display;
use std::os::raw as ctype;
pub const MPV_CLIENT_API_MAJOR: ctype::c_ulong = 2;
@@ -86,11 +87,79 @@ pub mod mpv_log_level {
}
/// The reason a file stopped.
-pub use libmpv2_sys::mpv_end_file_reason as EndFileReason;
-pub mod mpv_end_file_reason {
- pub use libmpv2_sys::mpv_end_file_reason_MPV_END_FILE_REASON_EOF as Eof;
- pub use libmpv2_sys::mpv_end_file_reason_MPV_END_FILE_REASON_ERROR as Error;
- pub use libmpv2_sys::mpv_end_file_reason_MPV_END_FILE_REASON_QUIT as Quit;
- pub use libmpv2_sys::mpv_end_file_reason_MPV_END_FILE_REASON_REDIRECT as Redirect;
- pub use libmpv2_sys::mpv_end_file_reason_MPV_END_FILE_REASON_STOP as Stop;
+#[derive(Debug, Clone, Copy)]
+pub enum EndFileReason {
+ /**
+ * The end of file was reached. Sometimes this may also happen on
+ * incomplete or corrupted files, or if the network connection was
+ * interrupted when playing a remote file. It also happens if the
+ * playback range was restricted with --end or --frames or similar.
+ */
+ Eof,
+
+ /**
+ * Playback was stopped by an external action (e.g. playlist controls).
+ */
+ Stop,
+
+ /**
+ * Playback was stopped by the quit command or player shutdown.
+ */
+ Quit,
+
+ /**
+ * Some kind of error happened that lead to playback abort. Does not
+ * necessarily happen on incomplete or broken files (in these cases, both
+ * MPV_END_FILE_REASON_ERROR or MPV_END_FILE_REASON_EOF are possible).
+ *
+ * mpv_event_end_file.error will be set.
+ */
+ Error,
+
+ /**
+ * The file was a playlist or similar. When the playlist is read, its
+ * entries will be appended to the playlist after the entry of the current
+ * file, the entry of the current file is removed, and a MPV_EVENT_END_FILE
+ * event is sent with reason set to MPV_END_FILE_REASON_REDIRECT. Then
+ * playback continues with the playlist contents.
+ * Since API version 1.18.
+ */
+ Redirect,
+}
+
+impl From<libmpv2_sys::mpv_end_file_reason> for EndFileReason {
+ fn from(value: libmpv2_sys::mpv_end_file_reason) -> Self {
+ match value {
+ 0 => Self::Eof,
+ 2 => Self::Stop,
+ 3 => Self::Quit,
+ 4 => Self::Error,
+ 5 => Self::Redirect,
+ _ => unreachable!("Other enum variants do not exist yet"),
+ }
+ }
+}
+
+impl Display for EndFileReason {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ EndFileReason::Eof => f.write_str("The end of file was reached.")?,
+ EndFileReason::Error => {
+ f.write_str(
+ "Playback was stopped by an external action (e.g. playlist controls).",
+ )?;
+ }
+ EndFileReason::Quit => {
+ f.write_str("Playback was stopped by the quit command or player shutdown.")?;
+ }
+ EndFileReason::Redirect => {
+ f.write_str("Some kind of error happened that lead to playback abort.")?;
+ }
+ EndFileReason::Stop => {
+ f.write_str("The file was a playlist or similar.")?;
+ }
+ }
+
+ Ok(())
+ }
}
diff --git a/pkgs/by-name/yt/yt/libmpv2/src/mpv/events.rs b/pkgs/by-name/yt/yt/libmpv2/src/mpv/events.rs
index b6513890..115c23fc 100644
--- a/pkgs/by-name/yt/yt/libmpv2/src/mpv/events.rs
+++ b/pkgs/by-name/yt/yt/libmpv2/src/mpv/events.rs
@@ -270,7 +270,7 @@ impl EventContext {
if let Err(e) = mpv_err((), end_file.error) {
Some(Err(e))
} else {
- Some(Ok(Event::EndFile(end_file.reason as _)))
+ Some(Ok(Event::EndFile(end_file.reason.into())))
}
}
mpv_event_id::FileLoaded => Some(Ok(Event::FileLoaded)),
diff --git a/pkgs/by-name/yt/yt/scripts/mkdb.sh b/pkgs/by-name/yt/yt/scripts/mkdb.sh
index 307e4459..f4246a49 100755
--- a/pkgs/by-name/yt/yt/scripts/mkdb.sh
+++ b/pkgs/by-name/yt/yt/scripts/mkdb.sh
@@ -1,9 +1,10 @@
#!/usr/bin/env sh
root="$(dirname "$0")/.."
+db="$root/target/database.sqlite"
-rm "$root/target/database.sqlite"
+[ -f "$db" ] && rm "$db"
-sqlite3 "$root/target/database.sqlite" <"$root/src/storage/video_database/schema.sql"
+sqlite3 "$db" <"$root/src/storage/video_database/schema.sql"
# vim: ft=sh
diff --git a/pkgs/by-name/yt/yt/src/cache/mod.rs b/pkgs/by-name/yt/yt/src/cache/mod.rs
index b60cda89..f9715e40 100644
--- a/pkgs/by-name/yt/yt/src/cache/mod.rs
+++ b/pkgs/by-name/yt/yt/src/cache/mod.rs
@@ -6,16 +6,14 @@ use log::info;
use crate::{
app::App,
storage::video_database::{
- downloader::set_video_cache_path, getters::get_videos, setters::set_video_status, Video,
- VideoStatus,
+ downloader::set_video_cache_path, getters::get_videos, Video, VideoStatus,
},
};
async fn invalidate_video(app: &App, video: &Video) -> Result<()> {
info!("Invalidating cache of video: '{}'", video.title);
- set_video_status(app, &video.extractor_hash, VideoStatus::Watch, None).await?;
- set_video_cache_path(app, &video, None).await?;
+ set_video_cache_path(app, &video.extractor_hash, None).await?;
Ok(())
}
diff --git a/pkgs/by-name/yt/yt/src/cli.rs b/pkgs/by-name/yt/yt/src/cli.rs
index 66b1d4c1..799c9ee4 100644
--- a/pkgs/by-name/yt/yt/src/cli.rs
+++ b/pkgs/by-name/yt/yt/src/cli.rs
@@ -1,12 +1,18 @@
use std::path::PathBuf;
-use clap::{ArgAction, Parser, Subcommand};
+use chrono::NaiveDate;
+use clap::{ArgAction, Args, Parser, Subcommand};
use url::Url;
+use crate::{
+ select::selection_file::duration::Duration,
+ storage::video_database::extractor_hash::LazyExtractorHash,
+};
+
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
/// An command line interface to select, download and watch videos
-pub struct Args {
+pub struct CliArgs {
#[command(subcommand)]
/// The subcommand to execute [default: select]
pub command: Option<Command>,
@@ -51,8 +57,11 @@ pub enum Command {
command: CacheCommand,
},
- /// Open a `git rebase` like file to select the videos to watch
- Select,
+ /// Change the state of videos in the database (the default)
+ Select {
+ #[command(subcommand)]
+ cmd: Option<SelectCommand>,
+ },
/// Subscribe to an URL
Subscribe {
@@ -73,12 +82,16 @@ pub enum Command {
/// Update the video database
Update {
#[arg(short, long, default_value = "20")]
- /// The number of videos to stop updating
+ /// The number of videos to updating
max_backlog: u32,
#[arg(short, long)]
/// The subscriptions to update (can be given multiple times)
subscriptions: Vec<String>,
+
+ #[arg(short, long, default_value = "6")]
+ /// How many processes to spawn at the same time
+ concurrent_processes: usize,
},
/// Update one subscription (this is here to parallelize the normal `update`)
@@ -95,6 +108,67 @@ pub enum Command {
Subscriptions {},
}
+impl Default for Command {
+ fn default() -> Self {
+ Self::Select {
+ cmd: Some(SelectCommand::default()),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Args)]
+#[command(infer_subcommands = true)]
+/// Mark the video given by the hash to be watched
+pub struct SharedSelectionCommandArgs {
+ /// The short extractor hash
+ pub hash: LazyExtractorHash,
+
+ pub title: String,
+
+ pub date: NaiveDate,
+
+ pub publisher: String,
+
+ pub duration: Duration,
+
+ pub url: Url,
+}
+
+#[derive(Subcommand, Clone, Debug, Default)]
+#[command(infer_subcommands = true)]
+pub enum SelectCommand {
+ #[default]
+ /// Open a `git rebase` like file to select the videos to watch (the default)
+ File,
+
+ Watch {
+ #[command(flatten)]
+ shared: SharedSelectionCommandArgs,
+
+ #[arg(short, long, default_value = "0")]
+ /// The ordering priority (higher means more at the top)
+ priority: i64,
+ },
+
+ /// Mark the video given by the hash to be dropped
+ Drop {
+ #[command(flatten)]
+ shared: SharedSelectionCommandArgs,
+ },
+
+ /// Open the video URL in Firefox's `timesinks.youtube` profile
+ Url {
+ #[command(flatten)]
+ shared: SharedSelectionCommandArgs,
+ },
+
+ /// Reset the videos status to 'Pick'
+ Pick {
+ #[command(flatten)]
+ shared: SharedSelectionCommandArgs,
+ },
+}
+
#[derive(Subcommand, Clone, Debug)]
pub enum CheckCommand {
/// Check if the given info.json is deserializable
diff --git a/pkgs/by-name/yt/yt/src/constants.rs b/pkgs/by-name/yt/yt/src/constants.rs
index 17e0f018..fbe51413 100644
--- a/pkgs/by-name/yt/yt/src/constants.rs
+++ b/pkgs/by-name/yt/yt/src/constants.rs
@@ -3,32 +3,9 @@ use std::{env::temp_dir, path::PathBuf};
use anyhow::Context;
pub const HELP_STR: &str = include_str!("./select/selection_file/help.str");
+pub const LOCAL_COMMENTS_LENGTH: usize = 1000;
-pub const YT_DLP_FLAGS: [&str; 13] = [
- // Ignore errors arising of unavailable sponsor block API
- "--ignore-errors",
- "--format",
- "bestvideo[height<=?1080]+bestaudio/best",
- "--embed-chapters",
- "--progress",
- "--write-comments",
- "--extractor-args",
- "youtube:max_comments=150,all,100;comment_sort=top",
- "--write-info-json",
- "--sponsorblock-mark",
- "default",
- "--sponsorblock-remove",
- "sponsor",
-];
-pub const MPV_FLAGS: [&str; 4] = [
- "--speed=2.7",
- "--volume=75",
- "--keep-open=yes",
- "--msg-level=osd/libass=fatal",
-];
-
-pub const CONCURRENT: u32 = 5;
-
+pub const CONCURRENT_DOWNLOADS: u32 = 5;
// We download to the temp dir to avoid taxing the disk
pub fn download_dir() -> PathBuf {
const DOWNLOAD_DIR: &str = "/tmp/yt";
@@ -48,6 +25,21 @@ fn get_data_path(name: &'static str) -> anyhow::Result<PathBuf> {
.place_data_file(name)
.with_context(|| format!("Failed to place data file: '{}'", name))
}
+fn get_config_path(name: &'static str) -> anyhow::Result<PathBuf> {
+ let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX)?;
+ xdg_dirs
+ .place_config_file(name)
+ .with_context(|| format!("Failed to place config file: '{}'", name))
+}
+
+pub fn mpv_config_path() -> anyhow::Result<PathBuf> {
+ const MPV_CONFIG_PATH: &str = "mpv.conf";
+ get_config_path(MPV_CONFIG_PATH)
+}
+pub fn mpv_input_path() -> anyhow::Result<PathBuf> {
+ const MPV_INPUT_CONFIG_PATH: &str = "mpv.input.conf";
+ get_config_path(MPV_INPUT_CONFIG_PATH)
+}
pub fn status_path() -> anyhow::Result<PathBuf> {
const STATUS_PATH: &str = "running.info.json";
diff --git a/pkgs/by-name/yt/yt/src/download/mod.rs b/pkgs/by-name/yt/yt/src/download/mod.rs
index 2dc7c431..1499171f 100644
--- a/pkgs/by-name/yt/yt/src/download/mod.rs
+++ b/pkgs/by-name/yt/yt/src/download/mod.rs
@@ -6,8 +6,7 @@ use crate::{
storage::video_database::{
downloader::{get_next_uncached_video, set_video_cache_path},
extractor_hash::ExtractorHash,
- setters::set_video_status,
- Video, VideoStatus,
+ Video,
},
};
@@ -64,10 +63,15 @@ impl Downloader {
if let Some(_) = &self.current_download {
let current_download = self.current_download.take().expect("Is Some");
+ if current_download.task_handle.is_finished() {
+ current_download.task_handle.await??;
+ continue;
+ }
+
if next_video.extractor_hash != current_download.extractor_hash {
info!(
"Noticed, that the next video is not the video being downloaded, replacing it ('{}' vs. '{}')!",
- next_video.extractor_hash.format(app).await?, current_download.extractor_hash.format(app).await?
+ next_video.extractor_hash.into_short_hash(app).await?, current_download.extractor_hash.into_short_hash(app).await?
);
// Replace the currently downloading video
@@ -79,7 +83,7 @@ impl Downloader {
} else {
debug!(
"Currently downloading '{}'",
- current_download.extractor_hash.format(app).await?
+ current_download.extractor_hash.into_short_hash(app).await?
);
// Reset the taken value
self.current_download = Some(current_download);
@@ -130,6 +134,7 @@ impl Downloader {
// }
async fn actually_cache_video(app: &App, video: &Video) -> Result<()> {
+ debug!("Download started: {}", &video.title);
let result = yt_dlp::download(&[video.url.clone()], &download_opts())
.await
.with_context(|| format!("Failed to download video: '{}'", video.title))?;
@@ -137,9 +142,7 @@ impl Downloader {
assert_eq!(result.len(), 1);
let result = &result[0];
- set_video_cache_path(app, video, Some(&result)).await?;
-
- set_video_status(app, &(video.extractor_hash), VideoStatus::Cached, None).await?;
+ set_video_cache_path(app, &video.extractor_hash, Some(&result)).await?;
info!(
"Video '{}' was downlaoded to path: {}",
diff --git a/pkgs/by-name/yt/yt/src/main.rs b/pkgs/by-name/yt/yt/src/main.rs
index b89c2eec..92fd1a8d 100644
--- a/pkgs/by-name/yt/yt/src/main.rs
+++ b/pkgs/by-name/yt/yt/src/main.rs
@@ -3,8 +3,9 @@ use std::fs;
use anyhow::{bail, Context, Result};
use app::App;
use clap::Parser;
-use cli::{CacheCommand, CheckCommand};
+use cli::{CacheCommand, CheckCommand, SelectCommand};
use log::info;
+use select::cmds::handle_select_cmd;
use yt_dlp::wrapper::info_json::InfoJson;
use crate::{cli::Command, storage::subscriptions::get_subscriptions};
@@ -25,7 +26,7 @@ pub mod watch;
#[tokio::main]
async fn main() -> Result<()> {
- let args = cli::Args::parse();
+ let args = cli::CliArgs::parse();
stderrlog::new()
.module(module_path!())
.modules(&["yt_dlp".to_owned(), "libmpv2".to_owned()])
@@ -39,7 +40,7 @@ async fn main() -> Result<()> {
let app = App::new().await?;
- match args.command.unwrap_or(Command::Select) {
+ match args.command.unwrap_or(Command::default()) {
Command::Download { urls } => {
if urls.is_empty() {
info!("Downloading urls from database");
@@ -51,8 +52,13 @@ async fn main() -> Result<()> {
todo!()
}
}
- Command::Select => {
- select::select(&app).await?;
+ Command::Select { cmd } => {
+ let cmd = cmd.unwrap_or(SelectCommand::default());
+
+ match cmd {
+ SelectCommand::File => select::select(&app).await?,
+ _ => handle_select_cmd(&app, cmd).await?,
+ }
}
Command::Subscribe { name, url } => {
subscribe::subscribe(name, url)
@@ -67,6 +73,7 @@ async fn main() -> Result<()> {
Command::Update {
max_backlog,
subscriptions,
+ concurrent_processes,
} => {
let all_subs = get_subscriptions()?;
@@ -79,7 +86,7 @@ async fn main() -> Result<()> {
}
}
- update::update(max_backlog, subscriptions).await?;
+ update::update(max_backlog, subscriptions, concurrent_processes).await?;
}
Command::UpdateOnce {
sub_name,
diff --git a/pkgs/by-name/yt/yt/src/select/cmds.rs b/pkgs/by-name/yt/yt/src/select/cmds.rs
new file mode 100644
index 00000000..a2a440f8
--- /dev/null
+++ b/pkgs/by-name/yt/yt/src/select/cmds.rs
@@ -0,0 +1,50 @@
+use crate::{
+ app::App,
+ cli::SelectCommand,
+ storage::video_database::{getters::get_video_by_hash, setters::set_video_status, VideoStatus},
+};
+
+use anyhow::{Context, Result};
+
+pub async fn handle_select_cmd(app: &App, cmd: SelectCommand) -> Result<()> {
+ match cmd {
+ SelectCommand::Pick { shared } => {
+ set_video_status(
+ app,
+ &shared.hash.realize(app).await?,
+ VideoStatus::Pick,
+ None,
+ )
+ .await?
+ }
+ SelectCommand::Drop { shared } => {
+ set_video_status(
+ app,
+ &shared.hash.realize(app).await?,
+ VideoStatus::Drop,
+ None,
+ )
+ .await?
+ }
+ SelectCommand::Watch { shared, priority } => {
+ let hash = shared.hash.realize(&app).await?;
+ let video = get_video_by_hash(app, &hash).await?;
+
+ if let Some(_) = video.cache_path {
+ // Do nothing, as the video *should* already have a `Cached` status and a
+ // cache_path.
+ } else {
+ set_video_status(app, &hash, VideoStatus::Watch, Some(priority)).await?;
+ }
+ }
+
+ SelectCommand::Url { shared } => {
+ let mut firefox = std::process::Command::new("firefox");
+ firefox.args(["-P", "timesinks.youtube"]);
+ firefox.arg(shared.url.as_str());
+ let _handle = firefox.spawn().context("Failed to run firefox")?;
+ }
+ SelectCommand::File => unreachable!("This should have been filtered out"),
+ }
+ Ok(())
+}
diff --git a/pkgs/by-name/yt/yt/src/select/mod.rs b/pkgs/by-name/yt/yt/src/select/mod.rs
index abb389f0..34fcf56a 100644
--- a/pkgs/by-name/yt/yt/src/select/mod.rs
+++ b/pkgs/by-name/yt/yt/src/select/mod.rs
@@ -6,16 +6,20 @@ use std::{
use crate::{
app::App,
+ cli::CliArgs,
constants::{last_select, HELP_STR},
- storage::video_database::{getters::get_videos, setters::set_video_status, VideoStatus},
+ storage::video_database::{getters::get_videos, VideoStatus},
};
use anyhow::{bail, Context, Result};
+use clap::Parser;
+use cmds::handle_select_cmd;
use futures::future::join_all;
+use log::debug;
+use selection_file::process_line;
use tempfile::Builder;
-use self::selection_file::{filter_line, LineCommand};
-
+pub mod cmds;
pub mod selection_file;
pub async fn select(app: &App) -> Result<()> {
@@ -89,32 +93,32 @@ pub async fn select(app: &App) -> Result<()> {
let reader = BufReader::new(&read_file);
- let mut priority = 1;
for line in reader.lines() {
let line = line.context("Failed to read a line")?;
+ if let Some(line) = process_line(&line)? {
+ debug!(
+ "Parsed command: `{}`",
+ line.iter()
+ .map(|val| format!("\"{}\"", val))
+ .collect::<Vec<String>>()
+ .join(" ")
+ );
+
+ let arg_line = ["yt", "select"]
+ .into_iter()
+ .chain(line.iter().map(|val| val.as_str()));
+
+ let args = CliArgs::parse_from(arg_line);
+
+ let cmd = if let crate::cli::Command::Select { cmd } =
+ args.command.expect("This will be some")
+ {
+ cmd
+ } else {
+ unreachable!("This is checked in the `filter_line` function")
+ };
- if let Some(line) = filter_line(app, &line)
- .await
- .with_context(|| format!("Failed to process line: '{}'", line))?
- {
- match line.cmd {
- LineCommand::Pick => {
- set_video_status(app, &line.hash, VideoStatus::Pick, None).await?
- }
- LineCommand::Drop => {
- set_video_status(app, &line.hash, VideoStatus::Drop, None).await?
- }
- LineCommand::Watch => {
- set_video_status(app, &line.hash, VideoStatus::Watch, Some(priority)).await?;
- priority += 1;
- }
- LineCommand::Url => {
- let mut firefox = std::process::Command::new("firefox");
- firefox.args(["-P", "timesinks.youtube"]);
- firefox.arg(line.url.as_str());
- let _handle = firefox.spawn().context("Failed to run firefox")?;
- }
- }
+ handle_select_cmd(&app, cmd.expect("This value should always be some here")).await?
}
}
diff --git a/pkgs/by-name/yt/yt/src/select/selection_file/display.rs b/pkgs/by-name/yt/yt/src/select/selection_file/display.rs
index 8a58ffdd..3649b2b8 100644
--- a/pkgs/by-name/yt/yt/src/select/selection_file/display.rs
+++ b/pkgs/by-name/yt/yt/src/select/selection_file/display.rs
@@ -36,12 +36,12 @@ impl Video {
f,
r#"{} {} "{}" "{}" "{}" "{}" "{}"{}"#,
self.status.as_command(),
- self.extractor_hash.format(app).await?,
+ self.extractor_hash.into_short_hash(app).await?,
self.title.replace(['"', '„', '”'], "'"),
publish_date,
parent_subscription_name,
Duration::from(self.duration),
- self.url.as_str().replace('"', "'"),
+ self.url.as_str().replace('"', "\\\""),
"\n"
)?;
diff --git a/pkgs/by-name/yt/yt/src/select/selection_file/duration.rs b/pkgs/by-name/yt/yt/src/select/selection_file/duration.rs
index 700d9202..9e4b3a83 100644
--- a/pkgs/by-name/yt/yt/src/select/selection_file/duration.rs
+++ b/pkgs/by-name/yt/yt/src/select/selection_file/duration.rs
@@ -1,14 +1,46 @@
+use std::str::FromStr;
+
+use anyhow::{Context, Result};
+
+#[derive(Copy, Clone, Debug)]
pub struct Duration {
time: u32,
}
-impl From<&str> for Duration {
- fn from(v: &str) -> Self {
- let buf: Vec<_> = v.split(':').take(2).collect();
- Self {
- time: (buf[0].parse::<u32>().expect("Should be a number") * 60)
- + buf[1].parse::<u32>().expect("Should be a number"),
+impl FromStr for Duration {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ fn parse_num(str: &str, suffix: char) -> Result<u32> {
+ str.strip_suffix(suffix)
+ .expect("it has a 'h' suffix")
+ .parse::<u32>()
+ .context("Failed to parse hours")
}
+
+ let buf: Vec<_> = s.split(' ').collect();
+
+ let hours;
+ let minutes;
+ let seconds;
+
+ assert_eq!(buf.len(), 2, "Other lengths should not happen");
+
+ if buf[0].ends_with('h') {
+ hours = parse_num(buf[0], 'h')?;
+ minutes = parse_num(buf[1], 'm')?;
+ seconds = 0;
+ } else if buf[0].ends_with('m') {
+ hours = 0;
+ minutes = parse_num(buf[0], 'm')?;
+ seconds = parse_num(buf[1], 's')?;
+ } else {
+ unreachable!("The first part always ends with 'h' or 'm'")
+ }
+
+ Ok(Self {
+ time: (hours * 60 * 60) + (minutes * 60) + seconds,
+ })
}
}
@@ -37,9 +69,9 @@ impl std::fmt::Display for Duration {
if self.time == 0 {
write!(f, "[No Duration]")
} else if h > 0 {
- write!(f, "[{h}h {m}m]")
+ write!(f, "{h}h {m}m")
} else {
- write!(f, "[{m}m {s}s]")
+ write!(f, "{m}m {s}s")
}
}
}
@@ -50,11 +82,11 @@ mod test {
#[test]
fn test_display_duration_1h() {
let dur = Duration { time: 60 * 60 };
- assert_eq!("[1h 0m]".to_owned(), dur.to_string());
+ assert_eq!("1h 0m".to_owned(), dur.to_string());
}
#[test]
fn test_display_duration_30min() {
let dur = Duration { time: 60 * 30 };
- assert_eq!("[30m 0s]".to_owned(), dur.to_string());
+ assert_eq!("30m 0s".to_owned(), dur.to_string());
}
}
diff --git a/pkgs/by-name/yt/yt/src/select/selection_file/mod.rs b/pkgs/by-name/yt/yt/src/select/selection_file/mod.rs
index 957fcd08..c63ca85a 100644
--- a/pkgs/by-name/yt/yt/src/select/selection_file/mod.rs
+++ b/pkgs/by-name/yt/yt/src/select/selection_file/mod.rs
@@ -1,79 +1,25 @@
//! The data structures needed to express the file, which the user edits
-use anyhow::{bail, Context};
-use url::Url;
-
-use crate::{app::App, storage::video_database::extractor_hash::ExtractorHash};
+use anyhow::{Context, Result};
+use trinitry::Trinitry;
pub mod display;
pub mod duration;
-pub enum LineCommand {
- Pick,
- Drop,
- Watch,
- Url,
-}
-
-impl std::str::FromStr for LineCommand {
- type Err = anyhow::Error;
- fn from_str(v: &str) -> Result<Self, <Self as std::str::FromStr>::Err> {
- match v {
- "pick" | "p" => Ok(Self::Pick),
- "drop" | "d" => Ok(Self::Drop),
- "watch" | "w" => Ok(Self::Watch),
- "url" | "u" => Ok(Self::Url),
- other => bail!("'{}' is not a recognized command!", other),
- }
- }
-}
-
-pub struct Line {
- pub cmd: LineCommand,
- pub hash: ExtractorHash,
- pub url: Url,
-}
-
-impl Line {
- pub async fn from_str(app: &App, s: &str) -> anyhow::Result<Self> {
- let buf: Vec<_> = s.split_whitespace().collect();
-
- let url_as_str = buf
- .last()
- .with_context(|| format!("The line '{}' misses it's url field!'", s))?
- .trim_matches('"');
+pub fn process_line(line: &str) -> Result<Option<Vec<String>>> {
+ // Filter out comments and empty lines
+ if line.starts_with('#') || line.trim().is_empty() {
+ Ok(None)
+ } else {
+ // pick 2195db "CouchRecherche? Gunnar und Han von STRG_F sind #mitfunkzuhause" "2020-04-01" "STRG_F - Live" "[1h 5m]" "https://www.youtube.com/watch?v=C8UXOaoMrXY"
- let url: Url = Url::parse(url_as_str)
- .with_context(|| format!("The url '{}' could not be parsed!", url_as_str))?;
+ let tri =
+ Trinitry::new(line).with_context(|| format!("Failed to parse line '{}'", line))?;
- Ok(Line {
- cmd: buf
- .get(0)
- .with_context(|| format!("The line '{}' is missing it's command!", s))?
- .parse()?,
- hash: ExtractorHash::parse_from_short_version(
- app,
- buf.get(1)
- .with_context(|| format!("The line '{}' is missing it's blake3 hash!", s))?,
- )
- .await
- .with_context(|| {
- format!(
- "Can't parse '{}' as blake3 hash!",
- buf.get(1).expect("Already checked"),
- )
- })?,
- url,
- })
- }
-}
+ let mut vec = Vec::with_capacity(tri.arguments().len() + 1);
+ vec.push(tri.command().to_owned());
+ vec.extend(tri.arguments().to_vec().into_iter());
-pub async fn filter_line(app: &App, line: &str) -> anyhow::Result<Option<Line>> {
- // Filter out comments and empty lines
- if line.starts_with('#') || line.trim().is_empty() {
- return Ok(None);
+ Ok(Some(vec))
}
-
- let line: Line = Line::from_str(app, line).await?;
- Ok(Some(line))
}
diff --git a/pkgs/by-name/yt/yt/src/storage/video_database/downloader.rs b/pkgs/by-name/yt/yt/src/storage/video_database/downloader.rs
index 5c472ac5..ca3a2ea3 100644
--- a/pkgs/by-name/yt/yt/src/storage/video_database/downloader.rs
+++ b/pkgs/by-name/yt/yt/src/storage/video_database/downloader.rs
@@ -12,14 +12,17 @@ use super::{ExtractorHash, Video};
/// Returns to next video which should be downloaded. This respects the priority assigned by select.
/// It does not return videos, which are already cached.
pub async fn get_next_uncached_video(app: &App) -> Result<Option<Video>> {
+ let status = VideoStatus::Watch.as_db_integer();
+
let result = query!(
r#"
SELECT *
FROM videos
- WHERE status = 'Watching' AND cache_path IS NULL
+ WHERE status = ? AND cache_path IS NULL
ORDER BY priority ASC
LIMIT 1;
- "#
+ "#,
+ status
)
.fetch_one(&app.database)
.await;
@@ -46,7 +49,11 @@ pub async fn get_next_uncached_video(app: &App) -> Result<Option<Video>> {
cache_path: base.cache_path.as_ref().map(|val| PathBuf::from(val)),
description: base.description.clone(),
duration: base.duration,
- extractor_hash: ExtractorHash::new(base.extractor_hash.parse()?),
+ extractor_hash: ExtractorHash::from_hash(
+ base.extractor_hash
+ .parse()
+ .expect("The hash in the db should be valid"),
+ ),
last_status_change: base.last_status_change,
parent_subscription_name: base.parent_subscription_name.clone(),
priority: base.priority,
@@ -99,7 +106,11 @@ pub async fn get_next_video_watchable(app: &App) -> Result<Option<Video>> {
cache_path: base.cache_path.as_ref().map(|val| PathBuf::from(val)),
description: base.description.clone(),
duration: base.duration,
- extractor_hash: ExtractorHash::new(base.extractor_hash.parse()?),
+ extractor_hash: ExtractorHash::from_hash(
+ base.extractor_hash
+ .parse()
+ .expect("The db extractor_hash should be valid blake3 hash"),
+ ),
last_status_change: base.last_status_change,
parent_subscription_name: base.parent_subscription_name.clone(),
priority: base.priority,
@@ -116,30 +127,32 @@ pub async fn get_next_video_watchable(app: &App) -> Result<Option<Video>> {
}
/// Update the cached path of a video. Will be set to NULL if the path is None
-pub async fn set_video_cache_path(app: &App, video: &Video, path: Option<&Path>) -> Result<()> {
- debug!(
- "Setting cache path from '{}' to '{:#?}'",
- video.title,
- path.unwrap_or(&PathBuf::from("None")).display()
- );
-
+/// This will also set the status to `Cached` when path is Some, otherwise it set's the status to
+/// `Watch`.
+pub async fn set_video_cache_path(
+ app: &App,
+ video: &ExtractorHash,
+ path: Option<&Path>,
+) -> Result<()> {
if let Some(path) = path {
debug!(
"Setting cache path from '{}' to '{}'",
- video.title,
+ video.into_short_hash(app).await?,
path.display()
);
let path_str = path.display().to_string();
- let extractor_hash = video.extractor_hash.0.to_string();
+ let extractor_hash = video.hash().to_string();
+ let status = VideoStatus::Cached.as_db_integer();
query!(
r#"
UPDATE videos
- SET cache_path = ?
+ SET cache_path = ?, status = ?
WHERE extractor_hash = ?;
"#,
path_str,
+ status,
extractor_hash
)
.execute(&app.database)
@@ -147,14 +160,21 @@ pub async fn set_video_cache_path(app: &App, video: &Video, path: Option<&Path>)
Ok(())
} else {
- let extractor_hash = video.extractor_hash.0.to_string();
+ debug!(
+ "Setting cache path from '{}' to 'NULL'",
+ video.into_short_hash(app).await?,
+ );
+
+ let extractor_hash = video.hash().to_string();
+ let status = VideoStatus::Watch.as_db_integer();
query!(
r#"
UPDATE videos
- SET cache_path = NULL
+ SET cache_path = NULL, status = ?
WHERE extractor_hash = ?;
"#,
+ status,
extractor_hash
)
.execute(&app.database)
diff --git a/pkgs/by-name/yt/yt/src/storage/video_database/extractor_hash.rs b/pkgs/by-name/yt/yt/src/storage/video_database/extractor_hash.rs
index e90a5277..ac2f46ee 100644
--- a/pkgs/by-name/yt/yt/src/storage/video_database/extractor_hash.rs
+++ b/pkgs/by-name/yt/yt/src/storage/video_database/extractor_hash.rs
@@ -1,4 +1,4 @@
-use std::{collections::HashMap, fmt::Display};
+use std::{collections::HashMap, fmt::Display, str::FromStr};
use anyhow::{bail, Result};
use blake3::Hash;
@@ -9,35 +9,95 @@ use crate::{app::App, storage::video_database::getters::get_all_hashes};
static EXTRACTOR_HASH_LENGTH: OnceCell<usize> = OnceCell::const_new();
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ExtractorHash(pub(super) Hash);
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ExtractorHash {
+ hash: Hash,
+}
+
+#[derive(Debug, Clone)]
+pub struct ShortHash(String);
-impl Display for ExtractorHash {
+impl Display for ShortHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
+#[derive(Debug, Clone)]
+pub struct LazyExtractorHash {
+ value: ShortHash,
+}
+
+impl FromStr for LazyExtractorHash {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ // perform some cheap validation
+ if s.len() > 64 {
+ bail!("A hash can only contain 64 bytes!");
+ }
+
+ Ok(Self {
+ value: ShortHash(s.to_owned()),
+ })
+ }
+}
+
+impl LazyExtractorHash {
+ /// Turn the [`LazyExtractorHash`] into the [`ExtractorHash`]
+ pub async fn realize(self, app: &App) -> Result<ExtractorHash> {
+ ExtractorHash::from_short_hash(app, &self.value).await
+ }
+}
+
impl ExtractorHash {
- pub fn new(hash: Hash) -> Self {
- Self(hash)
+ pub fn from_hash(hash: Hash) -> Self {
+ Self { hash }
+ }
+ pub async fn from_short_hash(app: &App, s: &ShortHash) -> Result<Self> {
+ Ok(Self {
+ hash: Self::short_hash_to_full_hash(app, s).await?,
+ })
+ }
+
+ pub fn hash(&self) -> &Hash {
+ &self.hash
}
- pub fn parse_from_full_version(s: &str) -> Result<Self> {
- assert_eq!(s.len(), 64);
+ pub async fn into_short_hash(&self, app: &App) -> Result<ShortHash> {
+ let needed_chars = if let Some(needed_chars) = EXTRACTOR_HASH_LENGTH.get() {
+ debug!("Using cached char length: {}", needed_chars);
+ *needed_chars
+ } else {
+ let needed_chars = self.get_needed_char_len(app).await?;
+ debug!("Setting the needed has char lenght.");
+ EXTRACTOR_HASH_LENGTH
+ .set(needed_chars)
+ .expect("This should work at this stage");
+
+ needed_chars
+ };
+
+ debug!("Formatting a hash with char length: {}", needed_chars);
- let hash = s.parse()?;
- Ok(Self::new(hash))
+ Ok(ShortHash(
+ self.hash()
+ .to_hex()
+ .chars()
+ .into_iter()
+ .take(needed_chars)
+ .collect::<String>(),
+ ))
}
- pub async fn parse_from_short_version(app: &App, s: &str) -> Result<Self> {
+ async fn short_hash_to_full_hash(app: &App, s: &ShortHash) -> Result<Hash> {
let all_hashes = get_all_hashes(app).await?;
- let needed_chars = s.len();
+ let needed_chars = s.0.len();
for hash in all_hashes {
- if &hash.to_hex()[..needed_chars] == s {
- return Ok(Self::new(hash));
+ if &hash.to_hex()[..needed_chars] == s.0 {
+ return Ok(hash);
}
}
@@ -78,29 +138,4 @@ impl ExtractorHash {
Ok(needed_chars)
}
-
- pub async fn format(&self, app: &App) -> Result<String> {
- let needed_chars = if let Some(needed_chars) = EXTRACTOR_HASH_LENGTH.get() {
- debug!("Using cached char length: {}", needed_chars);
- *needed_chars
- } else {
- let needed_chars = self.get_needed_char_len(app).await?;
- debug!("Setting the needed has char lenght.");
- EXTRACTOR_HASH_LENGTH
- .set(needed_chars)
- .expect("This should work at this stage");
-
- needed_chars
- };
-
- debug!("Formatting a hash with char length: {}", needed_chars);
-
- Ok(self
- .0
- .to_hex()
- .chars()
- .into_iter()
- .take(needed_chars)
- .collect::<String>())
- }
}
diff --git a/pkgs/by-name/yt/yt/src/storage/video_database/getters.rs b/pkgs/by-name/yt/yt/src/storage/video_database/getters.rs
index 6685603c..cec6c426 100644
--- a/pkgs/by-name/yt/yt/src/storage/video_database/getters.rs
+++ b/pkgs/by-name/yt/yt/src/storage/video_database/getters.rs
@@ -1,10 +1,7 @@
//! These functions interact with the storage db in a read-only way. They are added on-demaned (as
//! you could theoretically just could do everything with the `get_videos` function), as
//! performance or convince requires.
-use std::{
- fs::{File},
- path::PathBuf,
-};
+use std::{fs::File, path::PathBuf};
use anyhow::{bail, Context, Result};
use blake3::Hash;
@@ -23,6 +20,42 @@ use crate::{
use super::VideoStatus;
+macro_rules! video_from_record {
+ ($record:expr) => {
+ let thumbnail_url = if let Some(url) = &$record.thumbnail_url {
+ Some(Url::parse(&url)?)
+ } else {
+ None
+ };
+
+ Ok(Video {
+ cache_path: $record.cache_path.as_ref().map(|val| PathBuf::from(val)),
+ description: $record.description.clone(),
+ duration: $record.duration,
+ extractor_hash: ExtractorHash::from_hash(
+ $record
+ .extractor_hash
+ .parse()
+ .expect("The db hash should be a valid blake3 hash"),
+ ),
+ last_status_change: $record.last_status_change,
+ parent_subscription_name: $record.parent_subscription_name.clone(),
+ publish_date: $record.publish_date,
+ status: VideoStatus::from_db_integer($record.status),
+ thumbnail_url,
+ title: $record.title.clone(),
+ url: Url::parse(&$record.url)?,
+ priority: $record.priority,
+ status_change: if $record.status_change == 1 {
+ true
+ } else {
+ assert_eq!($record.status_change, 0);
+ false
+ },
+ })
+ };
+}
+
/// Get the lines to display at the selection file
pub async fn get_videos(app: &App, allowed_states: &[VideoStatus]) -> Result<Vec<Video>> {
let mut qb: QueryBuilder<Sqlite> = QueryBuilder::new(
@@ -77,9 +110,11 @@ pub async fn get_videos(app: &App, allowed_states: &[VideoStatus]) -> Result<Vec
.map(|val| PathBuf::from(val)),
description: base.get::<Option<String>, &str>("description").clone(),
duration: base.get("duration"),
- extractor_hash: ExtractorHash::parse_from_full_version(
- &base.get::<String, &str>("extractor_hash"),
- )?,
+ extractor_hash: ExtractorHash::from_hash(
+ base.get::<String, &str>("extractor_hash")
+ .parse()
+ .expect("The db hash should be a valid blake3 hash"),
+ ),
last_status_change: base.get("last_status_change"),
parent_subscription_name: base
.get::<Option<String>, &str>("parent_subscription_name")
@@ -90,7 +125,15 @@ pub async fn get_videos(app: &App, allowed_states: &[VideoStatus]) -> Result<Vec
title: base.get::<String, &str>("title").to_owned(),
url: Url::parse(base.get("url"))?,
priority: base.get("priority"),
- status_change: base.get("status_change"),
+ status_change: {
+ let val = base.get::<i64, &str>("status_change");
+ if val == 1 {
+ true
+ } else {
+ assert_eq!(val, 0, "Can only be 1 or 0");
+ false
+ }
+ },
})
})
.collect::<Result<Vec<Video>>>()?;
@@ -115,6 +158,21 @@ pub async fn get_video_info_json(video: &Video) -> Result<Option<InfoJson>> {
}
}
+pub async fn get_video_by_hash(app: &App, hash: &ExtractorHash) -> Result<Video> {
+ let ehash = hash.hash().to_string();
+
+ let raw_video = query!(
+ "
+ SELECT * FROM videos WHERE extractor_hash = ?;
+ ",
+ ehash
+ )
+ .fetch_one(&app.database)
+ .await?;
+
+ video_from_record! {raw_video}
+}
+
pub async fn get_currently_playing_video(app: &App) -> Result<Option<Video>> {
let mut videos: Vec<Video> = get_changing_videos(app, VideoStatus::Cached).await?;
@@ -132,7 +190,27 @@ pub async fn get_currently_playing_video(app: &App) -> Result<Option<Video>> {
}
pub async fn get_changing_videos(app: &App, old_state: VideoStatus) -> Result<Vec<Video>> {
- todo!()
+ let status = old_state.as_db_integer();
+
+ let matching = query!(
+ r#"
+ SELECT *
+ FROM videos
+ WHERE status_change = 1 AND status = ?;
+ "#,
+ status
+ )
+ .fetch_all(&app.database)
+ .await?;
+
+ let real_videos: Vec<Video> = matching
+ .iter()
+ .map(|base| -> Result<Video> {
+ video_from_record! {base}
+ })
+ .collect::<Result<Vec<Video>>>()?;
+
+ Ok(real_videos)
}
pub async fn get_all_hashes(app: &App) -> Result<Vec<Hash>> {
diff --git a/pkgs/by-name/yt/yt/src/storage/video_database/schema.sql b/pkgs/by-name/yt/yt/src/storage/video_database/schema.sql
index e4d1e514..2634eef4 100644
--- a/pkgs/by-name/yt/yt/src/storage/video_database/schema.sql
+++ b/pkgs/by-name/yt/yt/src/storage/video_database/schema.sql
@@ -1,16 +1,16 @@
-- The base schema
-- Keep this table in sync with the `Video` structure
CREATE TABLE IF NOT EXISTS videos (
- cache_path TEXT UNIQUE,
+ cache_path TEXT UNIQUE CHECK (CASE WHEN cache_path IS NOT NULL THEN status == 2 ELSE 1 END),
description TEXT,
duration FLOAT,
extractor_hash TEXT UNIQUE NOT NULL PRIMARY KEY,
- last_status_change INTEGER NOT NULL,
+ last_status_change INTEGER NOT NULL,
parent_subscription_name TEXT,
- priority INTEGER NOT NULL DEFAULT 0,
+ priority INTEGER NOT NULL DEFAULT 0,
publish_date INTEGER,
- status INTEGER NOT NULL CHECK (status IN (0, 1, 2, 3, 4, 5)),
- status_change INTEGER NOT NULL CHECK (status_change IN (0, 1)),
+ status INTEGER NOT NULL DEFAULT 0 CHECK (status IN (0, 1, 2, 3, 4, 5) AND CASE WHEN status == 2 THEN cache_path IS NOT NULL ELSE 1 END AND CASE WHEN status != 2 THEN cache_path IS NULL ELSE 1 END),
+ status_change INTEGER NOT NULL DEFAULT 0 CHECK (status_change IN (0, 1)),
thumbnail_url TEXT,
title TEXT NOT NULL,
url TEXT UNIQUE NOT NULL
diff --git a/pkgs/by-name/yt/yt/src/storage/video_database/setters.rs b/pkgs/by-name/yt/yt/src/storage/video_database/setters.rs
index 4f1d98cf..251f1e6f 100644
--- a/pkgs/by-name/yt/yt/src/storage/video_database/setters.rs
+++ b/pkgs/by-name/yt/yt/src/storage/video_database/setters.rs
@@ -16,7 +16,7 @@ pub async fn set_video_status(
new_status: VideoStatus,
new_priority: Option<i64>,
) -> Result<()> {
- let video_hash = video_hash.0.to_string();
+ let video_hash = video_hash.hash().to_string();
let new_status = new_status.as_db_integer();
let old = query!(
@@ -74,13 +74,54 @@ pub async fn set_video_status(
Ok(())
}
+/// Mark a video as watched.
+/// This will both set the status to `Watched` and the cache_path to Null.
+pub async fn set_video_watched(app: &App, video_hash: &ExtractorHash) -> Result<()> {
+ // FIXME: Also delete the cache file <2024-08-19>
+
+ let video_hash = video_hash.hash().to_string();
+ let new_status = VideoStatus::Watched.as_db_integer();
+
+ let old = query!(
+ r#"
+ SELECT status, priority
+ FROM videos
+ WHERE extractor_hash = ?
+ "#,
+ video_hash
+ )
+ .fetch_one(&app.database)
+ .await?;
+
+ if old.status == new_status {
+ return Ok(());
+ }
+
+ let now = Utc::now().timestamp();
+
+ query!(
+ r#"
+ UPDATE videos
+ SET status = ?, last_status_change = ?, cache_path = NULL
+ WHERE extractor_hash = ?;
+ "#,
+ new_status,
+ now,
+ video_hash
+ )
+ .execute(&app.database)
+ .await?;
+
+ Ok(())
+}
+
pub async fn set_state_change(
app: &App,
video_extractor_hash: &ExtractorHash,
changing: bool,
) -> Result<()> {
let state_change = if changing { 1 } else { 0 };
- let video_extractor_hash = video_extractor_hash.to_string();
+ let video_extractor_hash = video_extractor_hash.hash().to_string();
query!(
r#"
@@ -111,14 +152,37 @@ pub async fn add_video(app: &App, video: Video) -> Result<()> {
};
let status = video.status.as_db_integer();
+ let status_change = if video.status_change { 1 } else { 0 };
let url = video.url.to_string();
- let extractor_hash = video.extractor_hash.0.to_string();
+ let extractor_hash = video.extractor_hash.hash().to_string();
query!(
r#"
- INSERT INTO videos (parent_subscription_name, status, last_status_change, title, url, description, duration, publish_date, thumbnail_url, extractor_hash)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
- "#, parent_subscription_name, status, video.last_status_change, video.title, url, video.description, video.duration, video.publish_date, thumbnail_url, extractor_hash
+ INSERT INTO videos (
+ parent_subscription_name,
+ status,
+ status_change,
+ last_status_change,
+ title,
+ url,
+ description,
+ duration,
+ publish_date,
+ thumbnail_url,
+ extractor_hash)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
+ "#,
+ parent_subscription_name,
+ status,
+ status_change,
+ video.last_status_change,
+ video.title,
+ url,
+ video.description,
+ video.duration,
+ video.publish_date,
+ thumbnail_url,
+ extractor_hash
)
.execute(&app.database)
.await?;
diff --git a/pkgs/by-name/yt/yt/src/update/mod.rs b/pkgs/by-name/yt/yt/src/update/mod.rs
index 2f97361d..2cbae9fd 100644
--- a/pkgs/by-name/yt/yt/src/update/mod.rs
+++ b/pkgs/by-name/yt/yt/src/update/mod.rs
@@ -1,10 +1,10 @@
-use std::{env::current_exe, str::FromStr};
+use std::{env::current_exe, str::FromStr, sync::Arc};
use anyhow::{bail, Context, Ok, Result};
use chrono::{DateTime, Utc};
use log::{debug, error, info, warn};
use serde_json::{json, Value};
-use tokio::{process::Command, task::JoinSet};
+use tokio::{process::Command, sync::Semaphore, task::JoinSet};
use yt_dlp::unsmuggle_url;
use crate::{
@@ -12,37 +12,51 @@ use crate::{
storage::{
subscriptions::{get_subscriptions, Subscription},
video_database::{
- extractor_hash::ExtractorHash, getters::get_video_hashes, setters::add_video,
- Video, VideoStatus,
+ extractor_hash::ExtractorHash, getters::get_video_hashes, setters::add_video, Video,
+ VideoStatus,
},
},
};
-pub async fn update(max_backlog: u32, subs_to_update: Vec<String>) -> Result<()> {
+pub async fn update(
+ max_backlog: u32,
+ subs_to_update: Vec<String>,
+ concurrent_processes: usize,
+) -> Result<()> {
let subscriptions = get_subscriptions()?;
let mut join_set = JoinSet::new();
+ let permits = Arc::new(Semaphore::const_new(concurrent_processes));
- for key in subscriptions.0.keys() {
+ for key in subscriptions.0.into_keys() {
if subs_to_update.contains(&key) || subs_to_update.is_empty() {
- let exe_name = current_exe().context("Failed to get the current executable")?;
+ let new_permits = Arc::clone(&permits);
- let mut child = Command::new(exe_name)
- // TODO: Add currying of the verbosity flags <2024-07-28>
- // .arg("-vvvv")
- .arg("update-once")
- .arg(&key)
- .arg(max_backlog.to_string())
- .spawn()
- .context("Failed to call yt update-once")?;
+ join_set.spawn(async move {
+ let _permit = new_permits
+ .acquire()
+ .await
+ .expect("The semaphore should not be closed");
- let new_key = key.clone();
+ debug!(
+ "Started downloading: `yt 'update-once' '{}' '{}'`",
+ &key,
+ max_backlog.to_string()
+ );
- // PERFORMANCE: Switch back to a somewhat parallellied version <2024-07-29>
- let output = child.wait().await;
- join_set.spawn(async move {
- // let output = child.wait().await;
- (output, new_key)
+ let exe_name = current_exe().context("Failed to get the current executable")?;
+ let mut child = Command::new(exe_name)
+ // TODO: Add currying of the verbosity flags <2024-07-28>
+ // .arg("-vvvv")
+ .arg("update-once")
+ .arg(&key)
+ .arg(max_backlog.to_string())
+ .spawn()
+ .context("Failed to call yt update-once")?;
+
+ let output = child.wait().await;
+
+ Ok((output, key))
});
} else {
info!(
@@ -53,7 +67,7 @@ pub async fn update(max_backlog: u32, subs_to_update: Vec<String>) -> Result<()>
}
while let Some(res) = join_set.join_next().await {
- let (output, key) = res?;
+ let (output, key) = res??;
debug!("{} finished its update.", &key);
match output {
@@ -199,7 +213,7 @@ async fn update_subscription(app: &App, sub: &Subscription, max_backlog: u32) ->
cache_path: None,
description: entry.description.clone(),
duration: entry.duration,
- extractor_hash: ExtractorHash::new(extractor_hash),
+ extractor_hash: ExtractorHash::from_hash(extractor_hash),
last_status_change: Utc::now().timestamp(),
parent_subscription_name: Some(sub.name.clone()),
priority: 0,
diff --git a/pkgs/by-name/yt/yt/src/watch/events.rs b/pkgs/by-name/yt/yt/src/watch/events.rs
new file mode 100644
index 00000000..cab6807f
--- /dev/null
+++ b/pkgs/by-name/yt/yt/src/watch/events.rs
@@ -0,0 +1,160 @@
+use std::{env::current_exe, usize};
+
+use anyhow::{bail, Result};
+use libmpv2::{events::Event, EndFileReason, Mpv};
+use log::{debug, info};
+use tokio::process::Command;
+
+use crate::{
+ app::App,
+ comments::get_comments,
+ constants::LOCAL_COMMENTS_LENGTH,
+ storage::video_database::{
+ extractor_hash::ExtractorHash,
+ setters::{set_state_change, set_video_watched},
+ },
+};
+
+pub struct MpvEventHandler {
+ currently_playing_index: Option<usize>,
+ current_playlist_position: usize,
+ current_playlist: Vec<ExtractorHash>,
+}
+
+impl MpvEventHandler {
+ pub fn from_playlist(playlist: Vec<ExtractorHash>) -> Self {
+ Self {
+ currently_playing_index: None,
+ current_playlist: playlist,
+ current_playlist_position: 0,
+ }
+ }
+
+ async fn mark_cvideo_watched(&mut self, app: &App) -> Result<()> {
+ if let Some(index) = self.currently_playing_index {
+ let video_hash = &self.current_playlist[(index) as usize];
+ set_video_watched(&app, video_hash).await?;
+ }
+ Ok(())
+ }
+
+ async fn mark_cvideo_inactive(&mut self, app: &App) -> Result<()> {
+ if let Some(index) = self.currently_playing_index {
+ let video_hash = &self.current_playlist[(index) as usize];
+ self.currently_playing_index = None;
+ set_state_change(&app, video_hash, false).await?;
+ }
+ Ok(())
+ }
+ async fn mark_video_active(&mut self, app: &App, playlist_index: usize) -> Result<()> {
+ let video_hash = &self.current_playlist[(playlist_index) as usize];
+ self.currently_playing_index = Some(playlist_index);
+ set_state_change(&app, video_hash, true).await?;
+ Ok(())
+ }
+
+ /// This will return [`true`], if the event handling should be stopped
+ pub async fn handle_mpv_event<'a>(
+ &mut self,
+ app: &App,
+ mpv: &Mpv,
+ event: Event<'a>,
+ ) -> Result<bool> {
+ match event {
+ Event::EndFile(r) => match r {
+ EndFileReason::Eof => {
+ info!("Mpv reached eof of current video. Marking it watched.");
+
+ self.mark_cvideo_watched(app).await?;
+ self.mark_cvideo_inactive(app).await?;
+ }
+ EndFileReason::Stop => {}
+ EndFileReason::Quit => {
+ info!("Mpv quit. Exiting playback");
+
+ self.mark_cvideo_watched(app).await?;
+ self.mark_cvideo_inactive(app).await?;
+ return Ok(true);
+ }
+ EndFileReason::Error => {
+ unreachable!("This have raised a separate error")
+ }
+ EndFileReason::Redirect => {
+ todo!("We probably need to handle this somehow");
+ }
+ },
+ Event::StartFile(playlist_index) => {
+ self.mark_video_active(app, (playlist_index - 1) as usize)
+ .await?;
+ self.current_playlist_position = (playlist_index - 1) as usize;
+ }
+ Event::FileLoaded => {}
+ Event::ClientMessage(a) => {
+ debug!("Got Client Message event: '{}'", a.join(" "));
+
+ match a.as_slice() {
+ &["yt-comments-external"] => {
+ let binary = current_exe().expect("A current exe should exist");
+
+ let status = Command::new("riverctl")
+ .args(["focus-output", "next"])
+ .status()
+ .await?;
+ if !status.success() {
+ bail!("focusing the next output failed!");
+ }
+
+ let status = Command::new("alacritty")
+ .args(&[
+ "--title",
+ "floating please",
+ "--command",
+ binary.to_str().expect("Should be valid unicode"),
+ "comments",
+ ])
+ .status()
+ .await?;
+ if !status.success() {
+ bail!("Falied to start `yt comments`");
+ }
+
+ let status = Command::new("riverctl")
+ .args(["focus-output", "next"])
+ .status()
+ .await?;
+ if !status.success() {
+ bail!("focusing the next output failed!");
+ }
+ }
+ &["yt-comments-local"] => {
+ let comments: String = get_comments(app)
+ .await?
+ .render(false)
+ .replace("\"", "")
+ .replace("'", "")
+ .chars()
+ .take(LOCAL_COMMENTS_LENGTH)
+ .collect();
+
+ mpv.execute("show-text", &[&format!("'{}'", comments), "6000"])?;
+ }
+ &["yt-description"] => {
+ // let description = description(app).await?;
+ mpv.execute("script-message", &["osc-message", "'<YT Description>'"])?;
+ }
+ &["yt-mark-watch-later"] => {
+ self.mark_cvideo_inactive(app).await?;
+ mpv.execute("write-watch-later-config", &[])?;
+ mpv.execute("playlist-remove", &["current"])?;
+ }
+ other => {
+ debug!("Unknown message: {}", other.join(" "))
+ }
+ }
+ }
+ _ => {}
+ }
+
+ Ok(false)
+ }
+}
diff --git a/pkgs/by-name/yt/yt/src/watch/mod.rs b/pkgs/by-name/yt/yt/src/watch/mod.rs
index 683d49c1..3a4f8983 100644
--- a/pkgs/by-name/yt/yt/src/watch/mod.rs
+++ b/pkgs/by-name/yt/yt/src/watch/mod.rs
@@ -1,19 +1,17 @@
use anyhow::Result;
-use libmpv2::{
- events::{Event, EventContext},
- Mpv,
-};
-use log::{debug, info};
+use events::MpvEventHandler;
+use libmpv2::{events::EventContext, Mpv};
+use log::{debug, info, warn};
use crate::{
app::App,
cache::maintain,
- comments::get_comments,
- storage::video_database::{
- extractor_hash::ExtractorHash, getters::get_videos, setters::set_state_change, VideoStatus,
- },
+ constants::{mpv_config_path, mpv_input_path},
+ storage::video_database::{extractor_hash::ExtractorHash, getters::get_videos, VideoStatus},
};
+pub mod events;
+
pub async fn watch(app: &App) -> Result<()> {
maintain(app, false).await?;
@@ -22,11 +20,43 @@ pub async fn watch(app: &App) -> Result<()> {
// the player (and e.g. close the window).
mpv.set_property("input-default-bindings", "yes")?;
mpv.set_property("input-vo-keyboard", "yes")?;
+
// Show the on screen controller.
mpv.set_property("osc", "yes")?;
+
+ // Don't automatically advance to the next video (or exit the player)
+ mpv.set_option("keep-open", "always")?;
Ok(())
})?;
+ let config_path = mpv_config_path()?;
+ if config_path.try_exists()? {
+ info!("Found mpv.conf at '{}'!", config_path.display());
+ mpv.execute(
+ "load-config-file",
+ &[config_path.to_str().expect("This should be utf8-able")],
+ )?;
+ } else {
+ warn!(
+ "Did not find a mpv.conf file at '{}'",
+ config_path.display()
+ );
+ }
+
+ let input_path = mpv_input_path()?;
+ if input_path.try_exists()? {
+ info!("Found mpv.input.conf at '{}'!", input_path.display());
+ mpv.execute(
+ "load-input-conf",
+ &[input_path.to_str().expect("This should be utf8-able")],
+ )?;
+ } else {
+ warn!(
+ "Did not find a mpv.input.conf file at '{}'",
+ input_path.display()
+ );
+ }
+
let mut ev_ctx = EventContext::new(mpv.ctx);
ev_ctx.disable_deprecated_events()?;
@@ -58,37 +88,17 @@ pub async fn watch(app: &App) -> Result<()> {
playlist_cache.push(play_thing.extractor_hash);
}
+ let mut mpv_event_handler = MpvEventHandler::from_playlist(playlist_cache);
loop {
if let Some(ev) = ev_ctx.wait_event(600.) {
match ev {
- Ok(Event::EndFile(r)) => {
- println!("Exiting! Reason: {:?}", r);
- break;
- }
- Ok(Event::StartFile(playlist_index)) => {
- let video_hash = &playlist_cache[playlist_index as usize];
- set_state_change(&app, video_hash, true).await?;
- }
- Ok(Event::ClientMessage(a)) => {
- debug!("Got Client Message event: '{}'", a.join(" "));
-
- match a.as_slice() {
- &["yt-comments"] => {
- let comments = get_comments(app).await?.render(false);
- mpv.execute("script-message", &["osc-message", &comments])?;
- }
- &["yt-description"] => {
- // let description = description(app).await?;
- mpv.execute("script-message", &["osc-message", "'<YT Description>'"])?;
- }
- other => {
- debug!("Unknown message: {}", other.join(" "))
- }
+ Ok(event) => {
+ debug!("Mpv event triggered: {:#?}", event);
+ if mpv_event_handler.handle_mpv_event(app, &mpv, event).await? {
+ break;
}
}
-
- Ok(e) => println!("Event triggered: {:#?}", e),
- Err(e) => println!("Event errored: {}", e),
+ Err(e) => debug!("Mpv Event errored: {}", e),
}
}
}
diff --git a/pkgs/by-name/yt/yt/yt_dlp/src/lib.rs b/pkgs/by-name/yt/yt/yt_dlp/src/lib.rs
index 8425d0d3..851c9ab7 100644
--- a/pkgs/by-name/yt/yt/yt_dlp/src/lib.rs
+++ b/pkgs/by-name/yt/yt/yt_dlp/src/lib.rs
@@ -1,4 +1,4 @@
-use std::{fs::File, io::Write};
+// use std::{fs::File, io::Write};
use std::{path::PathBuf, sync::Once};
@@ -283,8 +283,8 @@ pub async fn extract_info(
let result_str = json_dumps(py, result)?;
- let mut file = File::create("output.info.json").unwrap();
- write!(file, "{}", result_str).unwrap();
+ //let mut file = File::create("output.info.json").unwrap();
+ //write!(file, "{}", result_str).unwrap();
Ok(serde_json::from_str(&result_str)
.expect("Python should be able to produce correct json"))
diff --git a/pkgs/by-name/yt/yt/yt_dlp/src/wrapper/info_json.rs b/pkgs/by-name/yt/yt/yt_dlp/src/wrapper/info_json.rs
index 8673998e..e9bf0402 100644
--- a/pkgs/by-name/yt/yt/yt_dlp/src/wrapper/info_json.rs
+++ b/pkgs/by-name/yt/yt/yt_dlp/src/wrapper/info_json.rs
@@ -155,7 +155,7 @@ pub struct RequestedDownloads {
pub fps: f64,
pub height: u32,
pub infojson_filename: PathBuf,
- pub language: String,
+ pub language: Option<String>,
pub protocol: String,
pub requested_formats: Vec<Format>,
pub resolution: String,
@@ -444,6 +444,7 @@ pub struct Format {
pub format_index: Option<String>,
pub format_note: Option<String>,
pub fps: Option<f64>,
+ pub fragment_base_url: Option<Todo>,
pub fragments: Option<Vec<Fragment>>,
pub has_drm: Option<bool>,
pub height: Option<u32>,