radio-rs

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

commit 47bb2484a81e610a81ac977cbb4a19ba5c081871
Author: Alex Balgavy <alex@balgavy.eu>
Date:   Sat,  4 Jun 2022 20:55:00 +0200

Initial working

Diffstat:
A.gitignore | 1+
ACargo.lock | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACargo.toml | 11+++++++++++
Aconfig.toml | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/config.rs | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/main.rs | 24++++++++++++++++++++++++
Asrc/radio/baseradio.rs | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/radio/mod.rs | 1+
Asrc/screen.rs | 39+++++++++++++++++++++++++++++++++++++++
Asrc/tunein.rs | 20++++++++++++++++++++
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); +}