about summary refs log tree commit diff stats
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-x[-rw-r--r--]contrib/external_commands_script.sh (renamed from crates/bytes/.gitignore)12
-rw-r--r--crates/bytes/Cargo.lock65
-rw-r--r--crates/bytes/src/serde.rs19
-rw-r--r--crates/colors/Cargo.toml (renamed from crates/bytes/Cargo.toml)21
-rw-r--r--crates/colors/src/custom.rs75
-rw-r--r--crates/colors/src/lib.rs97
-rw-r--r--crates/colors/src/list.rs233
-rw-r--r--crates/colors/src/support.rs126
-rw-r--r--crates/fmt/Cargo.toml2
-rw-r--r--crates/libmpv2/Cargo.toml2
-rw-r--r--crates/libmpv2/libmpv2-sys/Cargo.toml2
-rw-r--r--crates/libmpv2/src/mpv.rs28
-rw-r--r--crates/libmpv2/src/mpv/events.rs2
-rw-r--r--crates/libmpv2/src/mpv/protocol.rs2
-rwxr-xr-xcrates/libmpv2/update.sh6
-rw-r--r--crates/yt/Cargo.toml41
-rw-r--r--crates/yt/src/ansi_escape_codes.rs15
-rw-r--r--crates/yt/src/app.rs8
-rw-r--r--crates/yt/src/cache/mod.rs105
-rw-r--r--crates/yt/src/cli.rs417
-rw-r--r--crates/yt/src/commands/cache/implm.rs40
-rw-r--r--crates/yt/src/commands/cache/mod.rs19
-rw-r--r--crates/yt/src/commands/config/implm.rs23
-rw-r--r--crates/yt/src/commands/config/mod.rs (renamed from crates/yt/src/constants.rs)8
-rw-r--r--crates/yt/src/commands/database/implm.rs45
-rw-r--r--crates/yt/src/commands/database/mod.rs41
-rw-r--r--crates/yt/src/commands/download/implm/download/download_options.rs (renamed from crates/yt/src/download/download_options.rs)11
-rw-r--r--crates/yt/src/commands/download/implm/download/mod.rs290
-rw-r--r--crates/yt/src/commands/download/implm/download/progress_hook.rs210
-rw-r--r--crates/yt/src/commands/download/implm/mod.rs55
-rw-r--r--crates/yt/src/commands/download/mod.rs34
-rw-r--r--crates/yt/src/commands/implm.rs38
-rw-r--r--crates/yt/src/commands/mod.rs164
-rw-r--r--crates/yt/src/commands/playlist/implm.rs110
-rw-r--r--crates/yt/src/commands/playlist/mod.rs20
-rw-r--r--crates/yt/src/commands/select/implm/fs_generators/help.str (renamed from crates/yt/src/select/selection_file/help.str)0
-rw-r--r--crates/yt/src/commands/select/implm/fs_generators/help.str.license (renamed from crates/yt/src/select/selection_file/help.str.license)0
-rw-r--r--crates/yt/src/commands/select/implm/fs_generators/mod.rs355
-rw-r--r--crates/yt/src/commands/select/implm/mod.rs52
-rw-r--r--crates/yt/src/commands/select/implm/standalone/add.rs (renamed from crates/yt/src/select/cmds/add.rs)79
-rw-r--r--crates/yt/src/commands/select/implm/standalone/mod.rs132
-rw-r--r--crates/yt/src/commands/select/mod.rs230
-rw-r--r--crates/yt/src/commands/show/implm/mod.rs110
-rw-r--r--crates/yt/src/commands/show/mod.rs30
-rw-r--r--crates/yt/src/commands/status/implm.rs166
-rw-r--r--crates/yt/src/commands/status/mod.rs20
-rw-r--r--crates/yt/src/commands/subscriptions/implm.rs287
-rw-r--r--crates/yt/src/commands/subscriptions/mod.rs84
-rw-r--r--crates/yt/src/commands/update/implm/mod.rs62
-rw-r--r--crates/yt/src/commands/update/implm/updater.rs (renamed from crates/yt/src/update/updater.rs)119
-rw-r--r--crates/yt/src/commands/update/mod.rs27
-rw-r--r--crates/yt/src/commands/videos/implm.rs73
-rw-r--r--crates/yt/src/commands/videos/mod.rs46
-rw-r--r--crates/yt/src/commands/watch/implm/mod.rs244
-rw-r--r--crates/yt/src/commands/watch/implm/playlist_handler/client_messages.rs (renamed from crates/yt/src/watch/playlist_handler/client_messages/mod.rs)44
-rw-r--r--crates/yt/src/commands/watch/implm/playlist_handler/mod.rs225
-rw-r--r--crates/yt/src/commands/watch/mod.rs24
-rw-r--r--crates/yt/src/comments/comment.rs152
-rw-r--r--crates/yt/src/comments/description.rs46
-rw-r--r--crates/yt/src/comments/mod.rs167
-rw-r--r--crates/yt/src/config/default.rs110
-rw-r--r--crates/yt/src/config/definitions.rs67
-rw-r--r--crates/yt/src/config/file_system.rs120
-rw-r--r--crates/yt/src/config/mod.rs178
-rw-r--r--crates/yt/src/config/non_empty_vec.rs83
-rw-r--r--crates/yt/src/config/paths.rs58
-rw-r--r--crates/yt/src/config/support.rs161
-rw-r--r--crates/yt/src/download/mod.rs369
-rw-r--r--crates/yt/src/download/progress_hook.rs198
-rw-r--r--crates/yt/src/main.rs273
-rw-r--r--crates/yt/src/output/mod.rs (renamed from crates/yt/src/comments/output.rs)17
-rw-r--r--crates/yt/src/select/cmds/mod.rs113
-rw-r--r--crates/yt/src/select/duration.rs (renamed from crates/yt/src/select/selection_file/duration.rs)24
-rw-r--r--crates/yt/src/select/mod.rs275
-rw-r--r--crates/yt/src/select/selection_file/mod.rs42
-rw-r--r--crates/yt/src/shared/bytes/error.rs (renamed from crates/bytes/src/error.rs)0
-rw-r--r--crates/yt/src/shared/bytes/mod.rs (renamed from crates/bytes/src/lib.rs)24
-rw-r--r--crates/yt/src/shared/mod.rs11
-rw-r--r--crates/yt/src/status/mod.rs130
-rw-r--r--crates/yt/src/storage/db/extractor_hash.rs220
-rw-r--r--crates/yt/src/storage/db/get/extractor_hash.rs68
-rw-r--r--crates/yt/src/storage/db/get/mod.rs15
-rw-r--r--crates/yt/src/storage/db/get/playlist.rs68
-rw-r--r--crates/yt/src/storage/db/get/subscription.rs65
-rw-r--r--crates/yt/src/storage/db/get/txn_log.rs43
-rw-r--r--crates/yt/src/storage/db/get/video/mod.rs261
-rw-r--r--crates/yt/src/storage/db/insert/maintenance.rs38
-rw-r--r--crates/yt/src/storage/db/insert/mod.rs115
-rw-r--r--crates/yt/src/storage/db/insert/playlist.rs222
-rw-r--r--crates/yt/src/storage/db/insert/subscription.rs128
-rw-r--r--crates/yt/src/storage/db/insert/video/mod.rs610
-rw-r--r--crates/yt/src/storage/db/mod.rs18
-rw-r--r--crates/yt/src/storage/db/playlist/mod.rs59
-rw-r--r--crates/yt/src/storage/db/subscription.rs58
-rw-r--r--crates/yt/src/storage/db/txn_log.rs24
-rw-r--r--crates/yt/src/storage/db/video/comments/display.rs (renamed from crates/yt/src/comments/display.rs)73
-rw-r--r--crates/yt/src/storage/db/video/comments/mod.rs202
-rw-r--r--crates/yt/src/storage/db/video/comments/raw.rs87
-rw-r--r--crates/yt/src/storage/db/video/comments/tests.rs249
-rw-r--r--crates/yt/src/storage/db/video/mod.rs (renamed from crates/yt/src/storage/video_database/mod.rs)205
-rw-r--r--crates/yt/src/storage/migrate/mod.rs56
-rw-r--r--crates/yt/src/storage/migrate/sql/4_Three_to_Four.sql24
-rw-r--r--crates/yt/src/storage/migrate/sql/5_Four_to_Five.sql15
-rw-r--r--crates/yt/src/storage/migrate/sql/6_Five_to_Six.sql12
-rw-r--r--crates/yt/src/storage/mod.rs6
-rw-r--r--crates/yt/src/storage/notify.rs (renamed from crates/yt/src/storage/video_database/notify.rs)4
-rw-r--r--crates/yt/src/storage/subscriptions.rs141
-rw-r--r--crates/yt/src/storage/video_database/downloader.rs130
-rw-r--r--crates/yt/src/storage/video_database/extractor_hash.rs163
-rw-r--r--crates/yt/src/storage/video_database/get/mod.rs307
-rw-r--r--crates/yt/src/storage/video_database/get/playlist/iterator.rs101
-rw-r--r--crates/yt/src/storage/video_database/get/playlist/mod.rs167
-rw-r--r--crates/yt/src/storage/video_database/set/mod.rs333
-rw-r--r--crates/yt/src/storage/video_database/set/playlist.rs101
-rw-r--r--crates/yt/src/subscribe/mod.rs184
-rw-r--r--crates/yt/src/unreachable.rs50
-rw-r--r--crates/yt/src/update/mod.rs204
-rw-r--r--crates/yt/src/version/mod.rs11
-rw-r--r--crates/yt/src/videos/display/format_video.rs94
-rw-r--r--crates/yt/src/videos/display/mod.rs229
-rw-r--r--crates/yt/src/videos/format_video.rs133
-rw-r--r--crates/yt/src/videos/mod.rs213
-rw-r--r--crates/yt/src/watch/mod.rs178
-rw-r--r--crates/yt/src/watch/playlist.rs99
-rw-r--r--crates/yt/src/watch/playlist_handler/mod.rs342
-rw-r--r--crates/yt/src/yt_dlp/mod.rs253
-rw-r--r--crates/yt/tests/_testenv/init.rs136
-rw-r--r--crates/yt/tests/_testenv/mod.rs35
-rw-r--r--crates/yt/tests/_testenv/run.rs183
-rw-r--r--crates/yt/tests/_testenv/util.rs371
-rw-r--r--crates/yt/tests/select/base.rs50
-rw-r--r--crates/yt/tests/select/file.rs31
-rw-r--r--crates/yt/tests/select/mod.rs25
-rw-r--r--crates/yt/tests/select/options.rs51
-rw-r--r--crates/yt/tests/subscriptions/import_export/golden.txt2
-rw-r--r--crates/yt/tests/subscriptions/import_export/golden.txt.license (renamed from crates/bytes/Cargo.lock.license)2
-rw-r--r--crates/yt/tests/subscriptions/import_export/mod.rs35
-rw-r--r--crates/yt/tests/subscriptions/mod.rs12
-rw-r--r--crates/yt/tests/subscriptions/naming_subscriptions/golden.txt2
-rw-r--r--crates/yt/tests/subscriptions/naming_subscriptions/golden.txt.license9
-rw-r--r--crates/yt/tests/subscriptions/naming_subscriptions/mod.rs33
-rw-r--r--crates/yt/tests/tests.rs22
-rw-r--r--crates/yt/tests/videos/downloading.rs52
-rw-r--r--crates/yt/tests/videos/mod.rs11
-rw-r--r--crates/yt/tests/watch/focus_switch.rs53
-rw-r--r--crates/yt/tests/watch/mod.rs135
-rw-r--r--crates/yt_dlp/Cargo.toml15
-rw-r--r--crates/yt_dlp/crates/pyo3-pylogger/.gitignore13
-rw-r--r--crates/yt_dlp/crates/pyo3-pylogger/Cargo.toml31
-rw-r--r--crates/yt_dlp/crates/pyo3-pylogger/LICENSE201
-rw-r--r--crates/yt_dlp/crates/pyo3-pylogger/README.md160
-rw-r--r--crates/yt_dlp/crates/pyo3-pylogger/src/kv.rs127
-rw-r--r--crates/yt_dlp/crates/pyo3-pylogger/src/level.rs43
-rw-r--r--crates/yt_dlp/crates/pyo3-pylogger/src/lib.rs211
-rwxr-xr-xcrates/yt_dlp/crates/pyo3-pylogger/update.sh (renamed from crates/bytes/update.sh)8
-rw-r--r--crates/yt_dlp/examples/main.rs15
-rw-r--r--crates/yt_dlp/src/info_json.rs48
-rw-r--r--crates/yt_dlp/src/lib.rs260
-rw-r--r--crates/yt_dlp/src/logging.rs171
-rw-r--r--crates/yt_dlp/src/options.rs215
-rw-r--r--crates/yt_dlp/src/post_processors/dearrow.rs150
-rw-r--r--crates/yt_dlp/src/post_processors/mod.rs91
-rw-r--r--crates/yt_dlp/src/progress_hook.rs83
-rw-r--r--crates/yt_dlp/src/python_error.rs105
-rwxr-xr-xcrates/yt_dlp/update.sh4
165 files changed, 10377 insertions, 6615 deletions
diff --git a/crates/bytes/.gitignore b/contrib/external_commands_script.sh
index 8876ea6..219eae7 100644..100755
--- a/crates/bytes/.gitignore
+++ b/contrib/external_commands_script.sh
@@ -1,6 +1,8 @@
+#! /usr/bin/env sh
+
 # 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.
@@ -8,4 +10,10 @@
 # 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>.
 
-/target
+riverctl focus-output next
+
+alacritty --title "floating please" --command "$@"
+
+riverctl focus-output next
+
+# vim: ft=sh
diff --git a/crates/bytes/Cargo.lock b/crates/bytes/Cargo.lock
deleted file mode 100644
index b30ba3d..0000000
--- a/crates/bytes/Cargo.lock
+++ /dev/null
@@ -1,65 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "bytes"
-version = "1.0.0"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.86"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.37"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "serde"
-version = "1.0.210"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.210"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "syn"
-version = "2.0.77"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
diff --git a/crates/bytes/src/serde.rs b/crates/bytes/src/serde.rs
deleted file mode 100644
index 4341e32..0000000
--- a/crates/bytes/src/serde.rs
+++ /dev/null
@@ -1,19 +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 serde::{Serialize, Serializer};
-
-use crate::Bytes;
-
-impl Serialize for Bytes {
-    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
-        serializer.serialize_str(self.to_string().as_str())
-    }
-}
diff --git a/crates/bytes/Cargo.toml b/crates/colors/Cargo.toml
index 4439aa8..4edefcf 100644
--- a/crates/bytes/Cargo.toml
+++ b/crates/colors/Cargo.toml
@@ -1,7 +1,8 @@
 # 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
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# Copyright (C) 2025 uutils developers
+# SPDX-License-Identifier: MIT
 #
 # This file is part of Yt.
 #
@@ -9,25 +10,17 @@
 # If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 
 [package]
-name = "bytes"
-description = "Simple byte formatting utilities"
-keywords = []
-categories = []
-version.workspace = true
-edition.workspace = true
+name = "colors"
 authors.workspace = true
 license.workspace = true
+description = "A owo-colors inspired color crate."
+version.workspace = true
+edition.workspace = true
 repository.workspace = true
 rust-version.workspace = true
 publish = false
 
 [dependencies]
-serde.workspace = true
-
-[dev-dependencies]
 
 [lints]
 workspace = true
-
-[package.metadata.docs.rs]
-all-features = true
diff --git a/crates/colors/src/custom.rs b/crates/colors/src/custom.rs
new file mode 100644
index 0000000..fd6b7b3
--- /dev/null
+++ b/crates/colors/src/custom.rs
@@ -0,0 +1,75 @@
+// 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>.
+
+// Taken from <https://github.com/owo-colors/owo-colors/blob/61f8bba2f5f80e9f4fa600fbfdf2c21656f1d523/src/colors/custom.rs>
+// at 2025-07-16T18:05:55 CEST.
+
+const U8_TO_STR: [[u8; 3]; 256] = generate_lookup();
+
+const fn generate_lookup() -> [[u8; 3]; 256] {
+    let mut table = [[0, 0, 0]; 256];
+
+    let mut i = 0;
+    while i < 256 {
+        table[i] = [
+            b'0' + (i / 100) as u8,
+            b'0' + (i / 10 % 10) as u8,
+            b'0' + (i % 10) as u8,
+        ];
+        i += 1;
+    }
+
+    table
+}
+
+#[derive(Clone, Copy)]
+pub(crate) enum Plane {
+    Fg,
+    Bg,
+}
+
+pub(crate) const fn rgb_to_ansi(r: u8, g: u8, b: u8, plane: Plane) -> [u8; 18] {
+    let mut buf = *b"\x1b[p8;2;rrr;ggg;bbb";
+
+    let r = U8_TO_STR[r as usize];
+    let g = U8_TO_STR[g as usize];
+    let b = U8_TO_STR[b as usize];
+
+    // p 2
+    buf[2] = match plane {
+        Plane::Fg => b'3',
+        Plane::Bg => b'4',
+    };
+
+    // r 7
+    buf[7] = r[0];
+    buf[8] = r[1];
+    buf[9] = r[2];
+
+    // g 11
+    buf[11] = g[0];
+    buf[12] = g[1];
+    buf[13] = g[2];
+
+    // b 15
+    buf[15] = b[0];
+    buf[16] = b[1];
+    buf[17] = b[2];
+
+    buf
+}
+
+/// This exists since [`unwrap()`] isn't const-safe (it invokes formatting infrastructure)
+pub(crate) const fn bytes_to_str(bytes: &'static [u8]) -> &'static str {
+    match core::str::from_utf8(bytes) {
+        Ok(o) => o,
+        Err(_e) => panic!("Const parsing &[u8] to a string failed!"),
+    }
+}
diff --git a/crates/colors/src/lib.rs b/crates/colors/src/lib.rs
new file mode 100644
index 0000000..663e19a
--- /dev/null
+++ b/crates/colors/src/lib.rs
@@ -0,0 +1,97 @@
+// 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, Write};
+
+use crate::{
+    list::{elements, methods},
+    support::{CSE, CSI, elements_inner},
+};
+
+pub(crate) mod custom;
+mod list;
+mod support;
+
+#[derive(Debug)]
+pub struct Canvas<I: Display>(I);
+
+impl<I: Display> Colorize for Canvas<I> {
+    fn render_into(self, base: &mut String, use_colors: bool) {
+        write!(base, "{}", self.0).expect("Is written into a string");
+
+        if use_colors {
+            // Reset the color and style, if we used colours.
+            base.write_str(CSI).expect("In-memory write");
+            base.write_str("0").expect("In-memory write");
+            base.write_str(CSE).expect("In-memory write");
+        }
+    }
+}
+
+pub trait IntoCanvas: Display + Sized {
+    fn into_canvas(self) -> Canvas<Self> {
+        Canvas(self)
+    }
+
+    methods! { IntoCanvas }
+}
+
+impl<I: Display> IntoCanvas for I {}
+
+pub trait Colorize: Sized {
+    /// Turn this colorized struct into a string, by writing into the base.
+    fn render_into(self, base: &mut String, use_colors: bool);
+
+    /// Turn this colorized struct into a string for consumption.
+    fn render(self, use_colors: bool) -> String {
+        let mut base = String::new();
+        self.render_into(&mut base, use_colors);
+        base
+    }
+
+    methods! { Colorize }
+}
+
+elements! {}
+
+#[cfg(test)]
+mod tests {
+    use crate::{Colorize, IntoCanvas};
+
+    #[test]
+    fn test_colorize_basic() {
+        let base = "Base".green().render(true);
+        #[rustfmt::skip]
+        let expected = concat!(
+            "\x1b[32m",
+            "Base",
+            "\x1b[0m",
+        );
+
+        assert_eq!(base.as_str(), expected);
+    }
+
+    #[test]
+    fn test_colorize_combo() {
+        let base = "Base".green().on_red().bold().strike_through().render(true);
+
+        #[rustfmt::skip]
+        let expected = concat!(
+            "\x1b[9m",  // strike_through
+            "\x1b[1m",  // bold
+            "\x1b[41m", // on_red
+            "\x1b[32m", // green
+            "Base",
+            "\x1b[0m",
+        );
+
+        assert_eq!(base.as_str(), expected);
+    }
+}
diff --git a/crates/colors/src/list.rs b/crates/colors/src/list.rs
new file mode 100644
index 0000000..35fcb83
--- /dev/null
+++ b/crates/colors/src/list.rs
@@ -0,0 +1,233 @@
+// 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::support::prepend_input;
+
+prepend_input! {
+    crate::support::methods_inner as methods (($tt:tt) -> {$tt}),
+    crate::support::elements_inner as elements,
+
+    <shared_input>
+    {
+        // Colors
+        Black black 30,
+        OnBlack on_black 40,
+
+        Red red 31,
+        OnRed on_red 41,
+
+        Green green 32,
+        OnGreen on_green 42,
+
+        Yellow yellow 33,
+        OnYellow on_yellow 43,
+
+        Blue blue 34,
+        OnBlue on_blue 44,
+
+        Magenta magenta 35,
+        OnMagenta on_magenta 45,
+
+        Cyan cyan 36,
+        OnCyan on_cyan 46,
+
+        White white 37,
+        OnWhite on_white 47,
+
+        Default default 39,
+        OnDefault on_default 49,
+
+        // Bright bright colors
+        BrightBlack bright_black 90,
+        OnBrightBlack on_bright_black 100,
+
+        BrightRed bright_red 91,
+        OnBrightRed on_bright_red 101,
+
+        BrightGreen bright_green 92,
+        OnBrightGreen on_bright_green 102,
+
+        BrightYellow bright_yellow 93,
+        OnBrightYellow on_bright_yellow 103,
+
+        BrightBlue bright_blue 94,
+        OnBrightBlue on_bright_blue 104,
+
+        BrightMagenta bright_magenta 95,
+        OnBrightMagenta on_bright_magenta 105,
+
+        BrightCyan bright_cyan 96,
+        OnBrightCyan on_bright_cyan 106,
+
+        BrightWhite bright_white 97,
+        OnBrightWhite on_bright_white 107,
+
+        // CSS colors
+        // TODO(@bpeetz): Also support background colors with these values. <2025-07-16>
+        AliceBlue alice_blue (240, 248, 255),
+        AntiqueWhite antique_white (250, 235, 215),
+        Aqua aqua (0, 255, 255),
+        Aquamarine aquamarine (127, 255, 212),
+        Azure azure (240, 255, 255),
+        Beige beige (245, 245, 220),
+        Bisque bisque (255, 228, 196),
+        // Black black (0, 0, 0),
+        BlanchedAlmond blanched_almond (255, 235, 205),
+        // Blue blue (0, 0, 255),
+        BlueViolet blue_violet (138, 43, 226),
+        Brown brown (165, 42, 42),
+        BurlyWood burly_wood (222, 184, 135),
+        CadetBlue cadet_blue (95, 158, 160),
+        Chartreuse chartreuse (127, 255, 0),
+        Chocolate chocolate (210, 105, 30),
+        Coral coral (255, 127, 80),
+        CornflowerBlue cornflower_blue (100, 149, 237),
+        Cornsilk cornsilk (255, 248, 220),
+        Crimson crimson (220, 20, 60),
+        DarkBlue dark_blue (0, 0, 139),
+        DarkCyan dark_cyan (0, 139, 139),
+        DarkGoldenRod dark_golden_rod (184, 134, 11),
+        DarkGray dark_gray (169, 169, 169),
+        DarkGrey dark_grey (169, 169, 169),
+        DarkGreen dark_green (0, 100, 0),
+        DarkKhaki dark_khaki (189, 183, 107),
+        DarkMagenta dark_magenta (139, 0, 139),
+        DarkOliveGreen dark_olive_green (85, 107, 47),
+        DarkOrange dark_orange (255, 140, 0),
+        DarkOrchid dark_orchid (153, 50, 204),
+        DarkRed dark_red (139, 0, 0),
+        DarkSalmon dark_salmon (233, 150, 122),
+        DarkSeaGreen dark_sea_green (143, 188, 143),
+        DarkSlateBlue dark_slate_blue (72, 61, 139),
+        DarkSlateGray dark_slate_gray (47, 79, 79),
+        DarkSlateGrey dark_slate_grey (47, 79, 79),
+        DarkTurquoise dark_turquoise (0, 206, 209),
+        DarkViolet dark_violet (148, 0, 211),
+        DeepPink deep_pink (255, 20, 147),
+        DeepSkyBlue deep_sky_blue (0, 191, 255),
+        DimGray dim_gray (105, 105, 105),
+        DimGrey dim_grey (105, 105, 105),
+        DodgerBlue dodger_blue (30, 144, 255),
+        FireBrick fire_brick (178, 34, 34),
+        FloralWhite floral_white (255, 250, 240),
+        ForestGreen forest_green (34, 139, 34),
+        Fuchsia fuchsia (255, 0, 255),
+        Gainsboro gainsboro (220, 220, 220),
+        GhostWhite ghost_white (248, 248, 255),
+        Gold gold (255, 215, 0),
+        GoldenRod golden_rod (218, 165, 32),
+        Gray gray (128, 128, 128),
+        Grey grey (128, 128, 128),
+        // Green green (0, 128, 0),
+        GreenYellow green_yellow (173, 255, 47),
+        HoneyDew honey_dew (240, 255, 240),
+        HotPink hot_pink (255, 105, 180),
+        IndianRed indian_red (205, 92, 92),
+        Indigo indigo (75, 0, 130),
+        Ivory ivory (255, 255, 240),
+        Khaki khaki (240, 230, 140),
+        Lavender lavender (230, 230, 250),
+        LavenderBlush lavender_blush (255, 240, 245),
+        LawnGreen lawn_green (124, 252, 0),
+        LemonChiffon lemon_chiffon (255, 250, 205),
+        LightBlue light_blue (173, 216, 230),
+        LightCoral light_coral (240, 128, 128),
+        LightCyan light_cyan (224, 255, 255),
+        LightGoldenRodYellow light_golden_rod_yellow (250, 250, 210),
+        LightGray light_gray (211, 211, 211),
+        LightGrey light_grey (211, 211, 211),
+        LightGreen light_green (144, 238, 144),
+        LightPink light_pink (255, 182, 193),
+        LightSalmon light_salmon (255, 160, 122),
+        LightSeaGreen light_sea_green (32, 178, 170),
+        LightSkyBlue light_sky_blue (135, 206, 250),
+        LightSlateGray light_slate_gray (119, 136, 153),
+        LightSlateGrey light_slate_grey (119, 136, 153),
+        LightSteelBlue light_steel_blue (176, 196, 222),
+        LightYellow light_yellow (255, 255, 224),
+        Lime lime (0, 255, 0),
+        LimeGreen lime_green (50, 205, 50),
+        Linen linen (250, 240, 230),
+        // Magenta magenta (255, 0, 255),
+        Maroon maroon (128, 0, 0),
+        MediumAquaMarine medium_aqua_marine (102, 205, 170),
+        MediumBlue medium_blue (0, 0, 205),
+        MediumOrchid medium_orchid (186, 85, 211),
+        MediumPurple medium_purple (147, 112, 219),
+        MediumSeaGreen medium_sea_green (60, 179, 113),
+        MediumSlateBlue medium_slate_blue (123, 104, 238),
+        MediumSpringGreen medium_spring_green (0, 250, 154),
+        MediumTurquoise medium_turquoise (72, 209, 204),
+        MediumVioletRed medium_violet_red (199, 21, 133),
+        MidnightBlue midnight_blue (25, 25, 112),
+        MintCream mint_cream (245, 255, 250),
+        MistyRose misty_rose (255, 228, 225),
+        Moccasin moccasin (255, 228, 181),
+        NavajoWhite navajo_white (255, 222, 173),
+        Navy navy (0, 0, 128),
+        OldLace old_lace (253, 245, 230),
+        Olive olive (128, 128, 0),
+        OliveDrab olive_drab (107, 142, 35),
+        Orange orange (255, 165, 0),
+        OrangeRed orange_red (255, 69, 0),
+        Orchid orchid (218, 112, 214),
+        PaleGoldenRod pale_golden_rod (238, 232, 170),
+        PaleGreen pale_green (152, 251, 152),
+        PaleTurquoise pale_turquoise (175, 238, 238),
+        PaleVioletRed pale_violet_red (219, 112, 147),
+        PapayaWhip papaya_whip (255, 239, 213),
+        PeachPuff peach_puff (255, 218, 185),
+        Peru peru (205, 133, 63),
+        Pink pink (255, 192, 203),
+        Plum plum (221, 160, 221),
+        PowderBlue powder_blue (176, 224, 230),
+        Purple purple (128, 0, 128),
+        RebeccaPurple rebecca_purple (102, 51, 153),
+        // Red red (255, 0, 0),
+        RosyBrown rosy_brown (188, 143, 143),
+        RoyalBlue royal_blue (65, 105, 225),
+        SaddleBrown saddle_brown (139, 69, 19),
+        Salmon salmon (250, 128, 114),
+        SandyBrown sandy_brown (244, 164, 96),
+        SeaGreen sea_green (46, 139, 87),
+        SeaShell sea_shell (255, 245, 238),
+        Sienna sienna (160, 82, 45),
+        Silver silver (192, 192, 192),
+        SkyBlue sky_blue (135, 206, 235),
+        SlateBlue slate_blue (106, 90, 205),
+        SlateGray slate_gray (112, 128, 144),
+        SlateGrey slate_grey (112, 128, 144),
+        Snow snow (255, 250, 250),
+        SpringGreen spring_green (0, 255, 127),
+        SteelBlue steel_blue (70, 130, 180),
+        Tan tan (210, 180, 140),
+        Teal teal (0, 128, 128),
+        Thistle thistle (216, 191, 216),
+        Tomato tomato (255, 99, 71),
+        Turquoise turquoise (64, 224, 208),
+        Violet violet (238, 130, 238),
+        Wheat wheat (245, 222, 179),
+        // White white (255, 255, 255),
+        WhiteSmoke white_smoke (245, 245, 245),
+        // Yellow yellow (255, 255, 0),
+        YellowGreen yellow_green (154, 205, 50),
+
+        // Styles
+        Bold bold 1,
+        Dim dim 2,
+        Italic italic 3,
+        Underline underline 4,
+        Blink blink 5,
+        BlinkFast blink_fast 6,
+        Reversed reversed 7,
+        Hidden hidden 8,
+        StrikeThrough strike_through 9,
+    }
+}
diff --git a/crates/colors/src/support.rs b/crates/colors/src/support.rs
new file mode 100644
index 0000000..3c3f87d
--- /dev/null
+++ b/crates/colors/src/support.rs
@@ -0,0 +1,126 @@
+// 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>.
+
+pub(super) const CSI: &str = "\x1b[";
+pub(super) const CSE: &str = "m";
+
+macro_rules! elements_inner {
+    (
+        $(
+            $name:ident $_:ident $number:tt
+        ),*
+        $(,)?
+    ) => {
+        $(
+            #[derive(Debug)]
+            pub struct $name<I: Colorize>(I);
+
+            impl<I: Colorize> Colorize for $name<I> {
+                fn render_into(self, base: &mut String, use_colors: bool) {
+                    elements_inner! {@parse_number $number}
+
+                    if use_colors {
+                        base.write_str(CSI).expect("In-memory write");
+                        base.write_str(NUMBERS).expect("In-memory write");
+                        base.write_str(CSE).expect("In-memory write");
+                    }
+                    self.0.render_into(base, use_colors);
+                    // The canvas is resetting the colours again.
+                }
+            }
+        )*
+    };
+
+    (@parse_number $single:literal) => {
+        const NUMBERS: &str = stringify!($single);
+    };
+    (@parse_number ($red:literal, $green:literal, $blue:literal)) => {
+        const NUMBERS_U8: [u8; 18] = $crate::custom::rgb_to_ansi($red, $green, $blue, $crate::custom::Plane::Fg);
+
+        const NUMBERS: &str = $crate::custom::bytes_to_str(&NUMBERS_U8);
+    }
+}
+pub(super) use elements_inner;
+
+macro_rules! methods_inner {
+    (
+        Colorize
+
+        $(
+            $struct_name:ident $fn_name:ident $_:tt
+        ),*
+        $(,)?
+    ) => {
+        $(
+            fn $fn_name(self) -> $struct_name<Self> {
+                $struct_name(self)
+            }
+        )*
+    };
+    (
+        IntoCanvas
+
+        $(
+            $struct_name:ident $fn_name:ident $_:tt
+        ),*
+        $(,)?
+    ) => {
+        $(
+            fn $fn_name(self) -> $struct_name<Canvas<Self>> {
+                $struct_name(Canvas(self))
+            }
+        )*
+    };
+}
+pub(super) use methods_inner;
+
+macro_rules! prepend_input {
+    (
+        $(
+            $existing_macro_name:path as $new_macro_name:ident $(($macro_rule:tt -> $macro_apply:tt))?
+        ),*
+        $(,)?
+
+        <shared_input>
+        $shared_input:tt
+    ) => {
+        $(
+            prepend_input! {
+                @generate_macro
+                $existing_macro_name as $new_macro_name $(($macro_rule -> $macro_apply))?
+                <shared_input>
+                $shared_input
+            }
+        )*
+    };
+
+    (
+        @generate_macro
+        $existing_macro_name:path as $new_macro_name:ident $((($($macro_rule:tt)*) -> {$($macro_apply:tt)*}))?
+
+        <shared_input>
+        {
+            $(
+                $shared_input:tt
+            )*
+        }
+    ) => {
+        macro_rules! $new_macro_name {
+            ($($($macro_rule)*)?) => {
+                $existing_macro_name! {
+                    $($($macro_apply)*)?
+                    $($shared_input)*
+                }
+            }
+        }
+        pub(super) use $new_macro_name;
+    }
+}
+pub(crate) use prepend_input;
diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml
index f3cf4ad..dd314ab 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.1"
+unicode-width = "0.2.2"
 
 [lints]
 workspace = true
diff --git a/crates/libmpv2/Cargo.toml b/crates/libmpv2/Cargo.toml
index fb2f5bf..67fbfec 100644
--- a/crates/libmpv2/Cargo.toml
+++ b/crates/libmpv2/Cargo.toml
@@ -28,7 +28,7 @@ log.workspace = true
 
 [dev-dependencies]
 crossbeam = "0.8"
-sdl2 = "0.37.0"
+sdl2 = "0.38.0"
 
 [features]
 default = ["protocols", "render"]
diff --git a/crates/libmpv2/libmpv2-sys/Cargo.toml b/crates/libmpv2/libmpv2-sys/Cargo.toml
index 96141d3..0be2c7a 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.72.0" }
+bindgen = { version = "0.72.1" }
diff --git a/crates/libmpv2/src/mpv.rs b/crates/libmpv2/src/mpv.rs
index 29dac8d..d8164c0 100644
--- a/crates/libmpv2/src/mpv.rs
+++ b/crates/libmpv2/src/mpv.rs
@@ -552,21 +552,21 @@ impl Mpv {
     ///
     /// # Examples
     ///
-    /// ```dont_run
-    /// # use libmpv2::{Mpv};
-    /// # use libmpv2::mpv_node::MpvNode;
-    /// # use libmpv2::mpv::errors::Result;
-    /// # use std::collections::HashMap;
-    /// #
-    /// # fn main() -> Result<()> {
-    /// # let mpv = Mpv::new()?;
+    /// ```text
+    ///# use libmpv2::{Mpv};
+    ///# use libmpv2::mpv_node::MpvNode;
+    ///# use libmpv2::mpv::errors::Result;
+    ///# use std::collections::HashMap;
+    ///#
+    ///# fn main() -> Result<()> {
+    ///# let mpv = Mpv::new()?;
     /// mpv.command("loadfile", &["test-data/jellyfish.mp4", "append-play"]).unwrap();
-    /// # let node = mpv.get_property::<MpvNode>("playlist").unwrap();
-    /// # let mut list = node.array().unwrap().collect::<Vec<_>>();
-    /// # let map = list.pop().unwrap().map().unwrap().collect::<HashMap<_, _>>();
-    /// # assert_eq!(map, HashMap::from([(String::from("id"), MpvNode::Int64(1)), (String::from("current"), MpvNode::Flag(true)), (String::from("filename"), MpvNode::String(String::from("test-data/jellyfish.mp4")))]));
-    /// # Ok(())
-    /// # }
+    ///# let node = mpv.get_property::<MpvNode>("playlist").unwrap();
+    ///# let mut list = node.array().unwrap().collect::<Vec<_>>();
+    ///# let map = list.pop().unwrap().map().unwrap().collect::<HashMap<_, _>>();
+    ///# assert_eq!(map, HashMap::from([(String::from("id"), MpvNode::Int64(1)), (String::from("current"), MpvNode::Flag(true)), (String::from("filename"), MpvNode::String(String::from("test-data/jellyfish.mp4")))]));
+    ///# Ok(())
+    ///# }
     /// ```
     pub fn command(&self, name: &str, args: &[&str]) -> Result<()> {
         fn escape(input: &str) -> String {
diff --git a/crates/libmpv2/src/mpv/events.rs b/crates/libmpv2/src/mpv/events.rs
index f10ff6e..9f6324a 100644
--- a/crates/libmpv2/src/mpv/events.rs
+++ b/crates/libmpv2/src/mpv/events.rs
@@ -238,7 +238,7 @@ impl EventContext {
     /// Returns `Some(Err(...))` if there was invalid utf-8, or if either an
     /// `MPV_EVENT_GET_PROPERTY_REPLY`, `MPV_EVENT_SET_PROPERTY_REPLY`, `MPV_EVENT_COMMAND_REPLY`,
     /// or `MPV_EVENT_PROPERTY_CHANGE` event failed, or if `MPV_EVENT_END_FILE` reported an error.
-    pub fn wait_event(&mut self, timeout: f64) -> Option<Result<Event>> {
+    pub fn wait_event(&mut self, timeout: f64) -> Option<Result<Event<'_>>> {
         let event = unsafe { *libmpv2_sys::mpv_wait_event(self.ctx.as_ptr(), timeout) };
 
         // debug!("Got an event from mpv: {:#?}", event);
diff --git a/crates/libmpv2/src/mpv/protocol.rs b/crates/libmpv2/src/mpv/protocol.rs
index ee33411..070fb66 100644
--- a/crates/libmpv2/src/mpv/protocol.rs
+++ b/crates/libmpv2/src/mpv/protocol.rs
@@ -24,7 +24,7 @@ impl Mpv {
     ///
     /// # Panics
     /// Panics if a context already exists
-    pub fn create_protocol_context<T, U>(&self) -> ProtocolContext<T, U>
+    pub fn create_protocol_context<T, U>(&self) -> ProtocolContext<'_, T, U>
     where
         T: RefUnwindSafe,
         U: RefUnwindSafe,
diff --git a/crates/libmpv2/update.sh b/crates/libmpv2/update.sh
index ecd5aa8..e1669a9 100755
--- a/crates/libmpv2/update.sh
+++ b/crates/libmpv2/update.sh
@@ -10,8 +10,4 @@
 # 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>.
 
-cd "$(dirname "$0")" || exit 1
-[ "$1" = "upgrade" ] && cargo upgrade --incompatible
-cargo update
-
-./libmpv2-sys/update.sh "$@"
+"$(dirname "$0")/libmpv2-sys/update.sh" "$@"
diff --git a/crates/yt/Cargo.toml b/crates/yt/Cargo.toml
index c6d8c30..6184eb7 100644
--- a/crates/yt/Cargo.toml
+++ b/crates/yt/Cargo.toml
@@ -24,43 +24,44 @@ rust-version.workspace = true
 publish = false
 
 [dependencies]
-anyhow = "1.0.98"
-blake3 = "1.8.2"
-chrono = { version = "0.4.41", features = ["now"] }
+anyhow = "1.0.100"
+blake3 = { version = "1.8.2", features = ["serde"] }
+chrono = { version = "0.4.42", features = ["now"] }
 chrono-humanize = "0.2.3"
-clap = { version = "4.5.40", features = ["derive"] }
-clap_complete = { version = "4.5.54", features = ["unstable-dynamic"] }
+clap = { version = "4.5.53", features = ["derive"] }
+clap_complete = { version = "4.5.61", features = ["unstable-dynamic"] }
+colors.workspace = true
 futures = "0.3.31"
-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"
-xdg = "3.0.0"
-shlex = "1.3.0"
-bytes.workspace = true
 libmpv2.workspace = true
 log.workspace = true
+notify = { version = "8.2.0", default-features = false }
+regex = "1.12.2"
 serde.workspace = true
 serde_json.workspace = true
+shlex = "1.3.0"
+sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] }
+stderrlog = "0.6.0"
+tempfile = "3.23.0"
+termsize.workspace = true
+tokio-util = { version = "0.7.17", features = ["rt"] }
 tokio.workspace = true
+toml = "0.9.8"
 url.workspace = true
-yt_dlp.workspace = true
-termsize.workspace = true
 uu_fmt.workspace = true
-notify = { version = "8.0.0", default-features = false }
-tokio-util = { version = "0.7.15", features = ["rt"] }
+xdg = "3.0.0"
+yt_dlp.workspace = true
+reqwest = "0.12.24"
 
 [[bin]]
 name = "yt"
 doc = false
 path = "src/main.rs"
 
-[dev-dependencies]
-
 [lints]
 workspace = true
 
+[dev-dependencies]
+pretty_assertions = "1.4.1"
+
 [package.metadata.docs.rs]
 all-features = true
diff --git a/crates/yt/src/ansi_escape_codes.rs b/crates/yt/src/ansi_escape_codes.rs
index 462a126..28a8370 100644
--- a/crates/yt/src/ansi_escape_codes.rs
+++ b/crates/yt/src/ansi_escape_codes.rs
@@ -10,10 +10,10 @@
 
 // see: https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands
 const CSI: &str = "\x1b[";
-pub fn erase_in_display_from_cursor() {
+pub(crate) fn erase_from_cursor_to_bottom() {
     print!("{CSI}0J");
 }
-pub fn cursor_up(number: usize) {
+pub(crate) 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 {
@@ -21,16 +21,9 @@ pub fn cursor_up(number: usize) {
     }
 }
 
-pub fn clear_whole_line() {
+pub(crate) fn clear_whole_line() {
     eprint!("{CSI}2K");
 }
-pub fn move_to_col(x: usize) {
+pub(crate) 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/crates/yt/src/app.rs b/crates/yt/src/app.rs
index 15a9388..3ea12a4 100644
--- a/crates/yt/src/app.rs
+++ b/crates/yt/src/app.rs
@@ -16,13 +16,13 @@ use sqlx::{SqlitePool, sqlite::SqliteConnectOptions};
 use crate::{config::Config, storage::migrate::migrate_db};
 
 #[derive(Debug)]
-pub struct App {
-    pub database: SqlitePool,
-    pub config: Config,
+pub(crate) struct App {
+    pub(crate) database: SqlitePool,
+    pub(crate) config: Config,
 }
 
 impl App {
-    pub async fn new(config: Config, should_migrate_db: bool) -> Result<Self> {
+    pub(crate) async fn new(config: Config, should_migrate_db: bool) -> Result<Self> {
         let options = SqliteConnectOptions::new()
             .filename(&config.paths.database_path)
             .optimize_on_close(true, None)
diff --git a/crates/yt/src/cache/mod.rs b/crates/yt/src/cache/mod.rs
deleted file mode 100644
index 83d5ee0..0000000
--- a/crates/yt/src/cache/mod.rs
+++ /dev/null
@@ -1,105 +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 anyhow::{Context, Result};
-use log::{debug, info};
-use tokio::fs;
-
-use crate::{
-    app::App,
-    storage::video_database::{
-        Video, VideoStatus, VideoStatusMarker, downloader::set_video_cache_path, get,
-    },
-};
-
-async fn invalidate_video(app: &App, video: &Video, hard: bool) -> Result<()> {
-    info!("Invalidating cache of video: '{}'", video.title);
-
-    if hard {
-        if let VideoStatus::Cached {
-            cache_path: path, ..
-        } = &video.status
-        {
-            info!("Removing cached video at: '{}'", path.display());
-            if let Err(err) = fs::remove_file(path).await.map_err(|err| err.kind()) {
-                match err {
-                    std::io::ErrorKind::NotFound => {
-                        // The path is already gone
-                        debug!(
-                            "Not actually removing path: '{}'. It is already gone.",
-                            path.display()
-                        );
-                    }
-                    err => Err(std::io::Error::from(err)).with_context(|| {
-                        format!(
-                            "Failed to delete video ('{}') cache path: '{}'.",
-                            video.title,
-                            path.display()
-                        )
-                    })?,
-                }
-            }
-        }
-    }
-
-    set_video_cache_path(app, &video.extractor_hash, None).await?;
-
-    Ok(())
-}
-
-pub async fn invalidate(app: &App, hard: bool) -> Result<()> {
-    let all_cached_things = get::videos(app, &[VideoStatusMarker::Cached]).await?;
-
-    info!("Got videos to invalidate: '{}'", all_cached_things.len());
-
-    for video in all_cached_things {
-        invalidate_video(app, &video, hard).await?;
-    }
-
-    Ok(())
-}
-
-/// # Panics
-/// Only if internal assertions fail.
-pub async fn maintain(app: &App, all: bool) -> Result<()> {
-    let domain = if all {
-        VideoStatusMarker::ALL.as_slice()
-    } else {
-        &[VideoStatusMarker::Watch, VideoStatusMarker::Cached]
-    };
-
-    let cached_videos = get::videos(app, domain).await?;
-
-    let mut found_focused = 0;
-    for vid in cached_videos {
-        if let VideoStatus::Cached {
-            cache_path: path,
-            is_focused,
-        } = &vid.status
-        {
-            info!("Checking if path ('{}') exists", path.display());
-            if !path.exists() {
-                invalidate_video(app, &vid, false).await?;
-            }
-
-            if *is_focused {
-                found_focused += 1;
-            }
-        }
-    }
-
-    assert!(
-        found_focused <= 1,
-        "Only one video can be focused at a time"
-    );
-
-    Ok(())
-}
diff --git a/crates/yt/src/cli.rs b/crates/yt/src/cli.rs
index 41fadf4..9a24403 100644
--- a/crates/yt/src/cli.rs
+++ b/crates/yt/src/cli.rs
@@ -9,443 +9,50 @@
 // 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::{self, Display, Formatter},
-    path::PathBuf,
-    str::FromStr,
-};
+use std::path::PathBuf;
 
-use anyhow::Context;
-use bytes::Bytes;
-use chrono::NaiveDate;
-use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
-use url::Url;
+use clap::{ArgAction, Parser};
 
-use crate::{
-    select::selection_file::duration::MaybeDuration,
-    storage::video_database::extractor_hash::LazyExtractorHash,
-};
+use crate::commands::Command;
 
 #[derive(Parser, Debug)]
 #[clap(author, about, long_about = None)]
 #[allow(clippy::module_name_repetitions)]
 /// An command line interface to select, download and watch videos
-pub struct CliArgs {
+pub(crate) struct CliArgs {
     #[command(subcommand)]
     /// The subcommand to execute [default: select]
-    pub command: Option<Command>,
+    pub(crate) command: Option<Command>,
 
     /// Show the version and exit
     #[arg(long, short = 'V', action= ArgAction::SetTrue)]
-    pub version: bool,
+    pub(crate) version: bool,
 
     /// Do not perform database migration before starting.
     /// Setting this could cause runtime database access errors.
     #[arg(long, short, action=ArgAction::SetTrue, default_value_t = false)]
-    pub no_migrate_db: bool,
+    pub(crate) no_migrate_db: bool,
 
     /// Display colors [defaults to true, if the config file has no value]
     #[arg(long, short = 'C')]
-    pub color: Option<bool>,
+    pub(crate) color: Option<bool>,
 
     /// Set the path to the videos.db. This overrides the default and the config file.
     #[arg(long, short)]
-    pub db_path: Option<PathBuf>,
+    pub(crate) db_path: Option<PathBuf>,
 
     /// Set the path to the config.toml.
     /// This overrides the default.
     #[arg(long, short)]
-    pub config_path: Option<PathBuf>,
+    pub(crate) config_path: Option<PathBuf>,
 
     /// Increase message verbosity
     #[arg(long="verbose", short = 'v', action = ArgAction::Count)]
-    pub verbosity: u8,
+    pub(crate) verbosity: u8,
 
     /// Silence all output
     #[arg(long, short = 'q')]
-    pub quiet: bool,
-}
-
-#[derive(Subcommand, Debug)]
-pub enum Command {
-    /// Download and cache URLs
-    Download {
-        /// Forcefully re-download all cached videos (i.e. delete the cache path, then download).
-        #[arg(short, long)]
-        force: bool,
-
-        /// The maximum size the download dir should have. Beware that the value must be given in
-        /// bytes.
-        #[arg(short, long, value_parser = byte_parser)]
-        max_cache_size: Option<u64>,
-    },
-
-    /// Select, download and watch in one command.
-    Sedowa {},
-    /// Download and watch in one command.
-    Dowa {},
-
-    /// Work with single videos
-    Videos {
-        #[command(subcommand)]
-        cmd: VideosCommand,
-    },
-
-    /// Watch the already cached (and selected) videos
-    Watch {},
-
-    /// Visualize the current playlist
-    Playlist {
-        /// Linger and display changes
-        #[arg(short, long)]
-        watch: bool,
-    },
-
-    /// Show, which videos have been selected to be watched (and their cache status)
-    Status {},
-
-    /// Show, the configuration options in effect
-    Config {},
-
-    /// Display the comments of the currently playing video
-    Comments {},
-    /// Display the description of the currently playing video
-    Description {},
-
-    /// Manipulate the video cache in the database
-    #[command(visible_alias = "db")]
-    Database {
-        #[command(subcommand)]
-        command: CacheCommand,
-    },
-
-    /// Change the state of videos in the database (the default)
-    Select {
-        #[command(subcommand)]
-        cmd: Option<SelectCommand>,
-    },
-
-    /// Update the video database
-    Update {
-        /// The maximal number of videos to fetch for each subscription.
-        #[arg(short, long)]
-        max_backlog: Option<usize>,
-
-        /// How many subs were already checked.
-        ///
-        /// Only used in the progress display in combination with `--grouped`.
-        #[arg(short, long, hide = true)]
-        current_progress: Option<usize>,
-
-        /// How many subs are to be checked.
-        ///
-        /// Only used in the progress display in combination with `--grouped`.
-        #[arg(short, long, hide = true)]
-        total_number: Option<usize>,
-
-        /// The subscriptions to update
-        subscriptions: Vec<String>,
-
-        /// Perform the updates in blocks.
-        ///
-        /// This works around the memory leaks in the default update invocation.
-        #[arg(
-            short,
-            long,
-            conflicts_with = "total_number",
-            conflicts_with = "current_progress"
-        )]
-        grouped: bool,
-    },
-
-    /// Manipulate subscription
-    #[command(visible_alias = "subs")]
-    Subscriptions {
-        #[command(subcommand)]
-        cmd: SubscriptionCommand,
-    },
-}
-
-fn byte_parser(input: &str) -> Result<u64, anyhow::Error> {
-    Ok(input
-        .parse::<Bytes>()
-        .with_context(|| format!("Failed to parse '{input}' as bytes!"))?
-        .as_u64())
-}
-
-impl Default for Command {
-    fn default() -> Self {
-        Self::Select {
-            cmd: Some(SelectCommand::default()),
-        }
-    }
-}
-
-#[derive(Subcommand, Clone, Debug)]
-pub enum VideosCommand {
-    /// List the videos in the database
-    #[command(visible_alias = "ls")]
-    List {
-        /// An optional search query to limit the results
-        #[arg(action = ArgAction::Append)]
-        search_query: Option<String>,
-
-        /// The number of videos to show
-        #[arg(short, long)]
-        limit: Option<usize>,
-    },
-
-    /// Get detailed information about a video
-    Info {
-        /// The short hash of the video
-        hash: LazyExtractorHash,
-    },
-}
-
-#[derive(Subcommand, Clone, Debug)]
-pub enum SubscriptionCommand {
-    /// Subscribe to an URL
-    Add {
-        #[arg(short, long)]
-        /// The human readable name of the subscription
-        name: Option<String>,
-
-        /// The URL to listen to
-        url: Url,
-    },
-
-    /// Unsubscribe from an URL
-    Remove {
-        /// The human readable name of the subscription
-        name: String,
-    },
-
-    /// Import a bunch of URLs as subscriptions.
-    Import {
-        /// The file containing the URLs. Will use Stdin otherwise.
-        file: Option<PathBuf>,
-
-        /// Remove any previous subscriptions
-        #[arg(short, long)]
-        force: bool,
-    },
-    /// Write all subscriptions in an format understood by `import`
-    Export {},
-
-    /// List all subscriptions
-    List {},
-}
-
-#[derive(Clone, Debug, Args)]
-#[command(infer_subcommands = true)]
-/// Mark the video given by the hash to be watched
-pub struct SharedSelectionCommandArgs {
-    /// The ordering priority (higher means more at the top)
-    #[arg(short, long)]
-    pub priority: Option<i64>,
-
-    /// The subtitles to download (e.g. 'en,de,sv')
-    #[arg(short = 'l', long)]
-    pub subtitle_langs: Option<String>,
-
-    /// The speed to set mpv to
-    #[arg(short, long)]
-    pub speed: Option<f64>,
-
-    /// The short extractor hash
-    pub hash: LazyExtractorHash,
-
-    pub title: Option<String>,
-
-    pub date: Option<OptionalNaiveDate>,
-
-    pub publisher: Option<OptionalPublisher>,
-
-    pub duration: Option<MaybeDuration>,
-
-    pub url: Option<Url>,
-}
-#[derive(Clone, Debug, Copy)]
-pub struct OptionalNaiveDate {
-    pub date: Option<NaiveDate>,
-}
-impl FromStr for OptionalNaiveDate {
-    type Err = anyhow::Error;
-    fn from_str(v: &str) -> Result<Self, Self::Err> {
-        if v == "[No release date]" {
-            Ok(Self { date: None })
-        } else {
-            Ok(Self {
-                date: Some(NaiveDate::from_str(v)?),
-            })
-        }
-    }
-}
-#[derive(Clone, Debug)]
-pub struct OptionalPublisher {
-    pub publisher: Option<String>,
-}
-impl FromStr for OptionalPublisher {
-    type Err = anyhow::Error;
-    fn from_str(v: &str) -> Result<Self, Self::Err> {
-        if v == "[No author]" {
-            Ok(Self { publisher: None })
-        } else {
-            Ok(Self {
-                publisher: Some(v.to_owned()),
-            })
-        }
-    }
-}
-
-#[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>
-pub enum SelectCommand {
-    /// Open a `git rebase` like file to select the videos to watch (the default)
-    File {
-        /// Include done (watched, dropped) videos
-        #[arg(long, short)]
-        done: 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.
-    /// When a playlist is added, the `start` and `stop` arguments can be used to select which
-    /// playlist entries to include.
-    #[command(visible_alias = "a")]
-    Add {
-        urls: Vec<Url>,
-
-        /// Start adding playlist entries at this playlist index (zero based and inclusive)
-        #[arg(short = 's', long)]
-        start: Option<usize>,
-
-        /// Stop adding playlist entries at this playlist index (zero based and inclusive)
-        #[arg(short = 'e', long)]
-        stop: Option<usize>,
-    },
-
-    /// Mark the video given by the hash to be watched
-    #[command(visible_alias = "w")]
-    Watch {
-        #[command(flatten)]
-        shared: SharedSelectionCommandArgs,
-    },
-
-    /// Mark the video given by the hash to be dropped
-    #[command(visible_alias = "d")]
-    Drop {
-        #[command(flatten)]
-        shared: SharedSelectionCommandArgs,
-    },
-
-    /// Mark the video given by the hash as already watched
-    #[command(visible_alias = "wd")]
-    Watched {
-        #[command(flatten)]
-        shared: SharedSelectionCommandArgs,
-    },
-
-    /// Open the video URL in Firefox's `timesinks.youtube` profile
-    #[command(visible_alias = "u")]
-    Url {
-        #[command(flatten)]
-        shared: SharedSelectionCommandArgs,
-    },
-
-    /// Reset the videos status to 'Pick'
-    #[command(visible_alias = "p")]
-    Pick {
-        #[command(flatten)]
-        shared: SharedSelectionCommandArgs,
-    },
-}
-impl Default for SelectCommand {
-    fn default() -> Self {
-        Self::File {
-            done: false,
-            use_last_selection: false,
-        }
-    }
-}
-
-#[derive(Subcommand, Clone, Copy, Debug)]
-pub enum CacheCommand {
-    /// Invalidate all cache entries
-    Invalidate {
-        /// Also delete the cache path
-        #[arg(short = 'f', long)]
-        hard: bool,
-    },
-
-    /// Perform basic maintenance operations on the database.
-    /// This helps recovering from invalid db states after a crash (or force exit via <CTRL-C>).
-    ///
-    /// 1. Check every path for validity (removing all invalid cache entries)
-    #[command(verbatim_doc_comment)]
-    Maintain {
-        /// Check every video (otherwise only the videos to be watched are checked)
-        #[arg(short, long)]
-        all: bool,
-    },
+    pub(crate) quiet: bool,
 }
 
 #[cfg(test)]
diff --git a/crates/yt/src/commands/cache/implm.rs b/crates/yt/src/commands/cache/implm.rs
new file mode 100644
index 0000000..fd0fbce
--- /dev/null
+++ b/crates/yt/src/commands/cache/implm.rs
@@ -0,0 +1,40 @@
+// 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::{
+    app::App,
+    commands::CacheCommand,
+    storage::db::{
+        insert::Operations,
+        video::{Video, VideoStatusMarker},
+    },
+};
+
+use anyhow::Result;
+
+impl CacheCommand {
+    pub(in crate::commands) async fn implm(self, app: &App) -> Result<()> {
+        match self {
+            CacheCommand::Clear {} => {
+                let mut videos = Video::in_states(app, &[VideoStatusMarker::Cached]).await?;
+
+                let mut ops = Operations::new("Cache clear");
+
+                for vid in &mut videos {
+                    vid.remove_download_path(&mut ops);
+                }
+
+                ops.commit(app).await?;
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/crates/yt/src/commands/cache/mod.rs b/crates/yt/src/commands/cache/mod.rs
new file mode 100644
index 0000000..4ed4b40
--- /dev/null
+++ b/crates/yt/src/commands/cache/mod.rs
@@ -0,0 +1,19 @@
+// 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 clap::Subcommand;
+
+mod implm;
+
+#[derive(Debug, Subcommand)]
+pub(super) enum CacheCommand {
+    /// Remove all downloaded video files.
+    Clear {},
+}
diff --git a/crates/yt/src/commands/config/implm.rs b/crates/yt/src/commands/config/implm.rs
new file mode 100644
index 0000000..00c28a9
--- /dev/null
+++ b/crates/yt/src/commands/config/implm.rs
@@ -0,0 +1,23 @@
+// 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::{app::App, commands::config::ConfigCommand};
+
+use anyhow::Result;
+
+impl ConfigCommand {
+    pub(in crate::commands) fn implm(self, app: &App) -> Result<()> {
+        let config_str = toml::to_string(&app.config)?;
+
+        print!("{config_str}");
+
+        Ok(())
+    }
+}
diff --git a/crates/yt/src/constants.rs b/crates/yt/src/commands/config/mod.rs
index 0f5b918..503b4f7 100644
--- a/crates/yt/src/constants.rs
+++ b/crates/yt/src/commands/config/mod.rs
@@ -1,6 +1,5 @@
 // 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
 //
@@ -9,4 +8,9 @@
 // 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 const HELP_STR: &str = include_str!("./select/selection_file/help.str");
+use clap::Parser;
+
+mod implm;
+
+#[derive(Parser, Debug)]
+pub(super) struct ConfigCommand {}
diff --git a/crates/yt/src/commands/database/implm.rs b/crates/yt/src/commands/database/implm.rs
new file mode 100644
index 0000000..07d346b
--- /dev/null
+++ b/crates/yt/src/commands/database/implm.rs
@@ -0,0 +1,45 @@
+// 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::{
+    app::App,
+    commands::DatabaseCommand,
+    storage::db::{
+        insert::{Committable, subscription, video},
+        txn_log::TxnLog,
+    },
+};
+
+use anyhow::Result;
+
+impl DatabaseCommand {
+    pub(in crate::commands) async fn implm(&self, app: &App) -> Result<()> {
+        match self {
+            DatabaseCommand::Log { kind } => match kind {
+                super::OperationType::Video => {
+                    let log = TxnLog::<video::Operation>::get(app).await?;
+                    display_log(&log);
+                }
+                super::OperationType::Subscription => {
+                    let log = TxnLog::<subscription::Operation>::get(app).await?;
+                    display_log(&log);
+                }
+            },
+        }
+
+        Ok(())
+    }
+}
+
+fn display_log<O: Committable>(log: &TxnLog<O>) {
+    for (time, value) in log.inner() {
+        println!("At {time}: {value:?}");
+    }
+}
diff --git a/crates/yt/src/commands/database/mod.rs b/crates/yt/src/commands/database/mod.rs
new file mode 100644
index 0000000..06e3169
--- /dev/null
+++ b/crates/yt/src/commands/database/mod.rs
@@ -0,0 +1,41 @@
+// 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::{self, Display};
+
+use clap::{Subcommand, ValueEnum};
+
+mod implm;
+
+#[derive(Subcommand, Debug)]
+pub(super) enum DatabaseCommand {
+    /// Show the history of operations, in they groups they were committed in.
+    Log {
+        /// What kind of operation to show.
+        #[arg(short, long, default_value_t)]
+        kind: OperationType,
+    },
+}
+
+#[derive(Debug, Clone, Copy, ValueEnum, Default)]
+pub(super) enum OperationType {
+    #[default]
+    Video,
+    Subscription,
+}
+
+impl Display for OperationType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            OperationType::Video => f.write_str("video"),
+            OperationType::Subscription => f.write_str("subscription"),
+        }
+    }
+}
diff --git a/crates/yt/src/download/download_options.rs b/crates/yt/src/commands/download/implm/download/download_options.rs
index 558adfd..15fed7e 100644
--- a/crates/yt/src/download/download_options.rs
+++ b/crates/yt/src/commands/download/implm/download/download_options.rs
@@ -13,11 +13,14 @@ use anyhow::Context;
 use serde_json::{Value, json};
 use yt_dlp::{YoutubeDL, options::YoutubeDLOptions};
 
-use crate::{app::App, storage::video_database::YtDlpOptions};
+use crate::app::App;
 
 use super::progress_hook::wrapped_progress_hook;
 
-pub fn download_opts(app: &App, additional_opts: &YtDlpOptions) -> anyhow::Result<YoutubeDL> {
+pub(crate) fn download_opts(
+    app: &App,
+    subtitle_langs: Option<&String>,
+) -> anyhow::Result<YoutubeDL> {
     YoutubeDLOptions::new()
         .with_progress_hook(wrapped_progress_hook)
         .set("extract_flat", "in_playlist")
@@ -106,8 +109,8 @@ pub fn download_opts(app: &App, additional_opts: &YtDlpOptions) -> anyhow::Resul
         .set(
             "subtitleslangs",
             Value::Array(
-                additional_opts
-                    .subtitle_langs
+                subtitle_langs
+                    .map_or("", String::as_str)
                     .split(',')
                     .map(|val| Value::String(val.to_owned()))
                     .collect::<Vec<_>>(),
diff --git a/crates/yt/src/commands/download/implm/download/mod.rs b/crates/yt/src/commands/download/implm/download/mod.rs
new file mode 100644
index 0000000..ab9de80
--- /dev/null
+++ b/crates/yt/src/commands/download/implm/download/mod.rs
@@ -0,0 +1,290 @@
+// 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 std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration};
+
+use crate::{
+    app::App,
+    commands::download::implm::download::download_options::download_opts,
+    shared::bytes::Bytes,
+    storage::{
+        db::{extractor_hash::ExtractorHash, insert::Operations, video::Video},
+        notify::{wait_for_cache_reduction, wait_for_db_write},
+    },
+    yt_dlp::get_current_cache_allocation,
+};
+
+use anyhow::{Context, Result, bail};
+use log::{debug, error, info, warn};
+use tokio::{select, task::JoinHandle, time};
+use yt_dlp::YoutubeDL;
+
+#[allow(clippy::module_name_repetitions)]
+pub(crate) mod download_options;
+pub(crate) mod progress_hook;
+
+#[derive(Debug)]
+#[allow(clippy::module_name_repetitions)]
+pub(crate) struct CurrentDownload {
+    task_handle: JoinHandle<Result<(PathBuf, Video)>>,
+    yt_dlp: Arc<YoutubeDL>,
+    extractor_hash: ExtractorHash,
+}
+
+impl CurrentDownload {
+    fn new_from_video(app: &App, video: Video) -> Result<Self> {
+        let extractor_hash = video.extractor_hash;
+
+        debug!("Download started: {}", &video.title);
+        let yt_dlp = Arc::new(download_opts(app, video.subtitle_langs.as_ref())?);
+
+        let local_yt_dlp = Arc::clone(&yt_dlp);
+
+        let task_handle = tokio::task::spawn_blocking(move || {
+            let mut result = local_yt_dlp
+                .download(&[video.url.clone()])
+                .with_context(|| format!("Failed to download video: '{}'", video.title))?;
+
+            assert_eq!(result.len(), 1);
+            Ok((result.remove(0), video))
+        });
+
+        Ok(Self {
+            task_handle,
+            yt_dlp,
+            extractor_hash,
+        })
+    }
+
+    fn abort(self) -> Result<()> {
+        debug!("Cancelling download.");
+        self.yt_dlp.close()?;
+
+        Ok(())
+    }
+
+    fn is_finished(&self) -> bool {
+        self.task_handle.is_finished()
+    }
+
+    async fn finalize(self, app: &App) -> Result<()> {
+        let (result, mut video) = self.task_handle.await??;
+
+        let mut ops = Operations::new("Downloader: Set download path");
+        video.set_download_path(&result, &mut ops);
+        ops.commit(app)
+            .await
+            .with_context(|| format!("Failed to commit download of video: '{}'", video.title))?;
+
+        info!(
+            "Video '{}' was downlaoded to path: {}",
+            video.title,
+            result.display()
+        );
+
+        Ok(())
+    }
+}
+
+enum CacheSizeCheck {
+    /// The video can be downloaded
+    Fits,
+
+    /// The video and the current cache size together would exceed the size
+    TooLarge,
+
+    /// The video would not even fit into the empty cache
+    ExceedsMaxCacheSize,
+}
+
+#[derive(Debug)]
+pub(crate) struct Downloader {
+    current_download: Option<CurrentDownload>,
+    video_size_cache: HashMap<ExtractorHash, u64>,
+    printed_warning: bool,
+    cached_cache_allocation: Option<Bytes>,
+}
+
+impl Default for Downloader {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl Downloader {
+    #[must_use]
+    pub(crate) fn new() -> Self {
+        Self {
+            current_download: None,
+            video_size_cache: HashMap::new(),
+            printed_warning: false,
+            cached_cache_allocation: None,
+        }
+    }
+
+    /// Check if enough cache is available.
+    ///
+    /// Will wait for the next cache deletion if not.
+    async fn is_enough_cache_available(
+        &mut self,
+        app: &App,
+        max_cache_size: u64,
+        next_video: &Video,
+    ) -> Result<CacheSizeCheck> {
+        if let Some(cdownload) = &self.current_download {
+            if cdownload.extractor_hash == next_video.extractor_hash {
+                // If the video is already being downloaded it will always fit. Otherwise the
+                // download would not have been started.
+                return Ok(CacheSizeCheck::Fits);
+            }
+        }
+        let cache_allocation = get_current_cache_allocation(app).await?;
+        let video_size = self.get_approx_video_size(next_video)?;
+
+        if video_size >= max_cache_size {
+            error!(
+                "The video '{}' ({}) exceeds the maximum cache size ({})! \
+                 Please set a bigger maximum (`--max-cache-size`) or skip it.",
+                next_video.title,
+                Bytes::new(video_size),
+                Bytes::new(max_cache_size)
+            );
+
+            return Ok(CacheSizeCheck::ExceedsMaxCacheSize);
+        }
+
+        if cache_allocation.as_u64() + video_size >= max_cache_size {
+            if !self.printed_warning {
+                warn!(
+                    "Can't download video: '{}' ({}) as it's too large for the cache ({} of {} allocated). \
+                     Waiting for cache size reduction..",
+                    next_video.title,
+                    Bytes::new(video_size),
+                    &cache_allocation,
+                    Bytes::new(max_cache_size)
+                );
+                self.printed_warning = true;
+
+                // Update this value immediately.
+                // This avoids printing the "Current cache size has changed .." warning below.
+                self.cached_cache_allocation = Some(cache_allocation);
+            }
+
+            if let Some(cca) = self.cached_cache_allocation {
+                if cca != cache_allocation {
+                    // Only print the warning if the display string has actually changed.
+                    // Otherwise, we might confuse the user
+                    if cca.to_string() != cache_allocation.to_string() {
+                        warn!("Current cache size has changed, it's now: '{cache_allocation}'");
+                    }
+                    debug!(
+                        "Cache size has changed: {} -> {}",
+                        cca.as_u64(),
+                        cache_allocation.as_u64()
+                    );
+                    self.cached_cache_allocation = Some(cache_allocation);
+                }
+            } else {
+                unreachable!(
+                    "The `printed_warning` should be false in this case, \
+                    and thus should have already set the `cached_cache_allocation`."
+                );
+            }
+
+            // Wait and hope, that a large video is deleted from the cache.
+            wait_for_cache_reduction(app).await?;
+            Ok(CacheSizeCheck::TooLarge)
+        } else {
+            self.printed_warning = false;
+            Ok(CacheSizeCheck::Fits)
+        }
+    }
+
+    /// The entry point to the Downloader.
+    /// This Downloader will periodically check if the database has changed, and then also
+    /// change which videos it downloads.
+    /// This will run, until the database doesn't contain any watchable videos
+    pub(crate) async fn consume(&mut self, app: Arc<App>, max_cache_size: u64) -> Result<()> {
+        while let Some(next_video) = Video::next_to_download(&app).await? {
+            match self
+                .is_enough_cache_available(&app, max_cache_size, &next_video)
+                .await?
+            {
+                CacheSizeCheck::Fits => (),
+                CacheSizeCheck::TooLarge => continue,
+                CacheSizeCheck::ExceedsMaxCacheSize => bail!("Giving up."),
+            }
+
+            if self.current_download.is_some() {
+                let current_download = self.current_download.take().expect("It is `Some`.");
+
+                if current_download.is_finished() {
+                    // The download is done, finalize it and leave it removed.
+                    current_download.finalize(&app).await?;
+                    continue;
+                }
+
+                if next_video.extractor_hash == current_download.extractor_hash {
+                    // We still want to download the same video.
+                    // reset the taken value
+                    self.current_download = Some(current_download);
+                } else {
+                    info!(
+                        "Noticed, that the next video is not the video being downloaded, replacing it ('{}' vs. '{}')!",
+                        next_video.extractor_hash.as_short_hash(&app).await?,
+                        current_download.extractor_hash.as_short_hash(&app).await?
+                    );
+
+                    // Replace the currently downloading video
+                    current_download
+                        .abort()
+                        .context("Failed to abort last download")?;
+
+                    let new_current_download = CurrentDownload::new_from_video(&app, next_video)?;
+
+                    self.current_download = Some(new_current_download);
+                }
+            } else {
+                info!(
+                    "No video is being downloaded right now, setting it to '{}'",
+                    next_video.title
+                );
+                let new_current_download = CurrentDownload::new_from_video(&app, next_video)?;
+                self.current_download = Some(new_current_download);
+            }
+
+            // We have to continuously check, if the current download is done.
+            // As such we simply wait or recheck on the next write to the db.
+            select! {
+                () = time::sleep(Duration::from_secs(1)) => (),
+                Ok(()) = wait_for_db_write(&app) => (),
+            }
+        }
+
+        info!("Finished downloading!");
+        Ok(())
+    }
+
+    fn get_approx_video_size(&mut self, video: &Video) -> Result<u64> {
+        if let Some(value) = self.video_size_cache.get(&video.extractor_hash) {
+            Ok(*value)
+        } else {
+            let size = video.get_approx_size()?;
+
+            assert_eq!(
+                self.video_size_cache.insert(video.extractor_hash, size),
+                None
+            );
+
+            Ok(size)
+        }
+    }
+}
diff --git a/crates/yt/src/commands/download/implm/download/progress_hook.rs b/crates/yt/src/commands/download/implm/download/progress_hook.rs
new file mode 100644
index 0000000..a414d4a
--- /dev/null
+++ b/crates/yt/src/commands/download/implm/download/progress_hook.rs
@@ -0,0 +1,210 @@
+// 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::{
+    collections::HashSet,
+    io::{Write, stderr},
+    process,
+    sync::{Mutex, atomic::Ordering},
+};
+
+use colors::{Colorize, IntoCanvas};
+use log::{Level, log_enabled};
+use yt_dlp::{json_cast, json_get, wrap_progress_hook};
+
+use crate::{
+    ansi_escape_codes::{clear_whole_line, move_to_col},
+    config::SHOULD_DISPLAY_COLOR,
+    select::duration::MaybeDuration,
+    shared::bytes::Bytes,
+};
+
+macro_rules! json_get_default {
+    ($value:expr, $name:literal, $convert:ident, $default:expr) => {
+        $value.get($name).map_or($default, |v| {
+            if v == &serde_json::Value::Null {
+                $default
+            } else {
+                json_cast!(@log_key $name, v, $convert)
+            }
+        })
+    };
+}
+
+static TITLES: Mutex<Option<HashSet<String>>> = Mutex::new(None);
+
+fn format_bytes(bytes: u64) -> String {
+    let bytes = Bytes::new(bytes);
+    bytes.to_string()
+}
+
+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")
+}
+
+/// # Panics
+/// If expectations fail.
+#[allow(clippy::needless_pass_by_value)]
+pub(crate) 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(());
+    }
+
+    let info_dict = json_get!(input, "info_dict", as_object);
+
+    let get_title = || -> String {
+        match json_get!(info_dict, "ext", as_str) {
+            "vtt" => {
+                format!(
+                    "Subtitles ({})",
+                    json_get_default!(info_dict, "name", as_str, "<No Subtitle Language>")
+                )
+            }
+            "webm" | "mp4" | "mp3" | "m4a" => {
+                json_get_default!(info_dict, "title", as_str, "<No title>").to_owned()
+            }
+            other => panic!("The extension '{other}' is not yet implemented"),
+        }
+    };
+
+    match json_get!(input, "status", as_str) {
+        "downloading" => {
+            let elapsed = json_get_default!(input, "elapsed", as_f64, 0.0);
+            let eta = json_get_default!(input, "eta", as_f64, 0.0);
+            let speed = json_get_default!(input, "speed", as_f64, 0.0);
+
+            let downloaded_bytes = json_get!(input, "downloaded_bytes", as_u64);
+            let (total_bytes, bytes_is_estimate): (u64, &'static str) = {
+                let total_bytes = json_get_default!(input, "total_bytes", as_u64, 0);
+
+                if total_bytes == 0 {
+                    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
+                    let maybe_estimate =
+                        json_get_default!(input, "total_bytes_estimate", as_f64, 0.0) as u64;
+
+                    #[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);
+
+            let should_use_color = SHOULD_DISPLAY_COLOR.load(Ordering::Relaxed);
+
+            let title = get_title();
+
+            eprint!(
+                "{} [{}/{} at {}] -> [{} of {}{} {}] ",
+                (&title).bold().blue().render(should_use_color),
+                MaybeDuration::from_secs_f64(elapsed)
+                    .bold()
+                    .yellow()
+                    .render(should_use_color),
+                MaybeDuration::from_secs_f64(eta)
+                    .bold()
+                    .yellow()
+                    .render(should_use_color),
+                format_speed(speed).bold().green().render(should_use_color),
+                format_bytes(downloaded_bytes)
+                    .bold()
+                    .red()
+                    .render(should_use_color),
+                bytes_is_estimate.bold().red().render(should_use_color),
+                format_bytes(total_bytes)
+                    .bold()
+                    .red()
+                    .render(should_use_color),
+                format!("{percent:.02}%")
+                    .bold()
+                    .cyan()
+                    .render(should_use_color),
+            );
+            stderr().flush()?;
+
+            {
+                let mut titles = TITLES.lock().expect("The lock should work");
+
+                match titles.as_mut() {
+                    Some(titles) => {
+                        titles.insert(title);
+                    }
+                    None => *titles = Some(HashSet::from_iter([title])),
+                }
+            }
+        }
+        "finished" => {
+            let should_use_color = SHOULD_DISPLAY_COLOR.load(Ordering::Relaxed);
+            let title = get_title();
+
+            let has_already_been_printed = {
+                let titles = TITLES.lock().expect("The lock should work");
+
+                match titles.as_ref() {
+                    Some(titles) => titles.contains(&title),
+                    None => false,
+                }
+            };
+
+            if has_already_been_printed {
+                eprintln!("-> Finished downloading.");
+            } else {
+                eprintln!(
+                    "Download of {} already finished.",
+                    title.bold().blue().render(should_use_color)
+                );
+            }
+        }
+        "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(())
+}
+
+wrap_progress_hook!(progress_hook, wrapped_progress_hook);
diff --git a/crates/yt/src/commands/download/implm/mod.rs b/crates/yt/src/commands/download/implm/mod.rs
new file mode 100644
index 0000000..c74a909
--- /dev/null
+++ b/crates/yt/src/commands/download/implm/mod.rs
@@ -0,0 +1,55 @@
+// 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::sync::Arc;
+
+use crate::{
+    app::App,
+    commands::download::DownloadCommand,
+    shared::bytes::Bytes,
+    storage::db::{
+        insert::{Operations, maintenance::clear_stale_downloaded_paths},
+        video::{Video, VideoStatusMarker},
+    },
+};
+
+use anyhow::Result;
+use log::info;
+
+mod download;
+
+impl DownloadCommand {
+    pub(in crate::commands) async fn implm(self, app: Arc<App>) -> Result<()> {
+        let DownloadCommand {
+            force,
+            max_cache_size,
+        } = self;
+
+        let max_cache_size = max_cache_size.unwrap_or(app.config.download.max_cache_size.as_u64());
+        info!("Max cache size: '{}'", Bytes::new(max_cache_size));
+
+        clear_stale_downloaded_paths(&app).await?;
+        if force {
+            let mut all = Video::in_states(&app, &[VideoStatusMarker::Cached]).await?;
+
+            let mut ops = Operations::new("Download: Clear old download paths due to `--force`");
+            for a in &mut all {
+                a.remove_download_path(&mut ops);
+            }
+            ops.commit(&app).await?;
+        }
+
+        download::Downloader::new()
+            .consume(app, max_cache_size)
+            .await?;
+
+        Ok(())
+    }
+}
diff --git a/crates/yt/src/commands/download/mod.rs b/crates/yt/src/commands/download/mod.rs
new file mode 100644
index 0000000..15026ba
--- /dev/null
+++ b/crates/yt/src/commands/download/mod.rs
@@ -0,0 +1,34 @@
+// 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 anyhow::Context;
+use clap::Parser;
+
+use crate::shared::bytes::Bytes;
+
+mod implm;
+
+#[derive(Parser, Debug)]
+pub(super) struct DownloadCommand {
+    /// Forcefully re-download all cached videos (i.e. delete all already downloaded paths, then download).
+    #[arg(short, long)]
+    force: bool,
+
+    /// The maximum size the download dir should have.
+    #[arg(short, long, value_parser = byte_parser)]
+    max_cache_size: Option<u64>,
+}
+
+fn byte_parser(input: &str) -> Result<u64, anyhow::Error> {
+    Ok(input
+        .parse::<Bytes>()
+        .with_context(|| format!("Failed to parse '{input}' as bytes!"))?
+        .as_u64())
+}
diff --git a/crates/yt/src/commands/implm.rs b/crates/yt/src/commands/implm.rs
new file mode 100644
index 0000000..7c60c6a
--- /dev/null
+++ b/crates/yt/src/commands/implm.rs
@@ -0,0 +1,38 @@
+// 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::sync::Arc;
+
+use crate::commands::Command;
+
+use anyhow::Result;
+
+impl Command {
+    pub(crate) async fn implm(self, app: crate::app::App) -> Result<()> {
+        match self {
+            Command::Cache { cmd } => cmd.implm(&app).await?,
+            Command::Config { cmd } => cmd.implm(&app)?,
+            Command::Database { cmd } => cmd.implm(&app).await?,
+            Command::Download { cmd } => cmd.implm(Arc::new(app)).await?,
+            Command::Playlist { cmd } => cmd.implm(&app).await?,
+            Command::Select { cmd } => {
+                cmd.unwrap_or_default().implm(&app).await?;
+            }
+            Command::Show { cmd } => cmd.implm(&app).await?,
+            Command::Status { cmd } => cmd.implm(&app).await?,
+            Command::Subscriptions { cmd } => cmd.implm(&app).await?,
+            Command::Update { cmd } => cmd.implm(&app).await?,
+            Command::Videos { cmd } => cmd.implm(&app).await?,
+            Command::Watch { cmd } => cmd.implm(Arc::new(app)).await?,
+        }
+
+        Ok(())
+    }
+}
diff --git a/crates/yt/src/commands/mod.rs b/crates/yt/src/commands/mod.rs
new file mode 100644
index 0000000..431acef
--- /dev/null
+++ b/crates/yt/src/commands/mod.rs
@@ -0,0 +1,164 @@
+// 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::{ffi::OsStr, thread};
+
+use clap::Subcommand;
+use clap_complete::CompletionCandidate;
+use tokio::runtime::Runtime;
+
+use crate::{
+    app::App,
+    commands::{
+        cache::CacheCommand, config::ConfigCommand, database::DatabaseCommand,
+        download::DownloadCommand, playlist::PlaylistCommand, select::SelectCommand,
+        show::ShowCommand, status::StatusCommand, subscriptions::SubscriptionCommand,
+        update::UpdateCommand, videos::VideosCommand, watch::WatchCommand,
+    },
+    config::Config,
+    storage::db::subscription::Subscriptions,
+};
+
+pub(super) mod implm;
+
+mod cache;
+mod config;
+mod database;
+mod download;
+mod playlist;
+mod select;
+mod show;
+mod status;
+mod subscriptions;
+mod update;
+mod videos;
+mod watch;
+
+#[derive(Subcommand, Debug)]
+#[allow(private_interfaces)] // Only the main `implm` method should be accessible.
+pub(super) enum Command {
+    /// Manipulate the download cache
+    Cache {
+        #[command(subcommand)]
+        cmd: CacheCommand,
+    },
+
+    /// Show, the configuration options in effect.
+    Config {
+        #[command(flatten)]
+        cmd: ConfigCommand,
+    },
+
+    /// Interact with the video database.
+    #[command(visible_alias = "db")]
+    Database {
+        #[command(subcommand)]
+        cmd: DatabaseCommand,
+    },
+
+    /// Download and cache URLs
+    Download {
+        #[command(flatten)]
+        cmd: DownloadCommand,
+    },
+
+    /// Visualize the current playlist
+    Playlist {
+        #[command(flatten)]
+        cmd: PlaylistCommand,
+    },
+
+    /// Change the state of videos in the database (the default)
+    Select {
+        #[command(subcommand)]
+        cmd: Option<SelectCommand>,
+    },
+
+    /// Show things about the currently playing video.
+    Show {
+        #[command(subcommand)]
+        cmd: ShowCommand,
+    },
+
+    /// Show, which videos have been selected to be watched (and their cache status)
+    Status {
+        #[command(flatten)]
+        cmd: StatusCommand,
+    },
+
+    /// Manipulate subscription
+    #[command(visible_alias = "subs")]
+    Subscriptions {
+        #[command(subcommand)]
+        cmd: SubscriptionCommand,
+    },
+
+    /// Update the video database
+    Update {
+        #[command(flatten)]
+        cmd: UpdateCommand,
+    },
+
+    /// Work with single videos
+    Videos {
+        #[command(subcommand)]
+        cmd: VideosCommand,
+    },
+
+    /// Watch the already cached (and selected) videos
+    Watch {
+        #[command(flatten)]
+        cmd: WatchCommand,
+    },
+}
+
+impl Default for Command {
+    fn default() -> Self {
+        Self::Select {
+            cmd: Some(SelectCommand::default()),
+        }
+    }
+}
+
+fn complete_subscription(current: &OsStr) -> Vec<CompletionCandidate> {
+    let mut output = vec![];
+
+    let Some(current_prog) = current.to_str().map(ToOwned::to_owned) else {
+        return output;
+    };
+
+    let Ok(config) = Config::from_config_file(None, None, None) else {
+        return output;
+    };
+
+    let handle = thread::spawn(move || {
+        let Ok(rt) = Runtime::new() else {
+            return output;
+        };
+
+        let Ok(app) = rt.block_on(App::new(config, false)) else {
+            return output;
+        };
+
+        let Ok(all) = rt.block_on(Subscriptions::get(&app)) else {
+            return output;
+        };
+
+        for sub in all.0.into_keys() {
+            if sub.starts_with(&current_prog) {
+                output.push(CompletionCandidate::new(sub));
+            }
+        }
+
+        output
+    });
+
+    handle.join().unwrap_or_default()
+}
diff --git a/crates/yt/src/commands/playlist/implm.rs b/crates/yt/src/commands/playlist/implm.rs
new file mode 100644
index 0000000..603184b
--- /dev/null
+++ b/crates/yt/src/commands/playlist/implm.rs
@@ -0,0 +1,110 @@
+// 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::Write, path::Path};
+
+use crate::{
+    ansi_escape_codes,
+    app::App,
+    commands::playlist::PlaylistCommand,
+    storage::{
+        db::{
+            playlist::Playlist,
+            video::{Video, VideoStatus},
+        },
+        notify::wait_for_db_write,
+    },
+    videos::RenderWithApp,
+};
+
+use anyhow::Result;
+use futures::{TryStreamExt, stream::FuturesOrdered};
+
+impl PlaylistCommand {
+    pub(in crate::commands) async fn implm(self, app: &App) -> Result<()> {
+        let PlaylistCommand { watch } = self;
+
+        let mut previous_output_length = 0;
+        loop {
+            let playlist = Playlist::create(app).await?.videos;
+
+            let output = playlist
+                .into_iter()
+                .map(|video| async move {
+                    let mut output = String::new();
+
+                    let (_, is_focused) = cache_values(&video);
+
+                    if is_focused {
+                        output.push_str("🔻 ");
+                    } else {
+                        output.push_str("  ");
+                    }
+
+                    output.push_str(&video.title_fmt().to_string(app));
+
+                    output.push_str(" (");
+                    output.push_str(&video.parent_subscription_name_fmt().to_string(app));
+                    output.push(')');
+
+                    output.push_str(" [");
+                    output.push_str(&video.duration_fmt().to_string(app));
+
+                    if is_focused {
+                        output.push_str(" (");
+                        if let Some(percent) = video.watch_progress_percent_fmt() {
+                            write!(output, "{}", percent.to_string(app))?;
+                        } else {
+                            write!(output, "{}", video.watch_progress_fmt().to_string(app))?;
+                        }
+
+                        output.push(')');
+                    }
+                    output.push(']');
+
+                    output.push('\n');
+
+                    Ok::<String, anyhow::Error>(output)
+                })
+                .collect::<FuturesOrdered<_>>()
+                .try_collect::<String>()
+                .await?;
+
+            // Delete the previous output
+            ansi_escape_codes::cursor_up(previous_output_length);
+            ansi_escape_codes::erase_from_cursor_to_bottom();
+
+            previous_output_length = output.chars().filter(|ch| *ch == '\n').count();
+
+            print!("{output}");
+
+            if !watch {
+                break;
+            }
+
+            wait_for_db_write(app).await?;
+        }
+
+        Ok(())
+    }
+}
+
+/// Extract the values of the [`VideoStatus::Cached`] value from a Video.
+fn cache_values(video: &Video) -> (&Path, bool) {
+    if let VideoStatus::Cached {
+        cache_path,
+        is_focused,
+    } = &video.status
+    {
+        (cache_path, *is_focused)
+    } else {
+        unreachable!("All of these videos should be cached");
+    }
+}
diff --git a/crates/yt/src/commands/playlist/mod.rs b/crates/yt/src/commands/playlist/mod.rs
new file mode 100644
index 0000000..8d3407d
--- /dev/null
+++ b/crates/yt/src/commands/playlist/mod.rs
@@ -0,0 +1,20 @@
+// 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 clap::Parser;
+
+mod implm;
+
+#[derive(Parser, Debug)]
+pub(super) struct PlaylistCommand {
+    /// Linger and display changes
+    #[arg(short, long)]
+    watch: bool,
+}
diff --git a/crates/yt/src/select/selection_file/help.str b/crates/yt/src/commands/select/implm/fs_generators/help.str
index e3cc347..e3cc347 100644
--- a/crates/yt/src/select/selection_file/help.str
+++ b/crates/yt/src/commands/select/implm/fs_generators/help.str
diff --git a/crates/yt/src/select/selection_file/help.str.license b/crates/yt/src/commands/select/implm/fs_generators/help.str.license
index a0e196c..a0e196c 100644
--- a/crates/yt/src/select/selection_file/help.str.license
+++ b/crates/yt/src/commands/select/implm/fs_generators/help.str.license
diff --git a/crates/yt/src/commands/select/implm/fs_generators/mod.rs b/crates/yt/src/commands/select/implm/fs_generators/mod.rs
new file mode 100644
index 0000000..10da032
--- /dev/null
+++ b/crates/yt/src/commands/select/implm/fs_generators/mod.rs
@@ -0,0 +1,355 @@
+// 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::{
+    collections::HashMap,
+    env,
+    fs::{self, File, OpenOptions},
+    io::{BufRead, BufReader, BufWriter, Read, Write},
+    iter,
+    os::fd::{AsFd, AsRawFd},
+    path::Path,
+};
+
+use crate::{
+    app::App,
+    cli::CliArgs,
+    commands::{
+        Command,
+        select::{
+            SelectCommand, SelectSplitSortKey, SelectSplitSortMode,
+            implm::standalone::{self, handle_select_cmd},
+        },
+    },
+    storage::db::{
+        extractor_hash::ExtractorHash,
+        insert::Operations,
+        video::{Video, VideoStatusMarker},
+    },
+};
+
+use anyhow::{Context, Result, bail};
+use clap::Parser;
+use futures::{TryStreamExt, stream::FuturesOrdered};
+use log::info;
+use shlex::Shlex;
+use tokio::process;
+
+const HELP_STR: &str = include_str!("./help.str");
+
+pub(crate) async fn select_split(
+    app: &App,
+    done: bool,
+    sort_key: SelectSplitSortKey,
+    sort_mode: SelectSplitSortMode,
+) -> Result<()> {
+    let temp_dir = tempfile::Builder::new()
+        .prefix("yt_video_select-")
+        .rand_bytes(6)
+        .tempdir()
+        .context("Failed to get tempdir")?;
+
+    let matching_videos = get_videos(app, done).await?;
+
+    let mut no_author = vec![];
+    let mut author_map = HashMap::new();
+    for video in matching_videos {
+        if let Some(sub) = &video.parent_subscription_name {
+            if author_map.contains_key(sub) {
+                let vec: &mut Vec<_> = author_map
+                    .get_mut(sub)
+                    .expect("This key is set, we checked in the if above");
+
+                vec.push(video);
+            } else {
+                author_map.insert(sub.to_owned(), vec![video]);
+            }
+        } else {
+            no_author.push(video);
+        }
+    }
+
+    let author_map = {
+        let mut temp_vec: Vec<_> = author_map.into_iter().collect();
+
+        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
+    };
+
+    for (index, (name, videos)) in author_map
+        .into_iter()
+        .chain(iter::once((
+            "<No parent subscription>".to_owned(),
+            no_author,
+        )))
+        .enumerate()
+    {
+        let mut file_path = temp_dir.path().join(format!("{index:02}_{name}"));
+        file_path.set_extension("yts");
+
+        let tmp_file = File::create(&file_path)
+            .with_context(|| format!("Falied to create file at: {}", file_path.display()))?;
+
+        write_videos_to_file(app, &tmp_file, &videos)
+            .await
+            .with_context(|| format!("Falied to populate file at: {}", file_path.display()))?;
+    }
+
+    open_editor_at(temp_dir.path()).await?;
+
+    let mut paths = vec![];
+    for maybe_entry in temp_dir
+        .path()
+        .read_dir()
+        .context("Failed to open temp dir for reading")?
+    {
+        let entry = maybe_entry.context("Failed to read entry in temp dir")?;
+
+        if !entry.file_type()?.is_file() {
+            bail!("Found non-file entry: {}", entry.path().display());
+        }
+
+        paths.push(entry.path());
+    }
+
+    paths.sort();
+
+    let mut persistent_file = OpenOptions::new()
+        .read(false)
+        .write(true)
+        .create(true)
+        .truncate(true)
+        .open(&app.config.paths.last_selection_path)
+        .context("Failed to open persistent selection file")?;
+
+    for path in paths {
+        let mut read_file = File::open(path)?;
+
+        let mut buffer = vec![];
+        read_file.read_to_end(&mut buffer)?;
+        persistent_file.write_all(&buffer)?;
+    }
+
+    persistent_file.flush()?;
+    let persistent_file = OpenOptions::new()
+        .read(true)
+        .open(format!(
+            "/proc/self/fd/{}",
+            persistent_file.as_fd().as_raw_fd()
+        ))
+        .context("Failed to re-open persistent file")?;
+
+    let processed = process_file(app, &persistent_file).await?;
+
+    info!("Processed {processed} records.");
+    temp_dir.close().context("Failed to close the temp dir")?;
+    Ok(())
+}
+
+pub(crate) async fn select_file(app: &App, done: bool, use_last_selection: bool) -> Result<()> {
+    let temp_file = tempfile::Builder::new()
+        .prefix("yt_video_select-")
+        .suffix(".yts")
+        .rand_bytes(6)
+        .tempfile()
+        .context("Failed to get tempfile")?;
+
+    if use_last_selection {
+        fs::copy(&app.config.paths.last_selection_path, &temp_file)?;
+    } else {
+        let matching_videos = get_videos(app, done).await?;
+
+        write_videos_to_file(app, temp_file.as_file(), &matching_videos).await?;
+    }
+
+    open_editor_at(temp_file.path()).await?;
+
+    let read_file = OpenOptions::new().read(true).open(temp_file.path())?;
+    fs::copy(temp_file.path(), &app.config.paths.last_selection_path)
+        .context("Failed to persist selection file")?;
+
+    let processed = process_file(app, &read_file).await?;
+    info!("Processed {processed} records.");
+
+    Ok(())
+}
+
+async fn get_videos(app: &App, include_done: bool) -> Result<Vec<Video>> {
+    if include_done {
+        Video::in_states(app, VideoStatusMarker::ALL).await
+    } else {
+        Video::in_states(
+            app,
+            &[
+                VideoStatusMarker::Pick,
+                //
+                VideoStatusMarker::Watch,
+                VideoStatusMarker::Cached,
+            ],
+        )
+        .await
+    }
+}
+
+async fn write_videos_to_file(app: &App, file: &File, videos: &[Video]) -> Result<()> {
+    // Warm-up the cache for the display rendering of the videos.
+    // Otherwise the futures would all try to warm it up at the same time.
+    if let Some(vid) = videos.first() {
+        drop(vid.to_line_display(app, None).await?);
+    }
+
+    let mut edit_file = BufWriter::new(file);
+
+    videos
+        .iter()
+        .map(|vid| vid.to_select_file_display(app))
+        .collect::<FuturesOrdered<_>>()
+        .try_collect::<Vec<String>>()
+        .await?
+        .into_iter()
+        .try_for_each(|line| -> Result<()> {
+            edit_file
+                .write_all(line.as_bytes())
+                .context("Failed to write to `edit_file`")?;
+
+            Ok(())
+        })?;
+
+    edit_file.write_all(HELP_STR.as_bytes())?;
+    edit_file.flush().context("Failed to flush edit file")?;
+
+    Ok(())
+}
+
+async fn process_file(app: &App, file: &File) -> Result<i64> {
+    let mut line_number = 0;
+
+    let mut ops = Operations::new("Select: process file");
+
+    // Fetch all the hashes once, instead of every time we need to process a line.
+    let all_hashes = ExtractorHash::get_all(app).await?;
+
+    let reader = BufReader::new(file);
+    for line in reader.lines() {
+        let line = line.context("Failed to read a line")?;
+
+        if let Some(line) = process_line(&line)? {
+            line_number -= 1;
+
+            // debug!(
+            //     "Parsed command: `{}`",
+            //     line.iter()
+            //         .map(|val| format!("\"{}\"", val))
+            //         .collect::<Vec<String>>()
+            //         .join(" ")
+            // );
+
+            let arg_line = ["yt", "select"]
+                .into_iter()
+                .chain(line.iter().map(String::as_str));
+
+            let args = CliArgs::parse_from(arg_line);
+
+            let Command::Select { cmd } = args
+                .command
+                .expect("This will be some, as we constructed it above.")
+            else {
+                unreachable!("This is checked in the `filter_line` function")
+            };
+
+            match cmd.expect(
+                "This value should always be some \
+                    here, as it would otherwise thrown an error above.",
+            ) {
+                SelectCommand::File { .. } | SelectCommand::Split { .. } => {
+                    bail!("You cannot use `select file` or `select split` recursively.")
+                }
+                SelectCommand::Add { urls, start, stop } => {
+                    Box::pin(standalone::add::add(app, urls, start, stop)).await?;
+                }
+                other => {
+                    let shared = other
+                        .clone()
+                        .into_shared()
+                        .expect("The ones without shared should have been filtered out.");
+
+                    let hash = shared.hash.realize(app, Some(&all_hashes)).await?;
+                    let mut video = hash
+                        .get_with_app(app)
+                        .await
+                        .expect("The hash was already realized, it should therefore exist");
+
+                    handle_select_cmd(app, other, &mut video, Some(line_number), &mut ops).await?;
+                }
+            }
+        }
+    }
+
+    ops.commit(app).await?;
+    Ok(-line_number)
+}
+
+async fn open_editor_at(path: &Path) -> Result<()> {
+    let editor = env::var("EDITOR").unwrap_or("nvim".to_owned());
+
+    let mut nvim = process::Command::new(&editor);
+    nvim.arg(path);
+    let status = nvim
+        .status()
+        .await
+        .with_context(|| format!("Falied to run editor: {editor}"))?;
+
+    if status.success() {
+        Ok(())
+    } else {
+        bail!("Editor ({editor}) exited with error status: {}", status)
+    }
+}
+
+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 split: Vec<_> = {
+            let mut shl = Shlex::new(line);
+            let res = shl.by_ref().collect();
+
+            if shl.had_error {
+                bail!("Failed to parse line '{line}'")
+            }
+
+            assert_eq!(shl.line_no, 1, "A unexpected newline appeared");
+            res
+        };
+
+        assert!(!split.is_empty());
+
+        Ok(Some(split))
+    }
+}
diff --git a/crates/yt/src/commands/select/implm/mod.rs b/crates/yt/src/commands/select/implm/mod.rs
new file mode 100644
index 0000000..f39c77f
--- /dev/null
+++ b/crates/yt/src/commands/select/implm/mod.rs
@@ -0,0 +1,52 @@
+// 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::{app::App, commands::select::SelectCommand, storage::db::insert::Operations};
+
+use anyhow::Result;
+
+mod fs_generators;
+mod standalone;
+
+impl SelectCommand {
+    pub(in crate::commands) async fn implm(self, app: &App) -> Result<()> {
+        match self {
+            SelectCommand::File {
+                done,
+                use_last_selection,
+            } => Box::pin(fs_generators::select_file(app, done, use_last_selection)).await?,
+            SelectCommand::Split {
+                done,
+                sort_key,
+                sort_mode,
+            } => Box::pin(fs_generators::select_split(app, done, sort_key, sort_mode)).await?,
+            SelectCommand::Add { urls, start, stop } => {
+                Box::pin(standalone::add::add(app, urls, start, stop)).await?;
+            }
+            other => {
+                let shared = other
+                    .clone()
+                    .into_shared()
+                    .expect("The ones without shared should have been filtered out.");
+                let hash = shared.hash.realize(app, None).await?;
+                let mut video = hash
+                    .get_with_app(app)
+                    .await
+                    .expect("The hash was already realized, it should therefore exist");
+
+                let mut ops = Operations::new("Main: handle select cmd");
+                standalone::handle_select_cmd(app, other, &mut video, None, &mut ops).await?;
+                ops.commit(app).await?;
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/crates/yt/src/select/cmds/add.rs b/crates/yt/src/commands/select/implm/standalone/add.rs
index 2fff298..2a7db53 100644
--- a/crates/yt/src/select/cmds/add.rs
+++ b/crates/yt/src/commands/select/implm/standalone/add.rs
@@ -10,20 +10,17 @@
 
 use crate::{
     app::App,
-    download::download_options::download_opts,
-    storage::video_database::{
-        self, extractor_hash::ExtractorHash, get::get_all_hashes, set::add_video,
-    },
-    update::video_entry_to_video,
+    storage::db::{extractor_hash::ExtractorHash, insert::Operations, video::Video},
+    yt_dlp::yt_dlp_opts_updating,
 };
 
 use anyhow::{Context, Result, bail};
-use log::{error, warn};
+use log::{debug, error, warn};
 use url::Url;
-use yt_dlp::{YoutubeDL, info_json::InfoJson, json_cast, json_get};
+use yt_dlp::{YoutubeDL, info_json::InfoJson, json_cast, json_get, json_try_get};
 
 #[allow(clippy::too_many_lines)]
-pub(super) async fn add(
+pub(crate) async fn add(
     app: &App,
     urls: Vec<Url>,
     start: Option<usize>,
@@ -31,11 +28,17 @@ pub(super) async fn add(
 ) -> Result<()> {
     for url in urls {
         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}'"))?;
+            let entry = if json_try_get!(entry, "formats", as_array).is_some() {
+                // We assume, that this entry is already processed.
+                debug!("Refusing to re-process entry again");
+                entry
+            } else {
+                let url = json_get!(entry, "url", as_str).parse()?;
+
+                yt_dlp
+                    .extract_info(&url, false, true)
+                    .with_context(|| format!("Failed to fetch entry for url: '{url}'"))?
+            };
 
             add_entry(app, entry).await?;
 
@@ -45,50 +48,46 @@ pub(super) async fn add(
         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)
+            let hashes = ExtractorHash::get_all(app)
                 .await
                 .context("Failed to fetch all video hashes")?;
-            let extractor_hash = blake3::hash(json_get!(entry, "id", as_str).as_bytes());
+
+            let extractor_hash = ExtractorHash::from_info_json(&entry);
             if hashes.contains(&extractor_hash) {
                 error!(
                     "Video '{}'{} is already in the database. Skipped adding it",
-                    ExtractorHash::from_hash(extractor_hash)
-                        .into_short_hash(app)
+                    extractor_hash
+                        .as_short_hash(app)
                         .await
                         .with_context(|| format!(
                             "Failed to format hash of video '{}' as short hash",
-                            entry
-                                .get("url")
-                                .map_or("<Unknown video Url>".to_owned(), ToString::to_string)
+                            json_try_get!(entry, "url", as_str).unwrap_or("<Unknown video Url>")
                         ))?,
-                    entry
-                        .get("title")
-                        .map_or(String::new(), |title| format!(" ('{title}')"))
+                    json_try_get!(entry, "title", as_str)
+                        .map_or(String::new(), |title| format!(" (\"{title}\")"))
                 );
                 return Ok(());
             }
 
-            let video = video_entry_to_video(&entry, None)?;
-            add_video(app, video.clone()).await?;
+            let mut ops = Operations::new("SelectAdd: Video entry to video");
+            let video = Video::from_info_json(&entry, None)?.add(&mut ops)?;
+            ops.commit(app).await?;
 
-            println!("{}", &video.to_line_display(app).await?);
+            println!("{}", &video.to_line_display(app, None).await?);
 
             Ok(())
         }
 
-        let yt_dlp = download_opts(
-            app,
-            &video_database::YtDlpOptions {
-                subtitle_langs: String::new(),
-            },
+        let yt_dlp = yt_dlp_opts_updating(
+            start.unwrap_or(0) + stop.map_or(usize::MAX, |val| val.saturating_add(1)),
         )?;
 
         let entry = yt_dlp
             .extract_info(&url, false, true)
             .with_context(|| format!("Failed to fetch entry for url: '{url}'"))?;
 
-        match entry.get("_type").map(|val| json_cast!(val, as_str)) {
-            Some("Video") => {
+        match json_try_get!(entry, "_type", as_str) {
+            Some("video") => {
                 add_entry(app, entry).await?;
                 if start.is_some() || stop.is_some() {
                     warn!(
@@ -96,12 +95,15 @@ pub(super) async fn add(
                     );
                 }
             }
-            Some("Playlist") => {
-                if let Some(entries) = entry.get("entries") {
-                    let entries = json_cast!(entries, as_array);
+            Some("playlist") => {
+                if let Some(entries) = json_try_get!(entry, "entries", as_array) {
                     let start = start.unwrap_or(0);
                     let stop = stop.unwrap_or(entries.len() - 1);
 
+                    if entries.is_empty() {
+                        bail!("Failed to add playlist, as it is empty (contains no entries).")
+                    }
+
                     let respected_entries =
                         take_vector(entries, start, stop).with_context(|| {
                             format!(
@@ -141,8 +143,7 @@ pub(super) async fn add(
                 }
             }
             other => bail!(
-                "Your URL should point to a video or a playlist, but points to a '{:#?}'",
-                other
+                "Your URL should point to a video or a playlist, but points to a '{other:#?}'"
             ),
         }
     }
@@ -164,7 +165,7 @@ fn take_vector<T>(vector: &[T], start: usize, stop: usize) -> Result<&[T]> {
 
 #[cfg(test)]
 mod test {
-    use crate::select::cmds::add::take_vector;
+    use super::take_vector;
 
     #[test]
     fn test_vector_take() {
diff --git a/crates/yt/src/commands/select/implm/standalone/mod.rs b/crates/yt/src/commands/select/implm/standalone/mod.rs
new file mode 100644
index 0000000..9512e32
--- /dev/null
+++ b/crates/yt/src/commands/select/implm/standalone/mod.rs
@@ -0,0 +1,132 @@
+// 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 crate::{
+    ansi_escape_codes,
+    app::App,
+    commands::select::{SelectCommand, SharedSelectionCommandArgs},
+    storage::db::{
+        insert::{Operations, video::Operation},
+        video::{Priority, Video, VideoStatus},
+    },
+};
+
+use anyhow::{Context, Result, bail};
+
+pub(super) mod add;
+
+pub(crate) async fn handle_select_cmd(
+    app: &App,
+    cmd: SelectCommand,
+    video: &mut Video,
+    line_number: Option<i64>,
+    ops: &mut Operations<Operation>,
+) -> Result<()> {
+    let status = match cmd {
+        SelectCommand::Pick { shared } => Some((VideoStatus::Pick, shared)),
+        SelectCommand::Drop { shared } => Some((VideoStatus::Drop, shared)),
+        SelectCommand::Watched { shared } => Some((VideoStatus::Watched, shared)),
+        SelectCommand::Watch { shared } => {
+            if let VideoStatus::Cached {
+                cache_path,
+                is_focused,
+            } = &video.status
+            {
+                Some((
+                    VideoStatus::Cached {
+                        cache_path: cache_path.to_owned(),
+                        is_focused: *is_focused,
+                    },
+                    shared,
+                ))
+            } else {
+                Some((VideoStatus::Watch, shared))
+            }
+        }
+        SelectCommand::Url { shared } => {
+            let Some(url) = shared.url else {
+                bail!("You need to provide a url to `select url ..`")
+            };
+
+            let mut firefox = std::process::Command::new(app.config.commands.url_opener.first());
+            firefox.args(app.config.commands.url_opener.tail());
+            firefox.arg(url.as_str());
+            let _handle = firefox.spawn().context("Failed to run firefox")?;
+            None
+        }
+        SelectCommand::File { .. } | SelectCommand::Split { .. } | SelectCommand::Add { .. } => {
+            unreachable!("These should have been filtered out")
+        }
+    };
+
+    if let Some((status, shared)) = status {
+        handle_status_change(
+            app,
+            video,
+            shared,
+            line_number,
+            status,
+            line_number.is_none(),
+            ops,
+        )
+        .await?;
+    }
+
+    Ok(())
+}
+
+async fn handle_status_change(
+    app: &App,
+    video: &mut Video,
+    shared: SharedSelectionCommandArgs,
+    line_number: Option<i64>,
+    new_status: VideoStatus,
+    is_single: bool,
+    ops: &mut Operations<Operation>,
+) -> Result<()> {
+    let priority = compute_priority(line_number, shared.priority);
+
+    video.set_status(new_status, ops);
+    if let Some(priority) = priority {
+        video.set_priority(priority, ops);
+    }
+
+    if let Some(subtitle_langs) = shared.subtitle_langs {
+        video.set_subtitle_langs(subtitle_langs, ops);
+    }
+    if let Some(playback_speed) = shared.playback_speed {
+        video.set_playback_speed(playback_speed, ops);
+    }
+
+    if !is_single {
+        ansi_escape_codes::clear_whole_line();
+        ansi_escape_codes::move_to_col(1);
+    }
+
+    eprint!("{}", &video.to_line_display(app, None).await?);
+
+    if is_single {
+        eprintln!();
+    } else {
+        stderr().flush()?;
+    }
+
+    Ok(())
+}
+
+fn compute_priority(line_number: Option<i64>, priority: Option<i64>) -> Option<Priority> {
+    if let Some(pri) = priority {
+        Some(Priority::from(pri))
+    } else {
+        line_number.map(Priority::from)
+    }
+}
diff --git a/crates/yt/src/commands/select/mod.rs b/crates/yt/src/commands/select/mod.rs
new file mode 100644
index 0000000..db69238
--- /dev/null
+++ b/crates/yt/src/commands/select/mod.rs
@@ -0,0 +1,230 @@
+// 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::{self, Display, Formatter},
+    str::FromStr,
+};
+
+use chrono::NaiveDate;
+use clap::{Args, Subcommand, ValueEnum};
+use url::Url;
+
+use crate::{select::duration::MaybeDuration, storage::db::extractor_hash::LazyExtractorHash};
+
+mod implm;
+
+#[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>
+#[allow(private_interfaces)] // Only the main `implm` method should be accessible.
+pub(super) enum SelectCommand {
+    /// Open a `git rebase` like file to select the videos to watch (the default)
+    File {
+        /// Include done (watched, dropped) videos
+        #[arg(long, short)]
+        done: 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.
+    /// When a playlist is added, the `start` and `stop` arguments can be used to select which
+    /// playlist entries to include.
+    #[command(visible_alias = "a")]
+    Add {
+        urls: Vec<Url>,
+
+        /// Start adding playlist entries at this playlist index (zero based and inclusive)
+        #[arg(short = 's', long)]
+        start: Option<usize>,
+
+        /// Stop adding playlist entries at this playlist index (zero based and inclusive)
+        #[arg(short = 'e', long)]
+        stop: Option<usize>,
+    },
+
+    /// Mark the video given by the hash to be watched
+    #[command(visible_alias = "w")]
+    Watch {
+        #[command(flatten)]
+        shared: SharedSelectionCommandArgs,
+    },
+
+    /// Mark the video given by the hash to be dropped
+    #[command(visible_alias = "d")]
+    Drop {
+        #[command(flatten)]
+        shared: SharedSelectionCommandArgs,
+    },
+
+    /// Mark the video given by the hash as already watched
+    #[command(visible_alias = "wd")]
+    Watched {
+        #[command(flatten)]
+        shared: SharedSelectionCommandArgs,
+    },
+
+    /// Open the video URL in your specified command
+    #[command(visible_alias = "u")]
+    Url {
+        #[command(flatten)]
+        shared: SharedSelectionCommandArgs,
+    },
+
+    /// Reset the videos status to 'Pick'
+    #[command(visible_alias = "p")]
+    Pick {
+        #[command(flatten)]
+        shared: SharedSelectionCommandArgs,
+    },
+}
+impl Default for SelectCommand {
+    fn default() -> Self {
+        Self::File {
+            done: false,
+            use_last_selection: false,
+        }
+    }
+}
+
+#[derive(Clone, Debug, Args)]
+#[command(infer_subcommands = true)]
+/// Mark the video given by the hash to be watched
+struct SharedSelectionCommandArgs {
+    /// The ordering priority (higher means more at the top)
+    #[arg(short, long)]
+    priority: Option<i64>,
+
+    /// The subtitles to download (e.g. 'en,de,sv')
+    #[arg(short = 'l', long)]
+    subtitle_langs: Option<String>,
+
+    /// The speed to set mpv to
+    #[arg(short = 's', long)]
+    playback_speed: Option<f64>,
+
+    /// The short extractor hash
+    hash: LazyExtractorHash,
+
+    title: Option<String>,
+
+    date: Option<OptionalNaiveDate>,
+
+    publisher: Option<OptionalPublisher>,
+
+    duration: Option<MaybeDuration>,
+
+    url: Option<Url>,
+}
+
+impl SelectCommand {
+    fn into_shared(self) -> Option<SharedSelectionCommandArgs> {
+        match self {
+            SelectCommand::File { .. }
+            | SelectCommand::Split { .. }
+            | SelectCommand::Add { .. } => None,
+            SelectCommand::Watch { shared }
+            | SelectCommand::Drop { shared }
+            | SelectCommand::Watched { shared }
+            | SelectCommand::Url { shared }
+            | SelectCommand::Pick { shared } => Some(shared),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Copy)]
+struct OptionalNaiveDate {
+    date: Option<NaiveDate>,
+}
+impl FromStr for OptionalNaiveDate {
+    type Err = anyhow::Error;
+    fn from_str(v: &str) -> Result<Self, Self::Err> {
+        if v == "[No release date]" {
+            Ok(Self { date: None })
+        } else {
+            Ok(Self {
+                date: Some(NaiveDate::from_str(v)?),
+            })
+        }
+    }
+}
+#[derive(Clone, Debug)]
+struct OptionalPublisher {
+    publisher: Option<String>,
+}
+impl FromStr for OptionalPublisher {
+    type Err = anyhow::Error;
+    fn from_str(v: &str) -> Result<Self, Self::Err> {
+        if v == "[No author]" {
+            Ok(Self { publisher: None })
+        } else {
+            Ok(Self {
+                publisher: Some(v.to_owned()),
+            })
+        }
+    }
+}
+
+#[derive(Default, ValueEnum, Clone, Copy, Debug)]
+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)]
+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"),
+        }
+    }
+}
diff --git a/crates/yt/src/commands/show/implm/mod.rs b/crates/yt/src/commands/show/implm/mod.rs
new file mode 100644
index 0000000..a2e40fd
--- /dev/null
+++ b/crates/yt/src/commands/show/implm/mod.rs
@@ -0,0 +1,110 @@
+// 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::{
+    fs::{self, OpenOptions},
+    io,
+    process::Command,
+};
+
+use crate::{
+    app::App,
+    commands::ShowCommand,
+    output::{display_fmt_and_less, display_less},
+    storage::db::video::Video,
+};
+
+use anyhow::{Context, Result, anyhow, bail};
+use tempfile::Builder;
+use tokio_util::bytes::Buf;
+
+impl ShowCommand {
+    pub(in crate::commands) async fn implm(&self, app: &App) -> Result<()> {
+        match self {
+            ShowCommand::Description {} => {
+                let description = Video::get_current_description(app).await?;
+
+                display_fmt_and_less(&description)?;
+            }
+            ShowCommand::Comments {} => {
+                let comments = Video::get_current_comments(app).await?;
+
+                display_less(comments.render(app.config.global.display_colors))?;
+            }
+            ShowCommand::Thumbnail {} => {
+                let video = Video::currently_focused(app).await?.ok_or(anyhow!(
+                    "You need to have a current video to display its info"
+                ))?;
+
+                if let Some(url) = video.thumbnail_url {
+                    let response = reqwest::get(url.clone())
+                        .await
+                        .with_context(|| format!("Failed to download thumbnail from url: {url}"))?;
+                    let response = response
+                        .error_for_status()
+                        .context("Failed to download thumbnail")?;
+
+                    let (tmp_path, mut tmp) = {
+                        let file = Builder::new().prefix("yt-thumbnail-download").tempfile()?;
+                        let (_, path) = file.keep()?;
+                        let new_file = OpenOptions::new()
+                            .write(true)
+                            .read(false)
+                            .create(false)
+                            .truncate(true)
+                            .open(&path)?;
+
+                        (path, new_file)
+                    };
+
+                    let mut content = response.bytes().await?.reader();
+                    io::copy(&mut content, &mut tmp)?;
+
+                    let status = Command::new(app.config.commands.image_show.first())
+                        .args(app.config.commands.image_show.tail())
+                        .arg(tmp_path.as_os_str())
+                        .status()
+                        .context("Failed to spawn image show command")?;
+
+                    if !status.success() {
+                        bail!(
+                            "{:?} failed with status: {}",
+                            &app.config.commands.image_show.join(" "),
+                            status
+                        );
+                    }
+
+                    fs::remove_file(&tmp_path).with_context(|| {
+                        format!(
+                            "Failed to cleanup downloaded thumbnail image at: {}",
+                            tmp_path.display()
+                        )
+                    })?;
+                } else {
+                    eprintln!("Current video does not have a thumbnail.");
+                }
+            }
+            ShowCommand::Info {} => {
+                let video = Video::currently_focused(app).await?.ok_or(anyhow!(
+                    "You need to have a current video to display its info"
+                ))?;
+
+                display_less(
+                    video
+                        .to_info_display(app, None)
+                        .await
+                        .context("Failed to format video")?,
+                )?;
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/crates/yt/src/commands/show/mod.rs b/crates/yt/src/commands/show/mod.rs
new file mode 100644
index 0000000..60f2e51
--- /dev/null
+++ b/crates/yt/src/commands/show/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 clap::Subcommand;
+
+mod implm;
+
+#[derive(Subcommand, Debug)]
+pub(super) enum ShowCommand {
+    /// Display the description of the currently playing video
+    Description {},
+
+    /// Display the comments of the currently playing video.
+    Comments {},
+
+    /// Display the thumbnail of the currently playing video.
+    Thumbnail {},
+
+    /// Display general info of the currently playing video.
+    ///
+    /// This is the same as running `yt videos info <hash of current video>`
+    Info {},
+}
diff --git a/crates/yt/src/commands/status/implm.rs b/crates/yt/src/commands/status/implm.rs
new file mode 100644
index 0000000..5832fde
--- /dev/null
+++ b/crates/yt/src/commands/status/implm.rs
@@ -0,0 +1,166 @@
+// 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::time::Duration;
+
+use crate::{
+    app::App,
+    commands::status::StatusCommand,
+    select::duration::MaybeDuration,
+    shared::bytes::Bytes,
+    storage::db::{
+        subscription::Subscriptions,
+        video::{Video, VideoStatusMarker},
+    },
+    yt_dlp::get_current_cache_allocation,
+};
+
+use anyhow::{Context, Result};
+
+macro_rules! get {
+    ($videos:expr, $status:ident) => {
+        $videos
+            .iter()
+            .filter(|vid| vid.status.as_marker() == VideoStatusMarker::$status)
+            .count()
+    };
+
+    (@collect $videos:expr, $status:ident) => {
+        $videos
+            .iter()
+            .filter(|vid| vid.status.as_marker() == VideoStatusMarker::$status)
+            .collect()
+    };
+}
+
+impl StatusCommand {
+    pub(in crate::commands) async fn implm(self, app: &App) -> Result<()> {
+        let StatusCommand { format } = self;
+
+        let all_videos = Video::in_states(app, VideoStatusMarker::ALL).await?;
+
+        // lengths
+        let picked_videos_len = get!(all_videos, Pick);
+
+        let watch_videos_len = get!(all_videos, Watch);
+        let cached_videos_len = get!(all_videos, Cached);
+        let watched_videos_len = get!(all_videos, Watched);
+        let watched_videos: Vec<_> = get!(@collect all_videos, Watched);
+
+        let drop_videos_len = get!(all_videos, Drop);
+        let dropped_videos_len = get!(all_videos, Dropped);
+
+        let subscriptions = Subscriptions::get(app).await?;
+        let active_subscriptions_len = subscriptions
+            .0
+            .iter()
+            .filter(|(_, sub)| sub.is_active)
+            .count();
+        let subscriptions_len = subscriptions.0.len();
+
+        let watchtime_status = {
+            let total_watch_time_raw = watched_videos
+                .iter()
+                .fold(Duration::default(), |acc, vid| acc + vid.watch_progress);
+
+            // Most things are watched at a speed of s (which is defined in the config file).
+            // Thus
+            //      y = x * s -> y / s = x
+            let total_watch_time = Duration::from_secs_f64(
+                (total_watch_time_raw.as_secs_f64()) / app.config.select.playback_speed,
+            );
+
+            let speed = app.config.select.playback_speed;
+
+            // Do not print the adjusted time, if the user has keep the speed level at 1.
+            #[allow(clippy::float_cmp)]
+            if speed == 1.0 {
+                format!(
+                    "Total Watchtime: {}\n",
+                    MaybeDuration::from_std(total_watch_time_raw)
+                )
+            } else {
+                format!(
+                    "Total Watchtime: {} (at {speed} speed: {})\n",
+                    MaybeDuration::from_std(total_watch_time_raw),
+                    MaybeDuration::from_std(total_watch_time),
+                )
+            }
+        };
+
+        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: Bytes = get_current_cache_allocation(app)
+            .await
+            .context("Failed to get current cache allocation")?;
+
+        if let Some(fmt) = format {
+            let output = fmt
+                .replace(
+                    "{picked_videos_len}",
+                    picked_videos_len.to_string().as_str(),
+                )
+                .replace("{watch_videos_len}", watch_videos_len.to_string().as_str())
+                .replace(
+                    "{cached_videos_len}",
+                    cached_videos_len.to_string().as_str(),
+                )
+                .replace(
+                    "{watched_videos_len}",
+                    watched_videos_len.to_string().as_str(),
+                )
+                .replace("{watch_rate}", watch_rate.to_string().as_str())
+                .replace("{drop_videos_len}", drop_videos_len.to_string().as_str())
+                .replace(
+                    "{dropped_videos_len}",
+                    dropped_videos_len.to_string().as_str(),
+                )
+                .replace("{watchtime_status}", watchtime_status.to_string().as_str())
+                .replace(
+                    "{subscriptions_len}",
+                    subscriptions_len.to_string().as_str(),
+                )
+                .replace(
+                    "{active_subscriptions_len}",
+                    active_subscriptions_len.to_string().as_str(),
+                )
+                .replace("{cache_usage}", cache_usage.to_string().as_str());
+
+            print!("{output}");
+        } else {
+            println!(
+                "\
+Picked   Videos: {picked_videos_len}
+
+Watch    Videos: {watch_videos_len}
+Cached   Videos: {cached_videos_len}
+Watched  Videos: {watched_videos_len} (watch rate: {watch_rate:.2} %)
+
+Drop     Videos: {drop_videos_len}
+Dropped  Videos: {dropped_videos_len}
+
+{watchtime_status}
+
+  Subscriptions: {subscriptions_len} ({active_subscriptions_len} active)
+    Cache usage: {cache_usage}"
+            );
+        }
+
+        Ok(())
+    }
+}
diff --git a/crates/yt/src/commands/status/mod.rs b/crates/yt/src/commands/status/mod.rs
new file mode 100644
index 0000000..4a8dee7
--- /dev/null
+++ b/crates/yt/src/commands/status/mod.rs
@@ -0,0 +1,20 @@
+// 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 clap::Parser;
+
+mod implm;
+
+#[derive(Parser, Debug)]
+pub(super) struct StatusCommand {
+    /// Which format to use
+    #[arg(short, long)]
+    format: Option<String>,
+}
diff --git a/crates/yt/src/commands/subscriptions/implm.rs b/crates/yt/src/commands/subscriptions/implm.rs
new file mode 100644
index 0000000..1e2e545
--- /dev/null
+++ b/crates/yt/src/commands/subscriptions/implm.rs
@@ -0,0 +1,287 @@
+// 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::str::FromStr;
+
+use crate::{
+    app::App,
+    commands::subscriptions::{SubscriptionCommand, SubscriptionStatus},
+    storage::db::{
+        insert::{Operations, subscription::Operation},
+        subscription::{Subscription, Subscriptions, check_url},
+    },
+};
+
+use anyhow::{Context, Result, bail};
+use log::{error, warn};
+use tokio::{
+    fs::File,
+    io::{AsyncBufRead, AsyncBufReadExt, BufReader, stdin},
+};
+use url::Url;
+use yt_dlp::{json_cast, json_get, json_try_get, options::YoutubeDLOptions};
+
+impl SubscriptionCommand {
+    pub(in crate::commands) async fn implm(self, app: &App) -> Result<()> {
+        match self {
+            SubscriptionCommand::Add {
+                name,
+                url,
+                no_check,
+            } => {
+                let mut ops = Operations::new("main: subscribe");
+                subscribe(app, name, url, no_check, &mut ops)
+                    .await
+                    .context("Failed to add a subscription")?;
+                ops.commit(app).await?;
+            }
+            SubscriptionCommand::Remove { name } => {
+                let mut present_subscriptions = Subscriptions::get(app).await?;
+
+                let mut ops = Operations::new("Subscribe: unsubscribe");
+                if let Some(subscription) = present_subscriptions.0.remove(&name) {
+                    subscription.remove(&mut ops);
+                } else {
+                    bail!("Couldn't find subscription: '{}'", &name);
+                }
+                ops.commit(app)
+                    .await
+                    .with_context(|| format!("Failed to unsubscribe from {name:?}"))?;
+            }
+            SubscriptionCommand::SetStatus { name, new_status } => {
+                let mut present_subscriptions = Subscriptions::get(app).await?;
+
+                let mut ops = Operations::new("Subscribe: Set Status");
+                if let Some(subscription) = present_subscriptions.0.remove(&name) {
+                    subscription.set_is_active(
+                        match new_status {
+                            SubscriptionStatus::Active => true,
+                            SubscriptionStatus::Inactive => false,
+                        },
+                        &mut ops,
+                    );
+                } else {
+                    bail!("Couldn't find subscription: '{}'", &name);
+                }
+                ops.commit(app)
+                    .await
+                    .with_context(|| format!("Failed to change status of {name:?}"))?;
+            }
+            SubscriptionCommand::List { active } => {
+                let all_subs = Subscriptions::get(app).await?;
+
+                let all_subs = if active {
+                    all_subs.remove_inactive()
+                } else {
+                    all_subs
+                };
+
+                for (key, val) in all_subs.0 {
+                    println!(
+                        "{}: '{}' ({})",
+                        key,
+                        val.url,
+                        if val.is_active { "active" } else { "inactive" }
+                    );
+                }
+            }
+            SubscriptionCommand::Export {} => {
+                let all_subs = Subscriptions::get(app).await?;
+                for val in all_subs.0.values() {
+                    println!("{}", val.url);
+                }
+            }
+            SubscriptionCommand::Import {
+                file,
+                force,
+                no_check,
+            } => {
+                if let Some(file) = file {
+                    let f = File::open(file).await?;
+
+                    import(app, BufReader::new(f), force, no_check).await?;
+                } else {
+                    import(app, BufReader::new(stdin()), force, no_check).await?;
+                }
+            }
+        }
+
+        Ok(())
+    }
+}
+
+async fn import<W: AsyncBufRead + AsyncBufReadExt + Unpin>(
+    app: &App,
+    reader: W,
+    force: bool,
+    no_check: bool,
+) -> Result<()> {
+    let mut ops = Operations::new("SubscribeImport: init");
+
+    let all = Subscriptions::get(app).await?;
+    if force {
+        all.remove(&mut ops);
+    }
+    ops.commit(app).await?;
+    let mut ops = Operations::new("SubscribeImport: after all subs remove");
+
+    let mut lines = reader.lines();
+    while let Some(line) = lines.next_line().await? {
+        let url =
+            Url::from_str(&line).with_context(|| format!("Failed to parse '{line}' as url"))?;
+
+        match subscribe(app, None, url, no_check, &mut ops)
+            .await
+            .with_context(|| format!("Failed to subscribe to: '{line}'"))
+        {
+            Ok(()) => (),
+            Err(err) => eprintln!(
+                "Error while subscribing to '{}': '{}'",
+                line,
+                err.source().expect("Should have a source")
+            ),
+        }
+    }
+    ops.commit(app).await?;
+
+    Ok(())
+}
+
+async fn subscribe(
+    app: &App,
+    name: Option<String>,
+    url: Url,
+    no_check: bool,
+    ops: &mut Operations<Operation>,
+) -> Result<()> {
+    if !(url.as_str().ends_with("videos")
+        || url.as_str().ends_with("streams")
+        || url.as_str().ends_with("shorts")
+        || url.as_str().ends_with("videos/")
+        || url.as_str().ends_with("streams/")
+        || url.as_str().ends_with("shorts/"))
+        && url.as_str().contains("youtube.com")
+    {
+        warn!(
+            "Your youtube url does not seem like it actually tracks a channels playlist \
+            (videos, streams, shorts). Adding subscriptions for each of them..."
+        );
+
+        let url = Url::parse(&(url.as_str().to_owned() + "/"))
+            .expect("This was an url, it should stay one");
+
+        let (videos, streams, shorts) = if let Some(name) = name {
+            (
+                Some(name.clone() + " {Videos}"),
+                Some(name.clone() + " {Streams}"),
+                Some(name.clone() + " {Shorts}"),
+            )
+        } else {
+            (None, None, None)
+        };
+
+        let _ = actual_subscribe(
+            app,
+            videos,
+            url.join("videos/").expect("See above."),
+            no_check,
+            ops,
+        )
+        .await
+        .map_err(|err| {
+            error!("Failed to subscribe to the '{}' variant: {err}", "{Videos}");
+        });
+
+        let _ = actual_subscribe(
+            app,
+            streams,
+            url.join("streams/").expect("See above."),
+            no_check,
+            ops,
+        )
+        .await
+        .map_err(|err| {
+            error!(
+                "Failed to subscribe to the '{}' variant: {err}",
+                "{Streams}"
+            );
+        });
+
+        let _ = actual_subscribe(
+            app,
+            shorts,
+            url.join("shorts/").expect("See above."),
+            no_check,
+            ops,
+        )
+        .await
+        .map_err(|err| {
+            error!("Failed to subscribe to the '{}' variant: {err}", "{Shorts}");
+        });
+    } else {
+        actual_subscribe(app, name, url, no_check, ops).await?;
+    }
+
+    Ok(())
+}
+
+async fn actual_subscribe(
+    app: &App,
+    name: Option<String>,
+    url: Url,
+    no_check: bool,
+    ops: &mut Operations<Operation>,
+) -> Result<()> {
+    if !no_check && !check_url(url.clone()).await? {
+        bail!("The url ('{}') does not represent a playlist!", &url)
+    }
+
+    let name = if let Some(name) = name {
+        name
+    } else {
+        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 json_try_get!(info, "_type", as_str) == Some("playlist") {
+            json_get!(info, "title", as_str).to_owned()
+        } else {
+            bail!("The url ('{}') does not represent a playlist!", &url)
+        }
+    };
+
+    let present_subscriptions = Subscriptions::get(app).await?;
+
+    if let Some(subs) = present_subscriptions.0.get(&name) {
+        bail!(
+            "The subscription '{}' could not be added, \
+                as another one with the same name ('{}') already exists. \
+                It points to the Url: '{}'",
+            name,
+            name,
+            subs.url
+        );
+    }
+
+    let sub = Subscription {
+        name,
+        url,
+        is_active: true,
+    };
+
+    sub.add(ops);
+
+    Ok(())
+}
diff --git a/crates/yt/src/commands/subscriptions/mod.rs b/crates/yt/src/commands/subscriptions/mod.rs
new file mode 100644
index 0000000..6a16a9a
--- /dev/null
+++ b/crates/yt/src/commands/subscriptions/mod.rs
@@ -0,0 +1,84 @@
+// 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::path::PathBuf;
+
+use clap::{Subcommand, ValueEnum};
+use clap_complete::ArgValueCompleter;
+use url::Url;
+
+use crate::commands::complete_subscription;
+
+mod implm;
+
+#[derive(Subcommand, Clone, Debug)]
+pub(super) enum SubscriptionCommand {
+    /// Subscribe to an URL
+    Add {
+        #[arg(short, long)]
+        /// The human readable name of the subscription
+        name: Option<String>,
+
+        /// The URL to listen to
+        url: Url,
+
+        /// Don't check, whether the URL actually points to something yt understands.
+        #[arg(long, default_value_t = false)]
+        no_check: bool,
+    },
+
+    /// Unsubscribe from an URL
+    Remove {
+        /// The human readable name of the subscription
+        #[arg(add = ArgValueCompleter::new(complete_subscription))]
+        name: String,
+    },
+
+    /// Change the status of an subscription.
+    ///
+    /// An active subscription will be updated in `yt update`, while an inactive one will not.
+    SetStatus {
+        /// The human readable name of the subscription
+        #[arg(add = ArgValueCompleter::new(complete_subscription))]
+        name: String,
+
+        /// What should this subscription be considered now?
+        new_status: SubscriptionStatus,
+    },
+
+    /// Import a bunch of URLs as subscriptions.
+    Import {
+        /// The file containing the URLs. Will use Stdin otherwise.
+        file: Option<PathBuf>,
+
+        /// Remove any previous subscriptions
+        #[arg(short, long)]
+        force: bool,
+
+        /// Don't check, whether the URLs actually point to something yt understands.
+        #[arg(long, default_value_t = false)]
+        no_check: bool,
+    },
+    /// Write all subscriptions in an format understood by `import`
+    Export {},
+
+    /// List all subscriptions
+    List {
+        /// Only show active subscriptions
+        #[arg(short, long, default_value_t = false)]
+        active: bool,
+    },
+}
+
+#[derive(ValueEnum, Debug, Clone, Copy)]
+pub(in crate::commands) enum SubscriptionStatus {
+    Active,
+    Inactive,
+}
diff --git a/crates/yt/src/commands/update/implm/mod.rs b/crates/yt/src/commands/update/implm/mod.rs
new file mode 100644
index 0000000..10626ac
--- /dev/null
+++ b/crates/yt/src/commands/update/implm/mod.rs
@@ -0,0 +1,62 @@
+// 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::{
+    app::App,
+    commands::update::{UpdateCommand, implm::updater::Updater},
+    storage::db::{
+        extractor_hash::ExtractorHash,
+        subscription::{Subscription, Subscriptions},
+    },
+};
+
+use anyhow::{Result, bail};
+
+mod updater;
+
+impl UpdateCommand {
+    pub(in crate::commands) async fn implm(self, app: &App) -> Result<()> {
+        let UpdateCommand {
+            max_backlog,
+            subscriptions: subscription_names_to_update,
+        } = self;
+
+        let mut all_subs = Subscriptions::get(app).await?.remove_inactive();
+
+        let max_backlog = max_backlog.unwrap_or(app.config.update.max_backlog);
+
+        let subs: Vec<Subscription> = if subscription_names_to_update.is_empty() {
+            all_subs.0.into_values().collect()
+        } else {
+            subscription_names_to_update
+                .into_iter()
+                .map(|sub| {
+                    if let Some(val) = all_subs.0.remove(&sub) {
+                        Ok(val)
+                    } else {
+                        bail!(
+                            "Your specified subscription to update '{}' is not a subscription!",
+                            sub
+                        )
+                    }
+                })
+                .collect::<Result<_>>()?
+        };
+
+        // We can get away with not having to re-fetch the hashes every time, as the returned video
+        // should not contain duplicates.
+        let hashes = ExtractorHash::get_all(app).await?;
+
+        let updater = Updater::new(max_backlog, app.config.update.pool_size, hashes);
+        updater.update(app, subs).await?;
+
+        Ok(())
+    }
+}
diff --git a/crates/yt/src/update/updater.rs b/crates/yt/src/commands/update/implm/updater.rs
index 75d12dc..2b96bf2 100644
--- a/crates/yt/src/update/updater.rs
+++ b/crates/yt/src/commands/update/implm/updater.rs
@@ -8,42 +8,38 @@
 // 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},
-    sync::atomic::{AtomicUsize, Ordering},
-};
+use std::sync::atomic::{AtomicUsize, Ordering};
 
 use anyhow::{Context, Result};
-use blake3::Hash;
 use futures::{StreamExt, future::join_all, stream};
 use log::{Level, debug, error, log_enabled};
-use serde_json::json;
+use tokio::io::{AsyncWriteExt, stderr};
 use tokio_util::task::LocalPoolHandle;
 use yt_dlp::{
-    info_json::InfoJson, json_cast, json_get, options::YoutubeDLOptions, process_ie_result,
+    info_json::InfoJson, json_cast, json_try_get, options::YoutubeDLOptions, process_ie_result,
     python_error::PythonError,
 };
 
 use crate::{
-    ansi_escape_codes::{clear_whole_line, move_to_col},
+    ansi_escape_codes,
     app::App,
-    storage::subscriptions::Subscription,
+    storage::db::{
+        extractor_hash::ExtractorHash, insert::Operations, subscription::Subscription, video::Video,
+    },
+    yt_dlp::yt_dlp_opts_updating,
 };
 
-use super::process_subscription;
-
 pub(super) struct Updater {
     max_backlog: usize,
-    hashes: Vec<Hash>,
+    hashes: Vec<ExtractorHash>,
     pool: LocalPoolHandle,
 }
 
 static REACHED_NUMBER: AtomicUsize = const { AtomicUsize::new(1) };
 
 impl Updater {
-    pub(super) fn new(max_backlog: usize, hashes: Vec<Hash>) -> Self {
-        // TODO(@bpeetz): The number should not be hardcoded. <2025-06-14>
-        let pool = LocalPoolHandle::new(16);
+    pub(super) fn new(max_backlog: usize, max_threads: usize, hashes: Vec<ExtractorHash>) -> Self {
+        let pool = LocalPoolHandle::new(max_threads);
 
         Self {
             max_backlog,
@@ -52,22 +48,12 @@ impl Updater {
         }
     }
 
-    pub(super) async fn update(
-        self,
-        app: &App,
-        subscriptions: Vec<Subscription>,
-        total_number: Option<usize>,
-        current_progress: Option<usize>,
-    ) -> Result<()> {
-        let total_number = total_number.unwrap_or(subscriptions.len());
-
-        if let Some(current_progress) = current_progress {
-            REACHED_NUMBER.store(current_progress, Ordering::Relaxed);
-        }
+    pub(super) async fn update(self, app: &App, subscriptions: Vec<Subscription>) -> Result<()> {
+        let total_number = subscriptions.len();
 
         let mut stream = stream::iter(subscriptions)
             .map(|sub| self.get_new_entries(sub, total_number))
-            .buffer_unordered(16 * 4);
+            .buffer_unordered(app.config.update.futures);
 
         while let Some(output) = stream.next().await {
             let mut entries = output?;
@@ -86,6 +72,7 @@ impl Updater {
         Ok(())
     }
 
+    #[allow(clippy::too_many_lines)]
     async fn get_new_entries(
         &self,
         sub: Subscription,
@@ -94,32 +81,21 @@ impl Updater {
         let max_backlog = self.max_backlog;
         let hashes = self.hashes.clone();
 
-        let yt_dlp = YoutubeDLOptions::new()
-            .set("playliststart", 1)
-            .set("playlistend", 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()?;
+        let yt_dlp = yt_dlp_opts_updating(max_backlog)?;
 
         self.pool
             .spawn_pinned(move || {
                 async move {
                     if !log_enabled!(Level::Debug) {
-                        clear_whole_line();
-                        move_to_col(1);
+                        ansi_escape_codes::clear_whole_line();
+                        ansi_escape_codes::move_to_col(1);
                         eprint!(
                             "({}/{total_number}) Checking playlist {}...",
                             REACHED_NUMBER.fetch_add(1, Ordering::Relaxed),
                             sub.name
                         );
-                        move_to_col(1);
-                        stderr().flush()?;
+                        ansi_escape_codes::move_to_col(1);
+                        stderr().flush().await?;
                     }
 
                     let info = yt_dlp
@@ -127,20 +103,19 @@ impl Updater {
                         .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 entries = json_try_get!(info, "entries", as_array).unwrap_or(&empty);
 
                     let valid_entries: Vec<(Subscription, InfoJson)> = entries
                         .iter()
                         .take(max_backlog)
                         .filter_map(|entry| -> Option<(Subscription, InfoJson)> {
-                            let id = json_get!(entry, "id", as_str);
-                            let extractor_hash = blake3::hash(id.as_bytes());
+                            let extractor_hash =
+                                ExtractorHash::from_info_json(json_cast!(entry, as_object));
 
                             if hashes.contains(&extractor_hash) {
                                 debug!(
-                                    "Skipping entry, as it is already present: '{extractor_hash}'",
+                                    "Skipping entry, as it is \
+                                        already present: '{extractor_hash}'",
                                 );
                                 None
                             } else {
@@ -168,12 +143,17 @@ impl Updater {
                             Err(err) => {
                                 match err {
                                     process_ie_result::Error::Python(PythonError(err)) => {
-                                        if err.contains( "Join this channel to get access to members-only content ",) {
+                                        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 ")
+                                                .strip_prefix(
+                                                    "DownloadError: \u{1b}[0;31mERROR:\u{1b}[0m ",
+                                                )
                                                 .unwrap_or(&err);
                                             error!("While fetching {:#?}: {error}", sub.name);
                                         }
@@ -181,9 +161,13 @@ impl Updater {
                                         None
                                     }
                                     process_ie_result::Error::InfoJsonPrepare(error) => {
-                                        error!("While fetching {:#?}: Failed to prepare info json: {error}", sub.name);
+                                        error!(
+                                            "While fetching {:#?}: Failed to prepare \
+                                            info json: {error}",
+                                            sub.name
+                                        );
                                         None
-                                    },
+                                    }
                                 }
                             }
                         }))
@@ -192,3 +176,30 @@ impl Updater {
             .await?
     }
 }
+
+async fn process_subscription(app: &App, sub: Subscription, entry: InfoJson) -> Result<()> {
+    let mut ops = Operations::new("Update: process subscription");
+    let video = Video::from_info_json(&entry, Some(&sub))
+        .context("Failed to parse search entry as Video")?;
+
+    let title = video.title.clone();
+    let url = video.url.clone();
+    let video = video.add(&mut ops).with_context(|| {
+        format!("Failed to add video to database: '{title}' (with url: '{url}')")
+    })?;
+
+    ops.commit(app).await.with_context(|| {
+        format!(
+            "Failed to add video to database: '{}' (with url: '{}')",
+            video.title, video.url
+        )
+    })?;
+    println!(
+        "{}",
+        &video
+            .to_line_display(app, None)
+            .await
+            .with_context(|| format!("Failed to format video: '{}'", video.title))?
+    );
+    Ok(())
+}
diff --git a/crates/yt/src/commands/update/mod.rs b/crates/yt/src/commands/update/mod.rs
new file mode 100644
index 0000000..cb29148
--- /dev/null
+++ b/crates/yt/src/commands/update/mod.rs
@@ -0,0 +1,27 @@
+// 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 clap::Parser;
+use clap_complete::ArgValueCompleter;
+
+use crate::commands::complete_subscription;
+
+mod implm;
+
+#[derive(Parser, Debug)]
+pub(super) struct UpdateCommand {
+    /// The maximal number of videos to fetch for each subscription.
+    #[arg(short, long)]
+    max_backlog: Option<usize>,
+
+    /// The subscriptions to update
+    #[arg(add = ArgValueCompleter::new(complete_subscription))]
+    subscriptions: Vec<String>,
+}
diff --git a/crates/yt/src/commands/videos/implm.rs b/crates/yt/src/commands/videos/implm.rs
new file mode 100644
index 0000000..2a018c7
--- /dev/null
+++ b/crates/yt/src/commands/videos/implm.rs
@@ -0,0 +1,73 @@
+// 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::{
+    app::App,
+    commands::videos::VideosCommand,
+    storage::db::video::{Video, VideoStatusMarker},
+};
+
+use anyhow::{Context, Result};
+use futures::{TryStreamExt, stream::FuturesUnordered};
+
+impl VideosCommand {
+    pub(in crate::commands) async fn implm(self, app: &App) -> Result<()> {
+        match self {
+            VideosCommand::List {
+                search_query,
+                limit,
+                format,
+            } => {
+                let all_videos = Video::in_states(app, VideoStatusMarker::ALL).await?;
+
+                // turn one video to a color display, to pre-warm the hash shrinking cache
+                if let Some(val) = all_videos.first() {
+                    val.to_line_display(app, format.clone()).await?;
+                }
+
+                let limit = limit.unwrap_or(all_videos.len());
+
+                let all_video_strings: Vec<String> = all_videos
+                    .into_iter()
+                    .take(limit)
+                    .map(|vid| to_line_display_owned(vid, app, format.clone()))
+                    .collect::<FuturesUnordered<_>>()
+                    .try_collect::<Vec<String>>()
+                    .await?;
+
+                if let Some(query) = search_query {
+                    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"));
+                }
+            }
+            VideosCommand::Info { hash, format } => {
+                let video = hash.realize(app, None).await?.get_with_app(app).await?;
+
+                print!(
+                    "{}",
+                    &video
+                        .to_info_display(app, format)
+                        .await
+                        .context("Failed to format video")?
+                );
+            }
+        }
+
+        Ok(())
+    }
+}
+
+async fn to_line_display_owned(video: Video, app: &App, format: Option<String>) -> Result<String> {
+    video.to_line_display(app, format).await
+}
diff --git a/crates/yt/src/commands/videos/mod.rs b/crates/yt/src/commands/videos/mod.rs
new file mode 100644
index 0000000..ca20715
--- /dev/null
+++ b/crates/yt/src/commands/videos/mod.rs
@@ -0,0 +1,46 @@
+// 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 clap::{ArgAction, Subcommand};
+
+use crate::storage::db::extractor_hash::LazyExtractorHash;
+
+mod implm;
+
+#[derive(Subcommand, Clone, Debug)]
+pub(super) enum VideosCommand {
+    /// List the videos in the database
+    #[command(visible_alias = "ls")]
+    List {
+        /// An optional search query to limit the results
+        #[arg(action = ArgAction::Append)]
+        search_query: Option<String>,
+
+        /// The format string to use.
+        // TODO(@bpeetz): Encode the default format, as the default string here. <2025-07-04>
+        #[arg(short, long)]
+        format: Option<String>,
+
+        /// The number of videos to show
+        #[arg(short, long)]
+        limit: Option<usize>,
+    },
+
+    /// Get detailed information about a video
+    Info {
+        /// The short hash of the video
+        hash: LazyExtractorHash,
+
+        /// The format string to use.
+        // TODO(@bpeetz): Encode the default format, as the default string here. <2025-07-04>
+        #[arg(short, long)]
+        format: Option<String>,
+    },
+}
diff --git a/crates/yt/src/commands/watch/implm/mod.rs b/crates/yt/src/commands/watch/implm/mod.rs
new file mode 100644
index 0000000..8182216
--- /dev/null
+++ b/crates/yt/src/commands/watch/implm/mod.rs
@@ -0,0 +1,244 @@
+// 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::{
+    fs,
+    path::PathBuf,
+    sync::{
+        Arc,
+        atomic::{AtomicBool, Ordering},
+    },
+};
+
+use crate::{
+    app::App,
+    commands::watch::{WatchCommand, implm::playlist_handler::Status},
+    storage::{
+        db::{
+            insert::{Operations, maintenance::clear_stale_downloaded_paths},
+            playlist::Playlist,
+        },
+        notify::wait_for_db_write,
+    },
+};
+
+use anyhow::{Context, Result};
+use libmpv2::{Mpv, events::EventContext};
+use log::{debug, info, trace, warn};
+use tokio::{task, time};
+
+mod playlist_handler;
+
+impl WatchCommand {
+    #[allow(clippy::too_many_lines)]
+    pub(in crate::commands) async fn implm(self, app: Arc<App>) -> Result<()> {
+        let WatchCommand {
+            provide_ipc_socket,
+            headless,
+        } = self;
+
+        clear_stale_downloaded_paths(&app).await?;
+
+        let ipc_socket = if provide_ipc_socket {
+            Some(app.config.paths.mpv_ipc_socket_path.clone())
+        } else {
+            None
+        };
+
+        let (mpv, mut ev_ctx) =
+            init_mpv(&app, ipc_socket, headless).context("Failed to initialize mpv instance")?;
+        let mpv = Arc::new(mpv);
+
+        if provide_ipc_socket {
+            println!("{}", app.config.paths.mpv_ipc_socket_path.display());
+        }
+
+        let should_break = Arc::new(AtomicBool::new(false));
+        let local_app = Arc::clone(&app);
+        let local_mpv = Arc::clone(&mpv);
+        let local_should_break = Arc::clone(&should_break);
+        let progress_handle = task::spawn(async move {
+            loop {
+                if local_should_break.load(Ordering::Relaxed) {
+                    trace!("WatchProgressThread: Stopping, as we received exit signal.");
+                    break;
+                }
+
+                let mut playlist = Playlist::create(&local_app).await?;
+
+                if let Some(index) = playlist.current_index() {
+                    trace!("WatchProgressThread: Saving watch progress for current video");
+
+                    let mut ops =
+                        Operations::new("WatchProgressThread: save watch progress thread");
+                    playlist.save_watch_progress(&local_mpv, index, &mut ops);
+                    ops.commit(&local_app).await?;
+                } else {
+                    trace!(
+                        "WatchProgressThread: Tried to save current watch progress, but no video active."
+                    );
+                }
+
+                time::sleep(local_app.config.watch.progress_save_intervall).await;
+            }
+
+            Ok::<(), anyhow::Error>(())
+        });
+
+        let playlist = Playlist::create(&app).await?;
+        playlist.resync_with_mpv(&app, &mpv)?;
+
+        let mut have_warned = (false, 0);
+        'watchloop: loop {
+            'waitloop: while let Ok(value) = playlist_handler::status(&app).await {
+                match value {
+                    Status::NoMoreAvailable => {
+                        break 'watchloop;
+                    }
+                    Status::NoCached { marked_watch } => {
+                        // try again next time.
+                        if have_warned.0 {
+                            if have_warned.1 != marked_watch {
+                                warn!("Now {marked_watch} videos are marked as to be watched.");
+                                have_warned.1 = marked_watch;
+                            }
+                        } else {
+                            warn!(
+                                "There is nothing to watch yet, but still {marked_watch} videos marked as to be watched. \
+                                Will idle, until they become available"
+                            );
+                            have_warned = (true, marked_watch);
+                        }
+                        wait_for_db_write(&app).await?;
+
+                        // Add the new videos, if they are there.
+                        let playlist = Playlist::create(&app).await?;
+                        playlist.resync_with_mpv(&app, &mpv)?;
+                    }
+                    Status::Available { newly_available } => {
+                        debug!(
+                            "Checked for currently available videos and found {newly_available}!"
+                        );
+                        have_warned.0 = false;
+
+                        // Something just became available!
+                        break 'waitloop;
+                    }
+                }
+            }
+
+            // TODO(@bpeetz): Is the following assumption correct? <2025-07-10>
+            // We wait until forever for the next event, because we really don't need to do anything
+            // else.
+            if let Some(ev) = ev_ctx.wait_event(f64::MAX) {
+                match ev {
+                    Ok(event) => {
+                        trace!("Mpv event triggered: {event:#?}");
+                        if playlist_handler::handle_mpv_event(&app, &mpv, &event)
+                            .await
+                            .with_context(|| format!("Failed to handle mpv event: '{event:#?}'"))?
+                        {
+                            break;
+                        }
+                    }
+                    Err(e) => debug!("Mpv Event errored: {e}"),
+                }
+            }
+        }
+        should_break.store(true, Ordering::Relaxed);
+        progress_handle.await??;
+
+        if provide_ipc_socket {
+            fs::remove_file(&app.config.paths.mpv_ipc_socket_path).with_context(|| {
+                format!(
+                    "Failed to clean-up the mpv ipc socket at {}",
+                    app.config.paths.mpv_ipc_socket_path.display()
+                )
+            })?;
+        }
+
+        Ok(())
+    }
+}
+
+fn init_mpv(app: &App, ipc_socket: Option<PathBuf>, headless: bool) -> Result<(Mpv, EventContext)> {
+    // set some default values, to make things easier (these can be overridden by the config file,
+    // which we load later)
+    let mpv = Mpv::with_initializer(|mpv| {
+        if let Some(socket) = ipc_socket {
+            mpv.set_property(
+                "input-ipc-server",
+                socket
+                    .to_str()
+                    .expect("This path comes from us, it should never contain not-utf8"),
+            )?;
+        }
+
+        if headless {
+            // Do not provide video output.
+            mpv.set_property("vid", "no")?;
+        } else {
+            // Enable default key bindings, so the user can actually interact with
+            // the player (and e.g. close the window).
+            mpv.set_property("input-default-bindings", "yes")?;
+            mpv.set_property("input-vo-keyboard", "yes")?;
+
+            // Show the on screen controller.
+            mpv.set_property("osc", "yes")?;
+
+            // Don't automatically advance to the next video (or exit the player)
+            mpv.set_option("keep-open", "always")?;
+
+            // Always display an window, even for non-video playback.
+            // As mpv does not have cli access, no window means no control and no user feedback.
+            mpv.set_option("force-window", "yes")?;
+        }
+
+        Ok(())
+    })
+    .context("Failed to initialize mpv")?;
+
+    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")?],
+        )?;
+    } else {
+        warn!(
+            "Did not find a mpv.conf file at '{}'",
+            config_path.display()
+        );
+    }
+
+    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")?],
+        )?;
+    } else {
+        warn!(
+            "Did not find a mpv.input.conf file at '{}'",
+            input_path.display()
+        );
+    }
+
+    let ev_ctx = EventContext::new(mpv.ctx);
+    ev_ctx.disable_deprecated_events()?;
+
+    Ok((mpv, ev_ctx))
+}
diff --git a/crates/yt/src/watch/playlist_handler/client_messages/mod.rs b/crates/yt/src/commands/watch/implm/playlist_handler/client_messages.rs
index c05ca87..fd7e035 100644
--- a/crates/yt/src/watch/playlist_handler/client_messages/mod.rs
+++ b/crates/yt/src/commands/watch/implm/playlist_handler/client_messages.rs
@@ -10,7 +10,7 @@
 
 use std::{env, time::Duration};
 
-use crate::{app::App, comments};
+use crate::{app::App, storage::db::video::Video};
 
 use anyhow::{Context, Result, bail};
 use libmpv2::Mpv;
@@ -23,19 +23,8 @@ async fn run_self_in_external_command(app: &App, args: &[&str]) -> Result<()> {
     let binary =
         env::current_exe().context("Failed to determine the current executable to re-execute")?;
 
-    let status = Command::new("riverctl")
-        .args(["focus-output", "next"])
-        .status()
-        .await?;
-    if !status.success() {
-        bail!("focusing the next output failed!");
-    }
-
     let arguments = [
         &[
-            "--title",
-            "floating please",
-            "--command",
             binary
                 .to_str()
                 .context("Failed to turn the executable path to a utf8-string")?,
@@ -50,29 +39,24 @@ async fn run_self_in_external_command(app: &App, args: &[&str]) -> Result<()> {
     ]
     .concat();
 
-    let status = Command::new("alacritty").args(arguments).status().await?;
-    if !status.success() {
-        bail!("Falied to start `yt comments`");
-    }
-
-    let status = Command::new("riverctl")
-        .args(["focus-output", "next"])
+    let status = Command::new(app.config.commands.external_spawn.first())
+        .args(app.config.commands.external_spawn.tail())
+        .args(arguments)
         .status()
         .await?;
-
     if !status.success() {
-        bail!("focusing the next output failed!");
+        bail!("Falied to start (external) `yt {}`", args.join(" "));
     }
 
     Ok(())
 }
 
 pub(super) async fn handle_yt_description_external(app: &App) -> Result<()> {
-    run_self_in_external_command(app, &["description"]).await?;
+    run_self_in_external_command(app, &["show", "description"]).await?;
     Ok(())
 }
 pub(super) async fn handle_yt_description_local(app: &App, mpv: &Mpv) -> Result<()> {
-    let description: String = comments::description::get(app)
+    let description: String = Video::get_current_description(app)
         .await?
         .chars()
         .take(app.config.watch.local_displays_length)
@@ -83,11 +67,11 @@ pub(super) async fn handle_yt_description_local(app: &App, mpv: &Mpv) -> Result<
 }
 
 pub(super) async fn handle_yt_comments_external(app: &App) -> Result<()> {
-    run_self_in_external_command(app, &["comments"]).await?;
+    run_self_in_external_command(app, &["show", "comments"]).await?;
     Ok(())
 }
 pub(super) async fn handle_yt_comments_local(app: &App, mpv: &Mpv) -> Result<()> {
-    let comments: String = comments::get(app)
+    let comments: String = Video::get_current_comments(app)
         .await?
         .render(false)
         .chars()
@@ -97,3 +81,13 @@ pub(super) async fn handle_yt_comments_local(app: &App, mpv: &Mpv) -> Result<()>
     mpv_message(mpv, &comments, Duration::from_secs(6))?;
     Ok(())
 }
+
+pub(super) async fn handle_yt_info_external(app: &App) -> Result<()> {
+    run_self_in_external_command(app, &["show", "info"]).await?;
+    Ok(())
+}
+
+pub(super) async fn handle_yt_thumbnail_external(app: &App) -> Result<()> {
+    run_self_in_external_command(app, &["show", "thumbnail"]).await?;
+    Ok(())
+}
diff --git a/crates/yt/src/commands/watch/implm/playlist_handler/mod.rs b/crates/yt/src/commands/watch/implm/playlist_handler/mod.rs
new file mode 100644
index 0000000..bdb77d2
--- /dev/null
+++ b/crates/yt/src/commands/watch/implm/playlist_handler/mod.rs
@@ -0,0 +1,225 @@
+// 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::time::Duration;
+
+use crate::{
+    app::App,
+    storage::db::{
+        insert::{Operations, playlist::VideoTransition},
+        playlist::{Playlist, PlaylistIndex},
+        video::{Video, VideoStatusMarker},
+    },
+};
+
+use anyhow::{Context, Result};
+use libmpv2::{EndFileReason, Mpv, events::Event};
+use log::{debug, info};
+
+mod client_messages;
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum Status {
+    /// There are no videos cached and no more marked to be watched.
+    /// Waiting is pointless.
+    NoMoreAvailable,
+
+    /// There are no videos cached, but some (> 0) are marked to be watched.
+    /// So we should wait for them to become available.
+    NoCached { marked_watch: usize },
+
+    /// There are videos cached and ready to be inserted into the playback queue.
+    Available { newly_available: usize },
+}
+
+fn mpv_message(mpv: &Mpv, message: &str, time: Duration) -> Result<()> {
+    mpv.command(
+        "show-text",
+        &[message, time.as_millis().to_string().as_str()],
+    )?;
+    Ok(())
+}
+
+/// Return the status of the playback queue
+pub(crate) async fn status(app: &App) -> Result<Status> {
+    let playlist = Playlist::create(app).await?;
+
+    let playlist_len = playlist.len();
+    let marked_watch_num = Video::in_states(app, &[VideoStatusMarker::Watch])
+        .await?
+        .len();
+
+    if playlist_len == 0 && marked_watch_num == 0 {
+        Ok(Status::NoMoreAvailable)
+    } else if playlist_len == 0 && marked_watch_num != 0 {
+        Ok(Status::NoCached {
+            marked_watch: marked_watch_num,
+        })
+    } else if playlist_len != 0 {
+        Ok(Status::Available {
+            newly_available: playlist_len,
+        })
+    } else {
+        unreachable!(
+            "The playlist length is {playlist_len}, but the number of marked watch videos is {marked_watch_num}! This is a bug."
+        );
+    }
+}
+
+/// # Returns
+/// This will return [`true`], if the event handling should be stopped
+///
+/// # Panics
+/// Only if internal assertions fail.
+#[allow(clippy::too_many_lines)]
+pub(crate) async fn handle_mpv_event(app: &App, mpv: &Mpv, event: &Event<'_>) -> Result<bool> {
+    let mut ops = Operations::new("PlaylistHandler: handle event");
+
+    // Construct the playlist lazily.
+    // This avoids unneeded db lookups.
+    // (We use the moved `call_once` as guard for this)
+    let call_once = String::new();
+    let playlist = move || {
+        drop(call_once);
+        Playlist::create(app)
+    };
+
+    let should_stop_event_handling = match event {
+        Event::EndFile(r) => match r.reason {
+            EndFileReason::Eof => {
+                info!("Mpv reached the end of the current video. Marking it watched.");
+                playlist().await?.resync_with_mpv(app, mpv)?;
+
+                false
+            }
+            EndFileReason::Stop => {
+                // This reason is incredibly ambiguous. It _both_ means actually pausing a
+                // video and going to the next one in the playlist.
+                // Oh, and it's also called, when a video is removed from the playlist (at
+                // least via "playlist-remove current")
+                info!("Paused video (or went to next playlist entry); Doing nothing");
+
+                false
+            }
+            EndFileReason::Quit => {
+                info!("Mpv quit. Exiting playback");
+
+                playlist().await?.save_current_watch_progress(mpv, &mut ops);
+
+                true
+            }
+            EndFileReason::Error => {
+                unreachable!("This should have been raised as a separate error")
+            }
+            EndFileReason::Redirect => {
+                // TODO: We probably need to handle this somehow <2025-02-17>
+                false
+            }
+        },
+        Event::StartFile(_) => {
+            let mut playlist = playlist().await?;
+
+            let mpv_pos = usize::try_from(mpv.get_property::<i64>("playlist-pos")?)
+                .expect("The value is strictly positive");
+
+            let yt_pos = playlist.current_index().map(usize::from);
+
+            if (Some(mpv_pos) != yt_pos) || yt_pos.is_none() {
+                debug!(
+                    "StartFileHandler: mpv pos {mpv_pos} and our pos {yt_pos:?} do not align. Reloading.."
+                );
+
+                if let Some((_, vid)) = playlist.get_focused_mut() {
+                    vid.set_focused(false, &mut ops);
+                    ops.commit(app)
+                        .await
+                        .context("Failed to commit video unfocusing")?;
+
+                    ops = Operations::new("PlaylistHandler: after set-focused");
+                }
+
+                let video = playlist
+                    .get_mut(PlaylistIndex::from(mpv_pos))
+                    .expect("The mpv pos should not be out of bounds");
+
+                video.set_focused(true, &mut ops);
+
+                playlist.resync_with_mpv(app, mpv)?;
+            }
+
+            false
+        }
+        Event::Seek => {
+            playlist().await?.save_current_watch_progress(mpv, &mut ops);
+
+            false
+        }
+        Event::ClientMessage(a) => {
+            debug!("Got Client Message event: '{}'", a.join(" "));
+
+            match a.as_slice() {
+                &["yt-comments-external"] => {
+                    client_messages::handle_yt_comments_external(app).await?;
+                }
+                &["yt-comments-local"] => {
+                    client_messages::handle_yt_comments_local(app, mpv).await?;
+                }
+
+                &["yt-description-external"] => {
+                    client_messages::handle_yt_description_external(app).await?;
+                }
+                &["yt-description-local"] => {
+                    client_messages::handle_yt_description_local(app, mpv).await?;
+                }
+
+                &["yt-info-external"] => {
+                    client_messages::handle_yt_info_external(app).await?;
+                }
+                &["yt-thumbnail-external"] => {
+                    client_messages::handle_yt_thumbnail_external(app).await?;
+                }
+
+                &["yt-mark-picked"] => {
+                    playlist().await?.mark_current_done(
+                        app,
+                        mpv,
+                        VideoTransition::Picked,
+                        &mut ops,
+                    )?;
+
+                    mpv_message(mpv, "Marked the video as picked", Duration::from_secs(3))?;
+                }
+                &["yt-mark-watched"] => {
+                    playlist().await?.mark_current_done(
+                        app,
+                        mpv,
+                        VideoTransition::Watched,
+                        &mut ops,
+                    )?;
+
+                    mpv_message(mpv, "Marked the video watched", Duration::from_secs(3))?;
+                }
+                &["yt-check-new-videos"] => {
+                    playlist().await?.resync_with_mpv(app, mpv)?;
+                }
+                other => {
+                    debug!("Unknown message: {}", other.join(" "));
+                }
+            }
+
+            false
+        }
+        _ => false,
+    };
+
+    ops.commit(app).await?;
+
+    Ok(should_stop_event_handling)
+}
diff --git a/crates/yt/src/commands/watch/mod.rs b/crates/yt/src/commands/watch/mod.rs
new file mode 100644
index 0000000..ea4c513
--- /dev/null
+++ b/crates/yt/src/commands/watch/mod.rs
@@ -0,0 +1,24 @@
+// 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 clap::Parser;
+
+mod implm;
+
+#[derive(Parser, Debug)]
+pub(super) struct WatchCommand {
+    /// Print the path to an ipc socket for mpv control to stdout at startup.
+    #[arg(long)]
+    provide_ipc_socket: bool,
+
+    /// Don't start an mpv window at all.
+    #[arg(long)]
+    headless: bool,
+}
diff --git a/crates/yt/src/comments/comment.rs b/crates/yt/src/comments/comment.rs
deleted file mode 100644
index 5bc939c..0000000
--- a/crates/yt/src/comments/comment.rs
+++ /dev/null
@@ -1,152 +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::{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/crates/yt/src/comments/description.rs b/crates/yt/src/comments/description.rs
deleted file mode 100644
index 878b573..0000000
--- a/crates/yt/src/comments/description.rs
+++ /dev/null
@@ -1,46 +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 crate::{
-    App,
-    comments::output::display_fmt_and_less,
-    storage::video_database::{Video, get},
-    unreachable::Unreachable,
-};
-
-use anyhow::{Result, bail};
-use yt_dlp::json_cast;
-
-pub async fn description(app: &App) -> Result<()> {
-    let description = get(app).await?;
-    display_fmt_and_less(description).await?;
-
-    Ok(())
-}
-
-pub async fn get(app: &App) -> Result<String> {
-    let currently_playing_video: Video =
-        if let Some(video) = get::currently_focused_video(app).await? {
-            video
-        } else {
-            bail!("Could not find a currently playing video!");
-        };
-
-    let info_json = get::video_info_json(&currently_playing_video)?.unreachable(
-        "A currently *playing* must be cached. And thus the info.json should be available",
-    );
-
-    Ok(info_json
-        .get("description")
-        .map(|val| json_cast!(val, as_str))
-        .unwrap_or("<No description>")
-        .to_owned())
-}
diff --git a/crates/yt/src/comments/mod.rs b/crates/yt/src/comments/mod.rs
deleted file mode 100644
index 54031a4..0000000
--- a/crates/yt/src/comments/mod.rs
+++ /dev/null
@@ -1,167 +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 std::mem;
-
-use anyhow::{Result, bail};
-use comment::{Comment, CommentExt, Comments, Parent};
-use output::display_fmt_and_less;
-use regex::Regex;
-use yt_dlp::json_cast;
-
-use crate::{
-    app::App,
-    storage::video_database::{Video, get},
-    unreachable::Unreachable,
-};
-
-mod comment;
-mod display;
-pub mod output;
-
-pub mod description;
-pub use description::*;
-
-#[allow(clippy::too_many_lines)]
-pub async fn get(app: &App) -> Result<Comments> {
-    let currently_playing_video: Video =
-        if let Some(video) = get::currently_focused_video(app).await? {
-            video
-        } else {
-            bail!("Could not find a currently playing video!");
-        };
-
-    let info_json = 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 = if let Some(comments) = info_json.get("comments") {
-        json_cast!(comments, as_array)
-    } else {
-        bail!(
-            "The video ('{}') does not have comments!",
-            info_json
-                .get("title")
-                .map(|val| json_cast!(val, as_str))
-                .unwrap_or("<No Title>")
-        )
-    };
-
-    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 {
-            comments.push(CommentExt::from(c));
-        }
-    }
-
-    comments.vec.iter_mut().for_each(|comment| {
-       let replies = mem::take(&mut comment.replies);
-       let mut output_replies: Vec<CommentExt>  = vec![];
-
-       let re = Regex::new(r"\u{200b}?(@[^\t\s]+)\u{200b}?").unreachable("This is hardcoded");
-       for reply in replies {
-           if let Some(replyee_match) =  re.captures(&reply.value.text){
-               let full_match = replyee_match.get(0).unreachable("This will always exist");
-               let text = reply.
-                   value.
-                   text[0..full_match.start()]
-                   .to_owned()
-                   +
-                   &reply
-                   .value
-                   .text[full_match.end()..];
-               let text: &str = text.trim().trim_matches('\u{200b}');
-
-               let replyee = replyee_match.get(1).unreachable("This should also exist").as_str();
-
-
-               if let Some(parent) = output_replies
-                   .iter_mut()
-                   // .rev()
-                   .flat_map(|com| &mut com.replies)
-                   .flat_map(|com| &mut com.replies)
-                   .flat_map(|com| &mut com.replies)
-                   .filter(|com| com.value.author == replyee)
-                   .last()
-               {
-                   parent.replies.push(CommentExt::from(Comment {
-                       text: text.to_owned(),
-                       ..reply.value
-                   }));
-               } else if let Some(parent) = output_replies
-                   .iter_mut()
-                   // .rev()
-                   .flat_map(|com| &mut com.replies)
-                   .flat_map(|com| &mut com.replies)
-                   .filter(|com| com.value.author == replyee)
-                   .last()
-               {
-                   parent.replies.push(CommentExt::from(Comment {
-                       text: text.to_owned(),
-                       ..reply.value
-                   }));
-               } else if let Some(parent) = output_replies
-                   .iter_mut()
-                   // .rev()
-                   .flat_map(|com| &mut com.replies)
-                   .filter(|com| com.value.author == replyee)
-                   .last()
-               {
-                   parent.replies.push(CommentExt::from(Comment {
-                       text: text.to_owned(),
-                       ..reply.value
-                   }));
-               } else if let Some(parent) = output_replies.iter_mut()
-                   // .rev()
-                   .filter(|com| com.value.author == replyee)
-                   .last()
-               {
-                   parent.replies.push(CommentExt::from(Comment {
-                       text: text.to_owned(),
-                       ..reply.value
-                   }));
-               } else {
-                   eprintln!(
-                   "Failed to find a parent for ('{}') both directly and via replies! The reply text was:\n'{}'\n",
-                   replyee,
-                   reply.value.text
-               );
-                   output_replies.push(reply);
-               }
-           } else {
-               output_replies.push(reply);
-           }
-       }
-       comment.replies = output_replies;
-    });
-
-    Ok(comments)
-}
-
-pub async fn comments(app: &App) -> Result<()> {
-    let comments = get(app).await?;
-
-    display_fmt_and_less(comments.render(true)).await?;
-
-    Ok(())
-}
-
-#[cfg(test)]
-mod test {
-    #[test]
-    fn test_string_replacement() {
-        let s = "A \n\nB\n\nC".to_owned();
-        assert_eq!("A \n  \n  B\n  \n  C", s.replace('\n', "\n  "));
-    }
-}
diff --git a/crates/yt/src/config/default.rs b/crates/yt/src/config/default.rs
deleted file mode 100644
index 4ed643b..0000000
--- a/crates/yt/src/config/default.rs
+++ /dev/null
@@ -1,110 +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 std::path::PathBuf;
-
-use anyhow::{Context, Result};
-
-fn get_runtime_path(name: &'static str) -> Result<PathBuf> {
-    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);
-    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);
-    xdg_dirs
-        .place_config_file(name)
-        .with_context(|| format!("Failed to place config file: '{name}'"))
-}
-
-pub(super) fn create_path(path: PathBuf) -> Result<PathBuf> {
-    if !path.exists() {
-        if let Some(parent) = path.parent() {
-            std::fs::create_dir_all(parent)
-                .with_context(|| format!("Failed to create the '{}' directory", path.display()))?;
-        }
-    }
-
-    Ok(path)
-}
-
-pub(crate) const PREFIX: &str = "yt";
-
-pub(crate) mod global {
-    pub(crate) fn display_colors() -> bool {
-        // TODO: This should probably check if the output is a tty and otherwise return `false` <2025-02-14>
-        true
-    }
-}
-
-pub(crate) mod select {
-    pub(crate) fn playback_speed() -> f64 {
-        2.7
-    }
-    pub(crate) fn subtitle_langs() -> &'static str {
-        ""
-    }
-}
-
-pub(crate) mod watch {
-    pub(crate) fn local_displays_length() -> usize {
-        1000
-    }
-}
-
-pub(crate) mod update {
-    pub(crate) fn max_backlog() -> usize {
-        20
-    }
-}
-
-pub(crate) mod paths {
-    use std::{env::temp_dir, path::PathBuf};
-
-    use anyhow::Result;
-
-    use super::{PREFIX, create_path, get_config_path, get_data_path, get_runtime_path};
-
-    // We download to the temp dir to avoid taxing the disk
-    pub(crate) fn download_dir() -> Result<PathBuf> {
-        let temp_dir = temp_dir();
-
-        create_path(temp_dir.join(PREFIX))
-    }
-    pub(crate) fn mpv_config_path() -> Result<PathBuf> {
-        get_config_path("mpv.conf")
-    }
-    pub(crate) fn mpv_input_path() -> Result<PathBuf> {
-        get_config_path("mpv.input.conf")
-    }
-    pub(crate) fn database_path() -> Result<PathBuf> {
-        get_data_path("videos.sqlite")
-    }
-    pub(crate) fn config_path() -> Result<PathBuf> {
-        get_config_path("config.toml")
-    }
-    pub(crate) fn last_selection_path() -> Result<PathBuf> {
-        get_runtime_path("selected.yts")
-    }
-}
-
-pub(crate) mod download {
-    pub(crate) fn max_cache_size() -> &'static str {
-        "3 GiB"
-    }
-}
diff --git a/crates/yt/src/config/definitions.rs b/crates/yt/src/config/definitions.rs
deleted file mode 100644
index ce8c0d4..0000000
--- a/crates/yt/src/config/definitions.rs
+++ /dev/null
@@ -1,67 +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 std::path::PathBuf;
-
-use serde::Deserialize;
-
-#[derive(Debug, Deserialize, PartialEq)]
-#[serde(deny_unknown_fields)]
-pub(crate) struct ConfigFile {
-    pub global: Option<GlobalConfig>,
-    pub select: Option<SelectConfig>,
-    pub watch: Option<WatchConfig>,
-    pub paths: Option<PathsConfig>,
-    pub download: Option<DownloadConfig>,
-    pub update: Option<UpdateConfig>,
-}
-
-#[derive(Debug, Deserialize, PartialEq, Clone, Copy)]
-#[serde(deny_unknown_fields)]
-pub(crate) struct GlobalConfig {
-    pub display_colors: Option<bool>,
-}
-
-#[derive(Debug, Deserialize, PartialEq, Clone, Copy)]
-#[serde(deny_unknown_fields)]
-pub(crate) struct UpdateConfig {
-    pub max_backlog: Option<usize>,
-}
-
-#[derive(Debug, Deserialize, PartialEq, Clone)]
-#[serde(deny_unknown_fields)]
-pub(crate) struct DownloadConfig {
-    /// This will then be converted to an u64
-    pub max_cache_size: Option<String>,
-}
-
-#[derive(Debug, Deserialize, PartialEq, Clone)]
-#[serde(deny_unknown_fields)]
-pub(crate) struct SelectConfig {
-    pub playback_speed: Option<f64>,
-    pub subtitle_langs: Option<String>,
-}
-
-#[derive(Debug, Deserialize, PartialEq, Clone, Copy)]
-#[serde(deny_unknown_fields)]
-pub(crate) struct WatchConfig {
-    pub local_displays_length: Option<usize>,
-}
-
-#[derive(Debug, Deserialize, PartialEq, Clone)]
-#[serde(deny_unknown_fields)]
-pub(crate) struct PathsConfig {
-    pub download_dir: Option<PathBuf>,
-    pub mpv_config_path: Option<PathBuf>,
-    pub mpv_input_path: Option<PathBuf>,
-    pub database_path: Option<PathBuf>,
-    pub last_selection_path: Option<PathBuf>,
-}
diff --git a/crates/yt/src/config/file_system.rs b/crates/yt/src/config/file_system.rs
deleted file mode 100644
index 2463e9d..0000000
--- a/crates/yt/src/config/file_system.rs
+++ /dev/null
@@ -1,120 +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 crate::config::{DownloadConfig, PathsConfig, SelectConfig, WatchConfig};
-
-use super::{
-    Config, GlobalConfig, UpdateConfig,
-    default::{create_path, download, global, paths, select, update, watch},
-};
-
-use std::{fs::read_to_string, path::PathBuf};
-
-use anyhow::{Context, Result};
-use bytes::Bytes;
-
-macro_rules! get {
-    ($default:path, $config:expr, $key_one:ident, $($keys:ident),*) => {
-        {
-            let maybe_value = get!{@option $config, $key_one, $($keys),*};
-            if let Some(value) = maybe_value {
-                value
-            } else {
-                $default().to_owned()
-            }
-        }
-    };
-
-    (@option $config:expr, $key_one:ident, $($keys:ident),*) => {
-        if let Some(key) = $config.$key_one.clone() {
-            get!{@option key, $($keys),*}
-        } else {
-            None
-        }
-    };
-    (@option $config:expr, $key_one:ident) => {
-        $config.$key_one
-    };
-
-    (@path_if_none $config:expr, $option_default:expr, $default:path, $key_one:ident, $($keys:ident),*) => {
-        {
-            let maybe_download_dir: Option<PathBuf> =
-                get! {@option $config, $key_one, $($keys),*};
-
-            let down_dir = if let Some(dir) = maybe_download_dir {
-                PathBuf::from(dir)
-            } else {
-                if let Some(path) = $option_default {
-                    path
-                } else {
-                    $default()
-                        .with_context(|| format!("Failed to get default path for: '{}.{}'", stringify!($key_one), stringify!($($keys),*)))?
-                }
-            };
-            create_path(down_dir)?
-        }
-    };
-    (@path $config:expr, $default:path, $key_one:ident, $($keys:ident),*) => {
-        get! {@path_if_none $config, None, $default, $key_one, $($keys),*}
-    };
-}
-
-impl Config {
-    pub fn from_config_file(
-        db_path: Option<PathBuf>,
-        config_path: Option<PathBuf>,
-        display_colors: Option<bool>,
-    ) -> Result<Self> {
-        let config_file_path =
-            config_path.map_or_else(|| -> Result<_> { paths::config_path() }, Ok)?;
-
-        let config: super::definitions::ConfigFile =
-            toml::from_str(&read_to_string(config_file_path).unwrap_or(String::new()))
-                .context("Failed to parse the config file as toml")?;
-
-        Ok(Self {
-            global: GlobalConfig {
-                display_colors: {
-                    let config_value: Option<bool> = get! {@option config, global, display_colors};
-
-                    display_colors.unwrap_or(config_value.unwrap_or_else(global::display_colors))
-                },
-            },
-            select: SelectConfig {
-                playback_speed: get! {select::playback_speed, config, select, playback_speed},
-                subtitle_langs: get! {select::subtitle_langs, config, select, subtitle_langs},
-            },
-            watch: WatchConfig {
-                local_displays_length: get! {watch::local_displays_length, config, watch, local_displays_length},
-            },
-            update: UpdateConfig {
-                max_backlog: get! {update::max_backlog, config, update, max_backlog},
-            },
-            paths: PathsConfig {
-                download_dir: get! {@path config, paths::download_dir, paths, download_dir},
-                mpv_config_path: get! {@path config, paths::mpv_config_path, paths, mpv_config_path},
-                mpv_input_path: get! {@path config, paths::mpv_input_path, paths, mpv_input_path},
-                database_path: get! {@path_if_none config, db_path, paths::database_path, paths, database_path},
-                last_selection_path: get! {@path config, paths::last_selection_path, paths, last_selection_path},
-            },
-            download: DownloadConfig {
-                max_cache_size: {
-                    let bytes_str: String =
-                        get! {download::max_cache_size, config, download, max_cache_size};
-                    let number: Bytes = bytes_str
-                        .parse()
-                        .context("Failed to parse max_cache_size")?;
-                    number
-                },
-            },
-        })
-    }
-}
diff --git a/crates/yt/src/config/mod.rs b/crates/yt/src/config/mod.rs
index a10f7c2..05bb4cf 100644
--- a/crates/yt/src/config/mod.rs
+++ b/crates/yt/src/config/mod.rs
@@ -1,6 +1,5 @@
 // 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
 //
@@ -9,68 +8,131 @@
 // 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>.
 
-#![allow(clippy::module_name_repetitions)]
+use std::sync::atomic::{AtomicBool, Ordering};
 
-use std::path::PathBuf;
+use crate::config::support::mk_config;
 
-use bytes::Bytes;
-use serde::Serialize;
+mod non_empty_vec;
+mod paths;
+mod support;
 
-mod default;
-mod definitions;
-pub mod file_system;
+pub(crate) static SHOULD_DISPLAY_COLOR: AtomicBool = AtomicBool::new(false);
 
-#[derive(Serialize, Debug)]
-pub struct Config {
-    pub global: GlobalConfig,
-    pub select: SelectConfig,
-    pub watch: WatchConfig,
-    pub paths: PathsConfig,
-    pub download: DownloadConfig,
-    pub update: UpdateConfig,
-}
-// These structures could get non-copy fields in the future.
+// We need to do both things to comply with what the config expects.
+#[allow(clippy::trivially_copy_pass_by_ref, clippy::unnecessary_wraps)]
+fn set_static_should_display_color(value: &bool) -> anyhow::Result<()> {
+    SHOULD_DISPLAY_COLOR.store(*value, Ordering::Relaxed);
 
-#[derive(Serialize, Debug)]
-#[allow(missing_copy_implementations)]
-pub struct GlobalConfig {
-    pub display_colors: bool,
-}
-#[derive(Serialize, Debug)]
-#[allow(missing_copy_implementations)]
-pub struct UpdateConfig {
-    pub max_backlog: usize,
-}
-#[derive(Serialize, Debug)]
-#[allow(missing_copy_implementations)]
-pub struct DownloadConfig {
-    pub max_cache_size: Bytes,
-}
-#[derive(Serialize, Debug)]
-pub struct SelectConfig {
-    pub playback_speed: f64,
-    pub subtitle_langs: String,
-}
-#[derive(Serialize, Debug)]
-#[allow(missing_copy_implementations)]
-pub struct WatchConfig {
-    pub local_displays_length: usize,
-}
-#[derive(Serialize, Debug)]
-pub struct PathsConfig {
-    pub download_dir: PathBuf,
-    pub mpv_config_path: PathBuf,
-    pub mpv_input_path: PathBuf,
-    pub database_path: PathBuf,
-    pub last_selection_path: PathBuf,
+    Ok(())
 }
 
-// pub fn status_path() -> anyhow::Result<PathBuf> {
-//     const STATUS_PATH: &str = "running.info.json";
-//     get_runtime_path(STATUS_PATH)
-// }
+mk_config! {
+    use std::path::PathBuf;
+    use std::io::IsTerminal;
+    use std::time::Duration;
+
+    use crate::shared::bytes::Bytes;
+
+    use super::set_static_should_display_color;
+
+    use super::paths::get_config_path;
+    use super::paths::get_runtime_path;
+    use super::paths::get_data_path;
+    use super::paths::ensure_parent_dir;
+    use super::paths::ensure_dir;
+    use super::paths::PREFIX;
+
+    use super::non_empty_vec::NonEmptyVec;
+    use super::non_empty_vec::non_empty_vec;
+
+    struct Config {
+        global: GlobalConfig = {
+            /// Whether to display colors.
+            display_colors: bool where display_color: Option<bool> =! {|config_value: Option<bool>|
+                Ok::<_, anyhow::Error>(
+                    display_color
+                    .unwrap_or(
+                        config_value
+                        .unwrap_or_else(|| std::io::stderr().is_terminal())
+                    )
+                )
+            } => set_static_should_display_color,
+        },
+        select: SelectConfig = {
+            /// The playback speed to use, when it is not overridden.
+            playback_speed: f64 =: 2.7,
+
+            /// The subtitle langs to download, when it is not overridden.
+            subtitle_langs: String =: String::new(),
+        },
+        watch: WatchConfig = {
+            /// How many chars to display at most, when displaying information on mpv's local on screen
+            /// display.
+            local_displays_length: usize =: 1000,
 
-// pub fn subscriptions() -> anyhow::Result<PathBuf> {
-//     const SUBSCRIPTIONS: &str = "subscriptions.json";
-//     get_data_path(SUBSCRIPTIONS)
-// }
+            /// How long to wait between saving the video watch progress.
+            progress_save_intervall: Duration =: Duration::from_secs(10),
+        },
+        commands: CommandsConfig = {
+            /// Which command to execute, when showing the thumbnail.
+            ///
+            /// This command will be executed with the one argument, being the path to the image file to display.
+            image_show: NonEmptyVec<String> =: non_empty_vec!["imv".to_owned()],
+
+            /// Which command to use, when spawing one of the external commands (e.g.
+            /// `yt-comments-external` from mpv).
+            ///
+            /// The command will be called with a series of args that should be executed.
+            /// For example,
+            /// `<your_specified_command> <path_to_yt_binary> --db-path <path_to_current_db_path> comments`
+            external_spawn: NonEmptyVec<String> =: non_empty_vec!["alacritty".to_owned(), "-e".to_owned()],
+
+            /// Which command to use, when opening video urls (like in the `yt select url` case).
+            ///
+            /// This command will be called with one argument, being the url of the video to open.
+            url_opener: NonEmptyVec<String> =: non_empty_vec!["firefox".to_owned()],
+        },
+        paths: PathsConfig = {
+            /// Where to store downloaded files.
+            download_dir: PathBuf =: {
+                // We download to the temp dir to avoid taxing the disk
+                let temp_dir = std::env::temp_dir();
+
+                temp_dir.join(PREFIX)
+            } => ensure_dir,
+
+            /// Path to the mpv configuration file.
+            mpv_config_path: PathBuf =? get_config_path("mpv.conf") => ensure_parent_dir,
+
+            /// Path to the mpv input configuration file.
+            mpv_input_path: PathBuf =? get_config_path("mpv.input.conf") => ensure_parent_dir,
+
+            /// Which path to use for mpv ipc socket creation.
+            mpv_ipc_socket_path: PathBuf =? get_runtime_path("mpv.ipc.socket") => ensure_parent_dir,
+
+            /// Path to the video database.
+            database_path: PathBuf where db_path: Option<PathBuf> =! {|config_value: Option<PathBuf>| {
+                db_path.map_or_else(|| config_value.map_or_else(|| get_data_path("videos.sqlite"), Ok), Ok)
+            }} => ensure_parent_dir,
+
+            /// Where to store the selection file before applying it.
+            last_selection_path: PathBuf =? get_runtime_path("selected.yts") => ensure_parent_dir,
+        },
+        download: DownloadConfig = {
+            /// The maximum cache size.
+            max_cache_size: Bytes =? "3 GiB".parse(),
+        },
+        update: UpdateConfig = {
+            /// How many videos to download, when checking for new ones.
+            max_backlog: usize =: 20,
+
+            /// How many threads to use in the thread pool for fetching new videos.
+            pool_size: usize =: 16,
+
+            /// How many subscriptions to fetch at once.
+            ///
+            /// For example, 16 means, that we will fetch 16 subscriptions at the same time.
+            futures: usize =: 16 * 4,
+        },
+    }
+}
diff --git a/crates/yt/src/config/non_empty_vec.rs b/crates/yt/src/config/non_empty_vec.rs
new file mode 100644
index 0000000..bd2c5e3
--- /dev/null
+++ b/crates/yt/src/config/non_empty_vec.rs
@@ -0,0 +1,83 @@
+// 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::{
+    collections::VecDeque,
+    fmt::{Display, Write},
+};
+
+use anyhow::bail;
+use serde::{Deserialize, Serialize};
+
+macro_rules! non_empty_vec {
+    ($first:expr $(, $($others:expr),+ $(,)?)?) => {{
+        let inner: Vec<_> = vec![$first $(, $($others,)+)?];
+        inner.try_into().expect("Has a first arg")
+    }}
+}
+pub(crate) use non_empty_vec;
+
+/// A vector that is non-empty.
+#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
+#[serde(try_from = "Vec<T>")]
+#[serde(into = "Vec<T>")]
+pub(crate) struct NonEmptyVec<T: Clone> {
+    first: T,
+    rest: Vec<T>,
+}
+
+impl<T: Clone> TryFrom<Vec<T>> for NonEmptyVec<T> {
+    type Error = anyhow::Error;
+
+    fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
+        let mut queue = VecDeque::from(value);
+
+        if let Some(first) = queue.pop_front() {
+            Ok(Self {
+                first,
+                rest: queue.into(),
+            })
+        } else {
+            bail!("You need to have at least one element in a non-empty vector.")
+        }
+    }
+}
+
+impl<T: Clone> From<NonEmptyVec<T>> for Vec<T> {
+    fn from(value: NonEmptyVec<T>) -> Self {
+        let mut base = vec![value.first];
+        base.extend(value.rest);
+        base
+    }
+}
+
+impl<T: Clone> NonEmptyVec<T> {
+    pub(crate) fn first(&self) -> &T {
+        &self.first
+    }
+
+    pub(crate) fn tail(&self) -> &[T] {
+        self.rest.as_ref()
+    }
+
+    pub(crate) fn join(&self, sep: &str) -> String
+    where
+        T: Display,
+    {
+        let mut output = String::new();
+        write!(output, "{}", self.first()).expect("In-memory, does not fail");
+
+        for elem in &self.rest {
+            write!(output, "{sep}{elem}").expect("In-memory, does not fail");
+        }
+
+        output
+    }
+}
diff --git a/crates/yt/src/config/paths.rs b/crates/yt/src/config/paths.rs
new file mode 100644
index 0000000..66975dd
--- /dev/null
+++ b/crates/yt/src/config/paths.rs
@@ -0,0 +1,58 @@
+// 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 std::path::{Path, PathBuf};
+
+use anyhow::{Context, Result};
+
+pub(super) fn get_runtime_path(name: &'static str) -> Result<PathBuf> {
+    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX);
+    xdg_dirs
+        .place_runtime_file(name)
+        .with_context(|| format!("Failed to place runtime file: '{name}'"))
+}
+pub(super) fn get_data_path(name: &'static str) -> Result<PathBuf> {
+    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX);
+    xdg_dirs
+        .place_data_file(name)
+        .with_context(|| format!("Failed to place data file: '{name}'"))
+}
+pub(super) fn get_config_path(name: &'static str) -> Result<PathBuf> {
+    let xdg_dirs = xdg::BaseDirectories::with_prefix(PREFIX);
+    xdg_dirs
+        .place_config_file(name)
+        .with_context(|| format!("Failed to place config file: '{name}'"))
+}
+
+pub(super) fn ensure_parent_dir(path: &Path) -> Result<()> {
+    if !path.exists() {
+        if let Some(parent) = path.parent() {
+            std::fs::create_dir_all(parent)
+                .with_context(|| format!("Failed to create the '{}' directory", path.display()))?;
+        }
+    }
+
+    Ok(())
+}
+pub(super) fn ensure_dir(path: &Path) -> Result<()> {
+    if !path.exists() {
+        std::fs::create_dir_all(path)
+            .with_context(|| format!("Failed to create the '{}' directory", path.display()))?;
+    }
+
+    Ok(())
+}
+
+pub(super) fn config_path() -> Result<PathBuf> {
+    get_config_path("config.toml")
+}
+
+pub(crate) const PREFIX: &str = "yt";
diff --git a/crates/yt/src/config/support.rs b/crates/yt/src/config/support.rs
new file mode 100644
index 0000000..96e7ba4
--- /dev/null
+++ b/crates/yt/src/config/support.rs
@@ -0,0 +1,161 @@
+// 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_rules! mk_config {
+    (
+        $(use $usage_path:path;)*
+
+        struct $name:ident {
+            $(
+              $(#[$attr0:meta])*
+              $subconfig_name:ident : $subconfig_type:ident = {
+                $(
+                    $(#[$attr1:meta])*
+                    $field_name:ident : $field_type:ty $(
+                        where $extra_input:ident: $extra_input_type:ty
+                    ),* = $errors:tt  $default:expr $(=> $finalizer:ident)?
+                ),*
+                $(,)?
+              }
+            ),*
+            $(,)?
+        }
+    ) => {
+        mod _inner {
+            #![allow(non_snake_case)]
+
+            $(use $usage_path;)*
+
+            #[derive(serde::Serialize, Debug)]
+            pub(crate) struct $name {
+                $(
+                    $(#[$attr0])*
+                    pub(crate) $subconfig_name: $subconfig_type
+                ),*
+            }
+
+            #[derive(Debug, serde::Deserialize, PartialEq)]
+            #[serde(deny_unknown_fields)]
+            #[allow(non_camel_case_types)]
+            struct config {
+                $(
+                    $subconfig_name: Option<$subconfig_name>
+                ),*
+            }
+
+            impl $name {
+                pub(crate) fn from_config_file(
+                    config_file_path: Option<std::path::PathBuf>,
+                    $(
+                        $(
+                            $(
+                                $extra_input: $extra_input_type,
+                            )*
+                        )*
+                    )*
+                ) -> anyhow::Result<Self> {
+                    use anyhow::Context;
+
+                    let config_file_path =
+                        config_file_path.map_or_else(|| -> anyhow::Result<_> { super::paths::config_path() }, Ok)?;
+
+                    let config: config =
+                        toml::from_str(&std::fs::read_to_string(config_file_path).unwrap_or(String::new()))
+                            .context("Failed to parse the config file as toml")?;
+
+                    Ok(Self {
+                        $(
+                            $subconfig_name: {
+                                let toplevel = config.$subconfig_name.unwrap_or_default();
+                                $subconfig_type {
+                                    $(
+                                        $field_name: $field_name(toplevel.$field_name, $($extra_input),*)?
+                                    ),*
+                                }
+                            }
+                        ),*
+                    })
+                }
+
+                pub(crate) fn run_finalizers(&self) -> anyhow::Result<()> {
+                    #[allow(unused_imports)]
+                    use anyhow::Context;
+
+                    $(
+                        $(
+                            $(
+                                $finalizer(&self.$subconfig_name.$field_name)
+                                  .context(
+                                        concat!(
+                                            "While running the finalizer for config value '",
+                                            stringify!($subconfig_name),
+                                            ".",
+                                            stringify!($field_name),
+                                            "'"
+                                        )
+                                  )?;
+                            )?
+                        )*
+                    )*
+
+                    Ok(())
+                }
+            }
+
+            $(
+                #[derive(serde::Serialize, Debug)]
+                pub(crate) struct $subconfig_type {
+                    $(
+                        $(#[$attr1])*
+                        pub(crate) $field_name: $field_type
+                    ),*
+                }
+
+                #[derive(Debug, Default, serde::Deserialize, PartialEq)]
+                #[serde(deny_unknown_fields)]
+                #[allow(non_camel_case_types)]
+                struct $subconfig_name {
+                    $(
+                        $field_name: Option<$field_type>
+                    ),*
+                }
+
+                $(
+                    fn $field_name(
+                        config_value: Option<$field_type>,
+                        $($extra_input: $extra_input_type),*
+                    ) -> anyhow::Result<$field_type> {
+                        use anyhow::Context;
+
+                        let expr = $crate::config::support::maybe_wrap_type!($field_type =$errors $default)(config_value);
+
+                        expr.context(concat!("Failed to generate default config value for '", stringify!($field_name),"'"))
+                    }
+                )*
+            )*
+        }
+        pub(crate) use self::_inner::*;
+    };
+}
+
+macro_rules! maybe_wrap_type {
+    ($ty:ty =! $val:expr) => {
+        (|config_value: Option<$ty>| $val(config_value))
+    };
+    ($ty:ty =? $val:expr) => {
+        (|config_value: Option<$ty>| config_value.map_or_else(|| $val, Ok))
+    };
+    ($ty:ty =: $val:expr) => {
+        (|config_value: Option<$ty>| Ok::<_, anyhow::Error>(config_value.unwrap_or_else(|| $val)))
+    };
+}
+
+pub(crate) use maybe_wrap_type;
+pub(crate) use mk_config;
diff --git a/crates/yt/src/download/mod.rs b/crates/yt/src/download/mod.rs
deleted file mode 100644
index 6065cf9..0000000
--- a/crates/yt/src/download/mod.rs
+++ /dev/null
@@ -1,369 +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 std::{collections::HashMap, io, str::FromStr, sync::Arc, time::Duration};
-
-use crate::{
-    app::App,
-    download::download_options::download_opts,
-    storage::video_database::{
-        Video, YtDlpOptions,
-        downloader::{get_next_uncached_video, set_video_cache_path},
-        extractor_hash::ExtractorHash,
-        get::get_video_yt_dlp_opts,
-        notify::wait_for_cache_reduction,
-    },
-    unreachable::Unreachable,
-};
-
-use anyhow::{Context, Result, bail};
-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)]
-pub struct CurrentDownload {
-    task_handle: JoinHandle<Result<()>>,
-    extractor_hash: ExtractorHash,
-}
-
-impl CurrentDownload {
-    fn new_from_video(app: Arc<App>, video: Video) -> Self {
-        let extractor_hash = video.extractor_hash;
-
-        let task_handle = tokio::spawn(async move {
-            Downloader::actually_cache_video(&app, &video)
-                .await
-                .with_context(|| format!("Failed to cache video: '{}'", video.title))?;
-            Ok(())
-        });
-
-        Self {
-            task_handle,
-            extractor_hash,
-        }
-    }
-}
-
-enum CacheSizeCheck {
-    /// The video can be downloaded
-    Fits,
-
-    /// The video and the current cache size together would exceed the size
-    TooLarge,
-
-    /// The video would not even fit into the empty cache
-    ExceedsMaxCacheSize,
-}
-
-#[derive(Debug)]
-pub struct Downloader {
-    current_download: Option<CurrentDownload>,
-    video_size_cache: HashMap<ExtractorHash, u64>,
-    printed_warning: bool,
-    cached_cache_allocation: Option<Bytes>,
-}
-
-impl Default for Downloader {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl Downloader {
-    #[must_use]
-    pub fn new() -> Self {
-        Self {
-            current_download: None,
-            video_size_cache: HashMap::new(),
-            printed_warning: false,
-            cached_cache_allocation: None,
-        }
-    }
-
-    /// Check if enough cache is available. Will wait for 10s if it's not.
-    async fn is_enough_cache_available(
-        &mut self,
-        app: &App,
-        max_cache_size: u64,
-        next_video: &Video,
-    ) -> Result<CacheSizeCheck> {
-        if let Some(cdownload) = &self.current_download {
-            if cdownload.extractor_hash == next_video.extractor_hash {
-                // If the video is already being downloaded it will always fit. Otherwise the
-                // download would not have been started.
-                return Ok(CacheSizeCheck::Fits);
-            }
-        }
-        let cache_allocation = Self::get_current_cache_allocation(app).await?;
-        let video_size = self.get_approx_video_size(app, next_video)?;
-
-        if video_size >= max_cache_size {
-            error!(
-                "The video '{}' ({}) exceeds the maximum cache size ({})! \
-                 Please set a bigger maximum (`--max-cache-size`) or skip it.",
-                next_video.title,
-                Bytes::new(video_size),
-                Bytes::new(max_cache_size)
-            );
-
-            return Ok(CacheSizeCheck::ExceedsMaxCacheSize);
-        }
-
-        if cache_allocation.as_u64() + video_size >= max_cache_size {
-            if !self.printed_warning {
-                warn!(
-                    "Can't download video: '{}' ({}) as it's too large for the cache ({} of {} allocated). \
-                     Waiting for cache size reduction..",
-                    next_video.title,
-                    Bytes::new(video_size),
-                    &cache_allocation,
-                    Bytes::new(max_cache_size)
-                );
-                self.printed_warning = true;
-
-                // Update this value immediately.
-                // This avoids printing the "Current cache size has changed .." warning below.
-                self.cached_cache_allocation = Some(cache_allocation);
-            }
-
-            if let Some(cca) = self.cached_cache_allocation {
-                if cca != cache_allocation {
-                    // Only print the warning if the display string has actually changed.
-                    // Otherwise, we might confuse the user
-                    if cca.to_string() != cache_allocation.to_string() {
-                        warn!(
-                            "Current cache size has changed, it's now: '{}'",
-                            cache_allocation
-                        );
-                    }
-                    debug!(
-                        "Cache size has changed: {} -> {}",
-                        cca.as_u64(),
-                        cache_allocation.as_u64()
-                    );
-                    self.cached_cache_allocation = Some(cache_allocation);
-                }
-            } else {
-                unreachable!(
-                    "The `printed_warning` should be false in this case, \
-                    and thus should have already set the `cached_cache_allocation`."
-                );
-            }
-
-            // Wait and hope, that a large video is deleted from the cache.
-            wait_for_cache_reduction(app).await?;
-            Ok(CacheSizeCheck::TooLarge)
-        } else {
-            self.printed_warning = false;
-            Ok(CacheSizeCheck::Fits)
-        }
-    }
-
-    /// The entry point to the Downloader.
-    /// This Downloader will periodically check if the database has changed, and then also
-    /// change which videos it downloads.
-    /// This will run, until the database doesn't contain any watchable videos
-    pub async fn consume(&mut self, app: Arc<App>, max_cache_size: u64) -> Result<()> {
-        while let Some(next_video) = get_next_uncached_video(&app).await? {
-            match self
-                .is_enough_cache_available(&app, max_cache_size, &next_video)
-                .await?
-            {
-                CacheSizeCheck::Fits => (),
-                CacheSizeCheck::TooLarge => continue,
-                CacheSizeCheck::ExceedsMaxCacheSize => bail!("Giving up."),
-            };
-
-            if self.current_download.is_some() {
-                let current_download = self.current_download.take().unreachable("It is `Some`.");
-
-                if current_download.task_handle.is_finished() {
-                    current_download.task_handle.await??;
-                    continue;
-                }
-
-                if next_video.extractor_hash == current_download.extractor_hash {
-                    // Reset the taken value
-                    self.current_download = Some(current_download);
-                } else {
-                    info!(
-                        "Noticed, that the next video is not the video being downloaded, replacing it ('{}' vs. '{}')!",
-                        next_video.extractor_hash.into_short_hash(&app).await?,
-                        current_download
-                            .extractor_hash
-                            .into_short_hash(&app)
-                            .await?
-                    );
-
-                    // Replace the currently downloading video
-                    // FIXME(@bpeetz): This does not work (probably because of the python part.) <2025-02-21>
-                    current_download.task_handle.abort();
-
-                    let new_current_download =
-                        CurrentDownload::new_from_video(Arc::clone(&app), next_video);
-
-                    self.current_download = Some(new_current_download);
-                }
-            } else {
-                info!(
-                    "No video is being downloaded right now, setting it to '{}'",
-                    next_video.title
-                );
-                let new_current_download =
-                    CurrentDownload::new_from_video(Arc::clone(&app), next_video);
-                self.current_download = Some(new_current_download);
-            }
-
-            // TODO(@bpeetz): Why do we sleep here? <2025-02-21>
-            time::sleep(Duration::from_secs(1)).await;
-        }
-
-        info!("Finished downloading!");
-        Ok(())
-    }
-
-    pub async fn get_current_cache_allocation(app: &App) -> Result<Bytes> {
-        fn dir_size(mut dir: fs::ReadDir) -> BoxFuture<'static, Result<Bytes>> {
-            async move {
-                let mut acc = 0;
-                while let Some(entry) = dir.next_entry().await? {
-                    let size = match entry.metadata().await? {
-                        data if data.is_dir() => {
-                            let path = entry.path();
-                            let read_dir = fs::read_dir(path).await?;
-
-                            dir_size(read_dir).await?.as_u64()
-                        }
-                        data => data.len(),
-                    };
-                    acc += size;
-                }
-                Ok(Bytes::new(acc))
-            }
-            .boxed()
-        }
-
-        let read_dir_result = match fs::read_dir(&app.config.paths.download_dir).await {
-            Ok(ok) => ok,
-            Err(err) => match err.kind() {
-                io::ErrorKind::NotFound => {
-                    fs::create_dir_all(&app.config.paths.download_dir)
-                        .await
-                        .with_context(|| {
-                            format!(
-                                "Failed to create download dir at: '{}'",
-                                &app.config.paths.download_dir.display()
-                            )
-                        })?;
-
-                    info!(
-                        "Created empty download dir at '{}'",
-                        &app.config.paths.download_dir.display(),
-                    );
-
-                    // The new dir should not contain anything (otherwise we would not have had to
-                    // create it)
-                    return Ok(Bytes::new(0));
-                }
-                err => Err(io::Error::from(err)).with_context(|| {
-                    format!(
-                        "Failed to get dir size of download dir at: '{}'",
-                        &app.config.paths.download_dir.display()
-                    )
-                })?,
-            },
-        };
-
-        dir_size(read_dir_result).await
-    }
-
-    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 {
-            // the subtitle file size should be negligible
-            let add_opts = YtDlpOptions {
-                subtitle_langs: String::new(),
-            };
-            let yt_dlp = download_opts(app, &add_opts)?;
-
-            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.get("filesize") {
-                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;
-
-                // TODO: yt_dlp gets this from the format
-                #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
-                let tbr = json_get!(result, "tbr", as_f64).ceil() as u64;
-
-                duration * tbr * (1000 / 8)
-            } else {
-                let hardcoded_default = Bytes::from_str("250 MiB").expect("This is hardcoded");
-                error!(
-                    "Failed to find a filesize for video: '{}' (Using hardcoded value of {})",
-                    video.title, hardcoded_default
-                );
-                hardcoded_default.as_u64()
-            };
-
-            assert_eq!(
-                self.video_size_cache.insert(video.extractor_hash, size),
-                None
-            );
-
-            Ok(size)
-        }
-    }
-
-    async fn actually_cache_video(app: &App, video: &Video) -> Result<()> {
-        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()])
-            .with_context(|| format!("Failed to download video: '{}'", video.title))?;
-
-        assert_eq!(result.len(), 1);
-        let result = &result[0];
-
-        set_video_cache_path(app, &video.extractor_hash, Some(result)).await?;
-
-        info!(
-            "Video '{}' was downlaoded to path: {}",
-            video.title,
-            result.display()
-        );
-
-        Ok(())
-    }
-}
diff --git a/crates/yt/src/download/progress_hook.rs b/crates/yt/src/download/progress_hook.rs
deleted file mode 100644
index c507165..0000000
--- a/crates/yt/src/download/progress_hook.rs
+++ /dev/null
@@ -1,198 +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},
-    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/crates/yt/src/main.rs b/crates/yt/src/main.rs
index 744fe5f..705e642 100644
--- a/crates/yt/src/main.rs
+++ b/crates/yt/src/main.rs
@@ -11,50 +11,32 @@
 
 // `yt` is not a library. Besides, the `anyhow::Result` type is really useless, if you're not going
 // to print it anyways.
-#![allow(clippy::missing_errors_doc)]
+#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
 
-use std::{env::current_exe, sync::Arc};
-
-use anyhow::{Context, Result, bail};
+use anyhow::{Context, Result};
 use app::App;
-use bytes::Bytes;
-use cache::{invalidate, maintain};
 use clap::{CommandFactory, Parser};
-use cli::{CacheCommand, SelectCommand, SubscriptionCommand, VideosCommand};
 use config::Config;
-use log::{error, info};
-use select::cmds::handle_select_cmd;
-use storage::video_database::get::video_by_hash;
-use tokio::{
-    fs::File,
-    io::{BufReader, stdin},
-    task::JoinHandle,
-};
+use log::info;
+
+use crate::commands::Command;
 
-use crate::{cli::Command, storage::subscriptions};
+pub(crate) mod output;
+pub(crate) mod yt_dlp;
 
-pub mod ansi_escape_codes;
-pub mod app;
-pub mod cli;
-pub mod unreachable;
+pub(crate) mod ansi_escape_codes;
+pub(crate) mod app;
+pub(crate) mod cli;
+pub(crate) mod commands;
+pub(crate) mod shared;
 
-pub mod cache;
-pub mod comments;
-pub mod config;
-pub mod constants;
-pub mod download;
-pub mod select;
-pub mod status;
-pub mod storage;
-pub mod subscribe;
-pub mod update;
-pub mod version;
-pub mod videos;
-pub mod watch;
+pub(crate) mod config;
+pub(crate) mod select;
+pub(crate) mod storage;
+pub(crate) mod version;
+pub(crate) mod videos;
 
 #[tokio::main]
-// 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();
 
@@ -84,223 +66,24 @@ async fn main() -> Result<()> {
         }
     });
 
-    let config = Config::from_config_file(args.db_path, args.config_path, args.color)?;
+    let config = Config::from_config_file(args.config_path, args.color, args.db_path)?;
     if args.version {
         version::show(&config).await?;
         return Ok(());
     }
 
-    let app = App::new(config, !args.no_migrate_db).await?;
-
-    match args.command.unwrap_or(Command::default()) {
-        Command::Download {
-            force,
-            max_cache_size,
-        } => {
-            let max_cache_size =
-                max_cache_size.unwrap_or(app.config.download.max_cache_size.as_u64());
-            info!("Max cache size: '{}'", Bytes::new(max_cache_size));
-
-            maintain(&app, false).await?;
-            if force {
-                invalidate(&app, true).await?;
-            }
-
-            download::Downloader::new()
-                .consume(Arc::new(app), max_cache_size)
-                .await?;
-        }
-        Command::Select { cmd } => {
-            let cmd = cmd.unwrap_or(SelectCommand::default());
-
-            match cmd {
-                SelectCommand::File {
-                    done,
-                    use_last_selection,
-                } => 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?,
-            }
-        }
-        Command::Sedowa {} => {
-            Box::pin(select::select_file(&app, false, false)).await?;
-
-            let arc_app = Arc::new(app);
-            dowa(arc_app).await?;
-        }
-        Command::Dowa {} => {
-            let arc_app = Arc::new(app);
-            dowa(arc_app).await?;
-        }
-        Command::Videos { cmd } => match cmd {
-            VideosCommand::List {
-                search_query,
-                limit,
-            } => {
-                videos::query(&app, limit, search_query)
-                    .await
-                    .context("Failed to query videos")?;
-            }
-            VideosCommand::Info { hash } => {
-                let video = video_by_hash(&app, &hash.realize(&app).await?).await?;
-
-                print!(
-                    "{}",
-                    &video
-                        .to_info_display(&app)
-                        .await
-                        .context("Failed to format video")?
-                );
-            }
-        },
-        Command::Update {
-            max_backlog,
-            subscriptions,
-            grouped,
-            current_progress,
-            total_number,
-        } => {
-            let all_subs = subscriptions::get(&app).await?;
-
-            for sub in &subscriptions {
-                if !all_subs.0.contains_key(sub) {
-                    bail!(
-                        "Your specified subscription to update '{}' is not a subscription!",
-                        sub
-                    )
-                }
-            }
-
-            let max_backlog = max_backlog.unwrap_or(app.config.update.max_backlog);
-
-            if grouped {
-                const CHUNK_SIZE: usize = 50;
-
-                assert!(current_progress.is_none() && total_number.is_none());
-
-                let subs = {
-                    if subscriptions.is_empty() {
-                        all_subs.0.into_iter().map(|sub| sub.0).collect()
-                    } else {
-                        subscriptions
-                    }
-                };
-
-                let total_number = subs.len();
-                let mut current_progress = 0;
-                for chunk in subs.chunks(CHUNK_SIZE) {
-                    info!(
-                        "$ yt update {}",
-                        chunk
-                            .iter()
-                            .map(|sub_name| format!("{sub_name:#?}"))
-                            .collect::<Vec<_>>()
-                            .join(" ")
-                    );
-
-                    let status = std::process::Command::new(
-                        current_exe().context("Failed to get the current exe to re-execute")?,
-                    )
-                    .arg("update")
-                    .args(["--current-progress", current_progress.to_string().as_str()])
-                    .args(["--total-number", total_number.to_string().as_str()])
-                    .args(chunk)
-                    .status()?;
+    // Perform config finalization _after_ checking for the version
+    // so that version always works.
+    config
+        .run_finalizers()
+        .context("Failed to finalize config for usage")?;
 
-                    if !status.success() {
-                        bail!("grouped yt update: Child process failed.");
-                    }
-
-                    current_progress += CHUNK_SIZE;
-                }
-            } else {
-                update::update(
-                    &app,
-                    max_backlog,
-                    subscriptions,
-                    total_number,
-                    current_progress,
-                )
-                .await?;
-            }
-        }
-        Command::Subscriptions { cmd } => match cmd {
-            SubscriptionCommand::Add { name, url } => {
-                subscribe::subscribe(&app, name, url)
-                    .await
-                    .context("Failed to add a subscription")?;
-            }
-            SubscriptionCommand::Remove { name } => {
-                subscribe::unsubscribe(&app, name)
-                    .await
-                    .context("Failed to remove a subscription")?;
-            }
-            SubscriptionCommand::List {} => {
-                let all_subs = subscriptions::get(&app).await?;
-
-                for (key, val) in all_subs.0 {
-                    println!("{}: '{}'", key, val.url);
-                }
-            }
-            SubscriptionCommand::Export {} => {
-                let all_subs = subscriptions::get(&app).await?;
-                for val in all_subs.0.values() {
-                    println!("{}", val.url);
-                }
-            }
-            SubscriptionCommand::Import { file, force } => {
-                if let Some(file) = file {
-                    let f = File::open(file).await?;
-
-                    subscribe::import(&app, BufReader::new(f), force).await?;
-                } else {
-                    subscribe::import(&app, BufReader::new(stdin()), force).await?;
-                }
-            }
-        },
-
-        Command::Watch {} => watch::watch(Arc::new(app)).await?,
-        Command::Playlist { watch } => watch::playlist::playlist(&app, watch).await?,
-
-        Command::Status {} => status::show(&app).await?,
-        Command::Config {} => status::config(&app)?,
-
-        Command::Database { command } => match command {
-            CacheCommand::Invalidate { hard } => invalidate(&app, hard).await?,
-            CacheCommand::Maintain { all } => maintain(&app, all).await?,
-        },
-
-        Command::Comments {} => {
-            comments::comments(&app).await?;
-        }
-        Command::Description {} => {
-            comments::description(&app).await?;
-        }
-    }
-
-    Ok(())
-}
-
-async fn dowa(arc_app: Arc<App>) -> Result<()> {
-    let max_cache_size = arc_app.config.download.max_cache_size;
-    info!("Max cache size: '{max_cache_size}'");
-
-    let arc_app_clone = Arc::clone(&arc_app);
-    let download: JoinHandle<()> = tokio::spawn(async move {
-        let result = download::Downloader::new()
-            .consume(arc_app_clone, max_cache_size.as_u64())
-            .await;
+    let app = App::new(config, !args.no_migrate_db).await?;
 
-        if let Err(err) = result {
-            error!("Error from downloader: {err:?}");
-        }
-    });
+    args.command
+        .unwrap_or(Command::default())
+        .implm(app)
+        .await?;
 
-    watch::watch(arc_app).await?;
-    download.await?;
     Ok(())
 }
diff --git a/crates/yt/src/comments/output.rs b/crates/yt/src/output/mod.rs
index cb3a9c4..2f74519 100644
--- a/crates/yt/src/comments/output.rs
+++ b/crates/yt/src/output/mod.rs
@@ -17,9 +17,7 @@ use std::{
 use anyhow::{Context, Result};
 use uu_fmt::{FmtOptions, process_text};
 
-use crate::unreachable::Unreachable;
-
-pub async fn display_fmt_and_less(input: String) -> Result<()> {
+pub(crate) fn display_less(input: String) -> Result<()> {
     let mut less = Command::new("less")
         .args(["--raw-control-chars"])
         .stdin(Stdio::piped())
@@ -27,12 +25,11 @@ pub async fn display_fmt_and_less(input: String) -> Result<()> {
         .spawn()
         .context("Failed to run less")?;
 
-    let input = format_text(&input);
     let mut stdin = less.stdin.take().context("Failed to open stdin")?;
     std::thread::spawn(move || {
         stdin
             .write_all(input.as_bytes())
-            .unreachable("Should be able to write to the stdin of less");
+            .expect("Should be able to write to the stdin of less");
     });
 
     let _ = less.wait().context("Failed to await less")?;
@@ -40,9 +37,15 @@ pub async fn display_fmt_and_less(input: String) -> Result<()> {
     Ok(())
 }
 
+pub(crate) fn display_fmt_and_less(input: &str) -> Result<()> {
+    display_less(format_text(&input, None))
+}
+
 #[must_use]
-pub fn format_text(input: &str) -> String {
-    let width = termsize::get().map_or(90, |size| size.cols);
+pub(crate) fn format_text(input: &str, termsize: Option<u16>) -> String {
+    let input = input.trim();
+
+    let width = termsize.unwrap_or_else(|| termsize::get().map_or(90, |size| size.cols));
     let fmt_opts = FmtOptions {
         uniform: true,
         split_only: true,
diff --git a/crates/yt/src/select/cmds/mod.rs b/crates/yt/src/select/cmds/mod.rs
deleted file mode 100644
index 9da795a..0000000
--- a/crates/yt/src/select/cmds/mod.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 crate::{
-    app::App,
-    cli::{SelectCommand, SharedSelectionCommandArgs},
-    storage::video_database::{
-        Priority, VideoOptions, VideoStatus,
-        get::video_by_hash,
-        set::{set_video_options, video_status},
-    },
-};
-
-use anyhow::{Context, Result, bail};
-
-mod add;
-
-pub async fn handle_select_cmd(
-    app: &App,
-    cmd: SelectCommand,
-    line_number: Option<i64>,
-) -> Result<()> {
-    match cmd {
-        SelectCommand::Pick { shared } => {
-            handle_status_change(app, shared, line_number, VideoStatus::Pick).await?;
-        }
-        SelectCommand::Drop { shared } => {
-            handle_status_change(app, shared, line_number, VideoStatus::Drop).await?;
-        }
-        SelectCommand::Watched { shared } => {
-            handle_status_change(app, shared, line_number, VideoStatus::Watched).await?;
-        }
-        SelectCommand::Add { urls, start, stop } => {
-            Box::pin(add::add(app, urls, start, stop)).await?;
-        }
-        SelectCommand::Watch { shared } => {
-            let hash = shared.hash.clone().realize(app).await?;
-
-            let video = video_by_hash(app, &hash).await?;
-
-            if let VideoStatus::Cached {
-                cache_path,
-                is_focused,
-            } = video.status
-            {
-                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?;
-            }
-        }
-
-        SelectCommand::Url { shared } => {
-            let Some(url) = shared.url else {
-                bail!("You need to provide a url to `select url ..`")
-            };
-
-            let mut firefox = std::process::Command::new("firefox");
-            firefox.args(["-P", "timesinks.youtube"]);
-            firefox.arg(url.as_str());
-            let _handle = firefox.spawn().context("Failed to run firefox")?;
-        }
-        SelectCommand::File { .. } | SelectCommand::Split { .. } => {
-            unreachable!("This should have been filtered out")
-        }
-    }
-    Ok(())
-}
-
-async fn handle_status_change(
-    app: &App,
-    shared: SharedSelectionCommandArgs,
-    line_number: Option<i64>,
-    new_status: VideoStatus,
-) -> Result<()> {
-    let hash = shared.hash.realize(app).await?;
-    let video_options = VideoOptions::new(
-        shared
-            .subtitle_langs
-            .unwrap_or(app.config.select.subtitle_langs.clone()),
-        shared.speed.unwrap_or(app.config.select.playback_speed),
-    );
-    let priority = compute_priority(line_number, shared.priority);
-
-    video_status(app, &hash, new_status, priority).await?;
-    set_video_options(app, &hash, &video_options).await?;
-
-    Ok(())
-}
-
-fn compute_priority(line_number: Option<i64>, priority: Option<i64>) -> Option<Priority> {
-    if let Some(pri) = priority {
-        Some(Priority::from(pri))
-    } else {
-        line_number.map(Priority::from)
-    }
-}
diff --git a/crates/yt/src/select/selection_file/duration.rs b/crates/yt/src/select/duration.rs
index 668a0b8..f1de2ea 100644
--- a/crates/yt/src/select/selection_file/duration.rs
+++ b/crates/yt/src/select/duration.rs
@@ -20,51 +20,45 @@ const HOUR: u64 = 60 * MINUTE;
 const DAY: u64 = 24 * HOUR;
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct MaybeDuration {
+pub(crate) struct MaybeDuration {
     time: Option<Duration>,
 }
 
 impl MaybeDuration {
     #[must_use]
-    pub fn from_std(d: Duration) -> Self {
+    pub(crate) fn from_std(d: Duration) -> Self {
         Self { time: Some(d) }
     }
 
     #[must_use]
-    pub fn from_secs_f64(d: f64) -> Self {
+    pub(crate) fn from_secs_f64(d: f64) -> Self {
         Self {
             time: Some(Duration::from_secs_f64(d)),
         }
     }
     #[must_use]
-    pub fn from_maybe_secs_f64(d: Option<f64>) -> Self {
+    pub(crate) fn from_maybe_secs_f64(d: Option<f64>) -> Self {
         Self {
             time: d.map(Duration::from_secs_f64),
         }
     }
     #[must_use]
-    pub fn from_secs(d: u64) -> Self {
+    #[cfg(test)]
+    pub(crate) fn from_secs(d: u64) -> Self {
         Self {
             time: Some(Duration::from_secs(d)),
         }
     }
 
-    #[must_use]
-    pub fn zero() -> Self {
-        Self {
-            time: Some(Duration::default()),
-        }
-    }
-
     /// Try to return the current duration encoded as seconds.
     #[must_use]
-    pub fn as_secs(&self) -> Option<u64> {
+    pub(crate) fn as_secs(&self) -> Option<u64> {
         self.time.map(|v| v.as_secs())
     }
 
     /// Try to return the current duration encoded as seconds and nanoseconds.
     #[must_use]
-    pub fn as_secs_f64(&self) -> Option<f64> {
+    pub(crate) fn as_secs_f64(&self) -> Option<f64> {
         self.time.map(|v| v.as_secs_f64())
     }
 }
@@ -209,7 +203,7 @@ impl std::fmt::Display for MaybeDuration {
 mod test {
     use std::str::FromStr;
 
-    use crate::select::selection_file::duration::{DAY, HOUR, MINUTE};
+    use crate::select::duration::{DAY, HOUR, MINUTE};
 
     use super::MaybeDuration;
 
diff --git a/crates/yt/src/select/mod.rs b/crates/yt/src/select/mod.rs
index 135bd76..b02677f 100644
--- a/crates/yt/src/select/mod.rs
+++ b/crates/yt/src/select/mod.rs
@@ -9,280 +9,7 @@
 // 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::{
-    collections::HashMap,
-    env::{self},
-    fs::{self, File},
-    io::{BufRead, BufReader, BufWriter, Write},
-    iter,
-    path::Path,
-    string::String,
-};
-
-use crate::{
-    app::App,
-    cli::{CliArgs, SelectSplitSortKey, SelectSplitSortMode},
-    constants::HELP_STR,
-    storage::video_database::{Video, VideoStatusMarker, get},
-    unreachable::Unreachable,
-};
-
-use anyhow::{Context, Result, bail};
-use clap::Parser;
-use cmds::handle_select_cmd;
-use futures::{TryStreamExt, stream::FuturesOrdered};
-use log::info;
-use selection_file::process_line;
-use tempfile::Builder;
-use tokio::process::Command;
-
-pub mod cmds;
-pub mod selection_file;
-
-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)
-        .tempdir()
-        .context("Failed to get tempdir")?;
-
-    let matching_videos = get_videos(app, done).await?;
-
-    let mut no_author = vec![];
-    let mut author_map = HashMap::new();
-    for video in matching_videos {
-        if let Some(sub) = &video.parent_subscription_name {
-            if author_map.contains_key(sub) {
-                let vec: &mut Vec<_> = author_map
-                    .get_mut(sub)
-                    .unreachable("This key is set, we checked in the if above");
-
-                vec.push(video);
-            } else {
-                author_map.insert(sub.to_owned(), vec![video]);
-            }
-        } else {
-            no_author.push(video);
-        }
-    }
-
-    let author_map = {
-        let mut temp_vec: Vec<_> = author_map.into_iter().collect();
-
-        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
-    };
-
-    for (index, (name, videos)) in author_map
-        .into_iter()
-        .chain(iter::once((
-            "<No parent subscription>".to_owned(),
-            no_author,
-        )))
-        .enumerate()
-    {
-        let mut file_path = temp_dir.path().join(format!("{index:02}_{name}"));
-        file_path.set_extension("yts");
-
-        let tmp_file = File::create(&file_path)
-            .with_context(|| format!("Falied to create file at: {}", file_path.display()))?;
-
-        write_videos_to_file(app, &tmp_file, &videos)
-            .await
-            .with_context(|| format!("Falied to populate file at: {}", file_path.display()))?;
-    }
-
-    open_editor_at(temp_dir.path()).await?;
-
-    let mut paths = vec![];
-    for maybe_entry in temp_dir
-        .path()
-        .read_dir()
-        .context("Failed to open temp dir for reading")?
-    {
-        let entry = maybe_entry.context("Failed to read entry in temp dir")?;
-
-        if !entry.file_type()?.is_file() {
-            bail!("Found non-file entry: {}", entry.path().display());
-        }
-
-        paths.push(entry.path());
-    }
-
-    paths.sort();
-
-    let mut processed = 0;
-    for path in paths {
-        let read_file = File::open(path)?;
-        processed = process_file(app, &read_file, processed).await?;
-    }
-
-    info!("Processed {processed} records.");
-    temp_dir.close().context("Failed to close the temp dir")?;
-    Ok(())
-}
-
-pub async fn select_file(app: &App, done: bool, use_last_selection: bool) -> Result<()> {
-    let temp_file = Builder::new()
-        .prefix("yt_video_select-")
-        .suffix(".yts")
-        .rand_bytes(6)
-        .tempfile()
-        .context("Failed to get tempfile")?;
-
-    if use_last_selection {
-        fs::copy(&app.config.paths.last_selection_path, &temp_file)?;
-    } else {
-        let matching_videos = get_videos(app, done).await?;
-
-        write_videos_to_file(app, temp_file.as_file(), &matching_videos).await?;
-    }
-
-    open_editor_at(temp_file.path()).await?;
-
-    let read_file = temp_file.reopen()?;
-    fs::copy(temp_file.path(), &app.config.paths.last_selection_path)
-        .context("Failed to persist selection file")?;
-
-    let processed = process_file(app, &read_file, 0).await?;
-    info!("Processed {processed} records.");
-
-    Ok(())
-}
-
-async fn get_videos(app: &App, include_done: bool) -> Result<Vec<Video>> {
-    if include_done {
-        get::videos(app, VideoStatusMarker::ALL).await
-    } else {
-        get::videos(
-            app,
-            &[
-                VideoStatusMarker::Pick,
-                //
-                VideoStatusMarker::Watch,
-                VideoStatusMarker::Cached,
-            ],
-        )
-        .await
-    }
-}
-
-async fn write_videos_to_file(app: &App, file: &File, videos: &[Video]) -> Result<()> {
-    // Warm-up the cache for the display rendering of the videos.
-    // Otherwise the futures would all try to warm it up at the same time.
-    if let Some(vid) = videos.first() {
-        drop(vid.to_line_display(app).await?);
-    }
-
-    let mut edit_file = BufWriter::new(file);
-
-    videos
-        .iter()
-        .map(|vid| vid.to_select_file_display(app))
-        .collect::<FuturesOrdered<_>>()
-        .try_collect::<Vec<String>>()
-        .await?
-        .into_iter()
-        .try_for_each(|line| -> Result<()> {
-            edit_file
-                .write_all(line.as_bytes())
-                .context("Failed to write to `edit_file`")?;
-
-            Ok(())
-        })?;
-
-    edit_file.write_all(HELP_STR.as_bytes())?;
-    edit_file.flush().context("Failed to flush edit file")?;
-
-    Ok(())
-}
-
-async fn process_file(app: &App, file: &File, processed: i64) -> Result<i64> {
-    let reader = BufReader::new(file);
-
-    let mut line_number = -processed;
-
-    for line in reader.lines() {
-        let line = line.context("Failed to read a line")?;
-
-        if let Some(line) = process_line(&line)? {
-            line_number -= 1;
-
-            // debug!(
-            //     "Parsed command: `{}`",
-            //     line.iter()
-            //         .map(|val| format!("\"{}\"", val))
-            //         .collect::<Vec<String>>()
-            //         .join(" ")
-            // );
-
-            let arg_line = ["yt", "select"]
-                .into_iter()
-                .chain(line.iter().map(String::as_str));
-
-            let args = CliArgs::parse_from(arg_line);
-
-            let crate::cli::Command::Select { cmd } = args
-                .command
-                .unreachable("This will be some, as we constructed it above.")
-            else {
-                unreachable!("This is checked in the `filter_line` function")
-            };
-
-            Box::pin(handle_select_cmd(
-                app,
-                cmd.unreachable(
-                    "This value should always be some \
-                    here, as it would otherwise thrown an error above.",
-                ),
-                Some(line_number),
-            ))
-            .await?;
-        }
-    }
-
-    Ok(-line_number)
-}
-
-async fn open_editor_at(path: &Path) -> Result<()> {
-    let editor = env::var("EDITOR").unwrap_or("nvim".to_owned());
-
-    let mut nvim = Command::new(&editor);
-    nvim.arg(path);
-    let status = nvim
-        .status()
-        .await
-        .with_context(|| format!("Falied to run editor: {editor}"))?;
-
-    if status.success() {
-        Ok(())
-    } else {
-        bail!("Editor ({editor}) exited with error status: {}", status)
-    }
-}
+pub(crate) mod duration;
 
 // // FIXME: There should be no reason why we need to re-run yt, just to get the help string. But I've
 // // yet to find a way to do it without the extra exec <2024-08-20>
diff --git a/crates/yt/src/select/selection_file/mod.rs b/crates/yt/src/select/selection_file/mod.rs
deleted file mode 100644
index f5e0531..0000000
--- a/crates/yt/src/select/selection_file/mod.rs
+++ /dev/null
@@ -1,42 +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>.
-
-//! The data structures needed to express the file, which the user edits
-
-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 split: Vec<_> = {
-            let mut shl = Shlex::new(line);
-            let res = shl.by_ref().collect();
-
-            if shl.had_error {
-                bail!("Failed to parse line '{line}'")
-            }
-
-            assert_eq!(shl.line_no, 1, "A unexpected newline appeared");
-            res
-        };
-
-        assert!(!split.is_empty());
-
-        Ok(Some(split))
-    }
-}
diff --git a/crates/bytes/src/error.rs b/crates/yt/src/shared/bytes/error.rs
index c9783d8..c9783d8 100644
--- a/crates/bytes/src/error.rs
+++ b/crates/yt/src/shared/bytes/error.rs
diff --git a/crates/bytes/src/lib.rs b/crates/yt/src/shared/bytes/mod.rs
index 2a9248d..31e782e 100644
--- a/crates/bytes/src/lib.rs
+++ b/crates/yt/src/shared/bytes/mod.rs
@@ -16,6 +16,7 @@
 )]
 use std::{fmt::Display, str::FromStr};
 
+use ::serde::{Deserialize, Serialize};
 use error::BytesError;
 
 const B: u64 = 1;
@@ -31,10 +32,11 @@ const MB: u64 = 1000 * KB;
 const GB: u64 = 1000 * MB;
 const TB: u64 = 1000 * GB;
 
-pub mod error;
-pub mod serde;
+pub(crate) mod error;
 
-#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Deserialize, Serialize)]
+#[serde(try_from = "String")]
+#[serde(into = "String")]
 pub struct Bytes(u64);
 
 impl Bytes {
@@ -131,6 +133,20 @@ impl Display for Bytes {
     }
 }
 
+impl From<Bytes> for String {
+    fn from(value: Bytes) -> Self {
+        value.to_string()
+    }
+}
+
+impl TryFrom<String> for Bytes {
+    type Error = BytesError;
+
+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        value.as_str().parse()
+    }
+}
+
 // taken from this stack overflow question: https://stackoverflow.com/a/76572321
 /// Round to significant digits (rather than digits after the decimal).
 ///
@@ -149,7 +165,7 @@ impl Display for Bytes {
 ///# }
 /// ```
 #[must_use]
-pub fn precision_f64(x: f64, decimals: u32) -> f64 {
+pub(crate) fn precision_f64(x: f64, decimals: u32) -> f64 {
     if x == 0. || decimals == 0 {
         0.
     } else {
diff --git a/crates/yt/src/shared/mod.rs b/crates/yt/src/shared/mod.rs
new file mode 100644
index 0000000..d3cc563
--- /dev/null
+++ b/crates/yt/src/shared/mod.rs
@@ -0,0 +1,11 @@
+// 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>.
+
+pub(crate) mod bytes;
diff --git a/crates/yt/src/status/mod.rs b/crates/yt/src/status/mod.rs
deleted file mode 100644
index 6883802..0000000
--- a/crates/yt/src/status/mod.rs
+++ /dev/null
@@ -1,130 +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 std::time::Duration;
-
-use crate::{
-    app::App,
-    download::Downloader,
-    select::selection_file::duration::MaybeDuration,
-    storage::{
-        subscriptions,
-        video_database::{VideoStatusMarker, get},
-    },
-};
-
-use anyhow::{Context, Result};
-use bytes::Bytes;
-
-macro_rules! get {
-    ($videos:expr, $status:ident) => {
-        $videos
-            .iter()
-            .filter(|vid| vid.status.as_marker() == VideoStatusMarker::$status)
-            .count()
-    };
-
-    (@collect $videos:expr, $status:ident) => {
-        $videos
-            .iter()
-            .filter(|vid| vid.status.as_marker() == VideoStatusMarker::$status)
-            .collect()
-    };
-}
-
-pub async fn show(app: &App) -> Result<()> {
-    let all_videos = get::videos(app, VideoStatusMarker::ALL).await?;
-
-    // lengths
-    let picked_videos_len = get!(all_videos, Pick);
-
-    let watch_videos_len = get!(all_videos, Watch);
-    let cached_videos_len = get!(all_videos, Cached);
-    let watched_videos_len = get!(all_videos, Watched);
-    let watched_videos: Vec<_> = get!(@collect all_videos, Watched);
-
-    let drop_videos_len = get!(all_videos, Drop);
-    let dropped_videos_len = get!(all_videos, Dropped);
-
-    let subscriptions = subscriptions::get(app).await?;
-    let subscriptions_len = subscriptions.0.len();
-
-    let watchtime_status = {
-        let total_watch_time_raw = watched_videos
-            .iter()
-            .fold(Duration::default(), |acc, vid| acc + vid.watch_progress);
-
-        // Most things are watched at a speed of s (which is defined in the config file).
-        // Thus
-        //      y = x * s -> y / s = x
-        let total_watch_time = Duration::from_secs_f64(
-            (total_watch_time_raw.as_secs_f64()) / app.config.select.playback_speed,
-        );
-
-        let speed = app.config.select.playback_speed;
-
-        // Do not print the adjusted time, if the user has keep the speed level at 1.
-        #[allow(clippy::float_cmp)]
-        if speed == 1.0 {
-            format!(
-                "Total Watchtime: {}\n",
-                MaybeDuration::from_std(total_watch_time_raw)
-            )
-        } else {
-            format!(
-                "Total Watchtime: {} (at {speed} speed: {})\n",
-                MaybeDuration::from_std(total_watch_time_raw),
-                MaybeDuration::from_std(total_watch_time),
-            )
-        }
-    };
-
-    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")?;
-    let cache_usage: Bytes = cache_usage_raw;
-    println!(
-        "\
-Picked   Videos: {picked_videos_len}
-
-Watch    Videos: {watch_videos_len}
-Cached   Videos: {cached_videos_len}
-Watched  Videos: {watched_videos_len} (watch rate: {watch_rate:.2} %)
-
-Drop     Videos: {drop_videos_len}
-Dropped  Videos: {dropped_videos_len}
-
-{watchtime_status}
-
-  Subscriptions: {subscriptions_len}
-    Cache usage: {cache_usage}"
-    );
-
-    Ok(())
-}
-
-pub fn config(app: &App) -> Result<()> {
-    let config_str = toml::to_string(&app.config)?;
-
-    print!("{config_str}");
-
-    Ok(())
-}
diff --git a/crates/yt/src/storage/db/extractor_hash.rs b/crates/yt/src/storage/db/extractor_hash.rs
new file mode 100644
index 0000000..3ad8273
--- /dev/null
+++ b/crates/yt/src/storage/db/extractor_hash.rs
@@ -0,0 +1,220 @@
+// 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 std::{collections::HashSet, fmt::Display, str::FromStr};
+
+use anyhow::{Context, Result, bail};
+use blake3::Hash;
+use log::debug;
+use serde::{Deserialize, Serialize};
+use tokio::sync::OnceCell;
+use yt_dlp::{info_json::InfoJson, json_cast, json_get, json_try_get};
+
+use crate::app::App;
+
+static EXTRACTOR_HASH_LENGTH: OnceCell<usize> = OnceCell::const_new();
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)]
+pub(crate) struct ExtractorHash {
+    hash: Hash,
+}
+
+impl Display for ExtractorHash {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.hash.fmt(f)
+    }
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct ShortHash(String);
+
+impl Display for ShortHash {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+#[derive(Debug, Clone)]
+#[allow(clippy::module_name_repetitions)]
+pub(crate) struct LazyExtractorHash {
+    value: ShortHash,
+}
+
+impl FromStr for LazyExtractorHash {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+        // perform some cheap validation
+        if s.len() > 64 {
+            bail!("A hash can only contain 64 bytes!");
+        }
+
+        Ok(Self {
+            value: ShortHash(s.to_owned()),
+        })
+    }
+}
+
+impl LazyExtractorHash {
+    /// Turn the [`LazyExtractorHash`] into the [`ExtractorHash`]
+    pub(crate) async fn realize(
+        self,
+        app: &App,
+        all_hashes: Option<&[ExtractorHash]>,
+    ) -> Result<ExtractorHash> {
+        ExtractorHash::from_short_hash(app, &self.value, all_hashes).await
+    }
+}
+
+impl ExtractorHash {
+    #[must_use]
+    pub(crate) fn from_hash(hash: Hash) -> Self {
+        Self { hash }
+    }
+
+    pub(crate) async fn from_short_hash(
+        app: &App,
+        s: &ShortHash,
+        all_hashes: Option<&[Self]>,
+    ) -> Result<Self> {
+        let all_hashes = if let Some(all) = all_hashes {
+            all
+        } else {
+            &Self::get_all(app)
+                .await
+                .context("Failed to fetch all extractor-hashes from the database")?
+        };
+        let needed_chars = s.0.len();
+        for hash in all_hashes {
+            // PERFORMANCE(@bpeetz): This could avoid the string construction and just use a
+            // numeric equality check instead. <2025-07-15>
+            if hash.hash().to_hex()[..needed_chars] == s.0 {
+                return Ok(*hash);
+            }
+        }
+        bail!("Your shortend hash, does not match a real hash (this is probably a bug)!");
+    }
+
+    pub(crate) fn from_info_json(entry: &InfoJson) -> Self {
+        // HACK(@bpeetz): The code that follows is a gross hack.
+        // One would expect the `id` to be unique _and_ constant for each and every possible info JSON.
+        // But .. it's just not. The `ARDMediathek` extractor, will sometimes return different `id`s for the same
+        // video, effectively causing us to insert the same video again into the db (which fails,
+        // because the URL is still unique).
+        //
+        // As such we _should_ probably find a constant value for all extractors, but that just does
+        // not exist currently, without processing each entry (which is expensive and which I would
+        // like to avoid).
+        //
+        // Therefor, we simply special case the `ARDBetaMediathek` extractor. <2025-07-04>
+
+        // NOTE(@bpeetz): `yt-dlp` apparently uses these two different names for the same thing <2025-07-04>
+        let ie_key = {
+            if let Some(ie_key) = json_try_get!(entry, "ie_key", as_str) {
+                ie_key
+            } else if let Some(extractor_key) = json_try_get!(entry, "extractor_key", as_str) {
+                extractor_key
+            } else {
+                unreachable!(
+                    "Either `ie_key` or `extractor_key` \
+                should be set on every entry info json"
+                )
+            }
+        };
+
+        if ie_key == "ARDBetaMediathek" {
+            // NOTE(@bpeetz): The mediathek is changing their Id scheme, from an `short` old Id to the
+            // new id. As the new id is too long for some people, yt-dlp will be default return the old
+            // one (when it is still available!). The new one is called `display_id`.
+            // Therefore, we simply check if the new one is explicitly returned, and otherwise use the
+            // normal `id` value, as these are cases where the old one is no longer available. <2025-07-04>
+            let id = if let Some(display_id) = json_try_get!(entry, "display_id", as_str) {
+                display_id.as_bytes()
+            } else {
+                json_get!(entry, "id", as_str).as_bytes()
+            };
+
+            Self {
+                hash: blake3::hash(id),
+            }
+        } else {
+            Self {
+                hash: blake3::hash(json_get!(entry, "id", as_str).as_bytes()),
+            }
+        }
+    }
+
+    #[must_use]
+    pub(crate) fn hash(&self) -> &Hash {
+        &self.hash
+    }
+
+    pub(crate) async fn as_short_hash(&self, app: &App) -> Result<ShortHash> {
+        let needed_chars = if let Some(needed_chars) = EXTRACTOR_HASH_LENGTH.get() {
+            *needed_chars
+        } else {
+            let needed_chars = self
+                .get_needed_char_len(app)
+                .await
+                .context("Failed to calculate needed char length")?;
+            EXTRACTOR_HASH_LENGTH
+                .set(needed_chars)
+                .expect("This should work at this stage, as we checked above that it is empty.");
+
+            needed_chars
+        };
+
+        Ok(ShortHash(
+            self.hash()
+                .to_hex()
+                .chars()
+                .take(needed_chars)
+                .collect::<String>(),
+        ))
+    }
+
+    async fn get_needed_char_len(&self, app: &App) -> Result<usize> {
+        debug!("Calculating the needed hash char length");
+        let all_hashes = Self::get_all(app)
+            .await
+            .context("Failed to fetch all extractor -hashesh from database")?;
+
+        let all_char_vec_hashes = all_hashes
+            .into_iter()
+            .map(|hash| hash.hash().to_hex().chars().collect::<Vec<char>>())
+            .collect::<Vec<Vec<_>>>();
+
+        // This value should be updated later, if not rust will panic in the assertion.
+        let mut needed_chars: usize = 1000;
+        'outer: for i in 1..64 {
+            let i_chars: Vec<String> = all_char_vec_hashes
+                .iter()
+                .map(|vec| vec.iter().take(i).collect::<String>())
+                .collect();
+
+            let mut uniqnes_hashmap: HashSet<String> = HashSet::new();
+            for ch in i_chars {
+                if !uniqnes_hashmap.insert(ch) {
+                    // The key was already in the hash map, thus we have a duplicated char and need
+                    // at least one char more
+                    continue 'outer;
+                }
+            }
+
+            needed_chars = i;
+            break 'outer;
+        }
+
+        assert!(needed_chars <= 64, "Hashes are only 64 bytes long");
+
+        Ok(needed_chars)
+    }
+}
diff --git a/crates/yt/src/storage/db/get/extractor_hash.rs b/crates/yt/src/storage/db/get/extractor_hash.rs
new file mode 100644
index 0000000..c8e150a
--- /dev/null
+++ b/crates/yt/src/storage/db/get/extractor_hash.rs
@@ -0,0 +1,68 @@
+// 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 anyhow::Result;
+use blake3::Hash;
+use sqlx::{SqliteConnection, query};
+
+use crate::{
+    app::App,
+    storage::db::{
+        extractor_hash::ExtractorHash,
+        video::{Video, video_from_record},
+    },
+};
+
+impl ExtractorHash {
+    pub(crate) async fn get(&self, txn: &mut SqliteConnection) -> Result<Video> {
+        let extractor_hash = self.hash().to_string();
+
+        let base = query!(
+            r#"
+            SELECT *
+            FROM videos
+            WHERE extractor_hash = ?
+            "#,
+            extractor_hash
+        )
+        .fetch_one(txn)
+        .await?;
+
+        Ok(video_from_record!(base))
+    }
+
+    pub(crate) async fn get_with_app(&self, app: &App) -> Result<Video> {
+        let mut txn = app.database.begin().await?;
+        let out = self.get(&mut txn).await?;
+        txn.commit().await?;
+
+        Ok(out)
+    }
+
+    pub(crate) async fn get_all(app: &App) -> Result<Vec<Self>> {
+        let hashes_hex = query!(
+            r#"
+        SELECT extractor_hash
+        FROM videos;
+        "#
+        )
+        .fetch_all(&app.database)
+        .await?;
+
+        Ok(hashes_hex
+            .iter()
+            .map(|hash| {
+                Self::from_hash(Hash::from_hex(&hash.extractor_hash).expect(
+                    "These values started as blake3 hashes, they should stay blake3 hashes",
+                ))
+            })
+            .collect())
+    }
+}
diff --git a/crates/yt/src/storage/db/get/mod.rs b/crates/yt/src/storage/db/get/mod.rs
new file mode 100644
index 0000000..4bcd066
--- /dev/null
+++ b/crates/yt/src/storage/db/get/mod.rs
@@ -0,0 +1,15 @@
+// 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>.
+
+pub(crate) mod extractor_hash;
+pub(crate) mod playlist;
+pub(crate) mod subscription;
+pub(crate) mod txn_log;
+pub(crate) mod video;
diff --git a/crates/yt/src/storage/db/get/playlist.rs b/crates/yt/src/storage/db/get/playlist.rs
new file mode 100644
index 0000000..5094523
--- /dev/null
+++ b/crates/yt/src/storage/db/get/playlist.rs
@@ -0,0 +1,68 @@
+// 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::{
+    app::App,
+    storage::db::{
+        playlist::{Playlist, PlaylistIndex},
+        video::{Video, VideoStatusMarker},
+    },
+};
+
+use anyhow::Result;
+
+impl Playlist {
+    /// Get an video based in its index.
+    #[must_use]
+    pub(crate) fn get_mut(&mut self, index: PlaylistIndex) -> Option<&mut Video> {
+        self.videos.get_mut(Into::<usize>::into(index))
+    }
+
+    /// Create a playlist, by loading it from the database.
+    pub(crate) async fn create(app: &App) -> Result<Self> {
+        let videos = Video::in_states(app, &[VideoStatusMarker::Cached]).await?;
+
+        Ok(Self { videos })
+    }
+
+    /// Return the current playlist index.
+    ///
+    /// This effectively looks for the currently focused video and returns it's index.
+    ///
+    /// # Panics
+    /// Only if internal assertions fail.
+    pub(crate) fn current_index(&self) -> Option<PlaylistIndex> {
+        if let Some((index, _)) = self.get_focused() {
+            Some(index)
+        } else {
+            None
+        }
+    }
+
+    /// Get the currently focused video, if it exists.
+    #[must_use]
+    pub(crate) fn get_focused_mut(&mut self) -> Option<(PlaylistIndex, &mut Video)> {
+        self.videos
+            .iter_mut()
+            .enumerate()
+            .find(|(_, v)| v.is_focused())
+            .map(|(index, video)| (PlaylistIndex::from(index), video))
+    }
+
+    /// Get the currently focused video, if it exists.
+    #[must_use]
+    pub(crate) fn get_focused(&self) -> Option<(PlaylistIndex, &Video)> {
+        self.videos
+            .iter()
+            .enumerate()
+            .find(|(_, v)| v.is_focused())
+            .map(|(index, video)| (PlaylistIndex::from(index), video))
+    }
+}
diff --git a/crates/yt/src/storage/db/get/subscription.rs b/crates/yt/src/storage/db/get/subscription.rs
new file mode 100644
index 0000000..1d0b660
--- /dev/null
+++ b/crates/yt/src/storage/db/get/subscription.rs
@@ -0,0 +1,65 @@
+// 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::collections::HashMap;
+
+use crate::{
+    app::App,
+    storage::db::subscription::{Subscription, Subscriptions},
+};
+
+use anyhow::Result;
+use sqlx::query;
+use url::Url;
+
+impl Subscriptions {
+    /// Get a list of subscriptions
+    pub(crate) async fn get(app: &App) -> Result<Self> {
+        let raw_subs = query!(
+            "
+        SELECT *
+        FROM subscriptions;
+        "
+        )
+        .fetch_all(&app.database)
+        .await?;
+
+        let subscriptions: HashMap<String, Subscription> = raw_subs
+            .into_iter()
+            .map(|sub| {
+                (
+                    sub.name.clone(),
+                    Subscription::new(
+                        sub.name,
+                        Url::parse(&sub.url).expect("It was an URL, when we inserted it."),
+                        if sub.is_active == 1 {
+                            true
+                        } else if sub.is_active == 0 {
+                            false
+                        } else {
+                            unreachable!("These are the only two options")
+                        },
+                    ),
+                )
+            })
+            .collect();
+
+        Ok(Subscriptions(subscriptions))
+    }
+
+    pub(crate) fn remove_inactive(self) -> Self {
+        Self(
+            self.0
+                .into_iter()
+                .filter(|(_, sub)| sub.is_active)
+                .collect(),
+        )
+    }
+}
diff --git a/crates/yt/src/storage/db/get/txn_log.rs b/crates/yt/src/storage/db/get/txn_log.rs
new file mode 100644
index 0000000..1a6df2c
--- /dev/null
+++ b/crates/yt/src/storage/db/get/txn_log.rs
@@ -0,0 +1,43 @@
+// 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::{
+    app::App,
+    storage::db::{insert::Committable, txn_log::TxnLog, video::TimeStamp},
+};
+
+use anyhow::Result;
+use sqlx::query;
+
+impl<O: Committable> TxnLog<O> {
+    /// Get the log of all operations that have been performed.
+    pub(crate) async fn get(app: &App) -> Result<Self> {
+        let raw_ops = query!(
+            "
+        SELECT *
+        FROM txn_log
+        ORDER BY timestamp ASC;
+        "
+        )
+        .fetch_all(&app.database)
+        .await?;
+
+        let inner = raw_ops
+            .into_iter()
+            .filter_map(|raw_op| {
+                serde_json::from_str(&raw_op.operation)
+                    .map(|parsed_op| (TimeStamp::from_secs(raw_op.timestamp), parsed_op))
+                    .ok()
+            })
+            .collect();
+
+        Ok(TxnLog::new(inner))
+    }
+}
diff --git a/crates/yt/src/storage/db/get/video/mod.rs b/crates/yt/src/storage/db/get/video/mod.rs
new file mode 100644
index 0000000..69adb6b
--- /dev/null
+++ b/crates/yt/src/storage/db/get/video/mod.rs
@@ -0,0 +1,261 @@
+// 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::{fs::File, path::PathBuf};
+
+use anyhow::{Context, Result, bail};
+use log::debug;
+use sqlx::query;
+use yt_dlp::{info_json::InfoJson, json_cast, json_try_get};
+
+use crate::{
+    app::App,
+    storage::db::video::{
+        Video, VideoStatus, VideoStatusMarker,
+        comments::{Comments, raw::RawComment},
+        video_from_record,
+    },
+};
+
+impl Video {
+    /// Returns to next video which should be downloaded. This respects the priority assigned by select.
+    /// It does not return videos, which are already downloaded.
+    ///
+    /// # Panics
+    /// Only if assertions fail.
+    pub(crate) async fn next_to_download(app: &App) -> Result<Option<Self>> {
+        let status = VideoStatus::Watch.as_marker().as_db_integer();
+
+        // NOTE: The ORDER BY statement should be the same as the one in [`in_states`]. <2024-08-22>
+        let result = query!(
+            r#"
+        SELECT *
+        FROM videos
+        WHERE status = ? AND cache_path IS NULL
+        ORDER BY priority DESC, publish_date DESC
+        LIMIT 1;
+    "#,
+            status
+        )
+        .fetch_one(&app.database)
+        .await;
+
+        if let Err(sqlx::Error::RowNotFound) = result {
+            Ok(None)
+        } else {
+            let base = result?;
+
+            Ok(Some(video_from_record!(base)))
+        }
+    }
+
+    /// Returns the description of the current video.
+    /// The returned description will be set to `<No description>` in the absence of one.
+    ///
+    /// # Errors
+    /// If no current video exists.
+    ///
+    /// # Panics
+    /// If the current video lacks the `info.json` file.
+    pub(crate) async fn get_current_description(app: &App) -> Result<String> {
+        let Some(currently_playing_video) = Video::currently_focused(app).await? else {
+            bail!("Could not find a currently playing video!");
+        };
+
+        let info_json = &currently_playing_video.get_info_json()?.expect(
+            "A currently *playing* must be cached. \
+                And thus the info.json should be available.",
+        );
+
+        let description = json_try_get!(info_json, "description", as_str)
+            .unwrap_or("<No description>")
+            .to_owned();
+
+        Ok(description)
+    }
+
+    /// Returns the comments of the current video.
+    /// The returned [`Comments`] will be empty in the absence of comments.
+    ///
+    /// # Errors
+    /// If no current video exists.
+    ///
+    /// # Panics
+    /// If the current video lacks the `info.json` file.
+    pub(crate) async fn get_current_comments(app: &App) -> Result<Comments> {
+        let Some(currently_playing_video) = Video::currently_focused(app).await? else {
+            bail!("Could not find a currently playing video!");
+        };
+
+        let info_json = &currently_playing_video.get_info_json()?.expect(
+            "A currently *playing* video must be cached. \
+                And thus the info.json should be available.",
+        );
+
+        let raw_comments = if let Some(comments) = json_try_get!(info_json, "comments", as_array) {
+            comments
+                .iter()
+                .cloned()
+                .map(serde_json::from_value)
+                .collect::<Result<Vec<RawComment>, _>>()?
+        } else {
+            // TODO(@bpeetz): We could display a `<No-comments>` here. <2025-07-15>
+
+            bail!(
+                "The video ('{}') does not have comments!",
+                json_try_get!(info_json, "title", as_str).unwrap_or("<No Title>")
+            )
+        };
+
+        Ok(Comments::from_raw(raw_comments))
+    }
+
+    /// Optionally returns the video that is currently focused.
+    ///
+    /// # Panics
+    /// Only if assertions fail.
+    pub(crate) async fn currently_focused(app: &App) -> Result<Option<Self>> {
+        let status = VideoStatusMarker::Cached.as_db_integer();
+
+        let result = query!(
+            r#"
+            SELECT *
+            FROM videos
+            WHERE status = ? AND is_focused = 1
+            "#,
+            status
+        )
+        .fetch_one(&app.database)
+        .await;
+
+        if let Err(sqlx::Error::RowNotFound) = result {
+            Ok(None)
+        } else {
+            let base = result?;
+
+            Ok(Some(video_from_record!(base)))
+        }
+    }
+
+    /// Calculate the [`info_json`] location on-disk for this video.
+    ///
+    /// Will return [`None`], if the video does not have an downloaded [`info_json`]
+    pub(crate) fn info_json_path(&self) -> Result<Option<PathBuf>> {
+        if let VideoStatus::Cached { mut cache_path, .. } = self.status.clone() {
+            if !cache_path.set_extension("info.json") {
+                bail!(
+                    "Failed to change path extension to 'info.json': {}",
+                    cache_path.display()
+                );
+            }
+
+            Ok(Some(cache_path))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Fetch the [`info_json`], downloaded on-disk for this video.
+    ///
+    /// Will return [`None`], if the video does not have an downloaded [`info_json`]
+    pub(crate) fn get_info_json(&self) -> Result<Option<InfoJson>> {
+        if let Some(path) = self.info_json_path()? {
+            let info_json_string = File::open(path)?;
+            let info_json = serde_json::from_reader(&info_json_string)?;
+
+            Ok(Some(info_json))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Returns this videos `is_focused` flag if it is set.
+    ///
+    /// Will return `false` for not-downloaded videos.
+    pub(crate) fn is_focused(&self) -> bool {
+        if let VideoStatus::Cached { is_focused, .. } = &self.status {
+            *is_focused
+        } else {
+            false
+        }
+    }
+
+    /// Returns the videos that are in the `allowed_states`.
+    ///
+    /// # Panics
+    /// Only, if assertions fail.
+    pub(crate) async fn in_states(
+        app: &App,
+        allowed_states: &[VideoStatusMarker],
+    ) -> Result<Vec<Video>> {
+        fn test(all_states: &[VideoStatusMarker], check: VideoStatusMarker) -> Option<i64> {
+            if all_states.contains(&check) {
+                Some(check.as_db_integer())
+            } else {
+                None
+            }
+        }
+        fn states_to_string(allowed_states: &[VideoStatusMarker]) -> String {
+            let mut states = allowed_states
+                .iter()
+                .fold(String::from("&["), |mut acc, state| {
+                    acc.push_str(state.as_str());
+                    acc.push_str(", ");
+                    acc
+                });
+            states = states.trim().to_owned();
+            states = states.trim_end_matches(',').to_owned();
+            states.push(']');
+            states
+        }
+
+        debug!(
+            "Fetching videos in the states: '{}'",
+            states_to_string(allowed_states)
+        );
+        let active_pick: Option<i64> = test(allowed_states, VideoStatusMarker::Pick);
+        let active_watch: Option<i64> = test(allowed_states, VideoStatusMarker::Watch);
+        let active_cached: Option<i64> = test(allowed_states, VideoStatusMarker::Cached);
+        let active_watched: Option<i64> = test(allowed_states, VideoStatusMarker::Watched);
+        let active_drop: Option<i64> = test(allowed_states, VideoStatusMarker::Drop);
+        let active_dropped: Option<i64> = test(allowed_states, VideoStatusMarker::Dropped);
+
+        // NOTE: The ORDER BY statement should be the same as the one in [`next_to_download`]. <2024-08-22>
+        let videos = query!(
+            r"
+          SELECT *
+          FROM videos
+          WHERE status IN (?,?,?,?,?,?)
+          ORDER BY priority DESC, publish_date DESC;
+          ",
+            active_pick,
+            active_watch,
+            active_cached,
+            active_watched,
+            active_drop,
+            active_dropped,
+        )
+        .fetch_all(&app.database)
+        .await
+        .with_context(|| {
+            format!(
+                "Failed to query videos with states: '{}'",
+                states_to_string(allowed_states)
+            )
+        })?;
+
+        let real_videos: Vec<Video> = videos
+            .iter()
+            .map(|base| -> Video { video_from_record!(base) })
+            .collect();
+
+        Ok(real_videos)
+    }
+}
diff --git a/crates/yt/src/storage/db/insert/maintenance.rs b/crates/yt/src/storage/db/insert/maintenance.rs
new file mode 100644
index 0000000..d87c1ae
--- /dev/null
+++ b/crates/yt/src/storage/db/insert/maintenance.rs
@@ -0,0 +1,38 @@
+// 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::{
+    app::App,
+    storage::db::{
+        insert::Operations,
+        video::{Video, VideoStatus, VideoStatusMarker},
+    },
+};
+
+use anyhow::Result;
+
+/// Remove the downloaded paths from videos in the db, that no longer exist on the file system.
+pub(crate) async fn clear_stale_downloaded_paths(app: &App) -> Result<()> {
+    let mut cached_videos = Video::in_states(app, &[VideoStatusMarker::Cached]).await?;
+
+    let mut ops = Operations::new("DbMaintain: init");
+    for vid in &mut cached_videos {
+        if let VideoStatus::Cached { cache_path, .. } = &vid.status {
+            if !cache_path.exists() {
+                vid.remove_download_path(&mut ops);
+            }
+        } else {
+            unreachable!("We only asked for cached videos.")
+        }
+    }
+    ops.commit(app).await?;
+
+    Ok(())
+}
diff --git a/crates/yt/src/storage/db/insert/mod.rs b/crates/yt/src/storage/db/insert/mod.rs
new file mode 100644
index 0000000..3458608
--- /dev/null
+++ b/crates/yt/src/storage/db/insert/mod.rs
@@ -0,0 +1,115 @@
+// 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::mem;
+
+use crate::app::App;
+
+use anyhow::Result;
+use chrono::Utc;
+use log::{debug, trace};
+use serde::{Serialize, de::DeserializeOwned};
+use sqlx::{SqliteConnection, query};
+
+pub(crate) mod maintenance;
+pub(crate) mod playlist;
+pub(crate) mod subscription;
+pub(crate) mod video;
+
+pub(crate) trait Committable:
+    Sized + std::fmt::Debug + Serialize + DeserializeOwned
+{
+    async fn commit(self, txn: &mut SqliteConnection) -> Result<()>;
+}
+
+#[derive(Debug)]
+pub(crate) struct Operations<O: Committable> {
+    name: &'static str,
+    ops: Vec<O>,
+}
+
+impl<O: Committable> Default for Operations<O> {
+    fn default() -> Self {
+        Self::new("<default impl>")
+    }
+}
+
+impl<O: Committable> Operations<O> {
+    #[must_use]
+    pub(crate) fn new(name: &'static str) -> Self {
+        Self {
+            name,
+            ops: Vec::new(),
+        }
+    }
+
+    pub(crate) async fn commit(mut self, app: &App) -> Result<()> {
+        let ops = mem::take(&mut self.ops);
+
+        if ops.is_empty() {
+            return Ok(());
+        }
+
+        trace!("Begin commit of {}", self.name);
+        let mut txn = app.database.begin().await?;
+
+        for op in ops {
+            trace!("Commiting operation: {op:?}");
+            add_operation_to_txn_log(&op, &mut txn).await?;
+            op.commit(&mut txn).await?;
+        }
+
+        txn.commit().await?;
+        trace!("End commit of {}", self.name);
+
+        Ok(())
+    }
+
+    pub(crate) fn push(&mut self, op: O) {
+        self.ops.push(op);
+    }
+}
+
+impl<O: Committable> Drop for Operations<O> {
+    fn drop(&mut self) {
+        assert!(
+            self.ops.is_empty(),
+            "Trying to drop uncommitted operations (name: {}) ({:#?}). This is a bug.",
+            self.name,
+            self.ops
+        );
+    }
+}
+
+async fn add_operation_to_txn_log<O: Committable>(
+    operation: &O,
+    txn: &mut SqliteConnection,
+) -> Result<()> {
+    debug!("Adding operation to txn log: {operation:?}");
+
+    let now = Utc::now().timestamp();
+    let operation = serde_json::to_string(&operation).expect("should be serializable");
+
+    query!(
+        r#"
+        INSERT INTO txn_log (
+            timestamp,
+            operation
+        )
+        VALUES (?, ?);
+        "#,
+        now,
+        operation,
+    )
+    .execute(txn)
+    .await?;
+
+    Ok(())
+}
diff --git a/crates/yt/src/storage/db/insert/playlist.rs b/crates/yt/src/storage/db/insert/playlist.rs
new file mode 100644
index 0000000..4d3e140
--- /dev/null
+++ b/crates/yt/src/storage/db/insert/playlist.rs
@@ -0,0 +1,222 @@
+// 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::{cmp::Ordering, time::Duration};
+
+use anyhow::{Context, Result};
+use colors::Colorize;
+use libmpv2::Mpv;
+use log::{debug, trace};
+
+use crate::{
+    app::App,
+    storage::db::{
+        insert::{Operations, video::Operation},
+        playlist::{Playlist, PlaylistIndex},
+        video::VideoStatus,
+    },
+};
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum VideoTransition {
+    Watched,
+    Picked,
+}
+
+impl Playlist {
+    pub(crate) fn mark_current_done(
+        &mut self,
+        app: &App,
+        mpv: &Mpv,
+        new_state: VideoTransition,
+        ops: &mut Operations<Operation>,
+    ) -> Result<()> {
+        let (current_index, current_video) = self
+            .get_focused_mut()
+            .expect("This should be some at this point");
+
+        debug!(
+            "Playlist handler will mark video '{}' {:?}.",
+            current_video.title, new_state
+        );
+
+        match new_state {
+            VideoTransition::Watched => current_video.set_watched(ops),
+            VideoTransition::Picked => current_video.set_status(VideoStatus::Pick, ops),
+        }
+
+        self.save_watch_progress(mpv, current_index, ops);
+
+        self.videos.remove(Into::<usize>::into(current_index));
+
+        {
+            // Decide which video to mark focused now.
+            let index = usize::from(current_index);
+            let playlist_length = self.len();
+
+            if playlist_length == 0 {
+                // There are no new videos to mark focused.
+            } else {
+                let index = match index.cmp(&playlist_length) {
+                    Ordering::Greater => {
+                        unreachable!(
+                            "The index '{index}' cannot exceed the \
+                        playlist length '{playlist_length}' as indices are 0 based."
+                        );
+                    }
+                    Ordering::Less => {
+                        // The index is still valid.
+                        // Therefore, we keep the user at this position.
+                        index
+                    }
+                    Ordering::Equal => {
+                        // The index is pointing to the end of the playlist. We could either go the second
+                        // to last entry (i.e., one entry back) or wrap around to the start.
+                        // We wrap around.
+                        0
+                    }
+                };
+
+                let next = self
+                    .get_mut(PlaylistIndex::from(index))
+                    .expect("We checked that the index is still good");
+                next.set_focused(true, ops);
+            }
+
+            // Tell mpv about our decision.
+            self.resync_with_mpv(app, mpv)?;
+        }
+
+        Ok(())
+    }
+
+    /// Sync the mpv playlist with this playlist.
+    pub(crate) fn resync_with_mpv(&self, app: &App, mpv: &Mpv) -> Result<()> {
+        fn get_playlist_count(mpv: &Mpv) -> Result<usize> {
+            mpv.get_property::<i64>("playlist/count")
+                .context("Failed to get mpv playlist len")
+                .map(|count| {
+                    usize::try_from(count).expect("The playlist_count should always be positive")
+                })
+        }
+
+        if get_playlist_count(mpv)? != 0 {
+            // We could also use `loadlist`, but that would require use to start a unix socket or even
+            // write all the video paths to a file beforehand
+            mpv.command("playlist-clear", &[])?;
+            mpv.command("playlist-remove", &["current"])?;
+        }
+
+        assert_eq!(
+            get_playlist_count(mpv)?,
+            0,
+            "The playlist should be empty at this point."
+        );
+
+        debug!("MpvReload: Adding {} videos to playlist.", self.len());
+
+        self.videos
+            .iter()
+            .enumerate()
+            .try_for_each(|(index, video)| {
+                let VideoStatus::Cached {
+                    cache_path,
+                    is_focused,
+                } = &video.status
+                else {
+                    unreachable!("All of the videos in a playlist are cached");
+                };
+
+                let options = format!(
+                    "speed={},start={}",
+                    video
+                        .playback_speed
+                        .unwrap_or(app.config.select.playback_speed),
+                    i64::try_from(video.watch_progress.as_secs())
+                        .expect("This should not overflow"),
+                );
+
+                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",
+                        "-1", // Not used for `append-play`, but needed for the next args to take effect.
+                        options.as_str(),
+                    ],
+                )?;
+
+                if *is_focused {
+                    debug!("MpvReload: Setting playlist position to {index}");
+                    mpv.set_property("playlist-pos", index.to_string().as_str())?;
+                }
+
+                Ok::<(), anyhow::Error>(())
+            })?;
+
+        Ok(())
+    }
+
+    pub(crate) fn save_current_watch_progress(
+        &mut self,
+        mpv: &Mpv,
+        ops: &mut Operations<Operation>,
+    ) {
+        let (index, _) = self
+            .get_focused_mut()
+            .expect("This should be some at this point");
+
+        self.save_watch_progress(mpv, index, ops);
+    }
+
+    /// Saves the `watch_progress` of a video at the index.
+    pub(crate) fn save_watch_progress(
+        &mut self,
+        mpv: &Mpv,
+        at: PlaylistIndex,
+        ops: &mut Operations<Operation>,
+    ) {
+        let current_video = self
+            .get_mut(at)
+            .expect("We should never produce invalid playlist indices");
+
+        let watch_progress = match mpv.get_property::<i64>("time-pos") {
+            Ok(time) => u64::try_from(time)
+                .expect("This conversion should never fail as the `time-pos` property is positive"),
+            Err(err) => {
+                // We cannot hard error here, as we would open us to an race condition between mpv
+                // changing the current video and we saving it.
+                trace!(
+                    "While trying to save the watch progress for the current video: \
+                    Failed to get the watchprogress of the currently playling video: \
+                    (This is probably expected, nevertheless showing the raw error) \
+                    {err}"
+                );
+
+                return;
+            }
+        };
+
+        let watch_progress = Duration::from_secs(watch_progress);
+
+        debug!(
+            "Setting the watch progress for the current_video '{}' to {}s",
+            current_video.title_fmt().render(false),
+            watch_progress.as_secs(),
+        );
+
+        current_video.set_watch_progress(watch_progress, ops);
+    }
+}
diff --git a/crates/yt/src/storage/db/insert/subscription.rs b/crates/yt/src/storage/db/insert/subscription.rs
new file mode 100644
index 0000000..54409a9
--- /dev/null
+++ b/crates/yt/src/storage/db/insert/subscription.rs
@@ -0,0 +1,128 @@
+// 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::storage::db::{
+    insert::{Committable, Operations},
+    subscription::{Subscription, Subscriptions},
+};
+
+use anyhow::Result;
+use serde::{Deserialize, Serialize};
+use sqlx::query;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub(crate) enum Operation {
+    Add(Subscription),
+    Remove(Subscription),
+    SetIsActive {
+        target: Subscription,
+        is_active: bool,
+    },
+}
+
+impl Committable for Operation {
+    async fn commit(self, txn: &mut sqlx::SqliteConnection) -> Result<()> {
+        match self {
+            Operation::Add(subscription) => {
+                let url = subscription.url.as_str();
+
+                query!(
+                    "
+                    INSERT INTO subscriptions (
+                        name,
+                        url
+                    ) VALUES (?, ?);
+                    ",
+                    subscription.name,
+                    url
+                )
+                .execute(txn)
+                .await?;
+
+                println!(
+                    "Subscribed to '{}' at '{}'",
+                    subscription.name, subscription.url
+                );
+                Ok(())
+            }
+            Operation::Remove(subscription) => {
+                let output = query!(
+                    "
+                    DELETE FROM subscriptions
+                    WHERE name = ?
+                    ",
+                    subscription.name,
+                )
+                .execute(txn)
+                .await?;
+
+                assert_eq!(
+                    output.rows_affected(),
+                    1,
+                    "The removed subscription query did effect more (or less) than one row. This is a bug."
+                );
+
+                println!(
+                    "Unsubscribed from '{}' at '{}'",
+                    subscription.name, subscription.url
+                );
+
+                Ok(())
+            }
+            Operation::SetIsActive { target, is_active } => {
+                query!(
+                    "
+                    UPDATE subscriptions
+                    SET is_active = ?
+                    WHERE name = ?;
+                    ",
+                    is_active,
+                    target.name
+                )
+                .execute(txn)
+                .await?;
+
+                println!(
+                    "Marked '{}' as '{}'",
+                    target.name,
+                    if is_active { "active" } else { "inactive" }
+                );
+                Ok(())
+            }
+        }
+    }
+}
+
+impl Subscription {
+    pub(crate) fn add(self, ops: &mut Operations<Operation>) {
+        ops.push(Operation::Add(self));
+    }
+
+    pub(crate) fn remove(self, ops: &mut Operations<Operation>) {
+        ops.push(Operation::Remove(self));
+    }
+
+    pub(crate) fn set_is_active(self, is_active: bool, ops: &mut Operations<Operation>) {
+        if self.is_active != is_active {
+            ops.push(Operation::SetIsActive {
+                target: self,
+                is_active,
+            });
+        }
+    }
+}
+
+impl Subscriptions {
+    pub(crate) fn remove(self, ops: &mut Operations<Operation>) {
+        for sub in self.0.into_values() {
+            ops.push(Operation::Remove(sub));
+        }
+    }
+}
diff --git a/crates/yt/src/storage/db/insert/video/mod.rs b/crates/yt/src/storage/db/insert/video/mod.rs
new file mode 100644
index 0000000..da62e37
--- /dev/null
+++ b/crates/yt/src/storage/db/insert/video/mod.rs
@@ -0,0 +1,610 @@
+// 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::{
+    path::{Path, PathBuf},
+    time,
+};
+
+use crate::storage::db::{
+    extractor_hash::ExtractorHash,
+    insert::{Committable, Operations},
+    video::{Priority, Video, VideoStatus, VideoStatusMarker},
+};
+
+use anyhow::{Context, Result};
+use chrono::Utc;
+use log::debug;
+use serde::{Deserialize, Serialize};
+use sqlx::query;
+use tokio::fs;
+
+use super::super::video::TimeStamp;
+
+const fn is_focused_to_value(is_focused: bool) -> Option<i8> {
+    if is_focused { Some(1) } else { None }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub(crate) enum Operation {
+    Add {
+        description: Option<String>,
+        title: String,
+        parent_subscription_name: Option<String>,
+        thumbnail_url: Option<String>,
+        url: String,
+        extractor_hash: String,
+        status: i64,
+        cache_path: Option<String>,
+        is_focused: Option<i8>,
+        duration: Option<f64>,
+        last_status_change: i64,
+        publish_date: Option<i64>,
+        watch_progress: i64,
+    },
+    // TODO(@bpeetz): Could both the {`Set`,`Remove`}`DownloadPath` ops, be merged into SetStatus
+    // {`Cached`,`Watch`}? <2025-07-14>
+    SetDownloadPath {
+        video: ExtractorHash,
+        path: PathBuf,
+    },
+    RemoveDownloadPath {
+        video: ExtractorHash,
+    },
+    SetStatus {
+        video: ExtractorHash,
+        status: VideoStatus,
+    },
+    SetPriority {
+        video: ExtractorHash,
+        priority: Priority,
+    },
+    SetPlaybackSpeed {
+        video: ExtractorHash,
+        playback_speed: f64,
+    },
+    SetSubtitleLangs {
+        video: ExtractorHash,
+        subtitle_langs: String,
+    },
+    SetWatchProgress {
+        video: ExtractorHash,
+        watch_progress: time::Duration,
+    },
+    SetIsFocused {
+        video: ExtractorHash,
+        is_focused: bool,
+    },
+}
+
+impl Committable for Operation {
+    #[allow(clippy::too_many_lines)]
+    async fn commit(self, txn: &mut sqlx::SqliteConnection) -> Result<()> {
+        match self {
+            Operation::SetDownloadPath { video, path } => {
+                debug!("Setting cache path from '{video}' to '{}'", path.display());
+
+                let path_str = path.display().to_string();
+                let extractor_hash = video.hash().to_string();
+                let status = VideoStatusMarker::Cached.as_db_integer();
+
+                query!(
+                    r#"
+                UPDATE videos
+                SET cache_path = ?, status = ?
+                WHERE extractor_hash = ?;
+                "#,
+                    path_str,
+                    status,
+                    extractor_hash
+                )
+                .execute(txn)
+                .await?;
+
+                Ok(())
+            }
+            Operation::RemoveDownloadPath { video } => {
+                let extractor_hash = video.hash().to_string();
+                let status = VideoStatus::Watch.as_marker().as_db_integer();
+
+                let old = video.get(&mut *txn).await?;
+
+                debug!("Deleting download path of '{video}' ({}).", old.title);
+
+                if let VideoStatus::Cached { cache_path, .. } = &old.status {
+                    if let Ok(true) = cache_path.try_exists() {
+                        fs::remove_file(cache_path).await?;
+                    }
+
+                    {
+                        let info_json_path = old.info_json_path()?.expect("Is downloaded");
+
+                        if let Ok(true) = info_json_path.try_exists() {
+                            fs::remove_file(info_json_path).await?;
+                        }
+                    }
+
+                    {
+                        if old.subtitle_langs.is_some() {
+                            // TODO(@bpeetz): Also clean-up the downloaded subtitle files. <2025-07-05>
+                        }
+                    }
+                } else {
+                    unreachable!(
+                        "A video cannot have a download path deletion \
+                        queued without being marked as Cached."
+                    );
+                }
+
+                query!(
+                    r#"
+                    UPDATE videos
+                    SET cache_path = NULL, status = ?, is_focused = ?
+                    WHERE extractor_hash = ?;
+                    "#,
+                    status,
+                    None::<i32>,
+                    extractor_hash
+                )
+                .execute(txn)
+                .await?;
+
+                Ok(())
+            }
+            Operation::SetStatus { video, status } => {
+                let extractor_hash = video.hash().to_string();
+
+                let old = video.get(&mut *txn).await?;
+
+                let old_marker = old.status.as_marker();
+
+                let (cache_path, is_focused) = {
+                    fn cache_path_to_string(path: &Path) -> Result<String> {
+                        Ok(path
+                            .to_str()
+                            .with_context(|| {
+                                format!(
+                                    "Failed to parse cache path ('{}') as utf8 string",
+                                    path.display()
+                                )
+                            })?
+                            .to_owned())
+                    }
+
+                    if let VideoStatus::Cached {
+                        cache_path,
+                        is_focused,
+                    } = &status
+                    {
+                        (
+                            Some(cache_path_to_string(cache_path)?),
+                            is_focused_to_value(*is_focused),
+                        )
+                    } else {
+                        (None, None)
+                    }
+                };
+
+                let new_status = status.as_marker();
+
+                assert_ne!(
+                    old_marker, new_status,
+                    "We should have never generated this operation"
+                );
+
+                let now = Utc::now().timestamp();
+
+                debug!(
+                    "Changing status of video ('{}' {extractor_hash}) \
+                        from {old_marker:#?} to {new_status:#?}.",
+                    old.title
+                );
+
+                let new_status = new_status.as_db_integer();
+                query!(
+                    r#"
+                    UPDATE videos
+                    SET status = ?, last_status_change = ?, cache_path = ?, is_focused = ?
+                    WHERE extractor_hash = ?;
+                    "#,
+                    new_status,
+                    now,
+                    cache_path,
+                    is_focused,
+                    extractor_hash,
+                )
+                .execute(txn)
+                .await?;
+
+                Ok(())
+            }
+            Operation::SetPriority { video, priority } => {
+                let extractor_hash = video.hash().to_string();
+
+                let new_priority = priority.as_db_integer();
+                query!(
+                    r#"
+                    UPDATE videos
+                    SET priority = ?
+                    WHERE extractor_hash = ?;
+                    "#,
+                    new_priority,
+                    extractor_hash
+                )
+                .execute(txn)
+                .await?;
+
+                Ok(())
+            }
+            Operation::SetWatchProgress {
+                video,
+                watch_progress,
+            } => {
+                let video_extractor_hash = video.hash().to_string();
+                let watch_progress = i64::try_from(watch_progress.as_secs())
+                    .expect("This should never exceed its bounds");
+
+                query!(
+                    r#"
+                    UPDATE videos
+                    SET watch_progress = ?
+                    WHERE extractor_hash = ?;
+                    "#,
+                    watch_progress,
+                    video_extractor_hash,
+                )
+                .execute(txn)
+                .await?;
+
+                Ok(())
+            }
+            Operation::SetPlaybackSpeed {
+                video,
+                playback_speed,
+            } => {
+                let extractor_hash = video.hash().to_string();
+
+                query!(
+                    r#"
+                    UPDATE videos
+                    SET playback_speed = ?
+                    WHERE extractor_hash = ?;
+                    "#,
+                    playback_speed,
+                    extractor_hash
+                )
+                .execute(txn)
+                .await?;
+
+                Ok(())
+            }
+            Operation::SetSubtitleLangs {
+                video,
+                subtitle_langs,
+            } => {
+                let extractor_hash = video.hash().to_string();
+
+                query!(
+                    r#"
+                    UPDATE videos
+                    SET subtitle_langs = ?
+                    WHERE extractor_hash = ?;
+                    "#,
+                    subtitle_langs,
+                    extractor_hash
+                )
+                .execute(txn)
+                .await?;
+
+                Ok(())
+            }
+            Operation::SetIsFocused { video, is_focused } => {
+                debug!("Set is_focused of video: '{video}' to {is_focused}");
+                let new_hash = video.hash().to_string();
+                let new_is_focused = is_focused_to_value(is_focused);
+
+                query!(
+                    r#"
+                    UPDATE videos
+                    SET is_focused = ?
+                    WHERE extractor_hash = ?;
+                    "#,
+                    new_is_focused,
+                    new_hash,
+                )
+                .execute(txn)
+                .await?;
+
+                Ok(())
+            }
+            Operation::Add {
+                parent_subscription_name,
+                thumbnail_url,
+                url,
+                extractor_hash,
+                status,
+                cache_path,
+                is_focused,
+                duration,
+                last_status_change,
+                publish_date,
+                watch_progress,
+                description,
+                title,
+            } => {
+                query!(
+                    r#"
+                    INSERT INTO videos (
+                        description,
+                        duration,
+                        extractor_hash,
+                        is_focused,
+                        last_status_change,
+                        parent_subscription_name,
+                        publish_date,
+                        status,
+                        thumbnail_url,
+                        title,
+                        url,
+                        watch_progress,
+                        cache_path
+                        )
+                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
+                    "#,
+                    description,
+                    duration,
+                    extractor_hash,
+                    is_focused,
+                    last_status_change,
+                    parent_subscription_name,
+                    publish_date,
+                    status,
+                    thumbnail_url,
+                    title,
+                    url,
+                    watch_progress,
+                    cache_path,
+                )
+                .execute(txn)
+                .await?;
+
+                Ok(())
+            }
+        }
+    }
+}
+
+impl Video {
+    /// Add this in-memory video to the db.
+    pub(crate) fn add(self, ops: &mut Operations<Operation>) -> Result<Self> {
+        let description = self.description.clone();
+        let title = self.title.clone();
+        let parent_subscription_name = self.parent_subscription_name.clone();
+
+        let thumbnail_url = self.thumbnail_url.as_ref().map(ToString::to_string);
+
+        let url = self.url.to_string();
+        let extractor_hash = self.extractor_hash.hash().to_string();
+
+        let status = self.status.as_marker().as_db_integer();
+        let (cache_path, is_focused) = if let VideoStatus::Cached {
+            cache_path,
+            is_focused,
+        } = &self.status
+        {
+            (
+                Some(
+                    cache_path
+                        .to_str()
+                        .with_context(|| {
+                            format!(
+                                "Failed to prase cache path '{}' as utf-8 string",
+                                cache_path.display()
+                            )
+                        })?
+                        .to_string(),
+                ),
+                is_focused_to_value(*is_focused),
+            )
+        } else {
+            (None, None)
+        };
+
+        let duration: Option<f64> = self.duration.as_secs_f64();
+        let last_status_change: i64 = self.last_status_change.as_secs();
+        let publish_date: Option<i64> = self.publish_date.map(TimeStamp::as_secs);
+        let watch_progress: i64 =
+            i64::try_from(self.watch_progress.as_secs()).expect("This should never exceed a u32");
+
+        ops.push(Operation::Add {
+            description,
+            title,
+            parent_subscription_name,
+            thumbnail_url,
+            url,
+            extractor_hash,
+            status,
+            cache_path,
+            is_focused,
+            duration,
+            last_status_change,
+            publish_date,
+            watch_progress,
+        });
+
+        Ok(self)
+    }
+
+    /// Set the download path of a video.
+    ///
+    /// # Note
+    /// This will also set the status to `Cached`.
+    pub(crate) fn set_download_path(&mut self, path: &Path, ops: &mut Operations<Operation>) {
+        if let VideoStatus::Cached { cache_path, .. } = &mut self.status {
+            if cache_path != path {
+                // Update the in-memory video.
+                path.clone_into(cache_path);
+
+                ops.push(Operation::SetDownloadPath {
+                    video: self.extractor_hash,
+                    path: path.to_owned(),
+                });
+            }
+        } else {
+            self.status = VideoStatus::Cached {
+                cache_path: path.to_owned(),
+                is_focused: false,
+            };
+
+            ops.push(Operation::SetDownloadPath {
+                video: self.extractor_hash,
+                path: path.to_owned(),
+            });
+        }
+    }
+
+    /// Remove the download path of a video.
+    ///
+    /// # Note
+    /// This will also set the status to `Watch`.
+    ///
+    /// # Panics
+    /// If the status is not `Cached`.
+    pub(crate) fn remove_download_path(&mut self, ops: &mut Operations<Operation>) {
+        if let VideoStatus::Cached { .. } = &mut self.status {
+            self.status = VideoStatus::Watch;
+            ops.push(Operation::RemoveDownloadPath {
+                video: self.extractor_hash,
+            });
+        } else {
+            unreachable!("Can only remove the path from a `Cached` video");
+        }
+    }
+
+    /// Update the `is_focused` flag of this video.
+    ///
+    /// # Note
+    /// It will only actually add operations, if the `is_focused` flag is different.
+    ///
+    /// # Panics
+    /// If the status is not `Cached`.
+    pub(crate) fn set_focused(&mut self, new_is_focused: bool, ops: &mut Operations<Operation>) {
+        if let VideoStatus::Cached { is_focused, .. } = &mut self.status {
+            if *is_focused != new_is_focused {
+                *is_focused = new_is_focused;
+
+                ops.push(Operation::SetIsFocused {
+                    video: self.extractor_hash,
+                    is_focused: new_is_focused,
+                });
+            }
+        } else {
+            unreachable!("Can only change `is_focused` on a Cached video.");
+        }
+    }
+
+    /// Set the status of this video.
+    ///
+    /// # Note
+    /// This will not actually add any operations, if the new status equals the old one.
+    pub(crate) fn set_status(&mut self, status: VideoStatus, ops: &mut Operations<Operation>) {
+        if self.status != status {
+            status.clone_into(&mut self.status);
+
+            ops.push(Operation::SetStatus {
+                video: self.extractor_hash,
+                status,
+            });
+        }
+    }
+
+    /// Set the priority of this video.
+    ///
+    /// # Note
+    /// This will not actually add any operations, if the new priority equals the old one.
+    pub(crate) fn set_priority(&mut self, priority: Priority, ops: &mut Operations<Operation>) {
+        if self.priority != priority {
+            self.priority = priority;
+
+            ops.push(Operation::SetPriority {
+                video: self.extractor_hash,
+                priority,
+            });
+        }
+    }
+
+    /// Set the watch progress.
+    ///
+    /// # Note
+    /// This will not actually add any operations,
+    /// if the new watch progress equals the old one.
+    pub(crate) fn set_watch_progress(
+        &mut self,
+        watch_progress: time::Duration,
+        ops: &mut Operations<Operation>,
+    ) {
+        if self.watch_progress != watch_progress {
+            self.watch_progress = watch_progress;
+
+            ops.push(Operation::SetWatchProgress {
+                video: self.extractor_hash,
+                watch_progress,
+            });
+        }
+    }
+
+    /// Set the playback speed of this video.
+    ///
+    /// # Note
+    /// This will not actually add any operations, if the new speed equals the old one.
+    pub(crate) fn set_playback_speed(
+        &mut self,
+        playback_speed: f64,
+        ops: &mut Operations<Operation>,
+    ) {
+        if self.playback_speed != Some(playback_speed) {
+            self.playback_speed = Some(playback_speed);
+
+            ops.push(Operation::SetPlaybackSpeed {
+                video: self.extractor_hash,
+                playback_speed,
+            });
+        }
+    }
+
+    /// Set the subtitle langs of this video.
+    ///
+    /// # Note
+    /// This will not actually add any operations, if the new langs equal the old one.
+    pub(crate) fn set_subtitle_langs(
+        &mut self,
+        subtitle_langs: String,
+        ops: &mut Operations<Operation>,
+    ) {
+        if self.subtitle_langs.as_ref() != Some(&subtitle_langs) {
+            self.subtitle_langs = Some(subtitle_langs.clone());
+
+            ops.push(Operation::SetSubtitleLangs {
+                video: self.extractor_hash,
+                subtitle_langs,
+            });
+        }
+    }
+
+    /// Mark this video watched.
+    /// This will both set the status to `Watched` and the `cache_path` to Null.
+    ///
+    /// # Panics
+    /// Only if assertions fail.
+    pub(crate) fn set_watched(&mut self, ops: &mut Operations<Operation>) {
+        self.remove_download_path(ops);
+        self.set_status(VideoStatus::Watched, ops);
+    }
+}
diff --git a/crates/yt/src/storage/db/mod.rs b/crates/yt/src/storage/db/mod.rs
new file mode 100644
index 0000000..926bab0
--- /dev/null
+++ b/crates/yt/src/storage/db/mod.rs
@@ -0,0 +1,18 @@
+// 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>.
+
+pub(crate) mod get;
+pub(crate) mod insert;
+
+pub(crate) mod extractor_hash;
+pub(crate) mod playlist;
+pub(crate) mod subscription;
+pub(crate) mod txn_log;
+pub(crate) mod video;
diff --git a/crates/yt/src/storage/db/playlist/mod.rs b/crates/yt/src/storage/db/playlist/mod.rs
new file mode 100644
index 0000000..7366e8e
--- /dev/null
+++ b/crates/yt/src/storage/db/playlist/mod.rs
@@ -0,0 +1,59 @@
+// 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::ops::Add;
+
+use crate::storage::db::video::Video;
+
+/// Zero-based index into the internal playlist.
+#[derive(Debug, Clone, Copy)]
+pub(crate) struct PlaylistIndex(usize);
+
+impl From<PlaylistIndex> for usize {
+    fn from(value: PlaylistIndex) -> Self {
+        value.0
+    }
+}
+
+impl From<usize> for PlaylistIndex {
+    fn from(value: usize) -> Self {
+        Self(value)
+    }
+}
+
+impl Add<usize> for PlaylistIndex {
+    type Output = Self;
+
+    fn add(self, rhs: usize) -> Self::Output {
+        Self(self.0 + rhs)
+    }
+}
+
+impl Add for PlaylistIndex {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self::Output {
+        Self(self.0 + rhs.0)
+    }
+}
+
+/// A representation of the internal Playlist
+#[derive(Debug)]
+pub(crate) struct Playlist {
+    pub(crate) videos: Vec<Video>,
+}
+
+impl Playlist {
+    /// Returns the number of videos in the playlist
+    #[must_use]
+    pub(crate) fn len(&self) -> usize {
+        self.videos.len()
+    }
+}
diff --git a/crates/yt/src/storage/db/subscription.rs b/crates/yt/src/storage/db/subscription.rs
new file mode 100644
index 0000000..39385b9
--- /dev/null
+++ b/crates/yt/src/storage/db/subscription.rs
@@ -0,0 +1,58 @@
+// 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::collections::HashMap;
+
+use anyhow::Result;
+use log::debug;
+use serde::{Deserialize, Serialize};
+use url::Url;
+use yt_dlp::{json_cast, json_try_get, options::YoutubeDLOptions};
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub(crate) struct Subscription {
+    /// The human readable name of this subscription
+    pub(crate) name: String,
+
+    /// The URL this subscription subscribes to
+    pub(crate) url: Url,
+
+    pub(crate) is_active: bool,
+}
+
+impl Subscription {
+    #[must_use]
+    pub(crate) fn new(name: String, url: Url, is_active: bool) -> Self {
+        Self {
+            name,
+            url,
+            is_active,
+        }
+    }
+}
+
+#[derive(Default, Debug)]
+pub(crate) struct Subscriptions(pub(crate) HashMap<String, Subscription>);
+
+/// Check whether an URL could be used as a subscription URL
+pub(crate) 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(json_try_get!(info, "_type", as_str) == Some("playlist"))
+}
diff --git a/crates/yt/src/storage/db/txn_log.rs b/crates/yt/src/storage/db/txn_log.rs
new file mode 100644
index 0000000..64884b0
--- /dev/null
+++ b/crates/yt/src/storage/db/txn_log.rs
@@ -0,0 +1,24 @@
+// 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::storage::db::{insert::Committable, video::TimeStamp};
+
+pub(crate) struct TxnLog<O: Committable> {
+    inner: Vec<(TimeStamp, O)>,
+}
+
+impl<O: Committable> TxnLog<O> {
+    pub(crate) fn new(inner: Vec<(TimeStamp, O)>) -> Self {
+        Self { inner }
+    }
+    pub(crate) fn inner(&self) -> &[(TimeStamp, O)] {
+        &self.inner
+    }
+}
diff --git a/crates/yt/src/comments/display.rs b/crates/yt/src/storage/db/video/comments/display.rs
index 6166b2b..c372603 100644
--- a/crates/yt/src/comments/display.rs
+++ b/crates/yt/src/storage/db/video/comments/display.rs
@@ -1,6 +1,5 @@
 // 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
 //
@@ -13,27 +12,22 @@ use std::fmt::Write;
 
 use chrono::{Local, TimeZone};
 use chrono_humanize::{Accuracy, HumanTime, Tense};
+use colors::{Colorize, IntoCanvas};
 
-use crate::comments::comment::CommentExt;
-
-use super::comment::Comments;
+use crate::{
+    output::format_text,
+    storage::db::video::comments::{Comment, Comments},
+};
 
 impl Comments {
-    pub fn render(&self, color: bool) -> String {
-        self.render_help(color).expect("This should never fail.")
+    pub(crate) fn render(&self, use_color: bool) -> String {
+        self.render_help(use_color)
+            .expect("This should never fail.")
     }
 
-    fn render_help(&self, color: bool) -> Result<String, std::fmt::Error> {
-        macro_rules! c {
-            ($color_str:expr, $write:ident, $color:expr) => {
-                if $color {
-                    $write.write_str(concat!("\x1b[", $color_str, "m"))?
-                }
-            };
-        }
-
+    fn render_help(&self, use_color: bool) -> Result<String, std::fmt::Error> {
         fn format(
-            comment: &CommentExt,
+            comment: &Comment,
             f: &mut String,
             ident_count: u32,
             color: bool,
@@ -43,14 +37,16 @@ impl Comments {
 
             f.write_str(ident)?;
 
-            if value.author_is_uploader {
-                c!("91;1", f, color);
-            } else {
-                c!("35", f, color);
-            }
+            write!(
+                f,
+                "{}",
+                if value.author_is_uploader {
+                    (&value.author).bold().bright_red().render(color)
+                } else {
+                    (&value.author).purple().render(color)
+                }
+            )?;
 
-            f.write_str(&value.author)?;
-            c!("0", f, color);
             if value.edited || value.is_favorited {
                 f.write_str("[")?;
                 if value.edited {
@@ -65,7 +61,6 @@ impl Comments {
                 f.write_str("]")?;
             }
 
-            c!("36;1", f, color);
             write!(
                 f,
                 " {}",
@@ -76,17 +71,31 @@ impl Comments {
                         .expect("This should be valid")
                 )
                 .to_text_en(Accuracy::Rough, Tense::Past)
+                .bold()
+                .cyan()
+                .render(color)
             )?;
-            c!("0", f, color);
 
-            // c!("31;1", f);
-            // f.write_fmt(format_args!(" [{}]", comment.value.like_count))?;
-            // c!("0", f);
+            write!(
+                f,
+                " [{}]",
+                comment.value.like_count.bold().red().render(color)
+            )?;
 
             f.write_str(":\n")?;
             f.write_str(ident)?;
 
-            f.write_str(&value.text.replace('\n', &format!("\n{ident}")))?;
+            f.write_str(
+                &format_text(
+                    value.text.trim(),
+                    Some(
+                        termsize::get().map_or(90, |ts| ts.cols)
+                            - u16::try_from(ident_count).expect("Should never overflow"),
+                    ),
+                )
+                .trim()
+                .replace('\n', &format!("\n{ident}")),
+            )?;
             f.write_str("\n")?;
 
             if comment.replies.is_empty() {
@@ -105,12 +114,12 @@ impl Comments {
 
         let mut f = String::new();
 
-        if !&self.vec.is_empty() {
-            let mut children = self.vec.clone();
+        if !&self.inner.is_empty() {
+            let mut children = self.inner.clone();
             children.sort_by(|a, b| b.value.like_count.cmp(&a.value.like_count));
 
             for child in children {
-                format(&child, &mut f, 0, color)?;
+                format(&child, &mut f, 0, use_color)?;
             }
         }
         Ok(f)
diff --git a/crates/yt/src/storage/db/video/comments/mod.rs b/crates/yt/src/storage/db/video/comments/mod.rs
new file mode 100644
index 0000000..41a03be
--- /dev/null
+++ b/crates/yt/src/storage/db/video/comments/mod.rs
@@ -0,0 +1,202 @@
+// 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::mem;
+
+use regex::{Captures, Regex};
+
+use crate::storage::db::video::comments::raw::{Parent, RawComment};
+
+pub(crate) mod display;
+pub(crate) mod raw;
+
+#[cfg(test)]
+mod tests;
+
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) struct Comment {
+    value: RawComment,
+    replies: Vec<Self>,
+}
+
+#[derive(Debug, Default, PartialEq)]
+pub(crate) struct Comments {
+    inner: Vec<Comment>,
+}
+
+impl Comments {
+    pub(crate) fn from_raw(raw: Vec<RawComment>) -> Self {
+        let mut me = Self::default();
+
+        // Apply the parent -> child mapping yt provides us with.
+        for raw_comment in raw {
+            if let Parent::Id(id) = &raw_comment.parent {
+                me.insert(&(id.clone()), Comment::from(raw_comment));
+            } else {
+                me.inner.push(Comment::from(raw_comment));
+            }
+        }
+
+        {
+            // Sort the final comments chronologically.
+            // This ensures that replies are matched with the comment they actually replied to and
+            // not a later comment from the same author.
+            for comment in &mut me.inner {
+                comment
+                    .replies
+                    .sort_by_key(|comment| comment.value.timestamp);
+
+                for reply in &comment.replies {
+                    assert!(reply.replies.is_empty());
+                }
+            }
+        }
+
+        {
+            let find_reply_indicator =
+                Regex::new(r"\u{200b}?(@[^\t\s]+)\u{200b}?").expect("This is hardcoded");
+
+            // Try to re-construct the replies for the reply comments.
+            for comment in &mut me.inner {
+                let previous_replies = mem::take(&mut comment.replies);
+
+                let mut reply_tree = Comments::default();
+
+                for reply in previous_replies {
+                    // We try to reconstruct the parent child relation ship by looking (naively)
+                    // for a reply indicator. Currently, this is just the `@<some_name>`, as yt
+                    // seems to insert that by default if you press `reply-to` in their clients.
+                    //
+                    // This follows these steps:
+                    // - Does this reply have a “reply indicator”?
+                    // - If yes, try to resolve the indicator.
+                    // - If it is resolvable, add this reply to the [`Comment`] it resolved to.
+                    // - If not, keep the comment as reply.
+
+                    if let Some(reply_indicator_matches) =
+                        find_reply_indicator.captures(&reply.value.text.clone())
+                    {
+                        // We found a reply indicator.
+                        // First we traverse the current `reply_tree` in reversed order to find a
+                        // match, than we check if the reply indicator matches the reply tree root
+                        // and afterward we declare it unmatching and add it as toplevel.
+
+                        let reply_target_author = reply_indicator_matches
+                            .get(1)
+                            .expect("This should also exist")
+                            .as_str();
+
+                        if let Some(parent) = reply_tree.find_author_mut(reply_target_author) {
+                            parent
+                                .replies
+                                .push(comment_from_reply(reply, &reply_indicator_matches));
+                        } else if comment.value.author == reply_target_author {
+                            reply_tree
+                                .add_toplevel(comment_from_reply(reply, &reply_indicator_matches));
+                        } else {
+                            eprintln!(
+                                "Failed to find a parent for ('{}') both directly \
+                                    and via replies! The reply text was:\n'{}'\n",
+                                reply_target_author, reply.value.text
+                            );
+                            reply_tree.add_toplevel(reply);
+                        }
+                    } else {
+                        // The comment text did not contain a reply indicator, so add it as
+                        // toplevel.
+                        reply_tree.add_toplevel(reply);
+                    }
+                }
+
+                comment.replies = reply_tree.inner;
+            }
+        }
+
+        me
+    }
+
+    fn add_toplevel(&mut self, value: Comment) {
+        self.inner.push(value);
+    }
+
+    fn insert(&mut self, id: &str, value: Comment) {
+        let parent = self
+            .inner
+            .iter_mut()
+            .find(|c| c.value.id.id == id)
+            .expect("One of these should exist");
+
+        parent.replies.push(value);
+    }
+
+    fn find_author_mut(&mut self, reply_target_author: &str) -> Option<&mut Comment> {
+        fn perform_check<'a>(
+            comment: &'a mut Comment,
+            reply_target_author: &str,
+        ) -> Option<&'a mut Comment> {
+            // TODO(@bpeetz): This is a workaround until rust has lexiographic lifetime support. <2025-07-18>
+            fn find_in_replies<'a>(
+                comment: &'a mut Comment,
+                reply_target_author: &str,
+            ) -> Option<&'a mut Comment> {
+                comment
+                    .replies
+                    .iter_mut()
+                    .rev()
+                    .find_map(|reply: &mut Comment| perform_check(reply, reply_target_author))
+            }
+            let comment_author_matches_target = comment.value.author == reply_target_author;
+
+            match find_in_replies(comment, reply_target_author) {
+                Some(_) => Some(
+                    // PERFORMANCE(@bpeetz): We should not need to run this code twice. <2025-07-18>
+                    find_in_replies(comment, reply_target_author)
+                        .expect("We already had a Some result for this."),
+                ),
+                None if comment_author_matches_target => Some(comment),
+                None => None,
+            }
+        }
+
+        for comment in self.inner.iter_mut().rev() {
+            if let Some(output) = perform_check(comment, reply_target_author) {
+                return Some(output);
+            }
+        }
+
+        None
+    }
+}
+fn comment_from_reply(reply: Comment, reply_indicator_matches: &Captures<'_>) -> Comment {
+    Comment::from(RawComment {
+        text: {
+            // Remove the `@<some_name>` for the comment text.
+            let full_match = reply_indicator_matches
+                .get(0)
+                .expect("This will always exist");
+
+            let text = reply.value.text[0..full_match.start()].to_owned()
+                + &reply.value.text[full_match.end()..];
+
+            text.trim_matches(|c: char| c == '\u{200b}' || c == '\u{2060}' || c.is_whitespace())
+                .to_owned()
+        },
+        ..reply.value
+    })
+}
+
+impl From<RawComment> for Comment {
+    fn from(value: RawComment) -> Self {
+        Self {
+            value,
+            replies: vec![],
+        }
+    }
+}
diff --git a/crates/yt/src/storage/db/video/comments/raw.rs b/crates/yt/src/storage/db/video/comments/raw.rs
new file mode 100644
index 0000000..3b7f40f
--- /dev/null
+++ b/crates/yt/src/storage/db/video/comments/raw.rs
@@ -0,0 +1,87 @@
+// 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 serde::{Deserialize, Deserializer};
+use url::Url;
+
+#[derive(Debug, Deserialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
+#[serde(from = "String")]
+#[serde(deny_unknown_fields)]
+pub(crate) struct Id {
+    pub(crate) 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('.').next_back().unwrap_or(&value).to_owned(),
+        }
+    }
+}
+
+#[derive(Debug, Deserialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
+#[serde(from = "String")]
+#[serde(deny_unknown_fields)]
+pub(crate) enum Parent {
+    Root,
+    Id(String),
+}
+
+impl From<String> for Parent {
+    fn from(value: String) -> Self {
+        if value == "root" {
+            Self::Root
+        } else {
+            Self::Id(value)
+        }
+    }
+}
+
+#[derive(Debug, Deserialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
+#[allow(clippy::struct_excessive_bools)]
+pub(crate) struct RawComment {
+    pub(crate) id: Id,
+    pub(crate) text: String,
+    #[serde(default = "zero")]
+    pub(crate) like_count: u32,
+    pub(crate) is_pinned: bool,
+    pub(crate) author_id: String,
+    #[serde(default = "unknown")]
+    pub(crate) author: String,
+    pub(crate) author_is_verified: bool,
+    pub(crate) author_thumbnail: Url,
+    pub(crate) parent: Parent,
+    #[serde(deserialize_with = "edited_from_time_text", alias = "_time_text")]
+    pub(crate) edited: bool,
+    // Can't also be deserialized, as it's already used in 'edited'
+    // _time_text: String,
+    pub(crate) timestamp: i64,
+    pub(crate) author_url: Option<Url>,
+    pub(crate) author_is_uploader: bool,
+    pub(crate) 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)
+    }
+}
diff --git a/crates/yt/src/storage/db/video/comments/tests.rs b/crates/yt/src/storage/db/video/comments/tests.rs
new file mode 100644
index 0000000..03e3597
--- /dev/null
+++ b/crates/yt/src/storage/db/video/comments/tests.rs
@@ -0,0 +1,249 @@
+// 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 pretty_assertions::assert_eq;
+use url::Url;
+
+use crate::storage::db::video::comments::{
+    Comment, Comments, RawComment,
+    raw::{Id, Parent},
+};
+
+/// Generate both an [`expected`] and an [`input`] value from an expected comment expression.
+macro_rules! mk_comments {
+    () => {{
+        let input: Vec<RawComment> = vec![];
+        let expected: Comments = Comments {
+            inner: vec![],
+        };
+
+        (input, expected)
+    }};
+
+    (
+        $(
+            parent: $parent:expr, $actual_parent:ident,
+            (
+                @ $name:ident : $comment:literal
+                $(
+                    $reply_chain:tt
+                )*
+            )
+        )+
+    ) => {{
+        let (nested_input, _) = mk_comments!(
+            $(
+                $(
+                    parent: $parent, $name,
+                    $reply_chain
+                )*
+            )+
+        );
+
+        let mut input: Vec<RawComment> = vec![
+            $(
+                mk_comments!(@to_raw input $name $comment $parent, $actual_parent)
+            ),+
+        ];
+        input.extend(nested_input);
+
+        let expected: Comments = Comments {
+            inner: vec![
+                $(
+                    Comment {
+                        value: mk_comments!(@to_raw expected $name $comment $parent, $actual_parent),
+                        replies: {
+                            let (_, nested_expected) = mk_comments!(
+                                $(
+                                    parent: $parent, $name,
+                                    $reply_chain
+                                )*
+                            );
+
+                            nested_expected.inner
+                        },
+                    }
+                ),+
+            ]
+        };
+
+        (input, expected)
+    }};
+    (
+        $(
+            (
+                @ $name:ident : $comment:literal
+                $(
+                    $reply_chain:tt
+                )*
+            )
+        )+
+    ) => {{
+        let (nested_input, _) = mk_comments!(
+            $(
+                $(
+                    parent: mk_comments!(@mk_id $name $comment), $name,
+                    $reply_chain
+                )*
+            )+
+        );
+
+        let mut input: Vec<RawComment> = vec![
+            $(
+                mk_comments!(@to_raw input $name $comment)
+            ),+
+        ];
+        input.extend(nested_input);
+
+        let expected: Comments = Comments {
+            inner: vec![
+                $(
+                    Comment {
+                        value: mk_comments!(@to_raw expected $name $comment),
+                        replies: {
+                            let (_, nested_expected) = mk_comments!(
+                                $(
+                                    parent: mk_comments!(@mk_id $name $comment), $name,
+                                    $reply_chain
+                                )*
+                            );
+
+                            nested_expected.inner
+                        },
+                    }
+                ),+
+            ]
+        };
+
+        (input, expected)
+    }};
+
+    (@mk_id $name:ident $comment:literal) => {{
+        use std::hash::{Hash, Hasher};
+
+        let input = format!("{}{}", stringify!($name), $comment);
+
+        let mut digest = std::hash::DefaultHasher::new();
+        input.hash(&mut digest);
+        Id { id: digest.finish().to_string() }
+    }};
+
+    (@to_raw $state:ident $name:ident $comment:literal $($parent:expr, $actual_parent:ident)?) => {
+        RawComment {
+            id: mk_comments!(@mk_id $name $comment),
+            text: mk_comments!(@mk_text $state $comment $(, $actual_parent)?),
+            like_count: 0,
+            is_pinned: false,
+            author_id: stringify!($name).to_owned(),
+            author: format!("@{}", stringify!($name)),
+            author_is_verified: false,
+            author_thumbnail: Url::from_file_path("/dev/null").unwrap(),
+            parent: mk_comments!(@mk_parent $($parent)?),
+            edited: false,
+            timestamp: 0,
+            author_url: None,
+            author_is_uploader: false,
+            is_favorited: false,
+        }
+    };
+
+    (@mk_parent) => {
+        Parent::Root
+    };
+    (@mk_parent $parent:expr) => {
+        Parent::Id($parent.id)
+    };
+
+    (@mk_text input $text:expr) => {
+        $text.to_owned()
+    };
+    (@mk_text input $text:expr, $actual_parent:ident) => {
+        format!("@{} {}", stringify!($actual_parent), $text)
+    };
+    (@mk_text expected $text:expr $(, $_:tt)?) => {
+        $text.to_owned()
+    };
+}
+
+#[test]
+fn test_comments_toplevel() {
+    let (input, expected) = mk_comments!(
+        (@kant: "I think, that using the results of an action to determine morality is flawed.")
+        (@hume: "I think, that we should use our feeling for morality more.")
+        (@lock: "I think, that we should rely on the sum of happiness caused by an action to determine it's morality.")
+    );
+
+    assert_eq!(Comments::from_raw(input), expected);
+}
+
+#[test]
+fn test_comments_replies_1_level() {
+    let (input, expected) = mk_comments!(
+        (@hume: "I think, that we should use our feeling for morality more."
+            (@kant: "This is so wrong! I shall now dedicate my next 7? years to writing books that prove this.")
+            (@lock: "It feels not very applicable, no? We should focus on something that can be used in the court of law!"))
+    );
+
+    assert_eq!(
+        Comments::from_raw(input).render(true),
+        expected.render(true)
+    );
+}
+
+#[test]
+fn test_comments_replies_2_levels() {
+    let (input, expected) = mk_comments!(
+        (@singer: "We perform medical studies on animals; Children have lower or similar mental ability as these animals.."
+            (@singer: "Therefore, we should perform these studies on children instead, if we were to follow our own principals"
+                (@james: "This is ridiculous! I will not entertain this thought.")
+                (@singer: "Although one could also use this argument to argue for abortion _after_ birth.")))
+    );
+
+    assert_eq!(
+        Comments::from_raw(input).render(true),
+        expected.render(true)
+    );
+}
+
+#[test]
+fn test_comments_replies_3_levels() {
+    let (input, expected) = mk_comments!(
+        (@singer: "We perform medical studies on animals; Children have lower or similar mental ability as these animals.."
+            (@singer: "Therefore, we should perform these studies on children instead, if we were to follow our own principals"
+                (@james: "This is ridiculous! I will not entertain this thought."
+                    (@singer: "You know that I am not actually suggesting that? This is but a way to critizise the society"))
+                (@singer: "Although one could also use this argument to argue for abortion _after_ birth.")))
+    );
+
+    assert_eq!(
+        Comments::from_raw(input).render(true),
+        expected.render(true)
+    );
+}
+
+#[test]
+fn test_comments_sub_answer_selection() {
+    let (input, expected) = mk_comments!(
+        (@coffeewolfproductions9113: "I mean, brothels and sex workers in of themselves are not a bad thing."
+            (@aikikaname6508: "probably not so much in the 50s, pre contraception")
+            (@as_ri1mb: "it’s an incredibly sad, degrading line of work, often resulting in self loathing and self-deletion."
+                (@coffeewolfproductions9113: "Are you speaking from experience?"
+                    (@as_ri1mb: "what an immature response, as expected."
+                        (@coffeewolfproductions9113: "I literally just asked if you were talking from experience.")))))
+
+    );
+
+    eprintln!("{}", expected.render(true));
+
+    assert_eq!(
+        Comments::from_raw(input).render(true),
+        expected.render(true)
+    );
+}
diff --git a/crates/yt/src/storage/video_database/mod.rs b/crates/yt/src/storage/db/video/mod.rs
index 74d09f0..deeb82c 100644
--- a/crates/yt/src/storage/video_database/mod.rs
+++ b/crates/yt/src/storage/db/video/mod.rs
@@ -1,6 +1,5 @@
 // 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
 //
@@ -9,55 +8,108 @@
 // 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, Write},
-    path::PathBuf,
-    time::Duration,
-};
+use std::{fmt::Display, path::PathBuf, time::Duration};
 
 use chrono::{DateTime, Utc};
+use serde::{Deserialize, Serialize};
 use url::Url;
 
-use crate::{
-    app::App, select::selection_file::duration::MaybeDuration,
-    storage::video_database::extractor_hash::ExtractorHash,
-};
+use crate::{select::duration::MaybeDuration, storage::db::extractor_hash::ExtractorHash};
 
-pub mod downloader;
-pub mod extractor_hash;
-pub mod get;
-pub mod notify;
-pub mod set;
+pub(crate) mod comments;
+
+macro_rules! video_from_record {
+    ($record:expr) => {
+        $crate::storage::db::video::Video {
+            description: $record.description.clone(),
+            duration: $crate::select::duration::MaybeDuration::from_maybe_secs_f64(
+                $record.duration,
+            ),
+            extractor_hash: $crate::storage::db::extractor_hash::ExtractorHash::from_hash(
+                $record
+                    .extractor_hash
+                    .parse()
+                    .expect("The db hash should be a valid blake3 hash"),
+            ),
+            last_status_change: $crate::storage::db::video::TimeStamp::from_secs(
+                $record.last_status_change,
+            ),
+            parent_subscription_name: $record.parent_subscription_name.clone(),
+            publish_date: $record
+                .publish_date
+                .map(|pd| $crate::storage::db::video::TimeStamp::from_secs(pd)),
+            status: {
+                let marker =
+                    $crate::storage::db::video::VideoStatusMarker::from_db_integer($record.status);
+                let optional = if let Some(cache_path) = &$record.cache_path {
+                    Some((
+                        std::path::PathBuf::from(cache_path),
+                        if $record.is_focused == Some(1) {
+                            true
+                        } else {
+                            false
+                        },
+                    ))
+                } else {
+                    None
+                };
+                $crate::storage::db::video::VideoStatus::from_marker(marker, optional)
+            },
+            thumbnail_url: if let Some(url) = &$record.thumbnail_url {
+                Some(url::Url::parse(url).expect("Parsing this as url should always work"))
+            } else {
+                None
+            },
+            title: $record.title.clone(),
+            url: url::Url::parse(&$record.url).expect("Parsing this as url should always work"),
+            priority: $crate::storage::db::video::Priority::from($record.priority),
+            watch_progress: std::time::Duration::from_secs(
+                u64::try_from($record.watch_progress).expect("The record is positive i64"),
+            ),
+            subtitle_langs: $record.subtitle_langs.clone(),
+            playback_speed: $record.playback_speed,
+        }
+    };
+}
+pub(crate) use video_from_record;
 
 #[derive(Debug, Clone)]
-pub struct Video {
-    pub description: Option<String>,
-    pub duration: MaybeDuration,
-    pub extractor_hash: ExtractorHash,
-    pub last_status_change: TimeStamp,
+pub(crate) struct Video {
+    pub(crate) description: Option<String>,
+    pub(crate) duration: MaybeDuration,
+    pub(crate) extractor_hash: ExtractorHash,
+    pub(crate) last_status_change: TimeStamp,
 
     /// The associated subscription this video was fetched from (null, when the video was `add`ed)
-    pub parent_subscription_name: Option<String>,
-    pub priority: Priority,
-    pub publish_date: Option<TimeStamp>,
-    pub status: VideoStatus,
-    pub thumbnail_url: Option<Url>,
-    pub title: String,
-    pub url: Url,
+    pub(crate) parent_subscription_name: Option<String>,
+    pub(crate) priority: Priority,
+    pub(crate) publish_date: Option<TimeStamp>,
+    pub(crate) status: VideoStatus,
+    pub(crate) thumbnail_url: Option<Url>,
+    pub(crate) title: String,
+    pub(crate) url: Url,
 
     /// The seconds the user has already watched the video
-    pub watch_progress: Duration,
+    pub(crate) watch_progress: Duration,
+
+    /// Which subtitles to include, when downloading this video.
+    /// In the form of `lang1,lang2,lang3` (e.g. `en,de,sv`)
+    pub(crate) subtitle_langs: Option<String>,
+
+    /// The playback speed to use, when watching this video.
+    /// Value is in percent, so 1 is 100%, 2.7 is 270%, and so on.
+    pub(crate) playback_speed: Option<f64>,
 }
 
 /// The priority of a [`Video`].
-#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-pub struct Priority {
+#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub(crate) struct Priority {
     value: i64,
 }
 impl Priority {
     /// Return the underlying value to insert that into the database
     #[must_use]
-    pub fn as_db_integer(&self) -> i64 {
+    pub(crate) fn as_db_integer(self) -> i64 {
         self.value
     }
 }
@@ -74,25 +126,25 @@ impl Display for Priority {
 
 /// An UNIX time stamp.
 #[derive(Debug, Default, Clone, Copy)]
-pub struct TimeStamp {
+pub(crate) struct TimeStamp {
     value: i64,
 }
 impl TimeStamp {
     /// Return the seconds since the UNIX epoch for this [`TimeStamp`].
     #[must_use]
-    pub fn as_secs(&self) -> i64 {
+    pub(crate) fn as_secs(self) -> i64 {
         self.value
     }
 
     /// Construct a [`TimeStamp`] from a count of seconds since the UNIX epoch.
     #[must_use]
-    pub fn from_secs(value: i64) -> Self {
+    pub(crate) fn from_secs(value: i64) -> Self {
         Self { value }
     }
 
     /// Construct a [`TimeStamp`] from the current time.
     #[must_use]
-    pub fn from_now() -> Self {
+    pub(crate) fn from_now() -> Self {
         Self {
             value: Utc::now().timestamp(),
         }
@@ -107,49 +159,6 @@ impl Display for TimeStamp {
     }
 }
 
-#[derive(Debug)]
-pub struct VideoOptions {
-    pub yt_dlp: YtDlpOptions,
-    pub mpv: MpvOptions,
-}
-impl VideoOptions {
-    pub(crate) fn new(subtitle_langs: String, playback_speed: f64) -> Self {
-        let yt_dlp = YtDlpOptions { subtitle_langs };
-        let mpv = MpvOptions { playback_speed };
-        Self { yt_dlp, mpv }
-    }
-
-    /// This will write out the options that are different from the defaults.
-    /// Beware, that this does not set the priority.
-    #[must_use]
-    pub fn to_cli_flags(self, app: &App) -> String {
-        let mut f = String::new();
-
-        if (self.mpv.playback_speed - app.config.select.playback_speed).abs() > f64::EPSILON {
-            write!(f, " --speed '{}'", self.mpv.playback_speed).expect("Works");
-        }
-        if self.yt_dlp.subtitle_langs != app.config.select.subtitle_langs {
-            write!(f, " --subtitle-langs '{}'", self.yt_dlp.subtitle_langs).expect("Works");
-        }
-
-        f.trim().to_owned()
-    }
-}
-
-#[derive(Debug, Clone, Copy)]
-/// Additionally settings passed to mpv on watch
-pub struct MpvOptions {
-    /// The playback speed. (1 is 100%, 2.7 is 270%, and so on)
-    pub playback_speed: f64,
-}
-
-#[derive(Debug)]
-/// Additionally configuration options, passed to yt-dlp on download
-pub struct YtDlpOptions {
-    /// In the form of `lang1,lang2,lang3` (e.g. `en,de,sv`)
-    pub subtitle_langs: String,
-}
-
 /// # Video Lifetime (words in <brackets> are commands):
 ///      <Pick>
 ///     /    \
@@ -158,8 +167,8 @@ pub struct YtDlpOptions {
 /// Cache                       // yt cache
 ///     |
 /// Watched                     // yt watch
-#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
-pub enum VideoStatus {
+#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub(crate) enum VideoStatus {
     #[default]
     Pick,
 
@@ -186,7 +195,10 @@ impl VideoStatus {
     /// # Panics
     /// Only if internal expectations fail.
     #[must_use]
-    pub fn from_marker(marker: VideoStatusMarker, optional: Option<(PathBuf, bool)>) -> Self {
+    pub(crate) fn from_marker(
+        marker: VideoStatusMarker,
+        optional: Option<(PathBuf, bool)>,
+    ) -> Self {
         match marker {
             VideoStatusMarker::Pick => Self::Pick,
             VideoStatusMarker::Watch => Self::Watch,
@@ -204,26 +216,9 @@ impl VideoStatus {
         }
     }
 
-    /// Turn the [`VideoStatus`] to its internal parts. This is only really useful for the database
-    /// functions.
-    #[must_use]
-    pub fn to_parts_for_db(self) -> (VideoStatusMarker, Option<(PathBuf, bool)>) {
-        match self {
-            VideoStatus::Pick => (VideoStatusMarker::Pick, None),
-            VideoStatus::Watch => (VideoStatusMarker::Watch, None),
-            VideoStatus::Cached {
-                cache_path,
-                is_focused,
-            } => (VideoStatusMarker::Cached, Some((cache_path, is_focused))),
-            VideoStatus::Watched => (VideoStatusMarker::Watched, None),
-            VideoStatus::Drop => (VideoStatusMarker::Drop, None),
-            VideoStatus::Dropped => (VideoStatusMarker::Dropped, None),
-        }
-    }
-
     /// Return the associated [`VideoStatusMarker`] for this [`VideoStatus`].
     #[must_use]
-    pub fn as_marker(&self) -> VideoStatusMarker {
+    pub(crate) fn as_marker(&self) -> VideoStatusMarker {
         match self {
             VideoStatus::Pick => VideoStatusMarker::Pick,
             VideoStatus::Watch => VideoStatusMarker::Watch,
@@ -237,7 +232,7 @@ impl VideoStatus {
 
 /// Unit only variant of [`VideoStatus`]
 #[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
-pub enum VideoStatusMarker {
+pub(crate) enum VideoStatusMarker {
     #[default]
     Pick,
 
@@ -255,7 +250,7 @@ pub enum VideoStatusMarker {
 }
 
 impl VideoStatusMarker {
-    pub const ALL: &'static [Self; 6] = &[
+    pub(crate) const ALL: &'static [Self; 6] = &[
         Self::Pick,
         //
         Self::Watch,
@@ -267,7 +262,7 @@ impl VideoStatusMarker {
     ];
 
     #[must_use]
-    pub fn as_command(&self) -> &str {
+    pub(crate) fn as_command(&self) -> &str {
         // NOTE: Keep the serialize able variants synced with the main `select` function <2024-06-14>
         // Also try to ensure, that the strings have the same length
         match self {
@@ -281,7 +276,7 @@ impl VideoStatusMarker {
     }
 
     #[must_use]
-    pub fn as_db_integer(&self) -> i64 {
+    pub(crate) fn as_db_integer(self) -> i64 {
         // These numbers should not change their mapping!
         // Oh, and keep them in sync with the SQLite check constraint.
         match self {
@@ -296,7 +291,7 @@ impl VideoStatusMarker {
         }
     }
     #[must_use]
-    pub fn from_db_integer(num: i64) -> Self {
+    pub(crate) fn from_db_integer(num: i64) -> Self {
         match num {
             0 => Self::Pick,
 
@@ -314,7 +309,7 @@ impl VideoStatusMarker {
     }
 
     #[must_use]
-    pub fn as_str(&self) -> &'static str {
+    pub(crate) fn as_str(self) -> &'static str {
         match self {
             Self::Pick => "Pick",
 
diff --git a/crates/yt/src/storage/migrate/mod.rs b/crates/yt/src/storage/migrate/mod.rs
index 953d079..c5187ee 100644
--- a/crates/yt/src/storage/migrate/mod.rs
+++ b/crates/yt/src/storage/migrate/mod.rs
@@ -75,7 +75,7 @@ macro_rules! make_upgrade {
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
-pub enum DbVersion {
+pub(crate) enum DbVersion {
     /// The database is not yet initialized.
     Empty,
 
@@ -91,8 +91,17 @@ pub enum DbVersion {
 
     /// Introduced: 2025-03-21.
     Three,
+
+    /// Introduced: 2025-07-05.
+    Four,
+
+    /// Introduced: 2025-07-20.
+    Five,
+
+    /// Introduced: 2025-08-26.
+    Six,
 }
-const CURRENT_VERSION: DbVersion = DbVersion::Three;
+const CURRENT_VERSION: DbVersion = DbVersion::Six;
 
 async fn add_error_context(
     function: impl Future<Output = Result<()>>,
@@ -143,6 +152,9 @@ impl DbVersion {
             DbVersion::One => 1,
             DbVersion::Two => 2,
             DbVersion::Three => 3,
+            DbVersion::Four => 4,
+            DbVersion::Five => 5,
+            DbVersion::Six => 6,
 
             DbVersion::Empty => unreachable!("A empty version does not have an associated integer"),
         }
@@ -154,11 +166,17 @@ impl DbVersion {
             (1, "yt") => Ok(DbVersion::One),
             (2, "yt") => Ok(DbVersion::Two),
             (3, "yt") => Ok(DbVersion::Three),
+            (4, "yt") => Ok(DbVersion::Four),
+            (5, "yt") => Ok(DbVersion::Five),
+            (6, "yt") => Ok(DbVersion::Six),
 
             (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}'"),
+            (4, other) => bail!("Db version is Four, but got unknown namespace: '{other}'"),
+            (5, other) => bail!("Db version is Five, but got unknown namespace: '{other}'"),
+            (6, other) => bail!("Db version is Six, 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}')"),
@@ -188,8 +206,20 @@ impl DbVersion {
                 make_upgrade! {app, Self::Two, Self::Three, "./sql/3_Two_to_Three.sql"}
             }
 
-            // This is the current_version
             Self::Three => {
+                make_upgrade! {app, Self::Three, Self::Four, "./sql/4_Three_to_Four.sql"}
+            }
+
+            Self::Four => {
+                make_upgrade! {app, Self::Four, Self::Five, "./sql/5_Four_to_Five.sql"}
+            }
+
+            Self::Five => {
+                make_upgrade! {app, Self::Five, Self::Six, "./sql/6_Five_to_Six.sql"}
+            }
+
+            // This is the current_version
+            Self::Six => {
                 assert_eq!(self, CURRENT_VERSION);
                 assert_eq!(self, get_version(app).await?);
                 Ok(())
@@ -222,9 +252,10 @@ fn get_current_date() -> i64 {
 ///
 /// # Panics
 /// Only if internal assertions fail.
-pub async fn get_version(app: &App) -> Result<DbVersion> {
+pub(crate) async fn get_version(app: &App) -> Result<DbVersion> {
     get_version_db(&app.database).await
 }
+
 /// Return the current database version.
 ///
 /// In contrast to the [`get_version`] function, this function does not
@@ -232,13 +263,19 @@ pub async fn get_version(app: &App) -> Result<DbVersion> {
 ///
 /// # Panics
 /// Only if internal assertions fail.
-pub async fn get_version_db(pool: &SqlitePool) -> Result<DbVersion> {
+pub(crate) async fn get_version_db(pool: &SqlitePool) -> Result<DbVersion> {
     let version_table_exists = {
         let query = query!(
-            "SELECT 1 as result FROM sqlite_master WHERE type = 'table' AND name = 'version'"
+            "
+            SELECT 1 as result
+            FROM sqlite_master
+            WHERE type = 'table'
+            AND name = 'version'
+            "
         )
         .fetch_optional(pool)
         .await?;
+
         if let Some(output) = query {
             assert_eq!(output.result, 1);
             true
@@ -246,13 +283,16 @@ pub async fn get_version_db(pool: &SqlitePool) -> Result<DbVersion> {
             false
         }
     };
+
     if !version_table_exists {
         return Ok(DbVersion::Empty);
     }
 
     let current_version = query!(
         "
-        SELECT namespace, number FROM version WHERE valid_to IS NULL;
+        SELECT namespace, number
+        FROM version
+        WHERE valid_to IS NULL;
         "
     )
     .fetch_one(pool)
@@ -262,7 +302,7 @@ pub async fn get_version_db(pool: &SqlitePool) -> Result<DbVersion> {
     DbVersion::from_db(current_version.number, current_version.namespace.as_str())
 }
 
-pub async fn migrate_db(app: &App) -> Result<()> {
+pub(crate) async fn migrate_db(app: &App) -> Result<()> {
     let current_version = get_version(app)
         .await
         .context("Failed to determine initial version")?;
diff --git a/crates/yt/src/storage/migrate/sql/4_Three_to_Four.sql b/crates/yt/src/storage/migrate/sql/4_Three_to_Four.sql
new file mode 100644
index 0000000..9c283a1
--- /dev/null
+++ b/crates/yt/src/storage/migrate/sql/4_Three_to_Four.sql
@@ -0,0 +1,24 @@
+-- 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>.
+
+ALTER TABLE videos
+ADD COLUMN subtitle_langs TEXT;
+
+ALTER TABLE videos
+ADD COLUMN playback_speed REAL CHECK (playback_speed >= 0);
+
+UPDATE videos
+   SET playback_speed = video_options.playback_speed,
+       subtitle_langs = video_options.subtitle_langs
+  FROM video_options
+ WHERE videos.extractor_hash = video_options.extractor_hash;
+
+
+DROP TABLE video_options;
diff --git a/crates/yt/src/storage/migrate/sql/5_Four_to_Five.sql b/crates/yt/src/storage/migrate/sql/5_Four_to_Five.sql
new file mode 100644
index 0000000..6c4b7cc
--- /dev/null
+++ b/crates/yt/src/storage/migrate/sql/5_Four_to_Five.sql
@@ -0,0 +1,15 @@
+-- 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>.
+
+
+CREATE TABLE txn_log (
+    timestamp INTEGER NOT NULL,
+    operation TEXT    NOT NULL
+) STRICT;
diff --git a/crates/yt/src/storage/migrate/sql/6_Five_to_Six.sql b/crates/yt/src/storage/migrate/sql/6_Five_to_Six.sql
new file mode 100644
index 0000000..6a2cbcc
--- /dev/null
+++ b/crates/yt/src/storage/migrate/sql/6_Five_to_Six.sql
@@ -0,0 +1,12 @@
+-- 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>.
+
+ALTER TABLE subscriptions
+ADD COLUMN is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1));
diff --git a/crates/yt/src/storage/mod.rs b/crates/yt/src/storage/mod.rs
index d352b41..6dcff74 100644
--- a/crates/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(crate) mod db;
+pub(crate) mod migrate;
+pub(crate) mod notify;
diff --git a/crates/yt/src/storage/video_database/notify.rs b/crates/yt/src/storage/notify.rs
index b55c00a..e0ee4e9 100644
--- a/crates/yt/src/storage/video_database/notify.rs
+++ b/crates/yt/src/storage/notify.rs
@@ -26,7 +26,7 @@ use tokio::task;
 
 /// This functions registers a watcher for the database and only returns once a write was
 /// registered for the database.
-pub async fn wait_for_db_write(app: &App) -> Result<()> {
+pub(crate) async fn wait_for_db_write(app: &App) -> Result<()> {
     let db_path: PathBuf = app.config.paths.database_path.clone();
     task::spawn_blocking(move || wait_for_db_write_sync(&db_path)).await?
 }
@@ -53,7 +53,7 @@ fn wait_for_db_write_sync(db_path: &Path) -> Result<()> {
 }
 
 /// This functions registers a watcher for the cache path and returns once a file was removed
-pub async fn wait_for_cache_reduction(app: &App) -> Result<()> {
+pub(crate) async fn wait_for_cache_reduction(app: &App) -> Result<()> {
     let download_directory: PathBuf = app.config.paths.download_dir.clone();
     task::spawn_blocking(move || wait_for_cache_reduction_sync(&download_directory)).await?
 }
diff --git a/crates/yt/src/storage/subscriptions.rs b/crates/yt/src/storage/subscriptions.rs
deleted file mode 100644
index 1ab0d72..0000000
--- a/crates/yt/src/storage/subscriptions.rs
+++ /dev/null
@@ -1,141 +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>.
-
-//! Handle subscriptions
-
-use std::collections::HashMap;
-
-use anyhow::Result;
-use log::debug;
-use sqlx::query;
-use url::Url;
-use yt_dlp::options::YoutubeDLOptions;
-
-use crate::{app::App, unreachable::Unreachable};
-
-#[derive(Clone, Debug)]
-pub struct Subscription {
-    /// The human readable name of this subscription
-    pub name: String,
-
-    /// The URL this subscription subscribes to
-    pub url: Url,
-}
-
-impl Subscription {
-    #[must_use]
-    pub fn new(name: String, url: Url) -> Self {
-        Self { name, url }
-    }
-}
-
-/// Check whether an URL could be used as a subscription URL
-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.get("_type") == Some(&serde_json::Value::String("Playlist".to_owned())))
-}
-
-#[derive(Default, Debug)]
-pub struct Subscriptions(pub(crate) HashMap<String, Subscription>);
-
-/// Remove all subscriptions
-pub async fn remove_all(app: &App) -> Result<()> {
-    query!(
-        "
-        DELETE FROM subscriptions;
-    ",
-    )
-    .execute(&app.database)
-    .await?;
-
-    Ok(())
-}
-
-/// Get a list of subscriptions
-pub async fn get(app: &App) -> Result<Subscriptions> {
-    let raw_subs = query!(
-        "
-        SELECT *
-        FROM subscriptions;
-    "
-    )
-    .fetch_all(&app.database)
-    .await?;
-
-    let subscriptions: HashMap<String, Subscription> = raw_subs
-        .into_iter()
-        .map(|sub| {
-            (
-                sub.name.clone(),
-                Subscription::new(
-                    sub.name,
-                    Url::parse(&sub.url).unreachable("It was an URL, when we inserted it."),
-                ),
-            )
-        })
-        .collect();
-
-    Ok(Subscriptions(subscriptions))
-}
-
-pub async fn add_subscription(app: &App, sub: &Subscription) -> Result<()> {
-    let url = sub.url.to_string();
-
-    query!(
-        "
-        INSERT INTO subscriptions (
-            name,
-            url
-        ) VALUES (?, ?);
-    ",
-        sub.name,
-        url
-    )
-    .execute(&app.database)
-    .await?;
-
-    println!("Subscribed to '{}' at '{}'", sub.name, sub.url);
-    Ok(())
-}
-
-/// # Panics
-/// Only if assertions fail
-pub async fn remove_subscription(app: &App, sub: &Subscription) -> Result<()> {
-    let output = query!(
-        "
-        DELETE FROM subscriptions
-        WHERE name = ?
-    ",
-        sub.name,
-    )
-    .execute(&app.database)
-    .await?;
-
-    assert_eq!(
-        output.rows_affected(),
-        1,
-        "The remove subscriptino query did effect more (or less) than one row. This is a bug."
-    );
-
-    println!("Unsubscribed from '{}' at '{}'", sub.name, sub.url);
-
-    Ok(())
-}
diff --git a/crates/yt/src/storage/video_database/downloader.rs b/crates/yt/src/storage/video_database/downloader.rs
deleted file mode 100644
index a95081e..0000000
--- a/crates/yt/src/storage/video_database/downloader.rs
+++ /dev/null
@@ -1,130 +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 std::path::{Path, PathBuf};
-
-use anyhow::Result;
-use log::debug;
-use sqlx::query;
-
-use crate::{
-    app::App,
-    storage::video_database::{VideoStatus, VideoStatusMarker},
-    unreachable::Unreachable,
-    video_from_record,
-};
-
-use super::{ExtractorHash, Video};
-
-/// Returns to next video which should be downloaded. This respects the priority assigned by select.
-/// It does not return videos, which are already cached.
-///
-/// # Panics
-/// Only if assertions fail.
-pub async fn get_next_uncached_video(app: &App) -> Result<Option<Video>> {
-    let status = VideoStatus::Watch.as_marker().as_db_integer();
-
-    // NOTE: The ORDER BY statement should be the same as the one in [`get::videos`].<2024-08-22>
-    let result = query!(
-        r#"
-        SELECT *
-        FROM videos
-        WHERE status = ? AND cache_path IS NULL
-        ORDER BY priority DESC, publish_date DESC
-        LIMIT 1;
-    "#,
-        status
-    )
-    .fetch_one(&app.database)
-    .await;
-
-    if let Err(sqlx::Error::RowNotFound) = result {
-        Ok(None)
-    } else {
-        let base = result?;
-
-        Ok(Some(video_from_record! {base}))
-    }
-}
-
-/// Update the cached path of a video. Will be set to NULL if the path is None
-/// This will also set the status to `Cached` when path is Some, otherwise it set's the status to
-/// `Watch`.
-pub async fn set_video_cache_path(
-    app: &App,
-    video: &ExtractorHash,
-    path: Option<&Path>,
-) -> Result<()> {
-    if let Some(path) = path {
-        debug!(
-            "Setting cache path from '{}' to '{}'",
-            video.into_short_hash(app).await?,
-            path.display()
-        );
-
-        let path_str = path.display().to_string();
-        let extractor_hash = video.hash().to_string();
-        let status = VideoStatusMarker::Cached.as_db_integer();
-
-        query!(
-            r#"
-            UPDATE videos
-            SET cache_path = ?, status = ?
-            WHERE extractor_hash = ?;
-        "#,
-            path_str,
-            status,
-            extractor_hash
-        )
-        .execute(&app.database)
-        .await?;
-
-        Ok(())
-    } else {
-        debug!(
-            "Setting cache path from '{}' to NULL",
-            video.into_short_hash(app).await?,
-        );
-
-        let extractor_hash = video.hash().to_string();
-        let status = VideoStatus::Watch.as_marker().as_db_integer();
-
-        query!(
-            r#"
-            UPDATE videos
-            SET cache_path = NULL, status = ?
-            WHERE extractor_hash = ?;
-        "#,
-            status,
-            extractor_hash
-        )
-        .execute(&app.database)
-        .await?;
-
-        Ok(())
-    }
-}
-
-/// Returns the number of cached videos
-pub async fn get_allocated_cache(app: &App) -> Result<u32> {
-    let count = query!(
-        r#"
-        SELECT COUNT(cache_path) as count
-        FROM videos
-        WHERE cache_path IS NOT NULL;
-"#,
-    )
-    .fetch_one(&app.database)
-    .await?;
-
-    Ok(u32::try_from(count.count)
-        .unreachable("The value should be strictly positive (and bolow `u32::Max`)"))
-}
diff --git a/crates/yt/src/storage/video_database/extractor_hash.rs b/crates/yt/src/storage/video_database/extractor_hash.rs
deleted file mode 100644
index df545d7..0000000
--- a/crates/yt/src/storage/video_database/extractor_hash.rs
+++ /dev/null
@@ -1,163 +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 std::{collections::HashSet, fmt::Display, str::FromStr};
-
-use anyhow::{Context, Result, bail};
-use blake3::Hash;
-use log::debug;
-use tokio::sync::OnceCell;
-
-use crate::{app::App, storage::video_database::get::get_all_hashes, unreachable::Unreachable};
-
-static EXTRACTOR_HASH_LENGTH: OnceCell<usize> = OnceCell::const_new();
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
-pub struct ExtractorHash {
-    hash: Hash,
-}
-
-impl Display for ExtractorHash {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.hash.fmt(f)
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct ShortHash(String);
-
-impl Display for ShortHash {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.0.fmt(f)
-    }
-}
-
-#[derive(Debug, Clone)]
-#[allow(clippy::module_name_repetitions)]
-pub struct LazyExtractorHash {
-    value: ShortHash,
-}
-
-impl FromStr for LazyExtractorHash {
-    type Err = anyhow::Error;
-
-    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
-        // perform some cheap validation
-        if s.len() > 64 {
-            bail!("A hash can only contain 64 bytes!");
-        }
-
-        Ok(Self {
-            value: ShortHash(s.to_owned()),
-        })
-    }
-}
-
-impl LazyExtractorHash {
-    /// Turn the [`LazyExtractorHash`] into the [`ExtractorHash`]
-    pub async fn realize(self, app: &App) -> Result<ExtractorHash> {
-        ExtractorHash::from_short_hash(app, &self.value).await
-    }
-}
-
-impl ExtractorHash {
-    #[must_use]
-    pub fn from_hash(hash: Hash) -> Self {
-        Self { hash }
-    }
-    pub async fn from_short_hash(app: &App, s: &ShortHash) -> Result<Self> {
-        Ok(Self {
-            hash: Self::short_hash_to_full_hash(app, s).await?,
-        })
-    }
-
-    #[must_use]
-    pub fn hash(&self) -> &Hash {
-        &self.hash
-    }
-
-    pub async fn into_short_hash(&self, app: &App) -> Result<ShortHash> {
-        let needed_chars = if let Some(needed_chars) = EXTRACTOR_HASH_LENGTH.get() {
-            *needed_chars
-        } else {
-            let needed_chars = self
-                .get_needed_char_len(app)
-                .await
-                .context("Failed to calculate needed char length")?;
-            EXTRACTOR_HASH_LENGTH.set(needed_chars).unreachable(
-                "This should work at this stage, as we checked above that it is empty.",
-            );
-
-            needed_chars
-        };
-
-        Ok(ShortHash(
-            self.hash()
-                .to_hex()
-                .chars()
-                .take(needed_chars)
-                .collect::<String>(),
-        ))
-    }
-
-    async fn short_hash_to_full_hash(app: &App, s: &ShortHash) -> Result<Hash> {
-        let all_hashes = get_all_hashes(app)
-            .await
-            .context("Failed to fetch all extractor -hashesh from database")?;
-
-        let needed_chars = s.0.len();
-
-        for hash in all_hashes {
-            if hash.to_hex()[..needed_chars] == s.0 {
-                return Ok(hash);
-            }
-        }
-
-        bail!("Your shortend hash, does not match a real hash (this is probably a bug)!");
-    }
-
-    async fn get_needed_char_len(&self, app: &App) -> Result<usize> {
-        debug!("Calculating the needed hash char length");
-        let all_hashes = get_all_hashes(app)
-            .await
-            .context("Failed to fetch all extractor -hashesh from database")?;
-
-        let all_char_vec_hashes = all_hashes
-            .into_iter()
-            .map(|hash| hash.to_hex().chars().collect::<Vec<char>>())
-            .collect::<Vec<Vec<_>>>();
-
-        // This value should be updated later, if not rust will panic in the assertion.
-        let mut needed_chars: usize = 1000;
-        'outer: for i in 1..64 {
-            let i_chars: Vec<String> = all_char_vec_hashes
-                .iter()
-                .map(|vec| vec.iter().take(i).collect::<String>())
-                .collect();
-
-            let mut uniqnes_hashmap: HashSet<String> = HashSet::new();
-            for ch in i_chars {
-                if !uniqnes_hashmap.insert(ch) {
-                    // The key was already in the hash map, thus we have a duplicated char and need
-                    // at least one char more
-                    continue 'outer;
-                }
-            }
-
-            needed_chars = i;
-            break 'outer;
-        }
-
-        assert!(needed_chars <= 64, "Hashes are only 64 bytes long");
-
-        Ok(needed_chars)
-    }
-}
diff --git a/crates/yt/src/storage/video_database/get/mod.rs b/crates/yt/src/storage/video_database/get/mod.rs
deleted file mode 100644
index e76131e..0000000
--- a/crates/yt/src/storage/video_database/get/mod.rs
+++ /dev/null
@@ -1,307 +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>.
-
-//! These functions interact with the storage db in a read-only way. They are added on-demand (as
-//! you could theoretically just could do everything with the `get_videos` function), as
-//! performance or convince requires.
-use std::{fs::File, path::PathBuf};
-
-use anyhow::{Context, Result, bail};
-use blake3::Hash;
-use log::{debug, trace};
-use sqlx::query;
-use yt_dlp::info_json::InfoJson;
-
-use crate::{
-    app::App,
-    storage::{
-        subscriptions::Subscription,
-        video_database::{Video, extractor_hash::ExtractorHash},
-    },
-    unreachable::Unreachable,
-};
-
-use super::{MpvOptions, VideoOptions, VideoStatus, VideoStatusMarker, YtDlpOptions};
-
-mod playlist;
-pub use playlist::*;
-
-#[macro_export]
-macro_rules! video_from_record {
-    ($record:expr) => {
-        Video {
-            description: $record.description.clone(),
-            duration: $crate::storage::video_database::MaybeDuration::from_maybe_secs_f64(
-                $record.duration,
-            ),
-            extractor_hash:
-                $crate::storage::video_database::extractor_hash::ExtractorHash::from_hash(
-                    $record
-                        .extractor_hash
-                        .parse()
-                        .expect("The db hash should be a valid blake3 hash"),
-                ),
-            last_status_change: $crate::storage::video_database::TimeStamp::from_secs(
-                $record.last_status_change,
-            ),
-            parent_subscription_name: $record.parent_subscription_name.clone(),
-            publish_date: $record
-                .publish_date
-                .map(|pd| $crate::storage::video_database::TimeStamp::from_secs(pd)),
-            status: {
-                let marker = $crate::storage::video_database::VideoStatusMarker::from_db_integer(
-                    $record.status,
-                );
-
-                let optional = if let Some(cache_path) = &$record.cache_path {
-                    Some((
-                        PathBuf::from(cache_path),
-                        if $record.is_focused == Some(1) {
-                            true
-                        } else {
-                            false
-                        },
-                    ))
-                } else {
-                    None
-                };
-
-                $crate::storage::video_database::VideoStatus::from_marker(marker, optional)
-            },
-            thumbnail_url: if let Some(url) = &$record.thumbnail_url {
-                Some(url::Url::parse(&url).expect("Parsing this as url should always work"))
-            } else {
-                None
-            },
-            title: $record.title.clone(),
-            url: url::Url::parse(&$record.url).expect("Parsing this as url should always work"),
-            priority: $crate::storage::video_database::Priority::from($record.priority),
-
-            watch_progress: std::time::Duration::from_secs(
-                u64::try_from($record.watch_progress).expect("The record is positive i64"),
-            ),
-        }
-    };
-}
-
-/// Returns the videos that are in the `allowed_states`.
-///
-/// # Panics
-/// Only, if assertions fail.
-pub async fn videos(app: &App, allowed_states: &[VideoStatusMarker]) -> Result<Vec<Video>> {
-    fn test(all_states: &[VideoStatusMarker], check: VideoStatusMarker) -> Option<i64> {
-        if all_states.contains(&check) {
-            trace!("State '{check:?}' marked as active");
-            Some(check.as_db_integer())
-        } else {
-            trace!("State '{check:?}' marked as inactive");
-            None
-        }
-    }
-    fn states_to_string(allowed_states: &[VideoStatusMarker]) -> String {
-        let mut states = allowed_states
-            .iter()
-            .fold(String::from("&["), |mut acc, state| {
-                acc.push_str(state.as_str());
-                acc.push_str(", ");
-                acc
-            });
-        states = states.trim().to_owned();
-        states = states.trim_end_matches(',').to_owned();
-        states.push(']');
-        states
-    }
-
-    debug!(
-        "Fetching videos in the states: '{}'",
-        states_to_string(allowed_states)
-    );
-    let active_pick: Option<i64> = test(allowed_states, VideoStatusMarker::Pick);
-    let active_watch: Option<i64> = test(allowed_states, VideoStatusMarker::Watch);
-    let active_cached: Option<i64> = test(allowed_states, VideoStatusMarker::Cached);
-    let active_watched: Option<i64> = test(allowed_states, VideoStatusMarker::Watched);
-    let active_drop: Option<i64> = test(allowed_states, VideoStatusMarker::Drop);
-    let active_dropped: Option<i64> = test(allowed_states, VideoStatusMarker::Dropped);
-
-    let videos = query!(
-        r"
-        SELECT *
-          FROM videos
-          WHERE status IN (?,?,?,?,?,?)
-          ORDER BY priority DESC, publish_date DESC;
-          ",
-        active_pick,
-        active_watch,
-        active_cached,
-        active_watched,
-        active_drop,
-        active_dropped,
-    )
-    .fetch_all(&app.database)
-    .await
-    .with_context(|| {
-        format!(
-            "Failed to query videos with states: '{}'",
-            states_to_string(allowed_states)
-        )
-    })?;
-
-    let real_videos: Vec<Video> = videos
-        .iter()
-        .map(|base| -> Video {
-            video_from_record! {base}
-        })
-        .collect();
-
-    Ok(real_videos)
-}
-
-pub fn video_info_json(video: &Video) -> Result<Option<InfoJson>> {
-    if let VideoStatus::Cached { mut cache_path, .. } = video.status.clone() {
-        if !cache_path.set_extension("info.json") {
-            bail!(
-                "Failed to change path extension to 'info.json': {}",
-                cache_path.display()
-            );
-        }
-        let info_json_string = File::open(cache_path)?;
-        let info_json: InfoJson = serde_json::from_reader(&info_json_string)?;
-
-        Ok(Some(info_json))
-    } else {
-        Ok(None)
-    }
-}
-
-pub async fn video_by_hash(app: &App, hash: &ExtractorHash) -> Result<Video> {
-    let ehash = hash.hash().to_string();
-
-    let raw_video = query!(
-        "
-        SELECT * FROM videos WHERE extractor_hash = ?;
-        ",
-        ehash
-    )
-    .fetch_one(&app.database)
-    .await?;
-
-    Ok(video_from_record! {raw_video})
-}
-
-pub async fn get_all_hashes(app: &App) -> Result<Vec<Hash>> {
-    let hashes_hex = query!(
-        r#"
-        SELECT extractor_hash
-        FROM videos;
-    "#
-    )
-    .fetch_all(&app.database)
-    .await?;
-
-    Ok(hashes_hex
-        .iter()
-        .map(|hash| {
-            Hash::from_hex(&hash.extractor_hash).unreachable(
-                "These values started as blake3 hashes, they should stay blake3 hashes",
-            )
-        })
-        .collect())
-}
-
-pub async fn get_video_hashes(app: &App, subs: &Subscription) -> Result<Vec<Hash>> {
-    let hashes_hex = query!(
-        r#"
-        SELECT extractor_hash
-        FROM videos
-        WHERE parent_subscription_name = ?;
-    "#,
-        subs.name
-    )
-    .fetch_all(&app.database)
-    .await?;
-
-    Ok(hashes_hex
-        .iter()
-        .map(|hash| {
-            Hash::from_hex(&hash.extractor_hash).unreachable(
-                "These values started as blake3 hashes, they should stay blake3 hashes",
-            )
-        })
-        .collect())
-}
-
-pub async fn get_video_yt_dlp_opts(app: &App, hash: &ExtractorHash) -> Result<YtDlpOptions> {
-    let ehash = hash.hash().to_string();
-
-    let yt_dlp_options = query!(
-        r#"
-        SELECT subtitle_langs
-        FROM video_options
-        WHERE extractor_hash = ?;
-    "#,
-        ehash
-    )
-    .fetch_one(&app.database)
-    .await
-    .with_context(|| {
-        format!("Failed to fetch the `yt_dlp_video_opts` for video with hash: '{hash}'",)
-    })?;
-
-    Ok(YtDlpOptions {
-        subtitle_langs: yt_dlp_options.subtitle_langs,
-    })
-}
-pub async fn video_mpv_opts(app: &App, hash: &ExtractorHash) -> Result<MpvOptions> {
-    let ehash = hash.hash().to_string();
-
-    let mpv_options = query!(
-        r#"
-        SELECT playback_speed
-        FROM video_options
-        WHERE extractor_hash = ?;
-    "#,
-        ehash
-    )
-    .fetch_one(&app.database)
-    .await
-    .with_context(|| {
-        format!("Failed to fetch the `mpv_video_opts` for video with hash: '{hash}'")
-    })?;
-
-    Ok(MpvOptions {
-        playback_speed: mpv_options.playback_speed,
-    })
-}
-
-pub async fn get_video_opts(app: &App, hash: &ExtractorHash) -> Result<VideoOptions> {
-    let ehash = hash.hash().to_string();
-
-    let opts = query!(
-        r#"
-        SELECT playback_speed, subtitle_langs
-        FROM video_options
-        WHERE extractor_hash = ?;
-    "#,
-        ehash
-    )
-    .fetch_one(&app.database)
-    .await
-    .with_context(|| format!("Failed to fetch the `video_opts` for video with hash: '{hash}'"))?;
-
-    let mpv = MpvOptions {
-        playback_speed: opts.playback_speed,
-    };
-    let yt_dlp = YtDlpOptions {
-        subtitle_langs: opts.subtitle_langs,
-    };
-
-    Ok(VideoOptions { yt_dlp, mpv })
-}
diff --git a/crates/yt/src/storage/video_database/get/playlist/iterator.rs b/crates/yt/src/storage/video_database/get/playlist/iterator.rs
deleted file mode 100644
index 4c45bf7..0000000
--- a/crates/yt/src/storage/video_database/get/playlist/iterator.rs
+++ /dev/null
@@ -1,101 +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::{
-    collections::VecDeque,
-    path::{Path, PathBuf},
-};
-
-use crate::storage::video_database::{Video, VideoStatus};
-
-use super::Playlist;
-
-/// Turn a cached video into it's `cache_path`
-fn to_cache_video(video: Video) -> PathBuf {
-    if let VideoStatus::Cached { cache_path, .. } = video.status {
-        cache_path
-    } else {
-        unreachable!("ALl of these videos should be cached.")
-    }
-}
-
-#[derive(Debug)]
-pub struct PlaylistIterator {
-    paths: VecDeque<PathBuf>,
-}
-
-impl Iterator for PlaylistIterator {
-    type Item = <Playlist as IntoIterator>::Item;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        self.paths.pop_front()
-    }
-}
-
-impl DoubleEndedIterator for PlaylistIterator {
-    fn next_back(&mut self) -> Option<Self::Item> {
-        self.paths.pop_back()
-    }
-}
-
-impl IntoIterator for Playlist {
-    type Item = PathBuf;
-
-    type IntoIter = PlaylistIterator;
-
-    fn into_iter(self) -> Self::IntoIter {
-        let paths = self.videos.into_iter().map(to_cache_video).collect();
-        Self::IntoIter { paths }
-    }
-}
-
-#[derive(Debug)]
-pub struct PlaylistIteratorBorrowed<'a> {
-    paths: Vec<&'a Path>,
-    index: usize,
-}
-
-impl<'a> Iterator for PlaylistIteratorBorrowed<'a> {
-    type Item = <&'a Playlist as IntoIterator>::Item;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let output = self.paths.get(self.index);
-        self.index += 1;
-        output.map(|v| &**v)
-    }
-}
-
-impl<'a> Playlist {
-    #[must_use]
-    pub fn iter(&'a self) -> PlaylistIteratorBorrowed<'a> {
-        <&Self as IntoIterator>::into_iter(self)
-    }
-}
-
-impl<'a> IntoIterator for &'a Playlist {
-    type Item = &'a Path;
-
-    type IntoIter = PlaylistIteratorBorrowed<'a>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        let paths = self
-            .videos
-            .iter()
-            .map(|vid| {
-                if let VideoStatus::Cached { cache_path, .. } = &vid.status {
-                    cache_path.as_path()
-                } else {
-                    unreachable!("ALl of these videos should be cached.")
-                }
-            })
-            .collect();
-        Self::IntoIter { paths, index: 0 }
-    }
-}
diff --git a/crates/yt/src/storage/video_database/get/playlist/mod.rs b/crates/yt/src/storage/video_database/get/playlist/mod.rs
deleted file mode 100644
index f6aadbf..0000000
--- a/crates/yt/src/storage/video_database/get/playlist/mod.rs
+++ /dev/null
@@ -1,167 +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>.
-
-//! This file contains the getters for the internal playlist
-
-use std::{ops::Add, path::PathBuf};
-
-use crate::{
-    app::App,
-    storage::video_database::{Video, VideoStatusMarker, extractor_hash::ExtractorHash},
-    video_from_record,
-};
-
-use anyhow::Result;
-use sqlx::query;
-
-pub mod iterator;
-
-/// Zero-based index into the internal playlist.
-#[derive(Debug, Clone, Copy)]
-pub struct PlaylistIndex(usize);
-
-impl From<PlaylistIndex> for usize {
-    fn from(value: PlaylistIndex) -> Self {
-        value.0
-    }
-}
-
-impl From<usize> for PlaylistIndex {
-    fn from(value: usize) -> Self {
-        Self(value)
-    }
-}
-
-impl Add<usize> for PlaylistIndex {
-    type Output = Self;
-
-    fn add(self, rhs: usize) -> Self::Output {
-        Self(self.0 + rhs)
-    }
-}
-
-impl Add for PlaylistIndex {
-    type Output = Self;
-
-    fn add(self, rhs: Self) -> Self::Output {
-        Self(self.0 + rhs.0)
-    }
-}
-
-/// A representation of the internal Playlist
-#[derive(Debug)]
-pub struct Playlist {
-    videos: Vec<Video>,
-}
-
-impl Playlist {
-    /// Return the videos of this playlist.
-    #[must_use]
-    pub fn as_videos(&self) -> &[Video] {
-        &self.videos
-    }
-
-    /// Turn this playlist to it's videos
-    #[must_use]
-    pub fn to_videos(self) -> Vec<Video> {
-        self.videos
-    }
-
-    /// Find the index of the video specified by the `video_hash`.
-    ///
-    /// # Panics
-    /// Only if internal assertions fail.
-    #[must_use]
-    pub fn find_index(&self, video_hash: &ExtractorHash) -> Option<PlaylistIndex> {
-        if let Some((index, value)) = self
-            .videos
-            .iter()
-            .enumerate()
-            .find(|(_, other)| other.extractor_hash == *video_hash)
-        {
-            assert_eq!(value.extractor_hash, *video_hash);
-            Some(PlaylistIndex(index))
-        } else {
-            None
-        }
-    }
-
-    /// Select a video based on it's index
-    #[must_use]
-    pub fn get(&self, index: PlaylistIndex) -> Option<&Video> {
-        self.videos.get(index.0)
-    }
-
-    /// Returns the number of videos in the playlist
-    #[must_use]
-    pub fn len(&self) -> usize {
-        self.videos.len()
-    }
-    /// Is the playlist empty?
-    #[must_use]
-    pub fn is_empty(&self) -> bool {
-        self.videos.is_empty()
-    }
-}
-
-/// Return the current playlist index.
-///
-/// This effectively looks for the currently focused video and returns it's index.
-///
-/// # Panics
-/// Only if internal assertions fail.
-pub async fn current_playlist_index(app: &App) -> Result<Option<PlaylistIndex>> {
-    if let Some(focused) = currently_focused_video(app).await? {
-        let playlist = playlist(app).await?;
-        let index = playlist
-            .find_index(&focused.extractor_hash)
-            .expect("All focused videos must also be in the playlist");
-        Ok(Some(index))
-    } else {
-        Ok(None)
-    }
-}
-
-/// Return the video in the playlist at the position `index`.
-pub async fn playlist_entry(app: &App, index: PlaylistIndex) -> Result<Option<Video>> {
-    let playlist = playlist(app).await?;
-
-    if let Some(vid) = playlist.get(index) {
-        Ok(Some(vid.to_owned()))
-    } else {
-        Ok(None)
-    }
-}
-
-pub async fn playlist(app: &App) -> Result<Playlist> {
-    let videos = super::videos(app, &[VideoStatusMarker::Cached]).await?;
-
-    Ok(Playlist { videos })
-}
-
-/// This returns the video with the `is_focused` flag set.
-/// # Panics
-/// Only if assertions fail.
-pub async fn currently_focused_video(app: &App) -> Result<Option<Video>> {
-    let cached_status = VideoStatusMarker::Cached.as_db_integer();
-    let record = query!(
-        "SELECT * FROM videos WHERE is_focused = 1 AND status = ?",
-        cached_status
-    )
-    .fetch_one(&app.database)
-    .await;
-
-    if let Err(sqlx::Error::RowNotFound) = record {
-        Ok(None)
-    } else {
-        let base = record?;
-        Ok(Some(video_from_record! {base}))
-    }
-}
diff --git a/crates/yt/src/storage/video_database/set/mod.rs b/crates/yt/src/storage/video_database/set/mod.rs
deleted file mode 100644
index 8c1be4a..0000000
--- a/crates/yt/src/storage/video_database/set/mod.rs
+++ /dev/null
@@ -1,333 +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>.
-
-//! These functions change the database. They are added on a demand basis.
-
-use std::path::{Path, PathBuf};
-
-use anyhow::{Context, Result};
-use chrono::Utc;
-use log::{debug, info};
-use sqlx::query;
-use tokio::fs;
-
-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(
-    app: &App,
-    video_hash: &ExtractorHash,
-    new_status: VideoStatus,
-    new_priority: Option<Priority>,
-) -> Result<()> {
-    let video_hash = video_hash.hash().to_string();
-
-    let old = {
-        let base = query!(
-            r#"
-    SELECT *
-    FROM videos
-    WHERE extractor_hash = ?
-    "#,
-            video_hash
-        )
-        .fetch_one(&app.database)
-        .await?;
-
-        video_from_record! {base}
-    };
-
-    let old_marker = old.status.as_marker();
-    let (cache_path, is_focused) = {
-        fn cache_path_to_string(path: &Path) -> Result<String> {
-            Ok(path
-                .to_str()
-                .with_context(|| {
-                    format!(
-                        "Failed to parse cache path ('{}') as utf8 string",
-                        path.display()
-                    )
-                })?
-                .to_owned())
-        }
-
-        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)
-        }
-    };
-
-    let new_status = new_status.as_marker();
-
-    if let Some(new_priority) = new_priority {
-        if old_marker == new_status && old.priority == new_priority {
-            return Ok(());
-        }
-
-        let now = Utc::now().timestamp();
-
-        debug!(
-            "Running status change: {:#?} -> {:#?}...",
-            old_marker, new_status,
-        );
-
-        let new_status = new_status.as_db_integer();
-        let new_priority = new_priority.as_db_integer();
-        query!(
-            r#"
-        UPDATE videos
-        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)
-        .await?;
-    } else {
-        if old_marker == new_status {
-            return Ok(());
-        }
-
-        let now = Utc::now().timestamp();
-
-        debug!(
-            "Running status change: {:#?} -> {:#?}...",
-            old_marker, new_status,
-        );
-
-        let new_status = new_status.as_db_integer();
-        query!(
-            r#"
-        UPDATE videos
-        SET status = ?, last_status_change = ?, cache_path = ?, is_focused = ?
-        WHERE extractor_hash = ?;
-        "#,
-            new_status,
-            now,
-            cache_path,
-            is_focused,
-            video_hash
-        )
-        .execute(&app.database)
-        .await?;
-    }
-
-    debug!("Finished status change.");
-    Ok(())
-}
-
-/// Mark a video as watched.
-/// This will both set the status to `Watched` and the `cache_path` to Null.
-///
-/// # Panics
-/// Only if assertions fail.
-pub async fn video_watched(app: &App, video: &ExtractorHash) -> Result<()> {
-    let old = {
-        let video_hash = video.hash().to_string();
-
-        let base = query!(
-            r#"
-    SELECT *
-    FROM videos
-    WHERE extractor_hash = ?
-    "#,
-            video_hash
-        )
-        .fetch_one(&app.database)
-        .await?;
-
-        video_from_record! {base}
-    };
-
-    info!("Will set video watched: '{}'", old.title);
-
-    if let VideoStatus::Cached { cache_path, .. } = &old.status {
-        if let Ok(true) = cache_path.try_exists() {
-            fs::remove_file(cache_path).await?;
-        }
-    } else {
-        unreachable!("The video must be marked as Cached before it can be marked Watched");
-    }
-
-    video_status(app, video, VideoStatus::Watched, None).await?;
-
-    Ok(())
-}
-
-pub(crate) async fn video_watch_progress(
-    app: &App,
-    extractor_hash: &ExtractorHash,
-    watch_progress: u32,
-) -> std::result::Result<(), anyhow::Error> {
-    let video_extractor_hash = extractor_hash.hash().to_string();
-
-    query!(
-        r#"
-            UPDATE videos
-            SET watch_progress = ?
-            WHERE extractor_hash = ?;
-        "#,
-        watch_progress,
-        video_extractor_hash,
-    )
-    .execute(&app.database)
-    .await?;
-
-    Ok(())
-}
-
-pub async fn set_video_options(
-    app: &App,
-    hash: &ExtractorHash,
-    video_options: &VideoOptions,
-) -> Result<()> {
-    let video_extractor_hash = hash.hash().to_string();
-    let playback_speed = video_options.mpv.playback_speed;
-    let subtitle_langs = &video_options.yt_dlp.subtitle_langs;
-
-    query!(
-        r#"
-            UPDATE video_options
-            SET playback_speed = ?, subtitle_langs = ?
-            WHERE extractor_hash = ?;
-        "#,
-        playback_speed,
-        subtitle_langs,
-        video_extractor_hash,
-    )
-    .execute(&app.database)
-    .await?;
-
-    Ok(())
-}
-
-/// # Panics
-/// Only if internal expectations fail.
-pub async fn add_video(app: &App, video: Video) -> Result<()> {
-    let parent_subscription_name = video.parent_subscription_name;
-
-    let thumbnail_url = video.thumbnail_url.map(|val| val.to_string());
-
-    let url = video.url.to_string();
-    let extractor_hash = video.extractor_hash.hash().to_string();
-
-    let default_subtitle_langs = &app.config.select.subtitle_langs;
-    let default_mpv_playback_speed = app.config.select.playback_speed;
-
-    let status = video.status.as_marker().as_db_integer();
-    let (cache_path, is_focused) = if let VideoStatus::Cached {
-        cache_path,
-        is_focused,
-    } = video.status
-    {
-        (
-            Some(
-                cache_path
-                    .to_str()
-                    .with_context(|| {
-                        format!(
-                            "Failed to prase cache path '{}' as utf-8 string",
-                            cache_path.display()
-                        )
-                    })?
-                    .to_string(),
-            ),
-            is_focused_to_value(is_focused),
-        )
-    } else {
-        (None, None)
-    };
-
-    let duration: Option<f64> = video.duration.as_secs_f64();
-    let last_status_change: i64 = video.last_status_change.as_secs();
-    let publish_date: Option<i64> = video.publish_date.map(|pd| pd.as_secs());
-    let watch_progress: i64 =
-        i64::try_from(video.watch_progress.as_secs()).expect("This should never exceed a u32");
-
-    let mut tx = app.database.begin().await?;
-    query!(
-        r#"
-        INSERT INTO videos (
-            description,
-            duration,
-            extractor_hash,
-            is_focused,
-            last_status_change,
-            parent_subscription_name,
-            publish_date,
-            status,
-            thumbnail_url,
-            title,
-            url,
-            watch_progress,
-            cache_path
-            )
-        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
-    "#,
-        video.description,
-        duration,
-        extractor_hash,
-        is_focused,
-        last_status_change,
-        parent_subscription_name,
-        publish_date,
-        status,
-        thumbnail_url,
-        video.title,
-        url,
-        watch_progress,
-        cache_path,
-    )
-    .execute(&mut *tx)
-    .await?;
-
-    query!(
-        r#"
-        INSERT INTO video_options (
-            extractor_hash,
-            subtitle_langs,
-            playback_speed)
-        VALUES (?, ?, ?);
-    "#,
-        extractor_hash,
-        default_subtitle_langs,
-        default_mpv_playback_speed
-    )
-    .execute(&mut *tx)
-    .await?;
-
-    tx.commit().await?;
-
-    Ok(())
-}
diff --git a/crates/yt/src/storage/video_database/set/playlist.rs b/crates/yt/src/storage/video_database/set/playlist.rs
deleted file mode 100644
index 547df21..0000000
--- a/crates/yt/src/storage/video_database/set/playlist.rs
+++ /dev/null
@@ -1,101 +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 anyhow::Result;
-use log::debug;
-use sqlx::query;
-
-use crate::{
-    app::App,
-    storage::video_database::{extractor_hash::ExtractorHash, get},
-};
-
-/// Set a video to be focused.
-/// This optionally takes another video hash, which marks the old focused video.
-/// This will then be disabled.
-///
-/// # Panics
-/// Only if internal assertions fail.
-pub async fn focused(
-    app: &App,
-    new_video_hash: &ExtractorHash,
-    old_video_hash: Option<&ExtractorHash>,
-) -> Result<()> {
-    unfocused(app, old_video_hash).await?;
-
-    debug!("Focusing video: '{new_video_hash}'");
-    let new_hash = new_video_hash.hash().to_string();
-    query!(
-        r#"
-            UPDATE videos
-            SET is_focused = 1
-            WHERE extractor_hash = ?;
-        "#,
-        new_hash,
-    )
-    .execute(&app.database)
-    .await?;
-
-    assert_eq!(
-        *new_video_hash,
-        get::currently_focused_video(app)
-            .await?
-            .expect("This is some at this point")
-            .extractor_hash
-    );
-    Ok(())
-}
-
-/// 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: 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 = NULL
-            WHERE extractor_hash = ?;
-        "#,
-        hash
-    )
-    .execute(&app.database)
-    .await?;
-
-    assert!(
-        get::currently_focused_video(app).await?.is_none(),
-        "We assumed that the video we just removed was actually a focused one."
-    );
-    Ok(())
-}
diff --git a/crates/yt/src/subscribe/mod.rs b/crates/yt/src/subscribe/mod.rs
deleted file mode 100644
index a965ac0..0000000
--- a/crates/yt/src/subscribe/mod.rs
+++ /dev/null
@@ -1,184 +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 std::str::FromStr;
-
-use anyhow::{Context, Result, bail};
-use futures::FutureExt;
-use log::warn;
-use tokio::io::{AsyncBufRead, AsyncBufReadExt};
-use url::Url;
-use yt_dlp::{json_get, options::YoutubeDLOptions};
-
-use crate::{
-    app::App,
-    storage::subscriptions::{
-        Subscription, add_subscription, check_url, get, remove_all, remove_subscription,
-    },
-    unreachable::Unreachable,
-};
-
-pub async fn unsubscribe(app: &App, name: String) -> Result<()> {
-    let present_subscriptions = get(app).await?;
-
-    if let Some(subscription) = present_subscriptions.0.get(&name) {
-        remove_subscription(app, subscription).await?;
-    } else {
-        bail!("Couldn't find subscription: '{}'", &name);
-    }
-
-    Ok(())
-}
-
-pub async fn import<W: AsyncBufRead + AsyncBufReadExt + Unpin>(
-    app: &App,
-    reader: W,
-    force: bool,
-) -> Result<()> {
-    if force {
-        remove_all(app).await?;
-    }
-
-    let mut lines = reader.lines();
-    while let Some(line) = lines.next_line().await? {
-        let url =
-            Url::from_str(&line).with_context(|| format!("Failed to parse '{line}' as url"))?;
-        match subscribe(app, None, url)
-            .await
-            .with_context(|| format!("Failed to subscribe to: '{line}'"))
-        {
-            Ok(()) => (),
-            Err(err) => eprintln!(
-                "Error while subscribing to '{}': '{}'",
-                line,
-                err.source().unreachable("Should have a source")
-            ),
-        }
-    }
-
-    Ok(())
-}
-
-pub async fn subscribe(app: &App, name: Option<String>, url: Url) -> Result<()> {
-    if !(url.as_str().ends_with("videos")
-        || url.as_str().ends_with("streams")
-        || url.as_str().ends_with("shorts")
-        || url.as_str().ends_with("videos/")
-        || url.as_str().ends_with("streams/")
-        || url.as_str().ends_with("shorts/"))
-        && url.as_str().contains("youtube.com")
-    {
-        warn!(
-            "Your youtbe url does not seem like it actually tracks a channels playlist (videos, streams, shorts). Adding subscriptions for each of them..."
-        );
-
-        let url = Url::parse(&(url.as_str().to_owned() + "/"))
-            .unreachable("This was an url, it should stay one");
-
-        if let Some(name) = name {
-            let out: Result<()> = async move {
-                actual_subscribe(
-                    app,
-                    Some(name.clone() + " {Videos}"),
-                    url.join("videos/")
-                        .unreachable("The url should allow being joined onto"),
-                )
-                .await
-                .with_context(|| {
-                    format!("Failed to subscribe to '{}'", name.clone() + " {Videos}")
-                })?;
-
-                actual_subscribe(
-                    app,
-                    Some(name.clone() + " {Streams}"),
-                    url.join("streams/").unreachable("See above."),
-                )
-                .await
-                .with_context(|| {
-                    format!("Failed to subscribe to '{}'", name.clone() + " {Streams}")
-                })?;
-
-                actual_subscribe(
-                    app,
-                    Some(name.clone() + " {Shorts}"),
-                    url.join("shorts/").unreachable("See above."),
-                )
-                .await
-                .with_context(|| format!("Failed to subscribe to '{}'", name + " {Shorts}"))?;
-
-                Ok(())
-            }
-            .boxed()
-            .await;
-
-            out?;
-        } else {
-            actual_subscribe(app, None, url.join("videos/").unreachable("See above."))
-                .await
-                .with_context(|| format!("Failed to subscribe to the '{}' variant", "{Videos}"))?;
-
-            actual_subscribe(app, None, url.join("streams/").unreachable("See above."))
-                .await
-                .with_context(|| format!("Failed to subscribe to the '{}' variant", "{Streams}"))?;
-
-            actual_subscribe(app, None, url.join("shorts/").unreachable("See above."))
-                .await
-                .with_context(|| format!("Failed to subscribe to the '{}' variant", "{Shorts}"))?;
-        }
-    } else {
-        actual_subscribe(app, name, url).await?;
-    }
-
-    Ok(())
-}
-
-async fn actual_subscribe(app: &App, name: Option<String>, url: Url) -> Result<()> {
-    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 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)
-        }
-    };
-
-    let present_subscriptions = get(app).await?;
-
-    if let Some(subs) = present_subscriptions.0.get(&name) {
-        bail!(
-            "The subscription '{}' could not be added, \
-                as another one with the same name ('{}') already exists. It links to the Url: '{}'",
-            name,
-            name,
-            subs.url
-        );
-    }
-
-    let sub = Subscription { name, url };
-
-    add_subscription(app, &sub).await?;
-
-    Ok(())
-}
diff --git a/crates/yt/src/unreachable.rs b/crates/yt/src/unreachable.rs
deleted file mode 100644
index 436fbb6..0000000
--- a/crates/yt/src/unreachable.rs
+++ /dev/null
@@ -1,50 +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>.
-
-// This has been taken from: https://gitlab.torproject.org/tpo/core/arti/-/issues/950
-
-// The functions here should be annotated with `#[inline(always)]`.
-#![allow(clippy::inline_always)]
-
-use std::fmt::Debug;
-
-/// Trait for something that can possibly be unwrapped, like a Result or Option.
-/// It provides semantic information, that unwrapping here should never happen.
-pub trait Unreachable<T> {
-    /// Like `expect()`, but does not trigger clippy.
-    ///
-    /// # Usage
-    ///
-    /// This method only exists so that we can use it without hitting
-    /// `clippy::missing_panics_docs`.  Therefore, we should only use it
-    /// for situations where we are certain that the panic cannot occur
-    /// unless something else is very broken.  Consider instead using
-    /// `expect()` and adding a `Panics` section to your function
-    /// documentation.
-    ///
-    /// # Panics
-    ///
-    /// Panics if this is not an object that can be unwrapped, such as
-    /// None or  an Err.
-    fn unreachable(self, msg: &str) -> T;
-}
-impl<T> Unreachable<T> for Option<T> {
-    #[inline(always)]
-    fn unreachable(self, msg: &str) -> T {
-        self.expect(msg)
-    }
-}
-impl<T, E: Debug> Unreachable<T> for Result<T, E> {
-    #[inline(always)]
-    fn unreachable(self, msg: &str) -> T {
-        self.expect(msg)
-    }
-}
diff --git a/crates/yt/src/update/mod.rs b/crates/yt/src/update/mod.rs
deleted file mode 100644
index 7f9bee7..0000000
--- a/crates/yt/src/update/mod.rs
+++ /dev/null
@@ -1,204 +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 std::{str::FromStr, time::Duration};
-
-use anyhow::{Context, Ok, Result};
-use chrono::{DateTime, Utc};
-use log::warn;
-use url::Url;
-use yt_dlp::{info_json::InfoJson, json_cast, json_get};
-
-use crate::{
-    app::App,
-    select::selection_file::duration::MaybeDuration,
-    storage::{
-        subscriptions::{self, Subscription},
-        video_database::{
-            Priority, TimeStamp, Video, VideoStatus, extractor_hash::ExtractorHash,
-            get::get_all_hashes, set::add_video,
-        },
-    },
-};
-
-mod updater;
-use updater::Updater;
-
-pub async fn update(
-    app: &App,
-    max_backlog: usize,
-    subscription_names_to_update: Vec<String>,
-    total_number: Option<usize>,
-    current_progress: Option<usize>,
-) -> Result<()> {
-    let subscriptions = subscriptions::get(app).await?;
-
-    let subs: Vec<Subscription> = if subscription_names_to_update.is_empty() {
-        subscriptions.0.into_values().collect()
-    } else {
-        subscriptions
-            .0
-            .into_values()
-            .filter(|sub| subscription_names_to_update.contains(&sub.name))
-            .collect()
-    };
-
-    // We can get away with not having to re-fetch the hashes every time, as the returned video
-    // should not contain duplicates.
-    let hashes = get_all_hashes(app).await?;
-
-    let updater = Updater::new(max_backlog, hashes);
-    updater
-        .update(app, subs, total_number, current_progress)
-        .await?;
-
-    Ok(())
-}
-
-#[allow(clippy::too_many_lines)]
-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}'). \
-                    Expected `YYYY-MM-DD`, has the format changed?"
-        );
-        if let Some(date_string) = extended {
-            format!("{f}\nThe parsed '{date_string}' can't be turned to a valid UTC date.'")
-        } else {
-            f
-        }
-    }
-
-    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)
-            .collect::<String>()
-            .parse()
-            .with_context(|| fmt_context(date, None))?;
-        let month: u32 = date
-            .chars()
-            .skip(4)
-            .take(2)
-            .collect::<String>()
-            .parse()
-            .with_context(|| fmt_context(date, None))?;
-        let day: u32 = date
-            .chars()
-            .skip(4 + 2)
-            .take(2)
-            .collect::<String>()
-            .parse()
-            .with_context(|| fmt_context(date, None))?;
-
-        let date_string = format!("{year:04}-{month:02}-{day:02}T00:00:00Z");
-        Some(
-            DateTime::<Utc>::from_str(&date_string)
-                .with_context(|| fmt_context(date, Some(&date_string)))?
-                .timestamp(),
-        )
-    } else {
-        warn!(
-            "The video '{}' lacks it's upload date!",
-            json_get!(entry, "title", as_str)
-        );
-        None
-    };
-
-    let thumbnail_url = match (&entry.get("thumbnails"), &entry.get("thumbnail")) {
-        (None, None) => None,
-        (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) => {
-            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 = json_get!(entry, "webpage_url", as_str).parse()?;
-        // TODO(@bpeetz): We should probably add this? <2025-06-14>
-        // if '#__youtubedl_smuggle' not in smug_url:
-        //     return smug_url, default
-        // url, _, sdata = smug_url.rpartition('#')
-        // jsond = urllib.parse.parse_qs(sdata)['__youtubedl_smuggle'][0]
-        // data = json.loads(jsond)
-        // return url, data
-
-        smug_url
-    };
-
-    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.get("uploader").map(|val| json_cast!(val, as_str)) {
-        if entry
-            .get("webpage_url_domain")
-            .map(|val| json_cast!(val, as_str))
-            == Some("youtube.com")
-        {
-            Some(format!("{uploader} - Videos"))
-        } else {
-            Some(uploader.to_owned())
-        }
-    } else {
-        None
-    };
-
-    let video = Video {
-        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,
-        priority: Priority::default(),
-        publish_date: publish_date.map(TimeStamp::from_secs),
-        status: VideoStatus::Pick,
-        thumbnail_url,
-        title: json_get!(entry, "title", as_str).to_owned(),
-        url,
-        watch_progress: Duration::default(),
-    };
-    Ok(video)
-}
-
-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")?;
-
-    add_video(app, video.clone())
-        .await
-        .with_context(|| format!("Failed to add video to database: '{}'", video.title))?;
-    println!(
-        "{}",
-        &video
-            .to_line_display(app)
-            .await
-            .with_context(|| format!("Failed to format video: '{}'", video.title))?
-    );
-    Ok(())
-}
diff --git a/crates/yt/src/version/mod.rs b/crates/yt/src/version/mod.rs
index 95660c0..b12eadd 100644
--- a/crates/yt/src/version/mod.rs
+++ b/crates/yt/src/version/mod.rs
@@ -14,7 +14,7 @@ use yt_dlp::options::YoutubeDLOptions;
 
 use crate::{config::Config, storage::migrate::get_version_db};
 
-pub async fn show(config: &Config) -> Result<()> {
+pub(crate) async fn show(config: &Config) -> Result<()> {
     let db_version = {
         let options = SqliteConnectOptions::new()
             .filename(&config.paths.database_path)
@@ -30,17 +30,20 @@ pub async fn show(config: &Config) -> Result<()> {
             .context("Failed to determine database version")?
     };
 
-    let yt_dlp_version = {
+    let (yt_dlp, python) = {
         let yt_dlp = YoutubeDLOptions::new().build()?;
-        yt_dlp.version()
+        yt_dlp.version()?
     };
 
+    let python = python.replace('\n', " ");
+
     println!(
         "{}: {}
 
 db version: {db_version}
 
-yt-dlp: {yt_dlp_version}",
+yt-dlp: {yt_dlp}
+python: {python}",
         env!("CARGO_PKG_NAME"),
         env!("CARGO_PKG_VERSION"),
     );
diff --git a/crates/yt/src/videos/display/format_video.rs b/crates/yt/src/videos/display/format_video.rs
deleted file mode 100644
index b97acb1..0000000
--- a/crates/yt/src/videos/display/format_video.rs
+++ /dev/null
@@ -1,94 +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 anyhow::Result;
-
-use crate::{app::App, comments::output::format_text, storage::video_database::Video};
-
-impl Video {
-    pub async fn to_info_display(&self, app: &App) -> Result<String> {
-        let cache_path = self.cache_path_fmt(app);
-        let description = self.description_fmt();
-        let duration = self.duration_fmt(app);
-        let extractor_hash = self.extractor_hash_fmt(app).await?;
-        let in_playlist = self.in_playlist_fmt(app);
-        let last_status_change = self.last_status_change_fmt(app);
-        let parent_subscription_name = self.parent_subscription_name_fmt(app);
-        let priority = self.priority_fmt();
-        let publish_date = self.publish_date_fmt(app);
-        let status = self.status_fmt(app);
-        let thumbnail_url = self.thumbnail_url_fmt();
-        let title = self.title_fmt(app);
-        let url = self.url_fmt(app);
-        let watch_progress = self.watch_progress_fmt(app);
-        let video_options = self.video_options_fmt(app).await?;
-
-        let watched_percentage_fmt = {
-            if let Some(duration) = self.duration.as_secs() {
-                format!(
-                    " (watched: {:0.0}%)",
-                    (self.watch_progress.as_secs() / duration) * 100
-                )
-            } else {
-                format!(" {watch_progress}")
-            }
-        };
-
-        let string = format!(
-            "\
-{title} ({extractor_hash})
-| -> {cache_path}
-| -> {duration}{watched_percentage_fmt}
-| -> {parent_subscription_name}
-| -> priority: {priority}
-| -> {publish_date}
-| -> status: {status} since {last_status_change} ({in_playlist})
-| -> {thumbnail_url}
-| -> {url}
-| -> options: {}
-{}\n",
-            video_options.to_string().trim(),
-            format_text(description.to_string().as_str())
-        );
-        Ok(string)
-    }
-
-    pub async fn to_line_display(&self, app: &App) -> Result<String> {
-        let f = format!(
-            "{} {} {} {} {} {}",
-            self.status_fmt(app),
-            self.extractor_hash_fmt(app).await?,
-            self.title_fmt(app),
-            self.publish_date_fmt(app),
-            self.parent_subscription_name_fmt(app),
-            self.duration_fmt(app)
-        );
-
-        Ok(f)
-    }
-
-    pub async fn to_select_file_display(&self, app: &App) -> Result<String> {
-        let f = format!(
-            r#"{}{} {} "{}" "{}" "{}" "{}" "{}"{}"#,
-            self.status_fmt_no_color(),
-            self.video_options_fmt_no_color(app).await?,
-            self.extractor_hash_fmt_no_color(app).await?,
-            self.title_fmt_no_color(),
-            self.publish_date_fmt_no_color(),
-            self.parent_subscription_name_fmt_no_color(),
-            self.duration_fmt_no_color(),
-            self.url_fmt_no_color(),
-            '\n'
-        );
-
-        Ok(f)
-    }
-}
diff --git a/crates/yt/src/videos/display/mod.rs b/crates/yt/src/videos/display/mod.rs
deleted file mode 100644
index 1188569..0000000
--- a/crates/yt/src/videos/display/mod.rs
+++ /dev/null
@@ -1,229 +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 owo_colors::OwoColorize;
-use url::Url;
-
-use crate::{
-    app::App,
-    select::selection_file::duration::MaybeDuration,
-    storage::video_database::{TimeStamp, Video, VideoStatus, get::get_video_opts},
-};
-
-use anyhow::{Context, Result};
-
-pub mod format_video;
-
-macro_rules! get {
-    ($value:expr, $key:ident, $name:expr, $code:tt) => {
-        if let Some(value) = &$value.$key {
-            $code(value)
-        } else {
-            concat!("[No ", $name, "]").to_owned()
-        }
-    };
-}
-
-fn maybe_add_color<F>(app: &App, input: String, mut color_fn: F) -> String
-where
-    F: FnMut(String) -> String,
-{
-    if app.config.global.display_colors {
-        color_fn(input)
-    } else {
-        input
-    }
-}
-impl Video {
-    #[must_use]
-    pub fn cache_path_fmt(&self, app: &App) -> String {
-        let cache_path = if let VideoStatus::Cached {
-            cache_path,
-            is_focused: _,
-        } = &self.status
-        {
-            cache_path.to_string_lossy().to_string()
-        } else {
-            "[No Cache Path]".to_owned()
-        };
-        maybe_add_color(app, cache_path, |v| v.blue().bold().to_string())
-    }
-
-    #[must_use]
-    pub fn description_fmt(&self) -> String {
-        get!(
-            self,
-            description,
-            "Description",
-            (|value: &str| value.to_owned())
-        )
-    }
-
-    #[must_use]
-    pub fn duration_fmt_no_color(&self) -> String {
-        self.duration.to_string()
-    }
-    #[must_use]
-    pub fn duration_fmt(&self, app: &App) -> String {
-        let duration = self.duration_fmt_no_color();
-        maybe_add_color(app, duration, |v| v.cyan().bold().to_string())
-    }
-
-    #[must_use]
-    pub fn watch_progress_fmt(&self, app: &App) -> String {
-        maybe_add_color(
-            app,
-            MaybeDuration::from_std(self.watch_progress).to_string(),
-            |v| v.cyan().bold().to_string(),
-        )
-    }
-
-    pub async fn extractor_hash_fmt_no_color(&self, app: &App) -> Result<String> {
-        let hash = self
-            .extractor_hash
-            .into_short_hash(app)
-            .await
-            .with_context(|| {
-                format!(
-                    "Failed to format extractor hash, whilst formatting video: '{}'",
-                    self.title
-                )
-            })?
-            .to_string();
-        Ok(hash)
-    }
-    pub async fn extractor_hash_fmt(&self, app: &App) -> Result<String> {
-        let hash = self.extractor_hash_fmt_no_color(app).await?;
-        Ok(maybe_add_color(app, hash, |v| {
-            v.bright_purple().italic().to_string()
-        }))
-    }
-
-    #[must_use]
-    pub fn in_playlist_fmt(&self, app: &App) -> String {
-        let output = match &self.status {
-            VideoStatus::Pick
-            | VideoStatus::Watch
-            | VideoStatus::Watched
-            | VideoStatus::Drop
-            | VideoStatus::Dropped => "Not in the playlist",
-            VideoStatus::Cached { is_focused, .. } => {
-                if *is_focused {
-                    "In the playlist and focused"
-                } else {
-                    "In the playlist"
-                }
-            }
-        };
-        maybe_add_color(app, output.to_owned(), |v| v.yellow().italic().to_string())
-    }
-    #[must_use]
-    pub fn last_status_change_fmt(&self, app: &App) -> String {
-        maybe_add_color(app, self.last_status_change.to_string(), |v| {
-            v.bright_cyan().to_string()
-        })
-    }
-
-    #[must_use]
-    pub fn parent_subscription_name_fmt_no_color(&self) -> String {
-        get!(
-            self,
-            parent_subscription_name,
-            "author",
-            (|sub: &str| sub.replace('"', "'"))
-        )
-    }
-    #[must_use]
-    pub fn parent_subscription_name_fmt(&self, app: &App) -> String {
-        let psn = self.parent_subscription_name_fmt_no_color();
-        maybe_add_color(app, psn, |v| v.bright_magenta().to_string())
-    }
-
-    #[must_use]
-    pub fn priority_fmt(&self) -> String {
-        self.priority.to_string()
-    }
-
-    #[must_use]
-    pub fn publish_date_fmt_no_color(&self) -> String {
-        get!(
-            self,
-            publish_date,
-            "release date",
-            (|date: &TimeStamp| date.to_string())
-        )
-    }
-    #[must_use]
-    pub fn publish_date_fmt(&self, app: &App) -> String {
-        let date = self.publish_date_fmt_no_color();
-        maybe_add_color(app, date, |v| v.bright_white().bold().to_string())
-    }
-
-    #[must_use]
-    pub fn status_fmt_no_color(&self) -> String {
-        // TODO: We might support `.trim()`ing that, as the extra whitespace could be bad in the
-        // selection file. <2024-10-07>
-        self.status.as_marker().as_command().to_string()
-    }
-    #[must_use]
-    pub fn status_fmt(&self, app: &App) -> String {
-        let status = self.status_fmt_no_color();
-        maybe_add_color(app, status, |v| v.red().bold().to_string())
-    }
-
-    #[must_use]
-    pub fn thumbnail_url_fmt(&self) -> String {
-        get!(
-            self,
-            thumbnail_url,
-            "thumbnail URL",
-            (|url: &Url| url.to_string())
-        )
-    }
-
-    #[must_use]
-    pub fn title_fmt_no_color(&self) -> String {
-        self.title.replace(['"', '„', '”', '“'], "'")
-    }
-    #[must_use]
-    pub fn title_fmt(&self, app: &App) -> String {
-        let title = self.title_fmt_no_color();
-        maybe_add_color(app, title, |v| v.green().bold().to_string())
-    }
-
-    #[must_use]
-    pub fn url_fmt_no_color(&self) -> String {
-        self.url.as_str().replace('"', "\\\"")
-    }
-    #[must_use]
-    pub fn url_fmt(&self, app: &App) -> String {
-        let url = self.url_fmt_no_color();
-        maybe_add_color(app, url, |v| v.italic().to_string())
-    }
-
-    pub async fn video_options_fmt_no_color(&self, app: &App) -> Result<String> {
-        let video_options = {
-            let opts = get_video_opts(app, &self.extractor_hash)
-                .await
-                .with_context(|| {
-                    format!("Failed to get video options for video: '{}'", self.title)
-                })?
-                .to_cli_flags(app);
-            let opts_white = if opts.is_empty() { "" } else { " " };
-            format!("{opts_white}{opts}")
-        };
-        Ok(video_options)
-    }
-    pub async fn video_options_fmt(&self, app: &App) -> Result<String> {
-        let opts = self.video_options_fmt_no_color(app).await?;
-        Ok(maybe_add_color(app, opts, |v| v.bright_green().to_string()))
-    }
-}
diff --git a/crates/yt/src/videos/format_video.rs b/crates/yt/src/videos/format_video.rs
new file mode 100644
index 0000000..6598780
--- /dev/null
+++ b/crates/yt/src/videos/format_video.rs
@@ -0,0 +1,133 @@
+// 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::Result;
+use colors::Colorize;
+
+use crate::{app::App, output::format_text, storage::db::video::Video, videos::RenderWithApp};
+
+impl Video {
+    pub(crate) async fn to_info_display(
+        &self,
+        app: &App,
+        format: Option<String>,
+    ) -> Result<String> {
+        let cache_path = self.cache_path_fmt().to_string(app);
+        let description = self.description_fmt().to_string(app);
+        let duration = self.duration_fmt().to_string(app);
+        let extractor_hash = self.extractor_hash_fmt(app).await?.to_string(app);
+        let in_playlist = self.in_playlist_fmt().to_string(app);
+        let last_status_change = self.last_status_change_fmt().to_string(app);
+        let parent_subscription_name = self.parent_subscription_name_fmt().to_string(app);
+        let priority = self.priority_fmt().to_string(app);
+        let publish_date = self.publish_date_fmt().to_string(app);
+        let status = self.status_fmt().to_string(app);
+        let thumbnail_url = self.thumbnail_url_fmt().to_string(app);
+        let title = self.title_fmt().to_string(app);
+        let url = self.url_fmt().to_string(app);
+        let video_options = self.video_options_fmt(app).to_string(app);
+
+        let watched_percentage_fmt = {
+            if let Some(percent) = self.watch_progress_percent_fmt() {
+                format!(" (watched: {})", percent.to_string(app))
+            } else {
+                format!(" {}", self.watch_progress_fmt().to_string(app))
+            }
+        };
+
+        let options = video_options.to_string();
+        let options = options.trim();
+        let description = format_text(description.to_string().as_str(), None);
+
+        let string = if let Some(format) = format {
+            format
+                .replace("{title}", &title)
+                .replace("{extractor_hash}", &extractor_hash)
+                .replace("{cache_path}", &cache_path)
+                .replace("{duration}", &duration)
+                .replace("{watched_percentage_fmt}", &watched_percentage_fmt)
+                .replace("{parent_subscription_name}", &parent_subscription_name)
+                .replace("{priority}", &priority)
+                .replace("{publish_date}", &publish_date)
+                .replace("{status}", &status)
+                .replace("{last_status_change}", &last_status_change)
+                .replace("{in_playlist}", &in_playlist)
+                .replace("{thumbnail_url}", &thumbnail_url)
+                .replace("{url}", &url)
+                .replace("{options}", options)
+                .replace("{description}", &description)
+        } else {
+            format!(
+                "\
+{title} ({extractor_hash})
+| -> {cache_path}
+| -> {duration}{watched_percentage_fmt}
+| -> {parent_subscription_name}
+| -> priority: {priority}
+| -> {publish_date}
+| -> status: {status} since {last_status_change} ({in_playlist})
+| -> {thumbnail_url}
+| -> {url}
+| -> options: {options}
+{description}\n",
+            )
+        };
+        Ok(string)
+    }
+
+    pub(crate) async fn to_line_display(
+        &self,
+        app: &App,
+        format: Option<String>,
+    ) -> Result<String> {
+        let status = self.status_fmt().to_string(app);
+        let extractor_hash = self.extractor_hash_fmt(app).await?.to_string(app);
+        let title = self.title_fmt().to_string(app);
+        let publish_date = self.publish_date_fmt().to_string(app);
+        let parent_subscription_name = self.parent_subscription_name_fmt().to_string(app);
+        let duration = self.duration_fmt().to_string(app);
+        let url = self.url_fmt().to_string(app);
+
+        let f = if let Some(format) = format {
+            format
+                .replace("{status}", &status)
+                .replace("{extractor_hash}", &extractor_hash)
+                .replace("{title}", &title)
+                .replace("{publish_date}", &publish_date)
+                .replace("{parent_subscription_name}", &parent_subscription_name)
+                .replace("{duration}", &duration)
+                .replace("{url}", &url)
+        } else {
+            format!(
+                "{status} {extractor_hash} {title} {publish_date} {parent_subscription_name} {duration}"
+            )
+        };
+
+        Ok(f)
+    }
+
+    pub(crate) async fn to_select_file_display(&self, app: &App) -> Result<String> {
+        let f = format!(
+            r#"{}{} {} "{}" "{}" "{}" "{}" "{}"{}"#,
+            self.status_fmt().render(false),
+            self.video_options_fmt(app).render(false),
+            self.extractor_hash_fmt(app).await?.render(false),
+            self.title_fmt().render(false),
+            self.publish_date_fmt().render(false),
+            self.parent_subscription_name_fmt().render(false),
+            self.duration_fmt().render(false),
+            self.url_fmt().render(false),
+            '\n'
+        );
+
+        Ok(f)
+    }
+}
diff --git a/crates/yt/src/videos/mod.rs b/crates/yt/src/videos/mod.rs
index 960340b..c2f01fa 100644
--- a/crates/yt/src/videos/mod.rs
+++ b/crates/yt/src/videos/mod.rs
@@ -9,46 +9,205 @@
 // 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::Result;
-use futures::{TryStreamExt, stream::FuturesUnordered};
+use std::fmt::Write;
 
-pub mod display;
+use anyhow::{Context, Result};
+use colors::{Colorize, IntoCanvas};
+use url::Url;
 
 use crate::{
     app::App,
-    storage::video_database::{Video, VideoStatusMarker, get},
+    select::duration::MaybeDuration,
+    storage::db::video::{TimeStamp, Video, VideoStatus},
 };
 
-async fn to_line_display_owned(video: Video, app: &App) -> Result<String> {
-    video.to_line_display(app).await
+pub(crate) mod format_video;
+
+macro_rules! get {
+    ($value:expr, $key:ident, $name:expr, $code:tt) => {
+        if let Some(value) = &$value.$key {
+            $code(value)
+        } else {
+            concat!("[No ", $name, "]").to_owned()
+        }
+    };
 }
 
-pub async fn query(app: &App, limit: Option<usize>, search_query: Option<String>) -> Result<()> {
-    let all_videos = get::videos(app, VideoStatusMarker::ALL).await?;
+pub(crate) trait RenderWithApp: Colorize {
+    fn to_string(self, app: &App) -> String {
+        self.render(app.config.global.display_colors)
+    }
+}
+impl<C: Colorize> RenderWithApp for C {}
+
+impl Video {
+    #[must_use]
+    pub(crate) fn cache_path_fmt(&self) -> impl Colorize {
+        let cache_path = if let VideoStatus::Cached {
+            cache_path,
+            is_focused: _,
+        } = &self.status
+        {
+            cache_path.to_string_lossy().to_string()
+        } else {
+            "[No Cache Path]".to_owned()
+        };
+
+        cache_path.blue().bold()
+    }
+
+    #[must_use]
+    pub(crate) fn description_fmt(&self) -> impl Colorize {
+        get!(
+            self,
+            description,
+            "Description",
+            (|value: &str| value.to_owned())
+        )
+        .into_canvas()
+    }
+
+    #[must_use]
+    pub(crate) fn duration_fmt(&self) -> impl Colorize {
+        self.duration.cyan().bold()
+    }
+
+    #[must_use]
+    pub(crate) fn watch_progress_fmt(&self) -> impl Colorize {
+        MaybeDuration::from_std(self.watch_progress).cyan().bold()
+    }
+    #[must_use]
+    pub(crate) fn watch_progress_percent_fmt(&self) -> Option<impl Colorize> {
+        self.duration.as_secs_f64().map(|duration| {
+            let watch_progress = self.watch_progress.as_secs_f64();
+
+            (format!("{:0.0}%", (watch_progress / duration) * 100.0)).into_canvas()
+        })
+    }
+
+    pub(crate) async fn extractor_hash_fmt(&self, app: &App) -> Result<impl Colorize> {
+        let hash = self
+            .extractor_hash
+            .as_short_hash(app)
+            .await
+            .with_context(|| {
+                format!(
+                    "Failed to format extractor hash, whilst formatting video: '{}'",
+                    self.title
+                )
+            })?;
 
-    // turn one video to a color display, to pre-warm the hash shrinking cache
-    if let Some(val) = all_videos.first() {
-        val.to_line_display(app).await?;
+        Ok(hash.purple().bold().italic())
     }
 
-    let limit = limit.unwrap_or(all_videos.len());
+    #[must_use]
+    pub(crate) fn in_playlist_fmt(&self) -> impl Colorize {
+        let output = match &self.status {
+            VideoStatus::Pick
+            | VideoStatus::Watch
+            | VideoStatus::Watched
+            | VideoStatus::Drop
+            | VideoStatus::Dropped => "Not in the playlist",
+            VideoStatus::Cached { is_focused, .. } => {
+                if *is_focused {
+                    "In the playlist and focused"
+                } else {
+                    "In the playlist"
+                }
+            }
+        };
+        output.yellow().italic()
+    }
+    #[must_use]
+    pub(crate) fn last_status_change_fmt(&self) -> impl Colorize {
+        self.last_status_change.bright_cyan()
+    }
 
-    let all_video_strings: Vec<String> = all_videos
-        .into_iter()
-        .take(limit)
-        .map(|vid| to_line_display_owned(vid, app))
-        .collect::<FuturesUnordered<_>>()
-        .try_collect::<Vec<String>>()
-        .await?;
+    #[must_use]
+    pub(crate) fn parent_subscription_name_fmt(&self) -> impl Colorize {
+        let psn = get!(
+            self,
+            parent_subscription_name,
+            "author",
+            (|sub: &str| sub.replace('"', "'"))
+        );
 
-    if let Some(query) = search_query {
-        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"));
+        psn.bright_magenta()
     }
 
-    Ok(())
+    #[must_use]
+    pub(crate) fn priority_fmt(&self) -> impl Colorize {
+        self.priority.into_canvas()
+    }
+
+    #[must_use]
+    pub(crate) fn publish_date_fmt(&self) -> impl Colorize {
+        let date = get!(
+            self,
+            publish_date,
+            "release date",
+            (|date: &TimeStamp| date.to_string())
+        );
+
+        date.bright_white().bold()
+    }
+
+    #[must_use]
+    pub(crate) fn status_fmt(&self) -> impl Colorize {
+        // TODO: We might support `.trim()`ing that, as the extra whitespace could be bad in the
+        // selection file. <2024-10-07>
+        let status = self.status.as_marker().as_command().to_owned();
+
+        status.red().bold()
+    }
+
+    #[must_use]
+    pub(crate) fn thumbnail_url_fmt(&self) -> impl Colorize {
+        get!(
+            self,
+            thumbnail_url,
+            "thumbnail URL",
+            (|url: &Url| url.to_string())
+        )
+        .into_canvas()
+    }
+
+    #[must_use]
+    pub(crate) fn title_fmt(&self) -> impl Colorize {
+        let title = self.title.replace(['"', '„', '”', '“'], "'");
+
+        title.green().bold()
+    }
+
+    #[must_use]
+    pub(crate) fn url_fmt(&self) -> impl Colorize {
+        let url = self.url.as_str().replace('"', "\\\"");
+
+        url.italic()
+    }
+
+    pub(crate) fn video_options_fmt(&self, app: &App) -> impl Colorize {
+        let video_options = {
+            let mut opts = String::new();
+
+            if let Some(playback_speed) = self.playback_speed {
+                if (playback_speed - app.config.select.playback_speed).abs() > f64::EPSILON {
+                    write!(opts, " --playback-speed '{}'", playback_speed).expect("In-memory");
+                }
+            }
+
+            if let Some(subtitle_langs) = &self.subtitle_langs {
+                if subtitle_langs != &app.config.select.subtitle_langs {
+                    write!(opts, " --subtitle-langs '{}'", subtitle_langs).expect("In-memory");
+                }
+            }
+
+            let opts = opts.trim().to_owned();
+
+            let opts_white = if opts.is_empty() { "" } else { " " };
+            format!("{opts_white}{opts}")
+        };
+
+        video_options.bright_green()
+    }
 }
diff --git a/crates/yt/src/watch/mod.rs b/crates/yt/src/watch/mod.rs
deleted file mode 100644
index c32a76f..0000000
--- a/crates/yt/src/watch/mod.rs
+++ /dev/null
@@ -1,178 +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 std::{
-    sync::{
-        Arc,
-        atomic::{AtomicBool, Ordering},
-    },
-    time::Duration,
-};
-
-use anyhow::{Context, Result};
-use libmpv2::{Mpv, events::EventContext};
-use log::{debug, info, trace, warn};
-use playlist_handler::{reload_mpv_playlist, save_watch_progress};
-use tokio::{task, time::sleep};
-
-use self::playlist_handler::Status;
-use crate::{
-    app::App,
-    cache::maintain,
-    storage::video_database::{get, notify::wait_for_db_write},
-};
-
-pub mod playlist;
-pub mod playlist_handler;
-
-fn init_mpv(app: &App) -> Result<(Mpv, EventContext)> {
-    // set some default values, to make things easier (these can be overridden by the config file,
-    // which we load later)
-    let mpv = Mpv::with_initializer(|mpv| {
-        // Enable default key bindings, so the user can actually interact with
-        // the player (and e.g. close the window).
-        mpv.set_property("input-default-bindings", "yes")?;
-        mpv.set_property("input-vo-keyboard", "yes")?;
-
-        // Show the on screen controller.
-        mpv.set_property("osc", "yes")?;
-
-        // Don't automatically advance to the next video (or exit the player)
-        mpv.set_option("keep-open", "always")?;
-
-        // Always display an window, even for non-video playback.
-        // As mpv does not have cli access, no window means no control and no user feedback.
-        mpv.set_option("force-window", "yes")?;
-        Ok(())
-    })
-    .context("Failed to initialize mpv")?;
-
-    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")?],
-        )?;
-    } else {
-        warn!(
-            "Did not find a mpv.conf file at '{}'",
-            config_path.display()
-        );
-    }
-
-    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")?],
-        )?;
-    } else {
-        warn!(
-            "Did not find a mpv.input.conf file at '{}'",
-            input_path.display()
-        );
-    }
-
-    let ev_ctx = EventContext::new(mpv.ctx);
-    ev_ctx.disable_deprecated_events()?;
-
-    Ok((mpv, ev_ctx))
-}
-
-pub async fn watch(app: Arc<App>) -> Result<()> {
-    maintain(&app, false).await?;
-
-    let (mpv, mut ev_ctx) = init_mpv(&app).context("Failed to initialize mpv instance")?;
-    let mpv = Arc::new(mpv);
-    reload_mpv_playlist(&app, &mpv, None, None).await?;
-
-    let should_break = Arc::new(AtomicBool::new(false));
-
-    let local_app = Arc::clone(&app);
-    let local_mpv = Arc::clone(&mpv);
-    let local_should_break = Arc::clone(&should_break);
-    let progress_handle = task::spawn(async move {
-        loop {
-            if local_should_break.load(Ordering::Relaxed) {
-                break;
-            }
-
-            if get::currently_focused_video(&local_app).await?.is_some() {
-                save_watch_progress(&local_app, &local_mpv).await?;
-            }
-
-            sleep(Duration::from_secs(30)).await;
-        }
-
-        Ok::<(), anyhow::Error>(())
-    });
-
-    let mut have_warned = (false, 0);
-    'watchloop: loop {
-        'waitloop: while let Ok(value) = playlist_handler::status(&app).await {
-            match value {
-                Status::NoMoreAvailable => {
-                    break 'watchloop;
-                }
-                Status::NoCached { marked_watch } => {
-                    // try again next time.
-                    if have_warned.0 {
-                        if have_warned.1 != marked_watch {
-                            warn!("Now {} videos are marked as to be watched.", marked_watch);
-                            have_warned.1 = marked_watch;
-                        }
-                    } else {
-                        warn!(
-                            "There is nothing to watch yet, but still {} videos marked as to be watched. \
-                        Will idle, until they become available",
-                            marked_watch
-                        );
-                        have_warned = (true, marked_watch);
-                    }
-                    wait_for_db_write(&app).await?;
-                }
-                Status::Available { newly_available } => {
-                    debug!("Check and found {newly_available} videos!");
-                    have_warned.0 = false;
-
-                    // Something just became available!
-                    break 'waitloop;
-                }
-            }
-        }
-
-        if let Some(ev) = ev_ctx.wait_event(30.) {
-            match ev {
-                Ok(event) => {
-                    trace!("Mpv event triggered: {:#?}", event);
-                    if playlist_handler::handle_mpv_event(&app, &mpv, &event)
-                        .await
-                        .with_context(|| format!("Failed to handle mpv event: '{event:#?}'"))?
-                    {
-                        break;
-                    }
-                }
-                Err(e) => debug!("Mpv Event errored: {}", e),
-            }
-        }
-    }
-
-    should_break.store(true, Ordering::Relaxed);
-    progress_handle.await??;
-
-    Ok(())
-}
diff --git a/crates/yt/src/watch/playlist.rs b/crates/yt/src/watch/playlist.rs
deleted file mode 100644
index ff383d0..0000000
--- a/crates/yt/src/watch/playlist.rs
+++ /dev/null
@@ -1,99 +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::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},
-};
-
-use anyhow::Result;
-use futures::{TryStreamExt, stream::FuturesOrdered};
-
-/// Extract the values of the [`VideoStatus::Cached`] value from a Video.
-fn cache_values(video: &Video) -> (&Path, bool) {
-    if let VideoStatus::Cached {
-        cache_path,
-        is_focused,
-    } = &video.status
-    {
-        (cache_path, *is_focused)
-    } else {
-        unreachable!("All of these videos should be cached");
-    }
-}
-
-/// # Panics
-/// Only if internal assertions fail.
-pub async fn playlist(app: &App, watch: bool) -> Result<()> {
-    let mut previous_output_length = 0;
-    loop {
-        let playlist = get::playlist(app).await?.to_videos();
-
-        let output = playlist
-            .into_iter()
-            .map(|video| async move {
-                let mut output = String::new();
-
-                let (_, is_focused) = cache_values(&video);
-
-                if is_focused {
-                    output.push_str("🔻 ");
-                } else {
-                    output.push_str("  ");
-                }
-
-                output.push_str(&video.title_fmt(app));
-
-                output.push_str(" (");
-                output.push_str(&video.parent_subscription_name_fmt(app));
-                output.push(')');
-
-                output.push_str(" [");
-                output.push_str(&video.duration_fmt(app));
-
-                if is_focused {
-                    output.push_str(" (");
-                    output.push_str(&if let Some(duration) = video.duration.as_secs() {
-                        format!("{:0.0}%", (video.watch_progress.as_secs() / duration) * 100)
-                    } else {
-                        video.watch_progress_fmt(app)
-                    });
-                    output.push(')');
-                }
-                output.push(']');
-
-                output.push('\n');
-
-                Ok::<String, anyhow::Error>(output)
-            })
-            .collect::<FuturesOrdered<_>>()
-            .try_collect::<String>()
-            .await?;
-
-        // Delete the previous output
-        cursor_up(previous_output_length);
-        erase_in_display_from_cursor();
-
-        previous_output_length = output.chars().filter(|ch| *ch == '\n').count();
-
-        print!("{output}");
-
-        if !watch {
-            break;
-        }
-
-        wait_for_db_write(app).await?;
-    }
-
-    Ok(())
-}
diff --git a/crates/yt/src/watch/playlist_handler/mod.rs b/crates/yt/src/watch/playlist_handler/mod.rs
deleted file mode 100644
index 29b8f39..0000000
--- a/crates/yt/src/watch/playlist_handler/mod.rs
+++ /dev/null
@@ -1,342 +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::{cmp::Ordering, time::Duration};
-
-use crate::{
-    app::App,
-    storage::video_database::{
-        VideoStatus, VideoStatusMarker,
-        extractor_hash::ExtractorHash,
-        get::{self, Playlist, PlaylistIndex},
-        set,
-    },
-};
-
-use anyhow::{Context, Result};
-use libmpv2::{EndFileReason, Mpv, events::Event};
-use log::{debug, info};
-
-mod client_messages;
-
-#[derive(Debug, Clone, Copy)]
-pub enum Status {
-    /// There are no videos cached and no more marked to be watched.
-    /// Waiting is pointless.
-    NoMoreAvailable,
-
-    /// There are no videos cached, but some (> 0) are marked to be watched.
-    /// So we should wait for them to become available.
-    NoCached { marked_watch: usize },
-
-    /// There are videos cached and ready to be inserted into the playback queue.
-    Available { newly_available: usize },
-}
-
-fn mpv_message(mpv: &Mpv, message: &str, time: Duration) -> Result<()> {
-    mpv.command(
-        "show-text",
-        &[message, time.as_millis().to_string().as_str()],
-    )?;
-    Ok(())
-}
-
-async fn apply_video_options(app: &App, mpv: &Mpv, video: &ExtractorHash) -> Result<()> {
-    let options = get::video_mpv_opts(app, video).await?;
-    let video = get::video_by_hash(app, video).await?;
-
-    mpv.set_property("speed", options.playback_speed)?;
-
-    // We already start at 0, so setting it twice adds a uncomfortable skip sound.
-    if video.watch_progress.as_secs() != 0 {
-        mpv.set_property(
-            "time-pos",
-            i64::try_from(video.watch_progress.as_secs()).expect("This should not overflow"),
-        )?;
-    }
-    Ok(())
-}
-
-async fn mark_video_watched(app: &App, mpv: &Mpv) -> Result<()> {
-    let current_video = get::currently_focused_video(app)
-        .await?
-        .expect("This should be some at this point");
-
-    debug!(
-        "playlist handler will mark video '{}' watched.",
-        current_video.title
-    );
-
-    save_watch_progress(app, mpv).await?;
-
-    set::video_watched(app, &current_video.extractor_hash).await?;
-
-    Ok(())
-}
-
-/// Saves the `watch_progress` of the currently focused video.
-pub(super) async fn save_watch_progress(app: &App, mpv: &Mpv) -> Result<()> {
-    let current_video = get::currently_focused_video(app)
-        .await?
-        .expect("This should be some at this point");
-    let watch_progress = u32::try_from(
-        mpv.get_property::<i64>("time-pos")
-            .context("Failed to get the watchprogress of the currently playling video")?,
-    )
-    .expect("This conversion should never fail as the `time-pos` property is positive");
-
-    debug!(
-        "Setting the watch progress for the current_video '{}' to {watch_progress}s",
-        current_video.title_fmt_no_color()
-    );
-
-    set::video_watch_progress(app, &current_video.extractor_hash, watch_progress).await
-}
-
-/// Sync the mpv playlist with the internal playlist.
-///
-/// This takes an `maybe_playlist` argument, if you have already fetched the playlist and want to
-/// add that.
-pub(super) async fn reload_mpv_playlist(
-    app: &App,
-    mpv: &Mpv,
-    maybe_playlist: Option<Playlist>,
-    maybe_index: Option<PlaylistIndex>,
-) -> Result<()> {
-    fn get_playlist_count(mpv: &Mpv) -> Result<usize> {
-        mpv.get_property::<i64>("playlist/count")
-            .context("Failed to get mpv playlist len")
-            .map(|count| {
-                usize::try_from(count).expect("The playlist_count should always be positive")
-            })
-    }
-
-    if get_playlist_count(mpv)? != 0 {
-        // We could also use `loadlist`, but that would require use to start a unix socket or even
-        // write all the video paths to a file beforehand
-        mpv.command("playlist-clear", &[])?;
-        mpv.command("playlist-remove", &["current"])?;
-    }
-
-    assert_eq!(
-        get_playlist_count(mpv)?,
-        0,
-        "The playlist should be empty at this point."
-    );
-
-    let playlist = if let Some(p) = maybe_playlist {
-        p
-    } else {
-        get::playlist(app).await?
-    };
-
-    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",
-            ],
-        )?;
-
-        Ok::<(), anyhow::Error>(())
-    })?;
-
-    let index = if let Some(index) = maybe_index {
-        let index = usize::from(index);
-        let playlist_length = get_playlist_count(mpv)?;
-
-        match index.cmp(&playlist_length) {
-            Ordering::Greater => {
-                unreachable!(
-                    "The index '{index}' execeeds the playlist length '{playlist_length}'."
-                );
-            }
-            Ordering::Less => index,
-            Ordering::Equal => {
-                // The index is pointing to the end of the playlist. We could either go the second
-                // to last entry (i.e., one entry back) or wrap around to the start.
-                // We wrap around:
-                0
-            }
-        }
-    } else {
-        get::current_playlist_index(app)
-            .await?
-            .map_or(0, usize::from)
-    };
-    mpv.set_property("playlist-pos", index.to_string().as_str())?;
-
-    Ok(())
-}
-
-/// Return the status of the playback queue
-pub async fn status(app: &App) -> Result<Status> {
-    let playlist = get::playlist(app).await?;
-
-    let playlist_len = playlist.len();
-    let marked_watch_num = get::videos(app, &[VideoStatusMarker::Watch]).await?.len();
-
-    if playlist_len == 0 && marked_watch_num == 0 {
-        Ok(Status::NoMoreAvailable)
-    } else if playlist_len == 0 && marked_watch_num != 0 {
-        Ok(Status::NoCached {
-            marked_watch: marked_watch_num,
-        })
-    } else if playlist_len != 0 {
-        Ok(Status::Available {
-            newly_available: playlist_len,
-        })
-    } else {
-        unreachable!(
-            "The playlist length is {playlist_len}, but the number of marked watch videos is {marked_watch_num}! This is a bug."
-        );
-    }
-}
-
-/// # Returns
-/// This will return [`true`], if the event handling should be stopped
-///
-/// # Panics
-/// Only if internal assertions fail.
-#[allow(clippy::too_many_lines)]
-pub async fn handle_mpv_event(app: &App, mpv: &Mpv, event: &Event<'_>) -> Result<bool> {
-    match event {
-        Event::EndFile(r) => match r.reason {
-            EndFileReason::Eof => {
-                info!("Mpv reached the end of the current video. Marking it watched.");
-                mark_video_watched(app, mpv).await?;
-                reload_mpv_playlist(app, mpv, None, None).await?;
-            }
-            EndFileReason::Stop => {
-                // This reason is incredibly ambiguous. It _both_ means actually pausing a
-                // video and going to the next one in the playlist.
-                // Oh, and it's also called, when a video is removed from the playlist (at
-                // least via "playlist-remove current")
-                info!("Paused video (or went to next playlist entry); Doing nothing");
-            }
-            EndFileReason::Quit => {
-                info!("Mpv quit. Exiting playback");
-
-                save_watch_progress(app, mpv).await?;
-
-                return Ok(true);
-            }
-            EndFileReason::Error => {
-                unreachable!("This should have been raised as a separate error")
-            }
-            EndFileReason::Redirect => {
-                // TODO: We probably need to handle this somehow <2025-02-17>
-            }
-        },
-        Event::StartFile(_) => {
-            let mpv_pos = usize::try_from(mpv.get_property::<i64>("playlist-pos")?)
-                .expect("The value is strictly positive");
-
-            let next_video = {
-                let yt_pos = get::current_playlist_index(app).await?.map(usize::from);
-
-                if (Some(mpv_pos) != yt_pos) || yt_pos.is_none() {
-                    let playlist = get::playlist(app).await?;
-                    let video = playlist
-                        .get(PlaylistIndex::from(mpv_pos))
-                        .expect("The mpv pos should not be out of bounds");
-
-                    set::focused(
-                        app,
-                        &video.extractor_hash,
-                        get::currently_focused_video(app)
-                            .await?
-                            .as_ref()
-                            .map(|v| &v.extractor_hash),
-                    )
-                    .await?;
-
-                    video.extractor_hash
-                } else {
-                    get::currently_focused_video(app)
-                        .await?
-                        .expect("We have a focused video")
-                        .extractor_hash
-                }
-            };
-
-            apply_video_options(app, mpv, &next_video).await?;
-        }
-        Event::Seek => {
-            save_watch_progress(app, mpv).await?;
-        }
-        Event::ClientMessage(a) => {
-            debug!("Got Client Message event: '{}'", a.join(" "));
-
-            match a.as_slice() {
-                &["yt-comments-external"] => {
-                    client_messages::handle_yt_comments_external(app).await?;
-                }
-                &["yt-comments-local"] => {
-                    client_messages::handle_yt_comments_local(app, mpv).await?;
-                }
-
-                &["yt-description-external"] => {
-                    client_messages::handle_yt_description_external(app).await?;
-                }
-                &["yt-description-local"] => {
-                    client_messages::handle_yt_description_local(app, mpv).await?;
-                }
-
-                &["yt-mark-picked"] => {
-                    let current_video = get::currently_focused_video(app)
-                        .await?
-                        .expect("This should exist at this point");
-                    let current_index = get::current_playlist_index(app)
-                        .await?
-                        .expect("This should exist, as we can mark this video picked");
-
-                    save_watch_progress(app, mpv).await?;
-
-                    set::video_status(
-                        app,
-                        &current_video.extractor_hash,
-                        VideoStatus::Pick,
-                        Some(current_video.priority),
-                    )
-                    .await?;
-
-                    reload_mpv_playlist(app, mpv, None, Some(current_index)).await?;
-                    mpv_message(mpv, "Marked the video as picked", Duration::from_secs(3))?;
-                }
-                &["yt-mark-watched"] => {
-                    let current_index = get::current_playlist_index(app)
-                        .await?
-                        .expect("This should exist, as we can mark this video picked");
-                    mark_video_watched(app, mpv).await?;
-
-                    reload_mpv_playlist(app, mpv, None, Some(current_index)).await?;
-                    mpv_message(mpv, "Marked the video watched", Duration::from_secs(3))?;
-                }
-                &["yt-check-new-videos"] => {
-                    reload_mpv_playlist(app, mpv, None, None).await?;
-                }
-                other => {
-                    debug!("Unknown message: {}", other.join(" "));
-                }
-            }
-        }
-        _ => {}
-    }
-
-    Ok(false)
-}
diff --git a/crates/yt/src/yt_dlp/mod.rs b/crates/yt/src/yt_dlp/mod.rs
new file mode 100644
index 0000000..eaa80a1
--- /dev/null
+++ b/crates/yt/src/yt_dlp/mod.rs
@@ -0,0 +1,253 @@
+// 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::{borrow::ToOwned, str::FromStr, time::Duration};
+
+use anyhow::{Context, Result};
+use chrono::{DateTime, Utc};
+use futures::{FutureExt, future::BoxFuture};
+use log::{error, warn};
+use serde_json::json;
+use tokio::{fs, io};
+use url::Url;
+use yt_dlp::{
+    YoutubeDL, info_json::InfoJson, json_cast, json_get, json_try_get, options::YoutubeDLOptions,
+};
+
+use crate::{
+    app::App,
+    select::duration::MaybeDuration,
+    shared::bytes::Bytes,
+    storage::db::{
+        extractor_hash::ExtractorHash,
+        subscription::Subscription,
+        video::{Priority, TimeStamp, Video, VideoStatus},
+    },
+};
+
+pub(crate) fn yt_dlp_opts_updating(max_backlog: usize) -> Result<YoutubeDL> {
+    Ok(YoutubeDLOptions::new()
+        .set("playliststart", 1)
+        .set("playlistend", 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()?)
+}
+
+impl Video {
+    pub(crate) fn get_approx_size(&self) -> Result<u64> {
+        let yt_dlp = {
+            YoutubeDLOptions::new()
+                .set("prefer_free_formats", true)
+                .set("format", "bestvideo[height<=?1080]+bestaudio/best")
+                .set("fragment_retries", 10)
+                .set("retries", 10)
+                .set("getcomments", false)
+                .set("ignoreerrors", false)
+                .build()
+                .context("Failed to instanciate get approx size yt_dlp")
+        }?;
+
+        let result = yt_dlp
+            .extract_info(&self.url, false, true)
+            .with_context(|| format!("Failed to extract video information: '{}'", self.title))?;
+
+        let size = if let Some(filesize) = json_try_get!(result, "filesize", as_u64) {
+            filesize
+        } else if let Some(num) = json_try_get!(result, "filesize_approx", as_u64) {
+            num
+        } else if let Some(duration) = json_try_get!(result, "duration", as_f64)
+            && let Some(tbr) = json_try_get!(result, "tbr", as_f64)
+        {
+            #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
+            let duration = duration.ceil() as u64;
+
+            // TODO: yt_dlp gets this from the format
+            #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
+            let tbr = tbr.ceil() as u64;
+
+            duration * tbr * (1000 / 8)
+        } else {
+            let hardcoded_default = Bytes::from_str("250 MiB").expect("This is hardcoded");
+            error!(
+                "Failed to find a filesize for video: {:?} (Using hardcoded value of {})",
+                self.title, hardcoded_default
+            );
+            hardcoded_default.as_u64()
+        };
+
+        Ok(size)
+    }
+}
+
+impl Video {
+    #[allow(clippy::too_many_lines)]
+    pub(crate) fn from_info_json(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}'). \
+                    Expected `YYYY-MM-DD`, has the format changed?"
+            );
+            if let Some(date_string) = extended {
+                format!("{f}\nThe parsed '{date_string}' can't be turned to a valid UTC date.'")
+            } else {
+                f
+            }
+        }
+
+        let publish_date = if let Some(date) = json_try_get!(entry, "upload_date", as_str) {
+            let year: u32 = date
+                .chars()
+                .take(4)
+                .collect::<String>()
+                .parse()
+                .with_context(|| fmt_context(date, None))?;
+            let month: u32 = date
+                .chars()
+                .skip(4)
+                .take(2)
+                .collect::<String>()
+                .parse()
+                .with_context(|| fmt_context(date, None))?;
+            let day: u32 = date
+                .chars()
+                .skip(4 + 2)
+                .take(2)
+                .collect::<String>()
+                .parse()
+                .with_context(|| fmt_context(date, None))?;
+
+            let date_string = format!("{year:04}-{month:02}-{day:02}T00:00:00Z");
+            Some(
+                DateTime::<Utc>::from_str(&date_string)
+                    .with_context(|| fmt_context(date, Some(&date_string)))?
+                    .timestamp(),
+            )
+        } else {
+            warn!(
+                "The video '{}' lacks it's upload date!",
+                json_get!(entry, "title", as_str)
+            );
+            None
+        };
+
+        let thumbnail_url = match (
+            &json_try_get!(entry, "thumbnails", as_array),
+            &json_try_get!(entry, "thumbnail", as_str),
+        ) {
+            (None, None) => None,
+            (None, Some(thumbnail)) => Some(Url::from_str(thumbnail)?),
+
+            // TODO: The algorithm is not exactly the best <2024-05-28>
+            (Some(thumbnails), None) => {
+                if let Some(thumbnail) = thumbnails.first() {
+                    Some(Url::from_str(json_get!(
+                        json_cast!(thumbnail, as_object),
+                        "url",
+                        as_str
+                    ))?)
+                } else {
+                    None
+                }
+            }
+            (Some(_), Some(thumnail)) => Some(Url::from_str(thumnail)?),
+        };
+
+        let url = {
+            let smug_url: Url = json_get!(entry, "webpage_url", as_str).parse()?;
+            // TODO(@bpeetz): We should probably add this? <2025-06-14>
+            // if '#__youtubedl_smuggle' not in smug_url:
+            //     return smug_url, default
+            // url, _, sdata = smug_url.rpartition('#')
+            // jsond = urllib.parse.parse_qs(sdata)['__youtubedl_smuggle'][0]
+            // data = json.loads(jsond)
+            // return url, data
+
+            smug_url
+        };
+
+        let extractor_hash = ExtractorHash::from_info_json(entry);
+
+        let subscription_name = if let Some(sub) = sub {
+            Some(sub.name.clone())
+        } else if let Some(uploader) = json_try_get!(entry, "uploader", as_str) {
+            if json_try_get!(entry, "webpage_url_domain", as_str) == Some("youtube.com") {
+                Some(format!("{uploader} - Videos"))
+            } else {
+                Some(uploader.to_owned())
+            }
+        } else {
+            None
+        };
+
+        let video = Video {
+            description: json_try_get!(entry, "description", as_str).map(ToOwned::to_owned),
+            duration: MaybeDuration::from_maybe_secs_f64(json_try_get!(entry, "duration", as_f64)),
+            extractor_hash,
+            last_status_change: TimeStamp::from_now(),
+            parent_subscription_name: subscription_name,
+            priority: Priority::default(),
+            publish_date: publish_date.map(TimeStamp::from_secs),
+            status: VideoStatus::Pick,
+            thumbnail_url,
+            title: json_get!(entry, "title", as_str).to_owned(),
+            url,
+            watch_progress: Duration::default(),
+            playback_speed: None,
+            subtitle_langs: None,
+        };
+        Ok(video)
+    }
+}
+
+pub(crate) async fn get_current_cache_allocation(app: &App) -> Result<Bytes> {
+    fn dir_size(mut dir: fs::ReadDir) -> BoxFuture<'static, Result<Bytes>> {
+        async move {
+            let mut acc = 0;
+            while let Some(entry) = dir.next_entry().await? {
+                let size = match entry.metadata().await? {
+                    data if data.is_dir() => {
+                        let path = entry.path();
+                        let read_dir = fs::read_dir(path).await?;
+
+                        dir_size(read_dir).await?.as_u64()
+                    }
+                    data => data.len(),
+                };
+                acc += size;
+            }
+            Ok(Bytes::new(acc))
+        }
+        .boxed()
+    }
+
+    let read_dir_result = match fs::read_dir(&app.config.paths.download_dir).await {
+        Ok(ok) => ok,
+        Err(err) => match err.kind() {
+            io::ErrorKind::NotFound => {
+                unreachable!("The download dir should always be created in the config finalizers.");
+            }
+            err => Err(io::Error::from(err)).with_context(|| {
+                format!(
+                    "Failed to get dir size of download dir at: '{}'",
+                    &app.config.paths.download_dir.display()
+                )
+            })?,
+        },
+    };
+
+    dir_size(read_dir_result).await
+}
diff --git a/crates/yt/tests/_testenv/init.rs b/crates/yt/tests/_testenv/init.rs
new file mode 100644
index 0000000..5970c7c
--- /dev/null
+++ b/crates/yt/tests/_testenv/init.rs
@@ -0,0 +1,136 @@
+// 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::{
+    env,
+    fs::{self, OpenOptions},
+    io::{self, Write},
+    path::PathBuf,
+};
+
+use crate::{_testenv::Paths, testenv::TestEnv};
+
+fn target_dir() -> PathBuf {
+    // Tests exe is in target/debug/deps, the *yt* exe is in target/debug
+    env::current_exe()
+        .expect("./target/debug/deps/yt-*")
+        .parent()
+        .expect("./target/debug/deps")
+        .parent()
+        .expect("./target/debug")
+        .parent()
+        .expect("./target")
+        .to_path_buf()
+}
+
+fn test_dir(name: &'static str) -> PathBuf {
+    target_dir().join("tests").join(name)
+}
+
+fn prepare_files_and_dirs(name: &'static str) -> io::Result<Paths> {
+    let test_dir = test_dir(name);
+
+    fs::create_dir_all(&test_dir)?;
+
+    let db_path = test_dir.join("database.sqlite");
+    let last_selection_path = test_dir.join("last_selection");
+    let config_path = test_dir.join("config.toml");
+    let download_dir = test_dir.join("download");
+
+    {
+        // Remove all files, that are not the download dir.
+        for entry in fs::read_dir(test_dir).expect("Works") {
+            let entry = entry.unwrap();
+            let entry_ft = entry.file_type().unwrap();
+
+            if entry_ft.is_dir() && entry.file_name() == "download" {
+                continue;
+            }
+
+            if entry_ft.is_dir() {
+                fs::remove_dir_all(entry.path()).unwrap();
+            } else {
+                fs::remove_file(entry.path()).unwrap();
+            }
+        }
+    }
+
+    fs::create_dir_all(&download_dir)?;
+
+    {
+        let mut config_file = OpenOptions::new()
+            .write(true)
+            .truncate(true)
+            .create(true)
+            .open(&config_path)?;
+
+        writeln!(
+            config_file,
+            r#"[paths]
+download_dir = "{}"
+mpv_config_path = "/dev/null"
+mpv_input_path = "/dev/null"
+database_path = "{}"
+last_selection_path = "{}"
+
+[download]
+max_cache_size = "100GiB"
+
+[update]
+max_backlog = 1
+"#,
+            download_dir.display(),
+            db_path.display(),
+            last_selection_path.display(),
+        )?;
+        config_file.flush()?;
+    }
+
+    Ok(Paths {
+        db: db_path,
+        last_selection: last_selection_path,
+        config: config_path,
+        download_dir,
+    })
+}
+
+/// Find the *yt* executable.
+fn find_yt_exe() -> PathBuf {
+    let target = target_dir().join("debug");
+
+    let exe_name = if cfg!(windows) { "yt.exe" } else { "yt" };
+
+    target.join(exe_name)
+}
+
+impl TestEnv {
+    pub(crate) fn new(name: &'static str) -> TestEnv {
+        let yt_exe = find_yt_exe();
+        let test_dir = test_dir(name);
+
+        let paths = prepare_files_and_dirs(name).expect("config dir");
+
+        TestEnv {
+            name,
+            yt_exe,
+            test_dir,
+            paths,
+            spawned_childs: vec![],
+        }
+    }
+}
+
+impl Drop for TestEnv {
+    fn drop(&mut self) {
+        for child in &mut self.spawned_childs {
+            drop(child.kill());
+        }
+    }
+}
diff --git a/crates/yt/tests/_testenv/mod.rs b/crates/yt/tests/_testenv/mod.rs
new file mode 100644
index 0000000..38d1f0a
--- /dev/null
+++ b/crates/yt/tests/_testenv/mod.rs
@@ -0,0 +1,35 @@
+// 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>.
+
+//! This code was taken from *fd* at 30-06-2025.
+
+use std::{path::PathBuf, process};
+
+mod init;
+mod run;
+pub(crate) mod util;
+
+/// Environment for the integration tests.
+pub(crate) struct TestEnv {
+    pub(crate) name: &'static str,
+    pub(crate) yt_exe: PathBuf,
+    pub(crate) test_dir: PathBuf,
+
+    pub(crate) paths: Paths,
+
+    pub(crate) spawned_childs: Vec<process::Child>,
+}
+
+pub(crate) struct Paths {
+    pub(crate) db: PathBuf,
+    pub(crate) last_selection: PathBuf,
+    pub(crate) config: PathBuf,
+    pub(crate) download_dir: PathBuf,
+}
diff --git a/crates/yt/tests/_testenv/run.rs b/crates/yt/tests/_testenv/run.rs
new file mode 100644
index 0000000..578d823
--- /dev/null
+++ b/crates/yt/tests/_testenv/run.rs
@@ -0,0 +1,183 @@
+// 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::{
+    collections::HashMap,
+    process::{self, Stdio},
+};
+
+use colors::{Colorize, IntoCanvas};
+
+use crate::testenv::TestEnv;
+
+/// Format an error message for when *yt* did not exit successfully.
+fn format_exit_error(args: &[&str], output: &process::Output) -> String {
+    format!(
+        "`yt {}` did not exit successfully.\nstdout:\n---\n{}---\nstderr:\n---\n{}---",
+        args.join(" "),
+        String::from_utf8_lossy(&output.stdout),
+        String::from_utf8_lossy(&output.stderr)
+    )
+}
+
+/// Format an error message for when the output of *yt* did not match the expected output.
+fn format_output_error(args: &[&str], expected: &str, actual: &str) -> String {
+    fn normalize_str(input: &str) -> &str {
+        if input.is_empty() { "<Empty>" } else { input }
+    }
+
+    let expected = normalize_str(expected);
+    let actual = normalize_str(actual);
+
+    format!(
+        concat!(
+            "`yt {}` did not produce the expected output.\n",
+            "expected:\n---\n{}\n---\n and actual:\n---\n{}\n---\n"
+        ),
+        args.join(" "),
+        expected,
+        actual
+    )
+}
+
+/// Normalize the output for comparison.
+fn normalize_output(s: &str, trim_start: bool, normalize_line: bool) -> String {
+    // Split into lines and normalize separators.
+    let mut lines = s
+        .replace('\0', "NULL\n")
+        .lines()
+        .map(|line| {
+            let line = if trim_start { line.trim_start() } else { line };
+            let line = line.replace('/', std::path::MAIN_SEPARATOR_STR);
+            if normalize_line {
+                let mut words: Vec<_> = line.split_whitespace().collect();
+                words.sort_unstable();
+                return words.join(" ");
+            }
+            line
+        })
+        .collect::<Vec<_>>();
+
+    lines.sort();
+    lines.join("\n")
+}
+
+impl TestEnv {
+    /// Assert that calling *yt* in the specified path under the root working directory,
+    /// and with the specified arguments produces the expected output.
+    pub(crate) fn assert_output(&self, args: &[&str], expected: &str) {
+        let expected = normalize_output(expected, true, false);
+        let actual = self.run(args);
+
+        assert!(
+            expected == actual,
+            "{}",
+            format_output_error(args, &expected, &actual)
+        );
+    }
+
+    /// Run *yt* once with the first args, pipe the output of this command to *yt* with the second
+    /// args and return the normalized output.
+    pub(crate) fn run_piped(&self, first_args: &[&str], second_args: &[&str]) -> String {
+        let mut first_cmd = self.prepare_yt(first_args);
+        let mut second_cmd = self.prepare_yt(second_args);
+
+        first_cmd.stdout(Stdio::piped());
+        let mut first_child = first_cmd.spawn().expect("yt spawn");
+
+        second_cmd.stdin(first_child.stdout.take().expect("Was set"));
+
+        let first_output = first_child.wait_with_output().expect("yt run");
+        assert!(
+            first_output.status.success(),
+            "{}",
+            format_exit_error(first_args, &first_output)
+        );
+
+        Self::finalize_cmd(second_cmd, second_args)
+    }
+
+    /// Run *yt*, with the given args.
+    /// Returns the normalized stdout output.
+    pub(crate) fn run(&self, args: &[&str]) -> String {
+        let cmd = self.prepare_yt(args);
+        Self::finalize_cmd(cmd, args)
+    }
+
+    /// Run *yt*, with the given args and environment variables.
+    /// Returns the normalized stdout output.
+    pub(crate) fn run_env(&self, args: &[&str], env: &HashMap<&str, &str>) -> String {
+        let mut cmd = self.prepare_yt(args);
+        cmd.envs(env.iter());
+        Self::finalize_cmd(cmd, args)
+    }
+
+    /// Run *yt*, with the given args and fork into the background.
+    /// Returns a sender for the lines on stdout.
+    pub(crate) fn run_background(&mut self, args: &[&str]) -> process::ChildStdout {
+        let mut cmd = self.prepare_yt(args);
+
+        cmd.stdout(Stdio::piped());
+        cmd.stderr(Stdio::inherit());
+
+        // The whole point of this function, is calling a command and keep it running for the whole
+        // programs run-time.
+        //
+        // And we provide a clean-up mechanism.
+        #[allow(clippy::zombie_processes)]
+        let mut child = cmd.spawn().expect("yt spawn");
+
+        let stdout = child.stdout.take().expect("Was piped");
+
+        self.spawned_childs.push(child);
+
+        stdout
+    }
+
+    fn finalize_cmd(mut cmd: process::Command, args: &[&str]) -> String {
+        let child = cmd.spawn().expect("yt spawn");
+        let output = child.wait_with_output().expect("yt output");
+
+        assert!(
+            output.status.success(),
+            "{}",
+            format_exit_error(args, &output)
+        );
+
+        normalize_output(&String::from_utf8_lossy(&output.stdout), false, false)
+    }
+
+    fn prepare_yt(&self, args: &[&str]) -> process::Command {
+        let mut cmd = process::Command::new(&self.yt_exe);
+
+        cmd.current_dir(&self.test_dir);
+
+        cmd.args([
+            "-v",
+            "--color",
+            "false",
+            "--config-path",
+            self.paths.config.to_str().unwrap(),
+        ]);
+
+        cmd.stdout(Stdio::piped());
+        cmd.stderr(Stdio::piped());
+
+        cmd.args(args);
+
+        eprintln!(
+            "{} `yt {}`",
+            self.name.blue().italic().render(true),
+            args.join(" ")
+        );
+
+        cmd
+    }
+}
diff --git a/crates/yt/tests/_testenv/util.rs b/crates/yt/tests/_testenv/util.rs
new file mode 100644
index 0000000..6633fbf
--- /dev/null
+++ b/crates/yt/tests/_testenv/util.rs
@@ -0,0 +1,371 @@
+// 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::_testenv::TestEnv;
+
+use std::{
+    collections::HashMap,
+    fs::{OpenOptions, Permissions},
+    io::Write,
+    os::unix::fs::PermissionsExt,
+    path::PathBuf,
+};
+
+pub(crate) fn get_first_hash(env: &TestEnv) -> String {
+    let output = env.run(&["videos", "ls", "--format", "{extractor_hash}"]);
+
+    let first_hash = output.lines().next().unwrap();
+    first_hash.to_owned()
+}
+
+#[derive(Clone, Copy)]
+pub(crate) enum Subscription {
+    Tagesschau,
+}
+
+impl Subscription {
+    const fn as_url(self) -> &'static str {
+        match self {
+            Subscription::Tagesschau => {
+                "https://www.ardmediathek.de/sendung/tagesschau/Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXU"
+            }
+        }
+    }
+
+    const fn as_name(self) -> &'static str {
+        match self {
+            Subscription::Tagesschau => "Tagesschau",
+        }
+    }
+}
+
+/// Provide the given number of videos.
+pub(crate) fn provide_videos(env: &TestEnv, sub: Subscription, num: u8) {
+    add_sub(env, sub);
+    update_sub(env, num, sub);
+}
+
+pub(crate) fn add_sub(env: &TestEnv, sub: Subscription) {
+    env.run(&[
+        "subs",
+        "add",
+        sub.as_url(),
+        "--name",
+        sub.as_name(),
+        "--no-check",
+    ]);
+}
+
+pub(crate) fn update_sub(env: &TestEnv, num_of_videos: u8, sub: Subscription) {
+    let num_of_videos: &str = u8_to_char(num_of_videos);
+
+    env.run(&["update", "--max-backlog", num_of_videos, sub.as_name()]);
+}
+
+pub(crate) fn run_select(env: &TestEnv, sed_regex: &str) {
+    let mut map = HashMap::new();
+
+    let command = make_command(
+        env,
+        format!(r#"sed --in-place '{sed_regex}' "$1""#).as_str(),
+    );
+    map.insert("EDITOR", command.to_str().unwrap());
+
+    env.run_env(&["select", "file", "--done"], &map);
+}
+
+fn make_command(env: &TestEnv, shell_command: &str) -> PathBuf {
+    let command_path = env.test_dir.join("command.sh");
+
+    {
+        let mut file = OpenOptions::new()
+            .write(true)
+            .create(true)
+            .truncate(true)
+            .open(&command_path)
+            .unwrap();
+
+        writeln!(file, "#!/usr/bin/env sh").unwrap();
+
+        file.write_all(shell_command.as_bytes()).unwrap();
+        file.flush().unwrap();
+
+        {
+            let perms = Permissions::from_mode(0o0700);
+            file.set_permissions(perms).unwrap();
+        }
+    }
+
+    command_path
+}
+
+// Char conversion {{{
+#[allow(clippy::too_many_lines)]
+const fn u8_to_char(input: u8) -> &'static str {
+    match input {
+        0 => "0",
+        1 => "1",
+        2 => "2",
+        3 => "3",
+        4 => "4",
+        5 => "5",
+        6 => "6",
+        7 => "7",
+        8 => "8",
+        9 => "9",
+        10 => "10",
+        11 => "11",
+        12 => "12",
+        13 => "13",
+        14 => "14",
+        15 => "15",
+        16 => "16",
+        17 => "17",
+        18 => "18",
+        19 => "19",
+        20 => "20",
+        21 => "21",
+        22 => "22",
+        23 => "23",
+        24 => "24",
+        25 => "25",
+        26 => "26",
+        27 => "27",
+        28 => "28",
+        29 => "29",
+        30 => "30",
+        31 => "31",
+        32 => "32",
+        33 => "33",
+        34 => "34",
+        35 => "35",
+        36 => "36",
+        37 => "37",
+        38 => "38",
+        39 => "39",
+        40 => "40",
+        41 => "41",
+        42 => "42",
+        43 => "43",
+        44 => "44",
+        45 => "45",
+        46 => "46",
+        47 => "47",
+        48 => "48",
+        49 => "49",
+        50 => "50",
+        51 => "51",
+        52 => "52",
+        53 => "53",
+        54 => "54",
+        55 => "55",
+        56 => "56",
+        57 => "57",
+        58 => "58",
+        59 => "59",
+        60 => "60",
+        61 => "61",
+        62 => "62",
+        63 => "63",
+        64 => "64",
+        65 => "65",
+        66 => "66",
+        67 => "67",
+        68 => "68",
+        69 => "69",
+        70 => "70",
+        71 => "71",
+        72 => "72",
+        73 => "73",
+        74 => "74",
+        75 => "75",
+        76 => "76",
+        77 => "77",
+        78 => "78",
+        79 => "79",
+        80 => "80",
+        81 => "81",
+        82 => "82",
+        83 => "83",
+        84 => "84",
+        85 => "85",
+        86 => "86",
+        87 => "87",
+        88 => "88",
+        89 => "89",
+        90 => "90",
+        91 => "91",
+        92 => "92",
+        93 => "93",
+        94 => "94",
+        95 => "95",
+        96 => "96",
+        97 => "97",
+        98 => "98",
+        99 => "99",
+        100 => "100",
+        101 => "101",
+        102 => "102",
+        103 => "103",
+        104 => "104",
+        105 => "105",
+        106 => "106",
+        107 => "107",
+        108 => "108",
+        109 => "109",
+        110 => "110",
+        111 => "111",
+        112 => "112",
+        113 => "113",
+        114 => "114",
+        115 => "115",
+        116 => "116",
+        117 => "117",
+        118 => "118",
+        119 => "119",
+        120 => "120",
+        121 => "121",
+        122 => "122",
+        123 => "123",
+        124 => "124",
+        125 => "125",
+        126 => "126",
+        127 => "127",
+        128 => "128",
+        129 => "129",
+        130 => "130",
+        131 => "131",
+        132 => "132",
+        133 => "133",
+        134 => "134",
+        135 => "135",
+        136 => "136",
+        137 => "137",
+        138 => "138",
+        139 => "139",
+        140 => "140",
+        141 => "141",
+        142 => "142",
+        143 => "143",
+        144 => "144",
+        145 => "145",
+        146 => "146",
+        147 => "147",
+        148 => "148",
+        149 => "149",
+        150 => "150",
+        151 => "151",
+        152 => "152",
+        153 => "153",
+        154 => "154",
+        155 => "155",
+        156 => "156",
+        157 => "157",
+        158 => "158",
+        159 => "159",
+        160 => "160",
+        161 => "161",
+        162 => "162",
+        163 => "163",
+        164 => "164",
+        165 => "165",
+        166 => "166",
+        167 => "167",
+        168 => "168",
+        169 => "169",
+        170 => "170",
+        171 => "171",
+        172 => "172",
+        173 => "173",
+        174 => "174",
+        175 => "175",
+        176 => "176",
+        177 => "177",
+        178 => "178",
+        179 => "179",
+        180 => "180",
+        181 => "181",
+        182 => "182",
+        183 => "183",
+        184 => "184",
+        185 => "185",
+        186 => "186",
+        187 => "187",
+        188 => "188",
+        189 => "189",
+        190 => "190",
+        191 => "191",
+        192 => "192",
+        193 => "193",
+        194 => "194",
+        195 => "195",
+        196 => "196",
+        197 => "197",
+        198 => "198",
+        199 => "199",
+        200 => "200",
+        201 => "201",
+        202 => "202",
+        203 => "203",
+        204 => "204",
+        205 => "205",
+        206 => "206",
+        207 => "207",
+        208 => "208",
+        209 => "209",
+        210 => "210",
+        211 => "211",
+        212 => "212",
+        213 => "213",
+        214 => "214",
+        215 => "215",
+        216 => "216",
+        217 => "217",
+        218 => "218",
+        219 => "219",
+        220 => "220",
+        221 => "221",
+        222 => "222",
+        223 => "223",
+        224 => "224",
+        225 => "225",
+        226 => "226",
+        227 => "227",
+        228 => "228",
+        229 => "229",
+        230 => "230",
+        231 => "231",
+        232 => "232",
+        233 => "233",
+        234 => "234",
+        235 => "235",
+        236 => "236",
+        237 => "237",
+        238 => "238",
+        239 => "239",
+        240 => "240",
+        241 => "241",
+        242 => "242",
+        243 => "243",
+        244 => "244",
+        245 => "245",
+        246 => "246",
+        247 => "247",
+        248 => "248",
+        249 => "249",
+        250 => "250",
+        251 => "251",
+        252 => "252",
+        253 => "253",
+        254 => "254",
+        255 => "255",
+    }
+}
+// }}}
diff --git a/crates/yt/tests/select/base.rs b/crates/yt/tests/select/base.rs
new file mode 100644
index 0000000..24e198b
--- /dev/null
+++ b/crates/yt/tests/select/base.rs
@@ -0,0 +1,50 @@
+// 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::{collections::HashMap, fs};
+
+use crate::{
+    _testenv::{
+        TestEnv,
+        util::{self, Subscription},
+    },
+    select::get_videos_in_state,
+};
+
+#[test]
+fn test_base() {
+    let env = TestEnv::new(module_path!());
+
+    util::provide_videos(&env, Subscription::Tagesschau, 4);
+
+    let first_hash = &util::get_first_hash(&env);
+    env.run(&["select", "drop", first_hash]);
+
+    let mut map = HashMap::new();
+    map.insert("EDITOR", "true");
+    env.run_env(&["select", "file", "--done"], &map);
+
+    fs::remove_file(&env.paths.last_selection).unwrap();
+
+    env.run_env(&["select", "split", "--done"], &map);
+    assert_states(&env);
+
+    env.run_env(&["select", "file", "--use-last-selection"], &map);
+    assert_states(&env);
+}
+
+fn assert_states(env: &TestEnv) {
+    assert_eq!(get_videos_in_state(env, "Picked"), 3);
+    assert_eq!(get_videos_in_state(env, "Drop"), 1);
+
+    assert_eq!(get_videos_in_state(env, "Watch"), 0);
+    assert_eq!(get_videos_in_state(env, "Cached"), 0);
+    assert_eq!(get_videos_in_state(env, "Watched"), 0);
+}
diff --git a/crates/yt/tests/select/file.rs b/crates/yt/tests/select/file.rs
new file mode 100644
index 0000000..b8bd2b5
--- /dev/null
+++ b/crates/yt/tests/select/file.rs
@@ -0,0 +1,31 @@
+// 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::{
+    _testenv::util::{self, Subscription},
+    select::get_videos_in_state,
+    testenv::TestEnv,
+};
+
+#[test]
+fn test_file() {
+    let env = TestEnv::new(module_path!());
+
+    util::provide_videos(&env, Subscription::Tagesschau, 4);
+
+    util::run_select(&env, "s/pick/watch/");
+
+    assert_eq!(get_videos_in_state(&env, "Picked"), 0);
+    assert_eq!(get_videos_in_state(&env, "Drop"), 0);
+
+    assert_eq!(get_videos_in_state(&env, "Watch"), 4);
+    assert_eq!(get_videos_in_state(&env, "Cached"), 0);
+    assert_eq!(get_videos_in_state(&env, "Watched"), 0);
+}
diff --git a/crates/yt/tests/select/mod.rs b/crates/yt/tests/select/mod.rs
new file mode 100644
index 0000000..d7033f8
--- /dev/null
+++ b/crates/yt/tests/select/mod.rs
@@ -0,0 +1,25 @@
+// 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::_testenv::TestEnv;
+
+mod base;
+mod file;
+mod options;
+
+fn get_videos_in_state(env: &TestEnv, state: &str) -> usize {
+    let status = env.run(&[
+        "status",
+        "--format",
+        format!("{{{}_videos_len}}", state.to_lowercase()).as_str(),
+    ]);
+
+    status.parse().unwrap()
+}
diff --git a/crates/yt/tests/select/options.rs b/crates/yt/tests/select/options.rs
new file mode 100644
index 0000000..6a0d155
--- /dev/null
+++ b/crates/yt/tests/select/options.rs
@@ -0,0 +1,51 @@
+// 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::{_testenv::util, select::get_videos_in_state, testenv::TestEnv};
+
+#[test]
+fn test_options() {
+    let env = TestEnv::new(module_path!());
+
+    util::provide_videos(&env, util::Subscription::Tagesschau, 1);
+
+    let video_hash = &util::get_first_hash(&env);
+    env.run(&["select", "watch", video_hash]);
+
+    env.assert_output(&["videos", "info", video_hash, "--format", "{options}"], "");
+
+    env.run(&[
+        "select",
+        "watch",
+        video_hash,
+        "--playback-speed",
+        "1",
+        "--subtitle-langs",
+        "en,de,sv",
+    ]);
+
+    env.assert_output(
+        &["videos", "info", video_hash, "--format", "{options}"],
+        "--playback-speed '1' --subtitle-langs 'en,de,sv'",
+    );
+
+    env.run(&["select", "watch", video_hash, "-s", "1.7", "-l", "de"]);
+
+    env.assert_output(
+        &["videos", "info", video_hash, "--format", "{options}"],
+        "--playback-speed '1.7' --subtitle-langs 'de'",
+    );
+
+    assert_eq!(get_videos_in_state(&env, "Picked"), 0);
+    assert_eq!(get_videos_in_state(&env, "Drop"), 0);
+    assert_eq!(get_videos_in_state(&env, "Watch"), 1);
+    assert_eq!(get_videos_in_state(&env, "Cached"), 0);
+    assert_eq!(get_videos_in_state(&env, "Watched"), 0);
+}
diff --git a/crates/yt/tests/subscriptions/import_export/golden.txt b/crates/yt/tests/subscriptions/import_export/golden.txt
new file mode 100644
index 0000000..7ed5419
--- /dev/null
+++ b/crates/yt/tests/subscriptions/import_export/golden.txt
@@ -0,0 +1,2 @@
+Nyheter på lätt svenska: 'https://www.svtplay.se/nyheter-pa-latt-svenska'
+tagesschau: 'https://www.ardmediathek.de/sendung/tagesschau/Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXU'
diff --git a/crates/bytes/Cargo.lock.license b/crates/yt/tests/subscriptions/import_export/golden.txt.license
index d4d410f..7813eb6 100644
--- a/crates/bytes/Cargo.lock.license
+++ b/crates/yt/tests/subscriptions/import_export/golden.txt.license
@@ -1,6 +1,6 @@
 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.
diff --git a/crates/yt/tests/subscriptions/import_export/mod.rs b/crates/yt/tests/subscriptions/import_export/mod.rs
new file mode 100644
index 0000000..1156508
--- /dev/null
+++ b/crates/yt/tests/subscriptions/import_export/mod.rs
@@ -0,0 +1,35 @@
+// 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::testenv::TestEnv;
+
+#[test]
+fn test_import_export() {
+    let env = TestEnv::new(module_path!());
+
+    env.run(&[
+        "subs",
+        "add",
+        "https://www.svtplay.se/nyheter-pa-latt-svenska",
+    ]);
+    env.run(&[
+        "subs",
+        "add",
+        "https://www.ardmediathek.de/sendung/tagesschau/Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXU",
+    ]);
+
+    let before = env.run(&["subs", "list"]);
+    env.assert_output(&["subs", "list"], include_str!("./golden.txt"));
+
+    env.run_piped(&["subs", "export"], &["subs", "import", "--force"]);
+
+    env.assert_output(&["subs", "list"], &before);
+    env.assert_output(&["subs", "list"], include_str!("./golden.txt"));
+}
diff --git a/crates/yt/tests/subscriptions/mod.rs b/crates/yt/tests/subscriptions/mod.rs
new file mode 100644
index 0000000..0b300c5
--- /dev/null
+++ b/crates/yt/tests/subscriptions/mod.rs
@@ -0,0 +1,12 @@
+// 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>.
+
+mod import_export;
+mod naming_subscriptions;
diff --git a/crates/yt/tests/subscriptions/naming_subscriptions/golden.txt b/crates/yt/tests/subscriptions/naming_subscriptions/golden.txt
new file mode 100644
index 0000000..46ede50
--- /dev/null
+++ b/crates/yt/tests/subscriptions/naming_subscriptions/golden.txt
@@ -0,0 +1,2 @@
+Nyheter: 'https://www.svtplay.se/nyheter-pa-latt-svenska'
+Vewn: 'https://www.ardmediathek.de/sendung/tagesschau/Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXU'
diff --git a/crates/yt/tests/subscriptions/naming_subscriptions/golden.txt.license b/crates/yt/tests/subscriptions/naming_subscriptions/golden.txt.license
new file mode 100644
index 0000000..7813eb6
--- /dev/null
+++ b/crates/yt/tests/subscriptions/naming_subscriptions/golden.txt.license
@@ -0,0 +1,9 @@
+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/tests/subscriptions/naming_subscriptions/mod.rs b/crates/yt/tests/subscriptions/naming_subscriptions/mod.rs
new file mode 100644
index 0000000..50fe3e4
--- /dev/null
+++ b/crates/yt/tests/subscriptions/naming_subscriptions/mod.rs
@@ -0,0 +1,33 @@
+// 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::testenv::TestEnv;
+
+#[test]
+fn test_naming_subscriptions() {
+    let env = TestEnv::new(module_path!());
+
+    env.run(&[
+        "subs",
+        "add",
+        "https://www.svtplay.se/nyheter-pa-latt-svenska",
+        "--name",
+        "Nyheter",
+    ]);
+    env.run(&[
+        "subs",
+        "add",
+        "https://www.ardmediathek.de/sendung/tagesschau/Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXU",
+        "--name",
+        "Vewn",
+    ]);
+
+    env.assert_output(&["subs", "list"], include_str!("./golden.txt"));
+}
diff --git a/crates/yt/tests/tests.rs b/crates/yt/tests/tests.rs
new file mode 100644
index 0000000..89c3091
--- /dev/null
+++ b/crates/yt/tests/tests.rs
@@ -0,0 +1,22 @@
+// 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 this, for the background run pids
+// #![feature(linux_pidfd)]
+
+#![allow(unused_crate_dependencies)]
+
+mod _testenv;
+pub(crate) use _testenv as testenv;
+
+mod select;
+mod subscriptions;
+mod videos;
+mod watch;
diff --git a/crates/yt/tests/videos/downloading.rs b/crates/yt/tests/videos/downloading.rs
new file mode 100644
index 0000000..f026858
--- /dev/null
+++ b/crates/yt/tests/videos/downloading.rs
@@ -0,0 +1,52 @@
+// 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::{_testenv::util, testenv::TestEnv};
+
+#[test]
+fn test_downloading() {
+    let env = TestEnv::new(module_path!());
+
+    util::provide_videos(&env, util::Subscription::Tagesschau, 1);
+
+    let first_hash = &util::get_first_hash(&env);
+    env.run(&["select", "watch", first_hash]);
+
+    env.run(&["download"]);
+
+    let usage = get_cache_usage(&env);
+    assert!(usage > 0.0);
+
+    env.run(&["cache", "clear"]);
+
+    let usage = get_cache_usage(&env);
+
+    #[allow(clippy::float_cmp)]
+    {
+        assert_eq!(usage, 0.0);
+    }
+}
+
+fn get_cache_usage(env: &TestEnv) -> f64 {
+    let status = env.run(&["status", "--format", "{cache_usage}"]);
+
+    let split: Vec<_> = status.split(' ').collect();
+    let usage: f64 = split[0].parse().unwrap();
+    let unit = split[1];
+
+    #[allow(clippy::cast_precision_loss)]
+    match unit {
+        "B" => usage * (1024u64.pow(0)) as f64,
+        "KiB" => usage * (1024u64.pow(1)) as f64,
+        "MiB" => usage * (1024u64.pow(2)) as f64,
+        "GiB" => usage * (1024u64.pow(3)) as f64,
+        other => unreachable!("Unknown unit: {other}"),
+    }
+}
diff --git a/crates/yt/tests/videos/mod.rs b/crates/yt/tests/videos/mod.rs
new file mode 100644
index 0000000..6a80761
--- /dev/null
+++ b/crates/yt/tests/videos/mod.rs
@@ -0,0 +1,11 @@
+// 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>.
+
+mod downloading;
diff --git a/crates/yt/tests/watch/focus_switch.rs b/crates/yt/tests/watch/focus_switch.rs
new file mode 100644
index 0000000..81246f3
--- /dev/null
+++ b/crates/yt/tests/watch/focus_switch.rs
@@ -0,0 +1,53 @@
+// 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 yt_dlp::json_cast;
+
+use crate::{_testenv::util, testenv::TestEnv, watch::MpvControl};
+
+#[test]
+#[ignore = "Currently, this test is missing it's goal"]
+fn test_focus_switch() {
+    let mut env = TestEnv::new(module_path!());
+
+    {
+        util::provide_videos(&env, util::Subscription::Tagesschau, 32);
+
+        util::run_select(&env, "s/pick/watch/");
+
+        env.run(&["download"]);
+    }
+
+    let mut mpv = MpvControl::new(&mut env);
+
+    assert_pos(&mut mpv, 0);
+
+    for i in 1..32 {
+        mpv.assert(&["playlist-next", "weak"]);
+        assert_pos(&mut mpv, i);
+    }
+
+    mpv.assert(&["playlist-next", "weak"]);
+    assert_pos(&mut mpv, 2);
+
+    mpv.assert(&["playlist-prev", "weak"]);
+    assert_pos(&mut mpv, 1);
+
+    mpv.assert(&["playlist-prev", "weak"]);
+    assert_pos(&mut mpv, 0);
+
+    mpv.assert(&["playlist-prev", "weak"]);
+    assert_pos(&mut mpv, 0);
+}
+
+fn assert_pos(mpv: &mut MpvControl, pos: i64) {
+    let mpv_pos = mpv.assert(&["get_property", "playlist-pos"]);
+    assert_eq!(json_cast!(mpv_pos, as_i64), pos);
+}
diff --git a/crates/yt/tests/watch/mod.rs b/crates/yt/tests/watch/mod.rs
new file mode 100644
index 0000000..7af8b39
--- /dev/null
+++ b/crates/yt/tests/watch/mod.rs
@@ -0,0 +1,135 @@
+// 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::{BufRead, BufReader, Write},
+    os::unix::net::UnixStream,
+    path::PathBuf,
+    sync::atomic::AtomicU64,
+};
+
+use colors::{Colorize, IntoCanvas};
+use serde_json::json;
+use yt_dlp::{json_cast, json_get, json_try_get};
+
+use crate::_testenv::TestEnv;
+
+mod focus_switch;
+
+struct MpvControl {
+    stream: UnixStream,
+    current_request_id: AtomicU64,
+    name: &'static str,
+}
+
+impl MpvControl {
+    fn new(env: &mut TestEnv) -> Self {
+        let socket_path = {
+            let stdout = env.run_background(&[
+                "watch",
+                // "--headless",
+                "--provide-ipc-socket",
+            ]);
+
+            let line = {
+                let mut buf = String::new();
+                let mut reader = BufReader::new(stdout);
+                reader.read_line(&mut buf).expect("In-memory");
+                buf
+            };
+
+            PathBuf::from(line.trim())
+        };
+
+        let stream = UnixStream::connect(&socket_path).unwrap_or_else(|e| {
+            panic!(
+                "Path to socket ('{}') should exist, but did not: {e}",
+                socket_path.display()
+            )
+        });
+
+        let mut me = Self {
+            stream,
+            name: env.name,
+            current_request_id: AtomicU64::new(0),
+        };
+
+        // Disable all events.
+        // We do not use them, and this should reduce the read load on the socket.
+        me.assert(&["disable_event", "all"]);
+
+        me
+    }
+
+    /// Run a command and assert that it ran successfully.
+    fn assert(&mut self, args: &[&str]) -> serde_json::Value {
+        let out = self.command(args);
+
+        out.unwrap_or_else(|e| panic!("`mpv {}` failed; error {e}.", args.join(" ")))
+    }
+
+    /// Run a command in mpv.
+    /// Will return true if the command ran correctly and false if not.
+    fn command(&mut self, args: &[&str]) -> Result<serde_json::Value, String> {
+        let tl_rid = self
+            .current_request_id
+            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
+
+        eprint!(
+            "{} `mpv {}`",
+            self.name.blue().italic().render(true),
+            args.join(" ")
+        );
+
+        writeln!(
+            self.stream,
+            "{}",
+            json!( { "command": args, "request_id": tl_rid })
+        )
+        .expect("Should always work");
+
+        loop {
+            let response: serde_json::Value = {
+                let mut reader = BufReader::new(&mut self.stream);
+
+                let mut buf = String::new();
+                reader.read_line(&mut buf).expect("Works");
+                serde_json::from_str(&buf).expect("Mpv only returns json")
+            };
+
+            if let Some(rid) = json_try_get!(response, "request_id", as_u64) {
+                if rid == tl_rid {
+                    let error = json_get!(response, "error", as_str);
+
+                    if error == "success" {
+                        let data: serde_json::Value = {
+                            match response.get("data") {
+                                Some(val) => val.to_owned(),
+                                None => serde_json::Value::Null,
+                            }
+                        };
+
+                        eprintln!(", {}: {data}", "output".bright_blue().render(true),);
+                        return Ok(data);
+                    }
+
+                    eprintln!(", {}: {error}", "error".bright_red().render(true));
+                    return Err(error.to_owned());
+                }
+            }
+        }
+    }
+}
+
+impl Drop for MpvControl {
+    fn drop(&mut self) {
+        self.assert(&["quit"]);
+    }
+}
diff --git a/crates/yt_dlp/Cargo.toml b/crates/yt_dlp/Cargo.toml
index 4f62eec..eb2924d 100644
--- a/crates/yt_dlp/Cargo.toml
+++ b/crates/yt_dlp/Cargo.toml
@@ -22,20 +22,13 @@ rust-version.workspace = true
 publish = true
 
 [dependencies]
-curl = "0.4.48"
-indexmap = { version = "2.9.0", default-features = false }
+curl = "0.4.49"
 log.workspace = true
-rustpython = { git = "https://github.com/RustPython/RustPython.git", features = [
-  "threading",
-  "stdlib",
-  "stdio",
-  "freeze-stdlib",
-  "importlib",
-  "ssl",
-], default-features = false }
+pyo3 = { workspace = true }
+pyo3-pylogger = { path = "crates/pyo3-pylogger" }
 serde = { workspace = true, features = ["derive"] }
 serde_json.workspace = true
-thiserror = "2.0.12"
+thiserror = "2.0.17"
 url.workspace = true
 
 [lints]
diff --git a/crates/yt_dlp/crates/pyo3-pylogger/.gitignore b/crates/yt_dlp/crates/pyo3-pylogger/.gitignore
new file mode 100644
index 0000000..733c5bc
--- /dev/null
+++ b/crates/yt_dlp/crates/pyo3-pylogger/.gitignore
@@ -0,0 +1,13 @@
+# yt - A fully featured command line YouTube client
+#
+# Copyright (C) 2025 Dylan Bobby Storey <dylan.storey@gmail.com>, cpu <daniel@binaryparadox.net>, Warren Snipes <contact@warrensnipes.dev>
+# SPDX-License-Identifier: Apache-2.0
+#
+# 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>.
+
+target
+Cargo.lock
+.idea
diff --git a/crates/yt_dlp/crates/pyo3-pylogger/Cargo.toml b/crates/yt_dlp/crates/pyo3-pylogger/Cargo.toml
new file mode 100644
index 0000000..a2676e7
--- /dev/null
+++ b/crates/yt_dlp/crates/pyo3-pylogger/Cargo.toml
@@ -0,0 +1,31 @@
+# yt - A fully featured command line YouTube client
+#
+# Copyright (C) 2025 Dylan Bobby Storey <dylan.storey@gmail.com>, cpu <daniel@binaryparadox.net>, Warren Snipes <contact@warrensnipes.dev>
+# SPDX-License-Identifier: Apache-2.0
+#
+# 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>.
+
+[package]
+name = "pyo3-pylogger"
+version = "1.9.0"
+edition = "2021"
+authors = [
+  "Dylan Bobby Storey <dylan.storey@gmail.com>",
+  "cpu <daniel@binaryparadox.net>",
+  "Warren Snipes <contact@warrensnipes.dev>",
+]
+description = "Enables `log` for pyo3 based Rust applications using the `logging` modules."
+publish = ["crates-io"]
+license = "Apache-2.0"
+readme = "README.md"
+homepage = "https://github.com/dylanbstorey/pyo3-pylogger"
+repository = "https://github.com/dylanbstorey/pyo3-pylogger"
+documentation = "https://github.com/dylanbstorey/pyo3-pylogger"
+
+[dependencies]
+pyo3 = { workspace = true }
+log = { workspace = true }
+phf = { version = "0.13", features = ["macros"] }
diff --git a/crates/yt_dlp/crates/pyo3-pylogger/LICENSE b/crates/yt_dlp/crates/pyo3-pylogger/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/crates/yt_dlp/crates/pyo3-pylogger/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/crates/yt_dlp/crates/pyo3-pylogger/README.md b/crates/yt_dlp/crates/pyo3-pylogger/README.md
new file mode 100644
index 0000000..e68903b
--- /dev/null
+++ b/crates/yt_dlp/crates/pyo3-pylogger/README.md
@@ -0,0 +1,160 @@
+<!--
+yt - A fully featured command line YouTube client
+
+Copyright (C) 2025 Dylan Bobby Storey <dylan.storey@gmail.com>, cpu <daniel@binaryparadox.net>, Warren Snipes <contact@warrensnipes.dev>
+SPDX-License-Identifier: Apache-2.0
+
+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>.
+-->
+
+# pyo3-pylogger
+
+Enables log messages for pyo3 embedded Python applications using Python's
+`logging` or module.
+
+# Features
+
+- Logging integration between Python's `logging` module and Rust's `log` crate
+- Structured logging support via the logging
+  [extra](https://docs.python.org/3/library/logging.html#logging.Logger.debug)
+  field (requires `kv` or `tracing-kv`feature)
+- Integration with Rust's `tracing` library (requires `tracing` feature)
+
+# Usage
+
+```rust
+use log::{info, warn};
+use pyo3::{ffi::c_str, prelude::*};
+fn main() {
+    // register the host handler with python logger, providing a logger target
+    pyo3_pylogger::register("example_application_py_logger");
+
+    // initialize up a logger
+    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init();
+    //just show the logger working from Rust.
+    info!("Just some normal information!");
+    warn!("Something spooky happened!");
+
+    // Ask pyo3 to set up embedded Python interpreter
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        // Python code can now `import logging` as usual
+        py.run(
+            c_str!(
+                r#"
+import logging
+logging.getLogger().setLevel(0)
+logging.debug('DEBUG')
+logging.info('INFO')
+logging.warning('WARNING')
+logging.error('ERROR')
+logging.getLogger('foo.bar.baz').info('INFO')"#
+            ),
+            None,
+            None,
+        )
+        .unwrap();
+    })
+}
+
+
+```
+
+## Outputs
+
+```bash
+[2025-03-28T01:12:29Z INFO  helloworld] Just some normal information!
+[2025-03-28T01:12:29Z WARN  helloworld] Something spooky happened!
+[2025-03-28T01:12:29Z DEBUG example_application_py_logger] DEBUG
+[2025-03-28T01:12:29Z INFO  example_application_py_logger] INFO
+[2025-03-28T01:12:29Z WARN  example_application_py_logger] WARNING
+[2025-03-28T01:12:29Z ERROR example_application_py_logger] ERROR
+[2025-03-28T01:12:29Z INFO  example_application_py_logger::foo::bar::baz] INFO
+```
+
+## Structured Logging
+
+To enable structured logging support, add the `kv` feature to your `Cargo.toml`:
+
+```toml
+[dependencies]
+pyo3-pylogger = { version = "0.4", features = ["kv"] }
+```
+
+Then you can use Python's `extra` parameter to pass structured data:
+
+```python
+logging.info("Processing order", extra={"order_id": "12345", "amount": 99.99})
+```
+
+When using a structured logging subscriber in Rust, these key-value pairs will
+be properly captured, for example:
+
+```bash
+[2025-03-28T01:12:29Z INFO  example_application_py_logger] Processing order order_id=12345 amount=99.99
+```
+
+## Tracing Support
+
+To enable integration with Rust's `tracing` library, add the `tracing` feature
+to your `Cargo.toml`:
+
+```toml
+[dependencies]
+pyo3-pylogger = { version = "0.4", default-features = false, features = ["tracing"] }
+```
+
+When the `tracing` feature is enabled, Python logs will be forwarded to the
+active tracing subscriber:
+
+```rust
+use tracing::{info, warn};
+use pyo3::{ffi::c_str, prelude::*};
+
+fn main() {
+    // Register the tracing handler with Python logger
+    pyo3_pylogger::register_tracing("example_application_py_logger");
+
+    // Initialize tracing subscriber
+    tracing_subscriber::fmt::init();
+
+    // Tracing events from Rust
+    info!("Tracing information from Rust");
+
+    // Python logging will be captured by the tracing subscriber
+    pyo3::prepare_freethreaded_python();
+    Python::with_gil(|py| {
+        py.run(
+            c_str!(
+                r#"
+import logging
+logging.getLogger().setLevel(0)
+logging.info('This will be captured by tracing')"#
+            ),
+            None,
+            None,
+        )
+        .unwrap();
+    })
+}
+```
+
+### Structured Data with Tracing
+
+The `tracing` feature automatically supports Python's `extra` field for
+structured data. However, the KV fields are json serialized and not available as
+tracing attributes. This is a limitation of the `tracing` library and is not
+specific to this crate. See
+[this issue](https://github.com/tokio-rs/tracing/issues/372) for more
+information.
+
+# Feature Flags
+
+- `kv`: Enables structured logging support via Python's `extra` fields. This
+  adds support for the `log` crate's key-value system.
+- `tracing`: Enables integration with Rust's `tracing` library.
+- `tracing-kv`: Enables structured logging support via Python's `extra` fields
+  and integration with Rust's `tracing` library.
diff --git a/crates/yt_dlp/crates/pyo3-pylogger/src/kv.rs b/crates/yt_dlp/crates/pyo3-pylogger/src/kv.rs
new file mode 100644
index 0000000..67a0c3e
--- /dev/null
+++ b/crates/yt_dlp/crates/pyo3-pylogger/src/kv.rs
@@ -0,0 +1,127 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2025 Dylan Bobby Storey <dylan.storey@gmail.com>, cpu <daniel@binaryparadox.net>, Warren Snipes <contact@warrensnipes.dev>
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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>.
+
+//! Key-Value handling module for Python LogRecord attributes.
+//!
+//! This module provides functionality to extract and handle custom key-value pairs
+//! from Python LogRecord objects, facilitating integration between Python's logging
+//! system and Rust's log crate.
+
+use pyo3::{
+    Bound, PyAny, PyResult,
+    types::{PyAnyMethods, PyDict, PyDictMethods, PyListMethods},
+};
+use std::collections::HashMap;
+
+/// A static hashset containing all standard [LogRecord](https://github.com/python/cpython/blob/8a00c9a4d2ce9d373b13f8f0a2265a65f4523293/Lib/logging/__init__.py#L286-L287) attributes defined in the CPython logging module.
+///
+/// This set is used to differentiate between standard [LogRecord](https://github.com/python/cpython/blob/8a00c9a4d2ce9d373b13f8f0a2265a65f4523293/Lib/logging/__init__.py#L286-L287) attributes and custom key-value pairs
+/// that users might add to their log records. The attributes listed here correspond to the default
+/// attributes created by Python's [makeRecord](https://github.com/python/cpython/blob/8a00c9a4d2ce9d373b13f8f0a2265a65f4523293/Lib/logging/__init__.py#L1633-L1634) function.
+pub static LOG_RECORD_KV_ATTRIBUTES: phf::Set<&'static str> = phf::phf_set! {
+    "name",
+    "msg",
+    "args",
+    "levelname",
+    "levelno",
+    "pathname",
+    "filename",
+    "module",
+    "exc_info",
+    "exc_text",
+    "stack_info",
+    "lineno",
+    "funcName",
+    "created",
+    "msecs",
+    "relativeCreated",
+    "thread",
+    "threadName",
+    "processName",
+    "process",
+    "taskName",
+};
+
+/// Extracts custom key-value pairs from a Python LogRecord object.
+///
+/// This function examines the `__dict__` of a LogRecord(https://github.com/python/cpython/blob/8a00c9a4d2ce9d373b13f8f0a2265a65f4523293/Lib/logging/__init__.py#L286-L287) object and identifies any attributes
+/// that are not part of the standard [LogRecord](https://github.com/python/cpython/blob/8a00c9a4d2ce9d373b13f8f0a2265a65f4523293/Lib/logging/__init__.py#L286-L287) attributes. These custom attributes are
+/// treated as key-value pairs for structured logging.
+///
+/// # Arguments
+/// * `record` - A reference to a Python LogRecord object
+///
+/// # Returns
+/// * `PyResult<Option<HashMap<String, pyo3::Bound<'a, pyo3::PyAny>>>>` - If custom attributes
+///   are found, returns a HashMap containing the key-value pairs. Returns None if no custom
+///   attributes are present.
+///
+/// # Note
+/// This function relies on the fact that Python will not implement new attributes on the LogRecord object.
+/// If new attributes are added, this function will not be able to filter them out and will return them as key-value pairs.
+/// In that future, [LOG_RECORD_KV_ATTRIBUTES] will need to be updated to include the new attributes.
+/// This is an unfortunate side effect of using the `__dict__` attribute to extract key-value pairs. However, there are no other ways to handle this given that CPython does not distinguish between user-provided attributes and attributes created by the logging module.
+pub fn find_kv_args<'a>(
+    record: &Bound<'a, PyAny>,
+) -> PyResult<Option<std::collections::HashMap<String, pyo3::Bound<'a, pyo3::PyAny>>>> {
+    let dict: Bound<'_, PyDict> = record.getattr("__dict__")?.extract()?;
+
+    // We can abuse the fact that Python dictionaries are ordered by insertion order to reverse iterate over the keys
+    // and stop at the first key that is not a predefined key-value pair attribute.
+    let mut kv_args: Option<HashMap<String, pyo3::Bound<'_, pyo3::PyAny>>> = None;
+
+    for item in dict.items().iter().rev() {
+        let (key, value) =
+            item.extract::<(pyo3::Bound<'_, pyo3::PyAny>, pyo3::Bound<'_, pyo3::PyAny>)>()?;
+
+        let key_str = key.to_string();
+        if LOG_RECORD_KV_ATTRIBUTES.contains(&key_str) {
+            break;
+        }
+        if kv_args.is_none() {
+            kv_args = Some(HashMap::new());
+        }
+
+        kv_args.as_mut().unwrap().insert(key_str, value);
+    }
+
+    Ok(kv_args)
+}
+
+/// A wrapper struct that implements the `log::kv::Source` trait for Python key-value pairs.
+///
+/// This struct allows Python LogRecord custom attributes to be used with Rust's
+/// structured logging system by implementing the necessary trait for key-value handling.
+///
+/// # Type Parameters
+/// * `'a` - The lifetime of the contained Python values
+pub struct KVSource<'a>(pub HashMap<String, pyo3::Bound<'a, pyo3::PyAny>>);
+
+impl log::kv::Source for KVSource<'_> {
+    /// Visits each key-value pair in the source, converting Python values to debug representations.
+    ///
+    /// # Arguments
+    /// * `visitor` - The visitor that will process each key-value pair
+    ///
+    /// # Returns
+    /// * `Result<(), log::kv::Error>` - Success if all pairs are visited successfully,
+    ///   or an error if visitation fails
+    fn visit<'kvs>(
+        &'kvs self,
+        visitor: &mut dyn log::kv::VisitSource<'kvs>,
+    ) -> Result<(), log::kv::Error> {
+        for (key, value) in &self.0 {
+            let v: log::kv::Value<'_> = log::kv::Value::from_debug(value);
+
+            visitor.visit_pair(log::kv::Key::from_str(key), v)?;
+        }
+        Ok(())
+    }
+}
diff --git a/crates/yt_dlp/crates/pyo3-pylogger/src/level.rs b/crates/yt_dlp/crates/pyo3-pylogger/src/level.rs
new file mode 100644
index 0000000..d244ef4
--- /dev/null
+++ b/crates/yt_dlp/crates/pyo3-pylogger/src/level.rs
@@ -0,0 +1,43 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2025 Dylan Bobby Storey <dylan.storey@gmail.com>, cpu <daniel@binaryparadox.net>, Warren Snipes <contact@warrensnipes.dev>
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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>.
+
+/// A wrapper type for logging levels that supports both `tracing` and `log` features.
+pub(crate) struct Level(pub log::Level);
+
+/// Converts a numeric level value to the appropriate logging Level.
+///
+/// # Arguments
+///
+/// * `level` - A u8 value representing the logging level:
+///   * 40+ = Error
+///   * 30-39 = Warn
+///   * 20-29 = Info
+///   * 10-19 = Debug
+///   * 0-9 = Trace
+///
+/// # Returns
+///
+/// Returns a `Level` wrapper containing either a `tracing::Level` or `log::Level`
+/// depending on which feature is enabled.
+pub(crate) fn get_level(level: u8) -> Level {
+    {
+        if level.ge(&40u8) {
+            Level(log::Level::Error)
+        } else if level.ge(&30u8) {
+            Level(log::Level::Warn)
+        } else if level.ge(&20u8) {
+            Level(log::Level::Info)
+        } else if level.ge(&10u8) {
+            Level(log::Level::Debug)
+        } else {
+            Level(log::Level::Trace)
+        }
+    }
+}
diff --git a/crates/yt_dlp/crates/pyo3-pylogger/src/lib.rs b/crates/yt_dlp/crates/pyo3-pylogger/src/lib.rs
new file mode 100644
index 0000000..3ecb123
--- /dev/null
+++ b/crates/yt_dlp/crates/pyo3-pylogger/src/lib.rs
@@ -0,0 +1,211 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2025 Dylan Bobby Storey <dylan.storey@gmail.com>, cpu <daniel@binaryparadox.net>, Warren Snipes <contact@warrensnipes.dev>
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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::{
+    ffi::CString,
+    sync::{self, OnceLock},
+};
+
+use log::{debug, log_enabled};
+use pyo3::{
+    Bound, Py, PyAny, PyResult, Python, pyfunction,
+    sync::OnceLockExt,
+    types::{PyAnyMethods, PyDict, PyListMethods, PyModuleMethods},
+    wrap_pyfunction,
+};
+
+mod kv;
+mod level;
+
+static LOGGER: sync::OnceLock<Py<PyAny>> = OnceLock::new();
+
+/// 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>(record: Bound<'py, 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!(log::Level::Debug) && !return_value {
+        let message: String = {
+            let get_message = record.getattr("getMessage").expect("Is set");
+            let message: String = get_message
+                .call((), None)
+                .expect("Can be called")
+                .extract()
+                .expect("Downcasting works");
+
+            message.as_str().to_owned()
+        };
+
+        debug!("Swollowed error message: '{message}'");
+    }
+    return_value
+}
+
+/// Consume a Python `logging.LogRecord` and emit a Rust `Log` instead.
+#[pyfunction]
+fn host_log(record: Bound<'_, PyAny>, rust_target: &str) -> PyResult<()> {
+    let level = record.getattr("levelno")?.extract()?;
+    let message = record.getattr("getMessage")?.call0()?.to_string();
+    let pathname = record.getattr("pathname")?.extract::<String>()?;
+    let lineno = record.getattr("lineno")?.extract::<u32>()?;
+
+    let logger_name = record.getattr("name")?.extract::<String>()?;
+
+    let full_target: Option<String> = if logger_name.trim().is_empty() || logger_name == "root" {
+        None
+    } else {
+        // Libraries (ex: tracing_subscriber::filter::Directive) expect rust-style targets like foo::bar,
+        // and may not deal well with "." as a module separator:
+        let logger_name = logger_name.replace('.', "::");
+        Some(format!("{rust_target}::{logger_name}"))
+    };
+    let target = full_target.as_deref().unwrap_or(rust_target);
+
+    handle_record(record, target, &message, lineno, &pathname, level)?;
+
+    Ok(())
+}
+
+fn handle_record(
+    #[allow(unused_variables)] record: Bound<'_, PyAny>,
+    target: &str,
+    message: &str,
+    lineno: u32,
+    pathname: &str,
+    level: u8,
+) -> PyResult<()> {
+    // If log feature is enabled, use log::logger
+    let level = crate::level::get_level(level).0;
+
+    {
+        let mut metadata_builder = log::MetadataBuilder::new();
+        metadata_builder.target(target);
+        metadata_builder.level(level);
+
+        let mut record_builder = log::Record::builder();
+
+        {
+            let kv_args = kv::find_kv_args(&record)?;
+
+            let kv_source = kv_args.map(kv::KVSource);
+            if let Some(kv_source) = kv_source {
+                log::logger().log(
+                    &record_builder
+                        .metadata(metadata_builder.build())
+                        .args(format_args!("{}", &message))
+                        .line(Some(lineno))
+                        .file(Some(pathname))
+                        .module_path(Some(pathname))
+                        .key_values(&kv_source)
+                        .build(),
+                );
+                return Ok(());
+            }
+        }
+
+        log::logger().log(
+            &record_builder
+                .metadata(metadata_builder.build())
+                .args(format_args!("{}", &message))
+                .line(Some(lineno))
+                .file(Some(pathname))
+                .module_path(Some(pathname))
+                .build(),
+        );
+    }
+
+    Ok(())
+}
+
+/// Registers the host_log function in rust as the event handler for Python's logging logger
+/// This function needs to be called from within a pyo3 context as early as possible to ensure logging messages
+/// arrive to the rust consumer.
+pub fn setup_logging<'py>(py: Python<'py>, target: &str) -> PyResult<Bound<'py, PyAny>> {
+    let logger = LOGGER
+        .get_or_init_py_attached(py, || match setup_logging_inner(py, target) {
+            Ok(ok) => ok.unbind(),
+            Err(err) => {
+                panic!("Failed to initialize logger: {}", err);
+            }
+        })
+        .clone_ref(py);
+
+    Ok(logger.into_bound(py))
+}
+
+fn setup_logging_inner<'py>(py: Python<'py>, target: &str) -> PyResult<Bound<'py, PyAny>> {
+    let logging = py.import("logging")?;
+
+    logging.setattr("host_log", wrap_pyfunction!(host_log, &logging)?)?;
+
+    #[allow(clippy::uninlined_format_args)]
+    let code = CString::new(format!(
+        r#"
+class HostHandler(Handler):
+	def __init__(self, level=0):
+		super().__init__(level=level)
+
+	def emit(self, record: LogRecord):
+		host_log(record, "{}")
+
+oldBasicConfig = basicConfig
+def basicConfig(*pargs, **kwargs):
+    if "handlers" not in kwargs:
+        kwargs["handlers"] = [HostHandler()]
+    return oldBasicConfig(*pargs, **kwargs)
+"#,
+        target
+    ))?;
+
+    let logging_scope = logging.dict();
+    py.run(&code, Some(&logging_scope), None)?;
+
+    let all = logging.index()?;
+    all.append("HostHandler")?;
+
+    let logger = {
+        let get_logger = logging_scope.get_item("getLogger")?;
+        get_logger.call((target,), None)?
+    };
+
+    {
+        let basic_config = logging_scope.get_item("basicConfig")?;
+        basic_config.call(
+            (),
+            {
+                let dict = PyDict::new(py);
+
+                // Ensure that all events are logged by setting
+                // the log level to NOTSET (we filter on rust's side)
+                dict.set_item("level", 0)?;
+
+                Some(dict)
+            }
+            .as_ref(),
+        )?;
+    }
+
+    {
+        let add_filter = logger.getattr("addFilter")?;
+        add_filter.call((wrap_pyfunction!(filter_error_log, &logging)?,), None)?;
+    }
+
+    Ok(logger)
+}
diff --git a/crates/bytes/update.sh b/crates/yt_dlp/crates/pyo3-pylogger/update.sh
index c1a0215..dd3e57e 100755
--- a/crates/bytes/update.sh
+++ b/crates/yt_dlp/crates/pyo3-pylogger/update.sh
@@ -1,9 +1,9 @@
-#!/usr/bin/env sh
+#! /usr/bin/env sh
 
 # 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
+# Copyright (C) 2025 Dylan Bobby Storey <dylan.storey@gmail.com>, cpu <daniel@binaryparadox.net>, Warren Snipes <contact@warrensnipes.dev>
+# SPDX-License-Identifier: Apache-2.0
 #
 # This file is part of Yt.
 #
@@ -13,3 +13,5 @@
 cd "$(dirname "$0")" || exit 1
 [ "$1" = "upgrade" ] && cargo upgrade --incompatible
 cargo update
+
+# vim: ft=sh
diff --git a/crates/yt_dlp/examples/main.rs b/crates/yt_dlp/examples/main.rs
new file mode 100644
index 0000000..e924407
--- /dev/null
+++ b/crates/yt_dlp/examples/main.rs
@@ -0,0 +1,15 @@
+// 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>.
+
+fn main() {
+    let yt_dlp = yt_dlp::options::YoutubeDLOptions::new().build().unwrap();
+
+    dbg!(yt_dlp.version().unwrap());
+}
diff --git a/crates/yt_dlp/src/info_json.rs b/crates/yt_dlp/src/info_json.rs
index 31f4a69..402acb4 100644
--- a/crates/yt_dlp/src/info_json.rs
+++ b/crates/yt_dlp/src/info_json.rs
@@ -8,50 +8,46 @@
 // 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 rustpython::vm::{
-    PyRef, VirtualMachine,
-    builtins::{PyDict, PyStr},
+use pyo3::{
+    Bound, Python, intern,
+    types::{PyAnyMethods, PyDict},
 };
 
 pub type InfoJson = serde_json::Map<String, serde_json::Value>;
 
+/// # Panics
+/// If expectation about python operations fail.
+#[must_use]
 pub 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");
+    py: Python<'_>,
+) -> Bound<'_, PyDict> {
+    let json = py.import(intern!(py, "json")).expect("Module exists");
+    let loads = json.getattr(intern!(py, "loads")).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)
+        .call((self_str,), None)
         .expect("Vaild json is always a valid dict");
 
-    dict.downcast().expect("Should always be a dict")
+    dict.cast_into().expect("Should always be a dict")
 }
 
 /// # 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");
+#[must_use]
+pub fn json_dumps(input: &Bound<'_, PyDict>) -> serde_json::Map<String, serde_json::Value> {
+    let py = input.py();
+
+    let json = py.import(intern!(py, "json")).expect("Module exists");
+    let dumps = json.getattr(intern!(py, "dumps")).expect("Method exists");
     let dict = dumps
-        .call((input,), vm)
-        .map_err(|err| vm.print_exception(err))
+        .call((input,), None)
+        .map_err(|err| err.print(py))
         .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 string: String = dict.extract().expect("Should always be a string");
 
-    let value: serde_json::Value = serde_json::from_str(real_string).expect("Should be valid json");
+    let value: serde_json::Value = serde_json::from_str(&string).expect("Should be valid json");
 
     match value {
         serde_json::Value::Object(map) => map,
diff --git a/crates/yt_dlp/src/lib.rs b/crates/yt_dlp/src/lib.rs
index a1db606..4b252de 100644
--- a/crates/yt_dlp/src/lib.rs
+++ b/crates/yt_dlp/src/lib.rs
@@ -12,18 +12,16 @@
 
 use std::path::PathBuf;
 
-use indexmap::IndexMap;
-use log::info;
-use rustpython::vm::{
-    Interpreter, PyObjectRef, PyRef, VirtualMachine,
-    builtins::{PyDict, PyList, PyStr},
-    function::{FuncArgs, KwArgs, PosArgs},
+use log::{debug, info};
+use pyo3::{
+    Bound, Py, PyAny, Python, intern,
+    types::{PyAnyMethods, PyDict, PyIterator, PyList},
 };
 use url::Url;
 
 use crate::{
     info_json::{InfoJson, json_dumps, json_loads},
-    python_error::PythonError,
+    python_error::{IntoPythonError, PythonError},
 };
 
 pub mod info_json;
@@ -32,18 +30,16 @@ pub mod post_processors;
 pub mod progress_hook;
 pub mod python_error;
 
-mod logging;
-
 #[macro_export]
 macro_rules! json_get {
     ($value:expr, $name:literal, $into:ident) => {{
         match $value.get($name) {
-            Some(val) => $crate::json_cast!(val, $into),
+            Some(val) => $crate::json_cast!(@log_key $name, val, $into),
             None => panic!(
                 concat!(
                     "Expected '",
                     $name,
-                    "' to be a key for the'",
+                    "' to be a key for the '",
                     stringify!($value),
                     "' object: {:#?}"
                 ),
@@ -54,51 +50,86 @@ macro_rules! json_get {
 }
 
 #[macro_export]
+macro_rules! json_try_get {
+    ($value:expr, $name:literal, $into:ident) => {{
+        if let Some(val) = $value.get($name) {
+            if val.is_null() {
+                None
+            } else {
+                Some(json_cast!(@log_key $name, val, $into))
+            }
+        } else {
+            None
+        }
+    }};
+}
+
+#[macro_export]
 macro_rules! json_cast {
     ($value:expr, $into:ident) => {{
+        let value_name = stringify!($value);
+        json_cast!(@log_key value_name, $value, $into)
+    }};
+
+    (@log_key $name:expr, $value:expr, $into:ident) => {{
         match $value.$into() {
             Some(result) => result,
             None => panic!(
                 concat!(
-                    "Expected to be able to cast value ({:#?}) ",
+                    "Expected to be able to cast '{}' value (which is '{:?}') ",
                     stringify!($into)
                 ),
+                $name,
                 $value
             ),
         }
     }};
 }
 
+macro_rules! py_kw_args {
+    ($py:expr => $($kw_arg_name:ident = $kw_arg_val:expr),*) => {{
+        use $crate::python_error::IntoPythonError;
+
+        let dict = PyDict::new($py);
+
+        $(
+            dict.set_item(stringify!($kw_arg_name), $kw_arg_val).wrap_exc($py)?;
+        )*
+
+        Some(dict)
+    }
+    .as_ref()};
+}
+pub(crate) use py_kw_args;
+
 /// The core of the `yt_dlp` interface.
+#[derive(Debug)]
 pub struct YoutubeDL {
-    interpreter: Interpreter,
-    youtube_dl_class: PyObjectRef,
-    yt_dlp_module: PyObjectRef,
+    inner: Py<PyAny>,
     options: serde_json::Map<String, serde_json::Value>,
 }
 
-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")
-    }
-}
-
 impl YoutubeDL {
-    /// # Panics
+    /// Fetch the underlying `yt_dlp` and `python` version.
     ///
-    /// 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()
+    /// # Errors
+    /// If python attribute access fails.
+    pub fn version(&self) -> Result<(String, String), PythonError> {
+        Python::attach(|py| {
+            let yt_dlp = py
+                .import(intern!(py, "yt_dlp"))
+                .wrap_exc(py)?
+                .getattr(intern!(py, "version"))
+                .wrap_exc(py)?
+                .getattr(intern!(py, "__version__"))
+                .wrap_exc(py)?
+                .extract()
+                .wrap_exc(py)?;
+
+            let python = py.version();
+
+            Ok((yt_dlp, python.to_owned()))
+        })
     }
 
     /// Download a given list of URLs.
@@ -114,8 +145,9 @@ impl YoutubeDL {
             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))
+            let result_string = if let Some(filename) = json_try_get!(info_json, "filename", as_str)
+            {
+                PathBuf::from(filename)
             } else {
                 PathBuf::from(json_get!(
                     json_cast!(
@@ -157,55 +189,63 @@ impl YoutubeDL {
         download: bool,
         process: bool,
     ) -> Result<InfoJson, extract_info::Error> {
-        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);
-
+        Python::attach(|py| {
             let inner = self
-                .youtube_dl_class
-                .get_attr("extract_info", vm)
-                .map_err(|exc| PythonError::from_exception(vm, &exc))?;
+                .inner
+                .bind(py)
+                .getattr(intern!(py, "extract_info"))
+                .wrap_exc(py)?;
+
             let result = inner
-                .call_with_args(fun_args, vm)
-                .map_err(|exc| PythonError::from_exception(vm, &exc))?
-                .downcast::<PyDict>()
+                .call(
+                    (url.to_string(),),
+                    py_kw_args!(py => download = download, process = process),
+                )
+                .wrap_exc(py)?
+                .cast_into::<PyDict>()
                 .expect("This is a dict");
 
             // Resolve the generator object
-            if let Ok(generator) = result.get_item("entries", vm) {
-                if generator.payload_is::<PyList>() {
+            if let Ok(generator) = result.get_item(intern!(py, "entries")) {
+                if generator.is_instance_of::<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")
-                    });
+                } else if let Ok(generator) = generator.cast::<PyIterator>() {
+                    // A python generator object.
+                    let max_backlog = json_try_get!(self.options, "playlistend", as_u64)
+                        .map_or(10000, |playlistend| {
+                            usize::try_from(playlistend).expect("Should work")
+                        });
 
                     let mut out = vec![];
-                    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);
+                    for output in generator {
+                        out.push(output.wrap_exc(py)?);
 
                         if out.len() == max_backlog {
                             break;
                         }
                     }
+
+                    result.set_item(intern!(py, "entries"), out).wrap_exc(py)?;
+                } else {
+                    // Probably some sort of paged list (`OnDemand` or otherwise)
+                    let max_backlog = json_try_get!(self.options, "playlistend", as_u64)
+                        .map_or(10000, |playlistend| {
+                            usize::try_from(playlistend).expect("Should work")
+                        });
+
+                    let next = generator.getattr(intern!(py, "getslice")).wrap_exc(py)?;
+
+                    let output = next
+                        .call((), py_kw_args!(py => start = 0, end = max_backlog))
+                        .wrap_exc(py)?;
+
                     result
-                        .set_item("entries", vm.new_pyobj(out), vm)
-                        .map_err(|exc| PythonError::from_exception(vm, &exc))?;
+                        .set_item(intern!(py, "entries"), output)
+                        .wrap_exc(py)?;
                 }
             }
 
-            let result = self.prepare_info_json(result, vm)?;
+            let result = self.prepare_info_json(&result, py)?;
 
             Ok(result)
         })
@@ -229,54 +269,78 @@ impl YoutubeDL {
         ie_result: InfoJson,
         download: bool,
     ) -> Result<InfoJson, process_ie_result::Error> {
-        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);
-
+        Python::attach(|py| {
             let inner = self
-                .youtube_dl_class
-                .get_attr("process_ie_result", vm)
-                .map_err(|exc| PythonError::from_exception(vm, &exc))?;
+                .inner
+                .bind(py)
+                .getattr(intern!(py, "process_ie_result"))
+                .wrap_exc(py)?;
+
             let result = inner
-                .call_with_args(fun_args, vm)
-                .map_err(|exc| PythonError::from_exception(vm, &exc))?
-                .downcast::<PyDict>()
+                .call(
+                    (json_loads(ie_result, py),),
+                    py_kw_args!(py => download = download),
+                )
+                .wrap_exc(py)?
+                .cast_into::<PyDict>()
                 .expect("This is a dict");
 
-            let result = self.prepare_info_json(result, vm)?;
+            let result = self.prepare_info_json(&result, py)?;
 
             Ok(result)
         })
     }
 
-    fn prepare_info_json(
+    /// Close this [`YoutubeDL`] instance, and stop all currently running downloads.
+    ///
+    /// # Errors
+    /// If python operations fail.
+    pub fn close(&self) -> Result<(), close::Error> {
+        Python::attach(|py| {
+            debug!("Closing YoutubeDL.");
+
+            let inner = self
+                .inner
+                .bind(py)
+                .getattr(intern!(py, "close"))
+                .wrap_exc(py)?;
+
+            inner.call0().wrap_exc(py)?;
+
+            Ok(())
+        })
+    }
+
+    fn prepare_info_json<'py>(
         &self,
-        info: PyRef<PyDict>,
-        vm: &VirtualMachine,
+        info: &Bound<'py, PyDict>,
+        py: Python<'py>,
     ) -> Result<InfoJson, prepare::Error> {
         let sanitize = self
-            .youtube_dl_class
-            .get_attr("sanitize_info", vm)
-            .map_err(|exc| PythonError::from_exception(vm, &exc))?;
+            .inner
+            .bind(py)
+            .getattr(intern!(py, "sanitize_info"))
+            .wrap_exc(py)?;
 
-        let value = sanitize
-            .call((info,), vm)
-            .map_err(|exc| PythonError::from_exception(vm, &exc))?;
+        let value = sanitize.call((info,), None).wrap_exc(py)?;
 
-        let result = value.downcast::<PyDict>().expect("This should stay a dict");
+        let result = value.cast::<PyDict>().expect("This should stay a dict");
 
-        Ok(json_dumps(result, vm))
+        Ok(json_dumps(result))
     }
 }
 
 #[allow(missing_docs)]
+pub mod close {
+    use crate::python_error::PythonError;
+
+    #[derive(Debug, thiserror::Error)]
+    pub enum Error {
+        #[error(transparent)]
+        Python(#[from] PythonError),
+    }
+}
+#[allow(missing_docs)]
 pub mod process_ie_result {
     use crate::{prepare, python_error::PythonError};
 
diff --git a/crates/yt_dlp/src/logging.rs b/crates/yt_dlp/src/logging.rs
deleted file mode 100644
index 112836e..0000000
--- a/crates/yt_dlp/src/logging.rs
+++ /dev/null
@@ -1,171 +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>.
-
-// 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, 2025
-
-use log::{Level, MetadataBuilder, Record, logger};
-use rustpython::vm::{
-    PyObjectRef, PyRef, PyResult, VirtualMachine,
-    builtins::{PyInt, PyStr},
-    convert::ToPyObject,
-    function::FuncArgs,
-};
-
-/// Consume a Python `logging.LogRecord` and emit a Rust `Log` instead.
-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
-    } else {
-        // Libraries (ex: tracing_subscriber::filter::Directive) expect rust-style targets like foo::bar,
-        // and may not deal well with "." as a module separator:
-        let logger_name = logger_name.replace('.', "::");
-        Some(format!("{rust_target}::{logger_name}"))
-    };
-
-    let target = full_target.as_deref().unwrap_or(&rust_target);
-
-    // error
-    let error_metadata = if level >= 40 {
-        MetadataBuilder::new()
-            .target(target)
-            .level(Level::Error)
-            .build()
-    } else if level >= 30 {
-        MetadataBuilder::new()
-            .target(target)
-            .level(Level::Warn)
-            .build()
-    } else if level >= 20 {
-        MetadataBuilder::new()
-            .target(target)
-            .level(Level::Info)
-            .build()
-    } else if level >= 10 {
-        MetadataBuilder::new()
-            .target(target)
-            .level(Level::Debug)
-            .build()
-    } else {
-        MetadataBuilder::new()
-            .target(target)
-            .level(Level::Trace)
-            .build()
-    };
-
-    logger().log(
-        &Record::builder()
-            .metadata(error_metadata)
-            .args(format_args!("{}", &message))
-            .line(Some(lineno))
-            .file(None)
-            .module_path(Some(&pathname))
-            .build(),
-    );
-
-    Ok(())
-}
-
-/// Registers the `host_log` function in rust as the event handler for Python's logging logger
-/// This function needs to be called from within a pyo3 context as early as possible to ensure logging messages
-/// arrive to the rust consumer.
-///
-/// # Panics
-/// Only if internal assertions fail.
-#[allow(clippy::module_name_repetitions)]
-pub(super) fn setup_logging(vm: &VirtualMachine, target: &str) -> PyResult<PyObjectRef> {
-    let logging = vm.import("logging", 0)?;
-
-    let scope = vm.new_scope_with_builtins();
-
-    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):
-        super().__init__(level=level)
-
-    def emit(self, record):
-        host_log(record,"{target}")
-
-oldBasicConfig = basicConfig
-def basicConfig(*pargs, **kwargs):
-    if "handlers" not in kwargs:
-        kwargs["handlers"] = [HostHandler()]
-    return oldBasicConfig(*pargs, **kwargs)
-"#
-        )
-        .as_str(),
-        "<embedded logging inintializing code>".to_owned(),
-    )?;
-
-    Ok(scope.globals.to_pyobject(vm))
-}
diff --git a/crates/yt_dlp/src/options.rs b/crates/yt_dlp/src/options.rs
index 182b8a1..4b8906e 100644
--- a/crates/yt_dlp/src/options.rs
+++ b/crates/yt_dlp/src/options.rs
@@ -8,27 +8,21 @@
 // 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::env;
+use std::sync;
 
-use indexmap::IndexMap;
-use log::{Level, debug, error, log_enabled};
-use rustpython::{
-    InterpreterConfig,
-    vm::{
-        self, PyObjectRef, PyRef, PyResult, VirtualMachine,
-        builtins::{PyBaseException, PyStr},
-        function::{FuncArgs, KwArgs, PosArgs},
-    },
+use pyo3::{
+    Bound, IntoPyObjectExt, PyAny, PyResult, Python, intern,
+    types::{PyAnyMethods, PyCFunction, PyDict, PyTuple},
 };
+use pyo3_pylogger::setup_logging;
 
 use crate::{
-    YoutubeDL, json_loads, logging::setup_logging, post_processors, python_error::process_exception,
+    YoutubeDL, json_loads, post_processors, py_kw_args,
+    python_error::{IntoPythonError, PythonError},
 };
 
-/// Wrap your function with [`mk_python_function`].
-pub type ProgressHookFunction = fn(input: FuncArgs, vm: &VirtualMachine);
-
-pub type PostProcessorFunction = fn(vm: &VirtualMachine) -> PyResult<PyObjectRef>;
+pub type ProgressHookFunction = fn(py: Python<'_>) -> PyResult<Bound<'_, PyCFunction>>;
+pub type PostProcessorFunction = fn(py: Python<'_>) -> PyResult<Bound<'_, PyAny>>;
 
 /// Options, that are used to customize the download behaviour.
 ///
@@ -110,47 +104,36 @@ impl YoutubeDL {
     /// If a python call fails.
     #[allow(clippy::too_many_lines)]
     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(':') {
-                settings.path_list.push(path.to_owned());
-            }
-        } else {
-            error!(
-                "No PYTHONPATH found or invalid utf8. \
-                This means, that you probably did not \
-                supply a yt_dlp python package!"
-            );
-        }
-
-        settings.install_signal_handlers = false;
-
-        // NOTE(@bpeetz): Another value leads to an internal codegen error. <2025-06-13>
-        settings.optimize = 0;
-
-        settings.isolated = true;
-
-        let interpreter = InterpreterConfig::new()
-            .init_stdlib()
-            .settings(settings)
-            .interpreter();
+        Python::initialize();
 
         let output_options = options.options.clone();
 
-        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 yt_dlp_module = Python::attach(|py| {
+            let opts = json_loads(options.options, py);
 
-            let opts = json_loads(options.options, vm);
+            {
+                static CALL_ONCE: sync::Once = sync::Once::new();
+
+                CALL_ONCE.call_once(|| {
+                    py.run(
+                        c"
+import signal
+signal.signal(signal.SIGINT, signal.SIG_DFL)
+              ",
+                        None,
+                        None,
+                    )
+                    .unwrap_or_else(|err| {
+                        panic!("Failed to disable python signal handling: {err}")
+                    });
+                });
+            }
 
             {
                 // Setup the progress hook
-                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])
-                    })
-                    .expect("Should work?");
+                if let Some(ph) = options.progress_hook {
+                    opts.set_item(intern!(py, "progress_hooks"), vec![ph(py).wrap_exc(py)?])
+                        .wrap_exc(py)?;
                 }
             }
 
@@ -158,113 +141,55 @@ impl YoutubeDL {
                 // 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()
-                        };
+                let ytdl_logger = setup_logging(py, "yt_dlp").wrap_exc(py)?;
 
-                        debug!("Swollowed error message: '{message}'");
-                    }
-                    return_value
-                }
-
-                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)?;
-                }
-
-                {
-                    let add_filter = ytdl_logger.get_attr("addFilter", vm)?;
-                    add_filter.call(
-                        (vm.new_function("yt_dlp_error_filter", filter_error_log),),
-                        vm,
-                    )?;
-                }
-
-                opts.set_item("logger", ytdl_logger, vm)?;
+                opts.set_item(intern!(py, "logger"), ytdl_logger)
+                    .wrap_exc(py)?;
             }
 
-            let youtube_dl_class = class.call((opts,), vm)?;
+            let inner = {
+                let p_params = opts.into_bound_py_any(py).wrap_exc(py)?;
+                let p_auto_init = true.into_bound_py_any(py).wrap_exc(py)?;
+
+                py.import(intern!(py, "yt_dlp.YoutubeDL"))
+                    .wrap_exc(py)?
+                    .getattr(intern!(py, "YoutubeDL"))
+                    .wrap_exc(py)?
+                    .call1(
+                        PyTuple::new(
+                            py,
+                            [
+                                p_params.into_bound_py_any(py).wrap_exc(py)?,
+                                p_auto_init.into_bound_py_any(py).wrap_exc(py)?,
+                            ],
+                        )
+                        .wrap_exc(py)?,
+                    )
+                    .wrap_exc(py)?
+            };
 
             {
                 // Setup the post processors
-
-                let add_post_processor_fun = youtube_dl_class.get_attr("add_post_processor", vm)?;
+                let add_post_processor_fun = inner
+                    .getattr(intern!(py, "add_post_processor"))
+                    .wrap_exc(py)?;
 
                 for pp in options.post_processors {
-                    let args = {
-                        FuncArgs::new(
-                            PosArgs::new(vec![pp(vm)?]),
-                            KwArgs::new({
-                                let mut map = IndexMap::new();
-                                //  "when" can take any value in yt_dlp.utils.POSTPROCESS_WHEN
-                                map.insert("when".to_owned(), vm.new_pyobj("pre_process"));
-                                map
-                            }),
+                    add_post_processor_fun
+                        .call(
+                            (pp(py).wrap_exc(py)?.into_bound_py_any(py).wrap_exc(py)?,),
+                            // "when" can take any value in yt_dlp.utils.POSTPROCESS_WHEN
+                            py_kw_args!(py => when = "pre_process"),
                         )
-                    };
-
-                    add_post_processor_fun.call(args, vm)?;
+                        .wrap_exc(py)?;
                 }
             }
 
-            Ok::<_, PyRef<PyBaseException>>((yt_dlp_module, youtube_dl_class))
-        }) {
-            Ok(ok) => Ok(ok),
-            Err(err) => {
-                // TODO(@bpeetz): Do we want to run `interpreter.finalize` here? <2025-06-14>
-                // interpreter.finalize(Some(err));
-                interpreter.enter(|vm| {
-                    let buffer = process_exception(vm, &err);
-                    Err(build::Error::Python(buffer))
-                })
-            }
-        }?;
+            Ok::<_, PythonError>(inner.unbind())
+        })?;
 
         Ok(Self {
-            interpreter,
-            youtube_dl_class,
-            yt_dlp_module,
+            inner: yt_dlp_module,
             options: output_options,
         })
     }
@@ -272,9 +197,11 @@ impl YoutubeDL {
 
 #[allow(missing_docs)]
 pub mod build {
+    use crate::python_error::PythonError;
+
     #[derive(Debug, thiserror::Error)]
     pub enum Error {
-        #[error("Python threw an exception: {0}")]
-        Python(String),
+        #[error(transparent)]
+        Python(#[from] PythonError),
     }
 }
diff --git a/crates/yt_dlp/src/post_processors/dearrow.rs b/crates/yt_dlp/src/post_processors/dearrow.rs
index ab5478b..7787d68 100644
--- a/crates/yt_dlp/src/post_processors/dearrow.rs
+++ b/crates/yt_dlp/src/post_processors/dearrow.rs
@@ -9,58 +9,115 @@
 // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 
 use curl::easy::Easy;
-use log::{error, info, warn};
-use rustpython::vm::{
-    PyRef, VirtualMachine,
-    builtins::{PyDict, PyStr},
+use log::{error, info, trace, warn};
+use pyo3::{
+    Bound, PyAny, PyErr, PyResult, Python, exceptions, intern, pyfunction,
+    types::{PyAnyMethods, PyDict, PyModule},
+    wrap_pyfunction,
 };
 use serde::{Deserialize, Serialize};
 
-use crate::{pydict_cast, pydict_get, wrap_post_processor};
+use crate::{
+    pydict_cast, pydict_get,
+    python_error::{IntoPythonError, PythonError},
+};
 
-wrap_post_processor!("DeArrow", unwrapped_process, process);
+/// # Errors
+/// - If the underlying function returns an error.
+/// - If python operations fail.
+pub fn process(py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
+    #[pyfunction]
+    fn actual_processor(info_json: Bound<'_, PyDict>) -> PyResult<Bound<'_, PyDict>> {
+        let output = match unwrapped_process(info_json) {
+            Ok(ok) => ok,
+            Err(err) => {
+                return Err(PyErr::new::<exceptions::PyRuntimeError, _>(err.to_string()));
+            }
+        };
+        Ok(output)
+    }
+
+    let module = PyModule::new(py, "rust_post_processors")?;
+    let scope = PyDict::new(py);
+    scope.set_item(
+        intern!(py, "actual_processor"),
+        wrap_pyfunction!(actual_processor, module)?,
+    )?;
+    py.run(
+        c"
+import yt_dlp
+
+class DeArrow(yt_dlp.postprocessor.PostProcessor):
+    def run(self, info):
+        info = actual_processor(info)
+        return [], info
+
+inst = DeArrow()
+",
+        Some(&scope),
+        None,
+    )?;
+
+    Ok(scope.get_item(intern!(py, "inst"))?.cast_into()?)
+}
 
 /// # Errors
 /// If the API access fails.
-pub fn unwrapped_process(info: PyRef<PyDict>, vm: &VirtualMachine) -> Result<PyRef<PyDict>, Error> {
-    if pydict_get!(@vm, info, "extractor_key", PyStr).as_str() != "Youtube" {
-        warn!("DeArrow: Extractor did not match, exiting.");
+pub fn unwrapped_process(info: Bound<'_, PyDict>) -> Result<Bound<'_, PyDict>, Error> {
+    if pydict_get!(info, "extractor_key", String).as_str() != "Youtube" {
         return Ok(info);
     }
 
+    let mut retry_num = 3;
     let mut output: DeArrowApi = {
-        let output_bytes = {
-            let mut dst = Vec::new();
-
-            let mut easy = Easy::new();
-            easy.url(
-                format!(
-                    "https://sponsor.ajay.app/api/branding?videoID={}",
-                    pydict_get!(@vm, info, "id", PyStr).as_str()
-                )
-                .as_str(),
-            )?;
-
-            let mut transfer = easy.transfer();
-            transfer.write_function(|data| {
-                dst.extend_from_slice(data);
-                Ok(data.len())
-            })?;
-            transfer.perform()?;
-            drop(transfer);
-
-            dst
-        };
-
-        serde_json::from_slice(&output_bytes)?
+        loop {
+            let output_bytes = {
+                let mut dst = Vec::new();
+
+                let mut easy = Easy::new();
+                easy.url(
+                    format!(
+                        "https://sponsor.ajay.app/api/branding?videoID={}",
+                        pydict_get!(info, "id", String)
+                    )
+                    .as_str(),
+                )?;
+
+                let mut transfer = easy.transfer();
+                transfer.write_function(|data| {
+                    dst.extend_from_slice(data);
+                    Ok(data.len())
+                })?;
+                transfer.perform()?;
+                drop(transfer);
+
+                dst
+            };
+
+            match serde_json::from_slice(&output_bytes) {
+                Ok(ok) => break ok,
+                Err(err) => {
+                    if retry_num > 0 {
+                        trace!(
+                            "DeArrow: Api access failed, trying again ({retry_num} retries left)"
+                        );
+                        retry_num -= 1;
+                    } else {
+                        let err: serde_json::Error = err;
+                        return Err(err.into());
+                    }
+                }
+            }
+        }
     };
 
     // We pop the titles, so we need this vector reversed.
     output.titles.reverse();
 
     let title_len = output.titles.len();
+    let mut iterator = output.titles.clone();
     let selected = loop {
-        let Some(title) = output.titles.pop() else {
+        let Some(title) = iterator.pop() else {
             break false;
         };
 
@@ -73,7 +130,7 @@ pub fn unwrapped_process(info: PyRef<PyDict>, vm: &VirtualMachine) -> Result<PyR
             continue;
         }
 
-        update_title(&info, &title.value, vm);
+        update_title(&info, &title.value).wrap_exc(info.py())?;
 
         break true;
     };
@@ -81,7 +138,7 @@ pub fn unwrapped_process(info: PyRef<PyDict>, vm: &VirtualMachine) -> Result<PyR
     if !selected && title_len != 0 {
         // No title was selected, even though we had some titles.
         // Just pick the first one in this case.
-        update_title(&info, &output.titles[0].value, vm);
+        update_title(&info, &output.titles[0].value).wrap_exc(info.py())?;
     }
 
     Ok(info)
@@ -89,6 +146,9 @@ pub fn unwrapped_process(info: PyRef<PyDict>, vm: &VirtualMachine) -> Result<PyR
 
 #[derive(thiserror::Error, Debug)]
 pub enum Error {
+    #[error(transparent)]
+    Python(#[from] PythonError),
+
     #[error("Failed to access the DeArrow api: {0}")]
     Get(#[from] curl::Error),
 
@@ -96,17 +156,19 @@ pub enum Error {
     Deserialize(#[from] serde_json::Error),
 }
 
-fn update_title(info: &PyRef<PyDict>, new_title: &str, vm: &VirtualMachine) {
-    assert!(!info.contains_key("original_title", vm));
+fn update_title(info: &Bound<'_, PyDict>, new_title: &str) -> PyResult<()> {
+    let py = info.py();
 
-    if let Ok(old_title) = info.get_item("title", vm) {
+    assert!(!info.contains(intern!(py, "original_title"))?);
+
+    if let Ok(old_title) = info.get_item(intern!(py, "title")) {
         warn!(
             "DeArrow: Updating title from {:#?} to {:#?}",
-            pydict_cast!(@ref old_title, PyStr).as_str(),
+            pydict_cast!(old_title, &str),
             new_title
         );
 
-        info.set_item("original_title", old_title, vm)
+        info.set_item(intern!(py, "original_title"), old_title)
             .expect("We checked, it is a new key");
     } else {
         warn!("DeArrow: Setting title to {new_title:#?}");
@@ -118,8 +180,10 @@ fn update_title(info: &PyRef<PyDict>, new_title: &str, vm: &VirtualMachine) {
         new_title.replace('>', "")
     };
 
-    info.set_item("title", vm.new_pyobj(cleaned_title), vm)
+    info.set_item(intern!(py, "title"), cleaned_title)
         .expect("This should work?");
+
+    Ok(())
 }
 
 #[derive(Serialize, Deserialize)]
@@ -145,7 +209,7 @@ struct CasualVote {
     title: String,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Clone)]
 struct Title {
     /// Note: Titles will sometimes contain > before a word.
     /// This tells the auto-formatter to not format a word.
diff --git a/crates/yt_dlp/src/post_processors/mod.rs b/crates/yt_dlp/src/post_processors/mod.rs
index 00b0ad5..d9be3f5 100644
--- a/crates/yt_dlp/src/post_processors/mod.rs
+++ b/crates/yt_dlp/src/post_processors/mod.rs
@@ -12,8 +12,9 @@ pub mod dearrow;
 
 #[macro_export]
 macro_rules! pydict_get {
-    (@$vm:expr, $value:expr, $name:literal, $into:ident) => {{
-        match $value.get_item($name, $vm) {
+    ($value:expr, $name:literal, $into:ty) => {{
+        let item = $value.get_item(pyo3::intern!($value.py(), $name));
+        match &item {
             Ok(val) => $crate::pydict_cast!(val, $into),
             Err(_) => panic!(
                 concat!(
@@ -31,93 +32,17 @@ macro_rules! pydict_get {
 
 #[macro_export]
 macro_rules! pydict_cast {
-    ($value:expr, $into:ident) => {{
-        match $value.downcast::<$into>() {
+    ($value:expr, $into:ty) => {{
+        match $value.extract::<$into>() {
             Ok(result) => result,
             Err(val) => panic!(
                 concat!(
-                    "Expected to be able to downcast value ({:#?}) as ",
-                    stringify!($into)
+                    "Expected to be able to extract ",
+                    stringify!($into),
+                    " from value ({:#?})."
                 ),
                 val
             ),
         }
     }};
-    (@ref $value:expr, $into:ident) => {{
-        match $value.downcast_ref::<$into>() {
-            Some(result) => result,
-            None => panic!(
-                concat!(
-                    "Expected to be able to downcast value ({:#?}) as ",
-                    stringify!($into)
-                ),
-                $value
-            ),
-        }
-    }};
-}
-
-#[macro_export]
-macro_rules! wrap_post_processor {
-    ($name:literal, $unwrap:ident, $wrapped:ident) => {
-        use $crate::progress_hook::__priv::vm;
-
-        /// # Errors
-        /// - If the underlying function returns an error.
-        /// - If python operations fail.
-        pub fn $wrapped(vm: &vm::VirtualMachine) -> vm::PyResult<vm::PyObjectRef> {
-            fn actual_processor(
-                mut input: vm::function::FuncArgs,
-                vm: &vm::VirtualMachine,
-            ) -> vm::PyResult<vm::PyRef<vm::builtins::PyDict>> {
-                let input = input
-                    .args
-                    .remove(0)
-                    .downcast::<vm::builtins::PyDict>()
-                    .expect("Should be a py dict");
-
-                let output = match unwrapped_process(input, vm) {
-                    Ok(ok) => ok,
-                    Err(err) => {
-                        return Err(vm.new_runtime_error(err.to_string()));
-                    }
-                };
-
-                Ok(output)
-            }
-
-            let scope = vm.new_scope_with_builtins();
-
-            scope.globals.set_item(
-                "actual_processor",
-                vm.new_function("actual_processor", actual_processor).into(),
-                vm,
-            )?;
-
-            let local_scope = scope.clone();
-            vm.run_code_string(
-                local_scope,
-                format!(
-                    "
-import yt_dlp
-
-class {}(yt_dlp.postprocessor.PostProcessor):
-    def run(self, info):
-        info = actual_processor(info)
-        return [], info
-
-inst = {}()
-",
-                    $name, $name
-                )
-                .as_str(),
-                "<embedded post processor initializing code>".to_owned(),
-            )?;
-
-            Ok(scope
-                .globals
-                .get_item("inst", vm)
-                .expect("We just declared it"))
-        }
-    };
 }
diff --git a/crates/yt_dlp/src/progress_hook.rs b/crates/yt_dlp/src/progress_hook.rs
index b42ae21..7e5f8a5 100644
--- a/crates/yt_dlp/src/progress_hook.rs
+++ b/crates/yt_dlp/src/progress_hook.rs
@@ -9,46 +9,59 @@
 // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 
 #[macro_export]
-macro_rules! mk_python_function {
+macro_rules! wrap_progress_hook {
     ($name:ident, $new_name:ident) => {
-        pub fn $new_name(
-            mut args: $crate::progress_hook::__priv::vm::function::FuncArgs,
-            vm: &$crate::progress_hook::__priv::vm::VirtualMachine,
-        ) {
-            use $crate::progress_hook::__priv::vm;
-
-            let input = {
-                let dict: vm::PyRef<vm::builtins::PyDict> = args
-                    .args
-                    .remove(0)
-                    .downcast()
-                    .expect("The progress hook is always called with these args");
-                let new_dict = vm::builtins::PyDict::new_ref(&vm.ctx);
-                dict.into_iter()
-                    .filter_map(|(name, value)| {
-                        let real_name: vm::PyRefExact<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::progress_hook::__priv::json_dumps(new_dict, vm)
-            };
-            $name(input).expect("Shall not fail!");
+        pub(crate) fn $new_name(
+            py: yt_dlp::progress_hook::__priv::pyo3::Python<'_>,
+        ) -> yt_dlp::progress_hook::__priv::pyo3::PyResult<
+            yt_dlp::progress_hook::__priv::pyo3::Bound<
+                '_,
+                yt_dlp::progress_hook::__priv::pyo3::types::PyCFunction,
+            >,
+        > {
+            #[yt_dlp::progress_hook::__priv::pyo3::pyfunction]
+            #[pyo3(crate = "yt_dlp::progress_hook::__priv::pyo3")]
+            fn inner(
+                input: yt_dlp::progress_hook::__priv::pyo3::Bound<
+                    '_,
+                    yt_dlp::progress_hook::__priv::pyo3::types::PyDict,
+                >,
+            ) -> yt_dlp::progress_hook::__priv::pyo3::PyResult<()> {
+                let processed_input = {
+                    let new_dict = yt_dlp::progress_hook::__priv::pyo3::types::PyDict::new(input.py());
+
+                    input
+                        .into_iter()
+                        .filter_map(|(name, value)| {
+                            let real_name = yt_dlp::progress_hook::__priv::pyo3::types::PyAnyMethods::extract::<String>(&name).expect("Should always be a string");
+
+                            if real_name.starts_with('_') {
+                                None
+                            } else {
+                                Some((real_name, value))
+                            }
+                        })
+                        .for_each(|(key, value)| {
+                            yt_dlp::progress_hook::__priv::pyo3::types::PyDictMethods::set_item(&new_dict, &key, value)
+                                .expect("This is a transpositions, should always be valid");
+                        });
+                    yt_dlp::progress_hook::__priv::json_dumps(&new_dict)
+                };
+
+                $name(processed_input)?;
+
+                Ok(())
+            }
+
+            let module = yt_dlp::progress_hook::__priv::pyo3::types::PyModule::new(py, "progress_hook")?;
+            let fun = yt_dlp::progress_hook::__priv::pyo3::wrap_pyfunction!(inner, module)?;
+
+            Ok(fun)
         }
     };
 }
 
 pub mod __priv {
     pub use crate::info_json::{json_dumps, json_loads};
-    pub use rustpython::vm;
+    pub use pyo3;
 }
diff --git a/crates/yt_dlp/src/python_error.rs b/crates/yt_dlp/src/python_error.rs
index 9513956..0c442b3 100644
--- a/crates/yt_dlp/src/python_error.rs
+++ b/crates/yt_dlp/src/python_error.rs
@@ -8,109 +8,48 @@
 // 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;
+use std::fmt::{self, Display};
 
 use log::{Level, debug, log_enabled};
-use rustpython::vm::{
-    AsObject, PyPayload, PyRef, VirtualMachine,
-    builtins::{PyBaseException, PyBaseExceptionRef, PyStr},
-    py_io::Write,
-    suggestion::offer_suggestions,
-};
+use pyo3::{PyErr, Python, types::PyTracebackMethods};
 
 #[derive(thiserror::Error, Debug)]
 pub struct PythonError(pub String);
 
+pub(crate) trait IntoPythonError<T>: Sized {
+    fn wrap_exc(self, py: Python<'_>) -> Result<T, PythonError>;
+}
+
+impl<T> IntoPythonError<T> for Result<T, PyErr> {
+    fn wrap_exc(self, py: Python<'_>) -> Result<T, PythonError> {
+        self.map_err(|exc| PythonError::from_exception(py, &exc))
+    }
+}
+
 impl Display for PythonError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(f, "Python threw an exception: {}", self.0)
     }
 }
 
 impl PythonError {
-    pub(super) fn from_exception(vm: &VirtualMachine, exc: &PyRef<PyBaseException>) -> Self {
-        let buffer = process_exception(vm, exc);
+    pub(super) fn from_exception(py: Python<'_>, exc: &PyErr) -> Self {
+        let buffer = process_exception(py, exc);
         Self(buffer)
     }
 }
 
-pub(super) fn process_exception(vm: &VirtualMachine, err: &PyBaseExceptionRef) -> String {
-    let mut buffer = String::new();
-    write_exception(vm, &mut buffer, err)
-        .expect("We are writing into an *in-memory* string, it will always work");
-
+pub(super) fn process_exception(py: Python<'_>, err: &PyErr) -> String {
     if log_enabled!(Level::Debug) {
-        let mut output = String::new();
-        vm.write_exception(&mut output, err)
-            .expect("We are writing into an *in-memory* string, it will always work");
-        debug!("Python threw an exception: {output}");
-    }
+        let mut output = err.to_string();
 
-    buffer
-}
-
-// Inlined and changed from `vm.write_exception_inner`
-fn write_exception<W: Write>(
-    vm: &VirtualMachine,
-    output: &mut W,
-    exc: &PyBaseExceptionRef,
-) -> Result<(), W::Error> {
-    let varargs = exc.args();
-    let args_repr = {
-        match varargs.len() {
-            0 => vec![],
-            1 => {
-                let args0_repr = if true {
-                    varargs[0]
-                        .str(vm)
-                        .unwrap_or_else(|_| PyStr::from("<element str() failed>").into_ref(&vm.ctx))
-                } else {
-                    varargs[0].repr(vm).unwrap_or_else(|_| {
-                        PyStr::from("<element repr() failed>").into_ref(&vm.ctx)
-                    })
-                };
-                vec![args0_repr]
-            }
-            _ => varargs
-                .iter()
-                .map(|vararg| {
-                    vararg.repr(vm).unwrap_or_else(|_| {
-                        PyStr::from("<element repr() failed>").into_ref(&vm.ctx)
-                    })
-                })
-                .collect(),
+        if let Some(tb) = err.traceback(py) {
+            output.push('\n');
+            output.push_str(&tb.format().unwrap());
         }
-    };
 
-    let exc_class = exc.class();
-
-    if exc_class.fast_issubclass(vm.ctx.exceptions.syntax_error) {
-        unreachable!(
-            "A syntax error should never be raised, \
-            as yt_dlp should not have them and neither our embedded code"
-        );
+        debug!("Python threw an exception: {output}");
     }
 
-    let exc_name = exc_class.name();
-    match args_repr.len() {
-        0 => write!(output, "{exc_name}"),
-        1 => write!(output, "{}: {}", exc_name, args_repr[0]),
-        _ => write!(
-            output,
-            "{}: ({})",
-            exc_name,
-            args_repr
-                .iter()
-                .map(|val| val.as_str())
-                .collect::<Vec<_>>()
-                .join(", "),
-        ),
-    }?;
-
-    match offer_suggestions(exc, vm) {
-        Some(suggestions) => {
-            write!(output, ". Did you mean: '{suggestions}'?")
-        }
-        None => Ok(()),
-    }
+    err.to_string()
 }
diff --git a/crates/yt_dlp/update.sh b/crates/yt_dlp/update.sh
index c1a0215..52c96b5 100755
--- a/crates/yt_dlp/update.sh
+++ b/crates/yt_dlp/update.sh
@@ -10,6 +10,4 @@
 # 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>.
 
-cd "$(dirname "$0")" || exit 1
-[ "$1" = "upgrade" ] && cargo upgrade --incompatible
-cargo update
+"$(dirname "$0")/crates/pyo3-pylogger/update.sh" "$@"