kodik-mpv-plugin 0.1.0

mpv plugin to get direct links to Kodik
use std::{collections::HashMap, path::PathBuf};

use anyhow::{Result, anyhow};
use mpv_client::{Format, Handle, Node};

pub trait MpvResultExt<T> {
    fn mpv_context(self, context: impl Into<String>) -> Result<T>;
    fn with_mpv_context<F, C>(self, f: F) -> Result<T>
    where
        F: FnOnce() -> C,
        C: Into<String>;
}

impl<T> MpvResultExt<T> for std::result::Result<T, mpv_client::Error> {
    fn mpv_context(self, context: impl Into<String>) -> Result<T> {
        self.map_err(|err| anyhow!("{}: {:?}", context.into(), err))
    }

    fn with_mpv_context<F, C>(self, f: F) -> Result<T>
    where
        F: FnOnce() -> C,
        C: Into<String>,
    {
        self.map_err(|err| {
            let context = f().into();
            anyhow!("{context}: {err:?}")
        })
    }
}

pub trait MpvExt {
    fn get_script_opts(&mut self) -> Result<HashMap<String, Node>>;
    fn get_stream_open_filename(&mut self) -> Result<String>;
    fn set_stream_open_filename<S: Into<String>>(&mut self, filename: S) -> Result<()>;
    fn playlist_next_weak(&mut self) -> Result<()>;
    fn expand_path(&mut self, path: &str) -> Result<PathBuf>;
    fn playlist_remove(&mut self, index: i64) -> Result<()>;
    fn loadfile_insert_at(&mut self, url: &str, index: &str, options: &str) -> Result<()>;
    fn get_playlist_pos(&mut self) -> Result<i64>;
    fn set_playlist_pos(&mut self, pos: &str) -> Result<()>;
    fn playlist_play_index(&mut self, index: &str) -> Result<()>;
    fn get_ytdl_format(&mut self) -> Result<String>;
    fn video_add(&mut self, url: &str, flag: &str, title: &str) -> Result<()>;
    fn hook_add_ext(&mut self, reply: u64, name: &str, priority: i32) -> Result<()>;
    fn observe_property_ext<T: Format>(&mut self, reply: u64, name: impl AsRef<str>) -> Result<()>;
    fn set_file_local_options_start<S: ToString>(&mut self, time_pos: S) -> Result<()>;
    fn get_time_pos(&mut self) -> Result<f64>;
    fn reload_current_file(&mut self) -> Result<()>;
    fn get_current_tracks_video_title(&mut self) -> Result<String>;
    fn get_playlist_filename_by_index(&mut self, index: i64) -> Result<String>;
    fn get_playlist_count(&mut self) -> Result<i64>;
}

impl MpvExt for Handle {
    fn get_script_opts(&mut self) -> Result<HashMap<String, Node>> {
        let node = self
            .get_property("options/script-opts")
            .mpv_context("failed to `get-property script-opts`")?;

        let Node::Map(script_opts) = node else {
            anyhow::bail!("`script-opts` is not a map")
        };

        anyhow::Ok(script_opts)
    }

    fn get_stream_open_filename(&mut self) -> Result<String> {
        self.get_property("stream-open-filename")
            .mpv_context("failed to `get-property stream-open-filename`")
    }

    fn set_stream_open_filename<S: Into<String>>(&mut self, filename: S) -> Result<()> {
        let filename = filename.into();
        self.set_property("stream-open-filename", filename.clone())
            .with_mpv_context(|| format!("failed to `set-property stream-open-filename {filename}`"))
    }

    fn playlist_next_weak(&mut self) -> Result<()> {
        self.command(["playlist-next", "weak"])
            .mpv_context("failed to `playlist-next weak`")
    }

    fn expand_path(&mut self, path: &str) -> Result<PathBuf> {
        let node = self
            .command_ret(["expand-path", path])
            .with_mpv_context(|| format!("failed to `expand-path {path}`"))?;

        let Node::String(expanded_path) = node else {
            anyhow::bail!("`expand-path {path}` returned non-string value");
        };

        anyhow::Ok(PathBuf::from(expanded_path))
    }

    fn playlist_remove(&mut self, index: i64) -> Result<()> {
        self.command(["playlist-remove", &index.to_string()])
            .with_mpv_context(|| format!("failed to `playlist-remove {index}`"))
    }

    fn loadfile_insert_at(&mut self, url: &str, index: &str, options: &str) -> Result<()> {
        self.command(["loadfile", url, "insert-at", index, options])
            .with_mpv_context(|| format!("failed to `loadfile {url} insert-at {index} {options}`"))
    }

    fn get_playlist_pos(&mut self) -> Result<i64> {
        self.get_property("playlist-pos")
            .mpv_context("failed to `get-property playlist-pos`")
    }

    fn set_playlist_pos(&mut self, pos: &str) -> Result<()> {
        self.command(["set", "playlist-pos", pos])
            .with_mpv_context(|| format!("failed to `set playlist-pos {pos}`"))
    }

    fn playlist_play_index(&mut self, index: &str) -> Result<()> {
        self.command(["playlist-play-index", index])
            .with_mpv_context(|| format!("failed to `playlist-play-index {index}`"))
    }

    fn get_ytdl_format(&mut self) -> Result<String> {
        self.get_property("ytdl-format")
            .mpv_context("failed to `get-property ytdl-format`")
    }

    fn video_add(&mut self, url: &str, flag: &str, title: &str) -> Result<()> {
        self.command(["video-add", url, flag, title, "ru"])
            .with_mpv_context(|| format!("failed to `video-add {url} {flag} {title} ru`"))
    }

    fn hook_add_ext(&mut self, reply: u64, name: &str, priority: i32) -> Result<()> {
        self.hook_add(reply, name, priority)
            .with_mpv_context(|| format!("failed to `hook-add {reply} {name} {priority}`"))
    }

    fn observe_property_ext<T: Format>(&mut self, reply: u64, name: impl AsRef<str>) -> Result<()> {
        self.observe_property::<T>(reply, &name)
            .with_mpv_context(|| format!("failed to `observe-property {}`", name.as_ref()))
    }

    fn set_file_local_options_start<S: ToString>(&mut self, time_pos: S) -> Result<()> {
        let time_pos = time_pos.to_string();
        self.set_property("file-local-options/start", time_pos.clone())
            .with_mpv_context(|| format!("failed to `set-property file-local-options/start {time_pos}`"))
    }

    fn get_time_pos(&mut self) -> Result<f64> {
        self.get_property("time-pos")
            .mpv_context("failed to `get-property time-pos`")
    }

    fn reload_current_file(&mut self) -> Result<()> {
        self.command(["playlist-play-index", "current", "yes"])
            .mpv_context("failed to `playlist-play-index current yes`")
    }

    fn get_current_tracks_video_title(&mut self) -> Result<String> {
        self.get_property::<String>("current-tracks/video/title")
            .mpv_context("failed to `get-property current-tracks/video/title`")
    }

    fn get_playlist_filename_by_index(&mut self, index: i64) -> Result<String> {
        self.get_property(format!("playlist/{index}/filename"))
            .with_mpv_context(|| format!("failed to get `playlist/{index}/filename`"))
    }

    fn get_playlist_count(&mut self) -> Result<i64> {
        self.get_property("playlist-count")
            .mpv_context("failed to `get-property playlist-count`")
    }
}