about summary refs log tree commit diff stats
path: root/pkgs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-20 18:30:22 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-20 18:30:22 +0200
commit664315ead49e94a6be4402f97aef86b27477a5b2 (patch)
treeadbafbd7f33b12092e8ce03120af333856d6e7dc /pkgs
parentbuild(pkgs/yt): Update (diff)
downloadnixos-config-664315ead49e94a6be4402f97aef86b27477a5b2.tar.gz
nixos-config-664315ead49e94a6be4402f97aef86b27477a5b2.zip
fix(pkgs/yt): Fix some more usage problems
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/by-name/yt/yt/src/cache/mod.rs11
-rw-r--r--pkgs/by-name/yt/yt/src/cli.rs11
-rw-r--r--pkgs/by-name/yt/yt/src/main.rs2
-rw-r--r--pkgs/by-name/yt/yt/src/select/mod.rs36
-rw-r--r--pkgs/by-name/yt/yt/src/select/selection_file/display.rs10
-rw-r--r--pkgs/by-name/yt/yt/src/select/selection_file/help.str10
-rw-r--r--pkgs/by-name/yt/yt/src/status/mod.rs73
-rw-r--r--pkgs/by-name/yt/yt/src/storage/video_database/getters.rs16
-rw-r--r--pkgs/by-name/yt/yt/src/storage/video_database/mod.rs2
-rw-r--r--pkgs/by-name/yt/yt/src/storage/video_database/schema.sql18
-rw-r--r--pkgs/by-name/yt/yt/src/storage/video_database/setters.rs33
-rw-r--r--pkgs/by-name/yt/yt/src/watch/events.rs46
-rw-r--r--pkgs/by-name/yt/yt/src/watch/mod.rs3
13 files changed, 220 insertions, 51 deletions
diff --git a/pkgs/by-name/yt/yt/src/cache/mod.rs b/pkgs/by-name/yt/yt/src/cache/mod.rs
index 87f42604..6aa3a161 100644
--- a/pkgs/by-name/yt/yt/src/cache/mod.rs
+++ b/pkgs/by-name/yt/yt/src/cache/mod.rs
@@ -5,7 +5,8 @@ use tokio::fs;
 use crate::{
     app::App,
     storage::video_database::{
-        downloader::set_video_cache_path, getters::get_videos, Video, VideoStatus,
+        downloader::set_video_cache_path, getters::get_videos, setters::set_state_change, Video,
+        VideoStatus,
     },
 };
 
@@ -25,7 +26,7 @@ async fn invalidate_video(app: &App, video: &Video, hard: bool) -> Result<()> {
 }
 
 pub async fn invalidate(app: &App, hard: bool) -> Result<()> {
-    let all_cached_things = get_videos(app, &[VideoStatus::Cached]).await?;
+    let all_cached_things = get_videos(app, &[VideoStatus::Cached], None).await?;
 
     info!("Got videos to invalidate: '{}'", all_cached_things.len());
 
@@ -52,7 +53,7 @@ pub async fn maintain(app: &App, all: bool) -> Result<()> {
         vec![VideoStatus::Watch, VideoStatus::Cached]
     };
 
-    let cached_videos = get_videos(app, domain.as_slice()).await?;
+    let cached_videos = get_videos(app, domain.as_slice(), None).await?;
 
     for vid in cached_videos {
         if let Some(path) = vid.cache_path.as_ref() {
@@ -61,6 +62,10 @@ pub async fn maintain(app: &App, all: bool) -> Result<()> {
                 invalidate_video(app, &vid, false).await?;
             }
         }
+        if vid.status_change {
+            info!("Video '{}' has it's changing bit set. This is probably the result of an unexpectet exit. Clearing it", vid.title);
+            set_state_change(app, &vid.extractor_hash, false).await?;
+        }
     }
 
     Ok(())
diff --git a/pkgs/by-name/yt/yt/src/cli.rs b/pkgs/by-name/yt/yt/src/cli.rs
index 932712cc..45590d56 100644
--- a/pkgs/by-name/yt/yt/src/cli.rs
+++ b/pkgs/by-name/yt/yt/src/cli.rs
@@ -53,7 +53,8 @@ pub enum Command {
     Description {},
 
     /// Manipulate the video cache in the database
-    Cache {
+    #[command(visible_alias = "db")]
+    Database {
         #[command(subcommand)]
         command: CacheCommand,
     },
@@ -137,6 +138,7 @@ pub struct SharedSelectionCommandArgs {
 
 #[derive(Subcommand, Clone, Debug)]
 #[command(infer_subcommands = true)]
+// NOTE: Keep this in sync with the [`constants::HELP_STR`] constant. <2024-08-20>
 pub enum SelectCommand {
     /// Open a `git rebase` like file to select the videos to watch (the default)
     File {
@@ -201,7 +203,12 @@ pub enum CacheCommand {
         hard: bool,
     },
 
-    /// Check every path for validity (removing all invalid cache entries)
+    /// 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)
+    /// 2. Reset all `status_change` bits of videos to false.
+    #[command(verbatim_doc_comment)]
     Maintain {
         /// Check every video (otherwise only the videos to be watched are checked)
         #[arg(short, long)]
diff --git a/pkgs/by-name/yt/yt/src/main.rs b/pkgs/by-name/yt/yt/src/main.rs
index 26af85b2..302fba1e 100644
--- a/pkgs/by-name/yt/yt/src/main.rs
+++ b/pkgs/by-name/yt/yt/src/main.rs
@@ -102,7 +102,7 @@ async fn main() -> Result<()> {
 
         Command::Status {} => status::show(&app).await?,
 
-        Command::Cache { command } => match command {
+        Command::Database { command } => match command {
             CacheCommand::Invalidate { hard } => cache::invalidate(&app, hard).await?,
             CacheCommand::Maintain { all } => cache::maintain(&app, all).await?,
         },
diff --git a/pkgs/by-name/yt/yt/src/select/mod.rs b/pkgs/by-name/yt/yt/src/select/mod.rs
index 9c250909..2dfefef9 100644
--- a/pkgs/by-name/yt/yt/src/select/mod.rs
+++ b/pkgs/by-name/yt/yt/src/select/mod.rs
@@ -1,7 +1,8 @@
 use std::{
-    env, fs,
-    io::{BufRead, BufReader, BufWriter, Write},
-    process::Command,
+    env::{self},
+    fs,
+    io::{BufRead, Write},
+    io::{BufReader, BufWriter},
 };
 
 use crate::{
@@ -17,6 +18,7 @@ use cmds::handle_select_cmd;
 use futures::future::join_all;
 use selection_file::process_line;
 use tempfile::Builder;
+use tokio::process::Command;
 
 pub mod cmds;
 pub mod selection_file;
@@ -35,6 +37,7 @@ pub async fn select(app: &App, done: bool) -> Result<()> {
                 VideoStatus::Drop,
                 VideoStatus::Dropped,
             ],
+            None,
         )
         .await?
     } else {
@@ -46,6 +49,7 @@ pub async fn select(app: &App, done: bool) -> Result<()> {
                 VideoStatus::Watch,
                 VideoStatus::Cached,
             ],
+            None,
         )
         .await?
     };
@@ -82,6 +86,7 @@ pub async fn select(app: &App, done: bool) -> Result<()> {
                 .expect("This write should not fail");
         });
 
+        // edit_file.write_all(get_help().await?.as_bytes())?;
         edit_file.write_all(HELP_STR.as_bytes())?;
         edit_file.flush().context("Failed to flush edit file")?;
 
@@ -89,7 +94,7 @@ pub async fn select(app: &App, done: bool) -> Result<()> {
 
         let mut nvim = Command::new(editor);
         nvim.arg(temp_file.path());
-        let status = nvim.status().context("Falied to run nvim")?;
+        let status = nvim.status().await.context("Falied to run nvim")?;
         if !status.success() {
             bail!("nvim exited with error status: {}", status)
         }
@@ -144,3 +149,26 @@ pub async fn select(app: &App, done: bool) -> Result<()> {
 
     Ok(())
 }
+
+// // FIXME: There should be no reason why we need to re-run yt, just to get the help string. But I've
+// // jet to find a way to do it with out the extra exec <2024-08-20>
+// async fn get_help() -> Result<String> {
+//     let binary_name = current_exe()?;
+//     let cmd = Command::new(binary_name)
+//         .args(&["select", "--help"])
+//         .output()
+//         .await?;
+//
+//     assert_eq!(cmd.status.code(), Some(0));
+//
+//     let output = String::from_utf8(cmd.stdout).expect("Our help output was not utf8?");
+//
+//     let out = output
+//         .lines()
+//         .map(|line| format!("# {}\n", line))
+//         .collect::<String>();
+//
+//     debug!("Returning help: '{}'", &out);
+//
+//     Ok(out)
+// }
diff --git a/pkgs/by-name/yt/yt/src/select/selection_file/display.rs b/pkgs/by-name/yt/yt/src/select/selection_file/display.rs
index 5ab90316..9f5ae3c2 100644
--- a/pkgs/by-name/yt/yt/src/select/selection_file/display.rs
+++ b/pkgs/by-name/yt/yt/src/select/selection_file/display.rs
@@ -20,7 +20,10 @@ impl Video {
     pub async fn to_select_file_display(&self, app: &App) -> Result<String> {
         let mut f = String::new();
 
-        let opts = get_video_opts(app, &self.extractor_hash).await?;
+        let opts = get_video_opts(app, &self.extractor_hash)
+            .await?
+            .to_cli_flags();
+        let opts_white = if !opts.is_empty() { " " } else { "" };
 
         let publish_date = if let Some(date) = self.publish_date {
             DateTime::from_timestamp(date, 0)
@@ -40,9 +43,10 @@ impl Video {
         debug!("Formatting video for selection file: {}", self.title);
         write!(
             f,
-            r#"{} {} {} "{}" "{}" "{}" "{}" "{}"{}"#,
+            r#"{}{}{} {} "{}" "{}" "{}" "{}" "{}"{}"#,
             self.status.as_command(),
-            opts.to_cli_flags(),
+            opts_white,
+            opts,
             self.extractor_hash.into_short_hash(app).await?,
             self.title.replace(['"', '„', '”'], "'"),
             publish_date,
diff --git a/pkgs/by-name/yt/yt/src/select/selection_file/help.str b/pkgs/by-name/yt/yt/src/select/selection_file/help.str
index 130fe42a..6e296f6e 100644
--- a/pkgs/by-name/yt/yt/src/select/selection_file/help.str
+++ b/pkgs/by-name/yt/yt/src/select/selection_file/help.str
@@ -1,8 +1,10 @@
 # Commands:
-# w, watch  = watch id
-# d, drop   = mark id as watched
-# u, url    = open the associated URL in the `timesinks.youtube` Firefox profile
-# p, pick   = leave id as is; This is a noop
+#   w, watch [-p,-s,-l]   Mark the video given by the hash to be watched
+#   d, drop               Mark the video given by the hash to be dropped
+#   u, url                Open the video URL in Firefox's `timesinks.youtube` profile
+#   p, pick               Reset the videos status to 'Pick'
+#
+# See `yt select <cmd_name> --help` for more help.
 #
 # These lines can be re-ordered; they are executed from top to bottom.
 # vim: filetype=yts conceallevel=2 concealcursor=nc colorcolumn=
diff --git a/pkgs/by-name/yt/yt/src/status/mod.rs b/pkgs/by-name/yt/yt/src/status/mod.rs
index 82f75c8d..43632048 100644
--- a/pkgs/by-name/yt/yt/src/status/mod.rs
+++ b/pkgs/by-name/yt/yt/src/status/mod.rs
@@ -4,43 +4,74 @@ use crate::{
     app::App,
     storage::{
         subscriptions::get_subscriptions,
-        video_database::{getters::get_videos, VideoStatus},
+        video_database::{getters::get_videos, Video, VideoStatus},
     },
 };
 
+macro_rules! get {
+    ($videos:expr, $status:ident) => {
+        $videos
+            .iter()
+            .filter(|vid| vid.status == VideoStatus::$status)
+            .collect::<Vec<&Video>>()
+    };
+    (@changing $videos:expr, $status:ident) => {
+        $videos
+            .iter()
+            .filter(|vid| vid.status == VideoStatus::$status && vid.status_change)
+            .collect::<Vec<&Video>>()
+    };
+}
+
 pub async fn show(app: &App) -> Result<()> {
-    let pick_videos = get_videos(app, &[VideoStatus::Pick]).await?;
+    let all_videos = get_videos(
+        app,
+        &[
+            VideoStatus::Pick,
+            //
+            VideoStatus::Watch,
+            VideoStatus::Cached,
+            VideoStatus::Watched,
+            //
+            VideoStatus::Drop,
+            VideoStatus::Dropped,
+        ],
+        None,
+    )
+    .await?;
+
+    // lengths
+    let picked_videos_len = (get!(all_videos, Pick)).len();
 
-    let watch_videos = get_videos(app, &[VideoStatus::Watch]).await?;
-    let cached_videos = get_videos(app, &[VideoStatus::Cached]).await?;
-    let watched_videos = get_videos(app, &[VideoStatus::Watched]).await?;
+    let watch_videos_len = (get!(all_videos, Watch)).len();
+    let cached_videos_len = (get!(all_videos, Cached)).len();
+    let watched_videos_len = (get!(all_videos, Watched)).len();
 
-    let drop_videos = get_videos(app, &[VideoStatus::Drop]).await?;
-    let dropped_videos = get_videos(app, &[VideoStatus::Dropped]).await?;
+    let drop_videos_len = (get!(all_videos, Drop)).len();
+    let dropped_videos_len = (get!(all_videos, Dropped)).len();
 
-    // lengths
-    let picked_videos_len = pick_videos.len();
+    // changing
+    let picked_videos_changing = (get!(@changing all_videos, Pick)).len();
 
-    let watch_videos_len = watch_videos.len();
-    let cached_videos_len = cached_videos.len();
-    let watched_videos_len = watched_videos.len();
+    let watch_videos_changing = (get!(@changing all_videos, Watch)).len();
+    let cached_videos_changing = (get!(@changing all_videos, Cached)).len();
+    let watched_videos_changing = (get!(@changing all_videos, Watched)).len();
 
-    let drop_videos_len = drop_videos.len();
-    let dropped_videos_len = dropped_videos.len();
+    let drop_videos_changing = (get!(@changing all_videos, Drop)).len();
+    let dropped_videos_changing = (get!(@changing all_videos, Dropped)).len();
 
     let subscriptions = get_subscriptions()?;
     let subscriptions_len = subscriptions.0.len();
-
     println!(
         "\
-Picked   Videos: {picked_videos_len}
+Picked   Videos: {picked_videos_len} ({picked_videos_changing} changing)
 
-Watch    Videos: {watch_videos_len}
-Cached   Videos: {cached_videos_len}
-Watched  Videos: {watched_videos_len}
+Watch    Videos: {watch_videos_len} ({watch_videos_changing} changing)
+Cached   Videos: {cached_videos_len} ({cached_videos_changing} changing)
+Watched  Videos: {watched_videos_len} ({watched_videos_changing} changing)
 
-Drop     Videos: {drop_videos_len}
-Dropped  Videos: {dropped_videos_len}
+Drop     Videos: {drop_videos_len} ({drop_videos_changing} changing)
+Dropped  Videos: {dropped_videos_len} ({dropped_videos_changing} changing)
 
 
   Subscriptions: {subscriptions_len}"
diff --git a/pkgs/by-name/yt/yt/src/storage/video_database/getters.rs b/pkgs/by-name/yt/yt/src/storage/video_database/getters.rs
index 57c023e6..b61a5c86 100644
--- a/pkgs/by-name/yt/yt/src/storage/video_database/getters.rs
+++ b/pkgs/by-name/yt/yt/src/storage/video_database/getters.rs
@@ -57,7 +57,13 @@ macro_rules! video_from_record {
 }
 
 /// Get the lines to display at the selection file
-pub async fn get_videos(app: &App, allowed_states: &[VideoStatus]) -> Result<Vec<Video>> {
+/// [`changing` = true]: Means that we include *only* videos, that have the `status_changing` flag set
+/// [`changing` = None]: Means that we include *both* videos, that have the `status_changing` flag set and not set
+pub async fn get_videos(
+    app: &App,
+    allowed_states: &[VideoStatus],
+    changing: Option<bool>,
+) -> Result<Vec<Video>> {
     let mut qb: QueryBuilder<Sqlite> = QueryBuilder::new(
         "\
     SELECT *
@@ -80,6 +86,14 @@ pub async fn get_videos(app: &App, allowed_states: &[VideoStatus]) -> Result<Vec
         });
     qb.push(")");
 
+    if let Some(val) = changing {
+        if val {
+            qb.push(" AND status_change = 1");
+        } else {
+            qb.push(" AND status_change = 0");
+        }
+    }
+
     qb.push("\n    ORDER BY priority DESC;");
 
     debug!("Will run: \"{}\"", qb.sql());
diff --git a/pkgs/by-name/yt/yt/src/storage/video_database/mod.rs b/pkgs/by-name/yt/yt/src/storage/video_database/mod.rs
index 203cf651..1bb1376d 100644
--- a/pkgs/by-name/yt/yt/src/storage/video_database/mod.rs
+++ b/pkgs/by-name/yt/yt/src/storage/video_database/mod.rs
@@ -81,7 +81,7 @@ pub struct YtDlpOptions {
 /// Cache                       // yt cache
 ///     |
 /// Watched                     // yt watch
-#[derive(Default, Debug)]
+#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub enum VideoStatus {
     #[default]
     Pick,
diff --git a/pkgs/by-name/yt/yt/src/storage/video_database/schema.sql b/pkgs/by-name/yt/yt/src/storage/video_database/schema.sql
index d25b7015..2e9e18af 100644
--- a/pkgs/by-name/yt/yt/src/storage/video_database/schema.sql
+++ b/pkgs/by-name/yt/yt/src/storage/video_database/schema.sql
@@ -2,7 +2,11 @@
 
 -- Keep this table in sync with the `Video` structure
 CREATE TABLE IF NOT EXISTS videos (
-    cache_path                  TEXT UNIQUE                    CHECK (CASE WHEN cache_path IS NOT NULL THEN status == 2 ELSE 1 END),
+    cache_path                  TEXT UNIQUE                    CHECK (CASE WHEN cache_path IS NOT NULL THEN
+                                                                            status == 2
+                                                                      ELSE
+                                                                            1
+                                                                      END),
     description                 TEXT,
     duration                    FLOAT,
     extractor_hash              TEXT UNIQUE NOT NULL PRIMARY KEY,
@@ -10,7 +14,17 @@ CREATE TABLE IF NOT EXISTS videos (
     parent_subscription_name    TEXT,
     priority                    INTEGER     NOT NULL DEFAULT 0,
     publish_date                INTEGER,
-    status                      INTEGER     NOT NULL DEFAULT 0 CHECK (status IN (0, 1, 2, 3, 4, 5) AND CASE WHEN status == 2 THEN cache_path IS NOT NULL ELSE 1 END AND CASE WHEN status != 2 THEN cache_path IS NULL ELSE 1 END),
+    status                      INTEGER     NOT NULL DEFAULT 0 CHECK (status IN (0, 1, 2, 3, 4, 5) AND
+                                                                      CASE WHEN status == 2 THEN
+                                                                           cache_path IS NOT NULL
+                                                                      ELSE
+                                                                           1
+                                                                      END AND
+                                                                      CASE WHEN status != 2 THEN
+                                                                           cache_path IS NULL
+                                                                      ELSE
+                                                                           1
+                                                                      END),
     status_change               INTEGER     NOT NULL DEFAULT 0 CHECK (status_change IN (0, 1)),
     thumbnail_url               TEXT,
     title                       TEXT        NOT NULL,
diff --git a/pkgs/by-name/yt/yt/src/storage/video_database/setters.rs b/pkgs/by-name/yt/yt/src/storage/video_database/setters.rs
index 242cf67a..dbcb897e 100644
--- a/pkgs/by-name/yt/yt/src/storage/video_database/setters.rs
+++ b/pkgs/by-name/yt/yt/src/storage/video_database/setters.rs
@@ -2,6 +2,7 @@
 
 use anyhow::Result;
 use chrono::Utc;
+use log::debug;
 use sqlx::query;
 use tokio::fs;
 
@@ -18,11 +19,10 @@ pub async fn set_video_status(
     new_priority: Option<i64>,
 ) -> Result<()> {
     let video_hash = video_hash.hash().to_string();
-    let new_status = new_status.as_db_integer();
 
     let old = query!(
         r#"
-    SELECT status, priority
+    SELECT status, priority, cache_path
     FROM videos
     WHERE extractor_hash = ?
     "#,
@@ -31,6 +31,16 @@ pub async fn set_video_status(
     .fetch_one(&app.database)
     .await?;
 
+    let cache_path = if (VideoStatus::from_db_integer(old.status) == VideoStatus::Cached)
+        && (new_status != VideoStatus::Cached)
+    {
+        None
+    } else {
+        old.cache_path.as_deref()
+    };
+
+    let new_status = new_status.as_db_integer();
+
     if let Some(new_priority) = new_priority {
         if old.status == new_status && old.priority == new_priority {
             return Ok(());
@@ -38,15 +48,22 @@ pub async fn set_video_status(
 
         let now = Utc::now().timestamp();
 
+        debug!(
+            "Running status change: {:#?} -> {:#?}...",
+            VideoStatus::from_db_integer(old.status),
+            VideoStatus::from_db_integer(new_status),
+        );
+
         query!(
             r#"
         UPDATE videos
-        SET status = ?, last_status_change = ?, priority = ?
+        SET status = ?, last_status_change = ?, priority = ?, cache_path = ?
         WHERE extractor_hash = ?;
         "#,
             new_status,
             now,
             new_priority,
+            cache_path,
             video_hash
         )
         .execute(&app.database)
@@ -58,20 +75,28 @@ pub async fn set_video_status(
 
         let now = Utc::now().timestamp();
 
+        debug!(
+            "Running status change: {:#?} -> {:#?}...",
+            VideoStatus::from_db_integer(old.status),
+            VideoStatus::from_db_integer(new_status),
+        );
+
         query!(
             r#"
         UPDATE videos
-        SET status = ?, last_status_change = ?
+        SET status = ?, last_status_change = ?, cache_path = ?
         WHERE extractor_hash = ?;
         "#,
             new_status,
             now,
+            cache_path,
             video_hash
         )
         .execute(&app.database)
         .await?;
     }
 
+    debug!("Finished status change.");
     Ok(())
 }
 
diff --git a/pkgs/by-name/yt/yt/src/watch/events.rs b/pkgs/by-name/yt/yt/src/watch/events.rs
index d693fdce..09495c3e 100644
--- a/pkgs/by-name/yt/yt/src/watch/events.rs
+++ b/pkgs/by-name/yt/yt/src/watch/events.rs
@@ -11,8 +11,9 @@ use crate::{
     constants::LOCAL_COMMENTS_LENGTH,
     storage::video_database::{
         extractor_hash::ExtractorHash,
-        getters::{get_video_by_hash, get_video_mpv_opts},
+        getters::{get_video_by_hash, get_video_mpv_opts, get_videos},
         setters::{set_state_change, set_video_watched},
+        VideoStatus,
     },
 };
 
@@ -31,6 +32,43 @@ impl MpvEventHandler {
         }
     }
 
+    /// Checks, whether new videos are ready to be played
+    pub async fn possibly_add_new_videos(&mut self, app: &App, mpv: &Mpv) -> Result<()> {
+        let play_things = get_videos(app, &[VideoStatus::Cached], Some(false)).await?;
+
+        // There is nothing to watch
+        if play_things.len() == 0 {
+            return Ok(());
+        }
+
+        let play_things = play_things
+            .into_iter()
+            .filter(|val| !self.current_playlist.contains(&val.extractor_hash))
+            .collect::<Vec<_>>();
+
+        info!(
+            "{} videos are cached and will be added to the list to be played",
+            play_things.len()
+        );
+
+        self.current_playlist.reserve(play_things.len());
+
+        for play_thing in play_things {
+            debug!("Adding '{}' to playlist.", play_thing.title);
+
+            let orig_cache_path = play_thing.cache_path.expect("Is cached and thus some");
+            let cache_path = orig_cache_path.to_str().expect("Should be vaild utf8");
+            let cache_path = format!("\"{}\"", cache_path);
+
+            let args = &[&cache_path, "append-play"];
+
+            mpv.execute("loadfile", args)?;
+            self.current_playlist.push(play_thing.extractor_hash);
+        }
+
+        Ok(())
+    }
+
     async fn mark_video_watched(&mut self, app: &App, hash: &ExtractorHash) -> Result<()> {
         let video = get_video_by_hash(app, hash).await?;
         set_video_watched(&app, &video).await?;
@@ -59,7 +97,7 @@ impl MpvEventHandler {
         Ok(())
     }
 
-    /// Apply the options set with e.g. `watch --speed`
+    /// Apply the options set with e.g. `watch --speed=<speed>`
     async fn apply_options(&self, app: &App, mpv: &Mpv, hash: &ExtractorHash) -> Result<()> {
         let options = get_video_mpv_opts(app, hash).await?;
 
@@ -87,11 +125,11 @@ impl MpvEventHandler {
                 EndFileReason::Quit => {
                     info!("Mpv quit. Exiting playback");
 
-                    self.mark_cvideo_inactive(app).await?;
                     // draining the playlist is okay, as mpv is done playing
                     let videos = mem::take(&mut self.current_playlist);
                     for video in videos {
                         self.mark_video_watched(app, &video).await?;
+                        set_state_change(&app, &video, false).await?;
                     }
                     return Ok(true);
                 }
@@ -103,6 +141,8 @@ impl MpvEventHandler {
                 }
             },
             Event::StartFile(playlist_index) => {
+                self.possibly_add_new_videos(app, &mpv).await?;
+
                 self.mark_video_active(app, (playlist_index - 1) as usize)
                     .await?;
                 self.current_playlist_position = (playlist_index - 1) as usize;
diff --git a/pkgs/by-name/yt/yt/src/watch/mod.rs b/pkgs/by-name/yt/yt/src/watch/mod.rs
index 4b558968..83996c1a 100644
--- a/pkgs/by-name/yt/yt/src/watch/mod.rs
+++ b/pkgs/by-name/yt/yt/src/watch/mod.rs
@@ -62,8 +62,7 @@ pub async fn watch(app: &App) -> Result<()> {
     let mut ev_ctx = EventContext::new(mpv.ctx);
     ev_ctx.disable_deprecated_events()?;
 
-    let play_things = get_videos(app, &[VideoStatus::Cached]).await?;
-
+    let play_things = get_videos(app, &[VideoStatus::Cached], Some(false)).await?;
     info!(
         "{} videos are cached and ready to be played",
         play_things.len()