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:
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,