about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-02-22 11:43:18 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-02-22 11:43:18 +0100
commit686ea29b06162b1a70dc473cea70b00e379c4f29 (patch)
treed9af9ed38a338f3ae0d18fe706cd443affe74e7a
parentfeat(yt/watch/playlist_handler): Rewrite to use new db layout (diff)
downloadyt-686ea29b06162b1a70dc473cea70b00e379c4f29.zip
feat(yt/watch/playlist): Init
This leverages the fact, that we store the playlist information in the
database to visualize the current playlist.
-rw-r--r--yt/src/cli.rs7
-rw-r--r--yt/src/main.rs1
-rw-r--r--yt/src/watch/playlist.rs99
3 files changed, 107 insertions, 0 deletions
diff --git a/yt/src/cli.rs b/yt/src/cli.rs
index 948138d..9851211 100644
--- a/yt/src/cli.rs
+++ b/yt/src/cli.rs
@@ -89,6 +89,13 @@ pub enum Command {
     /// 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 {},
 
diff --git a/yt/src/main.rs b/yt/src/main.rs
index b32b1d3..9141fac 100644
--- a/yt/src/main.rs
+++ b/yt/src/main.rs
@@ -204,6 +204,7 @@ async fn main() -> Result<()> {
         },
 
         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)?,
diff --git a/yt/src/watch/playlist.rs b/yt/src/watch/playlist.rs
new file mode 100644
index 0000000..9b07b09
--- /dev/null
+++ b/yt/src/watch/playlist.rs
@@ -0,0 +1,99 @@
+use std::path::Path;
+
+use crate::{
+    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");
+    }
+}
+
+// ANSI ESCAPE CODES Wrappers {{{
+// see: https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands
+const CSI: &str = "\x1b[";
+fn erase_in_display_from_cursor() {
+    print!("{CSI}0J");
+}
+fn cursor_up(number: usize) {
+    print!("{CSI}{number}A");
+}
+// }}}
+
+/// # Panics
+/// Only if internal assertions fail.
+pub async fn playlist(app: &App, watch: bool) -> Result<()> {
+    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(())
+}