songrec/
songrec.rs

1use std::sync::mpsc;
2use std::thread;
3use std::time::Duration;
4
5use crate::config::Config;
6use crate::fingerprinting::algorithm::SignatureGenerator;
7use crate::fingerprinting::communication::{recognize_song_from_signature_with_config, recognize_song_from_signature};
8use crate::audio::recorder::AudioRecorder;
9use crate::audio::processor::AudioProcessor;
10use crate::{Result, SongRecError};
11
12/// Main SongRec struct for audio recognition
13pub struct SongRec {
14    config: Config,
15}
16
17/// Result of a song recognition
18#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
19pub struct RecognitionResult {
20    pub song_name: String,
21    pub artist_name: String,
22    pub album_name: Option<String>,
23    pub track_key: String,
24    pub release_year: Option<String>,
25    pub genre: Option<String>,
26    pub recognition_timestamp: chrono::DateTime<chrono::Utc>,
27    pub raw_response: serde_json::Value,
28}
29
30/// Stream of recognition results for continuous monitoring
31pub struct RecognitionStream {
32    receiver: mpsc::Receiver<Result<RecognitionResult>>,
33    _handles: Vec<thread::JoinHandle<()>>, // Keep handles to prevent threads from being dropped
34}
35
36impl SongRec {
37    /// Create a new SongRec instance with the given configuration
38    pub fn new(config: Config) -> Self {
39        Self { config }
40    }
41
42    /// Recognize a song from an audio file
43    pub fn recognize_from_file(&self, file_path: &str) -> Result<RecognitionResult> {
44        // Generate signature from file
45        let signature = SignatureGenerator::make_signature_from_file(file_path)
46            .map_err(|e| SongRecError::FingerprintingError(e.to_string()))?;
47
48        // Recognize song from signature with config
49        let response = recognize_song_from_signature_with_config(&signature, &self.config)
50            .map_err(|e| SongRecError::NetworkError(e.to_string()))?;
51
52        // Parse response into RecognitionResult
53        self.parse_recognition_response(response)
54    }
55
56    /// Recognize a song from raw audio samples
57    pub fn recognize_from_samples(&self, samples: &[i16], sample_rate: u32) -> Result<RecognitionResult> {
58        // Create signature generator and process samples
59        let mut generator = SignatureGenerator::new();
60        
61        // Process the samples to generate a signature
62        for chunk in samples.chunks(128) {
63            generator.do_fft(chunk, sample_rate);
64        }
65
66        let signature = generator.get_signature();
67
68        // Recognize song from signature
69        let response = recognize_song_from_signature(&signature)
70            .map_err(|e| SongRecError::NetworkError(e.to_string()))?;
71
72        // Parse response into RecognitionResult
73        self.parse_recognition_response(response)
74    }
75
76    /// Start continuous recognition from the default audio device
77    pub fn start_continuous_recognition(&self) -> Result<RecognitionStream> {
78        self.start_continuous_recognition_with_device(None)
79    }
80
81    /// Start continuous recognition from a specific audio device
82    pub fn start_continuous_recognition_with_device(&self, device_name: Option<String>) -> Result<RecognitionStream> {
83        let (result_tx, result_rx) = mpsc::channel();
84        let (_control_tx, control_rx) = mpsc::channel();
85        
86        let config = self.config.clone();
87        
88        // Start audio recording thread
89        let recorder_handle = {
90            let result_tx = result_tx.clone();
91            let config_for_thread = config.clone();
92            
93            thread::spawn(move || {
94                let mut recorder = AudioRecorder::new(config_for_thread.clone());
95                
96                match recorder.start_recording(device_name, control_rx) {
97                    Ok(sample_rx) => {
98                        // Process audio samples
99                        let mut processor = AudioProcessor::with_config(config_for_thread.clone());
100                        
101                        for samples in sample_rx {
102                            match processor.process_samples(&samples) {
103                                Ok(Some(signature)) => {
104                                    // Try to recognize the signature with config
105                                    match recognize_song_from_signature_with_config(&signature, &config_for_thread) {
106                                        Ok(response) => {
107                                            // Parse and send result
108                                            match SongRec::parse_recognition_response_static(response) {
109                                                Ok(result) => {
110                                                    if result_tx.send(Ok(result)).is_err() {
111                                                        break; // Receiver dropped, stop processing
112                                                    }
113                                                },
114                                                Err(e) => {
115                                                    if result_tx.send(Err(e)).is_err() {
116                                                        break;
117                                                    }
118                                                }
119                                            }
120                                        },
121                                        Err(e) => {
122                                            let error = SongRecError::NetworkError(e.to_string());
123                                            if result_tx.send(Err(error)).is_err() {
124                                                break;
125                                            }
126                                        }
127                                    }
128                                },
129                                Ok(None) => {
130                                    // Not enough samples yet, continue
131                                },
132                                Err(e) => {
133                                    let error = SongRecError::FingerprintingError(e.to_string());
134                                    if result_tx.send(Err(error)).is_err() {
135                                        break;
136                                    }
137                                }
138                            }
139                        }
140                    },
141                    Err(e) => {
142                        let error = SongRecError::AudioError(e.to_string());
143                        let _ = result_tx.send(Err(error));
144                    }
145                }
146            })
147        };
148
149        Ok(RecognitionStream {
150            receiver: result_rx,
151            _handles: vec![recorder_handle],
152        })
153    }
154
155    /// Parse a recognition response from the API into a RecognitionResult
156    fn parse_recognition_response(&self, response: serde_json::Value) -> Result<RecognitionResult> {
157        Self::parse_recognition_response_static(response)
158    }
159
160    /// Static version of parse_recognition_response for use in threads
161    fn parse_recognition_response_static(response: serde_json::Value) -> Result<RecognitionResult> {
162        // First check if we have any matches
163        let matches = response.get("matches")
164            .and_then(|m| m.as_array())
165            .ok_or_else(|| SongRecError::NetworkError("Invalid response format: no matches array".to_string()))?;
166            
167        if matches.is_empty() {
168            return Err(SongRecError::NetworkError("No track found in response".to_string()));
169        }
170        
171        // The track info is at the top level of the response, not inside the matches
172        let track = response.get("track")
173            .ok_or_else(|| SongRecError::NetworkError("No track found in response".to_string()))?;
174
175        // Extract song details from the track
176        let song_name = track
177            .get("title")
178            .and_then(|v| v.as_str())
179            .unwrap_or("Unknown")
180            .to_string();
181
182        let artist_name = track
183            .get("subtitle")
184            .and_then(|v| v.as_str())
185            .unwrap_or("Unknown")
186            .to_string();
187
188        let album_name = track
189            .pointer("/sections/0/metadata/0/text")
190            .and_then(|v| v.as_str())
191            .map(|s| s.to_string());
192
193        let track_key = track
194            .get("key")
195            .and_then(|v| v.as_str())
196            .unwrap_or("")
197            .to_string();
198
199        let release_year = track
200            .pointer("/sections/0/metadata")
201            .and_then(|metadata| {
202                if let Some(metadata_array) = metadata.as_array() {
203                    for item in metadata_array {
204                        if let Some(title) = item.pointer("/title").and_then(|v| v.as_str()) {
205                            if title == "Released" {
206                                return item.pointer("/text").and_then(|v| v.as_str()).map(|s| s.to_string());
207                            }
208                        }
209                    }
210                }
211                None
212            });
213
214        let genre = track
215            .pointer("/genres/primary")
216            .and_then(|v| v.as_str())
217            .map(|s| s.to_string());
218
219        Ok(RecognitionResult {
220            song_name,
221            artist_name,
222            album_name,
223            track_key,
224            release_year,
225            genre,
226            recognition_timestamp: chrono::Utc::now(),
227            raw_response: response,
228        })
229    }
230}
231
232impl RecognitionStream {
233    /// Get the next recognition result from the stream
234    pub fn next(&self) -> Option<Result<RecognitionResult>> {
235        self.receiver.recv().ok()
236    }
237
238    /// Try to get the next recognition result without blocking
239    pub fn try_next(&self) -> Option<Result<RecognitionResult>> {
240        self.receiver.try_recv().ok()
241    }
242
243    /// Wait for the next recognition result with a timeout
244    pub fn next_timeout(&self, timeout: Duration) -> Option<Result<RecognitionResult>> {
245        self.receiver.recv_timeout(timeout).ok()
246    }
247}
248
249impl Iterator for RecognitionStream {
250    type Item = Result<RecognitionResult>;
251
252    fn next(&mut self) -> Option<Self::Item> {
253        RecognitionStream::next(self)
254    }
255}