about summary refs log blame commit diff stats
path: root/src/cli.rs
blob: 42b637881cf04b1c68cd1562c6297b20757b26ed (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12










                                                                          
                    
                 



                                                
                                               










                                                                  
                                                                  
                      
                                                                                      

                                 



                                        










                                                                                                 

                                                                                                 
                                                       
      




                               




                                                                                    

                                                 
























                                                                
                           
                                            
                                 


                                                                     








                                      



                                                                           
 







                                                





                                                         


                                        








                                              






















                                                                   
                    
      
                                                                   
                              
            




                                                  


                                                            






                                                   













                                   





                                                                                  


                                                                                                    
      
                                                      

                                           

                                                      
                                   



                                           
                                                           
                                    



                                           
                                                                   
                                   




                                           
                                   





                                           


                                      































                                                                                              
// 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>,
    },

    /// 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,
    },

    /// 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,
    },
}