mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-04 23:26:58 +02:00
133 lines
4.5 KiB
Rust
133 lines
4.5 KiB
Rust
//! Metadata from a descriptor already opened by the native document provider.
|
|
//! This entry point is not exposed to extensions and does not grant filesystem roots.
|
|
|
|
use crate::cancellation::RequestLease;
|
|
use crate::tags::{AudioTagsError, check_lease, open_audio_file};
|
|
use spotiflac_core::{cover, tags};
|
|
use std::sync::Arc;
|
|
|
|
#[derive(uniffi::Record)]
|
|
pub struct DescriptorLibraryMetadata {
|
|
pub metadata_json: String,
|
|
pub cover_bytes: Vec<u8>,
|
|
pub cover_mime: String,
|
|
}
|
|
|
|
/// The caller keeps the descriptor open until this synchronous read returns.
|
|
/// Reopening its descriptor path preserves SAF access instead of resolving a
|
|
/// protected /storage or /mnt/media_rw path and copying the entire audio file.
|
|
#[uniffi::export]
|
|
pub fn read_library_metadata_from_descriptor(
|
|
descriptor: i32,
|
|
hint: String,
|
|
scan_time: String,
|
|
include_cover: bool,
|
|
lease: Option<Arc<RequestLease>>,
|
|
) -> Result<DescriptorLibraryMetadata, AudioTagsError> {
|
|
if descriptor < 0 {
|
|
return Err("invalid audio descriptor".to_owned().into());
|
|
}
|
|
let check = || check_lease(lease.as_deref());
|
|
check()?;
|
|
let directory = if cfg!(any(target_os = "android", target_os = "linux")) {
|
|
"/proc/self/fd"
|
|
} else {
|
|
"/dev/fd"
|
|
};
|
|
let path = format!("{directory}/{descriptor}");
|
|
let mut file = open_audio_file(&path)?;
|
|
let format = tags::library_extension(&path, &hint);
|
|
let (metadata, mut artwork) = if include_cover && format == "flac" {
|
|
tags::read_library_metadata_with_cover(&mut file, &path, &hint, &scan_time, 0, &check)?
|
|
} else {
|
|
let metadata = tags::read_library_metadata(&mut file, &path, &hint, &scan_time, 0, &check)?;
|
|
let artwork = if include_cover {
|
|
let format = if matches!(format.as_str(), "mp4" | "aac") {
|
|
"m4a"
|
|
} else {
|
|
&format
|
|
};
|
|
tags::extract_cover(&mut file, format, &check).ok()
|
|
} else {
|
|
None
|
|
};
|
|
(metadata, artwork)
|
|
};
|
|
if let Some(artwork) = artwork.as_mut()
|
|
&& let Ok(resized) = cover::library_thumbnail(&artwork.data, &check)
|
|
{
|
|
artwork.mime = if resized.starts_with(b"\x89PNG") {
|
|
"image/png"
|
|
} else if resized.starts_with(b"\xff\xd8") {
|
|
"image/jpeg"
|
|
} else {
|
|
&artwork.mime
|
|
}
|
|
.into();
|
|
artwork.data = resized.to_vec();
|
|
}
|
|
check()?;
|
|
let (cover_bytes, cover_mime) = artwork.map_or_else(
|
|
|| (Vec::new(), String::new()),
|
|
|artwork| (artwork.data, artwork.mime),
|
|
);
|
|
Ok(DescriptorLibraryMetadata {
|
|
metadata_json: metadata.to_string(),
|
|
cover_bytes,
|
|
cover_mime,
|
|
})
|
|
}
|
|
|
|
#[cfg(all(test, unix))]
|
|
mod tests {
|
|
use super::*;
|
|
use std::fs::{File, OpenOptions};
|
|
use std::io::Write;
|
|
use std::os::fd::AsRawFd;
|
|
|
|
#[test]
|
|
fn descriptor_remains_open_and_audio_does_not_need_a_path_grant() {
|
|
let path = std::env::temp_dir().join(format!("library-fd-{}.wav", std::process::id()));
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.create_new(true)
|
|
.open(&path)
|
|
.unwrap();
|
|
let mut wav = b"RIFF".to_vec();
|
|
wav.extend(38_u32.to_le_bytes());
|
|
wav.extend(b"WAVEfmt ");
|
|
wav.extend(16_u32.to_le_bytes());
|
|
wav.extend(1_u16.to_le_bytes());
|
|
wav.extend(1_u16.to_le_bytes());
|
|
wav.extend(44100_u32.to_le_bytes());
|
|
wav.extend(88200_u32.to_le_bytes());
|
|
wav.extend(2_u16.to_le_bytes());
|
|
wav.extend(16_u16.to_le_bytes());
|
|
wav.extend(b"data");
|
|
wav.extend(2_u32.to_le_bytes());
|
|
wav.extend(0_i16.to_le_bytes());
|
|
file.write_all(&wav).unwrap();
|
|
drop(file);
|
|
let source = File::open(&path).unwrap();
|
|
let read = read_library_metadata_from_descriptor(
|
|
source.as_raw_fd(),
|
|
"Track.wav".into(),
|
|
"2026-09-30T00:00:00Z".into(),
|
|
true,
|
|
None,
|
|
);
|
|
std::fs::remove_file(path).unwrap();
|
|
let metadata = read.unwrap();
|
|
let json: serde_json::Value = serde_json::from_str(&metadata.metadata_json).unwrap();
|
|
assert_eq!(json["format"], "wav");
|
|
assert_eq!(json["sampleRate"], 44100);
|
|
assert_eq!(json["bitDepth"], 16);
|
|
assert!(metadata.cover_bytes.is_empty());
|
|
assert!(source.metadata().unwrap().is_file());
|
|
assert!(
|
|
read_library_metadata_from_descriptor(-1, "x.flac".into(), "".into(), false, None)
|
|
.is_err()
|
|
);
|
|
}
|
|
}
|