#![warn(clippy::all, clippy::nursery, clippy::pedantic, clippy::cargo)]
#![doc = include_str!("../README.md")]
use nskeyedarchiver_converter::Converter;
use std::{
cell::RefCell,
fs,
io::{BufReader, Read},
path::{Path, PathBuf},
};
use zip::read::ZipArchive;
pub struct File {
path: PathBuf,
archive: RefCell<ZipArchive<BufReader<fs::File>>>,
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Could not open file: {0}")]
FileOpen(#[from] std::io::Error),
#[error("Could not parse file: {0}")]
Zip(#[from] zip::result::ZipError),
#[error("Could not unpack the file's metadata: {0}")]
NSKeyedArchiver(#[from] nskeyedarchiver_converter::ConverterError),
#[error("Could not parse the file's metadata: {0}")]
Plist(#[from] plist::Error),
}
impl File {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let path = path.as_ref().to_path_buf();
let file = fs::File::open(&path)
.map(BufReader::new)
.map_err(Error::FileOpen)?;
Ok(Self {
path,
archive: RefCell::new(ZipArchive::new(file)?),
})
}
pub fn metadata(&self) -> Result<plist::Value, Error> {
let mut archive = self.archive.borrow_mut();
let plist = archive
.by_name("Document.archive")?
.bytes()
.collect::<Result<Vec<_>, _>>()?;
let plist: plist::Value = Converter::from_bytes(&plist)?.decode()?;
Ok(plist)
}
pub fn thumbnail(&self) -> Result<Vec<u8>, Error> {
let mut archive = self.archive.borrow_mut();
let thumbnail = archive
.by_name("QuickLook/Thumbnail.png")?
.bytes()
.collect::<Result<Vec<_>, _>>()?;
Ok(thumbnail)
}
pub fn timelapse_segments(&self) -> Result<Vec<Vec<u8>>, Error> {
let mut archive = self.archive.borrow_mut();
let mut segments = archive
.file_names()
.filter(|name| name.starts_with("video/segments/"))
.map(ToString::to_string)
.collect::<Vec<_>>();
segments.sort_unstable_by(|a, b| {
let a: u32 = a
.split('-')
.last()
.and_then(|s| s.split('.').next())
.map_or_else(|| unreachable!(), str::parse)
.unwrap_or_else(|_| unreachable!());
let b: u32 = b
.split('-')
.last()
.and_then(|s| s.split('.').next())
.map_or_else(|| unreachable!(), str::parse)
.unwrap_or_else(|_| unreachable!());
a.cmp(&b)
});
let segments = segments
.iter()
.map(|name| {
archive
.by_name(name)?
.bytes()
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
Ok(segments)
}
}
impl Clone for File {
fn clone(&self) -> Self {
Self::open(&self.path).expect("Could not clone file")
}
}