Parse id3 tags from mpeg files

This commit is contained in:
Savanni D'Gerinel 2023-03-02 00:19:57 -05:00
parent 2cc7bf2bca
commit 4606a117f6
5 changed files with 103 additions and 24 deletions

View File

@ -124,10 +124,14 @@ export class TrackCard extends HTMLElement {
trackName.name = this["name"]; trackName.name = this["name"];
container.appendChild(trackName); container.appendChild(trackName);
} }
this["length"] && container.appendChild(document.createTextNode("1:23")); if (this["length"]) {
this["album"] && container.appendChild(document.createTextNode(this["length"]));
container.appendChild(document.createTextNode("Shatter Me")); }
this["artist"] && if (this["album"]) {
container.appendChild(document.createTextNode("Lindsey Stirling")); container.appendChild(document.createTextNode(this["album"]));
}
if (this["artist"]) {
container.appendChild(document.createTextNode(this["artist"]));
}
} }
} }

View File

@ -25,6 +25,8 @@ const updateTrackList = (tracks: TrackInfo[]) => {
let track_formats = _.map(tracks, (info) => { let track_formats = _.map(tracks, (info) => {
let card: TrackCard = document.createElement("track-card"); let card: TrackCard = document.createElement("track-card");
card.name = info.name || null; card.name = info.name || null;
card.album = info.album || null;
card.artist = info.artist || null;
return card; return card;
}); });
_.map(track_formats, (trackCard) => { _.map(track_formats, (trackCard) => {

View File

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.7.6" version = "0.7.6"
@ -77,6 +83,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -118,6 +133,16 @@ dependencies = [
"instant", "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]] [[package]]
name = "flow" name = "flow"
version = "0.1.0" version = "0.1.0"
@ -329,6 +354,17 @@ dependencies = [
"want", "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]] [[package]]
name = "idna" name = "idna"
version = "0.3.0" version = "0.3.0"
@ -421,6 +457,15 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.5" version = "0.8.5"
@ -456,6 +501,7 @@ name = "music-player"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"flow", "flow",
"id3",
"mime", "mime",
"mime_guess", "mime_guess",
"rusqlite", "rusqlite",

View File

@ -7,8 +7,9 @@ edition = "2021"
[dependencies] [dependencies]
flow = { path = "../../flow" } flow = { path = "../../flow" }
mime = { version = "0.3" } id3 = { version = "1.6" }
mime_guess = { version = "2.0" } mime_guess = { version = "2.0" }
mime = { version = "0.3" }
rusqlite = { version = "0.28" } rusqlite = { version = "0.28" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
thiserror = { version = "1.0" } thiserror = { version = "1.0" }

View File

@ -1,4 +1,5 @@
use crate::audio::{TrackId, TrackInfo}; use crate::audio::{TrackId, TrackInfo};
use id3::{Tag, TagLike};
use std::{ use std::{
fs::{DirEntry, ReadDir}, fs::{DirEntry, ReadDir},
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -7,10 +8,12 @@ use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ScannerError { pub enum ScannerError {
#[error("Cannot scan {0}")] #[error("Cannot scan file")]
CannotScan(PathBuf), CannotScan,
#[error("Not found {0}")] #[error("File not found")]
NotFound(PathBuf), FileNotFound,
#[error("Tag not found")]
TagNotFound,
#[error("IO error {0}")] #[error("IO error {0}")]
IO(std::io::Error), IO(std::io::Error),
} }
@ -21,6 +24,19 @@ impl From<std::io::Error> for ScannerError {
} }
} }
impl From<id3::Error> 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 { pub trait MusicScanner: Send {
fn scan<'a>(&'a self) -> Box<dyn Iterator<Item = Result<TrackInfo, ScannerError>> + 'a>; fn scan<'a>(&'a self) -> Box<dyn Iterator<Item = Result<TrackInfo, ScannerError>> + 'a>;
} }
@ -49,19 +65,10 @@ impl FileIterator {
); );
let mimetype = mime_guess::from_path(path.clone()) let mimetype = mime_guess::from_path(path.clone())
.first() .first()
.ok_or(ScannerError::CannotScan(path.clone()))?; .ok_or(ScannerError::CannotScan)?;
match (mimetype.type_(), mimetype.subtype().as_str()) { match (mimetype.type_(), mimetype.subtype().as_str()) {
(mime::AUDIO, "mpeg") => Ok(TrackInfo { (mime::AUDIO, "mpeg") => TrackInfo::scan_id3(path, mimetype),
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, "ogg") => Ok(TrackInfo { (mime::AUDIO, "ogg") => Ok(TrackInfo {
id: TrackId::from(path.to_str().unwrap().to_owned()), id: TrackId::from(path.to_str().unwrap().to_owned()),
@ -86,11 +93,30 @@ impl FileIterator {
filetype: mimetype, filetype: mimetype,
}), }),
*/ */
_ => Err(ScannerError::CannotScan(path)), _ => Err(ScannerError::CannotScan),
} }
} }
} }
impl TrackInfo {
fn scan_id3(path: PathBuf, mimetype: mime::Mime) -> Result<TrackInfo, ScannerError> {
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 { enum EntryInfo {
Dir(PathBuf), Dir(PathBuf),
File(PathBuf), File(PathBuf),
@ -136,7 +162,7 @@ impl Iterator for FileIterator {
} }
Err(err) => { Err(err) => {
if err.kind() == std::io::ErrorKind::NotFound { if err.kind() == std::io::ErrorKind::NotFound {
Some(Err(ScannerError::NotFound(dir))) Some(Err(ScannerError::FileNotFound))
} else { } else {
Some(Err(ScannerError::from(err))) Some(Err(ScannerError::from(err)))
} }