about summary refs log tree commit diff stats
path: root/crates
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-07-15 07:07:44 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-07-15 07:07:44 +0200
commitd6e17110dae3f1afe35415065e9a08d0f90f2592 (patch)
tree2b971f59d79f9cb844b9dec6ed1732acb83c8005 /crates
parentfix(crates/yt/update): Remove the `--grouped` update support (diff)
downloadyt-d6e17110dae3f1afe35415065e9a08d0f90f2592.zip
feat(crates/yt): Support a `--format` argument for most commands with output
Diffstat (limited to 'crates')
-rw-r--r--crates/yt/src/cli.rs11
-rw-r--r--crates/yt/src/main.rs11
-rw-r--r--crates/yt/src/select/mod.rs2
-rw-r--r--crates/yt/src/status/mod.rs38
-rw-r--r--crates/yt/src/update/mod.rs2
-rw-r--r--crates/yt/src/videos/display/format_video.rs79
-rw-r--r--crates/yt/src/videos/display/mod.rs36
-rw-r--r--crates/yt/src/videos/mod.rs13
8 files changed, 144 insertions, 48 deletions
diff --git a/crates/yt/src/cli.rs b/crates/yt/src/cli.rs
index cd7ce2f..a19e035 100644
--- a/crates/yt/src/cli.rs
+++ b/crates/yt/src/cli.rs
@@ -116,7 +116,11 @@ pub(crate) enum Command {
     },
 
     /// Show, which videos have been selected to be watched (and their cache status)
-    Status {},
+    Status {
+        /// Which format to use
+        #[arg(short, long)]
+        format: Option<String>,
+    },
 
     /// Show, the configuration options in effect
     Config {},
@@ -182,6 +186,11 @@ pub(crate) enum VideosCommand {
         #[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>,
diff --git a/crates/yt/src/main.rs b/crates/yt/src/main.rs
index f9f48bf..4b9c4c2 100644
--- a/crates/yt/src/main.rs
+++ b/crates/yt/src/main.rs
@@ -148,18 +148,19 @@ async fn main() -> Result<()> {
             VideosCommand::List {
                 search_query,
                 limit,
+                format,
             } => {
-                videos::query(&app, limit, search_query)
+                videos::query(&app, limit, search_query, format)
                     .await
                     .context("Failed to query videos")?;
             }
-            VideosCommand::Info { hash } => {
-                let video = video_by_hash(&app, &hash.realize(&app).await?).await?;
+            VideosCommand::Info { hash, format } => {
+                let video = hash.realize(&app).await?.get_with_app(&app).await?;
 
                 print!(
                     "{}",
                     &video
-                        .to_info_display(&app)
+                        .to_info_display(&app, format)
                         .await
                         .context("Failed to format video")?
                 );
@@ -226,7 +227,7 @@ async fn main() -> Result<()> {
         } => watch::watch(Arc::new(app), provide_ipc_socket, headless).await?,
         Command::Playlist { watch } => watch::playlist::playlist(&app, watch).await?,
 
-        Command::Status {} => status::show(&app).await?,
+        Command::Status { format } => status::show(&app, format).await?,
         Command::Config {} => status::config(&app)?,
 
         Command::Database { command } => match command {
diff --git a/crates/yt/src/select/mod.rs b/crates/yt/src/select/mod.rs
index 9afe071..76d5ae8 100644
--- a/crates/yt/src/select/mod.rs
+++ b/crates/yt/src/select/mod.rs
@@ -210,7 +210,7 @@ async fn write_videos_to_file(app: &App, file: &File, videos: &[Video]) -> Resul
     // 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?);
+        drop(vid.to_line_display(app, None).await?);
     }
 
     let mut edit_file = BufWriter::new(file);
diff --git a/crates/yt/src/status/mod.rs b/crates/yt/src/status/mod.rs
index f9528e2..de706ec 100644
--- a/crates/yt/src/status/mod.rs
+++ b/crates/yt/src/status/mod.rs
@@ -101,8 +101,39 @@ pub(crate) async fn show(app: &App, format: Option<String>) -> Result<()> {
         .await
         .context("Failed to get current cache allocation")?;
     let cache_usage: Bytes = cache_usage_raw;
-    println!(
-        "\
+
+    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("{cache_usage}", cache_usage.to_string().as_str());
+
+        print!("{output}");
+    } else {
+        println!(
+            "\
 Picked   Videos: {picked_videos_len}
 
 Watch    Videos: {watch_videos_len}
@@ -116,7 +147,8 @@ Dropped  Videos: {dropped_videos_len}
 
   Subscriptions: {subscriptions_len}
     Cache usage: {cache_usage}"
-    );
+        );
+    }
 
     Ok(())
 }
diff --git a/crates/yt/src/update/mod.rs b/crates/yt/src/update/mod.rs
index 2fb1069..809289c 100644
--- a/crates/yt/src/update/mod.rs
+++ b/crates/yt/src/update/mod.rs
@@ -203,7 +203,7 @@ async fn process_subscription(app: &App, sub: Subscription, entry: InfoJson) ->
     println!(
         "{}",
         &video
-            .to_line_display(app)
+            .to_line_display(app, None)
             .await
             .with_context(|| format!("Failed to format video: '{}'", video.title))?
     );
diff --git a/crates/yt/src/videos/display/format_video.rs b/crates/yt/src/videos/display/format_video.rs
index 659c3be..80ac5dd 100644
--- a/crates/yt/src/videos/display/format_video.rs
+++ b/crates/yt/src/videos/display/format_video.rs
@@ -11,12 +11,13 @@
 
 use anyhow::Result;
 
-use crate::{app::App, comments::output::format_text, storage::video_database::Video};
+use crate::{app::App, comments::output::format_text, storage::db::video::Video};
 
 impl Video {
     pub(crate) async fn to_info_display(
         &self,
         app: &App,
+        format: Option<String>,
     ) -> Result<String> {
         let cache_path = self.cache_path_fmt(app);
         let description = self.description_fmt();
@@ -45,8 +46,30 @@ impl Video {
             }
         };
 
-        let string = format!(
-            "\
+        let options = video_options.to_string();
+        let options = options.trim();
+        let description = format_text(description.to_string().as_str());
+
+        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}
@@ -56,33 +79,49 @@ impl Video {
 | -> 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())
-        );
+| -> options: {options}
+{description}\n",
+            )
+        };
         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)
-        );
+    pub(crate) async fn to_line_display(
+        &self,
+        app: &App,
+        format: Option<String>,
+    ) -> Result<String> {
+        let status = self.status_fmt(app);
+        let extractor_hash = self.extractor_hash_fmt(app).await?;
+        let title = self.title_fmt(app);
+        let publish_date = self.publish_date_fmt(app);
+        let parent_subscription_name = self.parent_subscription_name_fmt(app);
+        let duration = self.duration_fmt(app);
+        let url = self.url_fmt(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 async fn to_select_file_display(&self, app: &App) -> Result<String> {
+    pub(crate) 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.video_options_fmt_no_color(app),
             self.extractor_hash_fmt_no_color(app).await?,
             self.title_fmt_no_color(),
             self.publish_date_fmt_no_color(),
diff --git a/crates/yt/src/videos/display/mod.rs b/crates/yt/src/videos/display/mod.rs
index 89f7575..54e98ed 100644
--- a/crates/yt/src/videos/display/mod.rs
+++ b/crates/yt/src/videos/display/mod.rs
@@ -9,13 +9,16 @@
 // You should have received a copy of the License along with this program.
 // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 
+use std::fmt::Write;
+
+use anyhow::{Context, Result};
 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},
+    storage::db::video::{TimeStamp, Video, VideoStatus},
 };
 
 pub(crate) mod format_video;
@@ -209,19 +212,30 @@ impl Video {
 
     pub(crate) fn video_options_fmt_no_color(&self, app: &App) -> 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 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}")
         };
-        Ok(video_options)
+        video_options
     }
-    pub(crate) 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()))
+
+    pub(crate) fn video_options_fmt(&self, app: &App) -> String {
+        let opts = self.video_options_fmt_no_color(app);
+        maybe_add_color(app, opts, |v| v.bright_green().to_string())
     }
 }
diff --git a/crates/yt/src/videos/mod.rs b/crates/yt/src/videos/mod.rs
index 092e75c..673d46e 100644
--- a/crates/yt/src/videos/mod.rs
+++ b/crates/yt/src/videos/mod.rs
@@ -16,23 +16,24 @@ pub(crate) mod display;
 
 use crate::{
     app::App,
-    storage::video_database::{Video, VideoStatusMarker, get},
+    storage::db::video::{Video, VideoStatusMarker},
 };
 
-async fn to_line_display_owned(video: Video, app: &App) -> Result<String> {
-    video.to_line_display(app).await
+async fn to_line_display_owned(video: Video, app: &App, format: Option<String>) -> Result<String> {
+    video.to_line_display(app, format).await
 }
 
 pub(crate) async fn query(
     app: &App,
     limit: Option<usize>,
     search_query: Option<String>,
+    format: Option<String>,
 ) -> Result<()> {
-    let all_videos = get::videos(app, VideoStatusMarker::ALL).await?;
+    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).await?;
+        val.to_line_display(app, format.clone()).await?;
     }
 
     let limit = limit.unwrap_or(all_videos.len());
@@ -40,7 +41,7 @@ pub(crate) async fn query(
     let all_video_strings: Vec<String> = all_videos
         .into_iter()
         .take(limit)
-        .map(|vid| to_line_display_owned(vid, app))
+        .map(|vid| to_line_display_owned(vid, app, format.clone()))
         .collect::<FuturesUnordered<_>>()
         .try_collect::<Vec<String>>()
         .await?;