Implement support for Wave files (#135)
* Implement support for Wave files Metadata extraction for such format is supported by the latest version of rust-id3, which has been updated in this commit. The code has been updated to handle such files and call the new APIs. * Code review
This commit is contained in:
parent
6c27409ef2
commit
652772ba0e
7 changed files with 90 additions and 108 deletions
70
Cargo.lock
generated
70
Cargo.lock
generated
|
@ -791,70 +791,6 @@ version = "1.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding"
|
|
||||||
version = "0.2.33"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
|
|
||||||
dependencies = [
|
|
||||||
"encoding-index-japanese",
|
|
||||||
"encoding-index-korean",
|
|
||||||
"encoding-index-simpchinese",
|
|
||||||
"encoding-index-singlebyte",
|
|
||||||
"encoding-index-tradchinese",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-japanese"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-korean"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-simpchinese"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-singlebyte"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-tradchinese"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding_index_tests"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.26"
|
version = "0.8.26"
|
||||||
|
@ -1224,15 +1160,13 @@ checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "id3"
|
name = "id3"
|
||||||
version = "0.5.1"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02c11bb50ce1568516aefbe4b6564c3feaf15a8e5ccbea90fa652012446ae9bf"
|
checksum = "f23fa956cb2f3e3e547993eb62b6f76b5babd86c3e7bb4fe1149ef192d90df51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"encoding",
|
|
||||||
"flate2",
|
"flate2",
|
||||||
"lazy_static",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -24,7 +24,7 @@ diesel_migrations = { version = "1.4", features = ["sqlite"] }
|
||||||
futures-util = { version = "0.3" }
|
futures-util = { version = "0.3" }
|
||||||
getopts = "0.2.15"
|
getopts = "0.2.15"
|
||||||
http = "0.2.2"
|
http = "0.2.2"
|
||||||
id3 = "0.5.1"
|
id3 = "0.6.3"
|
||||||
libsqlite3-sys = { version = "0.18", features = ["bundled", "bundled-windows"], optional = true }
|
libsqlite3-sys = { version = "0.18", features = ["bundled", "bundled-windows"], optional = true }
|
||||||
lewton = "0.10.1"
|
lewton = "0.10.1"
|
||||||
log = "0.4.5"
|
log = "0.4.5"
|
||||||
|
|
|
@ -27,15 +27,46 @@ pub struct SongTags {
|
||||||
pub has_artwork: bool,
|
pub has_artwork: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<id3::Tag> for SongTags {
|
||||||
|
fn from(tag: id3::Tag) -> Self {
|
||||||
|
let artist = tag.artist().map(|s| s.to_string());
|
||||||
|
let album_artist = tag.album_artist().map(|s| s.to_string());
|
||||||
|
let album = tag.album().map(|s| s.to_string());
|
||||||
|
let title = tag.title().map(|s| s.to_string());
|
||||||
|
let duration = tag.duration();
|
||||||
|
let disc_number = tag.disc();
|
||||||
|
let track_number = tag.track();
|
||||||
|
let year = tag
|
||||||
|
.year()
|
||||||
|
.map(|y| y as i32)
|
||||||
|
.or_else(|| tag.date_released().map(|d| d.year))
|
||||||
|
.or_else(|| tag.date_recorded().map(|d| d.year));
|
||||||
|
let has_artwork = tag.pictures().count() > 0;
|
||||||
|
|
||||||
|
SongTags {
|
||||||
|
artist,
|
||||||
|
album_artist,
|
||||||
|
album,
|
||||||
|
title,
|
||||||
|
duration,
|
||||||
|
disc_number,
|
||||||
|
track_number,
|
||||||
|
year,
|
||||||
|
has_artwork,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read(path: &Path) -> Option<SongTags> {
|
pub fn read(path: &Path) -> Option<SongTags> {
|
||||||
let data = match utils::get_audio_format(path) {
|
let data = match utils::get_audio_format(path) {
|
||||||
Some(AudioFormat::APE) => Some(read_ape(path)),
|
Some(AudioFormat::APE) => Some(read_ape(path)),
|
||||||
Some(AudioFormat::FLAC) => Some(read_flac(path)),
|
Some(AudioFormat::FLAC) => Some(read_flac(path)),
|
||||||
Some(AudioFormat::MP3) => Some(read_id3(path)),
|
Some(AudioFormat::MP3) => Some(read_mp3(path)),
|
||||||
Some(AudioFormat::MP4) => Some(read_mp4(path)),
|
Some(AudioFormat::MP4) => Some(read_mp4(path)),
|
||||||
Some(AudioFormat::MPC) => Some(read_ape(path)),
|
Some(AudioFormat::MPC) => Some(read_ape(path)),
|
||||||
Some(AudioFormat::OGG) => Some(read_vorbis(path)),
|
Some(AudioFormat::OGG) => Some(read_vorbis(path)),
|
||||||
Some(AudioFormat::OPUS) => Some(read_opus(path)),
|
Some(AudioFormat::OPUS) => Some(read_opus(path)),
|
||||||
|
Some(AudioFormat::WAVE) => Some(read_wave(path)),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
match data {
|
match data {
|
||||||
|
@ -48,49 +79,35 @@ pub fn read(path: &Path) -> Option<SongTags> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_id3(path: &Path) -> Result<SongTags> {
|
fn read_mp3(path: &Path) -> Result<SongTags> {
|
||||||
let tag = {
|
let tag = id3::Tag::read_from_path(&path).or_else(|error| {
|
||||||
match id3::Tag::read_from_path(&path) {
|
if let Some(tag) = error.partial_tag {
|
||||||
Ok(t) => Ok(t),
|
Ok(tag)
|
||||||
Err(e) => {
|
} else {
|
||||||
if let Some(t) = e.partial_tag {
|
Err(error)
|
||||||
Ok(t)
|
}
|
||||||
} else {
|
})?;
|
||||||
Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}?
|
|
||||||
};
|
|
||||||
let duration = {
|
let duration = {
|
||||||
mp3_duration::from_path(&path)
|
mp3_duration::from_path(&path)
|
||||||
.map(|d| d.as_secs() as u32)
|
.map(|d| d.as_secs() as u32)
|
||||||
.ok()
|
.ok()
|
||||||
};
|
};
|
||||||
|
|
||||||
let artist = tag.artist().map(|s| s.to_string());
|
let mut song_tags: SongTags = tag.into();
|
||||||
let album_artist = tag.album_artist().map(|s| s.to_string());
|
song_tags.duration = duration; // Use duration from mp3_duration instead of from tags.
|
||||||
let album = tag.album().map(|s| s.to_string());
|
Ok(song_tags)
|
||||||
let title = tag.title().map(|s| s.to_string());
|
}
|
||||||
let disc_number = tag.disc();
|
|
||||||
let track_number = tag.track();
|
|
||||||
let year = tag
|
|
||||||
.year()
|
|
||||||
.map(|y| y as i32)
|
|
||||||
.or_else(|| tag.date_released().and_then(|d| Some(d.year)))
|
|
||||||
.or_else(|| tag.date_recorded().and_then(|d| Some(d.year)));
|
|
||||||
let has_artwork = tag.pictures().count() > 0;
|
|
||||||
|
|
||||||
Ok(SongTags {
|
fn read_wave(path: &Path) -> Result<SongTags> {
|
||||||
artist,
|
let tag = id3::Tag::read_from_wav(&path).or_else(|error| {
|
||||||
album_artist,
|
if let Some(tag) = error.partial_tag {
|
||||||
album,
|
Ok(tag)
|
||||||
title,
|
} else {
|
||||||
duration,
|
Err(error)
|
||||||
disc_number,
|
}
|
||||||
track_number,
|
})?;
|
||||||
year,
|
Ok(tag.into())
|
||||||
has_artwork,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_ape_string(item: &ape::Item) -> Option<String> {
|
fn read_ape_string(item: &ape::Item) -> Option<String> {
|
||||||
|
@ -306,6 +323,10 @@ fn reads_file_metadata() {
|
||||||
read(Path::new("test-data/formats/sample.ape")).unwrap(),
|
read(Path::new("test-data/formats/sample.ape")).unwrap(),
|
||||||
sample_tags
|
sample_tags
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
read(Path::new("test-data/formats/sample.wav")).unwrap(),
|
||||||
|
sample_tags
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -325,4 +346,9 @@ fn reads_embedded_artwork() {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.has_artwork
|
.has_artwork
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
read(Path::new("test-data/artwork/sample.wav"))
|
||||||
|
.unwrap()
|
||||||
|
.has_artwork
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,12 @@ pub fn read(image_path: &Path) -> Result<DynamicImage> {
|
||||||
match utils::get_audio_format(image_path) {
|
match utils::get_audio_format(image_path) {
|
||||||
Some(AudioFormat::APE) => read_ape(image_path),
|
Some(AudioFormat::APE) => read_ape(image_path),
|
||||||
Some(AudioFormat::FLAC) => read_flac(image_path),
|
Some(AudioFormat::FLAC) => read_flac(image_path),
|
||||||
Some(AudioFormat::MP3) => read_id3(image_path),
|
Some(AudioFormat::MP3) => read_mp3(image_path),
|
||||||
Some(AudioFormat::MP4) => read_mp4(image_path),
|
Some(AudioFormat::MP4) => read_mp4(image_path),
|
||||||
Some(AudioFormat::MPC) => read_ape(image_path),
|
Some(AudioFormat::MPC) => read_ape(image_path),
|
||||||
Some(AudioFormat::OGG) => read_vorbis(image_path),
|
Some(AudioFormat::OGG) => read_vorbis(image_path),
|
||||||
Some(AudioFormat::OPUS) => read_opus(image_path),
|
Some(AudioFormat::OPUS) => read_opus(image_path),
|
||||||
|
Some(AudioFormat::WAVE) => read_wave(image_path),
|
||||||
None => Ok(image::open(image_path)?),
|
None => Ok(image::open(image_path)?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,9 +38,19 @@ fn read_flac(path: &Path) -> Result<DynamicImage> {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_id3(path: &Path) -> Result<DynamicImage> {
|
fn read_mp3(path: &Path) -> Result<DynamicImage> {
|
||||||
let tag = id3::Tag::read_from_path(path)?;
|
let tag = id3::Tag::read_from_path(path)?;
|
||||||
|
|
||||||
|
read_id3(&path, &tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_wave(path: &Path) -> Result<DynamicImage> {
|
||||||
|
let tag = id3::Tag::read_from_wav(path)?;
|
||||||
|
|
||||||
|
read_id3(&path, &tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_id3(path: &Path, tag: &id3::Tag) -> Result<DynamicImage> {
|
||||||
if let Some(p) = tag.pictures().next() {
|
if let Some(p) = tag.pictures().next() {
|
||||||
return Ok(image::load_from_memory(&p.data)?);
|
return Ok(image::load_from_memory(&p.data)?);
|
||||||
}
|
}
|
||||||
|
@ -117,4 +128,9 @@ fn can_read_artwork_data() {
|
||||||
.map(|d| d.to_rgb8())
|
.map(|d| d.to_rgb8())
|
||||||
.ok();
|
.ok();
|
||||||
assert_eq!(opus_img, None);
|
assert_eq!(opus_img, None);
|
||||||
|
|
||||||
|
let wave_img = read(Path::new("test-data/artwork/sample.wav"))
|
||||||
|
.unwrap()
|
||||||
|
.to_rgb8();
|
||||||
|
assert_eq!(wave_img, embedded_img);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ pub enum AudioFormat {
|
||||||
MPC,
|
MPC,
|
||||||
OGG,
|
OGG,
|
||||||
OPUS,
|
OPUS,
|
||||||
|
WAVE,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_audio_format(path: &Path) -> Option<AudioFormat> {
|
pub fn get_audio_format(path: &Path) -> Option<AudioFormat> {
|
||||||
|
@ -40,6 +41,7 @@ pub fn get_audio_format(path: &Path) -> Option<AudioFormat> {
|
||||||
"mpc" => Some(AudioFormat::MPC),
|
"mpc" => Some(AudioFormat::MPC),
|
||||||
"ogg" => Some(AudioFormat::OGG),
|
"ogg" => Some(AudioFormat::OGG),
|
||||||
"opus" => Some(AudioFormat::OPUS),
|
"opus" => Some(AudioFormat::OPUS),
|
||||||
|
"wav" => Some(AudioFormat::WAVE),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,4 +53,8 @@ fn can_guess_audio_format() {
|
||||||
get_audio_format(Path::new("animals/🐷/my🐖file.flac")),
|
get_audio_format(Path::new("animals/🐷/my🐖file.flac")),
|
||||||
Some(AudioFormat::FLAC)
|
Some(AudioFormat::FLAC)
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_audio_format(Path::new("animals/🐷/my🐖file.wav")),
|
||||||
|
Some(AudioFormat::WAVE)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
BIN
test-data/artwork/sample.wav
Normal file
BIN
test-data/artwork/sample.wav
Normal file
Binary file not shown.
BIN
test-data/formats/sample.wav
Normal file
BIN
test-data/formats/sample.wav
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue