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