about summary refs log tree commit diff stats
path: root/old
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-21 10:49:23 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-21 11:28:43 +0200
commit1debeb77f7986de1b659dcfdc442de6415e1d9f5 (patch)
tree4df3e7c3f6a2d1ec116e4088c5ace7f143a8b05f /old
downloadyt-1debeb77f7986de1b659dcfdc442de6415e1d9f5.tar.gz
yt-1debeb77f7986de1b659dcfdc442de6415e1d9f5.zip
chore: Initial Commit
This repository was migrated out of my nixos-config.
Diffstat (limited to 'old')
-rw-r--r--old/url.old/downloader.rs224
-rw-r--r--old/url.old/mod.rs25
-rw-r--r--old/ytc/main.rs85
-rw-r--r--old/yts/main.rs99
4 files changed, 433 insertions, 0 deletions
diff --git a/old/url.old/downloader.rs b/old/url.old/downloader.rs
new file mode 100644
index 0000000..b30b03c
--- /dev/null
+++ b/old/url.old/downloader.rs
@@ -0,0 +1,224 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// 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 <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+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/old/url.old/mod.rs b/old/url.old/mod.rs
new file mode 100644
index 0000000..cff6310
--- /dev/null
+++ b/old/url.old/mod.rs
@@ -0,0 +1,25 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// 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 <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+use anyhow::Result;
+use url::Url;
+
+use self::downloader::{Downloadable, Downloader};
+
+mod downloader;
+
+pub fn download(urls: Vec<Url>) -> Result<()> {
+    let downloadables = urls
+        .into_iter()
+        .map(|url| Downloadable { url, id: None })
+        .collect();
+    let downloader = Downloader::new(downloadables)?;
+    downloader.consume()
+}
diff --git a/old/ytc/main.rs b/old/ytc/main.rs
new file mode 100644
index 0000000..e1359f9
--- /dev/null
+++ b/old/ytc/main.rs
@@ -0,0 +1,85 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// 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 <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+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};
+
+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/old/yts/main.rs b/old/yts/main.rs
new file mode 100644
index 0000000..cd4ef35
--- /dev/null
+++ b/old/yts/main.rs
@@ -0,0 +1,99 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// 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 <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+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};
+
+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(())
+}