diff options
Diffstat (limited to '')
-rw-r--r-- | sys/nixpkgs/pkgs/yt/src/downloader.rs (renamed from sys/nixpkgs/pkgs/ytc/src/downloader.rs) | 112 | ||||
-rw-r--r-- | sys/nixpkgs/pkgs/ytc/src/main.rs | 193 |
2 files changed, 79 insertions, 226 deletions
diff --git a/sys/nixpkgs/pkgs/ytc/src/downloader.rs b/sys/nixpkgs/pkgs/yt/src/downloader.rs index dddebe05..1733500a 100644 --- a/sys/nixpkgs/pkgs/ytc/src/downloader.rs +++ b/sys/nixpkgs/pkgs/yt/src/downloader.rs @@ -1,7 +1,8 @@ use std::{ - fs, + fs::{self, canonicalize}, io::{stderr, stdout, Read}, mem, + os::unix::fs::symlink, path::PathBuf, process::Command, sync::mpsc::{self, Receiver, Sender}, @@ -9,40 +10,28 @@ use std::{ }; use anyhow::{bail, Context, Result}; -use log::debug; +use log::{debug, warn}; +use url::Url; -use crate::PlayThing; +use crate::constants::{status_path, CONCURRENT, DOWNLOAD_DIR, MPV_FLAGS, YT_DLP_FLAGS}; -const YT_DLP_FLAGS: [&str; 12] = [ - "--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", -]; - -const CONCURRENT: u32 = 5; - -const DOWNLOAD_DIR: &str = "/tmp/ytcc"; +#[derive(Debug)] +pub struct Downloadable { + pub url: Url, + pub id: Option<u32>, +} pub struct Downloader { sent: usize, download_thread: JoinHandle<Result<()>>, orx: Receiver<(PathBuf, Option<u32>)>, - itx: Option<Sender<PlayThing>>, - playspec: Vec<PlayThing>, + itx: Option<Sender<Downloadable>>, + playspec: Vec<Downloadable>, } impl Downloader { - pub fn new(mut playspec: Vec<PlayThing>) -> anyhow::Result<Downloader> { - let (itx, irx): (Sender<PlayThing>, Receiver<PlayThing>) = mpsc::channel(); + 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 Some(pt) = irx.recv().ok() { @@ -91,7 +80,10 @@ impl Downloader { debug!("Will add 1"); self.add(1).ok()?; } else { - debug!("Will drop sender"); + debug!( + "Done sending videos to be downloaded, downoladed: {} videos", + self.sent + ); let itx = mem::take(&mut self.itx); drop(itx) } @@ -99,7 +91,7 @@ impl Downloader { Some(ok) } Err(err) => { - debug!("Recieved error while listening: {}", err); + debug!("Received error while listening: {}", err); None } } @@ -110,9 +102,63 @@ impl Downloader { Err(err) => panic!("Can't join 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); + 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: &str) -> Result<PathBuf> { +fn download_url(url: &Url) -> Result<PathBuf> { let output_file = tempfile::NamedTempFile::new().context("Failed to create tempfile")?; output_file .as_file() @@ -130,17 +176,17 @@ fn download_url(url: &str) -> Result<PathBuf> { yt_dlp.args([ "--output", "%(channel)s/%(title)s.%(ext)s", - url, + 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 let Some(code) = status.code() { - if code != 0 { - bail!("yt_dlp execution failed with error: '{}'", status); - } + if !status.success() { + bail!("yt_dlp execution failed with error: '{}'", status); } + let mut path = String::new(); output_file .as_file() diff --git a/sys/nixpkgs/pkgs/ytc/src/main.rs b/sys/nixpkgs/pkgs/ytc/src/main.rs deleted file mode 100644 index 75084851..00000000 --- a/sys/nixpkgs/pkgs/ytc/src/main.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::{ - env, - fs::{self, canonicalize}, - io::{stderr, stdout}, - os::unix::fs::symlink, - path::PathBuf, - process::Command as StdCmd, -}; - -use anyhow::{bail, Context, Result}; -use clap::{Parser, Subcommand}; -use downloader::Downloader; -use log::debug; -use serde::Deserialize; - -mod downloader; - -const STATUS_PATH: &str = "ytcc/running"; - -/// 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>, - }, -} - -struct PlayThing { - url: String, - id: Option<u32>, -} - -#[derive(Deserialize)] -struct YtccListData { - url: String, - #[allow(unused)] - title: String, - #[allow(unused)] - description: String, - #[allow(unused)] - publish_date: String, - #[allow(unused)] - watch_date: Option<String>, - #[allow(unused)] - duration: String, - #[allow(unused)] - thumbnail_url: String, - #[allow(unused)] - extractor_hash: String, - id: u32, - #[allow(unused)] - playlists: Vec<YtccPlaylistData>, -} -#[derive(Deserialize)] -struct YtccPlaylistData { - #[allow(unused)] - name: String, - #[allow(unused)] - url: String, - #[allow(unused)] - reverse: bool, -} - -fn main() -> Result<()> { - let args = Args::parse(); - cli_log::init_cli_log!(); - - let playspec: Vec<PlayThing> = 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", - "--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.len() == 0 { - 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(PlayThing { - url: json.url.clone(), - id: Some(json.id), - }) - } - output - } - Command::Url { urls } => urls - .into_iter() - .map(|url| PlayThing { url, id: None }) - .collect(), - }; - - debug!("Initializing downloader"); - let mut downloader = Downloader::new(playspec)?; - - while let Some((path, id)) = downloader.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 = StdCmd::new("mpv"); - // mpv.stdout(stdout()); - mpv.stderr(stderr()); - mpv.args(["--speed=2.7", "--volume=75"]); - mpv.arg(&path); - - let status = mpv.status().context("Failed to run mpv")?; - if let Some(code) = status.code() { - if code == 0 { - fs::remove_file(&path)?; - if let Some(id) = id { - println!("\x1b[32;1mMarking {} as watched!\x1b[0m", id); - let mut ytcc = StdCmd::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: {}", code); - } - } - downloader.drop()?; - - Ok(()) -} - -fn status_path() -> Result<PathBuf> { - let out: PathBuf = format!( - "{}/{}", - env::var("XDG_RUNTIME_DIR").expect("This should always exist"), - STATUS_PATH - ) - .into(); - fs::create_dir_all(&out.parent().expect("Parent should exist"))?; - Ok(out) -} |