use crate::collection::Track;
use baken_core::cdjsafe::stick_path;
use std::collections::HashSet;
use std::path::Path;
#[derive(Debug, Default)]
pub struct Layout {
used: HashSet<String>,
}
impl Layout {
pub fn assign(&mut self, track: &Track) -> String {
let path =
|suffix: &str| stick_path(&track.artist, &track.album, track.file_name(), suffix);
let mut candidate = path("");
let mut n = 1;
while !self.used.insert(candidate.to_lowercase()) {
candidate = path(&format!("-{n}"));
n += 1;
}
candidate
}
}
pub fn sample_depth(path: &Path) -> u16 {
fn read(path: &Path) -> Option<u16> {
use std::io::Read;
let mut f = std::fs::File::open(path).ok()?;
let mut head = vec![0u8; 64 * 1024];
let n = f.read(&mut head).ok()?;
head.truncate(n);
if head.starts_with(b"fLaC") {
let body = &head[8..];
let bps = (((body[12] & 1) as u16) << 4) | ((body[13] >> 4) as u16);
return Some(bps + 1);
}
if head.starts_with(b"FORM") && &head[8..12] == b"AIFF"
|| head.starts_with(b"FORM") && &head[8..12] == b"AIFC"
{
let mut off = 12;
while off + 8 <= head.len() {
let len = u32::from_be_bytes(head[off + 4..off + 8].try_into().ok()?) as usize;
if &head[off..off + 4] == b"COMM" {
return Some(u16::from_be_bytes(
head[off + 14..off + 16].try_into().ok()?,
));
}
off += 8 + len + (len & 1);
}
}
if head.starts_with(b"RIFF") && &head[8..12] == b"WAVE" {
let mut off = 12;
while off + 8 <= head.len() {
let len = u32::from_le_bytes(head[off + 4..off + 8].try_into().ok()?) as usize;
if &head[off..off + 4] == b"fmt " {
return Some(u16::from_le_bytes(
head[off + 22..off + 24].try_into().ok()?,
));
}
off += 8 + len + (len & 1);
}
}
if let Some(i) = head
.windows(4)
.position(|w| w == b"alac")
.filter(|_| &head[4..8] == b"ftyp")
{
if let Some(j) = head[i + 4..].windows(4).position(|w| w == b"alac") {
let cfg = i + 4 + j + 4 + 4;
return head.get(cfg + 5).map(|&b| b as u16);
}
}
None
}
read(path).filter(|d| (8..=32).contains(d)).unwrap_or(16)
}
#[cfg(test)]
mod tests {
use super::*;
fn track(artist: &str, album: &str, file: &str) -> Track {
Track {
artist: artist.into(),
album: album.into(),
location: format!("/Volumes/X/{file}"),
..Default::default()
}
}
#[test]
fn rekordbox_naming_rules() {
let mut l = Layout::default();
assert_eq!(
l.assign(&track(
"Lidvall",
"The Strange Guy EP",
"Lidvall - The Strange Guy EP - 05 Where have you been-.flac"
)),
"/Contents/Lidvall/The Strange Guy EP/Lidvall - The Strange Guy EP - 05 Where hav.flac"
);
assert_eq!(
l.assign(&track("6EJOU ", "", "6EJOU - Psycho Dreams.aif")),
"/Contents/6EJOU/UnknownAlbum/6EJOU - Psycho Dreams.aif"
);
assert_eq!(
l.assign(&track("Oktobr", "CRVA002 | ANNIVERSARY", "x.flac")),
"/Contents/Oktobr/CRVA002 _ ANNIVERSARY/x.flac"
);
let a = l.assign(&track(
"T.A.M",
"",
"T.A.M - Raw-Hypnotic 2023 - Year Compilation - 11 Speedy Hypno.flac",
));
let b = l.assign(&track(
"T.A.M",
"",
"T.A.M - Raw-Hypnotic 2023 - Year Compilation - 12 Other.flac",
));
assert_eq!(
a,
"/Contents/T.A.M/UnknownAlbum/T.A.M - Raw-Hypnotic 2023 - Year Compilatio.flac"
);
assert_eq!(
b,
"/Contents/T.A.M/UnknownAlbum/T.A.M - Raw-Hypnotic 2023 - Year Compilat-1.flac"
);
assert_eq!(
l.assign(&track("", "", "盾.mp3")),
"/Contents/UnknownArtist/UnknownAlbum/盾.mp3"
);
}
}