Compare commits

..

17 commits
1.0.0 ... main

Author SHA1 Message Date
87f401f5cf
Make clippy happy
Some checks failed
build-plugin / build (dev) (push) Has been cancelled
build-plugin / build (release) (push) Has been cancelled
2024-07-15 02:43:20 +03:00
caa7d4c5a7
Fix hash for rust-toolchain.toml
Some checks are pending
build-plugin / build (dev) (push) Waiting to run
build-plugin / build (release) (push) Waiting to run
2024-07-15 02:35:26 +03:00
dff668c760
Add build workflow 2024-07-15 02:34:04 +03:00
38b50ccf4d
Update Nix flake 2024-07-15 02:22:14 +03:00
216688e63e
Add Nix dev shell and direnv support 2024-05-24 15:48:51 +03:00
1eca609a2d
Add Cargo.lock 2023-09-04 23:14:47 +03:00
d104354966
Calm down clippy 2023-03-10 06:04:40 +03:00
674e3b77ed
Bump to version to 1.1.1 2023-02-24 18:00:50 +03:00
bab000657b
Properly wait for new event: set timeout -1.0 to wait infinitely 2023-02-24 18:00:50 +03:00
9bebd5b648
Do not replace existing rpc.conf 2023-02-24 18:00:50 +03:00
d83116efc3
Add annotation of supporting only Linux 2023-02-19 18:55:29 +03:00
4c7c27c26e
Bump version to 1.1.0 2023-02-12 15:04:58 +03:00
9c854f0c5b
Added ability to pull cover art from album artist 2023-02-12 15:02:18 +03:00
e95168ccf4
Add keybinding only if doesn't exist 2023-02-12 15:00:06 +03:00
613b2d725e Bump version to 1.0.1 2023-02-11 15:16:46 +03:00
9c567ac312 Do not overwrite input.conf in install script 2023-02-11 15:13:55 +03:00
8b04b3b28d Use Default trait in Config 2023-02-11 15:11:30 +03:00
20 changed files with 1252 additions and 49 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

54
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,54 @@
name: build-plugin
on:
workflow_dispatch:
push:
branches: [ main ]
paths:
- "src/**"
- Cargo.lock
- rust-toolchain.toml
- flake.nix
- flake.lock
- shell.nix
pull_request:
branches: [ main ]
paths:
- "src/**"
- Cargo.lock
- rust-toolchain.toml
- flake.nix
- flake.lock
- shell.nix
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
profile: [ "dev", "release" ]
env:
BUILD_NAME: ${{ matrix.profile == 'dev' && 'debug' || matrix.profile }}
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v28
- name: Check
run: nix develop --command cargo clippy --profile ${{ matrix.profile }} -- -D warnings
- name: Build
run: nix develop --command cargo build --profile ${{ matrix.profile }}
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: mpv-rpc_${{ env.BUILD_NAME }}
path: target/${{ env.BUILD_NAME }}/libmpv_rpc.so

2
.gitignore vendored
View file

@ -1,2 +1,2 @@
/target /target
/Cargo.lock /.direnv

1005
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "mpv-rpc" name = "mpv-rpc"
version = "1.0.0" version = "1.1.1"
edition = "2021" edition = "2021"
[lib] [lib]

View file

@ -25,6 +25,7 @@
- Rusty! 🦀 - Rusty! 🦀
# Installation # Installation
*Only Linux is supported at the moment, see MPV docs, regarding C plugins*
1. Download [latest release](https://github.com/ryze312/mpv-rpc/releases/latest) and unzip it 1. Download [latest release](https://github.com/ryze312/mpv-rpc/releases/latest) and unzip it
2. Run the installer script 2. Run the installer script
3. Keybindings can be changed in input.conf 3. Keybindings can be changed in input.conf

Binary file not shown.

65
flake.lock Normal file
View file

@ -0,0 +1,65 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1720938532,
"narHash": "sha256-Q2ldXS7ZUJWDqdF+GjgRQPlDfdM6NyJ1aw7xUdzjY+4=",
"owner": "nix-community",
"repo": "fenix",
"rev": "e761b522381d124fdc3ba20cecb2be2abec1d8cf",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1720955038,
"narHash": "sha256-GaliJqfFwyYxReFywxAa8orCO+EnDq2NK2F+5aSc8vo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "aa247c0c90ecf4ae7a032c54fdc21b91ca274062",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixpkgs-unstable",
"type": "indirect"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"nixpkgs": "nixpkgs"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1720717809,
"narHash": "sha256-6I+fm+nTLF/iaj7ffiFGlSY7POmubwUaPA/Wq0Bm53M=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "ffbc5ad993d5cd2f3b8bcf9a511165470944ab91",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

32
flake.nix Normal file
View file

@ -0,0 +1,32 @@
{
inputs = {
nixpkgs.url = "nixpkgs/nixpkgs-unstable";
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, fenix }:
let
overlays = [ fenix.overlays.default ];
getPkgsFor = (system: import nixpkgs {
inherit system overlays;
});
forEachSystem = func: nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
"aarch64-darwin"
"x86_64-darwin"
] (system: func (getPkgsFor system));
in {
devShells = forEachSystem (pkgs: {
default = pkgs.callPackage ./shell.nix {};
});
};
}

View file

@ -1,5 +1,7 @@
#!/bin/bash #!/bin/bash
key='D'
script_binding='script-binding "libmpv_rpc/toggle-rpc"'
mpv_home="${MPV_HOME:-${XDG_CONFIG_HOME:-${HOME}/.config}/mpv}" mpv_home="${MPV_HOME:-${XDG_CONFIG_HOME:-${HOME}/.config}/mpv}"
scripts_dir="$mpv_home/scripts" scripts_dir="$mpv_home/scripts"
@ -11,10 +13,14 @@ echo -n "Copying script..."
cp ./bin/libmpv_rpc.so "$scripts_dir" cp ./bin/libmpv_rpc.so "$scripts_dir"
echo "Done!" echo "Done!"
echo -n "Copying default config..." if [ ! -f "$mpv_home/rpc.json" ]; then
cp ./config/rpc.json "$mpv_home" echo -n "Copying default config..."
echo "Done!" cp ./config/rpc.json "$mpv_home"
echo "Done!"
fi
echo -n "Adding keybinding entry to input.conf..." if ! grep -q "$script_binding" "$mpv_home/input.conf"; then
echo 'D script-binding "libmpv_rpc/toggle-rpc"' > "$mpv_home/input.conf" echo -n "Adding keybinding entry to input.conf..."
echo "Done!" echo "$key $script_binding" >> "$mpv_home/input.conf"
echo "Done!"
fi

4
rust-toolchain.toml Normal file
View file

@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
profile = "default"
targets = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"]

12
shell.nix Normal file
View file

@ -0,0 +1,12 @@
{ mkShell, fenix }:
let
toolchain = fenix.fromToolchainFile {
file = ./rust-toolchain.toml;
sha256 = "Ngiz76YP4HTY75GGdH2P+APE/DEIx2R/Dn+BwwOyzZU=";
};
in
mkShell {
packages = [ toolchain ];
}

View file

@ -26,13 +26,6 @@ const fn cover_art_default() -> bool {
} }
impl Config { impl Config {
pub fn default() -> Self {
Self {
active: active_default(),
cover_art: cover_art_default()
}
}
pub fn from_config_file(logger: &Logger) -> Self { pub fn from_config_file(logger: &Logger) -> Self {
let path = Config::get_config_path(); let path = Config::get_config_path();
logging::info!(logger, "Config path {path}"); logging::info!(logger, "Config path {path}");
@ -84,3 +77,12 @@ impl Config {
"/etc/mpv/".to_owned() "/etc/mpv/".to_owned()
} }
} }
impl Default for Config {
fn default() -> Self {
Self {
active: active_default(),
cover_art: cover_art_default()
}
}
}

View file

@ -157,7 +157,7 @@ impl DiscordClient {
return ("logo".to_string(), "mpv".to_string()) return ("logo".to_string(), "mpv".to_string())
} }
let cover_art_url = music_brainz::get_cover_art_url(&metadata.title, &metadata.album, &metadata.artist); let cover_art_url = music_brainz::get_cover_art_url(&metadata.title, &metadata.album, &metadata.artist, &metadata.album_artist);
let large_image = match cover_art_url { let large_image = match cover_art_url {
Some(url) => url, Some(url) => url,
None => "logo".to_string() None => "logo".to_string()
@ -265,7 +265,7 @@ impl MpvEventHandler for DiscordClient {
MpvEvent::FileLoaded(file_info) => self.set_presence(file_info), MpvEvent::FileLoaded(file_info) => self.set_presence(file_info),
MpvEvent::Seek(remaining_time) => self.set_timestamps(remaining_time), MpvEvent::Seek(remaining_time) => self.set_timestamps(remaining_time),
MpvEvent::Play(remaining_time) => self.set_timestamps(remaining_time), MpvEvent::Play(remaining_time) => self.set_timestamps(remaining_time),
MpvEvent::Pause(_) => self.clear_timestamps(), MpvEvent::Pause => self.clear_timestamps(),
MpvEvent::Buffering => self.clear_timestamps(), MpvEvent::Buffering => self.clear_timestamps(),
MpvEvent::Toggle => self.toggle_activity(), MpvEvent::Toggle => self.toggle_activity(),
MpvEvent::Exit => self.close(), MpvEvent::Exit => self.close(),

View file

@ -2,32 +2,52 @@ use musicbrainz_rs::entity::release_group::{ReleaseGroup, ReleaseGroupSearchQuer
use musicbrainz_rs::entity::CoverartResponse; use musicbrainz_rs::entity::CoverartResponse;
use musicbrainz_rs::{Search, FetchCoverart}; use musicbrainz_rs::{Search, FetchCoverart};
pub fn get_cover_art_url(title: &Option<String>, album: &Option<String>, artist: &Option<String>) -> Option<String>{ pub fn get_cover_art_url(title: &Option<String>, album: &Option<String>, artist: &Option<String>, album_artist: &Option<String>) -> Option<String>{
get_track_cover_art(artist, title).or(
get_album_cover_art(album_artist, album)
)
}
fn get_track_cover_art(artist: &Option<String>, title: &Option<String>) -> Option<String> {
let mut builder = ReleaseGroupSearchQuery::query_builder(); let mut builder = ReleaseGroupSearchQuery::query_builder();
if let Some(ref title) = title { if let Some(ref title) = title {
builder.release_group(title); builder.release_group(title);
} }
if let Some(ref album) = album {
builder.or().release_group(album);
}
if let Some(ref artist) = artist { if let Some(ref artist) = artist {
// Some artist fields might contain + characters // Some artist fields might contain + characters
// Pointing at multiple artists // Pointing at multiple artists
for part in artist.split("+") { for part in artist.split('+') {
builder.and().artist(part);
}
}
get_cover_art_from_query(builder.build())
}
fn get_album_cover_art(album_artist: &Option<String>, album: &Option<String>) -> Option<String> {
let mut builder = ReleaseGroupSearchQuery::query_builder();
if let Some(ref album) = album {
builder.release_group(album);
}
if let Some(ref album_artist) = album_artist {
// Some artist fields might contain + characters
// Pointing at multiple artists
for part in album_artist.split('+') {
builder.and().artist(part); builder.and().artist(part);
} }
} }
let query = builder.build(); get_cover_art_from_query(builder.build())
}
fn get_cover_art_from_query(query: String) -> Option<String> {
let result = match ReleaseGroup::search(query).execute() { let result = match ReleaseGroup::search(query).execute() {
Ok(res) => res, Ok(res) => res,
Err(_) => return None Err(_) => return None
}; };
let release = match result.entities.get(0) { let release = match result.entities.first() {
Some(group) => group, Some(group) => group,
None => return None None => return None
}; };

View file

@ -22,5 +22,5 @@ fn mpv_open_cplugin(handle: *mut mpv_handle) -> std::os::raw::c_int {
}; };
plugin.run(); plugin.run();
return 0; 0
} }

View file

@ -1,6 +1,8 @@
use std::env; use std::env;
pub mod macros; pub mod macros;
#[allow(unused_imports)]
pub use macros::{error, warning, info}; pub use macros::{error, warning, info};
#[allow(dead_code)] #[allow(dead_code)]
@ -49,19 +51,19 @@ impl Logger {
pub fn info(&self, message: &str) { pub fn info(&self, message: &str) {
if self.log_level >= LogLevel::Info { if self.log_level >= LogLevel::Info {
println!("[mpv-rpc (INFO)] {}", message); println!("[mpv-rpc (INFO)] {message}");
} }
} }
pub fn warning(&self, message: &str) { pub fn warning(&self, message: &str) {
if self.log_level >= LogLevel::Warn { if self.log_level >= LogLevel::Warn {
println!("[mpv-rpc (WARN)] {}", message); println!("[mpv-rpc (WARN)] {message}");
} }
} }
pub fn error(&self, message: &str) { pub fn error(&self, message: &str) {
if self.log_level >= LogLevel::Error { if self.log_level >= LogLevel::Error {
println!("[mpv-rpc (ERROR)] {}", message); println!("[mpv-rpc (ERROR)] {message}");
} }
} }
} }

View file

@ -25,7 +25,7 @@ impl MpvEventQueue {
Ok(new_self) Ok(new_self)
} }
pub fn from_ptr<'a>(handle: *mut mpv_handle, logger: Rc<Logger>) -> Result<Self, &'static str> { pub fn from_ptr(handle: *mut mpv_handle, logger: Rc<Logger>) -> Result<Self, &'static str> {
MpvEventQueue::new(Handle::from_ptr(handle), logger) MpvEventQueue::new(Handle::from_ptr(handle), logger)
} }
@ -41,9 +41,8 @@ impl MpvEventQueue {
} }
pub fn next_event(&mut self) -> Option<MpvEvent> { pub fn next_event(&mut self) -> Option<MpvEvent> {
let event = self.mpv.wait_event(0.0); let event = self.mpv.wait_event(-1.0);
let mpv_event = self.convert_event(event); self.convert_event(event)
mpv_event
} }
pub fn handle_request(&self, request: MpvRequest) -> Result<(), &'static str> { pub fn handle_request(&self, request: MpvRequest) -> Result<(), &'static str> {
@ -78,12 +77,14 @@ impl MpvEventQueue {
fn get_file_info_event(&self) -> Option<MpvEvent> { fn get_file_info_event(&self) -> Option<MpvEvent> {
let filename = self.mpv.get_property("filename").unwrap(); let filename = self.mpv.get_property("filename").unwrap();
let artist = self.mpv.get_property("metadata/by-key/artist").ok(); let artist = self.mpv.get_property("metadata/by-key/artist").ok();
let album_artist = self.mpv.get_property("metadata/by-key/album_artist").ok();
let album = self.mpv.get_property("metadata/by-key/album").ok(); let album = self.mpv.get_property("metadata/by-key/album").ok();
let title = self.mpv.get_property("metadata/by-key/title").ok(); let title = self.mpv.get_property("metadata/by-key/title").ok();
let track = self.mpv.get_property("metadata/by-key/track").ok(); let track = self.mpv.get_property("metadata/by-key/track").ok();
let metadata = FileMetadata { let metadata = FileMetadata {
artist, artist,
album_artist,
album, album,
title, title,
track track
@ -110,7 +111,7 @@ impl MpvEventQueue {
let time = self.get_remaining_time(); let time = self.get_remaining_time();
match pause { match pause {
false => Some(MpvEvent::Play(time)), false => Some(MpvEvent::Play(time)),
true => Some(MpvEvent::Pause(time)) true => Some(MpvEvent::Pause)
} }
} }

View file

@ -5,6 +5,7 @@ pub struct FileInfo {
pub struct FileMetadata { pub struct FileMetadata {
pub artist: Option<String>, pub artist: Option<String>,
pub album_artist: Option<String>,
pub album: Option<String>, pub album: Option<String>,
pub title: Option<String>, pub title: Option<String>,
pub track: Option<String> pub track: Option<String>
@ -16,7 +17,7 @@ pub enum MpvEvent {
Exit, Exit,
FileLoaded(FileInfo), FileLoaded(FileInfo),
Play(i64), Play(i64),
Pause(i64), Pause,
Seek(i64) Seek(i64)
} }
@ -29,5 +30,5 @@ pub trait MpvEventHandler {
} }
pub trait MpvRequester { pub trait MpvRequester {
fn next_request<'a>(&mut self) -> Option<MpvRequest>; fn next_request(&mut self) -> Option<MpvRequest>;
} }

View file

@ -48,10 +48,7 @@ impl RPCPlugin {
} }
fn handle_event(&mut self, event: MpvEvent) -> bool { fn handle_event(&mut self, event: MpvEvent) -> bool {
let exit = match event { let exit = matches!(event, MpvEvent::Exit);
MpvEvent::Exit => true,
_ => false
};
if let Err(e) = self.discord.handle_event(event) { if let Err(e) = self.discord.handle_event(event) {
logging::error!(self.logger, "Failed to handle event: {e}"); logging::error!(self.logger, "Failed to handle event: {e}");