nixfiles/src/utils/format_detection.rs

130 lines
2.9 KiB
Rust
Raw Normal View History

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);
2023-08-31 23:50:30 +01:00
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)
}