pub mod dvtm_wm169;
use std::io::*;
use std::sync::{ Arc, atomic::AtomicBool };
use crate::tags_impl::*;
use crate::*;
use crate::util::insert_tag;
use memchr::memmem;
use prost::Message;
#[derive(Default)]
pub struct Dji {
pub model: Option<String>,
pub frame_readout_time: Option<f64>
}
impl Dji {
pub fn detect<P: AsRef<std::path::Path>>(buffer: &[u8], _filepath: P) -> Option<Self> {
if memmem::find(buffer, b"djmd").is_some() && memmem::find(buffer, b"DJI meta").is_some() {
Some(Self {
model: None,
frame_readout_time: None
})
} else {
None
}
}
pub fn parse<T: Read + Seek, F: Fn(f64)>(&mut self, stream: &mut T, size: usize, progress_cb: F, cancel_flag: Arc<AtomicBool>) -> Result<Vec<SampleInfo>> {
let mut samples = Vec::new();
let mut first_timestamp = 0;
let mut focal_length = None;
let mut distortion_coeffs = None;
let mut exposure_time = 0.0;
let mut fps = 59.94;
let mut sensor_fps = 59.969295501708984;
let mut sample_rate = 2000.0;
let mut first_vsync = 0;
let mut prev_ts = 0.0;
let ctx = util::get_metadata_track_samples(stream, size, true, |mut info: SampleInfo, data: &[u8], file_position: u64| {
if size > 0 {
progress_cb(file_position as f64 / size as f64);
}
if let Ok(parsed) = dvtm_wm169::ProductMeta::decode(data) {
let mut tag_map = GroupedTagMap::new();
if let Some(ref clip) = parsed.clip_meta {
self.model = clip.clip_meta_header .as_ref().map(|h| h.product_name.replace("DJI ", ""));
self.frame_readout_time = clip.sensor_readout_time .as_ref().map(|h| h.readout_time as f64 / 1000_000.0);
focal_length = clip.digital_focal_length .as_ref().map(|h| h.focal_length as f64);
distortion_coeffs = clip.distortion_coefficients.as_ref().map(|h| h.coeffients.clone());
if let Some(v) = clip.sensor_fps.as_ref().map(|h| h.sensor_frame_rate as f64) {
sensor_fps = v;
}
if let Some(v) = clip.imu_sampling_rate.as_ref().map(|h| h.imu_sampling_rate as f64) {
sample_rate = v;
}
let v = serde_json::to_value(&clip).map_err(|_| Error::new(ErrorKind::Other, "Serialize error"));
if let Ok(vv) = v {
log::debug!("Metadata: {:?}", &vv);
insert_tag(&mut tag_map, tag!(parsed GroupId::Default, TagId::Metadata, "Metadata", Json, |v| serde_json::to_string(v).unwrap(), vv, vec![]));
}
}
if let Some(ref stream) = parsed.stream_meta {
if let Some(ref meta) = stream.video_stream_meta {
fps = meta.framerate as f64;
}
}
let fps_ratio = fps / sensor_fps;
let mut quats = Vec::new();
if let Some(ref frame) = parsed.frame_meta {
let frame_ts = frame.frame_meta_header.as_ref().unwrap().frame_timestamp as i64;
if info.sample_index == 0 { first_timestamp = frame_ts; }
let frame_relative_ts = frame_ts - first_timestamp;
if let Some(ref e) = frame.camera_frame_meta {
exposure_time = e.exposure_time.as_ref().and_then(|v| Some(*v.exposure_time.get(0)? as f64 / *v.exposure_time.get(1)? as f64)).unwrap_or_default() * 1000.0;
}
if let Some(ref imu) = frame.imu_frame_meta {
if let Some(ref attitude) = imu.imu_attitude_after_fusion {
let len = attitude.attitude.len() as f64;
let vsync_duration = 1000.0 / fps.max(1.0);
if first_vsync == 0 {
first_vsync = attitude.vsync;
}
let frame_timestamp = (frame_relative_ts as f64 / fps_ratio) / 1000.0;
for (i, q) in attitude.attitude.iter().enumerate() {
let index = i as f64 - attitude.offset as f64;
let t = (index / len) * vsync_duration;
let ts = frame_timestamp + t - (exposure_time / 2.0);
prev_ts = ts;
let quat = util::multiply_quats(
(q.quaternion_w as f64,
q.quaternion_x as f64,
q.quaternion_y as f64,
q.quaternion_z as f64),
(0.5, -0.5, -0.5, 0.5),
);
let quat = util::multiply_quats((0.0, 0.0, 1.0, 0.0), (quat.w, quat.x, quat.y, quat.z));
quats.push(TimeQuaternion {
t: ts,
v: quat,
});
}
if info.sample_index == 0 { log::debug!("Quaternions: {:?}", &quats); }
util::insert_tag(&mut tag_map, tag!(parsed GroupId::Quaternion, TagId::Data, "Quaternion data", Vec_TimeQuaternion_f64, |v| format!("{:?}", v), quats, vec![]));
}
}
}
info.tag_map = Some(tag_map);
samples.push(info);
}
}, cancel_flag)?;
match (samples.first_mut(), focal_length, distortion_coeffs) {
(Some(sample), Some(focal_length), Some(coeffs)) if coeffs.len() >= 4 => {
if let Some(tkhd) = ctx.tracks.iter().filter(|x| x.track_type == mp4parse::TrackType::Video).filter_map(|x| x.tkhd.as_ref()).next() {
let (w, h) = (tkhd.width >> 16, tkhd.height >> 16);
let profile = self.get_lens_profile(w, h, focal_length, &coeffs);
if let Some(ref mut tag_map) = sample.tag_map {
insert_tag(tag_map, tag!(parsed GroupId::Lens, TagId::Data, "Lens profile", Json, |v| serde_json::to_string(v).unwrap(), profile, vec![]));
}
}
},
_ => { }
}
Ok(samples)
}
pub fn normalize_imu_orientation(v: String) -> String {
v
}
pub fn camera_type(&self) -> String {
"DJI".to_owned()
}
pub fn frame_readout_time(&self) -> Option<f64> {
self.frame_readout_time
}
fn get_lens_profile(&self, width: u32, height: u32, focal_length: f64, coeffs: &[f32]) -> serde_json::Value {
let model = self.model.clone().unwrap_or_default();
let half_width = width as f64 / 2.0;
let half_height = height as f64 / 2.0;
serde_json::json!({
"calibrated_by": "DJI",
"camera_brand": "DJI",
"camera_model": model,
"calib_dimension": { "w": width, "h": height },
"orig_dimension": { "w": width, "h": height },
"frame_readout_time": self.frame_readout_time,
"official": true,
"fisheye_params": {
"camera_matrix": [
[ focal_length, 0.0, half_width ],
[ 0.0, focal_length, half_height ],
[ 0.0, 0.0, 1.0 ]
],
"distortion_coeffs": coeffs
},
"sync_settings": {
"initial_offset": 0,
"initial_offset_inv": false,
"search_size": 0.5,
"max_sync_points": 5,
"every_nth_frame": 1,
"time_per_syncpoint": 0.6,
"do_autosync": false
},
"calibrator_version": "---"
})
}
}