use log::{error, info, warn};
use std::error::Error;
use std::sync::{Arc, Mutex};
use chrono::Local;
use gettextrs::gettext;
#[cfg(all(target_os = "linux", feature = "mpris"))]
use mpris_server::PlaybackStatus;
use crate::core::http_task::http_task;
use crate::core::microphone_thread::microphone_thread;
use crate::core::processing_thread::processing_thread;
use crate::core::thread_messages::{
spawn_big_thread, GUIMessage, MicrophoneMessage, ProcessingMessage,
};
use crate::core::preferences::{Preferences, PreferencesInterface};
#[cfg(all(target_os = "linux", feature = "mpris"))]
use crate::plugins::mpris_player::{get_player, update_song};
use crate::utils::csv_song_history::SongHistoryRecord;
pub enum CLIOutputType {
SongName,
JSON,
CSV,
}
pub struct CLIParameters {
pub enable_mpris: bool,
pub list_devices: bool,
pub recognize_once: bool,
pub audio_device: Option<String>,
pub request_interval: u64,
pub input_file: Option<String>,
pub output_type: CLIOutputType,
}
pub fn cli_main(parameters: CLIParameters) -> Result<(), Box<dyn Error>> {
let (gui_tx, gui_rx) = async_channel::unbounded();
let (microphone_tx, microphone_rx) = async_channel::unbounded();
let (processing_tx, processing_rx) = async_channel::unbounded();
let (http_tx, http_rx) = async_channel::unbounded();
let gui_tx_2 = gui_tx.clone();
let gui_tx_3 = gui_tx.clone();
let processing_tx_2 = processing_tx.clone();
let microphone_tx_2 = microphone_tx.clone();
let microphone_tx_3 = microphone_tx.clone();
let preferences_interface = Arc::new(Mutex::new(PreferencesInterface {
preferences_file_path: None,
preferences: Preferences::with_interval(parameters.request_interval),
}));
spawn_big_thread(move || {
microphone_thread(
microphone_rx,
microphone_tx_2,
processing_tx_2,
gui_tx_2,
preferences_interface,
true,
);
});
spawn_big_thread(move || {
processing_thread(processing_rx, http_tx, gui_tx_3);
});
glib::spawn_future_local(http_task(http_rx, gui_tx, microphone_tx_3));
let main_loop = glib::MainLoop::new(None, false);
let loop_inner = main_loop.clone();
glib::spawn_future_local(async move {
let do_recognize_once = parameters.recognize_once || parameters.input_file.is_some();
#[cfg(all(target_os = "linux", feature = "mpris"))]
let mpris_obj = {
let do_enable_mpris = parameters.enable_mpris && !do_recognize_once;
if do_enable_mpris {
get_player(false).await
} else {
None
}
};
#[cfg(all(target_os = "linux", feature = "mpris"))]
let mut last_cover_path = None;
let mut last_track: Option<String> = None;
let audio_dev_name = parameters.audio_device.as_ref().map(|dev| dev.to_string());
let input_file_name = parameters.input_file.as_ref().map(|dev| dev.to_string());
if let Some(ref filename) = parameters.input_file {
processing_tx
.try_send(ProcessingMessage::ProcessAudioFile(filename.to_string()))
.unwrap();
}
let mut csv_writer = csv::Writer::from_writer(std::io::stdout());
while let Ok(gui_message) = gui_rx.recv().await {
match gui_message {
GUIMessage::DevicesList(device_names) => {
if input_file_name.is_some() {
continue;
}
for device in device_names.iter() {
info!(
"{} {} ({})",
gettext("Available device:"),
device.inner_name,
device.display_name
);
}
if parameters.list_devices {
loop_inner.quit();
break;
}
let dev_name = if let Some(dev) = &audio_dev_name {
let mut found: bool = false;
for device in device_names.iter() {
if &device.inner_name == dev || &device.display_name == dev {
found = true;
break;
}
}
if !found {
error!("{}", gettext("Exiting: audio device not found"));
break;
}
dev
} else {
if device_names.is_empty() {
error!("{}", gettext("Exiting: no audio devices found!"));
break;
}
&device_names[0].inner_name
};
info!("{}", gettext("Using device %s").replace("%s", dev_name));
microphone_tx
.try_send(MicrophoneMessage::MicrophoneRecordStart(
dev_name.to_owned(),
))
.unwrap();
}
GUIMessage::NetworkStatus(reachable) => {
#[cfg(all(target_os = "linux", feature = "mpris"))]
{
let mpris_status = if reachable {
PlaybackStatus::Playing
} else {
PlaybackStatus::Paused
};
if let Some(ref player) = mpris_obj {
if let Err(error) = player.set_playback_status(mpris_status).await {
error!("Could not set MPRIS playback status: {:?}", error);
}
}
}
if !reachable {
if input_file_name.is_some() {
error!("{}", gettext("Error: Network unreachable"));
break;
} else {
warn!("{}", gettext("Warning: Network unreachable"));
}
}
}
GUIMessage::ErrorMessage(string) => {
if string != gettext("No match for this song") || input_file_name.is_some() {
error!("{} {}", gettext("Error:"), string);
}
if input_file_name.is_some() {
break;
}
}
GUIMessage::MicrophoneRecording if !do_recognize_once => {
info!("{}", gettext("Recording started!"));
}
GUIMessage::SongRecognized(message) => {
let track_key = Some(message.track_key.clone());
let song_name = format!("{} - {}", message.artist_name, message.song_name);
if last_track != track_key {
#[cfg(all(target_os = "linux", feature = "mpris"))]
if let Some(ref player) = mpris_obj {
update_song(player, &message, &mut last_cover_path).await;
}
last_track = track_key;
match parameters.output_type {
CLIOutputType::JSON => {
println!("{}", message.shazam_json);
}
CLIOutputType::CSV => {
csv_writer
.serialize(SongHistoryRecord {
song_name,
album: Some(message.album_name.unwrap_or_default()),
track_key: Some(message.track_key),
release_year: Some(
message.release_year.unwrap_or_default(),
),
genre: Some(message.genre.unwrap_or_default()),
recognition_date: Local::now().format("%c").to_string(),
})
.unwrap();
csv_writer.flush().unwrap();
}
CLIOutputType::SongName => {
println!("{}", song_name);
}
};
}
if do_recognize_once {
break;
}
}
_ => {}
}
}
gui_rx.close();
loop_inner.quit();
});
main_loop.run();
Ok(())
}