1use std::io::{ Read, Seek, Write, Result };
5use std::path::*;
6use byteorder::{ BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt };
7use std::time::Instant;
8
9mod desc_reader;
10mod progress_stream;
11mod writer;
12mod insta360;
13use progress_stream::*;
14
15const fn fourcc(s: &str) -> u32 {
28 let s = s.as_bytes();
29 (s[3] as u32) | ((s[2] as u32) << 8) | ((s[1] as u32) << 16) | ((s[0] as u32) << 24)
30}
31const fn has_children(typ: u32, is_read: bool) -> bool {
32 typ == fourcc("moov") || typ == fourcc("trak") || typ == fourcc("edts") ||
33 typ == fourcc("mdia") || typ == fourcc("minf") || typ == fourcc("stbl") ||
34 (typ == fourcc("stsd") && is_read)
35}
36fn typ_to_str(typ: u32) -> String {
37 match String::from_utf8(vec![(typ >> 24) as u8, (typ >> 16) as u8, (typ >> 8) as u8, typ as u8 ]) {
38 Ok(x) => x,
39 Err(_) => format!("{:08X}", typ)
40 }
41}
42
43pub fn read_box<R: Read + Seek>(reader: &mut R) -> Result<(u32, u64, u64, i64)> {
44 let pos = reader.stream_position()?;
45 let size = reader.read_u32::<BigEndian>()?;
46 let typ = reader.read_u32::<BigEndian>()?;
47 if size == 1 {
48 let largesize = reader.read_u64::<BigEndian>()?;
49 Ok((typ, pos, largesize, 16))
50 } else {
51 Ok((typ, pos, size as u64, 8))
52 }
53}
54
55pub fn join_files<P: AsRef<Path>, F: Fn(f64)>(files: &[P], output_file: &P, progress_cb: F) -> Result<()> {
56 let mut open_files = Vec::with_capacity(files.len());
57 for x in files {
58 let f = std::fs::File::open(x)?;
59 let size = f.metadata()?.len() as usize;
60 open_files.push((f, size));
61 }
62 join_file_streams(&mut open_files, std::fs::File::create(output_file)?, progress_cb)
63}
64
65pub fn join_file_streams<F: Fn(f64), I: Read + Seek, O: Read + Write + Seek>(files: &mut [(I, usize)], output_file: O, progress_cb: F) -> Result<()> {
66 let mut desc = desc_reader::Desc::default();
68 desc.moov_tracks.resize(10, Default::default());
69 let mut total_size = 0;
70 let num_files = files.len() as f64;
71 let mut insta360_max_read = None;
72 for (i, fs) in files.iter_mut().enumerate() {
73 let filesize = fs.1;
74 let mut fs = std::io::BufReader::with_capacity(16*1024, &mut fs.0);
75 total_size += filesize;
76
77 { while let Ok((typ, offs, size, header_size)) = read_box(&mut fs) {
79 let org_pos = fs.stream_position()?;
80 if typ == fourcc("mdat") {
81 log::debug!("Reading {}, offset: {}, size: {size}, header_size: {header_size}", typ_to_str(typ), offs);
82 desc.mdat_position.push((None, org_pos, size - header_size as u64));
83 desc.mdat_final_position = org_pos;
84 break;
85 }
86 fs.seek(std::io::SeekFrom::Start(org_pos + size - header_size as u64))?;
87 }
88
89 if insta360_max_read.is_none() {
90 fs.seek(std::io::SeekFrom::End(-40))?;
91 let mut buf = vec![0u8; 40];
92 fs.read_exact(&mut buf)?;
93 if &buf[8..] == insta360::MAGIC {
95 insta360_max_read = Some(filesize as u64 - (&buf[..]).read_u32::<LittleEndian>()? as u64);
96 }
97 }
98
99 fs.seek(std::io::SeekFrom::Start(0))?;
100 }
101
102 desc_reader::read_desc(&mut fs, &mut desc, 0, u64::MAX, i)?;
103
104 if let Some(mdat) = desc.mdat_position.last_mut() {
105 mdat.0 = Some(i);
106 desc.mdat_offset += mdat.2;
107 for t in &mut desc.moov_tracks {
108 t.sample_offset = t.stsz_count;
109 t.chunk_offset = t.stco.len() as u32;
110 }
111 }
112
113 progress_cb(((i as f64 + 1.0) / num_files) * 0.1);
114 }
115
116 let mut debounce = Instant::now();
118 let f_out = ProgressStream::new(output_file, |total| {
119 if (Instant::now() - debounce).as_millis() > 100 {
120 progress_cb((0.1 + ((total as f64 / total_size as f64) * 0.9)).min(0.9999));
121 debounce = Instant::now();
122 }
123 });
124 let mut f_out = std::io::BufWriter::with_capacity(64*1024, f_out);
125
126 writer::get_first(files).seek(std::io::SeekFrom::Start(0))?;
127 writer::rewrite_from_desc(files, &mut f_out, &mut desc, 0, insta360_max_read.unwrap_or(u64::MAX))?;
128
129 for track in &desc.moov_tracks {
131 f_out.seek(std::io::SeekFrom::Start(track.co64_final_position))?;
132 for x in &track.stco {
133 f_out.write_u64::<BigEndian>(*x + desc.mdat_final_position)?;
134 }
135 }
136
137 if insta360_max_read.is_some() {
138 f_out.seek(std::io::SeekFrom::End(0))?;
140 let offsets = insta360::get_insta360_offsets(files)?;
141 insta360::merge_metadata(files, &offsets, f_out)?;
142 }
143
144 progress_cb(1.0);
145
146 Ok(())
147}
148
149pub fn update_file_times(input_path: &PathBuf, output_path: &PathBuf) {
150 if let Err(e) = || -> std::io::Result<()> {
151 let org_time = filetime_creation::FileTime::from_creation_time(&std::fs::metadata(&input_path)?).ok_or(std::io::ErrorKind::Other)?;
152 if cfg!(target_os = "windows") {
153 ::log::debug!("Updating creation time of {} to {}", output_path.display(), org_time.to_string());
154 filetime_creation::set_file_ctime(output_path, org_time)?;
155 } else {
156 ::log::debug!("Updating modification time of {} to {}", output_path.display(), org_time.to_string());
157 filetime_creation::set_file_mtime(output_path, org_time)?;
158 }
159 Ok(())
160 }() {
161 ::log::warn!("Failed to update file times: {e:?}");
162 }
163}