commit 47bb2484a81e610a81ac977cbb4a19ba5c081871
Author: Alex Balgavy <alex@balgavy.eu>
Date: Sat, 4 Jun 2022 20:55:00 +0200
Initial working
Diffstat:
10 files changed, 398 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
@@ -0,0 +1,73 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "proc-macro2"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "radio"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "toml",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
diff --git a/Cargo.toml b/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "radio"
+version = "0.1.0"
+authors = ["Alex Balgavy <alex@balgavy.eu>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+toml = "0.5.9"
+serde = { version = "1.0", features = ["derive"] }
diff --git a/config.toml b/config.toml
@@ -0,0 +1,128 @@
+[config]
+player = "MPC"
+
+[[config.radios]]
+name = "Nightride FM"
+website = "https://nightride.fm"
+url = "https://nightride.fm/stream/nightride.m4a"
+mpc_load_option = "ADD"
+
+[[config.radios]]
+name = "Nightwave Plaza"
+website = "https://plaza.one"
+url = "https://plaza.one/mp3"
+mpc_load_option = "ADD"
+
+[[config.radios]]
+name = "6forty"
+website = "https://6fortyradio.com"
+url = "http://54.173.171.80:8000/6forty"
+mpc_load_option = "ADD"
+
+[[config.radios]]
+name = "Fnoob Techno"
+website = "https://fnoobtechno.com"
+url = "http://play.fnoobtechno.com:2199/tunein/fnoobtechno320.pls"
+mpc_load_option = "ADD"
+
+[[config.radios]]
+name = "65daysofstatic - Wreckage Systems"
+website = "https://65daysofstatic.com"
+url = "https://wreckage-systems.club/radio/8000/stream192.mp3"
+mpc_load_option = "ADD"
+
+[[config.radios]]
+name = "WFMU"
+website = "https://wfmu.org/"
+url = "https://wfmu.org/wfmu.pls"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "Vintage Obscura"
+website = "https://vintageobscura.net/"
+url = "https://radio.vintageobscura.net/stream"
+mpc_load_option = "ADD"
+
+[[config.radios]]
+name = "Worldwide FM"
+website = "https://worldwidefm.net/"
+url = "https://worldwidefm.out.airtime.pro/worldwidefm_b"
+mpc_load_option = "ADD"
+
+[[config.radios]]
+name = "Psychedelicized"
+website = "https://psychedelicized.com/"
+url = "https://streaming.live365.com/a37043"
+mpc_load_option = "ADD"
+
+[[config.radios]]
+name = "SOMA - Groove Salad ambient/downtempo"
+website = "https://somafm.com/"
+url = "https://somafm.com/groovesalad256.pls"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "SOMA - Mission Control ambient space"
+website = "https://somafm.com/"
+url = "https://somafm.com/missioncontrol.pls"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "SOMA - The Trip prog house/trance"
+website = "https://somafm.com/"
+url = "https://somafm.com/thetrip.pls"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "SOMA - Beat Blender deep house downtempo"
+website = "https://somafm.com/"
+url = "https://somafm.com/beatblender.pls"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "SOMA - Dub Step"
+website = "https://somafm.com/"
+url = "https://somafm.com/dubstep256.pls"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "SOMA - Defcon"
+website = "https://somafm.com/"
+url = "https://somafm.com/defcon256.pls"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "SOMA - Deep Space deep ambient electro/experimental"
+website = "https://somafm.com/"
+url = "https://somafm.com/deepspaceone.pls"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "SOMA - Thistle Radio Celtic"
+website = "https://somafm.com/"
+url = "https://somafm.com/thistle.pls"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "SOMA - Fluid instr. hip hop liquid trap"
+website = "https://somafm.com/"
+url = "https://somafm.com/fluid.pls"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "Bluemars"
+website = "http://echoesofbluemars.org/"
+url = "http://streams.echoesofbluemars.org:8000/bluemars.m3u"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "Cryosleep"
+website = "http://echoesofbluemars.org/"
+url = "http://streams.echoesofbluemars.org:8000/cryosleep.m3u"
+mpc_load_option = "LOAD"
+
+[[config.radios]]
+name = "Voices From Within"
+website = "http://echoesofbluemars.org/"
+url = "http://streams.echoesofbluemars.org:8000/voicesfromwithin.m3u"
+mpc_load_option = "LOAD"
diff --git a/src/config.rs b/src/config.rs
@@ -0,0 +1,41 @@
+use crate::*;
+use serde::Deserialize;
+use std::fs;
+
+#[derive(Deserialize)]
+pub struct ConfigData {
+ pub config: Config,
+}
+
+#[derive(Deserialize)]
+pub struct Config {
+ pub radios: Vec<Item>,
+ pub player: PlaybackOptions,
+}
+
+#[derive(Deserialize)]
+pub struct Item {
+ pub name: String,
+ pub website: String,
+ pub url: String,
+ pub mpc_load_option: Option<MpcLoadOptions>,
+}
+
+pub fn read_config() -> ConfigData {
+ let filename = "config.toml";
+ let contents = match fs::read_to_string(filename) {
+ Ok(c) => c,
+ Err(_) => {
+ eprintln!("Could not read file `{}`", filename);
+ exit(1);
+ }
+ };
+ let data: ConfigData = match toml::from_str(&contents) {
+ Ok(d) => d,
+ Err(_) => {
+ eprintln!("Unable to load data from `{}`", filename);
+ exit(1);
+ }
+ };
+ return data;
+}
diff --git a/src/main.rs b/src/main.rs
@@ -0,0 +1,24 @@
+mod radio;
+mod screen;
+mod tunein;
+mod config;
+use radio::*;
+use std::process::exit;
+use tunein::*;
+fn main() {
+ let data = config::read_config();
+ let choices: Vec<String> = data
+ .config
+ .radios
+ .iter()
+ .map(|x| format!("{} ({})", x.name, x.website))
+ .collect();
+ let (choice_i, _) = screen::list_menu(&choices[..]);
+ let chosen_radio = &data.config.radios[choice_i];
+ let radio = baseradio::BaseRadio::new(&chosen_radio.url);
+ let opts = RadioOptions {
+ method: data.config.player,
+ load_options: chosen_radio.mpc_load_option,
+ };
+ radio.play(&opts);
+}
diff --git a/src/radio/baseradio.rs b/src/radio/baseradio.rs
@@ -0,0 +1,60 @@
+use crate::tunein::*;
+use std::process::Command;
+pub struct BaseRadio {
+ stream: String,
+}
+
+impl BaseRadio {
+ pub fn new(stream: &str) -> Self {
+ Self {
+ stream: stream.to_string(),
+ }
+ }
+}
+
+impl TuneIn for BaseRadio {
+ 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
@@ -0,0 +1 @@
+pub mod baseradio;
diff --git a/src/screen.rs b/src/screen.rs
@@ -0,0 +1,39 @@
+use std::io::{stdin, stdout, Write};
+
+pub fn clear() {
+ println!("\x1B[H\x1B[2J");
+}
+
+pub fn list_menu(list: &[String]) -> (usize, String) {
+ let mut message = None;
+ loop {
+ clear();
+ println!("== Internet Radio Player ==");
+ for (i, val) in list.iter().enumerate() {
+ println!("{} {}", i + 1, val);
+ }
+ if message.is_some() {
+ println!("Error: {}", message.unwrap());
+ }
+
+ print!("Enter number or press ^C to go back> ");
+ let _ = stdout().flush();
+
+ let mut input_text = String::new();
+ if stdin().read_line(&mut input_text).is_err() {
+ message = Some("did not input text.");
+ continue;
+ }
+ let trimmed = input_text.trim();
+
+ let index = match trimmed.parse::<usize>() {
+ Ok(n) if n > 0 && n <= list.len() => n - 1,
+ _ => {
+ message = Some("choice out of range");
+ continue;
+ }
+ };
+
+ return (index, list[index].to_string());
+ }
+}
diff --git a/src/tunein.rs b/src/tunein.rs
@@ -0,0 +1,20 @@
+use serde::Deserialize;
+#[derive(Deserialize, Clone, Copy)]
+pub enum PlaybackOptions {
+ MPC,
+ MPV,
+}
+#[derive(Deserialize, Clone, Copy)]
+pub enum MpcLoadOptions {
+ ADD,
+ LOAD,
+}
+#[derive(Clone, Copy)]
+pub struct RadioOptions {
+ pub method: PlaybackOptions,
+ pub load_options: Option<MpcLoadOptions>,
+}
+
+pub trait TuneIn {
+ fn play(&self, options: &RadioOptions);
+}