feat(lib): read audio attributes from shared files at scan time
Every search reply we sent carried no bitrate or duration, so our files failed every bitrate filter in other clients and sorted last in SoulseekQt, whatever their quality. The share scan now reads each MP3's first frame header (and Xing block for variable bitrate) and each FLAC's STREAMINFO, advertising bitrate, duration, VBR, sample rate and bit depth as far as the format gives them; other files stay bare. Only the file's head is read, no crate is needed, and a file that does not parse is shared without attributes rather than skipped.
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
//! peer-facing *virtual path* (the shared directory's own name followed by the
|
||||
//! backslash-separated relative path, matching the Soulseek wire convention).
|
||||
|
||||
mod audio;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -19,7 +21,8 @@ pub struct SharedFile {
|
||||
/// The real filesystem path used to serve the bytes.
|
||||
pub real_path: PathBuf,
|
||||
pub size: u64,
|
||||
/// Optional `(code, value)` audio attributes (empty for now).
|
||||
/// `(code, value)` audio attributes read from the file's headers: bitrate,
|
||||
/// duration, VBR, sample rate, bit depth, as far as the format gives them.
|
||||
pub attributes: Vec<(u32, u32)>,
|
||||
}
|
||||
|
||||
@@ -202,9 +205,9 @@ fn scan_root(
|
||||
files.push(SharedFile {
|
||||
search_key: virtual_path.to_lowercase(),
|
||||
virtual_path,
|
||||
attributes: audio::probe(&path),
|
||||
real_path: path,
|
||||
size: meta.len(),
|
||||
attributes: Vec::new(),
|
||||
});
|
||||
folders_with_files.insert(dir.clone());
|
||||
}
|
||||
@@ -244,6 +247,40 @@ mod tests {
|
||||
root
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_scanned_mp3_carries_its_bitrate() {
|
||||
let root = std::env::temp_dir()
|
||||
.join(format!("soulseek-shares-audio-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
// 100 frames of MPEG-1 Layer III at 128 kbps, 44.1 kHz.
|
||||
let mut mp3 = Vec::new();
|
||||
for _ in 0..100 {
|
||||
mp3.extend_from_slice(&[0xFF, 0xFB, 0x90, 0x00]);
|
||||
mp3.resize(mp3.len() + 413, 0);
|
||||
}
|
||||
std::fs::write(root.join("track.mp3"), &mp3).unwrap();
|
||||
std::fs::write(root.join("cover.jpg"), b"jpeg").unwrap();
|
||||
|
||||
let shares = Shares::scan(&root).unwrap();
|
||||
let track = shares
|
||||
.get(
|
||||
"soulseek-shares-audio-{pid}\\track.mp3"
|
||||
.replace("{pid}", &std::process::id().to_string())
|
||||
.as_str(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(track.attributes.contains(&(0, 128)));
|
||||
assert!(track.attributes.contains(&(1, 2)));
|
||||
let cover = shares
|
||||
.files()
|
||||
.iter()
|
||||
.find(|f| f.virtual_path.ends_with("cover.jpg"))
|
||||
.unwrap();
|
||||
assert!(cover.attributes.is_empty());
|
||||
let _ = std::fs::remove_dir_all(root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_counts_files_and_folders() {
|
||||
let root = temp_tree();
|
||||
|
||||
Reference in New Issue
Block a user