diff options
Diffstat (limited to 'src/videos')
-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 | 66 |
3 files changed, 0 insertions, 546 deletions
diff --git a/src/videos/display/format_video.rs b/src/videos/display/format_video.rs deleted file mode 100644 index 50646a1..0000000 --- a/src/videos/display/format_video.rs +++ /dev/null @@ -1,166 +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::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 deleted file mode 100644 index d919dd2..0000000 --- a/src/videos/display/mod.rs +++ /dev/null @@ -1,314 +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::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 deleted file mode 100644 index 59baa8c..0000000 --- a/src/videos/mod.rs +++ /dev/null @@ -1,66 +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 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}, -}; - -pub async fn query(app: &App, limit: Option<usize>, search_query: Option<String>) -> Result<()> { - let all_videos = get_videos(app, VideoStatus::ALL, None).await?; - - // turn one video to a color display, to pre-warm the hash shrinking cache - if let Some(val) = all_videos.first() { - val.to_formatted_video(app).await?; - } - - let limit = limit.unwrap_or(all_videos.len()); - - let all_video_strings: Vec<String> = all_videos - .into_iter() - .take(limit) - .map(|vid| vid.to_formatted_video_owned(app)) - .collect::<FuturesUnordered<_>>() - .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()); - - let matches = Pattern::parse( - &query.replace(' ', "\\ "), - CaseMatching::Ignore, - Normalization::Smart, - ) - .match_list(all_video_strings, &mut matcher); - - matches - .iter() - .rev() - .for_each(|(val, key)| println!("{} ({})", val, key)); - } else { - println!("{}", all_video_strings.join("\n")) - } - - Ok(()) -} |