// 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 crate::{ app::App, cli::{SelectCommand, SharedSelectionCommandArgs}, download::download_options::download_opts, storage::video_database::{ self, getters::get_video_by_hash, setters::{add_video, set_video_options, set_video_status}, VideoOptions, VideoStatus, }, update::video_entry_to_video, videos::display::format_video::FormatVideo, }; 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, cmd: SelectCommand, line_number: Option, ) -> Result<()> { match cmd { SelectCommand::Pick { shared } => { handle_status_change(app, shared, line_number, VideoStatus::Pick).await?; } SelectCommand::Drop { shared } => { handle_status_change(app, shared, line_number, VideoStatus::Drop).await?; } 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_formatted_video(app).await?.colorize()).to_line_display() ); 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::>()?; } } 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?; let video = get_video_by_hash(app, &hash).await?; if video.cache_path.is_some() { handle_status_change(app, shared, line_number, VideoStatus::Cached).await?; } else { handle_status_change(app, shared, line_number, VideoStatus::Watch).await?; } } SelectCommand::Url { shared } => { let mut firefox = std::process::Command::new("firefox"); firefox.args(["-P", "timesinks.youtube"]); firefox.arg(shared.url.as_str()); let _handle = firefox.spawn().context("Failed to run firefox")?; } SelectCommand::File { .. } => unreachable!("This should have been filtered out"), } Ok(()) } async fn handle_status_change( app: &App, shared: SharedSelectionCommandArgs, line_number: Option, new_status: VideoStatus, ) -> Result<()> { let hash = shared.hash.realize(app).await?; let video_options = VideoOptions::new( shared .subtitle_langs .unwrap_or(app.config.select.subtitle_langs.clone()), shared.speed.unwrap_or(app.config.select.playback_speed), ); let priority = compute_priority(line_number, shared.priority); set_video_status(app, &hash, new_status, priority).await?; set_video_options(app, &hash, &video_options).await?; Ok(()) } fn compute_priority(line_number: Option, priority: Option) -> Option { if let Some(pri) = priority { Some(pri) } else { line_number } }