commit
a872ea2f65
@ -0,0 +1,4 @@ |
|||||||
|
/target |
||||||
|
**/*.rs.bk |
||||||
|
.idea |
||||||
|
*.iml |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,17 @@ |
|||||||
|
[package] |
||||||
|
name = "rapblock" |
||||||
|
version = "0.1.0" |
||||||
|
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] |
||||||
|
edition = "2018" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
mpris = "^1.1" |
||||||
|
reqwest = "0.9.10" |
||||||
|
percent-encoding = "1.0.1" |
||||||
|
serde = "1.0.88" |
||||||
|
serde_derive = "1.0.88" |
||||||
|
serde_json = "1.0" |
||||||
|
failure = "0.1.5" |
||||||
|
lazy_static = "1.2.0" |
||||||
|
log = "0.4" |
||||||
|
env_logger = "0.6" |
@ -0,0 +1,86 @@ |
|||||||
|
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; |
||||||
|
use failure::Error; |
||||||
|
use std::io::Read; |
||||||
|
use std::time::Duration; |
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)] |
||||||
|
struct MBArtistQueryResult { |
||||||
|
created : String, |
||||||
|
count: i32, |
||||||
|
offset: i32, |
||||||
|
artists: Option<Vec<MBArtist>>, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)] |
||||||
|
struct Tag { |
||||||
|
count: i32, // negative doesn't make sense, but it sometimes occurs
|
||||||
|
name: String, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)] |
||||||
|
struct MBArtist { |
||||||
|
id: String, |
||||||
|
score: i32, |
||||||
|
name: String, |
||||||
|
#[serde(rename="sort-name")] |
||||||
|
sort_name: String, |
||||||
|
tags: Option<Vec<Tag>> |
||||||
|
} |
||||||
|
|
||||||
|
lazy_static! { |
||||||
|
static ref BAD_GENRES : Vec<&'static str> = vec![ |
||||||
|
"hip hop", "hip-hop", "hiphop", "rnb", "rap", "rapper", |
||||||
|
"hardcore hip hop", "new york hip hop", "east coast hip hop", |
||||||
|
"pop rap" |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
pub fn artist_sucks(artist : &str) -> Result<bool, Error> { |
||||||
|
let query = utf8_percent_encode(&format!("\"{}\"", artist), DEFAULT_ENCODE_SET).to_string(); |
||||||
|
let url = format!("https://musicbrainz.org/ws/2/artist?query={}&fmt=json&inc=tags", query); |
||||||
|
|
||||||
|
info!("Querying MusicBrainz for artist \"{}\", query URL: {}", artist, url); |
||||||
|
|
||||||
|
let mut resp = String::new(); |
||||||
|
|
||||||
|
reqwest::Client::builder() |
||||||
|
.timeout(Duration::from_millis(2000)) |
||||||
|
.build()? |
||||||
|
.get(&url) |
||||||
|
.header(reqwest::header::USER_AGENT, "Bad Spotify Song Skipper/0.0.1 ( ondra@ondrovo.com )") |
||||||
|
.header(reqwest::header::ACCEPT, "text/json") |
||||||
|
.send()? |
||||||
|
.read_to_string(&mut resp)?; |
||||||
|
|
||||||
|
debug!("Resp: {}", resp); |
||||||
|
|
||||||
|
let result : MBArtistQueryResult = serde_json::from_str(&resp)?; |
||||||
|
|
||||||
|
info!("Response OK"); |
||||||
|
debug!("{:#?}", result); |
||||||
|
|
||||||
|
if result.count == 0 { |
||||||
|
// not found, let's hope it's OK
|
||||||
|
warn!("No results!"); |
||||||
|
return Err(failure::err_msg("Artist not found")); |
||||||
|
} else { |
||||||
|
info!("Got {} results", result.count); |
||||||
|
let artists = result.artists.as_ref().unwrap(); |
||||||
|
if artists[0].tags.is_some() { |
||||||
|
let tags = artists[0].tags.as_ref().unwrap(); |
||||||
|
let as_vec : Vec<&String> = tags.iter().map(|t| &t.name).collect(); |
||||||
|
debug!("First artist has tags: {:?}", tags); |
||||||
|
for tag in as_vec { |
||||||
|
if BAD_GENRES.contains(&tag.as_str()) { |
||||||
|
info!("Found a bad tag: {}", tag); |
||||||
|
return Ok(true); |
||||||
|
} |
||||||
|
} |
||||||
|
info!("All tags OK"); |
||||||
|
return Ok(false); |
||||||
|
} else { |
||||||
|
warn!("No tags, can't determine genre"); |
||||||
|
return Err(failure::err_msg("Artist found, but has no tags")); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,109 @@ |
|||||||
|
#[macro_use] extern crate serde_derive; |
||||||
|
#[macro_use] extern crate lazy_static; |
||||||
|
#[macro_use] extern crate log; |
||||||
|
|
||||||
|
mod brainz; |
||||||
|
|
||||||
|
use mpris::{PlayerFinder,Event}; |
||||||
|
use failure::Error; |
||||||
|
use std::time::Duration; |
||||||
|
|
||||||
|
const DELAY_FIND_PLAYER : Duration = Duration::from_millis(1000); |
||||||
|
|
||||||
|
fn main() -> Result<(), Error> { |
||||||
|
let env = env_logger::Env::default().default_filter_or("info"); |
||||||
|
env_logger::Builder::from_env(env).init(); |
||||||
|
|
||||||
|
'main_loop: |
||||||
|
loop { |
||||||
|
let player = PlayerFinder::new() |
||||||
|
.expect("Could not connect to D-Bus") |
||||||
|
.find_active(); |
||||||
|
|
||||||
|
if let Ok(player) = player { |
||||||
|
info!("Connected to player: {}{}", player.bus_name().to_string(), player.unique_name()); |
||||||
|
|
||||||
|
let events = player.events(); |
||||||
|
if events.is_err() { |
||||||
|
error!("Could not start event stream!"); |
||||||
|
// add a delay so we don't run too hot here
|
||||||
|
::std::thread::sleep(DELAY_FIND_PLAYER); |
||||||
|
continue 'main_loop; |
||||||
|
} |
||||||
|
|
||||||
|
'event_loop: |
||||||
|
for event in events.unwrap() { |
||||||
|
match event { |
||||||
|
Ok(event) => { |
||||||
|
debug!("MPRIS event: {:#?}", event); |
||||||
|
match &event { |
||||||
|
Event::PlayerShutDown => { |
||||||
|
info!("Player shut down"); |
||||||
|
break 'event_loop; |
||||||
|
}, |
||||||
|
Event::TrackChanged(metadata) => { |
||||||
|
let title = metadata.title().unwrap_or(""); |
||||||
|
info!("--- new track : {} ---", title); |
||||||
|
debug!("{:#?}", event); |
||||||
|
|
||||||
|
if title.is_empty() { |
||||||
|
warn!("!!! Spotify is giving us garbage - empty metadata struct !!!"); |
||||||
|
// wait for next event
|
||||||
|
continue 'event_loop; |
||||||
|
} |
||||||
|
|
||||||
|
'artists_loop: |
||||||
|
for ar in metadata.artists().into_iter().chain(metadata.artists()) { |
||||||
|
for a in ar { |
||||||
|
info!("Checking artist: {}", a); |
||||||
|
|
||||||
|
let verdict = brainz::artist_sucks(&a); |
||||||
|
match verdict { |
||||||
|
Ok(verdict) => { |
||||||
|
if verdict { |
||||||
|
if player.can_go_next().unwrap_or(false) { |
||||||
|
info!(">>>>>> SKIP >>>>>>"); |
||||||
|
if player.next().is_err() { |
||||||
|
break 'artists_loop; |
||||||
|
} |
||||||
|
} else { |
||||||
|
info!("<><><> STOP <><><>"); |
||||||
|
if player.pause().is_err() { |
||||||
|
break 'artists_loop; |
||||||
|
} |
||||||
|
} |
||||||
|
// we add a delay here to prevent going insane on rap playlists
|
||||||
|
::std::thread::sleep(Duration::from_millis(1000)); |
||||||
|
} else { |
||||||
|
info!("Good artist, let it play"); |
||||||
|
} |
||||||
|
}, |
||||||
|
Err(e) => { |
||||||
|
error!("Something went wrong: {}", e); |
||||||
|
info!("Letting to play"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
_ => { |
||||||
|
debug!("Event not handled."); |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
Err(err) => { |
||||||
|
error!("D-Bus error: {}. Aborting.", err); |
||||||
|
break 'event_loop; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
info!("Event stream ended - player likely shut down"); |
||||||
|
} else { |
||||||
|
debug!("No player found, waiting..."); |
||||||
|
} |
||||||
|
|
||||||
|
::std::thread::sleep(DELAY_FIND_PLAYER); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
Loading…
Reference in new issue