music_player_scanner/
lib.rs1#[cfg(test)]
2mod tests;
3
4use anyhow::Error;
5use futures::future::BoxFuture;
6use music_player_storage::{searcher::Searcher, Database};
7use music_player_types::types::{Album, Artist, Song};
8use std::{io::Write, thread};
9
10use lofty::{AudioFile, Probe, Tag};
11use music_player_settings::{get_application_directory, read_settings, Settings};
12use walkdir::WalkDir;
13
14pub async fn scan_directory(
15 save: impl for<'a> Fn(&'a Song, &'a Database) -> BoxFuture<'a, ()> + 'static,
16 db: &Database,
17 searcher: &Searcher,
18) -> Result<Vec<Song>, Error> {
19 let config = read_settings().unwrap();
20 let settings = config.try_deserialize::<Settings>().unwrap();
21
22 let mut songs: Vec<Song> = Vec::new();
23
24 let supported_formats = vec![
25 "audio/mpeg",
26 "audio/mp4",
27 "audio/m4a",
29 "audio/aac",
30 ];
31
32 let total = WalkDir::new(&settings.music_directory)
33 .follow_links(true)
34 .into_iter()
35 .filter_map(|e| e.ok())
36 .count();
37
38 let cloned_db = db.clone();
39 let (done_tx, done_rx) = std::sync::mpsc::channel::<()>();
40 let (tx, rx) = std::sync::mpsc::channel::<(Album, Song, Artist, usize)>();
41 let searcher = searcher.clone();
42
43 thread::spawn(move || {
44 while let Ok((album, track, artist, index)) = rx.recv() {
45 let id = format!("{:x}", md5::compute(track.uri.as_ref().unwrap()));
46 match searcher.insert_artist(artist) {
47 Ok(_) => {}
48 Err(e) => println!("Error inserting artist: {}", e),
49 };
50 match searcher.insert_album(album) {
51 Ok(_) => {}
52 Err(e) => println!("Error inserting album: {}", e),
53 };
54 match searcher.insert_song(track, &id) {
55 Ok(_) => {}
56 Err(e) => println!("Error inserting song: {}", e),
57 };
58
59 if index + 1 == total {
60 done_tx.send(()).unwrap();
61 break;
62 }
63 }
64 });
65
66 for (index, entry) in WalkDir::new(&settings.music_directory)
67 .follow_links(true)
68 .into_iter()
69 .filter_map(|e| e.ok())
70 .enumerate()
71 {
72 let path = format!("{}", entry.path().display());
73 let guess = mime_guess::from_path(&path);
74 let mime = guess.first_or_octet_stream();
75
76 if supported_formats.iter().any(|x| x.to_owned() == mime) {
77 match Probe::open(&path)
78 .expect("ERROR: Bad path provided!")
79 .read()
80 {
81 Ok(tagged_file) => {
82 let tag = match tagged_file.primary_tag() {
83 Some(primary_tag) => primary_tag,
84 None => tagged_file.first_tag().expect("ERROR: No tags found!"),
88 };
89
90 let properties = tagged_file.properties();
91 let mut song: Song = tag.try_into().unwrap();
92 song.with_properties(properties);
93 song.uri = Some(path.clone());
94
95 let album = song.album.clone();
96 let cover = extract_and_save_album_cover(tag, &album);
97 song.cover = cover.clone();
98 save(&song, &cloned_db).await;
99 songs.push(song);
100
101 let mut track: Song = tag.try_into().unwrap();
102 track.uri = Some(path.clone());
103 track.cover = cover.clone();
104 track.with_properties(properties);
105
106 let artist: Artist = tag.try_into().unwrap();
107 let mut album: Album = tag.try_into().unwrap();
108 album.cover = cover.clone();
109
110 tx.send((album, track, artist, index)).unwrap();
111 }
112 Err(e) => println!("ERROR: {}, {}", e, path),
113 }
114 }
115 }
116 done_rx.recv().unwrap();
117 Ok(songs)
118}
119
120fn extract_and_save_album_cover(tag: &Tag, album: &str) -> Option<String> {
121 let pictures = tag.pictures();
122 if pictures.len() > 0 {
123 let covers_path = format!("{}/covers", get_application_directory());
124 let picture = &pictures[0];
125 let album = md5::compute(album.as_bytes());
126 let filename = format!("{}/{:x}", covers_path, album);
127 match picture.mime_type() {
128 lofty::MimeType::Jpeg => {
129 let filename = format!("{}.jpg", filename);
130 let mut file = std::fs::File::create(filename).unwrap();
131 file.write_all(picture.data()).unwrap();
132 Some(format!("{:x}.jpg", album))
133 }
134 lofty::MimeType::Png => {
135 let filename = format!("{}.png", filename);
136 let mut file = std::fs::File::create(filename).unwrap();
137 file.write_all(picture.data()).unwrap();
138 Some(format!("{:x}.png", album))
139 }
140 _ => {
141 println!("Unsupported picture format");
142 None
143 }
144 }
145 } else {
146 None
147 }
148}