diff options
Diffstat (limited to 'src/select/selection_file')
-rw-r--r-- | src/select/selection_file/display.rs | 103 | ||||
-rw-r--r-- | src/select/selection_file/duration.rs | 102 | ||||
-rw-r--r-- | src/select/selection_file/help.str | 10 | ||||
-rw-r--r-- | src/select/selection_file/help.str.license | 9 | ||||
-rw-r--r-- | src/select/selection_file/mod.rs | 35 |
5 files changed, 259 insertions, 0 deletions
diff --git a/src/select/selection_file/display.rs b/src/select/selection_file/display.rs new file mode 100644 index 0000000..12d128c --- /dev/null +++ b/src/select/selection_file/display.rs @@ -0,0 +1,103 @@ +// 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::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? + .to_cli_flags(); + 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(), + 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 fn to_color_display(&self) -> 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!("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"); + + f + } +} diff --git a/src/select/selection_file/duration.rs b/src/select/selection_file/duration.rs new file mode 100644 index 0000000..4224ead --- /dev/null +++ b/src/select/selection_file/duration.rs @@ -0,0 +1,102 @@ +// 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::str::FromStr; + +use anyhow::{Context, Result}; + +#[derive(Copy, Clone, Debug)] +pub struct Duration { + time: u32, +} + +impl FromStr for Duration { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + fn parse_num(str: &str, suffix: char) -> Result<u32> { + str.strip_suffix(suffix) + .expect("it has a 'h' suffix") + .parse::<u32>() + .context("Failed to parse hours") + } + + let buf: Vec<_> = s.split(' ').collect(); + + let hours; + let minutes; + let seconds; + + assert_eq!(buf.len(), 2, "Other lengths should not happen"); + + if buf[0].ends_with('h') { + hours = parse_num(buf[0], 'h')?; + minutes = parse_num(buf[1], 'm')?; + seconds = 0; + } else if buf[0].ends_with('m') { + hours = 0; + minutes = parse_num(buf[0], 'm')?; + seconds = parse_num(buf[1], 's')?; + } else { + unreachable!("The first part always ends with 'h' or 'm'") + } + + Ok(Self { + time: (hours * 60 * 60) + (minutes * 60) + seconds, + }) + } +} + +impl From<Option<f64>> for Duration { + fn from(value: Option<f64>) -> Self { + Self { + time: value.unwrap_or(0.0).ceil() as u32, + } + } +} + +impl std::fmt::Display for Duration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + const SECOND: u32 = 1; + const MINUTE: u32 = 60 * SECOND; + const HOUR: u32 = 60 * MINUTE; + + let base_hour = self.time - (self.time % HOUR); + let base_min = (self.time % HOUR) - ((self.time % HOUR) % MINUTE); + let base_sec = (self.time % HOUR) % MINUTE; + + let h = base_hour / HOUR; + let m = base_min / MINUTE; + let s = base_sec / SECOND; + + if self.time == 0 { + write!(f, "[No Duration]") + } else if h > 0 { + write!(f, "{h}h {m}m") + } else { + write!(f, "{m}m {s}s") + } + } +} +#[cfg(test)] +mod test { + use super::Duration; + + #[test] + fn test_display_duration_1h() { + let dur = Duration { time: 60 * 60 }; + assert_eq!("1h 0m".to_owned(), dur.to_string()); + } + #[test] + fn test_display_duration_30min() { + let dur = Duration { time: 60 * 30 }; + assert_eq!("30m 0s".to_owned(), dur.to_string()); + } +} diff --git a/src/select/selection_file/help.str b/src/select/selection_file/help.str new file mode 100644 index 0000000..6e296f6 --- /dev/null +++ b/src/select/selection_file/help.str @@ -0,0 +1,10 @@ +# Commands: +# w, watch [-p,-s,-l] Mark the video given by the hash to be watched +# d, drop Mark the video given by the hash to be dropped +# u, url Open the video URL in Firefox's `timesinks.youtube` profile +# p, pick Reset the videos status to 'Pick' +# +# See `yt select <cmd_name> --help` for more help. +# +# These lines can be re-ordered; they are executed from top to bottom. +# vim: filetype=yts conceallevel=2 concealcursor=nc colorcolumn= diff --git a/src/select/selection_file/help.str.license b/src/select/selection_file/help.str.license new file mode 100644 index 0000000..d4d410f --- /dev/null +++ b/src/select/selection_file/help.str.license @@ -0,0 +1,9 @@ +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>. diff --git a/src/select/selection_file/mod.rs b/src/select/selection_file/mod.rs new file mode 100644 index 0000000..bdb0866 --- /dev/null +++ b/src/select/selection_file/mod.rs @@ -0,0 +1,35 @@ +// 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>. + +//! The data structures needed to express the file, which the user edits + +use anyhow::{Context, Result}; +use trinitry::Trinitry; + +pub mod display; +pub mod duration; + +pub fn process_line(line: &str) -> Result<Option<Vec<String>>> { + // Filter out comments and empty lines + if line.starts_with('#') || line.trim().is_empty() { + Ok(None) + } else { + // pick 2195db "CouchRecherche? Gunnar und Han von STRG_F sind #mitfunkzuhause" "2020-04-01" "STRG_F - Live" "[1h 5m]" "https://www.youtube.com/watch?v=C8UXOaoMrXY" + + let tri = + Trinitry::new(line).with_context(|| format!("Failed to parse line '{}'", line))?; + + let mut vec = Vec::with_capacity(tri.arguments().len() + 1); + vec.push(tri.command().to_owned()); + vec.extend(tri.arguments().to_vec().into_iter()); + + Ok(Some(vec)) + } +} |