aboutsummaryrefslogtreecommitdiffstats
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,
+ },
+}