From 4606a117f6f2e821e3057a96d182e4984797b0ff Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 2 Mar 2023 00:19:57 -0500 Subject: [PATCH] Parse id3 tags from mpeg files --- music-player/client/src/blocks/track.ts | 14 ++++-- music-player/client/src/main.ts | 2 + music-player/server/Cargo.lock | 46 ++++++++++++++++++ music-player/server/Cargo.toml | 3 +- music-player/server/src/music_scanner.rs | 62 +++++++++++++++++------- 5 files changed, 103 insertions(+), 24 deletions(-) diff --git a/music-player/client/src/blocks/track.ts b/music-player/client/src/blocks/track.ts index 46a742e..b2b9e36 100644 --- a/music-player/client/src/blocks/track.ts +++ b/music-player/client/src/blocks/track.ts @@ -124,10 +124,14 @@ export class TrackCard extends HTMLElement { trackName.name = this["name"]; container.appendChild(trackName); } - this["length"] && container.appendChild(document.createTextNode("1:23")); - this["album"] && - container.appendChild(document.createTextNode("Shatter Me")); - this["artist"] && - container.appendChild(document.createTextNode("Lindsey Stirling")); + if (this["length"]) { + container.appendChild(document.createTextNode(this["length"])); + } + if (this["album"]) { + container.appendChild(document.createTextNode(this["album"])); + } + if (this["artist"]) { + container.appendChild(document.createTextNode(this["artist"])); + } } } diff --git a/music-player/client/src/main.ts b/music-player/client/src/main.ts index ec15f2e..f0ab519 100644 --- a/music-player/client/src/main.ts +++ b/music-player/client/src/main.ts @@ -25,6 +25,8 @@ const updateTrackList = (tracks: TrackInfo[]) => { let track_formats = _.map(tracks, (info) => { let card: TrackCard = document.createElement("track-card"); card.name = info.name || null; + card.album = info.album || null; + card.artist = info.artist || null; return card; }); _.map(track_formats, (trackCard) => { diff --git a/music-player/server/Cargo.lock b/music-player/server/Cargo.lock index 8cfe79c..4682b1f 100644 --- a/music-player/server/Cargo.lock +++ b/music-player/server/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.7.6" @@ -77,6 +83,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -118,6 +133,16 @@ dependencies = [ "instant", ] +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flow" version = "0.1.0" @@ -329,6 +354,17 @@ dependencies = [ "want", ] +[[package]] +name = "id3" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d7a833474b30425eb64132d1f9b727b4e39537418bcc3288497c8d2f5c8948" +dependencies = [ + "bitflags", + "byteorder", + "flate2", +] + [[package]] name = "idna" version = "0.3.0" @@ -421,6 +457,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.5" @@ -456,6 +501,7 @@ name = "music-player" version = "0.1.0" dependencies = [ "flow", + "id3", "mime", "mime_guess", "rusqlite", diff --git a/music-player/server/Cargo.toml b/music-player/server/Cargo.toml index 655a674..c849ff4 100644 --- a/music-player/server/Cargo.toml +++ b/music-player/server/Cargo.toml @@ -7,8 +7,9 @@ edition = "2021" [dependencies] flow = { path = "../../flow" } -mime = { version = "0.3" } +id3 = { version = "1.6" } mime_guess = { version = "2.0" } +mime = { version = "0.3" } rusqlite = { version = "0.28" } serde = { version = "1.0", features = ["derive"] } thiserror = { version = "1.0" } diff --git a/music-player/server/src/music_scanner.rs b/music-player/server/src/music_scanner.rs index da7f1c4..ac92aa0 100644 --- a/music-player/server/src/music_scanner.rs +++ b/music-player/server/src/music_scanner.rs @@ -1,4 +1,5 @@ use crate::audio::{TrackId, TrackInfo}; +use id3::{Tag, TagLike}; use std::{ fs::{DirEntry, ReadDir}, path::{Path, PathBuf}, @@ -7,10 +8,12 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum ScannerError { - #[error("Cannot scan {0}")] - CannotScan(PathBuf), - #[error("Not found {0}")] - NotFound(PathBuf), + #[error("Cannot scan file")] + CannotScan, + #[error("File not found")] + FileNotFound, + #[error("Tag not found")] + TagNotFound, #[error("IO error {0}")] IO(std::io::Error), } @@ -21,6 +24,19 @@ impl From for ScannerError { } } +impl From for ScannerError { + fn from(err: id3::Error) -> ScannerError { + match err.kind { + id3::ErrorKind::Io(err) => ScannerError::IO(err), + id3::ErrorKind::StringDecoding(_) => ScannerError::CannotScan, + id3::ErrorKind::NoTag => ScannerError::TagNotFound, + id3::ErrorKind::Parsing => ScannerError::CannotScan, + id3::ErrorKind::InvalidInput => ScannerError::CannotScan, + id3::ErrorKind::UnsupportedFeature => ScannerError::CannotScan, + } + } +} + pub trait MusicScanner: Send { fn scan<'a>(&'a self) -> Box> + 'a>; } @@ -49,19 +65,10 @@ impl FileIterator { ); let mimetype = mime_guess::from_path(path.clone()) .first() - .ok_or(ScannerError::CannotScan(path.clone()))?; + .ok_or(ScannerError::CannotScan)?; match (mimetype.type_(), mimetype.subtype().as_str()) { - (mime::AUDIO, "mpeg") => Ok(TrackInfo { - id: TrackId::from(path.to_str().unwrap().to_owned()), - album: None, - artist: None, - name: path - .file_stem() - .and_then(|s| s.to_str()) - .map(|s| s.to_owned()), - track_number: None, - filetype: mimetype, - }), + (mime::AUDIO, "mpeg") => TrackInfo::scan_id3(path, mimetype), + /* (mime::AUDIO, "ogg") => Ok(TrackInfo { id: TrackId::from(path.to_str().unwrap().to_owned()), @@ -86,11 +93,30 @@ impl FileIterator { filetype: mimetype, }), */ - _ => Err(ScannerError::CannotScan(path)), + _ => Err(ScannerError::CannotScan), } } } +impl TrackInfo { + fn scan_id3(path: PathBuf, mimetype: mime::Mime) -> Result { + let tags = Tag::read_from_path(path.clone()).map_err(ScannerError::from)?; + + Ok(TrackInfo { + id: TrackId::from(path.to_str().unwrap().to_owned()), + album: tags.album().map(|s| s.to_owned()), + artist: tags.artist().map(|s| s.to_owned()), + name: tags.title().map(|s| s.to_owned()).or_else(|| { + path.file_stem() + .and_then(|s| s.to_str()) + .map(|s| s.to_owned()) + }), + track_number: None, + filetype: mimetype, + }) + } +} + enum EntryInfo { Dir(PathBuf), File(PathBuf), @@ -136,7 +162,7 @@ impl Iterator for FileIterator { } Err(err) => { if err.kind() == std::io::ErrorKind::NotFound { - Some(Err(ScannerError::NotFound(dir))) + Some(Err(ScannerError::FileNotFound)) } else { Some(Err(ScannerError::from(err))) }