diff options
Diffstat (limited to 'sys/nixpkgs/pkgs/yt/src')
-rw-r--r-- | sys/nixpkgs/pkgs/yt/src/bin/yt/main.rs | 91 | ||||
-rw-r--r-- | sys/nixpkgs/pkgs/yt/src/bin/ytc/args.rs | 26 | ||||
-rw-r--r-- | sys/nixpkgs/pkgs/yt/src/bin/ytc/main.rs | 77 | ||||
-rw-r--r-- | sys/nixpkgs/pkgs/yt/src/bin/yts/args.rs | 41 | ||||
-rw-r--r-- | sys/nixpkgs/pkgs/yt/src/bin/yts/main.rs | 91 | ||||
-rw-r--r-- | sys/nixpkgs/pkgs/yt/src/constants.rs | 51 | ||||
-rw-r--r-- | sys/nixpkgs/pkgs/yt/src/downloader.rs | 212 | ||||
-rw-r--r-- | sys/nixpkgs/pkgs/yt/src/help.str | 8 | ||||
-rw-r--r-- | sys/nixpkgs/pkgs/yt/src/lib.rs | 185 |
9 files changed, 0 insertions, 782 deletions
diff --git a/sys/nixpkgs/pkgs/yt/src/bin/yt/main.rs b/sys/nixpkgs/pkgs/yt/src/bin/yt/main.rs deleted file mode 100644 index 37348834..00000000 --- a/sys/nixpkgs/pkgs/yt/src/bin/yt/main.rs +++ /dev/null @@ -1,91 +0,0 @@ -use anyhow::{bail, Context, Result}; -use std::{ - env, fs, - io::{BufRead, BufReader, BufWriter, Write}, - process::Command as StdCmd, -}; -use tempfile::Builder; -use yt::{ - constants::{last_select, HELP_STR}, - downloader::Downloader, - filter_line, YtccListData, -}; - -fn main() -> Result<()> { - cli_log::init_cli_log!(); - - let json_map = { - let mut ytcc = StdCmd::new("ytcc"); - ytcc.args([ - "--output", - "json", - "list", - "--order-by", - "publish_date", - "desc", - ]); - - serde_json::from_slice::<Vec<YtccListData>>( - &ytcc.output().context("Failed to json from ytcc")?.stdout, - ) - .context("Failed to deserialize json output")? - }; - - let temp_file = Builder::new() - .prefix("yt_video_select-") - .suffix(".yts") - .rand_bytes(6) - .tempfile() - .context("Failed to get tempfile")?; - - { - let mut edit_file = BufWriter::new(&temp_file); - - json_map.iter().for_each(|line| { - let line = line.to_string(); - edit_file - .write_all(line.as_bytes()) - .expect("This write should not fail"); - }); - - edit_file.write_all(HELP_STR.as_bytes())?; - edit_file.flush().context("Failed to flush edit file")?; - - let mut nvim = StdCmd::new("nvim"); - nvim.arg(temp_file.path()); - let status = nvim.status().context("Falied to run nvim")?; - if !status.success() { - bail!("nvim exited with error status: {}", status) - } - } - - let read_file = temp_file.reopen()?; - fs::copy( - temp_file.path(), - last_select().context("Failed to get the persistent selection file path")?, - ) - .context("Failed to persist selection file")?; - - let mut watching = Vec::new(); - let reader = BufReader::new(&read_file); - for line in reader.lines() { - let line = line.context("Failed to read line")?; - - if let Some(downloadable) = - filter_line(&line).with_context(|| format!("Failed to process line: '{}'", line))? - { - watching.push(downloadable); - } - } - - if watching.is_empty() { - return Ok(()); - } - - let downloader = Downloader::new(watching).context("Failed to construct downloader")?; - downloader - .consume() - .context("Failed to consume downloader")?; - - Ok(()) -} diff --git a/sys/nixpkgs/pkgs/yt/src/bin/ytc/args.rs b/sys/nixpkgs/pkgs/yt/src/bin/ytc/args.rs deleted file mode 100644 index 8b2d6a61..00000000 --- a/sys/nixpkgs/pkgs/yt/src/bin/ytc/args.rs +++ /dev/null @@ -1,26 +0,0 @@ -use clap::{Parser, Subcommand}; -/// A helper for downloading and playing youtube videos -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -pub struct Args { - #[command(subcommand)] - /// The subcommand to execute - pub subcommand: Command, -} -#[derive(Subcommand, Debug)] -pub enum Command { - #[clap(value_parser)] - /// Work based of ytcc ids - Id { - #[clap(value_parser)] - /// A list of ids to play - ids: Vec<u32>, - }, - #[clap(value_parser)] - /// Work based of raw youtube urls - Url { - #[clap(value_parser)] - /// A list of urls to play - urls: Vec<String>, - }, -} diff --git a/sys/nixpkgs/pkgs/yt/src/bin/ytc/main.rs b/sys/nixpkgs/pkgs/yt/src/bin/ytc/main.rs deleted file mode 100644 index b38157df..00000000 --- a/sys/nixpkgs/pkgs/yt/src/bin/ytc/main.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::{env, process::Command as StdCmd}; - -use anyhow::{bail, Context, Result}; -use clap::Parser; -use log::debug; -use url::Url; -use yt::{ - downloader::{Downloadable, Downloader}, - YtccListData, -}; - -use crate::args::{Args, Command}; - -mod args; - -fn main() -> Result<()> { - let args = Args::parse(); - cli_log::init_cli_log!(); - - let playspec: Vec<Downloadable> = match args.subcommand { - Command::Id { ids } => { - let mut output = Vec::with_capacity(ids.len()); - for id in ids { - debug!("Adding {}", id); - let mut ytcc = StdCmd::new("ytcc"); - ytcc.args([ - "--output", - "json", - "list", - "--watched", - "--unwatched", - "--attributes", - "url", - "--ids", - id.to_string().as_str(), - ]); - let json = serde_json::from_slice::<Vec<YtccListData>>( - &ytcc.output().context("Failed to get url from id")?.stdout, - ) - .context("Failed to deserialize json output")?; - - if json.is_empty() { - bail!("Could not find a video with id: {}", id); - } - assert_eq!(json.len(), 1); - let json = json.first().expect("Has only one element"); - - debug!("Id resolved to: '{}'", &json.url); - - output.push(Downloadable { - url: Url::parse(&json.url)?, - id: Some(json.id), - }) - } - output - } - Command::Url { urls } => { - let mut output = Vec::with_capacity(urls.len()); - for url in urls { - output.push(Downloadable { - url: Url::parse(&url).context("Failed to parse url")?, - id: None, - }) - } - output - } - }; - - debug!("Initializing downloader"); - let downloader = Downloader::new(playspec)?; - - downloader - .consume() - .context("Failed to consume downloader")?; - - Ok(()) -} diff --git a/sys/nixpkgs/pkgs/yt/src/bin/yts/args.rs b/sys/nixpkgs/pkgs/yt/src/bin/yts/args.rs deleted file mode 100644 index 56989421..00000000 --- a/sys/nixpkgs/pkgs/yt/src/bin/yts/args.rs +++ /dev/null @@ -1,41 +0,0 @@ -use clap::{Parser, Subcommand}; -/// A helper for selecting which videos to download from ytcc to ytc -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -pub struct Args { - #[command(subcommand)] - /// subcommand to execute - pub subcommand: Option<Command>, -} - -#[derive(Subcommand, Debug)] -pub enum Command { - #[clap(value_parser)] - /// Which ordering to use - Order { - #[command(subcommand)] - command: OrderCommand, - }, -} - -#[derive(Subcommand, Debug)] -pub enum OrderCommand { - #[clap(value_parser)] - /// Order by date - #[group(required = true)] - Date { - #[arg(value_parser)] - /// Order descending - desc: bool, - #[clap(value_parser)] - /// Order ascending - asc: bool, - }, - #[clap(value_parser)] - /// Pass a raw SQL 'ORDER BY' value - Raw { - #[clap(value_parser)] - /// The raw value(s) to pass - value: Vec<String>, - }, -} diff --git a/sys/nixpkgs/pkgs/yt/src/bin/yts/main.rs b/sys/nixpkgs/pkgs/yt/src/bin/yts/main.rs deleted file mode 100644 index 7398db61..00000000 --- a/sys/nixpkgs/pkgs/yt/src/bin/yts/main.rs +++ /dev/null @@ -1,91 +0,0 @@ -use anyhow::{bail, Context, Result}; -use clap::Parser; -use std::{ - env, - io::{BufRead, BufReader, Write}, - process::Command as StdCmd, -}; -use tempfile::NamedTempFile; -use yt::{constants::HELP_STR, filter_line, YtccListData}; - -use crate::args::{Args, Command, OrderCommand}; - -mod args; - -fn main() -> Result<()> { - let args = Args::parse(); - cli_log::init_cli_log!(); - - let ordering = match args.subcommand.unwrap_or(Command::Order { - command: OrderCommand::Date { - desc: true, - asc: false, - }, - }) { - Command::Order { command } => match command { - OrderCommand::Date { desc, asc } => { - if desc { - vec!["--order-by".into(), "publish_date".into(), "desc".into()] - } else if asc { - vec!["--order-by".into(), "publish_date".into(), "asc".into()] - } else { - vec!["--order-by".into(), "publish_date".into(), "desc".into()] - } - } - OrderCommand::Raw { value } => [vec!["--order-by".into()], value].concat(), - }, - }; - - let json_map = { - let mut ytcc = StdCmd::new("ytcc"); - ytcc.args(["--output", "json", "list"]); - ytcc.args(ordering); - - serde_json::from_slice::<Vec<YtccListData>>( - &ytcc.output().context("Failed to json from ytcc")?.stdout, - ) - .context("Failed to deserialize json output")? - }; - - let mut edit_file = NamedTempFile::new().context("Failed to get tempfile")?; - - json_map.iter().for_each(|line| { - let line = line.to_string(); - edit_file - .write_all(line.as_bytes()) - .expect("This write should not fail"); - }); - - write!(&edit_file, "{}", HELP_STR)?; - edit_file.flush().context("Failed to flush edit file")?; - - let read_file = edit_file.reopen()?; - - let mut nvim = StdCmd::new("nvim"); - nvim.arg(edit_file.path()); - - let status = nvim.status().context("Falied to run nvim")?; - if !status.success() { - bail!("Nvim exited with error status: {}", status) - } - - let mut watching = Vec::new(); - let reader = BufReader::new(&read_file); - for line in reader.lines() { - let line = line.context("Failed to read line")?; - - if let Some(downloadable) = - filter_line(&line).with_context(|| format!("Failed to process line: '{}'", line))? - { - watching.push(downloadable); - } - } - - let watching: String = watching - .iter() - .map(|d| d.to_string()) - .collect::<Vec<String>>() - .join("\n"); - println!("{}", &watching); - Ok(()) -} diff --git a/sys/nixpkgs/pkgs/yt/src/constants.rs b/sys/nixpkgs/pkgs/yt/src/constants.rs deleted file mode 100644 index 5e233656..00000000 --- a/sys/nixpkgs/pkgs/yt/src/constants.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::{env, fs, path::PathBuf}; - -pub const HELP_STR: &str = include_str!("./help.str"); - -pub const YT_DLP_FLAGS: [&str; 13] = [ - // Ignore errors arising of unavailable sponsor block API - "--ignore-errors", - "--format", - "bestvideo[height<=?1080]+bestaudio/best", - "--embed-chapters", - "--progress", - "--write-comments", - "--extractor-args", - "youtube:max_comments=150,all,100;comment_sort=top", - "--write-info-json", - "--sponsorblock-mark", - "default", - "--sponsorblock-remove", - "sponsor", -]; -pub const MPV_FLAGS: [&str; 4] = [ - "--speed=2.7", - "--volume=75", - "--keep-open=yes", - "--msg-level=osd/libass=fatal", -]; - -pub const CONCURRENT: u32 = 5; - -pub const DOWNLOAD_DIR: &str = "/tmp/ytcc"; - -fn get_runtime_path(component: &'static str) -> anyhow::Result<PathBuf> { - let out: PathBuf = format!( - "{}/{}", - env::var("XDG_RUNTIME_DIR").expect("This should always exist"), - component - ) - .into(); - fs::create_dir_all(out.parent().expect("Parent should exist"))?; - Ok(out) -} - -const STATUS_PATH: &str = "ytcc/running"; -pub fn status_path() -> anyhow::Result<PathBuf> { - get_runtime_path(STATUS_PATH) -} - -const LAST_SELECT: &str = "ytcc/selected.yts"; -pub fn last_select() -> anyhow::Result<PathBuf> { - get_runtime_path(LAST_SELECT) -} diff --git a/sys/nixpkgs/pkgs/yt/src/downloader.rs b/sys/nixpkgs/pkgs/yt/src/downloader.rs deleted file mode 100644 index e915700d..00000000 --- a/sys/nixpkgs/pkgs/yt/src/downloader.rs +++ /dev/null @@ -1,212 +0,0 @@ -use std::{ - fs::{self, canonicalize}, - io::{stderr, stdout, Read}, - mem, - os::unix::fs::symlink, - path::PathBuf, - process::Command, - sync::mpsc::{self, Receiver, Sender}, - thread::{self, JoinHandle}, -}; - -use anyhow::{bail, Context, Result}; -use log::{debug, error, warn}; -use url::Url; - -use crate::constants::{status_path, CONCURRENT, DOWNLOAD_DIR, MPV_FLAGS, YT_DLP_FLAGS}; - -#[derive(Debug)] -pub struct Downloadable { - pub url: Url, - pub id: Option<u32>, -} - -impl std::fmt::Display for Downloadable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - f, - "{}|{}", - self.url.as_str().replace('|', ";"), - self.id.unwrap_or(0), - ) - } -} - -pub struct Downloader { - sent: usize, - download_thread: JoinHandle<Result<()>>, - orx: Receiver<(PathBuf, Option<u32>)>, - itx: Option<Sender<Downloadable>>, - playspec: Vec<Downloadable>, -} - -impl Downloader { - pub fn new(mut playspec: Vec<Downloadable>) -> anyhow::Result<Downloader> { - let (itx, irx): (Sender<Downloadable>, Receiver<Downloadable>) = mpsc::channel(); - let (otx, orx) = mpsc::channel(); - let jh = thread::spawn(move || -> Result<()> { - while let Ok(pt) = irx.recv() { - debug!("Got '{}' to be downloaded", pt); - let path = download_url(&pt.url) - .with_context(|| format!("Failed to download url: '{}'", &pt.url))?; - otx.send((path, pt.id)).expect("Should not be dropped"); - } - debug!("Finished Downloading everything"); - Ok(()) - }); - - playspec.reverse(); - let mut output = Downloader { - sent: 0, - download_thread: jh, - orx, - itx: Some(itx), - playspec, - }; - if output.playspec.len() <= CONCURRENT as usize { - output.add(output.playspec.len() as u32)?; - } else { - output.add(CONCURRENT)?; - } - Ok(output) - } - - pub fn add(&mut self, number_to_add: u32) -> Result<()> { - debug!("Adding {} to be downloaded concurrently", number_to_add); - for _ in 0..number_to_add { - let pt = self.playspec.pop().expect("This call should be guarded"); - self.itx.as_ref().expect("Should still be valid").send(pt)?; - self.sent += 1; - } - Ok(()) - } - - /// Return the next video already downloaded, will block until the download is complete - pub fn next(&mut self) -> Option<(PathBuf, Option<u32>)> { - debug!("Requesting next output"); - match self.orx.recv() { - Ok(ok) => { - debug!("Output downloaded to: {}", ok.0.display()); - if !self.playspec.is_empty() { - self.add(1).ok()?; - } else { - debug!( - "Done sending videos to be downloaded, downoladed: {} videos", - self.sent - ); - let itx = mem::take(&mut self.itx); - drop(itx) - } - debug!("Returning: {}|{}", ok.0.display(), ok.1.unwrap_or(0)); - Some(ok) - } - Err(err) => { - debug!("Received error while listening: {}", err); - None - } - } - } - - pub fn drop(self) -> anyhow::Result<()> { - // Check that we really downloaded everything - assert_eq!(self.playspec.len(), 0); - match self.download_thread.join() { - Ok(ok) => ok, - Err(err) => panic!("Failed to join downloader thread: '{:#?}'", err), - } - } - - pub fn consume(mut self) -> anyhow::Result<()> { - while let Some((path, id)) = self.next() { - debug!("Next path to play is: '{}'", path.display()); - let mut info_json = canonicalize(&path).context("Failed to canoncialize path")?; - info_json.set_extension("info.json"); - - if status_path()?.is_symlink() { - fs::remove_file(status_path()?).context("Failed to delete old status file")?; - } else if !status_path()?.exists() { - debug!( - "The status path at '{}' does not exists", - status_path()?.display() - ); - } else { - bail!( - "The status path ('{}') is not a symlink but exists!", - status_path()?.display() - ); - } - - symlink(info_json, status_path()?).context("Failed to symlink")?; - - let mut mpv = Command::new("mpv"); - mpv.stdout(stdout()); - mpv.stderr(stderr()); - mpv.args(MPV_FLAGS); - // TODO: Set the title to the name of the video, not the path <2024-02-09> - // mpv.arg(format!("--title=")) - mpv.arg(&path); - - let status = mpv.status().context("Failed to run mpv")?; - if status.success() { - fs::remove_file(&path)?; - if let Some(id) = id { - println!("\x1b[32;1mMarking {} as watched!\x1b[0m", id); - let mut ytcc = std::process::Command::new("ytcc"); - ytcc.stdout(stdout()); - ytcc.stderr(stderr()); - ytcc.args(["mark"]); - ytcc.arg(id.to_string()); - let status = ytcc.status().context("Failed to run ytcc")?; - if let Some(code) = status.code() { - if code != 0 { - bail!("Ytcc failed with status: {}", code); - } - } - } - debug!("mpv exited with: '{}'", status); - } else { - warn!("mpv exited with: '{}'", status); - } - } - self.drop()?; - Ok(()) - } -} - -fn download_url(url: &Url) -> Result<PathBuf> { - let output_file = tempfile::NamedTempFile::new().context("Failed to create tempfile")?; - output_file - .as_file() - .set_len(0) - .context("Failed to truncate temp-file")?; - if !Into::<PathBuf>::into(DOWNLOAD_DIR).exists() { - fs::create_dir_all(DOWNLOAD_DIR) - .with_context(|| format!("Failed to create download dir at: {}", DOWNLOAD_DIR))? - } - let mut yt_dlp = Command::new("yt-dlp"); - yt_dlp.current_dir(DOWNLOAD_DIR); - yt_dlp.stdout(stdout()); - yt_dlp.stderr(stderr()); - yt_dlp.args(YT_DLP_FLAGS); - yt_dlp.args([ - "--output", - "%(channel)s/%(title)s.%(ext)s", - url.as_str(), - "--print-to-file", - "after_move:filepath", - ]); - yt_dlp.arg(output_file.path().as_os_str()); - - let status = yt_dlp.status().context("Failed to run yt-dlp")?; - if !status.success() { - error!("yt-dlp execution failed with error: '{}'", status); - } - - let mut path = String::new(); - output_file - .as_file() - .read_to_string(&mut path) - .context("Failed to read output file temp file")?; - let path = path.trim(); - Ok(path.into()) -} diff --git a/sys/nixpkgs/pkgs/yt/src/help.str b/sys/nixpkgs/pkgs/yt/src/help.str deleted file mode 100644 index 130fe42a..00000000 --- a/sys/nixpkgs/pkgs/yt/src/help.str +++ /dev/null @@ -1,8 +0,0 @@ -# Commands: -# w, watch = watch id -# d, drop = mark id as watched -# u, url = open the associated URL in the `timesinks.youtube` Firefox profile -# p, pick = leave id as is; This is a noop -# -# These lines can be re-ordered; they are executed from top to bottom. -# vim: filetype=yts conceallevel=2 concealcursor=nc colorcolumn= diff --git a/sys/nixpkgs/pkgs/yt/src/lib.rs b/sys/nixpkgs/pkgs/yt/src/lib.rs deleted file mode 100644 index b089c1a2..00000000 --- a/sys/nixpkgs/pkgs/yt/src/lib.rs +++ /dev/null @@ -1,185 +0,0 @@ -use anyhow::{bail, Context}; -use downloader::Downloadable; -use serde::Deserialize; -use url::Url; - -pub mod constants; -pub mod downloader; - -#[derive(Deserialize)] -pub struct YtccListData { - pub url: String, - pub title: String, - pub description: String, - pub publish_date: String, - pub watch_date: Option<f64>, - pub duration: String, - pub thumbnail_url: Option<String>, - pub extractor_hash: String, - pub id: u32, - pub playlists: Vec<YtccPlaylistData>, -} - -impl std::fmt::Display for YtccListData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - f, - r#"pick {} "{}" "{}" "{}" "{}" "{}"{}"#, - self.id, - self.title.replace(['"', '„', '”'], "'"), - self.publish_date, - self.playlists - .iter() - .map(|p| p.name.replace('"', "'")) - .collect::<Vec<String>>() - .join(", "), - Duration::from(self.duration.trim()), - self.url.replace('"', "'"), - "\n" - ) - } -} - -#[derive(Deserialize)] -pub struct YtccPlaylistData { - pub name: String, - pub url: String, - pub reverse: bool, -} - -pub enum LineCommand { - Pick, - Drop, - Watch, - Url, -} - -impl std::str::FromStr for LineCommand { - type Err = anyhow::Error; - fn from_str(v: &str) -> Result<Self, <Self as std::str::FromStr>::Err> { - match v { - "pick" | "p" => Ok(Self::Pick), - "drop" | "d" => Ok(Self::Drop), - "watch" | "w" => Ok(Self::Watch), - "url" | "u" => Ok(Self::Url), - other => bail!("'{}' is not a recognized command!", other), - } - } -} - -pub struct Line { - pub cmd: LineCommand, - pub id: u32, - pub url: Url, -} - -/// We expect that each line is correctly formatted, and simply use default ones if they are not -impl From<&str> for Line { - fn from(v: &str) -> Self { - let buf: Vec<_> = v.split_whitespace().collect(); - let url: Url = Url::parse( - buf.last() - .expect("This should always exists") - .trim_matches('"'), - ) - .expect("This parsing should work,as the url is generated"); - - Line { - cmd: buf - .get(0) - .unwrap_or(&"pick") - .parse() - .unwrap_or(LineCommand::Pick), - id: buf.get(1).unwrap_or(&"0").parse().unwrap_or(0), - url, - } - } -} - -pub struct Duration { - time: u32, -} - -impl From<&str> for Duration { - fn from(v: &str) -> Self { - let buf: Vec<_> = v.split(':').take(2).collect(); - Self { - time: (buf[0].parse::<u32>().expect("Should be a number") * 60) - + buf[1].parse::<u32>().expect("Should be a number"), - } - } -} - -impl std::fmt::Display for Duration { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - const SECOND: u32 = 1; - const MINUTE: u32 = 60 * SECOND; - const HOUR: u32 = 60 * MINUTE; - - let base_hour = self.time - (self.time % HOUR); - let base_min = (self.time % HOUR) - ((self.time % HOUR) % MINUTE); - let base_sec = (self.time % HOUR) % MINUTE; - - let h = base_hour / HOUR; - let m = base_min / MINUTE; - let s = base_sec / SECOND; - - if self.time == 0 { - write!(f, "[No Duration]") - } else if h > 0 { - write!(f, "[{h}h {m}m]") - } else { - write!(f, "[{m}m {s}s]") - } - } -} -#[cfg(test)] -mod test { - use crate::Duration; - - #[test] - fn test_display_duration_1h() { - let dur = Duration { time: 60 * 60 }; - assert_eq!("[1h 0m]".to_owned(), dur.to_string()); - } - #[test] - fn test_display_duration_30min() { - let dur = Duration { time: 60 * 30 }; - assert_eq!("[30m 0s]".to_owned(), dur.to_string()); - } -} - -pub fn ytcc_drop(id: u32) -> anyhow::Result<()> { - let mut ytcc = std::process::Command::new("ytcc"); - ytcc.args(["mark", &format!("{}", id)]); - if !ytcc.status().context("Failed to run ytcc")?.success() { - bail!("`ytcc mark {}` failed to execute", id) - } - Ok(()) -} - -pub fn filter_line(line: &str) -> anyhow::Result<Option<Downloadable>> { - // Filter out comments and empty lines - if line.starts_with('#') || line.trim().is_empty() { - return Ok(None); - } - - let line = Line::from(line); - match line.cmd { - LineCommand::Pick => Ok(None), - LineCommand::Drop => ytcc_drop(line.id) - .with_context(|| format!("Failed to drop: {}", line.id)) - .map(|_| None), - LineCommand::Watch => Ok(Some(Downloadable { - id: Some(line.id), - url: line.url, - })), - LineCommand::Url => { - let mut firefox = std::process::Command::new("firefox"); - firefox.args(["-P", "timesinks.youtube"]); - firefox.arg(line.url.as_str()); - let _handle = firefox.spawn().context("Failed to run firefox")?; - Ok(None) - } - } -} |