aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml4
-rw-r--r--crates/colors/Cargo.toml26
-rw-r--r--crates/colors/src/custom.rs65
-rw-r--r--crates/colors/src/lib.rs87
-rw-r--r--crates/colors/src/list.rs223
-rw-r--r--crates/colors/src/support.rs116
-rw-r--r--crates/yt/Cargo.toml1
-rw-r--r--crates/yt/src/config/mod.rs16
-rw-r--r--crates/yt/src/storage/db/insert/playlist.rs3
-rw-r--r--crates/yt/src/videos/mod.rs210
-rw-r--r--crates/yt/tests/_testenv/run.rs8
-rw-r--r--crates/yt/tests/watch/mod.rs12
12 files changed, 730 insertions, 41 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 64b8091..f985372 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,11 +11,12 @@
[workspace]
resolver = "2"
members = [
- "crates/yt_dlp",
+ "crates/colors",
"crates/libmpv2",
"crates/libmpv2/libmpv2-sys",
"crates/termsize",
"crates/yt",
+ "crates/yt_dlp",
]
[workspace.package]
@@ -33,6 +34,7 @@ yt_dlp = { path = "./crates/yt_dlp" }
libmpv2 = { path = "./crates/libmpv2" }
termsize = { path = "./crates/termsize" }
uu_fmt = { path = "./crates/fmt" }
+colors = { path = "./crates/colors" }
# Shared
pyo3 = { version = "0.25.1", features = ["macros"], default-features = false }
diff --git a/crates/colors/Cargo.toml b/crates/colors/Cargo.toml
new file mode 100644
index 0000000..4edefcf
--- /dev/null
+++ b/crates/colors/Cargo.toml
@@ -0,0 +1,26 @@
+# yt - A fully featured command line YouTube client
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# Copyright (C) 2025 uutils developers
+# SPDX-License-Identifier: MIT
+#
+# 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>.
+
+[package]
+name = "colors"
+authors.workspace = true
+license.workspace = true
+description = "A owo-colors inspired color crate."
+version.workspace = true
+edition.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+publish = false
+
+[dependencies]
+
+[lints]
+workspace = true
diff --git a/crates/colors/src/custom.rs b/crates/colors/src/custom.rs
new file mode 100644
index 0000000..2adcfa9
--- /dev/null
+++ b/crates/colors/src/custom.rs
@@ -0,0 +1,65 @@
+// Taken from <https://github.com/owo-colors/owo-colors/blob/61f8bba2f5f80e9f4fa600fbfdf2c21656f1d523/src/colors/custom.rs>
+// at 2025-07-16T18:05:55 CEST.
+
+const U8_TO_STR: [[u8; 3]; 256] = generate_lookup();
+
+const fn generate_lookup() -> [[u8; 3]; 256] {
+ let mut table = [[0, 0, 0]; 256];
+
+ let mut i = 0;
+ while i < 256 {
+ table[i] = [
+ b'0' + (i / 100) as u8,
+ b'0' + (i / 10 % 10) as u8,
+ b'0' + (i % 10) as u8,
+ ];
+ i += 1;
+ }
+
+ table
+}
+
+#[derive(Clone, Copy)]
+pub(crate) enum Plane {
+ Fg,
+ Bg,
+}
+
+pub(crate) const fn rgb_to_ansi(r: u8, g: u8, b: u8, plane: Plane) -> [u8; 18] {
+ let mut buf = *b"\x1b[p8;2;rrr;ggg;bbb";
+
+ let r = U8_TO_STR[r as usize];
+ let g = U8_TO_STR[g as usize];
+ let b = U8_TO_STR[b as usize];
+
+ // p 2
+ buf[2] = match plane {
+ Plane::Fg => b'3',
+ Plane::Bg => b'4',
+ };
+
+ // r 7
+ buf[7] = r[0];
+ buf[8] = r[1];
+ buf[9] = r[2];
+
+ // g 11
+ buf[11] = g[0];
+ buf[12] = g[1];
+ buf[13] = g[2];
+
+ // b 15
+ buf[15] = b[0];
+ buf[16] = b[1];
+ buf[17] = b[2];
+
+ buf
+}
+
+/// This exists since [`unwrap()`] isn't const-safe (it invokes formatting infrastructure)
+pub(crate) const fn bytes_to_str(bytes: &'static [u8]) -> &'static str {
+ match core::str::from_utf8(bytes) {
+ Ok(o) => o,
+ Err(_e) => panic!("Const parsing &[u8] to a string failed!"),
+ }
+}
diff --git a/crates/colors/src/lib.rs b/crates/colors/src/lib.rs
new file mode 100644
index 0000000..ee2c1f8
--- /dev/null
+++ b/crates/colors/src/lib.rs
@@ -0,0 +1,87 @@
+use std::fmt::{Display, Write};
+
+use crate::{
+ list::{elements, methods},
+ support::{CSE, CSI, elements_inner},
+};
+
+pub mod custom;
+mod list;
+mod support;
+
+#[derive(Debug)]
+pub struct Canvas<I: Display>(I);
+
+impl<I: Display> Colorize for Canvas<I> {
+ fn render_into(self, base: &mut String, use_colors: bool) {
+ write!(base, "{}", self.0).expect("Is written into a string");
+
+ if use_colors {
+ // Reset the color and style, if we used colours.
+ base.write_str(CSI).expect("In-memory write");
+ base.write_str("0").expect("In-memory write");
+ base.write_str(CSE).expect("In-memory write");
+ }
+ }
+}
+
+pub trait IntoCanvas: Display + Sized {
+ fn into_canvas(self) -> Canvas<Self> {
+ Canvas(self)
+ }
+
+ methods! { IntoCanvas }
+}
+
+impl<I: Display> IntoCanvas for I {}
+
+pub trait Colorize: Sized {
+ /// Turn this colorized struct into a string, by writing into the base.
+ fn render_into(self, base: &mut String, use_colors: bool);
+
+ /// Turn this colorized struct into a string for consumption.
+ fn render(self, use_colors: bool) -> String {
+ let mut base = String::new();
+ self.render_into(&mut base, use_colors);
+ base
+ }
+
+ methods! { Colorize }
+}
+
+elements! {}
+
+#[cfg(test)]
+mod tests {
+ use crate::{Colorize, IntoCanvas};
+
+ #[test]
+ fn test_colorize_basic() {
+ let base = "Base".green().render(true);
+ #[rustfmt::skip]
+ let expected = concat!(
+ "\x1b[32m",
+ "Base",
+ "\x1b[0m",
+ );
+
+ assert_eq!(base.as_str(), expected);
+ }
+
+ #[test]
+ fn test_colorize_combo() {
+ let base = "Base".green().on_red().bold().strike_through().render(true);
+
+ #[rustfmt::skip]
+ let expected = concat!(
+ "\x1b[9m", // strike_through
+ "\x1b[1m", // bold
+ "\x1b[41m", // on_red
+ "\x1b[32m", // green
+ "Base",
+ "\x1b[0m",
+ );
+
+ assert_eq!(base.as_str(), expected);
+ }
+}
diff --git a/crates/colors/src/list.rs b/crates/colors/src/list.rs
new file mode 100644
index 0000000..ecbe465
--- /dev/null
+++ b/crates/colors/src/list.rs
@@ -0,0 +1,223 @@
+use crate::support::prepend_input;
+
+prepend_input! {
+ crate::support::methods_inner as methods (($tt:tt) -> {$tt}),
+ crate::support::elements_inner as elements,
+
+ <shared_input>
+ {
+ // Colors
+ Black black 30,
+ OnBlack on_black 40,
+
+ Red red 31,
+ OnRed on_red 41,
+
+ Green green 32,
+ OnGreen on_green 42,
+
+ Yellow yellow 33,
+ OnYellow on_yellow 43,
+
+ Blue blue 34,
+ OnBlue on_blue 44,
+
+ Magenta magenta 35,
+ OnMagenta on_magenta 45,
+
+ Cyan cyan 36,
+ OnCyan on_cyan 46,
+
+ White white 37,
+ OnWhite on_white 47,
+
+ Default default 39,
+ OnDefault on_default 49,
+
+ // Bright bright colors
+ BrightBlack bright_black 90,
+ OnBrightBlack on_bright_black 100,
+
+ BrightRed bright_red 91,
+ OnBrightRed on_bright_red 101,
+
+ BrightGreen bright_green 92,
+ OnBrightGreen on_bright_green 102,
+
+ BrightYellow bright_yellow 93,
+ OnBrightYellow on_bright_yellow 103,
+
+ BrightBlue bright_blue 94,
+ OnBrightBlue on_bright_blue 104,
+
+ BrightMagenta bright_magenta 95,
+ OnBrightMagenta on_bright_magenta 105,
+
+ BrightCyan bright_cyan 96,
+ OnBrightCyan on_bright_cyan 106,
+
+ BrightWhite bright_white 97,
+ OnBrightWhite on_bright_white 107,
+
+ // CSS colors
+ // TODO(@bpeetz): Also support background colors with these values. <2025-07-16>
+ AliceBlue alice_blue (240, 248, 255),
+ AntiqueWhite antique_white (250, 235, 215),
+ Aqua aqua (0, 255, 255),
+ Aquamarine aquamarine (127, 255, 212),
+ Azure azure (240, 255, 255),
+ Beige beige (245, 245, 220),
+ Bisque bisque (255, 228, 196),
+ // Black black (0, 0, 0),
+ BlanchedAlmond blanched_almond (255, 235, 205),
+ // Blue blue (0, 0, 255),
+ BlueViolet blue_violet (138, 43, 226),
+ Brown brown (165, 42, 42),
+ BurlyWood burly_wood (222, 184, 135),
+ CadetBlue cadet_blue (95, 158, 160),
+ Chartreuse chartreuse (127, 255, 0),
+ Chocolate chocolate (210, 105, 30),
+ Coral coral (255, 127, 80),
+ CornflowerBlue cornflower_blue (100, 149, 237),
+ Cornsilk cornsilk (255, 248, 220),
+ Crimson crimson (220, 20, 60),
+ DarkBlue dark_blue (0, 0, 139),
+ DarkCyan dark_cyan (0, 139, 139),
+ DarkGoldenRod dark_golden_rod (184, 134, 11),
+ DarkGray dark_gray (169, 169, 169),
+ DarkGrey dark_grey (169, 169, 169),
+ DarkGreen dark_green (0, 100, 0),
+ DarkKhaki dark_khaki (189, 183, 107),
+ DarkMagenta dark_magenta (139, 0, 139),
+ DarkOliveGreen dark_olive_green (85, 107, 47),
+ DarkOrange dark_orange (255, 140, 0),
+ DarkOrchid dark_orchid (153, 50, 204),
+ DarkRed dark_red (139, 0, 0),
+ DarkSalmon dark_salmon (233, 150, 122),
+ DarkSeaGreen dark_sea_green (143, 188, 143),
+ DarkSlateBlue dark_slate_blue (72, 61, 139),
+ DarkSlateGray dark_slate_gray (47, 79, 79),
+ DarkSlateGrey dark_slate_grey (47, 79, 79),
+ DarkTurquoise dark_turquoise (0, 206, 209),
+ DarkViolet dark_violet (148, 0, 211),
+ DeepPink deep_pink (255, 20, 147),
+ DeepSkyBlue deep_sky_blue (0, 191, 255),
+ DimGray dim_gray (105, 105, 105),
+ DimGrey dim_grey (105, 105, 105),
+ DodgerBlue dodger_blue (30, 144, 255),
+ FireBrick fire_brick (178, 34, 34),
+ FloralWhite floral_white (255, 250, 240),
+ ForestGreen forest_green (34, 139, 34),
+ Fuchsia fuchsia (255, 0, 255),
+ Gainsboro gainsboro (220, 220, 220),
+ GhostWhite ghost_white (248, 248, 255),
+ Gold gold (255, 215, 0),
+ GoldenRod golden_rod (218, 165, 32),
+ Gray gray (128, 128, 128),
+ Grey grey (128, 128, 128),
+ // Green green (0, 128, 0),
+ GreenYellow green_yellow (173, 255, 47),
+ HoneyDew honey_dew (240, 255, 240),
+ HotPink hot_pink (255, 105, 180),
+ IndianRed indian_red (205, 92, 92),
+ Indigo indigo (75, 0, 130),
+ Ivory ivory (255, 255, 240),
+ Khaki khaki (240, 230, 140),
+ Lavender lavender (230, 230, 250),
+ LavenderBlush lavender_blush (255, 240, 245),
+ LawnGreen lawn_green (124, 252, 0),
+ LemonChiffon lemon_chiffon (255, 250, 205),
+ LightBlue light_blue (173, 216, 230),
+ LightCoral light_coral (240, 128, 128),
+ LightCyan light_cyan (224, 255, 255),
+ LightGoldenRodYellow light_golden_rod_yellow (250, 250, 210),
+ LightGray light_gray (211, 211, 211),
+ LightGrey light_grey (211, 211, 211),
+ LightGreen light_green (144, 238, 144),
+ LightPink light_pink (255, 182, 193),
+ LightSalmon light_salmon (255, 160, 122),
+ LightSeaGreen light_sea_green (32, 178, 170),
+ LightSkyBlue light_sky_blue (135, 206, 250),
+ LightSlateGray light_slate_gray (119, 136, 153),
+ LightSlateGrey light_slate_grey (119, 136, 153),
+ LightSteelBlue light_steel_blue (176, 196, 222),
+ LightYellow light_yellow (255, 255, 224),
+ Lime lime (0, 255, 0),
+ LimeGreen lime_green (50, 205, 50),
+ Linen linen (250, 240, 230),
+ // Magenta magenta (255, 0, 255),
+ Maroon maroon (128, 0, 0),
+ MediumAquaMarine medium_aqua_marine (102, 205, 170),
+ MediumBlue medium_blue (0, 0, 205),
+ MediumOrchid medium_orchid (186, 85, 211),
+ MediumPurple medium_purple (147, 112, 219),
+ MediumSeaGreen medium_sea_green (60, 179, 113),
+ MediumSlateBlue medium_slate_blue (123, 104, 238),
+ MediumSpringGreen medium_spring_green (0, 250, 154),
+ MediumTurquoise medium_turquoise (72, 209, 204),
+ MediumVioletRed medium_violet_red (199, 21, 133),
+ MidnightBlue midnight_blue (25, 25, 112),
+ MintCream mint_cream (245, 255, 250),
+ MistyRose misty_rose (255, 228, 225),
+ Moccasin moccasin (255, 228, 181),
+ NavajoWhite navajo_white (255, 222, 173),
+ Navy navy (0, 0, 128),
+ OldLace old_lace (253, 245, 230),
+ Olive olive (128, 128, 0),
+ OliveDrab olive_drab (107, 142, 35),
+ Orange orange (255, 165, 0),
+ OrangeRed orange_red (255, 69, 0),
+ Orchid orchid (218, 112, 214),
+ PaleGoldenRod pale_golden_rod (238, 232, 170),
+ PaleGreen pale_green (152, 251, 152),
+ PaleTurquoise pale_turquoise (175, 238, 238),
+ PaleVioletRed pale_violet_red (219, 112, 147),
+ PapayaWhip papaya_whip (255, 239, 213),
+ PeachPuff peach_puff (255, 218, 185),
+ Peru peru (205, 133, 63),
+ Pink pink (255, 192, 203),
+ Plum plum (221, 160, 221),
+ PowderBlue powder_blue (176, 224, 230),
+ Purple purple (128, 0, 128),
+ RebeccaPurple rebecca_purple (102, 51, 153),
+ // Red red (255, 0, 0),
+ RosyBrown rosy_brown (188, 143, 143),
+ RoyalBlue royal_blue (65, 105, 225),
+ SaddleBrown saddle_brown (139, 69, 19),
+ Salmon salmon (250, 128, 114),
+ SandyBrown sandy_brown (244, 164, 96),
+ SeaGreen sea_green (46, 139, 87),
+ SeaShell sea_shell (255, 245, 238),
+ Sienna sienna (160, 82, 45),
+ Silver silver (192, 192, 192),
+ SkyBlue sky_blue (135, 206, 235),
+ SlateBlue slate_blue (106, 90, 205),
+ SlateGray slate_gray (112, 128, 144),
+ SlateGrey slate_grey (112, 128, 144),
+ Snow snow (255, 250, 250),
+ SpringGreen spring_green (0, 255, 127),
+ SteelBlue steel_blue (70, 130, 180),
+ Tan tan (210, 180, 140),
+ Teal teal (0, 128, 128),
+ Thistle thistle (216, 191, 216),
+ Tomato tomato (255, 99, 71),
+ Turquoise turquoise (64, 224, 208),
+ Violet violet (238, 130, 238),
+ Wheat wheat (245, 222, 179),
+ // White white (255, 255, 255),
+ WhiteSmoke white_smoke (245, 245, 245),
+ // Yellow yellow (255, 255, 0),
+ YellowGreen yellow_green (154, 205, 50),
+
+ // Styles
+ Bold bold 1,
+ Dim dim 2,
+ Italic italic 3,
+ Underline underline 4,
+ Blink blink 5,
+ BlinkFast blink_fast 6,
+ Reversed reversed 7,
+ Hidden hidden 8,
+ StrikeThrough strike_through 9,
+ }
+}
diff --git a/crates/colors/src/support.rs b/crates/colors/src/support.rs
new file mode 100644
index 0000000..b42ce5d
--- /dev/null
+++ b/crates/colors/src/support.rs
@@ -0,0 +1,116 @@
+pub(super) const CSI: &str = "\x1b[";
+pub(super) const CSE: &str = "m";
+
+macro_rules! elements_inner {
+ (
+ $(
+ $name:ident $_:ident $number:tt
+ ),*
+ $(,)?
+ ) => {
+ $(
+ #[derive(Debug)]
+ pub struct $name<I: Colorize>(I);
+
+ impl<I: Colorize> Colorize for $name<I> {
+ fn render_into(self, base: &mut String, use_colors: bool) {
+ elements_inner! {@parse_number $number}
+
+ if use_colors {
+ base.write_str(CSI).expect("In-memory write");
+ base.write_str(NUMBERS).expect("In-memory write");
+ base.write_str(CSE).expect("In-memory write");
+ }
+ self.0.render_into(base, use_colors);
+ // The canvas is resetting the colours again.
+ }
+ }
+ )*
+ };
+
+ (@parse_number $single:literal) => {
+ const NUMBERS: &str = stringify!($single);
+ };
+ (@parse_number ($red:literal, $green:literal, $blue:literal)) => {
+ const NUMBERS_U8: [u8; 18] = $crate::custom::rgb_to_ansi($red, $green, $blue, $crate::custom::Plane::Fg);
+
+ const NUMBERS: &str = $crate::custom::bytes_to_str(&NUMBERS_U8);
+ }
+}
+pub(super) use elements_inner;
+
+macro_rules! methods_inner {
+ (
+ Colorize
+
+ $(
+ $struct_name:ident $fn_name:ident $_:tt
+ ),*
+ $(,)?
+ ) => {
+ $(
+ fn $fn_name(self) -> $struct_name<Self> {
+ $struct_name(self)
+ }
+ )*
+ };
+ (
+ IntoCanvas
+
+ $(
+ $struct_name:ident $fn_name:ident $_:tt
+ ),*
+ $(,)?
+ ) => {
+ $(
+ fn $fn_name(self) -> $struct_name<Canvas<Self>> {
+ $struct_name(Canvas(self))
+ }
+ )*
+ };
+}
+pub(super) use methods_inner;
+
+macro_rules! prepend_input {
+ (
+ $(
+ $existing_macro_name:path as $new_macro_name:ident $(($macro_rule:tt -> $macro_apply:tt))?
+ ),*
+ $(,)?
+
+ <shared_input>
+ $shared_input:tt
+ ) => {
+ $(
+ prepend_input! {
+ @generate_macro
+ $existing_macro_name as $new_macro_name $(($macro_rule -> $macro_apply))?
+ <shared_input>
+ $shared_input
+ }
+ )*
+ };
+
+ (
+ @generate_macro
+ $existing_macro_name:path as $new_macro_name:ident $((($($macro_rule:tt)*) -> {$($macro_apply:tt)*}))?
+
+ <shared_input>
+ {
+ $(
+ $shared_input:tt
+ )*
+ }
+ ) => {
+ macro_rules! $new_macro_name {
+ ($($($macro_rule)*)?) => {
+ $existing_macro_name! {
+ $($($macro_apply)*)?
+ $($shared_input)*
+ }
+ }
+ }
+ pub(super) use $new_macro_name;
+ }
+}
+pub(crate) use prepend_input;
diff --git a/crates/yt/Cargo.toml b/crates/yt/Cargo.toml
index d36751c..54a7ed7 100644
--- a/crates/yt/Cargo.toml
+++ b/crates/yt/Cargo.toml
@@ -30,6 +30,7 @@ chrono = { version = "0.4.41", features = ["now"] }
chrono-humanize = "0.2.3"
clap = { version = "4.5.40", features = ["derive"] }
clap_complete = { version = "4.5.54", features = ["unstable-dynamic"] }
+colors.workspace = true
futures = "0.3.31"
owo-colors = "4.2.2"
regex = "1.11.1"
diff --git a/crates/yt/src/config/mod.rs b/crates/yt/src/config/mod.rs
index adbafdd..52962ab 100644
--- a/crates/yt/src/config/mod.rs
+++ b/crates/yt/src/config/mod.rs
@@ -1,8 +1,20 @@
+use std::sync::atomic::{AtomicBool, Ordering};
+
use crate::config::support::mk_config;
mod paths;
mod support;
+pub(crate) static SHOULD_DISPLAY_COLOR: AtomicBool = AtomicBool::new(false);
+
+// We need to do both things to comply with what the config expects.
+#[allow(clippy::trivially_copy_pass_by_ref, clippy::unnecessary_wraps)]
+fn set_static_should_display_color(value: &bool) -> anyhow::Result<()> {
+ SHOULD_DISPLAY_COLOR.store(*value, Ordering::Relaxed);
+
+ Ok(())
+}
+
mk_config! {
use std::path::PathBuf;
use std::io::IsTerminal;
@@ -10,6 +22,8 @@ mk_config! {
use crate::shared::bytes::Bytes;
+ use super::set_static_should_display_color;
+
use super::paths::get_config_path;
use super::paths::get_runtime_path;
use super::paths::get_data_path;
@@ -27,7 +41,7 @@ mk_config! {
.unwrap_or_else(|| std::io::stderr().is_terminal())
)
)
- },
+ } => set_static_should_display_color,
},
select: SelectConfig = {
/// The playback speed to use, when it is not overridden.
diff --git a/crates/yt/src/storage/db/insert/playlist.rs b/crates/yt/src/storage/db/insert/playlist.rs
index 2613fb3..dc474ce 100644
--- a/crates/yt/src/storage/db/insert/playlist.rs
+++ b/crates/yt/src/storage/db/insert/playlist.rs
@@ -1,6 +1,7 @@
use std::{cmp::Ordering, time::Duration};
use anyhow::{Context, Result};
+use colors::Colorize;
use libmpv2::Mpv;
use log::{debug, trace};
@@ -198,7 +199,7 @@ impl Playlist {
debug!(
"Setting the watch progress for the current_video '{}' to {}s",
- current_video.title_fmt_no_color(),
+ current_video.title_fmt().render(false),
watch_progress.as_secs(),
);
diff --git a/crates/yt/src/videos/mod.rs b/crates/yt/src/videos/mod.rs
index 673d46e..3775e0f 100644
--- a/crates/yt/src/videos/mod.rs
+++ b/crates/yt/src/videos/mod.rs
@@ -9,51 +9,197 @@
// 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 futures::{TryStreamExt, stream::FuturesUnordered};
+use std::fmt::Write;
-pub(crate) mod display;
+use anyhow::{Context, Result};
+use colors::{Colorize, IntoCanvas};
+use url::Url;
use crate::{
app::App,
- storage::db::video::{Video, VideoStatusMarker},
+ select::duration::MaybeDuration,
+ storage::db::video::{TimeStamp, Video, VideoStatus},
};
-async fn to_line_display_owned(video: Video, app: &App, format: Option<String>) -> Result<String> {
- video.to_line_display(app, format).await
+pub(crate) 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()
+ }
+ };
}
-pub(crate) async fn query(
- app: &App,
- limit: Option<usize>,
- search_query: Option<String>,
- format: Option<String>,
-) -> Result<()> {
- let all_videos = Video::in_states(app, VideoStatusMarker::ALL).await?;
+pub(crate) trait RenderWithApp: Colorize {
+ fn to_string(self, app: &App) -> String {
+ self.render(app.config.global.display_colors)
+ }
+}
+impl<C: Colorize> RenderWithApp for C {}
+
+impl Video {
+ #[must_use]
+ pub(crate) fn cache_path_fmt(&self) -> impl Colorize {
+ let cache_path = if let VideoStatus::Cached {
+ cache_path,
+ is_focused: _,
+ } = &self.status
+ {
+ cache_path.to_string_lossy().to_string()
+ } else {
+ "[No Cache Path]".to_owned()
+ };
+
+ cache_path.blue().bold()
+ }
- // 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, format.clone()).await?;
+ #[must_use]
+ pub(crate) fn description_fmt(&self) -> impl Colorize {
+ get!(
+ self,
+ description,
+ "Description",
+ (|value: &str| value.to_owned())
+ )
+ .into_canvas()
}
- let limit = limit.unwrap_or(all_videos.len());
+ #[must_use]
+ pub(crate) fn duration_fmt(&self) -> impl Colorize {
+ self.duration.cyan().bold()
+ }
- let all_video_strings: Vec<String> = all_videos
- .into_iter()
- .take(limit)
- .map(|vid| to_line_display_owned(vid, app, format.clone()))
- .collect::<FuturesUnordered<_>>()
- .try_collect::<Vec<String>>()
- .await?;
+ #[must_use]
+ pub(crate) fn watch_progress_fmt(&self) -> impl Colorize {
+ MaybeDuration::from_std(self.watch_progress).cyan().bold()
+ }
- if let Some(query) = search_query {
- all_video_strings
- .into_iter()
- .filter(|video| video.to_lowercase().contains(&query.to_lowercase()))
- .for_each(|video| println!("{video}"));
- } else {
- println!("{}", all_video_strings.join("\n"));
+ pub(crate) async fn extractor_hash_fmt(&self, app: &App) -> Result<impl Colorize> {
+ let hash = self
+ .extractor_hash
+ .as_short_hash(app)
+ .await
+ .with_context(|| {
+ format!(
+ "Failed to format extractor hash, whilst formatting video: '{}'",
+ self.title
+ )
+ })?;
+
+ Ok(hash.purple().bold().italic())
}
- Ok(())
+ #[must_use]
+ pub(crate) fn in_playlist_fmt(&self) -> impl Colorize {
+ let output = match &self.status {
+ VideoStatus::Pick
+ | VideoStatus::Watch
+ | VideoStatus::Watched
+ | VideoStatus::Drop
+ | VideoStatus::Dropped => "Not in the playlist",
+ VideoStatus::Cached { is_focused, .. } => {
+ if *is_focused {
+ "In the playlist and focused"
+ } else {
+ "In the playlist"
+ }
+ }
+ };
+ output.yellow().italic()
+ }
+ #[must_use]
+ pub(crate) fn last_status_change_fmt(&self) -> impl Colorize {
+ self.last_status_change.bright_cyan()
+ }
+
+ #[must_use]
+ pub(crate) fn parent_subscription_name_fmt(&self) -> impl Colorize {
+ let psn = get!(
+ self,
+ parent_subscription_name,
+ "author",
+ (|sub: &str| sub.replace('"', "'"))
+ );
+
+ psn.bright_magenta()
+ }
+
+ #[must_use]
+ pub(crate) fn priority_fmt(&self) -> impl Colorize {
+ self.priority.into_canvas()
+ }
+
+ #[must_use]
+ pub(crate) fn publish_date_fmt(&self) -> impl Colorize {
+ let date = get!(
+ self,
+ publish_date,
+ "release date",
+ (|date: &TimeStamp| date.to_string())
+ );
+
+ date.bright_white().bold()
+ }
+
+ #[must_use]
+ pub(crate) fn status_fmt(&self) -> impl Colorize {
+ // 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_marker().as_command().to_owned();
+
+ status.red().bold()
+ }
+
+ #[must_use]
+ pub(crate) fn thumbnail_url_fmt(&self) -> impl Colorize {
+ get!(
+ self,
+ thumbnail_url,
+ "thumbnail URL",
+ (|url: &Url| url.to_string())
+ )
+ .into_canvas()
+ }
+
+ #[must_use]
+ pub(crate) fn title_fmt(&self) -> impl Colorize {
+ let title = self.title.replace(['"', '„', '”', '“'], "'");
+
+ title.green().bold()
+ }
+
+ #[must_use]
+ pub(crate) fn url_fmt(&self) -> impl Colorize {
+ let url = self.url.as_str().replace('"', "\\\"");
+
+ url.italic()
+ }
+
+ pub(crate) fn video_options_fmt(&self, app: &App) -> impl Colorize {
+ let video_options = {
+ 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}")
+ };
+
+ video_options.bright_green()
+ }
}
diff --git a/crates/yt/tests/_testenv/run.rs b/crates/yt/tests/_testenv/run.rs
index 954e112..74f5e86 100644
--- a/crates/yt/tests/_testenv/run.rs
+++ b/crates/yt/tests/_testenv/run.rs
@@ -3,7 +3,7 @@ use std::{
process::{self, Stdio},
};
-use owo_colors::OwoColorize;
+use colors::{Colorize, IntoCanvas};
use crate::testenv::TestEnv;
@@ -161,7 +161,11 @@ impl TestEnv {
cmd.args(args);
- eprintln!("{} `yt {}`", self.name.blue().italic(), args.join(" "));
+ eprintln!(
+ "{} `yt {}`",
+ self.name.blue().italic().render(true),
+ args.join(" ")
+ );
cmd
}
diff --git a/crates/yt/tests/watch/mod.rs b/crates/yt/tests/watch/mod.rs
index 534c210..9c5a203 100644
--- a/crates/yt/tests/watch/mod.rs
+++ b/crates/yt/tests/watch/mod.rs
@@ -5,7 +5,7 @@ use std::{
path::PathBuf,
};
-use owo_colors::OwoColorize;
+use colors::{Colorize, IntoCanvas};
use serde_json::json;
use yt_dlp::{json_cast, json_get, progress_hook::__priv::vm::common::atomic::Radium};
@@ -72,7 +72,11 @@ impl MpvControl {
.current_request_id
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
- eprint!("{} `mpv {}`", self.name.blue().italic(), args.join(" "));
+ eprint!(
+ "{} `mpv {}`",
+ self.name.blue().italic().render(true),
+ args.join(" ")
+ );
writeln!(
self.stream,
@@ -102,11 +106,11 @@ impl MpvControl {
}
};
- eprintln!(", {}: {data}", "output".bright_blue(),);
+ eprintln!(", {}: {data}", "output".bright_blue().render(true),);
return Ok(data);
}
- eprintln!(", {}: {error}", "error".bright_red());
+ eprintln!(", {}: {error}", "error".bright_red().render(true));
return Err(error.to_owned());
}
}