Browse and listen to thousands of radio stations across the globe right from your terminal ๐ ๐ป ๐ตโจ
radio
rust
tokio
web-radio
command-line-tool
tui
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}