aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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?;