about summary refs log tree commit diff stats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Cargo.lock2461
-rw-r--r--Cargo.toml20
-rw-r--r--NEWS.md79
-rw-r--r--crates/fmt/Cargo.toml2
-rw-r--r--crates/libmpv2/examples/opengl.rs17
-rw-r--r--crates/libmpv2/libmpv2-sys/Cargo.toml2
-rw-r--r--crates/libmpv2/src/lib.rs2
-rw-r--r--crates/libmpv2/src/mpv/events.rs50
-rw-r--r--crates/libmpv2/src/mpv/protocol.rs127
-rw-r--r--crates/libmpv2/src/mpv/render.rs48
-rw-r--r--crates/libmpv2/src/tests.rs24
-rw-r--r--crates/yt/Cargo.toml (renamed from yt/Cargo.toml)18
-rw-r--r--crates/yt/src/ansi_escape_codes.rs26
-rw-r--r--crates/yt/src/app.rs (renamed from yt/src/app.rs)0
-rw-r--r--crates/yt/src/cache/mod.rs (renamed from yt/src/cache/mod.rs)0
-rw-r--r--crates/yt/src/cli.rs (renamed from yt/src/cli.rs)20
-rw-r--r--crates/yt/src/comments/comment.rs152
-rw-r--r--crates/yt/src/comments/description.rs (renamed from yt/src/comments/description.rs)8
-rw-r--r--crates/yt/src/comments/display.rs (renamed from yt/src/comments/display.rs)0
-rw-r--r--crates/yt/src/comments/mod.rs (renamed from yt/src/comments/mod.rs)26
-rw-r--r--crates/yt/src/comments/output.rs (renamed from yt/src/comments/output.rs)0
-rw-r--r--crates/yt/src/config/default.rs (renamed from yt/src/config/default.rs)6
-rw-r--r--crates/yt/src/config/definitions.rs (renamed from yt/src/config/definitions.rs)0
-rw-r--r--crates/yt/src/config/file_system.rs (renamed from yt/src/config/file_system.rs)0
-rw-r--r--crates/yt/src/config/mod.rs (renamed from yt/src/config/mod.rs)0
-rw-r--r--crates/yt/src/constants.rs (renamed from yt/src/constants.rs)0
-rw-r--r--crates/yt/src/download/download_options.rs118
-rw-r--r--crates/yt/src/download/mod.rs (renamed from yt/src/download/mod.rs)31
-rw-r--r--crates/yt/src/download/progress_hook.rs188
-rw-r--r--crates/yt/src/main.rs (renamed from yt/src/main.rs)33
-rw-r--r--crates/yt/src/select/cmds/add.rs (renamed from yt/src/select/cmds/add.rs)111
-rw-r--r--crates/yt/src/select/cmds/mod.rs (renamed from yt/src/select/cmds/mod.rs)13
-rw-r--r--crates/yt/src/select/mod.rs (renamed from yt/src/select/mod.rs)15
-rw-r--r--crates/yt/src/select/selection_file/duration.rs (renamed from yt/src/select/selection_file/duration.rs)0
-rw-r--r--crates/yt/src/select/selection_file/help.str (renamed from yt/src/select/selection_file/help.str)0
-rw-r--r--crates/yt/src/select/selection_file/help.str.license (renamed from yt/src/select/selection_file/help.str.license)0
-rw-r--r--crates/yt/src/select/selection_file/mod.rs (renamed from yt/src/select/selection_file/mod.rs)0
-rw-r--r--crates/yt/src/status/mod.rs (renamed from yt/src/status/mod.rs)11
-rw-r--r--crates/yt/src/storage/migrate/mod.rs (renamed from yt/src/storage/migrate/mod.rs)189
-rw-r--r--crates/yt/src/storage/migrate/sql/0_Empty_to_Zero.sql (renamed from yt/src/storage/migrate/sql/00_empty_to_zero.sql)0
-rw-r--r--crates/yt/src/storage/migrate/sql/1_Zero_to_One.sql (renamed from yt/src/storage/migrate/sql/01_zero_to_one.sql)0
-rw-r--r--crates/yt/src/storage/migrate/sql/2_One_to_Two.sql (renamed from yt/src/storage/migrate/sql/02_one_to_two.sql)0
-rw-r--r--crates/yt/src/storage/migrate/sql/3_Two_to_Three.sql85
-rw-r--r--crates/yt/src/storage/mod.rs (renamed from yt/src/storage/mod.rs)2
-rw-r--r--crates/yt/src/storage/subscriptions.rs (renamed from yt/src/storage/subscriptions.rs)25
-rw-r--r--crates/yt/src/storage/video_database/downloader.rs (renamed from yt/src/storage/video_database/downloader.rs)0
-rw-r--r--crates/yt/src/storage/video_database/extractor_hash.rs (renamed from yt/src/storage/video_database/extractor_hash.rs)0
-rw-r--r--crates/yt/src/storage/video_database/get/mod.rs (renamed from yt/src/storage/video_database/get/mod.rs)8
-rw-r--r--crates/yt/src/storage/video_database/get/playlist/iterator.rs (renamed from yt/src/storage/video_database/get/playlist/iterator.rs)0
-rw-r--r--crates/yt/src/storage/video_database/get/playlist/mod.rs (renamed from yt/src/storage/video_database/get/playlist/mod.rs)0
-rw-r--r--crates/yt/src/storage/video_database/mod.rs (renamed from yt/src/storage/video_database/mod.rs)0
-rw-r--r--crates/yt/src/storage/video_database/notify.rs (renamed from yt/src/storage/video_database/notify.rs)0
-rw-r--r--crates/yt/src/storage/video_database/set/mod.rs (renamed from yt/src/storage/video_database/set/mod.rs)60
-rw-r--r--crates/yt/src/storage/video_database/set/playlist.rs (renamed from yt/src/storage/video_database/set/playlist.rs)36
-rw-r--r--crates/yt/src/subscribe/mod.rs (renamed from yt/src/subscribe/mod.rs)31
-rw-r--r--crates/yt/src/unreachable.rs (renamed from yt/src/unreachable.rs)0
-rw-r--r--crates/yt/src/update/mod.rs (renamed from yt/src/update/mod.rs)69
-rw-r--r--crates/yt/src/update/updater.rs167
-rw-r--r--crates/yt/src/version/mod.rs (renamed from yt/src/version/mod.rs)0
-rw-r--r--crates/yt/src/videos/display/format_video.rs (renamed from yt/src/videos/display/format_video.rs)0
-rw-r--r--crates/yt/src/videos/display/mod.rs (renamed from yt/src/videos/display/mod.rs)0
-rw-r--r--crates/yt/src/videos/mod.rs (renamed from yt/src/videos/mod.rs)0
-rw-r--r--crates/yt/src/watch/mod.rs (renamed from yt/src/watch/mod.rs)18
-rw-r--r--crates/yt/src/watch/playlist.rs (renamed from yt/src/watch/playlist.rs)12
-rw-r--r--crates/yt/src/watch/playlist_handler/client_messages/mod.rs (renamed from yt/src/watch/playlist_handler/client_messages/mod.rs)0
-rw-r--r--crates/yt/src/watch/playlist_handler/mod.rs (renamed from yt/src/watch/playlist_handler/mod.rs)29
-rw-r--r--crates/yt_dlp/.cargo/config.toml12
-rw-r--r--crates/yt_dlp/Cargo.toml13
-rw-r--r--crates/yt_dlp/src/duration.rs78
-rw-r--r--crates/yt_dlp/src/error.rs68
-rw-r--r--crates/yt_dlp/src/lib.rs956
-rw-r--r--crates/yt_dlp/src/logging.rs148
-rw-r--r--crates/yt_dlp/src/progress_hook.rs41
-rw-r--r--crates/yt_dlp/src/python_json_decode_failed.error_msg5
-rw-r--r--crates/yt_dlp/src/python_json_decode_failed.error_msg.license9
-rw-r--r--crates/yt_dlp/src/tests.rs89
-rw-r--r--crates/yt_dlp/src/wrapper/info_json.rs824
-rw-r--r--crates/yt_dlp/src/wrapper/mod.rs12
-rw-r--r--crates/yt_dlp/src/wrapper/yt_dlp_options.rs62
-rw-r--r--flake.lock48
-rw-r--r--flake.nix31
-rwxr-xr-xscripts/mkdb.sh9
-rw-r--r--yt/src/comments/comment.rs65
-rw-r--r--yt/src/download/download_options.rs113
-rw-r--r--yt/src/update/updater.rs171
85 files changed, 4058 insertions, 3035 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f12d722..4a17021 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "addr2line"
@@ -13,9 +13,28 @@ dependencies = [
 
 [[package]]
 name = "adler2"
-version = "2.0.0"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "adler32"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.3.3",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
 
 [[package]]
 name = "aho-corasick"
@@ -49,9 +68,9 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.6.18"
+version = "0.6.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -64,44 +83,44 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "1.0.10"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.6"
+version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.1.2"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
+checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
 dependencies = [
  "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.7"
+version = "3.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
+checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
 dependencies = [
  "anstyle",
- "once_cell",
+ "once_cell_polyfill",
  "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "anyhow"
-version = "1.0.96"
+version = "1.0.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
 
 [[package]]
 name = "arrayref"
@@ -116,6 +135,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
 
 [[package]]
+name = "ascii"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
+
+[[package]]
 name = "atoi"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -125,6 +150,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "atomic"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
 name = "autocfg"
 version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -132,9 +166,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 
 [[package]]
 name = "backtrace"
-version = "0.3.74"
+version = "0.3.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
+checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
 dependencies = [
  "addr2line",
  "cfg-if",
@@ -153,20 +187,20 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
 
 [[package]]
 name = "base64ct"
-version = "1.6.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
 
 [[package]]
 name = "bindgen"
-version = "0.71.1"
+version = "0.72.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
+checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
 dependencies = [
- "bitflags 2.8.0",
+ "bitflags 2.9.1",
  "cexpr",
  "clang-sys",
- "itertools",
+ "itertools 0.13.0",
  "log",
  "prettyplease",
  "proc-macro2",
@@ -185,25 +219,33 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.8.0"
+version = "2.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
 dependencies = [
  "serde",
 ]
 
 [[package]]
+name = "blake2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
+dependencies = [
+ "digest",
+]
+
+[[package]]
 name = "blake3"
-version = "1.6.0"
+version = "1.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937"
+checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
 dependencies = [
  "arrayref",
  "arrayvec",
  "cc",
  "cfg-if",
- "constant_time_eq",
- "memmap2",
+ "constant_time_eq 0.3.1",
 ]
 
 [[package]]
@@ -216,10 +258,27 @@ dependencies = [
 ]
 
 [[package]]
+name = "bstr"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
 name = "bumpalo"
-version = "3.17.0"
+version = "3.18.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
+
+[[package]]
+name = "bytemuck"
+version = "1.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
 
 [[package]]
 name = "byteorder"
@@ -229,22 +288,60 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.4.1"
+version = "1.5.0"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "bytes"
-version = "1.10.0"
+version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "bzip2"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
+dependencies = [
+ "bzip2-sys",
+ "libbz2-rs-sys",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.13+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "caseless"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8"
+dependencies = [
+ "unicode-normalization",
+]
+
+[[package]]
+name = "castaway"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
+dependencies = [
+ "rustversion",
+]
 
 [[package]]
 name = "cc"
-version = "1.2.15"
+version = "1.2.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
+checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
 dependencies = [
  "shlex",
 ]
@@ -260,22 +357,28 @@ dependencies = [
 
 [[package]]
 name = "cfg-if"
-version = "1.0.0"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
 
 [[package]]
 name = "chrono"
-version = "0.4.39"
+version = "0.4.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
  "js-sys",
  "num-traits",
  "wasm-bindgen",
- "windows-targets 0.52.6",
+ "windows-link",
 ]
 
 [[package]]
@@ -300,9 +403,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.30"
+version = "4.5.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
+checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -310,9 +413,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.30"
+version = "4.5.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
+checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
 dependencies = [
  "anstream",
  "anstyle",
@@ -322,9 +425,9 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.5.28"
+version = "4.5.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
+checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -334,15 +437,38 @@ dependencies = [
 
 [[package]]
 name = "clap_lex"
-version = "0.7.4"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+
+[[package]]
+name = "clipboard-win"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
+dependencies = [
+ "error-code",
+]
 
 [[package]]
 name = "colorchoice"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "compact_str"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
 
 [[package]]
 name = "concurrent-queue"
@@ -366,6 +492,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
 
 [[package]]
+name = "constant_time_eq"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
 name = "core-foundation-sys"
 version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -382,9 +524,9 @@ dependencies = [
 
 [[package]]
 name = "crc"
-version = "3.2.1"
+version = "3.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
+checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
 dependencies = [
  "crc-catalog",
 ]
@@ -396,6 +538,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
 
 [[package]]
+name = "crc32fast"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
 name = "crossbeam"
 version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -410,9 +561,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.14"
+version = "0.5.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
+checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
 dependencies = [
  "crossbeam-utils",
 ]
@@ -452,6 +603,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
 
 [[package]]
+name = "crunchy"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
+
+[[package]]
 name = "crypto-common"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -462,10 +619,19 @@ dependencies = [
 ]
 
 [[package]]
+name = "csv-core"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
 name = "der"
-version = "0.7.9"
+version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
 dependencies = [
  "const-oid",
  "pem-rfc7468",
@@ -485,6 +651,27 @@ dependencies = [
 ]
 
 [[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
 name = "displaydoc"
 version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -496,21 +683,74 @@ dependencies = [
 ]
 
 [[package]]
+name = "dns-lookup"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "socket2",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
 name = "dotenvy"
 version = "0.15.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
 
 [[package]]
+name = "dyn-clone"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
+
+[[package]]
 name = "either"
-version = "1.13.0"
+version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
 dependencies = [
  "serde",
 ]
 
 [[package]]
+name = "endian-type"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
+
+[[package]]
+name = "env_filter"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_home"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
+
+[[package]]
+name = "env_logger"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "jiff",
+ "log",
+]
+
+[[package]]
 name = "equivalent"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -518,15 +758,21 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
 
 [[package]]
 name = "errno"
-version = "0.3.10"
+version = "0.3.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
+checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
 dependencies = [
  "libc",
  "windows-sys 0.59.0",
 ]
 
 [[package]]
+name = "error-code"
+version = "3.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
+
+[[package]]
 name = "etcetera"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -549,12 +795,29 @@ dependencies = [
 ]
 
 [[package]]
+name = "exitcode"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
+
+[[package]]
 name = "fastrand"
 version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
 
 [[package]]
+name = "fd-lock"
+version = "4.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
+dependencies = [
+ "cfg-if",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
 name = "filetime"
 version = "0.2.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -567,6 +830,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "flate2"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
+dependencies = [
+ "crc32fast",
+ "libz-rs-sys",
+ "miniz_oxide",
+]
+
+[[package]]
 name = "flume"
 version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -579,9 +853,24 @@ dependencies = [
 
 [[package]]
 name = "foldhash"
-version = "0.1.4"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[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"
@@ -703,26 +992,45 @@ dependencies = [
 ]
 
 [[package]]
+name = "gethostname"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55"
+dependencies = [
+ "rustix",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "getopts"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
 name = "getrandom"
-version = "0.2.15"
+version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
 dependencies = [
  "cfg-if",
  "libc",
- "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasi 0.11.1+wasi-snapshot-preview1",
 ]
 
 [[package]]
 name = "getrandom"
-version = "0.3.1"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
 dependencies = [
  "cfg-if",
  "libc",
- "wasi 0.13.3+wasi-0.2.2",
- "windows-targets 0.52.6",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
 ]
 
 [[package]]
@@ -738,10 +1046,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
 
 [[package]]
+name = "half"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
+[[package]]
 name = "hashbrown"
-version = "0.15.2"
+version = "0.15.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
 dependencies = [
  "allocator-api2",
  "equivalent",
@@ -765,9 +1083,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
 [[package]]
 name = "hermit-abi"
-version = "0.4.0"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
 
 [[package]]
 name = "hex"
@@ -776,6 +1094,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
 [[package]]
+name = "hexf-parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
+
+[[package]]
 name = "hkdf"
 version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -804,16 +1128,17 @@ dependencies = [
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.61"
+version = "0.1.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
  "iana-time-zone-haiku",
  "js-sys",
+ "log",
  "wasm-bindgen",
- "windows-core",
+ "windows-core 0.61.2",
 ]
 
 [[package]]
@@ -827,21 +1152,22 @@ dependencies = [
 
 [[package]]
 name = "icu_collections"
-version = "1.5.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
 dependencies = [
  "displaydoc",
+ "potential_utf",
  "yoke",
  "zerofrom",
  "zerovec",
 ]
 
 [[package]]
-name = "icu_locid"
-version = "1.5.0"
+name = "icu_locale_core"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
 dependencies = [
  "displaydoc",
  "litemap",
@@ -851,30 +1177,10 @@ dependencies = [
 ]
 
 [[package]]
-name = "icu_locid_transform"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
-dependencies = [
- "displaydoc",
- "icu_locid",
- "icu_locid_transform_data",
- "icu_provider",
- "tinystr",
- "zerovec",
-]
-
-[[package]]
-name = "icu_locid_transform_data"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
-
-[[package]]
 name = "icu_normalizer"
-version = "1.5.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
 dependencies = [
  "displaydoc",
  "icu_collections",
@@ -882,68 +1188,55 @@ dependencies = [
  "icu_properties",
  "icu_provider",
  "smallvec",
- "utf16_iter",
- "utf8_iter",
- "write16",
  "zerovec",
 ]
 
 [[package]]
 name = "icu_normalizer_data"
-version = "1.5.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
 
 [[package]]
 name = "icu_properties"
-version = "1.5.1"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
 dependencies = [
  "displaydoc",
  "icu_collections",
- "icu_locid_transform",
+ "icu_locale_core",
  "icu_properties_data",
  "icu_provider",
- "tinystr",
+ "potential_utf",
+ "zerotrie",
  "zerovec",
 ]
 
 [[package]]
 name = "icu_properties_data"
-version = "1.5.0"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
 
 [[package]]
 name = "icu_provider"
-version = "1.5.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
 dependencies = [
  "displaydoc",
- "icu_locid",
- "icu_provider_macros",
+ "icu_locale_core",
  "stable_deref_trait",
  "tinystr",
  "writeable",
  "yoke",
  "zerofrom",
+ "zerotrie",
  "zerovec",
 ]
 
 [[package]]
-name = "icu_provider_macros"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
 name = "idna"
 version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -956,9 +1249,9 @@ dependencies = [
 
 [[package]]
 name = "idna_adapter"
-version = "1.2.0"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
 dependencies = [
  "icu_normalizer",
  "icu_properties",
@@ -966,27 +1259,21 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.7.1"
+version = "2.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
+checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
 dependencies = [
  "equivalent",
  "hashbrown",
 ]
 
 [[package]]
-name = "indoc"
-version = "2.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
-
-[[package]]
 name = "inotify"
 version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
 dependencies = [
- "bitflags 2.8.0",
+ "bitflags 2.9.1",
  "inotify-sys",
  "libc",
 ]
@@ -1001,10 +1288,22 @@ dependencies = [
 ]
 
 [[package]]
+name = "is-macro"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "is-terminal"
-version = "0.4.15"
+version = "0.4.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
+checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
 dependencies = [
  "hermit-abi",
  "libc",
@@ -1027,10 +1326,43 @@ dependencies = [
 ]
 
 [[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
 name = "itoa"
-version = "1.0.14"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "jiff"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
+dependencies = [
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
 
 [[package]]
 name = "js-sys"
@@ -1043,10 +1375,29 @@ dependencies = [
 ]
 
 [[package]]
+name = "junction"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72bbdfd737a243da3dfc1f99ee8d6e166480f17ab4ac84d7c34aacd73fc7bd16"
+dependencies = [
+ "scopeguard",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
+dependencies = [
+ "cpufeatures",
+]
+
+[[package]]
 name = "kqueue"
-version = "1.0.8"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
+checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
 dependencies = [
  "kqueue-sys",
  "libc",
@@ -1072,30 +1423,91 @@ dependencies = [
 ]
 
 [[package]]
+name = "lexical-parse-float"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2"
+dependencies = [
+ "lexical-parse-integer",
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-parse-integer"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e"
+dependencies = [
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-util"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3"
+dependencies = [
+ "static_assertions",
+]
+
+[[package]]
+name = "lexopt"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7"
+
+[[package]]
+name = "libbz2-rs-sys"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0864a00c8d019e36216b69c2c4ce50b83b7bd966add3cf5ba554ec44f8bebcf5"
+
+[[package]]
 name = "libc"
-version = "0.2.169"
+version = "0.2.173"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
+checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb"
+
+[[package]]
+name = "libffi"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebfd30a67b482a08116e753d0656cb626548cf4242543e5cc005be7639d99838"
+dependencies = [
+ "libc",
+ "libffi-sys",
+]
+
+[[package]]
+name = "libffi-sys"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f003aa318c9f0ee69eb0ada7c78f5c9d2fedd2ceb274173b5c7ff475eee584a3"
+dependencies = [
+ "cc",
+]
 
 [[package]]
 name = "libloading"
-version = "0.8.6"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
+checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
 dependencies = [
  "cfg-if",
- "windows-targets 0.52.6",
+ "windows-targets 0.53.2",
 ]
 
 [[package]]
 name = "libm"
-version = "0.2.11"
+version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
 
 [[package]]
 name = "libmpv2"
-version = "1.4.1"
+version = "1.5.0"
 dependencies = [
  "crossbeam",
  "libmpv2-sys",
@@ -1105,7 +1517,7 @@ dependencies = [
 
 [[package]]
 name = "libmpv2-sys"
-version = "1.4.1"
+version = "1.5.0"
 dependencies = [
  "bindgen",
 ]
@@ -1116,7 +1528,7 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
 dependencies = [
- "bitflags 2.8.0",
+ "bitflags 2.9.1",
  "libc",
  "redox_syscall",
 ]
@@ -1133,22 +1545,31 @@ dependencies = [
 ]
 
 [[package]]
+name = "libz-rs-sys"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221"
+dependencies = [
+ "zlib-rs",
+]
+
+[[package]]
 name = "linux-raw-sys"
-version = "0.4.15"
+version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
 
 [[package]]
 name = "litemap"
-version = "0.7.4"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
+checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
 
 [[package]]
 name = "lock_api"
-version = "0.4.12"
+version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
 dependencies = [
  "autocfg",
  "scopeguard",
@@ -1156,9 +1577,99 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.26"
+version = "0.4.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "lz4_flex"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5"
+dependencies = [
+ "twox-hash",
+]
+
+[[package]]
+name = "lzma-sys"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "mac_address"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
+dependencies = [
+ "nix",
+ "winapi",
+]
+
+[[package]]
+name = "malachite-base"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c738d3789301e957a8f7519318fcbb1b92bb95863b28f6938ae5a05be6259f34"
+dependencies = [
+ "hashbrown",
+ "itertools 0.14.0",
+ "libm",
+ "ryu",
+]
+
+[[package]]
+name = "malachite-bigint"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f46b904a4725706c5ad0133b662c20b388a3ffb04bda5154029dcb0cd28ae34"
+dependencies = [
+ "malachite-base",
+ "malachite-nz",
+ "num-integer",
+ "num-traits",
+ "paste",
+]
+
+[[package]]
+name = "malachite-nz"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1707c9a1fa36ce21749b35972bfad17bbf34cf5a7c96897c0491da321e387d3b"
+dependencies = [
+ "itertools 0.14.0",
+ "libm",
+ "malachite-base",
+ "wide",
+]
+
+[[package]]
+name = "malachite-q"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d764801aa4e96bbb69b389dcd03b50075345131cd63ca2e380bca71cc37a3675"
+dependencies = [
+ "itertools 0.14.0",
+ "malachite-base",
+ "malachite-nz",
+]
+
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
+[[package]]
+name = "matches"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
 
 [[package]]
 name = "md-5"
@@ -1172,15 +1683,15 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.7.4"
+version = "2.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
 
 [[package]]
 name = "memmap2"
-version = "0.9.5"
+version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
 dependencies = [
  "libc",
 ]
@@ -1202,23 +1713,54 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
 [[package]]
 name = "miniz_oxide"
-version = "0.8.5"
+version = "0.8.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
 dependencies = [
  "adler2",
 ]
 
 [[package]]
 name = "mio"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
+checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
 dependencies = [
  "libc",
  "log",
- "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys 0.52.0",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "mt19937"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df7151a832e54d2d6b2c827a20e5bcdd80359281cd2c354e725d4b82e7c471de"
+dependencies = [
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "nibble_vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.9.1",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
 ]
 
 [[package]]
@@ -1237,7 +1779,7 @@ version = "8.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
 dependencies = [
- "bitflags 2.8.0",
+ "bitflags 2.9.1",
  "filetime",
  "inotify",
  "kqueue",
@@ -1283,6 +1825,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
 name = "num-integer"
 version = "0.1.46"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1313,6 +1864,36 @@ dependencies = [
 ]
 
 [[package]]
+name = "num_cpus"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "object"
 version = "0.36.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1323,15 +1904,81 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.20.3"
+version = "1.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+
+[[package]]
+name = "openssl"
+version = "0.10.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
+dependencies = [
+ "bitflags 2.9.1",
+ "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",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "optional"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978aa494585d3ca4ad74929863093e87cac9790d81fe7aba2b3dc2890643a0fc"
 
 [[package]]
 name = "owo-colors"
-version = "4.1.0"
+version = "4.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56"
+checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec"
+
+[[package]]
+name = "page_size"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
+dependencies = [
+ "libc",
+ "winapi",
+]
 
 [[package]]
 name = "parking"
@@ -1341,9 +1988,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
 
 [[package]]
 name = "parking_lot"
-version = "0.12.3"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
 dependencies = [
  "lock_api",
  "parking_lot_core",
@@ -1351,9 +1998,9 @@ dependencies = [
 
 [[package]]
 name = "parking_lot_core"
-version = "0.9.10"
+version = "0.9.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
 dependencies = [
  "cfg-if",
  "libc",
@@ -1363,6 +2010,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
 name = "pem-rfc7468"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1379,20 +2032,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pest"
-version = "2.7.15"
+version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc"
+checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
 dependencies = [
  "memchr",
- "thiserror",
+ "thiserror 2.0.12",
  "ucd-trie",
 ]
 
 [[package]]
 name = "pest_derive"
-version = "2.7.15"
+version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e"
+checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
 dependencies = [
  "pest",
  "pest_generator",
@@ -1400,9 +2053,9 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.15"
+version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b"
+checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
 dependencies = [
  "pest",
  "pest_meta",
@@ -1413,9 +2066,9 @@ dependencies = [
 
 [[package]]
 name = "pest_meta"
-version = "2.7.15"
+version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea"
+checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
 dependencies = [
  "once_cell",
  "pest",
@@ -1423,6 +2076,44 @@ dependencies = [
 ]
 
 [[package]]
+name = "phf"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
 name = "pin-project-lite"
 version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1457,114 +2148,114 @@ dependencies = [
 
 [[package]]
 name = "pkg-config"
-version = "0.3.31"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "pmutil"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
+checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
 
 [[package]]
 name = "portable-atomic"
-version = "1.10.0"
+version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
+checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
 
 [[package]]
-name = "ppv-lite86"
-version = "0.2.20"
+name = "portable-atomic-util"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
 dependencies = [
- "zerocopy",
+ "portable-atomic",
 ]
 
 [[package]]
-name = "prettyplease"
-version = "0.2.29"
+name = "potential_utf"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
+checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
 dependencies = [
- "proc-macro2",
- "syn",
+ "zerovec",
 ]
 
 [[package]]
-name = "proc-macro2"
-version = "1.0.93"
+name = "ppv-lite86"
+version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
 dependencies = [
- "unicode-ident",
+ "zerocopy",
 ]
 
 [[package]]
-name = "pyo3"
-version = "0.23.4"
+name = "prettyplease"
+version = "0.2.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc"
+checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55"
 dependencies = [
- "cfg-if",
- "indoc",
- "libc",
- "memoffset",
- "once_cell",
- "portable-atomic",
- "pyo3-build-config",
- "pyo3-ffi",
- "pyo3-macros",
- "unindent",
+ "proc-macro2",
+ "syn",
 ]
 
 [[package]]
-name = "pyo3-build-config"
-version = "0.23.4"
+name = "proc-macro2"
+version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
 dependencies = [
- "once_cell",
- "target-lexicon",
+ "unicode-ident",
 ]
 
 [[package]]
-name = "pyo3-ffi"
-version = "0.23.4"
+name = "pymath"
+version = "0.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d"
+checksum = "5b66ab66a8610ce209d8b36cd0fecc3a15c494f715e0cb26f0586057f293abc9"
 dependencies = [
  "libc",
- "pyo3-build-config",
 ]
 
 [[package]]
-name = "pyo3-macros"
-version = "0.23.4"
+name = "quote"
+version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
 dependencies = [
  "proc-macro2",
- "pyo3-macros-backend",
- "quote",
- "syn",
 ]
 
 [[package]]
-name = "pyo3-macros-backend"
-version = "0.23.4"
+name = "r-efi"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+
+[[package]]
+name = "radium"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4"
+checksum = "db0b76288902db304c864a12046b73d2d895cc34a4bb8137baaeebe9978a072c"
 dependencies = [
- "heck",
- "proc-macro2",
- "pyo3-build-config",
- "quote",
- "syn",
+ "cfg-if",
 ]
 
 [[package]]
-name = "quote"
-version = "1.0.38"
+name = "radix_trie"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
+checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
 dependencies = [
- "proc-macro2",
+ "endian-type",
+ "nibble_vec",
 ]
 
 [[package]]
@@ -1575,7 +2266,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 dependencies = [
  "libc",
  "rand_chacha",
- "rand_core",
+ "rand_core 0.6.4",
 ]
 
 [[package]]
@@ -1585,7 +2276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
 dependencies = [
  "ppv-lite86",
- "rand_core",
+ "rand_core 0.6.4",
 ]
 
 [[package]]
@@ -1594,16 +2285,36 @@ version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.3",
 ]
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.9"
+version = "0.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
+dependencies = [
+ "bitflags 2.9.1",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f"
+checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
 dependencies = [
- "bitflags 2.8.0",
+ "getrandom 0.2.16",
+ "libredox",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -1636,10 +2347,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 
 [[package]]
+name = "result-like"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abf7172fef6a7d056b5c26bf6c826570267562d51697f4982ff3ba4aec68a9df"
+dependencies = [
+ "result-like-derive",
+]
+
+[[package]]
+name = "result-like-derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d6574c02e894d66370cfc681e5d68fedbc9a548fb55b30a96b3f0ae22d0fe5"
+dependencies = [
+ "pmutil",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "rsa"
-version = "0.9.7"
+version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519"
+checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
 dependencies = [
  "const-oid",
  "digest",
@@ -1648,7 +2380,7 @@ dependencies = [
  "num-traits",
  "pkcs1",
  "pkcs8",
- "rand_core",
+ "rand_core 0.6.4",
  "signature",
  "spki",
  "subtle",
@@ -1656,10 +2388,71 @@ dependencies = [
 ]
 
 [[package]]
+name = "ruff_python_ast"
+version = "0.0.0"
+source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a"
+dependencies = [
+ "aho-corasick",
+ "bitflags 2.9.1",
+ "compact_str",
+ "is-macro",
+ "itertools 0.14.0",
+ "memchr",
+ "ruff_python_trivia",
+ "ruff_source_file",
+ "ruff_text_size",
+ "rustc-hash",
+]
+
+[[package]]
+name = "ruff_python_parser"
+version = "0.0.0"
+source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a"
+dependencies = [
+ "bitflags 2.9.1",
+ "bstr",
+ "compact_str",
+ "memchr",
+ "ruff_python_ast",
+ "ruff_python_trivia",
+ "ruff_text_size",
+ "rustc-hash",
+ "static_assertions",
+ "unicode-ident",
+ "unicode-normalization",
+ "unicode_names2",
+]
+
+[[package]]
+name = "ruff_python_trivia"
+version = "0.0.0"
+source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a"
+dependencies = [
+ "itertools 0.14.0",
+ "ruff_source_file",
+ "ruff_text_size",
+ "unicode-ident",
+]
+
+[[package]]
+name = "ruff_source_file"
+version = "0.0.0"
+source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a"
+dependencies = [
+ "memchr",
+ "ruff_text_size",
+]
+
+[[package]]
+name = "ruff_text_size"
+version = "0.0.0"
+source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a"
+
+[[package]]
 name = "rustc-demangle"
-version = "0.1.24"
+version = "0.1.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
 
 [[package]]
 name = "rustc-hash"
@@ -1669,11 +2462,11 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
 
 [[package]]
 name = "rustix"
-version = "0.38.44"
+version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
 dependencies = [
- "bitflags 2.8.0",
+ "bitflags 2.9.1",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -1681,16 +2474,386 @@ dependencies = [
 ]
 
 [[package]]
+name = "rustpython"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "cfg-if",
+ "dirs-next",
+ "env_logger",
+ "lexopt",
+ "libc",
+ "log",
+ "ruff_python_parser",
+ "rustpython-compiler",
+ "rustpython-pylib",
+ "rustpython-stdlib",
+ "rustpython-vm",
+ "rustyline",
+]
+
+[[package]]
+name = "rustpython-codegen"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "ahash",
+ "bitflags 2.9.1",
+ "indexmap",
+ "itertools 0.14.0",
+ "log",
+ "malachite-bigint",
+ "memchr",
+ "num-complex",
+ "num-traits",
+ "ruff_python_ast",
+ "ruff_source_file",
+ "ruff_text_size",
+ "rustpython-compiler-core",
+ "rustpython-compiler-source",
+ "rustpython-literal",
+ "rustpython-wtf8",
+ "thiserror 2.0.12",
+ "unicode_names2",
+]
+
+[[package]]
+name = "rustpython-common"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "ascii",
+ "bitflags 2.9.1",
+ "bstr",
+ "cfg-if",
+ "getrandom 0.3.3",
+ "itertools 0.14.0",
+ "libc",
+ "lock_api",
+ "malachite-base",
+ "malachite-bigint",
+ "malachite-q",
+ "memchr",
+ "num-traits",
+ "once_cell",
+ "parking_lot",
+ "radium",
+ "rustpython-literal",
+ "rustpython-wtf8",
+ "siphasher",
+ "unicode_names2",
+ "widestring",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustpython-compiler"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "ruff_python_ast",
+ "ruff_python_parser",
+ "ruff_source_file",
+ "ruff_text_size",
+ "rustpython-codegen",
+ "rustpython-compiler-core",
+ "rustpython-compiler-source",
+ "thiserror 2.0.12",
+]
+
+[[package]]
+name = "rustpython-compiler-core"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "bitflags 2.9.1",
+ "itertools 0.14.0",
+ "lz4_flex",
+ "malachite-bigint",
+ "num-complex",
+ "ruff_source_file",
+ "rustpython-wtf8",
+]
+
+[[package]]
+name = "rustpython-compiler-source"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "ruff_source_file",
+ "ruff_text_size",
+]
+
+[[package]]
+name = "rustpython-derive"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "proc-macro2",
+ "rustpython-compiler",
+ "rustpython-derive-impl",
+ "syn",
+]
+
+[[package]]
+name = "rustpython-derive-impl"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "itertools 0.14.0",
+ "maplit",
+ "proc-macro2",
+ "quote",
+ "rustpython-compiler-core",
+ "rustpython-doc",
+ "syn",
+ "syn-ext",
+ "textwrap",
+]
+
+[[package]]
+name = "rustpython-doc"
+version = "0.3.0"
+source = "git+https://github.com/RustPython/__doc__?tag=0.3.0#8b62ce5d796d68a091969c9fa5406276cb483f79"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "rustpython-literal"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "hexf-parse",
+ "is-macro",
+ "lexical-parse-float",
+ "num-traits",
+ "rustpython-wtf8",
+ "unic-ucd-category",
+]
+
+[[package]]
+name = "rustpython-pylib"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "glob",
+]
+
+[[package]]
+name = "rustpython-sre_engine"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "bitflags 2.9.1",
+ "num_enum",
+ "optional",
+ "rustpython-wtf8",
+]
+
+[[package]]
+name = "rustpython-stdlib"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "adler32",
+ "ahash",
+ "ascii",
+ "base64",
+ "blake2",
+ "bzip2",
+ "cfg-if",
+ "crc32fast",
+ "crossbeam-utils",
+ "csv-core",
+ "digest",
+ "dns-lookup",
+ "dyn-clone",
+ "flate2",
+ "foreign-types-shared",
+ "gethostname",
+ "hex",
+ "indexmap",
+ "itertools 0.14.0",
+ "junction",
+ "libc",
+ "libz-rs-sys",
+ "lzma-sys",
+ "mac_address",
+ "malachite-bigint",
+ "md-5",
+ "memchr",
+ "memmap2",
+ "mt19937",
+ "nix",
+ "num-complex",
+ "num-integer",
+ "num-traits",
+ "num_enum",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "page_size",
+ "parking_lot",
+ "paste",
+ "pymath",
+ "rand_core 0.9.3",
+ "rustix",
+ "rustpython-common",
+ "rustpython-derive",
+ "rustpython-vm",
+ "schannel",
+ "sha-1",
+ "sha2",
+ "sha3",
+ "socket2",
+ "system-configuration",
+ "termios",
+ "ucd",
+ "unic-char-property",
+ "unic-normal",
+ "unic-ucd-age",
+ "unic-ucd-bidi",
+ "unic-ucd-category",
+ "unic-ucd-ident",
+ "unicode-casing",
+ "unicode_names2",
+ "uuid",
+ "widestring",
+ "windows-sys 0.59.0",
+ "xml-rs",
+ "xz2",
+]
+
+[[package]]
+name = "rustpython-vm"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "ahash",
+ "ascii",
+ "bitflags 2.9.1",
+ "bstr",
+ "caseless",
+ "cfg-if",
+ "chrono",
+ "constant_time_eq 0.4.2",
+ "crossbeam-utils",
+ "errno",
+ "exitcode",
+ "getrandom 0.3.3",
+ "glob",
+ "half",
+ "hex",
+ "indexmap",
+ "is-macro",
+ "itertools 0.14.0",
+ "junction",
+ "libc",
+ "libffi",
+ "libloading",
+ "log",
+ "malachite-bigint",
+ "memchr",
+ "memoffset",
+ "nix",
+ "num-complex",
+ "num-integer",
+ "num-traits",
+ "num_cpus",
+ "num_enum",
+ "once_cell",
+ "optional",
+ "parking_lot",
+ "paste",
+ "result-like",
+ "ruff_python_ast",
+ "ruff_python_parser",
+ "ruff_source_file",
+ "ruff_text_size",
+ "rustix",
+ "rustpython-codegen",
+ "rustpython-common",
+ "rustpython-compiler",
+ "rustpython-compiler-core",
+ "rustpython-compiler-source",
+ "rustpython-derive",
+ "rustpython-literal",
+ "rustpython-sre_engine",
+ "rustyline",
+ "schannel",
+ "static_assertions",
+ "strum",
+ "strum_macros",
+ "thiserror 2.0.12",
+ "thread_local",
+ "timsort",
+ "uname",
+ "unic-ucd-bidi",
+ "unic-ucd-category",
+ "unic-ucd-ident",
+ "unicode-casing",
+ "unicode_names2",
+ "which",
+ "widestring",
+ "windows",
+ "windows-sys 0.59.0",
+ "winreg",
+]
+
+[[package]]
+name = "rustpython-wtf8"
+version = "0.4.0"
+source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+dependencies = [
+ "ascii",
+ "bstr",
+ "itertools 0.14.0",
+ "memchr",
+]
+
+[[package]]
 name = "rustversion"
-version = "1.0.19"
+version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
+checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
+
+[[package]]
+name = "rustyline"
+version = "15.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f"
+dependencies = [
+ "bitflags 2.9.1",
+ "cfg-if",
+ "clipboard-win",
+ "fd-lock",
+ "home",
+ "libc",
+ "log",
+ "memchr",
+ "nix",
+ "radix_trie",
+ "unicode-segmentation",
+ "unicode-width",
+ "utf8parse",
+ "windows-sys 0.59.0",
+]
 
 [[package]]
 name = "ryu"
-version = "1.0.19"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "safe_arch"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323"
+dependencies = [
+ "bytemuck",
+]
 
 [[package]]
 name = "same-file"
@@ -1702,6 +2865,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
 name = "scopeguard"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1732,18 +2904,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.218"
+version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.218"
+version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1752,9 +2924,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.139"
+version = "1.0.140"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
 dependencies = [
  "itoa",
  "memchr",
@@ -1764,9 +2936,9 @@ dependencies = [
 
 [[package]]
 name = "serde_spanned"
-version = "0.6.8"
+version = "0.6.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
 dependencies = [
  "serde",
 ]
@@ -1784,6 +2956,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "sha-1"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
 name = "sha1"
 version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1796,9 +2979,9 @@ dependencies = [
 
 [[package]]
 name = "sha2"
-version = "0.10.8"
+version = "0.10.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
 dependencies = [
  "cfg-if",
  "cpufeatures",
@@ -1806,6 +2989,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "sha3"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
+dependencies = [
+ "digest",
+ "keccak",
+]
+
+[[package]]
 name = "shlex"
 version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1813,9 +3006,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
 name = "signal-hook-registry"
-version = "1.4.2"
+version = "1.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
+checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
 dependencies = [
  "libc",
 ]
@@ -1827,10 +3020,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
 dependencies = [
  "digest",
- "rand_core",
+ "rand_core 0.6.4",
 ]
 
 [[package]]
+name = "siphasher"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
+
+[[package]]
 name = "slab"
 version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1841,18 +3040,18 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.14.0"
+version = "1.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "socket2"
-version = "0.5.8"
+version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
+checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
 dependencies = [
  "libc",
  "windows-sys 0.52.0",
@@ -1879,9 +3078,9 @@ dependencies = [
 
 [[package]]
 name = "sqlx"
-version = "0.8.3"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f"
+checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
 dependencies = [
  "sqlx-core",
  "sqlx-macros",
@@ -1892,11 +3091,12 @@ dependencies = [
 
 [[package]]
 name = "sqlx-core"
-version = "0.8.3"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0"
+checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
 dependencies = [
- "bytes 1.10.0",
+ "base64",
+ "bytes 1.10.1",
  "crc",
  "crossbeam-queue",
  "either",
@@ -1916,7 +3116,7 @@ dependencies = [
  "serde_json",
  "sha2",
  "smallvec",
- "thiserror",
+ "thiserror 2.0.12",
  "tokio",
  "tokio-stream",
  "tracing",
@@ -1925,9 +3125,9 @@ dependencies = [
 
 [[package]]
 name = "sqlx-macros"
-version = "0.8.3"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310"
+checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1938,9 +3138,9 @@ dependencies = [
 
 [[package]]
 name = "sqlx-macros-core"
-version = "0.8.3"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad"
+checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
 dependencies = [
  "dotenvy",
  "either",
@@ -1957,22 +3157,21 @@ dependencies = [
  "sqlx-postgres",
  "sqlx-sqlite",
  "syn",
- "tempfile",
  "tokio",
  "url",
 ]
 
 [[package]]
 name = "sqlx-mysql"
-version = "0.8.3"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233"
+checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
 dependencies = [
  "atoi",
  "base64",
- "bitflags 2.8.0",
+ "bitflags 2.9.1",
  "byteorder",
- "bytes 1.10.0",
+ "bytes 1.10.1",
  "crc",
  "digest",
  "dotenvy",
@@ -1999,20 +3198,20 @@ dependencies = [
  "smallvec",
  "sqlx-core",
  "stringprep",
- "thiserror",
+ "thiserror 2.0.12",
  "tracing",
  "whoami",
 ]
 
 [[package]]
 name = "sqlx-postgres"
-version = "0.8.3"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613"
+checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
 dependencies = [
  "atoi",
  "base64",
- "bitflags 2.8.0",
+ "bitflags 2.9.1",
  "byteorder",
  "crc",
  "dotenvy",
@@ -2036,16 +3235,16 @@ dependencies = [
  "smallvec",
  "sqlx-core",
  "stringprep",
- "thiserror",
+ "thiserror 2.0.12",
  "tracing",
  "whoami",
 ]
 
 [[package]]
 name = "sqlx-sqlite"
-version = "0.8.3"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540"
+checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
 dependencies = [
  "atoi",
  "flume",
@@ -2060,6 +3259,7 @@ dependencies = [
  "serde",
  "serde_urlencoded",
  "sqlx-core",
+ "thiserror 2.0.12",
  "tracing",
  "url",
 ]
@@ -2071,6 +3271,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
 [[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
 name = "stderrlog"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2101,6 +3307,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
 
 [[package]]
+name = "strum"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
+
+[[package]]
+name = "strum_macros"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn",
+]
+
+[[package]]
 name = "subtle"
 version = "2.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2108,9 +3333,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
 
 [[package]]
 name = "syn"
-version = "2.0.98"
+version = "2.0.103"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
+checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2118,10 +3343,21 @@ dependencies = [
 ]
 
 [[package]]
+name = "syn-ext"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b126de4ef6c2a628a68609dd00733766c3b015894698a438ebdf374933fc31d1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "synstructure"
-version = "0.13.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2129,20 +3365,34 @@ dependencies = [
 ]
 
 [[package]]
-name = "target-lexicon"
-version = "0.12.16"
+name = "system-configuration"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
+checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
+dependencies = [
+ "bitflags 2.9.1",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
 
 [[package]]
 name = "tempfile"
-version = "3.17.1"
+version = "3.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
+checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
 dependencies = [
- "cfg-if",
  "fastrand",
- "getrandom 0.3.1",
+ "getrandom 0.3.3",
  "once_cell",
  "rustix",
  "windows-sys 0.59.0",
@@ -2158,27 +3408,62 @@ dependencies = [
 ]
 
 [[package]]
+name = "termios"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
 name = "termsize"
-version = "1.4.1"
+version = "1.5.0"
 dependencies = [
  "libc",
  "winapi",
 ]
 
 [[package]]
+name = "textwrap"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
 name = "thiserror"
-version = "2.0.11"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl 2.0.12",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
 dependencies = [
- "thiserror-impl",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.11"
+version = "2.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2187,19 +3472,24 @@ dependencies = [
 
 [[package]]
 name = "thread_local"
-version = "1.1.8"
+version = "1.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
 dependencies = [
  "cfg-if",
- "once_cell",
 ]
 
 [[package]]
+name = "timsort"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "639ce8ef6d2ba56be0383a94dd13b92138d58de44c62618303bb798fa92bdc00"
+
+[[package]]
 name = "tinystr"
-version = "0.7.6"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
 dependencies = [
  "displaydoc",
  "zerovec",
@@ -2207,9 +3497,9 @@ dependencies = [
 
 [[package]]
 name = "tinyvec"
-version = "1.8.1"
+version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
+checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
 dependencies = [
  "tinyvec_macros",
 ]
@@ -2222,12 +3512,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.43.0"
+version = "1.45.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
+checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
 dependencies = [
  "backtrace",
- "bytes 1.10.0",
+ "bytes 1.10.1",
  "libc",
  "mio",
  "pin-project-lite",
@@ -2261,9 +3551,9 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.8.20"
+version = "0.8.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
+checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
 dependencies = [
  "serde",
  "serde_spanned",
@@ -2273,27 +3563,34 @@ dependencies = [
 
 [[package]]
 name = "toml_datetime"
-version = "0.6.8"
+version = "0.6.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "toml_edit"
-version = "0.22.24"
+version = "0.22.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
 dependencies = [
  "indexmap",
  "serde",
  "serde_spanned",
  "toml_datetime",
+ "toml_write",
  "winnow",
 ]
 
 [[package]]
+name = "toml_write"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
+
+[[package]]
 name = "tracing"
 version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2307,9 +3604,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-attributes"
-version = "0.1.28"
+version = "0.1.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
+checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2318,9 +3615,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-core"
-version = "0.1.33"
+version = "0.1.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
+checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
 dependencies = [
  "once_cell",
 ]
@@ -2336,28 +3633,164 @@ dependencies = [
 ]
 
 [[package]]
+name = "twox-hash"
+version = "1.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
+dependencies = [
+ "cfg-if",
+ "static_assertions",
+]
+
+[[package]]
 name = "typenum"
 version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
 
 [[package]]
+name = "ucd"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe4fa6e588762366f1eb4991ce59ad1b93651d0b769dfb4e4d1c5c4b943d1159"
+
+[[package]]
 name = "ucd-trie"
 version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
 
 [[package]]
+name = "uname"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-normal"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f09d64d33589a94628bc2aeb037f35c2e25f3f049c7348b5aa5580b48e6bba62"
+dependencies = [
+ "unic-ucd-normal",
+]
+
+[[package]]
+name = "unic-ucd-age"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8cfdfe71af46b871dc6af2c24fcd360e2f3392ee4c5111877f2947f311671c"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-bidi"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-category"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b8d4591f5fcfe1bd4453baaf803c40e1b1e69ff8455c47620440b46efef91c0"
+dependencies = [
+ "matches",
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-hangul"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb1dc690e19010e1523edb9713224cba5ef55b54894fe33424439ec9a40c0054"
+dependencies = [
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-ident"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-normal"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86aed873b8202d22b13859dda5fe7c001d271412c31d411fd9b827e030569410"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-hangul",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
+[[package]]
 name = "unicode-bidi"
 version = "0.3.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
 
 [[package]]
+name = "unicode-casing"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "623f59e6af2a98bdafeb93fa277ac8e1e40440973001ca15cf4ae1541cd16d56"
+
+[[package]]
 name = "unicode-ident"
-version = "1.0.17"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
 
 [[package]]
 name = "unicode-normalization"
@@ -2382,15 +3815,31 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
 
 [[package]]
 name = "unicode-width"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
 
 [[package]]
-name = "unindent"
-version = "0.2.3"
+name = "unicode_names2"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1673eca9782c84de5f81b82e4109dcfb3611c8ba0d52930ec4a9478f547b2dd"
+dependencies = [
+ "phf",
+ "unicode_names2_generator",
+]
+
+[[package]]
+name = "unicode_names2_generator"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
+checksum = "b91e5b84611016120197efd7dc93ef76774f4e084cd73c9fb3ea4a86c570c56e"
+dependencies = [
+ "getopts",
+ "log",
+ "phf_codegen",
+ "rand",
+]
 
 [[package]]
 name = "url"
@@ -2405,12 +3854,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "utf16_iter"
-version = "1.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
-
-[[package]]
 name = "utf8_iter"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2424,12 +3867,23 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
 [[package]]
 name = "uu_fmt"
-version = "1.4.1"
+version = "1.5.0"
 dependencies = [
  "unicode-width",
 ]
 
 [[package]]
+name = "uuid"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
+dependencies = [
+ "atomic",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
 name = "vcpkg"
 version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2459,15 +3913,15 @@ dependencies = [
 
 [[package]]
 name = "wasi"
-version = "0.11.0+wasi-snapshot-preview1"
+version = "0.11.1+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
 
 [[package]]
 name = "wasi"
-version = "0.13.3+wasi-0.2.2"
+version = "0.14.2+wasi-0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
 dependencies = [
  "wit-bindgen-rt",
 ]
@@ -2537,16 +3991,44 @@ dependencies = [
 ]
 
 [[package]]
+name = "which"
+version = "7.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
+dependencies = [
+ "either",
+ "env_home",
+ "rustix",
+ "winsafe",
+]
+
+[[package]]
 name = "whoami"
-version = "1.5.2"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d"
+checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7"
 dependencies = [
  "redox_syscall",
  "wasite",
 ]
 
 [[package]]
+name = "wide"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22"
+dependencies = [
+ "bytemuck",
+ "safe_arch",
+]
+
+[[package]]
+name = "widestring"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
+
+[[package]]
 name = "winapi"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2578,6 +4060,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
+name = "windows"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
+dependencies = [
+ "windows-core 0.52.0",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
 name = "windows-core"
 version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2587,6 +4079,65 @@ dependencies = [
 ]
 
 [[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
 name = "windows-sys"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2637,7 +4188,7 @@ dependencies = [
  "windows_aarch64_gnullvm 0.52.6",
  "windows_aarch64_msvc 0.52.6",
  "windows_i686_gnu 0.52.6",
- "windows_i686_gnullvm",
+ "windows_i686_gnullvm 0.52.6",
  "windows_i686_msvc 0.52.6",
  "windows_x86_64_gnu 0.52.6",
  "windows_x86_64_gnullvm 0.52.6",
@@ -2645,6 +4196,22 @@ dependencies = [
 ]
 
 [[package]]
+name = "windows-targets"
+version = "0.53.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
+dependencies = [
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
+[[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2657,6 +4224,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
 [[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2669,6 +4242,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
 [[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
 name = "windows_i686_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2681,12 +4260,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
 
 [[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
 name = "windows_i686_gnullvm"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
 [[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2699,6 +4290,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
 [[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2711,6 +4308,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
 [[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2723,6 +4326,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
 [[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2735,46 +4344,77 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
 name = "winnow"
-version = "0.7.3"
+version = "0.7.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
+checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
-name = "wit-bindgen-rt"
-version = "0.33.0"
+name = "winreg"
+version = "0.55.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
+checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
 dependencies = [
- "bitflags 2.8.0",
+ "cfg-if",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
-name = "write16"
-version = "1.0.0"
+name = "winsafe"
+version = "0.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags 2.9.1",
+]
 
 [[package]]
 name = "writeable"
-version = "0.5.5"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
 
 [[package]]
 name = "xdg"
-version = "2.5.2"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
+checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda"
+
+[[package]]
+name = "xz2"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
+dependencies = [
+ "lzma-sys",
+]
 
 [[package]]
 name = "yoke"
-version = "0.7.5"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
 dependencies = [
  "serde",
  "stable_deref_trait",
@@ -2784,9 +4424,9 @@ dependencies = [
 
 [[package]]
 name = "yoke-derive"
-version = "0.7.5"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2796,11 +4436,11 @@ dependencies = [
 
 [[package]]
 name = "yt"
-version = "1.4.1"
+version = "1.5.0"
 dependencies = [
  "anyhow",
  "blake3",
- "bytes 1.4.1",
+ "bytes 1.5.0",
  "chrono",
  "chrono-humanize",
  "clap",
@@ -2828,32 +4468,30 @@ dependencies = [
 
 [[package]]
 name = "yt_dlp"
-version = "1.4.1"
+version = "1.5.0"
 dependencies = [
- "bytes 1.4.1",
+ "indexmap",
  "log",
- "pyo3",
- "serde",
+ "rustpython",
  "serde_json",
- "tokio",
+ "thiserror 2.0.12",
  "url",
 ]
 
 [[package]]
 name = "zerocopy"
-version = "0.7.35"
+version = "0.8.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
 dependencies = [
- "byteorder",
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.7.35"
+version = "0.8.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2862,18 +4500,18 @@ dependencies = [
 
 [[package]]
 name = "zerofrom"
-version = "0.1.5"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
 dependencies = [
  "zerofrom-derive",
 ]
 
 [[package]]
 name = "zerofrom-derive"
-version = "0.1.5"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2888,10 +4526,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
 
 [[package]]
+name = "zerotrie"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
 name = "zerovec"
-version = "0.10.4"
+version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
 dependencies = [
  "yoke",
  "zerofrom",
@@ -2900,11 +4549,17 @@ dependencies = [
 
 [[package]]
 name = "zerovec-derive"
-version = "0.10.3"
+version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
+
+[[package]]
+name = "zlib-rs"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a"
diff --git a/Cargo.toml b/Cargo.toml
index eb3f735..470eb58 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,13 +16,13 @@ members = [
   "crates/libmpv2",
   "crates/libmpv2/libmpv2-sys",
   "crates/termsize",
-  "yt",
+  "crates/yt",
 ]
 
 [workspace.package]
-edition = "2021"
-version = "1.4.1"
-rust-version = "1.80.0"
+edition = "2024"
+version = "1.5.0"
+rust-version = "1.85.0"
 authors = ["Benedikt Peetz <benedikt.peetz@b-peetz.de>"]
 repository = "https://git.vhack.eu/soispha/clients/yt"
 license = "GPL-3.0-or-later"
@@ -37,11 +37,11 @@ termsize = { path = "./crates/termsize" }
 uu_fmt = { path = "./crates/fmt" }
 
 # Shared
-log = "0.4.26"
-serde = { version = "1.0.218", features = ["derive"] }
-serde_json = "1.0.139"
+log = "0.4.27"
+serde = { version = "1.0.219", features = ["derive"] }
+serde_json = "1.0.140"
 url = { version = "2.5.4", features = ["serde"] }
-tokio = { version = "1.43.0", features = [
+tokio = { version = "1.45.1", features = [
   "rt-multi-thread",
   "macros",
   "process",
@@ -59,6 +59,10 @@ codegen-units = 1
 panic = "abort"
 split-debuginfo = "off"
 
+[profile.dev]
+# Otherwise, yt_dlp is just too slow
+opt-level = 2
+
 [workspace.lints.rust]
 # rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html
 warnings = "warn"
diff --git a/NEWS.md b/NEWS.md
index 95ce885..4e57d7b 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -14,6 +14,85 @@ If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
 
 - - -
+## [v1.5.0](https://git.vhack.eu/soispha/clients/yt/compare/2146109725115a9d01cc08ebbe3ef9c533ef1a89..v1.5.0) - 2025-02-22
+#### Bug Fixes
+- **(crates/libmpv2)** Improve the error message for the `RawError` - ([0bd13d5](https://git.vhack.eu/soispha/clients/yt/commit/0bd13d5c26495649dabc23a4fb6b37fe682e3aec)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/libmpv2/Mpv::command)** Correctly escape arguments - ([66c7392](https://git.vhack.eu/soispha/clients/yt/commit/66c739237cc352fedf6276a3163097c1f1f32bd4)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/libmpv2/mpv)** Log the setting of properties - ([d2081fb](https://git.vhack.eu/soispha/clients/yt/commit/d2081fbfed6b2bde727aaf766bf0435ec05a3574)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/termsize)** Remove all of `clippy`'s warnings - ([4b63c7b](https://git.vhack.eu/soispha/clients/yt/commit/4b63c7be4207bf2ff7884189a388e83633b20b26)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/yt_dlp)** Actually return errors instead of panicing - ([4d1b813](https://git.vhack.eu/soispha/clients/yt/commit/4d1b8136bb23d009ee04d863780225ad9d9f9eed)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/yt_dlp)** Avoid printing the file extension in the progress display - ([325b230](https://git.vhack.eu/soispha/clients/yt/commit/325b23039850953705a57c0a331045b169548751)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/yt_dlp/error::PythonError)** Add the python type as `kind` - ([c83dfe5](https://git.vhack.eu/soispha/clients/yt/commit/c83dfe5268e2db39fe731b2d38387b76d9586057)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/yt_dlp/lib)** Actually resolve the `entries` generator object - ([c7601c2](https://git.vhack.eu/soispha/clients/yt/commit/c7601c2e6cc86a3123d8c9dc13afce9430520583)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/yt_dlp/lib)** Swallow all error logs from yt_dlp - ([d1022c5](https://git.vhack.eu/soispha/clients/yt/commit/d1022c5c5c82557d8c2c45fad88b67cc3e6582e3)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/yt_dlp/progress_hook)** Print the progress to stderr - ([1786ba0](https://git.vhack.eu/soispha/clients/yt/commit/1786ba0b87d9883e4c75126e5d72af02134cc8b8)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/yt_dlp/wrappers/info_json)** Serialize the `InfoType`s with their correct name - ([dc19623](https://git.vhack.eu/soispha/clients/yt/commit/dc19623a18ac47d9c660d98db768c64d99decff9)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/yt_dlp/wrappers/info_json)** Don't serialize `None` values - ([fc5771e](https://git.vhack.eu/soispha/clients/yt/commit/fc5771e35b459af6210cbd9a2e7c33b6c462d337)) - [@soispha](https://git.vhack.eu/soispha)
+- **(package)** Update to account for modifications in `mkdb.sh` - ([a1dbd45](https://git.vhack.eu/soispha/clients/yt/commit/a1dbd45cbe2b21aa50eefcb6c9f016a7aaa4863c)) - [@soispha](https://git.vhack.eu/soispha)
+- **(package/blake3)** Migrate to the new `fetchCargoVendor` fetcher - ([c8ce2b7](https://git.vhack.eu/soispha/clients/yt/commit/c8ce2b7862eb273ed81cd399490e0536b2965a20)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt)** Remove most of the references to the zero version `Video` struct - ([74346a5](https://git.vhack.eu/soispha/clients/yt/commit/74346a5e43235be37bffca6dd0cb0ead66a529b5)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/)** Box large futures - ([335bfe9](https://git.vhack.eu/soispha/clients/yt/commit/335bfe91d7efdfd5a89eaf7728511f96dab3fef3)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/cli)** Make most of the arguments to `yt select <cmd> <hash>` optional - ([ceb0ff2](https://git.vhack.eu/soispha/clients/yt/commit/ceb0ff290707905af56106401b3f2a326971c505)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/download/download_options)** Stop trying to write annotations - ([2bcd30e](https://git.vhack.eu/soispha/clients/yt/commit/2bcd30e3f44fb7dbf859d72caf2a33a96ee5618b)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/main)** Call `watch` with the required `Arc<App>` - ([761a780](https://git.vhack.eu/soispha/clients/yt/commit/761a780499eeb6afe93b55b06d9df5c75aa9d7cc)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/main)** Actually remove the `yt check output-info-json` - ([e07db3a](https://git.vhack.eu/soispha/clients/yt/commit/e07db3a810c2e0f43b20d73ea4258f3ea8f240d4)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/select/cmds/add)** Don't try to add a video that is already added - ([69c94a3](https://git.vhack.eu/soispha/clients/yt/commit/69c94a3068689a575a21a0db6170bde9acad768d)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/select/selection_file/help.str)** Disable vim line wrapping - ([5cf5936](https://git.vhack.eu/soispha/clients/yt/commit/5cf5936c3f2fa0750a2e67d8ff4b6624c3141402)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/status)** Don't show the database version in `yt status` - ([cf8662c](https://git.vhack.eu/soispha/clients/yt/commit/cf8662ce03e5677ddb7de880ceb59d5d84a63259)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/status)** Show the current database version - ([8d7df29](https://git.vhack.eu/soispha/clients/yt/commit/8d7df29bbce0ceb258fa6c591003d379fcdb704f)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/storage/migrate)** Improve error reporting - ([e21c289](https://git.vhack.eu/soispha/clients/yt/commit/e21c289f8d21b802d2dc609233bc28cde65da224)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/storage/migrate/sql/01_zero_to_one.sql)** Account for duration being NULL - ([62cdd76](https://git.vhack.eu/soispha/clients/yt/commit/62cdd76443bbecfbdb70a82a7936a2e602338692)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/storage/notify)** Switch from a polling based system to inotify - ([dc8539e](https://git.vhack.eu/soispha/clients/yt/commit/dc8539e3707c1a281b3aef9c7a6e8f929845d965)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/watch)** Always open a `mpv` window - ([c662429](https://git.vhack.eu/soispha/clients/yt/commit/c6624299c45225ec3971e8119979d7421c56f70d)) - [@soispha](https://git.vhack.eu/soispha)
+#### Build system
+- **(.envrc)** Align with current state of the repository - ([6e35bf4](https://git.vhack.eu/soispha/clients/yt/commit/6e35bf41687f92a109ee36fa3169b9fb3f72b7f2)) - [@soispha](https://git.vhack.eu/soispha)
+- **(.envrc)** Always save the `output.info.json` if in devshell - ([2146109](https://git.vhack.eu/soispha/clients/yt/commit/2146109725115a9d01cc08ebbe3ef9c533ef1a89)) - [@soispha](https://git.vhack.eu/soispha)
+- **(flake)** Add `ffmpeg` to the devshell - ([7cc3e3c](https://git.vhack.eu/soispha/clients/yt/commit/7cc3e3cca0de6638625e2997002f78cfd8e03294)) - [@soispha](https://git.vhack.eu/soispha)
+- **(rustfmt.toml)** Add - ([ae13afa](https://git.vhack.eu/soispha/clients/yt/commit/ae13afa1aeb8aaed94b1d72aa6207bbfe373dd52)) - [@soispha](https://git.vhack.eu/soispha)
+- **(scripts/cprh)** Remove - ([3c91e60](https://git.vhack.eu/soispha/clients/yt/commit/3c91e6046b791664a6e7a0fdc41d369df0ee204a)) - [@soispha](https://git.vhack.eu/soispha)
+- **(treewide)** Update - ([7387b68](https://git.vhack.eu/soispha/clients/yt/commit/7387b6853893b3b6a04edb95a830b810e3311ba0)) - [@soispha](https://git.vhack.eu/soispha)
+- **(treewide)** Update - ([938b4f1](https://git.vhack.eu/soispha/clients/yt/commit/938b4f1b11dce643942d5e6cd505b206319709a2)) - [@soispha](https://git.vhack.eu/soispha)
+- **({.envrc,scripts/mkdb})** Mark the `sqlx` database - ([a1b3f95](https://git.vhack.eu/soispha/clients/yt/commit/a1b3f95bd3f7b447e918eec5bd67d7b5e8333eb0)) - [@soispha](https://git.vhack.eu/soispha)
+#### Documentation
+- **(yt/cli)** Remove last references to the external update and status_change bits - ([6e9a03f](https://git.vhack.eu/soispha/clients/yt/commit/6e9a03f4e2c6b57ed569bf12ca5e2954149006fb)) - [@soispha](https://git.vhack.eu/soispha)
+#### Features
+- **(crates/yt_dlp/lib)** Wrap `process_ie_result` function - ([8a53fbd](https://git.vhack.eu/soispha/clients/yt/commit/8a53fbd8af842e2ca1bca0b352113ff7e965f51f)) - [@soispha](https://git.vhack.eu/soispha)
+- **(version)** Include `yt-dlp` and `python` version in `--version` - ([431538e](https://git.vhack.eu/soispha/clients/yt/commit/431538e9c90090c8802460f3a625a0cd38a5e72a)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt)** Make colorization of the output configurable - ([e30b69d](https://git.vhack.eu/soispha/clients/yt/commit/e30b69dd4c2ebfb4ae77b38037b66f3e6fcb17bc)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/)** Use concrete types in the `Video` structure - ([9e8657c](https://git.vhack.eu/soispha/clients/yt/commit/9e8657c9762dbb66f3322976606a1b4334d45a6b)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/cli)** Make running the migrations of the database optional - ([31f15dc](https://git.vhack.eu/soispha/clients/yt/commit/31f15dc02bdbb815ce2d53f10d710a65b0b7bf0b)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/select/cmds/add)** Support `start` `stop` args - ([b3785ad](https://git.vhack.eu/soispha/clients/yt/commit/b3785ad44cb48143ed44cee48190b8646d668946)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/select/selection_file/duration)** Support durations up to days - ([14db4ea](https://git.vhack.eu/soispha/clients/yt/commit/14db4eadd57d0a3837227d9d74b84a133bacd434)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/status)** Include the approximate total watch time - ([e902437](https://git.vhack.eu/soispha/clients/yt/commit/e9024377cf5b16f9a81b0f750891307cd6acebe4)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/storage/migrate)** Add version two - ([8dbcf33](https://git.vhack.eu/soispha/clients/yt/commit/8dbcf33c8c6114e5699472d47c39f114103dc02e)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/storage/migrate)** Add db version One - ([93b7432](https://git.vhack.eu/soispha/clients/yt/commit/93b74321bf30ef33e82b0e9337d8cc3b6ca6e663)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/storage/migrate)** Init database migration system - ([9d6721b](https://git.vhack.eu/soispha/clients/yt/commit/9d6721bce1ed93e66c589d34e20393c78c7a423b)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/update)** Port the Python updater to rust - ([1d7bc17](https://git.vhack.eu/soispha/clients/yt/commit/1d7bc17e62a64ec213e43530b050bc41f978c610)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/version)** Show _current_ database version - ([e6bdcb4](https://git.vhack.eu/soispha/clients/yt/commit/e6bdcb4816cd54b7477f50cdebf06b07e7b9c58e)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/watch/playlist)** Init - ([686ea29](https://git.vhack.eu/soispha/clients/yt/commit/686ea29b06162b1a70dc473cea70b00e379c4f29)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/watch/playlist_handler)** Rewrite to use new db layout - ([4a008ef](https://git.vhack.eu/soispha/clients/yt/commit/4a008ef549f595af18f7cf2d0e9940d2627ae8c4)) - [@soispha](https://git.vhack.eu/soispha)
+#### Miscellaneous Chores
+- **(crates/libmpv2)** Make `cargo clippy` happy - ([b1474f9](https://git.vhack.eu/soispha/clients/yt/commit/b1474f9dc8dc1ed22c2a78680e40bd315cb82b0f)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/termsize)** Vendor - ([9da970f](https://git.vhack.eu/soispha/clients/yt/commit/9da970f1f44f19432680e255f91f73fbb8fbe3c8)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/yt_dlp/wrappers/info_json)** Add further fields - ([832ad82](https://git.vhack.eu/soispha/clients/yt/commit/832ad8265015284f1d95c3426f074aaeacd05864)) - [@soispha](https://git.vhack.eu/soispha)
+- **(crates/yt_dlp/wrappers/info_json)** Add further fields - ([674e499](https://git.vhack.eu/soispha/clients/yt/commit/674e4992d320ca0057121eb4474c370abccee8ab)) - [@soispha](https://git.vhack.eu/soispha)
+- **(old)** Remove - ([e2b90b4](https://git.vhack.eu/soispha/clients/yt/commit/e2b90b40333e35214f0b1c1e1f575bb688a99e74)) - [@soispha](https://git.vhack.eu/soispha)
+- **(treewide)** Add/Update the license headers - ([4cbc424](https://git.vhack.eu/soispha/clients/yt/commit/4cbc424e0d1b51c03dc7c3e49dae361cbf4c4b77)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt)** Change the type of `max_backlog` to `usize` - ([b5145c5](https://git.vhack.eu/soispha/clients/yt/commit/b5145c5d4ef674016f4e4217f67c2969a8dee962)) - [@soispha](https://git.vhack.eu/soispha)
+#### Refactoring
+- **(crates/fmt)** Init forked `uu_fmt` library - ([7cfa693](https://git.vhack.eu/soispha/clients/yt/commit/7cfa6939deb5496a07313a2a34632da1a3fb1b89)) - [@soispha](https://git.vhack.eu/soispha)
+- **(treewide)** Remove all references of the now obsolete update_raw.py - ([6a137c6](https://git.vhack.eu/soispha/clients/yt/commit/6a137c6ca968654810ccfddd90908a227287387f)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/)** Use the new `termsize` and `uu_fmt` crates - ([6da5602](https://git.vhack.eu/soispha/clients/yt/commit/6da5602d083bf26312071e68bfb1eb130da98934)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/description)** Move to the `comments` subdirectory - ([f98665d](https://git.vhack.eu/soispha/clients/yt/commit/f98665d992e3af91e52318e0c6e9334c891343bd)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/storage/video_database)** Move `getters,setters` to `get,set` - ([9496583](https://git.vhack.eu/soispha/clients/yt/commit/9496583cc76fbd7347384716f2898f870743f16d)) - [@soispha](https://git.vhack.eu/soispha)
+- **(yt/videos/display)** Streamline video formatting - ([27fae0b](https://git.vhack.eu/soispha/clients/yt/commit/27fae0bcd380fdf7396c33678f4aa3fa2df192cf)) - [@soispha](https://git.vhack.eu/soispha)
+#### Style
+- **(treewide)** Re-format - ([55a9411](https://git.vhack.eu/soispha/clients/yt/commit/55a94110287ad2b1a55953febac48422a9d3ba89)) - [@soispha](https://git.vhack.eu/soispha)
+#### Tests
+- **(crates/yt_dlp)** Ignore tests that hang forever - ([405858e](https://git.vhack.eu/soispha/clients/yt/commit/405858e3e7d2e5c06e49f1c195c46d64916afb65)) - [@soispha](https://git.vhack.eu/soispha)
+
+- - -
+
 ## [v1.4.1](https://git.vhack.eu/soispha/clients/yt/compare/72434a90d6a3dbba48d40a23b840befe7649b558..v1.4.1) - 2024-12-14
 #### Bug Fixes
 - **(yt_dlp/wrappers/info_json)** Add further fields to `RequestedDownloads` - ([72434a9](https://git.vhack.eu/soispha/clients/yt/commit/72434a90d6a3dbba48d40a23b840befe7649b558)) - [@soispha](https://git.vhack.eu/soispha)
diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml
index 7f82a09..f3cf4ad 100644
--- a/crates/fmt/Cargo.toml
+++ b/crates/fmt/Cargo.toml
@@ -24,7 +24,7 @@ publish = false
 path = "src/fmt.rs"
 
 [dependencies]
-unicode-width = "0.2.0"
+unicode-width = "0.2.1"
 
 [lints]
 workspace = true
diff --git a/crates/libmpv2/examples/opengl.rs b/crates/libmpv2/examples/opengl.rs
index 8eb9647..9f595aa 100644
--- a/crates/libmpv2/examples/opengl.rs
+++ b/crates/libmpv2/examples/opengl.rs
@@ -38,13 +38,16 @@ fn main() {
         Ok(())
     })
     .unwrap();
-    let mut render_context = RenderContext::new(unsafe { mpv.ctx.as_mut() }, vec![
-        RenderParam::ApiType(RenderParamApiType::OpenGl),
-        RenderParam::InitParams(OpenGLInitParams {
-            get_proc_address,
-            ctx: video,
-        }),
-    ])
+    let mut render_context = RenderContext::new(
+        unsafe { mpv.ctx.as_mut() },
+        vec![
+            RenderParam::ApiType(RenderParamApiType::OpenGl),
+            RenderParam::InitParams(OpenGLInitParams {
+                get_proc_address,
+                ctx: video,
+            }),
+        ],
+    )
     .expect("Failed creating render context");
 
     event_subsystem
diff --git a/crates/libmpv2/libmpv2-sys/Cargo.toml b/crates/libmpv2/libmpv2-sys/Cargo.toml
index b0514b8..96141d3 100644
--- a/crates/libmpv2/libmpv2-sys/Cargo.toml
+++ b/crates/libmpv2/libmpv2-sys/Cargo.toml
@@ -23,4 +23,4 @@ rust-version.workspace = true
 publish = false
 
 [build-dependencies]
-bindgen = { version = "0.71.1" }
+bindgen = { version = "0.72.0" }
diff --git a/crates/libmpv2/src/lib.rs b/crates/libmpv2/src/lib.rs
index d47e620..f6c2103 100644
--- a/crates/libmpv2/src/lib.rs
+++ b/crates/libmpv2/src/lib.rs
@@ -35,7 +35,7 @@ use std::os::raw as ctype;
 pub const MPV_CLIENT_API_MAJOR: ctype::c_ulong = 2;
 pub const MPV_CLIENT_API_MINOR: ctype::c_ulong = 2;
 pub const MPV_CLIENT_API_VERSION: ctype::c_ulong =
-    MPV_CLIENT_API_MAJOR << 16 | MPV_CLIENT_API_MINOR;
+    (MPV_CLIENT_API_MAJOR << 16) | MPV_CLIENT_API_MINOR;
 
 mod mpv;
 #[cfg(test)]
diff --git a/crates/libmpv2/src/mpv/events.rs b/crates/libmpv2/src/mpv/events.rs
index e27da2c..f10ff6e 100644
--- a/crates/libmpv2/src/mpv/events.rs
+++ b/crates/libmpv2/src/mpv/events.rs
@@ -70,26 +70,28 @@ impl<'a> PropertyData<'a> {
     // SAFETY: meant to extract the data from an event property. See `mpv_event_property` in
     // `client.h`
     unsafe fn from_raw(format: MpvFormat, ptr: *mut ctype::c_void) -> Result<PropertyData<'a>> {
-        assert!(!ptr.is_null());
-        match format {
-            mpv_format::Flag => Ok(PropertyData::Flag(*(ptr as *mut bool))),
-            mpv_format::String => {
-                let char_ptr = *(ptr as *mut *mut ctype::c_char);
-                Ok(PropertyData::Str(mpv_cstr_to_str!(char_ptr)?))
-            }
-            mpv_format::OsdString => {
-                let char_ptr = *(ptr as *mut *mut ctype::c_char);
-                Ok(PropertyData::OsdStr(mpv_cstr_to_str!(char_ptr)?))
-            }
-            mpv_format::Double => Ok(PropertyData::Double(*(ptr as *mut f64))),
-            mpv_format::Int64 => Ok(PropertyData::Int64(*(ptr as *mut i64))),
-            mpv_format::Node => {
-                let sys_node = *(ptr as *mut libmpv2_sys::mpv_node);
-                let node = SysMpvNode::new(sys_node, false);
-                Ok(PropertyData::Node(node.value().unwrap()))
+        unsafe {
+            assert!(!ptr.is_null());
+            match format {
+                mpv_format::Flag => Ok(PropertyData::Flag(*(ptr as *mut bool))),
+                mpv_format::String => {
+                    let char_ptr = *(ptr as *mut *mut ctype::c_char);
+                    Ok(PropertyData::Str(mpv_cstr_to_str!(char_ptr)?))
+                }
+                mpv_format::OsdString => {
+                    let char_ptr = *(ptr as *mut *mut ctype::c_char);
+                    Ok(PropertyData::OsdStr(mpv_cstr_to_str!(char_ptr)?))
+                }
+                mpv_format::Double => Ok(PropertyData::Double(*(ptr as *mut f64))),
+                mpv_format::Int64 => Ok(PropertyData::Int64(*(ptr as *mut i64))),
+                mpv_format::Node => {
+                    let sys_node = *(ptr as *mut libmpv2_sys::mpv_node);
+                    let node = SysMpvNode::new(sys_node, false);
+                    Ok(PropertyData::Node(node.value().unwrap()))
+                }
+                mpv_format::None => unreachable!(),
+                _ => unimplemented!(),
             }
-            mpv_format::None => unreachable!(),
-            _ => unimplemented!(),
         }
     }
 }
@@ -146,11 +148,13 @@ pub enum Event<'a> {
 }
 
 unsafe extern "C" fn wu_wrapper<F: Fn() + Send + 'static>(ctx: *mut c_void) {
-    if ctx.is_null() {
-        panic!("ctx for wakeup wrapper is NULL");
-    }
+    unsafe {
+        if ctx.is_null() {
+            panic!("ctx for wakeup wrapper is NULL");
+        }
 
-    (*(ctx as *mut F))();
+        (*(ctx as *mut F))();
+    }
 }
 
 /// Context to listen to events.
diff --git a/crates/libmpv2/src/mpv/protocol.rs b/crates/libmpv2/src/mpv/protocol.rs
index ec840d8..ee33411 100644
--- a/crates/libmpv2/src/mpv/protocol.rs
+++ b/crates/libmpv2/src/mpv/protocol.rs
@@ -63,26 +63,28 @@ where
     T: RefUnwindSafe,
     U: RefUnwindSafe,
 {
-    let data = user_data as *mut ProtocolData<T, U>;
+    unsafe {
+        let data = user_data as *mut ProtocolData<T, U>;
 
-    (*info).cookie = user_data;
-    (*info).read_fn = Some(read_wrapper::<T, U>);
-    (*info).seek_fn = Some(seek_wrapper::<T, U>);
-    (*info).size_fn = Some(size_wrapper::<T, U>);
-    (*info).close_fn = Some(close_wrapper::<T, U>);
+        (*info).cookie = user_data;
+        (*info).read_fn = Some(read_wrapper::<T, U>);
+        (*info).seek_fn = Some(seek_wrapper::<T, U>);
+        (*info).size_fn = Some(size_wrapper::<T, U>);
+        (*info).close_fn = Some(close_wrapper::<T, U>);
 
-    let ret = panic::catch_unwind(|| {
-        let uri = mpv_cstr_to_str!(uri as *const _).unwrap();
-        ptr::write(
-            (*data).cookie,
-            ((*data).open_fn)(&mut (*data).user_data, uri),
-        );
-    });
+        let ret = panic::catch_unwind(|| {
+            let uri = mpv_cstr_to_str!(uri as *const _).unwrap();
+            ptr::write(
+                (*data).cookie,
+                ((*data).open_fn)(&mut (*data).user_data, uri),
+            );
+        });
 
-    if ret.is_ok() {
-        0
-    } else {
-        mpv_error::Generic as _
+        if ret.is_ok() {
+            0
+        } else {
+            mpv_error::Generic as _
+        }
     }
 }
 
@@ -95,13 +97,15 @@ where
     T: RefUnwindSafe,
     U: RefUnwindSafe,
 {
-    let data = cookie as *mut ProtocolData<T, U>;
+    unsafe {
+        let data = cookie as *mut ProtocolData<T, U>;
 
-    let ret = panic::catch_unwind(|| {
-        let slice = slice::from_raw_parts_mut(buf, nbytes as _);
-        ((*data).read_fn)(&mut *(*data).cookie, slice)
-    });
-    ret.unwrap_or(-1)
+        let ret = panic::catch_unwind(|| {
+            let slice = slice::from_raw_parts_mut(buf, nbytes as _);
+            ((*data).read_fn)(&mut *(*data).cookie, slice)
+        });
+        ret.unwrap_or(-1)
+    }
 }
 
 unsafe extern "C" fn seek_wrapper<T, U>(cookie: *mut ctype::c_void, offset: i64) -> i64
@@ -109,18 +113,21 @@ where
     T: RefUnwindSafe,
     U: RefUnwindSafe,
 {
-    let data = cookie as *mut ProtocolData<T, U>;
+    unsafe {
+        let data = cookie as *mut ProtocolData<T, U>;
 
-    if (*data).seek_fn.is_none() {
-        return mpv_error::Unsupported as _;
-    }
+        if (*data).seek_fn.is_none() {
+            return mpv_error::Unsupported as _;
+        }
 
-    let ret =
-        panic::catch_unwind(|| (*(*data).seek_fn.as_ref().unwrap())(&mut *(*data).cookie, offset));
-    if let Ok(ret) = ret {
-        ret
-    } else {
-        mpv_error::Generic as _
+        let ret = panic::catch_unwind(|| {
+            (*(*data).seek_fn.as_ref().unwrap())(&mut *(*data).cookie, offset)
+        });
+        if let Ok(ret) = ret {
+            ret
+        } else {
+            mpv_error::Generic as _
+        }
     }
 }
 
@@ -129,17 +136,20 @@ where
     T: RefUnwindSafe,
     U: RefUnwindSafe,
 {
-    let data = cookie as *mut ProtocolData<T, U>;
+    unsafe {
+        let data = cookie as *mut ProtocolData<T, U>;
 
-    if (*data).size_fn.is_none() {
-        return mpv_error::Unsupported as _;
-    }
+        if (*data).size_fn.is_none() {
+            return mpv_error::Unsupported as _;
+        }
 
-    let ret = panic::catch_unwind(|| (*(*data).size_fn.as_ref().unwrap())(&mut *(*data).cookie));
-    if let Ok(ret) = ret {
-        ret
-    } else {
-        mpv_error::Unsupported as _
+        let ret =
+            panic::catch_unwind(|| (*(*data).size_fn.as_ref().unwrap())(&mut *(*data).cookie));
+        if let Ok(ret) = ret {
+            ret
+        } else {
+            mpv_error::Unsupported as _
+        }
     }
 }
 
@@ -149,9 +159,11 @@ where
     T: RefUnwindSafe,
     U: RefUnwindSafe,
 {
-    let data = Box::from_raw(cookie as *mut ProtocolData<T, U>);
+    unsafe {
+        let data = Box::from_raw(cookie as *mut ProtocolData<T, U>);
 
-    panic::catch_unwind(|| (data.close_fn)(Box::from_raw(data.cookie)));
+        panic::catch_unwind(|| (data.close_fn)(Box::from_raw(data.cookie)));
+    }
 }
 
 struct ProtocolData<T, U> {
@@ -224,20 +236,23 @@ impl<T: RefUnwindSafe, U: RefUnwindSafe> Protocol<T, U> {
         seek_fn: Option<StreamSeek<T>>,
         size_fn: Option<StreamSize<T>>,
     ) -> Protocol<T, U> {
-        let c_layout = Layout::from_size_align(mem::size_of::<T>(), mem::align_of::<T>()).unwrap();
-        let cookie = alloc::alloc(c_layout) as *mut T;
-        let data = Box::into_raw(Box::new(ProtocolData {
-            cookie,
-            user_data,
+        unsafe {
+            let c_layout =
+                Layout::from_size_align(mem::size_of::<T>(), mem::align_of::<T>()).unwrap();
+            let cookie = alloc::alloc(c_layout) as *mut T;
+            let data = Box::into_raw(Box::new(ProtocolData {
+                cookie,
+                user_data,
 
-            open_fn,
-            close_fn,
-            read_fn,
-            seek_fn,
-            size_fn,
-        }));
+                open_fn,
+                close_fn,
+                read_fn,
+                seek_fn,
+                size_fn,
+            }));
 
-        Protocol { name, data }
+            Protocol { name, data }
+        }
     }
 
     fn register(&self, ctx: *mut libmpv2_sys::mpv_handle) -> Result<()> {
diff --git a/crates/libmpv2/src/mpv/render.rs b/crates/libmpv2/src/mpv/render.rs
index 6457048..02f70bb 100644
--- a/crates/libmpv2/src/mpv/render.rs
+++ b/crates/libmpv2/src/mpv/render.rs
@@ -125,26 +125,30 @@ impl<C> From<&RenderParam<C>> for u32 {
 }
 
 unsafe extern "C" fn gpa_wrapper<GLContext>(ctx: *mut c_void, name: *const i8) -> *mut c_void {
-    if ctx.is_null() {
-        panic!("ctx for get_proc_address wrapper is NULL");
-    }
+    unsafe {
+        if ctx.is_null() {
+            panic!("ctx for get_proc_address wrapper is NULL");
+        }
 
-    let params: *mut OpenGLInitParams<GLContext> = ctx as _;
-    let params = &*params;
-    (params.get_proc_address)(
-        &params.ctx,
-        CStr::from_ptr(name)
-            .to_str()
-            .expect("Could not convert function name to str"),
-    )
+        let params: *mut OpenGLInitParams<GLContext> = ctx as _;
+        let params = &*params;
+        (params.get_proc_address)(
+            &params.ctx,
+            CStr::from_ptr(name)
+                .to_str()
+                .expect("Could not convert function name to str"),
+        )
+    }
 }
 
 unsafe extern "C" fn ru_wrapper<F: Fn() + Send + 'static>(ctx: *mut c_void) {
-    if ctx.is_null() {
-        panic!("ctx for render_update wrapper is NULL");
-    }
+    unsafe {
+        if ctx.is_null() {
+            panic!("ctx for render_update wrapper is NULL");
+        }
 
-    (*(ctx as *mut F))();
+        (*(ctx as *mut F))();
+    }
 }
 
 impl<C> From<OpenGLInitParams<C>> for libmpv2_sys::mpv_opengl_init_params {
@@ -197,14 +201,18 @@ impl<C> From<RenderParam<C>> for libmpv2_sys::mpv_render_param {
 }
 
 unsafe fn free_void_data<T>(ptr: *mut c_void) {
-    drop(Box::<T>::from_raw(ptr as *mut T));
+    unsafe {
+        drop(Box::<T>::from_raw(ptr as *mut T));
+    }
 }
 
 unsafe fn free_init_params<C>(ptr: *mut c_void) {
-    let params = Box::from_raw(ptr as *mut libmpv2_sys::mpv_opengl_init_params);
-    drop(Box::from_raw(
-        params.get_proc_address_ctx as *mut OpenGLInitParams<C>,
-    ));
+    unsafe {
+        let params = Box::from_raw(ptr as *mut libmpv2_sys::mpv_opengl_init_params);
+        drop(Box::from_raw(
+            params.get_proc_address_ctx as *mut OpenGLInitParams<C>,
+        ));
+    }
 }
 
 impl RenderContext {
diff --git a/crates/libmpv2/src/tests.rs b/crates/libmpv2/src/tests.rs
index 6106eb2..68753fc 100644
--- a/crates/libmpv2/src/tests.rs
+++ b/crates/libmpv2/src/tests.rs
@@ -54,10 +54,10 @@ fn properties() {
         0.6,
         f64::round(subg * f64::powi(10.0, 4)) / f64::powi(10.0, 4)
     );
-    mpv.command("loadfile", &[
-        "test-data/speech_12kbps_mb.wav",
-        "append-play",
-    ])
+    mpv.command(
+        "loadfile",
+        &["test-data/speech_12kbps_mb.wav", "append-play"],
+    )
     .unwrap();
     thread::sleep(Duration::from_millis(250));
 
@@ -185,10 +185,10 @@ fn events() {
 fn node_map() {
     let mpv = Mpv::new().unwrap();
 
-    mpv.command("loadfile", &[
-        "test-data/speech_12kbps_mb.wav",
-        "append-play",
-    ])
+    mpv.command(
+        "loadfile",
+        &["test-data/speech_12kbps_mb.wav", "append-play"],
+    )
     .unwrap();
 
     thread::sleep(Duration::from_millis(250));
@@ -217,10 +217,10 @@ fn node_map() {
 fn node_array() -> Result<()> {
     let mpv = Mpv::new()?;
 
-    mpv.command("loadfile", &[
-        "test-data/speech_12kbps_mb.wav",
-        "append-play",
-    ])
+    mpv.command(
+        "loadfile",
+        &["test-data/speech_12kbps_mb.wav", "append-play"],
+    )
     .unwrap();
 
     thread::sleep(Duration::from_millis(250));
diff --git a/yt/Cargo.toml b/crates/yt/Cargo.toml
index 6f6e470..17d4016 100644
--- a/yt/Cargo.toml
+++ b/crates/yt/Cargo.toml
@@ -24,21 +24,21 @@ rust-version.workspace = true
 publish = false
 
 [dependencies]
-anyhow = "1.0.96"
-blake3 = "1.6.0"
-chrono = { version = "0.4.39", features = ["now"] }
+anyhow = "1.0.98"
+blake3 = "1.8.2"
+chrono = { version = "0.4.41", features = ["now"] }
 chrono-humanize = "0.2.3"
-clap = { version = "4.5.30", features = ["derive"] }
+clap = { version = "4.5.40", features = ["derive"] }
 futures = "0.3.31"
 nucleo-matcher = "0.3.1"
-owo-colors = "4.1.0"
+owo-colors = "4.2.1"
 regex = "1.11.1"
-sqlx = { version = "0.8.3", features = ["runtime-tokio", "sqlite"] }
+sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] }
 stderrlog = "0.6.0"
-tempfile = "3.17.1"
-toml = "0.8.20"
+tempfile = "3.20.0"
+toml = "0.8.23"
 trinitry = { version = "0.2.2" }
-xdg = "2.5.2"
+xdg = "3.0.0"
 bytes.workspace = true
 libmpv2.workspace = true
 log.workspace = true
diff --git a/crates/yt/src/ansi_escape_codes.rs b/crates/yt/src/ansi_escape_codes.rs
new file mode 100644
index 0000000..ae1805d
--- /dev/null
+++ b/crates/yt/src/ansi_escape_codes.rs
@@ -0,0 +1,26 @@
+// see: https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands
+const CSI: &str = "\x1b[";
+pub fn erase_in_display_from_cursor() {
+    print!("{CSI}0J");
+}
+pub fn cursor_up(number: usize) {
+    // HACK(@bpeetz): The default is `1` and running this command with a
+    // number of `0` results in it using the default (i.e., `1`) <2025-03-25>
+    if number != 0 {
+        print!("{CSI}{number}A");
+    }
+}
+
+pub fn clear_whole_line() {
+    eprint!("{CSI}2K");
+}
+pub fn move_to_col(x: usize) {
+    eprint!("{CSI}{x}G");
+}
+
+pub fn hide_cursor() {
+    eprint!("{CSI}?25l");
+}
+pub fn show_cursor() {
+    eprint!("{CSI}?25h");
+}
diff --git a/yt/src/app.rs b/crates/yt/src/app.rs
index 15a9388..15a9388 100644
--- a/yt/src/app.rs
+++ b/crates/yt/src/app.rs
diff --git a/yt/src/cache/mod.rs b/crates/yt/src/cache/mod.rs
index 83d5ee0..83d5ee0 100644
--- a/yt/src/cache/mod.rs
+++ b/crates/yt/src/cache/mod.rs
diff --git a/yt/src/cli.rs b/crates/yt/src/cli.rs
index 037f45c..de7a5b8 100644
--- a/yt/src/cli.rs
+++ b/crates/yt/src/cli.rs
@@ -40,10 +40,6 @@ pub struct CliArgs {
     #[arg(long, short, action=ArgAction::SetTrue, default_value_t = false)]
     pub no_migrate_db: bool,
 
-    /// Increase message verbosity
-    #[arg(long="verbose", short = 'v', action = ArgAction::Count)]
-    pub verbosity: u8,
-
     /// Display colors [defaults to true, if the config file has no value]
     #[arg(long, short = 'C')]
     pub color: Option<bool>,
@@ -57,6 +53,10 @@ pub struct CliArgs {
     #[arg(long, short)]
     pub config_path: Option<PathBuf>,
 
+    /// Increase message verbosity
+    #[arg(long="verbose", short = 'v', action = ArgAction::Count)]
+    pub verbosity: u8,
+
     /// Silence all output
     #[arg(long, short = 'q')]
     pub quiet: bool,
@@ -103,12 +103,6 @@ pub enum Command {
     /// Show, the configuration options in effect
     Config {},
 
-    /// Perform various tests
-    Check {
-        #[command(subcommand)]
-        command: CheckCommand,
-    },
-
     /// Display the comments of the currently playing video
     Comments {},
     /// Display the description of the currently playing video
@@ -355,12 +349,6 @@ impl Default for SelectCommand {
     }
 }
 
-#[derive(Subcommand, Clone, Debug)]
-pub enum CheckCommand {
-    /// Check if the given `*.info.json` file is deserializable.
-    InfoJson { path: PathBuf },
-}
-
 #[derive(Subcommand, Clone, Copy, Debug)]
 pub enum CacheCommand {
     /// Invalidate all cache entries
diff --git a/crates/yt/src/comments/comment.rs b/crates/yt/src/comments/comment.rs
new file mode 100644
index 0000000..5bc939c
--- /dev/null
+++ b/crates/yt/src/comments/comment.rs
@@ -0,0 +1,152 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+// This file is part of Yt.
+//
+// You should have received a copy of the License along with this program.
+// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+use serde::{Deserialize, Deserializer, Serialize};
+use url::Url;
+
+#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
+#[serde(from = "String")]
+#[serde(deny_unknown_fields)]
+pub enum Parent {
+    Root,
+    Id(String),
+}
+
+impl Parent {
+    #[must_use]
+    pub fn id(&self) -> Option<&str> {
+        if let Self::Id(id) = self {
+            Some(id)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<String> for Parent {
+    fn from(value: String) -> Self {
+        if value == "root" {
+            Self::Root
+        } else {
+            Self::Id(value)
+        }
+    }
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
+#[serde(from = "String")]
+#[serde(deny_unknown_fields)]
+pub struct Id {
+    pub id: String,
+}
+impl From<String> for Id {
+    fn from(value: String) -> Self {
+        Self {
+            // Take the last element if the string is split with dots, otherwise take the full id
+            id: value.split('.').last().unwrap_or(&value).to_owned(),
+        }
+    }
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
+#[allow(clippy::struct_excessive_bools)]
+pub struct Comment {
+    pub id: Id,
+    pub text: String,
+    #[serde(default = "zero")]
+    pub like_count: u32,
+    pub is_pinned: bool,
+    pub author_id: String,
+    #[serde(default = "unknown")]
+    pub author: String,
+    pub author_is_verified: bool,
+    pub author_thumbnail: Url,
+    pub parent: Parent,
+    #[serde(deserialize_with = "edited_from_time_text", alias = "_time_text")]
+    pub edited: bool,
+    // Can't also be deserialized, as it's already used in 'edited'
+    // _time_text: String,
+    pub timestamp: i64,
+    pub author_url: Option<Url>,
+    pub author_is_uploader: bool,
+    pub is_favorited: bool,
+}
+
+fn unknown() -> String {
+    "<Unknown>".to_string()
+}
+fn zero() -> u32 {
+    0
+}
+fn edited_from_time_text<'de, D>(d: D) -> Result<bool, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let s = String::deserialize(d)?;
+    if s.contains(" (edited)") {
+        Ok(true)
+    } else {
+        Ok(false)
+    }
+}
+
+#[derive(Debug, Clone)]
+#[allow(clippy::module_name_repetitions)]
+pub struct CommentExt {
+    pub value: Comment,
+    pub replies: Vec<CommentExt>,
+}
+
+#[derive(Debug, Default)]
+pub struct Comments {
+    pub(super) vec: Vec<CommentExt>,
+}
+
+impl Comments {
+    pub fn new() -> Self {
+        Self::default()
+    }
+    pub fn push(&mut self, value: CommentExt) {
+        self.vec.push(value);
+    }
+    pub fn get_mut(&mut self, key: &str) -> Option<&mut CommentExt> {
+        self.vec.iter_mut().filter(|c| c.value.id.id == key).last()
+    }
+    pub fn insert(&mut self, key: &str, value: CommentExt) {
+        let parent = self
+            .vec
+            .iter_mut()
+            .filter(|c| c.value.id.id == key)
+            .last()
+            .expect("One of these should exist");
+        parent.push_reply(value);
+    }
+}
+impl CommentExt {
+    pub fn push_reply(&mut self, value: CommentExt) {
+        self.replies.push(value);
+    }
+    pub fn get_mut_reply(&mut self, key: &str) -> Option<&mut CommentExt> {
+        self.replies
+            .iter_mut()
+            .filter(|c| c.value.id.id == key)
+            .last()
+    }
+}
+
+impl From<Comment> for CommentExt {
+    fn from(value: Comment) -> Self {
+        Self {
+            replies: vec![],
+            value,
+        }
+    }
+}
diff --git a/yt/src/comments/description.rs b/crates/yt/src/comments/description.rs
index d22a40f..e8cb29d 100644
--- a/yt/src/comments/description.rs
+++ b/crates/yt/src/comments/description.rs
@@ -17,7 +17,7 @@ use crate::{
 };
 
 use anyhow::{Result, bail};
-use yt_dlp::wrapper::info_json::InfoJson;
+use yt_dlp::{InfoJson, json_cast};
 
 pub async fn description(app: &App) -> Result<()> {
     let description = get(app).await?;
@@ -39,6 +39,8 @@ pub async fn get(app: &App) -> Result<String> {
     );
 
     Ok(info_json
-        .description
-        .unwrap_or("<No description>".to_owned()))
+        .get("description")
+        .map(|val| json_cast!(val, as_str))
+        .unwrap_or("<No description>")
+        .to_owned())
 }
diff --git a/yt/src/comments/display.rs b/crates/yt/src/comments/display.rs
index 6166b2b..6166b2b 100644
--- a/yt/src/comments/display.rs
+++ b/crates/yt/src/comments/display.rs
diff --git a/yt/src/comments/mod.rs b/crates/yt/src/comments/mod.rs
index daecf8d..876146d 100644
--- a/yt/src/comments/mod.rs
+++ b/crates/yt/src/comments/mod.rs
@@ -11,11 +11,11 @@
 
 use std::mem;
 
-use anyhow::{Context, Result, bail};
-use comment::{CommentExt, Comments};
+use anyhow::{Result, bail};
+use comment::{Comment, CommentExt, Comments, Parent};
 use output::display_fmt_and_less;
 use regex::Regex;
-use yt_dlp::wrapper::info_json::{Comment, InfoJson, Parent};
+use yt_dlp::{InfoJson, json_cast};
 
 use crate::{
     app::App,
@@ -39,23 +39,25 @@ pub async fn get(app: &App) -> Result<Comments> {
             bail!("Could not find a currently playing video!");
         };
 
-    let mut info_json: InfoJson = get::video_info_json(&currently_playing_video)?.unreachable(
-        "A currently *playing* must be cached. And thus the info.json should be available",
+    let info_json: InfoJson = get::video_info_json(&currently_playing_video)?.unreachable(
+        "A currently *playing* video must be cached. And thus the info.json should be available",
     );
 
-    let base_comments = mem::take(&mut info_json.comments).with_context(|| {
-        format!(
+    let base_comments = if let Some(comments) = info_json.get("comments") {
+        json_cast!(comments, as_array)
+    } else {
+        bail!(
             "The video ('{}') does not have comments!",
             info_json
-                .title
-                .as_ref()
-                .unwrap_or(&("<No Title>".to_owned()))
+                .get("title")
+                .map(|val| json_cast!(val, as_str))
+                .unwrap_or("<No Title>")
         )
-    })?;
-    drop(info_json);
+    };
 
     let mut comments = Comments::new();
     for c in base_comments {
+        let c: Comment = serde_json::from_value(c.to_owned())?;
         if let Parent::Id(id) = &c.parent {
             comments.insert(&(id.clone()), CommentExt::from(c));
         } else {
diff --git a/yt/src/comments/output.rs b/crates/yt/src/comments/output.rs
index cb3a9c4..cb3a9c4 100644
--- a/yt/src/comments/output.rs
+++ b/crates/yt/src/comments/output.rs
diff --git a/yt/src/config/default.rs b/crates/yt/src/config/default.rs
index a1d327a..4ed643b 100644
--- a/yt/src/config/default.rs
+++ b/crates/yt/src/config/default.rs
@@ -14,19 +14,19 @@ use std::path::PathBuf;
 use anyhow::{Context, Result};
 
 fn get_runtime_path(name: &'static str) -> Result<PathBuf> {
-    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX)?;
+    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX);
     xdg_dirs
         .place_runtime_file(name)
         .with_context(|| format!("Failed to place runtime file: '{name}'"))
 }
 fn get_data_path(name: &'static str) -> Result<PathBuf> {
-    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX)?;
+    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX);
     xdg_dirs
         .place_data_file(name)
         .with_context(|| format!("Failed to place data file: '{name}'"))
 }
 fn get_config_path(name: &'static str) -> Result<PathBuf> {
-    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX)?;
+    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX);
     xdg_dirs
         .place_config_file(name)
         .with_context(|| format!("Failed to place config file: '{name}'"))
diff --git a/yt/src/config/definitions.rs b/crates/yt/src/config/definitions.rs
index ce8c0d4..ce8c0d4 100644
--- a/yt/src/config/definitions.rs
+++ b/crates/yt/src/config/definitions.rs
diff --git a/yt/src/config/file_system.rs b/crates/yt/src/config/file_system.rs
index 2463e9d..2463e9d 100644
--- a/yt/src/config/file_system.rs
+++ b/crates/yt/src/config/file_system.rs
diff --git a/yt/src/config/mod.rs b/crates/yt/src/config/mod.rs
index a10f7c2..a10f7c2 100644
--- a/yt/src/config/mod.rs
+++ b/crates/yt/src/config/mod.rs
diff --git a/yt/src/constants.rs b/crates/yt/src/constants.rs
index 0f5b918..0f5b918 100644
--- a/yt/src/constants.rs
+++ b/crates/yt/src/constants.rs
diff --git a/crates/yt/src/download/download_options.rs b/crates/yt/src/download/download_options.rs
new file mode 100644
index 0000000..03c20ba
--- /dev/null
+++ b/crates/yt/src/download/download_options.rs
@@ -0,0 +1,118 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+// This file is part of Yt.
+//
+// You should have received a copy of the License along with this program.
+// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+use anyhow::Context;
+use serde_json::{Value, json};
+use yt_dlp::{YoutubeDL, YoutubeDLOptions};
+
+use crate::{app::App, storage::video_database::YtDlpOptions};
+
+use super::progress_hook::wrapped_progress_hook;
+
+pub fn download_opts(app: &App, additional_opts: &YtDlpOptions) -> anyhow::Result<YoutubeDL> {
+    YoutubeDLOptions::new()
+        .with_progress_hook(wrapped_progress_hook)
+        .set("extract_flat", "in_playlist")
+        .set(
+            "extractor_args",
+            json! {
+            {
+                "youtube": {
+                    "comment_sort": [ "top" ],
+                    "max_comments": [ "150", "all", "100" ]
+                }
+            }
+            },
+        )
+        //.set("cookiesfrombrowser", json! {("firefox", "me.google", None::<String>, "youtube_dlp")})
+        .set("prefer_free_formats", true)
+        .set("ffmpeg_location", env!("FFMPEG_LOCATION"))
+        .set("format", "bestvideo[height<=?1080]+bestaudio/best")
+        .set("fragment_retries", 10)
+        .set("getcomments", true)
+        .set("ignoreerrors", false)
+        .set("retries", 10)
+        .set("writeinfojson", true)
+        // NOTE: This results in a constant warning message.  <2025-01-04>
+        //.set("writeannotations", true)
+        .set("writesubtitles", true)
+        .set("writeautomaticsub", true)
+        .set(
+            "outtmpl",
+            json! {
+            {
+                "default": app.config.paths.download_dir.join("%(channel)s/%(title)s.%(ext)s"),
+                "chapter": "%(title)s - %(section_number)03d %(section_title)s [%(id)s].%(ext)s"
+            }
+            },
+        )
+        .set("compat_opts", json! {{}})
+        .set("forceprint", json! {{}})
+        .set("print_to_file", json! {{}})
+        .set("windowsfilenames", false)
+        .set("restrictfilenames", false)
+        .set("trim_file_names", false)
+        .set(
+            "postprocessors",
+            json! {
+            [
+                {
+                    "api": "https://sponsor.ajay.app",
+                    "categories": [
+                        "interaction",
+                        "intro",
+                        "music_offtopic",
+                        "sponsor",
+                        "outro",
+                        "poi_highlight",
+                        "preview",
+                        "selfpromo",
+                        "filler",
+                        "chapter"
+                    ],
+                    "key": "SponsorBlock",
+                    "when": "after_filter"
+                },
+                {
+                    "force_keyframes": false,
+                    "key": "ModifyChapters",
+                    "remove_chapters_patterns": [],
+                    "remove_ranges": [],
+                    "remove_sponsor_segments": [ "sponsor" ],
+                    "sponsorblock_chapter_title": "[SponsorBlock]: %(category_names)l"
+                },
+                {
+                    "add_chapters": true,
+                    "add_infojson": null,
+                    "add_metadata": false,
+                    "key": "FFmpegMetadata"
+                },
+                {
+                    "key": "FFmpegConcat",
+                    "only_multi_video": true,
+                    "when": "playlist"
+                }
+            ]
+            },
+        )
+        .set(
+            "subtitleslangs",
+            Value::Array(
+                additional_opts
+                    .subtitle_langs
+                    .split(',')
+                    .map(|val| Value::String(val.to_owned()))
+                    .collect::<Vec<_>>(),
+            ),
+        )
+        .build()
+        .context("Failed to instanciate download yt_dlp")
+}
diff --git a/yt/src/download/mod.rs b/crates/yt/src/download/mod.rs
index 984d400..110bf55 100644
--- a/yt/src/download/mod.rs
+++ b/crates/yt/src/download/mod.rs
@@ -29,9 +29,11 @@ use bytes::Bytes;
 use futures::{FutureExt, future::BoxFuture};
 use log::{debug, error, info, warn};
 use tokio::{fs, task::JoinHandle, time};
+use yt_dlp::{json_cast, json_get};
 
 #[allow(clippy::module_name_repetitions)]
 pub mod download_options;
+pub mod progress_hook;
 
 #[derive(Debug)]
 #[allow(clippy::module_name_repetitions)]
@@ -109,7 +111,7 @@ impl Downloader {
             }
         }
         let cache_allocation = Self::get_current_cache_allocation(app).await?;
-        let video_size = self.get_approx_video_size(app, next_video).await?;
+        let video_size = self.get_approx_video_size(app, next_video)?;
 
         if video_size >= max_cache_size {
             error!(
@@ -291,7 +293,7 @@ impl Downloader {
         dir_size(read_dir_result).await
     }
 
-    async fn get_approx_video_size(&mut self, app: &App, video: &Video) -> Result<u64> {
+    fn get_approx_video_size(&mut self, app: &App, video: &Video) -> Result<u64> {
         if let Some(value) = self.video_size_cache.get(&video.extractor_hash) {
             Ok(*value)
         } else {
@@ -299,25 +301,25 @@ impl Downloader {
             let add_opts = YtDlpOptions {
                 subtitle_langs: String::new(),
             };
-            let opts = &download_opts(app, &add_opts);
+            let yt_dlp = download_opts(app, &add_opts)?;
 
-            let result = yt_dlp::extract_info(opts, &video.url, false, true)
-                .await
+            let result = yt_dlp
+                .extract_info(&video.url, false, true)
                 .with_context(|| {
                     format!("Failed to extract video information: '{}'", video.title)
                 })?;
 
-            let size = if let Some(val) = result.filesize {
-                val
-            } else if let Some(val) = result.filesize_approx {
-                val
-            } else if result.duration.is_some() && result.tbr.is_some() {
+            let size = if let Some(val) = result.get("filesize") {
+                json_cast!(val, as_u64)
+            } else if let Some(val) = result.get("filesize_approx") {
+                json_cast!(val, as_u64)
+            } else if result.get("duration").is_some() && result.get("tbr").is_some() {
                 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
-                let duration = result.duration.expect("Is some").ceil() as u64;
+                let duration = json_get!(result, "duration", as_f64).ceil() as u64;
 
                 // TODO: yt_dlp gets this from the format
                 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
-                let tbr = result.tbr.expect("Is Some").ceil() as u64;
+                let tbr = json_get!(result, "tbr", as_f64).ceil() as u64;
 
                 duration * tbr * (1000 / 8)
             } else {
@@ -342,9 +344,10 @@ impl Downloader {
         debug!("Download started: {}", &video.title);
 
         let addional_opts = get_video_yt_dlp_opts(app, &video.extractor_hash).await?;
+        let yt_dlp = download_opts(app, &addional_opts)?;
 
-        let result = yt_dlp::download(&[video.url.clone()], &download_opts(app, &addional_opts))
-            .await
+        let result = yt_dlp
+            .download(&[video.url.to_owned()])
             .with_context(|| format!("Failed to download video: '{}'", video.title))?;
 
         assert_eq!(result.len(), 1);
diff --git a/crates/yt/src/download/progress_hook.rs b/crates/yt/src/download/progress_hook.rs
new file mode 100644
index 0000000..b75ec00
--- /dev/null
+++ b/crates/yt/src/download/progress_hook.rs
@@ -0,0 +1,188 @@
+use std::{
+    io::{Write, stderr},
+    process,
+};
+
+use bytes::Bytes;
+use log::{Level, log_enabled};
+use yt_dlp::mk_python_function;
+
+use crate::{
+    ansi_escape_codes::{clear_whole_line, move_to_col},
+    select::selection_file::duration::MaybeDuration,
+};
+
+/// # Panics
+/// If expectations fail.
+#[allow(clippy::too_many_lines, clippy::needless_pass_by_value)]
+pub fn progress_hook(
+    input: serde_json::Map<String, serde_json::Value>,
+) -> Result<(), std::io::Error> {
+    // Only add the handler, if the log-level is higher than Debug (this avoids covering debug
+    // messages).
+    if log_enabled!(Level::Debug) {
+        return Ok(());
+    }
+
+    macro_rules! get {
+        (@interrogate $item:ident, $type_fun:ident, $get_fun:ident, $name:expr) => {{
+            let a = $item.get($name).expect(concat!(
+                "The field '",
+                stringify!($name),
+                "' should exist."
+            ));
+
+            if a.$type_fun() {
+                a.$get_fun().expect(
+                    "The should have been checked in the if guard, so unpacking here is fine",
+                )
+            } else {
+                panic!(
+                    "Value {} => \n{}\n is not of type: {}",
+                    $name,
+                    a,
+                    stringify!($type_fun)
+                );
+            }
+        }};
+
+        ($type_fun:ident, $get_fun:ident, $name1:expr, $name2:expr) => {{
+            let a = get! {@interrogate input, is_object, as_object, $name1};
+            let b = get! {@interrogate a, $type_fun, $get_fun, $name2};
+            b
+        }};
+
+        ($type_fun:ident, $get_fun:ident, $name:expr) => {{
+            get! {@interrogate input, $type_fun, $get_fun, $name}
+        }};
+    }
+
+    macro_rules! default_get {
+        (@interrogate $item:ident, $default:expr, $get_fun:ident, $name:expr) => {{
+            let a = if let Some(field) = $item.get($name) {
+                field.$get_fun().unwrap_or($default)
+            } else {
+                $default
+            };
+            a
+        }};
+
+        ($get_fun:ident, $default:expr, $name1:expr, $name2:expr) => {{
+            let a = get! {@interrogate input, is_object, as_object, $name1};
+            let b = default_get! {@interrogate a, $default, $get_fun, $name2};
+            b
+        }};
+
+        ($get_fun:ident, $default:expr, $name:expr) => {{
+            default_get! {@interrogate input, $default, $get_fun, $name}
+        }};
+    }
+
+    macro_rules! c {
+        ($color:expr, $format:expr) => {
+            format!("\x1b[{}m{}\x1b[0m", $color, $format)
+        };
+    }
+
+    #[allow(clippy::items_after_statements)]
+    fn format_bytes(bytes: u64) -> String {
+        let bytes = Bytes::new(bytes);
+        bytes.to_string()
+    }
+
+    #[allow(clippy::items_after_statements)]
+    fn format_speed(speed: f64) -> String {
+        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
+        let bytes = Bytes::new(speed.floor() as u64);
+        format!("{bytes}/s")
+    }
+
+    let get_title = || -> String {
+        match get! {is_string, as_str, "info_dict", "ext"} {
+            "vtt" => {
+                format!(
+                    "Subtitles ({})",
+                    default_get! {as_str, "<No Subtitle Language>", "info_dict", "name"}
+                )
+            }
+            "webm" | "mp4" | "mp3" | "m4a" => {
+                default_get! { as_str, "<No title>", "info_dict", "title"}.to_owned()
+            }
+            other => panic!("The extension '{other}' is not yet implemented"),
+        }
+    };
+
+    match get! {is_string, as_str, "status"} {
+        "downloading" => {
+            let elapsed = default_get! {as_f64, 0.0f64, "elapsed"};
+            let eta = default_get! {as_f64, 0.0, "eta"};
+            let speed = default_get! {as_f64, 0.0, "speed"};
+
+            let downloaded_bytes = get! {is_u64, as_u64, "downloaded_bytes"};
+            let (total_bytes, bytes_is_estimate): (u64, &'static str) = {
+                let total_bytes = default_get!(as_u64, 0, "total_bytes");
+                if total_bytes == 0 {
+                    let maybe_estimate = default_get!(as_u64, 0, "total_bytes_estimate");
+
+                    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
+                    if maybe_estimate == 0 {
+                        // The download speed should be in bytes per second and the eta in seconds.
+                        // Thus multiplying them gets us the raw bytes (which were estimated by `yt_dlp`, from their `info.json`)
+                        let bytes_still_needed = (speed * eta).ceil() as u64;
+
+                        (downloaded_bytes + bytes_still_needed, "~")
+                    } else {
+                        (maybe_estimate, "~")
+                    }
+                } else {
+                    (total_bytes, "")
+                }
+            };
+
+            let percent: f64 = {
+                if total_bytes == 0 {
+                    100.0
+                } else {
+                    #[allow(
+                        clippy::cast_possible_truncation,
+                        clippy::cast_sign_loss,
+                        clippy::cast_precision_loss
+                    )]
+                    {
+                        (downloaded_bytes as f64 / total_bytes as f64) * 100.0
+                    }
+                }
+            };
+
+            clear_whole_line();
+            move_to_col(1);
+
+            eprint!(
+                "'{}' [{}/{} at {}] -> [{} of {}{} {}] ",
+                c!("34;1", get_title()),
+                c!("33;1", MaybeDuration::from_secs_f64(elapsed)),
+                c!("33;1", MaybeDuration::from_secs_f64(eta)),
+                c!("32;1", format_speed(speed)),
+                c!("31;1", format_bytes(downloaded_bytes)),
+                c!("31;1", bytes_is_estimate),
+                c!("31;1", format_bytes(total_bytes)),
+                c!("36;1", format!("{:.02}%", percent))
+            );
+            stderr().flush()?;
+        }
+        "finished" => {
+            eprintln!("-> Finished downloading.");
+        }
+        "error" => {
+            // TODO: This should probably return an Err. But I'm not so sure where the error would
+            // bubble up to (i.e., who would catch it) <2025-01-21>
+            eprintln!("-> Error while downloading: {}", get_title());
+            process::exit(1);
+        }
+        other => unreachable!("'{other}' should not be a valid state!"),
+    }
+
+    Ok(())
+}
+
+mk_python_function!(progress_hook, wrapped_progress_hook);
diff --git a/yt/src/main.rs b/crates/yt/src/main.rs
index ffb3e14..39f52f4 100644
--- a/yt/src/main.rs
+++ b/crates/yt/src/main.rs
@@ -13,16 +13,16 @@
 // to print it anyways.
 #![allow(clippy::missing_errors_doc)]
 
-use std::{fs, sync::Arc};
+use std::sync::Arc;
 
 use anyhow::{Context, Result, bail};
 use app::App;
 use bytes::Bytes;
 use cache::{invalidate, maintain};
 use clap::Parser;
-use cli::{CacheCommand, CheckCommand, SelectCommand, SubscriptionCommand, VideosCommand};
+use cli::{CacheCommand, SelectCommand, SubscriptionCommand, VideosCommand};
 use config::Config;
-use log::info;
+use log::{error, info};
 use select::cmds::handle_select_cmd;
 use storage::video_database::get::video_by_hash;
 use tokio::{
@@ -30,10 +30,10 @@ use tokio::{
     io::{BufReader, stdin},
     task::JoinHandle,
 };
-use yt_dlp::wrapper::info_json::InfoJson;
 
 use crate::{cli::Command, storage::subscriptions};
 
+pub mod ansi_escape_codes;
 pub mod app;
 pub mod cli;
 pub mod unreachable;
@@ -200,7 +200,7 @@ async fn main() -> Result<()> {
                     subscribe::import(&app, BufReader::new(f), force).await?;
                 } else {
                     subscribe::import(&app, BufReader::new(stdin()), force).await?;
-                };
+                }
             }
         },
 
@@ -215,17 +215,6 @@ async fn main() -> Result<()> {
             CacheCommand::Maintain { all } => maintain(&app, all).await?,
         },
 
-        Command::Check { command } => match command {
-            CheckCommand::InfoJson { path } => {
-                let string = fs::read_to_string(&path)
-                    .with_context(|| format!("Failed to read '{}' to string!", path.display()))?;
-
-                drop(
-                    serde_json::from_str::<InfoJson>(&string)
-                        .context("Failed to deserialize value")?,
-                );
-            }
-        },
         Command::Comments {} => {
             comments::comments(&app).await?;
         }
@@ -242,15 +231,17 @@ async fn dowa(arc_app: Arc<App>) -> Result<()> {
     info!("Max cache size: '{}'", max_cache_size);
 
     let arc_app_clone = Arc::clone(&arc_app);
-    let download: JoinHandle<Result<()>> = tokio::spawn(async move {
-        download::Downloader::new()
+    let download: JoinHandle<()> = tokio::spawn(async move {
+        let result = download::Downloader::new()
             .consume(arc_app_clone, max_cache_size.as_u64())
-            .await?;
+            .await;
 
-        Ok(())
+        if let Err(err) = result {
+            error!("Error from downloader: {err:?}");
+        }
     });
 
     watch::watch(arc_app).await?;
-    download.await??;
+    download.await?;
     Ok(())
 }
diff --git a/yt/src/select/cmds/add.rs b/crates/yt/src/select/cmds/add.rs
index da58ec2..387b3a1 100644
--- a/yt/src/select/cmds/add.rs
+++ b/crates/yt/src/select/cmds/add.rs
@@ -14,15 +14,13 @@ use crate::{
     storage::video_database::{
         self, extractor_hash::ExtractorHash, get::get_all_hashes, set::add_video,
     },
-    unreachable::Unreachable,
     update::video_entry_to_video,
 };
 
 use anyhow::{Context, Result, bail};
 use log::{error, warn};
-use serde_json::{Map, Value};
 use url::Url;
-use yt_dlp::wrapper::info_json::InfoType;
+use yt_dlp::{InfoJson, YoutubeDL, json_cast, json_get};
 
 #[allow(clippy::too_many_lines)]
 pub(super) async fn add(
@@ -32,17 +30,11 @@ pub(super) async fn add(
     stop: Option<usize>,
 ) -> Result<()> {
     for url in urls {
-        async fn process_and_add(
-            app: &App,
-            entry: yt_dlp::wrapper::info_json::InfoJson,
-            opts: &Map<String, Value>,
-        ) -> Result<()> {
-            let url = entry
-                .url
-                .unreachable("`yt_dlp` should guarantee that this is Some at this point");
-
-            let entry = yt_dlp::extract_info(opts, &url, false, true)
-                .await
+        async fn process_and_add(app: &App, entry: InfoJson, yt_dlp: &YoutubeDL) -> Result<()> {
+            let url = json_get!(entry, "url", as_str).parse()?;
+
+            let entry = yt_dlp
+                .extract_info(&url, false, true)
                 .with_context(|| format!("Failed to fetch entry for url: '{url}'"))?;
 
             add_entry(app, entry).await?;
@@ -50,19 +42,13 @@ pub(super) async fn add(
             Ok(())
         }
 
-        async fn add_entry(app: &App, entry: yt_dlp::wrapper::info_json::InfoJson) -> Result<()> {
+        async fn add_entry(app: &App, entry: InfoJson) -> Result<()> {
             // We have to re-fetch all hashes every time, because a user could try to add the same
             // URL twice (for whatever reason.)
             let hashes = get_all_hashes(app)
                 .await
                 .context("Failed to fetch all video hashes")?;
-            let extractor_hash = blake3::hash(
-                entry
-                    .id
-                    .as_ref()
-                    .expect("This should be some at this point")
-                    .as_bytes(),
-            );
+            let extractor_hash = blake3::hash(json_get!(entry, "id", as_str).as_bytes());
             if hashes.contains(&extractor_hash) {
                 error!(
                     "Video '{}'{} is already in the database. Skipped adding it",
@@ -72,17 +58,17 @@ pub(super) async fn add(
                         .with_context(|| format!(
                             "Failed to format hash of video '{}' as short hash",
                             entry
-                                .url
-                                .map_or("<Unknown video Url>".to_owned(), |url| url.to_string())
+                                .get("url")
+                                .map_or("<Unknown video Url>".to_owned(), ToString::to_string)
                         ))?,
                     entry
-                        .title
+                        .get("title")
                         .map_or(String::new(), |title| format!(" ('{title}')"))
                 );
                 return Ok(());
             }
 
-            let video = video_entry_to_video(entry, None)?;
+            let video = video_entry_to_video(&entry, None)?;
             add_video(app, video.clone()).await?;
 
             println!("{}", &video.to_line_display(app).await?);
@@ -90,16 +76,19 @@ pub(super) async fn add(
             Ok(())
         }
 
-        let opts = download_opts(app, &video_database::YtDlpOptions {
-            subtitle_langs: String::new(),
-        });
+        let yt_dlp = download_opts(
+            app,
+            &video_database::YtDlpOptions {
+                subtitle_langs: String::new(),
+            },
+        )?;
 
-        let entry = yt_dlp::extract_info(&opts, &url, false, true)
-            .await
+        let entry = yt_dlp
+            .extract_info(&url, false, true)
             .with_context(|| format!("Failed to fetch entry for url: '{url}'"))?;
 
-        match entry._type {
-            Some(InfoType::Video) => {
+        match entry.get("_type").map(|val| json_cast!(val, as_str)) {
+            Some("Video") => {
                 add_entry(app, entry).await?;
                 if start.is_some() || stop.is_some() {
                     warn!(
@@ -107,13 +96,14 @@ pub(super) async fn add(
                     );
                 }
             }
-            Some(InfoType::Playlist) => {
-                if let Some(entries) = entry.entries {
+            Some("Playlist") => {
+                if let Some(entries) = entry.get("entries") {
+                    let entries = json_cast!(entries, as_array);
                     let start = start.unwrap_or(0);
                     let stop = stop.unwrap_or(entries.len() - 1);
 
-                    let mut respected_entries: Vec<_> = take_vector(entries, start, stop)
-                        .with_context(|| {
+                    let respected_entries =
+                        take_vector(entries, start, stop).with_context(|| {
                             format!(
                                 "Failed to take entries starting at: {start} and ending with {stop}"
                             )
@@ -123,11 +113,23 @@ pub(super) async fn add(
                         warn!("No entries found, after applying your start/stop limits.");
                     } else {
                         // Pre-warm the cache
-                        process_and_add(app, respected_entries.remove(0), &opts).await?;
+                        process_and_add(
+                            app,
+                            json_cast!(respected_entries[0], as_object).to_owned(),
+                            &yt_dlp,
+                        )
+                        .await?;
+                        let respected_entries = &respected_entries[1..];
 
                         let futures: Vec<_> = respected_entries
-                            .into_iter()
-                            .map(|entry| process_and_add(app, entry, &opts))
+                            .iter()
+                            .map(|entry| {
+                                process_and_add(
+                                    app,
+                                    json_cast!(entry, as_object).to_owned(),
+                                    &yt_dlp,
+                                )
+                            })
                             .collect();
 
                         for fut in futures {
@@ -148,7 +150,7 @@ pub(super) async fn add(
     Ok(())
 }
 
-fn take_vector<T>(vector: Vec<T>, start: usize, stop: usize) -> Result<Vec<T>> {
+fn take_vector<T>(vector: &[T], start: usize, stop: usize) -> Result<&[T]> {
     let length = vector.len();
 
     if stop >= length {
@@ -157,26 +159,7 @@ fn take_vector<T>(vector: Vec<T>, start: usize, stop: usize) -> Result<Vec<T>> {
         );
     }
 
-    let end_skip = {
-        let base = length
-            .checked_sub(stop)
-            .unreachable("The check above should have caught this case.");
-
-        base.checked_sub(1)
-            .unreachable("The check above should have caught this case.")
-    };
-
-    // NOTE: We're using this instead of the `vector[start..=stop]` notation, because I wanted to
-    // avoid the needed allocation to turn the slice into a vector. <2025-01-04>
-
-    // TODO: This function could also just return a slice, but oh well.. <2025-01-04>
-    Ok(vector
-        .into_iter()
-        .skip(start)
-        .rev()
-        .skip(end_skip)
-        .rev()
-        .collect())
+    Ok(&vector[start..=stop])
 }
 
 #[cfg(test)]
@@ -187,7 +170,7 @@ mod test {
     fn test_vector_take() {
         let vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
 
-        let new_vec = take_vector(vec, 2, 8).unwrap();
+        let new_vec = take_vector(&vec, 2, 8).unwrap();
 
         assert_eq!(new_vec, vec![2, 3, 4, 5, 6, 7, 8]);
     }
@@ -196,13 +179,13 @@ mod test {
     fn test_vector_take_overflow() {
         let vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
 
-        assert!(take_vector(vec, 0, 12).is_err());
+        assert!(take_vector(&vec, 0, 12).is_err());
     }
 
     #[test]
     fn test_vector_take_equal() {
         let vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
 
-        assert!(take_vector(vec, 0, 11).is_err());
+        assert!(take_vector(&vec, 0, 11).is_err());
     }
 }
diff --git a/yt/src/select/cmds/mod.rs b/crates/yt/src/select/cmds/mod.rs
index ea41f99..aabcd3d 100644
--- a/yt/src/select/cmds/mod.rs
+++ b/crates/yt/src/select/cmds/mod.rs
@@ -51,10 +51,15 @@ pub async fn handle_select_cmd(
                 is_focused,
             } = video.status
             {
-                handle_status_change(app, shared, line_number, VideoStatus::Cached {
-                    cache_path,
-                    is_focused,
-                })
+                handle_status_change(
+                    app,
+                    shared,
+                    line_number,
+                    VideoStatus::Cached {
+                        cache_path,
+                        is_focused,
+                    },
+                )
                 .await?;
             } else {
                 handle_status_change(app, shared, line_number, VideoStatus::Watch).await?;
diff --git a/yt/src/select/mod.rs b/crates/yt/src/select/mod.rs
index 54db65c..8db9ae3 100644
--- a/yt/src/select/mod.rs
+++ b/crates/yt/src/select/mod.rs
@@ -53,12 +53,15 @@ pub async fn select(app: &App, done: bool, use_last_selection: bool) -> Result<(
         let matching_videos = if done {
             get::videos(app, VideoStatusMarker::ALL).await?
         } else {
-            get::videos(app, &[
-                VideoStatusMarker::Pick,
-                //
-                VideoStatusMarker::Watch,
-                VideoStatusMarker::Cached,
-            ])
+            get::videos(
+                app,
+                &[
+                    VideoStatusMarker::Pick,
+                    //
+                    VideoStatusMarker::Watch,
+                    VideoStatusMarker::Cached,
+                ],
+            )
             .await?
         };
 
diff --git a/yt/src/select/selection_file/duration.rs b/crates/yt/src/select/selection_file/duration.rs
index 77c4fc5..77c4fc5 100644
--- a/yt/src/select/selection_file/duration.rs
+++ b/crates/yt/src/select/selection_file/duration.rs
diff --git a/yt/src/select/selection_file/help.str b/crates/yt/src/select/selection_file/help.str
index e3cc347..e3cc347 100644
--- a/yt/src/select/selection_file/help.str
+++ b/crates/yt/src/select/selection_file/help.str
diff --git a/yt/src/select/selection_file/help.str.license b/crates/yt/src/select/selection_file/help.str.license
index a0e196c..a0e196c 100644
--- a/yt/src/select/selection_file/help.str.license
+++ b/crates/yt/src/select/selection_file/help.str.license
diff --git a/yt/src/select/selection_file/mod.rs b/crates/yt/src/select/selection_file/mod.rs
index abd26c4..abd26c4 100644
--- a/yt/src/select/selection_file/mod.rs
+++ b/crates/yt/src/select/selection_file/mod.rs
diff --git a/yt/src/status/mod.rs b/crates/yt/src/status/mod.rs
index bc45cfb..18bef7d 100644
--- a/yt/src/status/mod.rs
+++ b/crates/yt/src/status/mod.rs
@@ -87,6 +87,15 @@ pub async fn show(app: &App) -> Result<()> {
         }
     };
 
+    let watch_rate: f64 = {
+        fn to_f64(input: usize) -> f64 {
+            f64::from(u32::try_from(input).expect("This should never exceed u32::MAX"))
+        }
+
+        let count = to_f64(watched_videos_len) / (to_f64(drop_videos_len) + to_f64(dropped_videos_len));
+        count * 100.0
+    };
+
     let cache_usage_raw = Downloader::get_current_cache_allocation(app)
         .await
         .context("Failed to get current cache allocation")?;
@@ -97,7 +106,7 @@ Picked   Videos: {picked_videos_len}
 
 Watch    Videos: {watch_videos_len}
 Cached   Videos: {cached_videos_len}
-Watched  Videos: {watched_videos_len}
+Watched  Videos: {watched_videos_len} (watch rate: {watch_rate:.2} %)
 
 Drop     Videos: {drop_videos_len}
 Dropped  Videos: {dropped_videos_len}
diff --git a/yt/src/storage/migrate/mod.rs b/crates/yt/src/storage/migrate/mod.rs
index badeb6f..953d079 100644
--- a/yt/src/storage/migrate/mod.rs
+++ b/crates/yt/src/storage/migrate/mod.rs
@@ -21,6 +21,59 @@ use sqlx::{Sqlite, SqlitePool, Transaction, query};
 
 use crate::app::App;
 
+macro_rules! make_upgrade {
+    ($app:expr, $old_version:expr, $new_version:expr, $sql_name:expr) => {
+        add_error_context(
+            async {
+                let mut tx = $app
+                    .database
+                    .begin()
+                    .await
+                    .context("Failed to start the update transaction")?;
+                debug!("Migrating: {} -> {}", $old_version, $new_version);
+
+                sqlx::raw_sql(include_str!($sql_name))
+                    .execute(&mut *tx)
+                    .await
+                    .context("Failed to run the update sql script")?;
+
+                set_db_version(
+                    &mut tx,
+                    if $old_version == Self::Empty {
+                        // There is no previous version we would need to remove
+                        None
+                    } else {
+                        Some($old_version)
+                    },
+                    $new_version,
+                )
+                .await
+                .with_context(|| format!("Failed to set the new version ({})", $new_version))?;
+
+                tx.commit()
+                    .await
+                    .context("Failed to commit the update transaction")?;
+
+                // NOTE: This is needed, so that sqlite "sees" our changes to the table
+                // without having to reconnect. <2025-02-18>
+                query!("VACUUM")
+                    .execute(&$app.database)
+                    .await
+                    .context("Failed to vacuum database")?;
+
+                Ok(())
+            },
+            $new_version,
+        )
+        .await?;
+
+        Box::pin($new_version.update($app)).await.context(concat!(
+            "While updating to version: ",
+            stringify!($new_version)
+        ))
+    };
+}
+
 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
 pub enum DbVersion {
     /// The database is not yet initialized.
@@ -35,8 +88,11 @@ pub enum DbVersion {
 
     /// Introduced: 2025-02-18.
     Two,
+
+    /// Introduced: 2025-03-21.
+    Three,
 }
-const CURRENT_VERSION: DbVersion = DbVersion::Two;
+const CURRENT_VERSION: DbVersion = DbVersion::Three;
 
 async fn add_error_context(
     function: impl Future<Output = Result<()>>,
@@ -44,7 +100,7 @@ async fn add_error_context(
 ) -> Result<()> {
     function
         .await
-        .with_context(|| format!("Format failed to migrate database to version: {level}"))
+        .with_context(|| format!("Failed to migrate database to version: {level}"))
 }
 
 async fn set_db_version(
@@ -83,21 +139,26 @@ async fn set_db_version(
 impl DbVersion {
     fn as_sql_integer(self) -> i32 {
         match self {
-            DbVersion::Empty => unreachable!("A empty version does not have an associated integer"),
             DbVersion::Zero => 0,
             DbVersion::One => 1,
             DbVersion::Two => 2,
+            DbVersion::Three => 3,
+
+            DbVersion::Empty => unreachable!("A empty version does not have an associated integer"),
         }
     }
+
     fn from_db(number: i64, namespace: &str) -> Result<Self> {
         match (number, namespace) {
             (0, "yt") => Ok(DbVersion::Zero),
             (1, "yt") => Ok(DbVersion::One),
             (2, "yt") => Ok(DbVersion::Two),
+            (3, "yt") => Ok(DbVersion::Three),
 
             (0, other) => bail!("Db version is Zero, but got unknown namespace: '{other}'"),
             (1, other) => bail!("Db version is One, but got unknown namespace: '{other}'"),
             (2, other) => bail!("Db version is Two, but got unknown namespace: '{other}'"),
+            (3, other) => bail!("Db version is Three, but got unknown namespace: '{other}'"),
 
             (other, "yt") => bail!("Got unkown version for 'yt' namespace: {other}"),
             (num, nasp) => bail!("Got unkown version number ({num}) and namespace ('{nasp}')"),
@@ -111,126 +172,24 @@ impl DbVersion {
     #[allow(clippy::too_many_lines)]
     async fn update(self, app: &App) -> Result<()> {
         match self {
-            DbVersion::Empty => {
-                add_error_context(
-                    async {
-                        let mut tx = app
-                            .database
-                            .begin()
-                            .await
-                            .context("Failed to start transaction")?;
-                        debug!("Migrate: Empty -> Zero");
-
-                        sqlx::raw_sql(include_str!("./sql/00_empty_to_zero.sql"))
-                            .execute(&mut *tx)
-                            .await
-                            .context("Failed to execute sql update script")?;
-
-                        set_db_version(&mut tx, None, DbVersion::Zero)
-                            .await
-                            .context("Failed to set new version")?;
-
-                        tx.commit()
-                            .await
-                            .context("Failed to commit changes")?;
-
-                        // NOTE: This is needed, so that sqlite "sees" our changes to the table
-                        // without having to reconnect. <2025-02-18>
-                        query!("VACUUM")
-                            .execute(&app.database)
-                            .await
-                            .context("Failed to vacuum database")?;
-
-                        Ok(())
-                    },
-                    DbVersion::One,
-                )
-                .await?;
-                Box::pin(Self::Zero.update(app)).await
+            Self::Empty => {
+                make_upgrade! {app, Self::Empty, Self::Zero, "./sql/0_Empty_to_Zero.sql"}
             }
 
-            DbVersion::Zero => {
-                add_error_context(
-                    async {
-                        let mut tx = app
-                            .database
-                            .begin()
-                            .await
-                            .context("Failed to start transaction")?;
-                        debug!("Migrate: Zero -> One");
-
-                        sqlx::raw_sql(include_str!("./sql/01_zero_to_one.sql"))
-                            .execute(&mut *tx)
-                            .await
-                            .context("Failed to execute the update sql script")?;
-
-                        set_db_version(&mut tx, Some(DbVersion::Zero), DbVersion::One)
-                            .await
-                            .context("Failed to set the new version")?;
-
-                        tx.commit()
-                            .await
-                            .context("Failed to commit the update transaction")?;
-
-                        // NOTE: This is needed, so that sqlite "sees" our changes to the table
-                        // without having to reconnect. <2025-02-18>
-                        query!("VACUUM")
-                            .execute(&app.database)
-                            .await
-                            .context("Failed to vacuum database")?;
-
-                        Ok(())
-                    },
-                    DbVersion::Zero,
-                )
-                .await?;
-
-                Box::pin(Self::One.update(app)).await
+            Self::Zero => {
+                make_upgrade! {app, Self::Zero, Self::One, "./sql/1_Zero_to_One.sql"}
             }
 
-            DbVersion::One => {
-                add_error_context(
-                    async {
-                        let mut tx = app
-                            .database
-                            .begin()
-                            .await
-                            .context("Failed to start the update transaction")?;
-                        debug!("Migrate: One -> Two");
-
-                        sqlx::raw_sql(include_str!("./sql/02_one_to_two.sql"))
-                            .execute(&mut *tx)
-                            .await
-                            .context("Failed to run the update sql script")?;
-
-                        set_db_version(&mut tx, Some(DbVersion::One), DbVersion::Two)
-                            .await
-                            .context("Failed to set the new version")?;
-
-                        tx.commit()
-                            .await
-                            .context("Failed to commit the update transaction")?;
-
-                        // NOTE: This is needed, so that sqlite "sees" our changes to the table
-                        // without having to reconnect. <2025-02-18>
-                        query!("VACUUM")
-                            .execute(&app.database)
-                            .await
-                            .context("Failed to vacuum database")?;
-
-                        Ok(())
-                    },
-                    DbVersion::One,
-                )
-                .await?;
+            Self::One => {
+                make_upgrade! {app, Self::One, Self::Two, "./sql/2_One_to_Two.sql"}
+            }
 
-                Box::pin(Self::Two.update(app))
-                    .await
-                    .context("Failed to update to version: Three")
+            Self::Two => {
+                make_upgrade! {app, Self::Two, Self::Three, "./sql/3_Two_to_Three.sql"}
             }
 
             // This is the current_version
-            DbVersion::Two => {
+            Self::Three => {
                 assert_eq!(self, CURRENT_VERSION);
                 assert_eq!(self, get_version(app).await?);
                 Ok(())
diff --git a/yt/src/storage/migrate/sql/00_empty_to_zero.sql b/crates/yt/src/storage/migrate/sql/0_Empty_to_Zero.sql
index d703bfc..d703bfc 100644
--- a/yt/src/storage/migrate/sql/00_empty_to_zero.sql
+++ b/crates/yt/src/storage/migrate/sql/0_Empty_to_Zero.sql
diff --git a/yt/src/storage/migrate/sql/01_zero_to_one.sql b/crates/yt/src/storage/migrate/sql/1_Zero_to_One.sql
index da9315b..da9315b 100644
--- a/yt/src/storage/migrate/sql/01_zero_to_one.sql
+++ b/crates/yt/src/storage/migrate/sql/1_Zero_to_One.sql
diff --git a/yt/src/storage/migrate/sql/02_one_to_two.sql b/crates/yt/src/storage/migrate/sql/2_One_to_Two.sql
index 806de07..806de07 100644
--- a/yt/src/storage/migrate/sql/02_one_to_two.sql
+++ b/crates/yt/src/storage/migrate/sql/2_One_to_Two.sql
diff --git a/crates/yt/src/storage/migrate/sql/3_Two_to_Three.sql b/crates/yt/src/storage/migrate/sql/3_Two_to_Three.sql
new file mode 100644
index 0000000..b33f849
--- /dev/null
+++ b/crates/yt/src/storage/migrate/sql/3_Two_to_Three.sql
@@ -0,0 +1,85 @@
+-- yt - A fully featured command line YouTube client
+--
+-- Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+-- SPDX-License-Identifier: GPL-3.0-or-later
+--
+-- This file is part of Yt.
+--
+-- You should have received a copy of the License along with this program.
+-- If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+
+-- 1. Create new table
+-- 2. Copy data
+-- 3. Drop old table
+-- 4. Rename new into old
+
+-- remove the original TRANSACTION
+COMMIT TRANSACTION;
+
+-- tweak config
+PRAGMA foreign_keys=OFF;
+
+-- start your own TRANSACTION
+BEGIN TRANSACTION;
+
+CREATE TABLE videos_new (
+    cache_path                  TEXT    UNIQUE                       CHECK (CASE
+                                                                              WHEN cache_path IS NOT NULL THEN status == 2
+                                                                              ELSE 1
+                                                                            END),
+    description                 TEXT,
+    duration                    REAL,
+    extractor_hash              TEXT    UNIQUE NOT NULL PRIMARY KEY,
+    last_status_change          INTEGER        NOT NULL,
+    parent_subscription_name    TEXT,
+    priority                    INTEGER        NOT NULL DEFAULT 0,
+    publish_date                INTEGER,
+    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
+                                                                              WHEN status != 2 THEN cache_path IS NULL
+                                                                              ELSE 1
+                                                                            END),
+    thumbnail_url               TEXT,
+    title                       TEXT           NOT NULL,
+    url                         TEXT    UNIQUE NOT NULL,
+    is_focused                  INTEGER UNIQUE          DEFAULT NULL CHECK (CASE
+                                                                              WHEN is_focused IS NOT NULL THEN is_focused == 1
+                                                                              ELSE 1
+                                                                            END),
+    watch_progress              INTEGER        NOT NULL DEFAULT 0    CHECK (watch_progress <= duration)
+) STRICT;
+
+INSERT INTO videos_new SELECT
+    videos.cache_path,
+    videos.description,
+    videos.duration,
+    videos.extractor_hash,
+    videos.last_status_change,
+    videos.parent_subscription_name,
+    videos.priority,
+    videos.publish_date,
+    videos.status,
+    videos.thumbnail_url,
+    videos.title,
+    videos.url,
+    dummy.is_focused,
+    videos.watch_progress
+FROM videos, (SELECT NULL AS is_focused) AS dummy;
+
+DROP TABLE videos;
+
+ALTER TABLE videos_new RENAME TO videos;
+
+-- check foreign key constraint still upholding.
+PRAGMA foreign_key_check;
+
+-- commit your own TRANSACTION
+COMMIT TRANSACTION;
+
+-- rollback all config you setup before.
+PRAGMA foreign_keys=ON;
+
+-- start a new TRANSACTION to let migrator commit it.
+BEGIN TRANSACTION;
diff --git a/yt/src/storage/mod.rs b/crates/yt/src/storage/mod.rs
index 8653eb3..d352b41 100644
--- a/yt/src/storage/mod.rs
+++ b/crates/yt/src/storage/mod.rs
@@ -9,6 +9,6 @@
 // You should have received a copy of the License along with this program.
 // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 
+pub mod migrate;
 pub mod subscriptions;
 pub mod video_database;
-pub mod migrate;
diff --git a/yt/src/storage/subscriptions.rs b/crates/yt/src/storage/subscriptions.rs
index 3673eee..6c0d08a 100644
--- a/yt/src/storage/subscriptions.rs
+++ b/crates/yt/src/storage/subscriptions.rs
@@ -15,10 +15,9 @@ use std::collections::HashMap;
 
 use anyhow::Result;
 use log::debug;
-use serde_json::{Value, json};
 use sqlx::query;
 use url::Url;
-use yt_dlp::wrapper::info_json::InfoType;
+use yt_dlp::YoutubeDLOptions;
 
 use crate::{app::App, unreachable::Unreachable};
 
@@ -39,21 +38,19 @@ impl Subscription {
 }
 
 /// Check whether an URL could be used as a subscription URL
-pub async fn check_url(url: &Url) -> Result<bool> {
-    let Value::Object(yt_opts) = json!( {
-        "playliststart": 1,
-        "playlistend": 10,
-        "noplaylist": false,
-        "extract_flat": "in_playlist",
-    }) else {
-        unreachable!("This is hardcoded");
-    };
-
-    let info = yt_dlp::extract_info(&yt_opts, url, false, false).await?;
+pub async fn check_url(url: Url) -> Result<bool> {
+    let yt_dlp = YoutubeDLOptions::new()
+        .set("playliststart", 1)
+        .set("playlistend", 10)
+        .set("noplaylist", false)
+        .set("extract_flat", "in_playlist")
+        .build()?;
+
+    let info = yt_dlp.extract_info(&url, false, false)?;
 
     debug!("{:#?}", info);
 
-    Ok(info._type == Some(InfoType::Playlist))
+    Ok(info.get("_type") == Some(&serde_json::Value::String("Playlist".to_owned())))
 }
 
 #[derive(Default, Debug)]
diff --git a/yt/src/storage/video_database/downloader.rs b/crates/yt/src/storage/video_database/downloader.rs
index a95081e..a95081e 100644
--- a/yt/src/storage/video_database/downloader.rs
+++ b/crates/yt/src/storage/video_database/downloader.rs
diff --git a/yt/src/storage/video_database/extractor_hash.rs b/crates/yt/src/storage/video_database/extractor_hash.rs
index df545d7..df545d7 100644
--- a/yt/src/storage/video_database/extractor_hash.rs
+++ b/crates/yt/src/storage/video_database/extractor_hash.rs
diff --git a/yt/src/storage/video_database/get/mod.rs b/crates/yt/src/storage/video_database/get/mod.rs
index 6a4220e..0456cd3 100644
--- a/yt/src/storage/video_database/get/mod.rs
+++ b/crates/yt/src/storage/video_database/get/mod.rs
@@ -18,7 +18,7 @@ use anyhow::{Context, Result, bail};
 use blake3::Hash;
 use log::{debug, trace};
 use sqlx::query;
-use yt_dlp::wrapper::info_json::InfoJson;
+use yt_dlp::InfoJson;
 
 use crate::{
     app::App,
@@ -64,7 +64,11 @@ macro_rules! video_from_record {
                 let optional = if let Some(cache_path) = &$record.cache_path {
                     Some((
                         PathBuf::from(cache_path),
-                        if $record.is_focused == 1 { true } else { false },
+                        if $record.is_focused == Some(1) {
+                            true
+                        } else {
+                            false
+                        },
                     ))
                 } else {
                     None
diff --git a/yt/src/storage/video_database/get/playlist/iterator.rs b/crates/yt/src/storage/video_database/get/playlist/iterator.rs
index 4c45bf7..4c45bf7 100644
--- a/yt/src/storage/video_database/get/playlist/iterator.rs
+++ b/crates/yt/src/storage/video_database/get/playlist/iterator.rs
diff --git a/yt/src/storage/video_database/get/playlist/mod.rs b/crates/yt/src/storage/video_database/get/playlist/mod.rs
index f6aadbf..f6aadbf 100644
--- a/yt/src/storage/video_database/get/playlist/mod.rs
+++ b/crates/yt/src/storage/video_database/get/playlist/mod.rs
diff --git a/yt/src/storage/video_database/mod.rs b/crates/yt/src/storage/video_database/mod.rs
index 74d09f0..74d09f0 100644
--- a/yt/src/storage/video_database/mod.rs
+++ b/crates/yt/src/storage/video_database/mod.rs
diff --git a/yt/src/storage/video_database/notify.rs b/crates/yt/src/storage/video_database/notify.rs
index b55c00a..b55c00a 100644
--- a/yt/src/storage/video_database/notify.rs
+++ b/crates/yt/src/storage/video_database/notify.rs
diff --git a/yt/src/storage/video_database/set/mod.rs b/crates/yt/src/storage/video_database/set/mod.rs
index 4006fde..8c1be4a 100644
--- a/yt/src/storage/video_database/set/mod.rs
+++ b/crates/yt/src/storage/video_database/set/mod.rs
@@ -19,17 +19,17 @@ use log::{debug, info};
 use sqlx::query;
 use tokio::fs;
 
-use crate::{
-    app::App,
-    storage::video_database::{VideoStatusMarker, extractor_hash::ExtractorHash},
-    video_from_record,
-};
+use crate::{app::App, storage::video_database::extractor_hash::ExtractorHash, video_from_record};
 
 use super::{Priority, Video, VideoOptions, VideoStatus};
 
 mod playlist;
 pub use playlist::*;
 
+const fn is_focused_to_value(is_focused: bool) -> Option<i8> {
+    if is_focused { Some(1) } else { None }
+}
+
 /// Set a new status for a video.
 /// This will only update the status time stamp/priority when the status or the priority has changed .
 pub async fn video_status(
@@ -56,7 +56,7 @@ pub async fn video_status(
     };
 
     let old_marker = old.status.as_marker();
-    let cache_path = {
+    let (cache_path, is_focused) = {
         fn cache_path_to_string(path: &Path) -> Result<String> {
             Ok(path
                 .to_str()
@@ -69,13 +69,17 @@ pub async fn video_status(
                 .to_owned())
         }
 
-        match (old_marker, &new_status) {
-            (VideoStatusMarker::Cached, VideoStatus::Cached { cache_path, .. }) => {
-                Some(cache_path_to_string(cache_path)?)
-            }
-            (_, VideoStatus::Cached { cache_path, .. }) => Some(cache_path_to_string(cache_path)?),
-
-            (VideoStatusMarker::Cached | _, _) => None,
+        if let VideoStatus::Cached {
+            cache_path,
+            is_focused,
+        } = &new_status
+        {
+            (
+                Some(cache_path_to_string(cache_path)?),
+                is_focused_to_value(*is_focused),
+            )
+        } else {
+            (None, None)
         }
     };
 
@@ -98,13 +102,14 @@ pub async fn video_status(
         query!(
             r#"
         UPDATE videos
-        SET status = ?, last_status_change = ?, priority = ?, cache_path = ?
+        SET status = ?, last_status_change = ?, priority = ?, cache_path = ?, is_focused = ?
         WHERE extractor_hash = ?;
         "#,
             new_status,
             now,
             new_priority,
             cache_path,
+            is_focused,
             video_hash
         )
         .execute(&app.database)
@@ -125,12 +130,13 @@ pub async fn video_status(
         query!(
             r#"
         UPDATE videos
-        SET status = ?, last_status_change = ?, cache_path = ?
+        SET status = ?, last_status_change = ?, cache_path = ?, is_focused = ?
         WHERE extractor_hash = ?;
         "#,
             new_status,
             now,
             cache_path,
+            is_focused,
             video_hash
         )
         .execute(&app.database)
@@ -147,10 +153,9 @@ pub async fn video_status(
 /// # Panics
 /// Only if assertions fail.
 pub async fn video_watched(app: &App, video: &ExtractorHash) -> Result<()> {
-    let video_hash = video.hash().to_string();
-    let new_status = VideoStatusMarker::Watched.as_db_integer();
-
     let old = {
+        let video_hash = video.hash().to_string();
+
         let base = query!(
             r#"
     SELECT *
@@ -175,20 +180,7 @@ pub async fn video_watched(app: &App, video: &ExtractorHash) -> Result<()> {
         unreachable!("The video must be marked as Cached before it can be marked Watched");
     }
 
-    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?;
+    video_status(app, video, VideoStatus::Watched, None).await?;
 
     Ok(())
 }
@@ -271,10 +263,10 @@ pub async fn add_video(app: &App, video: Video) -> Result<()> {
                     })?
                     .to_string(),
             ),
-            is_focused,
+            is_focused_to_value(is_focused),
         )
     } else {
-        (None, false)
+        (None, None)
     };
 
     let duration: Option<f64> = video.duration.as_secs_f64();
diff --git a/yt/src/storage/video_database/set/playlist.rs b/crates/yt/src/storage/video_database/set/playlist.rs
index 7e97239..547df21 100644
--- a/yt/src/storage/video_database/set/playlist.rs
+++ b/crates/yt/src/storage/video_database/set/playlist.rs
@@ -28,12 +28,9 @@ pub async fn focused(
     new_video_hash: &ExtractorHash,
     old_video_hash: Option<&ExtractorHash>,
 ) -> Result<()> {
-    if let Some(old) = old_video_hash {
-        debug!("Unfocusing video: '{old}'");
-        unfocused(app, old).await?;
-    }
-    debug!("Focusing video: '{new_video_hash}'");
+    unfocused(app, old_video_hash).await?;
 
+    debug!("Focusing video: '{new_video_hash}'");
     let new_hash = new_video_hash.hash().to_string();
     query!(
         r#"
@@ -57,15 +54,38 @@ pub async fn focused(
 }
 
 /// Set a video to be no longer focused.
+/// This will use the supplied `video_hash` if it is [`Some`], otherwise it will simply un-focus
+/// the currently focused video.
 ///
 /// # Panics
 /// Only if internal assertions fail.
-pub async fn unfocused(app: &App, video_hash: &ExtractorHash) -> Result<()> {
-    let hash = video_hash.hash().to_string();
+pub async fn unfocused(app: &App, video_hash: Option<&ExtractorHash>) -> Result<()> {
+    let hash = if let Some(hash) = video_hash {
+        hash.hash().to_string()
+    } else {
+        let output = query!(
+            r#"
+                SELECT extractor_hash
+                FROM videos
+                WHERE is_focused = 1;
+            "#,
+        )
+        .fetch_optional(&app.database)
+        .await?;
+
+        if let Some(output) = output {
+            output.extractor_hash
+        } else {
+            // There is no unfocused video right now.
+            return Ok(());
+        }
+    };
+    debug!("Unfocusing video: '{hash}'");
+
     query!(
         r#"
             UPDATE videos
-            SET is_focused = 0
+            SET is_focused = NULL
             WHERE extractor_hash = ?;
         "#,
         hash
diff --git a/yt/src/subscribe/mod.rs b/crates/yt/src/subscribe/mod.rs
index 455ccb1..7ac0be4 100644
--- a/yt/src/subscribe/mod.rs
+++ b/crates/yt/src/subscribe/mod.rs
@@ -14,10 +14,9 @@ use std::str::FromStr;
 use anyhow::{Context, Result, bail};
 use futures::FutureExt;
 use log::warn;
-use serde_json::{Value, json};
 use tokio::io::{AsyncBufRead, AsyncBufReadExt};
 use url::Url;
-use yt_dlp::wrapper::info_json::InfoType;
+use yt_dlp::{YoutubeDLOptions, json_get};
 
 use crate::{
     app::App,
@@ -142,26 +141,24 @@ pub async fn subscribe(app: &App, name: Option<String>, url: Url) -> Result<()>
 }
 
 async fn actual_subscribe(app: &App, name: Option<String>, url: Url) -> Result<()> {
-    if !check_url(&url).await? {
+    if !check_url(url.clone()).await? {
         bail!("The url ('{}') does not represent a playlist!", &url)
-    };
+    }
 
     let name = if let Some(name) = name {
         name
     } else {
-        let Value::Object(yt_opts) = json!( {
-            "playliststart": 1,
-            "playlistend": 10,
-            "noplaylist": false,
-            "extract_flat": "in_playlist",
-        }) else {
-            unreachable!("This is hardcoded")
-        };
-
-        let info = yt_dlp::extract_info(&yt_opts, &url, false, false).await?;
-
-        if info._type == Some(InfoType::Playlist) {
-            info.title.expect("This should be some for a playlist")
+        let yt_dlp = YoutubeDLOptions::new()
+            .set("playliststart", 1)
+            .set("playlistend", 10)
+            .set("noplaylist", false)
+            .set("extract_flat", "in_playlist")
+            .build()?;
+
+        let info = yt_dlp.extract_info(&url, false, false)?;
+
+        if info.get("_type") == Some(&serde_json::Value::String("Playlist".to_owned())) {
+            json_get!(info, "title", as_str).to_owned()
         } else {
             bail!("The url ('{}') does not represent a playlist!", &url)
         }
diff --git a/yt/src/unreachable.rs b/crates/yt/src/unreachable.rs
index 436fbb6..436fbb6 100644
--- a/yt/src/unreachable.rs
+++ b/crates/yt/src/unreachable.rs
diff --git a/yt/src/update/mod.rs b/crates/yt/src/update/mod.rs
index 7efe0da..f0b1e2c 100644
--- a/yt/src/update/mod.rs
+++ b/crates/yt/src/update/mod.rs
@@ -15,7 +15,7 @@ use anyhow::{Context, Ok, Result};
 use chrono::{DateTime, Utc};
 use log::{info, warn};
 use url::Url;
-use yt_dlp::{unsmuggle_url, wrapper::info_json::InfoJson};
+use yt_dlp::{InfoJson, json_cast, json_get};
 
 use crate::{
     app::App,
@@ -72,19 +72,7 @@ pub async fn update(
 }
 
 #[allow(clippy::too_many_lines)]
-pub fn video_entry_to_video(entry: InfoJson, sub: Option<&Subscription>) -> Result<Video> {
-    macro_rules! unwrap_option {
-        ($option:expr) => {
-            match $option {
-                Some(x) => x,
-                None => anyhow::bail!(concat!(
-                    "Expected a value, but '",
-                    stringify!($option),
-                    "' is None!"
-                )),
-            }
-        };
-    }
+pub fn video_entry_to_video(entry: &InfoJson, sub: Option<&Subscription>) -> Result<Video> {
     fn fmt_context(date: &str, extended: Option<&str>) -> String {
         let f = format!(
             "Failed to parse the `upload_date` of the entry ('{date}'). \
@@ -97,7 +85,9 @@ pub fn video_entry_to_video(entry: InfoJson, sub: Option<&Subscription>) -> Resu
         }
     }
 
-    let publish_date = if let Some(date) = &entry.upload_date {
+    let publish_date = if let Some(date) = &entry.get("upload_date") {
+        let date = json_cast!(date, as_str);
+
         let year: u32 = date
             .chars()
             .take(4)
@@ -113,7 +103,7 @@ pub fn video_entry_to_video(entry: InfoJson, sub: Option<&Subscription>) -> Resu
             .with_context(|| fmt_context(date, None))?;
         let day: u32 = date
             .chars()
-            .skip(6)
+            .skip(4 + 2)
             .take(2)
             .collect::<String>()
             .parse()
@@ -128,42 +118,59 @@ pub fn video_entry_to_video(entry: InfoJson, sub: Option<&Subscription>) -> Resu
     } else {
         warn!(
             "The video '{}' lacks it's upload date!",
-            unwrap_option!(&entry.title)
+            json_get!(entry, "title", as_str)
         );
         None
     };
 
-    let thumbnail_url = match (&entry.thumbnails, &entry.thumbnail) {
+    let thumbnail_url = match (&entry.get("thumbnails"), &entry.get("thumbnail")) {
         (None, None) => None,
-        (None, Some(thumbnail)) => Some(thumbnail.to_owned()),
+        (None, Some(thumbnail)) => Some(Url::from_str(json_cast!(thumbnail, as_str))?),
 
         // TODO: The algorithm is not exactly the best <2024-05-28>
-        (Some(thumbnails), None) => thumbnails.first().map(|thumbnail| thumbnail.url.clone()),
-        (Some(_), Some(thumnail)) => Some(thumnail.to_owned()),
+        (Some(thumbnails), None) => {
+            if let Some(thumbnail) = json_cast!(thumbnails, as_array).first() {
+                Some(Url::from_str(json_get!(
+                    json_cast!(thumbnail, as_object),
+                    "url",
+                    as_str
+                ))?)
+            } else {
+                None
+            }
+        }
+        (Some(_), Some(thumnail)) => Some(Url::from_str(json_cast!(thumnail, as_str))?),
     };
 
     let url = {
-        let smug_url: Url = unwrap_option!(entry.webpage_url.clone());
-        unsmuggle_url(&smug_url)?
+        let smug_url: Url = json_get!(entry, "webpage_url", as_str).parse()?;
+        // unsmuggle_url(&smug_url)?
+        smug_url
     };
 
-    let extractor_hash = blake3::hash(unwrap_option!(entry.id).as_bytes());
+    let extractor_hash = blake3::hash(json_get!(entry, "id", as_str).as_bytes());
 
     let subscription_name = if let Some(sub) = sub {
         Some(sub.name.clone())
-    } else if let Some(uploader) = entry.uploader {
-        if entry.webpage_url_domain == Some("youtube.com".to_owned()) {
+    } else if let Some(uploader) = entry.get("uploader") {
+        if entry.get("webpage_url_domain")
+            == Some(&serde_json::Value::String("youtube.com".to_owned()))
+        {
             Some(format!("{uploader} - Videos"))
         } else {
-            Some(uploader.clone())
+            Some(json_cast!(uploader, as_str).to_owned())
         }
     } else {
         None
     };
 
     let video = Video {
-        description: entry.description.clone(),
-        duration: MaybeDuration::from_maybe_secs_f64(entry.duration),
+        description: entry
+            .get("description")
+            .map(|val| json_cast!(val, as_str).to_owned()),
+        duration: MaybeDuration::from_maybe_secs_f64(
+            entry.get("duration").map(|val| json_cast!(val, as_f64)),
+        ),
         extractor_hash: ExtractorHash::from_hash(extractor_hash),
         last_status_change: TimeStamp::from_now(),
         parent_subscription_name: subscription_name,
@@ -171,7 +178,7 @@ pub fn video_entry_to_video(entry: InfoJson, sub: Option<&Subscription>) -> Resu
         publish_date: publish_date.map(TimeStamp::from_secs),
         status: VideoStatus::Pick,
         thumbnail_url,
-        title: unwrap_option!(entry.title.clone()),
+        title: json_get!(entry, "title", as_str).to_owned(),
         url,
         watch_progress: Duration::default(),
     };
@@ -180,7 +187,7 @@ pub fn video_entry_to_video(entry: InfoJson, sub: Option<&Subscription>) -> Resu
 
 async fn process_subscription(app: &App, sub: &Subscription, entry: InfoJson) -> Result<()> {
     let video =
-        video_entry_to_video(entry, Some(sub)).context("Failed to parse search entry as Video")?;
+        video_entry_to_video(&entry, Some(sub)).context("Failed to parse search entry as Video")?;
 
     add_video(app, video.clone())
         .await
diff --git a/crates/yt/src/update/updater.rs b/crates/yt/src/update/updater.rs
new file mode 100644
index 0000000..8da654b
--- /dev/null
+++ b/crates/yt/src/update/updater.rs
@@ -0,0 +1,167 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+// This file is part of Yt.
+//
+// You should have received a copy of the License along with this program.
+// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+use std::io::{Write, stderr};
+
+use anyhow::{Context, Result};
+use blake3::Hash;
+use futures::{
+    StreamExt, TryStreamExt,
+    stream::{self},
+};
+use log::{Level, debug, error, log_enabled};
+use serde_json::json;
+use yt_dlp::{InfoJson, YoutubeDLOptions, json_cast, json_get};
+
+use crate::{
+    ansi_escape_codes::{clear_whole_line, move_to_col},
+    app::App,
+    storage::subscriptions::Subscription,
+};
+
+use super::process_subscription;
+
+pub(super) struct Updater<'a> {
+    max_backlog: usize,
+    hashes: &'a [Hash],
+}
+
+impl<'a> Updater<'a> {
+    pub(super) fn new(max_backlog: usize, hashes: &'a [Hash]) -> Self {
+        Self {
+            max_backlog,
+            hashes,
+        }
+    }
+
+    pub(super) async fn update(
+        &mut self,
+        app: &App,
+        subscriptions: &[&Subscription],
+    ) -> Result<()> {
+        let mut stream = stream::iter(subscriptions)
+            .map(|sub| self.get_new_entries(sub))
+            .buffer_unordered(100);
+
+        while let Some(output) = stream.next().await {
+            let mut entries = output?;
+
+            if entries.is_empty() {
+                continue;
+            }
+
+            let (sub, entry) = entries.remove(0);
+            process_subscription(app, sub, entry).await?;
+
+            let entry_stream: Result<()> = stream::iter(entries)
+                .map(|(sub, entry)| process_subscription(app, sub, entry))
+                .buffer_unordered(100)
+                .try_collect()
+                .await;
+            entry_stream?;
+        }
+
+        Ok(())
+    }
+
+    async fn get_new_entries(
+        &self,
+        sub: &'a Subscription,
+    ) -> Result<Vec<(&'a Subscription, InfoJson)>> {
+        let yt_dlp = YoutubeDLOptions::new()
+            .set("playliststart", 1)
+            .set("playlistend", self.max_backlog)
+            .set("noplaylist", false)
+            .set(
+                "extractor_args",
+                json! {{"youtubetab": {"approximate_date": [""]}}},
+            )
+            // TODO: This also removes unlisted and other stuff. Find a good way to remove the
+            // members-only videos from the feed. <2025-04-17>
+            .set("match-filter", "availability=public")
+            .build()?;
+
+        if !log_enabled!(Level::Debug) {
+            clear_whole_line();
+            move_to_col(1);
+            eprint!("Checking playlist {}...", sub.name);
+            move_to_col(1);
+            stderr().flush()?;
+        }
+
+        let info = yt_dlp
+            .extract_info(&sub.url, false, false)
+            .with_context(|| format!("Failed to get playlist '{}'.", sub.name))?;
+
+        let empty = vec![];
+        let entries = info
+            .get("entries")
+            .map_or(&empty, |val| json_cast!(val, as_array));
+
+        let valid_entries: Vec<(&Subscription, InfoJson)> = entries
+            .iter()
+            .take(self.max_backlog)
+            .filter_map(|entry| -> Option<(&Subscription, InfoJson)> {
+                let id = json_get!(entry, "id", as_str);
+                let extractor_hash = blake3::hash(id.as_bytes());
+                if self.hashes.contains(&extractor_hash) {
+                    debug!("Skipping entry, as it is already present: '{extractor_hash}'",);
+                    None
+                } else {
+                    Some((sub, json_cast!(entry, as_object).to_owned()))
+                }
+            })
+            .collect();
+
+        let processed_entries: Vec<(&Subscription, InfoJson)> = stream::iter(valid_entries)
+            .map(
+                async |(sub, entry)| match yt_dlp.process_ie_result(entry, false) {
+                    Ok(output) => Ok((sub, output)),
+                    Err(err) => Err(err),
+                },
+            )
+            .buffer_unordered(100)
+            .collect::<Vec<_>>()
+            .await
+            .into_iter()
+            // Don't fail the whole update, if one of the entries fails to fetch.
+            .filter_map(|base| match base {
+                Ok(ok) => Some(ok),
+                Err(err) => {
+                    // TODO(@bpeetz): Add this <2025-06-13>
+                    // if let YtDlpError::PythonError { error, kind } = &err {
+                    //     if kind.as_str() == "<class 'yt_dlp.utils.DownloadError'>"
+                    //         && error.to_string().as_str().contains(
+                    //             "Join this channel to get access to members-only content ",
+                    //         )
+                    //     {
+                    //         // Hide this error
+                    //     } else {
+                    //         let error_string = error.to_string();
+                    //         let error = error_string
+                    //             .strip_prefix("DownloadError: \u{1b}[0;31mERROR:\u{1b}[0m ")
+                    //             .expect("This prefix should exists");
+                    //         error!("{error}");
+                    //     }
+                    //     return None;
+                    // }
+
+                    // TODO(@bpeetz): Ideally, we _would_ actually exit on unexpected errors, but
+                    // this is fine for now.  <2025-06-13>
+                    // Some(Err(err).context("Failed to process new entries."))
+                    error!("While processing entry: {err}");
+                    None
+                }
+            })
+            .collect();
+
+        Ok(processed_entries)
+    }
+}
diff --git a/yt/src/version/mod.rs b/crates/yt/src/version/mod.rs
index 05d85e0..05d85e0 100644
--- a/yt/src/version/mod.rs
+++ b/crates/yt/src/version/mod.rs
diff --git a/yt/src/videos/display/format_video.rs b/crates/yt/src/videos/display/format_video.rs
index b97acb1..b97acb1 100644
--- a/yt/src/videos/display/format_video.rs
+++ b/crates/yt/src/videos/display/format_video.rs
diff --git a/yt/src/videos/display/mod.rs b/crates/yt/src/videos/display/mod.rs
index 1188569..1188569 100644
--- a/yt/src/videos/display/mod.rs
+++ b/crates/yt/src/videos/display/mod.rs
diff --git a/yt/src/videos/mod.rs b/crates/yt/src/videos/mod.rs
index e821772..e821772 100644
--- a/yt/src/videos/mod.rs
+++ b/crates/yt/src/videos/mod.rs
diff --git a/yt/src/watch/mod.rs b/crates/yt/src/watch/mod.rs
index 6827b2c..c32a76f 100644
--- a/yt/src/watch/mod.rs
+++ b/crates/yt/src/watch/mod.rs
@@ -58,9 +58,12 @@ fn init_mpv(app: &App) -> Result<(Mpv, EventContext)> {
     let config_path = &app.config.paths.mpv_config_path;
     if config_path.try_exists()? {
         info!("Found mpv.conf at '{}'!", config_path.display());
-        mpv.command("load-config-file", &[config_path
-            .to_str()
-            .context("Failed to parse the config path is utf8-stringt")?])?;
+        mpv.command(
+            "load-config-file",
+            &[config_path
+                .to_str()
+                .context("Failed to parse the config path is utf8-stringt")?],
+        )?;
     } else {
         warn!(
             "Did not find a mpv.conf file at '{}'",
@@ -71,9 +74,12 @@ fn init_mpv(app: &App) -> Result<(Mpv, EventContext)> {
     let input_path = &app.config.paths.mpv_input_path;
     if input_path.try_exists()? {
         info!("Found mpv.input.conf at '{}'!", input_path.display());
-        mpv.command("load-input-conf", &[input_path
-            .to_str()
-            .context("Failed to parse the input path as utf8 string")?])?;
+        mpv.command(
+            "load-input-conf",
+            &[input_path
+                .to_str()
+                .context("Failed to parse the input path as utf8 string")?],
+        )?;
     } else {
         warn!(
             "Did not find a mpv.input.conf file at '{}'",
diff --git a/yt/src/watch/playlist.rs b/crates/yt/src/watch/playlist.rs
index 6ac8b12..ff383d0 100644
--- a/yt/src/watch/playlist.rs
+++ b/crates/yt/src/watch/playlist.rs
@@ -11,6 +11,7 @@
 use std::path::Path;
 
 use crate::{
+    ansi_escape_codes::{cursor_up, erase_in_display_from_cursor},
     app::App,
     storage::video_database::{Video, VideoStatus, get, notify::wait_for_db_write},
 };
@@ -31,17 +32,6 @@ fn cache_values(video: &Video) -> (&Path, bool) {
     }
 }
 
-// ANSI ESCAPE CODES Wrappers {{{
-// see: https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands
-const CSI: &str = "\x1b[";
-fn erase_in_display_from_cursor() {
-    print!("{CSI}0J");
-}
-fn cursor_up(number: usize) {
-    print!("{CSI}{number}A");
-}
-// }}}
-
 /// # Panics
 /// Only if internal assertions fail.
 pub async fn playlist(app: &App, watch: bool) -> Result<()> {
diff --git a/yt/src/watch/playlist_handler/client_messages/mod.rs b/crates/yt/src/watch/playlist_handler/client_messages/mod.rs
index 6f7a59e..6f7a59e 100644
--- a/yt/src/watch/playlist_handler/client_messages/mod.rs
+++ b/crates/yt/src/watch/playlist_handler/client_messages/mod.rs
diff --git a/yt/src/watch/playlist_handler/mod.rs b/crates/yt/src/watch/playlist_handler/mod.rs
index 2672ff5..29b8f39 100644
--- a/yt/src/watch/playlist_handler/mod.rs
+++ b/crates/yt/src/watch/playlist_handler/mod.rs
@@ -41,10 +41,10 @@ pub enum Status {
 }
 
 fn mpv_message(mpv: &Mpv, message: &str, time: Duration) -> Result<()> {
-    mpv.command("show-text", &[
-        message,
-        time.as_millis().to_string().as_str(),
-    ])?;
+    mpv.command(
+        "show-text",
+        &[message, time.as_millis().to_string().as_str()],
+    )?;
     Ok(())
 }
 
@@ -139,15 +139,18 @@ pub(super) async fn reload_mpv_playlist(
 
     debug!("Will add {} videos to playlist.", playlist.len());
     playlist.into_iter().try_for_each(|cache_path| {
-        mpv.command("loadfile", &[
-            cache_path.to_str().with_context(|| {
-                format!(
-                    "Failed to parse the video cache path ('{}') as valid utf8",
-                    cache_path.display()
-                )
-            })?,
-            "append-play",
-        ])?;
+        mpv.command(
+            "loadfile",
+            &[
+                cache_path.to_str().with_context(|| {
+                    format!(
+                        "Failed to parse the video cache path ('{}') as valid utf8",
+                        cache_path.display()
+                    )
+                })?,
+                "append-play",
+            ],
+        )?;
 
         Ok::<(), anyhow::Error>(())
     })?;
diff --git a/crates/yt_dlp/.cargo/config.toml b/crates/yt_dlp/.cargo/config.toml
deleted file mode 100644
index d84f14d..0000000
--- a/crates/yt_dlp/.cargo/config.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-# yt - A fully featured command line YouTube client
-#
-# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of Yt.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-[env]
-PYO3_PYTHON = "/nix/store/7xzk119acyws2c4ysygdv66l0grxkr39-python3-3.11.9-env/bin/python3"
diff --git a/crates/yt_dlp/Cargo.toml b/crates/yt_dlp/Cargo.toml
index a948a34..ddd5f9b 100644
--- a/crates/yt_dlp/Cargo.toml
+++ b/crates/yt_dlp/Cargo.toml
@@ -10,7 +10,7 @@
 
 [package]
 name = "yt_dlp"
-description = "A wrapper around the python yt_dlp library"
+description = "A rust fii wrapper library for the python yt_dlp library"
 keywords = []
 categories = []
 version.workspace = true
@@ -19,19 +19,16 @@ authors.workspace = true
 license.workspace = true
 repository.workspace = true
 rust-version.workspace = true
-publish = false
+publish = true
 
 [dependencies]
-pyo3 = { version = "0.23.4", features = ["auto-initialize"] }
-bytes.workspace = true
+indexmap = { version = "2.9.0", default-features = false }
 log.workspace = true
-serde.workspace = true
+rustpython = { git = "https://github.com/RustPython/RustPython.git", features = ["threading", "stdlib", "stdio", "importlib", "ssl"], default-features = false }
 serde_json.workspace = true
+thiserror = "2.0.12"
 url.workspace = true
 
-[dev-dependencies]
-tokio.workspace = true
-
 [lints]
 workspace = true
 
diff --git a/crates/yt_dlp/src/duration.rs b/crates/yt_dlp/src/duration.rs
deleted file mode 100644
index 19181a5..0000000
--- a/crates/yt_dlp/src/duration.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-// TODO: This file should be de-duplicated with the same file in the 'yt' crate <2024-06-25>
-
-#[derive(Debug, Clone, Copy)]
-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 From<Option<f64>> for Duration {
-    fn from(value: Option<f64>) -> Self {
-        Self {
-            #[allow(
-                clippy::cast_possible_truncation,
-                clippy::cast_precision_loss,
-                clippy::cast_sign_loss
-            )]
-            time: value.unwrap_or(0.0).ceil() as u32,
-        }
-    }
-}
-
-impl std::fmt::Display for Duration {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-        const SECOND: u32 = 1;
-        const MINUTE: u32 = 60 * SECOND;
-        const HOUR: u32 = 60 * MINUTE;
-
-        let base_hour = self.time - (self.time % HOUR);
-        let base_min = (self.time % HOUR) - ((self.time % HOUR) % MINUTE);
-        let base_sec = (self.time % HOUR) % MINUTE;
-
-        let h = base_hour / HOUR;
-        let m = base_min / MINUTE;
-        let s = base_sec / SECOND;
-
-        if self.time == 0 {
-            write!(f, "0s")
-        } else if h > 0 {
-            write!(f, "{h}h {m}m")
-        } else {
-            write!(f, "{m}m {s}s")
-        }
-    }
-}
-#[cfg(test)]
-mod test {
-    use super::Duration;
-
-    #[test]
-    fn test_display_duration_1h() {
-        let dur = Duration { time: 60 * 60 };
-        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());
-    }
-}
diff --git a/crates/yt_dlp/src/error.rs b/crates/yt_dlp/src/error.rs
deleted file mode 100644
index 3881f0b..0000000
--- a/crates/yt_dlp/src/error.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-use std::{fmt::Display, io};
-
-use pyo3::Python;
-
-#[derive(Debug)]
-#[allow(clippy::module_name_repetitions)]
-pub enum YtDlpError {
-    ResponseParseError {
-        error: serde_json::error::Error,
-    },
-    PythonError {
-        error: Box<pyo3::PyErr>,
-        kind: String,
-    },
-    IoError {
-        error: io::Error,
-    },
-}
-
-impl std::error::Error for YtDlpError {}
-
-impl Display for YtDlpError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            YtDlpError::ResponseParseError { error } => write!(
-                f,
-                include_str!("./python_json_decode_failed.error_msg"),
-                error
-            ),
-            YtDlpError::PythonError { error, kind: _ } => write!(f, "Python error: {error}"),
-            YtDlpError::IoError { error } => write!(f, "Io error: {error}"),
-        }
-    }
-}
-
-impl From<serde_json::error::Error> for YtDlpError {
-    fn from(value: serde_json::error::Error) -> Self {
-        Self::ResponseParseError { error: value }
-    }
-}
-
-impl From<pyo3::PyErr> for YtDlpError {
-    fn from(value: pyo3::PyErr) -> Self {
-        Python::with_gil(|py| {
-            let kind = value.get_type(py).to_string();
-            Self::PythonError {
-                error: Box::new(value),
-                kind,
-            }
-        })
-    }
-}
-
-impl From<io::Error> for YtDlpError {
-    fn from(value: io::Error) -> Self {
-        Self::IoError { error: value }
-    }
-}
diff --git a/crates/yt_dlp/src/lib.rs b/crates/yt_dlp/src/lib.rs
index 40610c2..34b8a5d 100644
--- a/crates/yt_dlp/src/lib.rs
+++ b/crates/yt_dlp/src/lib.rs
@@ -1,551 +1,541 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-// The pyo3 `pyfunction` proc-macros call unsafe functions internally, which trigger this lint.
-#![allow(unsafe_op_in_unsafe_fn)]
-#![allow(clippy::missing_errors_doc)]
-
-use std::io::stderr;
-use std::{env, process};
-use std::{fs::File, io::Write};
-
-use std::{path::PathBuf, sync::Once};
-
-use crate::{duration::Duration, logging::setup_logging, wrapper::info_json::InfoJson};
-
-use bytes::Bytes;
-use error::YtDlpError;
-use log::{Level, debug, info, log_enabled};
-use pyo3::types::{PyString, PyTuple, PyTupleMethods};
-use pyo3::{
-    Bound, PyAny, PyResult, Python, pyfunction,
-    types::{PyAnyMethods, PyDict, PyDictMethods, PyList, PyListMethods, PyModule},
-    wrap_pyfunction,
+//! The `yt_dlp` interface is completely contained in the [`YoutubeDL`] structure.
+
+use std::io::Write;
+use std::mem;
+use std::{env, fs::File, path::PathBuf};
+
+use indexmap::IndexMap;
+use log::{Level, debug, error, info, log_enabled};
+use logging::setup_logging;
+use rustpython::vm::builtins::PyList;
+use rustpython::{
+    InterpreterConfig,
+    vm::{
+        self, Interpreter, PyObjectRef, PyRef, VirtualMachine,
+        builtins::{PyBaseException, PyDict, PyStr},
+        function::{FuncArgs, KwArgs, PosArgs},
+    },
 };
-use serde::Serialize;
-use serde_json::{Map, Value};
 use url::Url;
 
-pub mod duration;
-pub mod error;
-pub mod logging;
-pub mod wrapper;
+mod logging;
+pub mod progress_hook;
 
-#[cfg(test)]
-mod tests;
-
-/// Synchronisation helper, to ensure that we don't setup the logger multiple times
-static SYNC_OBJ: Once = Once::new();
+#[macro_export]
+macro_rules! json_get {
+    ($value:expr, $name:literal, $into:ident) => {
+        $crate::json_cast!($value.get($name).expect("Should exist"), $into)
+    };
+}
 
-/// Add a logger to the yt-dlp options.
-/// If you have an logger set (i.e. for rust), than this will log to rust
-///
-/// # Panics
-/// This should never panic.
-pub fn add_logger_and_sig_handler<'a>(
-    opts: Bound<'a, PyDict>,
-    py: Python<'_>,
-) -> PyResult<Bound<'a, PyDict>> {
-    /// Is the specified record to be logged? Returns false for no,
-    /// true for yes. Filters can either modify log records in-place or
-    /// return a completely different record instance which will replace
-    /// the original log record in any future processing of the event.
-    #[pyfunction]
-    fn filter_error_log(_py: Python<'_>, record: &Bound<'_, PyAny>) -> bool {
-        // Filter out all error logs (they are propagated as rust errors)
-        let levelname: String = record
-            .getattr("levelname")
-            .expect("This should exist")
-            .extract()
-            .expect("This should be a String");
-
-        let return_value = levelname.as_str() != "ERROR";
-
-        if log_enabled!(Level::Debug) && !return_value {
-            let message: String = record
-                .call_method0("getMessage")
-                .expect("This method exists")
-                .extract()
-                .expect("The message is a string");
-
-            debug!("Swollowed error message: '{message}'");
-        }
-        return_value
-    }
+#[macro_export]
+macro_rules! json_cast {
+    ($value:expr, $into:ident) => {
+        $value.$into().expect(concat!(
+            "Should be able to cast value into ",
+            stringify!($into)
+        ))
+    };
+}
 
-    setup_logging(py, "yt_dlp")?;
-
-    let logging = PyModule::import(py, "logging")?;
-    let ytdl_logger = logging.call_method1("getLogger", ("yt_dlp",))?;
-
-    // Ensure that all events are logged by setting the log level to NOTSET (we filter on rust's side)
-    // Also use this static, to ensure that we don't configure the logger every time
-    SYNC_OBJ.call_once(|| {
-        // Disable the SIGINT (Ctrl+C) handler, python installs.
-        // This allows the user to actually stop the application with Ctrl+C.
-        // This is here because it can only be run in the main thread and this was here already.
-        py.run(
-            c"\
-import signal
-signal.signal(signal.SIGINT, signal.SIG_DFL)",
-            None,
-            None,
-        )
-        .expect("This code should always work");
-
-        let config_opts = PyDict::new(py);
-        config_opts
-            .set_item("level", 0)
-            .expect("Setting this item should always work");
-
-        logging
-            .call_method("basicConfig", (), Some(&config_opts))
-            .expect("This method exists");
-    });
-
-    ytdl_logger.call_method1(
-        "addFilter",
-        (wrap_pyfunction!(filter_error_log, py).expect("This function can be wrapped"),),
-    )?;
-
-    // This was taken from `ytcc`, I don't think it is still applicable
-    // ytdl_logger.setattr("propagate", false)?;
-    // let logging_null_handler = logging.call_method0("NullHandler")?;
-    // ytdl_logger.setattr("addHandler", logging_null_handler)?;
-
-    opts.set_item("logger", ytdl_logger).expect("Should work");
-
-    Ok(opts)
+/// The core of the `yt_dlp` interface.
+pub struct YoutubeDL {
+    interpreter: Interpreter,
+    youtube_dl_class: PyObjectRef,
+    yt_dlp_module: PyObjectRef,
+    options: serde_json::Map<String, serde_json::Value>,
 }
 
-#[pyfunction]
-#[allow(clippy::too_many_lines)]
-#[allow(clippy::missing_panics_doc)]
-#[allow(clippy::items_after_statements)]
-#[allow(
-    clippy::cast_possible_truncation,
-    clippy::cast_sign_loss,
-    clippy::cast_precision_loss
-)]
-pub fn progress_hook(py: Python<'_>, input: &Bound<'_, PyDict>) -> PyResult<()> {
-    // Only add the handler, if the log-level is higher than Debug (this avoids covering debug
-    // messages).
-    if log_enabled!(Level::Debug) {
-        return Ok(());
+impl std::fmt::Debug for YoutubeDL {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        // TODO(@bpeetz): Use something useful here. <2025-06-13>
+        f.write_str("YoutubeDL")
     }
+}
 
-    // ANSI ESCAPE CODES Wrappers {{{
-    // see: https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands
-    const CSI: &str = "\x1b[";
-    fn clear_whole_line() {
-        eprint!("{CSI}2K");
-    }
-    fn move_to_col(x: usize) {
-        eprint!("{CSI}{x}G");
-    }
-    // }}}
-
-    let input: Map<String, Value> = serde_json::from_str(&json_dumps(
-        py,
-        input
-            .downcast::<PyAny>()
-            .expect("Will always work")
-            .to_owned(),
-    )?)
-    .expect("python's json is valid");
-
-    macro_rules! get {
-        (@interrogate $item:ident, $type_fun:ident, $get_fun:ident, $name:expr) => {{
-            let a = $item.get($name).expect(concat!(
-                "The field '",
-                stringify!($name),
-                "' should exist."
-            ));
-
-            if a.$type_fun() {
-                a.$get_fun().expect(
-                    "The should have been checked in the if guard, so unpacking here is fine",
-                )
-            } else {
-                panic!(
-                    "Value {} => \n{}\n is not of type: {}",
-                    $name,
-                    a,
-                    stringify!($type_fun)
-                );
+impl YoutubeDL {
+    /// Construct this instance from options.
+    ///
+    /// # Panics
+    /// If `yt_dlp` changed their interface.
+    ///
+    /// # Errors
+    /// If a python call fails.
+    pub fn from_options(mut options: YoutubeDLOptions) -> Result<Self, build::Error> {
+        let mut settings = vm::Settings::default();
+        if let Ok(python_path) = env::var("PYTHONPATH") {
+            for path in python_path.split(':') {
+                settings.path_list.push(path.to_owned());
             }
-        }};
+        } else {
+            error!(
+                "No PYTHONPATH found or invalid utf8. \
+                This means, that you probably did not \
+                supply the yt_dlp!"
+            );
+        }
 
-        ($type_fun:ident, $get_fun:ident, $name1:expr, $name2:expr) => {{
-            let a = get! {@interrogate input, is_object, as_object, $name1};
-            let b = get! {@interrogate a, $type_fun, $get_fun, $name2};
-            b
-        }};
+        settings.install_signal_handlers = false;
 
-        ($type_fun:ident, $get_fun:ident, $name:expr) => {{
-            get! {@interrogate input, $type_fun, $get_fun, $name}
-        }};
-    }
+        // NOTE(@bpeetz): Another value leads to an internal codegen error. <2025-06-13>
+        settings.optimize = 0;
 
-    macro_rules! default_get {
-        (@interrogate $item:ident, $default:expr, $get_fun:ident, $name:expr) => {{
-            let a = if let Some(field) = $item.get($name) {
-                field.$get_fun().unwrap_or($default)
-            } else {
-                $default
-            };
-            a
-        }};
-
-        ($get_fun:ident, $default:expr, $name1:expr, $name2:expr) => {{
-            let a = get! {@interrogate input, is_object, as_object, $name1};
-            let b = default_get! {@interrogate a, $default, $get_fun, $name2};
-            b
-        }};
-
-        ($get_fun:ident, $default:expr, $name:expr) => {{
-            default_get! {@interrogate input, $default, $get_fun, $name}
-        }};
-    }
+        settings.isolated = true;
 
-    macro_rules! c {
-        ($color:expr, $format:expr) => {
-            format!("\x1b[{}m{}\x1b[0m", $color, $format)
-        };
-    }
+        let interpreter = InterpreterConfig::new()
+            .init_stdlib()
+            .settings(settings)
+            .interpreter();
 
-    fn format_bytes(bytes: u64) -> String {
-        let bytes = Bytes::new(bytes);
-        bytes.to_string()
-    }
+        let output_options = options.options.clone();
 
-    fn format_speed(speed: f64) -> String {
-        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
-        let bytes = Bytes::new(speed.floor() as u64);
-        format!("{bytes}/s")
-    }
+        let (yt_dlp_module, youtube_dl_class) = match interpreter.enter(|vm| {
+            let yt_dlp_module = vm.import("yt_dlp", 0)?;
+            let class = yt_dlp_module.get_attr("YoutubeDL", vm)?;
 
-    let get_title = || -> String {
-        match get! {is_string, as_str, "info_dict", "ext"} {
-            "vtt" => {
-                format!(
-                    "Subtitles ({})",
-                    default_get! {as_str, "<No Subtitle Language>", "info_dict", "name"}
-                )
+            let maybe_hook = mem::take(&mut options.progress_hook);
+            let opts = options.into_py_dict(vm);
+            if let Some(function) = maybe_hook {
+                opts.get_or_insert(vm, vm.new_pyobj("progress_hooks"), || {
+                    let hook: PyObjectRef = vm.new_function("progress_hook", function).into();
+                    vm.new_pyobj(vec![hook])
+                })
+                .expect("Should work?");
             }
-            "webm" | "mp4" | "mp3" | "m4a" => {
-                default_get! { as_str, "<No title>", "info_dict", "title"}.to_owned()
-            }
-            other => panic!("The extension '{other}' is not yet implemented"),
-        }
-    };
 
-    match get! {is_string, as_str, "status"} {
-        "downloading" => {
-            let elapsed = default_get! {as_f64, 0.0f64, "elapsed"};
-            let eta = default_get! {as_f64, 0.0, "eta"};
-            let speed = default_get! {as_f64, 0.0, "speed"};
-
-            let downloaded_bytes = get! {is_u64, as_u64, "downloaded_bytes"};
-            let (total_bytes, bytes_is_estimate): (u64, &'static str) = {
-                let total_bytes = default_get!(as_u64, 0, "total_bytes");
-                if total_bytes == 0 {
-                    let maybe_estimate = default_get!(as_u64, 0, "total_bytes_estimate");
-
-                    if maybe_estimate == 0 {
-                        // The download speed should be in bytes per second and the eta in seconds.
-                        // Thus multiplying them gets us the raw bytes (which were estimated by `yt_dlp`, from their `info.json`)
-                        let bytes_still_needed = (speed * eta).ceil() as u64;
-
-                        (downloaded_bytes + bytes_still_needed, "~")
-                    } else {
-                        (maybe_estimate, "~")
+            {
+                // Unconditionally set a logger.
+                // Otherwise, yt_dlp will log to stderr.
+
+                /// Is the specified record to be logged? Returns false for no,
+                /// true for yes. Filters can either modify log records in-place or
+                /// return a completely different record instance which will replace
+                /// the original log record in any future processing of the event.
+                fn filter_error_log(mut input: FuncArgs, vm: &VirtualMachine) -> bool {
+                    let record = input.args.remove(0);
+
+                    // Filter out all error logs (they are propagated as rust errors)
+                    let levelname: PyRef<PyStr> = record
+                        .get_attr("levelname", vm)
+                        .expect("This should exist")
+                        .downcast()
+                        .expect("This should be a String");
+
+                    let return_value = levelname.as_str() != "ERROR";
+
+                    if log_enabled!(Level::Debug) && !return_value {
+                        let message: String = {
+                            let get_message = record.get_attr("getMessage", vm).expect("Is set");
+                            let message: PyRef<PyStr> = get_message
+                                .call((), vm)
+                                .expect("Can be called")
+                                .downcast()
+                                .expect("Downcasting works");
+
+                            message.as_str().to_owned()
+                        };
+
+                        debug!("Swollowed error message: '{message}'");
                     }
-                } else {
-                    (total_bytes, "")
+                    return_value
                 }
-            };
-            let percent: f64 = {
-                if total_bytes == 0 {
-                    100.0
-                } else {
-                    (downloaded_bytes as f64 / total_bytes as f64) * 100.0
+
+                let logging = setup_logging(vm, "yt_dlp")?;
+                let ytdl_logger = {
+                    let get_logger = logging.get_item("getLogger", vm)?;
+                    get_logger.call(("yt_dlp",), vm)?
+                };
+
+                {
+                    let args = FuncArgs::new(
+                        PosArgs::new(vec![]),
+                        KwArgs::new({
+                            let mut map = IndexMap::new();
+                            // Ensure that all events are logged by setting
+                            // the log level to NOTSET (we filter on rust's side)
+                            map.insert("level".to_owned(), vm.new_pyobj(0));
+                            map
+                        }),
+                    );
+
+                    let basic_config = logging.get_item("basicConfig", vm)?;
+                    basic_config.call(args, vm)?;
                 }
-            };
 
-            clear_whole_line();
-            move_to_col(1);
-
-            eprint!(
-                "'{}' [{}/{} at {}] -> [{} of {}{} {}] ",
-                c!("34;1", get_title()),
-                c!("33;1", Duration::from(Some(elapsed))),
-                c!("33;1", Duration::from(Some(eta))),
-                c!("32;1", format_speed(speed)),
-                c!("31;1", format_bytes(downloaded_bytes)),
-                c!("31;1", bytes_is_estimate),
-                c!("31;1", format_bytes(total_bytes)),
-                c!("36;1", format!("{:.02}%", percent))
-            );
-            stderr().flush()?;
-        }
-        "finished" => {
-            eprintln!("-> Finished downloading.");
-        }
-        "error" => {
-            // TODO: This should probably return an Err. But I'm not so sure where the error would
-            // bubble up to (i.e., who would catch it) <2025-01-21>
-            eprintln!("-> Error while downloading: {}", get_title());
-            process::exit(1);
-        }
-        other => unreachable!("'{other}' should not be a valid state!"),
-    };
+                {
+                    let add_filter = ytdl_logger.get_attr("addFilter", vm)?;
+                    add_filter.call(
+                        (vm.new_function("yt_dlp_error_filter", filter_error_log),),
+                        vm,
+                    )?;
+                }
 
-    Ok(())
-}
+                opts.set_item("logger", ytdl_logger, vm)?;
+            }
 
-pub fn add_hooks<'a>(opts: Bound<'a, PyDict>, py: Python<'_>) -> PyResult<Bound<'a, PyDict>> {
-    if let Some(hooks) = opts.get_item("progress_hooks")? {
-        let hooks = hooks.downcast::<PyList>()?;
-        hooks.append(wrap_pyfunction!(progress_hook, py)?)?;
+            let youtube_dl_class = class.call((opts,), vm)?;
 
-        opts.set_item("progress_hooks", hooks)?;
-    } else {
-        // No hooks are set yet
-        let hooks_list = PyList::new(py, &[wrap_pyfunction!(progress_hook, py)?])?;
+            Ok::<_, PyRef<PyBaseException>>((yt_dlp_module, youtube_dl_class))
+        }) {
+            Ok(ok) => ok,
+            Err(err) => {
+                interpreter.finalize(Some(err));
+                return Err(build::Error::Python);
+            }
+        };
 
-        opts.set_item("progress_hooks", hooks_list)?;
+        Ok(Self {
+            interpreter,
+            youtube_dl_class,
+            yt_dlp_module,
+            options: output_options,
+        })
     }
 
-    Ok(opts)
-}
-
-/// Take the result of the ie (may be modified) and resolve all unresolved
-/// references (URLs, playlist items).
-///
-/// It will also download the videos if 'download'.
-/// Returns the resolved `ie_result`.
-#[allow(clippy::unused_async)]
-#[allow(clippy::missing_panics_doc)]
-pub async fn process_ie_result(
-    yt_dlp_opts: &Map<String, Value>,
-    ie_result: InfoJson,
-    download: bool,
-) -> Result<InfoJson, YtDlpError> {
-    Python::with_gil(|py| -> Result<InfoJson, YtDlpError> {
-        let opts = json_map_to_py_dict(yt_dlp_opts, py)?;
-
-        let instance = get_yt_dlp(py, opts)?;
-
-        let args = {
-            let ie_result = json_loads_str(py, ie_result)?;
-            (ie_result,)
-        };
+    /// # Panics
+    ///
+    /// If `yt_dlp` changed their location or type of `__version__`.
+    pub fn version(&self) -> String {
+        let str_ref: PyRef<PyStr> = self.interpreter.enter_and_expect(
+            |vm| {
+                let version_module = self.yt_dlp_module.get_attr("version", vm)?;
+                let version = version_module.get_attr("__version__", vm)?;
+                let version = version.downcast().expect("This should always be a string");
+                Ok(version)
+            },
+            "yt_dlp version location has changed",
+        );
+        str_ref.to_string()
+    }
 
-        let kwargs = PyDict::new(py);
-        kwargs.set_item("download", download)?;
+    /// Download a given list of URLs.
+    /// Returns the paths they were downloaded to.
+    ///
+    /// # Errors
+    /// If one of the downloads error.
+    pub fn download(&self, urls: &[Url]) -> Result<Vec<PathBuf>, extract_info::Error> {
+        let mut out_paths = Vec::with_capacity(urls.len());
+
+        for url in urls {
+            info!("Started downloading url: '{url}'");
+            let info_json = self.extract_info(url, true, true)?;
+
+            // Try to work around yt-dlp type weirdness
+            let result_string = if let Some(filename) = info_json.get("filename") {
+                PathBuf::from(json_cast!(filename, as_str))
+            } else {
+                PathBuf::from(json_get!(
+                    json_cast!(
+                        json_get!(info_json, "requested_downloads", as_array)[0],
+                        as_object
+                    ),
+                    "filename",
+                    as_str
+                ))
+            };
 
-        let result = instance
-            .call_method("process_ie_result", args, Some(&kwargs))?
-            .downcast_into::<PyDict>()
-            .expect("This is a dict");
+            out_paths.push(result_string);
+            info!("Finished downloading url");
+        }
 
-        let result_str = json_dumps(py, result.into_any())?;
+        Ok(out_paths)
+    }
 
-        serde_json::from_str(&result_str).map_err(Into::into)
-    })
-}
+    /// `extract_info(self, url, download=True, ie_key=None, extra_info=None, process=True, force_generic_extractor=False)`
+    ///
+    /// Extract and return the information dictionary of the URL
+    ///
+    /// Arguments:
+    /// - `url`          URL to extract
+    ///
+    /// Keyword arguments:
+    /// :`download`     Whether to download videos
+    /// :`process`      Whether to resolve all unresolved references (URLs, playlist items).
+    ///                 Must be True for download to work
+    ///
+    /// # Panics
+    /// If expectations about python fail to hold.
+    ///
+    /// # Errors
+    /// If python operations fail.
+    pub fn extract_info(
+        &self,
+        url: &Url,
+        download: bool,
+        process: bool,
+    ) -> Result<InfoJson, extract_info::Error> {
+        match self.interpreter.enter(|vm| {
+            let pos_args = PosArgs::new(vec![vm.new_pyobj(url.to_string())]);
+
+            let kw_args = KwArgs::new({
+                let mut map = IndexMap::new();
+                map.insert("download".to_owned(), vm.new_pyobj(download));
+                map.insert("process".to_owned(), vm.new_pyobj(process));
+                map
+            });
+
+            let fun_args = FuncArgs::new(pos_args, kw_args);
+
+            let inner = self.youtube_dl_class.get_attr("extract_info", vm)?;
+            let result = inner
+                .call_with_args(fun_args, vm)?
+                .downcast::<PyDict>()
+                .expect("This is a dict");
+
+            // Resolve the generator object
+            if let Ok(generator) = result.get_item("entries", vm) {
+                if generator.payload_is::<PyList>() {
+                    // already resolved. Do nothing
+                } else {
+                    let max_backlog = self.options.get("playlistend").map_or(10000, |value| {
+                        usize::try_from(value.as_u64().expect("Works")).expect("Should work")
+                    });
+
+                    let mut out = vec![];
+                    let next = generator.get_attr("__next__", vm)?;
+                    while let Ok(output) = next.call((), vm) {
+                        out.push(output);
+
+                        if out.len() == max_backlog {
+                            break;
+                        }
+                    }
+                    result.set_item("entries", vm.new_pyobj(out), vm)?;
+                }
+            }
 
-/// `extract_info(self, url, download=True, ie_key=None, extra_info=None, process=True, force_generic_extractor=False)`
-///
-/// Extract and return the information dictionary of the URL
-///
-/// Arguments:
-/// @param url          URL to extract
-///
-/// Keyword arguments:
-/// @param download     Whether to download videos
-/// @param process      Whether to resolve all unresolved references (URLs, playlist items).
-///                     Must be True for download to work
-/// @param `ie_key`       Use only the extractor with this key
-///
-/// @param `extra_info`   Dictionary containing the extra values to add to the info (For internal use only)
-/// @`force_generic_extractor`  Force using the generic extractor (Deprecated; use `ie_key`='Generic')
-#[allow(clippy::unused_async)]
-#[allow(clippy::missing_panics_doc)]
-pub async fn extract_info(
-    yt_dlp_opts: &Map<String, Value>,
-    url: &Url,
-    download: bool,
-    process: bool,
-) -> Result<InfoJson, YtDlpError> {
-    Python::with_gil(|py| -> Result<InfoJson, YtDlpError> {
-        let opts = json_map_to_py_dict(yt_dlp_opts, py)?;
-
-        let instance = get_yt_dlp(py, opts)?;
-        let args = (url.as_str(),);
-
-        let kwargs = PyDict::new(py);
-        kwargs.set_item("download", download)?;
-        kwargs.set_item("process", process)?;
-
-        let result = instance
-            .call_method("extract_info", args, Some(&kwargs))?
-            .downcast_into::<PyDict>()
-            .expect("This is a dict");
-
-        // Resolve the generator object
-        if let Some(generator) = result.get_item("entries")? {
-            if generator.is_instance_of::<PyList>() {
-                // already resolved. Do nothing
-            } else {
-                let max_backlog = yt_dlp_opts.get("playlistend").map_or(10000, |value| {
-                    usize::try_from(value.as_u64().expect("Works")).expect("Should work")
-                });
+            let result = {
+                let sanitize = self.youtube_dl_class.get_attr("sanitize_info", vm)?;
+                let value = sanitize.call((result,), vm)?;
 
-                let mut out = vec![];
-                while let Ok(output) = generator.call_method0("__next__") {
-                    out.push(output);
+                value.downcast::<PyDict>().expect("This should stay a dict")
+            };
 
-                    if out.len() == max_backlog {
-                        break;
-                    }
+            let result_json = json_dumps(result, vm);
+
+            if let Ok(confirm) = env::var("YT_STORE_INFO_JSON") {
+                if confirm == "yes" {
+                    let mut file = File::create("output.info.json").unwrap();
+                    write!(
+                        file,
+                        "{}",
+                        serde_json::to_string_pretty(&serde_json::Value::Object(
+                            result_json.clone()
+                        ))
+                        .expect("Valid json")
+                    )
+                    .unwrap();
                 }
-                result.set_item("entries", out)?;
+            }
+
+            Ok::<_, PyRef<PyBaseException>>(result_json)
+        }) {
+            Ok(ok) => Ok(ok),
+            Err(err) => {
+                self.interpreter.enter(|vm| {
+                    vm.print_exception(err);
+                });
+                Err(extract_info::Error::Python)
             }
         }
+    }
+
+    /// Take the (potentially modified) result of the information extractor (i.e.,
+    /// [`Self::extract_info`] with `process` and `download` set to false)
+    /// and resolve all unresolved references (URLs,
+    /// playlist items).
+    ///
+    /// It will also download the videos if 'download' is true.
+    /// Returns the resolved `ie_result`.
+    ///
+    /// # Panics
+    /// If expectations about python fail to hold.
+    ///
+    /// # Errors
+    /// If python operations fail.
+    pub fn process_ie_result(
+        &self,
+        ie_result: InfoJson,
+        download: bool,
+    ) -> Result<InfoJson, process_ie_result::Error> {
+        match self.interpreter.enter(|vm| {
+            let pos_args = PosArgs::new(vec![vm.new_pyobj(json_loads(ie_result, vm))]);
+
+            let kw_args = KwArgs::new({
+                let mut map = IndexMap::new();
+                map.insert("download".to_owned(), vm.new_pyobj(download));
+                map
+            });
+
+            let fun_args = FuncArgs::new(pos_args, kw_args);
+
+            let inner = self.youtube_dl_class.get_attr("process_ie_result", vm)?;
+            let result = inner
+                .call_with_args(fun_args, vm)?
+                .downcast::<PyDict>()
+                .expect("This is a dict");
+
+            let result = {
+                let sanitize = self.youtube_dl_class.get_attr("sanitize_info", vm)?;
+                let value = sanitize.call((result,), vm)?;
+
+                value.downcast::<PyDict>().expect("This should stay a dict")
+            };
 
-        let result_str = json_dumps(py, result.into_any())?;
+            let result_json = json_dumps(result, vm);
 
-        if let Ok(confirm) = env::var("YT_STORE_INFO_JSON") {
-            if confirm == "yes" {
-                let mut file = File::create("output.info.json")?;
-                write!(file, "{result_str}").unwrap();
+            Ok::<_, PyRef<PyBaseException>>(result_json)
+        }) {
+            Ok(ok) => Ok(ok),
+            Err(err) => {
+                self.interpreter.enter(|vm| {
+                    vm.print_exception(err);
+                });
+                Err(process_ie_result::Error::Python)
             }
         }
-
-        serde_json::from_str(&result_str).map_err(Into::into)
-    })
+    }
 }
 
-/// # Panics
-/// Only if python fails to return a valid URL.
-pub fn unsmuggle_url(smug_url: &Url) -> PyResult<Url> {
-    Python::with_gil(|py| {
-        let utils = get_yt_dlp_utils(py)?;
-        let url = utils
-            .call_method1("unsmuggle_url", (smug_url.as_str(),))?
-            .downcast::<PyTuple>()?
-            .get_item(0)?;
-
-        let url: Url = url
-            .downcast::<PyString>()?
-            .to_string()
-            .parse()
-            .expect("Python should be able to return a valid url");
-
-        Ok(url)
-    })
+#[allow(missing_docs)]
+pub mod process_ie_result {
+    #[derive(Debug, thiserror::Error, Clone, Copy)]
+    pub enum Error {
+        #[error("Python threw an exception")]
+        Python,
+    }
 }
-
-/// Download a given list of URLs.
-/// Returns the paths they were downloaded to.
-///
-/// # Panics
-/// Only if `yt_dlp` changes their `info_json` schema.
-pub async fn download(
-    urls: &[Url],
-    download_options: &Map<String, Value>,
-) -> Result<Vec<PathBuf>, YtDlpError> {
-    let mut out_paths = Vec::with_capacity(urls.len());
-
-    for url in urls {
-        info!("Started downloading url: '{}'", url);
-        let info_json = extract_info(download_options, url, true, true).await?;
-
-        // Try to work around yt-dlp type weirdness
-        let result_string = if let Some(filename) = info_json.filename {
-            filename
-        } else {
-            info_json.requested_downloads.expect("This must exist")[0]
-                .filename
-                .clone()
-        };
-
-        out_paths.push(result_string);
-        info!("Finished downloading url: '{}'", url);
+#[allow(missing_docs)]
+pub mod extract_info {
+    #[derive(Debug, thiserror::Error, Clone, Copy)]
+    pub enum Error {
+        #[error("Python threw an exception")]
+        Python,
     }
-
-    Ok(out_paths)
 }
 
-fn json_map_to_py_dict<'a>(
-    map: &Map<String, Value>,
-    py: Python<'a>,
-) -> PyResult<Bound<'a, PyDict>> {
-    let json_string = serde_json::to_string(&map).expect("This must always work");
+pub type InfoJson = serde_json::Map<String, serde_json::Value>;
+pub type ProgressHookFunction = fn(input: FuncArgs, vm: &VirtualMachine);
 
-    let python_dict = json_loads(py, json_string)?;
-
-    Ok(python_dict)
+/// Options, that are used to customize the download behaviour.
+///
+/// In the future, this might get a Builder api.
+///
+/// See `help(yt_dlp.YoutubeDL())` from python for a full list of available options.
+#[derive(Default, Debug)]
+pub struct YoutubeDLOptions {
+    options: serde_json::Map<String, serde_json::Value>,
+    progress_hook: Option<ProgressHookFunction>,
 }
 
-fn json_dumps(py: Python<'_>, input: Bound<'_, PyAny>) -> PyResult<String> {
-    //     json.dumps(yt_dlp.sanitize_info(input))
+impl YoutubeDLOptions {
+    #[must_use]
+    pub fn new() -> Self {
+        Self {
+            options: serde_json::Map::new(),
+            progress_hook: None,
+        }
+    }
 
-    let yt_dlp = get_yt_dlp(py, PyDict::new(py))?;
-    let sanitized_result = yt_dlp.call_method1("sanitize_info", (input,))?;
+    #[must_use]
+    pub fn set(self, key: impl Into<String>, value: impl Into<serde_json::Value>) -> Self {
+        let mut options = self.options;
+        options.insert(key.into(), value.into());
 
-    let json = PyModule::import(py, "json")?;
-    let dumps = json.getattr("dumps")?;
+        Self {
+            options,
+            progress_hook: self.progress_hook,
+        }
+    }
 
-    let output = dumps.call1((sanitized_result,))?;
+    #[must_use]
+    pub fn with_progress_hook(self, progress_hook: ProgressHookFunction) -> Self {
+        if let Some(_previous_hook) = self.progress_hook {
+            todo!()
+        } else {
+            Self {
+                options: self.options,
+                progress_hook: Some(progress_hook),
+            }
+        }
+    }
 
-    let output_str = output.extract::<String>()?;
+    /// # Errors
+    /// If the underlying [`YoutubeDL::from_options`] errors.
+    pub fn build(self) -> Result<YoutubeDL, build::Error> {
+        YoutubeDL::from_options(self)
+    }
 
-    Ok(output_str)
-}
+    #[must_use]
+    pub fn from_json_options(options: serde_json::Map<String, serde_json::Value>) -> Self {
+        Self {
+            options,
+            progress_hook: None,
+        }
+    }
 
-fn json_loads_str<T: Serialize>(py: Python<'_>, input: T) -> PyResult<Bound<'_, PyDict>> {
-    let string = serde_json::to_string(&input).expect("Correct json must be pased");
+    #[must_use]
+    pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
+        self.options.get(key)
+    }
 
-    json_loads(py, string)
+    fn into_py_dict(self, vm: &VirtualMachine) -> PyRef<PyDict> {
+        json_loads(self.options, vm)
+    }
 }
 
-fn json_loads(py: Python<'_>, input: String) -> PyResult<Bound<'_, PyDict>> {
-    //     json.loads(input)
-
-    let json = PyModule::import(py, "json")?;
-    let dumps = json.getattr("loads")?;
+#[allow(missing_docs)]
+pub mod build {
+    #[derive(Debug, thiserror::Error)]
+    pub enum Error {
+        #[error("Python threw an exception")]
+        Python,
 
-    let output = dumps.call1((input,))?;
-
-    Ok(output
-        .downcast::<PyDict>()
-        .expect("This should always be a PyDict")
-        .clone())
+        #[error("Io error: {0}")]
+        Io(#[from] std::io::Error),
+    }
 }
 
-fn get_yt_dlp_utils(py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
-    let yt_dlp = PyModule::import(py, "yt_dlp")?;
-    let utils = yt_dlp.getattr("utils")?;
-
-    Ok(utils)
+fn json_loads(
+    input: serde_json::Map<String, serde_json::Value>,
+    vm: &VirtualMachine,
+) -> PyRef<PyDict> {
+    let json = vm.import("json", 0).expect("Module exists");
+    let loads = json.get_attr("loads", vm).expect("Method exists");
+    let self_str = serde_json::to_string(&serde_json::Value::Object(input)).expect("Vaild json");
+    let dict = loads
+        .call((self_str,), vm)
+        .expect("Vaild json is always a valid dict");
+
+    dict.downcast().expect("Should always be a dict")
 }
-fn get_yt_dlp<'a>(py: Python<'a>, opts: Bound<'a, PyDict>) -> PyResult<Bound<'a, PyAny>> {
-    // Unconditionally set a logger
-    let opts = add_logger_and_sig_handler(opts, py)?;
-    let opts = add_hooks(opts, py)?;
 
-    let yt_dlp = PyModule::import(py, "yt_dlp")?;
-    let youtube_dl = yt_dlp.call_method1("YoutubeDL", (opts,))?;
-
-    Ok(youtube_dl)
+/// # Panics
+/// If expectation about python operations fail.
+pub fn json_dumps(
+    input: PyRef<PyDict>,
+    vm: &VirtualMachine,
+) -> serde_json::Map<String, serde_json::Value> {
+    let json = vm.import("json", 0).expect("Module exists");
+    let dumps = json.get_attr("dumps", vm).expect("Method exists");
+    let dict = dumps
+        .call((input,), vm)
+        .map_err(|err| vm.print_exception(err))
+        .expect("Might not always work, but for our dicts it works");
+
+    let string: PyRef<PyStr> = dict.downcast().expect("Should always be a string");
+
+    let real_string = string.to_str().expect("Should be valid utf8");
+
+    // {
+    //     let mut file = File::create("debug.dump.json").unwrap();
+    //     write!(file, "{}", real_string).unwrap();
+    // }
+
+    let value: serde_json::Value = serde_json::from_str(real_string).expect("Should be valid json");
+
+    match value {
+        serde_json::Value::Object(map) => map,
+        _ => unreachable!("These should not be json.dumps output"),
+    }
 }
diff --git a/crates/yt_dlp/src/logging.rs b/crates/yt_dlp/src/logging.rs
index e731502..5cb4c1d 100644
--- a/crates/yt_dlp/src/logging.rs
+++ b/crates/yt_dlp/src/logging.rs
@@ -10,34 +10,66 @@
 
 // This file is taken from: https://github.com/dylanbstorey/pyo3-pylogger/blob/d89e0d6820ebc4f067647e3b74af59dbc4941dd5/src/lib.rs
 // It is licensed under the Apache 2.0 License, copyright up to 2024 by Dylan Storey
-// It was modified by Benedikt Peetz 2024
-
-// The pyo3 `pyfunction` proc-macros call unsafe functions internally, which trigger this lint.
-#![allow(unsafe_op_in_unsafe_fn)]
-
-use std::ffi::CString;
+// It was modified by Benedikt Peetz 2024, 2025
 
 use log::{Level, MetadataBuilder, Record, logger};
-use pyo3::{
-    Bound, PyAny, PyResult, Python,
-    prelude::{PyAnyMethods, PyListMethods, PyModuleMethods},
-    pyfunction, wrap_pyfunction,
+use rustpython::vm::{
+    PyObjectRef, PyRef, PyResult, VirtualMachine,
+    builtins::{PyInt, PyList, PyStr},
+    convert::ToPyObject,
+    function::FuncArgs,
 };
 
 /// Consume a Python `logging.LogRecord` and emit a Rust `Log` instead.
-#[allow(clippy::needless_pass_by_value)]
-#[pyfunction]
-fn host_log(record: Bound<'_, PyAny>, rust_target: &str) -> PyResult<()> {
-    let level = record.getattr("levelno")?;
-    let message = record.getattr("getMessage")?.call0()?.to_string();
-    let pathname = record.getattr("pathname")?.to_string();
-    let lineno = record
-        .getattr("lineno")?
-        .to_string()
-        .parse::<u32>()
-        .expect("This should always be a u32");
-
-    let logger_name = record.getattr("name")?.to_string();
+fn host_log(mut input: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
+    let record = input.args.remove(0);
+    let rust_target = {
+        let base: PyRef<PyStr> = input.args.remove(0).downcast().expect("Should be a string");
+        base.as_str().to_owned()
+    };
+
+    let level = {
+        let level: PyRef<PyInt> = record
+            .get_attr("levelno", vm)?
+            .downcast()
+            .expect("Should always be an int");
+        level.as_u32_mask()
+    };
+    let message = {
+        let get_message = record.get_attr("getMessage", vm)?;
+        let message: PyRef<PyStr> = get_message
+            .call((), vm)?
+            .downcast()
+            .expect("Downcasting works");
+
+        message.as_str().to_owned()
+    };
+
+    let pathname = {
+        let pathname: PyRef<PyStr> = record
+            .get_attr("pathname", vm)?
+            .downcast()
+            .expect("Is a string");
+
+        pathname.as_str().to_owned()
+    };
+
+    let lineno = {
+        let lineno: PyRef<PyInt> = record
+            .get_attr("lineno", vm)?
+            .downcast()
+            .expect("Is a number");
+
+        lineno.as_u32_mask()
+    };
+
+    let logger_name = {
+        let name: PyRef<PyStr> = record
+            .get_attr("name", vm)?
+            .downcast()
+            .expect("Should be a string");
+        name.as_str().to_owned()
+    };
 
     let full_target: Option<String> = if logger_name.trim().is_empty() || logger_name == "root" {
         None
@@ -48,25 +80,25 @@ fn host_log(record: Bound<'_, PyAny>, rust_target: &str) -> PyResult<()> {
         Some(format!("{rust_target}::{logger_name}"))
     };
 
-    let target = full_target.as_deref().unwrap_or(rust_target);
+    let target = full_target.as_deref().unwrap_or(&rust_target);
 
     // error
-    let error_metadata = if level.ge(40u8)? {
+    let error_metadata = if level >= 40 {
         MetadataBuilder::new()
             .target(target)
             .level(Level::Error)
             .build()
-    } else if level.ge(30u8)? {
+    } else if level >= 30 {
         MetadataBuilder::new()
             .target(target)
             .level(Level::Warn)
             .build()
-    } else if level.ge(20u8)? {
+    } else if level >= 20 {
         MetadataBuilder::new()
             .target(target)
             .level(Level::Info)
             .build()
-    } else if level.ge(10u8)? {
+    } else if level >= 10 {
         MetadataBuilder::new()
             .target(target)
             .level(Level::Debug)
@@ -98,13 +130,24 @@ fn host_log(record: Bound<'_, PyAny>, rust_target: &str) -> PyResult<()> {
 /// # Panics
 /// Only if internal assertions fail.
 #[allow(clippy::module_name_repetitions)]
-pub fn setup_logging(py: Python<'_>, target: &str) -> PyResult<()> {
-    let logging = py.import("logging")?;
+pub(super) fn setup_logging(vm: &VirtualMachine, target: &str) -> PyResult<PyObjectRef> {
+    let logging = vm.import("logging", 0)?;
 
-    logging.setattr("host_log", wrap_pyfunction!(host_log, &logging)?)?;
+    let scope = vm.new_scope_with_builtins();
 
-    py.run(
-        CString::new(format!(
+    for (key, value) in logging.dict().expect("Should be a dict") {
+        let key: PyRef<PyStr> = key.downcast().expect("Is a string");
+
+        scope.globals.set_item(key.as_str(), value, vm)?;
+    }
+    scope
+        .globals
+        .set_item("host_log", vm.new_function("host_log", host_log).into(), vm)?;
+
+    let local_scope = scope.clone();
+    vm.run_code_string(
+        local_scope,
+        format!(
             r#"
 class HostHandler(Handler):
     def __init__(self, level=0):
@@ -119,15 +162,36 @@ def basicConfig(*pargs, **kwargs):
         kwargs["handlers"] = [HostHandler()]
     return oldBasicConfig(*pargs, **kwargs)
 "#
-        ))
-        .expect("This is hardcoded")
-        .as_c_str(),
-        Some(&logging.dict()),
-        None,
+        )
+        .as_str(),
+        "<embedded logging inintializing code>".to_owned(),
     )?;
 
-    let all = logging.index()?;
-    all.append("HostHandler")?;
-
-    Ok(())
+    let all: PyRef<PyList> = logging
+        .get_attr("__all__", vm)?
+        .downcast()
+        .expect("Is a list");
+    all.borrow_vec_mut().push(vm.new_pyobj("HostHandler"));
+
+    // {
+    //     let logging_dict = logging.dict().expect("Exists");
+    //
+    //     for (key, val) in scope.globals {
+    //         let key: PyRef<PyStr> = key.downcast().expect("Is a string");
+    //
+    //         if !logging_dict.contains_key(key.as_str(), vm) {
+    //             logging_dict.set_item(key.as_str(), val, vm)?;
+    //         }
+    //     }
+    //
+    //     for (key, val) in scope.locals {
+    //         let key: PyRef<PyStr> = key.downcast().expect("Is a string");
+    //
+    //         if !logging_dict.contains_key(key.as_str(), vm) {
+    //             logging_dict.set_item(key.as_str(), val, vm)?;
+    //         }
+    //     }
+    // }
+
+    Ok(scope.globals.to_pyobject(vm))
 }
diff --git a/crates/yt_dlp/src/progress_hook.rs b/crates/yt_dlp/src/progress_hook.rs
new file mode 100644
index 0000000..7a7628a
--- /dev/null
+++ b/crates/yt_dlp/src/progress_hook.rs
@@ -0,0 +1,41 @@
+#[macro_export]
+macro_rules! mk_python_function {
+    ($name:ident, $new_name:ident) => {
+        pub fn $new_name(
+            mut args: $crate::progress_hook::rustpython::vm::function::FuncArgs,
+            vm: &$crate::progress_hook::rustpython::vm::VirtualMachine,
+        ) {
+            use $crate::progress_hook::rustpython;
+
+            let input = {
+                let dict: rustpython::vm::PyRef<rustpython::vm::builtins::PyDict> = args
+                    .args
+                    .remove(0)
+                    .downcast()
+                    .expect("The progress hook is always called with these args");
+                let new_dict = rustpython::vm::builtins::PyDict::new_ref(&vm.ctx);
+                dict.into_iter()
+                    .filter_map(|(name, value)| {
+                        let real_name: rustpython::vm::PyRefExact<rustpython::vm::builtins::PyStr> =
+                            name.downcast_exact(vm).expect("Is a string");
+                        let name_str = real_name.to_str().expect("Is a string");
+                        if name_str.starts_with('_') {
+                            None
+                        } else {
+                            Some((name_str.to_owned(), value))
+                        }
+                    })
+                    .for_each(|(key, value)| {
+                        new_dict
+                            .set_item(&key, value, vm)
+                            .expect("This is a transpositions, should always be valid");
+                    });
+
+                $crate::json_dumps(new_dict, vm)
+            };
+            $name(input).expect("Shall not fail!");
+        }
+    };
+}
+
+pub use rustpython;
diff --git a/crates/yt_dlp/src/python_json_decode_failed.error_msg b/crates/yt_dlp/src/python_json_decode_failed.error_msg
deleted file mode 100644
index d10688e..0000000
--- a/crates/yt_dlp/src/python_json_decode_failed.error_msg
+++ /dev/null
@@ -1,5 +0,0 @@
-Failed to decode yt-dlp's response: {}
-
-This is probably a bug.
-Try running the command again with the `YT_STORE_INFO_JSON=yes` environment variable set
-and maybe debug it further via `yt check info-json output.info.json`.
diff --git a/crates/yt_dlp/src/python_json_decode_failed.error_msg.license b/crates/yt_dlp/src/python_json_decode_failed.error_msg.license
deleted file mode 100644
index 7813eb6..0000000
--- a/crates/yt_dlp/src/python_json_decode_failed.error_msg.license
+++ /dev/null
@@ -1,9 +0,0 @@
-yt - A fully featured command line YouTube client
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of Yt.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
diff --git a/crates/yt_dlp/src/tests.rs b/crates/yt_dlp/src/tests.rs
deleted file mode 100644
index 91b6626..0000000
--- a/crates/yt_dlp/src/tests.rs
+++ /dev/null
@@ -1,89 +0,0 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-use std::sync::LazyLock;
-
-use serde_json::{Value, json};
-use url::Url;
-
-static YT_OPTS: LazyLock<serde_json::Map<String, Value>> = LazyLock::new(|| {
-    match json!({
-        "playliststart": 1,
-        "playlistend": 10,
-        "noplaylist": false,
-        "extract_flat": false,
-    }) {
-        Value::Object(obj) => obj,
-        _ => unreachable!("This json is hardcoded"),
-    }
-});
-
-#[tokio::test]
-#[ignore = "This test hangs forever"]
-async fn test_extract_info_video() {
-    let info = crate::extract_info(
-        &YT_OPTS,
-        &Url::parse("https://www.youtube.com/watch?v=dbjPnXaacAU").expect("Is valid."),
-        false,
-        false,
-    )
-    .await
-    .map_err(|err| format!("Encountered error: '{err}'"))
-    .unwrap();
-
-    println!("{info:#?}");
-}
-
-#[tokio::test]
-#[ignore = "This test hangs forever"]
-async fn test_extract_info_url() {
-    let err = crate::extract_info(
-        &YT_OPTS,
-        &Url::parse("https://google.com").expect("Is valid."),
-        false,
-        false,
-    )
-    .await
-    .map_err(|err| format!("Encountered error: '{err}'"))
-    .unwrap();
-
-    println!("{err:#?}");
-}
-
-#[tokio::test]
-#[ignore = "This test hangs forever"]
-async fn test_extract_info_playlist() {
-    let err = crate::extract_info(
-        &YT_OPTS,
-        &Url::parse("https://www.youtube.com/@TheGarriFrischer/videos").expect("Is valid."),
-        false,
-        true,
-    )
-    .await
-    .map_err(|err| format!("Encountered error: '{err}'"))
-    .unwrap();
-
-    println!("{err:#?}");
-}
-#[tokio::test]
-#[ignore = "This test hangs forever"]
-async fn test_extract_info_playlist_full() {
-    let err = crate::extract_info(
-        &YT_OPTS,
-        &Url::parse("https://www.youtube.com/@NixOS-Foundation/videos").expect("Is valid."),
-        false,
-        true,
-    )
-    .await
-    .map_err(|err| format!("Encountered error: '{err}'"))
-    .unwrap();
-
-    println!("{err:#?}");
-}
diff --git a/crates/yt_dlp/src/wrapper/info_json.rs b/crates/yt_dlp/src/wrapper/info_json.rs
deleted file mode 100644
index a2c00df..0000000
--- a/crates/yt_dlp/src/wrapper/info_json.rs
+++ /dev/null
@@ -1,824 +0,0 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-// `yt_dlp` named them like this.
-#![allow(clippy::pub_underscore_fields)]
-
-use std::{collections::HashMap, path::PathBuf};
-
-use pyo3::{Bound, PyResult, Python, types::PyDict};
-use serde::{Deserialize, Deserializer, Serialize};
-use serde_json::Value;
-use url::Url;
-
-use crate::json_loads_str;
-
-type Todo = String;
-type Extractor = String;
-type ExtractorKey = String;
-
-// TODO: Change this to map `_type` to a structure of values, instead of the options <2024-05-27>
-// And replace all the strings with better types (enums or urls)
-#[derive(Debug, Deserialize, Serialize, PartialEq)]
-#[serde(deny_unknown_fields)]
-pub struct InfoJson {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub __files_to_move: Option<FilesToMove>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub __last_playlist_index: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub __post_extractor: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub __x_forwarded_for_ip: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub _filename: Option<PathBuf>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub _format_sort_fields: Option<Vec<String>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub _has_drm: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub _type: Option<InfoType>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub _version: Option<Version>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub abr: Option<f64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub acodec: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub age_limit: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub artists: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub aspect_ratio: Option<f64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub asr: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub audio_channels: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub audio_ext: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub automatic_captions: Option<HashMap<String, Vec<Caption>>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub availability: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub average_rating: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub categories: Option<Vec<String>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub channel: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub channel_follower_count: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub channel_id: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub channel_is_verified: Option<bool>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub channel_url: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub chapters: Option<Vec<Chapter>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub comment_count: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub comments: Option<Vec<Comment>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub concurrent_view_count: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub container: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub description: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub direct: Option<bool>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub display_id: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub downloader_options: Option<DownloaderOptions>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub duration: Option<f64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub duration_string: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub dynamic_range: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub entries: Option<Vec<InfoJson>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub episode: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub episode_number: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub epoch: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ext: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub extractor: Option<Extractor>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub extractor_key: Option<ExtractorKey>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub filename: Option<PathBuf>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub filesize: Option<u64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub filesize_approx: Option<u64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub format: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub format_id: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub format_index: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub format_note: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub formats: Option<Vec<Format>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub fps: Option<f64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub fulltitle: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub genre: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub genres: Option<Vec<String>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub has_drm: Option<bool>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub heatmap: Option<Vec<HeatMapEntry>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub height: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub hls_aes: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub http_headers: Option<HttpHeader>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub id: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub ie_key: Option<ExtractorKey>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub is_live: Option<bool>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub language: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub language_preference: Option<i32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub license: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub like_count: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub live_status: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub location: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub manifest_url: Option<Url>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub media_type: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub modified_date: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub n_entries: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub original_url: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playable_in_embed: Option<bool>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist_autonumber: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist_channel: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist_channel_id: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist_count: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist_id: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist_index: Option<u64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist_title: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist_uploader: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist_uploader_id: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub playlist_webpage_url: Option<Url>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub preference: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub protocol: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub quality: Option<f64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub release_date: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub release_timestamp: Option<u64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub release_year: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub repost_count: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub requested_downloads: Option<Vec<RequestedDownloads>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub requested_entries: Option<Vec<u32>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub requested_formats: Option<Vec<Format>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub requested_subtitles: Option<HashMap<String, Subtitle>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub resolution: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub season: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub season_number: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub series: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub source_preference: Option<i32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub sponsorblock_chapters: Option<Vec<SponsorblockChapter>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub stretched_ratio: Option<Todo>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub subtitles: Option<HashMap<String, Vec<Caption>>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub tags: Option<Vec<String>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub tbr: Option<f64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub thumbnail: Option<Url>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub thumbnails: Option<Vec<ThumbNail>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub timestamp: Option<f64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub title: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub upload_date: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub uploader: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub uploader_id: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub uploader_url: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub url: Option<Url>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub vbr: Option<f64>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub vcodec: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub video_ext: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub view_count: Option<u32>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub was_live: Option<bool>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub webpage_url: Option<Url>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub webpage_url_basename: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub webpage_url_domain: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub width: Option<u32>,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq)]
-#[serde(deny_unknown_fields)]
-#[allow(missing_copy_implementations)]
-pub struct FilesToMove {}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq)]
-#[serde(deny_unknown_fields)]
-pub struct RequestedDownloads {
-    pub __files_to_merge: Option<Vec<Todo>>,
-    pub __finaldir: PathBuf,
-    pub __infojson_filename: PathBuf,
-    pub __postprocessors: Vec<Todo>,
-    pub __real_download: bool,
-    pub __write_download_archive: bool,
-    pub _filename: PathBuf,
-    pub _type: InfoType,
-    pub _version: Version,
-    pub abr: f64,
-    pub acodec: String,
-    pub aspect_ratio: Option<f64>,
-    pub asr: Option<u32>,
-    pub audio_channels: Option<u32>,
-    pub audio_ext: Option<String>,
-    pub chapters: Option<Vec<SponsorblockChapter>>,
-    pub duration: Option<f64>,
-    pub dynamic_range: Option<String>,
-    pub ext: String,
-    pub filename: PathBuf,
-    pub filepath: PathBuf,
-    pub filesize_approx: Option<u64>,
-    pub format: String,
-    pub format_id: String,
-    pub format_note: Option<String>,
-    pub fps: Option<f64>,
-    pub has_drm: Option<bool>,
-    pub height: Option<u32>,
-    pub http_headers: Option<HttpHeader>,
-    pub infojson_filename: PathBuf,
-    pub language: Option<String>,
-    pub manifest_url: Option<Url>,
-    pub protocol: String,
-    pub quality: Option<i64>,
-    pub requested_formats: Option<Vec<Format>>,
-    pub resolution: String,
-    pub tbr: f64,
-    pub url: Option<Url>,
-    pub vbr: f64,
-    pub vcodec: String,
-    pub video_ext: Option<String>,
-    pub width: Option<u32>,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd)]
-#[serde(deny_unknown_fields)]
-pub struct Subtitle {
-    pub ext: SubtitleExt,
-    pub filepath: PathBuf,
-    pub filesize: Option<u64>,
-    pub fragment_base_url: Option<Url>,
-    pub fragments: Option<Vec<Fragment>>,
-    pub manifest_url: Option<Url>,
-    pub name: Option<String>,
-    pub protocol: Option<Todo>,
-    pub url: Url,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Eq, Clone, Copy)]
-pub enum SubtitleExt {
-    #[serde(alias = "vtt")]
-    Vtt,
-
-    #[serde(alias = "mp4")]
-    Mp4,
-
-    #[serde(alias = "json")]
-    Json,
-    #[serde(alias = "json3")]
-    Json3,
-
-    #[serde(alias = "ttml")]
-    Ttml,
-
-    #[serde(alias = "srv1")]
-    Srv1,
-    #[serde(alias = "srv2")]
-    Srv2,
-    #[serde(alias = "srv3")]
-    Srv3,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd)]
-#[serde(deny_unknown_fields)]
-pub struct Caption {
-    pub ext: SubtitleExt,
-    pub filepath: Option<PathBuf>,
-    pub filesize: Option<u64>,
-    pub fragments: Option<Vec<SubtitleFragment>>,
-    pub fragment_base_url: Option<Url>,
-    pub manifest_url: Option<Url>,
-    pub name: Option<String>,
-    pub protocol: Option<String>,
-    pub url: String,
-    pub video_id: Option<String>,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd)]
-#[serde(deny_unknown_fields)]
-pub struct SubtitleFragment {
-    path: PathBuf,
-    duration: Option<f64>,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd)]
-#[serde(deny_unknown_fields)]
-pub struct Chapter {
-    pub end_time: f64,
-    pub start_time: f64,
-    pub title: String,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq)]
-#[serde(deny_unknown_fields)]
-pub struct SponsorblockChapter {
-    /// This is an utterly useless field, and should thus be ignored
-    pub _categories: Option<Vec<Vec<Value>>>,
-
-    pub categories: Option<Vec<SponsorblockChapterCategory>>,
-    pub category: Option<SponsorblockChapterCategory>,
-    pub category_names: Option<Vec<String>>,
-    pub end_time: f64,
-    pub name: Option<String>,
-    pub r#type: Option<SponsorblockChapterType>,
-    pub start_time: f64,
-    pub title: String,
-}
-
-pub fn get_none<'de, D, T>(_: D) -> Result<Option<T>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    Ok(None)
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Eq, Clone, Copy)]
-#[serde(deny_unknown_fields)]
-pub enum SponsorblockChapterType {
-    #[serde(alias = "skip")]
-    Skip,
-
-    #[serde(alias = "chapter")]
-    Chapter,
-
-    #[serde(alias = "poi")]
-    Poi,
-}
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Eq, Clone, Copy)]
-#[serde(deny_unknown_fields)]
-pub enum SponsorblockChapterCategory {
-    #[serde(alias = "filler")]
-    Filler,
-
-    #[serde(alias = "interaction")]
-    Interaction,
-
-    #[serde(alias = "music_offtopic")]
-    MusicOfftopic,
-
-    #[serde(alias = "poi_highlight")]
-    PoiHighlight,
-
-    #[serde(alias = "preview")]
-    Preview,
-
-    #[serde(alias = "sponsor")]
-    Sponsor,
-
-    #[serde(alias = "selfpromo")]
-    SelfPromo,
-
-    #[serde(alias = "chapter")]
-    Chapter,
-
-    #[serde(alias = "intro")]
-    Intro,
-
-    #[serde(alias = "outro")]
-    Outro,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd)]
-#[serde(deny_unknown_fields)]
-#[allow(missing_copy_implementations)]
-pub struct HeatMapEntry {
-    pub start_time: f64,
-    pub end_time: f64,
-    pub value: f64,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Eq, Clone, Copy)]
-#[serde(deny_unknown_fields)]
-pub enum InfoType {
-    #[serde(alias = "playlist")]
-    #[serde(rename(serialize = "playlist"))]
-    Playlist,
-
-    #[serde(alias = "url")]
-    #[serde(rename(serialize = "url"))]
-    Url,
-
-    #[serde(alias = "video")]
-    #[serde(rename(serialize = "video"))]
-    Video,
-}
-
-#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, PartialOrd, Ord)]
-#[serde(deny_unknown_fields)]
-pub struct Version {
-    pub current_git_head: Option<String>,
-    pub release_git_head: String,
-    pub repository: String,
-    pub version: String,
-}
-
-#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
-#[serde(from = "String")]
-#[serde(deny_unknown_fields)]
-pub enum Parent {
-    Root,
-    Id(String),
-}
-
-impl Parent {
-    #[must_use]
-    pub fn id(&self) -> Option<&str> {
-        if let Self::Id(id) = self {
-            Some(id)
-        } else {
-            None
-        }
-    }
-}
-
-impl From<String> for Parent {
-    fn from(value: String) -> Self {
-        if value == "root" {
-            Self::Root
-        } else {
-            Self::Id(value)
-        }
-    }
-}
-
-#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
-#[serde(from = "String")]
-#[serde(deny_unknown_fields)]
-pub struct Id {
-    pub id: String,
-}
-impl From<String> for Id {
-    fn from(value: String) -> Self {
-        Self {
-            // Take the last element if the string is split with dots, otherwise take the full id
-            id: value.split('.').last().unwrap_or(&value).to_owned(),
-        }
-    }
-}
-
-#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
-#[serde(deny_unknown_fields)]
-#[allow(clippy::struct_excessive_bools)]
-pub struct Comment {
-    pub id: Id,
-    pub text: String,
-    #[serde(default = "zero")]
-    pub like_count: u32,
-    pub is_pinned: bool,
-    pub author_id: String,
-    #[serde(default = "unknown")]
-    pub author: String,
-    pub author_is_verified: bool,
-    pub author_thumbnail: Url,
-    pub parent: Parent,
-    #[serde(deserialize_with = "edited_from_time_text", alias = "_time_text")]
-    pub edited: bool,
-    // Can't also be deserialized, as it's already used in 'edited'
-    // _time_text: String,
-    pub timestamp: i64,
-    pub author_url: Option<Url>,
-    pub author_is_uploader: bool,
-    pub is_favorited: bool,
-}
-fn unknown() -> String {
-    "<Unknown>".to_string()
-}
-fn zero() -> u32 {
-    0
-}
-fn edited_from_time_text<'de, D>(d: D) -> Result<bool, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    let s = String::deserialize(d)?;
-    if s.contains(" (edited)") {
-        Ok(true)
-    } else {
-        Ok(false)
-    }
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
-#[serde(deny_unknown_fields)]
-pub struct ThumbNail {
-    pub id: Option<String>,
-    pub preference: Option<i32>,
-    /// in the form of "[`height`]x[`width`]"
-    pub resolution: Option<String>,
-    pub url: Url,
-    pub width: Option<u32>,
-    pub height: Option<u32>,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd)]
-#[serde(deny_unknown_fields)]
-pub struct Format {
-    pub __needs_testing: Option<bool>,
-    pub __working: Option<bool>,
-    pub abr: Option<f64>,
-    pub acodec: Option<String>,
-    pub aspect_ratio: Option<f64>,
-    pub asr: Option<f64>,
-    pub audio_channels: Option<u32>,
-    pub audio_ext: Option<String>,
-    pub columns: Option<u32>,
-    pub container: Option<String>,
-    pub downloader_options: Option<DownloaderOptions>,
-    pub dynamic_range: Option<String>,
-    pub ext: String,
-    pub filepath: Option<PathBuf>,
-    pub filesize: Option<u64>,
-    pub filesize_approx: Option<u64>,
-    pub format: Option<String>,
-    pub format_id: String,
-    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>,
-    pub http_headers: Option<HttpHeader>,
-    pub is_dash_periods: Option<bool>,
-    pub is_live: Option<bool>,
-    pub language: Option<String>,
-    pub language_preference: Option<i32>,
-    pub manifest_stream_number: Option<u32>,
-    pub manifest_url: Option<Url>,
-    pub preference: Option<i32>,
-    pub protocol: Option<String>,
-    pub quality: Option<f64>,
-    pub resolution: Option<String>,
-    pub rows: Option<u32>,
-    pub source_preference: Option<i32>,
-    pub tbr: Option<f64>,
-    pub url: Url,
-    pub vbr: Option<f64>,
-    pub vcodec: String,
-    pub video_ext: Option<String>,
-    pub width: Option<u32>,
-}
-
-#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, PartialOrd, Ord)]
-#[serde(deny_unknown_fields)]
-#[allow(missing_copy_implementations)]
-pub struct DownloaderOptions {
-    http_chunk_size: u64,
-}
-
-#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, PartialOrd, Ord)]
-#[serde(deny_unknown_fields)]
-pub struct HttpHeader {
-    #[serde(alias = "User-Agent")]
-    pub user_agent: Option<String>,
-
-    #[serde(alias = "Accept")]
-    pub accept: Option<String>,
-
-    #[serde(alias = "X-Forwarded-For")]
-    pub x_forwarded_for: Option<String>,
-
-    #[serde(alias = "Accept-Language")]
-    pub accept_language: Option<String>,
-
-    #[serde(alias = "Sec-Fetch-Mode")]
-    pub sec_fetch_mode: Option<String>,
-}
-
-#[derive(Debug, Deserialize, Serialize, PartialEq, PartialOrd)]
-#[serde(deny_unknown_fields)]
-pub struct Fragment {
-    pub duration: Option<f64>,
-    pub fragment_count: Option<usize>,
-    pub path: Option<PathBuf>,
-    pub url: Option<Url>,
-}
-
-impl InfoJson {
-    pub fn to_py_dict(self, py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
-        let output: Bound<'_, PyDict> = json_loads_str(py, self)?;
-        Ok(output)
-    }
-}
diff --git a/crates/yt_dlp/src/wrapper/mod.rs b/crates/yt_dlp/src/wrapper/mod.rs
deleted file mode 100644
index 3fe3247..0000000
--- a/crates/yt_dlp/src/wrapper/mod.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-pub mod info_json;
-// pub mod yt_dlp_options;
diff --git a/crates/yt_dlp/src/wrapper/yt_dlp_options.rs b/crates/yt_dlp/src/wrapper/yt_dlp_options.rs
deleted file mode 100644
index 25595b5..0000000
--- a/crates/yt_dlp/src/wrapper/yt_dlp_options.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-use pyo3::{Bound, PyResult, Python, types::PyDict};
-use serde::Serialize;
-
-use crate::json_loads;
-
-#[derive(Serialize, Clone)]
-pub struct YtDlpOptions {
-    pub playliststart: u32,
-    pub playlistend: u32,
-    pub noplaylist: bool,
-    pub extract_flat: ExtractFlat,
-    // pub extractor_args: ExtractorArgs,
-    // pub format: String,
-    // pub fragment_retries: u32,
-    // #[serde(rename(serialize = "getcomments"))]
-    // pub get_comments: bool,
-    // #[serde(rename(serialize = "ignoreerrors"))]
-    // pub ignore_errors: bool,
-    // pub retries: u32,
-    // #[serde(rename(serialize = "writeinfojson"))]
-    // pub write_info_json: bool,
-    // pub postprocessors: Vec<serde_json::Map<String, serde_json::Value>>,
-}
-
-#[derive(Serialize, Copy, Clone)]
-pub enum ExtractFlat {
-    #[serde(rename(serialize = "in_playlist"))]
-    InPlaylist,
-
-    #[serde(rename(serialize = "discard_in_playlist"))]
-    DiscardInPlaylist,
-}
-
-#[derive(Serialize, Clone)]
-pub struct ExtractorArgs {
-    pub youtube: YoutubeExtractorArgs,
-}
-
-#[derive(Serialize, Clone)]
-pub struct YoutubeExtractorArgs {
-    comment_sort: Vec<String>,
-    max_comments: Vec<String>,
-}
-
-impl YtDlpOptions {
-    pub fn to_py_dict(self, py: Python) -> PyResult<Bound<PyDict>> {
-        let string = serde_json::to_string(&self).expect("This should always work");
-
-        let output: Bound<PyDict> = json_loads(py, string)?;
-        Ok(output)
-    }
-}
diff --git a/flake.lock b/flake.lock
index d5590d5..ba25c93 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,61 +1,27 @@
 {
   "nodes": {
-    "flake-utils": {
-      "inputs": {
-        "systems": "systems"
-      },
-      "locked": {
-        "lastModified": 1731533236,
-        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
-        "type": "github"
-      },
-      "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "type": "github"
-      }
-    },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1739866667,
-        "narHash": "sha256-EO1ygNKZlsAC9avfcwHkKGMsmipUk1Uc0TbrEZpkn64=",
+        "lastModified": 1749809936,
+        "narHash": "sha256-WPGRaj7CKfZukjcpxiacp29uYfMl3S9zFiEsVFv/HWM=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "73cf49b8ad837ade2de76f87eb53fc85ed5d4680",
+        "rev": "ec4c48ddcd5718cc1312f432b800fbbfe63ee2fe",
         "type": "github"
       },
       "original": {
         "owner": "NixOS",
-        "ref": "nixos-unstable",
+        "ref": "nixos-unstable-small",
         "repo": "nixpkgs",
         "type": "github"
       }
     },
     "root": {
       "inputs": {
-        "flake-utils": "flake-utils",
         "nixpkgs": "nixpkgs",
         "treefmt-nix": "treefmt-nix"
       }
     },
-    "systems": {
-      "locked": {
-        "lastModified": 1681028828,
-        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
-        "owner": "nix-systems",
-        "repo": "default",
-        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-systems",
-        "repo": "default",
-        "type": "github"
-      }
-    },
     "treefmt-nix": {
       "inputs": {
         "nixpkgs": [
@@ -63,11 +29,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1739829690,
-        "narHash": "sha256-mL1szCeIsjh6Khn3nH2cYtwO5YXG6gBiTw1A30iGeDU=",
+        "lastModified": 1749194973,
+        "narHash": "sha256-eEy8cuS0mZ2j/r/FE0/LYBSBcIs/MKOIVakwHVuqTfk=",
         "owner": "numtide",
         "repo": "treefmt-nix",
-        "rev": "3d0579f5cc93436052d94b73925b48973a104204",
+        "rev": "a05be418a1af1198ca0f63facb13c985db4cb3c5",
         "type": "github"
       },
       "original": {
diff --git a/flake.nix b/flake.nix
index 18a5f62..1a6b43b 100644
--- a/flake.nix
+++ b/flake.nix
@@ -11,9 +11,8 @@
   description = "yt";
 
   inputs = {
-    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small";
 
-    flake-utils.url = "github:numtide/flake-utils";
     treefmt-nix = {
       url = "github:numtide/treefmt-nix";
       inputs = {
@@ -25,21 +24,20 @@
   outputs = {
     self,
     nixpkgs,
-    flake-utils,
     treefmt-nix,
-  }: (flake-utils.lib.eachDefaultSystem (system: let
+  }: let
+    system = "x86_64-linux";
     pkgs = nixpkgs.legacyPackages."${system}";
 
-    python = pkgs.python3.withPackages (ps: [
-      ps.yt-dlp
-    ]);
-
     buildInputs = with pkgs; [
       mpv-unwrapped.dev
+      libffi
+      openssl
     ];
 
     nativeBuildInputs = with pkgs; [
       llvmPackages_latest.clang-unwrapped.lib
+      pkg-config
 
       # Needed for the tests in `libmpv2`
       SDL2
@@ -50,25 +48,27 @@
 
     treefmtEval = import ./treefmt.nix {inherit treefmt-nix pkgs;};
   in {
-    packages = {
+    packages."${system}" = {
       inherit yt tree-sitter-yts;
       default = self.packages.${system}.yt;
     };
 
-    checks = {
+    checks."${system}" = {
       inherit yt;
       formatting = treefmtEval.config.build.check self;
     };
 
-    formatter = treefmtEval.config.build.wrapper;
+    formatter."${system}" = treefmtEval.config.build.wrapper;
 
-    devShells.default = pkgs.mkShell {
+    devShells."${system}".default = pkgs.mkShell {
       env = let
         clang_version =
           pkgs.lib.versions.major
           pkgs.llvmPackages_latest.clang-unwrapped.version;
       in {
         FFMPEG_LOCATION = "${pkgs.lib.getExe pkgs.ffmpeg}";
+
+        # These are needed for `libmpv` to compile.
         LIBCLANG_PATH = "${pkgs.llvmPackages_latest.clang-unwrapped.lib}/lib/libclang.so";
         LIBCLANG_INCLUDE_PATH = "${pkgs.llvmPackages_latest.clang-unwrapped.lib}/lib/clang/${clang_version}/include";
         C_INCLUDE_PATH = "${pkgs.glibc.dev}/include";
@@ -89,6 +89,7 @@
         pkgs.cargo-flamegraph
 
         # Releng
+        pkgs.git-bug
         pkgs.reuse
         pkgs.cocogitto
 
@@ -100,7 +101,9 @@
         pkgs.sqlite-interactive
 
         # yt_dlp
-        python
+        pkgs.yt-dlp
+        pkgs.python3Packages.yt-dlp
+        pkgs.python3
         pkgs.jq
         pkgs.ffmpeg
 
@@ -109,5 +112,5 @@
         pkgs.tree-sitter
       ];
     };
-  }));
+  };
 }
diff --git a/scripts/mkdb.sh b/scripts/mkdb.sh
index 6bcebaf..f0c7740 100755
--- a/scripts/mkdb.sh
+++ b/scripts/mkdb.sh
@@ -16,9 +16,14 @@ db="$root/target/database.sqlx"
 [ -f "$db" ] && rm "$db"
 [ -d "$root/target" ] || mkdir "$root/target"
 
-fd . "$root/yt/src/storage/migrate/sql" | while read -r sql_file; do
+fd . "$root/crates/yt/src/storage/migrate/sql" | while read -r sql_file; do
     echo "Applying sql migration file: $(basename "$sql_file").."
-    sqlite3 "$db" <"$sql_file"
+    {
+        # NOTE(@bpeetz): The wrapping in a transaction is needed to simulate the rust code. <2025-05-07>
+        echo "BEGIN TRANSACTION;"
+        cat "$sql_file"
+        echo "COMMIT TRANSACTION;"
+    } | sqlite3 "$db"
 done
 
 # vim: ft=sh
diff --git a/yt/src/comments/comment.rs b/yt/src/comments/comment.rs
deleted file mode 100644
index 6b8cf73..0000000
--- a/yt/src/comments/comment.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-use yt_dlp::wrapper::info_json::Comment;
-
-#[derive(Debug, Clone)]
-#[allow(clippy::module_name_repetitions)]
-pub struct CommentExt {
-    pub value: Comment,
-    pub replies: Vec<CommentExt>,
-}
-
-#[derive(Debug, Default)]
-pub struct Comments {
-    pub(super) vec: Vec<CommentExt>,
-}
-
-impl Comments {
-    pub fn new() -> Self {
-        Self::default()
-    }
-    pub fn push(&mut self, value: CommentExt) {
-        self.vec.push(value);
-    }
-    pub fn get_mut(&mut self, key: &str) -> Option<&mut CommentExt> {
-        self.vec.iter_mut().filter(|c| c.value.id.id == key).last()
-    }
-    pub fn insert(&mut self, key: &str, value: CommentExt) {
-        let parent = self
-            .vec
-            .iter_mut()
-            .filter(|c| c.value.id.id == key)
-            .last()
-            .expect("One of these should exist");
-        parent.push_reply(value);
-    }
-}
-impl CommentExt {
-    pub fn push_reply(&mut self, value: CommentExt) {
-        self.replies.push(value);
-    }
-    pub fn get_mut_reply(&mut self, key: &str) -> Option<&mut CommentExt> {
-        self.replies
-            .iter_mut()
-            .filter(|c| c.value.id.id == key)
-            .last()
-    }
-}
-
-impl From<Comment> for CommentExt {
-    fn from(value: Comment) -> Self {
-        Self {
-            replies: vec![],
-            value,
-        }
-    }
-}
diff --git a/yt/src/download/download_options.rs b/yt/src/download/download_options.rs
deleted file mode 100644
index 8f5a609..0000000
--- a/yt/src/download/download_options.rs
+++ /dev/null
@@ -1,113 +0,0 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-use serde_json::{Value, json};
-
-use crate::{app::App, storage::video_database::YtDlpOptions};
-
-#[must_use]
-pub fn download_opts(app: &App, additional_opts: &YtDlpOptions) -> serde_json::Map<String, Value> {
-    match json!({
-      "extract_flat": "in_playlist",
-      "extractor_args": {
-        "youtube": {
-          "comment_sort": [
-            "top"
-          ],
-          "max_comments": [
-            "150",
-            "all",
-            "100"
-          ]
-        }
-      },
-
-      "prefer_free_formats": true,
-      "ffmpeg_location": env!("FFMPEG_LOCATION"),
-      "format": "bestvideo[height<=?1080]+bestaudio/best",
-      "fragment_retries": 10,
-      "getcomments": true,
-      "ignoreerrors": false,
-      "retries": 10,
-
-      "writeinfojson": true,
-      // NOTE: This results in a constant warning message.  <2025-01-04>
-      // "writeannotations": true,
-      "writesubtitles": true,
-      "writeautomaticsub": true,
-
-      "outtmpl": {
-        "default": app.config.paths.download_dir.join("%(channel)s/%(title)s.%(ext)s"),
-        "chapter": "%(title)s - %(section_number)03d %(section_title)s [%(id)s].%(ext)s"
-      },
-      "compat_opts": {},
-      "forceprint": {},
-      "print_to_file": {},
-      "windowsfilenames": false,
-      "restrictfilenames": false,
-      "trim_file_names": false,
-      "postprocessors": [
-        {
-          "api": "https://sponsor.ajay.app",
-          "categories": [
-            "interaction",
-            "intro",
-            "music_offtopic",
-            "sponsor",
-            "outro",
-            "poi_highlight",
-            "preview",
-            "selfpromo",
-            "filler",
-            "chapter"
-          ],
-          "key": "SponsorBlock",
-          "when": "after_filter"
-        },
-        {
-          "force_keyframes": false,
-          "key": "ModifyChapters",
-          "remove_chapters_patterns": [],
-          "remove_ranges": [],
-          "remove_sponsor_segments": [
-            "sponsor"
-          ],
-          "sponsorblock_chapter_title": "[SponsorBlock]: %(category_names)l"
-        },
-        {
-          "add_chapters": true,
-          "add_infojson": null,
-          "add_metadata": false,
-          "key": "FFmpegMetadata"
-        },
-        {
-          "key": "FFmpegConcat",
-          "only_multi_video": true,
-          "when": "playlist"
-        }
-      ]
-    }) {
-        Value::Object(mut obj) => {
-            obj.insert(
-                "subtitleslangs".to_owned(),
-                Value::Array(
-                    additional_opts
-                        .subtitle_langs
-                        .split(',')
-                        .map(|val| Value::String(val.to_owned()))
-                        .collect::<Vec<_>>(),
-                ),
-            );
-            obj
-        }
-        _ => unreachable!("This is an object"),
-    }
-}
diff --git a/yt/src/update/updater.rs b/yt/src/update/updater.rs
deleted file mode 100644
index 2b13378..0000000
--- a/yt/src/update/updater.rs
+++ /dev/null
@@ -1,171 +0,0 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-use std::io::{Write, stderr};
-
-use anyhow::{Context, Result};
-use blake3::Hash;
-use futures::{
-    StreamExt, TryStreamExt,
-    stream::{self},
-};
-use log::{Level, debug, error, log_enabled};
-use serde_json::json;
-use yt_dlp::{error::YtDlpError, process_ie_result, wrapper::info_json::InfoJson};
-
-use crate::{app::App, storage::subscriptions::Subscription};
-
-use super::process_subscription;
-
-pub(super) struct Updater<'a> {
-    max_backlog: usize,
-    hashes: &'a [Hash],
-}
-
-impl<'a> Updater<'a> {
-    pub(super) fn new(max_backlog: usize, hashes: &'a [Hash]) -> Self {
-        Self {
-            max_backlog,
-            hashes,
-        }
-    }
-
-    pub(super) async fn update(
-        &mut self,
-        app: &App,
-        subscriptions: &[&Subscription],
-    ) -> Result<()> {
-        let mut stream = stream::iter(subscriptions)
-            .map(|sub| self.get_new_entries(sub))
-            .buffer_unordered(100);
-
-        while let Some(output) = stream.next().await {
-            let mut entries = output?;
-
-            if entries.is_empty() {
-                continue;
-            }
-
-            let (sub, entry) = entries.remove(0);
-            process_subscription(app, sub, entry).await?;
-
-            let entry_stream: Result<()> = stream::iter(entries)
-                .map(|(sub, entry)| process_subscription(app, sub, entry))
-                .buffer_unordered(100)
-                .try_collect()
-                .await;
-            entry_stream?;
-        }
-
-        Ok(())
-    }
-
-    async fn get_new_entries(
-        &self,
-        sub: &'a Subscription,
-    ) -> Result<Vec<(&'a Subscription, InfoJson)>> {
-        // ANSI ESCAPE CODES Wrappers {{{
-        // see: https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands
-        const CSI: &str = "\x1b[";
-        fn clear_whole_line() {
-            eprint!("{CSI}2K");
-        }
-        fn move_to_col(x: usize) {
-            eprint!("{CSI}{x}G");
-        }
-        // fn hide_cursor() {
-        //     eprint!("{CSI}?25l");
-        // }
-        // fn show_cursor() {
-        //     eprint!("{CSI}?25h");
-        // }
-        // }}}
-
-        let json = json! {
-            {
-                "playliststart": 1,
-                "playlistend": self.max_backlog,
-                "noplaylist": false,
-                "extractor_args": {"youtubetab": {"approximate_date": [""]}},
-            }
-        };
-        let yt_dlp_opts = json.as_object().expect("This is hardcoded");
-
-        if !log_enabled!(Level::Debug) {
-            clear_whole_line();
-            move_to_col(1);
-            eprint!("Checking playlist {}...", sub.name);
-            move_to_col(1);
-            stderr().flush()?;
-        }
-
-        let info = yt_dlp::extract_info(yt_dlp_opts, &sub.url, false, false)
-            .await
-            .with_context(|| format!("Failed to get playlist '{}'.", sub.name))?;
-
-        let entries = info.entries.unwrap_or(vec![]);
-        let valid_entries: Vec<(&Subscription, InfoJson)> = entries
-            .into_iter()
-            .take(self.max_backlog)
-            .filter_map(|entry| -> Option<(&Subscription, InfoJson)> {
-                let id = entry.id.as_ref().expect("Should exist?");
-                let extractor_hash = blake3::hash(id.as_bytes());
-                if self.hashes.contains(&extractor_hash) {
-                    debug!(
-                        "Skipping entry, as it is already present: '{}'",
-                        extractor_hash
-                    );
-                    None
-                } else {
-                    Some((sub, entry))
-                }
-            })
-            .collect();
-
-        let processed_entries = {
-            let base: Result<Vec<(&Subscription, InfoJson)>, YtDlpError> =
-                stream::iter(valid_entries)
-                    .map(|(sub, entry)| async move {
-                        match process_ie_result(yt_dlp_opts, entry, false).await {
-                            Ok(output) => Ok((sub, output)),
-                            Err(err) => Err(err),
-                        }
-                    })
-                    .buffer_unordered(100)
-                    .try_collect()
-                    .await;
-            match base {
-                Ok(ok) => ok,
-                Err(err) => {
-                    if let YtDlpError::PythonError { error, kind } = &err {
-                        if kind.as_str() == "<class 'yt_dlp.utils.DownloadError'>"
-                            && error.to_string().as_str().contains(
-                                "Join this channel to get access to members-only content ",
-                            )
-                        {
-                            vec![]
-                        } else {
-                            let error_string = error.to_string();
-                            let error = error_string
-                                .strip_prefix("DownloadError: \u{1b}[0;31mERROR:\u{1b}[0m ")
-                                .expect("This prefix should exists");
-                            error!("{error}");
-                            vec![]
-                        }
-                    } else {
-                        Err(err).context("Failed to process new entries.")?
-                    }
-                }
-            }
-        };
-
-        Ok(processed_entries)
-    }
-}