radio-rs

Place a description here.
git clone git://git.alex.balgavy.eu/reponame.git
Log | Files | Refs

commit 7d74153be22a27f0e612f6afd5796686f5372f25
parent 51ab519777061a9bff83002cdb1f8dc9a9a4d509
Author: Alex Balgavy <alex@balgavy.eu>
Date:   Thu,  9 Jun 2022 14:54:46 +0200

Implemented Sounds of Earth radio

Diffstat:
MCargo.lock | 375+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MCargo.toml | 2++
Msrc/config.rs | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/main.rs | 35+++++++++++++++++++----------------
Dsrc/radio/baseradio.rs | 60------------------------------------------------------------
Msrc/radio/mod.rs | 2+-
Dsrc/radio/sounds_of_earth.rb | 32--------------------------------
Asrc/radio/sounds_of_earth.rs | 33+++++++++++++++++++++++++++++++++
Msrc/tunein.rs | 2+-
9 files changed, 495 insertions(+), 112 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1,5 +1,146 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "proc-macro2" version = "1.0.39" @@ -23,7 +164,52 @@ name = "radio" version = "0.1.0" dependencies = [ "serde", + "serde_json", "toml", + "ureq", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", ] [[package]] @@ -47,6 +233,23 @@ dependencies = [ ] [[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] name = "syn" version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -58,6 +261,21 @@ dependencies = [ ] [[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] name = "toml" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -67,7 +285,164 @@ dependencies = [ ] [[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] name = "unicode-ident" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9399fa2f927a3d327187cbd201480cee55bee6ac5d3c77dd27f0c6814cff16d5" +dependencies = [ + "base64", + "chunked_transfer", + "flate2", + "log", + "once_cell", + "rustls", + "serde", + "serde_json", + "url", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml @@ -9,3 +9,5 @@ edition = "2018" [dependencies] toml = "0.5.9" serde = { version = "1.0", features = ["derive"] } +ureq = { version = "2.4.0", features = ["json"] } +serde_json = "1.0" diff --git a/src/config.rs b/src/config.rs @@ -1,6 +1,8 @@ +use crate::tunein::*; use crate::*; use serde::Deserialize; use std::fs; +use std::process::Command; #[derive(Deserialize)] pub struct ConfigData { @@ -13,15 +15,66 @@ pub struct Config { pub player: PlaybackOptions, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct Item { pub name: String, - pub website: String, + pub description: Option<String>, + pub website: Option<String>, + #[serde(alias = "sound")] pub url: Option<String>, pub mpc_load_option: Option<MpcLoadOptions>, pub radios: Option<Vec<Item>>, } +impl TuneIn for Item { + fn play(&self, options: &RadioOptions) { + let stream = self.url.as_ref().unwrap(); + println!("Playing {}", stream); + match options { + RadioOptions { + method: PlaybackOptions::MPC, + load_options: Some(l), + } => { + Command::new("mpc") + .arg("clear") + .stdout(std::process::Stdio::null()) + .spawn() + .expect("MPC couldn't clear"); + match l { + MpcLoadOptions::ADD => { + Command::new("mpc") + .args(&["add", stream]) + .spawn() + .expect("MPC couldn't add stream"); + } + MpcLoadOptions::LOAD => { + Command::new("mpc") + .args(&["load", stream]) + .spawn() + .expect("MPC couldn't load stream"); + } + } + Command::new("mpc") + .arg("play") + .stdout(std::process::Stdio::null()) + .spawn() + .expect("MPC couldn't play"); + } + RadioOptions { + method: PlaybackOptions::MPV, + load_options: None, + } => { + let mut child = Command::new("mpv") + .args(&[stream, "--vid=no", "--volume=50"]) + .spawn() + .expect("MPV couldn't play"); + let _ = child.wait(); + } + _ => panic!("Wrong combination of playback options."), + } + } +} + pub fn read_config() -> ConfigData { let filename = "config.toml"; let contents = match fs::read_to_string(filename) { @@ -38,5 +91,14 @@ pub fn read_config() -> ConfigData { exit(1); } }; + + let radios = &data.config.radios; + assert!(radios + .iter() + .all(|r| (r.url.is_some() && r.mpc_load_option.is_some()) || (r.radios.is_some()))); + assert!(radios + .iter() + .all(|r| r.description.is_some() || r.website.is_some())); + return data; } diff --git a/src/main.rs b/src/main.rs @@ -6,11 +6,18 @@ use config::*; use radio::*; use std::process::exit; use tunein::*; +/* TODO: + * - optionally allow playing video + * - implement additional radios from Ruby scripts + */ fn choose_radio(radios: &Vec<Item>) -> &Item { loop { let choices: Vec<String> = radios .iter() - .map(|x| format!("{} ({})", x.name, x.website)) + .map(|x| match &x.website { + Some(w) => format!("{} ({})", x.name, w), + None => format!("{} ({})", x.name, x.description.as_ref().unwrap()), + }) .collect(); let (choice_i, _) = match screen::list_menu(&*choices) { @@ -20,37 +27,33 @@ fn choose_radio(radios: &Vec<Item>) -> &Item { let chosen_radio = match radios[choice_i].url { Some(_) => &radios[choice_i], None => { - let choices: Vec<String> = radios[choice_i] - .radios - .as_ref() - .unwrap() + let subradios = radios[choice_i].radios.as_ref().unwrap(); + let choices: Vec<String> = subradios .iter() - .map(|x| format!("{} ({})", x.name, x.website)) + .map(|x| match &x.website { + Some(w) => format!("{} ({})", x.name, w), + None => format!("{} ({})", x.name, x.description.as_ref().unwrap()), + }) .collect(); let (choice_i, _) = match screen::list_menu(&*choices) { Some((i, s)) => (i, s), None => continue, }; - &radios[choice_i] + &subradios[choice_i] } }; return chosen_radio; } } fn main() { - let data = config::read_config(); - assert!(data - .config - .radios - .iter() - .all(|x| (x.url.is_some() && x.mpc_load_option.is_some()) || (x.radios.is_some()))); + let mut data = config::read_config(); - let top_radios = &data.config.radios; + let top_radios = &mut data.config.radios; + top_radios.push(sounds_of_earth::new()); let chosen_radio = choose_radio(top_radios); - let radio = baseradio::StreamRadio::new(chosen_radio.url.as_ref().unwrap()); let opts = RadioOptions { method: data.config.player, load_options: chosen_radio.mpc_load_option, }; - radio.play(&opts); + chosen_radio.play(&opts); } diff --git a/src/radio/baseradio.rs b/src/radio/baseradio.rs @@ -1,60 +0,0 @@ -use crate::tunein::*; -use std::process::Command; -pub struct StreamRadio { - stream: String, -} - -impl StreamRadio { - pub fn new(stream: &str) -> Self { - Self { - stream: stream.to_string(), - } - } -} - -impl TuneIn for StreamRadio { - fn play(&self, options: &RadioOptions) { - match options { - RadioOptions { - method: PlaybackOptions::MPC, - load_options: Some(l), - } => { - Command::new("mpc") - .arg("clear") - .stdout(std::process::Stdio::null()) - .spawn() - .expect("MPC couldn't clear"); - match l { - MpcLoadOptions::ADD => { - Command::new("mpc") - .args(&["add", &self.stream]) - .spawn() - .expect("MPC couldn't add stream"); - } - MpcLoadOptions::LOAD => { - Command::new("mpc") - .args(&["load", &self.stream]) - .spawn() - .expect("MPC couldn't load stream"); - } - } - Command::new("mpc") - .arg("play") - .stdout(std::process::Stdio::null()) - .spawn() - .expect("MPC couldn't play"); - } - RadioOptions { - method: PlaybackOptions::MPV, - load_options: None, - } => { - let mut child = Command::new("mpv") - .args(&[&self.stream, "--vid=no", "--volume=50"]) - .spawn() - .expect("MPV couldn't play"); - let _ = child.wait(); - } - _ => panic!("Wrong combination of playback options."), - } - } -} diff --git a/src/radio/mod.rs b/src/radio/mod.rs @@ -1 +1 @@ -pub mod baseradio; +pub mod sounds_of_earth; diff --git a/src/radio/sounds_of_earth.rb b/src/radio/sounds_of_earth.rb @@ -1,32 +0,0 @@ -# TODO: convert to rust -class SoundsOfEarth < Radio - require 'json' - require 'open-uri' - - def initialize(_) - channels = retrieve_channels - @channel = choose_from_list(channels.map { |c| c[:link] }, channels.map { |c| c[:name] }) while @channel.nil? - super() - rescue Interrupt - @channel = nil - end - - def play - if @player == 'mpc' - system 'mpc', 'clear', 1 => '/dev/null' - system 'mpc', 'add', @channel - end - super @channel - end - - private - - def retrieve_channels - URI('https://soundsofearth.eco/regions.json').open do |response| - streams = JSON.parse(response.read)['results'] - streams.map { |stream| { name: "#{stream['name']} (#{stream['description']})", link: stream['sound'] } } - end - rescue OpenURI::HTTPError - [] - end -end diff --git a/src/radio/sounds_of_earth.rs b/src/radio/sounds_of_earth.rs @@ -0,0 +1,33 @@ +use crate::config::*; +use crate::tunein::*; +use serde_json; +use ureq; +fn retrieve_channels() -> Result<Vec<Item>, ureq::Error> { + let uri = "https://soundsofearth.eco/regions.json"; + let resp: ureq::Response = ureq::get(uri).call()?; + let mut body: serde_json::Value = resp.into_json()?; + let mut sounds: Vec<Item> = serde_json::from_value(body["results"].take()).unwrap(); + for s in &mut sounds { + s.mpc_load_option = Some(MpcLoadOptions::ADD); + } + Ok(sounds) +} +pub fn new() -> Item { + match retrieve_channels() { + Ok(radios) => { + assert!(radios + .iter() + .all(|r| r.description.is_some() || r.website.is_some())); + assert!(radios.iter().all(|r| r.url.is_some())); + return Item { + name: "Sounds of Earth".to_string(), + website: Some("https://www.soundsofearth.eco/".to_string()), + radios: Some(radios), + mpc_load_option: None, + url: None, + description: None, + }; + } + Err(e) => panic!("error getting channels: {}", e), + } +} diff --git a/src/tunein.rs b/src/tunein.rs @@ -4,7 +4,7 @@ pub enum PlaybackOptions { MPC, MPV, } -#[derive(Deserialize, Clone, Copy)] +#[derive(Deserialize, Clone, Copy, Debug)] pub enum MpcLoadOptions { ADD, LOAD,