about summary refs log tree commit diff stats
path: root/yt/src/cli.rs
diff options
context:
space:
mode:
Diffstat (limited to 'yt/src/cli.rs')
-rw-r--r--yt/src/cli.rs318
1 files changed, 318 insertions, 0 deletions
diff --git a/yt/src/cli.rs b/yt/src/cli.rs
new file mode 100644
index 0000000..d19586e
--- /dev/null
+++ b/yt/src/cli.rs
@@ -0,0 +1,318 @@
+// 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 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<Command>,
+
+    /// 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<PathBuf>,
+
+    /// Set the path to the config.toml.
+    /// This overrides the default.
+    #[arg(long, short)]
+    pub config_path: Option<PathBuf>,
+
+    /// 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<u64>,
+    },
+
+    /// Select, download and watch in one command.
+    Sedowa {},
+    /// Download and watch in one command.
+    Dowa {},
+
+    /// 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<SelectCommand>,
+    },
+
+    /// Update the video database
+    Update {
+        #[arg(short, long)]
+        /// The number of videos to updating
+        max_backlog: Option<u32>,
+
+        #[arg(short, long)]
+        /// The subscriptions to update (can be given multiple times)
+        subscriptions: Vec<String>,
+    },
+
+    /// Manipulate subscription
+    #[command(visible_alias = "subs")]
+    Subscriptions {
+        #[command(subcommand)]
+        cmd: SubscriptionCommand,
+    },
+}
+
+fn byte_parser(input: &str) -> Result<u64, anyhow::Error> {
+    Ok(input
+        .parse::<Bytes>()
+        .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<String>,
+
+        /// The number of videos to show
+        #[arg(short, long)]
+        limit: Option<usize>,
+    },
+
+    /// 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<String>,
+
+        /// 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<PathBuf>,
+
+        /// 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<i64>,
+
+    /// The subtitles to download (e.g. 'en,de,sv')
+    #[arg(short = 'l', long)]
+    pub subtitle_langs: Option<String>,
+
+    /// The speed to set mpv to
+    #[arg(short, long)]
+    pub speed: Option<f64>,
+
+    /// 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,
+    },
+
+    /// Add a video to the database
+    #[command(visible_alias = "a")]
+    Add { urls: Vec<Url> },
+
+    /// 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,
+    },
+}