Browse and listen to thousands of radio stations across the globe right from your terminal ๐ŸŒŽ ๐Ÿ“ป ๐ŸŽตโœจ
radio rust tokio web-radio command-line-tool tui
at main 6.3 kB view raw
1use std::time::Duration; 2 3use anyhow::Error; 4use app::CurrentDisplayMode; 5use clap::{arg, builder::ValueParser, Command}; 6 7mod app; 8mod audio; 9mod browse; 10mod cfg; 11mod decoder; 12mod extract; 13mod favorites; 14mod input; 15mod interactive; 16mod music; 17mod play; 18mod player; 19mod provider; 20mod search; 21mod server; 22mod service; 23mod tags; 24mod tui; 25mod types; 26mod visualization; 27 28fn cli() -> Command<'static> { 29 const VESRION: &str = env!("CARGO_PKG_VERSION"); 30 Command::new("tunein") 31 .version(VESRION) 32 .author("Tsiry Sandratraina <tsiry.sndr@fluentci.io>") 33 .about( 34 r#" 35 ______ ____ _______ ____ 36 /_ __/_ _____ ___ / _/__ / ___/ / / _/ 37 / / / // / _ \/ -_)/ // _ \ / /__/ /___/ / 38 /_/ \_,_/_//_/\__/___/_//_/ \___/____/___/ 39 40A simple CLI to listen to radio stations"#, 41 ) 42 .arg( 43 arg!(-p --provider "The radio provider to use, can be 'tunein' or 'radiobrowser'. Default is 'tunein'").default_value("tunein") 44 ) 45 .subcommand( 46 Command::new("search") 47 .about("Search for a radio station") 48 .arg(arg!(<query> "The query to search for")), 49 ) 50 .subcommand( 51 Command::new("play") 52 .about("Play a radio station") 53 .arg(arg!(<station> "The station to play")) 54 .arg(arg!(--volume "Set the initial volume (as a percent)").default_value("100")) 55 .arg(clap::Arg::new("display-mode").long("display-mode").help("Set the display mode to start with").default_value("Spectroscope")) 56 .arg(clap::Arg::new("enable-os-media-controls").long("enable-os-media-controls").help("Should enable OS media controls?").default_value("true").value_parser(ValueParser::bool())) 57 .arg(clap::Arg::new("poll-events-every").long("poll-events-every").help("Poll for events every specified milliseconds.").default_value("16")) 58 .arg(clap::Arg::new("poll-events-every-while-paused").long("poll-events-every-while-paused").help("Poll for events every specified milliseconds while player is paused.").default_value("100")), 59 ) 60 .subcommand( 61 Command::new("browse") 62 .about("Browse radio stations") 63 .arg(arg!([category] "The category (category name or id) to browse")) 64 .arg(arg!(--offset "The offset to start from").default_value("0")) 65 .arg(arg!(--limit "The number of results to show").default_value("100")), 66 ) 67 .subcommand( 68 Command::new("server") 69 .about("Start the server") 70 .arg(arg!([port] "The port to listen on").default_value("8090")), 71 ) 72 .subcommand( 73 Command::new("service") 74 .about("Manage systemd service for tunein-cli server") 75 .subcommand( 76 Command::new("install") 77 .about("Install systemd service for tunein-cli server") 78 ) 79 .subcommand( 80 Command::new("uninstall") 81 .about("Uninstall systemd service for tunein-cli server") 82 ) 83 .subcommand( 84 Command::new("status") 85 .about("Check status of tunein-cli systemd service") 86 ) 87 ) 88} 89 90#[tokio::main] 91async fn main() -> Result<(), Error> { 92 let matches = cli().get_matches(); 93 let provider = matches.value_of("provider").unwrap().to_string(); 94 95 match matches.subcommand() { 96 Some(("search", args)) => { 97 let query = args.value_of("query").unwrap(); 98 search::exec(query, provider.as_str()).await?; 99 } 100 Some(("play", args)) => { 101 let station = args.value_of("station").unwrap(); 102 let volume = args.value_of("volume").unwrap().parse::<f32>().unwrap(); 103 let display_mode = args 104 .value_of("display-mode") 105 .unwrap() 106 .parse::<CurrentDisplayMode>() 107 .unwrap(); 108 let enable_os_media_controls = args.get_one("enable-os-media-controls").unwrap(); 109 let poll_events_every = 110 Duration::from_millis(args.value_of("poll-events-every").unwrap().parse().unwrap()); 111 let poll_events_every_while_paused = Duration::from_millis( 112 args.value_of("poll-events-every-while-paused") 113 .unwrap() 114 .parse() 115 .unwrap(), 116 ); 117 play::exec( 118 station, 119 provider.as_str(), 120 volume, 121 display_mode, 122 *enable_os_media_controls, 123 poll_events_every, 124 poll_events_every_while_paused, 125 ) 126 .await?; 127 } 128 Some(("browse", args)) => { 129 let category = args.value_of("category"); 130 let offset = args.value_of("offset").unwrap(); 131 let limit = args.value_of("limit").unwrap(); 132 browse::exec( 133 category, 134 offset.parse::<u32>()?, 135 limit.parse::<u32>()?, 136 provider.as_str(), 137 ) 138 .await?; 139 } 140 Some(("server", args)) => { 141 let port = args.value_of("port").unwrap(); 142 let port = port.parse::<u16>().unwrap(); 143 server::exec(port).await?; 144 } 145 Some(("service", sub_m)) => match sub_m.subcommand() { 146 Some(("install", _)) => service::install()?, 147 Some(("uninstall", _)) => service::uninstall()?, 148 Some(("status", _)) => service::status()?, 149 _ => { 150 println!("Invalid subcommand. Use `tunein service --help` for more information"); 151 std::process::exit(1); 152 } 153 }, 154 None => { 155 interactive::run(provider.as_str()).await?; 156 } 157 Some((other, _)) => { 158 eprintln!( 159 "Unknown subcommand '{}'. Use `tunein --help` for available commands.", 160 other 161 ); 162 std::process::exit(1); 163 } 164 } 165 166 Ok(()) 167}