blob: 83624f45b70a9a8f8fcbbb314c721c6206a32471 [file] [log] [blame]
use std::{
io::Cursor,
sync::{Arc, Mutex},
};
use anyhow::anyhow;
use avm_analyzer_common::{
AvmStreamInfo, DEFAULT_PROTO_PATH_FRAME_SUFFIX_FIRST, DEFAULT_PROTO_PATH_FRAME_SUFFIX_TEMPLATE,
};
use egui::DroppedFile;
use itertools::Itertools;
use log::info;
use rfd::AsyncFileDialog;
use wasm_bindgen_futures::spawn_local;
use zip::ZipArchive;
use crate::stream::Stream;
use super::stream_name_from_file_name;
pub struct LocalStreamInfo {
pub file_name: String,
pub zip_bytes: Arc<[u8]>,
}
impl LocalStreamInfo {
pub fn get_stream_info(&self) -> anyhow::Result<AvmStreamInfo> {
let archive = ZipArchive::new(Cursor::new(self.zip_bytes.clone()))?;
let proto_file_names: Vec<_> = archive.file_names().filter(|n| n.ends_with(".pb")).sorted().collect();
let first_proto = proto_file_names
.first()
.ok_or(anyhow!("No protobufs (.pb) found in .zip archive."))?;
if !first_proto.ends_with(DEFAULT_PROTO_PATH_FRAME_SUFFIX_FIRST) {
return Err(anyhow!("Unexpected protobuf naming scheme: expected suffix: {DEFAULT_PROTO_PATH_FRAME_SUFFIX_FIRST}, actual name: {first_proto}"));
}
let proto_path_template = first_proto.replace(
DEFAULT_PROTO_PATH_FRAME_SUFFIX_FIRST,
DEFAULT_PROTO_PATH_FRAME_SUFFIX_TEMPLATE,
);
info!("Found {} frames from .zip stream.", proto_file_names.len());
let stream_info = AvmStreamInfo {
stream_name: self.file_name.clone(),
num_frames: proto_file_names.len(),
proto_path_template,
thumbnail_png: None,
};
for (i, proto_file_name) in proto_file_names.iter().enumerate() {
let expected = stream_info.get_proto_path(i);
if expected != **proto_file_name {
return Err(anyhow!(
"Unexpected protobuf in stream: Expected name: {expected}, actual name: {proto_file_name}"
));
}
}
Ok(stream_info)
}
}
const DEMO_STREAM_BYTES: &[u8] = include_bytes!("../../assets/leo_qcif.zip");
pub struct LocalStreamManager {
local_stream: Arc<Mutex<Option<LocalStreamInfo>>>,
}
impl LocalStreamManager {
pub fn new() -> Self {
Self {
local_stream: Arc::new(Mutex::new(None)),
}
}
pub fn load_demo_stream(&self) {
let demo_stream = Some(LocalStreamInfo {
file_name: "leo_qcif.ivf".into(),
zip_bytes: DEMO_STREAM_BYTES.into(),
});
*self.local_stream.lock().unwrap() = demo_stream;
}
/// Currently only .zip files are supported.
pub fn prompt_local_stream(&self) {
let task = AsyncFileDialog::new().add_filter("ZIP", &["zip"]).pick_file();
let local_stream = self.local_stream.clone();
spawn_local(async move {
let file = task.await;
if let Some(file) = file {
let bytes = file.read().await;
let mut local_stream = local_stream.lock().unwrap();
*local_stream = Some(LocalStreamInfo {
file_name: stream_name_from_file_name(&file.file_name()),
zip_bytes: bytes.into(),
});
}
});
}
pub fn handle_dropped_file(&self, file: DroppedFile) {
if let Some(bytes) = file.bytes {
let mut local_stream = self.local_stream.lock().unwrap();
*local_stream = Some(LocalStreamInfo {
file_name: file.name,
zip_bytes: bytes,
});
}
}
pub fn check_local_stream_ready(&self, stream: &mut Option<Stream>) -> anyhow::Result<()> {
if let Some(local_stream) = self.local_stream.lock().unwrap().take() {
info!(
"Loaded local stream {}: {} bytes",
local_stream.file_name,
local_stream.zip_bytes.len()
);
*stream = Some(Stream::from_local_file(local_stream)?);
}
Ok(())
}
}