diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-10-14 12:32:23 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-10-14 12:34:56 +0200 |
commit | 145a776039248a9460e9473e4bc9ef3d533b60c1 (patch) | |
tree | 7b2a948ae1f08335eba477c26bf1d5e83cdac24b | |
parent | fix(downloader): Don't display changed cache size on first run (diff) | |
download | yt-145a776039248a9460e9473e4bc9ef3d533b60c1.tar.gz yt-145a776039248a9460e9473e4bc9ef3d533b60c1.zip |
feat(videos): Provide a consistent display for the `Video` struct
Before, `Video`s where colourized differently, just because the colourization was not standardized. It now is.
Diffstat (limited to '')
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/main.rs | 12 | ||||
-rw-r--r-- | src/select/cmds.rs | 6 | ||||
-rw-r--r-- | src/select/mod.rs | 12 | ||||
-rw-r--r-- | src/select/selection_file/display.rs | 108 | ||||
-rw-r--r-- | src/select/selection_file/mod.rs | 1 | ||||
-rw-r--r-- | src/storage/video_database/extractor_hash.rs | 21 | ||||
-rw-r--r-- | src/storage/video_database/getters.rs | 14 | ||||
-rw-r--r-- | src/storage/video_database/mod.rs | 4 | ||||
-rw-r--r-- | src/update/mod.rs | 19 | ||||
-rw-r--r-- | src/videos/display/format_video.rs | 166 | ||||
-rw-r--r-- | src/videos/display/mod.rs | 314 | ||||
-rw-r--r-- | src/videos/mod.rs | 14 |
14 files changed, 567 insertions, 132 deletions
diff --git a/Cargo.lock b/Cargo.lock index 027c460..aa719bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1034,6 +1034,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] +name = "owo-colors" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" + +[[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2343,6 +2349,7 @@ dependencies = [ "libmpv2", "log", "nucleo-matcher", + "owo-colors", "regex", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 9854fb6..7995ab9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ bytes = { path = "./crates/bytes", features = ["serde"] } trinitry = { version = "0.2.2" } toml = "0.8.19" nucleo-matcher = "0.3.1" +owo-colors = "4.1.0" [[bin]] name = "yt" diff --git a/src/main.rs b/src/main.rs index 3f7e410..37283a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ use tokio::{ task::JoinHandle, }; use url::Url; +use videos::display::format_video::FormatVideo; use yt_dlp::wrapper::info_json::InfoJson; use crate::{cli::Command, storage::subscriptions::get_subscriptions}; @@ -128,7 +129,16 @@ async fn main() -> Result<()> { } VideosCommand::Info { hash } => { let video = get_video_by_hash(&app, &hash.realize(&app).await?).await?; - dbg!(video); + + print!( + "{}", + (&video + .to_formatted_video(&app) + .await + .context("Failed to format video")? + .colorize()) + .to_info_display() + ); } }, Command::Update { diff --git a/src/select/cmds.rs b/src/select/cmds.rs index b45cc48..6e71607 100644 --- a/src/select/cmds.rs +++ b/src/select/cmds.rs @@ -19,6 +19,7 @@ use crate::{ VideoOptions, VideoStatus, }, update::video_entry_to_video, + videos::display::format_video::FormatVideo, }; use anyhow::{bail, Context, Result}; @@ -57,7 +58,10 @@ pub async fn handle_select_cmd( entry: yt_dlp::wrapper::info_json::InfoJson, ) -> Result<()> { let video = video_entry_to_video(entry, None)?; - println!("{}", video.to_color_display(app).await?); + println!( + "{}", + (&video.to_formatted_video(app).await?.colorize()).to_line_display() + ); add_video(app, video).await?; Ok(()) diff --git a/src/select/mod.rs b/src/select/mod.rs index 2663a04..ca7a203 100644 --- a/src/select/mod.rs +++ b/src/select/mod.rs @@ -20,6 +20,7 @@ use crate::{ cli::CliArgs, constants::HELP_STR, storage::video_database::{getters::get_videos, VideoStatus}, + videos::display::format_video::FormatVideo, }; use anyhow::{bail, Context, Result}; @@ -63,23 +64,24 @@ pub async fn select(app: &App, done: bool, use_last_selection: bool) -> Result<( // Warmup 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) = matching_videos.first() { - let _ = vid.to_select_file_display(app).await?; + let _ = vid.to_formatted_video(app).await?; } let mut edit_file = BufWriter::new(&temp_file); join_all( matching_videos - .iter() - .map(|vid| async { vid.to_select_file_display(app).await }) + .into_iter() + .map(|vid| async { vid.to_formatted_video_owned(app).await }) .collect::<Vec<_>>(), ) .await .into_iter() .try_for_each(|line| -> Result<()> { - let line = line?; + let formatted_line = (&line?).to_select_file_display(); + edit_file - .write_all(line.as_bytes()) + .write_all(formatted_line.as_bytes()) .expect("This write should not fail"); Ok(()) diff --git a/src/select/selection_file/display.rs b/src/select/selection_file/display.rs deleted file mode 100644 index 0714015..0000000 --- a/src/select/selection_file/display.rs +++ /dev/null @@ -1,108 +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 std::fmt::Write; - -use anyhow::{Context, Result}; -use chrono::DateTime; -use log::debug; - -use crate::{ - app::App, - select::selection_file::duration::Duration, - storage::video_database::{getters::get_video_opts, Video}, -}; - -macro_rules! c { - ($color:expr, $format:expr) => { - format!("\x1b[{}m{}\x1b[0m", $color, $format) - }; -} - -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 - .with_context(|| format!("Failed to get video options for video: '{}'", self.title))? - .to_cli_flags(app); - let opts_white = if !opts.is_empty() { " " } else { "" }; - - let publish_date = if let Some(date) = self.publish_date { - DateTime::from_timestamp(date, 0) - .expect("This should not fail") - .format("%Y-%m-%d") - .to_string() - } else { - "[No release date]".to_owned() - }; - - let parent_subscription_name = if let Some(sub) = &self.parent_subscription_name { - sub.replace('"', "'") - } else { - "[No author]".to_owned() - }; - - debug!("Formatting video for selection file: {}", self.title); - write!( - f, - r#"{}{}{} {} "{}" "{}" "{}" "{}" "{}"{}"#, - self.status.as_command().trim(), - opts_white, - opts, - self.extractor_hash.into_short_hash(app).await?, - self.title.replace(['"', '„', '”'], "'"), - publish_date, - parent_subscription_name, - Duration::from(self.duration), - self.url.as_str().replace('"', "\\\""), - "\n" - )?; - - Ok(f) - } - - pub async fn to_color_display_owned(self, app: &App) -> Result<String> { - self.to_color_display(app).await - } - pub async fn to_color_display(&self, app: &App) -> Result<String> { - let mut f = String::new(); - - let publish_date = if let Some(date) = self.publish_date { - DateTime::from_timestamp(date, 0) - .expect("This should not fail") - .format("%Y-%m-%d") - .to_string() - } else { - "[No release date]".to_owned() - }; - - let parent_subscription_name = if let Some(sub) = &self.parent_subscription_name { - sub.replace('"', "'") - } else { - "[No author]".to_owned() - }; - - write!( - f, - r#"{} {} {} {} {} {}"#, - c!("31;1", self.status.as_command()), - c!("95;3", self.extractor_hash.into_short_hash(app).await?), - c!("32;1", self.title.replace(['"', '„', '”'], "'")), - c!("37;1", publish_date), - c!("34;1", parent_subscription_name), - c!("35;1", Duration::from(self.duration)), - ) - .expect("This write should always work"); - - Ok(f) - } -} diff --git a/src/select/selection_file/mod.rs b/src/select/selection_file/mod.rs index d228023..45809fa 100644 --- a/src/select/selection_file/mod.rs +++ b/src/select/selection_file/mod.rs @@ -13,7 +13,6 @@ use anyhow::{Context, Result}; use trinitry::Trinitry; -pub mod display; pub mod duration; pub fn process_line(line: &str) -> Result<Option<Vec<String>>> { diff --git a/src/storage/video_database/extractor_hash.rs b/src/storage/video_database/extractor_hash.rs index 62a9eda..c956919 100644 --- a/src/storage/video_database/extractor_hash.rs +++ b/src/storage/video_database/extractor_hash.rs @@ -10,7 +10,7 @@ use std::{collections::HashMap, fmt::Display, str::FromStr}; -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; use blake3::Hash; use log::debug; use tokio::sync::OnceCell; @@ -24,6 +24,12 @@ 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); @@ -78,7 +84,10 @@ impl ExtractorHash { 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?; + 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"); @@ -96,7 +105,9 @@ impl ExtractorHash { } async fn short_hash_to_full_hash(app: &App, s: &ShortHash) -> Result<Hash> { - let all_hashes = get_all_hashes(app).await?; + let all_hashes = get_all_hashes(app) + .await + .context("Failed to fetch all extractor -hashesh from database")?; let needed_chars = s.0.len(); @@ -111,7 +122,9 @@ impl ExtractorHash { 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?; + 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() diff --git a/src/storage/video_database/getters.rs b/src/storage/video_database/getters.rs index f2b0507..29dd014 100644 --- a/src/storage/video_database/getters.rs +++ b/src/storage/video_database/getters.rs @@ -287,7 +287,13 @@ pub async fn get_video_yt_dlp_opts(app: &App, hash: &ExtractorHash) -> Result<Yt ehash ) .fetch_one(&app.database) - .await?; + .await + .with_context(|| { + format!( + "Failed to fetch the `yt_dlp_video_opts` for video: {}", + hash + ) + })?; Ok(YtDlpOptions { subtitle_langs: yt_dlp_options.subtitle_langs, @@ -305,7 +311,8 @@ pub async fn get_video_mpv_opts(app: &App, hash: &ExtractorHash) -> Result<MpvOp ehash ) .fetch_one(&app.database) - .await?; + .await + .with_context(|| format!("Failed to fetch the `mpv_video_opts` for video: {}", hash))?; Ok(MpvOptions { playback_speed: mpv_options.playback_speed, @@ -324,7 +331,8 @@ pub async fn get_video_opts(app: &App, hash: &ExtractorHash) -> Result<VideoOpti ehash ) .fetch_one(&app.database) - .await?; + .await + .with_context(|| format!("Failed to fetch the `video_opts` for video: {}", hash))?; let mpv = MpvOptions { playback_speed: opts.playback_speed, diff --git a/src/storage/video_database/mod.rs b/src/storage/video_database/mod.rs index 0251eb1..1765f79 100644 --- a/src/storage/video_database/mod.rs +++ b/src/storage/video_database/mod.rs @@ -19,7 +19,7 @@ pub mod extractor_hash; pub mod getters; pub mod setters; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Video { pub cache_path: Option<PathBuf>, pub description: Option<String>, @@ -88,7 +88,7 @@ pub struct YtDlpOptions { /// Cache // yt cache /// | /// Watched // yt watch -#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] pub enum VideoStatus { #[default] Pick, diff --git a/src/update/mod.rs b/src/update/mod.rs index ce3a7f9..6abb8c4 100644 --- a/src/update/mod.rs +++ b/src/update/mod.rs @@ -29,6 +29,7 @@ use crate::{ VideoStatus, }, }, + videos::display::format_video::FormatVideo, }; pub async fn update( @@ -103,7 +104,9 @@ pub async fn update( for (url, value) in output_json { let sub = back_subs.get(&url).expect("This was stored before"); - process_subscription(app, sub, value, &hashes).await? + process_subscription(app, sub, value, &hashes) + .await + .with_context(|| format!("Failed to process subscription: '{}'", sub.name))? } } @@ -237,8 +240,18 @@ async fn process_subscription( // We already stored the video information unreachable!("The python update script should have never provided us a duplicated video"); } else { - println!("{}", video.to_color_display(app).await?); - add_video(app, video).await?; + add_video(app, video.clone()) + .await + .with_context(|| format!("Failed to add video to database: '{}'", video.title))?; + println!( + "{}", + (&video + .to_formatted_video(app) + .await + .with_context(|| format!("Failed to format video: '{}'", video.title))? + .colorize()) + .to_line_display() + ); Ok(()) } } diff --git a/src/videos/display/format_video.rs b/src/videos/display/format_video.rs new file mode 100644 index 0000000..50646a1 --- /dev/null +++ b/src/videos/display/format_video.rs @@ -0,0 +1,166 @@ +// 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 std::fmt::Display; + +pub trait FormatVideo { + type Output; + + fn cache_path(&self) -> Self::Output; + fn description(&self) -> Self::Output; + fn duration(&self) -> Self::Output; + fn extractor_hash(&self) -> Self::Output; + fn last_status_change(&self) -> Self::Output; + fn parent_subscription_name(&self) -> Self::Output; + fn priority(&self) -> Self::Output; + fn publish_date(&self) -> Self::Output; + fn status(&self) -> Self::Output; + fn status_change(&self) -> Self::Output; + fn thumbnail_url(&self) -> Self::Output; + fn title(&self) -> Self::Output; + fn url(&self) -> Self::Output; + fn video_options(&self) -> Self::Output; + + fn to_parts( + &self, + ) -> ( + Self::Output, + Self::Output, + Self::Output, + Self::Output, + Self::Output, + Self::Output, + Self::Output, + Self::Output, + Self::Output, + Self::Output, + Self::Output, + Self::Output, + Self::Output, + Self::Output, + ) { + let cache_path = self.cache_path(); + let description = self.description(); + let duration = self.duration(); + let extractor_hash = self.extractor_hash(); + let last_status_change = self.last_status_change(); + let parent_subscription_name = self.parent_subscription_name(); + let priority = self.priority(); + let publish_date = self.publish_date(); + let status = self.status(); + let status_change = self.status_change(); + let thumbnail_url = self.thumbnail_url(); + let title = self.title(); + let url = self.url(); + let video_options = self.video_options(); + + ( + cache_path, + description, + duration, + extractor_hash, + last_status_change, + parent_subscription_name, + priority, + publish_date, + status, + status_change, + thumbnail_url, + title, + url, + video_options, + ) + } + + fn to_info_display(&self) -> String + where + <Self as FormatVideo>::Output: Display, + { + let ( + cache_path, + description, + duration, + extractor_hash, + last_status_change, + parent_subscription_name, + priority, + publish_date, + status, + status_change, + thumbnail_url, + title, + url, + video_options, + ) = self.to_parts(); + + let status_change = if status_change.to_string().as_str() == "false" { + "currently not changing" + } else if status_change.to_string().as_str() == "true" { + "currently changing" + } else { + unreachable!("This is an formatted boolean"); + }; + + let string = format!( + "\ +{title} ({extractor_hash}) +| -> {cache_path} +| -> {duration} +| -> {parent_subscription_name} +| -> priority: {priority} +| -> {publish_date} +| -> status: {status} since {last_status_change} +| -> {status_change} +| -> {thumbnail_url} +| -> {url} +| -> options: {} +{description}\n", + video_options.to_string().trim() + ); + string + } + + fn to_line_display(&self) -> String + where + Self::Output: Display, + { + let f = format!( + "{} {} {} {} {} {}", + self.status(), + self.extractor_hash(), + self.title(), + self.publish_date(), + self.parent_subscription_name(), + self.duration() + ); + + f + } + + fn to_select_file_display(&self) -> String + where + Self::Output: Display, + { + let f = format!( + r#"{}{} {} "{}" "{}" "{}" "{}" "{}"{}"#, + self.status(), + self.video_options(), + self.extractor_hash(), + self.title(), + self.publish_date(), + self.parent_subscription_name(), + self.duration(), + self.url(), + '\n' + ); + + f + } +} diff --git a/src/videos/display/mod.rs b/src/videos/display/mod.rs new file mode 100644 index 0000000..d919dd2 --- /dev/null +++ b/src/videos/display/mod.rs @@ -0,0 +1,314 @@ +// 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 std::path::PathBuf; + +use chrono::DateTime; +use format_video::FormatVideo; +use owo_colors::OwoColorize; +use url::Url; + +use crate::{ + app::App, + select::selection_file::duration::Duration, + storage::video_database::{getters::get_video_opts, Video}, +}; + +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() + } + }; +} + +/// This is identical to a [`FormattedVideo`], but has colorized fields. +pub struct ColorizedFormattedVideo(FormattedVideo); + +impl FormattedVideo { + pub fn colorize(self) -> ColorizedFormattedVideo { + let Self { + cache_path, + description, + duration, + extractor_hash, + last_status_change, + parent_subscription_name, + priority, + publish_date, + status, + status_change, + thumbnail_url, + title, + url, + video_options, + } = self; + + ColorizedFormattedVideo(Self { + cache_path: cache_path.blue().bold().to_string(), + description, + duration: duration.cyan().bold().to_string(), + extractor_hash: extractor_hash.bright_purple().italic().to_string(), + last_status_change: last_status_change.bright_cyan().to_string(), + parent_subscription_name: parent_subscription_name.bright_magenta().to_string(), + priority, + publish_date: publish_date.bright_white().bold().to_string(), + status: status.red().bold().to_string(), + status_change, + thumbnail_url, + title: title.green().bold().to_string(), + url: url.italic().to_string(), + video_options: video_options.bright_green().to_string(), + }) + } +} + +/// This is a version of [`Video`] that has all the fields of the original [`Video`] structure +/// turned to [`String`]s to facilitate displaying it. +/// +/// This structure provides a way to display a [`Video`] in a coherent way, as it enforces to +/// always use the same colors for one field. +#[derive(Debug)] +pub struct FormattedVideo { + cache_path: String, + description: String, + duration: String, + extractor_hash: String, + last_status_change: String, + parent_subscription_name: String, + priority: String, + publish_date: String, + status: String, + status_change: String, + thumbnail_url: String, + title: String, + url: String, + /// This string contains the video options (speed, subtitle_languages, etc.). + /// It already starts with an extra whitespace, when these are not empty. + video_options: String, +} + +impl Video { + pub async fn to_formatted_video_owned(self, app: &App) -> Result<FormattedVideo> { + Self::to_formatted_video(&self, app).await + } + + pub async fn to_formatted_video(&self, app: &App) -> Result<FormattedVideo> { + fn date_from_stamp(stamp: i64) -> String { + DateTime::from_timestamp(stamp, 0) + .expect("The timestamps should always be valid") + .format("%Y-%m-%d") + .to_string() + } + + let cache_path: String = get!( + self, + cache_path, + "Cache Path", + (|value: &PathBuf| value.to_string_lossy().to_string()) + ); + let description = get!( + self, + description, + "Description", + (|value: &str| value.to_owned()) + ); + let duration = Duration::from(self.duration); + let extractor_hash = self + .extractor_hash + .into_short_hash(app) + .await + .with_context(|| { + format!( + "Failed to format extractor hash, whilst formatting video: '{}'", + self.title + ) + })?; + let last_status_change = date_from_stamp(self.last_status_change); + let parent_subscription_name = get!( + self, + parent_subscription_name, + "author", + (|sub: &str| sub.replace('"', "'")) + ); + let priority = self.priority; + let publish_date = get!( + self, + publish_date, + "release date", + (|date: &i64| date_from_stamp(*date)) + ); + // 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_command(); + let status_change = self.status_change; + let thumbnail_url = get!( + self, + thumbnail_url, + "thumbnail URL", + (|url: &Url| url.to_string()) + ); + let title = self.title.replace(['"', '„', '”'], "'"); + let url = self.url.as_str().replace('"', "\\\""); + + 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(FormattedVideo { + cache_path, + description, + duration: duration.to_string(), + extractor_hash: extractor_hash.to_string(), + last_status_change, + parent_subscription_name, + priority: priority.to_string(), + publish_date, + status: status.to_string(), + status_change: status_change.to_string(), + thumbnail_url, + title, + url, + video_options, + }) + } +} + +impl<'a> FormatVideo for &'a FormattedVideo { + type Output = &'a str; + + fn cache_path(&self) -> Self::Output { + &self.cache_path + } + + fn description(&self) -> Self::Output { + &self.description + } + + fn duration(&self) -> Self::Output { + &self.duration + } + + fn extractor_hash(&self) -> Self::Output { + &self.extractor_hash + } + + fn last_status_change(&self) -> Self::Output { + &self.last_status_change + } + + fn parent_subscription_name(&self) -> Self::Output { + &self.parent_subscription_name + } + + fn priority(&self) -> Self::Output { + &self.priority + } + + fn publish_date(&self) -> Self::Output { + &self.publish_date + } + + fn status(&self) -> Self::Output { + &self.status + } + + fn status_change(&self) -> Self::Output { + &self.status_change + } + + fn thumbnail_url(&self) -> Self::Output { + &self.thumbnail_url + } + + fn title(&self) -> Self::Output { + &self.title + } + + fn url(&self) -> Self::Output { + &self.url + } + + fn video_options(&self) -> Self::Output { + &self.video_options + } +} +impl<'a> FormatVideo for &'a ColorizedFormattedVideo { + type Output = &'a str; + + fn cache_path(&self) -> Self::Output { + &self.0.cache_path + } + + fn description(&self) -> Self::Output { + &self.0.description + } + + fn duration(&self) -> Self::Output { + &self.0.duration + } + + fn extractor_hash(&self) -> Self::Output { + &self.0.extractor_hash + } + + fn last_status_change(&self) -> Self::Output { + &self.0.last_status_change + } + + fn parent_subscription_name(&self) -> Self::Output { + &self.0.parent_subscription_name + } + + fn priority(&self) -> Self::Output { + &self.0.priority + } + + fn publish_date(&self) -> Self::Output { + &self.0.publish_date + } + + fn status(&self) -> Self::Output { + &self.0.status + } + + fn status_change(&self) -> Self::Output { + &self.0.status_change + } + + fn thumbnail_url(&self) -> Self::Output { + &self.0.thumbnail_url + } + + fn title(&self) -> Self::Output { + &self.0.title + } + + fn url(&self) -> Self::Output { + &self.0.url + } + + fn video_options(&self) -> Self::Output { + &self.0.video_options + } +} diff --git a/src/videos/mod.rs b/src/videos/mod.rs index 3876bd1..59baa8c 100644 --- a/src/videos/mod.rs +++ b/src/videos/mod.rs @@ -9,12 +9,15 @@ // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. use anyhow::Result; +use display::{format_video::FormatVideo, FormattedVideo}; use futures::{stream::FuturesUnordered, TryStreamExt}; use nucleo_matcher::{ pattern::{CaseMatching, Normalization, Pattern}, Matcher, }; +pub mod display; + use crate::{ app::App, storage::video_database::{getters::get_videos, VideoStatus}, @@ -25,7 +28,7 @@ pub async fn query(app: &App, limit: Option<usize>, search_query: Option<String> // turn one video to a color display, to pre-warm the hash shrinking cache if let Some(val) = all_videos.first() { - val.to_color_display(app).await?; + val.to_formatted_video(app).await?; } let limit = limit.unwrap_or(all_videos.len()); @@ -33,10 +36,13 @@ pub async fn query(app: &App, limit: Option<usize>, search_query: Option<String> let all_video_strings: Vec<String> = all_videos .into_iter() .take(limit) - .map(|vid| vid.to_color_display_owned(app)) + .map(|vid| vid.to_formatted_video_owned(app)) .collect::<FuturesUnordered<_>>() - .try_collect() - .await?; + .try_collect::<Vec<FormattedVideo>>() + .await? + .into_iter() + .map(|vid| (&vid.colorize()).to_line_display()) + .collect(); if let Some(query) = search_query { let mut matcher = Matcher::new(nucleo_matcher::Config::DEFAULT.match_paths()); |