diff --git a/src/lib.rs b/src/lib.rs index 92319e1..edb6465 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,10 @@ pub use types::*; mod mp4box; pub use mp4box::*; +pub use mp4box::{ + gps::{GpsBox, GpsDataBlockInfo}, + Mp4Box, +}; mod track; pub use track::{Mp4Track, TrackConfig}; diff --git a/src/mp4box/gps.rs b/src/mp4box/gps.rs new file mode 100644 index 0000000..1b78e1a --- /dev/null +++ b/src/mp4box/gps.rs @@ -0,0 +1,103 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +const GPS_DATA_BLOCK_HEADER_SIZE: u64 = 8; +const GPS_DATA_BLOCK_INFO_SIZE: u64 = 8; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)] +pub struct GpsDataBlockInfo { + /// File offset of GPS data block in bytes + pub offset: u32, + /// Size of GPS data block in bytes + pub size: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)] +pub struct GpsBox { + pub version_and_date: u64, + pub data_blocks: Vec, +} + +impl GpsBox { + pub fn get_type(&self) -> BoxType { + BoxType::GpsBox + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + + GPS_DATA_BLOCK_HEADER_SIZE + + (self.data_blocks.len() as u64 * GPS_DATA_BLOCK_INFO_SIZE) + } +} + +impl Mp4Box for GpsBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!( + "version_and_date=0x{:X}, num_blocks={}", + self.version_and_date, + self.data_blocks.len() + ); + Ok(s) + } +} + +impl ReadBox<&mut R> for GpsBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let version_and_date = reader.read_u64::()?; + + // TODO - size checks/etc + let mut data_blocks = Vec::new(); + let count = (size - HEADER_SIZE - GPS_DATA_BLOCK_HEADER_SIZE) / GPS_DATA_BLOCK_INFO_SIZE; + for _ in 0..count { + let offset = reader.read_u32::()?; + let size = reader.read_u32::()?; + if offset == 0 || size == 0 { + // log::warn!("Ignoring block offset={}, size={}", offset, size); + } else { + data_blocks.push(GpsDataBlockInfo { offset, size }); + } + } + + skip_bytes_to(reader, start + size)?; + + Ok(GpsBox { + version_and_date, + data_blocks, + }) + } +} + +impl WriteBox<&mut W> for GpsBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + writer.write_u64::(self.version_and_date)?; + for info in self.data_blocks.iter() { + writer.write_u32::(info.offset)?; + writer.write_u32::(info.size)?; + } + Ok(size) + } +} + +#[cfg(test)] +mod tests { + // TODO +} diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index 0af14b5..ab241fe 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -71,6 +71,7 @@ pub(crate) mod edts; pub(crate) mod elst; pub(crate) mod emsg; pub(crate) mod ftyp; +pub(crate) mod gps; pub(crate) mod hdlr; pub(crate) mod hev1; pub(crate) mod ilst; @@ -198,6 +199,7 @@ boxtype! { DayBox => 0xa9646179, CovrBox => 0x636f7672, DescBox => 0x64657363, + GpsBox => 0x67707320, WideBox => 0x77696465 } diff --git a/src/mp4box/moov.rs b/src/mp4box/moov.rs index ac19381..93daaa8 100644 --- a/src/mp4box/moov.rs +++ b/src/mp4box/moov.rs @@ -3,7 +3,7 @@ use std::io::{Read, Seek, Write}; use crate::meta::MetaBox; use crate::mp4box::*; -use crate::mp4box::{mvex::MvexBox, mvhd::MvhdBox, trak::TrakBox, udta::UdtaBox}; +use crate::mp4box::{gps::GpsBox, mvex::MvexBox, mvhd::MvhdBox, trak::TrakBox, udta::UdtaBox}; #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] pub struct MoovBox { @@ -20,6 +20,9 @@ pub struct MoovBox { #[serde(skip_serializing_if = "Option::is_none")] pub udta: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub gps: Option, } impl MoovBox { @@ -38,6 +41,9 @@ impl MoovBox { if let Some(udta) = &self.udta { size += udta.box_size(); } + if let Some(gps) = &self.gps { + size += gps.box_size(); + } size } } @@ -69,6 +75,7 @@ impl ReadBox<&mut R> for MoovBox { let mut meta = None; let mut udta = None; let mut mvex = None; + let mut gps = None; let mut traks = Vec::new(); let mut current = reader.stream_position()?; @@ -100,6 +107,9 @@ impl ReadBox<&mut R> for MoovBox { BoxType::UdtaBox => { udta = Some(UdtaBox::read_box(reader, s)?); } + BoxType::GpsBox => { + gps = Some(GpsBox::read_box(reader, s)?); + } _ => { // XXX warn!() skip_box(reader, s)?; @@ -121,6 +131,7 @@ impl ReadBox<&mut R> for MoovBox { udta, mvex, traks, + gps, }) } } @@ -140,6 +151,9 @@ impl WriteBox<&mut W> for MoovBox { if let Some(udta) = &self.udta { udta.write_box(writer)?; } + if let Some(gps) = &self.gps { + gps.write_box(writer)?; + } Ok(0) } } @@ -158,6 +172,7 @@ mod tests { traks: vec![], meta: Some(MetaBox::default()), udta: Some(UdtaBox::default()), + gps: Some(GpsBox::default()), }; let mut buf = Vec::new(); diff --git a/src/reader.rs b/src/reader.rs index 5a39eeb..0f3d791 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::io::{Read, Seek}; use std::time::Duration; +use crate::gps::GpsBox; use crate::meta::MetaBox; use crate::*; @@ -172,6 +173,18 @@ impl Mp4Reader { Err(Error::TrakNotFound(track_id)) } } + + pub fn into_inner(self) -> R { + self.reader + } + + pub fn gps_box(&mut self) -> Option { + if self.moov.gps.is_some() { + self.moov.gps.clone() + } else { + None + } + } } impl Mp4Reader {