// yt - A fully featured command line YouTube client // // Copyright (C) 2024 Benedikt Peetz // 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 . use std::path::PathBuf; use anyhow::Context; use bytes::Bytes; use chrono::NaiveDate; use clap::{ArgAction, Args, Parser, Subcommand}; use url::Url; use crate::{ select::selection_file::duration::Duration, storage::video_database::extractor_hash::LazyExtractorHash, }; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] /// An command line interface to select, download and watch videos pub struct CliArgs { #[command(subcommand)] /// The subcommand to execute [default: select] pub command: Option, /// Increase message verbosity #[arg(long="verbose", short = 'v', action = ArgAction::Count)] pub verbosity: u8, /// Set the path to the videos.db. This overrides the default and the config file. #[arg(long, short)] pub db_path: Option, /// Set the path to the config.toml. /// This overrides the default. #[arg(long, short)] pub config_path: Option, /// Silence all output #[arg(long, short = 'q')] pub quiet: bool, } #[derive(Subcommand, Debug)] pub enum Command { /// Download and cache URLs Download { /// Forcefully re-download all cached videos (i.e. delete the cache path, then download). #[arg(short, long)] force: bool, /// The maximum size the download dir should have. Beware that the value must be given in /// bytes. #[arg(short, long, value_parser = byte_parser)] max_cache_size: Option, }, /// Select, download and watch in one command. Sedowa {}, /// Work with single videos Videos { #[command(subcommand)] cmd: VideosCommand, }, /// Watch the already cached (and selected) videos Watch {}, /// Show, which videos have been selected to be watched (and their cache status) Status {}, /// Show, the configuration options in effect Config {}, /// Perform various tests Check { #[command(subcommand)] command: CheckCommand, }, /// Display the comments of the currently playing video Comments {}, /// Display the description of the currently playing video Description {}, /// Manipulate the video cache in the database #[command(visible_alias = "db")] Database { #[command(subcommand)] command: CacheCommand, }, /// Change the state of videos in the database (the default) Select { #[command(subcommand)] cmd: Option, }, /// Update the video database Update { #[arg(short, long)] /// The number of videos to updating max_backlog: Option, #[arg(short, long)] /// The subscriptions to update (can be given multiple times) subscriptions: Vec, }, /// Manipulate subscription #[command(visible_alias = "subs")] Subscriptions { #[command(subcommand)] cmd: SubscriptionCommand, }, } fn byte_parser(input: &str) -> Result { Ok(input .parse::() .with_context(|| format!("Failed to parse '{}' as bytes!", input))? .as_u64()) } impl Default for Command { fn default() -> Self { Self::Select { cmd: Some(SelectCommand::default()), } } } #[derive(Subcommand, Clone, Debug)] pub enum VideosCommand { /// List the videos in the database #[command(visible_alias = "ls")] List { /// An optional search query to limit the results #[arg(action = ArgAction::Append)] search_query: Option, /// The number of videos to show #[arg(short, long)] limit: Option, }, /// Get detailed information about a video Info { /// The short hash of the video hash: LazyExtractorHash, }, } #[derive(Subcommand, Clone, Debug)] pub enum SubscriptionCommand { /// Subscribe to an URL Add { #[arg(short, long)] /// The human readable name of the subscription name: Option, /// The URL to listen to url: Url, }, /// Unsubscribe from an URL Remove { /// The human readable name of the subscription name: String, }, /// Import a bunch of URLs as subscriptions. Import { /// The file containing the URLs. Will use Stdin otherwise. file: Option, /// Remove any previous subscriptions #[arg(short, long)] force: bool, }, /// Write all subscriptions in an format understood by `import` Export {}, /// List all subscriptions List {}, } #[derive(Clone, Debug, Args)] #[command(infer_subcommands = true)] /// Mark the video given by the hash to be watched pub struct SharedSelectionCommandArgs { /// The ordering priority (higher means more at the top) #[arg(short, long)] pub priority: Option, /// The subtitles to download (e.g. 'en,de,sv') #[arg(short = 'l', long)] pub subtitle_langs: Option, /// The speed to set mpv to #[arg(short, long)] pub speed: Option, /// The short extractor hash pub hash: LazyExtractorHash, pub title: String, pub date: NaiveDate, pub publisher: String, pub duration: Duration, pub url: Url, } #[derive(Subcommand, Clone, Debug)] // NOTE: Keep this in sync with the [`constants::HELP_STR`] constant. <2024-08-20> pub enum SelectCommand { /// Open a `git rebase` like file to select the videos to watch (the default) File { /// Include done (watched, dropped) videos #[arg(long, short)] done: bool, /// Use the last selection file (useful if you've spend time on it and want to get it again) #[arg(long, short, conflicts_with = "done")] use_last_selection: bool, }, /// Mark the video given by the hash to be watched #[command(visible_alias = "w")] Watch { #[command(flatten)] shared: SharedSelectionCommandArgs, }, /// Mark the video given by the hash to be dropped #[command(visible_alias = "d")] Drop { #[command(flatten)] shared: SharedSelectionCommandArgs, }, /// Mark the video given by the hash as already watched #[command(visible_alias = "wd")] Watched { #[command(flatten)] shared: SharedSelectionCommandArgs, }, /// Open the video URL in Firefox's `timesinks.youtube` profile #[command(visible_alias = "u")] Url { #[command(flatten)] shared: SharedSelectionCommandArgs, }, /// Reset the videos status to 'Pick' #[command(visible_alias = "p")] Pick { #[command(flatten)] shared: SharedSelectionCommandArgs, }, } impl Default for SelectCommand { fn default() -> Self { Self::File { done: false, use_last_selection: false, } } } #[derive(Subcommand, Clone, Debug)] pub enum CheckCommand { /// Check if the given info.json is deserializable InfoJson { path: PathBuf }, /// Check if the given update info.json is deserializable UpdateInfoJson { path: PathBuf }, } #[derive(Subcommand, Clone, Copy, Debug)] pub enum CacheCommand { /// Invalidate all cache entries Invalidate { /// Also delete the cache path #[arg(short, long)] hard: bool, }, /// Perform basic maintenance operations on the database. /// This helps recovering from invalid db states after a crash (or force exit via CTRL+C). /// /// 1. Check every path for validity (removing all invalid cache entries) /// 2. Reset all `status_change` bits of videos to false. #[command(verbatim_doc_comment)] Maintain { /// Check every video (otherwise only the videos to be watched are checked) #[arg(short, long)] all: bool, }, }