about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.envrc5
-rw-r--r--Cargo.lock609
-rw-r--r--Cargo.toml2
-rw-r--r--NEWS.md82
-rw-r--r--cog.toml4
-rw-r--r--crates/libmpv2/CHANGELOG.md8
-rw-r--r--crates/libmpv2/libmpv2-sys/build.rs4
-rw-r--r--crates/yt/Cargo.toml4
-rw-r--r--crates/yt/src/ansi_escape_codes.rs10
-rw-r--r--crates/yt/src/cli.rs78
-rw-r--r--crates/yt/src/download/mod.rs9
-rw-r--r--crates/yt/src/download/progress_hook.rs10
-rw-r--r--crates/yt/src/main.rs28
-rw-r--r--crates/yt/src/select/cmds/mod.rs4
-rw-r--r--crates/yt/src/select/mod.rs31
-rw-r--r--crates/yt/src/select/selection_file/mod.rs24
-rw-r--r--crates/yt/src/status/mod.rs3
-rw-r--r--crates/yt/src/update/updater.rs36
-rw-r--r--crates/yt/src/videos/mod.rs21
-rw-r--r--crates/yt_dlp/Cargo.toml10
-rw-r--r--crates/yt_dlp/README.md2
-rw-r--r--crates/yt_dlp/src/lib.rs229
-rw-r--r--crates/yt_dlp/src/post_processors/dearrow.rs118
-rw-r--r--crates/yt_dlp/src/post_processors/mod.rs30
-rw-r--r--crates/yt_dlp/src/progress_hook.rs10
-rw-r--r--flake.lock6
-rw-r--r--package/package.nix141
-rwxr-xr-xscripts/mkdb.sh2
28 files changed, 1212 insertions, 308 deletions
diff --git a/.envrc b/.envrc
index 5537ab5..bac1203 100644
--- a/.envrc
+++ b/.envrc
@@ -20,5 +20,6 @@ PATH_add ./target/release
 PATH_add ./target/profiling
 
 export DATABASE_URL="sqlite://$root/target/database.sqlx"
-export PYO3_PYTHON=python3
-export YT_STORE_INFO_JSON=yes
+
+# Plugins are not supported.
+export YTDLP_NO_PLUGINS=1
diff --git a/Cargo.lock b/Cargo.lock
index 4a17021..2e7b2cc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -159,6 +159,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
 name = "autocfg"
 version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -288,7 +294,7 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.5.0"
+version = "1.6.0"
 dependencies = [
  "serde",
 ]
@@ -424,6 +430,18 @@ dependencies = [
 ]
 
 [[package]]
+name = "clap_complete"
+version = "4.5.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aad5b1b4de04fead402672b48897030eec1f3bfe1550776322f59f6d6e6a5677"
+dependencies = [
+ "clap",
+ "clap_lex",
+ "is_executable",
+ "shlex",
+]
+
+[[package]]
 name = "clap_derive"
 version = "4.5.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -716,6 +734,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
 name = "endian-type"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -852,6 +879,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
 name = "foldhash"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1046,6 +1079,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
 
 [[package]]
+name = "h2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5"
+dependencies = [
+ "atomic-waker",
+ "bytes 1.10.1",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
 name = "half"
 version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1127,6 +1179,124 @@ dependencies = [
 ]
 
 [[package]]
+name = "http"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+dependencies = [
+ "bytes 1.10.1",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes 1.10.1",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes 1.10.1",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
+dependencies = [
+ "bytes 1.10.1",
+ "futures-channel",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes 1.10.1",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
+dependencies = [
+ "base64",
+ "bytes 1.10.1",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "system-configuration",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "windows-registry",
+]
+
+[[package]]
 name = "iana-time-zone"
 version = "0.1.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1288,6 +1458,22 @@ dependencies = [
 ]
 
 [[package]]
+name = "ipnet"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+
+[[package]]
+name = "iri-string"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
 name = "is-macro"
 version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1311,6 +1497,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "is_executable"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
 name = "is_terminal_polyfill"
 version = "1.70.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1507,7 +1702,7 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
 
 [[package]]
 name = "libmpv2"
-version = "1.5.0"
+version = "1.6.0"
 dependencies = [
  "crossbeam",
  "libmpv2-sys",
@@ -1517,7 +1712,7 @@ dependencies = [
 
 [[package]]
 name = "libmpv2-sys"
-version = "1.5.0"
+version = "1.6.0"
 dependencies = [
  "bindgen",
 ]
@@ -1583,9 +1778,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
 
 [[package]]
 name = "lz4_flex"
-version = "0.11.3"
+version = "0.11.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5"
+checksum = "2c592ad9fbc1b7838633b3ae55ce69b17d01150c72fcef229fbb819d39ee51ee"
 dependencies = [
  "twox-hash",
 ]
@@ -1706,6 +1901,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
 name = "minimal-lexical"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1742,6 +1943,23 @@ dependencies = [
 ]
 
 [[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
 name = "nibble_vec"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1798,16 +2016,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
 
 [[package]]
-name = "nucleo-matcher"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85"
-dependencies = [
- "memchr",
- "unicode-segmentation",
-]
-
-[[package]]
 name = "num-bigint-dig"
 version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2031,51 +2239,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
-name = "pest"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
-dependencies = [
- "memchr",
- "thiserror 2.0.12",
- "ucd-trie",
-]
-
-[[package]]
-name = "pest_derive"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
-dependencies = [
- "pest",
- "pest_generator",
-]
-
-[[package]]
-name = "pest_generator"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
-dependencies = [
- "pest",
- "pest_meta",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "pest_meta"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
-dependencies = [
- "once_cell",
- "pest",
- "sha2",
-]
-
-[[package]]
 name = "phf"
 version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2347,6 +2510,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 
 [[package]]
+name = "reqwest"
+version = "0.12.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813"
+dependencies = [
+ "base64",
+ "bytes 1.10.1",
+ "encoding_rs",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-tls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-native-tls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
 name = "result-like"
 version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2368,6 +2573,20 @@ dependencies = [
 ]
 
 [[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.16",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
 name = "rsa"
 version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2474,9 +2693,42 @@ dependencies = [
 ]
 
 [[package]]
+name = "rustls"
+version = "0.23.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
+dependencies = [
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
 name = "rustpython"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "cfg-if",
  "dirs-next",
@@ -2495,7 +2747,7 @@ dependencies = [
 [[package]]
 name = "rustpython-codegen"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "ahash",
  "bitflags 2.9.1",
@@ -2520,7 +2772,7 @@ dependencies = [
 [[package]]
 name = "rustpython-common"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "ascii",
  "bitflags 2.9.1",
@@ -2549,7 +2801,7 @@ dependencies = [
 [[package]]
 name = "rustpython-compiler"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "ruff_python_ast",
  "ruff_python_parser",
@@ -2564,7 +2816,7 @@ dependencies = [
 [[package]]
 name = "rustpython-compiler-core"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "bitflags 2.9.1",
  "itertools 0.14.0",
@@ -2578,7 +2830,7 @@ dependencies = [
 [[package]]
 name = "rustpython-compiler-source"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "ruff_source_file",
  "ruff_text_size",
@@ -2587,7 +2839,7 @@ dependencies = [
 [[package]]
 name = "rustpython-derive"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "proc-macro2",
  "rustpython-compiler",
@@ -2598,7 +2850,7 @@ dependencies = [
 [[package]]
 name = "rustpython-derive-impl"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "itertools 0.14.0",
  "maplit",
@@ -2622,7 +2874,7 @@ dependencies = [
 [[package]]
 name = "rustpython-literal"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "hexf-parse",
  "is-macro",
@@ -2635,7 +2887,7 @@ dependencies = [
 [[package]]
 name = "rustpython-pylib"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "glob",
 ]
@@ -2643,7 +2895,7 @@ dependencies = [
 [[package]]
 name = "rustpython-sre_engine"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "bitflags 2.9.1",
  "num_enum",
@@ -2654,7 +2906,7 @@ dependencies = [
 [[package]]
 name = "rustpython-stdlib"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "adler32",
  "ahash",
@@ -2728,7 +2980,7 @@ dependencies = [
 [[package]]
 name = "rustpython-vm"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "ahash",
  "ascii",
@@ -2804,7 +3056,7 @@ dependencies = [
 [[package]]
 name = "rustpython-wtf8"
 version = "0.4.0"
-source = "git+https://github.com/RustPython/RustPython.git#4e094eaa55d95e55b22cb0f579f9551d21d379cd"
+source = "git+https://github.com/RustPython/RustPython.git#c968fe0fd9d7466dc9d5d97e973b82ba35e516d8"
 dependencies = [
  "ascii",
  "bstr",
@@ -2903,6 +3155,29 @@ dependencies = [
 ]
 
 [[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.9.1",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
 name = "serde"
 version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3031,12 +3306,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
 
 [[package]]
 name = "slab"
-version = "0.4.9"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
-dependencies = [
- "autocfg",
-]
+checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
 
 [[package]]
 name = "smallvec"
@@ -3354,6 +3626,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
 name = "synstructure"
 version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3418,7 +3699,7 @@ dependencies = [
 
 [[package]]
 name = "termsize"
-version = "1.5.0"
+version = "1.6.0"
 dependencies = [
  "libc",
  "winapi",
@@ -3539,6 +3820,26 @@ dependencies = [
 ]
 
 [[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
 name = "tokio-stream"
 version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3550,6 +3851,21 @@ dependencies = [
 ]
 
 [[package]]
+name = "tokio-util"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
+dependencies = [
+ "bytes 1.10.1",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "hashbrown",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
 name = "toml"
 version = "0.8.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3591,6 +3907,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
 
 [[package]]
+name = "tower"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
+dependencies = [
+ "bitflags 2.9.1",
+ "bytes 1.10.1",
+ "futures-util",
+ "http",
+ "http-body",
+ "iri-string",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
 name = "tracing"
 version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3623,24 +3984,16 @@ dependencies = [
 ]
 
 [[package]]
-name = "trinitry"
-version = "0.2.2"
+name = "try-lock"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f814008587cd653ef1f92f9caf321e86a6f53899ec118fd50eaed55974863a40"
-dependencies = [
- "pest",
- "pest_derive",
-]
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
 [[package]]
 name = "twox-hash"
-version = "1.6.3"
+version = "2.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
-dependencies = [
- "cfg-if",
- "static_assertions",
-]
+checksum = "8b907da542cbced5261bd3256de1b3a1bf340a3d37f93425a07362a1d687de56"
 
 [[package]]
 name = "typenum"
@@ -3655,12 +4008,6 @@ 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"
@@ -3842,6 +4189,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
 name = "url"
 version = "2.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3867,7 +4220,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
 [[package]]
 name = "uu_fmt"
-version = "1.5.0"
+version = "1.6.0"
 dependencies = [
  "unicode-width",
 ]
@@ -3912,6 +4265,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
 name = "wasi"
 version = "0.11.1+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3959,6 +4321,19 @@ dependencies = [
 ]
 
 [[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
 name = "wasm-bindgen-macro"
 version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3991,6 +4366,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
 name = "which"
 version = "7.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4120,6 +4505,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
 
 [[package]]
+name = "windows-registry"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820"
+dependencies = [
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
 name = "windows-result"
 version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4436,30 +4832,31 @@ dependencies = [
 
 [[package]]
 name = "yt"
-version = "1.5.0"
+version = "1.6.0"
 dependencies = [
  "anyhow",
  "blake3",
- "bytes 1.5.0",
+ "bytes 1.6.0",
  "chrono",
  "chrono-humanize",
  "clap",
+ "clap_complete",
  "futures",
  "libmpv2",
  "log",
  "notify",
- "nucleo-matcher",
  "owo-colors",
  "regex",
  "serde",
  "serde_json",
+ "shlex",
  "sqlx",
  "stderrlog",
  "tempfile",
  "termsize",
  "tokio",
+ "tokio-util",
  "toml",
- "trinitry",
  "url",
  "uu_fmt",
  "xdg",
@@ -4468,11 +4865,13 @@ dependencies = [
 
 [[package]]
 name = "yt_dlp"
-version = "1.5.0"
+version = "1.6.0"
 dependencies = [
  "indexmap",
  "log",
+ "reqwest",
  "rustpython",
+ "serde",
  "serde_json",
  "thiserror 2.0.12",
  "url",
diff --git a/Cargo.toml b/Cargo.toml
index 470eb58..fa282b2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,7 +21,7 @@ members = [
 
 [workspace.package]
 edition = "2024"
-version = "1.5.0"
+version = "1.6.0"
 rust-version = "1.85.0"
 authors = ["Benedikt Peetz <benedikt.peetz@b-peetz.de>"]
 repository = "https://git.vhack.eu/soispha/clients/yt"
diff --git a/NEWS.md b/NEWS.md
index 4e57d7b..b1bc980 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -14,6 +14,88 @@ 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.6.0](https://git.foss-syndicate.org/soispha/clients/yt/compare/07db485f9c5206fbcfe2a5f9db28a9587edc6d2b..v1.6.0) - 2025-06-16
+#### Bug Fixes
+- **(libmpv2-sys)** Avoid generating comments, that confuse rustdoc - ([0c0e00d](https://git.foss-syndicate.org/soispha/clients/yt/commit/0c0e00da2c21c4b8325fa6145c808e9df0df0834)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(scripts/mkdb.sh)** Also use the `$DATABASE_URL` variable as source source - ([45e5500](https://git.foss-syndicate.org/soispha/clients/yt/commit/45e55007aa13b1ec24af4c543bc3b8699710301c)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/cli)** Remove duplicated short flag key (help also uses 'h') - ([c1122d6](https://git.foss-syndicate.org/soispha/clients/yt/commit/c1122d6ab31548aff9bf8aaa4a855a771355c8e9)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/download/get_file_size)** Correct deal with `filesize_approx` = Null - ([680f811](https://git.foss-syndicate.org/soispha/clients/yt/commit/680f811adc83554cfbaff56d8b50501786a949e2)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/downloader/progress_hook)** Silence clippy warnings - ([65ba5d7](https://git.foss-syndicate.org/soispha/clients/yt/commit/65ba5d738dcfeaecb398e246e0db5d7c4bf04b99)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/select/selection_file/duration)** Improve the duration parser - ([9e1c1ae](https://git.foss-syndicate.org/soispha/clients/yt/commit/9e1c1aec0548a6482e23ceac4e1265ef8baf8023)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/storage/migrate)** Correct the two to three migration script - ([7694496](https://git.foss-syndicate.org/soispha/clients/yt/commit/7694496efa621466e327b9c00fe1c5cc092ccc1f)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/storage/migrate)** Correctly state the upgrade to the topmost version - ([449c4c2](https://git.foss-syndicate.org/soispha/clients/yt/commit/449c4c26c91400e56e0e685958b825b3f02f4e40)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/storage/migrate)** Improve error messages - ([3a16edd](https://git.foss-syndicate.org/soispha/clients/yt/commit/3a16edde524f881c8955026350243a1b4d54d89b)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/storage/migrate)** Account for the fact that DbVersions::Empty means no Version - ([d1f004c](https://git.foss-syndicate.org/soispha/clients/yt/commit/d1f004ce48caf90ab4f3ec1d0bbb588c9cbf0fe9)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/storage/video_database/set)** Reset the `is_focused` flag - ([07db485](https://git.foss-syndicate.org/soispha/clients/yt/commit/07db485f9c5206fbcfe2a5f9db28a9587edc6d2b)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/subscribe)** Deal with moved url value - ([fb00ecf](https://git.foss-syndicate.org/soispha/clients/yt/commit/fb00ecf745c1bd12e026faabf235a75c2c775a3a)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/update)** Also handle the newly introduced error conditions - ([a7e1a2d](https://git.foss-syndicate.org/soispha/clients/yt/commit/a7e1a2d7475fc1304ef7b33aa2f170f8232bd1d8)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/update)** Correct the progress display in `--grouped` mode - ([35f400c](https://git.foss-syndicate.org/soispha/clients/yt/commit/35f400cebca70325e7e999f15dcaa562dbc78f25)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/update)** Avoid printing all the subscriptions that are not updated - ([810c0d3](https://git.foss-syndicate.org/soispha/clients/yt/commit/810c0d3e75287c15e8baf210f89c807a21d3acee)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/update/video_entry_to_video)** Cast the json objects - ([b6a57c5](https://git.foss-syndicate.org/soispha/clients/yt/commit/b6a57c5cad1ee7df56dad5ccb2317f936e682bbe)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/version)** Use yt_dlp's native python version imply - ([22f74fc](https://git.foss-syndicate.org/soispha/clients/yt/commit/22f74fc43b004045d13b0184ae075dea0ebc8eda)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/watch/playlist)** Workaround terminals, that treat 0 as 1 - ([b3be18a](https://git.foss-syndicate.org/soispha/clients/yt/commit/b3be18a0bfb55135135c9769ac531c098ca4d26c)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/{se,}dowa)** Don't exit completely, if the downloader fails - ([b70dd45](https://git.foss-syndicate.org/soispha/clients/yt/commit/b70dd458615bbad99cf05dbde3dc831a9922ba21)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt_dlp)** Avoid writing the json output to disk - ([c8601d6](https://git.foss-syndicate.org/soispha/clients/yt/commit/c8601d67c2dd67ed3ae4465fbf3906fa2cf15a98)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt_dlp/json_{cast,get})** Improve error reporting - ([c4f8c14](https://git.foss-syndicate.org/soispha/clients/yt/commit/c4f8c14b5636055a2973afe0d5ef6494d97a1a76)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+#### Build system
+- **(.envrc)** Also disable ytdlp plugins by default - ([1b8113a](https://git.foss-syndicate.org/soispha/clients/yt/commit/1b8113a72161e5d5f1f7a8328265f8075fc3491a)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(.envrc)** Remove outdated env variables - ([e51139d](https://git.foss-syndicate.org/soispha/clients/yt/commit/e51139da51bbb8725614356bd173d1d66af7f74f)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(cog.toml)** Use the correct remote url - ([4e2aeec](https://git.foss-syndicate.org/soispha/clients/yt/commit/4e2aeec877ec9083de5116bcca8da039389b9f09)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(cog.toml)** Use correct username - ([b9957a2](https://git.foss-syndicate.org/soispha/clients/yt/commit/b9957a2dc50b02f1df8bcb2dc3ddcc3c081b94d3)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(flake)** Document for what the `CLANG_*` env vars are needed - ([d03e537](https://git.foss-syndicate.org/soispha/clients/yt/commit/d03e5374a238dc2b701c259bbd5ade91c6b4a9ff)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(flake)** Remove `flake-utils` - ([d21e1ac](https://git.foss-syndicate.org/soispha/clients/yt/commit/d21e1ac26c5e57f7e5f9cb2fea937b807118187b)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(flake)** Switch to `nixpkgs-unstable-small` - ([1a807d2](https://git.foss-syndicate.org/soispha/clients/yt/commit/1a807d25bd1a47fb81b538a1638514cedb928148)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(flake)** Adapt the dev env to yt_dlp's new dependencies - ([b8682b4](https://git.foss-syndicate.org/soispha/clients/yt/commit/b8682b478a3a2322a370cc8eabf46d20d00e8c37)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(flake)** Add `git-bug` to the devshell - ([ebcd3e1](https://git.foss-syndicate.org/soispha/clients/yt/commit/ebcd3e153e01bd1432b583b2a09569ba2017b8ed)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(package/package.nix)** Update to the new build requirements - ([f590cef](https://git.foss-syndicate.org/soispha/clients/yt/commit/f590cef92da3931fae1607c6964b8125ab2f6307)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(treewide)** Update - ([2380d7d](https://git.foss-syndicate.org/soispha/clients/yt/commit/2380d7d7fdfdda91c26e8027f41aa6788f3590e0)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(treewide)** Update - ([0791777](https://git.foss-syndicate.org/soispha/clients/yt/commit/0791777665fe99d02b5e4aaaa43ca3483712dac9)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(treewide)** Update - ([fa79bd7](https://git.foss-syndicate.org/soispha/clients/yt/commit/fa79bd7eef3824ad208984df9cc7784bdab5ba2b)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(treewide)** Update - ([47754f5](https://git.foss-syndicate.org/soispha/clients/yt/commit/47754f54b978e7ed66ccd29c866fabe28607997e)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **({flake,Cargo}.lock)** Update - ([9c7dfa7](https://git.foss-syndicate.org/soispha/clients/yt/commit/9c7dfa7a8ca71bd5067741917a6f96061290976b)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+#### Documentation
+- **(yt/update)** Add comment about the `unsmuggle_url` invocation - ([6c47d93](https://git.foss-syndicate.org/soispha/clients/yt/commit/6c47d93c983b8807032220e107ac2f686abb14e2)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/watch/playlist_handler/client_messages)** Add TODO about `current_exe` - ([13a0621](https://git.foss-syndicate.org/soispha/clients/yt/commit/13a062150e4efaf4b87d9213cf68b5a4eabb0235)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt_dlp)** Fix typo in `Cargo.toml`'s description - ([848270e](https://git.foss-syndicate.org/soispha/clients/yt/commit/848270ed0d9ed0409fe563a130e2913d9dfcc897)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+#### Features
+- **(yt/cli)** Add support for command line completions - ([e635ee7](https://git.foss-syndicate.org/soispha/clients/yt/commit/e635ee79a4ec0d30dca271cc269fee40150ea821)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/select)** Support a directory selection process - ([e2d5dc6](https://git.foss-syndicate.org/soispha/clients/yt/commit/e2d5dc6a9f000a875c3f2a100f660adc2a43275a)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/select/split)** Make sorting configurable - ([8b644e4](https://git.foss-syndicate.org/soispha/clients/yt/commit/8b644e4e0e058a003984c02d48e829de437145c6)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/status)** Show the percentage of videos that were actually watched - ([ec4e0c9](https://git.foss-syndicate.org/soispha/clients/yt/commit/ec4e0c91d33b2a8c11b71d4cdb1edeaa44ce8247)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/storage/videos)** Validate in DB, that is_focused is UNIQUE - ([cf16b93](https://git.foss-syndicate.org/soispha/clients/yt/commit/cf16b93b563daee88b3bda4b30666b1b0766a8b0)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/update)** Print a nice progress number - ([c04d530](https://git.foss-syndicate.org/soispha/clients/yt/commit/c04d530a1a9e09dd26adc4116959e5481b970bc6)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/update)** Support grouped updates - ([8a42c83](https://git.foss-syndicate.org/soispha/clients/yt/commit/8a42c835a0dd1fcaa3475938d9442199d57acf75)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/update)** Specify subscriptions to update as positional args - ([51bbd90](https://git.foss-syndicate.org/soispha/clients/yt/commit/51bbd90ab1f08c9056c4e5799e3abba568ae75c9)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/videos/list)** Replace the nucleo matcher with a simple `contains` - ([c0a3b61](https://git.foss-syndicate.org/soispha/clients/yt/commit/c0a3b61fb344a5ca86cae1c31d2e42fbe56b6726)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt_dlp)** Support a DeArrow post processor - ([ab61a4e](https://git.foss-syndicate.org/soispha/clients/yt/commit/ab61a4e47a955dd4a5dabeef3ade1b85f6576b84)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **({yt/update,yt_dlp})** Use yt_dlp errors again - ([078dfa0](https://git.foss-syndicate.org/soispha/clients/yt/commit/078dfa09a40a384b5cb8cf8cffd9b68cc9678556)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **({yt_dlp,yt})** Migrate from pyo3 to rustpython - ([69145b4](https://git.foss-syndicate.org/soispha/clients/yt/commit/69145b4deed4fe512239a9f88e6af69d3b8c0309)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+#### Miscellaneous Chores
+- **(treewide)** Add missing copyright headers - ([fd029a6](https://git.foss-syndicate.org/soispha/clients/yt/commit/fd029a65d43e1eb935b470b88893c16c30c19746)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(treewide)** Migrate to rust edition 2024 - ([8be7171](https://git.foss-syndicate.org/soispha/clients/yt/commit/8be717167ed77f5a1021fa0825b386674c5c1a39)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt_dlp/wrappers/info_json)** Add additional missing field - ([8ef4cf9](https://git.foss-syndicate.org/soispha/clients/yt/commit/8ef4cf92635003fb79263d22126289d788e34633)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+#### Performance Improvements
+- **(yt/update/updater)** Acknowledge, that `yt_dlp` has a sync API - ([ab56f25](https://git.foss-syndicate.org/soispha/clients/yt/commit/ab56f2550d5086ccd1c6981b62081b70743a1f2c)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+#### Refactoring
+- **(yt)** Move to `crates/yt` - ([394d4f7](https://git.foss-syndicate.org/soispha/clients/yt/commit/394d4f7d105dadd7b516f198b0d6a9dda2d3f1a5)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt)** Consolidate the multiple ANSI escape code wrapper functions - ([efc35b5](https://git.foss-syndicate.org/soispha/clients/yt/commit/efc35b5bd76bf4e4aab6750ead45713a79e851f9)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/select)** Split the `select::select` function up - ([fb49841](https://git.foss-syndicate.org/soispha/clients/yt/commit/fb49841e1ec14b3ab2de981e439d4f10f5494cf5)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/select/selection_file)** Migrate from `trinitry` to `shlex` - ([56011be](https://git.foss-syndicate.org/soispha/clients/yt/commit/56011be94c09828b104008cb7bf3a19177bc1631)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/storage/migrate)** Factor out duplicated code into macro - ([137339d](https://git.foss-syndicate.org/soispha/clients/yt/commit/137339d1d2924da764c54517fcc6d5d11d46a69d)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/storage/migrate/sql)** Use predictable SQL paths - ([420f9c8](https://git.foss-syndicate.org/soispha/clients/yt/commit/420f9c87abe3a3480a2345cbad5ec427636b2cb5)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt_dlp)** Remove the unneeded `async` from the public functions - ([5b5caee](https://git.foss-syndicate.org/soispha/clients/yt/commit/5b5caee512dd82bc5106e69259ba916cd143deda)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt_dlp/lib)** De-duplicate the info json sanitize code - ([e46ab9b](https://git.foss-syndicate.org/soispha/clients/yt/commit/e46ab9bc8bd4ecc35363e27aea9b5445bc858b2d)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt_dlp/lib)** Explicitly convert python exceptions into an error - ([ada9550](https://git.foss-syndicate.org/soispha/clients/yt/commit/ada9550b02ee13a8378bd2ee27d536b83eec4820)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+#### Style
+- **(treewide)** Reformat - ([10b07fa](https://git.foss-syndicate.org/soispha/clients/yt/commit/10b07fa5a4f4080ef5417720b2d15179b72d2fc2)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(treewide)** Adopt rust edition 2024 rustfmt style - ([a78b66e](https://git.foss-syndicate.org/soispha/clients/yt/commit/a78b66ed784cd6f2f97771d9e170c8f8558140b8)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/cli)** Sort the toplevel flags alphabetically - ([77ea1d8](https://git.foss-syndicate.org/soispha/clients/yt/commit/77ea1d8223b57567b448fb973b7240adaab61778)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+- **(yt/select)** Apply clippy's suggestions - ([b4ee42a](https://git.foss-syndicate.org/soispha/clients/yt/commit/b4ee42a62c683632c589f39e6ceac0b48d222e87)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+#### Tests
+- **(yt/cli)** Test the CLI - ([10f9d8b](https://git.foss-syndicate.org/soispha/clients/yt/commit/10f9d8bfd0c84146638cfdaf6b076493f943e650)) - [@bpeetz](https://git.foss-syndicate.org/bpeetz)
+
+- - -
+
 ## [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)
diff --git a/cog.toml b/cog.toml
index 7cc3ac4..781175b 100644
--- a/cog.toml
+++ b/cog.toml
@@ -29,7 +29,7 @@ post_bump_hooks = [
 [changelog]
 path = "NEWS.md"
 template = "remote"
-remote = "git.vhack.eu"
+remote = "git.foss-syndicate.org"
 repository = "clients/yt"
 owner = "soispha"
-authors = [{ signature = "Benedikt Peetz", username = "soispha" }]
+authors = [{ signature = "Benedikt Peetz", username = "bpeetz" }]
diff --git a/crates/libmpv2/CHANGELOG.md b/crates/libmpv2/CHANGELOG.md
index dc6f861..a3d14d7 100644
--- a/crates/libmpv2/CHANGELOG.md
+++ b/crates/libmpv2/CHANGELOG.md
@@ -16,7 +16,7 @@ If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 
 ## Version 3.0.0
 
-- \[breaking\] Support libmpv version 2.0 (mpv version 0.35.0). Mpv versions \<=
+- [breaking] Support libmpv version 2.0 (mpv version 0.35.0). Mpv versions \<=
   0.34.0 will no longer be supported.
 - Add OpenGL rendering
 
@@ -29,10 +29,10 @@ If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 ## Version 2.0.0
 
 - Add method `Mpv::with_initializer` to set options before initialization
-- \[breaking\] Borrow `&mut self` in `wait_event` to disallow using two events
+- [breaking] Borrow `&mut self` in `wait_event` to disallow using two events
   where the first points to data freed in the second `wait_event` call
-- \[breaking\] `PropertyData<'_>` is no longer `Clone` or `PartialEq`,
-  `Event<'_>` is no longer `Clone` to avoid cloning/comparing `MpvNode`
+- [breaking] `PropertyData<'_>` is no longer `Clone` or `PartialEq`, `Event<'_>`
+  is no longer `Clone` to avoid cloning/comparing `MpvNode`
 
 ## Version 1.1.0
 
diff --git a/crates/libmpv2/libmpv2-sys/build.rs b/crates/libmpv2/libmpv2-sys/build.rs
index bf9a02e..45c2450 100644
--- a/crates/libmpv2/libmpv2-sys/build.rs
+++ b/crates/libmpv2/libmpv2-sys/build.rs
@@ -30,7 +30,9 @@ fn main() {
             ),
             "--verbose",
         ])
-        .generate_comments(true)
+        // NOTE(@bpeetz): The comments are interpreted as doc-tests,
+        // which obviously fail, as the code is c. <2025-06-16>
+        .generate_comments(false)
         .generate()
         .expect("Unable to generate bindings");
 
diff --git a/crates/yt/Cargo.toml b/crates/yt/Cargo.toml
index 6803e68..c6d8c30 100644
--- a/crates/yt/Cargo.toml
+++ b/crates/yt/Cargo.toml
@@ -29,16 +29,16 @@ blake3 = "1.8.2"
 chrono = { version = "0.4.41", features = ["now"] }
 chrono-humanize = "0.2.3"
 clap = { version = "4.5.40", features = ["derive"] }
+clap_complete = { version = "4.5.54", features = ["unstable-dynamic"] }
 futures = "0.3.31"
-nucleo-matcher = "0.3.1"
 owo-colors = "4.2.1"
 regex = "1.11.1"
 sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] }
 stderrlog = "0.6.0"
 tempfile = "3.20.0"
 toml = "0.8.23"
-trinitry = { version = "0.2.2" }
 xdg = "3.0.0"
+shlex = "1.3.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
index ae1805d..462a126 100644
--- a/crates/yt/src/ansi_escape_codes.rs
+++ b/crates/yt/src/ansi_escape_codes.rs
@@ -1,3 +1,13 @@
+// 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>.
+
 // see: https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands
 const CSI: &str = "\x1b[";
 pub fn erase_in_display_from_cursor() {
diff --git a/crates/yt/src/cli.rs b/crates/yt/src/cli.rs
index 634e422..41fadf4 100644
--- a/crates/yt/src/cli.rs
+++ b/crates/yt/src/cli.rs
@@ -9,12 +9,16 @@
 // 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::{path::PathBuf, str::FromStr};
+use std::{
+    fmt::{self, Display, Formatter},
+    path::PathBuf,
+    str::FromStr,
+};
 
 use anyhow::Context;
 use bytes::Bytes;
 use chrono::NaiveDate;
-use clap::{ArgAction, Args, Parser, Subcommand};
+use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
 use url::Url;
 
 use crate::{
@@ -294,6 +298,43 @@ impl FromStr for OptionalPublisher {
     }
 }
 
+#[derive(Default, ValueEnum, Clone, Copy, Debug)]
+pub enum SelectSplitSortKey {
+    /// Sort by the name of the publisher.
+    #[default]
+    Publisher,
+
+    /// Sort by the number of unselected videos per publisher.
+    Videos,
+}
+impl Display for SelectSplitSortKey {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        match self {
+            SelectSplitSortKey::Publisher => f.write_str("publisher"),
+            SelectSplitSortKey::Videos => f.write_str("videos"),
+        }
+    }
+}
+
+#[derive(Default, ValueEnum, Clone, Copy, Debug)]
+pub enum SelectSplitSortMode {
+    /// Sort in ascending order (small -> big)
+    #[default]
+    Asc,
+
+    /// Sort in descending order (big -> small)
+    Desc,
+}
+
+impl Display for SelectSplitSortMode {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        match self {
+            SelectSplitSortMode::Asc => f.write_str("asc"),
+            SelectSplitSortMode::Desc => f.write_str("desc"),
+        }
+    }
+}
+
 #[derive(Subcommand, Clone, Debug)]
 // NOTE: Keep this in sync with the [`constants::HELP_STR`] constant. <2024-08-20>
 // NOTE: Also keep this in sync with the `tree-sitter-yts/grammar.js`. <2024-11-04>
@@ -304,15 +345,26 @@ pub enum SelectCommand {
         #[arg(long, short)]
         done: bool,
 
-        /// Generate a directory, where each file contains only one subscription.
-        #[arg(long, short, conflicts_with = "use_last_selection")]
-        split: bool,
-
         /// Use the last selection file (useful if you've spend time on it and want to get it again)
         #[arg(long, short, conflicts_with = "done")]
         use_last_selection: bool,
     },
 
+    /// Generate a directory, where each file contains only one subscription.
+    Split {
+        /// Include done (watched, dropped) videos
+        #[arg(long, short)]
+        done: bool,
+
+        /// Which key to use for sorting.
+        #[arg(default_value_t)]
+        sort_key: SelectSplitSortKey,
+
+        /// Which mode to use for sorting.
+        #[arg(default_value_t)]
+        sort_mode: SelectSplitSortMode,
+    },
+
     /// Add a video to the database
     ///
     /// This optionally supports to add a playlist.
@@ -371,7 +423,6 @@ impl Default for SelectCommand {
         Self::File {
             done: false,
             use_last_selection: false,
-            split: false,
         }
     }
 }
@@ -381,7 +432,7 @@ pub enum CacheCommand {
     /// Invalidate all cache entries
     Invalidate {
         /// Also delete the cache path
-        #[arg(short, long)]
+        #[arg(short = 'f', long)]
         hard: bool,
     },
 
@@ -396,3 +447,14 @@ pub enum CacheCommand {
         all: bool,
     },
 }
+
+#[cfg(test)]
+mod test {
+    use clap::CommandFactory;
+
+    use super::CliArgs;
+    #[test]
+    fn verify_cli() {
+        CliArgs::command().debug_assert();
+    }
+}
diff --git a/crates/yt/src/download/mod.rs b/crates/yt/src/download/mod.rs
index 110bf55..6065cf9 100644
--- a/crates/yt/src/download/mod.rs
+++ b/crates/yt/src/download/mod.rs
@@ -311,8 +311,11 @@ impl Downloader {
 
             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 let Some(serde_json::Value::Number(num)) = result.get("filesize_approx") {
+                // NOTE(@bpeetz): yt_dlp sets this value to `Null`, instead of omitting it when it
+                // can't calculate the approximate filesize.
+                // Thus, we have to check, that it is actually non-null, before we cast it. <2025-06-15>
+                json_cast!(num, 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 = json_get!(result, "duration", as_f64).ceil() as u64;
@@ -347,7 +350,7 @@ impl Downloader {
         let yt_dlp = download_opts(app, &addional_opts)?;
 
         let result = yt_dlp
-            .download(&[video.url.to_owned()])
+            .download(&[video.url.clone()])
             .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
index b75ec00..c507165 100644
--- a/crates/yt/src/download/progress_hook.rs
+++ b/crates/yt/src/download/progress_hook.rs
@@ -1,3 +1,13 @@
+// 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},
     process,
diff --git a/crates/yt/src/main.rs b/crates/yt/src/main.rs
index 930d269..744fe5f 100644
--- a/crates/yt/src/main.rs
+++ b/crates/yt/src/main.rs
@@ -19,7 +19,7 @@ use anyhow::{Context, Result, bail};
 use app::App;
 use bytes::Bytes;
 use cache::{invalidate, maintain};
-use clap::Parser;
+use clap::{CommandFactory, Parser};
 use cli::{CacheCommand, SelectCommand, SubscriptionCommand, VideosCommand};
 use config::Config;
 use log::{error, info};
@@ -56,6 +56,8 @@ pub mod watch;
 // This is _the_ main function after all. It is not really good, but it sort of works.
 #[allow(clippy::too_many_lines)]
 async fn main() -> Result<()> {
+    clap_complete::CompleteEnv::with_factory(cli::CliArgs::command).complete();
+
     let args = cli::CliArgs::parse();
 
     // The default verbosity is 1 (Warn)
@@ -115,15 +117,12 @@ async fn main() -> Result<()> {
                 SelectCommand::File {
                     done,
                     use_last_selection,
-                    split,
-                } => {
-                    if split {
-                        assert!(!use_last_selection);
-                        Box::pin(select::select_split(&app, done)).await?
-                    } else {
-                        Box::pin(select::select_file(&app, done, use_last_selection)).await?
-                    }
-                }
+                } => Box::pin(select::select_file(&app, done, use_last_selection)).await?,
+                SelectCommand::Split {
+                    done,
+                    sort_key,
+                    sort_mode,
+                } => Box::pin(select::select_split(&app, done, sort_key, sort_mode)).await?,
                 _ => Box::pin(handle_select_cmd(&app, cmd, None)).await?,
             }
         }
@@ -219,7 +218,14 @@ async fn main() -> Result<()> {
                     current_progress += CHUNK_SIZE;
                 }
             } else {
-                update::update(&app, max_backlog, subscriptions, total_number, current_progress).await?;
+                update::update(
+                    &app,
+                    max_backlog,
+                    subscriptions,
+                    total_number,
+                    current_progress,
+                )
+                .await?;
             }
         }
         Command::Subscriptions { cmd } => match cmd {
diff --git a/crates/yt/src/select/cmds/mod.rs b/crates/yt/src/select/cmds/mod.rs
index aabcd3d..9da795a 100644
--- a/crates/yt/src/select/cmds/mod.rs
+++ b/crates/yt/src/select/cmds/mod.rs
@@ -76,7 +76,9 @@ pub async fn handle_select_cmd(
             firefox.arg(url.as_str());
             let _handle = firefox.spawn().context("Failed to run firefox")?;
         }
-        SelectCommand::File { .. } => unreachable!("This should have been filtered out"),
+        SelectCommand::File { .. } | SelectCommand::Split { .. } => {
+            unreachable!("This should have been filtered out")
+        }
     }
     Ok(())
 }
diff --git a/crates/yt/src/select/mod.rs b/crates/yt/src/select/mod.rs
index 668ab02..135bd76 100644
--- a/crates/yt/src/select/mod.rs
+++ b/crates/yt/src/select/mod.rs
@@ -21,7 +21,7 @@ use std::{
 
 use crate::{
     app::App,
-    cli::CliArgs,
+    cli::{CliArgs, SelectSplitSortKey, SelectSplitSortMode},
     constants::HELP_STR,
     storage::video_database::{Video, VideoStatusMarker, get},
     unreachable::Unreachable,
@@ -39,7 +39,12 @@ use tokio::process::Command;
 pub mod cmds;
 pub mod selection_file;
 
-pub async fn select_split(app: &App, done: bool) -> Result<()> {
+pub async fn select_split(
+    app: &App,
+    done: bool,
+    sort_key: SelectSplitSortKey,
+    sort_mode: SelectSplitSortMode,
+) -> Result<()> {
     let temp_dir = Builder::new()
         .prefix("yt_video_select-")
         .rand_bytes(6)
@@ -69,8 +74,24 @@ pub async fn select_split(app: &App, done: bool) -> Result<()> {
     let author_map = {
         let mut temp_vec: Vec<_> = author_map.into_iter().collect();
 
-        // PERFORMANCE: The clone here should not be neeed.  <2025-06-15>
-        temp_vec.sort_by_key(|(name, _)| name.to_owned());
+        match sort_key {
+            SelectSplitSortKey::Publisher => {
+                // PERFORMANCE: The clone here should not be neeed.  <2025-06-15>
+                temp_vec.sort_by_key(|(name, _): &(String, Vec<Video>)| name.to_owned());
+            }
+            SelectSplitSortKey::Videos => {
+                temp_vec.sort_by_key(|(_, videos): &(String, Vec<Video>)| videos.len());
+            }
+        }
+
+        match sort_mode {
+            SelectSplitSortMode::Asc => {
+                // Std's default mode is ascending.
+            }
+            SelectSplitSortMode::Desc => {
+                temp_vec.reverse();
+            }
+        }
 
         temp_vec
     };
@@ -243,7 +264,7 @@ async fn process_file(app: &App, file: &File, processed: i64) -> Result<i64> {
         }
     }
 
-    Ok(line_number * -1)
+    Ok(-line_number)
 }
 
 async fn open_editor_at(path: &Path) -> Result<()> {
diff --git a/crates/yt/src/select/selection_file/mod.rs b/crates/yt/src/select/selection_file/mod.rs
index abd26c4..f5e0531 100644
--- a/crates/yt/src/select/selection_file/mod.rs
+++ b/crates/yt/src/select/selection_file/mod.rs
@@ -11,22 +11,32 @@
 
 //! The data structures needed to express the file, which the user edits
 
-use anyhow::{Context, Result};
-use trinitry::Trinitry;
+use anyhow::{Result, bail};
+use shlex::Shlex;
 
 pub mod duration;
 
+/// # Panics
+/// If internal assertions fail.
 pub fn process_line(line: &str) -> Result<Option<Vec<String>>> {
     // Filter out comments and empty lines
     if line.starts_with('#') || line.trim().is_empty() {
         Ok(None)
     } else {
-        let tri = Trinitry::new(line).with_context(|| format!("Failed to parse line '{line}'"))?;
+        let split: Vec<_> = {
+            let mut shl = Shlex::new(line);
+            let res = shl.by_ref().collect();
 
-        let mut vec = Vec::with_capacity(tri.arguments().len() + 1);
-        vec.push(tri.command().to_owned());
-        vec.extend(tri.arguments().to_vec());
+            if shl.had_error {
+                bail!("Failed to parse line '{line}'")
+            }
 
-        Ok(Some(vec))
+            assert_eq!(shl.line_no, 1, "A unexpected newline appeared");
+            res
+        };
+
+        assert!(!split.is_empty());
+
+        Ok(Some(split))
     }
 }
diff --git a/crates/yt/src/status/mod.rs b/crates/yt/src/status/mod.rs
index 18bef7d..6883802 100644
--- a/crates/yt/src/status/mod.rs
+++ b/crates/yt/src/status/mod.rs
@@ -92,7 +92,8 @@ pub async fn show(app: &App) -> Result<()> {
             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));
+        let count =
+            to_f64(watched_videos_len) / (to_f64(drop_videos_len) + to_f64(dropped_videos_len));
         count * 100.0
     };
 
diff --git a/crates/yt/src/update/updater.rs b/crates/yt/src/update/updater.rs
index 04bcaa1..60e9855 100644
--- a/crates/yt/src/update/updater.rs
+++ b/crates/yt/src/update/updater.rs
@@ -19,7 +19,7 @@ use futures::{StreamExt, future::join_all, stream};
 use log::{Level, debug, error, log_enabled};
 use serde_json::json;
 use tokio_util::task::LocalPoolHandle;
-use yt_dlp::{InfoJson, YoutubeDLOptions, json_cast, json_get, process_ie_result};
+use yt_dlp::{InfoJson, PythonError, YoutubeDLOptions, json_cast, json_get, process_ie_result};
 
 use crate::{
     ansi_escape_codes::{clear_whole_line, move_to_col},
@@ -160,24 +160,28 @@ impl Updater {
                             }
                         })
                         // Don't fail the whole update, if one of the entries fails to fetch.
-                        .filter_map(|base| match base {
+                        .filter_map(move |base| match base {
                             Ok(ok) => Some(ok),
                             Err(err) => {
-                                let process_ie_result::Error::Python(err) = &err;
-
-                                if err.contains(
-                                    "Join this channel to get access to members-only content ",
-                                ) {
-                                    // Hide this error
-                                } else {
-                                    // Show the error, but don't fail.
-                                    let error = err
-                                        .strip_prefix("DownloadError: \u{1b}[0;31mERROR:\u{1b}[0m ")
-                                        .unwrap_or(err);
-                                    error!("{error}");
+                                match err {
+                                    process_ie_result::Error::Python(PythonError(err)) => {
+                                        if err.contains( "Join this channel to get access to members-only content ",) {
+                                            // Hide this error
+                                        } else {
+                                            // Show the error, but don't fail.
+                                            let error = err
+                                                .strip_prefix("DownloadError: \u{1b}[0;31mERROR:\u{1b}[0m ")
+                                                .unwrap_or(&err);
+                                            error!("While fetching {:#?}: {error}", sub.name);
+                                        }
+
+                                        None
+                                    }
+                                    process_ie_result::Error::InfoJsonPrepare(error) => {
+                                        error!("While fetching {:#?}: Failed to prepare info json: {error}", sub.name);
+                                        None
+                                    },
                                 }
-
-                                None
                             }
                         }))
                 }
diff --git a/crates/yt/src/videos/mod.rs b/crates/yt/src/videos/mod.rs
index e821772..960340b 100644
--- a/crates/yt/src/videos/mod.rs
+++ b/crates/yt/src/videos/mod.rs
@@ -11,10 +11,6 @@
 
 use anyhow::Result;
 use futures::{TryStreamExt, stream::FuturesUnordered};
-use nucleo_matcher::{
-    Matcher,
-    pattern::{CaseMatching, Normalization, Pattern},
-};
 
 pub mod display;
 
@@ -46,19 +42,10 @@ pub async fn query(app: &App, limit: Option<usize>, search_query: Option<String>
         .await?;
 
     if let Some(query) = search_query {
-        let mut matcher = Matcher::new(nucleo_matcher::Config::DEFAULT.match_paths());
-
-        let pattern_matches = Pattern::parse(
-            &query.replace(' ', "\\ "),
-            CaseMatching::Ignore,
-            Normalization::Smart,
-        )
-        .match_list(all_video_strings, &mut matcher);
-
-        pattern_matches
-            .iter()
-            .rev()
-            .for_each(|(val, key)| println!("{val} ({key})"));
+        all_video_strings
+            .into_iter()
+            .filter(|video| video.to_lowercase().contains(&query.to_lowercase()))
+            .for_each(|video| println!("{video}"));
     } else {
         println!("{}", all_video_strings.join("\n"));
     }
diff --git a/crates/yt_dlp/Cargo.toml b/crates/yt_dlp/Cargo.toml
index 90f2e10..81e1412 100644
--- a/crates/yt_dlp/Cargo.toml
+++ b/crates/yt_dlp/Cargo.toml
@@ -24,7 +24,15 @@ publish = true
 [dependencies]
 indexmap = { version = "2.9.0", default-features = false }
 log.workspace = true
-rustpython = { git = "https://github.com/RustPython/RustPython.git", features = ["threading", "stdlib", "stdio", "importlib", "ssl"], default-features = false }
+reqwest = { version = "0.12.20", features = ["blocking", "json"] }
+rustpython = { git = "https://github.com/RustPython/RustPython.git", features = [
+  "threading",
+  "stdlib",
+  "stdio",
+  "importlib",
+  "ssl",
+], default-features = false }
+serde = { workspace = true, features = ["derive"] }
 serde_json.workspace = true
 thiserror = "2.0.12"
 url.workspace = true
diff --git a/crates/yt_dlp/README.md b/crates/yt_dlp/README.md
index 591ef2e..ece8540 100644
--- a/crates/yt_dlp/README.md
+++ b/crates/yt_dlp/README.md
@@ -12,7 +12,7 @@ If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 
 # Yt_py
 
-> \[can be empty\]
+> [can be empty]
 
 Some text about the project.
 
diff --git a/crates/yt_dlp/src/lib.rs b/crates/yt_dlp/src/lib.rs
index dd42fc6..e7b37c6 100644
--- a/crates/yt_dlp/src/lib.rs
+++ b/crates/yt_dlp/src/lib.rs
@@ -1,10 +1,21 @@
+// 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>.
+
 //! The `yt_dlp` interface is completely contained in the [`YoutubeDL`] structure.
 
-use std::{self, env, mem, path::PathBuf};
+use std::{self, env, fmt::Display, path::PathBuf};
 
 use indexmap::IndexMap;
 use log::{Level, debug, error, info, log_enabled};
 use logging::setup_logging;
+use post_processors::PostProcessor;
 use rustpython::{
     InterpreterConfig,
     vm::{
@@ -18,23 +29,42 @@ use rustpython::{
 use url::Url;
 
 mod logging;
+pub mod post_processors;
 pub mod progress_hook;
 
 #[macro_export]
 macro_rules! json_get {
-    ($value:expr, $name:literal, $into:ident) => {
-        $crate::json_cast!($value.get($name).expect("Should exist"), $into)
-    };
+    ($value:expr, $name:literal, $into:ident) => {{
+        match $value.get($name) {
+            Some(val) => $crate::json_cast!(val, $into),
+            None => panic!(
+                concat!(
+                    "Expected '",
+                    $name,
+                    "' to be a key for the'",
+                    stringify!($value),
+                    "' object: {:#?}"
+                ),
+                $value
+            ),
+        }
+    }};
 }
 
 #[macro_export]
 macro_rules! json_cast {
-    ($value:expr, $into:ident) => {
-        $value.$into().expect(concat!(
-            "Should be able to cast value into ",
-            stringify!($into)
-        ))
-    };
+    ($value:expr, $into:ident) => {{
+        match $value.$into() {
+            Some(result) => result,
+            None => panic!(
+                concat!(
+                    "Expected to be able to cast value ({:#?}) ",
+                    stringify!($into)
+                ),
+                $value
+            ),
+        }
+    }};
 }
 
 /// The core of the `yt_dlp` interface.
@@ -43,6 +73,7 @@ pub struct YoutubeDL {
     youtube_dl_class: PyObjectRef,
     yt_dlp_module: PyObjectRef,
     options: serde_json::Map<String, serde_json::Value>,
+    post_processors: Vec<Box<dyn PostProcessor>>,
 }
 
 impl std::fmt::Debug for YoutubeDL {
@@ -60,7 +91,7 @@ impl YoutubeDL {
     ///
     /// # Errors
     /// If a python call fails.
-    pub fn from_options(mut options: YoutubeDLOptions) -> Result<Self, build::Error> {
+    pub fn from_options(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(':') {
@@ -92,9 +123,8 @@ impl YoutubeDL {
             let yt_dlp_module = vm.import("yt_dlp", 0)?;
             let class = yt_dlp_module.get_attr("YoutubeDL", vm)?;
 
-            let maybe_hook = mem::take(&mut options.progress_hook);
-            let opts = options.into_py_dict(vm);
-            if let Some(function) = maybe_hook {
+            let opts = json_loads(options.options, vm);
+            if let Some(function) = options.progress_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])
@@ -192,6 +222,7 @@ impl YoutubeDL {
             youtube_dl_class,
             yt_dlp_module,
             options: output_options,
+            post_processors: options.post_processors,
         })
     }
 
@@ -267,7 +298,7 @@ impl YoutubeDL {
         download: bool,
         process: bool,
     ) -> Result<InfoJson, extract_info::Error> {
-        match self.interpreter.enter(|vm| {
+        self.interpreter.enter(|vm| {
             let pos_args = PosArgs::new(vec![vm.new_pyobj(url.to_string())]);
 
             let kw_args = KwArgs::new({
@@ -279,9 +310,13 @@ impl YoutubeDL {
 
             let fun_args = FuncArgs::new(pos_args, kw_args);
 
-            let inner = self.youtube_dl_class.get_attr("extract_info", vm)?;
+            let inner = self
+                .youtube_dl_class
+                .get_attr("extract_info", vm)
+                .map_err(|exc| PythonError::from_exception(vm, &exc))?;
             let result = inner
-                .call_with_args(fun_args, vm)?
+                .call_with_args(fun_args, vm)
+                .map_err(|exc| PythonError::from_exception(vm, &exc))?
                 .downcast::<PyDict>()
                 .expect("This is a dict");
 
@@ -295,7 +330,9 @@ impl YoutubeDL {
                     });
 
                     let mut out = vec![];
-                    let next = generator.get_attr("__next__", vm)?;
+                    let next = generator
+                        .get_attr("__next__", vm)
+                        .map_err(|exc| PythonError::from_exception(vm, &exc))?;
                     while let Ok(output) = next.call((), vm) {
                         out.push(output);
 
@@ -303,27 +340,16 @@ impl YoutubeDL {
                             break;
                         }
                     }
-                    result.set_item("entries", vm.new_pyobj(out), vm)?;
+                    result
+                        .set_item("entries", vm.new_pyobj(out), vm)
+                        .map_err(|exc| PythonError::from_exception(vm, &exc))?;
                 }
             }
 
-            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_json = json_dumps(result, vm);
+            let result = self.prepare_info_json(result, vm)?;
 
-            Ok::<_, PyRef<PyBaseException>>(result_json)
-        }) {
-            Ok(ok) => Ok(ok),
-            Err(err) => self.interpreter.enter(|vm| {
-                let buffer = process_exception(vm, &err);
-                Err(extract_info::Error::Python(buffer))
-            }),
-        }
+            Ok(result)
+        })
     }
 
     /// Take the (potentially modified) result of the information extractor (i.e.,
@@ -344,7 +370,7 @@ impl YoutubeDL {
         ie_result: InfoJson,
         download: bool,
     ) -> Result<InfoJson, process_ie_result::Error> {
-        match self.interpreter.enter(|vm| {
+        self.interpreter.enter(|vm| {
             let pos_args = PosArgs::new(vec![vm.new_pyobj(json_loads(ie_result, vm))]);
 
             let kw_args = KwArgs::new({
@@ -355,46 +381,109 @@ impl YoutubeDL {
 
             let fun_args = FuncArgs::new(pos_args, kw_args);
 
-            let inner = self.youtube_dl_class.get_attr("process_ie_result", vm)?;
+            let inner = self
+                .youtube_dl_class
+                .get_attr("process_ie_result", vm)
+                .map_err(|exc| PythonError::from_exception(vm, &exc))?;
             let result = inner
-                .call_with_args(fun_args, vm)?
+                .call_with_args(fun_args, vm)
+                .map_err(|exc| PythonError::from_exception(vm, &exc))?
                 .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)?;
+            let result = self.prepare_info_json(result, vm)?;
 
-                value.downcast::<PyDict>().expect("This should stay a dict")
-            };
+            Ok(result)
+        })
+    }
 
-            let result_json = json_dumps(result, vm);
+    fn prepare_info_json(
+        &self,
+        info: PyRef<PyDict>,
+        vm: &VirtualMachine,
+    ) -> Result<InfoJson, prepare::Error> {
+        let sanitize = self
+            .youtube_dl_class
+            .get_attr("sanitize_info", vm)
+            .map_err(|exc| PythonError::from_exception(vm, &exc))?;
 
-            Ok::<_, PyRef<PyBaseException>>(result_json)
-        }) {
-            Ok(ok) => Ok(ok),
-            Err(err) => self.interpreter.enter(|vm| {
-                let buffer = process_exception(vm, &err);
-                Err(process_ie_result::Error::Python(buffer))
-            }),
+        let value = sanitize
+            .call((info,), vm)
+            .map_err(|exc| PythonError::from_exception(vm, &exc))?;
+
+        let result = value.downcast::<PyDict>().expect("This should stay a dict");
+
+        let mut json = json_dumps(result, vm);
+
+        for pp in &self.post_processors {
+            if pp
+                .extractors()
+                .iter()
+                .any(|extractor| *extractor == json_get!(json, "extractor_key", as_str))
+            {
+                json = pp.process(json)?;
+            } else {
+                error!("Extractor not found for {pp:#?}");
+            }
         }
+
+        Ok(json)
+    }
+}
+
+#[derive(thiserror::Error, Debug)]
+pub struct PythonError(pub String);
+
+impl Display for PythonError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "Python threw an exception: {}", self.0)
+    }
+}
+
+impl PythonError {
+    fn from_exception(vm: &VirtualMachine, exc: &PyRef<PyBaseException>) -> Self {
+        let buffer = process_exception(vm, exc);
+        Self(buffer)
     }
 }
 
 #[allow(missing_docs)]
 pub mod process_ie_result {
+    use crate::{PythonError, prepare};
+
     #[derive(Debug, thiserror::Error)]
     pub enum Error {
-        #[error("Python threw an exception: {0}")]
-        Python(String),
+        #[error(transparent)]
+        Python(#[from] PythonError),
+
+        #[error("Failed to prepare the info json")]
+        InfoJsonPrepare(#[from] prepare::Error),
     }
 }
 #[allow(missing_docs)]
 pub mod extract_info {
+    use crate::{PythonError, prepare};
+
     #[derive(Debug, thiserror::Error)]
     pub enum Error {
-        #[error("Python threw an exception: {0}")]
-        Python(String),
+        #[error(transparent)]
+        Python(#[from] PythonError),
+
+        #[error("Failed to prepare the info json")]
+        InfoJsonPrepare(#[from] prepare::Error),
+    }
+}
+#[allow(missing_docs)]
+pub mod prepare {
+    use crate::{PythonError, post_processors};
+
+    #[derive(Debug, thiserror::Error)]
+    pub enum Error {
+        #[error(transparent)]
+        Python(#[from] PythonError),
+
+        #[error("Failed to run a post processor")]
+        PostProcessorRun(#[from] post_processors::Error),
     }
 }
 
@@ -410,15 +499,19 @@ pub type ProgressHookFunction = fn(input: FuncArgs, vm: &VirtualMachine);
 pub struct YoutubeDLOptions {
     options: serde_json::Map<String, serde_json::Value>,
     progress_hook: Option<ProgressHookFunction>,
+    post_processors: Vec<Box<dyn PostProcessor>>,
 }
 
 impl YoutubeDLOptions {
     #[must_use]
     pub fn new() -> Self {
-        Self {
+        let me = Self {
             options: serde_json::Map::new(),
             progress_hook: None,
-        }
+            post_processors: vec![],
+        };
+
+        me.with_post_processor(post_processors::dearrow::DeArrowPP)
     }
 
     #[must_use]
@@ -426,10 +519,7 @@ impl YoutubeDLOptions {
         let mut options = self.options;
         options.insert(key.into(), value.into());
 
-        Self {
-            options,
-            progress_hook: self.progress_hook,
-        }
+        Self { options, ..self }
     }
 
     #[must_use]
@@ -438,12 +528,18 @@ impl YoutubeDLOptions {
             todo!()
         } else {
             Self {
-                options: self.options,
                 progress_hook: Some(progress_hook),
+                ..self
             }
         }
     }
 
+    #[must_use]
+    pub fn with_post_processor<P: PostProcessor + 'static>(mut self, post_processor: P) -> Self {
+        self.post_processors.push(Box::new(post_processor));
+        self
+    }
+
     /// # Errors
     /// If the underlying [`YoutubeDL::from_options`] errors.
     pub fn build(self) -> Result<YoutubeDL, build::Error> {
@@ -454,7 +550,7 @@ impl YoutubeDLOptions {
     pub fn from_json_options(options: serde_json::Map<String, serde_json::Value>) -> Self {
         Self {
             options,
-            progress_hook: None,
+            ..Self::new()
         }
     }
 
@@ -462,10 +558,6 @@ impl YoutubeDLOptions {
     pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
         self.options.get(key)
     }
-
-    fn into_py_dict(self, vm: &VirtualMachine) -> PyRef<PyDict> {
-        json_loads(self.options, vm)
-    }
 }
 
 #[allow(missing_docs)]
@@ -474,9 +566,6 @@ pub mod build {
     pub enum Error {
         #[error("Python threw an exception: {0}")]
         Python(String),
-
-        #[error("Io error: {0}")]
-        Io(#[from] std::io::Error),
     }
 }
 
diff --git a/crates/yt_dlp/src/post_processors/dearrow.rs b/crates/yt_dlp/src/post_processors/dearrow.rs
new file mode 100644
index 0000000..bdbea7c
--- /dev/null
+++ b/crates/yt_dlp/src/post_processors/dearrow.rs
@@ -0,0 +1,118 @@
+// 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 log::{info, warn};
+use serde::{Deserialize, Serialize};
+
+use crate::{InfoJson, json_get};
+
+use super::PostProcessor;
+
+#[derive(Debug, Clone, Copy)]
+pub struct DeArrowPP;
+
+impl PostProcessor for DeArrowPP {
+    fn extractors(&self) -> &'static [&'static str] {
+        &["Youtube"]
+    }
+
+    fn process(&self, mut info: InfoJson) -> Result<InfoJson, super::Error> {
+        let mut output: DeArrowApi = reqwest::blocking::get(format!(
+            "https://sponsor.ajay.app/api/branding?videoID={}",
+            json_get!(info, "id", as_str)
+        ))?
+        .json()?;
+
+        output.titles.reverse();
+
+        let title_len = output.titles.len();
+        loop {
+            let Some(title) = output.titles.pop() else {
+                break;
+            };
+
+            if (title.locked || title.votes < 1) && title_len > 1 {
+                info!(
+                    "Skipping title {:#?}, as it is not good enough",
+                    title.value
+                );
+                // Skip titles that are not “good” enough.
+                continue;
+            }
+
+            if let Some(old_title) = info.insert(
+                "title".to_owned(),
+                serde_json::Value::String(title.value.clone()),
+            ) {
+                warn!("Updating title from {:#?} to {:#?}", old_title, title.value);
+                info.insert("original_title".to_owned(), old_title);
+            } else {
+                warn!("Setting title to {:#?}", title.value);
+            }
+
+            break;
+        }
+
+        Ok(info)
+    }
+}
+
+#[derive(Serialize, Deserialize)]
+/// See: <https://wiki.sponsor.ajay.app/w/API_Docs/DeArrow>
+struct DeArrowApi {
+    titles: Vec<Title>,
+    thumbnails: Vec<Thumbnail>,
+
+    #[serde(alias = "randomTime")]
+    random_time: Option<f64>,
+
+    #[serde(alias = "videoDuration")]
+    video_duration: Option<f64>,
+
+    #[serde(alias = "casualVotes")]
+    casual_votes: Vec<String>,
+}
+
+#[derive(Serialize, Deserialize)]
+struct Title {
+    /// Note: Titles will sometimes contain > before a word.
+    /// This tells the auto-formatter to not format a word.
+    /// If you have no auto-formatter, you can ignore this and replace it with an empty string
+    #[serde(alias = "title")]
+    value: String,
+
+    original: bool,
+    votes: u64,
+    locked: bool,
+
+    #[serde(alias = "UUID")]
+    uuid: String,
+
+    /// only present if requested
+    #[serde(alias = "userID")]
+    user_id: Option<String>,
+}
+
+#[derive(Serialize, Deserialize)]
+struct Thumbnail {
+    // null if original is true
+    timestamp: Option<f64>,
+
+    original: bool,
+    votes: u64,
+    locked: bool,
+
+    #[serde(alias = "UUID")]
+    uuid: String,
+
+    /// only present if requested
+    #[serde(alias = "userID")]
+    user_id: Option<String>,
+}
diff --git a/crates/yt_dlp/src/post_processors/mod.rs b/crates/yt_dlp/src/post_processors/mod.rs
new file mode 100644
index 0000000..65801c2
--- /dev/null
+++ b/crates/yt_dlp/src/post_processors/mod.rs
@@ -0,0 +1,30 @@
+// 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 crate::InfoJson;
+
+pub mod dearrow;
+
+pub trait PostProcessor: std::fmt::Debug + Send {
+    /// Process a [`InfoJson`] object and return the updated one.
+    ///
+    /// # Errors
+    /// If the processing steps failed.
+    fn process(&self, info: InfoJson) -> Result<InfoJson, Error>;
+
+    /// The supported extractors for this post processor
+    fn extractors(&self) -> &'static [&'static str];
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[error("Failed to access a api: {0}")]
+    Get(#[from] reqwest::Error),
+}
diff --git a/crates/yt_dlp/src/progress_hook.rs b/crates/yt_dlp/src/progress_hook.rs
index 7a7628a..43f85e0 100644
--- a/crates/yt_dlp/src/progress_hook.rs
+++ b/crates/yt_dlp/src/progress_hook.rs
@@ -1,3 +1,13 @@
+// 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>.
+
 #[macro_export]
 macro_rules! mk_python_function {
     ($name:ident, $new_name:ident) => {
diff --git a/flake.lock b/flake.lock
index ba25c93..fcf9d4c 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
   "nodes": {
     "nixpkgs": {
       "locked": {
-        "lastModified": 1749809936,
-        "narHash": "sha256-WPGRaj7CKfZukjcpxiacp29uYfMl3S9zFiEsVFv/HWM=",
+        "lastModified": 1750005889,
+        "narHash": "sha256-5Ja4RfAWUqzX1B1MC/mSQzNBsTtXmlW4RQyPqmHVU90=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "ec4c48ddcd5718cc1312f432b800fbbfe63ee2fe",
+        "rev": "0fbc85d348db795d46453097b151c08712b86a84",
         "type": "github"
       },
       "original": {
diff --git a/package/package.nix b/package/package.nix
index c3bc338..5a29fad 100644
--- a/package/package.nix
+++ b/package/package.nix
@@ -8,68 +8,117 @@
 # 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>.
 {
-  ffmpeg,
-  glibc,
   lib,
-  llvmPackages_latest,
-  makeWrapper,
-  mpv-unwrapped,
-  python3,
   rustPlatform,
+  installShellFiles,
+  # buildInputs
+  mpv-unwrapped,
+  python3Packages,
+  ffmpeg,
+  openssl,
+  libffi,
+  # NativeBuildInputs
+  makeWrapper,
+  llvmPackages_latest,
+  glibc,
   sqlite,
-  tree-sitter-yts,
   fd,
-}: let
-  version = "0.1.0";
+  pkg-config,
+  SDL2,
+  # Passthru
+  tree-sitter-yts,
+}:
+rustPlatform.buildRustPackage (finalAttrs: {
+  pname = "yt";
+  inherit
+    ((builtins.fromTOML (builtins.readFile
+          ../Cargo.toml)).workspace.package)
+    version
+    ;
 
-  src = ./..;
+  src = lib.cleanSourceWith {
+    src = lib.cleanSource ./..;
+    filter = name: type:
+      (type == "directory")
+      || (builtins.elem (builtins.baseNameOf name) [
+        "Cargo.toml"
+        "Cargo.lock"
+        "mkdb.sh"
+        "help.str"
+        "raw_error_warning.txt"
+      ])
+      || (lib.strings.hasSuffix ".rs" (builtins.baseNameOf name))
+      || (lib.strings.hasSuffix ".h" (builtins.baseNameOf name))
+      || (lib.strings.hasSuffix ".sql" (builtins.baseNameOf name));
+  };
+
+  nativeBuildInputs = [
+    installShellFiles
+    makeWrapper
+    sqlite
+    fd
+    pkg-config
+  ];
 
   buildInputs = [
-    (python3.withPackages (ps: [ps.yt-dlp]))
+    python3Packages.yt-dlp
     mpv-unwrapped.dev
     ffmpeg
+    openssl
+    libffi
   ];
-in
-  rustPlatform.buildRustPackage {
-    inherit version src buildInputs;
-    pname = "yt";
 
-    nativeBuildInputs = [
-      makeWrapper
-      sqlite
-      fd
-    ];
+  checkInputs = [
+    # Needed for the tests in `libmpv2`
+    SDL2
+  ];
 
-    env = let
-      clang_version =
-        lib.versions.major
-        llvmPackages_latest.clang-unwrapped.version;
-    in {
-      FFMPEG_LOCATION = "${lib.getExe ffmpeg}";
-      PYO3_PYTHON = lib.getExe (python3.withPackages (ps: [ps.yt-dlp]));
+  env = let
+    clang_version =
+      lib.versions.major
+      llvmPackages_latest.clang-unwrapped.version;
+  in {
+    # Needed for the compile time sqlite checks.
+    DATABASE_URL = "sqlite://database.sqlx";
 
-      C_INCLUDE_PATH = "${glibc.dev}/include";
-      DATABASE_URL = "sqlite://target/database.sqlx";
-      LIBCLANG_INCLUDE_PATH = "${llvmPackages_latest.clang-unwrapped.lib}/lib/clang/${clang_version}/include";
-      LIBCLANG_PATH = "${llvmPackages_latest.clang-unwrapped.lib}/lib/libclang.so";
-    };
+    # Required by yt_dlp
+    FFMPEG_LOCATION = "${lib.getExe ffmpeg}";
 
-    doCheck = false;
+    # Needed for the libmpv2.
+    C_INCLUDE_PATH = "${glibc.dev}/include";
+    LIBCLANG_INCLUDE_PATH = "${llvmPackages_latest.clang-unwrapped.lib}/lib/clang/${clang_version}/include";
+    LIBCLANG_PATH = "${llvmPackages_latest.clang-unwrapped.lib}/lib/libclang.so";
+  };
 
-    prePatch = ''
-      bash ./scripts/mkdb.sh
-    '';
+  doCheck = true;
 
-    passthru = {
-      inherit tree-sitter-yts;
-    };
+  prePatch = ''
+    # Generate the sqlite db, so that we can run the comp-time sqlite checks.
+    bash ./scripts/mkdb.sh
+  '';
 
-    cargoLock = {
-      lockFile = ../Cargo.lock;
+  passthru = {
+    inherit tree-sitter-yts;
+  };
+
+  cargoLock = {
+    lockFile = ../Cargo.lock;
+    outputHashes = {
+      "ruff_python_ast-0.0.0" = "sha256-/CVpNBOBpvQhz7X80nUHC2x7ZxxCJH8O0WAABJKEriA=";
+      "rustpython-0.4.0" = "sha256-hsWqLRiqA0pUNFkHPwvu+Myh5a3p/VXotkPq5ZexqaQ=";
+      "rustpython-doc-0.3.0" = "sha256-34ERuLFKzUD9Xmf1zlafe42GLWZfUlw17ejf/NN6yH4=";
     };
+  };
+
+  postInstall = ''
+    installShellCompletion --cmd yt \
+      --bash <(COMPLETE=bash $out/bin/yt) \
+      --fish <(COMPLETE=fish $out/bin/yt) \
+      --zsh <(COMPLETE=zsh $out/bin/yt)
 
-    postInstall = ''
-      wrapProgram $out/bin/yt \
-        --prefix PATH : ${lib.makeBinPath buildInputs}
-    '';
-  }
+    # NOTE: We cannot clear the path, because we need access to the $EDITOR. <2025-04-04>
+    wrapProgram $out/bin/yt \
+      --prefix PATH : ${lib.makeBinPath finalAttrs.buildInputs} \
+      --set YTDLP_NO_PLUGINS 1
+  '';
+})
diff --git a/scripts/mkdb.sh b/scripts/mkdb.sh
index f0c7740..6674841 100755
--- a/scripts/mkdb.sh
+++ b/scripts/mkdb.sh
@@ -11,7 +11,7 @@
 # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 
 root="$(dirname "$0")/.."
-db="$root/target/database.sqlx"
+db="${DATABASE_URL#sqlite://}"
 
 [ -f "$db" ] && rm "$db"
 [ -d "$root/target" ] || mkdir "$root/target"