diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-10-07 19:43:13 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-10-07 19:43:13 +0200 |
commit | 1f0445952332d96acadecb936d9eaa7169d52082 (patch) | |
tree | e725d99a19c4b92c6a6850e35c9f9894a6e503eb /src | |
parent | feat(cli): Also add a `dowa` command (diff) | |
download | yt-1f0445952332d96acadecb936d9eaa7169d52082.tar.gz yt-1f0445952332d96acadecb936d9eaa7169d52082.zip |
feat(cli): Add a `add` command
This command allows adding URLs directly. Otherwise, the process would be: `yt subs add <URL>` -> `yt update` -> `yt subs remove <URL>`
Diffstat (limited to 'src')
-rw-r--r-- | src/cli.rs | 4 | ||||
-rw-r--r-- | src/select/cmds.rs | 60 | ||||
-rw-r--r-- | src/select/selection_file/display.rs | 5 | ||||
-rw-r--r-- | src/select/selection_file/help.str | 1 | ||||
-rw-r--r-- | src/update/mod.rs | 66 |
5 files changed, 109 insertions, 27 deletions
diff --git a/src/cli.rs b/src/cli.rs index 51809c0..d19586e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -238,6 +238,10 @@ pub enum SelectCommand { 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 { diff --git a/src/select/cmds.rs b/src/select/cmds.rs index 3a7a800..b45cc48 100644 --- a/src/select/cmds.rs +++ b/src/select/cmds.rs @@ -11,14 +11,19 @@ use crate::{ app::App, cli::{SelectCommand, SharedSelectionCommandArgs}, + download::download_options::download_opts, storage::video_database::{ + self, getters::get_video_by_hash, - setters::{set_video_options, set_video_status}, + setters::{add_video, set_video_options, set_video_status}, VideoOptions, VideoStatus, }, + update::video_entry_to_video, }; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; +use futures::future::join_all; +use yt_dlp::wrapper::info_json::InfoType; pub async fn handle_select_cmd( app: &App, @@ -35,6 +40,57 @@ pub async fn handle_select_cmd( SelectCommand::Watched { shared } => { handle_status_change(app, shared, line_number, VideoStatus::Watched).await?; } + SelectCommand::Add { urls } => { + for url in urls { + let opts = download_opts( + &app, + video_database::YtDlpOptions { + subtitle_langs: "".to_owned(), + }, + ); + let entry = yt_dlp::extract_info(&opts, &url, false, true) + .await + .with_context(|| format!("Failed to fetch entry for url: '{}'", url))?; + + async fn add_entry( + app: &App, + entry: yt_dlp::wrapper::info_json::InfoJson, + ) -> Result<()> { + let video = video_entry_to_video(entry, None)?; + println!("{}", video.to_color_display(app).await?); + add_video(app, video).await?; + + Ok(()) + } + + match entry._type { + Some(InfoType::Video) => { + add_entry(&app, entry).await?; + } + Some(InfoType::Playlist) => { + if let Some(mut entries) = entry.entries { + if !entries.is_empty() { + // Pre-warm the cache + add_entry(app, entries.remove(0)).await?; + + let futures: Vec<_> = entries + .into_iter() + .map(|entry| add_entry(&app, entry)) + .collect(); + + join_all(futures).await.into_iter().collect::<Result<_>>()?; + } + } else { + bail!("Your playlist does not seem to have any entries!") + } + } + other => bail!( + "Your URL should point to a video or a playlist, but points to a '{:#?}'", + other + ), + } + } + } SelectCommand::Watch { shared } => { let hash = shared.hash.clone().realize(app).await?; diff --git a/src/select/selection_file/display.rs b/src/select/selection_file/display.rs index 8ff6a15..0714015 100644 --- a/src/select/selection_file/display.rs +++ b/src/select/selection_file/display.rs @@ -10,7 +10,7 @@ use std::fmt::Write; -use anyhow::Result; +use anyhow::{Context, Result}; use chrono::DateTime; use log::debug; @@ -31,7 +31,8 @@ impl Video { let mut f = String::new(); let opts = get_video_opts(app, &self.extractor_hash) - .await? + .await + .with_context(|| format!("Failed to get video options for video: '{}'", self.title))? .to_cli_flags(app); let opts_white = if !opts.is_empty() { " " } else { "" }; diff --git a/src/select/selection_file/help.str b/src/select/selection_file/help.str index f3ad2f2..eb76ce5 100644 --- a/src/select/selection_file/help.str +++ b/src/select/selection_file/help.str @@ -4,6 +4,7 @@ # d, drop [-p,-s,-l] Mark the video given by the hash to be dropped # u, url [-p,-s,-l] Open the video URL in Firefox's `timesinks.youtube` profile # p, pick [-p,-s,-l] Reset the videos status to 'Pick' +# a, add URL Add a video, defined by the URL # # See `yt select <cmd_name> --help` for more help. # diff --git a/src/update/mod.rs b/src/update/mod.rs index ce0a7e5..ce3a7f9 100644 --- a/src/update/mod.rs +++ b/src/update/mod.rs @@ -120,12 +120,7 @@ pub async fn update( Ok(()) } -async fn process_subscription( - app: &App, - sub: &Subscription, - entry: InfoJson, - hashes: &[blake3::Hash], -) -> Result<()> { +pub fn video_entry_to_video(entry: InfoJson, sub: Option<&Subscription>) -> Result<Video> { macro_rules! unwrap_option { ($option:expr) => { match $option { @@ -197,26 +192,51 @@ async fn process_subscription( let extractor_hash = blake3::hash(unwrap_option!(entry.id).as_bytes()); - if hashes.contains(&extractor_hash) { + let subscription_name = if let Some(sub) = sub { + Some(sub.name.clone()) + } else { + if let Some(uploader) = entry.uploader { + if entry.webpage_url_domain == Some("youtube.com".to_owned()) { + Some(format!("{} - Videos", uploader)) + } else { + Some(uploader.clone()) + } + } else { + None + } + }; + + let video = Video { + cache_path: None, + description: entry.description.clone(), + duration: entry.duration, + extractor_hash: ExtractorHash::from_hash(extractor_hash), + last_status_change: Utc::now().timestamp(), + parent_subscription_name: subscription_name, + priority: 0, + publish_date, + status: VideoStatus::Pick, + status_change: false, + thumbnail_url, + title: unwrap_option!(entry.title.clone()), + url, + }; + Ok(video) +} + +async fn process_subscription( + app: &App, + sub: &Subscription, + entry: InfoJson, + hashes: &[blake3::Hash], +) -> Result<()> { + let video = + video_entry_to_video(entry, Some(sub)).context("Failed to parse search entry as Video")?; + + if hashes.contains(&video.extractor_hash.hash()) { // We already stored the video information unreachable!("The python update script should have never provided us a duplicated video"); } else { - let video = Video { - cache_path: None, - description: entry.description.clone(), - duration: entry.duration, - extractor_hash: ExtractorHash::from_hash(extractor_hash), - last_status_change: Utc::now().timestamp(), - parent_subscription_name: Some(sub.name.clone()), - priority: 0, - publish_date, - status: VideoStatus::Pick, - status_change: false, - thumbnail_url, - title: unwrap_option!(entry.title.clone()), - url, - }; - println!("{}", video.to_color_display(app).await?); add_video(app, video).await?; Ok(()) |