130 lines
2.9 KiB
Rust
130 lines
2.9 KiB
Rust
use bytes::{Buf, Bytes};
|
|
use std::{fs::File, io::Read, path::Path};
|
|
use thiserror::Error;
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum FormatDetectionError {
|
|
#[error("could not read file")]
|
|
FileReadError,
|
|
#[error("file format unrecognised")]
|
|
UnrecognisedFormat,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub enum FileFormat {
|
|
FLAC,
|
|
MP3,
|
|
OggVorbis,
|
|
OggOpus,
|
|
OggFLAC,
|
|
OggSpeex,
|
|
OggTheora,
|
|
AIFF,
|
|
Wav,
|
|
WavPack,
|
|
}
|
|
|
|
impl ToString for FileFormat {
|
|
fn to_string(&self) -> String {
|
|
match self {
|
|
FileFormat::FLAC => "FLAC".to_string(),
|
|
FileFormat::MP3 => "MP3".to_string(),
|
|
FileFormat::OggVorbis => "Ogg (Vorbis)".to_string(),
|
|
FileFormat::OggOpus => "Ogg (Opus)".to_string(),
|
|
FileFormat::OggFLAC => "Ogg (FLAC)".to_string(),
|
|
FileFormat::OggSpeex => "Ogg (Speex)".to_string(),
|
|
FileFormat::OggTheora => "Ogg (Theora)".to_string(),
|
|
FileFormat::AIFF => "AIFF".to_string(),
|
|
FileFormat::Wav => "Wav".to_string(),
|
|
FileFormat::WavPack => "WavPack".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn detect_ogg_subformat(path: &Path) -> Result<FileFormat, FormatDetectionError> {
|
|
let file = File::open(path);
|
|
|
|
if let Err(_error) = file {
|
|
return Err(FormatDetectionError::FileReadError);
|
|
}
|
|
|
|
let file = file.unwrap();
|
|
|
|
let limit = file
|
|
.metadata()
|
|
.map(|m| std::cmp::min(m.len(), 128) as usize + 1)
|
|
.unwrap_or(0);
|
|
|
|
let mut bytes = Vec::with_capacity(limit);
|
|
if let Err(_err) = file.take(128).read_to_end(&mut bytes) {
|
|
return Err(FormatDetectionError::FileReadError);
|
|
}
|
|
|
|
let mut mem = Bytes::from(bytes);
|
|
mem.advance(28);
|
|
|
|
let vorbis_type = mem.get_u8();
|
|
|
|
match vorbis_type {
|
|
0x01 => return Ok(FileFormat::OggVorbis),
|
|
0x7f => return Ok(FileFormat::OggFLAC),
|
|
0x80 => return Ok(FileFormat::OggTheora),
|
|
// S for speex
|
|
0x53 => return Ok(FileFormat::OggSpeex),
|
|
_ => {}
|
|
}
|
|
|
|
Err(FormatDetectionError::UnrecognisedFormat)
|
|
}
|
|
|
|
fn wavpack_matcher(buf: &[u8]) -> bool {
|
|
// 77 76 70 6b
|
|
buf.len() >= 4 && buf[0] == 0x77 && buf[1] == 0x76 && buf[2] == 0x70 && buf[3] == 0x6b
|
|
}
|
|
|
|
pub fn detect_format(path: &Path) -> Result<FileFormat, FormatDetectionError> {
|
|
let mut info = infer::Infer::new();
|
|
info.add("custom/wavpack", "wv", wavpack_matcher);
|
|
|
|
let kind = info.get_from_path(path);
|
|
|
|
if let Err(_error) = kind {
|
|
return Err(FormatDetectionError::FileReadError);
|
|
}
|
|
|
|
let kind = kind.unwrap();
|
|
|
|
if kind.is_none() {
|
|
return Err(FormatDetectionError::UnrecognisedFormat);
|
|
}
|
|
|
|
let kind = kind.unwrap();
|
|
|
|
match kind.mime_type() {
|
|
"audio/mpeg" => {
|
|
return Ok(FileFormat::MP3);
|
|
}
|
|
"audio/x-wav" => {
|
|
return Ok(FileFormat::Wav);
|
|
}
|
|
"custom/wavpack" => {
|
|
return Ok(FileFormat::WavPack);
|
|
}
|
|
"audio/ogg" => {
|
|
return detect_ogg_subformat(path);
|
|
}
|
|
"audio/x-flac" => {
|
|
return Ok(FileFormat::FLAC);
|
|
}
|
|
"audio/x-aiff" => {
|
|
return Ok(FileFormat::AIFF);
|
|
}
|
|
"audio/opus" => {
|
|
return Ok(FileFormat::OggOpus);
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
Err(FormatDetectionError::UnrecognisedFormat)
|
|
}
|