main.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. extern crate crypto;
  2. extern crate env_logger;
  3. extern crate futures;
  4. extern crate getopts;
  5. extern crate librespot;
  6. #[macro_use]
  7. extern crate log;
  8. extern crate rpassword;
  9. extern crate tokio_core;
  10. extern crate tokio_io;
  11. extern crate tokio_signal;
  12. use crypto::digest::Digest;
  13. use crypto::sha1::Sha1;
  14. use env_logger::LogBuilder;
  15. use futures::{Async, Future, Poll, Stream};
  16. use futures::sync::mpsc::UnboundedReceiver;
  17. use std::env;
  18. use std::io::{self, stderr, Write};
  19. use std::mem;
  20. use std::path::PathBuf;
  21. use std::process::exit;
  22. use std::str::FromStr;
  23. use tokio_core::reactor::{Core, Handle};
  24. use tokio_io::IoStream;
  25. use librespot::core::authentication::{get_credentials, Credentials};
  26. use librespot::core::cache::Cache;
  27. use librespot::core::config::{ConnectConfig, DeviceType, SessionConfig};
  28. use librespot::core::session::Session;
  29. use librespot::core::version;
  30. use librespot::connect::discovery::{discovery, DiscoveryStream};
  31. use librespot::connect::spirc::{Spirc, SpircTask};
  32. use librespot::playback::audio_backend::{self, Sink, BACKENDS};
  33. use librespot::playback::config::{Bitrate, PlayerConfig};
  34. use librespot::playback::mixer::{self, Mixer};
  35. use librespot::playback::player::{Player, PlayerEvent};
  36. mod player_event_handler;
  37. use player_event_handler::run_program_on_events;
  38. fn device_id(name: &str) -> String {
  39. let mut h = Sha1::new();
  40. h.input_str(name);
  41. h.result_str()
  42. }
  43. fn usage(program: &str, opts: &getopts::Options) -> String {
  44. let brief = format!("Usage: {} [options]", program);
  45. opts.usage(&brief)
  46. }
  47. fn setup_logging(verbose: bool) {
  48. let mut builder = LogBuilder::new();
  49. match env::var("RUST_LOG") {
  50. Ok(config) => {
  51. builder.parse(&config);
  52. builder.init().unwrap();
  53. if verbose {
  54. warn!("`--verbose` flag overidden by `RUST_LOG` environment variable");
  55. }
  56. }
  57. Err(_) => {
  58. if verbose {
  59. builder.parse("mdns=info,librespot=trace");
  60. } else {
  61. builder.parse("mdns=info,librespot=info");
  62. }
  63. builder.init().unwrap();
  64. }
  65. }
  66. }
  67. fn list_backends() {
  68. println!("Available Backends : ");
  69. for (&(name, _), idx) in BACKENDS.iter().zip(0..) {
  70. if idx == 0 {
  71. println!("- {} (default)", name);
  72. } else {
  73. println!("- {}", name);
  74. }
  75. }
  76. }
  77. #[derive(Clone)]
  78. struct Setup {
  79. backend: fn(Option<String>) -> Box<Sink>,
  80. device: Option<String>,
  81. mixer: fn() -> Box<Mixer>,
  82. cache: Option<Cache>,
  83. player_config: PlayerConfig,
  84. session_config: SessionConfig,
  85. connect_config: ConnectConfig,
  86. credentials: Option<Credentials>,
  87. enable_discovery: bool,
  88. zeroconf_port: u16,
  89. player_event_program: Option<String>,
  90. }
  91. fn setup(args: &[String]) -> Setup {
  92. let mut opts = getopts::Options::new();
  93. opts.optopt(
  94. "c",
  95. "cache",
  96. "Path to a directory where files will be cached.",
  97. "CACHE",
  98. ).optflag(
  99. "",
  100. "disable-audio-cache",
  101. "Disable caching of the audio data.",
  102. )
  103. .reqopt("n", "name", "Device name", "NAME")
  104. .optopt("", "device-type", "Displayed device type", "DEVICE_TYPE")
  105. .optopt(
  106. "b",
  107. "bitrate",
  108. "Bitrate (96, 160 or 320). Defaults to 160",
  109. "BITRATE",
  110. )
  111. .optopt(
  112. "",
  113. "onevent",
  114. "Run PROGRAM when playback is about to begin.",
  115. "PROGRAM",
  116. )
  117. .optflag("v", "verbose", "Enable verbose output")
  118. .optopt("u", "username", "Username to sign in with", "USERNAME")
  119. .optopt("p", "password", "Password", "PASSWORD")
  120. .optflag("", "disable-discovery", "Disable discovery mode")
  121. .optopt(
  122. "",
  123. "backend",
  124. "Audio backend to use. Use '?' to list options",
  125. "BACKEND",
  126. )
  127. .optopt(
  128. "",
  129. "device",
  130. "Audio device to use. Use '?' to list options if using portaudio",
  131. "DEVICE",
  132. )
  133. .optopt("", "mixer", "Mixer to use", "MIXER")
  134. .optopt(
  135. "",
  136. "initial-volume",
  137. "Initial volume in %, once connected (must be from 0 to 100)",
  138. "VOLUME",
  139. )
  140. .optopt(
  141. "",
  142. "zeroconf-port",
  143. "The port the internal server advertised over zeroconf uses.",
  144. "ZEROCONF_PORT",
  145. )
  146. .optflag(
  147. "",
  148. "enable-volume-normalisation",
  149. "Play all tracks at the same volume",
  150. )
  151. .optopt(
  152. "",
  153. "normalisation-pregain",
  154. "Pregain (dB) applied by volume normalisation",
  155. "PREGAIN",
  156. );
  157. let matches = match opts.parse(&args[1..]) {
  158. Ok(m) => m,
  159. Err(f) => {
  160. writeln!(
  161. stderr(),
  162. "error: {}\n{}",
  163. f.to_string(),
  164. usage(&args[0], &opts)
  165. ).unwrap();
  166. exit(1);
  167. }
  168. };
  169. let verbose = matches.opt_present("verbose");
  170. setup_logging(verbose);
  171. info!(
  172. "librespot {} ({}). Built on {}. Build ID: {}",
  173. version::short_sha(),
  174. version::commit_date(),
  175. version::short_now(),
  176. version::build_id()
  177. );
  178. let backend_name = matches.opt_str("backend");
  179. if backend_name == Some("?".into()) {
  180. list_backends();
  181. exit(0);
  182. }
  183. let backend = audio_backend::find(backend_name).expect("Invalid backend");
  184. let device = matches.opt_str("device");
  185. let mixer_name = matches.opt_str("mixer");
  186. let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
  187. let initial_volume = matches
  188. .opt_str("initial-volume")
  189. .map(|volume| {
  190. let volume = volume.parse::<i32>().unwrap();
  191. if volume < 0 || volume > 100 {
  192. panic!("Initial volume must be in the range 0-100");
  193. }
  194. volume * 0xFFFF / 100
  195. })
  196. .unwrap_or(0x8000);
  197. let zeroconf_port = matches
  198. .opt_str("zeroconf-port")
  199. .map(|port| port.parse::<u16>().unwrap())
  200. .unwrap_or(0);
  201. let name = matches.opt_str("name").unwrap();
  202. let use_audio_cache = !matches.opt_present("disable-audio-cache");
  203. let cache = matches
  204. .opt_str("c")
  205. .map(|cache_location| Cache::new(PathBuf::from(cache_location), use_audio_cache));
  206. let credentials = {
  207. let cached_credentials = cache.as_ref().and_then(Cache::credentials);
  208. let password = |username: &String| -> String {
  209. write!(stderr(), "Password for {}: ", username).unwrap();
  210. stderr().flush().unwrap();
  211. rpassword::read_password().unwrap()
  212. };
  213. get_credentials(
  214. matches.opt_str("username"),
  215. matches.opt_str("password"),
  216. cached_credentials,
  217. password,
  218. )
  219. };
  220. let session_config = {
  221. let device_id = device_id(&name);
  222. SessionConfig {
  223. user_agent: version::version_string(),
  224. device_id: device_id,
  225. }
  226. };
  227. let player_config = {
  228. let bitrate = matches
  229. .opt_str("b")
  230. .as_ref()
  231. .map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
  232. .unwrap_or(Bitrate::default());
  233. PlayerConfig {
  234. bitrate: bitrate,
  235. normalisation: matches.opt_present("enable-volume-normalisation"),
  236. normalisation_pregain: matches
  237. .opt_str("normalisation-pregain")
  238. .map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
  239. .unwrap_or(PlayerConfig::default().normalisation_pregain),
  240. }
  241. };
  242. let connect_config = {
  243. let device_type = matches
  244. .opt_str("device-type")
  245. .as_ref()
  246. .map(|device_type| DeviceType::from_str(device_type).expect("Invalid device type"))
  247. .unwrap_or(DeviceType::default());
  248. ConnectConfig {
  249. name: name,
  250. device_type: device_type,
  251. volume: initial_volume,
  252. }
  253. };
  254. let enable_discovery = !matches.opt_present("disable-discovery");
  255. Setup {
  256. backend: backend,
  257. cache: cache,
  258. session_config: session_config,
  259. player_config: player_config,
  260. connect_config: connect_config,
  261. credentials: credentials,
  262. device: device,
  263. enable_discovery: enable_discovery,
  264. zeroconf_port: zeroconf_port,
  265. mixer: mixer,
  266. player_event_program: matches.opt_str("onevent"),
  267. }
  268. }
  269. struct Main {
  270. cache: Option<Cache>,
  271. player_config: PlayerConfig,
  272. session_config: SessionConfig,
  273. connect_config: ConnectConfig,
  274. backend: fn(Option<String>) -> Box<Sink>,
  275. device: Option<String>,
  276. mixer: fn() -> Box<Mixer>,
  277. handle: Handle,
  278. discovery: Option<DiscoveryStream>,
  279. signal: IoStream<()>,
  280. spirc: Option<Spirc>,
  281. spirc_task: Option<SpircTask>,
  282. connect: Box<Future<Item = Session, Error = io::Error>>,
  283. shutdown: bool,
  284. player_event_channel: Option<UnboundedReceiver<PlayerEvent>>,
  285. player_event_program: Option<String>,
  286. }
  287. impl Main {
  288. fn new(handle: Handle, setup: Setup) -> Main {
  289. let mut task = Main {
  290. handle: handle.clone(),
  291. cache: setup.cache,
  292. session_config: setup.session_config,
  293. player_config: setup.player_config,
  294. connect_config: setup.connect_config,
  295. backend: setup.backend,
  296. device: setup.device,
  297. mixer: setup.mixer,
  298. connect: Box::new(futures::future::empty()),
  299. discovery: None,
  300. spirc: None,
  301. spirc_task: None,
  302. shutdown: false,
  303. signal: Box::new(tokio_signal::ctrl_c(&handle).flatten_stream()),
  304. player_event_channel: None,
  305. player_event_program: setup.player_event_program,
  306. };
  307. if setup.enable_discovery {
  308. let config = task.connect_config.clone();
  309. let device_id = task.session_config.device_id.clone();
  310. task.discovery = Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap());
  311. }
  312. if let Some(credentials) = setup.credentials {
  313. task.credentials(credentials);
  314. }
  315. task
  316. }
  317. fn credentials(&mut self, credentials: Credentials) {
  318. let config = self.session_config.clone();
  319. let handle = self.handle.clone();
  320. let connection = Session::connect(config, credentials, self.cache.clone(), handle);
  321. self.connect = connection;
  322. self.spirc = None;
  323. let task = mem::replace(&mut self.spirc_task, None);
  324. if let Some(task) = task {
  325. self.handle.spawn(task);
  326. }
  327. }
  328. }
  329. impl Future for Main {
  330. type Item = ();
  331. type Error = ();
  332. fn poll(&mut self) -> Poll<(), ()> {
  333. loop {
  334. let mut progress = false;
  335. if let Some(Async::Ready(Some(creds))) = self.discovery.as_mut().map(|d| d.poll().unwrap()) {
  336. if let Some(ref spirc) = self.spirc {
  337. spirc.shutdown();
  338. }
  339. self.credentials(creds);
  340. progress = true;
  341. }
  342. if let Async::Ready(session) = self.connect.poll().unwrap() {
  343. self.connect = Box::new(futures::future::empty());
  344. let device = self.device.clone();
  345. let mixer = (self.mixer)();
  346. let player_config = self.player_config.clone();
  347. let connect_config = self.connect_config.clone();
  348. let audio_filter = mixer.get_audio_filter();
  349. let backend = self.backend;
  350. let (player, event_channel) =
  351. Player::new(player_config, session.clone(), audio_filter, move || {
  352. (backend)(device)
  353. });
  354. let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer);
  355. self.spirc = Some(spirc);
  356. self.spirc_task = Some(spirc_task);
  357. self.player_event_channel = Some(event_channel);
  358. progress = true;
  359. }
  360. if let Async::Ready(Some(())) = self.signal.poll().unwrap() {
  361. if !self.shutdown {
  362. if let Some(ref spirc) = self.spirc {
  363. spirc.shutdown();
  364. }
  365. self.shutdown = true;
  366. } else {
  367. return Ok(Async::Ready(()));
  368. }
  369. progress = true;
  370. }
  371. if let Some(ref mut spirc_task) = self.spirc_task {
  372. if let Async::Ready(()) = spirc_task.poll().unwrap() {
  373. if self.shutdown {
  374. return Ok(Async::Ready(()));
  375. } else {
  376. panic!("Spirc shut down unexpectedly");
  377. }
  378. }
  379. }
  380. if let Some(ref mut player_event_channel) = self.player_event_channel {
  381. if let Async::Ready(Some(event)) = player_event_channel.poll().unwrap() {
  382. if let Some(ref program) = self.player_event_program {
  383. run_program_on_events(event, program);
  384. }
  385. }
  386. }
  387. if !progress {
  388. return Ok(Async::NotReady);
  389. }
  390. }
  391. }
  392. }
  393. fn main() {
  394. if env::var("RUST_BACKTRACE").is_err() {
  395. env::set_var("RUST_BACKTRACE", "full")
  396. }
  397. let mut core = Core::new().unwrap();
  398. let handle = core.handle();
  399. let args: Vec<String> = std::env::args().collect();
  400. core.run(Main::new(handle, setup(&args))).unwrap()
  401. }