aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs/sources/yt/src
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/sources/yt/src')
-rw-r--r--pkgs/sources/yt/src/bin/yt/main.rs91
-rw-r--r--pkgs/sources/yt/src/bin/ytc/args.rs26
-rw-r--r--pkgs/sources/yt/src/bin/ytc/main.rs77
-rw-r--r--pkgs/sources/yt/src/bin/yts/args.rs41
-rw-r--r--pkgs/sources/yt/src/bin/yts/main.rs91
-rw-r--r--pkgs/sources/yt/src/constants.rs51
-rw-r--r--pkgs/sources/yt/src/downloader.rs212
-rw-r--r--pkgs/sources/yt/src/help.str8
-rw-r--r--pkgs/sources/yt/src/lib.rs185
9 files changed, 0 insertions, 782 deletions
diff --git a/pkgs/sources/yt/src/bin/yt/main.rs b/pkgs/sources/yt/src/bin/yt/main.rs
deleted file mode 100644
index 37348834..00000000
--- a/pkgs/sources/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/pkgs/sources/yt/src/bin/ytc/args.rs b/pkgs/sources/yt/src/bin/ytc/args.rs
deleted file mode 100644
index 8b2d6a61..00000000
--- a/pkgs/sources/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/pkgs/sources/yt/src/bin/ytc/main.rs b/pkgs/sources/yt/src/bin/ytc/main.rs
deleted file mode 100644
index b38157df..00000000
--- a/pkgs/sources/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/pkgs/sources/yt/src/bin/yts/args.rs b/pkgs/sources/yt/src/bin/yts/args.rs
deleted file mode 100644
index 56989421..00000000
--- a/pkgs/sources/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/pkgs/sources/yt/src/bin/yts/main.rs b/pkgs/sources/yt/src/bin/yts/main.rs
deleted file mode 100644
index 7398db61..00000000
--- a/pkgs/sources/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/pkgs/sources/yt/src/constants.rs b/pkgs/sources/yt/src/constants.rs
deleted file mode 100644
index 5e233656..00000000
--- a/pkgs/sources/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/pkgs/sources/yt/src/downloader.rs b/pkgs/sources/yt/src/downloader.rs
deleted file mode 100644
index e915700d..00000000
--- a/pkgs/sources/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/pkgs/sources/yt/src/help.str b/pkgs/sources/yt/src/help.str
deleted file mode 100644
index 130fe42a..00000000
--- a/pkgs/sources/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/pkgs/sources/yt/src/lib.rs b/pkgs/sources/yt/src/lib.rs
deleted file mode 100644
index b089c1a2..00000000
--- a/pkgs/sources/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)
- }
- }
-}