aboutsummaryrefslogtreecommitdiffstats
path: root/src/watch/events
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-10-14 14:56:29 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-10-14 14:56:29 +0200
commit6c9286857ef8b314962b67f4a16a66e8c35531bc (patch)
tree9ced4485ec38b39f82cba258c06321a21c40000a /src/watch/events
parentbuild(Cargo.toml): Add further lints (diff)
downloadyt-6c9286857ef8b314962b67f4a16a66e8c35531bc.zip
refactor(treewide): Combine the separate crates in one workspace
Diffstat (limited to 'src/watch/events')
-rw-r--r--src/watch/events/mod.rs369
-rw-r--r--src/watch/events/playlist_handler.rs94
2 files changed, 0 insertions, 463 deletions
diff --git a/src/watch/events/mod.rs b/src/watch/events/mod.rs
deleted file mode 100644
index 41a7772..0000000
--- a/src/watch/events/mod.rs
+++ /dev/null
@@ -1,369 +0,0 @@
-// 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::{collections::HashMap, env::current_exe, mem, time::Duration};
-
-use anyhow::{bail, Result};
-use libmpv2::{
- events::{Event, PlaylistEntryId},
- EndFileReason, Mpv,
-};
-use log::{debug, info, warn};
-use tokio::{process::Command, time};
-
-use crate::{
- app::App,
- comments::get_comments,
- storage::video_database::{
- extractor_hash::ExtractorHash,
- getters::{get_video_by_hash, get_video_mpv_opts, get_videos},
- setters::{set_state_change, set_video_watched},
- VideoStatus,
- },
-};
-
-use playlist_handler::PlaylistHandler;
-
-mod playlist_handler;
-
-#[derive(Debug)]
-pub struct MpvEventHandler {
- watch_later_block_list: HashMap<ExtractorHash, ()>,
- playlist_handler: PlaylistHandler,
-}
-
-impl MpvEventHandler {
- pub fn from_playlist(playlist_cache: HashMap<String, ExtractorHash>) -> Self {
- let playlist_handler = PlaylistHandler::from_cache(playlist_cache);
- Self {
- playlist_handler,
- watch_later_block_list: HashMap::new(),
- }
- }
-
- /// Checks, whether new videos are ready to be played
- pub async fn possibly_add_new_videos(
- &mut self,
- app: &App,
- mpv: &Mpv,
- force_message: bool,
- ) -> Result<usize> {
- let play_things = get_videos(app, &[VideoStatus::Cached], Some(false)).await?;
-
- // There is nothing to watch
- if play_things.is_empty() {
- if force_message {
- Self::message(mpv, "No new videos available to add", "3000")?;
- }
- return Ok(0);
- }
-
- let mut blocked_videos = 0;
- let current_playlist = self.playlist_handler.playlist_ids(mpv)?;
- let play_things = play_things
- .into_iter()
- .filter(|val| !current_playlist.values().any(|a| a == &val.extractor_hash))
- .filter(|val| {
- if self
- .watch_later_block_list
- .contains_key(&val.extractor_hash)
- {
- blocked_videos += 1;
- false
- } else {
- true
- }
- })
- .collect::<Vec<_>>();
-
- info!(
- "{} videos are cached and will be added to the list to be played ({} are blocked)",
- play_things.len(),
- blocked_videos
- );
-
- let num = play_things.len();
- self.playlist_handler.reserve(play_things.len());
- for play_thing in play_things {
- debug!("Adding '{}' to playlist.", play_thing.title);
-
- let orig_cache_path = play_thing.cache_path.expect("Is cached and thus some");
- let cache_path = orig_cache_path.to_str().expect("Should be vaild utf8");
- let fmt_cache_path = format!("\"{}\"", cache_path);
-
- let args = &[&fmt_cache_path, "append-play"];
-
- mpv.execute("loadfile", args)?;
- self.playlist_handler
- .add(cache_path.to_owned(), play_thing.extractor_hash);
- }
-
- if force_message || num > 0 {
- Self::message(
- mpv,
- format!(
- "Added {} videos ({} are marked as watch later)",
- num, blocked_videos
- )
- .as_str(),
- "3000",
- )?;
- }
- Ok(num)
- }
-
- fn message(mpv: &Mpv, message: &str, time: &str) -> Result<()> {
- mpv.execute("show-text", &[format!("\"{}\"", message).as_str(), time])?;
- Ok(())
- }
-
- /// Get the hash of the currently playing video.
- /// You can specify an offset, which is added to the playlist_position to get, for example, the
- /// previous video (-1) or the next video (+1).
- /// Beware that setting an offset can cause an property error if it's out of bound.
- fn get_cvideo_hash(&mut self, mpv: &Mpv, offset: i64) -> Result<ExtractorHash> {
- let playlist_entry_id = {
- let playlist_position = {
- let raw = mpv.get_property::<i64>("playlist-pos")?;
- if raw == -1 {
- unreachable!( "This should only be called when a current video exists. Current state: '{:#?}'", self);
- } else {
- (raw + offset) as usize
- }
- };
-
- let raw =
- mpv.get_property::<i64>(format!("playlist/{}/id", playlist_position).as_str())?;
- PlaylistEntryId::new(raw)
- };
-
- // debug!("Trying to get playlist entry: '{}'", playlist_entry_id);
-
- let video_hash = self
- .playlist_handler
- .playlist_ids(mpv)?
- .get(&playlist_entry_id)
- .expect("The stored playling index should always be in the playlist")
- .to_owned();
-
- Ok(video_hash)
- }
- async fn mark_video_watched(&self, app: &App, hash: &ExtractorHash) -> Result<()> {
- let video = get_video_by_hash(app, hash).await?;
- debug!("MPV handler will mark video '{}' watched.", video.title);
- set_video_watched(app, &video).await?;
- Ok(())
- }
-
- async fn mark_video_inactive(
- &mut self,
- app: &App,
- mpv: &Mpv,
- playlist_index: PlaylistEntryId,
- ) -> Result<()> {
- let current_playlist = self.playlist_handler.playlist_ids(mpv)?;
- let video_hash = current_playlist
- .get(&playlist_index)
- .expect("The video index should always be correctly tracked");
-
- set_state_change(app, video_hash, false).await?;
- Ok(())
- }
- async fn mark_video_active(
- &mut self,
- app: &App,
- mpv: &Mpv,
- playlist_index: PlaylistEntryId,
- ) -> Result<()> {
- let current_playlist = self.playlist_handler.playlist_ids(mpv)?;
- let video_hash = current_playlist
- .get(&playlist_index)
- .expect("The video index should always be correctly tracked");
-
- set_state_change(app, video_hash, true).await?;
- Ok(())
- }
-
- /// Apply the options set with e.g. `watch --speed=<speed>`
- async fn apply_options(&self, app: &App, mpv: &Mpv, hash: &ExtractorHash) -> Result<()> {
- let options = get_video_mpv_opts(app, hash).await?;
-
- mpv.set_property("speed", options.playback_speed)?;
- Ok(())
- }
-
- /// This also returns the hash of the current video
- fn remove_cvideo_from_playlist(&mut self, mpv: &Mpv) -> Result<ExtractorHash> {
- let hash = self.get_cvideo_hash(mpv, 0)?;
- mpv.execute("playlist-remove", &["current"])?;
- Ok(hash)
- }
-
- /// Check if the playback queue is empty
- pub async fn check_idle(&mut self, app: &App, mpv: &Mpv) -> Result<bool> {
- if mpv.get_property::<bool>("idle-active")? {
- warn!("There is nothing to watch yet. Will idle, until something is available");
- let number_of_new_videos = self.possibly_add_new_videos(app, mpv, false).await?;
-
- if number_of_new_videos == 0 {
- time::sleep(Duration::from_secs(10)).await;
- Ok(true)
- } else {
- Ok(false)
- }
- } else {
- Ok(false)
- }
- }
-
- /// This will return [`true`], if the event handling should be stopped
- pub async fn handle_mpv_event<'a>(
- &mut self,
- app: &App,
- mpv: &Mpv,
- event: Event<'a>,
- ) -> Result<bool> {
- match event {
- Event::EndFile(r) => match r.reason {
- EndFileReason::Eof => {
- info!("Mpv reached eof of current video. Marking it inactive.");
-
- self.mark_video_inactive(app, mpv, r.playlist_entry_id)
- .await?;
- }
- EndFileReason::Stop => {
- // This reason is incredibly ambiguous. It _both_ means actually pausing a
- // video and going to the next one in the playlist.
- // Oh, and it's also called, when a video is removed from the playlist (at
- // least via "playlist-remove current")
- info!("Paused video (or went to next playlist entry); Marking it inactive");
-
- self.mark_video_inactive(app, mpv, r.playlist_entry_id)
- .await?;
- }
- EndFileReason::Quit => {
- info!("Mpv quit. Exiting playback");
-
- // draining the playlist is okay, as mpv is done playing
- let mut handler = mem::take(&mut self.playlist_handler);
- let videos = handler.playlist_ids(mpv)?;
- for hash in videos.values() {
- self.mark_video_watched(app, hash).await?;
- set_state_change(app, hash, false).await?;
- }
- return Ok(true);
- }
- EndFileReason::Error => {
- unreachable!("This will be raised as a separate error")
- }
- EndFileReason::Redirect => {
- todo!("We probably need to handle this somehow");
- }
- },
- Event::StartFile(entry_id) => {
- self.possibly_add_new_videos(app, mpv, false).await?;
-
- // We don't need to check, whether other videos are still active, as they should
- // have been marked inactive in the `Stop` handler.
- self.mark_video_active(app, mpv, entry_id).await?;
- let hash = self.get_cvideo_hash(mpv, 0)?;
- self.apply_options(app, mpv, &hash).await?;
- }
- Event::ClientMessage(a) => {
- debug!("Got Client Message event: '{}'", a.join(" "));
-
- match a.as_slice() {
- &["yt-comments-external"] => {
- let binary = current_exe().expect("A current exe should exist");
-
- let status = Command::new("riverctl")
- .args(["focus-output", "next"])
- .status()
- .await?;
- if !status.success() {
- bail!("focusing the next output failed!");
- }
-
- let status = Command::new("alacritty")
- .args([
- "--title",
- "floating please",
- "--command",
- binary.to_str().expect("Should be valid unicode"),
- "--db-path",
- app.config
- .paths
- .database_path
- .to_str()
- .expect("This should be convertible?"),
- "comments",
- ])
- .status()
- .await?;
- if !status.success() {
- bail!("Falied to start `yt comments`");
- }
-
- let status = Command::new("riverctl")
- .args(["focus-output", "next"])
- .status()
- .await?;
- if !status.success() {
- bail!("focusing the next output failed!");
- }
- }
- &["yt-comments-local"] => {
- let comments: String = get_comments(app)
- .await?
- .render(false)
- .replace("\"", "")
- .replace("'", "")
- .chars()
- .take(app.config.watch.local_comments_length)
- .collect();
-
- Self::message(mpv, &comments, "6000")?;
- }
- &["yt-description"] => {
- // let description = description(app).await?;
- Self::message(mpv, "<YT Description>", "6000")?;
- }
- &["yt-mark-watch-later"] => {
- mpv.execute("write-watch-later-config", &[])?;
-
- let hash = self.remove_cvideo_from_playlist(mpv)?;
- assert_eq!(
- self.watch_later_block_list.insert(hash, ()),
- None,
- "A video should not be blocked *and* in the playlist"
- );
-
- Self::message(mpv, "Marked the video to be watched later", "3000")?;
- }
- &["yt-mark-done-and-go-next"] => {
- let cvideo_hash = self.remove_cvideo_from_playlist(mpv)?;
- self.mark_video_watched(app, &cvideo_hash).await?;
-
- Self::message(mpv, "Marked the video watched", "3000")?;
- }
- &["yt-check-new-videos"] => {
- self.possibly_add_new_videos(app, mpv, true).await?;
- }
- other => {
- debug!("Unknown message: {}", other.join(" "))
- }
- }
- }
- _ => {}
- }
-
- Ok(false)
- }
-}
diff --git a/src/watch/events/playlist_handler.rs b/src/watch/events/playlist_handler.rs
deleted file mode 100644
index 0933856..0000000
--- a/src/watch/events/playlist_handler.rs
+++ /dev/null
@@ -1,94 +0,0 @@
-// 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::collections::HashMap;
-
-use anyhow::Result;
-use libmpv2::{events::PlaylistEntryId, mpv_node::MpvNode, Mpv};
-
-use crate::storage::video_database::extractor_hash::ExtractorHash;
-
-#[derive(Debug, Default)]
-pub struct PlaylistHandler {
- /// A map of the original file paths to the videos extractor hashes.
- /// Used to get the extractor hash from a video returned by mpv
- playlist_cache: HashMap<String, ExtractorHash>,
-
- /// A map of the playlist_entry_id field to their corresponding extractor hashes.
- playlist_ids: HashMap<PlaylistEntryId, ExtractorHash>,
-}
-impl PlaylistHandler {
- pub fn from_cache(cache: HashMap<String, ExtractorHash>) -> Self {
- Self {
- playlist_cache: cache,
- playlist_ids: HashMap::new(),
- }
- }
-
- pub fn reserve(&mut self, len: usize) {
- self.playlist_cache.reserve(len)
- }
- pub fn add(&mut self, cache_path: String, extractor_hash: ExtractorHash) {
- assert_eq!(
- self.playlist_cache.insert(cache_path, extractor_hash),
- None,
- "Only new video should ever be added"
- );
- }
-
- pub fn playlist_ids(&mut self, mpv: &Mpv) -> Result<&HashMap<PlaylistEntryId, ExtractorHash>> {
- let mpv_playlist: Vec<(String, PlaylistEntryId)> = match mpv.get_property("playlist")? {
- MpvNode::ArrayIter(array) => array
- .map(|val| match val {
- MpvNode::MapIter(map) => {
- struct BuildPlaylistEntry {
- filename: Option<String>,
- id: Option<PlaylistEntryId>,
- }
- let mut entry = BuildPlaylistEntry {
- filename: None,
- id: None,
- };
-
- map.for_each(|(key, value)| match key.as_str() {
- "filename" => {
- entry.filename = Some(value.str().expect("work").to_owned())
- }
- "id" => {
- entry.id = Some(PlaylistEntryId::new(value.i64().expect("Works")))
- }
- _ => (),
- });
- (entry.filename.expect("is some"), entry.id.expect("is some"))
- }
- _ => unreachable!(),
- })
- .collect(),
- _ => unreachable!(),
- };
-
- let mut playlist: HashMap<PlaylistEntryId, ExtractorHash> =
- HashMap::with_capacity(mpv_playlist.len());
- for (path, key) in mpv_playlist {
- let hash = self
- .playlist_cache
- .get(&path)
- .expect("All path should also be stored in the cache")
- .to_owned();
- playlist.insert(key, hash);
- }
-
- for (id, hash) in playlist {
- self.playlist_ids.entry(id).or_insert(hash);
- }
-
- Ok(&self.playlist_ids)
- }
-}