From b3c3b0b180b5455e2a018f047e84c63f4e28c271 Mon Sep 17 00:00:00 2001 From: voluntas Date: Tue, 3 Sep 2024 09:45:40 +0900 Subject: [PATCH 001/103] =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=82=BB=E3=83=B3?= =?UTF-8?q?=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS From addabdaabac35a4ef9bb715d08d45cd467025e6a Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 10:38:46 +0900 Subject: [PATCH 002/103] =?UTF-8?q?=E6=9C=80=E4=BD=8E=E9=99=90=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E3=81=AA=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E7=BE=A4?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Cargo.lock | 7 +++++++ Cargo.toml | 6 ++++++ README.md | 2 ++ src/lib.rs | 1 + 5 files changed, 17 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d15daf7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "shiguredo_mp4" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0ae2ce7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "shiguredo_mp4" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..01b2cc0 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +mp4-rust +======== diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8337712 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +// From 79d0f792544e757da856dee4d8107182f5552abf Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 12:14:55 +0900 Subject: [PATCH 003/103] =?UTF-8?q?=E6=9A=AB=E5=AE=9A=E3=83=88=E3=83=AC?= =?UTF-8?q?=E3=82=A4=E3=83=88=E7=BE=A4=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8337712..4888c46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,41 @@ -// +use std::io::{Read, Write}; + +// TODO: Add Error type + +// 単なる `Box` だと Rust の標準ライブラリのそれと名前が衝突するので変えておく +pub trait BaseBox: Encode + Decode { + fn box_type(&self) -> &[u8]; + + fn box_size(&self) -> std::io::Result { + let mut size = ByteSize(0); + self.encode(&mut size)?; + Ok(size.0) + } +} + +pub trait FullBox: BaseBox { + fn box_version(&self) -> u8; + fn box_flags(&self) -> u32; // u24 +} + +pub trait Encode { + fn encode(&self, writer: W) -> std::io::Result<()>; +} + +pub trait Decode: Sized { + fn decode(reader: R) -> std::io::Result; +} + +#[derive(Debug)] +struct ByteSize(pub u64); + +impl Write for ByteSize { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0 += buf.len() as u64; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} From 9089e0dd6c1cc609c7995ede2154535f15601e82 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 12:30:46 +0900 Subject: [PATCH 004/103] Add test --- tests/decode_test.rs | 10 ++++++++++ tests/testdata/black-h264-video.mp4 | Bin 0 -> 1635 bytes 2 files changed, 10 insertions(+) create mode 100644 tests/decode_test.rs create mode 100644 tests/testdata/black-h264-video.mp4 diff --git a/tests/decode_test.rs b/tests/decode_test.rs new file mode 100644 index 0000000..2542967 --- /dev/null +++ b/tests/decode_test.rs @@ -0,0 +1,10 @@ +use std::io::Read; + +use shiguredo_mp4::{Decode, Mp4File}; + +#[test] +fn decode_black_h264_video_mp4() -> std::io::Result<()> { + let input_bytes = include_bytes!("testdata/black-h264-video.mp4"); + let file = Mp4File::decode(&input_bytes[..])?; + Ok(()) +} diff --git a/tests/testdata/black-h264-video.mp4 b/tests/testdata/black-h264-video.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..a6b0d1a1d915c3511aa0a3f4ee189f4f39020f53 GIT binary patch literal 1635 zcmZ`)O^6&t6t0;ii;9vJR*kSqsT&BGneFb`jJpF38PRH9veljkgF~s@Mxph#WShG+9~b9*tPYBHyKoCw_;< zJj}~z;JZDi=TKI#e4@GUj5|GN?7AH)xS#Sd)x!|zB`WmZq z(S=8#SkdRc+oe&eO2GoSy3{D1=a~jt8?VKY0f=^l?;r&hUnvXjd+vGHr3up}kb00w zYf%T-kwKXxnj636QIjfoAkYPxm*s$^SOqhoM|GY>NMoQBr{k?sF6`DD-Z&PidmaPF~K#e6$WwwoW;He%wi_(F~T6sn6{f_idP5Q zxxNIpKg{{GQl3vc~-9hbZMEu6mb-EZH7ZyZrV z9=_RJdu(C!uGzXqFU^nl7e2b-AyjSnp!w`=XrB^gIl_jy7^N}z37Y#6i*uww?C&1^ zX94kj>y3Ms4?<16$BU@y0LnMbpxP739n&n@xns!;%ryVA-hjNhhjeZM5VFi;qrsPW zuJH}1$VRB+Rf8V)dofjn265Qrt$08m;UF4>?MeHEtj5bvR0B3gSJsN^@ zMHYZ&{xiM^-IZE3w{%wzSya}t%H9DEhp}PcuM1vrtA6felh9bjq_lNBrdC$pgtMQR X(gu76e-1W?-JGCW3999GQ7z|R+NXLI literal 0 HcmV?d00001 From 3080105aa934c4410f2b68445861f2f616944246 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 13:00:23 +0900 Subject: [PATCH 005/103] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E7=9A=84=E3=81=AA?= =?UTF-8?q?=E6=A7=8B=E9=80=A0=E4=BD=93=E3=82=84=E3=83=88=E3=83=AC=E3=82=A4?= =?UTF-8?q?=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 187 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4888c46..64f2c55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,12 +4,15 @@ use std::io::{Read, Write}; // 単なる `Box` だと Rust の標準ライブラリのそれと名前が衝突するので変えておく pub trait BaseBox: Encode + Decode { - fn box_type(&self) -> &[u8]; + fn box_type(&self) -> BoxType; - fn box_size(&self) -> std::io::Result { + fn box_size(&self) -> BoxSize { let mut size = ByteSize(0); - self.encode(&mut size)?; - Ok(size.0) + if self.encode(&mut size).is_err() { + BoxSize::Unknown + } else { + BoxSize::Known(size.0) + } } } @@ -39,3 +42,183 @@ impl Write for ByteSize { Ok(()) } } + +#[derive(Debug, Clone)] +pub struct Mp4File { + // TODO: ftyp_box + pub boxes: Vec, +} + +impl Decode for Mp4File { + fn decode(mut reader: R) -> std::io::Result { + let mut boxes = Vec::new(); + let mut buf = [0]; + while reader.read(&mut buf)? != 0 { + let b = B::decode(buf.chain(&mut reader))?; + boxes.push(b); + } + Ok(Self { boxes }) + } +} + +impl Encode for Mp4File { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + for b in &self.boxes { + b.encode(&mut writer)?; + } + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct BaseBoxHeader { + pub box_type: BoxType, + pub box_size: BoxSize, +} + +impl BaseBoxHeader { + pub fn from_box(b: &B) -> Self { + Self { + box_type: b.box_type(), + box_size: b.box_size(), + } + } +} + +impl Encode for BaseBoxHeader { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + let large_size = self.box_size.get() > u32::MAX as u64; + if large_size { + writer.write_u32(1)?; + } else { + writer.write_u32(self.box_size.get() as u32)?; + } + + match self.box_type { + BoxType::Normal(ty) => { + writer.write_all(&ty)?; + } + BoxType::Uuid(ty) => { + writer.write_all("uuid".as_bytes())?; + writer.write_all(&ty)?; + } + } + + if large_size { + writer.write_u64(self.box_size.get())?; + } + + Ok(()) + } +} + +impl Decode for BaseBoxHeader { + fn decode(reader: R) -> std::io::Result { + todo!() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum BoxType { + Normal([u8; 4]), + Uuid([u8; 16]), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum BoxSize { + Unknown, + Known(u64), +} + +impl BoxSize { + pub const fn get(self) -> u64 { + match self { + BoxSize::Unknown => 0, + BoxSize::Known(v) => v, + } + } +} + +#[derive(Debug, Clone)] +pub struct RawBox { + pub box_type: BoxType, + pub payload: Vec, +} + +impl Encode for RawBox { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + BaseBoxHeader::from_box(self).encode(&mut writer)?; + writer.write_all(&self.payload)?; + Ok(()) + } +} + +impl Decode for RawBox { + fn decode(reader: R) -> std::io::Result { + todo!() + } +} + +impl BaseBox for RawBox { + fn box_type(&self) -> BoxType { + todo!() + } +} + +pub trait WriteExt { + fn write_u8(&mut self, v: u8) -> std::io::Result<()>; + fn write_u16(&mut self, v: u16) -> std::io::Result<()>; + fn write_u32(&mut self, v: u32) -> std::io::Result<()>; + fn write_u64(&mut self, v: u64) -> std::io::Result<()>; +} + +impl WriteExt for T { + fn write_u8(&mut self, v: u8) -> std::io::Result<()> { + self.write_all(&[v]) + } + + fn write_u16(&mut self, v: u16) -> std::io::Result<()> { + self.write_all(&v.to_be_bytes()) + } + + fn write_u32(&mut self, v: u32) -> std::io::Result<()> { + self.write_all(&v.to_be_bytes()) + } + + fn write_u64(&mut self, v: u64) -> std::io::Result<()> { + self.write_all(&v.to_be_bytes()) + } +} + +pub trait ReadExt { + fn read_u8(&mut self) -> std::io::Result; + fn read_u16(&mut self) -> std::io::Result; + fn read_u32(&mut self) -> std::io::Result; + fn read_u64(&mut self) -> std::io::Result; +} + +impl ReadExt for T { + fn read_u8(&mut self) -> std::io::Result { + let mut buf = [0; 1]; + self.read_exact(&mut buf)?; + Ok(buf[0]) + } + + fn read_u16(&mut self) -> std::io::Result { + let mut buf = [0; 2]; + self.read_exact(&mut buf)?; + Ok(u16::from_be_bytes(buf)) + } + + fn read_u32(&mut self) -> std::io::Result { + let mut buf = [0; 4]; + self.read_exact(&mut buf)?; + Ok(u32::from_be_bytes(buf)) + } + + fn read_u64(&mut self) -> std::io::Result { + let mut buf = [0; 8]; + self.read_exact(&mut buf)?; + Ok(u64::from_be_bytes(buf)) + } +} From b45318f908e30d97415d570bffc5237d12b0966c Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 14:20:55 +0900 Subject: [PATCH 006/103] Add RawBox --- src/lib.rs | 127 +++++++++++++++++++++++++++++++++++++++---- tests/decode_test.rs | 7 +-- 2 files changed, 120 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 64f2c55..3860239 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,7 @@ -use std::io::{Read, Write}; +use std::{ + io::{ErrorKind, Read, Write}, + num::NonZeroU64, +}; // TODO: Add Error type @@ -10,8 +13,10 @@ pub trait BaseBox: Encode + Decode { let mut size = ByteSize(0); if self.encode(&mut size).is_err() { BoxSize::Unknown + } else if let Some(n) = NonZeroU64::new(size.0) { + BoxSize::Known(n) } else { - BoxSize::Known(size.0) + BoxSize::Unknown } } } @@ -83,6 +88,37 @@ impl BaseBoxHeader { box_size: b.box_size(), } } + + pub fn header_size(self) -> usize { + let mut size = 0; + + if matches!(self.box_type, BoxType::Normal(_)) { + size += 4; + } else { + size += 20; + } + + if matches!(self.box_size, BoxSize::Known(_)) { + size += 4; + } else { + size += 12; + } + + size + } + + pub fn payload_size(self) -> std::io::Result> { + match self.box_size { + BoxSize::Unknown => Ok(None), + BoxSize::Known(size) => { + let payload_size = size + .get() + .checked_sub(self.header_size() as u64) + .ok_or_else(|| ErrorKind::InvalidData)?; // TODO: error message + Ok(Some(payload_size)) + } + } + } } impl Encode for BaseBoxHeader { @@ -113,28 +149,74 @@ impl Encode for BaseBoxHeader { } impl Decode for BaseBoxHeader { - fn decode(reader: R) -> std::io::Result { - todo!() + fn decode(mut reader: R) -> std::io::Result { + let mut box_size = reader.read_u32()? as u64; + + let mut box_type = [0; 4]; + reader.read_exact(&mut box_type)?; + + let box_type = if box_type == [b'u', b'u', b'i', b'd'] { + let mut box_type = [0; 16]; + reader.read_exact(&mut box_type)?; + BoxType::Uuid(box_type) + } else { + BoxType::Normal(box_type) + }; + + if box_size == 1 { + box_size = reader.read_u64()?; + } + let box_size = if let Some(n) = NonZeroU64::new(box_size) { + BoxSize::Known(n) + } else { + BoxSize::Unknown + }; + + Ok(Self { box_type, box_size }) } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum BoxType { Normal([u8; 4]), Uuid([u8; 16]), } +impl BoxType { + pub fn as_bytes(&self) -> &[u8] { + match self { + BoxType::Normal(ty) => &ty[..], + BoxType::Uuid(ty) => &ty[..], + } + } +} + +impl std::fmt::Debug for BoxType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BoxType::Normal(ty) => { + if let Ok(ty) = std::str::from_utf8(ty) { + f.debug_tuple("BoxType").field(&ty).finish() + } else { + f.debug_tuple("BoxType").field(ty).finish() + } + } + BoxType::Uuid(ty) => f.debug_tuple("BoxType").field(ty).finish(), + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum BoxSize { Unknown, - Known(u64), + Known(NonZeroU64), } impl BoxSize { pub const fn get(self) -> u64 { match self { BoxSize::Unknown => 0, - BoxSize::Known(v) => v, + BoxSize::Known(v) => v.get(), } } } @@ -142,6 +224,7 @@ impl BoxSize { #[derive(Debug, Clone)] pub struct RawBox { pub box_type: BoxType, + pub box_size: BoxSize, pub payload: Vec, } @@ -154,14 +237,38 @@ impl Encode for RawBox { } impl Decode for RawBox { - fn decode(reader: R) -> std::io::Result { - todo!() + fn decode(mut reader: R) -> std::io::Result { + let header = BaseBoxHeader::decode(&mut reader)?; + dbg!(header); + + let mut payload = Vec::new(); + match header.payload_size()? { + None => { + reader.read_to_end(&mut payload)?; + } + Some(size) => { + reader.take(size).read_to_end(&mut payload)?; + if payload.len() as u64 != size { + // TODO: error message + return Err(std::io::ErrorKind::InvalidData.into()); + } + } + } + Ok(Self { + box_type: header.box_type, + box_size: header.box_size, + payload, + }) } } impl BaseBox for RawBox { fn box_type(&self) -> BoxType { - todo!() + self.box_type + } + + fn box_size(&self) -> BoxSize { + self.box_size } } diff --git a/tests/decode_test.rs b/tests/decode_test.rs index 2542967..b1cebd3 100644 --- a/tests/decode_test.rs +++ b/tests/decode_test.rs @@ -1,10 +1,9 @@ -use std::io::Read; - -use shiguredo_mp4::{Decode, Mp4File}; +use shiguredo_mp4::{Decode, Mp4File, RawBox}; #[test] fn decode_black_h264_video_mp4() -> std::io::Result<()> { let input_bytes = include_bytes!("testdata/black-h264-video.mp4"); - let file = Mp4File::decode(&input_bytes[..])?; + let file = Mp4File::::decode(&input_bytes[..])?; + assert_eq!(file.boxes.len(), 2); // TODO Ok(()) } From 0213c43abb7ee40332ae5fc5cb861d1a88187795 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 15:34:02 +0900 Subject: [PATCH 007/103] =?UTF-8?q?=E3=81=84=E3=82=8D=E3=81=84=E3=82=8D?= =?UTF-8?q?=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 243 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 150 insertions(+), 93 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3860239..56e0e34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,4 @@ -use std::{ - io::{ErrorKind, Read, Write}, - num::NonZeroU64, -}; +use std::io::{Read, Write}; // TODO: Add Error type @@ -10,15 +7,10 @@ pub trait BaseBox: Encode + Decode { fn box_type(&self) -> BoxType; fn box_size(&self) -> BoxSize { - let mut size = ByteSize(0); - if self.encode(&mut size).is_err() { - BoxSize::Unknown - } else if let Some(n) = NonZeroU64::new(size.0) { - BoxSize::Known(n) - } else { - BoxSize::Unknown - } + BoxSize::with_payload_size(self.box_type(), self.box_payload_size()) } + + fn box_payload_size(&self) -> u64; } pub trait FullBox: BaseBox { @@ -34,20 +26,6 @@ pub trait Decode: Sized { fn decode(reader: R) -> std::io::Result; } -#[derive(Debug)] -struct ByteSize(pub u64); - -impl Write for ByteSize { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.0 += buf.len() as u64; - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - #[derive(Debug, Clone)] pub struct Mp4File { // TODO: ftyp_box @@ -76,52 +54,46 @@ impl Encode for Mp4File { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct BaseBoxHeader { +pub struct BoxHeader { pub box_type: BoxType, pub box_size: BoxSize, } -impl BaseBoxHeader { +impl BoxHeader { pub fn from_box(b: &B) -> Self { - Self { - box_type: b.box_type(), - box_size: b.box_size(), - } + let box_type = b.box_type(); + let box_size = b.box_size(); + Self { box_type, box_size } } pub fn header_size(self) -> usize { - let mut size = 0; - - if matches!(self.box_type, BoxType::Normal(_)) { - size += 4; - } else { - size += 20; - } + self.box_type.external_size() + self.box_size.external_size() + } - if matches!(self.box_size, BoxSize::Known(_)) { - size += 4; + pub fn with_box_payload_reader(self, reader: R, f: F) -> std::io::Result + where + F: FnOnce(&mut std::io::Take) -> std::io::Result, + { + let mut reader = if self.box_size.get() == 0 { + reader.take(u64::MAX) } else { - size += 12; - } - - size - } + let payload_size = self + .box_size + .get() + .checked_sub(self.header_size() as u64) + .ok_or_else(|| std::io::ErrorKind::InvalidData)?; // TODO: error message + reader.take(payload_size) + }; - pub fn payload_size(self) -> std::io::Result> { - match self.box_size { - BoxSize::Unknown => Ok(None), - BoxSize::Known(size) => { - let payload_size = size - .get() - .checked_sub(self.header_size() as u64) - .ok_or_else(|| ErrorKind::InvalidData)?; // TODO: error message - Ok(Some(payload_size)) - } + let value = f(&mut reader)?; + if reader.limit() != 0 { + return Err(std::io::ErrorKind::InvalidData.into()); } + Ok(value) } } -impl Encode for BaseBoxHeader { +impl Encode for BoxHeader { fn encode(&self, mut writer: W) -> std::io::Result<()> { let large_size = self.box_size.get() > u32::MAX as u64; if large_size { @@ -148,7 +120,7 @@ impl Encode for BaseBoxHeader { } } -impl Decode for BaseBoxHeader { +impl Decode for BoxHeader { fn decode(mut reader: R) -> std::io::Result { let mut box_size = reader.read_u32()? as u64; @@ -166,16 +138,48 @@ impl Decode for BaseBoxHeader { if box_size == 1 { box_size = reader.read_u64()?; } - let box_size = if let Some(n) = NonZeroU64::new(box_size) { - BoxSize::Known(n) - } else { - BoxSize::Unknown - }; + let box_size = + BoxSize::new(box_type, box_size).ok_or_else(|| std::io::ErrorKind::InvalidData)?; // TODO: error message Ok(Self { box_type, box_size }) } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct BoxSize(u64); + +impl BoxSize { + pub const VARIABLE_SIZE: Self = Self(0); + + pub fn new(box_type: BoxType, box_size: u64) -> Option { + if box_size == 0 { + return Some(Self(0)); + } + + if box_size < 4 + box_type.external_size() as u64 { + None + } else { + Some(Self(box_size)) + } + } + + pub const fn with_payload_size(box_type: BoxType, payload_size: u64) -> Self { + Self(box_type.external_size() as u64 + payload_size) + } + + pub const fn get(self) -> u64 { + self.0 + } + + pub const fn external_size(self) -> usize { + if self.0 > u32::MAX as u64 { + 4 + 8 + } else { + 4 + } + } +} + #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum BoxType { Normal([u8; 4]), @@ -189,6 +193,15 @@ impl BoxType { BoxType::Uuid(ty) => &ty[..], } } + + // TODO: rename + pub const fn external_size(self) -> usize { + if matches!(self, Self::Normal(_)) { + 4 + } else { + 4 + 16 + } + } } impl std::fmt::Debug for BoxType { @@ -206,22 +219,7 @@ impl std::fmt::Debug for BoxType { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum BoxSize { - Unknown, - Known(NonZeroU64), -} - -impl BoxSize { - pub const fn get(self) -> u64 { - match self { - BoxSize::Unknown => 0, - BoxSize::Known(v) => v.get(), - } - } -} - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct RawBox { pub box_type: BoxType, pub box_size: BoxSize, @@ -230,7 +228,7 @@ pub struct RawBox { impl Encode for RawBox { fn encode(&self, mut writer: W) -> std::io::Result<()> { - BaseBoxHeader::from_box(self).encode(&mut writer)?; + BoxHeader::from_box(self).encode(&mut writer)?; writer.write_all(&self.payload)?; Ok(()) } @@ -238,22 +236,11 @@ impl Encode for RawBox { impl Decode for RawBox { fn decode(mut reader: R) -> std::io::Result { - let header = BaseBoxHeader::decode(&mut reader)?; + let header = BoxHeader::decode(&mut reader)?; dbg!(header); let mut payload = Vec::new(); - match header.payload_size()? { - None => { - reader.read_to_end(&mut payload)?; - } - Some(size) => { - reader.take(size).read_to_end(&mut payload)?; - if payload.len() as u64 != size { - // TODO: error message - return Err(std::io::ErrorKind::InvalidData.into()); - } - } - } + header.with_box_payload_reader(reader, |reader| reader.read_to_end(&mut payload))?; Ok(Self { box_type: header.box_type, box_size: header.box_size, @@ -270,8 +257,78 @@ impl BaseBox for RawBox { fn box_size(&self) -> BoxSize { self.box_size } + + fn box_payload_size(&self) -> u64 { + self.payload.len() as u64 + } } +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Brand([u8; 4]); + +impl Brand { + pub const fn new(brand: [u8; 4]) -> Self { + Self(brand) + } + + pub const fn get(self) -> [u8; 4] { + self.0 + } +} + +impl std::fmt::Debug for Brand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Ok(s) = std::str::from_utf8(&self.0) { + f.debug_tuple("Brand").field(&s).finish() + } else { + f.debug_tuple("Brand").field(&self.0).finish() + } + } +} + +impl Encode for Brand { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&self.0) + } +} + +impl Decode for Brand { + fn decode(mut reader: R) -> std::io::Result { + let mut buf = [0; 4]; + reader.read_exact(&mut buf)?; + Ok(Self(buf)) + } +} + +// /// [ISO/IEC 14496-12] FileTypeBox class +// #[derive(Debug, Clone, PartialEq, Eq)] +// pub struct FtypBox { +// pub major_brand: Brand, +// pub minor_version: u32, +// pub compatible_brands: Vec, +// } + +// impl Encode for FtypBox { +// fn encode(&self, mut writer: W) -> std::io::Result<()> { +// BoxHeader::from_box(self).encode(&mut writer)?; + +// self.major_brand.encode(&mut writer)?; +// writer.write_u32(self.minor_version)?; +// for brand in &self.compatible_brands { +// brand.encode(&mut writer)?; +// } +// Ok(()) +// } +// } + +// impl Decode for FtypBox { +// fn decode(mut reader: R) -> std::io::Result { +// let major_brand = Brand::decode(&mut reader)?; +// let minor_version = reader.read_u32()?; +// todo!(); +// } +// } + pub trait WriteExt { fn write_u8(&mut self, v: u8) -> std::io::Result<()>; fn write_u16(&mut self, v: u16) -> std::io::Result<()>; From 534ed6def6cbef056ba2e59c3fa57fbe2bff40d1 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 15:40:10 +0900 Subject: [PATCH 008/103] Remove WriteExt and ReadExt --- src/lib.rs | 124 ++++++++++++++++++++++++++--------------------------- 1 file changed, 61 insertions(+), 63 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 56e0e34..2feb20f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,10 +22,66 @@ pub trait Encode { fn encode(&self, writer: W) -> std::io::Result<()>; } +impl Encode for u8 { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&self.to_be_bytes()) + } +} + +impl Encode for u16 { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&self.to_be_bytes()) + } +} + +impl Encode for u32 { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&self.to_be_bytes()) + } +} + +impl Encode for u64 { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&self.to_be_bytes()) + } +} + pub trait Decode: Sized { fn decode(reader: R) -> std::io::Result; } +impl Decode for u8 { + fn decode(mut reader: R) -> std::io::Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + +impl Decode for u16 { + fn decode(mut reader: R) -> std::io::Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + +impl Decode for u32 { + fn decode(mut reader: R) -> std::io::Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + +impl Decode for u64 { + fn decode(mut reader: R) -> std::io::Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + #[derive(Debug, Clone)] pub struct Mp4File { // TODO: ftyp_box @@ -97,9 +153,9 @@ impl Encode for BoxHeader { fn encode(&self, mut writer: W) -> std::io::Result<()> { let large_size = self.box_size.get() > u32::MAX as u64; if large_size { - writer.write_u32(1)?; + 1u32.encode(&mut writer)?; } else { - writer.write_u32(self.box_size.get() as u32)?; + (self.box_size.get() as u32).encode(&mut writer)?; } match self.box_type { @@ -113,7 +169,7 @@ impl Encode for BoxHeader { } if large_size { - writer.write_u64(self.box_size.get())?; + self.box_size.get().encode(&mut writer)?; } Ok(()) @@ -122,7 +178,7 @@ impl Encode for BoxHeader { impl Decode for BoxHeader { fn decode(mut reader: R) -> std::io::Result { - let mut box_size = reader.read_u32()? as u64; + let mut box_size = u32::decode(&mut reader)? as u64; let mut box_type = [0; 4]; reader.read_exact(&mut box_type)?; @@ -136,7 +192,7 @@ impl Decode for BoxHeader { }; if box_size == 1 { - box_size = reader.read_u64()?; + box_size = u64::decode(&mut reader)?; } let box_size = BoxSize::new(box_type, box_size).ok_or_else(|| std::io::ErrorKind::InvalidData)?; // TODO: error message @@ -328,61 +384,3 @@ impl Decode for Brand { // todo!(); // } // } - -pub trait WriteExt { - fn write_u8(&mut self, v: u8) -> std::io::Result<()>; - fn write_u16(&mut self, v: u16) -> std::io::Result<()>; - fn write_u32(&mut self, v: u32) -> std::io::Result<()>; - fn write_u64(&mut self, v: u64) -> std::io::Result<()>; -} - -impl WriteExt for T { - fn write_u8(&mut self, v: u8) -> std::io::Result<()> { - self.write_all(&[v]) - } - - fn write_u16(&mut self, v: u16) -> std::io::Result<()> { - self.write_all(&v.to_be_bytes()) - } - - fn write_u32(&mut self, v: u32) -> std::io::Result<()> { - self.write_all(&v.to_be_bytes()) - } - - fn write_u64(&mut self, v: u64) -> std::io::Result<()> { - self.write_all(&v.to_be_bytes()) - } -} - -pub trait ReadExt { - fn read_u8(&mut self) -> std::io::Result; - fn read_u16(&mut self) -> std::io::Result; - fn read_u32(&mut self) -> std::io::Result; - fn read_u64(&mut self) -> std::io::Result; -} - -impl ReadExt for T { - fn read_u8(&mut self) -> std::io::Result { - let mut buf = [0; 1]; - self.read_exact(&mut buf)?; - Ok(buf[0]) - } - - fn read_u16(&mut self) -> std::io::Result { - let mut buf = [0; 2]; - self.read_exact(&mut buf)?; - Ok(u16::from_be_bytes(buf)) - } - - fn read_u32(&mut self) -> std::io::Result { - let mut buf = [0; 4]; - self.read_exact(&mut buf)?; - Ok(u32::from_be_bytes(buf)) - } - - fn read_u64(&mut self) -> std::io::Result { - let mut buf = [0; 8]; - self.read_exact(&mut buf)?; - Ok(u64::from_be_bytes(buf)) - } -} From ceca59699e56354cc28c4e6313abcc458e9cc6ef Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 15:42:13 +0900 Subject: [PATCH 009/103] Add io module --- src/io.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 68 +++--------------------------------------------------- 2 files changed, 68 insertions(+), 65 deletions(-) create mode 100644 src/io.rs diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..72dfeb9 --- /dev/null +++ b/src/io.rs @@ -0,0 +1,65 @@ +use std::io::{Read, Write}; + +pub trait Encode { + fn encode(&self, writer: W) -> std::io::Result<()>; +} + +impl Encode for u8 { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&self.to_be_bytes()) + } +} + +impl Encode for u16 { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&self.to_be_bytes()) + } +} + +impl Encode for u32 { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&self.to_be_bytes()) + } +} + +impl Encode for u64 { + fn encode(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&self.to_be_bytes()) + } +} + +pub trait Decode: Sized { + fn decode(reader: R) -> std::io::Result; +} + +impl Decode for u8 { + fn decode(mut reader: R) -> std::io::Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + +impl Decode for u16 { + fn decode(mut reader: R) -> std::io::Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + +impl Decode for u32 { + fn decode(mut reader: R) -> std::io::Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + +impl Decode for u64 { + fn decode(mut reader: R) -> std::io::Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 2feb20f..a36bf4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ use std::io::{Read, Write}; -// TODO: Add Error type +mod io; + +pub use io::{Decode, Encode}; // 単なる `Box` だと Rust の標準ライブラリのそれと名前が衝突するので変えておく pub trait BaseBox: Encode + Decode { @@ -18,70 +20,6 @@ pub trait FullBox: BaseBox { fn box_flags(&self) -> u32; // u24 } -pub trait Encode { - fn encode(&self, writer: W) -> std::io::Result<()>; -} - -impl Encode for u8 { - fn encode(&self, mut writer: W) -> std::io::Result<()> { - writer.write_all(&self.to_be_bytes()) - } -} - -impl Encode for u16 { - fn encode(&self, mut writer: W) -> std::io::Result<()> { - writer.write_all(&self.to_be_bytes()) - } -} - -impl Encode for u32 { - fn encode(&self, mut writer: W) -> std::io::Result<()> { - writer.write_all(&self.to_be_bytes()) - } -} - -impl Encode for u64 { - fn encode(&self, mut writer: W) -> std::io::Result<()> { - writer.write_all(&self.to_be_bytes()) - } -} - -pub trait Decode: Sized { - fn decode(reader: R) -> std::io::Result; -} - -impl Decode for u8 { - fn decode(mut reader: R) -> std::io::Result { - let mut buf = [0; Self::BITS as usize / 8]; - reader.read_exact(&mut buf)?; - Ok(Self::from_be_bytes(buf)) - } -} - -impl Decode for u16 { - fn decode(mut reader: R) -> std::io::Result { - let mut buf = [0; Self::BITS as usize / 8]; - reader.read_exact(&mut buf)?; - Ok(Self::from_be_bytes(buf)) - } -} - -impl Decode for u32 { - fn decode(mut reader: R) -> std::io::Result { - let mut buf = [0; Self::BITS as usize / 8]; - reader.read_exact(&mut buf)?; - Ok(Self::from_be_bytes(buf)) - } -} - -impl Decode for u64 { - fn decode(mut reader: R) -> std::io::Result { - let mut buf = [0; Self::BITS as usize / 8]; - reader.read_exact(&mut buf)?; - Ok(Self::from_be_bytes(buf)) - } -} - #[derive(Debug, Clone)] pub struct Mp4File { // TODO: ftyp_box From e049e941c3338446ce7cf1f1aa1656312327d084 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 15:53:52 +0900 Subject: [PATCH 010/103] Add io module --- src/io.rs | 85 ++++++++++++++++++++++++++++++++++++-------- src/lib.rs | 50 +++++++++++++++++--------- tests/decode_test.rs | 4 +-- 3 files changed, 105 insertions(+), 34 deletions(-) diff --git a/src/io.rs b/src/io.rs index 72dfeb9..40b2f4b 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,39 +1,94 @@ -use std::io::{Read, Write}; +use std::{ + backtrace::Backtrace, + io::{ErrorKind, Read, Write}, +}; + +pub type Result = std::result::Result; + +pub struct Error { + io: std::io::Error, + trace: Backtrace, +} + +impl Error { + pub(crate) fn invalid_data(message: &str) -> Self { + Self::from(std::io::Error::new(ErrorKind::InvalidData, message)) + } + + pub fn kind(&self) -> ErrorKind { + self.io.kind() + } +} + +impl From for Error { + fn from(value: std::io::Error) -> Self { + Self { + io: value, + trace: Backtrace::capture(), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.io) + } +} + +impl std::fmt::Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self}") + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.trace.status() == std::backtrace::BacktraceStatus::Captured { + write!(f, "{}\n\nBacktrace:\n{}", self.io, self.trace) + } else { + write!(f, "{}", self.io) + } + } +} pub trait Encode { - fn encode(&self, writer: W) -> std::io::Result<()>; + fn encode(&self, writer: W) -> Result<()>; } impl Encode for u8 { - fn encode(&self, mut writer: W) -> std::io::Result<()> { - writer.write_all(&self.to_be_bytes()) + fn encode(&self, mut writer: W) -> Result<()> { + writer.write_all(&self.to_be_bytes())?; + Ok(()) } } impl Encode for u16 { - fn encode(&self, mut writer: W) -> std::io::Result<()> { - writer.write_all(&self.to_be_bytes()) + fn encode(&self, mut writer: W) -> Result<()> { + writer.write_all(&self.to_be_bytes())?; + Ok(()) } } impl Encode for u32 { - fn encode(&self, mut writer: W) -> std::io::Result<()> { - writer.write_all(&self.to_be_bytes()) + fn encode(&self, mut writer: W) -> Result<()> { + writer.write_all(&self.to_be_bytes())?; + Ok(()) } } impl Encode for u64 { - fn encode(&self, mut writer: W) -> std::io::Result<()> { - writer.write_all(&self.to_be_bytes()) + fn encode(&self, mut writer: W) -> Result<()> { + writer.write_all(&self.to_be_bytes())?; + Ok(()) } } pub trait Decode: Sized { - fn decode(reader: R) -> std::io::Result; + fn decode(reader: R) -> Result; } impl Decode for u8 { - fn decode(mut reader: R) -> std::io::Result { + fn decode(mut reader: R) -> Result { let mut buf = [0; Self::BITS as usize / 8]; reader.read_exact(&mut buf)?; Ok(Self::from_be_bytes(buf)) @@ -41,7 +96,7 @@ impl Decode for u8 { } impl Decode for u16 { - fn decode(mut reader: R) -> std::io::Result { + fn decode(mut reader: R) -> Result { let mut buf = [0; Self::BITS as usize / 8]; reader.read_exact(&mut buf)?; Ok(Self::from_be_bytes(buf)) @@ -49,7 +104,7 @@ impl Decode for u16 { } impl Decode for u32 { - fn decode(mut reader: R) -> std::io::Result { + fn decode(mut reader: R) -> Result { let mut buf = [0; Self::BITS as usize / 8]; reader.read_exact(&mut buf)?; Ok(Self::from_be_bytes(buf)) @@ -57,7 +112,7 @@ impl Decode for u32 { } impl Decode for u64 { - fn decode(mut reader: R) -> std::io::Result { + fn decode(mut reader: R) -> Result { let mut buf = [0; Self::BITS as usize / 8]; reader.read_exact(&mut buf)?; Ok(Self::from_be_bytes(buf)) diff --git a/src/lib.rs b/src/lib.rs index a36bf4e..4d5d251 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use std::io::{Read, Write}; mod io; -pub use io::{Decode, Encode}; +pub use io::{Decode, Encode, Error, Result}; // 単なる `Box` だと Rust の標準ライブラリのそれと名前が衝突するので変えておく pub trait BaseBox: Encode + Decode { @@ -27,7 +27,7 @@ pub struct Mp4File { } impl Decode for Mp4File { - fn decode(mut reader: R) -> std::io::Result { + fn decode(mut reader: R) -> Result { let mut boxes = Vec::new(); let mut buf = [0]; while reader.read(&mut buf)? != 0 { @@ -39,7 +39,7 @@ impl Decode for Mp4File { } impl Encode for Mp4File { - fn encode(&self, mut writer: W) -> std::io::Result<()> { + fn encode(&self, mut writer: W) -> Result<()> { for b in &self.boxes { b.encode(&mut writer)?; } @@ -64,9 +64,9 @@ impl BoxHeader { self.box_type.external_size() + self.box_size.external_size() } - pub fn with_box_payload_reader(self, reader: R, f: F) -> std::io::Result + pub fn with_box_payload_reader(self, reader: R, f: F) -> Result where - F: FnOnce(&mut std::io::Take) -> std::io::Result, + F: FnOnce(&mut std::io::Take) -> Result, { let mut reader = if self.box_size.get() == 0 { reader.take(u64::MAX) @@ -75,20 +75,30 @@ impl BoxHeader { .box_size .get() .checked_sub(self.header_size() as u64) - .ok_or_else(|| std::io::ErrorKind::InvalidData)?; // TODO: error message + .ok_or_else(|| { + Error::invalid_data(&format!( + "Too small box size: actual={}, expected={} or more", + self.box_size.get(), + self.header_size() + )) + })?; reader.take(payload_size) }; let value = f(&mut reader)?; if reader.limit() != 0 { - return Err(std::io::ErrorKind::InvalidData.into()); + return Err(Error::invalid_data(&format!( + "Unconsumed {} bytes at the end of the box {:?}", + reader.limit(), + self.box_type + ))); } Ok(value) } } impl Encode for BoxHeader { - fn encode(&self, mut writer: W) -> std::io::Result<()> { + fn encode(&self, mut writer: W) -> Result<()> { let large_size = self.box_size.get() > u32::MAX as u64; if large_size { 1u32.encode(&mut writer)?; @@ -115,7 +125,7 @@ impl Encode for BoxHeader { } impl Decode for BoxHeader { - fn decode(mut reader: R) -> std::io::Result { + fn decode(mut reader: R) -> Result { let mut box_size = u32::decode(&mut reader)? as u64; let mut box_type = [0; 4]; @@ -132,8 +142,13 @@ impl Decode for BoxHeader { if box_size == 1 { box_size = u64::decode(&mut reader)?; } - let box_size = - BoxSize::new(box_type, box_size).ok_or_else(|| std::io::ErrorKind::InvalidData)?; // TODO: error message + let box_size = BoxSize::new(box_type, box_size).ok_or_else(|| { + Error::invalid_data(&format!( + "Too small box size: actual={}, expected={} or more", + box_size, + 4 + box_type.external_size() + )) + })?; Ok(Self { box_type, box_size }) } @@ -221,7 +236,7 @@ pub struct RawBox { } impl Encode for RawBox { - fn encode(&self, mut writer: W) -> std::io::Result<()> { + fn encode(&self, mut writer: W) -> Result<()> { BoxHeader::from_box(self).encode(&mut writer)?; writer.write_all(&self.payload)?; Ok(()) @@ -229,12 +244,12 @@ impl Encode for RawBox { } impl Decode for RawBox { - fn decode(mut reader: R) -> std::io::Result { + fn decode(mut reader: R) -> Result { let header = BoxHeader::decode(&mut reader)?; dbg!(header); let mut payload = Vec::new(); - header.with_box_payload_reader(reader, |reader| reader.read_to_end(&mut payload))?; + header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; Ok(Self { box_type: header.box_type, box_size: header.box_size, @@ -281,13 +296,14 @@ impl std::fmt::Debug for Brand { } impl Encode for Brand { - fn encode(&self, mut writer: W) -> std::io::Result<()> { - writer.write_all(&self.0) + fn encode(&self, mut writer: W) -> Result<()> { + writer.write_all(&self.0)?; + Ok(()) } } impl Decode for Brand { - fn decode(mut reader: R) -> std::io::Result { + fn decode(mut reader: R) -> Result { let mut buf = [0; 4]; reader.read_exact(&mut buf)?; Ok(Self(buf)) diff --git a/tests/decode_test.rs b/tests/decode_test.rs index b1cebd3..1c9aae2 100644 --- a/tests/decode_test.rs +++ b/tests/decode_test.rs @@ -1,7 +1,7 @@ -use shiguredo_mp4::{Decode, Mp4File, RawBox}; +use shiguredo_mp4::{Decode, Mp4File, RawBox, Result}; #[test] -fn decode_black_h264_video_mp4() -> std::io::Result<()> { +fn decode_black_h264_video_mp4() -> Result<()> { let input_bytes = include_bytes!("testdata/black-h264-video.mp4"); let file = Mp4File::::decode(&input_bytes[..])?; assert_eq!(file.boxes.len(), 2); // TODO From 6ec969da83f6639447364191bba638b2056b272e Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 16:02:00 +0900 Subject: [PATCH 011/103] Update Error --- src/io.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/io.rs b/src/io.rs index 40b2f4b..ad8429b 100644 --- a/src/io.rs +++ b/src/io.rs @@ -6,32 +6,28 @@ use std::{ pub type Result = std::result::Result; pub struct Error { - io: std::io::Error, - trace: Backtrace, + pub io_error: std::io::Error, + pub backtrace: Backtrace, } impl Error { pub(crate) fn invalid_data(message: &str) -> Self { Self::from(std::io::Error::new(ErrorKind::InvalidData, message)) } - - pub fn kind(&self) -> ErrorKind { - self.io.kind() - } } impl From for Error { fn from(value: std::io::Error) -> Self { Self { - io: value, - trace: Backtrace::capture(), + io_error: value, + backtrace: Backtrace::capture(), } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.io) + Some(&self.io_error) } } @@ -43,10 +39,10 @@ impl std::fmt::Debug for Error { impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.trace.status() == std::backtrace::BacktraceStatus::Captured { - write!(f, "{}\n\nBacktrace:\n{}", self.io, self.trace) + if self.backtrace.status() == std::backtrace::BacktraceStatus::Captured { + write!(f, "{}\n\nBacktrace:\n{}", self.io_error, self.backtrace) } else { - write!(f, "{}", self.io) + write!(f, "{}", self.io_error) } } } From 517bd268233fa6324321ec6840475a5e2251b955 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 16:05:38 +0900 Subject: [PATCH 012/103] Add basic_types module --- src/basic_types.rs | 270 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 270 +-------------------------------------------- 2 files changed, 272 insertions(+), 268 deletions(-) create mode 100644 src/basic_types.rs diff --git a/src/basic_types.rs b/src/basic_types.rs new file mode 100644 index 0000000..4f285e1 --- /dev/null +++ b/src/basic_types.rs @@ -0,0 +1,270 @@ +use std::io::{Read, Write}; + +use crate::{Decode, Encode, Error, Result}; + +// 単なる `Box` だと Rust の標準ライブラリのそれと名前が衝突するので変えておく +pub trait BaseBox: Encode + Decode { + fn box_type(&self) -> BoxType; + + fn box_size(&self) -> BoxSize { + BoxSize::with_payload_size(self.box_type(), self.box_payload_size()) + } + + fn box_payload_size(&self) -> u64; +} + +pub trait FullBox: BaseBox { + fn box_version(&self) -> u8; + fn box_flags(&self) -> u32; // u24 +} + +#[derive(Debug, Clone)] +pub struct Mp4File { + // TODO: ftyp_box + pub boxes: Vec, +} + +impl Decode for Mp4File { + fn decode(mut reader: R) -> Result { + let mut boxes = Vec::new(); + let mut buf = [0]; + while reader.read(&mut buf)? != 0 { + let b = B::decode(buf.chain(&mut reader))?; + boxes.push(b); + } + Ok(Self { boxes }) + } +} + +impl Encode for Mp4File { + fn encode(&self, mut writer: W) -> Result<()> { + for b in &self.boxes { + b.encode(&mut writer)?; + } + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct BoxHeader { + pub box_type: BoxType, + pub box_size: BoxSize, +} + +impl BoxHeader { + pub fn from_box(b: &B) -> Self { + let box_type = b.box_type(); + let box_size = b.box_size(); + Self { box_type, box_size } + } + + pub fn header_size(self) -> usize { + self.box_type.external_size() + self.box_size.external_size() + } + + pub fn with_box_payload_reader(self, reader: R, f: F) -> Result + where + F: FnOnce(&mut std::io::Take) -> Result, + { + let mut reader = if self.box_size.get() == 0 { + reader.take(u64::MAX) + } else { + let payload_size = self + .box_size + .get() + .checked_sub(self.header_size() as u64) + .ok_or_else(|| { + Error::invalid_data(&format!( + "Too small box size: actual={}, expected={} or more", + self.box_size.get(), + self.header_size() + )) + })?; + reader.take(payload_size) + }; + + let value = f(&mut reader)?; + if reader.limit() != 0 { + return Err(Error::invalid_data(&format!( + "Unconsumed {} bytes at the end of the box {:?}", + reader.limit(), + self.box_type + ))); + } + Ok(value) + } +} + +impl Encode for BoxHeader { + fn encode(&self, mut writer: W) -> Result<()> { + let large_size = self.box_size.get() > u32::MAX as u64; + if large_size { + 1u32.encode(&mut writer)?; + } else { + (self.box_size.get() as u32).encode(&mut writer)?; + } + + match self.box_type { + BoxType::Normal(ty) => { + writer.write_all(&ty)?; + } + BoxType::Uuid(ty) => { + writer.write_all("uuid".as_bytes())?; + writer.write_all(&ty)?; + } + } + + if large_size { + self.box_size.get().encode(&mut writer)?; + } + + Ok(()) + } +} + +impl Decode for BoxHeader { + fn decode(mut reader: R) -> Result { + let mut box_size = u32::decode(&mut reader)? as u64; + + let mut box_type = [0; 4]; + reader.read_exact(&mut box_type)?; + + let box_type = if box_type == [b'u', b'u', b'i', b'd'] { + let mut box_type = [0; 16]; + reader.read_exact(&mut box_type)?; + BoxType::Uuid(box_type) + } else { + BoxType::Normal(box_type) + }; + + if box_size == 1 { + box_size = u64::decode(&mut reader)?; + } + let box_size = BoxSize::new(box_type, box_size).ok_or_else(|| { + Error::invalid_data(&format!( + "Too small box size: actual={}, expected={} or more", + box_size, + 4 + box_type.external_size() + )) + })?; + + Ok(Self { box_type, box_size }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct BoxSize(u64); + +impl BoxSize { + pub const VARIABLE_SIZE: Self = Self(0); + + pub fn new(box_type: BoxType, box_size: u64) -> Option { + if box_size == 0 { + return Some(Self(0)); + } + + if box_size < 4 + box_type.external_size() as u64 { + None + } else { + Some(Self(box_size)) + } + } + + pub const fn with_payload_size(box_type: BoxType, payload_size: u64) -> Self { + Self(box_type.external_size() as u64 + payload_size) + } + + pub const fn get(self) -> u64 { + self.0 + } + + pub const fn external_size(self) -> usize { + if self.0 > u32::MAX as u64 { + 4 + 8 + } else { + 4 + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum BoxType { + Normal([u8; 4]), + Uuid([u8; 16]), +} + +impl BoxType { + pub fn as_bytes(&self) -> &[u8] { + match self { + BoxType::Normal(ty) => &ty[..], + BoxType::Uuid(ty) => &ty[..], + } + } + + pub const fn external_size(self) -> usize { + if matches!(self, Self::Normal(_)) { + 4 + } else { + 4 + 16 + } + } +} + +impl std::fmt::Debug for BoxType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BoxType::Normal(ty) => { + if let Ok(ty) = std::str::from_utf8(ty) { + f.debug_tuple("BoxType").field(&ty).finish() + } else { + f.debug_tuple("BoxType").field(ty).finish() + } + } + BoxType::Uuid(ty) => f.debug_tuple("BoxType").field(ty).finish(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RawBox { + pub box_type: BoxType, + pub box_size: BoxSize, + pub payload: Vec, +} + +impl Encode for RawBox { + fn encode(&self, mut writer: W) -> Result<()> { + BoxHeader::from_box(self).encode(&mut writer)?; + writer.write_all(&self.payload)?; + Ok(()) + } +} + +impl Decode for RawBox { + fn decode(mut reader: R) -> Result { + let header = BoxHeader::decode(&mut reader)?; + dbg!(header); + + let mut payload = Vec::new(); + header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; + Ok(Self { + box_type: header.box_type, + box_size: header.box_size, + payload, + }) + } +} + +impl BaseBox for RawBox { + fn box_type(&self) -> BoxType { + self.box_type + } + + fn box_size(&self) -> BoxSize { + self.box_size + } + + fn box_payload_size(&self) -> u64 { + self.payload.len() as u64 + } +} diff --git a/src/lib.rs b/src/lib.rs index 4d5d251..487ad04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,277 +1,11 @@ use std::io::{Read, Write}; +mod basic_types; mod io; +pub use basic_types::{BaseBox, BoxHeader, BoxSize, BoxType, FullBox, Mp4File, RawBox}; pub use io::{Decode, Encode, Error, Result}; -// 単なる `Box` だと Rust の標準ライブラリのそれと名前が衝突するので変えておく -pub trait BaseBox: Encode + Decode { - fn box_type(&self) -> BoxType; - - fn box_size(&self) -> BoxSize { - BoxSize::with_payload_size(self.box_type(), self.box_payload_size()) - } - - fn box_payload_size(&self) -> u64; -} - -pub trait FullBox: BaseBox { - fn box_version(&self) -> u8; - fn box_flags(&self) -> u32; // u24 -} - -#[derive(Debug, Clone)] -pub struct Mp4File { - // TODO: ftyp_box - pub boxes: Vec, -} - -impl Decode for Mp4File { - fn decode(mut reader: R) -> Result { - let mut boxes = Vec::new(); - let mut buf = [0]; - while reader.read(&mut buf)? != 0 { - let b = B::decode(buf.chain(&mut reader))?; - boxes.push(b); - } - Ok(Self { boxes }) - } -} - -impl Encode for Mp4File { - fn encode(&self, mut writer: W) -> Result<()> { - for b in &self.boxes { - b.encode(&mut writer)?; - } - Ok(()) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct BoxHeader { - pub box_type: BoxType, - pub box_size: BoxSize, -} - -impl BoxHeader { - pub fn from_box(b: &B) -> Self { - let box_type = b.box_type(); - let box_size = b.box_size(); - Self { box_type, box_size } - } - - pub fn header_size(self) -> usize { - self.box_type.external_size() + self.box_size.external_size() - } - - pub fn with_box_payload_reader(self, reader: R, f: F) -> Result - where - F: FnOnce(&mut std::io::Take) -> Result, - { - let mut reader = if self.box_size.get() == 0 { - reader.take(u64::MAX) - } else { - let payload_size = self - .box_size - .get() - .checked_sub(self.header_size() as u64) - .ok_or_else(|| { - Error::invalid_data(&format!( - "Too small box size: actual={}, expected={} or more", - self.box_size.get(), - self.header_size() - )) - })?; - reader.take(payload_size) - }; - - let value = f(&mut reader)?; - if reader.limit() != 0 { - return Err(Error::invalid_data(&format!( - "Unconsumed {} bytes at the end of the box {:?}", - reader.limit(), - self.box_type - ))); - } - Ok(value) - } -} - -impl Encode for BoxHeader { - fn encode(&self, mut writer: W) -> Result<()> { - let large_size = self.box_size.get() > u32::MAX as u64; - if large_size { - 1u32.encode(&mut writer)?; - } else { - (self.box_size.get() as u32).encode(&mut writer)?; - } - - match self.box_type { - BoxType::Normal(ty) => { - writer.write_all(&ty)?; - } - BoxType::Uuid(ty) => { - writer.write_all("uuid".as_bytes())?; - writer.write_all(&ty)?; - } - } - - if large_size { - self.box_size.get().encode(&mut writer)?; - } - - Ok(()) - } -} - -impl Decode for BoxHeader { - fn decode(mut reader: R) -> Result { - let mut box_size = u32::decode(&mut reader)? as u64; - - let mut box_type = [0; 4]; - reader.read_exact(&mut box_type)?; - - let box_type = if box_type == [b'u', b'u', b'i', b'd'] { - let mut box_type = [0; 16]; - reader.read_exact(&mut box_type)?; - BoxType::Uuid(box_type) - } else { - BoxType::Normal(box_type) - }; - - if box_size == 1 { - box_size = u64::decode(&mut reader)?; - } - let box_size = BoxSize::new(box_type, box_size).ok_or_else(|| { - Error::invalid_data(&format!( - "Too small box size: actual={}, expected={} or more", - box_size, - 4 + box_type.external_size() - )) - })?; - - Ok(Self { box_type, box_size }) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct BoxSize(u64); - -impl BoxSize { - pub const VARIABLE_SIZE: Self = Self(0); - - pub fn new(box_type: BoxType, box_size: u64) -> Option { - if box_size == 0 { - return Some(Self(0)); - } - - if box_size < 4 + box_type.external_size() as u64 { - None - } else { - Some(Self(box_size)) - } - } - - pub const fn with_payload_size(box_type: BoxType, payload_size: u64) -> Self { - Self(box_type.external_size() as u64 + payload_size) - } - - pub const fn get(self) -> u64 { - self.0 - } - - pub const fn external_size(self) -> usize { - if self.0 > u32::MAX as u64 { - 4 + 8 - } else { - 4 - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum BoxType { - Normal([u8; 4]), - Uuid([u8; 16]), -} - -impl BoxType { - pub fn as_bytes(&self) -> &[u8] { - match self { - BoxType::Normal(ty) => &ty[..], - BoxType::Uuid(ty) => &ty[..], - } - } - - // TODO: rename - pub const fn external_size(self) -> usize { - if matches!(self, Self::Normal(_)) { - 4 - } else { - 4 + 16 - } - } -} - -impl std::fmt::Debug for BoxType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BoxType::Normal(ty) => { - if let Ok(ty) = std::str::from_utf8(ty) { - f.debug_tuple("BoxType").field(&ty).finish() - } else { - f.debug_tuple("BoxType").field(ty).finish() - } - } - BoxType::Uuid(ty) => f.debug_tuple("BoxType").field(ty).finish(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RawBox { - pub box_type: BoxType, - pub box_size: BoxSize, - pub payload: Vec, -} - -impl Encode for RawBox { - fn encode(&self, mut writer: W) -> Result<()> { - BoxHeader::from_box(self).encode(&mut writer)?; - writer.write_all(&self.payload)?; - Ok(()) - } -} - -impl Decode for RawBox { - fn decode(mut reader: R) -> Result { - let header = BoxHeader::decode(&mut reader)?; - dbg!(header); - - let mut payload = Vec::new(); - header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; - Ok(Self { - box_type: header.box_type, - box_size: header.box_size, - payload, - }) - } -} - -impl BaseBox for RawBox { - fn box_type(&self) -> BoxType { - self.box_type - } - - fn box_size(&self) -> BoxSize { - self.box_size - } - - fn box_payload_size(&self) -> u64 { - self.payload.len() as u64 - } -} - #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Brand([u8; 4]); From 2a8d385867d1bf0c4f0fcc3d9d053fed3129c388 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 16:07:50 +0900 Subject: [PATCH 013/103] Add boxes module --- src/boxes.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 68 +------------------------------------------------ 2 files changed, 72 insertions(+), 67 deletions(-) create mode 100644 src/boxes.rs diff --git a/src/boxes.rs b/src/boxes.rs new file mode 100644 index 0000000..05be27d --- /dev/null +++ b/src/boxes.rs @@ -0,0 +1,71 @@ +use std::io::{Read, Write}; + +use crate::{Decode, Encode, Result}; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Brand([u8; 4]); + +impl Brand { + pub const fn new(brand: [u8; 4]) -> Self { + Self(brand) + } + + pub const fn get(self) -> [u8; 4] { + self.0 + } +} + +impl std::fmt::Debug for Brand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Ok(s) = std::str::from_utf8(&self.0) { + f.debug_tuple("Brand").field(&s).finish() + } else { + f.debug_tuple("Brand").field(&self.0).finish() + } + } +} + +impl Encode for Brand { + fn encode(&self, mut writer: W) -> Result<()> { + writer.write_all(&self.0)?; + Ok(()) + } +} + +impl Decode for Brand { + fn decode(mut reader: R) -> Result { + let mut buf = [0; 4]; + reader.read_exact(&mut buf)?; + Ok(Self(buf)) + } +} + +// /// [ISO/IEC 14496-12] FileTypeBox class +// #[derive(Debug, Clone, PartialEq, Eq)] +// pub struct FtypBox { +// pub major_brand: Brand, +// pub minor_version: u32, +// pub compatible_brands: Vec, +// } + +// impl Encode for FtypBox { +// fn encode(&self, mut writer: W) -> std::io::Result<()> { +// BoxHeader::from_box(self).encode(&mut writer)?; + +// self.major_brand.encode(&mut writer)?; +// writer.write_u32(self.minor_version)?; +// for brand in &self.compatible_brands { +// brand.encode(&mut writer)?; +// } +// Ok(()) +// } +// } + +// impl Decode for FtypBox { +// fn decode(mut reader: R) -> std::io::Result { +// let major_brand = Brand::decode(&mut reader)?; +// let minor_version = reader.read_u32()?; +// todo!(); +// } +// } +// diff --git a/src/lib.rs b/src/lib.rs index 487ad04..b55ef87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,74 +1,8 @@ use std::io::{Read, Write}; mod basic_types; +pub mod boxes; mod io; pub use basic_types::{BaseBox, BoxHeader, BoxSize, BoxType, FullBox, Mp4File, RawBox}; pub use io::{Decode, Encode, Error, Result}; - -#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Brand([u8; 4]); - -impl Brand { - pub const fn new(brand: [u8; 4]) -> Self { - Self(brand) - } - - pub const fn get(self) -> [u8; 4] { - self.0 - } -} - -impl std::fmt::Debug for Brand { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Ok(s) = std::str::from_utf8(&self.0) { - f.debug_tuple("Brand").field(&s).finish() - } else { - f.debug_tuple("Brand").field(&self.0).finish() - } - } -} - -impl Encode for Brand { - fn encode(&self, mut writer: W) -> Result<()> { - writer.write_all(&self.0)?; - Ok(()) - } -} - -impl Decode for Brand { - fn decode(mut reader: R) -> Result { - let mut buf = [0; 4]; - reader.read_exact(&mut buf)?; - Ok(Self(buf)) - } -} - -// /// [ISO/IEC 14496-12] FileTypeBox class -// #[derive(Debug, Clone, PartialEq, Eq)] -// pub struct FtypBox { -// pub major_brand: Brand, -// pub minor_version: u32, -// pub compatible_brands: Vec, -// } - -// impl Encode for FtypBox { -// fn encode(&self, mut writer: W) -> std::io::Result<()> { -// BoxHeader::from_box(self).encode(&mut writer)?; - -// self.major_brand.encode(&mut writer)?; -// writer.write_u32(self.minor_version)?; -// for brand in &self.compatible_brands { -// brand.encode(&mut writer)?; -// } -// Ok(()) -// } -// } - -// impl Decode for FtypBox { -// fn decode(mut reader: R) -> std::io::Result { -// let major_brand = Brand::decode(&mut reader)?; -// let minor_version = reader.read_u32()?; -// todo!(); -// } -// } From dc211ff5d21dcd84c08cf83ef6ee17ca2d7816fe Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 16:55:15 +0900 Subject: [PATCH 014/103] Add FtypBox --- src/basic_types.rs | 21 +++++++++-- src/boxes.rs | 92 +++++++++++++++++++++++++++++++--------------- src/io.rs | 27 ++++++++++++++ src/lib.rs | 2 - 4 files changed, 107 insertions(+), 35 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 4f285e1..9a0bbff 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -1,6 +1,6 @@ use std::io::{Read, Write}; -use crate::{Decode, Encode, Error, Result}; +use crate::{boxes::FtypBox, Decode, Encode, Error, Result}; // 単なる `Box` だと Rust の標準ライブラリのそれと名前が衝突するので変えておく pub trait BaseBox: Encode + Decode { @@ -20,24 +20,28 @@ pub trait FullBox: BaseBox { #[derive(Debug, Clone)] pub struct Mp4File { - // TODO: ftyp_box + pub ftyp_box: FtypBox, pub boxes: Vec, } impl Decode for Mp4File { fn decode(mut reader: R) -> Result { + let ftyp_box = FtypBox::decode(&mut reader)?; + let mut boxes = Vec::new(); let mut buf = [0]; while reader.read(&mut buf)? != 0 { let b = B::decode(buf.chain(&mut reader))?; boxes.push(b); } - Ok(Self { boxes }) + Ok(Self { ftyp_box, boxes }) } } impl Encode for Mp4File { fn encode(&self, mut writer: W) -> Result<()> { + self.ftyp_box.encode(&mut writer)?; + for b in &self.boxes { b.encode(&mut writer)?; } @@ -208,6 +212,17 @@ impl BoxType { 4 + 16 } } + + pub fn expect(self, expected: Self) -> Result<()> { + if self == expected { + Ok(()) + } else { + Err(Error::invalid_data(&format!( + "Expected box type {:?}, but got {:?}", + expected, self + ))) + } + } } impl std::fmt::Debug for BoxType { diff --git a/src/boxes.rs b/src/boxes.rs index 05be27d..a25c5ef 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1,11 +1,13 @@ use std::io::{Read, Write}; -use crate::{Decode, Encode, Result}; +use crate::{io::ExternalBytes, BaseBox, BoxHeader, BoxType, Decode, Encode, Result}; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Brand([u8; 4]); impl Brand { + // TODO: Add constants for the predefined brands + pub const fn new(brand: [u8; 4]) -> Self { Self(brand) } @@ -40,32 +42,62 @@ impl Decode for Brand { } } -// /// [ISO/IEC 14496-12] FileTypeBox class -// #[derive(Debug, Clone, PartialEq, Eq)] -// pub struct FtypBox { -// pub major_brand: Brand, -// pub minor_version: u32, -// pub compatible_brands: Vec, -// } - -// impl Encode for FtypBox { -// fn encode(&self, mut writer: W) -> std::io::Result<()> { -// BoxHeader::from_box(self).encode(&mut writer)?; - -// self.major_brand.encode(&mut writer)?; -// writer.write_u32(self.minor_version)?; -// for brand in &self.compatible_brands { -// brand.encode(&mut writer)?; -// } -// Ok(()) -// } -// } - -// impl Decode for FtypBox { -// fn decode(mut reader: R) -> std::io::Result { -// let major_brand = Brand::decode(&mut reader)?; -// let minor_version = reader.read_u32()?; -// todo!(); -// } -// } -// +/// [ISO/IEC 14496-12] FileTypeBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FtypBox { + pub major_brand: Brand, + pub minor_version: u32, + pub compatible_brands: Vec, +} + +impl FtypBox { + pub const TYPE: BoxType = BoxType::Normal([b'f', b't', b'y', b'p']); + + fn encode_payload(&self, mut writer: W) -> Result<()> { + self.major_brand.encode(&mut writer)?; + self.minor_version.encode(&mut writer)?; + for brand in &self.compatible_brands { + brand.encode(&mut writer)?; + } + Ok(()) + } +} + +impl Encode for FtypBox { + fn encode(&self, mut writer: W) -> Result<()> { + BoxHeader::from_box(self).encode(&mut writer)?; + self.encode_payload(&mut writer)?; + Ok(()) + } +} + +impl Decode for FtypBox { + fn decode(mut reader: R) -> Result { + let header = BoxHeader::decode(&mut reader)?; + header.box_type.expect(Self::TYPE)?; + + header.with_box_payload_reader(reader, |mut reader| { + let major_brand = Brand::decode(&mut reader)?; + let minor_version = u32::decode(&mut reader)?; + let mut compatible_brands = Vec::new(); + while reader.limit() > 0 { + compatible_brands.push(Brand::decode(&mut reader)?); + } + Ok(Self { + major_brand, + minor_version, + compatible_brands, + }) + }) + } +} + +impl BaseBox for FtypBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} diff --git a/src/io.rs b/src/io.rs index ad8429b..177788e 100644 --- a/src/io.rs +++ b/src/io.rs @@ -114,3 +114,30 @@ impl Decode for u64 { Ok(Self::from_be_bytes(buf)) } } + +#[derive(Debug, Default)] +pub struct ExternalBytes(pub u64); + +impl ExternalBytes { + pub fn calc(f: F) -> u64 + where + F: FnOnce(&mut Self) -> Result<()>, + { + let mut external_bytes = Self(0); + + // TODO: 途中で失敗した場合は、それまでに書き込まれたサイズでいい理由を書く + let _ = f(&mut external_bytes); + external_bytes.0 + } +} + +impl Write for ExternalBytes { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0 += buf.len() as u64; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index b55ef87..aacac9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -use std::io::{Read, Write}; - mod basic_types; pub mod boxes; mod io; From 386c3650b86d524cab06a0545b71e8f285df6570 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 17:00:49 +0900 Subject: [PATCH 015/103] Update Encode and Decode traits --- src/basic_types.rs | 34 +++++++++++++++++----------------- src/boxes.rs | 30 +++++++++++++++--------------- src/io.rs | 20 ++++++++++---------- tests/decode_test.rs | 2 +- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 9a0bbff..9a89a8e 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -25,13 +25,13 @@ pub struct Mp4File { } impl Decode for Mp4File { - fn decode(mut reader: R) -> Result { - let ftyp_box = FtypBox::decode(&mut reader)?; + fn decode(mut reader: &mut R) -> Result { + let ftyp_box = FtypBox::decode(reader)?; let mut boxes = Vec::new(); let mut buf = [0]; while reader.read(&mut buf)? != 0 { - let b = B::decode(buf.chain(&mut reader))?; + let b = B::decode(&mut buf.chain(&mut reader))?; boxes.push(b); } Ok(Self { ftyp_box, boxes }) @@ -39,11 +39,11 @@ impl Decode for Mp4File { } impl Encode for Mp4File { - fn encode(&self, mut writer: W) -> Result<()> { - self.ftyp_box.encode(&mut writer)?; + fn encode(&self, writer: &mut W) -> Result<()> { + self.ftyp_box.encode(writer)?; for b in &self.boxes { - b.encode(&mut writer)?; + b.encode(writer)?; } Ok(()) } @@ -100,12 +100,12 @@ impl BoxHeader { } impl Encode for BoxHeader { - fn encode(&self, mut writer: W) -> Result<()> { + fn encode(&self, writer: &mut W) -> Result<()> { let large_size = self.box_size.get() > u32::MAX as u64; if large_size { - 1u32.encode(&mut writer)?; + 1u32.encode(writer)?; } else { - (self.box_size.get() as u32).encode(&mut writer)?; + (self.box_size.get() as u32).encode(writer)?; } match self.box_type { @@ -119,7 +119,7 @@ impl Encode for BoxHeader { } if large_size { - self.box_size.get().encode(&mut writer)?; + self.box_size.get().encode(writer)?; } Ok(()) @@ -127,8 +127,8 @@ impl Encode for BoxHeader { } impl Decode for BoxHeader { - fn decode(mut reader: R) -> Result { - let mut box_size = u32::decode(&mut reader)? as u64; + fn decode(reader: &mut R) -> Result { + let mut box_size = u32::decode(reader)? as u64; let mut box_type = [0; 4]; reader.read_exact(&mut box_type)?; @@ -142,7 +142,7 @@ impl Decode for BoxHeader { }; if box_size == 1 { - box_size = u64::decode(&mut reader)?; + box_size = u64::decode(reader)?; } let box_size = BoxSize::new(box_type, box_size).ok_or_else(|| { Error::invalid_data(&format!( @@ -248,16 +248,16 @@ pub struct RawBox { } impl Encode for RawBox { - fn encode(&self, mut writer: W) -> Result<()> { - BoxHeader::from_box(self).encode(&mut writer)?; + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; writer.write_all(&self.payload)?; Ok(()) } } impl Decode for RawBox { - fn decode(mut reader: R) -> Result { - let header = BoxHeader::decode(&mut reader)?; + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; dbg!(header); let mut payload = Vec::new(); diff --git a/src/boxes.rs b/src/boxes.rs index a25c5ef..ef35e73 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -28,14 +28,14 @@ impl std::fmt::Debug for Brand { } impl Encode for Brand { - fn encode(&self, mut writer: W) -> Result<()> { + fn encode(&self, writer: &mut W) -> Result<()> { writer.write_all(&self.0)?; Ok(()) } } impl Decode for Brand { - fn decode(mut reader: R) -> Result { + fn decode(reader: &mut R) -> Result { let mut buf = [0; 4]; reader.read_exact(&mut buf)?; Ok(Self(buf)) @@ -53,35 +53,35 @@ pub struct FtypBox { impl FtypBox { pub const TYPE: BoxType = BoxType::Normal([b'f', b't', b'y', b'p']); - fn encode_payload(&self, mut writer: W) -> Result<()> { - self.major_brand.encode(&mut writer)?; - self.minor_version.encode(&mut writer)?; + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.major_brand.encode(writer)?; + self.minor_version.encode(writer)?; for brand in &self.compatible_brands { - brand.encode(&mut writer)?; + brand.encode(writer)?; } Ok(()) } } impl Encode for FtypBox { - fn encode(&self, mut writer: W) -> Result<()> { - BoxHeader::from_box(self).encode(&mut writer)?; - self.encode_payload(&mut writer)?; + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; Ok(()) } } impl Decode for FtypBox { - fn decode(mut reader: R) -> Result { - let header = BoxHeader::decode(&mut reader)?; + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; header.box_type.expect(Self::TYPE)?; - header.with_box_payload_reader(reader, |mut reader| { - let major_brand = Brand::decode(&mut reader)?; - let minor_version = u32::decode(&mut reader)?; + header.with_box_payload_reader(reader, |reader| { + let major_brand = Brand::decode(reader)?; + let minor_version = u32::decode(reader)?; let mut compatible_brands = Vec::new(); while reader.limit() > 0 { - compatible_brands.push(Brand::decode(&mut reader)?); + compatible_brands.push(Brand::decode(reader)?); } Ok(Self { major_brand, diff --git a/src/io.rs b/src/io.rs index 177788e..bec7632 100644 --- a/src/io.rs +++ b/src/io.rs @@ -48,43 +48,43 @@ impl std::fmt::Display for Error { } pub trait Encode { - fn encode(&self, writer: W) -> Result<()>; + fn encode(&self, writer: &mut W) -> Result<()>; } impl Encode for u8 { - fn encode(&self, mut writer: W) -> Result<()> { + fn encode(&self, writer: &mut W) -> Result<()> { writer.write_all(&self.to_be_bytes())?; Ok(()) } } impl Encode for u16 { - fn encode(&self, mut writer: W) -> Result<()> { + fn encode(&self, writer: &mut W) -> Result<()> { writer.write_all(&self.to_be_bytes())?; Ok(()) } } impl Encode for u32 { - fn encode(&self, mut writer: W) -> Result<()> { + fn encode(&self, writer: &mut W) -> Result<()> { writer.write_all(&self.to_be_bytes())?; Ok(()) } } impl Encode for u64 { - fn encode(&self, mut writer: W) -> Result<()> { + fn encode(&self, writer: &mut W) -> Result<()> { writer.write_all(&self.to_be_bytes())?; Ok(()) } } pub trait Decode: Sized { - fn decode(reader: R) -> Result; + fn decode(reader: &mut R) -> Result; } impl Decode for u8 { - fn decode(mut reader: R) -> Result { + fn decode(reader: &mut R) -> Result { let mut buf = [0; Self::BITS as usize / 8]; reader.read_exact(&mut buf)?; Ok(Self::from_be_bytes(buf)) @@ -92,7 +92,7 @@ impl Decode for u8 { } impl Decode for u16 { - fn decode(mut reader: R) -> Result { + fn decode(reader: &mut R) -> Result { let mut buf = [0; Self::BITS as usize / 8]; reader.read_exact(&mut buf)?; Ok(Self::from_be_bytes(buf)) @@ -100,7 +100,7 @@ impl Decode for u16 { } impl Decode for u32 { - fn decode(mut reader: R) -> Result { + fn decode(reader: &mut R) -> Result { let mut buf = [0; Self::BITS as usize / 8]; reader.read_exact(&mut buf)?; Ok(Self::from_be_bytes(buf)) @@ -108,7 +108,7 @@ impl Decode for u32 { } impl Decode for u64 { - fn decode(mut reader: R) -> Result { + fn decode(reader: &mut R) -> Result { let mut buf = [0; Self::BITS as usize / 8]; reader.read_exact(&mut buf)?; Ok(Self::from_be_bytes(buf)) diff --git a/tests/decode_test.rs b/tests/decode_test.rs index 1c9aae2..d70e2c7 100644 --- a/tests/decode_test.rs +++ b/tests/decode_test.rs @@ -3,7 +3,7 @@ use shiguredo_mp4::{Decode, Mp4File, RawBox, Result}; #[test] fn decode_black_h264_video_mp4() -> Result<()> { let input_bytes = include_bytes!("testdata/black-h264-video.mp4"); - let file = Mp4File::::decode(&input_bytes[..])?; + let file = Mp4File::::decode(&mut &input_bytes[..])?; assert_eq!(file.boxes.len(), 2); // TODO Ok(()) } From 2662143b6085646f5771ad66df3c4bec25f8044a Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 18 Sep 2024 17:35:08 +0900 Subject: [PATCH 016/103] Add RootBox, FreeBox, etc --- src/basic_types.rs | 4 +- src/boxes.rs | 87 +++++++++++++++++++++++++++++++++++++++++++- src/io.rs | 45 ++++++++++++++++++++++- tests/decode_test.rs | 5 +-- 4 files changed, 135 insertions(+), 6 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 9a89a8e..f3e034c 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -56,6 +56,8 @@ pub struct BoxHeader { } impl BoxHeader { + pub const MAX_SIZE: usize = (4 + 8) + (4 + 16); + pub fn from_box(b: &B) -> Self { let box_type = b.box_type(); let box_size = b.box_size(); @@ -258,7 +260,7 @@ impl Encode for RawBox { impl Decode for RawBox { fn decode(reader: &mut R) -> Result { let header = BoxHeader::decode(reader)?; - dbg!(header); + dbg!(header); // TODO: remove let mut payload = Vec::new(); header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; diff --git a/src/boxes.rs b/src/boxes.rs index ef35e73..39377a9 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1,6 +1,9 @@ use std::io::{Read, Write}; -use crate::{io::ExternalBytes, BaseBox, BoxHeader, BoxType, Decode, Encode, Result}; +use crate::{ + io::{ExternalBytes, PeekReader}, + BaseBox, BoxHeader, BoxType, Decode, Encode, RawBox, Result, +}; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Brand([u8; 4]); @@ -101,3 +104,85 @@ impl BaseBox for FtypBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RootBox { + Free(FreeBox), // free, mdat, moov + Unknown(RawBox), +} + +impl Encode for RootBox { + fn encode(&self, writer: &mut W) -> Result<()> { + match self { + RootBox::Free(b) => b.encode(writer), + RootBox::Unknown(b) => b.encode(writer), + } + } +} + +impl Decode for RootBox { + fn decode(reader: &mut R) -> Result { + let mut reader = PeekReader::<_, { BoxHeader::MAX_SIZE }>::new(reader); + let header = BoxHeader::decode(&mut reader)?; + match header.box_type { + FreeBox::TYPE => Decode::decode(&mut reader.into_reader()).map(Self::Free), + _ => Decode::decode(&mut reader.into_reader()).map(Self::Unknown), + } + } +} + +impl BaseBox for RootBox { + fn box_type(&self) -> BoxType { + match self { + RootBox::Free(b) => b.box_type(), + RootBox::Unknown(b) => b.box_type(), + } + } + + fn box_payload_size(&self) -> u64 { + match self { + RootBox::Free(b) => b.box_payload_size(), + RootBox::Unknown(b) => b.box_payload_size(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FreeBox { + pub box_type: BoxType, + pub payload: Vec, +} + +impl FreeBox { + pub const TYPE: BoxType = BoxType::Normal([b'f', b'r', b'e', b'e']); +} + +impl Encode for FreeBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + writer.write_all(&self.payload)?; + Ok(()) + } +} + +impl Decode for FreeBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + let mut payload = Vec::new(); + header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; + Ok(Self { + box_type: header.box_type, + payload, + }) + } +} + +impl BaseBox for FreeBox { + fn box_type(&self) -> BoxType { + self.box_type + } + + fn box_payload_size(&self) -> u64 { + self.payload.len() as u64 + } +} diff --git a/src/io.rs b/src/io.rs index bec7632..051e28d 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,6 +1,6 @@ use std::{ backtrace::Backtrace, - io::{ErrorKind, Read, Write}, + io::{Cursor, ErrorKind, Read, Write}, }; pub type Result = std::result::Result; @@ -141,3 +141,46 @@ impl Write for ExternalBytes { Ok(()) } } + +#[derive(Debug)] +pub struct PeekReader { + buf: [u8; N], + buf_start: usize, + inner: R, +} + +impl PeekReader { + pub fn new(inner: R) -> Self { + Self { + buf: [0; N], + buf_start: 0, + inner, + } + } + + pub fn into_reader(self) -> impl Read { + Read::chain( + Cursor::new(self.buf).take(self.buf_start as u64), + self.inner, + ) + } +} + +impl Read for PeekReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + if N < self.buf_start + buf.len() { + return Err(std::io::Error::new( + ErrorKind::Other, + format!("[BUG] Peek buffer exhausted: buffer_size={N}"), + )); + } + + let read_size = self + .inner + .read(&mut self.buf[self.buf_start..][..buf.len()])?; + buf[..read_size].copy_from_slice(&self.buf[self.buf_start..][..read_size]); + self.buf_start += read_size; + + Ok(read_size) + } +} diff --git a/tests/decode_test.rs b/tests/decode_test.rs index d70e2c7..0683db8 100644 --- a/tests/decode_test.rs +++ b/tests/decode_test.rs @@ -1,9 +1,8 @@ -use shiguredo_mp4::{Decode, Mp4File, RawBox, Result}; +use shiguredo_mp4::{boxes::RootBox, Decode, Mp4File, Result}; #[test] fn decode_black_h264_video_mp4() -> Result<()> { let input_bytes = include_bytes!("testdata/black-h264-video.mp4"); - let file = Mp4File::::decode(&mut &input_bytes[..])?; - assert_eq!(file.boxes.len(), 2); // TODO + let file = Mp4File::::decode(&mut &input_bytes[..])?; Ok(()) } From 03c1b19f29c9821c797562324c3c007ae30c9896 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 09:57:35 +0900 Subject: [PATCH 017/103] Add UnknownBox --- src/basic_types.rs | 49 ++++++++++++++++++++++++++++++++++++++++------ src/boxes.rs | 4 ++-- src/io.rs | 1 + src/lib.rs | 5 ++++- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index f3e034c..3d710f9 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -1,6 +1,9 @@ use std::io::{Read, Write}; -use crate::{boxes::FtypBox, Decode, Encode, Error, Result}; +use crate::{ + boxes::{FtypBox, RootBox}, + Decode, Encode, Error, Result, +}; // 単なる `Box` だと Rust の標準ライブラリのそれと名前が衝突するので変えておく pub trait BaseBox: Encode + Decode { @@ -18,8 +21,30 @@ pub trait FullBox: BaseBox { fn box_flags(&self) -> u32; // u24 } +pub trait IterUnknownBoxes { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator; +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct BoxBacktrace(Vec); + +impl BoxBacktrace { + pub const fn empty() -> Self { + Self(Vec::new()) + } + + pub fn get(&self) -> &[BoxType] { + &self.0 + } + + pub fn join(mut self, parent: BoxType) -> Self { + self.0.push(parent); + self + } +} + #[derive(Debug, Clone)] -pub struct Mp4File { +pub struct Mp4File { pub ftyp_box: FtypBox, pub boxes: Vec, } @@ -49,6 +74,12 @@ impl Encode for Mp4File { } } +impl IterUnknownBoxes for Mp4File { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + self.boxes.iter().flat_map(|b| b.iter_unknown_boxes()) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BoxHeader { pub box_type: BoxType, @@ -243,13 +274,13 @@ impl std::fmt::Debug for BoxType { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct RawBox { +pub struct UnknownBox { pub box_type: BoxType, pub box_size: BoxSize, pub payload: Vec, } -impl Encode for RawBox { +impl Encode for UnknownBox { fn encode(&self, writer: &mut W) -> Result<()> { BoxHeader::from_box(self).encode(writer)?; writer.write_all(&self.payload)?; @@ -257,7 +288,7 @@ impl Encode for RawBox { } } -impl Decode for RawBox { +impl Decode for UnknownBox { fn decode(reader: &mut R) -> Result { let header = BoxHeader::decode(reader)?; dbg!(header); // TODO: remove @@ -272,7 +303,7 @@ impl Decode for RawBox { } } -impl BaseBox for RawBox { +impl BaseBox for UnknownBox { fn box_type(&self) -> BoxType { self.box_type } @@ -285,3 +316,9 @@ impl BaseBox for RawBox { self.payload.len() as u64 } } + +impl IterUnknownBoxes for UnknownBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + std::iter::once((BoxBacktrace::empty(), self)) + } +} diff --git a/src/boxes.rs b/src/boxes.rs index 39377a9..da7857d 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2,7 +2,7 @@ use std::io::{Read, Write}; use crate::{ io::{ExternalBytes, PeekReader}, - BaseBox, BoxHeader, BoxType, Decode, Encode, RawBox, Result, + BaseBox, BoxHeader, BoxType, Decode, Encode, Result, UnknownBox, }; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -108,7 +108,7 @@ impl BaseBox for FtypBox { #[derive(Debug, Clone, PartialEq, Eq)] pub enum RootBox { Free(FreeBox), // free, mdat, moov - Unknown(RawBox), + Unknown(UnknownBox), } impl Encode for RootBox { diff --git a/src/io.rs b/src/io.rs index 051e28d..50f0da8 100644 --- a/src/io.rs +++ b/src/io.rs @@ -158,6 +158,7 @@ impl PeekReader { } } + // TODO: rename pub fn into_reader(self) -> impl Read { Read::chain( Cursor::new(self.buf).take(self.buf_start as u64), diff --git a/src/lib.rs b/src/lib.rs index aacac9d..29ea161 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,5 +2,8 @@ mod basic_types; pub mod boxes; mod io; -pub use basic_types::{BaseBox, BoxHeader, BoxSize, BoxType, FullBox, Mp4File, RawBox}; +pub use basic_types::{ + BaseBox, BoxBacktrace, BoxHeader, BoxSize, BoxType, FullBox, IterUnknownBoxes, Mp4File, + UnknownBox, +}; pub use io::{Decode, Encode, Error, Result}; From 7bd1a8836676b8e68337eca7ff33cd0095c332be Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 10:04:22 +0900 Subject: [PATCH 018/103] Update test --- src/basic_types.rs | 18 ++++++++---------- src/boxes.rs | 25 ++++++++++++++++++++++++- src/lib.rs | 3 +-- tests/decode_test.rs | 6 +++++- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 3d710f9..340bbbb 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -22,15 +22,15 @@ pub trait FullBox: BaseBox { } pub trait IterUnknownBoxes { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator; + fn iter_unknown_boxes(&self) -> impl '_ + Iterator; } #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct BoxBacktrace(Vec); +pub struct BoxPath(Vec); -impl BoxBacktrace { - pub const fn empty() -> Self { - Self(Vec::new()) +impl BoxPath { + pub fn new(box_type: BoxType) -> Self { + Self(vec![box_type]) } pub fn get(&self) -> &[BoxType] { @@ -75,7 +75,7 @@ impl Encode for Mp4File { } impl IterUnknownBoxes for Mp4File { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { self.boxes.iter().flat_map(|b| b.iter_unknown_boxes()) } } @@ -291,8 +291,6 @@ impl Encode for UnknownBox { impl Decode for UnknownBox { fn decode(reader: &mut R) -> Result { let header = BoxHeader::decode(reader)?; - dbg!(header); // TODO: remove - let mut payload = Vec::new(); header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; Ok(Self { @@ -318,7 +316,7 @@ impl BaseBox for UnknownBox { } impl IterUnknownBoxes for UnknownBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - std::iter::once((BoxBacktrace::empty(), self)) + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + std::iter::once((BoxPath::new(self.box_type), self)) } } diff --git a/src/boxes.rs b/src/boxes.rs index da7857d..94894f6 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2,7 +2,7 @@ use std::io::{Read, Write}; use crate::{ io::{ExternalBytes, PeekReader}, - BaseBox, BoxHeader, BoxType, Decode, Encode, Result, UnknownBox, + BaseBox, BoxHeader, BoxPath, BoxType, Decode, Encode, IterUnknownBoxes, Result, UnknownBox, }; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -105,6 +105,12 @@ impl BaseBox for FtypBox { } } +impl IterUnknownBoxes for FtypBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + std::iter::empty() + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum RootBox { Free(FreeBox), // free, mdat, moov @@ -147,6 +153,17 @@ impl BaseBox for RootBox { } } +impl IterUnknownBoxes for RootBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + match self { + RootBox::Free(b) => { + Box::new(b.iter_unknown_boxes()) as Box> + } + RootBox::Unknown(b) => Box::new(b.iter_unknown_boxes()), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct FreeBox { pub box_type: BoxType, @@ -186,3 +203,9 @@ impl BaseBox for FreeBox { self.payload.len() as u64 } } + +impl IterUnknownBoxes for FreeBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + std::iter::empty() + } +} diff --git a/src/lib.rs b/src/lib.rs index 29ea161..891833a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ pub mod boxes; mod io; pub use basic_types::{ - BaseBox, BoxBacktrace, BoxHeader, BoxSize, BoxType, FullBox, IterUnknownBoxes, Mp4File, - UnknownBox, + BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, FullBox, IterUnknownBoxes, Mp4File, UnknownBox, }; pub use io::{Decode, Encode, Error, Result}; diff --git a/tests/decode_test.rs b/tests/decode_test.rs index 0683db8..7ee00ff 100644 --- a/tests/decode_test.rs +++ b/tests/decode_test.rs @@ -1,8 +1,12 @@ -use shiguredo_mp4::{boxes::RootBox, Decode, Mp4File, Result}; +use shiguredo_mp4::{boxes::RootBox, Decode, IterUnknownBoxes, Mp4File, Result}; #[test] fn decode_black_h264_video_mp4() -> Result<()> { let input_bytes = include_bytes!("testdata/black-h264-video.mp4"); let file = Mp4File::::decode(&mut &input_bytes[..])?; + assert_eq!( + file.iter_unknown_boxes().map(|x| x.0).collect::>(), + Vec::new() + ); Ok(()) } From ba367ebec708aa8195edae920787b4b49fbff897 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 10:15:19 +0900 Subject: [PATCH 019/103] Add mdat box --- src/boxes.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 94894f6..7ae6494 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2,7 +2,8 @@ use std::io::{Read, Write}; use crate::{ io::{ExternalBytes, PeekReader}, - BaseBox, BoxHeader, BoxPath, BoxType, Decode, Encode, IterUnknownBoxes, Result, UnknownBox, + BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Encode, IterUnknownBoxes, Result, + UnknownBox, }; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -113,7 +114,8 @@ impl IterUnknownBoxes for FtypBox { #[derive(Debug, Clone, PartialEq, Eq)] pub enum RootBox { - Free(FreeBox), // free, mdat, moov + Free(FreeBox), + Mdat(MdatBox), Unknown(UnknownBox), } @@ -121,6 +123,7 @@ impl Encode for RootBox { fn encode(&self, writer: &mut W) -> Result<()> { match self { RootBox::Free(b) => b.encode(writer), + RootBox::Mdat(b) => b.encode(writer), RootBox::Unknown(b) => b.encode(writer), } } @@ -132,6 +135,7 @@ impl Decode for RootBox { let header = BoxHeader::decode(&mut reader)?; match header.box_type { FreeBox::TYPE => Decode::decode(&mut reader.into_reader()).map(Self::Free), + MdatBox::TYPE => Decode::decode(&mut reader.into_reader()).map(Self::Mdat), _ => Decode::decode(&mut reader.into_reader()).map(Self::Unknown), } } @@ -141,6 +145,7 @@ impl BaseBox for RootBox { fn box_type(&self) -> BoxType { match self { RootBox::Free(b) => b.box_type(), + RootBox::Mdat(b) => b.box_type(), RootBox::Unknown(b) => b.box_type(), } } @@ -148,6 +153,7 @@ impl BaseBox for RootBox { fn box_payload_size(&self) -> u64 { match self { RootBox::Free(b) => b.box_payload_size(), + RootBox::Mdat(b) => b.box_payload_size(), RootBox::Unknown(b) => b.box_payload_size(), } } @@ -159,14 +165,15 @@ impl IterUnknownBoxes for RootBox { RootBox::Free(b) => { Box::new(b.iter_unknown_boxes()) as Box> } + RootBox::Mdat(b) => Box::new(b.iter_unknown_boxes()), RootBox::Unknown(b) => Box::new(b.iter_unknown_boxes()), } } } +/// [ISO/IEC 14496-12] FreeSpaceBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct FreeBox { - pub box_type: BoxType, pub payload: Vec, } @@ -185,18 +192,74 @@ impl Encode for FreeBox { impl Decode for FreeBox { fn decode(reader: &mut R) -> Result { let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + + let mut payload = Vec::new(); + header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; + Ok(Self { payload }) + } +} + +impl BaseBox for FreeBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + self.payload.len() as u64 + } +} + +impl IterUnknownBoxes for FreeBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + std::iter::empty() + } +} + +/// [ISO/IEC 14496-12] MediaDataBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MdatBox { + pub is_variable_size: bool, + pub payload: Vec, +} + +impl MdatBox { + pub const TYPE: BoxType = BoxType::Normal([b'm', b'd', b'a', b't']); +} + +impl Encode for MdatBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + writer.write_all(&self.payload)?; + Ok(()) + } +} + +impl Decode for MdatBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + let mut payload = Vec::new(); header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; Ok(Self { - box_type: header.box_type, + is_variable_size: header.box_size == BoxSize::VARIABLE_SIZE, payload, }) } } -impl BaseBox for FreeBox { +impl BaseBox for MdatBox { fn box_type(&self) -> BoxType { - self.box_type + Self::TYPE + } + + fn box_size(&self) -> BoxSize { + if self.is_variable_size { + BoxSize::VARIABLE_SIZE + } else { + BoxSize::with_payload_size(Self::TYPE, self.box_payload_size()) + } } fn box_payload_size(&self) -> u64 { @@ -204,7 +267,7 @@ impl BaseBox for FreeBox { } } -impl IterUnknownBoxes for FreeBox { +impl IterUnknownBoxes for MdatBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { std::iter::empty() } From 82373357a9fb6005c6d1266b43d201e4762e3a33 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 10:23:52 +0900 Subject: [PATCH 020/103] Add moov box --- src/boxes.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index 7ae6494..fd8a6f4 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -116,6 +116,7 @@ impl IterUnknownBoxes for FtypBox { pub enum RootBox { Free(FreeBox), Mdat(MdatBox), + Moov(MoovBox), Unknown(UnknownBox), } @@ -124,6 +125,7 @@ impl Encode for RootBox { match self { RootBox::Free(b) => b.encode(writer), RootBox::Mdat(b) => b.encode(writer), + RootBox::Moov(b) => b.encode(writer), RootBox::Unknown(b) => b.encode(writer), } } @@ -136,6 +138,7 @@ impl Decode for RootBox { match header.box_type { FreeBox::TYPE => Decode::decode(&mut reader.into_reader()).map(Self::Free), MdatBox::TYPE => Decode::decode(&mut reader.into_reader()).map(Self::Mdat), + MoovBox::TYPE => Decode::decode(&mut reader.into_reader()).map(Self::Moov), _ => Decode::decode(&mut reader.into_reader()).map(Self::Unknown), } } @@ -146,6 +149,7 @@ impl BaseBox for RootBox { match self { RootBox::Free(b) => b.box_type(), RootBox::Mdat(b) => b.box_type(), + RootBox::Moov(b) => b.box_type(), RootBox::Unknown(b) => b.box_type(), } } @@ -154,6 +158,7 @@ impl BaseBox for RootBox { match self { RootBox::Free(b) => b.box_payload_size(), RootBox::Mdat(b) => b.box_payload_size(), + RootBox::Moov(b) => b.box_payload_size(), RootBox::Unknown(b) => b.box_payload_size(), } } @@ -166,6 +171,7 @@ impl IterUnknownBoxes for RootBox { Box::new(b.iter_unknown_boxes()) as Box> } RootBox::Mdat(b) => Box::new(b.iter_unknown_boxes()), + RootBox::Moov(b) => Box::new(b.iter_unknown_boxes()), RootBox::Unknown(b) => Box::new(b.iter_unknown_boxes()), } } @@ -272,3 +278,62 @@ impl IterUnknownBoxes for MdatBox { std::iter::empty() } } + +/// [ISO/IEC 14496-12] MovieBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MoovBox { + pub unknown_boxes: Vec, +} + +impl MoovBox { + pub const TYPE: BoxType = BoxType::Normal([b'm', b'o', b'o', b'v']); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } +} + +impl Encode for MoovBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for MoovBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + + header.with_box_payload_reader(reader, |reader| { + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + unknown_boxes.push(UnknownBox::decode(reader)?); + } + Ok(Self { unknown_boxes }) + }) + } +} + +impl BaseBox for MoovBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for MoovBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + self.unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()) + .map(|(path, b)| (path.join(Self::TYPE), b)) + } +} From aea1cd1c43c9e68105fadfd3ab609e03dfae1e9e Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 10:46:29 +0900 Subject: [PATCH 021/103] Add Mp4FileTime and FixedPointNumber --- src/basic_types.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++- src/boxes.rs | 64 ++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 3 +- 3 files changed, 132 insertions(+), 4 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 340bbbb..ee90488 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -1,4 +1,7 @@ -use std::io::{Read, Write}; +use std::{ + io::{Read, Write}, + time::Duration, +}; use crate::{ boxes::{FtypBox, RootBox}, @@ -320,3 +323,67 @@ impl IterUnknownBoxes for UnknownBox { std::iter::once((BoxPath::new(self.box_type), self)) } } + +/// 1904/1/1 からの経過秒数 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Mp4FileTime(u64); + +impl Mp4FileTime { + pub const fn from_secs(secs: u64) -> Self { + Self(secs) + } + + pub const fn as_secs(self) -> u64 { + self.0 + } + + pub const fn from_unix_time(unix_time: Duration) -> Self { + let delta = 2082844800; // 1904/1/1 から 1970/1/1 までの経過秒数 + let unix_time_secs = unix_time.as_secs(); + Self::from_secs(unix_time_secs + delta) + } +} + +impl Encode for Mp4FileTime { + fn encode(&self, writer: &mut W) -> Result<()> { + self.0.encode(writer) + } +} + +impl Decode for Mp4FileTime { + fn decode(reader: &mut R) -> Result { + Decode::decode(reader).map(Self) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct FixedPointNumber { + pub integer: T, + pub fraction: T, +} + +impl FixedPointNumber { + pub fn integer(value: T) -> Self { + Self { + integer: value, + fraction: T::default(), + } + } +} + +impl Encode for FixedPointNumber { + fn encode(&self, writer: &mut W) -> Result<()> { + self.integer.encode(writer)?; + self.fraction.encode(writer)?; + Ok(()) + } +} + +impl Decode for FixedPointNumber { + fn decode(reader: &mut R) -> Result { + Ok(Self { + integer: T::decode(reader)?, + fraction: T::decode(reader)?, + }) + } +} diff --git a/src/boxes.rs b/src/boxes.rs index fd8a6f4..6a7a71c 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2,8 +2,8 @@ use std::io::{Read, Write}; use crate::{ io::{ExternalBytes, PeekReader}, - BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Encode, IterUnknownBoxes, Result, - UnknownBox, + BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Encode, FixedPointNumber, + IterUnknownBoxes, Mp4FileTime, Result, UnknownBox, }; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -337,3 +337,63 @@ impl IterUnknownBoxes for MoovBox { .map(|(path, b)| (path.join(Self::TYPE), b)) } } + +/// [ISO/IEC 14496-12] MovieHeaderBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MvhdBox { + pub creation_time: Mp4FileTime, + pub modification_time: Mp4FileTime, + pub timescale: u32, + pub duration: u64, + pub rate: FixedPointNumber, + pub volume: i16, + pub matrix: [i32; 9], + pub next_track_id: u32, +} + +impl MvhdBox { + pub const TYPE: BoxType = BoxType::Normal([b'm', b'v', b'h', b'd']); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + Ok(()) + } +} + +impl Encode for MvhdBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for MvhdBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + + header.with_box_payload_reader(reader, |reader| { + // let mut unknown_boxes = Vec::new(); + // while reader.limit() > 0 { + // unknown_boxes.push(UnknownBox::decode(reader)?); + // } + Ok(Self {}) + }) + } +} + +impl BaseBox for MvhdBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for MvhdBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + std::iter::empty() + } +} diff --git a/src/lib.rs b/src/lib.rs index 891833a..c0203a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod boxes; mod io; pub use basic_types::{ - BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, FullBox, IterUnknownBoxes, Mp4File, UnknownBox, + BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, FixedPointNumber, FullBox, IterUnknownBoxes, + Mp4File, Mp4FileTime, UnknownBox, }; pub use io::{Decode, Encode, Error, Result}; From b4e55d8041c4485d2bad2dd5c4d0d4f050e96d45 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 11:11:57 +0900 Subject: [PATCH 022/103] =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=AA=E3=83=86=E3=82=A3=E7=B3=BB=E3=82=92=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 30 +++++++++-------- src/boxes.rs | 83 +++++++++++++++++++++++++++++++++++----------- src/io.rs | 79 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 32 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index ee90488..7f1d5d6 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -5,6 +5,7 @@ use std::{ use crate::{ boxes::{FtypBox, RootBox}, + io::PeekReader, Decode, Encode, Error, Result, }; @@ -133,6 +134,12 @@ impl BoxHeader { } Ok(value) } + + pub fn peek(reader: R) -> Result<(Self, impl Read)> { + let mut reader = PeekReader::<_, { BoxHeader::MAX_SIZE }>::new(reader); + let header = BoxHeader::decode(&mut reader)?; + Ok((header, reader.into_reader())) + } } impl Encode for BoxHeader { @@ -357,21 +364,18 @@ impl Decode for Mp4FileTime { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct FixedPointNumber { - pub integer: T, - pub fraction: T, +pub struct FixedPointNumber { + pub integer: I, + pub fraction: F, } -impl FixedPointNumber { - pub fn integer(value: T) -> Self { - Self { - integer: value, - fraction: T::default(), - } +impl FixedPointNumber { + pub const fn new(integer: I, fraction: F) -> Self { + Self { integer, fraction } } } -impl Encode for FixedPointNumber { +impl Encode for FixedPointNumber { fn encode(&self, writer: &mut W) -> Result<()> { self.integer.encode(writer)?; self.fraction.encode(writer)?; @@ -379,11 +383,11 @@ impl Encode for FixedPointNumber { } } -impl Decode for FixedPointNumber { +impl Decode for FixedPointNumber { fn decode(reader: &mut R) -> Result { Ok(Self { - integer: T::decode(reader)?, - fraction: T::decode(reader)?, + integer: I::decode(reader)?, + fraction: F::decode(reader)?, }) } } diff --git a/src/boxes.rs b/src/boxes.rs index 6a7a71c..1367a88 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1,9 +1,8 @@ use std::io::{Read, Write}; use crate::{ - io::{ExternalBytes, PeekReader}, - BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Encode, FixedPointNumber, - IterUnknownBoxes, Mp4FileTime, Result, UnknownBox, + io::ExternalBytes, BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Encode, Error, + FixedPointNumber, IterUnknownBoxes, Mp4FileTime, Result, UnknownBox, }; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -133,13 +132,12 @@ impl Encode for RootBox { impl Decode for RootBox { fn decode(reader: &mut R) -> Result { - let mut reader = PeekReader::<_, { BoxHeader::MAX_SIZE }>::new(reader); - let header = BoxHeader::decode(&mut reader)?; + let (header, mut reader) = BoxHeader::peek(reader)?; match header.box_type { - FreeBox::TYPE => Decode::decode(&mut reader.into_reader()).map(Self::Free), - MdatBox::TYPE => Decode::decode(&mut reader.into_reader()).map(Self::Mdat), - MoovBox::TYPE => Decode::decode(&mut reader.into_reader()).map(Self::Moov), - _ => Decode::decode(&mut reader.into_reader()).map(Self::Unknown), + FreeBox::TYPE => Decode::decode(&mut reader).map(Self::Free), + MdatBox::TYPE => Decode::decode(&mut reader).map(Self::Mdat), + MoovBox::TYPE => Decode::decode(&mut reader).map(Self::Moov), + _ => Decode::decode(&mut reader).map(Self::Unknown), } } } @@ -282,6 +280,7 @@ impl IterUnknownBoxes for MdatBox { /// [ISO/IEC 14496-12] MovieBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct MoovBox { + pub mvhd_box: MvhdBox, pub unknown_boxes: Vec, } @@ -289,6 +288,7 @@ impl MoovBox { pub const TYPE: BoxType = BoxType::Normal([b'm', b'o', b'o', b'v']); fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.mvhd_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -309,12 +309,25 @@ impl Decode for MoovBox { let header = BoxHeader::decode(reader)?; header.box_type.expect(Self::TYPE)?; - header.with_box_payload_reader(reader, |reader| { + header.with_box_payload_reader(reader, |mut reader| { + let mut mvhd_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { - unknown_boxes.push(UnknownBox::decode(reader)?); + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + MvhdBox::TYPE => mvhd_box = Some(Decode::decode(&mut reader)?), + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } } - Ok(Self { unknown_boxes }) + + let mvhd_box = mvhd_box + .ok_or_else(|| Error::invalid_data("Missing mandary 'mvhd' box in 'moov' box"))?; + Ok(Self { + mvhd_box, + unknown_boxes, + }) }) } } @@ -345,8 +358,8 @@ pub struct MvhdBox { pub modification_time: Mp4FileTime, pub timescale: u32, pub duration: u64, - pub rate: FixedPointNumber, - pub volume: i16, + pub rate: FixedPointNumber, + pub volume: FixedPointNumber, pub matrix: [i32; 9], pub next_track_id: u32, } @@ -354,7 +367,34 @@ pub struct MvhdBox { impl MvhdBox { pub const TYPE: BoxType = BoxType::Normal([b'm', b'v', b'h', b'd']); + pub const fn with_mandatory_fields( + creation_time: Mp4FileTime, + modification_time: Mp4FileTime, + timescale: u32, + duration: u64, + next_track_id: u32, + ) -> Self { + Self { + creation_time, + modification_time, + timescale, + duration, + rate: FixedPointNumber::new(1, 0), // 通常の再生速度 + volume: FixedPointNumber::new(1, 0), // 最大音量 + matrix: [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000], + next_track_id, + } + } + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.creation_time.encode(writer)?; + self.modification_time.encode(writer)?; + self.timescale.encode(writer)?; + self.duration.encode(writer)?; + self.rate.encode(writer)?; + self.volume.encode(writer)?; + self.matrix.encode(writer)?; + self.next_track_id.encode(writer)?; Ok(()) } } @@ -373,11 +413,16 @@ impl Decode for MvhdBox { header.box_type.expect(Self::TYPE)?; header.with_box_payload_reader(reader, |reader| { - // let mut unknown_boxes = Vec::new(); - // while reader.limit() > 0 { - // unknown_boxes.push(UnknownBox::decode(reader)?); - // } - Ok(Self {}) + Ok(Self { + creation_time: Decode::decode(reader)?, + modification_time: Decode::decode(reader)?, + timescale: Decode::decode(reader)?, + duration: Decode::decode(reader)?, + rate: Decode::decode(reader)?, + volume: Decode::decode(reader)?, + matrix: Decode::decode(reader)?, + next_track_id: Decode::decode(reader)?, + }) }) } } diff --git a/src/io.rs b/src/io.rs index 50f0da8..4e6c1e2 100644 --- a/src/io.rs +++ b/src/io.rs @@ -79,6 +79,43 @@ impl Encode for u64 { } } +impl Encode for i8 { + fn encode(&self, writer: &mut W) -> Result<()> { + writer.write_all(&self.to_be_bytes())?; + Ok(()) + } +} + +impl Encode for i16 { + fn encode(&self, writer: &mut W) -> Result<()> { + writer.write_all(&self.to_be_bytes())?; + Ok(()) + } +} + +impl Encode for i32 { + fn encode(&self, writer: &mut W) -> Result<()> { + writer.write_all(&self.to_be_bytes())?; + Ok(()) + } +} + +impl Encode for i64 { + fn encode(&self, writer: &mut W) -> Result<()> { + writer.write_all(&self.to_be_bytes())?; + Ok(()) + } +} + +impl Encode for [T; N] { + fn encode(&self, writer: &mut W) -> Result<()> { + for item in self { + item.encode(writer)?; + } + Ok(()) + } +} + pub trait Decode: Sized { fn decode(reader: &mut R) -> Result; } @@ -115,6 +152,48 @@ impl Decode for u64 { } } +impl Decode for i8 { + fn decode(reader: &mut R) -> Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + +impl Decode for i16 { + fn decode(reader: &mut R) -> Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + +impl Decode for i32 { + fn decode(reader: &mut R) -> Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + +impl Decode for i64 { + fn decode(reader: &mut R) -> Result { + let mut buf = [0; Self::BITS as usize / 8]; + reader.read_exact(&mut buf)?; + Ok(Self::from_be_bytes(buf)) + } +} + +impl Decode for [T; N] { + fn decode(reader: &mut R) -> Result { + let mut items = [T::default(); N]; + for item in &mut items { + *item = T::decode(reader)?; + } + Ok(items) + } +} + #[derive(Debug, Default)] pub struct ExternalBytes(pub u64); From eb25e46031e686044051d791ba8937230c196b16 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 11:25:18 +0900 Subject: [PATCH 023/103] Add FullBoxHeader and FullBoxFlags --- src/basic_types.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 4 +-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 7f1d5d6..5536837 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -21,8 +21,8 @@ pub trait BaseBox: Encode + Decode { } pub trait FullBox: BaseBox { - fn box_version(&self) -> u8; - fn box_flags(&self) -> u32; // u24 + fn full_box_version(&self) -> u8; + fn full_box_flags(&self) -> FullBoxFlags; } pub trait IterUnknownBoxes { @@ -199,6 +199,66 @@ impl Decode for BoxHeader { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FullBoxHeader { + pub version: u8, + pub flags: FullBoxFlags, +} + +impl FullBoxHeader { + pub fn from_box(b: &B) -> Self { + Self { + version: b.full_box_version(), + flags: b.full_box_flags(), + } + } +} + +impl Encode for FullBoxHeader { + fn encode(&self, writer: &mut W) -> Result<()> { + self.version.encode(writer)?; + self.flags.encode(writer)?; + Ok(()) + } +} + +impl Decode for FullBoxHeader { + fn decode(reader: &mut R) -> Result { + Ok(Self { + version: Decode::decode(reader)?, + flags: Decode::decode(reader)?, + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FullBoxFlags(u32); + +impl FullBoxFlags { + pub const fn new(flags: u32) -> Self { + Self(flags) + } + + pub const fn get(self) -> u32 { + self.0 + } +} + +impl Encode for FullBoxFlags { + fn encode(&self, writer: &mut W) -> Result<()> { + writer.write_all(&self.0.to_be_bytes()[..3])?; + Ok(()) + } +} + +impl Decode for FullBoxFlags { + fn decode(reader: &mut R) -> Result { + let mut buf = [0; 4]; + reader.read_exact(&mut buf[1..])?; + Ok(Self(u32::from_be_bytes(buf))) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct BoxSize(u64); diff --git a/src/lib.rs b/src/lib.rs index c0203a6..05ccd54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ pub mod boxes; mod io; pub use basic_types::{ - BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, FixedPointNumber, FullBox, IterUnknownBoxes, - Mp4File, Mp4FileTime, UnknownBox, + BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, FixedPointNumber, FullBox, FullBoxFlags, + FullBoxHeader, IterUnknownBoxes, Mp4File, Mp4FileTime, UnknownBox, }; pub use io::{Decode, Encode, Error, Result}; From 7100bed90e4f6225abd291b4d61d4ca19f2fc34e Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 11:48:31 +0900 Subject: [PATCH 024/103] Add mvhd box --- src/basic_types.rs | 12 ----- src/boxes.rs | 107 +++++++++++++++++++++++++++++++-------------- 2 files changed, 73 insertions(+), 46 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 5536837..2bce0a6 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -411,18 +411,6 @@ impl Mp4FileTime { } } -impl Encode for Mp4FileTime { - fn encode(&self, writer: &mut W) -> Result<()> { - self.0.encode(writer) - } -} - -impl Decode for Mp4FileTime { - fn decode(reader: &mut R) -> Result { - Decode::decode(reader).map(Self) - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct FixedPointNumber { pub integer: I, diff --git a/src/boxes.rs b/src/boxes.rs index 1367a88..c4a4d66 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2,7 +2,8 @@ use std::io::{Read, Write}; use crate::{ io::ExternalBytes, BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Encode, Error, - FixedPointNumber, IterUnknownBoxes, Mp4FileTime, Result, UnknownBox, + FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, IterUnknownBoxes, Mp4FileTime, Result, + UnknownBox, }; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -367,33 +368,24 @@ pub struct MvhdBox { impl MvhdBox { pub const TYPE: BoxType = BoxType::Normal([b'm', b'v', b'h', b'd']); - pub const fn with_mandatory_fields( - creation_time: Mp4FileTime, - modification_time: Mp4FileTime, - timescale: u32, - duration: u64, - next_track_id: u32, - ) -> Self { - Self { - creation_time, - modification_time, - timescale, - duration, - rate: FixedPointNumber::new(1, 0), // 通常の再生速度 - volume: FixedPointNumber::new(1, 0), // 最大音量 - matrix: [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000], - next_track_id, - } - } - fn encode_payload(&self, writer: &mut W) -> Result<()> { - self.creation_time.encode(writer)?; - self.modification_time.encode(writer)?; - self.timescale.encode(writer)?; - self.duration.encode(writer)?; + FullBoxHeader::from_box(self).encode(writer)?; + if self.full_box_version() == 1 { + self.creation_time.as_secs().encode(writer)?; + self.modification_time.as_secs().encode(writer)?; + self.timescale.encode(writer)?; + self.duration.encode(writer)?; + } else { + (self.creation_time.as_secs() as u32).encode(writer)?; + (self.modification_time.as_secs() as u32).encode(writer)?; + self.timescale.encode(writer)?; + (self.duration as u32).encode(writer)?; + } self.rate.encode(writer)?; self.volume.encode(writer)?; + [0; 2 + 4 * 2].encode(writer)?; self.matrix.encode(writer)?; + [0; 4 * 6].encode(writer)?; self.next_track_id.encode(writer)?; Ok(()) } @@ -413,20 +405,50 @@ impl Decode for MvhdBox { header.box_type.expect(Self::TYPE)?; header.with_box_payload_reader(reader, |reader| { - Ok(Self { - creation_time: Decode::decode(reader)?, - modification_time: Decode::decode(reader)?, - timescale: Decode::decode(reader)?, - duration: Decode::decode(reader)?, - rate: Decode::decode(reader)?, - volume: Decode::decode(reader)?, - matrix: Decode::decode(reader)?, - next_track_id: Decode::decode(reader)?, - }) + let full_header = FullBoxHeader::decode(reader)?; + let mut this = Self::default(); + + if full_header.version == 1 { + this.creation_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; + this.modification_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; + this.timescale = u32::decode(reader)?; + this.duration = u64::decode(reader)?; + } else { + this.creation_time = + u32::decode(reader).map(|v| Mp4FileTime::from_secs(v as u64))?; + this.modification_time = + u32::decode(reader).map(|v| Mp4FileTime::from_secs(v as u64))?; + this.timescale = u32::decode(reader)?; + this.duration = u32::decode(reader)? as u64; + } + + this.rate = FixedPointNumber::decode(reader)?; + this.volume = FixedPointNumber::decode(reader)?; + let _ = <[u8; 2 + 4 * 2]>::decode(reader)?; + this.matrix = <[i32; 9]>::decode(reader)?; + let _ = <[u8; 4 * 6]>::decode(reader)?; + this.next_track_id = u32::decode(reader)?; + + Ok(this) }) } } +impl Default for MvhdBox { + fn default() -> Self { + Self { + creation_time: Mp4FileTime::from_secs(0), + modification_time: Mp4FileTime::from_secs(0), + timescale: 0, + duration: 0, + rate: FixedPointNumber::new(1, 0), // 通常の再生速度 + volume: FixedPointNumber::new(1, 0), // 最大音量 + matrix: [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000], + next_track_id: 0, + } + } +} + impl BaseBox for MvhdBox { fn box_type(&self) -> BoxType { Self::TYPE @@ -437,6 +459,23 @@ impl BaseBox for MvhdBox { } } +impl FullBox for MvhdBox { + fn full_box_version(&self) -> u8 { + if self.creation_time.as_secs() > u32::MAX as u64 + || self.modification_time.as_secs() > u32::MAX as u64 + || self.duration > u32::MAX as u64 + { + 1 + } else { + 0 + } + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} + impl IterUnknownBoxes for MvhdBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { std::iter::empty() From 489d0e89542418c319d61233243b8ee5ecacd4dc Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 11:56:32 +0900 Subject: [PATCH 025/103] Add trak box --- src/boxes.rs | 181 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 130 insertions(+), 51 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index c4a4d66..c8d8cd3 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -282,6 +282,7 @@ impl IterUnknownBoxes for MdatBox { #[derive(Debug, Clone, PartialEq, Eq)] pub struct MoovBox { pub mvhd_box: MvhdBox, + pub trak_boxes: Vec, pub unknown_boxes: Vec, } @@ -290,11 +291,38 @@ impl MoovBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.mvhd_box.encode(writer)?; + for b in &self.trak_boxes { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } Ok(()) } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let mut mvhd_box = None; + let mut trak_boxes = Vec::new(); + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + MvhdBox::TYPE => mvhd_box = Some(Decode::decode(&mut reader)?), + TrakBox::TYPE => trak_boxes.push(Decode::decode(&mut reader)?), + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + + let mvhd_box = mvhd_box + .ok_or_else(|| Error::invalid_data("Missing mandary 'mvhd' box in 'moov' box"))?; + Ok(Self { + mvhd_box, + trak_boxes, + unknown_boxes, + }) + } } impl Encode for MoovBox { @@ -309,27 +337,7 @@ impl Decode for MoovBox { fn decode(reader: &mut R) -> Result { let header = BoxHeader::decode(reader)?; header.box_type.expect(Self::TYPE)?; - - header.with_box_payload_reader(reader, |mut reader| { - let mut mvhd_box = None; - let mut unknown_boxes = Vec::new(); - while reader.limit() > 0 { - let (header, mut reader) = BoxHeader::peek(&mut reader)?; - match header.box_type { - MvhdBox::TYPE => mvhd_box = Some(Decode::decode(&mut reader)?), - _ => { - unknown_boxes.push(UnknownBox::decode(&mut reader)?); - } - } - } - - let mvhd_box = mvhd_box - .ok_or_else(|| Error::invalid_data("Missing mandary 'mvhd' box in 'moov' box"))?; - Ok(Self { - mvhd_box, - unknown_boxes, - }) - }) + header.with_box_payload_reader(reader, Self::decode_payload) } } @@ -345,10 +353,15 @@ impl BaseBox for MoovBox { impl IterUnknownBoxes for MoovBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - self.unknown_boxes + self.trak_boxes .iter() .flat_map(|b| b.iter_unknown_boxes()) - .map(|(path, b)| (path.join(Self::TYPE), b)) + .chain( + self.unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()) + .map(|(path, b)| (path.join(Self::TYPE), b)), + ) } } @@ -389,6 +402,33 @@ impl MvhdBox { self.next_track_id.encode(writer)?; Ok(()) } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let full_header = FullBoxHeader::decode(reader)?; + let mut this = Self::default(); + + if full_header.version == 1 { + this.creation_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; + this.modification_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; + this.timescale = u32::decode(reader)?; + this.duration = u64::decode(reader)?; + } else { + this.creation_time = u32::decode(reader).map(|v| Mp4FileTime::from_secs(v as u64))?; + this.modification_time = + u32::decode(reader).map(|v| Mp4FileTime::from_secs(v as u64))?; + this.timescale = u32::decode(reader)?; + this.duration = u32::decode(reader)? as u64; + } + + this.rate = FixedPointNumber::decode(reader)?; + this.volume = FixedPointNumber::decode(reader)?; + let _ = <[u8; 2 + 4 * 2]>::decode(reader)?; + this.matrix = <[i32; 9]>::decode(reader)?; + let _ = <[u8; 4 * 6]>::decode(reader)?; + this.next_track_id = u32::decode(reader)?; + + Ok(this) + } } impl Encode for MvhdBox { @@ -403,34 +443,7 @@ impl Decode for MvhdBox { fn decode(reader: &mut R) -> Result { let header = BoxHeader::decode(reader)?; header.box_type.expect(Self::TYPE)?; - - header.with_box_payload_reader(reader, |reader| { - let full_header = FullBoxHeader::decode(reader)?; - let mut this = Self::default(); - - if full_header.version == 1 { - this.creation_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; - this.modification_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; - this.timescale = u32::decode(reader)?; - this.duration = u64::decode(reader)?; - } else { - this.creation_time = - u32::decode(reader).map(|v| Mp4FileTime::from_secs(v as u64))?; - this.modification_time = - u32::decode(reader).map(|v| Mp4FileTime::from_secs(v as u64))?; - this.timescale = u32::decode(reader)?; - this.duration = u32::decode(reader)? as u64; - } - - this.rate = FixedPointNumber::decode(reader)?; - this.volume = FixedPointNumber::decode(reader)?; - let _ = <[u8; 2 + 4 * 2]>::decode(reader)?; - this.matrix = <[i32; 9]>::decode(reader)?; - let _ = <[u8; 4 * 6]>::decode(reader)?; - this.next_track_id = u32::decode(reader)?; - - Ok(this) - }) + header.with_box_payload_reader(reader, Self::decode_payload) } } @@ -481,3 +494,69 @@ impl IterUnknownBoxes for MvhdBox { std::iter::empty() } } + +/// [ISO/IEC 14496-12] TrackBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TrakBox { + pub unknown_boxes: Vec, +} + +impl TrakBox { + pub const TYPE: BoxType = BoxType::Normal([b't', b'r', b'a', b'k']); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + + Ok(Self { unknown_boxes }) + } +} + +impl Encode for TrakBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for TrakBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for TrakBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for TrakBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + self.unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()) + .map(|(path, b)| (path.join(Self::TYPE), b)) + } +} From 133aecd0ffcc9b4594ad5442fa0ae465b20f0d04 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 12:36:14 +0900 Subject: [PATCH 026/103] Add tkhd box --- src/basic_types.rs | 18 ++++- src/boxes.rs | 187 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 199 insertions(+), 6 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 2bce0a6..eed62a4 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -235,13 +235,29 @@ impl Decode for FullBoxHeader { pub struct FullBoxFlags(u32); impl FullBoxFlags { + pub const fn empty() -> Self { + Self(0) + } + pub const fn new(flags: u32) -> Self { Self(flags) } + pub fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let flags = iter.into_iter().filter(|x| x.1).map(|x| 1 << x.0).sum(); + Self(flags) + } + pub const fn get(self) -> u32 { self.0 } + + pub const fn is_set(self, i: usize) -> bool { + (self.0 & (1 << i)) != 0 + } } impl Encode for FullBoxFlags { @@ -392,7 +408,7 @@ impl IterUnknownBoxes for UnknownBox { } /// 1904/1/1 からの経過秒数 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Mp4FileTime(u64); impl Mp4FileTime { diff --git a/src/boxes.rs b/src/boxes.rs index c8d8cd3..c65e529 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -307,8 +307,12 @@ impl MoovBox { while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; match header.box_type { - MvhdBox::TYPE => mvhd_box = Some(Decode::decode(&mut reader)?), - TrakBox::TYPE => trak_boxes.push(Decode::decode(&mut reader)?), + MvhdBox::TYPE if mvhd_box.is_none() => { + mvhd_box = Some(Decode::decode(&mut reader)?); + } + TrakBox::TYPE => { + trak_boxes.push(Decode::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -450,8 +454,8 @@ impl Decode for MvhdBox { impl Default for MvhdBox { fn default() -> Self { Self { - creation_time: Mp4FileTime::from_secs(0), - modification_time: Mp4FileTime::from_secs(0), + creation_time: Mp4FileTime::default(), + modification_time: Mp4FileTime::default(), timescale: 0, duration: 0, rate: FixedPointNumber::new(1, 0), // 通常の再生速度 @@ -498,6 +502,7 @@ impl IterUnknownBoxes for MvhdBox { /// [ISO/IEC 14496-12] TrackBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct TrakBox { + pub tkhd_box: TkhdBox, pub unknown_boxes: Vec, } @@ -505,6 +510,7 @@ impl TrakBox { pub const TYPE: BoxType = BoxType::Normal([b't', b'r', b'a', b'k']); fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.tkhd_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -512,17 +518,26 @@ impl TrakBox { } fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let mut tkhd_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; match header.box_type { + TkhdBox::TYPE if tkhd_box.is_none() => { + tkhd_box = Some(TkhdBox::decode(&mut reader)?) + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } } } - Ok(Self { unknown_boxes }) + let tkhd_box = tkhd_box + .ok_or_else(|| Error::invalid_data("Missing mandary 'tkhd' box in 'trak' box"))?; + Ok(Self { + tkhd_box, + unknown_boxes, + }) } } @@ -560,3 +575,165 @@ impl IterUnknownBoxes for TrakBox { .map(|(path, b)| (path.join(Self::TYPE), b)) } } + +/// [ISO/IEC 14496-12] TrackHeaderBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TkhdBox { + pub flag_track_enabled: bool, + pub flag_track_in_movie: bool, + pub flag_track_in_preview: bool, + pub flag_track_size_is_aspect_ratio: bool, + + pub creation_time: Mp4FileTime, + pub modification_time: Mp4FileTime, + pub track_id: u32, + pub duration: u64, + pub layer: i16, + pub alternate_group: i16, + pub volume: FixedPointNumber, + pub matrix: [i32; 9], + pub width: FixedPointNumber, + pub height: FixedPointNumber, +} + +impl TkhdBox { + pub const TYPE: BoxType = BoxType::Normal([b't', b'k', b'h', b'd']); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + if self.full_box_version() == 1 { + self.creation_time.as_secs().encode(writer)?; + self.modification_time.as_secs().encode(writer)?; + self.track_id.encode(writer)?; + [0; 4].encode(writer)?; + self.duration.encode(writer)?; + } else { + (self.creation_time.as_secs() as u32).encode(writer)?; + (self.modification_time.as_secs() as u32).encode(writer)?; + self.track_id.encode(writer)?; + [0; 4].encode(writer)?; + (self.duration as u32).encode(writer)?; + } + [0; 4 * 2].encode(writer)?; + self.layer.encode(writer)?; + self.alternate_group.encode(writer)?; + self.volume.encode(writer)?; + [0; 2].encode(writer)?; + self.matrix.encode(writer)?; + self.width.encode(writer)?; + self.height.encode(writer)?; + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let full_header = FullBoxHeader::decode(reader)?; + let mut this = Self::default(); + + this.flag_track_enabled = full_header.flags.is_set(0); + this.flag_track_in_movie = full_header.flags.is_set(1); + this.flag_track_in_preview = full_header.flags.is_set(2); + this.flag_track_size_is_aspect_ratio = full_header.flags.is_set(3); + + if full_header.version == 1 { + this.creation_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; + this.modification_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; + this.track_id = u32::decode(reader)?; + let _ = <[u8; 4]>::decode(reader)?; + this.duration = u64::decode(reader)?; + } else { + this.creation_time = u32::decode(reader).map(|v| Mp4FileTime::from_secs(v as u64))?; + this.modification_time = + u32::decode(reader).map(|v| Mp4FileTime::from_secs(v as u64))?; + this.track_id = u32::decode(reader)?; + let _ = <[u8; 4]>::decode(reader)?; + this.duration = u32::decode(reader)? as u64; + } + + let _ = <[u8; 4 * 2]>::decode(reader)?; + this.layer = i16::decode(reader)?; + this.alternate_group = i16::decode(reader)?; + this.volume = FixedPointNumber::decode(reader)?; + let _ = <[u8; 2]>::decode(reader)?; + this.matrix = <[i32; 9]>::decode(reader)?; + this.width = FixedPointNumber::decode(reader)?; + this.height = FixedPointNumber::decode(reader)?; + + Ok(this) + } +} + +impl Encode for TkhdBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for TkhdBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl Default for TkhdBox { + fn default() -> Self { + Self { + flag_track_enabled: false, + flag_track_in_movie: false, + flag_track_in_preview: false, + flag_track_size_is_aspect_ratio: false, + + creation_time: Mp4FileTime::default(), + modification_time: Mp4FileTime::default(), + track_id: 0, + duration: 0, + layer: 0, + alternate_group: 0, + volume: FixedPointNumber::new(0, 0), + matrix: [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000], + width: FixedPointNumber::new(0, 0), + height: FixedPointNumber::new(0, 0), + } + } +} + +impl BaseBox for TkhdBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for TkhdBox { + fn full_box_version(&self) -> u8 { + if self.creation_time.as_secs() > u32::MAX as u64 + || self.modification_time.as_secs() > u32::MAX as u64 + || self.duration > u32::MAX as u64 + { + 1 + } else { + 0 + } + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::from_iter([ + (0, self.flag_track_enabled), + (1, self.flag_track_in_movie), + (2, self.flag_track_in_preview), + (3, self.flag_track_size_is_aspect_ratio), + ]) + } +} + +impl IterUnknownBoxes for TkhdBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + std::iter::empty() + } +} From 188a1f19546797e2f88b022bb38bf2d3c49a221d Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 12:45:40 +0900 Subject: [PATCH 027/103] Add edts box --- src/boxes.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index c65e529..b040d65 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -363,9 +363,9 @@ impl IterUnknownBoxes for MoovBox { .chain( self.unknown_boxes .iter() - .flat_map(|b| b.iter_unknown_boxes()) - .map(|(path, b)| (path.join(Self::TYPE), b)), + .flat_map(|b| b.iter_unknown_boxes()), ) + .map(|(path, b)| (path.join(Self::TYPE), b)) } } @@ -503,6 +503,7 @@ impl IterUnknownBoxes for MvhdBox { #[derive(Debug, Clone, PartialEq, Eq)] pub struct TrakBox { pub tkhd_box: TkhdBox, + pub edts_box: Option, pub unknown_boxes: Vec, } @@ -511,6 +512,9 @@ impl TrakBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.tkhd_box.encode(writer)?; + if let Some(b) = &self.edts_box { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -519,6 +523,7 @@ impl TrakBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let mut tkhd_box = None; + let mut edts_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -526,6 +531,9 @@ impl TrakBox { TkhdBox::TYPE if tkhd_box.is_none() => { tkhd_box = Some(TkhdBox::decode(&mut reader)?) } + EdtsBox::TYPE if edts_box.is_none() => { + edts_box = Some(EdtsBox::decode(&mut reader)?) + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -536,6 +544,7 @@ impl TrakBox { .ok_or_else(|| Error::invalid_data("Missing mandary 'tkhd' box in 'trak' box"))?; Ok(Self { tkhd_box, + edts_box, unknown_boxes, }) } @@ -569,9 +578,14 @@ impl BaseBox for TrakBox { impl IterUnknownBoxes for TrakBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - self.unknown_boxes + self.edts_box .iter() .flat_map(|b| b.iter_unknown_boxes()) + .chain( + self.unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()), + ) .map(|(path, b)| (path.join(Self::TYPE), b)) } } @@ -737,3 +751,69 @@ impl IterUnknownBoxes for TkhdBox { std::iter::empty() } } + +/// [ISO/IEC 14496-12] EditBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EdtsBox { + pub unknown_boxes: Vec, +} + +impl EdtsBox { + pub const TYPE: BoxType = BoxType::Normal([b'e', b'd', b't', b's']); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + + Ok(Self { unknown_boxes }) + } +} + +impl Encode for EdtsBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for EdtsBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for EdtsBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for EdtsBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + self.unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()) + .map(|(path, b)| (path.join(Self::TYPE), b)) + } +} From 370246cbb86382f3d6ebe1720fe28c5cfcb997fe Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 12:55:15 +0900 Subject: [PATCH 028/103] Add predefined brands --- src/boxes.rs | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index b040d65..69cf44b 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -10,7 +10,23 @@ use crate::{ pub struct Brand([u8; 4]); impl Brand { - // TODO: Add constants for the predefined brands + pub const ISOM: Self = Self::new(*b"isom"); + pub const ISO2: Self = Self::new(*b"iso2"); + pub const MP71: Self = Self::new(*b"mp71"); + pub const ISO3: Self = Self::new(*b"iso3"); + pub const ISO4: Self = Self::new(*b"iso4"); + pub const ISO5: Self = Self::new(*b"iso5"); + pub const ISO6: Self = Self::new(*b"iso6"); + pub const ISO7: Self = Self::new(*b"iso7"); + pub const ISO8: Self = Self::new(*b"iso8"); + pub const ISO9: Self = Self::new(*b"iso9"); + pub const ISOA: Self = Self::new(*b"isoa"); + pub const ISOB: Self = Self::new(*b"isob"); + pub const RELO: Self = Self::new(*b"relo"); + + pub const MP41: Self = Self::new(*b"mp41"); + pub const AVC1: Self = Self::new(*b"avc1"); + pub const AV01: Self = Self::new(*b"av01"); pub const fn new(brand: [u8; 4]) -> Self { Self(brand) @@ -55,7 +71,7 @@ pub struct FtypBox { } impl FtypBox { - pub const TYPE: BoxType = BoxType::Normal([b'f', b't', b'y', b'p']); + pub const TYPE: BoxType = BoxType::Normal(*b"ftyp"); fn encode_payload(&self, writer: &mut W) -> Result<()> { self.major_brand.encode(writer)?; @@ -183,7 +199,7 @@ pub struct FreeBox { } impl FreeBox { - pub const TYPE: BoxType = BoxType::Normal([b'f', b'r', b'e', b'e']); + pub const TYPE: BoxType = BoxType::Normal(*b"free"); } impl Encode for FreeBox { @@ -229,7 +245,7 @@ pub struct MdatBox { } impl MdatBox { - pub const TYPE: BoxType = BoxType::Normal([b'm', b'd', b'a', b't']); + pub const TYPE: BoxType = BoxType::Normal(*b"mdat"); } impl Encode for MdatBox { @@ -287,7 +303,7 @@ pub struct MoovBox { } impl MoovBox { - pub const TYPE: BoxType = BoxType::Normal([b'm', b'o', b'o', b'v']); + pub const TYPE: BoxType = BoxType::Normal(*b"moov"); fn encode_payload(&self, writer: &mut W) -> Result<()> { self.mvhd_box.encode(writer)?; @@ -383,7 +399,7 @@ pub struct MvhdBox { } impl MvhdBox { - pub const TYPE: BoxType = BoxType::Normal([b'm', b'v', b'h', b'd']); + pub const TYPE: BoxType = BoxType::Normal(*b"mvhd"); fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; @@ -508,7 +524,7 @@ pub struct TrakBox { } impl TrakBox { - pub const TYPE: BoxType = BoxType::Normal([b't', b'r', b'a', b'k']); + pub const TYPE: BoxType = BoxType::Normal(*b"trak"); fn encode_payload(&self, writer: &mut W) -> Result<()> { self.tkhd_box.encode(writer)?; @@ -611,7 +627,7 @@ pub struct TkhdBox { } impl TkhdBox { - pub const TYPE: BoxType = BoxType::Normal([b't', b'k', b'h', b'd']); + pub const TYPE: BoxType = BoxType::Normal(*b"tkhd"); fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; @@ -759,7 +775,7 @@ pub struct EdtsBox { } impl EdtsBox { - pub const TYPE: BoxType = BoxType::Normal([b'e', b'd', b't', b's']); + pub const TYPE: BoxType = BoxType::Normal(*b"edts"); fn encode_payload(&self, writer: &mut W) -> Result<()> { for b in &self.unknown_boxes { From 16bb9ceaf807ef5bf8bdfc94915261f9dceb10f3 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 15:49:37 +0900 Subject: [PATCH 029/103] Add elst box --- src/boxes.rs | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 69cf44b..4574e3e 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -771,6 +771,7 @@ impl IterUnknownBoxes for TkhdBox { /// [ISO/IEC 14496-12] EditBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct EdtsBox { + pub elst_box: Option, pub unknown_boxes: Vec, } @@ -778,6 +779,9 @@ impl EdtsBox { pub const TYPE: BoxType = BoxType::Normal(*b"edts"); fn encode_payload(&self, writer: &mut W) -> Result<()> { + if let Some(b) = &self.elst_box { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -785,17 +789,23 @@ impl EdtsBox { } fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let mut elst_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; match header.box_type { + ElstBox::TYPE if elst_box.is_none() => { + elst_box = Some(ElstBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } } } - - Ok(Self { unknown_boxes }) + Ok(Self { + elst_box, + unknown_boxes, + }) } } @@ -833,3 +843,112 @@ impl IterUnknownBoxes for EdtsBox { .map(|(path, b)| (path.join(Self::TYPE), b)) } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ElstEntry { + pub edit_duration: u64, + pub media_time: i64, + pub media_rate: FixedPointNumber, +} + +/// [ISO/IEC 14496-12] EditListBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ElstBox { + pub entries: Vec, +} + +impl ElstBox { + pub const TYPE: BoxType = BoxType::Normal(*b"elst"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + + let version = self.full_box_version(); + for entry in &self.entries { + if version == 1 { + entry.edit_duration.encode(writer)?; + entry.media_time.encode(writer)?; + } else { + (entry.edit_duration as u32).encode(writer)?; + (entry.media_time as i32).encode(writer)?; + } + entry.media_rate.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let full_header = FullBoxHeader::decode(reader)?; + + let mut entries = Vec::new(); + let count = u32::decode(reader)? as usize; + for _ in 0..count { + let edit_duration; + let media_time; + if full_header.version == 1 { + edit_duration = u64::decode(reader)?; + media_time = i64::decode(reader)?; + } else { + edit_duration = u32::decode(reader)? as u64; + media_time = i32::decode(reader)? as i64; + } + let media_rate = FixedPointNumber::decode(reader)?; + entries.push(ElstEntry { + edit_duration, + media_time, + media_rate, + }); + } + + Ok(Self { entries }) + } +} + +impl Encode for ElstBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for ElstBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for ElstBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for ElstBox { + fn full_box_version(&self) -> u8 { + let large = self.entries.iter().any(|x| { + u32::try_from(x.edit_duration).is_err() || i32::try_from(x.media_time).is_err() + }); + if large { + 1 + } else { + 0 + } + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} + +impl IterUnknownBoxes for ElstBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + std::iter::empty() + } +} From 6879d68ab650050244ae9158e0ff5a4a2c3a088b Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 15:55:18 +0900 Subject: [PATCH 030/103] Add mdia box --- src/boxes.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index 4574e3e..c0353d9 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -520,6 +520,7 @@ impl IterUnknownBoxes for MvhdBox { pub struct TrakBox { pub tkhd_box: TkhdBox, pub edts_box: Option, + pub mdia_box: MdiaBox, pub unknown_boxes: Vec, } @@ -531,6 +532,7 @@ impl TrakBox { if let Some(b) = &self.edts_box { b.encode(writer)?; } + self.mdia_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -540,6 +542,7 @@ impl TrakBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let mut tkhd_box = None; let mut edts_box = None; + let mut mdia_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -550,6 +553,9 @@ impl TrakBox { EdtsBox::TYPE if edts_box.is_none() => { edts_box = Some(EdtsBox::decode(&mut reader)?) } + MdiaBox::TYPE if mdia_box.is_none() => { + mdia_box = Some(MdiaBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -558,9 +564,12 @@ impl TrakBox { let tkhd_box = tkhd_box .ok_or_else(|| Error::invalid_data("Missing mandary 'tkhd' box in 'trak' box"))?; + let mdia_box = mdia_box + .ok_or_else(|| Error::invalid_data("Missing mandary 'mdia' box in 'trak' box"))?; Ok(Self { tkhd_box, edts_box, + mdia_box, unknown_boxes, }) } @@ -597,6 +606,7 @@ impl IterUnknownBoxes for TrakBox { self.edts_box .iter() .flat_map(|b| b.iter_unknown_boxes()) + .chain(self.mdia_box.iter_unknown_boxes()) .chain( self.unknown_boxes .iter() @@ -952,3 +962,68 @@ impl IterUnknownBoxes for ElstBox { std::iter::empty() } } + +/// [ISO/IEC 14496-12] MediaBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MdiaBox { + pub unknown_boxes: Vec, +} + +impl MdiaBox { + pub const TYPE: BoxType = BoxType::Normal(*b"mdia"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + Ok(Self { unknown_boxes }) + } +} + +impl Encode for MdiaBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for MdiaBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for MdiaBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for MdiaBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + self.unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()) + .map(|(path, b)| (path.join(Self::TYPE), b)) + } +} From 7f6afe55fe6bd37550b28dfd81aba628dc220ef8 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 16:27:48 +0900 Subject: [PATCH 031/103] Add mdhd box --- src/boxes.rs | 223 +++++++++++++++++++++++++++++++++++++-------------- src/io.rs | 4 + 2 files changed, 165 insertions(+), 62 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index c0353d9..5d9a417 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -122,12 +122,6 @@ impl BaseBox for FtypBox { } } -impl IterUnknownBoxes for FtypBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - std::iter::empty() - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub enum RootBox { Free(FreeBox), @@ -182,10 +176,8 @@ impl BaseBox for RootBox { impl IterUnknownBoxes for RootBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { match self { - RootBox::Free(b) => { - Box::new(b.iter_unknown_boxes()) as Box> - } - RootBox::Mdat(b) => Box::new(b.iter_unknown_boxes()), + RootBox::Free(_) => Box::new(std::iter::empty()) as Box>, + RootBox::Mdat(_) => Box::new(std::iter::empty()), RootBox::Moov(b) => Box::new(b.iter_unknown_boxes()), RootBox::Unknown(b) => Box::new(b.iter_unknown_boxes()), } @@ -231,12 +223,6 @@ impl BaseBox for FreeBox { } } -impl IterUnknownBoxes for FreeBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - std::iter::empty() - } -} - /// [ISO/IEC 14496-12] MediaDataBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct MdatBox { @@ -288,12 +274,6 @@ impl BaseBox for MdatBox { } } -impl IterUnknownBoxes for MdatBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - std::iter::empty() - } -} - /// [ISO/IEC 14496-12] MovieBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct MoovBox { @@ -373,14 +353,13 @@ impl BaseBox for MoovBox { impl IterUnknownBoxes for MoovBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - self.trak_boxes + let iter0 = self.trak_boxes.iter().flat_map(|b| b.iter_unknown_boxes()); + let iter1 = self + .unknown_boxes .iter() - .flat_map(|b| b.iter_unknown_boxes()) - .chain( - self.unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()), - ) + .flat_map(|b| b.iter_unknown_boxes()); + iter0 + .chain(iter1) .map(|(path, b)| (path.join(Self::TYPE), b)) } } @@ -509,12 +488,6 @@ impl FullBox for MvhdBox { } } -impl IterUnknownBoxes for MvhdBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - std::iter::empty() - } -} - /// [ISO/IEC 14496-12] TrackBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct TrakBox { @@ -603,15 +576,15 @@ impl BaseBox for TrakBox { impl IterUnknownBoxes for TrakBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - self.edts_box + let iter0 = self.edts_box.iter().flat_map(|b| b.iter_unknown_boxes()); + let iter1 = self.mdia_box.iter_unknown_boxes(); + let iter2 = self + .unknown_boxes .iter() - .flat_map(|b| b.iter_unknown_boxes()) - .chain(self.mdia_box.iter_unknown_boxes()) - .chain( - self.unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()), - ) + .flat_map(|b| b.iter_unknown_boxes()); + iter0 + .chain(iter1) + .chain(iter2) .map(|(path, b)| (path.join(Self::TYPE), b)) } } @@ -772,12 +745,6 @@ impl FullBox for TkhdBox { } } -impl IterUnknownBoxes for TkhdBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - std::iter::empty() - } -} - /// [ISO/IEC 14496-12] EditBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct EdtsBox { @@ -847,10 +814,11 @@ impl BaseBox for EdtsBox { impl IterUnknownBoxes for EdtsBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - self.unknown_boxes + let iter0 = self + .unknown_boxes .iter() - .flat_map(|b| b.iter_unknown_boxes()) - .map(|(path, b)| (path.join(Self::TYPE), b)) + .flat_map(|b| b.iter_unknown_boxes()); + iter0.map(|(path, b)| (path.join(Self::TYPE), b)) } } @@ -957,15 +925,10 @@ impl FullBox for ElstBox { } } -impl IterUnknownBoxes for ElstBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - std::iter::empty() - } -} - /// [ISO/IEC 14496-12] MediaBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct MdiaBox { + pub mdhd_box: MdhdBox, pub unknown_boxes: Vec, } @@ -973,6 +936,7 @@ impl MdiaBox { pub const TYPE: BoxType = BoxType::Normal(*b"mdia"); fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.mdhd_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -980,16 +944,25 @@ impl MdiaBox { } fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let mut mdhd_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; match header.box_type { + MdhdBox::TYPE if mdhd_box.is_none() => { + mdhd_box = Some(MdhdBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } } } - Ok(Self { unknown_boxes }) + let mdhd_box = mdhd_box + .ok_or_else(|| Error::invalid_data("Missing mandary 'mdhd' box in 'trak' box"))?; + Ok(Self { + mdhd_box, + unknown_boxes, + }) } } @@ -1021,9 +994,135 @@ impl BaseBox for MdiaBox { impl IterUnknownBoxes for MdiaBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - self.unknown_boxes + let iter0 = self + .unknown_boxes .iter() - .flat_map(|b| b.iter_unknown_boxes()) - .map(|(path, b)| (path.join(Self::TYPE), b)) + .flat_map(|b| b.iter_unknown_boxes()); + iter0.map(|(path, b)| (path.join(Self::TYPE), b)) + } +} + +/// [ISO/IEC 14496-12] MediaHeaderBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MdhdBox { + pub creation_time: Mp4FileTime, + pub modification_time: Mp4FileTime, + pub timescale: u32, + pub duration: u64, + pub language: [u8; 3], // ISO-639-2/T language code +} + +impl MdhdBox { + pub const TYPE: BoxType = BoxType::Normal(*b"mdhd"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + if self.full_box_version() == 1 { + self.creation_time.as_secs().encode(writer)?; + self.modification_time.as_secs().encode(writer)?; + self.timescale.encode(writer)?; + self.duration.encode(writer)?; + } else { + (self.creation_time.as_secs() as u32).encode(writer)?; + (self.modification_time.as_secs() as u32).encode(writer)?; + self.timescale.encode(writer)?; + (self.duration as u32).encode(writer)?; + } + + let mut language = 0; + for l in &self.language { + language = (language << 5) + | l.checked_sub(0x60).ok_or_else(|| { + Error::invalid_input(&format!("Invalid language code: {:?}", self.language)) + })?; + } + language.encode(writer)?; + [0; 2].encode(writer)?; + + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let full_header = FullBoxHeader::decode(reader)?; + let mut this = Self::default(); + + if full_header.version == 1 { + this.creation_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; + this.modification_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; + this.timescale = u32::decode(reader)?; + this.duration = u64::decode(reader)?; + } else { + this.creation_time = u32::decode(reader).map(|v| Mp4FileTime::from_secs(v as u64))?; + this.modification_time = + u32::decode(reader).map(|v| Mp4FileTime::from_secs(v as u64))?; + this.timescale = u32::decode(reader)?; + this.duration = u32::decode(reader)? as u64; + } + + let language = u16::decode(reader)?; + this.language = [ + ((language >> 10) & 0b11111) as u8, + ((language >> 5) & 0b11111) as u8, + (language & 0b11111) as u8, + ]; + + let _ = <[u8; 2]>::decode(reader)?; + + Ok(this) + } +} + +impl Encode for MdhdBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for MdhdBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl Default for MdhdBox { + fn default() -> Self { + Self { + creation_time: Mp4FileTime::default(), + modification_time: Mp4FileTime::default(), + timescale: 0, + duration: 0, + language: *b"und", // undefined + } + } +} + +impl BaseBox for MdhdBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for MdhdBox { + fn full_box_version(&self) -> u8 { + if self.creation_time.as_secs() > u32::MAX as u64 + || self.modification_time.as_secs() > u32::MAX as u64 + || self.duration > u32::MAX as u64 + { + 1 + } else { + 0 + } + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) } } diff --git a/src/io.rs b/src/io.rs index 4e6c1e2..374efc2 100644 --- a/src/io.rs +++ b/src/io.rs @@ -14,6 +14,10 @@ impl Error { pub(crate) fn invalid_data(message: &str) -> Self { Self::from(std::io::Error::new(ErrorKind::InvalidData, message)) } + + pub(crate) fn invalid_input(message: &str) -> Self { + Self::from(std::io::Error::new(ErrorKind::InvalidInput, message)) + } } impl From for Error { From 78f085cb9d91e530c4e01b5a448052e09d35a6b9 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Thu, 19 Sep 2024 17:06:09 +0900 Subject: [PATCH 032/103] Add hdlr box --- src/basic_types.rs | 42 ++++++++++++++++++++++++ src/boxes.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 2 +- 3 files changed, 121 insertions(+), 2 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index eed62a4..433801f 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -455,3 +455,45 @@ impl Decode for FixedPointNumber { }) } } + +// エンコード時には終端 null が付与される文字列 +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Utf8String(String); + +impl Utf8String { + pub fn new(s: &str) -> Option { + if s.as_bytes().contains(&0) { + return None; + } + Some(Self(s.to_owned())) + } + + pub fn get(&self) -> &str { + &self.0 + } +} + +impl Encode for Utf8String { + fn encode(&self, writer: &mut W) -> Result<()> { + writer.write_all(self.0.as_bytes())?; + writer.write_all(&[0])?; + Ok(()) + } +} + +impl Decode for Utf8String { + fn decode(reader: &mut R) -> Result { + let mut bytes = Vec::new(); + loop { + let b = u8::decode(reader)?; + if b == 0 { + break; + } + bytes.push(b); + } + let s = String::from_utf8(bytes).map_err(|e| { + Error::invalid_data(&format!("Invalid UTF-8 string: {:?}", e.as_bytes())) + })?; + Ok(Self(s)) + } +} diff --git a/src/boxes.rs b/src/boxes.rs index 5d9a417..6c5afbf 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -3,7 +3,7 @@ use std::io::{Read, Write}; use crate::{ io::ExternalBytes, BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Encode, Error, FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, IterUnknownBoxes, Mp4FileTime, Result, - UnknownBox, + UnknownBox, Utf8String, }; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -929,6 +929,7 @@ impl FullBox for ElstBox { #[derive(Debug, Clone, PartialEq, Eq)] pub struct MdiaBox { pub mdhd_box: MdhdBox, + pub hdlr_box: HdlrBox, pub unknown_boxes: Vec, } @@ -937,6 +938,7 @@ impl MdiaBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.mdhd_box.encode(writer)?; + self.hdlr_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -945,6 +947,7 @@ impl MdiaBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let mut mdhd_box = None; + let mut hdlr_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -952,6 +955,9 @@ impl MdiaBox { MdhdBox::TYPE if mdhd_box.is_none() => { mdhd_box = Some(MdhdBox::decode(&mut reader)?); } + HdlrBox::TYPE if hdlr_box.is_none() => { + hdlr_box = Some(HdlrBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -959,8 +965,11 @@ impl MdiaBox { } let mdhd_box = mdhd_box .ok_or_else(|| Error::invalid_data("Missing mandary 'mdhd' box in 'trak' box"))?; + let hdlr_box = hdlr_box + .ok_or_else(|| Error::invalid_data("Missing mandary 'hdlr' box in 'trak' box"))?; Ok(Self { mdhd_box, + hdlr_box, unknown_boxes, }) } @@ -1126,3 +1135,71 @@ impl FullBox for MdhdBox { FullBoxFlags::new(0) } } + +/// [ISO/IEC 14496-12] HandlerBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HdlrBox { + pub handler_type: [u8; 4], + pub name: Utf8String, +} + +impl HdlrBox { + pub const TYPE: BoxType = BoxType::Normal(*b"hdlr"); + + pub const HANDLER_TYPE_SOUN: [u8; 4] = *b"soun"; + pub const HANDLER_TYPE_VIDE: [u8; 4] = *b"vide"; + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + [0; 4].encode(writer)?; + self.handler_type.encode(writer)?; + [0; 4 * 3].encode(writer)?; + self.name.encode(writer)?; + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let _full_header = FullBoxHeader::decode(reader)?; + let _ = <[u8; 4]>::decode(reader)?; + let handler_type = <[u8; 4]>::decode(reader)?; + let _ = <[u8; 4 * 3]>::decode(reader)?; + let name = Utf8String::decode(reader)?; + Ok(Self { handler_type, name }) + } +} + +impl Encode for HdlrBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for HdlrBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for HdlrBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for HdlrBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} diff --git a/src/lib.rs b/src/lib.rs index 05ccd54..3dfc275 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,6 @@ mod io; pub use basic_types::{ BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, FixedPointNumber, FullBox, FullBoxFlags, - FullBoxHeader, IterUnknownBoxes, Mp4File, Mp4FileTime, UnknownBox, + FullBoxHeader, IterUnknownBoxes, Mp4File, Mp4FileTime, UnknownBox, Utf8String, }; pub use io::{Decode, Encode, Error, Result}; From 8d330557b91aaac188f817bea67b21115f987079 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 10:17:45 +0900 Subject: [PATCH 033/103] Add minf box --- src/boxes.rs | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 6c5afbf..c92f97d 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -930,6 +930,7 @@ impl FullBox for ElstBox { pub struct MdiaBox { pub mdhd_box: MdhdBox, pub hdlr_box: HdlrBox, + pub minf_box: MinfBox, pub unknown_boxes: Vec, } @@ -939,6 +940,7 @@ impl MdiaBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.mdhd_box.encode(writer)?; self.hdlr_box.encode(writer)?; + self.minf_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -948,6 +950,7 @@ impl MdiaBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let mut mdhd_box = None; let mut hdlr_box = None; + let mut minf_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -958,6 +961,9 @@ impl MdiaBox { HdlrBox::TYPE if hdlr_box.is_none() => { hdlr_box = Some(HdlrBox::decode(&mut reader)?); } + MinfBox::TYPE if minf_box.is_none() => { + minf_box = Some(MinfBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -967,9 +973,12 @@ impl MdiaBox { .ok_or_else(|| Error::invalid_data("Missing mandary 'mdhd' box in 'trak' box"))?; let hdlr_box = hdlr_box .ok_or_else(|| Error::invalid_data("Missing mandary 'hdlr' box in 'trak' box"))?; + let minf_box = minf_box + .ok_or_else(|| Error::invalid_data("Missing mandary 'minf' box in 'trak' box"))?; Ok(Self { mdhd_box, hdlr_box, + minf_box, unknown_boxes, }) } @@ -1003,11 +1012,14 @@ impl BaseBox for MdiaBox { impl IterUnknownBoxes for MdiaBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self + let iter0 = self.minf_box.iter_unknown_boxes(); + let iter1 = self .unknown_boxes .iter() .flat_map(|b| b.iter_unknown_boxes()); - iter0.map(|(path, b)| (path.join(Self::TYPE), b)) + iter0 + .chain(iter1) + .map(|(path, b)| (path.join(Self::TYPE), b)) } } @@ -1203,3 +1215,106 @@ impl FullBox for HdlrBox { FullBoxFlags::new(0) } } + +/// [ISO/IEC 14496-12] MediaInformationBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MinfBox { + // vmhd か smhd のどちらかが必須 + // pub vmhd_box:Option, + // pub smhd_box:Option, + // pub dinf_box: DinfBox, + // pub stbl_box:StblBox, + pub unknown_boxes: Vec, +} + +impl MinfBox { + pub const TYPE: BoxType = BoxType::Normal(*b"minf"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + // let mut mdhd_box = None; + // let mut hdlr_box = None; + // let mut minf_box = None; + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + // MdhdBox::TYPE if mdhd_box.is_none() => { + // mdhd_box = Some(MdhdBox::decode(&mut reader)?); + // } + // HdlrBox::TYPE if hdlr_box.is_none() => { + // hdlr_box = Some(HdlrBox::decode(&mut reader)?); + // } + // MinfBox::TYPE if minf_box.is_none() => { + // minf_box = Some(MinfBox::decode(&mut reader)?); + // } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + // let mdhd_box = mdhd_box + // .ok_or_else(|| Error::invalid_data("Missing mandary 'mdhd' box in 'trak' box"))?; + // let hdlr_box = hdlr_box + // .ok_or_else(|| Error::invalid_data("Missing mandary 'hdlr' box in 'trak' box"))?; + // let minf_box = minf_box + // .ok_or_else(|| Error::invalid_data("Missing mandary 'minf' box in 'trak' box"))?; + Ok(Self { + // mdhd_box, + // hdlr_box, + // minf_box, + unknown_boxes, + }) + } +} + +impl Encode for MinfBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for MinfBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for MinfBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for MinfBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + let iter0 = std::iter::empty(); + let iter1 = std::iter::empty(); + let iter2 = std::iter::empty(); + let iter3 = std::iter::empty(); + let iter4 = self + .unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()); + iter0 + .chain(iter1) + .chain(iter2) + .chain(iter3) + .chain(iter4) + .map(|(path, b)| (path.join(Self::TYPE), b)) + } +} From 1ed8d4b6142fedf644af99de5fa925bb112de6a6 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 10:33:42 +0900 Subject: [PATCH 034/103] Add smhd box --- src/boxes.rs | 91 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index c92f97d..1d5b922 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1219,9 +1219,9 @@ impl FullBox for HdlrBox { /// [ISO/IEC 14496-12] MediaInformationBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct MinfBox { - // vmhd か smhd のどちらかが必須 + // smhd か vmhd のどちらかが必須 + pub smhd_box: Option, // pub vmhd_box:Option, - // pub smhd_box:Option, // pub dinf_box: DinfBox, // pub stbl_box:StblBox, pub unknown_boxes: Vec, @@ -1231,6 +1231,9 @@ impl MinfBox { pub const TYPE: BoxType = BoxType::Normal(*b"minf"); fn encode_payload(&self, writer: &mut W) -> Result<()> { + if let Some(b) = &self.smhd_box { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1238,37 +1241,23 @@ impl MinfBox { } fn decode_payload(mut reader: &mut std::io::Take) -> Result { - // let mut mdhd_box = None; - // let mut hdlr_box = None; - // let mut minf_box = None; + let mut smhd_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; match header.box_type { - // MdhdBox::TYPE if mdhd_box.is_none() => { - // mdhd_box = Some(MdhdBox::decode(&mut reader)?); - // } - // HdlrBox::TYPE if hdlr_box.is_none() => { - // hdlr_box = Some(HdlrBox::decode(&mut reader)?); - // } - // MinfBox::TYPE if minf_box.is_none() => { - // minf_box = Some(MinfBox::decode(&mut reader)?); - // } + SmhdBox::TYPE if smhd_box.is_none() => { + smhd_box = Some(SmhdBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } } } - // let mdhd_box = mdhd_box - // .ok_or_else(|| Error::invalid_data("Missing mandary 'mdhd' box in 'trak' box"))?; - // let hdlr_box = hdlr_box - // .ok_or_else(|| Error::invalid_data("Missing mandary 'hdlr' box in 'trak' box"))?; // let minf_box = minf_box // .ok_or_else(|| Error::invalid_data("Missing mandary 'minf' box in 'trak' box"))?; Ok(Self { - // mdhd_box, - // hdlr_box, - // minf_box, + smhd_box, unknown_boxes, }) } @@ -1318,3 +1307,63 @@ impl IterUnknownBoxes for MinfBox { .map(|(path, b)| (path.join(Self::TYPE), b)) } } + +/// [ISO/IEC 14496-12] SoundMediaHeaderBox class +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct SmhdBox { + pub balance: i16, +} + +impl SmhdBox { + pub const TYPE: BoxType = BoxType::Normal(*b"smhd"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + self.balance.encode(writer)?; + [0; 2].encode(writer)?; + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let _full_header = FullBoxHeader::decode(reader)?; + let balance = i16::decode(reader)?; + let _ = <[u8; 2]>::decode(reader)?; + Ok(Self { balance }) + } +} + +impl Encode for SmhdBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for SmhdBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for SmhdBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for SmhdBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} From 3fbbf82cf6887527b83bbaee1138c2747c73e85c Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 10:37:02 +0900 Subject: [PATCH 035/103] Add vmhd box --- src/boxes.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/src/boxes.rs b/src/boxes.rs index 1d5b922..2bb4bd9 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1221,7 +1221,7 @@ impl FullBox for HdlrBox { pub struct MinfBox { // smhd か vmhd のどちらかが必須 pub smhd_box: Option, - // pub vmhd_box:Option, + pub vmhd_box: Option, // pub dinf_box: DinfBox, // pub stbl_box:StblBox, pub unknown_boxes: Vec, @@ -1234,6 +1234,9 @@ impl MinfBox { if let Some(b) = &self.smhd_box { b.encode(writer)?; } + if let Some(b) = &self.vmhd_box { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1242,6 +1245,7 @@ impl MinfBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let mut smhd_box = None; + let mut vmhd_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1249,6 +1253,9 @@ impl MinfBox { SmhdBox::TYPE if smhd_box.is_none() => { smhd_box = Some(SmhdBox::decode(&mut reader)?); } + VmhdBox::TYPE if vmhd_box.is_none() => { + vmhd_box = Some(VmhdBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -1258,6 +1265,7 @@ impl MinfBox { // .ok_or_else(|| Error::invalid_data("Missing mandary 'minf' box in 'trak' box"))?; Ok(Self { smhd_box, + vmhd_box, unknown_boxes, }) } @@ -1367,3 +1375,67 @@ impl FullBox for SmhdBox { FullBoxFlags::new(0) } } + +/// [ISO/IEC 14496-12] VideoMediaHeaderBox class +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct VmhdBox { + pub graphicsmode: u16, + pub opcolor: [u16; 3], +} + +impl VmhdBox { + pub const TYPE: BoxType = BoxType::Normal(*b"vmhd"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + self.graphicsmode.encode(writer)?; + self.opcolor.encode(writer)?; + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let _full_header = FullBoxHeader::decode(reader)?; + let graphicsmode = u16::decode(reader)?; + let opcolor = <[u16; 3]>::decode(reader)?; + Ok(Self { + graphicsmode, + opcolor, + }) + } +} + +impl Encode for VmhdBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for VmhdBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for VmhdBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for VmhdBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} From 37379e472a69c3374e9df88d33f2802ab49302f4 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 10:40:19 +0900 Subject: [PATCH 036/103] Add dinf box --- src/boxes.rs | 96 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 9 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 2bb4bd9..e94fbb8 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1222,7 +1222,7 @@ pub struct MinfBox { // smhd か vmhd のどちらかが必須 pub smhd_box: Option, pub vmhd_box: Option, - // pub dinf_box: DinfBox, + pub dinf_box: DinfBox, // pub stbl_box:StblBox, pub unknown_boxes: Vec, } @@ -1237,6 +1237,7 @@ impl MinfBox { if let Some(b) = &self.vmhd_box { b.encode(writer)?; } + self.dinf_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1246,6 +1247,7 @@ impl MinfBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let mut smhd_box = None; let mut vmhd_box = None; + let mut dinf_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1256,16 +1258,20 @@ impl MinfBox { VmhdBox::TYPE if vmhd_box.is_none() => { vmhd_box = Some(VmhdBox::decode(&mut reader)?); } + DinfBox::TYPE if dinf_box.is_none() => { + dinf_box = Some(DinfBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } } } - // let minf_box = minf_box - // .ok_or_else(|| Error::invalid_data("Missing mandary 'minf' box in 'trak' box"))?; + let dinf_box = dinf_box + .ok_or_else(|| Error::invalid_data("Missing mandary 'dinf' box in 'trak' box"))?; Ok(Self { smhd_box, vmhd_box, + dinf_box, unknown_boxes, }) } @@ -1299,19 +1305,15 @@ impl BaseBox for MinfBox { impl IterUnknownBoxes for MinfBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = std::iter::empty(); + let iter0 = self.dinf_box.iter_unknown_boxes(); let iter1 = std::iter::empty(); - let iter2 = std::iter::empty(); - let iter3 = std::iter::empty(); - let iter4 = self + let iter2 = self .unknown_boxes .iter() .flat_map(|b| b.iter_unknown_boxes()); iter0 .chain(iter1) .chain(iter2) - .chain(iter3) - .chain(iter4) .map(|(path, b)| (path.join(Self::TYPE), b)) } } @@ -1439,3 +1441,79 @@ impl FullBox for VmhdBox { FullBoxFlags::new(0) } } + +/// [ISO/IEC 14496-12] MediaInformationBox class +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct DinfBox { + // pub stbl_box:StblBox, + pub unknown_boxes: Vec, +} + +impl DinfBox { + pub const TYPE: BoxType = BoxType::Normal(*b"dinf"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + //let mut smhd_box = None; + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + // SmhdBox::TYPE if smhd_box.is_none() => { + // smhd_box = Some(SmhdBox::decode(&mut reader)?); + // } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + // let dinf_box = dinf_box + // .ok_or_else(|| Error::invalid_data("Missing mandary 'dinf' box in 'trak' box"))?; + Ok(Self { unknown_boxes }) + } +} + +impl Encode for DinfBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for DinfBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for DinfBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for DinfBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + let iter0 = std::iter::empty(); + let iter1 = self + .unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()); + iter0 + .chain(iter1) + .map(|(path, b)| (path.join(Self::TYPE), b)) + } +} From f5f81bea1b5b809afad95f5eae1567e65760c0db Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 11:02:21 +0900 Subject: [PATCH 037/103] Add dref box --- src/boxes.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 10 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index e94fbb8..2b230fc 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1442,10 +1442,10 @@ impl FullBox for VmhdBox { } } -/// [ISO/IEC 14496-12] MediaInformationBox class +/// [ISO/IEC 14496-12] DataInformationBox class #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct DinfBox { - // pub stbl_box:StblBox, + pub dref_box: DrefBox, pub unknown_boxes: Vec, } @@ -1453,6 +1453,7 @@ impl DinfBox { pub const TYPE: BoxType = BoxType::Normal(*b"dinf"); fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.dref_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1460,22 +1461,25 @@ impl DinfBox { } fn decode_payload(mut reader: &mut std::io::Take) -> Result { - //let mut smhd_box = None; + let mut dref_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; match header.box_type { - // SmhdBox::TYPE if smhd_box.is_none() => { - // smhd_box = Some(SmhdBox::decode(&mut reader)?); - // } + DrefBox::TYPE if dref_box.is_none() => { + dref_box = Some(DrefBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } } } - // let dinf_box = dinf_box - // .ok_or_else(|| Error::invalid_data("Missing mandary 'dinf' box in 'trak' box"))?; - Ok(Self { unknown_boxes }) + let dref_box = dref_box + .ok_or_else(|| Error::invalid_data("Missing mandary 'dref' box in 'trak' box"))?; + Ok(Self { + dref_box, + unknown_boxes, + }) } } @@ -1507,7 +1511,7 @@ impl BaseBox for DinfBox { impl IterUnknownBoxes for DinfBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = std::iter::empty(); + let iter0 = self.dref_box.iter_unknown_boxes(); let iter1 = self .unknown_boxes .iter() @@ -1517,3 +1521,83 @@ impl IterUnknownBoxes for DinfBox { .map(|(path, b)| (path.join(Self::TYPE), b)) } } + +/// [ISO/IEC 14496-12] DataReferenceBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DrefBox { + pub url_box: Option, + pub unknown_boxes: Vec, +} + +impl DrefBox { + pub const TYPE: BoxType = BoxType::Normal(*b"dref"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + let entry_count = (self.url_box.is_some() as usize + self.unknown_boxes.len()) as u32; + entry_count.encode(writer)?; + if let Some(b) = &self.url_box { + b.encode(writer)?; + } + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let entry_count = u32::decode(reader)?; + let mut url_box = None; + let mut unknown_boxes = Vec::new(); + for _ in 0..entry_count { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + UrlBox::TYPE if url_box.is_none() => { + url_box = Some(UrlBox::decode(&mut reader)?); + } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + Ok(Self { + url_box, + unknown_boxes, + }) + } +} + +impl Encode for DrefBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for DrefBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for DrefBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for DrefBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + let iter0 = self + .unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()); + iter0.map(|(path, b)| (path.join(Self::TYPE), b)) + } +} From b0c38d50aa4462b1e9b86cd20efe9465cc0f5b4a Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 11:09:31 +0900 Subject: [PATCH 038/103] Add 'url ' box --- src/boxes.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/src/boxes.rs b/src/boxes.rs index 2b230fc..8e91812 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1443,7 +1443,7 @@ impl FullBox for VmhdBox { } /// [ISO/IEC 14496-12] DataInformationBox class -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct DinfBox { pub dref_box: DrefBox, pub unknown_boxes: Vec, @@ -1533,6 +1533,7 @@ impl DrefBox { pub const TYPE: BoxType = BoxType::Normal(*b"dref"); fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; let entry_count = (self.url_box.is_some() as usize + self.unknown_boxes.len()) as u32; entry_count.encode(writer)?; if let Some(b) = &self.url_box { @@ -1545,6 +1546,7 @@ impl DrefBox { } fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let _ = FullBoxHeader::decode(reader)?; let entry_count = u32::decode(reader)?; let mut url_box = None; let mut unknown_boxes = Vec::new(); @@ -1566,6 +1568,15 @@ impl DrefBox { } } +impl Default for DrefBox { + fn default() -> Self { + Self { + url_box: Some(UrlBox::default()), + unknown_boxes: Vec::new(), + } + } +} + impl Encode for DrefBox { fn encode(&self, writer: &mut W) -> Result<()> { BoxHeader::from_box(self).encode(writer)?; @@ -1592,6 +1603,16 @@ impl BaseBox for DrefBox { } } +impl FullBox for DrefBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} + impl IterUnknownBoxes for DrefBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { let iter0 = self @@ -1601,3 +1622,67 @@ impl IterUnknownBoxes for DrefBox { iter0.map(|(path, b)| (path.join(Self::TYPE), b)) } } + +/// [ISO/IEC 14496-12] DataEntryUrlBox class +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct UrlBox { + pub location: Option, +} + +impl UrlBox { + pub const TYPE: BoxType = BoxType::Normal(*b"url "); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + if let Some(l) = &self.location { + l.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let full_header = FullBoxHeader::decode(reader)?; + let location = if full_header.flags.is_set(1) { + Some(Utf8String::decode(reader)?) + } else { + None + }; + Ok(Self { location }) + } +} + +impl Encode for UrlBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for UrlBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for UrlBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for UrlBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(self.location.is_some() as u32) + } +} From 7054585be1f2ceaba689c9a66d75303d9d334210 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 11:15:05 +0900 Subject: [PATCH 039/103] Add stbl box --- src/boxes.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 8e91812..fe23ce4 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1223,7 +1223,7 @@ pub struct MinfBox { pub smhd_box: Option, pub vmhd_box: Option, pub dinf_box: DinfBox, - // pub stbl_box:StblBox, + pub stbl_box: StblBox, pub unknown_boxes: Vec, } @@ -1238,6 +1238,7 @@ impl MinfBox { b.encode(writer)?; } self.dinf_box.encode(writer)?; + self.stbl_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1248,6 +1249,7 @@ impl MinfBox { let mut smhd_box = None; let mut vmhd_box = None; let mut dinf_box = None; + let mut stbl_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1261,6 +1263,9 @@ impl MinfBox { DinfBox::TYPE if dinf_box.is_none() => { dinf_box = Some(DinfBox::decode(&mut reader)?); } + StblBox::TYPE if stbl_box.is_none() => { + stbl_box = Some(StblBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -1268,10 +1273,13 @@ impl MinfBox { } let dinf_box = dinf_box .ok_or_else(|| Error::invalid_data("Missing mandary 'dinf' box in 'trak' box"))?; + let stbl_box = stbl_box + .ok_or_else(|| Error::invalid_data("Missing mandary 'stbl' box in 'trak' box"))?; Ok(Self { smhd_box, vmhd_box, dinf_box, + stbl_box, unknown_boxes, }) } @@ -1306,7 +1314,7 @@ impl BaseBox for MinfBox { impl IterUnknownBoxes for MinfBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { let iter0 = self.dinf_box.iter_unknown_boxes(); - let iter1 = std::iter::empty(); + let iter1 = self.stbl_box.iter_unknown_boxes(); let iter2 = self .unknown_boxes .iter() @@ -1686,3 +1694,79 @@ impl FullBox for UrlBox { FullBoxFlags::new(self.location.is_some() as u32) } } + +/// [ISO/IEC 14496-12] SampleTableBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StblBox { + pub unknown_boxes: Vec, +} + +impl StblBox { + pub const TYPE: BoxType = BoxType::Normal(*b"stbl"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + //self.dref_box.encode(writer)?; + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + //let mut dref_box = None; + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + // DrefBox::TYPE if dref_box.is_none() => { + // dref_box = Some(DrefBox::decode(&mut reader)?); + // } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + // let dref_box = dref_box + // .ok_or_else(|| Error::invalid_data("Missing mandary 'dref' box in 'trak' box"))?; + Ok(Self { unknown_boxes }) + } +} + +impl Encode for StblBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for StblBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for StblBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for StblBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + let iter0 = std::iter::empty(); + let iter1 = self + .unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()); + iter0 + .chain(iter1) + .map(|(path, b)| (path.join(Self::TYPE), b)) + } +} From 4c0e7edd7b0469f38a511b4630e2cf98dbf1755f Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 11:23:51 +0900 Subject: [PATCH 040/103] Add Either enum --- src/basic_types.rs | 6 ++++++ src/boxes.rs | 27 ++++++++++++++------------- src/lib.rs | 2 +- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 433801f..709b450 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -497,3 +497,9 @@ impl Decode for Utf8String { Ok(Self(s)) } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Either { + A(A), + B(B), +} diff --git a/src/boxes.rs b/src/boxes.rs index fe23ce4..f146f34 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1,9 +1,9 @@ use std::io::{Read, Write}; use crate::{ - io::ExternalBytes, BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Encode, Error, - FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, IterUnknownBoxes, Mp4FileTime, Result, - UnknownBox, Utf8String, + io::ExternalBytes, BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Either, Encode, + Error, FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, IterUnknownBoxes, Mp4FileTime, + Result, UnknownBox, Utf8String, }; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -1219,9 +1219,7 @@ impl FullBox for HdlrBox { /// [ISO/IEC 14496-12] MediaInformationBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct MinfBox { - // smhd か vmhd のどちらかが必須 - pub smhd_box: Option, - pub vmhd_box: Option, + pub smhd_or_vmhd_box: Either, pub dinf_box: DinfBox, pub stbl_box: StblBox, pub unknown_boxes: Vec, @@ -1231,11 +1229,9 @@ impl MinfBox { pub const TYPE: BoxType = BoxType::Normal(*b"minf"); fn encode_payload(&self, writer: &mut W) -> Result<()> { - if let Some(b) = &self.smhd_box { - b.encode(writer)?; - } - if let Some(b) = &self.vmhd_box { - b.encode(writer)?; + match &self.smhd_or_vmhd_box { + Either::A(b) => b.encode(writer)?, + Either::B(b) => b.encode(writer)?, } self.dinf_box.encode(writer)?; self.stbl_box.encode(writer)?; @@ -1271,13 +1267,18 @@ impl MinfBox { } } } + let smhd_or_vmhd_box = smhd_box + .map(Either::A) + .or(vmhd_box.map(Either::B)) + .ok_or_else(|| { + Error::invalid_data("Either 'smhd' box or 'vmhd' box is mandatory in 'trak' box") + })?; let dinf_box = dinf_box .ok_or_else(|| Error::invalid_data("Missing mandary 'dinf' box in 'trak' box"))?; let stbl_box = stbl_box .ok_or_else(|| Error::invalid_data("Missing mandary 'stbl' box in 'trak' box"))?; Ok(Self { - smhd_box, - vmhd_box, + smhd_or_vmhd_box, dinf_box, stbl_box, unknown_boxes, diff --git a/src/lib.rs b/src/lib.rs index 3dfc275..687611a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ pub mod boxes; mod io; pub use basic_types::{ - BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, FixedPointNumber, FullBox, FullBoxFlags, + BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Either, FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, IterUnknownBoxes, Mp4File, Mp4FileTime, UnknownBox, Utf8String, }; pub use io::{Decode, Encode, Error, Result}; From 5702038ff7a35581d7bdc500a25ec3741779074d Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 11:42:56 +0900 Subject: [PATCH 041/103] Add utility method --- src/basic_types.rs | 11 ++++ src/boxes.rs | 141 +++++++++++++++++++++++++++++++++++---------- src/io.rs | 8 +++ 3 files changed, 130 insertions(+), 30 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 709b450..53bdf16 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -359,6 +359,17 @@ impl std::fmt::Debug for BoxType { } } +impl std::fmt::Display for BoxType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let BoxType::Normal(ty) = self { + if let Ok(ty) = std::str::from_utf8(&ty[..]) { + return write!(f, "{ty}"); + } + } + write!(f, "{:?}", self.as_bytes()) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct UnknownBox { pub box_type: BoxType, diff --git a/src/boxes.rs b/src/boxes.rs index f146f34..2877439 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -315,8 +315,7 @@ impl MoovBox { } } - let mvhd_box = mvhd_box - .ok_or_else(|| Error::invalid_data("Missing mandary 'mvhd' box in 'moov' box"))?; + let mvhd_box = mvhd_box.ok_or_else(|| Error::missing_box("mvhd", Self::TYPE))?; Ok(Self { mvhd_box, trak_boxes, @@ -535,10 +534,8 @@ impl TrakBox { } } - let tkhd_box = tkhd_box - .ok_or_else(|| Error::invalid_data("Missing mandary 'tkhd' box in 'trak' box"))?; - let mdia_box = mdia_box - .ok_or_else(|| Error::invalid_data("Missing mandary 'mdia' box in 'trak' box"))?; + let tkhd_box = tkhd_box.ok_or_else(|| Error::missing_box("tkhd", Self::TYPE))?; + let mdia_box = mdia_box.ok_or_else(|| Error::missing_box("mdia", Self::TYPE))?; Ok(Self { tkhd_box, edts_box, @@ -969,12 +966,9 @@ impl MdiaBox { } } } - let mdhd_box = mdhd_box - .ok_or_else(|| Error::invalid_data("Missing mandary 'mdhd' box in 'trak' box"))?; - let hdlr_box = hdlr_box - .ok_or_else(|| Error::invalid_data("Missing mandary 'hdlr' box in 'trak' box"))?; - let minf_box = minf_box - .ok_or_else(|| Error::invalid_data("Missing mandary 'minf' box in 'trak' box"))?; + let mdhd_box = mdhd_box.ok_or_else(|| Error::missing_box("mdhd", Self::TYPE))?; + let hdlr_box = hdlr_box.ok_or_else(|| Error::missing_box("hdlr", Self::TYPE))?; + let minf_box = minf_box.ok_or_else(|| Error::missing_box("minf", Self::TYPE))?; Ok(Self { mdhd_box, hdlr_box, @@ -1270,13 +1264,9 @@ impl MinfBox { let smhd_or_vmhd_box = smhd_box .map(Either::A) .or(vmhd_box.map(Either::B)) - .ok_or_else(|| { - Error::invalid_data("Either 'smhd' box or 'vmhd' box is mandatory in 'trak' box") - })?; - let dinf_box = dinf_box - .ok_or_else(|| Error::invalid_data("Missing mandary 'dinf' box in 'trak' box"))?; - let stbl_box = stbl_box - .ok_or_else(|| Error::invalid_data("Missing mandary 'stbl' box in 'trak' box"))?; + .ok_or_else(|| Error::missing_box("smhd | vmhd", Self::TYPE))?; + let dinf_box = dinf_box.ok_or_else(|| Error::missing_box("dinf", Self::TYPE))?; + let stbl_box = stbl_box.ok_or_else(|| Error::missing_box("stbl", Self::TYPE))?; Ok(Self { smhd_or_vmhd_box, dinf_box, @@ -1483,8 +1473,7 @@ impl DinfBox { } } } - let dref_box = dref_box - .ok_or_else(|| Error::invalid_data("Missing mandary 'dref' box in 'trak' box"))?; + let dref_box = dref_box.ok_or_else(|| Error::missing_box("dref", Self::TYPE))?; Ok(Self { dref_box, unknown_boxes, @@ -1699,6 +1688,7 @@ impl FullBox for UrlBox { /// [ISO/IEC 14496-12] SampleTableBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct StblBox { + pub stsd_box: StsdBox, pub unknown_boxes: Vec, } @@ -1706,7 +1696,7 @@ impl StblBox { pub const TYPE: BoxType = BoxType::Normal(*b"stbl"); fn encode_payload(&self, writer: &mut W) -> Result<()> { - //self.dref_box.encode(writer)?; + self.stsd_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1714,22 +1704,24 @@ impl StblBox { } fn decode_payload(mut reader: &mut std::io::Take) -> Result { - //let mut dref_box = None; + let mut stsd_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; match header.box_type { - // DrefBox::TYPE if dref_box.is_none() => { - // dref_box = Some(DrefBox::decode(&mut reader)?); - // } + StsdBox::TYPE if stsd_box.is_none() => { + stsd_box = Some(StsdBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } } } - // let dref_box = dref_box - // .ok_or_else(|| Error::invalid_data("Missing mandary 'dref' box in 'trak' box"))?; - Ok(Self { unknown_boxes }) + let stsd_box = stsd_box.ok_or_else(|| Error::missing_box("stsd", Self::TYPE))?; + Ok(Self { + stsd_box, + unknown_boxes, + }) } } @@ -1761,7 +1753,7 @@ impl BaseBox for StblBox { impl IterUnknownBoxes for StblBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = std::iter::empty(); + let iter0 = self.stsd_box.iter_unknown_boxes(); let iter1 = self .unknown_boxes .iter() @@ -1771,3 +1763,92 @@ impl IterUnknownBoxes for StblBox { .map(|(path, b)| (path.join(Self::TYPE), b)) } } + +/// [ISO/IEC 14496-12] SampleDescriptionBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StsdBox { + //pub entries: Vec, + pub unknown_boxes: Vec, +} + +impl StsdBox { + pub const TYPE: BoxType = BoxType::Normal(*b"stsd"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + let entry_count = (self.unknown_boxes.len()) as u32; + entry_count.encode(writer)?; + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let _ = FullBoxHeader::decode(reader)?; + let entry_count = u32::decode(reader)?; + // let mut url_box = None; + let mut unknown_boxes = Vec::new(); + for _ in 0..entry_count { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + // UrlBox::TYPE if url_box.is_none() => { + // url_box = Some(UrlBox::decode(&mut reader)?); + // } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + Ok(Self { + //url_box, + unknown_boxes, + }) + } +} + +impl Encode for StsdBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for StsdBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for StsdBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for StsdBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} + +impl IterUnknownBoxes for StsdBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + let iter0 = self + .unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()); + iter0.map(|(path, b)| (path.join(Self::TYPE), b)) + } +} diff --git a/src/io.rs b/src/io.rs index 374efc2..6acf9d9 100644 --- a/src/io.rs +++ b/src/io.rs @@ -3,6 +3,8 @@ use std::{ io::{Cursor, ErrorKind, Read, Write}, }; +use crate::BoxType; + pub type Result = std::result::Result; pub struct Error { @@ -18,6 +20,12 @@ impl Error { pub(crate) fn invalid_input(message: &str) -> Self { Self::from(std::io::Error::new(ErrorKind::InvalidInput, message)) } + + pub(crate) fn missing_box(missing_box: &str, parent_box: BoxType) -> Self { + Self::invalid_data(&format!( + "Missing mandatory '{missing_box}' box in '{parent_box}' box" + )) + } } impl From for Error { From 8caf706099588bf2bb3aced6b6a0f667906c531a Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 11:51:22 +0900 Subject: [PATCH 042/103] Add SampleEntry --- src/boxes.rs | 73 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 2877439..675271f 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1767,8 +1767,7 @@ impl IterUnknownBoxes for StblBox { /// [ISO/IEC 14496-12] SampleDescriptionBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct StsdBox { - //pub entries: Vec, - pub unknown_boxes: Vec, + pub entries: Vec, } impl StsdBox { @@ -1776,34 +1775,22 @@ impl StsdBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; - let entry_count = (self.unknown_boxes.len()) as u32; + let entry_count = (self.entries.len()) as u32; entry_count.encode(writer)?; - for b in &self.unknown_boxes { + for b in &self.entries { b.encode(writer)?; } Ok(()) } - fn decode_payload(mut reader: &mut std::io::Take) -> Result { + fn decode_payload(reader: &mut std::io::Take) -> Result { let _ = FullBoxHeader::decode(reader)?; let entry_count = u32::decode(reader)?; - // let mut url_box = None; - let mut unknown_boxes = Vec::new(); + let mut entries = Vec::new(); for _ in 0..entry_count { - let (header, mut reader) = BoxHeader::peek(&mut reader)?; - match header.box_type { - // UrlBox::TYPE if url_box.is_none() => { - // url_box = Some(UrlBox::decode(&mut reader)?); - // } - _ => { - unknown_boxes.push(UnknownBox::decode(&mut reader)?); - } - } + entries.push(SampleEntry::decode(reader)?); } - Ok(Self { - //url_box, - unknown_boxes, - }) + Ok(Self { entries }) } } @@ -1845,10 +1832,48 @@ impl FullBox for StsdBox { impl IterUnknownBoxes for StsdBox { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); + let iter0 = self.entries.iter().flat_map(|b| b.iter_unknown_boxes()); iter0.map(|(path, b)| (path.join(Self::TYPE), b)) } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SampleEntry { + Unknown(UnknownBox), +} + +impl Encode for SampleEntry { + fn encode(&self, writer: &mut W) -> Result<()> { + match self { + SampleEntry::Unknown(b) => b.encode(writer), + } + } +} + +impl Decode for SampleEntry { + fn decode(reader: &mut R) -> Result { + Ok(Self::Unknown(Decode::decode(reader)?)) + } +} + +impl BaseBox for SampleEntry { + fn box_type(&self) -> BoxType { + match self { + SampleEntry::Unknown(b) => b.box_type(), + } + } + + fn box_payload_size(&self) -> u64 { + match self { + SampleEntry::Unknown(b) => b.box_payload_size(), + } + } +} + +impl IterUnknownBoxes for SampleEntry { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + match self { + SampleEntry::Unknown(b) => b.iter_unknown_boxes(), + } + } +} From 5d17c1e5a48c8fb35c822b5b0a34a31f49ded074 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 12:25:44 +0900 Subject: [PATCH 043/103] Add avc1 box --- src/boxes.rs | 174 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 169 insertions(+), 5 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 675271f..070d077 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1839,33 +1839,41 @@ impl IterUnknownBoxes for StsdBox { #[derive(Debug, Clone, PartialEq, Eq)] pub enum SampleEntry { + Avc1(Avc1Box), Unknown(UnknownBox), } impl Encode for SampleEntry { fn encode(&self, writer: &mut W) -> Result<()> { match self { - SampleEntry::Unknown(b) => b.encode(writer), + Self::Avc1(b) => b.encode(writer), + Self::Unknown(b) => b.encode(writer), } } } impl Decode for SampleEntry { fn decode(reader: &mut R) -> Result { - Ok(Self::Unknown(Decode::decode(reader)?)) + let (header, mut reader) = BoxHeader::peek(reader)?; + match header.box_type { + Avc1Box::TYPE => Decode::decode(&mut reader).map(Self::Avc1), + _ => Decode::decode(&mut reader).map(Self::Unknown), + } } } impl BaseBox for SampleEntry { fn box_type(&self) -> BoxType { match self { - SampleEntry::Unknown(b) => b.box_type(), + Self::Avc1(b) => b.box_type(), + Self::Unknown(b) => b.box_type(), } } fn box_payload_size(&self) -> u64 { match self { - SampleEntry::Unknown(b) => b.box_payload_size(), + Self::Avc1(b) => b.box_payload_size(), + Self::Unknown(b) => b.box_payload_size(), } } } @@ -1873,7 +1881,163 @@ impl BaseBox for SampleEntry { impl IterUnknownBoxes for SampleEntry { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { match self { - SampleEntry::Unknown(b) => b.iter_unknown_boxes(), + Self::Avc1(b) => Box::new(b.iter_unknown_boxes()) as Box>, + Self::Unknown(b) => Box::new(b.iter_unknown_boxes()), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VisualSampleEntryFields { + data_reference_index: u16, + width: u16, + height: u16, + horizresolution: FixedPointNumber, + vertresolution: FixedPointNumber, + frame_count: u16, + compressorname: [u8; 32], + depth: u16, +} + +impl Default for VisualSampleEntryFields { + fn default() -> Self { + Self { + data_reference_index: 1, + width: 0, + height: 0, + horizresolution: FixedPointNumber::new(0x48, 0), // 72 dpi + vertresolution: FixedPointNumber::new(0x48, 0), // 72 dpi + frame_count: 1, + compressorname: [0; 32], + depth: 0x0018, // images are in colour with no alpha } } } + +impl Encode for VisualSampleEntryFields { + fn encode(&self, writer: &mut W) -> Result<()> { + [0; 6].encode(writer)?; + self.data_reference_index.encode(writer)?; + [0; 2 + 2 + 4 * 3].encode(writer)?; + self.width.encode(writer)?; + self.height.encode(writer)?; + self.horizresolution.encode(writer)?; + self.vertresolution.encode(writer)?; + [0; 4].encode(writer)?; + self.frame_count.encode(writer)?; + self.compressorname.encode(writer)?; + self.depth.encode(writer)?; + [0; 2].encode(writer)?; + Ok(()) + } +} + +impl Decode for VisualSampleEntryFields { + fn decode(reader: &mut R) -> Result { + let _ = <[u8; 6]>::decode(reader)?; + let data_reference_index = u16::decode(reader)?; + let _ = <[u8; 2 + 2 + 4 * 3]>::decode(reader)?; + let width = u16::decode(reader)?; + let height = u16::decode(reader)?; + let horizresolution = FixedPointNumber::decode(reader)?; + let vertresolution = FixedPointNumber::decode(reader)?; + let _ = <[u8; 4]>::decode(reader)?; + let frame_count = u16::decode(reader)?; + let compressorname = <[u8; 32]>::decode(reader)?; + let depth = u16::decode(reader)?; + let _ = <[u8; 2]>::decode(reader)?; + Ok(Self { + data_reference_index, + width, + height, + horizresolution, + vertresolution, + frame_count, + compressorname, + depth, + }) + } +} + +/// [ISO/IEC 14496-15] AVCSampleEntry class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Avc1Box { + pub visual: VisualSampleEntryFields, + + // btrt :: #mp4_btrt_box{} | undefined, + // pasp :: #mp4_pasp_box{} | undefined, + // avcc :: #mp4_avcc_box{} + pub unknown_boxes: Vec, +} + +impl Avc1Box { + pub const TYPE: BoxType = BoxType::Normal(*b"avc1"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.visual.encode(writer)?; + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let visual = VisualSampleEntryFields::decode(reader)?; + dbg!(&visual); + //let mut stsd_box = None; + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + // StsdBox::TYPE if stsd_box.is_none() => { + // stsd_box = Some(StsdBox::decode(&mut reader)?); + // } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + //let stsd_box = stsd_box.ok_or_else(|| Error::missing_box("stsd", Self::TYPE))?; + Ok(Self { + visual, + //stsd_box, + unknown_boxes, + }) + } +} + +impl Encode for Avc1Box { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for Avc1Box { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for Avc1Box { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for Avc1Box { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + let iter0 = self + .unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()); + iter0.map(|(path, b)| (path.join(Self::TYPE), b)) + } +} From b60606c3254692b0aebf087e2f4c11da8976345b Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 14:07:31 +0900 Subject: [PATCH 044/103] Add avcC box --- src/basic_types.rs | 21 +++++ src/boxes.rs | 197 ++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 2 +- 3 files changed, 210 insertions(+), 10 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 53bdf16..546ecea 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -514,3 +514,24 @@ pub enum Either { A(A), B(B), } + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Uint(u8); + +impl Uint { + pub const fn new(v: u8) -> Self { + Self(v & (1 << BITS) - 1) + } + + pub const fn checked_new(v: u8) -> Option { + if v.leading_zeros() < u8::BITS - BITS { + None + } else { + Some(Self(v)) + } + } + + pub const fn get(self) -> u8 { + self.0 + } +} diff --git a/src/boxes.rs b/src/boxes.rs index 070d077..0ae7ff4 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -3,7 +3,7 @@ use std::io::{Read, Write}; use crate::{ io::ExternalBytes, BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Either, Encode, Error, FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, IterUnknownBoxes, Mp4FileTime, - Result, UnknownBox, Utf8String, + Result, Uint, UnknownBox, Utf8String, }; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -1963,10 +1963,10 @@ impl Decode for VisualSampleEntryFields { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Avc1Box { pub visual: VisualSampleEntryFields, + pub avcc_box: AvccBox, // btrt :: #mp4_btrt_box{} | undefined, // pasp :: #mp4_pasp_box{} | undefined, - // avcc :: #mp4_avcc_box{} pub unknown_boxes: Vec, } @@ -1975,6 +1975,7 @@ impl Avc1Box { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.visual.encode(writer)?; + self.avcc_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1983,24 +1984,23 @@ impl Avc1Box { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let visual = VisualSampleEntryFields::decode(reader)?; - dbg!(&visual); - //let mut stsd_box = None; + let mut avcc_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; match header.box_type { - // StsdBox::TYPE if stsd_box.is_none() => { - // stsd_box = Some(StsdBox::decode(&mut reader)?); - // } + AvccBox::TYPE if avcc_box.is_none() => { + avcc_box = Some(AvccBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } } } - //let stsd_box = stsd_box.ok_or_else(|| Error::missing_box("stsd", Self::TYPE))?; + let avcc_box = avcc_box.ok_or_else(|| Error::missing_box("avcc", Self::TYPE))?; Ok(Self { visual, - //stsd_box, + avcc_box, unknown_boxes, }) } @@ -2041,3 +2041,182 @@ impl IterUnknownBoxes for Avc1Box { iter0.map(|(path, b)| (path.join(Self::TYPE), b)) } } + +/// [ISO/IEC 14496-15] AVCSampleEntry class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AvccBox { + configuration_version: u8, + avc_profile_indication: u8, + profile_compatibility: u8, + avc_level_indication: u8, + length_size_minus_one: Uint<2>, + sps_list: Vec>, + pps_list: Vec>, + chroma_format: Option>, + bit_depth_luma_minus8: Option>, + bit_depth_chroma_minus8: Option>, + sps_ext_list: Vec>, +} + +impl AvccBox { + pub const TYPE: BoxType = BoxType::Normal(*b"avcC"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.configuration_version.encode(writer)?; + self.avc_profile_indication.encode(writer)?; + self.profile_compatibility.encode(writer)?; + self.avc_level_indication.encode(writer)?; + (0b111111_00 | self.length_size_minus_one.get()).encode(writer)?; + + let sps_count = u8::try_from(self.sps_list.len()) + .ok() + .and_then(|n| Uint::<5>::checked_new(n)) + .ok_or_else(|| Error::invalid_input("Too many SPSs"))?; + (0b111_00000 | sps_count.get()).encode(writer)?; + for sps in &self.sps_list { + let size = u16::try_from(sps.len()) + .map_err(|e| Error::invalid_input(&format!("Too long SPS: {e}")))?; + size.encode(writer)?; + writer.write_all(&sps)?; + } + + let pps_count = + u8::try_from(self.pps_list.len()).map_err(|_| Error::invalid_input("Too many PPSs"))?; + pps_count.encode(writer)?; + for pps in &self.pps_list { + let size = u16::try_from(pps.len()) + .map_err(|e| Error::invalid_input(&format!("Too long PPS: {e}")))?; + size.encode(writer)?; + writer.write_all(&pps)?; + } + + if !matches!(self.avc_profile_indication, 66 | 77 | 88) { + let chroma_format = self.chroma_format.ok_or_else(|| { + Error::invalid_input("Missing 'chroma_format' field in 'avcC' boc") + })?; + let bit_depth_luma_minus8 = self.bit_depth_luma_minus8.ok_or_else(|| { + Error::invalid_input("Missing 'bit_depth_luma_minus8' field in 'avcC' boc") + })?; + let bit_depth_chroma_minus8 = self.bit_depth_chroma_minus8.ok_or_else(|| { + Error::invalid_input("Missing 'bit_depth_chroma_minus8' field in 'avcC' boc") + })?; + (0b111111_00 | chroma_format.get()).encode(writer)?; + (0b11111_000 | bit_depth_luma_minus8.get()).encode(writer)?; + (0b11111_000 | bit_depth_chroma_minus8.get()).encode(writer)?; + + let sps_ext_count = u8::try_from(self.sps_ext_list.len()) + .map_err(|_| Error::invalid_input("Too many SPS EXTs"))?; + sps_ext_count.encode(writer)?; + for sps_ext in &self.sps_ext_list { + let size = u16::try_from(sps_ext.len()) + .map_err(|e| Error::invalid_input(&format!("Too long SPS EXT: {e}")))?; + size.encode(writer)?; + writer.write_all(&sps_ext)?; + } + } + + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let configuration_version = u8::decode(reader)?; + let avc_profile_indication = u8::decode(reader)?; + let profile_compatibility = u8::decode(reader)?; + let avc_level_indication = u8::decode(reader)?; + let length_size_minus_one = Uint::new(u8::decode(reader)?); + + let sps_count = Uint::<5>::new(u8::decode(reader)?).get() as usize; + let mut sps_list = Vec::with_capacity(sps_count); + for _ in 0..sps_count { + let size = u16::decode(reader)? as usize; + let mut sps = vec![0; size]; + reader.read_exact(&mut sps)?; + sps_list.push(sps); + } + + let pps_count = u8::decode(reader)? as usize; + let mut pps_list = Vec::with_capacity(pps_count); + for _ in 0..pps_count { + let size = u16::decode(reader)? as usize; + let mut pps = vec![0; size]; + reader.read_exact(&mut pps)?; + pps_list.push(pps); + } + + let mut chroma_format = None; + let mut bit_depth_luma_minus8 = None; + let mut bit_depth_chroma_minus8 = None; + let mut sps_ext_list = Vec::new(); + if !matches!(avc_profile_indication, 66 | 77 | 88) { + chroma_format = Some(Uint::new(u8::decode(reader)?)); + bit_depth_luma_minus8 = Some(Uint::new(u8::decode(reader)?)); + bit_depth_chroma_minus8 = Some(Uint::new(u8::decode(reader)?)); + + let sps_ext_count = u8::decode(reader)? as usize; + for _ in 0..sps_ext_count { + let size = u16::decode(reader)? as usize; + let mut pps = vec![0; size]; + reader.read_exact(&mut pps)?; + sps_ext_list.push(pps); + } + } + + Ok(Self { + configuration_version, + avc_profile_indication, + profile_compatibility, + avc_level_indication, + length_size_minus_one, + sps_list, + pps_list, + chroma_format, + bit_depth_luma_minus8, + bit_depth_chroma_minus8, + sps_ext_list, + }) + } +} + +impl Default for AvccBox { + fn default() -> Self { + Self { + configuration_version: 1, + avc_profile_indication: 0, + profile_compatibility: 0, + avc_level_indication: 0, + length_size_minus_one: Uint::new(0), + sps_list: Vec::new(), + pps_list: Vec::new(), + chroma_format: None, + bit_depth_luma_minus8: None, + bit_depth_chroma_minus8: None, + sps_ext_list: Vec::new(), + } + } +} + +impl Encode for AvccBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for AvccBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for AvccBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 687611a..df8fa64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,6 @@ mod io; pub use basic_types::{ BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Either, FixedPointNumber, FullBox, FullBoxFlags, - FullBoxHeader, IterUnknownBoxes, Mp4File, Mp4FileTime, UnknownBox, Utf8String, + FullBoxHeader, IterUnknownBoxes, Mp4File, Mp4FileTime, Uint, UnknownBox, Utf8String, }; pub use io::{Decode, Encode, Error, Result}; From 85be748fb2a38487784edd3de2e38bdc9bfaf6ed Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 14:12:46 +0900 Subject: [PATCH 045/103] Add pasp box --- src/boxes.rs | 99 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 21 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 0ae7ff4..e31ecc6 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1889,14 +1889,14 @@ impl IterUnknownBoxes for SampleEntry { #[derive(Debug, Clone, PartialEq, Eq)] pub struct VisualSampleEntryFields { - data_reference_index: u16, - width: u16, - height: u16, - horizresolution: FixedPointNumber, - vertresolution: FixedPointNumber, - frame_count: u16, - compressorname: [u8; 32], - depth: u16, + pub data_reference_index: u16, + pub width: u16, + pub height: u16, + pub horizresolution: FixedPointNumber, + pub vertresolution: FixedPointNumber, + pub frame_count: u16, + pub compressorname: [u8; 32], + pub depth: u16, } impl Default for VisualSampleEntryFields { @@ -1964,9 +1964,8 @@ impl Decode for VisualSampleEntryFields { pub struct Avc1Box { pub visual: VisualSampleEntryFields, pub avcc_box: AvccBox, - + pub pasp_box: Option, // btrt :: #mp4_btrt_box{} | undefined, - // pasp :: #mp4_pasp_box{} | undefined, pub unknown_boxes: Vec, } @@ -1976,6 +1975,9 @@ impl Avc1Box { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.visual.encode(writer)?; self.avcc_box.encode(writer)?; + if let Some(b) = &self.pasp_box { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1985,6 +1987,7 @@ impl Avc1Box { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let visual = VisualSampleEntryFields::decode(reader)?; let mut avcc_box = None; + let mut pasp_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1992,6 +1995,9 @@ impl Avc1Box { AvccBox::TYPE if avcc_box.is_none() => { avcc_box = Some(AvccBox::decode(&mut reader)?); } + PaspBox::TYPE if pasp_box.is_none() => { + pasp_box = Some(PaspBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -2001,6 +2007,7 @@ impl Avc1Box { Ok(Self { visual, avcc_box, + pasp_box, unknown_boxes, }) } @@ -2045,17 +2052,17 @@ impl IterUnknownBoxes for Avc1Box { /// [ISO/IEC 14496-15] AVCSampleEntry class #[derive(Debug, Clone, PartialEq, Eq)] pub struct AvccBox { - configuration_version: u8, - avc_profile_indication: u8, - profile_compatibility: u8, - avc_level_indication: u8, - length_size_minus_one: Uint<2>, - sps_list: Vec>, - pps_list: Vec>, - chroma_format: Option>, - bit_depth_luma_minus8: Option>, - bit_depth_chroma_minus8: Option>, - sps_ext_list: Vec>, + pub configuration_version: u8, + pub avc_profile_indication: u8, + pub profile_compatibility: u8, + pub avc_level_indication: u8, + pub length_size_minus_one: Uint<2>, + pub sps_list: Vec>, + pub pps_list: Vec>, + pub chroma_format: Option>, + pub bit_depth_luma_minus8: Option>, + pub bit_depth_chroma_minus8: Option>, + pub sps_ext_list: Vec>, } impl AvccBox { @@ -2220,3 +2227,53 @@ impl BaseBox for AvccBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } } + +/// [ISO/IEC 14496-12] PixelAspectRatioBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PaspBox { + pub h_spacing: u32, + pub v_spacing: u32, +} + +impl PaspBox { + pub const TYPE: BoxType = BoxType::Normal(*b"pasp"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.h_spacing.encode(writer)?; + self.v_spacing.encode(writer)?; + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + Ok(Self { + h_spacing: u32::decode(reader)?, + v_spacing: u32::decode(reader)?, + }) + } +} + +impl Encode for PaspBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for PaspBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for PaspBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} From b36fbc78ab0013ed5a00490df20d5102b22d74b1 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 14:15:44 +0900 Subject: [PATCH 046/103] Add btrt box --- src/boxes.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/boxes.rs b/src/boxes.rs index e31ecc6..dbfd979 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1965,7 +1965,7 @@ pub struct Avc1Box { pub visual: VisualSampleEntryFields, pub avcc_box: AvccBox, pub pasp_box: Option, - // btrt :: #mp4_btrt_box{} | undefined, + pub btrt_box: Option, pub unknown_boxes: Vec, } @@ -1978,6 +1978,9 @@ impl Avc1Box { if let Some(b) = &self.pasp_box { b.encode(writer)?; } + if let Some(b) = &self.btrt_box { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1988,6 +1991,7 @@ impl Avc1Box { let visual = VisualSampleEntryFields::decode(reader)?; let mut avcc_box = None; let mut pasp_box = None; + let mut btrt_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1998,6 +2002,9 @@ impl Avc1Box { PaspBox::TYPE if pasp_box.is_none() => { pasp_box = Some(PaspBox::decode(&mut reader)?); } + BtrtBox::TYPE if btrt_box.is_none() => { + btrt_box = Some(BtrtBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -2008,6 +2015,7 @@ impl Avc1Box { visual, avcc_box, pasp_box, + btrt_box, unknown_boxes, }) } @@ -2277,3 +2285,56 @@ impl BaseBox for PaspBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } } + +/// [ISO/IEC 14496-12] BitRateBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BtrtBox { + pub buffer_size_db: u32, + pub max_bitrate: u32, + pub avg_bitrate: u32, +} + +impl BtrtBox { + pub const TYPE: BoxType = BoxType::Normal(*b"btrt"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.buffer_size_db.encode(writer)?; + self.max_bitrate.encode(writer)?; + self.avg_bitrate.encode(writer)?; + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + Ok(Self { + buffer_size_db: u32::decode(reader)?, + max_bitrate: u32::decode(reader)?, + avg_bitrate: u32::decode(reader)?, + }) + } +} + +impl Encode for BtrtBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for BtrtBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for BtrtBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} From 9d97c4a704682d0bfb130a3a371451e7529e7a88 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 14:21:47 +0900 Subject: [PATCH 047/103] Add stts box --- src/boxes.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index dbfd979..b9fc043 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1689,6 +1689,7 @@ impl FullBox for UrlBox { #[derive(Debug, Clone, PartialEq, Eq)] pub struct StblBox { pub stsd_box: StsdBox, + pub stts_box: SttsBox, pub unknown_boxes: Vec, } @@ -1697,6 +1698,7 @@ impl StblBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.stsd_box.encode(writer)?; + self.stts_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1705,6 +1707,7 @@ impl StblBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let mut stsd_box = None; + let mut stts_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1712,14 +1715,19 @@ impl StblBox { StsdBox::TYPE if stsd_box.is_none() => { stsd_box = Some(StsdBox::decode(&mut reader)?); } + SttsBox::TYPE if stts_box.is_none() => { + stts_box = Some(SttsBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } } } let stsd_box = stsd_box.ok_or_else(|| Error::missing_box("stsd", Self::TYPE))?; + let stts_box = stts_box.ok_or_else(|| Error::missing_box("stts", Self::TYPE))?; Ok(Self { stsd_box, + stts_box, unknown_boxes, }) } @@ -2338,3 +2346,78 @@ impl BaseBox for BtrtBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } } + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct SttsEntry { + pub sample_count: u32, + pub sample_delta: u32, +} + +/// [ISO/IEC 14496-12] TimeToSampleBox class +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct SttsBox { + pub entries: Vec, +} + +impl SttsBox { + pub const TYPE: BoxType = BoxType::Normal(*b"stts"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + (self.entries.len() as u32).encode(writer)?; + for entry in &self.entries { + entry.sample_count.encode(writer)?; + entry.sample_delta.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let _ = FullBoxHeader::decode(reader)?; + let count = u32::decode(reader)? as usize; + let mut entries = Vec::with_capacity(count); + for _ in 0..count { + entries.push(SttsEntry { + sample_count: u32::decode(reader)?, + sample_delta: u32::decode(reader)?, + }); + } + Ok(Self { entries }) + } +} + +impl Encode for SttsBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for SttsBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for SttsBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for SttsBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} From 0176eb2af0238f566b014f48c7ab583193e01d22 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 14:27:46 +0900 Subject: [PATCH 048/103] Add stsc box --- src/boxes.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index b9fc043..e2d995a 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1690,6 +1690,7 @@ impl FullBox for UrlBox { pub struct StblBox { pub stsd_box: StsdBox, pub stts_box: SttsBox, + pub stsc_box: StscBox, pub unknown_boxes: Vec, } @@ -1699,6 +1700,7 @@ impl StblBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.stsd_box.encode(writer)?; self.stts_box.encode(writer)?; + self.stsc_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1708,6 +1710,7 @@ impl StblBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let mut stsd_box = None; let mut stts_box = None; + let mut stsc_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1718,6 +1721,9 @@ impl StblBox { SttsBox::TYPE if stts_box.is_none() => { stts_box = Some(SttsBox::decode(&mut reader)?); } + StscBox::TYPE if stsc_box.is_none() => { + stsc_box = Some(StscBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -1725,9 +1731,11 @@ impl StblBox { } let stsd_box = stsd_box.ok_or_else(|| Error::missing_box("stsd", Self::TYPE))?; let stts_box = stts_box.ok_or_else(|| Error::missing_box("stts", Self::TYPE))?; + let stsc_box = stsc_box.ok_or_else(|| Error::missing_box("stsc", Self::TYPE))?; Ok(Self { stsd_box, stts_box, + stsc_box, unknown_boxes, }) } @@ -2421,3 +2429,81 @@ impl FullBox for SttsBox { FullBoxFlags::new(0) } } + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct StscEntry { + pub first_chunk: u32, + pub sample_per_chunk: u32, + pub sample_description_index: u32, +} + +/// [ISO/IEC 14496-12] SampleToChunkBox class +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct StscBox { + pub entries: Vec, +} + +impl StscBox { + pub const TYPE: BoxType = BoxType::Normal(*b"stsc"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + (self.entries.len() as u32).encode(writer)?; + for entry in &self.entries { + entry.first_chunk.encode(writer)?; + entry.sample_per_chunk.encode(writer)?; + entry.sample_description_index.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let _ = FullBoxHeader::decode(reader)?; + let count = u32::decode(reader)? as usize; + let mut entries = Vec::with_capacity(count); + for _ in 0..count { + entries.push(StscEntry { + first_chunk: u32::decode(reader)?, + sample_per_chunk: u32::decode(reader)?, + sample_description_index: u32::decode(reader)?, + }); + } + Ok(Self { entries }) + } +} + +impl Encode for StscBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for StscBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for StscBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for StscBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} From 0ddd7b5e37502f409b9f8e109b43c7eba69298ba Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 14:39:04 +0900 Subject: [PATCH 049/103] Add stsz box --- src/boxes.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 5 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index e2d995a..0399fe6 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1,4 +1,7 @@ -use std::io::{Read, Write}; +use std::{ + io::{Read, Write}, + num::NonZeroU32, +}; use crate::{ io::ExternalBytes, BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Either, Encode, @@ -1691,6 +1694,7 @@ pub struct StblBox { pub stsd_box: StsdBox, pub stts_box: SttsBox, pub stsc_box: StscBox, + pub stsz_box: StszBox, pub unknown_boxes: Vec, } @@ -1701,6 +1705,7 @@ impl StblBox { self.stsd_box.encode(writer)?; self.stts_box.encode(writer)?; self.stsc_box.encode(writer)?; + self.stsz_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1711,6 +1716,7 @@ impl StblBox { let mut stsd_box = None; let mut stts_box = None; let mut stsc_box = None; + let mut stsz_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1724,6 +1730,9 @@ impl StblBox { StscBox::TYPE if stsc_box.is_none() => { stsc_box = Some(StscBox::decode(&mut reader)?); } + StszBox::TYPE if stsz_box.is_none() => { + stsz_box = Some(StszBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -1732,10 +1741,12 @@ impl StblBox { let stsd_box = stsd_box.ok_or_else(|| Error::missing_box("stsd", Self::TYPE))?; let stts_box = stts_box.ok_or_else(|| Error::missing_box("stts", Self::TYPE))?; let stsc_box = stsc_box.ok_or_else(|| Error::missing_box("stsc", Self::TYPE))?; + let stsz_box = stsz_box.ok_or_else(|| Error::missing_box("stsz", Self::TYPE))?; Ok(Self { stsd_box, stts_box, stsc_box, + stsz_box, unknown_boxes, }) } @@ -2355,14 +2366,14 @@ impl BaseBox for BtrtBox { } } -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SttsEntry { pub sample_count: u32, pub sample_delta: u32, } /// [ISO/IEC 14496-12] TimeToSampleBox class -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SttsBox { pub entries: Vec, } @@ -2430,7 +2441,7 @@ impl FullBox for SttsBox { } } -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct StscEntry { pub first_chunk: u32, pub sample_per_chunk: u32, @@ -2438,7 +2449,7 @@ pub struct StscEntry { } /// [ISO/IEC 14496-12] SampleToChunkBox class -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct StscBox { pub entries: Vec, } @@ -2507,3 +2518,94 @@ impl FullBox for StscBox { FullBoxFlags::new(0) } } + +/// [ISO/IEC 14496-12] SampleSizeBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum StszBox { + Fixed { + sample_size: NonZeroU32, + sample_count: u32, + }, + Variable { + entry_sizes: Vec, + }, +} + +impl StszBox { + pub const TYPE: BoxType = BoxType::Normal(*b"stsz"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + match self { + StszBox::Fixed { + sample_size, + sample_count, + } => { + sample_size.get().encode(writer)?; + sample_count.encode(writer)?; + } + StszBox::Variable { entry_sizes } => { + 0u32.encode(writer)?; + (entry_sizes.len() as u32).encode(writer)?; + for size in entry_sizes { + size.encode(writer)?; + } + } + } + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let _ = FullBoxHeader::decode(reader)?; + let sample_size = u32::decode(reader)?; + let sample_count = u32::decode(reader)?; + if let Some(sample_size) = NonZeroU32::new(sample_size) { + Ok(Self::Fixed { + sample_size, + sample_count, + }) + } else { + let mut entry_sizes = Vec::with_capacity(sample_count as usize); + for _ in 0..sample_count { + entry_sizes.push(u32::decode(reader)?); + } + Ok(Self::Variable { entry_sizes }) + } + } +} + +impl Encode for StszBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for StszBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for StszBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for StszBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} From ef628096245a0d7823c92db18325bac58b15c3d2 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 14:47:07 +0900 Subject: [PATCH 050/103] Add stco and co64 boxes --- src/boxes.rs | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index 0399fe6..4e35bcb 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1695,6 +1695,7 @@ pub struct StblBox { pub stts_box: SttsBox, pub stsc_box: StscBox, pub stsz_box: StszBox, + pub stco_or_co64_box: Either, pub unknown_boxes: Vec, } @@ -1706,6 +1707,10 @@ impl StblBox { self.stts_box.encode(writer)?; self.stsc_box.encode(writer)?; self.stsz_box.encode(writer)?; + match &self.stco_or_co64_box { + Either::A(b) => b.encode(writer)?, + Either::B(b) => b.encode(writer)?, + } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1717,6 +1722,8 @@ impl StblBox { let mut stts_box = None; let mut stsc_box = None; let mut stsz_box = None; + let mut stco_box = None; + let mut co64_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1733,6 +1740,12 @@ impl StblBox { StszBox::TYPE if stsz_box.is_none() => { stsz_box = Some(StszBox::decode(&mut reader)?); } + StcoBox::TYPE if stco_box.is_none() => { + stco_box = Some(StcoBox::decode(&mut reader)?); + } + Co64Box::TYPE if co64_box.is_none() => { + co64_box = Some(Co64Box::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -1742,11 +1755,16 @@ impl StblBox { let stts_box = stts_box.ok_or_else(|| Error::missing_box("stts", Self::TYPE))?; let stsc_box = stsc_box.ok_or_else(|| Error::missing_box("stsc", Self::TYPE))?; let stsz_box = stsz_box.ok_or_else(|| Error::missing_box("stsz", Self::TYPE))?; + let stco_or_co64_box = stco_box + .map(Either::A) + .or(co64_box.map(Either::B)) + .ok_or_else(|| Error::missing_box("stco | co64", Self::TYPE))?; Ok(Self { stsd_box, stts_box, stsc_box, stsz_box, + stco_or_co64_box, unknown_boxes, }) } @@ -2609,3 +2627,133 @@ impl FullBox for StszBox { FullBoxFlags::new(0) } } + +/// [ISO/IEC 14496-12] ChunkOffsetBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StcoBox { + pub chunk_offsets: Vec, +} + +impl StcoBox { + pub const TYPE: BoxType = BoxType::Normal(*b"stco"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + (self.chunk_offsets.len() as u32).encode(writer)?; + for offset in &self.chunk_offsets { + offset.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let _ = FullBoxHeader::decode(reader)?; + let count = u32::decode(reader)? as usize; + let mut chunk_offsets = Vec::with_capacity(count); + for _ in 0..count { + chunk_offsets.push(u32::decode(reader)?); + } + Ok(Self { chunk_offsets }) + } +} + +impl Encode for StcoBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for StcoBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for StcoBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for StcoBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} + +/// [ISO/IEC 14496-12] ChunkLargeOffsetBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Co64Box { + pub chunk_offsets: Vec, +} + +impl Co64Box { + pub const TYPE: BoxType = BoxType::Normal(*b"co64"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + (self.chunk_offsets.len() as u32).encode(writer)?; + for offset in &self.chunk_offsets { + offset.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let _ = FullBoxHeader::decode(reader)?; + let count = u32::decode(reader)? as usize; + let mut chunk_offsets = Vec::with_capacity(count); + for _ in 0..count { + chunk_offsets.push(u64::decode(reader)?); + } + Ok(Self { chunk_offsets }) + } +} + +impl Encode for Co64Box { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for Co64Box { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for Co64Box { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl FullBox for Co64Box { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} From ca65db6a8d656725535709b3fcf8727c6b9380f3 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 14:51:06 +0900 Subject: [PATCH 051/103] Add udta box --- src/boxes.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index 4e35bcb..20b86fb 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -282,6 +282,7 @@ impl BaseBox for MdatBox { pub struct MoovBox { pub mvhd_box: MvhdBox, pub trak_boxes: Vec, + pub udta_box: Option, pub unknown_boxes: Vec, } @@ -293,6 +294,9 @@ impl MoovBox { for b in &self.trak_boxes { b.encode(writer)?; } + if let Some(b) = &self.udta_box { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -302,6 +306,7 @@ impl MoovBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let mut mvhd_box = None; let mut trak_boxes = Vec::new(); + let mut udta_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -312,6 +317,9 @@ impl MoovBox { TrakBox::TYPE => { trak_boxes.push(Decode::decode(&mut reader)?); } + UdtaBox::TYPE if udta_box.is_none() => { + udta_box = Some(Decode::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -322,6 +330,7 @@ impl MoovBox { Ok(Self { mvhd_box, trak_boxes, + udta_box, unknown_boxes, }) } @@ -2757,3 +2766,43 @@ impl FullBox for Co64Box { FullBoxFlags::new(0) } } + +/// [ISO/IEC 14496-12] UserDataBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UdtaBox { + // 必要になるまではこのボックスの中身は単なるバイト列として扱う + pub payload: Vec, +} + +impl UdtaBox { + pub const TYPE: BoxType = BoxType::Normal(*b"udta"); +} + +impl Encode for UdtaBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + writer.write_all(&self.payload)?; + Ok(()) + } +} + +impl Decode for UdtaBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + + let mut payload = Vec::new(); + header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; + Ok(Self { payload }) + } +} + +impl BaseBox for UdtaBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + self.payload.len() as u64 + } +} From fb4ad6daed7048c031de959406fd3f4fd84fe4e6 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 14:54:43 +0900 Subject: [PATCH 052/103] Rename test file --- tests/{decode_test.rs => decode_encode_test.rs} | 11 +++++++++++ 1 file changed, 11 insertions(+) rename tests/{decode_test.rs => decode_encode_test.rs} (55%) diff --git a/tests/decode_test.rs b/tests/decode_encode_test.rs similarity index 55% rename from tests/decode_test.rs rename to tests/decode_encode_test.rs index 7ee00ff..ec38a52 100644 --- a/tests/decode_test.rs +++ b/tests/decode_encode_test.rs @@ -10,3 +10,14 @@ fn decode_black_h264_video_mp4() -> Result<()> { ); Ok(()) } + +#[test] +fn encode_black_h264_video_mp4() -> Result<()> { + let input_bytes = include_bytes!("testdata/black-h264-video.mp4"); + let file = Mp4File::::decode(&mut &input_bytes[..])?; + assert_eq!( + file.iter_unknown_boxes().map(|x| x.0).collect::>(), + Vec::new() + ); + Ok(()) +} From 1f149851686fef8cc6889c7556fea10b72e72b11 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 15:08:59 +0900 Subject: [PATCH 053/103] =?UTF-8?q?=E3=82=A8=E3=83=B3=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=99=E3=82=8B=EF=BC=88=E3=81=BE=E3=81=A0=E5=A4=B1?= =?UTF-8?q?=E6=95=97=E3=81=99=E3=82=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 6 +++++- src/boxes.rs | 6 +++--- tests/decode_encode_test.rs | 29 ++++++++++++++++------------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 546ecea..d5e455e 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -294,7 +294,11 @@ impl BoxSize { } pub const fn with_payload_size(box_type: BoxType, payload_size: u64) -> Self { - Self(box_type.external_size() as u64 + payload_size) + let mut size = 4 + box_type.external_size() as u64 + payload_size; + if size > u32::MAX as u64 { + size += 8; + } + Self(size) } pub const fn get(self) -> u64 { diff --git a/src/boxes.rs b/src/boxes.rs index 20b86fb..64bacd4 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1088,9 +1088,9 @@ impl MdhdBox { let language = u16::decode(reader)?; this.language = [ - ((language >> 10) & 0b11111) as u8, - ((language >> 5) & 0b11111) as u8, - (language & 0b11111) as u8, + ((language >> 10) & 0b11111) as u8 + 0x60, + ((language >> 5) & 0b11111) as u8 + 0x60, + (language & 0b11111) as u8 + 0x60, ]; let _ = <[u8; 2]>::decode(reader)?; diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index ec38a52..a5894e2 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -1,23 +1,26 @@ -use shiguredo_mp4::{boxes::RootBox, Decode, IterUnknownBoxes, Mp4File, Result}; +use shiguredo_mp4::{Decode, Encode, IterUnknownBoxes, Mp4File, Result}; #[test] -fn decode_black_h264_video_mp4() -> Result<()> { +fn decode_encode_black_h264_video_mp4() -> Result<()> { let input_bytes = include_bytes!("testdata/black-h264-video.mp4"); - let file = Mp4File::::decode(&mut &input_bytes[..])?; - assert_eq!( - file.iter_unknown_boxes().map(|x| x.0).collect::>(), - Vec::new() - ); - Ok(()) -} + let file: Mp4File = Mp4File::decode(&mut &input_bytes[..])?; -#[test] -fn encode_black_h264_video_mp4() -> Result<()> { - let input_bytes = include_bytes!("testdata/black-h264-video.mp4"); - let file = Mp4File::::decode(&mut &input_bytes[..])?; + // デコード時に未処理のボックスがないことを確認する。 assert_eq!( file.iter_unknown_boxes().map(|x| x.0).collect::>(), Vec::new() ); + + let mut output_bytes = Vec::new(); + file.encode(&mut output_bytes)?; + + // エンコード結果が正しいことを確認する。 + // ボックスの順番は入れ替わる可能性があるので、バイト列をソートした上で比較する。 + let mut input_bytes = input_bytes.to_vec(); + input_bytes.sort(); + output_bytes.sort(); + assert_eq!(input_bytes.len(), output_bytes.len()); + assert_eq!(input_bytes, output_bytes); + Ok(()) } From 16a54bc5fec09f598618b5d972ac7102c7aa2a89 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 15:45:41 +0900 Subject: [PATCH 054/103] =?UTF-8?q?=E3=82=A8=E3=83=B3=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 4 ++-- src/boxes.rs | 45 +++++++++++++++++++------------------ tests/decode_encode_test.rs | 6 ++++- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index d5e455e..591b24e 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -47,7 +47,7 @@ impl BoxPath { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Mp4File { pub ftyp_box: FtypBox, pub boxes: Vec, @@ -262,7 +262,7 @@ impl FullBoxFlags { impl Encode for FullBoxFlags { fn encode(&self, writer: &mut W) -> Result<()> { - writer.write_all(&self.0.to_be_bytes()[..3])?; + writer.write_all(&self.0.to_be_bytes()[1..])?; Ok(()) } } diff --git a/src/boxes.rs b/src/boxes.rs index 64bacd4..172297e 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -406,9 +406,9 @@ impl MvhdBox { } self.rate.encode(writer)?; self.volume.encode(writer)?; - [0; 2 + 4 * 2].encode(writer)?; + [0u8; 2 + 4 * 2].encode(writer)?; self.matrix.encode(writer)?; - [0; 4 * 6].encode(writer)?; + [0u8; 4 * 6].encode(writer)?; self.next_track_id.encode(writer)?; Ok(()) } @@ -627,20 +627,20 @@ impl TkhdBox { self.creation_time.as_secs().encode(writer)?; self.modification_time.as_secs().encode(writer)?; self.track_id.encode(writer)?; - [0; 4].encode(writer)?; + [0u8; 4].encode(writer)?; self.duration.encode(writer)?; } else { (self.creation_time.as_secs() as u32).encode(writer)?; (self.modification_time.as_secs() as u32).encode(writer)?; self.track_id.encode(writer)?; - [0; 4].encode(writer)?; + [0u8; 4].encode(writer)?; (self.duration as u32).encode(writer)?; } - [0; 4 * 2].encode(writer)?; + [0u8; 4 * 2].encode(writer)?; self.layer.encode(writer)?; self.alternate_group.encode(writer)?; self.volume.encode(writer)?; - [0; 2].encode(writer)?; + [0u8; 2].encode(writer)?; self.matrix.encode(writer)?; self.width.encode(writer)?; self.height.encode(writer)?; @@ -703,8 +703,8 @@ impl Decode for TkhdBox { impl Default for TkhdBox { fn default() -> Self { Self { - flag_track_enabled: false, - flag_track_in_movie: false, + flag_track_enabled: true, + flag_track_in_movie: true, flag_track_in_preview: false, flag_track_size_is_aspect_ratio: false, @@ -851,6 +851,7 @@ impl ElstBox { FullBoxHeader::from_box(self).encode(writer)?; let version = self.full_box_version(); + (self.entries.len() as u32).encode(writer)?; for entry in &self.entries { if version == 1 { entry.edit_duration.encode(writer)?; @@ -1056,15 +1057,15 @@ impl MdhdBox { (self.duration as u32).encode(writer)?; } - let mut language = 0; + let mut language: u16 = 0; for l in &self.language { language = (language << 5) | l.checked_sub(0x60).ok_or_else(|| { Error::invalid_input(&format!("Invalid language code: {:?}", self.language)) - })?; + })? as u16; } language.encode(writer)?; - [0; 2].encode(writer)?; + [0u8; 2].encode(writer)?; Ok(()) } @@ -1169,9 +1170,9 @@ impl HdlrBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; - [0; 4].encode(writer)?; + [0u8; 4].encode(writer)?; self.handler_type.encode(writer)?; - [0; 4 * 3].encode(writer)?; + [0u8; 4 * 3].encode(writer)?; self.name.encode(writer)?; Ok(()) } @@ -1341,7 +1342,7 @@ impl SmhdBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; self.balance.encode(writer)?; - [0; 2].encode(writer)?; + [0u8; 2].encode(writer)?; Ok(()) } @@ -1652,10 +1653,10 @@ impl UrlBox { fn decode_payload(reader: &mut std::io::Take) -> Result { let full_header = FullBoxHeader::decode(reader)?; - let location = if full_header.flags.is_set(1) { - Some(Utf8String::decode(reader)?) - } else { + let location = if full_header.flags.is_set(0) { None + } else { + Some(Utf8String::decode(reader)?) }; Ok(Self { location }) } @@ -1693,7 +1694,7 @@ impl FullBox for UrlBox { } fn full_box_flags(&self) -> FullBoxFlags { - FullBoxFlags::new(self.location.is_some() as u32) + FullBoxFlags::new(self.location.is_none() as u32) } } @@ -1970,18 +1971,18 @@ impl Default for VisualSampleEntryFields { impl Encode for VisualSampleEntryFields { fn encode(&self, writer: &mut W) -> Result<()> { - [0; 6].encode(writer)?; + [0u8; 6].encode(writer)?; self.data_reference_index.encode(writer)?; - [0; 2 + 2 + 4 * 3].encode(writer)?; + [0u8; 2 + 2 + 4 * 3].encode(writer)?; self.width.encode(writer)?; self.height.encode(writer)?; self.horizresolution.encode(writer)?; self.vertresolution.encode(writer)?; - [0; 4].encode(writer)?; + [0u8; 4].encode(writer)?; self.frame_count.encode(writer)?; self.compressorname.encode(writer)?; self.depth.encode(writer)?; - [0; 2].encode(writer)?; + [0u8; 2].encode(writer)?; Ok(()) } } diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index a5894e2..ca3dafd 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -14,7 +14,11 @@ fn decode_encode_black_h264_video_mp4() -> Result<()> { let mut output_bytes = Vec::new(); file.encode(&mut output_bytes)?; - // エンコード結果が正しいことを確認する。 + // エンコード結果をデコードしたら同じ MP4 になっていることを確認する。 + let encoded_file: Mp4File = Mp4File::decode(&mut &output_bytes[..])?; + assert_eq!(file, encoded_file); + + // エンコード結果のバイト列が正しいことを確認する。 // ボックスの順番は入れ替わる可能性があるので、バイト列をソートした上で比較する。 let mut input_bytes = input_bytes.to_vec(); input_bytes.sort(); From 90e0f48032d60238d651c1c3441a086c83a1b6db Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 16:03:55 +0900 Subject: [PATCH 055/103] =?UTF-8?q?=E3=82=A8=E3=83=B3=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/boxes.rs | 4 ++-- tests/decode_encode_test.rs | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 172297e..a999fdc 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1450,7 +1450,7 @@ impl FullBox for VmhdBox { } fn full_box_flags(&self) -> FullBoxFlags { - FullBoxFlags::new(0) + FullBoxFlags::new(1) } } @@ -1982,7 +1982,7 @@ impl Encode for VisualSampleEntryFields { self.frame_count.encode(writer)?; self.compressorname.encode(writer)?; self.depth.encode(writer)?; - [0u8; 2].encode(writer)?; + (-1i16).encode(writer)?; Ok(()) } } diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index ca3dafd..1b5f6d4 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -19,12 +19,8 @@ fn decode_encode_black_h264_video_mp4() -> Result<()> { assert_eq!(file, encoded_file); // エンコード結果のバイト列が正しいことを確認する。 - // ボックスの順番は入れ替わる可能性があるので、バイト列をソートした上で比較する。 - let mut input_bytes = input_bytes.to_vec(); - input_bytes.sort(); - output_bytes.sort(); assert_eq!(input_bytes.len(), output_bytes.len()); - assert_eq!(input_bytes, output_bytes); + assert_eq!(&input_bytes[..], output_bytes); Ok(()) } From be95d9f5c744c84a9c5386c7ec402f8b54e01c46 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 16:21:40 +0900 Subject: [PATCH 056/103] =?UTF-8?q?opus=20=E7=94=A8=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=87=E3=83=BC=E3=82=BF=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/decode_encode_test.rs | 25 +++++++++++++++++++++++++ tests/testdata/beep-opus-audio.mp4 | Bin 0 -> 68676 bytes 2 files changed, 25 insertions(+) create mode 100644 tests/testdata/beep-opus-audio.mp4 diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index 1b5f6d4..53b9a0b 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -24,3 +24,28 @@ fn decode_encode_black_h264_video_mp4() -> Result<()> { Ok(()) } + +#[test] +fn decode_encode_beep_opus_audio_mp4() -> Result<()> { + let input_bytes = include_bytes!("testdata/beep-opus-audio.mp4"); + let file: Mp4File = Mp4File::decode(&mut &input_bytes[..])?; + + // デコード時に未処理のボックスがないことを確認する。 + assert_eq!( + file.iter_unknown_boxes().map(|x| x.0).collect::>(), + Vec::new() + ); + + let mut output_bytes = Vec::new(); + file.encode(&mut output_bytes)?; + + // エンコード結果をデコードしたら同じ MP4 になっていることを確認する。 + let encoded_file: Mp4File = Mp4File::decode(&mut &output_bytes[..])?; + assert_eq!(file, encoded_file); + + // エンコード結果のバイト列が正しいことを確認する。 + assert_eq!(input_bytes.len(), output_bytes.len()); + assert_eq!(&input_bytes[..], output_bytes); + + Ok(()) +} diff --git a/tests/testdata/beep-opus-audio.mp4 b/tests/testdata/beep-opus-audio.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..1e859a2c7e290dae8d27e49d676934407b710734 GIT binary patch literal 68676 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 z zO;OVyYSd1oaeq#P)8O{&4jxYI(+&y=yc?I6QIqPKVf!F#0o!3+C!}F<<$!dFJ!`H^ zfv*dle{@FLqFLp`<~2$r#Emx`Qm{o1(|A|fLr#4-BuRAG(DTQHJ$cxso6Y&=>}F;l zAb~&iyX*MkUOY;_ak~&@zq$-+`#)e0IO{MdAynF;a@?T1oW@a}? zCrPZ+T$FPLa)=(_;oDfJK3L)%s58ar>_80IH8sD(KAEC}lmxSRKI0DVlPF$T-2#K8 zO~y*&m#Xq#L9!BdjdCt-RC)&-N!U)l03gY8owB0$gQjc6P4fzRoO{)6(@cdP32x1? z=XfCxq-GJ>Jkh~=gvN^P z5bJLx>iv?s-ufZci_o3+hTu(ZTA@Nqrj4k4uxBCw63C4E!LiN-1jmaN9RG$Y4|cj@ zWV_tyF7qDhu@ffz)O{FsT8)6j^ooyp@Z7Sf)=pyYBFM6@TE2vffK{Y?JaLDr?P~0} zUmNjIp{s?}B(LXS#2H8c?w0m>p9CN{{-odtP~~}fOVfXcyLC6EM2AT|*%OOsMvUp| znt&k;ao4o4VMEv5kkSD6Ib!d}*6tDSn$V4)BzJ5zi@$@iTg+;0C%h4J0S>Bn&0}j3V?CRWpax{=A z5~<->Wby>MF#iZmHXaQ@f)h@6*+5C#st!e6>fUB;iUcBw2-E!Q_ch_|ej^I?0+FYy zZBB_`Ent$JUySemGg=CelYB!!QlD&2E`aV<$5H`6nWegeYrq2_xB0I3|9+Q@}8(iufADW$~f{-zj1tg)=ZpJ zgt+g12a^9!pJeBJ#=UTdf~kZ*uhau4Ay|FTYu70zKJAY?$#zM-oN8+fvZ~) z!LaW9=83QIn^(E>XGMlzAv>WsXs=88cLZFMJh+sy{ESmj;Z18oTJ5VWuVDf44Ap|T z5syKOBWB0!ledqbndPUMEpNquJMKZ}lZl>g&I3QvQaUKx;=&2?(u^F^jBcdjhVJ$% zT+P9?g=SGz3A)*73Zi-5`7w}HD0+i^&Ggg0&FTwgD((58G1^k@lOPxSCh<|mZENRQLwqHu;pbKCffI>lqr}%d%d(ojkTottf7VUIRqI8Unpb(fNa05` zo9|H!gyL$H(!|r|TVN9(Zgm$vvBKpOZg$3D8o$ z0|_`MAtfq#8lvL8N$L*YXM=|YuO0c!^q;B9Pgg|%If-M3n_sKQr;%2M8XJQ} z9eU^065XvE-d)^BoStM9Xyoj{mmEPUp|bIbWhIeq3vxxzdT$bX=Dzp}l8Z{fzdf1w zSqvdfwa>pky0=B+_Kk9k72hNa{)x{XAExRy@DJIsT&4$*kp^ttLYZ5j*==$v(q`KGw<8unFutB5a!ZOP-wcM)#xAIeYj&y;f4J<1;*To7QyEM*PLKT zd40O$G#kOn3_{JKX9o11=;4{95O}%utv?9}gcxIPX=x7vfy~V9?;8NeZ#I~>AP^}h z2nq!L`E&d325|QexB35k=0#`FAP_>Mt+lQx@SujRDdat2zIo#L)qwZ?bAy81`NRAC z&HGXs8ra$ZmmoAWv#~wD6mW&}>q4%DaLe4lL>G9B)ZF05-@VEc1c&6Q*bI3k!+irY z>+@@XY%J|8zMg}R3czgxFw**3O%X27|+jjhese+KwDlCoBI;9my3K|VU<3>t(6J^~*VykQ#1TG@br zKkpy+fUxvztRSTW0&n2>bRF1 Date: Fri, 20 Sep 2024 16:31:27 +0900 Subject: [PATCH 057/103] Add Opus box --- src/boxes.rs | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index a999fdc..312f75f 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1895,6 +1895,7 @@ impl IterUnknownBoxes for StsdBox { #[derive(Debug, Clone, PartialEq, Eq)] pub enum SampleEntry { Avc1(Avc1Box), + Opus(OpusBox), Unknown(UnknownBox), } @@ -1902,6 +1903,7 @@ impl Encode for SampleEntry { fn encode(&self, writer: &mut W) -> Result<()> { match self { Self::Avc1(b) => b.encode(writer), + Self::Opus(b) => b.encode(writer), Self::Unknown(b) => b.encode(writer), } } @@ -1912,6 +1914,7 @@ impl Decode for SampleEntry { let (header, mut reader) = BoxHeader::peek(reader)?; match header.box_type { Avc1Box::TYPE => Decode::decode(&mut reader).map(Self::Avc1), + OpusBox::TYPE => Decode::decode(&mut reader).map(Self::Opus), _ => Decode::decode(&mut reader).map(Self::Unknown), } } @@ -1921,6 +1924,7 @@ impl BaseBox for SampleEntry { fn box_type(&self) -> BoxType { match self { Self::Avc1(b) => b.box_type(), + Self::Opus(b) => b.box_type(), Self::Unknown(b) => b.box_type(), } } @@ -1928,6 +1932,7 @@ impl BaseBox for SampleEntry { fn box_payload_size(&self) -> u64 { match self { Self::Avc1(b) => b.box_payload_size(), + Self::Opus(b) => b.box_payload_size(), Self::Unknown(b) => b.box_payload_size(), } } @@ -1937,6 +1942,7 @@ impl IterUnknownBoxes for SampleEntry { fn iter_unknown_boxes(&self) -> impl '_ + Iterator { match self { Self::Avc1(b) => Box::new(b.iter_unknown_boxes()) as Box>, + Self::Opus(b) => Box::new(b.iter_unknown_boxes()), Self::Unknown(b) => Box::new(b.iter_unknown_boxes()), } } @@ -2807,3 +2813,143 @@ impl BaseBox for UdtaBox { self.payload.len() as u64 } } + +/// [https://gitlab.xiph.org/xiph/opus/-/blob/main/doc/opus_in_isobmff.html] OpusSampleEntry class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OpusBox { + pub audio: AudioSampleEntryFields, + pub unknown_boxes: Vec, +} + +impl OpusBox { + pub const TYPE: BoxType = BoxType::Normal(*b"Opus"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.audio.encode(writer)?; + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let audio = AudioSampleEntryFields::decode(reader)?; + // let mut avcc_box = None; + // let mut pasp_box = None; + // let mut btrt_box = None; + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + // AvccBox::TYPE if avcc_box.is_none() => { + // avcc_box = Some(AvccBox::decode(&mut reader)?); + // } + // PaspBox::TYPE if pasp_box.is_none() => { + // pasp_box = Some(PaspBox::decode(&mut reader)?); + // } + // BtrtBox::TYPE if btrt_box.is_none() => { + // btrt_box = Some(BtrtBox::decode(&mut reader)?); + // } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + //let avcc_box = avcc_box.ok_or_else(|| Error::missing_box("avcc", Self::TYPE))?; + Ok(Self { + audio, + // avcc_box, + // pasp_box, + // btrt_box, + unknown_boxes, + }) + } +} + +impl Encode for OpusBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for OpusBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for OpusBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + +impl IterUnknownBoxes for OpusBox { + fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + let iter0 = self + .unknown_boxes + .iter() + .flat_map(|b| b.iter_unknown_boxes()); + iter0.map(|(path, b)| (path.join(Self::TYPE), b)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AudioSampleEntryFields { + pub data_reference_index: u16, + pub channelcount: u16, + pub samplesize: u16, + pub samplerate: FixedPointNumber, +} + +impl Default for AudioSampleEntryFields { + fn default() -> Self { + Self { + data_reference_index: 1, + channelcount: 1, + samplesize: 16, + samplerate: FixedPointNumber::new(0, 0), + } + } +} + +impl Encode for AudioSampleEntryFields { + fn encode(&self, writer: &mut W) -> Result<()> { + [0u8; 6].encode(writer)?; + self.data_reference_index.encode(writer)?; + [0u8; 4 * 2].encode(writer)?; + self.channelcount.encode(writer)?; + self.samplesize.encode(writer)?; + [0u8; 2].encode(writer)?; + [0u8; 2].encode(writer)?; + self.samplerate.encode(writer)?; + Ok(()) + } +} + +impl Decode for AudioSampleEntryFields { + fn decode(reader: &mut R) -> Result { + let _ = <[u8; 6]>::decode(reader)?; + let data_reference_index = u16::decode(reader)?; + let _ = <[u8; 4 * 2]>::decode(reader)?; + let channelcount = u16::decode(reader)?; + let samplesize = u16::decode(reader)?; + let _ = <[u8; 2]>::decode(reader)?; + let _ = <[u8; 2]>::decode(reader)?; + let samplerate = FixedPointNumber::decode(reader)?; + Ok(Self { + data_reference_index, + channelcount, + samplesize, + samplerate, + }) + } +} From 961edadd5269619b56bf8e5045e35076fecf1431 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 16:42:29 +0900 Subject: [PATCH 058/103] Add dOps box --- src/boxes.rs | 113 +++++++++++++++++++++++++++++++++++++++++++-------- src/io.rs | 4 ++ 2 files changed, 101 insertions(+), 16 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 312f75f..6a0be93 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2818,6 +2818,8 @@ impl BaseBox for UdtaBox { #[derive(Debug, Clone, PartialEq, Eq)] pub struct OpusBox { pub audio: AudioSampleEntryFields, + pub dops_box: DopsBox, + pub btrt_box: Option, pub unknown_boxes: Vec, } @@ -2826,6 +2828,7 @@ impl OpusBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.audio.encode(writer)?; + self.dops_box.encode(writer)?; for b in &self.unknown_boxes { b.encode(writer)?; } @@ -2834,33 +2837,28 @@ impl OpusBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let audio = AudioSampleEntryFields::decode(reader)?; - // let mut avcc_box = None; - // let mut pasp_box = None; - // let mut btrt_box = None; + let mut dops_box = None; + let mut btrt_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; match header.box_type { - // AvccBox::TYPE if avcc_box.is_none() => { - // avcc_box = Some(AvccBox::decode(&mut reader)?); - // } - // PaspBox::TYPE if pasp_box.is_none() => { - // pasp_box = Some(PaspBox::decode(&mut reader)?); - // } - // BtrtBox::TYPE if btrt_box.is_none() => { - // btrt_box = Some(BtrtBox::decode(&mut reader)?); - // } + DopsBox::TYPE if dops_box.is_none() => { + dops_box = Some(DopsBox::decode(&mut reader)?); + } + BtrtBox::TYPE if btrt_box.is_none() => { + btrt_box = Some(BtrtBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } } } - //let avcc_box = avcc_box.ok_or_else(|| Error::missing_box("avcc", Self::TYPE))?; + let dops_box = dops_box.ok_or_else(|| Error::missing_box("dops", Self::TYPE))?; Ok(Self { audio, - // avcc_box, - // pasp_box, - // btrt_box, + dops_box, + btrt_box, unknown_boxes, }) } @@ -2953,3 +2951,86 @@ impl Decode for AudioSampleEntryFields { }) } } + +/// [https://gitlab.xiph.org/xiph/opus/-/blob/main/doc/opus_in_isobmff.html] OpusSpecificBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DopsBox { + pub version: u8, + pub output_channel_count: u8, + pub pre_skip: u16, + pub input_sample_rate: u32, + pub output_gain: i16, +} + +impl DopsBox { + pub const TYPE: BoxType = BoxType::Normal(*b"dOps"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.version.encode(writer)?; + self.output_channel_count.encode(writer)?; + self.pre_skip.encode(writer)?; + self.input_sample_rate.encode(writer)?; + self.output_gain.encode(writer)?; + 0u8.encode(writer)?; // ChannelMappingFamily + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let version = u8::decode(reader)?; + let output_channel_count = u8::decode(reader)?; + let pre_skip = u16::decode(reader)?; + let input_sample_rate = u32::decode(reader)?; + let output_gain = i16::decode(reader)?; + let channel_mapping_family = u8::decode(reader)?; + if channel_mapping_family != 0 { + return Err(Error::unsupported( + "`ChannelMappingFamily != 0` in 'dOps' box is not supported", + )); + } + Ok(Self { + version, + output_channel_count, + pre_skip, + input_sample_rate, + output_gain, + }) + } +} + +impl Default for DopsBox { + fn default() -> Self { + Self { + version: 0, + output_channel_count: 1, + pre_skip: 0, + input_sample_rate: 0, + output_gain: 0, + } + } +} + +impl Encode for DopsBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for DopsBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for DopsBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} diff --git a/src/io.rs b/src/io.rs index 6acf9d9..b8ddcbb 100644 --- a/src/io.rs +++ b/src/io.rs @@ -26,6 +26,10 @@ impl Error { "Missing mandatory '{missing_box}' box in '{parent_box}' box" )) } + + pub(crate) fn unsupported(message: &str) -> Self { + Self::from(std::io::Error::new(ErrorKind::Other, message)) + } } impl From for Error { From 7c0e237df5aa893149d2d2bdc7ce7db5e1e42ce1 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 16:49:53 +0900 Subject: [PATCH 059/103] Add sgpd box --- src/boxes.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index 6a0be93..dbf1ce7 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1706,6 +1706,7 @@ pub struct StblBox { pub stsc_box: StscBox, pub stsz_box: StszBox, pub stco_or_co64_box: Either, + pub sgpd_box: Option, pub unknown_boxes: Vec, } @@ -1721,6 +1722,9 @@ impl StblBox { Either::A(b) => b.encode(writer)?, Either::B(b) => b.encode(writer)?, } + if let Some(b) = &self.sgpd_box { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1734,6 +1738,7 @@ impl StblBox { let mut stsz_box = None; let mut stco_box = None; let mut co64_box = None; + let mut sgpd_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1756,6 +1761,9 @@ impl StblBox { Co64Box::TYPE if co64_box.is_none() => { co64_box = Some(Co64Box::decode(&mut reader)?); } + SgpdBox::TYPE if sgpd_box.is_none() => { + sgpd_box = Some(SgpdBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -1775,6 +1783,7 @@ impl StblBox { stsc_box, stsz_box, stco_or_co64_box, + sgpd_box, unknown_boxes, }) } @@ -2774,6 +2783,54 @@ impl FullBox for Co64Box { } } +/// [ISO/IEC 14496-12] SampleGroupDescriptionBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SgpdBox { + // 必要になるまではこのボックスの中身は単なるバイト列として扱う + pub payload: Vec, +} + +impl SgpdBox { + pub const TYPE: BoxType = BoxType::Normal(*b"sgpd"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + writer.write_all(&self.payload)?; + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let mut payload = Vec::new(); + reader.read_to_end(&mut payload)?; + Ok(Self { payload }) + } +} + +impl Encode for SgpdBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for SgpdBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for SgpdBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + /// [ISO/IEC 14496-12] UserDataBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct UdtaBox { From 4eafd0ba98a5c3db75bac0ec0de5275f5aae270d Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 16:52:12 +0900 Subject: [PATCH 060/103] Add sbgp box --- src/boxes.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index dbf1ce7..8556580 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1707,6 +1707,7 @@ pub struct StblBox { pub stsz_box: StszBox, pub stco_or_co64_box: Either, pub sgpd_box: Option, + pub sbgp_box: Option, pub unknown_boxes: Vec, } @@ -1725,6 +1726,9 @@ impl StblBox { if let Some(b) = &self.sgpd_box { b.encode(writer)?; } + if let Some(b) = &self.sbgp_box { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1739,6 +1743,7 @@ impl StblBox { let mut stco_box = None; let mut co64_box = None; let mut sgpd_box = None; + let mut sbgp_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1764,6 +1769,9 @@ impl StblBox { SgpdBox::TYPE if sgpd_box.is_none() => { sgpd_box = Some(SgpdBox::decode(&mut reader)?); } + SbgpBox::TYPE if sbgp_box.is_none() => { + sbgp_box = Some(SbgpBox::decode(&mut reader)?); + } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -1784,6 +1792,7 @@ impl StblBox { stsz_box, stco_or_co64_box, sgpd_box, + sbgp_box, unknown_boxes, }) } @@ -2831,6 +2840,54 @@ impl BaseBox for SgpdBox { } } +/// [ISO/IEC 14496-12] SampleToGroupBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SbgpBox { + // 必要になるまではこのボックスの中身は単なるバイト列として扱う + pub payload: Vec, +} + +impl SbgpBox { + pub const TYPE: BoxType = BoxType::Normal(*b"sbgp"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + writer.write_all(&self.payload)?; + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let mut payload = Vec::new(); + reader.read_to_end(&mut payload)?; + Ok(Self { payload }) + } +} + +impl Encode for SbgpBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for SbgpBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for SbgpBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } +} + /// [ISO/IEC 14496-12] UserDataBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct UdtaBox { From fd020229a65438ee0fcdd37201a49760e6625f33 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Fri, 20 Sep 2024 17:00:09 +0900 Subject: [PATCH 061/103] =?UTF-8?q?btrt=20=E3=81=AE=E6=9B=B8=E3=81=8D?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E6=BC=8F=E3=82=8C=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/boxes.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index 8556580..7e4ea5a 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2943,6 +2943,9 @@ impl OpusBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.audio.encode(writer)?; self.dops_box.encode(writer)?; + if let Some(b) = &self.btrt_box { + b.encode(writer)?; + } for b in &self.unknown_boxes { b.encode(writer)?; } From 8b093edf6998301999f2ec4b48e75c9a324efd81 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 09:52:02 +0900 Subject: [PATCH 062/103] =?UTF-8?q?BaseBox=20=E3=81=8B=E3=82=89=20Encode?= =?UTF-8?q?=20/=20Decode=20=E5=88=B6=E7=B4=84=E3=82=92=E5=A4=96=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 6 +++--- src/boxes.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 591b24e..ac78e44 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -10,7 +10,7 @@ use crate::{ }; // 単なる `Box` だと Rust の標準ライブラリのそれと名前が衝突するので変えておく -pub trait BaseBox: Encode + Decode { +pub trait BaseBox { fn box_type(&self) -> BoxType; fn box_size(&self) -> BoxSize { @@ -53,7 +53,7 @@ pub struct Mp4File { pub boxes: Vec, } -impl Decode for Mp4File { +impl Decode for Mp4File { fn decode(mut reader: &mut R) -> Result { let ftyp_box = FtypBox::decode(reader)?; @@ -67,7 +67,7 @@ impl Decode for Mp4File { } } -impl Encode for Mp4File { +impl Encode for Mp4File { fn encode(&self, writer: &mut W) -> Result<()> { self.ftyp_box.encode(writer)?; diff --git a/src/boxes.rs b/src/boxes.rs index 7e4ea5a..589c650 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2928,7 +2928,7 @@ impl BaseBox for UdtaBox { } } -/// [https://gitlab.xiph.org/xiph/opus/-/blob/main/doc/opus_in_isobmff.html] OpusSampleEntry class +/// [] OpusSampleEntry class #[derive(Debug, Clone, PartialEq, Eq)] pub struct OpusBox { pub audio: AudioSampleEntryFields, @@ -3069,7 +3069,7 @@ impl Decode for AudioSampleEntryFields { } } -/// [https://gitlab.xiph.org/xiph/opus/-/blob/main/doc/opus_in_isobmff.html] OpusSpecificBox class +/// [] OpusSpecificBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct DopsBox { pub version: u8, From dd85426359fac715a63fd339e72072d08d108995 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 10:54:23 +0900 Subject: [PATCH 063/103] =?UTF-8?q?=E3=83=88=E3=83=AC=E3=82=A4=E3=83=88?= =?UTF-8?q?=E6=95=B4=E7=90=86=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 48 +++-- src/boxes.rs | 468 +++++++++++++++++++++++++++++++++------------ src/lib.rs | 2 +- 3 files changed, 377 insertions(+), 141 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index ac78e44..15f62c3 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -18,6 +18,10 @@ pub trait BaseBox { } fn box_payload_size(&self) -> u64; + + fn actual_box(&self) -> &dyn BaseBox; + + fn children<'a>(&'a self) -> Box>; } pub trait FullBox: BaseBox { @@ -25,10 +29,6 @@ pub trait FullBox: BaseBox { fn full_box_flags(&self) -> FullBoxFlags; } -pub trait IterUnknownBoxes { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator; -} - #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct BoxPath(Vec); @@ -78,11 +78,12 @@ impl Encode for Mp4File { } } -impl IterUnknownBoxes for Mp4File { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - self.boxes.iter().flat_map(|b| b.iter_unknown_boxes()) - } -} +// TODO +// impl IterUnknownBoxes for Mp4File { +// fn iter_unknown_boxes(&self) -> impl '_ + Iterator { +// self.boxes.iter().flat_map(|b| b.iter_unknown_boxes()) +// } +// } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BoxHeader { @@ -414,11 +415,13 @@ impl BaseBox for UnknownBox { fn box_payload_size(&self) -> u64 { self.payload.len() as u64 } -} -impl IterUnknownBoxes for UnknownBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - std::iter::once((BoxPath::new(self.box_type), self)) + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) } } @@ -513,12 +516,31 @@ impl Decode for Utf8String { } } +// TODO: impl BaseBox #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Either { A(A), B(B), } +impl Either { + pub fn as_a(&self) -> Option<&A> { + if let Self::A(a) = self { + Some(a) + } else { + None + } + } + + pub fn as_b(&self) -> Option<&B> { + if let Self::B(b) = self { + Some(b) + } else { + None + } + } +} + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Uint(u8); diff --git a/src/boxes.rs b/src/boxes.rs index 589c650..1a64038 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -4,9 +4,9 @@ use std::{ }; use crate::{ - io::ExternalBytes, BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Decode, Either, Encode, - Error, FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, IterUnknownBoxes, Mp4FileTime, - Result, Uint, UnknownBox, Utf8String, + io::ExternalBytes, BaseBox, BoxHeader, BoxSize, BoxType, Decode, Either, Encode, Error, + FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, Mp4FileTime, Result, Uint, UnknownBox, + Utf8String, }; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -123,6 +123,14 @@ impl BaseBox for FtypBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -174,15 +182,22 @@ impl BaseBox for RootBox { RootBox::Unknown(b) => b.box_payload_size(), } } -} -impl IterUnknownBoxes for RootBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + fn actual_box(&self) -> &dyn BaseBox { + match self { + RootBox::Free(b) => b.actual_box(), + RootBox::Mdat(b) => b.actual_box(), + RootBox::Moov(b) => b.actual_box(), + RootBox::Unknown(b) => b.actual_box(), + } + } + + fn children<'a>(&'a self) -> Box> { match self { - RootBox::Free(_) => Box::new(std::iter::empty()) as Box>, - RootBox::Mdat(_) => Box::new(std::iter::empty()), - RootBox::Moov(b) => Box::new(b.iter_unknown_boxes()), - RootBox::Unknown(b) => Box::new(b.iter_unknown_boxes()), + RootBox::Free(b) => b.children(), + RootBox::Mdat(b) => b.children(), + RootBox::Moov(b) => b.children(), + RootBox::Unknown(b) => b.children(), } } } @@ -224,6 +239,14 @@ impl BaseBox for FreeBox { fn box_payload_size(&self) -> u64 { self.payload.len() as u64 } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } /// [ISO/IEC 14496-12] MediaDataBox class @@ -275,6 +298,14 @@ impl BaseBox for MdatBox { fn box_payload_size(&self) -> u64 { self.payload.len() as u64 } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } /// [ISO/IEC 14496-12] MovieBox class @@ -360,18 +391,18 @@ impl BaseBox for MoovBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } -} -impl IterUnknownBoxes for MoovBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self.trak_boxes.iter().flat_map(|b| b.iter_unknown_boxes()); - let iter1 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); - iter0 - .chain(iter1) - .map(|(path, b)| (path.join(Self::TYPE), b)) + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::once(self.mvhd_box.actual_box()) + .chain(self.trak_boxes.iter().map(BaseBox::actual_box)) + .chain(self.udta_box.iter().map(BaseBox::actual_box)) + .chain(self.trak_boxes.iter().map(BaseBox::actual_box)), + ) } } @@ -480,6 +511,14 @@ impl BaseBox for MvhdBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for MvhdBox { @@ -581,20 +620,19 @@ impl BaseBox for TrakBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } -} -impl IterUnknownBoxes for TrakBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self.edts_box.iter().flat_map(|b| b.iter_unknown_boxes()); - let iter1 = self.mdia_box.iter_unknown_boxes(); - let iter2 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); - iter0 - .chain(iter1) - .chain(iter2) - .map(|(path, b)| (path.join(Self::TYPE), b)) + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(std::iter::once(&self.tkhd_box).map(BaseBox::actual_box)) + .chain(self.edts_box.iter().map(BaseBox::actual_box)) + .chain(std::iter::once(&self.mdia_box).map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) } } @@ -730,6 +768,14 @@ impl BaseBox for TkhdBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for TkhdBox { @@ -819,15 +865,17 @@ impl BaseBox for EdtsBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } -} -impl IterUnknownBoxes for EdtsBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); - iter0.map(|(path, b)| (path.join(Self::TYPE), b)) + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(self.elst_box.iter().map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) } } @@ -916,6 +964,14 @@ impl BaseBox for ElstBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for ElstBox { @@ -1015,18 +1071,19 @@ impl BaseBox for MdiaBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } -} -impl IterUnknownBoxes for MdiaBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self.minf_box.iter_unknown_boxes(); - let iter1 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); - iter0 - .chain(iter1) - .map(|(path, b)| (path.join(Self::TYPE), b)) + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(std::iter::once(&self.mdhd_box).map(BaseBox::actual_box)) + .chain(std::iter::once(&self.hdlr_box).map(BaseBox::actual_box)) + .chain(std::iter::once(&self.minf_box).map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) } } @@ -1136,6 +1193,14 @@ impl BaseBox for MdhdBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for MdhdBox { @@ -1211,6 +1276,14 @@ impl BaseBox for HdlrBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for HdlrBox { @@ -1313,20 +1386,20 @@ impl BaseBox for MinfBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } -} -impl IterUnknownBoxes for MinfBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self.dinf_box.iter_unknown_boxes(); - let iter1 = self.stbl_box.iter_unknown_boxes(); - let iter2 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); - iter0 - .chain(iter1) - .chain(iter2) - .map(|(path, b)| (path.join(Self::TYPE), b)) + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(self.smhd_or_vmhd_box.as_a().map(BaseBox::actual_box)) + .chain(self.smhd_or_vmhd_box.as_b().map(BaseBox::actual_box)) + .chain(std::iter::once(&self.dinf_box).map(BaseBox::actual_box)) + .chain(std::iter::once(&self.stbl_box).map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) } } @@ -1378,6 +1451,14 @@ impl BaseBox for SmhdBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for SmhdBox { @@ -1442,6 +1523,14 @@ impl BaseBox for VmhdBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for VmhdBox { @@ -1518,18 +1607,17 @@ impl BaseBox for DinfBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } -} -impl IterUnknownBoxes for DinfBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self.dref_box.iter_unknown_boxes(); - let iter1 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); - iter0 - .chain(iter1) - .map(|(path, b)| (path.join(Self::TYPE), b)) + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(std::iter::once(&self.dref_box).map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) } } @@ -1612,6 +1700,18 @@ impl BaseBox for DrefBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(self.url_box.iter().map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) + } } impl FullBox for DrefBox { @@ -1624,16 +1724,6 @@ impl FullBox for DrefBox { } } -impl IterUnknownBoxes for DrefBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); - iter0.map(|(path, b)| (path.join(Self::TYPE), b)) - } -} - /// [ISO/IEC 14496-12] DataEntryUrlBox class #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct UrlBox { @@ -1686,6 +1776,14 @@ impl BaseBox for UrlBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for UrlBox { @@ -1822,18 +1920,24 @@ impl BaseBox for StblBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } -} -impl IterUnknownBoxes for StblBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self.stsd_box.iter_unknown_boxes(); - let iter1 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); - iter0 - .chain(iter1) - .map(|(path, b)| (path.join(Self::TYPE), b)) + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(std::iter::once(&self.stsd_box).map(BaseBox::actual_box)) + .chain(std::iter::once(&self.stts_box).map(BaseBox::actual_box)) + .chain(std::iter::once(&self.stsc_box).map(BaseBox::actual_box)) + .chain(std::iter::once(&self.stsz_box).map(BaseBox::actual_box)) + .chain(self.stco_or_co64_box.as_a().map(BaseBox::actual_box)) + .chain(self.stco_or_co64_box.as_b().map(BaseBox::actual_box)) + .chain(self.sgpd_box.iter().map(BaseBox::actual_box)) + .chain(self.sbgp_box.iter().map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) } } @@ -1891,6 +1995,14 @@ impl BaseBox for StsdBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(self.entries.iter().map(BaseBox::actual_box)) + } } impl FullBox for StsdBox { @@ -1903,13 +2015,6 @@ impl FullBox for StsdBox { } } -impl IterUnknownBoxes for StsdBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self.entries.iter().flat_map(|b| b.iter_unknown_boxes()); - iter0.map(|(path, b)| (path.join(Self::TYPE), b)) - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub enum SampleEntry { Avc1(Avc1Box), @@ -1954,14 +2059,20 @@ impl BaseBox for SampleEntry { Self::Unknown(b) => b.box_payload_size(), } } -} -impl IterUnknownBoxes for SampleEntry { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { + fn actual_box(&self) -> &dyn BaseBox { match self { - Self::Avc1(b) => Box::new(b.iter_unknown_boxes()) as Box>, - Self::Opus(b) => Box::new(b.iter_unknown_boxes()), - Self::Unknown(b) => Box::new(b.iter_unknown_boxes()), + Self::Avc1(b) => b.actual_box(), + Self::Opus(b) => b.actual_box(), + Self::Unknown(b) => b.actual_box(), + } + } + + fn children<'a>(&'a self) -> Box> { + match self { + Self::Avc1(b) => b.children(), + Self::Opus(b) => b.children(), + Self::Unknown(b) => b.children(), } } } @@ -2124,15 +2235,19 @@ impl BaseBox for Avc1Box { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } -} -impl IterUnknownBoxes for Avc1Box { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); - iter0.map(|(path, b)| (path.join(Self::TYPE), b)) + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(std::iter::once(&self.avcc_box).map(BaseBox::actual_box)) + .chain(self.pasp_box.iter().map(BaseBox::actual_box)) + .chain(self.btrt_box.iter().map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) } } @@ -2313,6 +2428,14 @@ impl BaseBox for AvccBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } /// [ISO/IEC 14496-12] PixelAspectRatioBox class @@ -2363,6 +2486,14 @@ impl BaseBox for PaspBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } /// [ISO/IEC 14496-12] BitRateBox class @@ -2416,6 +2547,14 @@ impl BaseBox for BtrtBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -2481,6 +2620,14 @@ impl BaseBox for SttsBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for SttsBox { @@ -2559,6 +2706,14 @@ impl BaseBox for StscBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for StscBox { @@ -2650,6 +2805,14 @@ impl BaseBox for StszBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for StszBox { @@ -2715,6 +2878,14 @@ impl BaseBox for StcoBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for StcoBox { @@ -2780,6 +2951,14 @@ impl BaseBox for Co64Box { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } impl FullBox for Co64Box { @@ -2838,6 +3017,14 @@ impl BaseBox for SgpdBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } /// [ISO/IEC 14496-12] SampleToGroupBox class @@ -2886,6 +3073,14 @@ impl BaseBox for SbgpBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } /// [ISO/IEC 14496-12] UserDataBox class @@ -2926,6 +3121,14 @@ impl BaseBox for UdtaBox { fn box_payload_size(&self) -> u64 { self.payload.len() as u64 } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } /// [] OpusSampleEntry class @@ -3005,15 +3208,18 @@ impl BaseBox for OpusBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } -} -impl IterUnknownBoxes for OpusBox { - fn iter_unknown_boxes(&self) -> impl '_ + Iterator { - let iter0 = self - .unknown_boxes - .iter() - .flat_map(|b| b.iter_unknown_boxes()); - iter0.map(|(path, b)| (path.join(Self::TYPE), b)) + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(std::iter::once(&self.dops_box).map(BaseBox::actual_box)) + .chain(self.btrt_box.iter().map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) } } @@ -3150,4 +3356,12 @@ impl BaseBox for DopsBox { fn box_payload_size(&self) -> u64 { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } } diff --git a/src/lib.rs b/src/lib.rs index df8fa64..2601223 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,6 @@ mod io; pub use basic_types::{ BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Either, FixedPointNumber, FullBox, FullBoxFlags, - FullBoxHeader, IterUnknownBoxes, Mp4File, Mp4FileTime, Uint, UnknownBox, Utf8String, + FullBoxHeader, Mp4File, Mp4FileTime, Uint, UnknownBox, Utf8String, }; pub use io::{Decode, Encode, Error, Result}; From 55a43deebaf4887b9bdc8a0f878847fa0a4960d1 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 10:58:53 +0900 Subject: [PATCH 064/103] Implement BaseBox for Either --- src/basic_types.rs | 44 ++++++++++++++++++++++++++++++++------------ src/boxes.rs | 6 ++---- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 15f62c3..1ede81b 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -22,6 +22,8 @@ pub trait BaseBox { fn actual_box(&self) -> &dyn BaseBox; fn children<'a>(&'a self) -> Box>; + + // TODO: add fn is_opaque_payload() } pub trait FullBox: BaseBox { @@ -516,27 +518,45 @@ impl Decode for Utf8String { } } -// TODO: impl BaseBox #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Either { A(A), B(B), } -impl Either { - pub fn as_a(&self) -> Option<&A> { - if let Self::A(a) = self { - Some(a) - } else { - None +impl BaseBox for Either { + fn box_type(&self) -> BoxType { + match self { + Self::A(x) => x.box_type(), + Self::B(x) => x.box_type(), } } - pub fn as_b(&self) -> Option<&B> { - if let Self::B(b) = self { - Some(b) - } else { - None + fn box_size(&self) -> BoxSize { + match self { + Self::A(x) => x.box_size(), + Self::B(x) => x.box_size(), + } + } + + fn box_payload_size(&self) -> u64 { + match self { + Self::A(x) => x.box_payload_size(), + Self::B(x) => x.box_payload_size(), + } + } + + fn actual_box(&self) -> &dyn BaseBox { + match self { + Self::A(x) => x.actual_box(), + Self::B(x) => x.actual_box(), + } + } + + fn children<'a>(&'a self) -> Box> { + match self { + Self::A(x) => x.children(), + Self::B(x) => x.children(), } } } diff --git a/src/boxes.rs b/src/boxes.rs index 1a64038..ced31be 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1394,8 +1394,7 @@ impl BaseBox for MinfBox { fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(self.smhd_or_vmhd_box.as_a().map(BaseBox::actual_box)) - .chain(self.smhd_or_vmhd_box.as_b().map(BaseBox::actual_box)) + .chain(std::iter::once(&self.smhd_or_vmhd_box).map(BaseBox::actual_box)) .chain(std::iter::once(&self.dinf_box).map(BaseBox::actual_box)) .chain(std::iter::once(&self.stbl_box).map(BaseBox::actual_box)) .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), @@ -1932,8 +1931,7 @@ impl BaseBox for StblBox { .chain(std::iter::once(&self.stts_box).map(BaseBox::actual_box)) .chain(std::iter::once(&self.stsc_box).map(BaseBox::actual_box)) .chain(std::iter::once(&self.stsz_box).map(BaseBox::actual_box)) - .chain(self.stco_or_co64_box.as_a().map(BaseBox::actual_box)) - .chain(self.stco_or_co64_box.as_b().map(BaseBox::actual_box)) + .chain(std::iter::once(&self.stco_or_co64_box).map(BaseBox::actual_box)) .chain(self.sgpd_box.iter().map(BaseBox::actual_box)) .chain(self.sbgp_box.iter().map(BaseBox::actual_box)) .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), From fcfc8cc1576662798fc9c69821151ce10db51ba3 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 11:02:01 +0900 Subject: [PATCH 065/103] Remove BoxPath --- src/basic_types.rs | 18 ------------------ src/lib.rs | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 1ede81b..68ac913 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -31,24 +31,6 @@ pub trait FullBox: BaseBox { fn full_box_flags(&self) -> FullBoxFlags; } -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct BoxPath(Vec); - -impl BoxPath { - pub fn new(box_type: BoxType) -> Self { - Self(vec![box_type]) - } - - pub fn get(&self) -> &[BoxType] { - &self.0 - } - - pub fn join(mut self, parent: BoxType) -> Self { - self.0.push(parent); - self - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct Mp4File { pub ftyp_box: FtypBox, diff --git a/src/lib.rs b/src/lib.rs index 2601223..abf988a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ pub mod boxes; mod io; pub use basic_types::{ - BaseBox, BoxHeader, BoxPath, BoxSize, BoxType, Either, FixedPointNumber, FullBox, FullBoxFlags, + BaseBox, BoxHeader, BoxSize, BoxType, Either, FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, Mp4File, Mp4FileTime, Uint, UnknownBox, Utf8String, }; pub use io::{Decode, Encode, Error, Result}; From 2c8c671ae6e6aa85a2c9c0f62aa4b21a656fe0fb Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 11:10:00 +0900 Subject: [PATCH 066/103] Add BaseBox::is_opaque_payload() --- src/basic_types.rs | 32 +++---- src/boxes.rs | 213 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 183 insertions(+), 62 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 68ac913..658daec 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -19,11 +19,11 @@ pub trait BaseBox { fn box_payload_size(&self) -> u64; + fn is_opaque_payload(&self) -> bool; + fn actual_box(&self) -> &dyn BaseBox; fn children<'a>(&'a self) -> Box>; - - // TODO: add fn is_opaque_payload() } pub trait FullBox: BaseBox { @@ -400,6 +400,10 @@ impl BaseBox for UnknownBox { self.payload.len() as u64 } + fn is_opaque_payload(&self) -> bool { + true + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -508,24 +512,19 @@ pub enum Either { impl BaseBox for Either { fn box_type(&self) -> BoxType { - match self { - Self::A(x) => x.box_type(), - Self::B(x) => x.box_type(), - } + self.actual_box().box_type() } fn box_size(&self) -> BoxSize { - match self { - Self::A(x) => x.box_size(), - Self::B(x) => x.box_size(), - } + self.actual_box().box_size() } fn box_payload_size(&self) -> u64 { - match self { - Self::A(x) => x.box_payload_size(), - Self::B(x) => x.box_payload_size(), - } + self.actual_box().box_payload_size() + } + + fn is_opaque_payload(&self) -> bool { + self.actual_box().is_opaque_payload() } fn actual_box(&self) -> &dyn BaseBox { @@ -536,10 +535,7 @@ impl BaseBox for Either { } fn children<'a>(&'a self) -> Box> { - match self { - Self::A(x) => x.children(), - Self::B(x) => x.children(), - } + self.actual_box().children() } } diff --git a/src/boxes.rs b/src/boxes.rs index ced31be..92e9497 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -124,6 +124,10 @@ impl BaseBox for FtypBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -165,24 +169,6 @@ impl Decode for RootBox { } impl BaseBox for RootBox { - fn box_type(&self) -> BoxType { - match self { - RootBox::Free(b) => b.box_type(), - RootBox::Mdat(b) => b.box_type(), - RootBox::Moov(b) => b.box_type(), - RootBox::Unknown(b) => b.box_type(), - } - } - - fn box_payload_size(&self) -> u64 { - match self { - RootBox::Free(b) => b.box_payload_size(), - RootBox::Mdat(b) => b.box_payload_size(), - RootBox::Moov(b) => b.box_payload_size(), - RootBox::Unknown(b) => b.box_payload_size(), - } - } - fn actual_box(&self) -> &dyn BaseBox { match self { RootBox::Free(b) => b.actual_box(), @@ -192,13 +178,24 @@ impl BaseBox for RootBox { } } + fn box_type(&self) -> BoxType { + self.actual_box().box_type() + } + + fn box_size(&self) -> BoxSize { + self.actual_box().box_size() + } + + fn box_payload_size(&self) -> u64 { + self.actual_box().box_payload_size() + } + + fn is_opaque_payload(&self) -> bool { + self.actual_box().is_opaque_payload() + } + fn children<'a>(&'a self) -> Box> { - match self { - RootBox::Free(b) => b.children(), - RootBox::Mdat(b) => b.children(), - RootBox::Moov(b) => b.children(), - RootBox::Unknown(b) => b.children(), - } + self.actual_box().children() } } @@ -240,6 +237,10 @@ impl BaseBox for FreeBox { self.payload.len() as u64 } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -299,6 +300,10 @@ impl BaseBox for MdatBox { self.payload.len() as u64 } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -392,6 +397,10 @@ impl BaseBox for MoovBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -512,6 +521,10 @@ impl BaseBox for MvhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -621,6 +634,10 @@ impl BaseBox for TrakBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -769,6 +786,10 @@ impl BaseBox for TkhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -866,6 +887,10 @@ impl BaseBox for EdtsBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -965,6 +990,10 @@ impl BaseBox for ElstBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1072,6 +1101,10 @@ impl BaseBox for MdiaBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1194,6 +1227,10 @@ impl BaseBox for MdhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1277,6 +1314,10 @@ impl BaseBox for HdlrBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1387,6 +1428,10 @@ impl BaseBox for MinfBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1451,6 +1496,10 @@ impl BaseBox for SmhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1523,6 +1572,10 @@ impl BaseBox for VmhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1607,6 +1660,10 @@ impl BaseBox for DinfBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1700,6 +1757,10 @@ impl BaseBox for DrefBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1776,6 +1837,10 @@ impl BaseBox for UrlBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1920,6 +1985,10 @@ impl BaseBox for StblBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -1994,6 +2063,10 @@ impl BaseBox for StsdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -2042,36 +2115,32 @@ impl Decode for SampleEntry { } impl BaseBox for SampleEntry { - fn box_type(&self) -> BoxType { + fn actual_box(&self) -> &dyn BaseBox { match self { - Self::Avc1(b) => b.box_type(), - Self::Opus(b) => b.box_type(), - Self::Unknown(b) => b.box_type(), + Self::Avc1(b) => b.actual_box(), + Self::Opus(b) => b.actual_box(), + Self::Unknown(b) => b.actual_box(), } } + fn box_type(&self) -> BoxType { + self.actual_box().box_type() + } + + fn box_size(&self) -> BoxSize { + self.actual_box().box_size() + } + fn box_payload_size(&self) -> u64 { - match self { - Self::Avc1(b) => b.box_payload_size(), - Self::Opus(b) => b.box_payload_size(), - Self::Unknown(b) => b.box_payload_size(), - } + self.actual_box().box_payload_size() } - fn actual_box(&self) -> &dyn BaseBox { - match self { - Self::Avc1(b) => b.actual_box(), - Self::Opus(b) => b.actual_box(), - Self::Unknown(b) => b.actual_box(), - } + fn is_opaque_payload(&self) -> bool { + self.actual_box().is_opaque_payload() } fn children<'a>(&'a self) -> Box> { - match self { - Self::Avc1(b) => b.children(), - Self::Opus(b) => b.children(), - Self::Unknown(b) => b.children(), - } + self.actual_box().children() } } @@ -2238,6 +2307,10 @@ impl BaseBox for Avc1Box { self } + fn is_opaque_payload(&self) -> bool { + false + } + fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -2427,6 +2500,10 @@ impl BaseBox for AvccBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -2485,6 +2562,10 @@ impl BaseBox for PaspBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -2546,6 +2627,10 @@ impl BaseBox for BtrtBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -2619,6 +2704,10 @@ impl BaseBox for SttsBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -2705,6 +2794,10 @@ impl BaseBox for StscBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -2804,6 +2897,10 @@ impl BaseBox for StszBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -2877,6 +2974,10 @@ impl BaseBox for StcoBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -2950,6 +3051,10 @@ impl BaseBox for Co64Box { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -3016,6 +3121,10 @@ impl BaseBox for SgpdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + true + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -3072,6 +3181,10 @@ impl BaseBox for SbgpBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + true + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -3120,6 +3233,10 @@ impl BaseBox for UdtaBox { self.payload.len() as u64 } + fn is_opaque_payload(&self) -> bool { + true + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -3207,6 +3324,10 @@ impl BaseBox for OpusBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } @@ -3355,6 +3476,10 @@ impl BaseBox for DopsBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } + fn is_opaque_payload(&self) -> bool { + false + } + fn actual_box(&self) -> &dyn BaseBox { self } From f3fd3d3a9d6346ec86c00457190ebfc991e9ebac Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 11:41:58 +0900 Subject: [PATCH 067/103] =?UTF-8?q?=E3=83=88=E3=83=AC=E3=82=A4=E3=83=88?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=81=AB=E3=83=86=E3=82=B9=E3=83=88=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=BE=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 15 ++++++++------- tests/decode_encode_test.rs | 31 ++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 658daec..42e8c93 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -37,6 +37,14 @@ pub struct Mp4File { pub boxes: Vec, } +impl Mp4File { + pub fn iter(&self) -> impl Iterator { + std::iter::empty() + .chain(std::iter::once(&self.ftyp_box).map(BaseBox::actual_box)) + .chain(self.boxes.iter().map(BaseBox::actual_box)) + } +} + impl Decode for Mp4File { fn decode(mut reader: &mut R) -> Result { let ftyp_box = FtypBox::decode(reader)?; @@ -62,13 +70,6 @@ impl Encode for Mp4File { } } -// TODO -// impl IterUnknownBoxes for Mp4File { -// fn iter_unknown_boxes(&self) -> impl '_ + Iterator { -// self.boxes.iter().flat_map(|b| b.iter_unknown_boxes()) -// } -// } - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BoxHeader { pub box_type: BoxType, diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index 53b9a0b..ff0b2ef 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -1,4 +1,7 @@ -use shiguredo_mp4::{Decode, Encode, IterUnknownBoxes, Mp4File, Result}; +use shiguredo_mp4::{ + boxes::{SbgpBox, SgpdBox, UdtaBox}, + BoxType, Decode, Encode, Mp4File, Result, +}; #[test] fn decode_encode_black_h264_video_mp4() -> Result<()> { @@ -6,10 +9,7 @@ fn decode_encode_black_h264_video_mp4() -> Result<()> { let file: Mp4File = Mp4File::decode(&mut &input_bytes[..])?; // デコード時に未処理のボックスがないことを確認する。 - assert_eq!( - file.iter_unknown_boxes().map(|x| x.0).collect::>(), - Vec::new() - ); + assert_eq!(collect_unknown_box_types(&file), Vec::new()); let mut output_bytes = Vec::new(); file.encode(&mut output_bytes)?; @@ -31,10 +31,7 @@ fn decode_encode_beep_opus_audio_mp4() -> Result<()> { let file: Mp4File = Mp4File::decode(&mut &input_bytes[..])?; // デコード時に未処理のボックスがないことを確認する。 - assert_eq!( - file.iter_unknown_boxes().map(|x| x.0).collect::>(), - Vec::new() - ); + assert_eq!(collect_unknown_box_types(&file), Vec::new()); let mut output_bytes = Vec::new(); file.encode(&mut output_bytes)?; @@ -49,3 +46,19 @@ fn decode_encode_beep_opus_audio_mp4() -> Result<()> { Ok(()) } + +fn collect_unknown_box_types(mp4: &Mp4File) -> Vec { + let mut stack = mp4.iter().collect::>(); + let mut unknowns = Vec::new(); + + while let Some(b) = stack.pop() { + if b.is_opaque_payload() + && !matches!(b.box_type(), SbgpBox::TYPE | SgpdBox::TYPE | UdtaBox::TYPE) + { + unknowns.push(b.box_type()); + } + stack.extend(b.children()); + } + + unknowns +} From 215a6acaf1a2057ad168176fc25dd433abb8fe47 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 11:44:56 +0900 Subject: [PATCH 068/103] =?UTF-8?q?UnknownBox=20=E3=81=AF=20boxes=20?= =?UTF-8?q?=E4=BB=A5=E4=B8=8B=E3=81=AB=E7=A7=BB=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 54 ------------------------------------------- src/boxes.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 2 +- 3 files changed, 56 insertions(+), 57 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 42e8c93..6b79f16 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -360,60 +360,6 @@ impl std::fmt::Display for BoxType { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UnknownBox { - pub box_type: BoxType, - pub box_size: BoxSize, - pub payload: Vec, -} - -impl Encode for UnknownBox { - fn encode(&self, writer: &mut W) -> Result<()> { - BoxHeader::from_box(self).encode(writer)?; - writer.write_all(&self.payload)?; - Ok(()) - } -} - -impl Decode for UnknownBox { - fn decode(reader: &mut R) -> Result { - let header = BoxHeader::decode(reader)?; - let mut payload = Vec::new(); - header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; - Ok(Self { - box_type: header.box_type, - box_size: header.box_size, - payload, - }) - } -} - -impl BaseBox for UnknownBox { - fn box_type(&self) -> BoxType { - self.box_type - } - - fn box_size(&self) -> BoxSize { - self.box_size - } - - fn box_payload_size(&self) -> u64 { - self.payload.len() as u64 - } - - fn is_opaque_payload(&self) -> bool { - true - } - - fn actual_box(&self) -> &dyn BaseBox { - self - } - - fn children<'a>(&'a self) -> Box> { - Box::new(std::iter::empty()) - } -} - /// 1904/1/1 からの経過秒数 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Mp4FileTime(u64); diff --git a/src/boxes.rs b/src/boxes.rs index 92e9497..ce06886 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -5,10 +5,63 @@ use std::{ use crate::{ io::ExternalBytes, BaseBox, BoxHeader, BoxSize, BoxType, Decode, Either, Encode, Error, - FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, Mp4FileTime, Result, Uint, UnknownBox, - Utf8String, + FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, Mp4FileTime, Result, Uint, Utf8String, }; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UnknownBox { + pub box_type: BoxType, + pub box_size: BoxSize, + pub payload: Vec, +} + +impl Encode for UnknownBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + writer.write_all(&self.payload)?; + Ok(()) + } +} + +impl Decode for UnknownBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + let mut payload = Vec::new(); + header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; + Ok(Self { + box_type: header.box_type, + box_size: header.box_size, + payload, + }) + } +} + +impl BaseBox for UnknownBox { + fn box_type(&self) -> BoxType { + self.box_type + } + + fn box_size(&self) -> BoxSize { + self.box_size + } + + fn box_payload_size(&self) -> u64 { + self.payload.len() as u64 + } + + fn is_opaque_payload(&self) -> bool { + true + } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } +} + #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Brand([u8; 4]); diff --git a/src/lib.rs b/src/lib.rs index abf988a..a30d394 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,6 @@ mod io; pub use basic_types::{ BaseBox, BoxHeader, BoxSize, BoxType, Either, FixedPointNumber, FullBox, FullBoxFlags, - FullBoxHeader, Mp4File, Mp4FileTime, Uint, UnknownBox, Utf8String, + FullBoxHeader, Mp4File, Mp4FileTime, Uint, Utf8String, }; pub use io::{Decode, Encode, Error, Result}; From c977f3eb41a0868f69603b1e9cd9438e8f43f248 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 11:56:25 +0900 Subject: [PATCH 069/103] =?UTF-8?q?vp9=20=E7=94=A8=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/boxes.rs | 2 +- src/io.rs | 1 + tests/decode_encode_test.rs | 22 ++++++++++++++++++++++ tests/testdata/black-vp9-video.mp4 | Bin 0 -> 1676 bytes 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/testdata/black-vp9-video.mp4 diff --git a/src/boxes.rs b/src/boxes.rs index ce06886..651d951 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -463,7 +463,7 @@ impl BaseBox for MoovBox { std::iter::once(self.mvhd_box.actual_box()) .chain(self.trak_boxes.iter().map(BaseBox::actual_box)) .chain(self.udta_box.iter().map(BaseBox::actual_box)) - .chain(self.trak_boxes.iter().map(BaseBox::actual_box)), + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), ) } } diff --git a/src/io.rs b/src/io.rs index b8ddcbb..26ad0e7 100644 --- a/src/io.rs +++ b/src/io.rs @@ -8,6 +8,7 @@ use crate::BoxType; pub type Result = std::result::Result; pub struct Error { + // TODO: add box_type field pub io_error: std::io::Error, pub backtrace: Backtrace, } diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index ff0b2ef..fbd0e2c 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -25,6 +25,28 @@ fn decode_encode_black_h264_video_mp4() -> Result<()> { Ok(()) } +#[test] +fn decode_encode_black_vp9_video_mp4() -> Result<()> { + let input_bytes = include_bytes!("testdata/black-vp9-video.mp4"); + let file: Mp4File = Mp4File::decode(&mut &input_bytes[..])?; + + // デコード時に未処理のボックスがないことを確認する。 + assert_eq!(collect_unknown_box_types(&file), Vec::new()); + + let mut output_bytes = Vec::new(); + file.encode(&mut output_bytes)?; + + // エンコード結果をデコードしたら同じ MP4 になっていることを確認する。 + let encoded_file: Mp4File = Mp4File::decode(&mut &output_bytes[..])?; + assert_eq!(file, encoded_file); + + // エンコード結果のバイト列が正しいことを確認する。 + assert_eq!(input_bytes.len(), output_bytes.len()); + assert_eq!(&input_bytes[..], output_bytes); + + Ok(()) +} + #[test] fn decode_encode_beep_opus_audio_mp4() -> Result<()> { let input_bytes = include_bytes!("testdata/beep-opus-audio.mp4"); diff --git a/tests/testdata/black-vp9-video.mp4 b/tests/testdata/black-vp9-video.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..5b6e0c72ed23eb40324be4b4e8348070d1dd66a8 GIT binary patch literal 1676 zcmdT_y>HV{5I;LYLWrS*B~s*6sR*!;Dha3prtmS83Q!jiLS5`Uhg!Akhy7{-LlqUo zio}Ev3mZuM5e$`>S($=ZI#p%Kgj9a_oG+pji690}Ilp`N@w&khmUP4!s%NfZ*9 z#4ETK$_rH@I@*jq5Bl$+aMbpVr&sChexXYjOLl&FhE|;J=68s{FPu{@LVnr)QR(QzeDs05Pj z1&PucU1<+jW0|v|@EzESp%_1V4eA=*+0l{ZH4(%;3`%{0558pYHKvyEdf zL1bn!@;9Bdu~=C+UtOqHYQ?~JQ`st~vJXM|g9G?kscc-+aC6(HiQ#~bHGMD8x*6#t z5*pqxTdsPD?xs)<*88ESSBNw}dI@lGVn<4z(}2o;cGFJ3R}lji@K3@Z3`&?r L6Vs?JVj7j7CX8h> literal 0 HcmV?d00001 From d611cd6f0ad44d9fd08a8f5eebe7b4c9ae57acf9 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 12:01:00 +0900 Subject: [PATCH 070/103] Add stss box --- src/boxes.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/boxes.rs b/src/boxes.rs index 651d951..083633e 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1921,6 +1921,7 @@ pub struct StblBox { pub stsc_box: StscBox, pub stsz_box: StszBox, pub stco_or_co64_box: Either, + pub stss_box: Option, pub sgpd_box: Option, pub sbgp_box: Option, pub unknown_boxes: Vec, @@ -1938,6 +1939,9 @@ impl StblBox { Either::A(b) => b.encode(writer)?, Either::B(b) => b.encode(writer)?, } + if let Some(b) = &self.stss_box { + b.encode(writer)?; + } if let Some(b) = &self.sgpd_box { b.encode(writer)?; } @@ -1957,6 +1961,7 @@ impl StblBox { let mut stsz_box = None; let mut stco_box = None; let mut co64_box = None; + let mut stss_box = None; let mut sgpd_box = None; let mut sbgp_box = None; let mut unknown_boxes = Vec::new(); @@ -1981,6 +1986,9 @@ impl StblBox { Co64Box::TYPE if co64_box.is_none() => { co64_box = Some(Co64Box::decode(&mut reader)?); } + StssBox::TYPE if stss_box.is_none() => { + stss_box = Some(StssBox::decode(&mut reader)?); + } SgpdBox::TYPE if sgpd_box.is_none() => { sgpd_box = Some(SgpdBox::decode(&mut reader)?); } @@ -2006,6 +2014,7 @@ impl StblBox { stsc_box, stsz_box, stco_or_co64_box, + stss_box, sgpd_box, sbgp_box, unknown_boxes, @@ -2054,6 +2063,7 @@ impl BaseBox for StblBox { .chain(std::iter::once(&self.stsc_box).map(BaseBox::actual_box)) .chain(std::iter::once(&self.stsz_box).map(BaseBox::actual_box)) .chain(std::iter::once(&self.stco_or_co64_box).map(BaseBox::actual_box)) + .chain(self.stss_box.iter().map(BaseBox::actual_box)) .chain(self.sgpd_box.iter().map(BaseBox::actual_box)) .chain(self.sbgp_box.iter().map(BaseBox::actual_box)) .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), @@ -3127,6 +3137,83 @@ impl FullBox for Co64Box { } } +/// [ISO/IEC 14496-12] SyncSampleBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StssBox { + pub sample_numbers: Vec, +} + +impl StssBox { + pub const TYPE: BoxType = BoxType::Normal(*b"stss"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + (self.sample_numbers.len() as u32).encode(writer)?; + for offset in &self.sample_numbers { + offset.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let _ = FullBoxHeader::decode(reader)?; + let count = u32::decode(reader)? as usize; + let mut sample_numbers = Vec::with_capacity(count); + for _ in 0..count { + sample_numbers.push(u32::decode(reader)?); + } + Ok(Self { sample_numbers }) + } +} + +impl Encode for StssBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for StssBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for StssBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } + + fn is_opaque_payload(&self) -> bool { + false + } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } +} + +impl FullBox for StssBox { + fn full_box_version(&self) -> u8 { + 0 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} + /// [ISO/IEC 14496-12] SampleGroupDescriptionBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct SgpdBox { From 26e5723e771cec3cc1c69049aba478d2f63489b9 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 12:30:52 +0900 Subject: [PATCH 071/103] Add vp08, vp09, vpcC boxes --- src/basic_types.rs | 1 + src/boxes.rs | 339 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 339 insertions(+), 1 deletion(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 6b79f16..c85c86c 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -21,6 +21,7 @@ pub trait BaseBox { fn is_opaque_payload(&self) -> bool; + // TODO: remove fn actual_box(&self) -> &dyn BaseBox; fn children<'a>(&'a self) -> Box>; diff --git a/src/boxes.rs b/src/boxes.rs index 083633e..caf1ce9 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2153,6 +2153,8 @@ impl FullBox for StsdBox { pub enum SampleEntry { Avc1(Avc1Box), Opus(OpusBox), + Vp08(Vp08Box), + Vp09(Vp09Box), Unknown(UnknownBox), } @@ -2161,6 +2163,8 @@ impl Encode for SampleEntry { match self { Self::Avc1(b) => b.encode(writer), Self::Opus(b) => b.encode(writer), + Self::Vp08(b) => b.encode(writer), + Self::Vp09(b) => b.encode(writer), Self::Unknown(b) => b.encode(writer), } } @@ -2172,6 +2176,8 @@ impl Decode for SampleEntry { match header.box_type { Avc1Box::TYPE => Decode::decode(&mut reader).map(Self::Avc1), OpusBox::TYPE => Decode::decode(&mut reader).map(Self::Opus), + Vp08Box::TYPE => Decode::decode(&mut reader).map(Self::Vp08), + Vp09Box::TYPE => Decode::decode(&mut reader).map(Self::Vp09), _ => Decode::decode(&mut reader).map(Self::Unknown), } } @@ -2182,6 +2188,8 @@ impl BaseBox for SampleEntry { match self { Self::Avc1(b) => b.actual_box(), Self::Opus(b) => b.actual_box(), + Self::Vp08(b) => b.actual_box(), + Self::Vp09(b) => b.actual_box(), Self::Unknown(b) => b.actual_box(), } } @@ -2385,7 +2393,7 @@ impl BaseBox for Avc1Box { } } -/// [ISO/IEC 14496-15] AVCSampleEntry class +/// [ISO/IEC 14496-15] AVCConfigurationBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct AvccBox { pub configuration_version: u8, @@ -2576,6 +2584,335 @@ impl BaseBox for AvccBox { } } +/// [] VP8SampleEntry class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Vp08Box { + pub visual: VisualSampleEntryFields, + pub vpcc_box: VpccBox, + pub pasp_box: Option, + pub btrt_box: Option, + pub unknown_boxes: Vec, +} + +impl Vp08Box { + pub const TYPE: BoxType = BoxType::Normal(*b"vp08"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.visual.encode(writer)?; + self.vpcc_box.encode(writer)?; + if let Some(b) = &self.pasp_box { + b.encode(writer)?; + } + if let Some(b) = &self.btrt_box { + b.encode(writer)?; + } + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let visual = VisualSampleEntryFields::decode(reader)?; + let mut vpcc_box = None; + let mut pasp_box = None; + let mut btrt_box = None; + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + VpccBox::TYPE if vpcc_box.is_none() => { + vpcc_box = Some(VpccBox::decode(&mut reader)?); + } + PaspBox::TYPE if pasp_box.is_none() => { + pasp_box = Some(PaspBox::decode(&mut reader)?); + } + BtrtBox::TYPE if btrt_box.is_none() => { + btrt_box = Some(BtrtBox::decode(&mut reader)?); + } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + let vpcc_box = vpcc_box.ok_or_else(|| Error::missing_box("vpcC", Self::TYPE))?; + Ok(Self { + visual, + vpcc_box, + pasp_box, + btrt_box, + unknown_boxes, + }) + } +} + +impl Encode for Vp08Box { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for Vp08Box { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for Vp08Box { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn is_opaque_payload(&self) -> bool { + false + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(std::iter::once(&self.vpcc_box).map(BaseBox::actual_box)) + .chain(self.pasp_box.iter().map(BaseBox::actual_box)) + .chain(self.btrt_box.iter().map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) + } +} + +/// [] VP9SampleEntry class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Vp09Box { + pub visual: VisualSampleEntryFields, + pub vpcc_box: VpccBox, + pub pasp_box: Option, + pub btrt_box: Option, + pub unknown_boxes: Vec, +} + +impl Vp09Box { + pub const TYPE: BoxType = BoxType::Normal(*b"vp09"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.visual.encode(writer)?; + self.vpcc_box.encode(writer)?; + if let Some(b) = &self.pasp_box { + b.encode(writer)?; + } + if let Some(b) = &self.btrt_box { + b.encode(writer)?; + } + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let visual = VisualSampleEntryFields::decode(reader)?; + let mut vpcc_box = None; + let mut pasp_box = None; + let mut btrt_box = None; + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + VpccBox::TYPE if vpcc_box.is_none() => { + vpcc_box = Some(VpccBox::decode(&mut reader)?); + } + PaspBox::TYPE if pasp_box.is_none() => { + pasp_box = Some(PaspBox::decode(&mut reader)?); + } + BtrtBox::TYPE if btrt_box.is_none() => { + btrt_box = Some(BtrtBox::decode(&mut reader)?); + } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + let vpcc_box = vpcc_box.ok_or_else(|| Error::missing_box("vpcC", Self::TYPE))?; + Ok(Self { + visual, + vpcc_box, + pasp_box, + btrt_box, + unknown_boxes, + }) + } +} + +impl Encode for Vp09Box { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for Vp09Box { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for Vp09Box { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn is_opaque_payload(&self) -> bool { + false + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(std::iter::once(&self.vpcc_box).map(BaseBox::actual_box)) + .chain(self.pasp_box.iter().map(BaseBox::actual_box)) + .chain(self.btrt_box.iter().map(BaseBox::actual_box)) + .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + ) + } +} + +/// [] VPCodecConfigurationBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VpccBox { + pub profile: u8, + pub level: u8, + pub bit_depth: Uint<4>, + pub chroma_subsampling: Uint<3>, + pub video_full_range_flag: bool, + pub colour_primaries: u8, + pub transfer_characteristics: u8, + pub matrix_coefficients: u8, + pub codec_initialization_data: Vec, +} + +impl VpccBox { + pub const TYPE: BoxType = BoxType::Normal(*b"vpcC"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + FullBoxHeader::from_box(self).encode(writer)?; + self.profile.encode(writer)?; + self.level.encode(writer)?; + ((self.bit_depth.get() << 4) + | (self.chroma_subsampling.get() << 1) + | self.video_full_range_flag as u8) + .encode(writer)?; + self.colour_primaries.encode(writer)?; + self.transfer_characteristics.encode(writer)?; + self.matrix_coefficients.encode(writer)?; + (self.codec_initialization_data.len() as u16).encode(writer)?; + writer.write_all(&self.codec_initialization_data)?; + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let header = FullBoxHeader::decode(reader)?; + if header.version != 1 { + return Err(Error::invalid_data(&format!( + "Unexpected full box header version: box=vpcC, version={}", + header.version + ))); + } + + let profile = u8::decode(reader)?; + let level = u8::decode(reader)?; + + let b = u8::decode(reader)?; + let bit_depth = Uint::new(b >> 4); + let chroma_subsampling = Uint::new(b >> 1); + let video_full_range_flag = (b & 1) != 0; + let colour_primaries = u8::decode(reader)?; + let transfer_characteristics = u8::decode(reader)?; + let matrix_coefficients = u8::decode(reader)?; + let mut codec_initialization_data = vec![0; u16::decode(reader)? as usize]; + reader.read_exact(&mut codec_initialization_data)?; + + Ok(Self { + profile, + level, + bit_depth, + chroma_subsampling, + video_full_range_flag, + colour_primaries, + transfer_characteristics, + matrix_coefficients, + codec_initialization_data, + }) + } +} + +impl Encode for VpccBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for VpccBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for VpccBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } + + fn actual_box(&self) -> &dyn BaseBox { + self + } + + fn is_opaque_payload(&self) -> bool { + false + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } +} + +impl FullBox for VpccBox { + fn full_box_version(&self) -> u8 { + 1 + } + + fn full_box_flags(&self) -> FullBoxFlags { + FullBoxFlags::new(0) + } +} + /// [ISO/IEC 14496-12] PixelAspectRatioBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct PaspBox { From 9685cedfcf561abb036783c2c743a4d0ad2694ee Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 12:34:26 +0900 Subject: [PATCH 072/103] Update test --- tests/decode_encode_test.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index fbd0e2c..dd3289f 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -42,6 +42,11 @@ fn decode_encode_black_vp9_video_mp4() -> Result<()> { // エンコード結果のバイト列が正しいことを確認する。 assert_eq!(input_bytes.len(), output_bytes.len()); + + // ボックスの順番は入れ替わるのでソートした結果を比較する + let mut input_bytes = input_bytes.to_vec(); + input_bytes.sort(); + output_bytes.sort(); assert_eq!(&input_bytes[..], output_bytes); Ok(()) @@ -76,6 +81,7 @@ fn collect_unknown_box_types(mp4: &Mp4File) -> Vec { while let Some(b) = stack.pop() { if b.is_opaque_payload() && !matches!(b.box_type(), SbgpBox::TYPE | SgpdBox::TYPE | UdtaBox::TYPE) + && !matches!(b.box_type().as_bytes(), b"fiel") { unknowns.push(b.box_type()); } From 8c3baebca90d56b7cfbf1cda0c42da95e1ea190d Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 12:36:51 +0900 Subject: [PATCH 073/103] =?UTF-8?q?=E4=B8=AD=E8=BA=AB=E3=82=92=E8=A7=A3?= =?UTF-8?q?=E9=87=88=E3=81=97=E3=81=AA=E3=81=84=E3=83=9C=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=B9=E3=81=AF=20UnknownBox=20=E3=81=AB=E9=9B=86=E7=B4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/boxes.rs | 202 ------------------------------------ tests/decode_encode_test.rs | 11 +- 2 files changed, 5 insertions(+), 208 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index caf1ce9..ee11e13 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -371,7 +371,6 @@ impl BaseBox for MdatBox { pub struct MoovBox { pub mvhd_box: MvhdBox, pub trak_boxes: Vec, - pub udta_box: Option, pub unknown_boxes: Vec, } @@ -383,9 +382,6 @@ impl MoovBox { for b in &self.trak_boxes { b.encode(writer)?; } - if let Some(b) = &self.udta_box { - b.encode(writer)?; - } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -395,7 +391,6 @@ impl MoovBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let mut mvhd_box = None; let mut trak_boxes = Vec::new(); - let mut udta_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -406,9 +401,6 @@ impl MoovBox { TrakBox::TYPE => { trak_boxes.push(Decode::decode(&mut reader)?); } - UdtaBox::TYPE if udta_box.is_none() => { - udta_box = Some(Decode::decode(&mut reader)?); - } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -419,7 +411,6 @@ impl MoovBox { Ok(Self { mvhd_box, trak_boxes, - udta_box, unknown_boxes, }) } @@ -462,7 +453,6 @@ impl BaseBox for MoovBox { Box::new( std::iter::once(self.mvhd_box.actual_box()) .chain(self.trak_boxes.iter().map(BaseBox::actual_box)) - .chain(self.udta_box.iter().map(BaseBox::actual_box)) .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), ) } @@ -1922,8 +1912,6 @@ pub struct StblBox { pub stsz_box: StszBox, pub stco_or_co64_box: Either, pub stss_box: Option, - pub sgpd_box: Option, - pub sbgp_box: Option, pub unknown_boxes: Vec, } @@ -1942,12 +1930,6 @@ impl StblBox { if let Some(b) = &self.stss_box { b.encode(writer)?; } - if let Some(b) = &self.sgpd_box { - b.encode(writer)?; - } - if let Some(b) = &self.sbgp_box { - b.encode(writer)?; - } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -1962,8 +1944,6 @@ impl StblBox { let mut stco_box = None; let mut co64_box = None; let mut stss_box = None; - let mut sgpd_box = None; - let mut sbgp_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -1989,12 +1969,6 @@ impl StblBox { StssBox::TYPE if stss_box.is_none() => { stss_box = Some(StssBox::decode(&mut reader)?); } - SgpdBox::TYPE if sgpd_box.is_none() => { - sgpd_box = Some(SgpdBox::decode(&mut reader)?); - } - SbgpBox::TYPE if sbgp_box.is_none() => { - sbgp_box = Some(SbgpBox::decode(&mut reader)?); - } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -2015,8 +1989,6 @@ impl StblBox { stsz_box, stco_or_co64_box, stss_box, - sgpd_box, - sbgp_box, unknown_boxes, }) } @@ -2064,8 +2036,6 @@ impl BaseBox for StblBox { .chain(std::iter::once(&self.stsz_box).map(BaseBox::actual_box)) .chain(std::iter::once(&self.stco_or_co64_box).map(BaseBox::actual_box)) .chain(self.stss_box.iter().map(BaseBox::actual_box)) - .chain(self.sgpd_box.iter().map(BaseBox::actual_box)) - .chain(self.sbgp_box.iter().map(BaseBox::actual_box)) .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), ) } @@ -3551,178 +3521,6 @@ impl FullBox for StssBox { } } -/// [ISO/IEC 14496-12] SampleGroupDescriptionBox class -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SgpdBox { - // 必要になるまではこのボックスの中身は単なるバイト列として扱う - pub payload: Vec, -} - -impl SgpdBox { - pub const TYPE: BoxType = BoxType::Normal(*b"sgpd"); - - fn encode_payload(&self, writer: &mut W) -> Result<()> { - writer.write_all(&self.payload)?; - Ok(()) - } - - fn decode_payload(reader: &mut std::io::Take) -> Result { - let mut payload = Vec::new(); - reader.read_to_end(&mut payload)?; - Ok(Self { payload }) - } -} - -impl Encode for SgpdBox { - fn encode(&self, writer: &mut W) -> Result<()> { - BoxHeader::from_box(self).encode(writer)?; - self.encode_payload(writer)?; - Ok(()) - } -} - -impl Decode for SgpdBox { - fn decode(reader: &mut R) -> Result { - let header = BoxHeader::decode(reader)?; - header.box_type.expect(Self::TYPE)?; - header.with_box_payload_reader(reader, Self::decode_payload) - } -} - -impl BaseBox for SgpdBox { - fn box_type(&self) -> BoxType { - Self::TYPE - } - - fn box_payload_size(&self) -> u64 { - ExternalBytes::calc(|writer| self.encode_payload(writer)) - } - - fn is_opaque_payload(&self) -> bool { - true - } - - fn actual_box(&self) -> &dyn BaseBox { - self - } - - fn children<'a>(&'a self) -> Box> { - Box::new(std::iter::empty()) - } -} - -/// [ISO/IEC 14496-12] SampleToGroupBox class -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SbgpBox { - // 必要になるまではこのボックスの中身は単なるバイト列として扱う - pub payload: Vec, -} - -impl SbgpBox { - pub const TYPE: BoxType = BoxType::Normal(*b"sbgp"); - - fn encode_payload(&self, writer: &mut W) -> Result<()> { - writer.write_all(&self.payload)?; - Ok(()) - } - - fn decode_payload(reader: &mut std::io::Take) -> Result { - let mut payload = Vec::new(); - reader.read_to_end(&mut payload)?; - Ok(Self { payload }) - } -} - -impl Encode for SbgpBox { - fn encode(&self, writer: &mut W) -> Result<()> { - BoxHeader::from_box(self).encode(writer)?; - self.encode_payload(writer)?; - Ok(()) - } -} - -impl Decode for SbgpBox { - fn decode(reader: &mut R) -> Result { - let header = BoxHeader::decode(reader)?; - header.box_type.expect(Self::TYPE)?; - header.with_box_payload_reader(reader, Self::decode_payload) - } -} - -impl BaseBox for SbgpBox { - fn box_type(&self) -> BoxType { - Self::TYPE - } - - fn box_payload_size(&self) -> u64 { - ExternalBytes::calc(|writer| self.encode_payload(writer)) - } - - fn is_opaque_payload(&self) -> bool { - true - } - - fn actual_box(&self) -> &dyn BaseBox { - self - } - - fn children<'a>(&'a self) -> Box> { - Box::new(std::iter::empty()) - } -} - -/// [ISO/IEC 14496-12] UserDataBox class -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UdtaBox { - // 必要になるまではこのボックスの中身は単なるバイト列として扱う - pub payload: Vec, -} - -impl UdtaBox { - pub const TYPE: BoxType = BoxType::Normal(*b"udta"); -} - -impl Encode for UdtaBox { - fn encode(&self, writer: &mut W) -> Result<()> { - BoxHeader::from_box(self).encode(writer)?; - writer.write_all(&self.payload)?; - Ok(()) - } -} - -impl Decode for UdtaBox { - fn decode(reader: &mut R) -> Result { - let header = BoxHeader::decode(reader)?; - header.box_type.expect(Self::TYPE)?; - - let mut payload = Vec::new(); - header.with_box_payload_reader(reader, |reader| Ok(reader.read_to_end(&mut payload)?))?; - Ok(Self { payload }) - } -} - -impl BaseBox for UdtaBox { - fn box_type(&self) -> BoxType { - Self::TYPE - } - - fn box_payload_size(&self) -> u64 { - self.payload.len() as u64 - } - - fn is_opaque_payload(&self) -> bool { - true - } - - fn actual_box(&self) -> &dyn BaseBox { - self - } - - fn children<'a>(&'a self) -> Box> { - Box::new(std::iter::empty()) - } -} - /// [] OpusSampleEntry class #[derive(Debug, Clone, PartialEq, Eq)] pub struct OpusBox { diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index dd3289f..4939b40 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -1,7 +1,4 @@ -use shiguredo_mp4::{ - boxes::{SbgpBox, SgpdBox, UdtaBox}, - BoxType, Decode, Encode, Mp4File, Result, -}; +use shiguredo_mp4::{BoxType, Decode, Encode, Mp4File, Result}; #[test] fn decode_encode_black_h264_video_mp4() -> Result<()> { @@ -80,8 +77,10 @@ fn collect_unknown_box_types(mp4: &Mp4File) -> Vec { while let Some(b) = stack.pop() { if b.is_opaque_payload() - && !matches!(b.box_type(), SbgpBox::TYPE | SgpdBox::TYPE | UdtaBox::TYPE) - && !matches!(b.box_type().as_bytes(), b"fiel") + && !matches!( + b.box_type().as_bytes(), + b"fiel" | b"sbgp" | b"sgpd" | b"udta" + ) { unknowns.push(b.box_type()); } From 490bd96a316666b7f2e886f0a405264d103b8254 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 12:40:33 +0900 Subject: [PATCH 074/103] s/is_opaque_payload/is_unknown_box/ --- src/basic_types.rs | 6 +-- src/boxes.rs | 80 ++++++++++++++++++------------------- tests/decode_encode_test.rs | 2 +- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index c85c86c..9836977 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -19,7 +19,7 @@ pub trait BaseBox { fn box_payload_size(&self) -> u64; - fn is_opaque_payload(&self) -> bool; + fn is_unknown_box(&self) -> bool; // TODO: remove fn actual_box(&self) -> &dyn BaseBox; @@ -471,8 +471,8 @@ impl BaseBox for Either { self.actual_box().box_payload_size() } - fn is_opaque_payload(&self) -> bool { - self.actual_box().is_opaque_payload() + fn is_unknown_box(&self) -> bool { + self.actual_box().is_unknown_box() } fn actual_box(&self) -> &dyn BaseBox { diff --git a/src/boxes.rs b/src/boxes.rs index ee11e13..6fd2bd1 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -49,7 +49,7 @@ impl BaseBox for UnknownBox { self.payload.len() as u64 } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { true } @@ -177,7 +177,7 @@ impl BaseBox for FtypBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -243,8 +243,8 @@ impl BaseBox for RootBox { self.actual_box().box_payload_size() } - fn is_opaque_payload(&self) -> bool { - self.actual_box().is_opaque_payload() + fn is_unknown_box(&self) -> bool { + self.actual_box().is_unknown_box() } fn children<'a>(&'a self) -> Box> { @@ -290,7 +290,7 @@ impl BaseBox for FreeBox { self.payload.len() as u64 } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -353,7 +353,7 @@ impl BaseBox for MdatBox { self.payload.len() as u64 } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -441,7 +441,7 @@ impl BaseBox for MoovBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -564,7 +564,7 @@ impl BaseBox for MvhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -677,7 +677,7 @@ impl BaseBox for TrakBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -829,7 +829,7 @@ impl BaseBox for TkhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -930,7 +930,7 @@ impl BaseBox for EdtsBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -1033,7 +1033,7 @@ impl BaseBox for ElstBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -1144,7 +1144,7 @@ impl BaseBox for MdiaBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -1270,7 +1270,7 @@ impl BaseBox for MdhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -1357,7 +1357,7 @@ impl BaseBox for HdlrBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -1471,7 +1471,7 @@ impl BaseBox for MinfBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -1539,7 +1539,7 @@ impl BaseBox for SmhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -1615,7 +1615,7 @@ impl BaseBox for VmhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -1703,7 +1703,7 @@ impl BaseBox for DinfBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -1800,7 +1800,7 @@ impl BaseBox for DrefBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -1880,7 +1880,7 @@ impl BaseBox for UrlBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -2019,7 +2019,7 @@ impl BaseBox for StblBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -2096,7 +2096,7 @@ impl BaseBox for StsdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -2176,8 +2176,8 @@ impl BaseBox for SampleEntry { self.actual_box().box_payload_size() } - fn is_opaque_payload(&self) -> bool { - self.actual_box().is_opaque_payload() + fn is_unknown_box(&self) -> bool { + self.actual_box().is_unknown_box() } fn children<'a>(&'a self) -> Box> { @@ -2348,7 +2348,7 @@ impl BaseBox for Avc1Box { self } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -2541,7 +2541,7 @@ impl BaseBox for AvccBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -2645,7 +2645,7 @@ impl BaseBox for Vp08Box { self } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -2751,7 +2751,7 @@ impl BaseBox for Vp09Box { self } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -2864,7 +2864,7 @@ impl BaseBox for VpccBox { self } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -2932,7 +2932,7 @@ impl BaseBox for PaspBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -2997,7 +2997,7 @@ impl BaseBox for BtrtBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -3074,7 +3074,7 @@ impl BaseBox for SttsBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -3164,7 +3164,7 @@ impl BaseBox for StscBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -3267,7 +3267,7 @@ impl BaseBox for StszBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -3344,7 +3344,7 @@ impl BaseBox for StcoBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -3421,7 +3421,7 @@ impl BaseBox for Co64Box { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -3498,7 +3498,7 @@ impl BaseBox for StssBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -3599,7 +3599,7 @@ impl BaseBox for OpusBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } @@ -3751,7 +3751,7 @@ impl BaseBox for DopsBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_opaque_payload(&self) -> bool { + fn is_unknown_box(&self) -> bool { false } diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index 4939b40..e79a461 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -76,7 +76,7 @@ fn collect_unknown_box_types(mp4: &Mp4File) -> Vec { let mut unknowns = Vec::new(); while let Some(b) = stack.pop() { - if b.is_opaque_payload() + if b.is_unknown_box() && !matches!( b.box_type().as_bytes(), b"fiel" | b"sbgp" | b"sgpd" | b"udta" From e9e9a7e1a67317e92982191b6208d17c3cdd0b93 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 12:52:34 +0900 Subject: [PATCH 075/103] Remove BaseBox::actual_box() --- src/basic_types.rs | 37 +++--- src/boxes.rs | 300 ++++++++++++--------------------------------- 2 files changed, 101 insertions(+), 236 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 9836977..da2a149 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -21,12 +21,13 @@ pub trait BaseBox { fn is_unknown_box(&self) -> bool; - // TODO: remove - fn actual_box(&self) -> &dyn BaseBox; - fn children<'a>(&'a self) -> Box>; } +pub(crate) fn as_box_object(t: &T) -> &dyn BaseBox { + t +} + pub trait FullBox: BaseBox { fn full_box_version(&self) -> u8; fn full_box_flags(&self) -> FullBoxFlags; @@ -41,8 +42,8 @@ pub struct Mp4File { impl Mp4File { pub fn iter(&self) -> impl Iterator { std::iter::empty() - .chain(std::iter::once(&self.ftyp_box).map(BaseBox::actual_box)) - .chain(self.boxes.iter().map(BaseBox::actual_box)) + .chain(std::iter::once(&self.ftyp_box).map(as_box_object)) + .chain(self.boxes.iter().map(as_box_object)) } } @@ -458,32 +459,34 @@ pub enum Either { B(B), } +impl Either { + fn inner_box(&self) -> &dyn BaseBox { + match self { + Self::A(x) => x, + Self::B(x) => x, + } + } +} + impl BaseBox for Either { fn box_type(&self) -> BoxType { - self.actual_box().box_type() + self.inner_box().box_type() } fn box_size(&self) -> BoxSize { - self.actual_box().box_size() + self.inner_box().box_size() } fn box_payload_size(&self) -> u64 { - self.actual_box().box_payload_size() + self.inner_box().box_payload_size() } fn is_unknown_box(&self) -> bool { - self.actual_box().is_unknown_box() - } - - fn actual_box(&self) -> &dyn BaseBox { - match self { - Self::A(x) => x.actual_box(), - Self::B(x) => x.actual_box(), - } + self.inner_box().is_unknown_box() } fn children<'a>(&'a self) -> Box> { - self.actual_box().children() + self.inner_box().children() } } diff --git a/src/boxes.rs b/src/boxes.rs index 6fd2bd1..e8462f3 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -4,8 +4,9 @@ use std::{ }; use crate::{ - io::ExternalBytes, BaseBox, BoxHeader, BoxSize, BoxType, Decode, Either, Encode, Error, - FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, Mp4FileTime, Result, Uint, Utf8String, + basic_types::as_box_object, io::ExternalBytes, BaseBox, BoxHeader, BoxSize, BoxType, Decode, + Either, Encode, Error, FixedPointNumber, FullBox, FullBoxFlags, FullBoxHeader, Mp4FileTime, + Result, Uint, Utf8String, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -53,10 +54,6 @@ impl BaseBox for UnknownBox { true } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -181,10 +178,6 @@ impl BaseBox for FtypBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -198,6 +191,17 @@ pub enum RootBox { Unknown(UnknownBox), } +impl RootBox { + fn inner_box(&self) -> &dyn BaseBox { + match self { + RootBox::Free(b) => b, + RootBox::Mdat(b) => b, + RootBox::Moov(b) => b, + RootBox::Unknown(b) => b, + } + } +} + impl Encode for RootBox { fn encode(&self, writer: &mut W) -> Result<()> { match self { @@ -222,33 +226,24 @@ impl Decode for RootBox { } impl BaseBox for RootBox { - fn actual_box(&self) -> &dyn BaseBox { - match self { - RootBox::Free(b) => b.actual_box(), - RootBox::Mdat(b) => b.actual_box(), - RootBox::Moov(b) => b.actual_box(), - RootBox::Unknown(b) => b.actual_box(), - } - } - fn box_type(&self) -> BoxType { - self.actual_box().box_type() + self.inner_box().box_type() } fn box_size(&self) -> BoxSize { - self.actual_box().box_size() + self.inner_box().box_size() } fn box_payload_size(&self) -> u64 { - self.actual_box().box_payload_size() + self.inner_box().box_payload_size() } fn is_unknown_box(&self) -> bool { - self.actual_box().is_unknown_box() + self.inner_box().is_unknown_box() } fn children<'a>(&'a self) -> Box> { - self.actual_box().children() + self.inner_box().children() } } @@ -294,10 +289,6 @@ impl BaseBox for FreeBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -357,10 +348,6 @@ impl BaseBox for MdatBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -445,15 +432,12 @@ impl BaseBox for MoovBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new( - std::iter::once(self.mvhd_box.actual_box()) - .chain(self.trak_boxes.iter().map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + std::iter::empty() + .chain(std::iter::once(&self.mvhd_box).map(as_box_object)) + .chain(self.trak_boxes.iter().map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -568,10 +552,6 @@ impl BaseBox for MvhdBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -681,17 +661,13 @@ impl BaseBox for TrakBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(std::iter::once(&self.tkhd_box).map(BaseBox::actual_box)) - .chain(self.edts_box.iter().map(BaseBox::actual_box)) - .chain(std::iter::once(&self.mdia_box).map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(std::iter::once(&self.tkhd_box).map(as_box_object)) + .chain(self.edts_box.iter().map(as_box_object)) + .chain(std::iter::once(&self.mdia_box).map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -833,10 +809,6 @@ impl BaseBox for TkhdBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -934,15 +906,11 @@ impl BaseBox for EdtsBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(self.elst_box.iter().map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(self.elst_box.iter().map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -1037,10 +1005,6 @@ impl BaseBox for ElstBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1148,17 +1112,13 @@ impl BaseBox for MdiaBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(std::iter::once(&self.mdhd_box).map(BaseBox::actual_box)) - .chain(std::iter::once(&self.hdlr_box).map(BaseBox::actual_box)) - .chain(std::iter::once(&self.minf_box).map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(std::iter::once(&self.mdhd_box).map(as_box_object)) + .chain(std::iter::once(&self.hdlr_box).map(as_box_object)) + .chain(std::iter::once(&self.minf_box).map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -1274,10 +1234,6 @@ impl BaseBox for MdhdBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1361,10 +1317,6 @@ impl BaseBox for HdlrBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1475,17 +1427,13 @@ impl BaseBox for MinfBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(std::iter::once(&self.smhd_or_vmhd_box).map(BaseBox::actual_box)) - .chain(std::iter::once(&self.dinf_box).map(BaseBox::actual_box)) - .chain(std::iter::once(&self.stbl_box).map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(std::iter::once(&self.smhd_or_vmhd_box).map(as_box_object)) + .chain(std::iter::once(&self.dinf_box).map(as_box_object)) + .chain(std::iter::once(&self.stbl_box).map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -1543,10 +1491,6 @@ impl BaseBox for SmhdBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1619,10 +1563,6 @@ impl BaseBox for VmhdBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1707,15 +1647,11 @@ impl BaseBox for DinfBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(std::iter::once(&self.dref_box).map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(std::iter::once(&self.dref_box).map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -1804,15 +1740,11 @@ impl BaseBox for DrefBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(self.url_box.iter().map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(self.url_box.iter().map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -1884,10 +1816,6 @@ impl BaseBox for UrlBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -2023,20 +1951,16 @@ impl BaseBox for StblBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(std::iter::once(&self.stsd_box).map(BaseBox::actual_box)) - .chain(std::iter::once(&self.stts_box).map(BaseBox::actual_box)) - .chain(std::iter::once(&self.stsc_box).map(BaseBox::actual_box)) - .chain(std::iter::once(&self.stsz_box).map(BaseBox::actual_box)) - .chain(std::iter::once(&self.stco_or_co64_box).map(BaseBox::actual_box)) - .chain(self.stss_box.iter().map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(std::iter::once(&self.stsd_box).map(as_box_object)) + .chain(std::iter::once(&self.stts_box).map(as_box_object)) + .chain(std::iter::once(&self.stsc_box).map(as_box_object)) + .chain(std::iter::once(&self.stsz_box).map(as_box_object)) + .chain(std::iter::once(&self.stco_or_co64_box).map(as_box_object)) + .chain(self.stss_box.iter().map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -2100,12 +2024,8 @@ impl BaseBox for StsdBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { - Box::new(self.entries.iter().map(BaseBox::actual_box)) + Box::new(self.entries.iter().map(as_box_object)) } } @@ -2128,6 +2048,18 @@ pub enum SampleEntry { Unknown(UnknownBox), } +impl SampleEntry { + fn inner_box(&self) -> &dyn BaseBox { + match self { + Self::Avc1(b) => b, + Self::Opus(b) => b, + Self::Vp08(b) => b, + Self::Vp09(b) => b, + Self::Unknown(b) => b, + } + } +} + impl Encode for SampleEntry { fn encode(&self, writer: &mut W) -> Result<()> { match self { @@ -2154,34 +2086,24 @@ impl Decode for SampleEntry { } impl BaseBox for SampleEntry { - fn actual_box(&self) -> &dyn BaseBox { - match self { - Self::Avc1(b) => b.actual_box(), - Self::Opus(b) => b.actual_box(), - Self::Vp08(b) => b.actual_box(), - Self::Vp09(b) => b.actual_box(), - Self::Unknown(b) => b.actual_box(), - } - } - fn box_type(&self) -> BoxType { - self.actual_box().box_type() + self.inner_box().box_type() } fn box_size(&self) -> BoxSize { - self.actual_box().box_size() + self.inner_box().box_size() } fn box_payload_size(&self) -> u64 { - self.actual_box().box_payload_size() + self.inner_box().box_payload_size() } fn is_unknown_box(&self) -> bool { - self.actual_box().is_unknown_box() + self.inner_box().is_unknown_box() } fn children<'a>(&'a self) -> Box> { - self.actual_box().children() + self.inner_box().children() } } @@ -2344,10 +2266,6 @@ impl BaseBox for Avc1Box { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn is_unknown_box(&self) -> bool { false } @@ -2355,10 +2273,10 @@ impl BaseBox for Avc1Box { fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(std::iter::once(&self.avcc_box).map(BaseBox::actual_box)) - .chain(self.pasp_box.iter().map(BaseBox::actual_box)) - .chain(self.btrt_box.iter().map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(std::iter::once(&self.avcc_box).map(as_box_object)) + .chain(self.pasp_box.iter().map(as_box_object)) + .chain(self.btrt_box.iter().map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -2545,10 +2463,6 @@ impl BaseBox for AvccBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -2641,10 +2555,6 @@ impl BaseBox for Vp08Box { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn is_unknown_box(&self) -> bool { false } @@ -2652,10 +2562,10 @@ impl BaseBox for Vp08Box { fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(std::iter::once(&self.vpcc_box).map(BaseBox::actual_box)) - .chain(self.pasp_box.iter().map(BaseBox::actual_box)) - .chain(self.btrt_box.iter().map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(std::iter::once(&self.vpcc_box).map(as_box_object)) + .chain(self.pasp_box.iter().map(as_box_object)) + .chain(self.btrt_box.iter().map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -2747,10 +2657,6 @@ impl BaseBox for Vp09Box { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn is_unknown_box(&self) -> bool { false } @@ -2758,10 +2664,10 @@ impl BaseBox for Vp09Box { fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(std::iter::once(&self.vpcc_box).map(BaseBox::actual_box)) - .chain(self.pasp_box.iter().map(BaseBox::actual_box)) - .chain(self.btrt_box.iter().map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(std::iter::once(&self.vpcc_box).map(as_box_object)) + .chain(self.pasp_box.iter().map(as_box_object)) + .chain(self.btrt_box.iter().map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -2860,10 +2766,6 @@ impl BaseBox for VpccBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn is_unknown_box(&self) -> bool { false } @@ -2936,10 +2838,6 @@ impl BaseBox for PaspBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3001,10 +2899,6 @@ impl BaseBox for BtrtBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3078,10 +2972,6 @@ impl BaseBox for SttsBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3168,10 +3058,6 @@ impl BaseBox for StscBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3271,10 +3157,6 @@ impl BaseBox for StszBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3348,10 +3230,6 @@ impl BaseBox for StcoBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3425,10 +3303,6 @@ impl BaseBox for Co64Box { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3502,10 +3376,6 @@ impl BaseBox for StssBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3603,16 +3473,12 @@ impl BaseBox for OpusBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() - .chain(std::iter::once(&self.dops_box).map(BaseBox::actual_box)) - .chain(self.btrt_box.iter().map(BaseBox::actual_box)) - .chain(self.unknown_boxes.iter().map(BaseBox::actual_box)), + .chain(std::iter::once(&self.dops_box).map(as_box_object)) + .chain(self.btrt_box.iter().map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), ) } } @@ -3755,10 +3621,6 @@ impl BaseBox for DopsBox { false } - fn actual_box(&self) -> &dyn BaseBox { - self - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } From aed49335afb698bf2c632191382a93e35ca72da7 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 12:54:26 +0900 Subject: [PATCH 076/103] Make BaseBox::is_unknown_box() optional --- src/basic_types.rs | 4 +- src/boxes.rs | 140 --------------------------------------------- 2 files changed, 3 insertions(+), 141 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index da2a149..ecd22f7 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -19,7 +19,9 @@ pub trait BaseBox { fn box_payload_size(&self) -> u64; - fn is_unknown_box(&self) -> bool; + fn is_unknown_box(&self) -> bool { + false + } fn children<'a>(&'a self) -> Box>; } diff --git a/src/boxes.rs b/src/boxes.rs index e8462f3..9e2c0e0 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -174,10 +174,6 @@ impl BaseBox for FtypBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -285,10 +281,6 @@ impl BaseBox for FreeBox { self.payload.len() as u64 } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -344,10 +336,6 @@ impl BaseBox for MdatBox { self.payload.len() as u64 } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -428,10 +416,6 @@ impl BaseBox for MoovBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -548,10 +532,6 @@ impl BaseBox for MvhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -657,10 +637,6 @@ impl BaseBox for TrakBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -805,10 +781,6 @@ impl BaseBox for TkhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -902,10 +874,6 @@ impl BaseBox for EdtsBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -1001,10 +969,6 @@ impl BaseBox for ElstBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1108,10 +1072,6 @@ impl BaseBox for MdiaBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -1230,10 +1190,6 @@ impl BaseBox for MdhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1313,10 +1269,6 @@ impl BaseBox for HdlrBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1423,10 +1375,6 @@ impl BaseBox for MinfBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -1487,10 +1435,6 @@ impl BaseBox for SmhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1559,10 +1503,6 @@ impl BaseBox for VmhdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1643,10 +1583,6 @@ impl BaseBox for DinfBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -1736,10 +1672,6 @@ impl BaseBox for DrefBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -1812,10 +1744,6 @@ impl BaseBox for UrlBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -1947,10 +1875,6 @@ impl BaseBox for StblBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -2020,10 +1944,6 @@ impl BaseBox for StsdBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(self.entries.iter().map(as_box_object)) } @@ -2266,10 +2186,6 @@ impl BaseBox for Avc1Box { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -2459,10 +2375,6 @@ impl BaseBox for AvccBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -2555,10 +2467,6 @@ impl BaseBox for Vp08Box { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -2657,10 +2565,6 @@ impl BaseBox for Vp09Box { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -2766,10 +2670,6 @@ impl BaseBox for VpccBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -2834,10 +2734,6 @@ impl BaseBox for PaspBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -2895,10 +2791,6 @@ impl BaseBox for BtrtBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -2968,10 +2860,6 @@ impl BaseBox for SttsBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3054,10 +2942,6 @@ impl BaseBox for StscBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3153,10 +3037,6 @@ impl BaseBox for StszBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3226,10 +3106,6 @@ impl BaseBox for StcoBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3299,10 +3175,6 @@ impl BaseBox for Co64Box { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3372,10 +3244,6 @@ impl BaseBox for StssBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } @@ -3469,10 +3337,6 @@ impl BaseBox for OpusBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new( std::iter::empty() @@ -3617,10 +3481,6 @@ impl BaseBox for DopsBox { ExternalBytes::calc(|writer| self.encode_payload(writer)) } - fn is_unknown_box(&self) -> bool { - false - } - fn children<'a>(&'a self) -> Box> { Box::new(std::iter::empty()) } From 3676e33cdd1a6635bfa970ddd856bd5db7a803d5 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 13:00:36 +0900 Subject: [PATCH 077/103] =?UTF-8?q?av1=20=E7=94=A8=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/decode_encode_test.rs | 27 +++++++++++++++++++++++++++ tests/testdata/black-av1-video.mp4 | Bin 0 -> 1525 bytes 2 files changed, 27 insertions(+) create mode 100644 tests/testdata/black-av1-video.mp4 diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index e79a461..cab3899 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -49,6 +49,33 @@ fn decode_encode_black_vp9_video_mp4() -> Result<()> { Ok(()) } +#[test] +fn decode_encode_black_av1_video_mp4() -> Result<()> { + let input_bytes = include_bytes!("testdata/black-av1-video.mp4"); + let file: Mp4File = Mp4File::decode(&mut &input_bytes[..])?; + + // デコード時に未処理のボックスがないことを確認する。 + assert_eq!(collect_unknown_box_types(&file), Vec::new()); + + let mut output_bytes = Vec::new(); + file.encode(&mut output_bytes)?; + + // エンコード結果をデコードしたら同じ MP4 になっていることを確認する。 + let encoded_file: Mp4File = Mp4File::decode(&mut &output_bytes[..])?; + assert_eq!(file, encoded_file); + + // エンコード結果のバイト列が正しいことを確認する。 + assert_eq!(input_bytes.len(), output_bytes.len()); + + // // ボックスの順番は入れ替わるのでソートした結果を比較する + // let mut input_bytes = input_bytes.to_vec(); + // input_bytes.sort(); + // output_bytes.sort(); + assert_eq!(&input_bytes[..], output_bytes); + + Ok(()) +} + #[test] fn decode_encode_beep_opus_audio_mp4() -> Result<()> { let input_bytes = include_bytes!("testdata/beep-opus-audio.mp4"); diff --git a/tests/testdata/black-av1-video.mp4 b/tests/testdata/black-av1-video.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..fa2619a818452f3c4c1a86e7e363b058886b0754 GIT binary patch literal 1525 zcma)+Ur1AN6vxm0q|3CX&NXG~%qdc8cQ=(D`Xd-+(2G!k;X`-38xHSvYrBq=iun?S zgb@|=Algf&X_^EDy(lRCBe0~9UbHCir6e#4itPKlPVb%g;9+OKb3fYg8`EF$ae zrp1Y`?muPWWA`g>Tq_jze*C?)?a1HDV|O3L`Fzea9w$25Jx_EU73og#;D{`~f0ykV z>mTCnoY`O|N?CKi87Nu2IL+s9DMm|N$7jzK?}_s^&SK86>hOny{e7Tmmdw6dEy;9O zU7XM6(yeyxibh|J%m8k8<&D49;8f>lKr8h&SJr!lXiSqO1brGYD>8ZtCP*`GNyhh*U}F)z5sNa)0e6>_~6@9z*0-*%|klqodqnj z+N*YGaBy-z;BHsmWUdZ&jRKZa@5y2v91a6kCRZP9WRaX&eXZre17RZ51vwOoVHT1c zYZfu;#Iyka0vU+?G)Z~@UiVz9MQ&snaft6J5upXnCZ$EKY0?EIva#ChWL=$&>Kfx{ zXI_2J?H83OET>EgRKD=wwSCY)wk^ySA0$};%6~Tm_NNOBDq#g{ z!F&urRvF?|`#gLds%Pbo@oH!x)B)WIwI?(O<7U*gi8%D>3n7jP>xqMU+eAgcct-ZC uBTOpNp9Gwqh!765&03R+;L33;8BjtjVLq;FmV_lGY)JrH;;F%waQ^}9d>I-5 literal 0 HcmV?d00001 From 46a5a1f2714cd3ccc54524f36853243cb6bb7062 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 13:53:12 +0900 Subject: [PATCH 078/103] Add av1C box --- src/boxes.rs | 245 ++++++++++++++++++++++++++++++++++-- tests/decode_encode_test.rs | 8 +- 2 files changed, 241 insertions(+), 12 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 9e2c0e0..7a03715 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1962,9 +1962,10 @@ impl FullBox for StsdBox { #[derive(Debug, Clone, PartialEq, Eq)] pub enum SampleEntry { Avc1(Avc1Box), - Opus(OpusBox), Vp08(Vp08Box), Vp09(Vp09Box), + Av01(Av01Box), + Opus(OpusBox), Unknown(UnknownBox), } @@ -1972,9 +1973,10 @@ impl SampleEntry { fn inner_box(&self) -> &dyn BaseBox { match self { Self::Avc1(b) => b, - Self::Opus(b) => b, Self::Vp08(b) => b, Self::Vp09(b) => b, + Self::Av01(b) => b, + Self::Opus(b) => b, Self::Unknown(b) => b, } } @@ -1984,9 +1986,10 @@ impl Encode for SampleEntry { fn encode(&self, writer: &mut W) -> Result<()> { match self { Self::Avc1(b) => b.encode(writer), - Self::Opus(b) => b.encode(writer), Self::Vp08(b) => b.encode(writer), Self::Vp09(b) => b.encode(writer), + Self::Av01(b) => b.encode(writer), + Self::Opus(b) => b.encode(writer), Self::Unknown(b) => b.encode(writer), } } @@ -1997,9 +2000,10 @@ impl Decode for SampleEntry { let (header, mut reader) = BoxHeader::peek(reader)?; match header.box_type { Avc1Box::TYPE => Decode::decode(&mut reader).map(Self::Avc1), - OpusBox::TYPE => Decode::decode(&mut reader).map(Self::Opus), Vp08Box::TYPE => Decode::decode(&mut reader).map(Self::Vp08), Vp09Box::TYPE => Decode::decode(&mut reader).map(Self::Vp09), + Av01Box::TYPE => Decode::decode(&mut reader).map(Self::Av01), + OpusBox::TYPE => Decode::decode(&mut reader).map(Self::Opus), _ => Decode::decode(&mut reader).map(Self::Unknown), } } @@ -2685,6 +2689,227 @@ impl FullBox for VpccBox { } } +/// [] AV1SampleEntry class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Av01Box { + pub visual: VisualSampleEntryFields, + pub av1c_box: Av1cBox, + pub pasp_box: Option, + pub btrt_box: Option, + pub unknown_boxes: Vec, +} + +impl Av01Box { + pub const TYPE: BoxType = BoxType::Normal(*b"av01"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.visual.encode(writer)?; + self.av1c_box.encode(writer)?; + if let Some(b) = &self.pasp_box { + b.encode(writer)?; + } + if let Some(b) = &self.btrt_box { + b.encode(writer)?; + } + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let visual = VisualSampleEntryFields::decode(reader)?; + let mut av1c_box = None; + let mut pasp_box = None; + let mut btrt_box = None; + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + Av1cBox::TYPE if av1c_box.is_none() => { + av1c_box = Some(Av1cBox::decode(&mut reader)?); + } + PaspBox::TYPE if pasp_box.is_none() => { + pasp_box = Some(PaspBox::decode(&mut reader)?); + } + BtrtBox::TYPE if btrt_box.is_none() => { + btrt_box = Some(BtrtBox::decode(&mut reader)?); + } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + let av1c_box = av1c_box.ok_or_else(|| Error::missing_box("av1c", Self::TYPE))?; + Ok(Self { + visual, + av1c_box, + pasp_box, + btrt_box, + unknown_boxes, + }) + } +} + +impl Encode for Av01Box { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for Av01Box { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for Av01Box { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(std::iter::once(&self.av1c_box).map(as_box_object)) + .chain(self.pasp_box.iter().map(as_box_object)) + .chain(self.btrt_box.iter().map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), + ) + } +} + +/// [] AV1CodecConfigurationBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Av1cBox { + pub seq_profile: Uint<3>, + pub seq_level_idx_0: Uint<5>, + pub seq_tier_0: Uint<1>, + pub high_bitdepth: Uint<1>, + pub twelve_bit: Uint<1>, + pub monochrome: Uint<1>, + pub chroma_subsampling_x: Uint<1>, + pub chroma_subsampling_y: Uint<1>, + pub chroma_sample_position: Uint<2>, + pub initial_presentation_delay_minus_one: Option>, + pub config_obus: Vec, +} + +impl Av1cBox { + pub const TYPE: BoxType = BoxType::Normal(*b"av1C"); + + const MARKER: u8 = 1; + const VERSION: u8 = 1; + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + ((Self::MARKER << 7) | Self::VERSION).encode(writer)?; + ((self.seq_profile.get() << 5) | self.seq_level_idx_0.get()).encode(writer)?; + ((self.seq_tier_0.get() << 7) + | (self.high_bitdepth.get() << 6) + | (self.twelve_bit.get() << 5) + | (self.monochrome.get() << 4) + | (self.chroma_subsampling_x.get() << 3) + | (self.chroma_subsampling_y.get() << 2) + | self.chroma_sample_position.get()) + .encode(writer)?; + if let Some(v) = self.initial_presentation_delay_minus_one { + (0b1_0000 | v.get()).encode(writer)?; + } else { + 0u8.encode(writer)?; + } + writer.write_all(&self.config_obus)?; + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let b = u8::decode(reader)?; + if (b >> 7) != Self::MARKER { + return Err(Error::invalid_data("Unexpected av1C marker")); + } + if (b & 0b0111_1111) != Self::VERSION { + return Err(Error::invalid_data(&format!( + "Unsupported av1C version: {}", + b & 0b0111_1111 + ))); + } + + let b = u8::decode(reader)?; + let seq_profile = Uint::new(b >> 5); + let seq_level_idx_0 = Uint::new(b); + + let b = u8::decode(reader)?; + let seq_tier_0 = Uint::new(b >> 7); + let high_bitdepth = Uint::new(b >> 6); + let twelve_bit = Uint::new(b >> 5); + let monochrome = Uint::new(b >> 4); + let chroma_subsampling_x = Uint::new(b >> 3); + let chroma_subsampling_y = Uint::new(b >> 2); + let chroma_sample_position = Uint::new(b); + + let b = u8::decode(reader)?; + let initial_presentation_delay_minus_one = if Uint::<1>::new(b >> 4).get() == 1 { + Some(Uint::new(b)) + } else { + None + }; + + let mut config_obus = Vec::new(); + reader.read_to_end(&mut config_obus)?; + + Ok(Self { + seq_profile, + seq_level_idx_0, + seq_tier_0, + high_bitdepth, + twelve_bit, + monochrome, + chroma_subsampling_x, + chroma_subsampling_y, + chroma_sample_position, + initial_presentation_delay_minus_one, + config_obus, + }) + } +} + +impl Encode for Av1cBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for Av1cBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for Av1cBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } +} + /// [ISO/IEC 14496-12] PixelAspectRatioBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct PaspBox { @@ -3402,7 +3627,6 @@ impl Decode for AudioSampleEntryFields { /// [] OpusSpecificBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct DopsBox { - pub version: u8, pub output_channel_count: u8, pub pre_skip: u16, pub input_sample_rate: u32, @@ -3411,9 +3635,10 @@ pub struct DopsBox { impl DopsBox { pub const TYPE: BoxType = BoxType::Normal(*b"dOps"); + const VERSION: u8 = 0; fn encode_payload(&self, writer: &mut W) -> Result<()> { - self.version.encode(writer)?; + Self::VERSION.encode(writer)?; self.output_channel_count.encode(writer)?; self.pre_skip.encode(writer)?; self.input_sample_rate.encode(writer)?; @@ -3424,6 +3649,12 @@ impl DopsBox { fn decode_payload(reader: &mut std::io::Take) -> Result { let version = u8::decode(reader)?; + if version != Self::VERSION { + return Err(Error::invalid_data(&format!( + "Unsupported dOps version: {version}" + ))); + } + let output_channel_count = u8::decode(reader)?; let pre_skip = u16::decode(reader)?; let input_sample_rate = u32::decode(reader)?; @@ -3435,7 +3666,6 @@ impl DopsBox { )); } Ok(Self { - version, output_channel_count, pre_skip, input_sample_rate, @@ -3447,7 +3677,6 @@ impl DopsBox { impl Default for DopsBox { fn default() -> Self { Self { - version: 0, output_channel_count: 1, pre_skip: 0, input_sample_rate: 0, diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index cab3899..a069f79 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -67,10 +67,10 @@ fn decode_encode_black_av1_video_mp4() -> Result<()> { // エンコード結果のバイト列が正しいことを確認する。 assert_eq!(input_bytes.len(), output_bytes.len()); - // // ボックスの順番は入れ替わるのでソートした結果を比較する - // let mut input_bytes = input_bytes.to_vec(); - // input_bytes.sort(); - // output_bytes.sort(); + // ボックスの順番は入れ替わるのでソートした結果を比較する + let mut input_bytes = input_bytes.to_vec(); + input_bytes.sort(); + output_bytes.sort(); assert_eq!(&input_bytes[..], output_bytes); Ok(()) From 6dc1b79eb8769ffd9bfc6d17584d7bfb3970a99d Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 13:58:32 +0900 Subject: [PATCH 079/103] =?UTF-8?q?pasp=20=E3=81=A8=20btrt=20=E3=82=82=20u?= =?UTF-8?q?nknown=20=E6=89=B1=E3=81=84=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/boxes.rs | 201 ------------------------------------ tests/decode_encode_test.rs | 2 +- 2 files changed, 1 insertion(+), 202 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 7a03715..83116cb 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2108,8 +2108,6 @@ impl Decode for VisualSampleEntryFields { pub struct Avc1Box { pub visual: VisualSampleEntryFields, pub avcc_box: AvccBox, - pub pasp_box: Option, - pub btrt_box: Option, pub unknown_boxes: Vec, } @@ -2119,12 +2117,6 @@ impl Avc1Box { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.visual.encode(writer)?; self.avcc_box.encode(writer)?; - if let Some(b) = &self.pasp_box { - b.encode(writer)?; - } - if let Some(b) = &self.btrt_box { - b.encode(writer)?; - } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -2134,8 +2126,6 @@ impl Avc1Box { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let visual = VisualSampleEntryFields::decode(reader)?; let mut avcc_box = None; - let mut pasp_box = None; - let mut btrt_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -2143,12 +2133,6 @@ impl Avc1Box { AvccBox::TYPE if avcc_box.is_none() => { avcc_box = Some(AvccBox::decode(&mut reader)?); } - PaspBox::TYPE if pasp_box.is_none() => { - pasp_box = Some(PaspBox::decode(&mut reader)?); - } - BtrtBox::TYPE if btrt_box.is_none() => { - btrt_box = Some(BtrtBox::decode(&mut reader)?); - } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -2158,8 +2142,6 @@ impl Avc1Box { Ok(Self { visual, avcc_box, - pasp_box, - btrt_box, unknown_boxes, }) } @@ -2194,8 +2176,6 @@ impl BaseBox for Avc1Box { Box::new( std::iter::empty() .chain(std::iter::once(&self.avcc_box).map(as_box_object)) - .chain(self.pasp_box.iter().map(as_box_object)) - .chain(self.btrt_box.iter().map(as_box_object)) .chain(self.unknown_boxes.iter().map(as_box_object)), ) } @@ -2389,8 +2369,6 @@ impl BaseBox for AvccBox { pub struct Vp08Box { pub visual: VisualSampleEntryFields, pub vpcc_box: VpccBox, - pub pasp_box: Option, - pub btrt_box: Option, pub unknown_boxes: Vec, } @@ -2400,12 +2378,6 @@ impl Vp08Box { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.visual.encode(writer)?; self.vpcc_box.encode(writer)?; - if let Some(b) = &self.pasp_box { - b.encode(writer)?; - } - if let Some(b) = &self.btrt_box { - b.encode(writer)?; - } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -2415,8 +2387,6 @@ impl Vp08Box { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let visual = VisualSampleEntryFields::decode(reader)?; let mut vpcc_box = None; - let mut pasp_box = None; - let mut btrt_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -2424,12 +2394,6 @@ impl Vp08Box { VpccBox::TYPE if vpcc_box.is_none() => { vpcc_box = Some(VpccBox::decode(&mut reader)?); } - PaspBox::TYPE if pasp_box.is_none() => { - pasp_box = Some(PaspBox::decode(&mut reader)?); - } - BtrtBox::TYPE if btrt_box.is_none() => { - btrt_box = Some(BtrtBox::decode(&mut reader)?); - } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -2439,8 +2403,6 @@ impl Vp08Box { Ok(Self { visual, vpcc_box, - pasp_box, - btrt_box, unknown_boxes, }) } @@ -2475,8 +2437,6 @@ impl BaseBox for Vp08Box { Box::new( std::iter::empty() .chain(std::iter::once(&self.vpcc_box).map(as_box_object)) - .chain(self.pasp_box.iter().map(as_box_object)) - .chain(self.btrt_box.iter().map(as_box_object)) .chain(self.unknown_boxes.iter().map(as_box_object)), ) } @@ -2487,8 +2447,6 @@ impl BaseBox for Vp08Box { pub struct Vp09Box { pub visual: VisualSampleEntryFields, pub vpcc_box: VpccBox, - pub pasp_box: Option, - pub btrt_box: Option, pub unknown_boxes: Vec, } @@ -2498,12 +2456,6 @@ impl Vp09Box { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.visual.encode(writer)?; self.vpcc_box.encode(writer)?; - if let Some(b) = &self.pasp_box { - b.encode(writer)?; - } - if let Some(b) = &self.btrt_box { - b.encode(writer)?; - } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -2513,8 +2465,6 @@ impl Vp09Box { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let visual = VisualSampleEntryFields::decode(reader)?; let mut vpcc_box = None; - let mut pasp_box = None; - let mut btrt_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -2522,12 +2472,6 @@ impl Vp09Box { VpccBox::TYPE if vpcc_box.is_none() => { vpcc_box = Some(VpccBox::decode(&mut reader)?); } - PaspBox::TYPE if pasp_box.is_none() => { - pasp_box = Some(PaspBox::decode(&mut reader)?); - } - BtrtBox::TYPE if btrt_box.is_none() => { - btrt_box = Some(BtrtBox::decode(&mut reader)?); - } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -2537,8 +2481,6 @@ impl Vp09Box { Ok(Self { visual, vpcc_box, - pasp_box, - btrt_box, unknown_boxes, }) } @@ -2573,8 +2515,6 @@ impl BaseBox for Vp09Box { Box::new( std::iter::empty() .chain(std::iter::once(&self.vpcc_box).map(as_box_object)) - .chain(self.pasp_box.iter().map(as_box_object)) - .chain(self.btrt_box.iter().map(as_box_object)) .chain(self.unknown_boxes.iter().map(as_box_object)), ) } @@ -2694,8 +2634,6 @@ impl FullBox for VpccBox { pub struct Av01Box { pub visual: VisualSampleEntryFields, pub av1c_box: Av1cBox, - pub pasp_box: Option, - pub btrt_box: Option, pub unknown_boxes: Vec, } @@ -2705,12 +2643,6 @@ impl Av01Box { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.visual.encode(writer)?; self.av1c_box.encode(writer)?; - if let Some(b) = &self.pasp_box { - b.encode(writer)?; - } - if let Some(b) = &self.btrt_box { - b.encode(writer)?; - } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -2720,8 +2652,6 @@ impl Av01Box { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let visual = VisualSampleEntryFields::decode(reader)?; let mut av1c_box = None; - let mut pasp_box = None; - let mut btrt_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -2729,12 +2659,6 @@ impl Av01Box { Av1cBox::TYPE if av1c_box.is_none() => { av1c_box = Some(Av1cBox::decode(&mut reader)?); } - PaspBox::TYPE if pasp_box.is_none() => { - pasp_box = Some(PaspBox::decode(&mut reader)?); - } - BtrtBox::TYPE if btrt_box.is_none() => { - btrt_box = Some(BtrtBox::decode(&mut reader)?); - } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -2744,8 +2668,6 @@ impl Av01Box { Ok(Self { visual, av1c_box, - pasp_box, - btrt_box, unknown_boxes, }) } @@ -2780,8 +2702,6 @@ impl BaseBox for Av01Box { Box::new( std::iter::empty() .chain(std::iter::once(&self.av1c_box).map(as_box_object)) - .chain(self.pasp_box.iter().map(as_box_object)) - .chain(self.btrt_box.iter().map(as_box_object)) .chain(self.unknown_boxes.iter().map(as_box_object)), ) } @@ -2910,117 +2830,6 @@ impl BaseBox for Av1cBox { } } -/// [ISO/IEC 14496-12] PixelAspectRatioBox class -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PaspBox { - pub h_spacing: u32, - pub v_spacing: u32, -} - -impl PaspBox { - pub const TYPE: BoxType = BoxType::Normal(*b"pasp"); - - fn encode_payload(&self, writer: &mut W) -> Result<()> { - self.h_spacing.encode(writer)?; - self.v_spacing.encode(writer)?; - Ok(()) - } - - fn decode_payload(reader: &mut std::io::Take) -> Result { - Ok(Self { - h_spacing: u32::decode(reader)?, - v_spacing: u32::decode(reader)?, - }) - } -} - -impl Encode for PaspBox { - fn encode(&self, writer: &mut W) -> Result<()> { - BoxHeader::from_box(self).encode(writer)?; - self.encode_payload(writer)?; - Ok(()) - } -} - -impl Decode for PaspBox { - fn decode(reader: &mut R) -> Result { - let header = BoxHeader::decode(reader)?; - header.box_type.expect(Self::TYPE)?; - header.with_box_payload_reader(reader, Self::decode_payload) - } -} - -impl BaseBox for PaspBox { - fn box_type(&self) -> BoxType { - Self::TYPE - } - - fn box_payload_size(&self) -> u64 { - ExternalBytes::calc(|writer| self.encode_payload(writer)) - } - - fn children<'a>(&'a self) -> Box> { - Box::new(std::iter::empty()) - } -} - -/// [ISO/IEC 14496-12] BitRateBox class -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BtrtBox { - pub buffer_size_db: u32, - pub max_bitrate: u32, - pub avg_bitrate: u32, -} - -impl BtrtBox { - pub const TYPE: BoxType = BoxType::Normal(*b"btrt"); - - fn encode_payload(&self, writer: &mut W) -> Result<()> { - self.buffer_size_db.encode(writer)?; - self.max_bitrate.encode(writer)?; - self.avg_bitrate.encode(writer)?; - Ok(()) - } - - fn decode_payload(reader: &mut std::io::Take) -> Result { - Ok(Self { - buffer_size_db: u32::decode(reader)?, - max_bitrate: u32::decode(reader)?, - avg_bitrate: u32::decode(reader)?, - }) - } -} - -impl Encode for BtrtBox { - fn encode(&self, writer: &mut W) -> Result<()> { - BoxHeader::from_box(self).encode(writer)?; - self.encode_payload(writer)?; - Ok(()) - } -} - -impl Decode for BtrtBox { - fn decode(reader: &mut R) -> Result { - let header = BoxHeader::decode(reader)?; - header.box_type.expect(Self::TYPE)?; - header.with_box_payload_reader(reader, Self::decode_payload) - } -} - -impl BaseBox for BtrtBox { - fn box_type(&self) -> BoxType { - Self::TYPE - } - - fn box_payload_size(&self) -> u64 { - ExternalBytes::calc(|writer| self.encode_payload(writer)) - } - - fn children<'a>(&'a self) -> Box> { - Box::new(std::iter::empty()) - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct SttsEntry { pub sample_count: u32, @@ -3489,7 +3298,6 @@ impl FullBox for StssBox { pub struct OpusBox { pub audio: AudioSampleEntryFields, pub dops_box: DopsBox, - pub btrt_box: Option, pub unknown_boxes: Vec, } @@ -3499,9 +3307,6 @@ impl OpusBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { self.audio.encode(writer)?; self.dops_box.encode(writer)?; - if let Some(b) = &self.btrt_box { - b.encode(writer)?; - } for b in &self.unknown_boxes { b.encode(writer)?; } @@ -3511,7 +3316,6 @@ impl OpusBox { fn decode_payload(mut reader: &mut std::io::Take) -> Result { let audio = AudioSampleEntryFields::decode(reader)?; let mut dops_box = None; - let mut btrt_box = None; let mut unknown_boxes = Vec::new(); while reader.limit() > 0 { let (header, mut reader) = BoxHeader::peek(&mut reader)?; @@ -3519,9 +3323,6 @@ impl OpusBox { DopsBox::TYPE if dops_box.is_none() => { dops_box = Some(DopsBox::decode(&mut reader)?); } - BtrtBox::TYPE if btrt_box.is_none() => { - btrt_box = Some(BtrtBox::decode(&mut reader)?); - } _ => { unknown_boxes.push(UnknownBox::decode(&mut reader)?); } @@ -3531,7 +3332,6 @@ impl OpusBox { Ok(Self { audio, dops_box, - btrt_box, unknown_boxes, }) } @@ -3566,7 +3366,6 @@ impl BaseBox for OpusBox { Box::new( std::iter::empty() .chain(std::iter::once(&self.dops_box).map(as_box_object)) - .chain(self.btrt_box.iter().map(as_box_object)) .chain(self.unknown_boxes.iter().map(as_box_object)), ) } diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index a069f79..2f0486e 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -106,7 +106,7 @@ fn collect_unknown_box_types(mp4: &Mp4File) -> Vec { if b.is_unknown_box() && !matches!( b.box_type().as_bytes(), - b"fiel" | b"sbgp" | b"sgpd" | b"udta" + b"btrt" | b"fiel" | b"pasp" | b"sbgp" | b"sgpd" | b"udta" ) { unknowns.push(b.box_type()); From fb8ebb89fac01150d44e96606d4482bb19c4bda4 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 14:07:44 +0900 Subject: [PATCH 080/103] Add OFFSET to Uint --- src/basic_types.rs | 16 +++----- src/boxes.rs | 100 ++++++++++++++++++++++----------------------- 2 files changed, 55 insertions(+), 61 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index ecd22f7..668afa9 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -493,19 +493,15 @@ impl BaseBox for Either { } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Uint(u8); +pub struct Uint(u8); -impl Uint { - pub const fn new(v: u8) -> Self { - Self(v & (1 << BITS) - 1) +impl Uint { + pub const fn from_u8(v: u8) -> Self { + Self((v >> OFFSET) & (1 << BITS) - 1) } - pub const fn checked_new(v: u8) -> Option { - if v.leading_zeros() < u8::BITS - BITS { - None - } else { - Some(Self(v)) - } + pub const fn to_u8(self) -> u8 { + self.0 << OFFSET } pub const fn get(self) -> u8 { diff --git a/src/boxes.rs b/src/boxes.rs index 83116cb..8a10297 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2207,11 +2207,9 @@ impl AvccBox { self.avc_level_indication.encode(writer)?; (0b111111_00 | self.length_size_minus_one.get()).encode(writer)?; - let sps_count = u8::try_from(self.sps_list.len()) - .ok() - .and_then(|n| Uint::<5>::checked_new(n)) - .ok_or_else(|| Error::invalid_input("Too many SPSs"))?; - (0b111_00000 | sps_count.get()).encode(writer)?; + let sps_count = + u8::try_from(self.sps_list.len()).map_err(|_| Error::invalid_input("Too many SPSs"))?; + (0b111_00000 | sps_count).encode(writer)?; for sps in &self.sps_list { let size = u16::try_from(sps.len()) .map_err(|e| Error::invalid_input(&format!("Too long SPS: {e}")))?; @@ -2262,9 +2260,9 @@ impl AvccBox { let avc_profile_indication = u8::decode(reader)?; let profile_compatibility = u8::decode(reader)?; let avc_level_indication = u8::decode(reader)?; - let length_size_minus_one = Uint::new(u8::decode(reader)?); + let length_size_minus_one = Uint::from_u8(u8::decode(reader)?); - let sps_count = Uint::<5>::new(u8::decode(reader)?).get() as usize; + let sps_count = Uint::<5>::from_u8(u8::decode(reader)?).get() as usize; let mut sps_list = Vec::with_capacity(sps_count); for _ in 0..sps_count { let size = u16::decode(reader)? as usize; @@ -2287,9 +2285,9 @@ impl AvccBox { let mut bit_depth_chroma_minus8 = None; let mut sps_ext_list = Vec::new(); if !matches!(avc_profile_indication, 66 | 77 | 88) { - chroma_format = Some(Uint::new(u8::decode(reader)?)); - bit_depth_luma_minus8 = Some(Uint::new(u8::decode(reader)?)); - bit_depth_chroma_minus8 = Some(Uint::new(u8::decode(reader)?)); + chroma_format = Some(Uint::from_u8(u8::decode(reader)?)); + bit_depth_luma_minus8 = Some(Uint::from_u8(u8::decode(reader)?)); + bit_depth_chroma_minus8 = Some(Uint::from_u8(u8::decode(reader)?)); let sps_ext_count = u8::decode(reader)? as usize; for _ in 0..sps_ext_count { @@ -2323,7 +2321,7 @@ impl Default for AvccBox { avc_profile_indication: 0, profile_compatibility: 0, avc_level_indication: 0, - length_size_minus_one: Uint::new(0), + length_size_minus_one: Uint::from_u8(0), sps_list: Vec::new(), pps_list: Vec::new(), chroma_format: None, @@ -2525,9 +2523,9 @@ impl BaseBox for Vp09Box { pub struct VpccBox { pub profile: u8, pub level: u8, - pub bit_depth: Uint<4>, - pub chroma_subsampling: Uint<3>, - pub video_full_range_flag: bool, + pub bit_depth: Uint<4, 4>, + pub chroma_subsampling: Uint<3, 1>, + pub video_full_range_flag: Uint<1>, pub colour_primaries: u8, pub transfer_characteristics: u8, pub matrix_coefficients: u8, @@ -2541,10 +2539,10 @@ impl VpccBox { FullBoxHeader::from_box(self).encode(writer)?; self.profile.encode(writer)?; self.level.encode(writer)?; - ((self.bit_depth.get() << 4) - | (self.chroma_subsampling.get() << 1) - | self.video_full_range_flag as u8) - .encode(writer)?; + (self.bit_depth.to_u8() + | self.chroma_subsampling.to_u8() + | self.video_full_range_flag.to_u8()) + .encode(writer)?; self.colour_primaries.encode(writer)?; self.transfer_characteristics.encode(writer)?; self.matrix_coefficients.encode(writer)?; @@ -2566,9 +2564,9 @@ impl VpccBox { let level = u8::decode(reader)?; let b = u8::decode(reader)?; - let bit_depth = Uint::new(b >> 4); - let chroma_subsampling = Uint::new(b >> 1); - let video_full_range_flag = (b & 1) != 0; + let bit_depth = Uint::from_u8(b); + let chroma_subsampling = Uint::from_u8(b); + let video_full_range_flag = Uint::from_u8(b); let colour_primaries = u8::decode(reader)?; let transfer_characteristics = u8::decode(reader)?; let matrix_coefficients = u8::decode(reader)?; @@ -2710,16 +2708,16 @@ impl BaseBox for Av01Box { /// [] AV1CodecConfigurationBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct Av1cBox { - pub seq_profile: Uint<3>, - pub seq_level_idx_0: Uint<5>, - pub seq_tier_0: Uint<1>, - pub high_bitdepth: Uint<1>, - pub twelve_bit: Uint<1>, - pub monochrome: Uint<1>, - pub chroma_subsampling_x: Uint<1>, - pub chroma_subsampling_y: Uint<1>, - pub chroma_sample_position: Uint<2>, - pub initial_presentation_delay_minus_one: Option>, + pub seq_profile: Uint<3, 5>, + pub seq_level_idx_0: Uint<5, 0>, + pub seq_tier_0: Uint<1, 7>, + pub high_bitdepth: Uint<1, 6>, + pub twelve_bit: Uint<1, 5>, + pub monochrome: Uint<1, 4>, + pub chroma_subsampling_x: Uint<1, 3>, + pub chroma_subsampling_y: Uint<1, 2>, + pub chroma_sample_position: Uint<2, 0>, + pub initial_presentation_delay_minus_one: Option>, pub config_obus: Vec, } @@ -2731,17 +2729,17 @@ impl Av1cBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { ((Self::MARKER << 7) | Self::VERSION).encode(writer)?; - ((self.seq_profile.get() << 5) | self.seq_level_idx_0.get()).encode(writer)?; - ((self.seq_tier_0.get() << 7) - | (self.high_bitdepth.get() << 6) - | (self.twelve_bit.get() << 5) - | (self.monochrome.get() << 4) - | (self.chroma_subsampling_x.get() << 3) - | (self.chroma_subsampling_y.get() << 2) - | self.chroma_sample_position.get()) + (self.seq_profile.to_u8() | self.seq_level_idx_0.to_u8()).encode(writer)?; + (self.seq_tier_0.to_u8() + | self.high_bitdepth.to_u8() + | self.twelve_bit.to_u8() + | self.monochrome.to_u8() + | self.chroma_subsampling_x.to_u8() + | self.chroma_subsampling_y.to_u8() + | self.chroma_sample_position.to_u8()) .encode(writer)?; if let Some(v) = self.initial_presentation_delay_minus_one { - (0b1_0000 | v.get()).encode(writer)?; + (0b1_0000 | v.to_u8()).encode(writer)?; } else { 0u8.encode(writer)?; } @@ -2762,21 +2760,21 @@ impl Av1cBox { } let b = u8::decode(reader)?; - let seq_profile = Uint::new(b >> 5); - let seq_level_idx_0 = Uint::new(b); + let seq_profile = Uint::from_u8(b); + let seq_level_idx_0 = Uint::from_u8(b); let b = u8::decode(reader)?; - let seq_tier_0 = Uint::new(b >> 7); - let high_bitdepth = Uint::new(b >> 6); - let twelve_bit = Uint::new(b >> 5); - let monochrome = Uint::new(b >> 4); - let chroma_subsampling_x = Uint::new(b >> 3); - let chroma_subsampling_y = Uint::new(b >> 2); - let chroma_sample_position = Uint::new(b); + let seq_tier_0 = Uint::from_u8(b); + let high_bitdepth = Uint::from_u8(b); + let twelve_bit = Uint::from_u8(b); + let monochrome = Uint::from_u8(b); + let chroma_subsampling_x = Uint::from_u8(b); + let chroma_subsampling_y = Uint::from_u8(b); + let chroma_sample_position = Uint::from_u8(b); let b = u8::decode(reader)?; - let initial_presentation_delay_minus_one = if Uint::<1>::new(b >> 4).get() == 1 { - Some(Uint::new(b)) + let initial_presentation_delay_minus_one = if Uint::<1, 4>::from_u8(b).get() == 1 { + Some(Uint::from_u8(b)) } else { None }; From 1813669b02dff771eb8bb2d730d9c7679abd8af5 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 14:08:39 +0900 Subject: [PATCH 081/103] =?UTF-8?q?h265=20=E7=94=A8=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/decode_encode_test.rs | 22 ++++++++++++++++++++++ tests/testdata/black-h265-video.mp4 | Bin 0 -> 4786 bytes 2 files changed, 22 insertions(+) create mode 100644 tests/testdata/black-h265-video.mp4 diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index 2f0486e..a30a018 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -22,6 +22,28 @@ fn decode_encode_black_h264_video_mp4() -> Result<()> { Ok(()) } +#[test] +fn decode_encode_black_h265_video_mp4() -> Result<()> { + let input_bytes = include_bytes!("testdata/black-h265-video.mp4"); + let file: Mp4File = Mp4File::decode(&mut &input_bytes[..])?; + + // デコード時に未処理のボックスがないことを確認する。 + assert_eq!(collect_unknown_box_types(&file), Vec::new()); + + let mut output_bytes = Vec::new(); + file.encode(&mut output_bytes)?; + + // エンコード結果をデコードしたら同じ MP4 になっていることを確認する。 + let encoded_file: Mp4File = Mp4File::decode(&mut &output_bytes[..])?; + assert_eq!(file, encoded_file); + + // エンコード結果のバイト列が正しいことを確認する。 + assert_eq!(input_bytes.len(), output_bytes.len()); + assert_eq!(&input_bytes[..], output_bytes); + + Ok(()) +} + #[test] fn decode_encode_black_vp9_video_mp4() -> Result<()> { let input_bytes = include_bytes!("testdata/black-vp9-video.mp4"); diff --git a/tests/testdata/black-h265-video.mp4 b/tests/testdata/black-h265-video.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..06b6416665d88c8a0656cd76bb31c8436e2b5d1e GIT binary patch literal 4786 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 Date: Tue, 24 Sep 2024 14:09:33 +0900 Subject: [PATCH 082/103] =?UTF-8?q?ctts=20=E3=81=A8=20sdtp=20=E3=81=AF=20B?= =?UTF-8?q?=20=E3=83=95=E3=83=AC=E3=83=BC=E3=83=A0=E3=82=92=E6=89=B1?= =?UTF-8?q?=E3=81=86=E3=82=88=E3=81=86=E3=81=AB=E3=81=AA=E3=82=8B=E3=81=BE?= =?UTF-8?q?=E3=81=A7=E3=81=AF=20unknown=20=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/decode_encode_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index a30a018..9f68032 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -128,7 +128,7 @@ fn collect_unknown_box_types(mp4: &Mp4File) -> Vec { if b.is_unknown_box() && !matches!( b.box_type().as_bytes(), - b"btrt" | b"fiel" | b"pasp" | b"sbgp" | b"sgpd" | b"udta" + b"btrt" | b"ctts" | b"fiel" | b"pasp" | b"sbgp" | b"sdtp" | b"sgpd" | b"udta" ) { unknowns.push(b.box_type()); From 1698e56aaad382e25ea73159f183decf36eddde5 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 14:57:52 +0900 Subject: [PATCH 083/103] =?UTF-8?q?hvcC=20=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 27 ++- src/boxes.rs | 379 +++++++++++++++++++++++++++++++----- tests/decode_encode_test.rs | 5 + 3 files changed, 349 insertions(+), 62 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 668afa9..7831a37 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -1,5 +1,6 @@ use std::{ io::{Read, Write}, + ops::{BitAnd, Shl, Shr, Sub}, time::Duration, }; @@ -493,18 +494,26 @@ impl BaseBox for Either { } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Uint(u8); - -impl Uint { - pub const fn from_u8(v: u8) -> Self { - Self((v >> OFFSET) & (1 << BITS) - 1) - } - - pub const fn to_u8(self) -> u8 { +pub struct Uint(T); + +impl Uint +where + T: Shr + + Shl + + BitAnd + + Sub + + From, +{ + // TODO: rename + pub fn from_bits(v: T) -> Self { + Self((v >> OFFSET) & (T::from(1) << BITS) - T::from(1)) + } + + pub fn to_bits(self) -> T { self.0 << OFFSET } - pub const fn get(self) -> u8 { + pub fn get(self) -> T { self.0 } } diff --git a/src/boxes.rs b/src/boxes.rs index 8a10297..f8de220 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1962,6 +1962,7 @@ impl FullBox for StsdBox { #[derive(Debug, Clone, PartialEq, Eq)] pub enum SampleEntry { Avc1(Avc1Box), + Hev1(Hev1Box), Vp08(Vp08Box), Vp09(Vp09Box), Av01(Av01Box), @@ -1973,6 +1974,7 @@ impl SampleEntry { fn inner_box(&self) -> &dyn BaseBox { match self { Self::Avc1(b) => b, + Self::Hev1(b) => b, Self::Vp08(b) => b, Self::Vp09(b) => b, Self::Av01(b) => b, @@ -1986,6 +1988,7 @@ impl Encode for SampleEntry { fn encode(&self, writer: &mut W) -> Result<()> { match self { Self::Avc1(b) => b.encode(writer), + Self::Hev1(b) => b.encode(writer), Self::Vp08(b) => b.encode(writer), Self::Vp09(b) => b.encode(writer), Self::Av01(b) => b.encode(writer), @@ -2000,6 +2003,7 @@ impl Decode for SampleEntry { let (header, mut reader) = BoxHeader::peek(reader)?; match header.box_type { Avc1Box::TYPE => Decode::decode(&mut reader).map(Self::Avc1), + Hev1Box::TYPE => Decode::decode(&mut reader).map(Self::Hev1), Vp08Box::TYPE => Decode::decode(&mut reader).map(Self::Vp08), Vp09Box::TYPE => Decode::decode(&mut reader).map(Self::Vp09), Av01Box::TYPE => Decode::decode(&mut reader).map(Self::Av01), @@ -2184,24 +2188,25 @@ impl BaseBox for Avc1Box { /// [ISO/IEC 14496-15] AVCConfigurationBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct AvccBox { - pub configuration_version: u8, pub avc_profile_indication: u8, pub profile_compatibility: u8, pub avc_level_indication: u8, - pub length_size_minus_one: Uint<2>, + pub length_size_minus_one: Uint, pub sps_list: Vec>, pub pps_list: Vec>, - pub chroma_format: Option>, - pub bit_depth_luma_minus8: Option>, - pub bit_depth_chroma_minus8: Option>, + pub chroma_format: Option>, + pub bit_depth_luma_minus8: Option>, + pub bit_depth_chroma_minus8: Option>, pub sps_ext_list: Vec>, } impl AvccBox { pub const TYPE: BoxType = BoxType::Normal(*b"avcC"); + const CONFIGURATION_VERSION: u8 = 1; + fn encode_payload(&self, writer: &mut W) -> Result<()> { - self.configuration_version.encode(writer)?; + Self::CONFIGURATION_VERSION.encode(writer)?; self.avc_profile_indication.encode(writer)?; self.profile_compatibility.encode(writer)?; self.avc_level_indication.encode(writer)?; @@ -2257,12 +2262,18 @@ impl AvccBox { fn decode_payload(reader: &mut std::io::Take) -> Result { let configuration_version = u8::decode(reader)?; + if configuration_version != Self::CONFIGURATION_VERSION { + return Err(Error::invalid_data(&format!( + "Unsupported avcC configuration version: {configuration_version}" + ))); + } + let avc_profile_indication = u8::decode(reader)?; let profile_compatibility = u8::decode(reader)?; let avc_level_indication = u8::decode(reader)?; - let length_size_minus_one = Uint::from_u8(u8::decode(reader)?); + let length_size_minus_one = Uint::from_bits(u8::decode(reader)?); - let sps_count = Uint::<5>::from_u8(u8::decode(reader)?).get() as usize; + let sps_count = Uint::::from_bits(u8::decode(reader)?).get() as usize; let mut sps_list = Vec::with_capacity(sps_count); for _ in 0..sps_count { let size = u16::decode(reader)? as usize; @@ -2285,9 +2296,9 @@ impl AvccBox { let mut bit_depth_chroma_minus8 = None; let mut sps_ext_list = Vec::new(); if !matches!(avc_profile_indication, 66 | 77 | 88) { - chroma_format = Some(Uint::from_u8(u8::decode(reader)?)); - bit_depth_luma_minus8 = Some(Uint::from_u8(u8::decode(reader)?)); - bit_depth_chroma_minus8 = Some(Uint::from_u8(u8::decode(reader)?)); + chroma_format = Some(Uint::from_bits(u8::decode(reader)?)); + bit_depth_luma_minus8 = Some(Uint::from_bits(u8::decode(reader)?)); + bit_depth_chroma_minus8 = Some(Uint::from_bits(u8::decode(reader)?)); let sps_ext_count = u8::decode(reader)? as usize; for _ in 0..sps_ext_count { @@ -2299,7 +2310,6 @@ impl AvccBox { } Ok(Self { - configuration_version, avc_profile_indication, profile_compatibility, avc_level_indication, @@ -2317,11 +2327,10 @@ impl AvccBox { impl Default for AvccBox { fn default() -> Self { Self { - configuration_version: 1, avc_profile_indication: 0, profile_compatibility: 0, avc_level_indication: 0, - length_size_minus_one: Uint::from_u8(0), + length_size_minus_one: Uint::from_bits(0), sps_list: Vec::new(), pps_list: Vec::new(), chroma_format: None, @@ -2362,6 +2371,270 @@ impl BaseBox for AvccBox { } } +/// [ISO/IEC 14496-15] HEVCSampleEntry class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Hev1Box { + pub visual: VisualSampleEntryFields, + pub hvcc_box: HvccBox, + pub unknown_boxes: Vec, +} + +impl Hev1Box { + pub const TYPE: BoxType = BoxType::Normal(*b"hev1"); + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + self.visual.encode(writer)?; + self.hvcc_box.encode(writer)?; + for b in &self.unknown_boxes { + b.encode(writer)?; + } + Ok(()) + } + + fn decode_payload(mut reader: &mut std::io::Take) -> Result { + let visual = VisualSampleEntryFields::decode(reader)?; + let mut hvcc_box = None; + let mut unknown_boxes = Vec::new(); + while reader.limit() > 0 { + let (header, mut reader) = BoxHeader::peek(&mut reader)?; + match header.box_type { + HvccBox::TYPE if hvcc_box.is_none() => { + hvcc_box = Some(HvccBox::decode(&mut reader)?); + } + _ => { + unknown_boxes.push(UnknownBox::decode(&mut reader)?); + } + } + } + let hvcc_box = hvcc_box.ok_or_else(|| Error::missing_box("hvcc", Self::TYPE))?; + Ok(Self { + visual, + hvcc_box, + unknown_boxes, + }) + } +} + +impl Encode for Hev1Box { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for Hev1Box { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for Hev1Box { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } + + fn children<'a>(&'a self) -> Box> { + Box::new( + std::iter::empty() + .chain(std::iter::once(&self.hvcc_box).map(as_box_object)) + .chain(self.unknown_boxes.iter().map(as_box_object)), + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HvccNalUintArray { + pub array_completeness: Uint, + pub nal_unit_type: Uint, + pub nalus: Vec>, +} + +/// [ISO/IEC 14496-15] HVCConfigurationBox class +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HvccBox { + pub general_profile_space: Uint, + pub general_tier_flag: Uint, + pub general_profile_idc: Uint, + pub general_profile_compatibility_flags: u32, + pub general_constraint_indicator_flags: Uint, + pub general_level_idc: u8, + pub min_spatial_segmentation_idc: Uint, + pub parallelism_type: Uint, + pub chroma_format_idc: Uint, + pub bit_depth_luma_minus8: Uint, + pub bit_depth_chroma_minus8: Uint, + pub avg_frame_rate: u16, + pub constant_frame_rate: Uint, + pub num_temporal_layers: Uint, + pub temporal_id_nested: Uint, + pub length_size_minus_one: Uint, + pub nalu_arrays: Vec, +} + +impl HvccBox { + pub const TYPE: BoxType = BoxType::Normal(*b"hvcC"); + + const CONFIGURATION_VERSION: u8 = 1; + + fn encode_payload(&self, writer: &mut W) -> Result<()> { + Self::CONFIGURATION_VERSION.encode(writer)?; + (self.general_profile_space.to_bits() + | self.general_tier_flag.to_bits() + | self.general_profile_idc.to_bits()) + .encode(writer)?; + self.general_profile_compatibility_flags.encode(writer)?; + writer.write_all(&self.general_constraint_indicator_flags.get().to_be_bytes()[2..])?; + self.general_level_idc.encode(writer)?; + (0b1111_0000 | self.min_spatial_segmentation_idc.to_bits()).encode(writer)?; + (0b1111_1000 | self.parallelism_type.to_bits()).encode(writer)?; + (0b1111_1000 | self.chroma_format_idc.to_bits()).encode(writer)?; + (0b1111_1000 | self.bit_depth_luma_minus8.to_bits()).encode(writer)?; + (0b1111_1000 | self.bit_depth_chroma_minus8.to_bits()).encode(writer)?; + self.avg_frame_rate.encode(writer)?; + (self.constant_frame_rate.to_bits() + | self.num_temporal_layers.to_bits() + | self.temporal_id_nested.to_bits() + | self.length_size_minus_one.to_bits()) + .encode(writer)?; + u8::try_from(self.nalu_arrays.len()) + .map_err(|_| { + Error::invalid_input(&format!("Too many NALU arrays: {}", self.nalu_arrays.len())) + })? + .encode(writer)?; + for nalu_array in &self.nalu_arrays { + (nalu_array.array_completeness.to_bits() | nalu_array.nal_unit_type.to_bits()) + .encode(writer)?; + u16::try_from(nalu_array.nalus.len()) + .map_err(|_| { + Error::invalid_input(&format!("Too many NALUs: {}", self.nalu_arrays.len())) + })? + .encode(writer)?; + for nalu in &nalu_array.nalus { + u16::try_from(nalu.len()) + .map_err(|_| { + Error::invalid_input(&format!("Too large NALU: {}", self.nalu_arrays.len())) + })? + .encode(writer)?; + writer.write_all(nalu)?; + } + } + Ok(()) + } + + fn decode_payload(reader: &mut std::io::Take) -> Result { + let configuration_version = u8::decode(reader)?; + if configuration_version != Self::CONFIGURATION_VERSION { + return Err(Error::invalid_data(&format!( + "Unsupported avcC version: {configuration_version}" + ))); + } + + let b = u8::decode(reader)?; + let general_profile_space = Uint::from_bits(b); + let general_tier_flag = Uint::from_bits(b); + let general_profile_idc = Uint::from_bits(b); + + let general_profile_compatibility_flags = u32::decode(reader)?; + + let mut buf = [0; 8]; + reader.read_exact(&mut buf[2..])?; + let general_constraint_indicator_flags = Uint::from_bits(u64::from_be_bytes(buf)); + + let general_level_idc = u8::decode(reader)?; + let min_spatial_segmentation_idc = Uint::from_bits(u16::decode(reader)?); + let parallelism_type = Uint::from_bits(u8::decode(reader)?); + let chroma_format_idc = Uint::from_bits(u8::decode(reader)?); + let bit_depth_luma_minus8 = Uint::from_bits(u8::decode(reader)?); + let bit_depth_chroma_minus8 = Uint::from_bits(u8::decode(reader)?); + let avg_frame_rate = u16::decode(reader)?; + + let b = u8::decode(reader)?; + let constant_frame_rate = Uint::from_bits(b); + let num_temporal_layers = Uint::from_bits(b); + let temporal_id_nested = Uint::from_bits(b); + let length_size_minus_one = Uint::from_bits(b); + + let num_of_arrays = u8::decode(reader)?; + let mut nalu_arrays = Vec::new(); + for _ in 0..num_of_arrays { + let b = u8::decode(reader)?; + let array_completeness = Uint::from_bits(b); + let nal_unit_type = Uint::from_bits(b); + + let num_nalus = u16::decode(reader)?; + let mut nalus = Vec::new(); + for _ in 0..num_nalus { + let nal_unit_length = u16::decode(reader)? as usize; + let mut nal_unit = vec![0; nal_unit_length]; + reader.read_exact(&mut nal_unit)?; + nalus.push(nal_unit); + } + nalu_arrays.push(HvccNalUintArray { + array_completeness, + nal_unit_type, + nalus, + }); + } + + Ok(Self { + general_profile_space, + general_tier_flag, + general_profile_idc, + general_profile_compatibility_flags, + general_constraint_indicator_flags, + general_level_idc, + min_spatial_segmentation_idc, + parallelism_type, + chroma_format_idc, + bit_depth_luma_minus8, + bit_depth_chroma_minus8, + avg_frame_rate, + constant_frame_rate, + num_temporal_layers, + temporal_id_nested, + length_size_minus_one, + nalu_arrays, + }) + } +} + +impl Encode for HvccBox { + fn encode(&self, writer: &mut W) -> Result<()> { + BoxHeader::from_box(self).encode(writer)?; + self.encode_payload(writer)?; + Ok(()) + } +} + +impl Decode for HvccBox { + fn decode(reader: &mut R) -> Result { + let header = BoxHeader::decode(reader)?; + header.box_type.expect(Self::TYPE)?; + header.with_box_payload_reader(reader, Self::decode_payload) + } +} + +impl BaseBox for HvccBox { + fn box_type(&self) -> BoxType { + Self::TYPE + } + + fn box_payload_size(&self) -> u64 { + ExternalBytes::calc(|writer| self.encode_payload(writer)) + } + + fn children<'a>(&'a self) -> Box> { + Box::new(std::iter::empty()) + } +} + /// [] VP8SampleEntry class #[derive(Debug, Clone, PartialEq, Eq)] pub struct Vp08Box { @@ -2523,9 +2796,9 @@ impl BaseBox for Vp09Box { pub struct VpccBox { pub profile: u8, pub level: u8, - pub bit_depth: Uint<4, 4>, - pub chroma_subsampling: Uint<3, 1>, - pub video_full_range_flag: Uint<1>, + pub bit_depth: Uint, + pub chroma_subsampling: Uint, + pub video_full_range_flag: Uint, pub colour_primaries: u8, pub transfer_characteristics: u8, pub matrix_coefficients: u8, @@ -2539,9 +2812,9 @@ impl VpccBox { FullBoxHeader::from_box(self).encode(writer)?; self.profile.encode(writer)?; self.level.encode(writer)?; - (self.bit_depth.to_u8() - | self.chroma_subsampling.to_u8() - | self.video_full_range_flag.to_u8()) + (self.bit_depth.to_bits() + | self.chroma_subsampling.to_bits() + | self.video_full_range_flag.to_bits()) .encode(writer)?; self.colour_primaries.encode(writer)?; self.transfer_characteristics.encode(writer)?; @@ -2564,9 +2837,9 @@ impl VpccBox { let level = u8::decode(reader)?; let b = u8::decode(reader)?; - let bit_depth = Uint::from_u8(b); - let chroma_subsampling = Uint::from_u8(b); - let video_full_range_flag = Uint::from_u8(b); + let bit_depth = Uint::from_bits(b); + let chroma_subsampling = Uint::from_bits(b); + let video_full_range_flag = Uint::from_bits(b); let colour_primaries = u8::decode(reader)?; let transfer_characteristics = u8::decode(reader)?; let matrix_coefficients = u8::decode(reader)?; @@ -2708,16 +2981,16 @@ impl BaseBox for Av01Box { /// [] AV1CodecConfigurationBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct Av1cBox { - pub seq_profile: Uint<3, 5>, - pub seq_level_idx_0: Uint<5, 0>, - pub seq_tier_0: Uint<1, 7>, - pub high_bitdepth: Uint<1, 6>, - pub twelve_bit: Uint<1, 5>, - pub monochrome: Uint<1, 4>, - pub chroma_subsampling_x: Uint<1, 3>, - pub chroma_subsampling_y: Uint<1, 2>, - pub chroma_sample_position: Uint<2, 0>, - pub initial_presentation_delay_minus_one: Option>, + pub seq_profile: Uint, + pub seq_level_idx_0: Uint, + pub seq_tier_0: Uint, + pub high_bitdepth: Uint, + pub twelve_bit: Uint, + pub monochrome: Uint, + pub chroma_subsampling_x: Uint, + pub chroma_subsampling_y: Uint, + pub chroma_sample_position: Uint, + pub initial_presentation_delay_minus_one: Option>, pub config_obus: Vec, } @@ -2729,17 +3002,17 @@ impl Av1cBox { fn encode_payload(&self, writer: &mut W) -> Result<()> { ((Self::MARKER << 7) | Self::VERSION).encode(writer)?; - (self.seq_profile.to_u8() | self.seq_level_idx_0.to_u8()).encode(writer)?; - (self.seq_tier_0.to_u8() - | self.high_bitdepth.to_u8() - | self.twelve_bit.to_u8() - | self.monochrome.to_u8() - | self.chroma_subsampling_x.to_u8() - | self.chroma_subsampling_y.to_u8() - | self.chroma_sample_position.to_u8()) + (self.seq_profile.to_bits() | self.seq_level_idx_0.to_bits()).encode(writer)?; + (self.seq_tier_0.to_bits() + | self.high_bitdepth.to_bits() + | self.twelve_bit.to_bits() + | self.monochrome.to_bits() + | self.chroma_subsampling_x.to_bits() + | self.chroma_subsampling_y.to_bits() + | self.chroma_sample_position.to_bits()) .encode(writer)?; if let Some(v) = self.initial_presentation_delay_minus_one { - (0b1_0000 | v.to_u8()).encode(writer)?; + (0b1_0000 | v.to_bits()).encode(writer)?; } else { 0u8.encode(writer)?; } @@ -2760,21 +3033,21 @@ impl Av1cBox { } let b = u8::decode(reader)?; - let seq_profile = Uint::from_u8(b); - let seq_level_idx_0 = Uint::from_u8(b); + let seq_profile = Uint::from_bits(b); + let seq_level_idx_0 = Uint::from_bits(b); let b = u8::decode(reader)?; - let seq_tier_0 = Uint::from_u8(b); - let high_bitdepth = Uint::from_u8(b); - let twelve_bit = Uint::from_u8(b); - let monochrome = Uint::from_u8(b); - let chroma_subsampling_x = Uint::from_u8(b); - let chroma_subsampling_y = Uint::from_u8(b); - let chroma_sample_position = Uint::from_u8(b); + let seq_tier_0 = Uint::from_bits(b); + let high_bitdepth = Uint::from_bits(b); + let twelve_bit = Uint::from_bits(b); + let monochrome = Uint::from_bits(b); + let chroma_subsampling_x = Uint::from_bits(b); + let chroma_subsampling_y = Uint::from_bits(b); + let chroma_sample_position = Uint::from_bits(b); let b = u8::decode(reader)?; - let initial_presentation_delay_minus_one = if Uint::<1, 4>::from_u8(b).get() == 1 { - Some(Uint::from_u8(b)) + let initial_presentation_delay_minus_one = if Uint::::from_bits(b).get() == 1 { + Some(Uint::from_bits(b)) } else { None }; diff --git a/tests/decode_encode_test.rs b/tests/decode_encode_test.rs index 9f68032..7cad99c 100644 --- a/tests/decode_encode_test.rs +++ b/tests/decode_encode_test.rs @@ -39,6 +39,11 @@ fn decode_encode_black_h265_video_mp4() -> Result<()> { // エンコード結果のバイト列が正しいことを確認する。 assert_eq!(input_bytes.len(), output_bytes.len()); + + // ボックスの順番は入れ替わるのでソートした結果を比較する + let mut input_bytes = input_bytes.to_vec(); + input_bytes.sort(); + output_bytes.sort(); assert_eq!(&input_bytes[..], output_bytes); Ok(()) From 052c1c64e75324fe7eb0e7265017ea01b8efc78f Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 15:00:15 +0900 Subject: [PATCH 084/103] Fix hvcc encode --- src/boxes.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index f8de220..2da44d0 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -2492,9 +2492,9 @@ impl HvccBox { self.general_profile_compatibility_flags.encode(writer)?; writer.write_all(&self.general_constraint_indicator_flags.get().to_be_bytes()[2..])?; self.general_level_idc.encode(writer)?; - (0b1111_0000 | self.min_spatial_segmentation_idc.to_bits()).encode(writer)?; - (0b1111_1000 | self.parallelism_type.to_bits()).encode(writer)?; - (0b1111_1000 | self.chroma_format_idc.to_bits()).encode(writer)?; + (0b1111_0000_0000_0000 | self.min_spatial_segmentation_idc.to_bits()).encode(writer)?; + (0b1111_1100 | self.parallelism_type.to_bits()).encode(writer)?; + (0b1111_1100 | self.chroma_format_idc.to_bits()).encode(writer)?; (0b1111_1000 | self.bit_depth_luma_minus8.to_bits()).encode(writer)?; (0b1111_1000 | self.bit_depth_chroma_minus8.to_bits()).encode(writer)?; self.avg_frame_rate.encode(writer)?; From e527c9d4c5b9129eb08183bb507a1bde55c0b75c Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 15:45:31 +0900 Subject: [PATCH 085/103] =?UTF-8?q?doc=20comment=20=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 65 ++++++++++++++++++++++++++++++++++++++++++---- src/boxes.rs | 1 + src/lib.rs | 2 ++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 7831a37..bdf473c 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -10,20 +10,34 @@ use crate::{ Decode, Encode, Error, Result, }; -// 単なる `Box` だと Rust の標準ライブラリのそれと名前が衝突するので変えておく +/// 全てのボックスが実装するトレイト +/// +/// 本来なら `Box` という名前が適切だが、それだと標準ライブラリの [`std::boxed::Box`] と名前が +/// 衝突してしまうので、それを避けるために `BaseBox` としている pub trait BaseBox { + /// ボックスの種別 fn box_type(&self) -> BoxType; + /// ボックスのサイズ + /// + /// サイズが可変長になる可能性がある `mdat` ボックス以外はデフォルト実装のままで問題ない fn box_size(&self) -> BoxSize { BoxSize::with_payload_size(self.box_type(), self.box_payload_size()) } + /// ボックスのペイロードのバイト数 fn box_payload_size(&self) -> u64; + /// 未知のボックスかどうか + /// + /// 基本的には `false` を返すデフォルト実装のままで問題ないが、 + /// [`UnknownBox`](crate::boxes::UnknownBox) を含む `enum` を定義する場合には、 + /// 独自の実装が必要となる fn is_unknown_box(&self) -> bool { false } + /// 子ボックスを走査するイテレーターを返す fn children<'a>(&'a self) -> Box>; } @@ -31,18 +45,27 @@ pub(crate) fn as_box_object(t: &T) -> &dyn BaseBox { t } +/// フルボックスを表すトレイト pub trait FullBox: BaseBox { + /// フルボックスのバージョンを返す fn full_box_version(&self) -> u8; + + /// フルボックスのフラグを返す fn full_box_flags(&self) -> FullBoxFlags; } +/// MP4 ファイルを表す構造体 #[derive(Debug, Clone, PartialEq, Eq)] pub struct Mp4File { + /// MP4 ファイルの先頭に位置する `ftyp` ボックス pub ftyp_box: FtypBox, + + /// `ftyp` に続くボックス群 pub boxes: Vec, } impl Mp4File { + /// ファイル内のトップレベルのボックス群を走査するイテレーターを返す pub fn iter(&self) -> impl Iterator { std::iter::empty() .chain(std::iter::once(&self.ftyp_box).map(as_box_object)) @@ -75,25 +98,32 @@ impl Encode for Mp4File { } } +/// [`BaseBox`] に共通のヘッダー #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BoxHeader { + /// ボックスの種別 pub box_type: BoxType, + + /// ボックスのサイズ pub box_size: BoxSize, } impl BoxHeader { - pub const MAX_SIZE: usize = (4 + 8) + (4 + 16); + const MAX_SIZE: usize = (4 + 8) + (4 + 16); + /// ボックスへの参照を受け取って、対応するヘッダーを作成する pub fn from_box(b: &B) -> Self { let box_type = b.box_type(); let box_size = b.box_size(); Self { box_type, box_size } } - pub fn header_size(self) -> usize { + /// ヘッダーをエンコードした際のバイト数を返す + pub fn external_size(self) -> usize { self.box_type.external_size() + self.box_size.external_size() } + /// このヘッダーに対応するボックスのペイロード部分をデコードするためのリーダーを引数にして、指定された関数を呼び出す pub fn with_box_payload_reader(self, reader: R, f: F) -> Result where F: FnOnce(&mut std::io::Take) -> Result, @@ -104,12 +134,12 @@ impl BoxHeader { let payload_size = self .box_size .get() - .checked_sub(self.header_size() as u64) + .checked_sub(self.external_size() as u64) .ok_or_else(|| { Error::invalid_data(&format!( "Too small box size: actual={}, expected={} or more", self.box_size.get(), - self.header_size() + self.external_size() )) })?; reader.take(payload_size) @@ -126,6 +156,9 @@ impl BoxHeader { Ok(value) } + /// ボックスのヘッダー部分を先読みする + /// + /// 返り値に含まれるリーダーには、ボックスのヘッダー部分のバイト列も含まれる pub fn peek(reader: R) -> Result<(Self, impl Read)> { let mut reader = PeekReader::<_, { BoxHeader::MAX_SIZE }>::new(reader); let header = BoxHeader::decode(&mut reader)?; @@ -190,13 +223,18 @@ impl Decode for BoxHeader { } } +/// [`FullBox`] に共通のヘッダー #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct FullBoxHeader { + /// バージョン pub version: u8, + + /// フラグ pub flags: FullBoxFlags, } impl FullBoxHeader { + /// フルボックスへの参照を受け取って、対応するヘッダーを作成する pub fn from_box(b: &B) -> Self { Self { version: b.full_box_version(), @@ -222,18 +260,22 @@ impl Decode for FullBoxHeader { } } +/// [`FullBox`] のヘッダー部分に含まれるビットフラグ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct FullBoxFlags(u32); impl FullBoxFlags { + /// 空のビットフラグを作成する pub const fn empty() -> Self { Self(0) } + /// [`u32`] を受け取って、対応するビットフラグを作成する pub const fn new(flags: u32) -> Self { Self(flags) } + /// `(ビット位置、フラグがセットされているかどうか)` のイテレーターを受け取って、対応するビットフラグを作成する pub fn from_iter(iter: I) -> Self where I: IntoIterator, @@ -242,10 +284,12 @@ impl FullBoxFlags { Self(flags) } + /// このビットフラグに対応する [`u32`] 値を返す pub const fn get(self) -> u32 { self.0 } + /// 指定されたビット位置のフラグがセットされているかどうかを判定する pub const fn is_set(self, i: usize) -> bool { (self.0 & (1 << i)) != 0 } @@ -266,12 +310,20 @@ impl Decode for FullBoxFlags { } } +/// [`BaseBox`] のサイズ +/// +/// ボックスのサイズは原則として、ヘッダー部分とペイロード部分のサイズを足した値となる。 +/// ただし、MP4 ファイルの末尾にあるボックスについてはサイズを 0 とすることで、ペイロードが可変長(追記可能)なボックスとして扱うことが可能となっている。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct BoxSize(u64); impl BoxSize { + /// ファイル末尾に位置する可変長のボックスを表すための特別な値 pub const VARIABLE_SIZE: Self = Self(0); + /// [`u64`] のサイズ値を受け取って、それが適切な場合には `Some(BoxSize)` が返される + /// + /// `box_size` の値が、指定されたボックス種別を保持するために必要な最小サイズを下回っている場合には [`None`] が返される pub fn new(box_type: BoxType, box_size: u64) -> Option { if box_size == 0 { return Some(Self(0)); @@ -284,6 +336,7 @@ impl BoxSize { } } + /// ボックス種別とペイロードサイズを受け取って、対応する [`BoxSize`] インスタンスを作成する pub const fn with_payload_size(box_type: BoxType, payload_size: u64) -> Self { let mut size = 4 + box_type.external_size() as u64 + payload_size; if size > u32::MAX as u64 { @@ -292,10 +345,12 @@ impl BoxSize { Self(size) } + /// ボックスのサイズの値を取得する pub const fn get(self) -> u64 { self.0 } + /// [`BoxHeader`] 内のサイズフィールドをエンコードする際に必要となるバイト数を返す pub const fn external_size(self) -> usize { if self.0 > u32::MAX as u64 { 4 + 8 diff --git a/src/boxes.rs b/src/boxes.rs index 2da44d0..fbdeacc 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1,3 +1,4 @@ +//! ボックス群 use std::{ io::{Read, Write}, num::NonZeroU32, diff --git a/src/lib.rs b/src/lib.rs index a30d394..c2a89ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +//! MP4 のボックスのエンコードおよびデコードを行うためのライブラリ +#![warn(missing_docs)] mod basic_types; pub mod boxes; mod io; From 9c96134d1e71111c8941c4d376b5f72988ec5aae Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 16:11:30 +0900 Subject: [PATCH 086/103] =?UTF-8?q?basic=5Ftypes=20=E5=86=85=E3=81=AE?= =?UTF-8?q?=E6=A7=8B=E9=80=A0=E4=BD=93=E3=81=AB=20doc=20comment=20?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index bdf473c..9eac4d7 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -360,13 +360,18 @@ impl BoxSize { } } +/// [`BaseBox`] の種別 #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum BoxType { + /// 四文字で表現される通常のボックス種別 Normal([u8; 4]), + + /// UUID 形式のボックス種別 Uuid([u8; 16]), } impl BoxType { + /// 種別を表すバイト列を返す pub fn as_bytes(&self) -> &[u8] { match self { BoxType::Normal(ty) => &ty[..], @@ -374,6 +379,7 @@ impl BoxType { } } + /// [`BoxHeader`] 内のボックス種別フィールドをエンコードする際に必要となるバイト数を返す pub const fn external_size(self) -> usize { if matches!(self, Self::Normal(_)) { 4 @@ -382,6 +388,7 @@ impl BoxType { } } + /// 自分が `expected` と同じ種別であるかをチェックする pub fn expect(self, expected: Self) -> Result<()> { if self == expected { Ok(()) @@ -420,19 +427,22 @@ impl std::fmt::Display for BoxType { } } -/// 1904/1/1 からの経過秒数 +/// MP4 ファイル内で使われる時刻形式(1904/1/1 からの経過秒数) #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Mp4FileTime(u64); impl Mp4FileTime { + /// 1904/1/1 からの経過秒数を引数にとって [`Mp4FileTime`] インスタンスを作成する pub const fn from_secs(secs: u64) -> Self { Self(secs) } + /// 1904/1/1 からの経過秒数を返す pub const fn as_secs(self) -> u64 { self.0 } + /// [`std::time::UNIX_EPOCH`] を起点とした経過時間を受け取って、対応する [`Mp4FileTime`] インスタンスを作成する pub const fn from_unix_time(unix_time: Duration) -> Self { let delta = 2082844800; // 1904/1/1 から 1970/1/1 までの経過秒数 let unix_time_secs = unix_time.as_secs(); @@ -440,13 +450,18 @@ impl Mp4FileTime { } } +/// 固定小数点数 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct FixedPointNumber { + /// 整数部 pub integer: I, + + /// 小数部 pub fraction: F, } impl FixedPointNumber { + /// 整数部と小数部を受け取って固定小数点数を返す pub const fn new(integer: I, fraction: F) -> Self { Self { integer, fraction } } @@ -469,11 +484,14 @@ impl Decode for FixedPointNumber { } } -// エンコード時には終端 null が付与される文字列 +/// null 終端の UTF-8 文字列 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Utf8String(String); impl Utf8String { + /// 終端の null を含まない文字列を受け取って [`Utf8String`] インスタンスを作成する + /// + /// 引数の文字列内の null 文字が含まれている場合には [`None`] が返される pub fn new(s: &str) -> Option { if s.as_bytes().contains(&0) { return None; @@ -481,6 +499,7 @@ impl Utf8String { Some(Self(s.to_owned())) } + /// このインスタンスが保持する、null 終端部分を含まない文字列を返す pub fn get(&self) -> &str { &self.0 } @@ -511,7 +530,9 @@ impl Decode for Utf8String { } } +/// `A` か `B` のどちらかの値を保持する列挙型 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[allow(missing_docs)] pub enum Either { A(A), B(B), @@ -548,6 +569,11 @@ impl BaseBox for Either { } } +/// 任意のビット数の非負の整数を表現するための型 +/// +/// - `T`: 数値の内部的な型。 最低限 `BITS` 分の数値を表現可能な型である必要がある。 +/// - `BITS`: 数値のビット数 +/// - `OFFSET`: 一つの `T` に複数の [`Uint`] 値がパックされる場合の、この数値のオフセット位置(ビット数) #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Uint(T); @@ -559,15 +585,19 @@ where + Sub + From, { - // TODO: rename + /// `T` が保持するビット列の `OFFSET` 位置から `BITS` 分のビット列に対応する整数値を返す pub fn from_bits(v: T) -> Self { Self((v >> OFFSET) & (T::from(1) << BITS) - T::from(1)) } + /// このインスタンスに対応する `T` 内のビット列を返す + /// + /// なお `OFFSET` が `0` の場合には、このメソッドは [`Uint::get()`] と等価である pub fn to_bits(self) -> T { self.0 << OFFSET } + /// このインスタンスが表現する整数値を返す pub fn get(self) -> T { self.0 } From 4a5124d71def44193d7858e8993766d0525bf80a Mon Sep 17 00:00:00 2001 From: voluntas Date: Tue, 24 Sep 2024 17:02:31 +0900 Subject: [PATCH 087/103] =?UTF-8?q?=E6=9C=80=E4=BD=8E=E9=99=90=E3=81=AE?= =?UTF-8?q?=E6=83=85=E5=A0=B1=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01b2cc0..211cbc4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,34 @@ -mp4-rust -======== +# mp4-rust + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +## About Shiguredo's open source software + +We will not respond to PRs or issues that have not been discussed on Discord. Also, Discord is only available in Japanese. + +Please read before use. + +## 時雨堂のオープンソースソフトウェアについて + +利用前に をお読みください。 + +## ライセンス + +Apache License 2.0 + +```text +Copyright 2024-2024, Takeru Ohta (Original Author) +Copyright 2024-2024, Shiguredo Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` From 2e8c0805a3d4c1bbb9130578900e7c3b0a34004e Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 17:03:34 +0900 Subject: [PATCH 088/103] =?UTF-8?q?boxes=20=E3=81=AB=20doc=20comment=20?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/boxes.rs | 102 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 91 insertions(+), 11 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index fbdeacc..537bd44 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -10,10 +10,18 @@ use crate::{ Result, Uint, Utf8String, }; +/// ペイロードの解釈方法が不明なボックスを保持するための構造体 +/// +/// ペイロードは単なるバイト列として扱われる #[derive(Debug, Clone, PartialEq, Eq)] pub struct UnknownBox { + /// ボックス種別 pub box_type: BoxType, + + /// ボックスサイズ pub box_size: BoxSize, + + /// ペイロード pub payload: Vec, } @@ -60,32 +68,67 @@ impl BaseBox for UnknownBox { } } +/// [`FtypBox`] で使われるブランド定義 +/// +/// ブランドは、対象の MP4 ファイルを読み込んで処理する際に必要となる要件(登場する可能性があるボックス群やハンドリングすべきフラグなど)を指定する #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Brand([u8; 4]); impl Brand { + /// [ISO/IEC 14496-12] `isom` ブランド pub const ISOM: Self = Self::new(*b"isom"); + + /// [ISO/IEC 14496-12] `avc1` ブランド + pub const AVC1: Self = Self::new(*b"avc1"); + + /// [ISO/IEC 14496-12] `iso2` ブランド pub const ISO2: Self = Self::new(*b"iso2"); + + /// [ISO/IEC 14496-12] `mp71` ブランド pub const MP71: Self = Self::new(*b"mp71"); + + /// [ISO/IEC 14496-12] `iso3` ブランド pub const ISO3: Self = Self::new(*b"iso3"); + + /// [ISO/IEC 14496-12] `iso4` ブランド pub const ISO4: Self = Self::new(*b"iso4"); + + /// [ISO/IEC 14496-12] `iso5` ブランド pub const ISO5: Self = Self::new(*b"iso5"); + + /// [ISO/IEC 14496-12] `iso6` ブランド pub const ISO6: Self = Self::new(*b"iso6"); + + /// [ISO/IEC 14496-12] `iso7` ブランド pub const ISO7: Self = Self::new(*b"iso7"); + + /// [ISO/IEC 14496-12] `iso8` ブランド pub const ISO8: Self = Self::new(*b"iso8"); + + /// [ISO/IEC 14496-12] `iso9` ブランド pub const ISO9: Self = Self::new(*b"iso9"); + + /// [ISO/IEC 14496-12] `isoa` ブランド pub const ISOA: Self = Self::new(*b"isoa"); + + /// [ISO/IEC 14496-12] `isob` ブランド pub const ISOB: Self = Self::new(*b"isob"); + + /// [ISO/IEC 14496-12] `relo` ブランド pub const RELO: Self = Self::new(*b"relo"); + /// [ISO/IEC 14496-14] `mp41` ブランド pub const MP41: Self = Self::new(*b"mp41"); - pub const AVC1: Self = Self::new(*b"avc1"); + + /// [] `av01` ブランド pub const AV01: Self = Self::new(*b"av01"); + /// バイト列を渡して、対応するブランドを作成する pub const fn new(brand: [u8; 4]) -> Self { Self(brand) } + /// このブランドを表すバイト列を取得する pub const fn get(self) -> [u8; 4] { self.0 } @@ -118,6 +161,7 @@ impl Decode for Brand { /// [ISO/IEC 14496-12] FileTypeBox class #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct FtypBox { pub major_brand: Brand, pub minor_version: u32, @@ -125,6 +169,7 @@ pub struct FtypBox { } impl FtypBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"ftyp"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -180,7 +225,9 @@ impl BaseBox for FtypBox { } } +/// [`Mp4File`](crate::Mp4File) のトップレベルに位置するボックス群のデフォルト実装 #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub enum RootBox { Free(FreeBox), Mdat(MdatBox), @@ -246,11 +293,13 @@ impl BaseBox for RootBox { /// [ISO/IEC 14496-12] FreeSpaceBox class #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct FreeBox { pub payload: Vec, } impl FreeBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"free"); } @@ -290,11 +339,15 @@ impl BaseBox for FreeBox { /// [ISO/IEC 14496-12] MediaDataBox class #[derive(Debug, Clone, PartialEq, Eq)] pub struct MdatBox { + /// ペイロードが可変長かどうか pub is_variable_size: bool, + + /// ペイロード pub payload: Vec, } impl MdatBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"mdat"); } @@ -344,6 +397,7 @@ impl BaseBox for MdatBox { /// [ISO/IEC 14496-12] MovieBox class #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct MoovBox { pub mvhd_box: MvhdBox, pub trak_boxes: Vec, @@ -351,6 +405,7 @@ pub struct MoovBox { } impl MoovBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"moov"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -427,8 +482,9 @@ impl BaseBox for MoovBox { } } -/// [ISO/IEC 14496-12] MovieHeaderBox class +/// [ISO/IEC 14496-12] MovieHeaderBox class (親: [`MoovBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct MvhdBox { pub creation_time: Mp4FileTime, pub modification_time: Mp4FileTime, @@ -441,6 +497,7 @@ pub struct MvhdBox { } impl MvhdBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"mvhd"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -555,8 +612,9 @@ impl FullBox for MvhdBox { } } -/// [ISO/IEC 14496-12] TrackBox class +/// [ISO/IEC 14496-12] TrackBox class (親: [`MoovBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct TrakBox { pub tkhd_box: TkhdBox, pub edts_box: Option, @@ -565,6 +623,7 @@ pub struct TrakBox { } impl TrakBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"trak"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -649,8 +708,9 @@ impl BaseBox for TrakBox { } } -/// [ISO/IEC 14496-12] TrackHeaderBox class +/// [ISO/IEC 14496-12] TrackHeaderBox class (親: [`TrakBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct TkhdBox { pub flag_track_enabled: bool, pub flag_track_in_movie: bool, @@ -670,6 +730,7 @@ pub struct TkhdBox { } impl TkhdBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"tkhd"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -809,14 +870,16 @@ impl FullBox for TkhdBox { } } -/// [ISO/IEC 14496-12] EditBox class +/// [ISO/IEC 14496-12] EditBox class (親: [`TrakBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct EdtsBox { pub elst_box: Option, pub unknown_boxes: Vec, } impl EdtsBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"edts"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -884,20 +947,24 @@ impl BaseBox for EdtsBox { } } +/// [`ElstBox`] に含まれるエントリー #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct ElstEntry { pub edit_duration: u64, pub media_time: i64, pub media_rate: FixedPointNumber, } -/// [ISO/IEC 14496-12] EditListBox class +/// [ISO/IEC 14496-12] EditListBox class (親: [`EdtsBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct ElstBox { pub entries: Vec, } impl ElstBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"elst"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -992,8 +1059,9 @@ impl FullBox for ElstBox { } } -/// [ISO/IEC 14496-12] MediaBox class +/// [ISO/IEC 14496-12] MediaBox class (親: [`TrakBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct MdiaBox { pub mdhd_box: MdhdBox, pub hdlr_box: HdlrBox, @@ -1002,6 +1070,7 @@ pub struct MdiaBox { } impl MdiaBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"mdia"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -1084,17 +1153,21 @@ impl BaseBox for MdiaBox { } } -/// [ISO/IEC 14496-12] MediaHeaderBox class +/// [ISO/IEC 14496-12] MediaHeaderBox class (親: [`MdiaBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct MdhdBox { pub creation_time: Mp4FileTime, pub modification_time: Mp4FileTime, pub timescale: u32, pub duration: u64, - pub language: [u8; 3], // ISO-639-2/T language code + + /// ISO-639-2/T language code + pub language: [u8; 3], } impl MdhdBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"mdhd"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -1213,17 +1286,22 @@ impl FullBox for MdhdBox { } } -/// [ISO/IEC 14496-12] HandlerBox class +/// [ISO/IEC 14496-12] HandlerBox class (親: [`MdiaBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct HdlrBox { pub handler_type: [u8; 4], pub name: Utf8String, } impl HdlrBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"hdlr"); + /// 音声用のハンドラー種別 pub const HANDLER_TYPE_SOUN: [u8; 4] = *b"soun"; + + /// 映像用のハンドラー種別 pub const HANDLER_TYPE_VIDE: [u8; 4] = *b"vide"; fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -1285,8 +1363,9 @@ impl FullBox for HdlrBox { } } -/// [ISO/IEC 14496-12] MediaInformationBox class +/// [ISO/IEC 14496-12] MediaInformationBox class (親: [`MdiaBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct MinfBox { pub smhd_or_vmhd_box: Either, pub dinf_box: DinfBox, @@ -1295,6 +1374,7 @@ pub struct MinfBox { } impl MinfBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"minf"); fn encode_payload(&self, writer: &mut W) -> Result<()> { From 05227630a6e20f61be3833976061617c9c4f7ff6 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Tue, 24 Sep 2024 17:17:27 +0900 Subject: [PATCH 089/103] Add badges --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 211cbc4..a0c4043 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # mp4-rust +[![shiguredo_mp4](https://img.shields.io/crates/v/shiguredo_mp4.svg)](https://crates.io/crates/shiguredo_mp4) +[![Documentation](https://docs.rs/shiguredo_mp4/badge.svg)](https://docs.rs/shiguredo_mp4) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ## About Shiguredo's open source software From 7237780ad5bc426053ff4d3ca6af7cf066fa0e40 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 09:20:04 +0900 Subject: [PATCH 090/103] =?UTF-8?q?boxes=20=E3=81=AB=20doc=20comment=20?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/boxes.rs | 109 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 24 deletions(-) diff --git a/src/boxes.rs b/src/boxes.rs index 537bd44..3414490 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -1467,13 +1467,15 @@ impl BaseBox for MinfBox { } } -/// [ISO/IEC 14496-12] SoundMediaHeaderBox class +/// [ISO/IEC 14496-12] SoundMediaHeaderBox class (親: [`MinfBox`]) #[derive(Debug, Default, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct SmhdBox { pub balance: i16, } impl SmhdBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"smhd"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -1531,14 +1533,16 @@ impl FullBox for SmhdBox { } } -/// [ISO/IEC 14496-12] VideoMediaHeaderBox class +/// [ISO/IEC 14496-12] VideoMediaHeaderBox class (親: [`MinfBox`]) #[derive(Debug, Default, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct VmhdBox { pub graphicsmode: u16, pub opcolor: [u16; 3], } impl VmhdBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"vmhd"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -1599,14 +1603,16 @@ impl FullBox for VmhdBox { } } -/// [ISO/IEC 14496-12] DataInformationBox class +/// [ISO/IEC 14496-12] DataInformationBox class (親: [`MinfBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct DinfBox { pub dref_box: DrefBox, pub unknown_boxes: Vec, } impl DinfBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"dinf"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -1673,14 +1679,16 @@ impl BaseBox for DinfBox { } } -/// [ISO/IEC 14496-12] DataReferenceBox class +/// [ISO/IEC 14496-12] DataReferenceBox class (親: [`DinfBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct DrefBox { pub url_box: Option, pub unknown_boxes: Vec, } impl DrefBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"dref"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -1772,13 +1780,15 @@ impl FullBox for DrefBox { } } -/// [ISO/IEC 14496-12] DataEntryUrlBox class +/// [ISO/IEC 14496-12] DataEntryUrlBox class (親: [`DrefBox`]) #[derive(Debug, Default, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct UrlBox { pub location: Option, } impl UrlBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"url "); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -1840,8 +1850,9 @@ impl FullBox for UrlBox { } } -/// [ISO/IEC 14496-12] SampleTableBox class +/// [ISO/IEC 14496-12] SampleTableBox class (親: [`MinfBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct StblBox { pub stsd_box: StsdBox, pub stts_box: SttsBox, @@ -1853,6 +1864,7 @@ pub struct StblBox { } impl StblBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"stbl"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -1970,13 +1982,15 @@ impl BaseBox for StblBox { } } -/// [ISO/IEC 14496-12] SampleDescriptionBox class +/// [ISO/IEC 14496-12] SampleDescriptionBox class (親: [`StblBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct StsdBox { pub entries: Vec, } impl StsdBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"stsd"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -2040,7 +2054,9 @@ impl FullBox for StsdBox { } } +/// [`StsdBox`] に含まれるエントリー #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub enum SampleEntry { Avc1(Avc1Box), Hev1(Hev1Box), @@ -2116,7 +2132,9 @@ impl BaseBox for SampleEntry { } } +/// 映像系の [`SampleEntry`] に共通のフィールドをまとめた構造体 #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct VisualSampleEntryFields { pub data_reference_index: u16, pub width: u16, @@ -2188,8 +2206,9 @@ impl Decode for VisualSampleEntryFields { } } -/// [ISO/IEC 14496-15] AVCSampleEntry class +/// [ISO/IEC 14496-15] AVCSampleEntry class (親: [`StsdBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct Avc1Box { pub visual: VisualSampleEntryFields, pub avcc_box: AvccBox, @@ -2197,6 +2216,7 @@ pub struct Avc1Box { } impl Avc1Box { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"avc1"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -2266,8 +2286,9 @@ impl BaseBox for Avc1Box { } } -/// [ISO/IEC 14496-15] AVCConfigurationBox class +/// [ISO/IEC 14496-15] AVCConfigurationBox class (親: [`Avc1Box`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct AvccBox { pub avc_profile_indication: u8, pub profile_compatibility: u8, @@ -2282,6 +2303,7 @@ pub struct AvccBox { } impl AvccBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"avcC"); const CONFIGURATION_VERSION: u8 = 1; @@ -2452,8 +2474,9 @@ impl BaseBox for AvccBox { } } -/// [ISO/IEC 14496-15] HEVCSampleEntry class +/// [ISO/IEC 14496-15] HEVCSampleEntry class (親: [`StsdBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct Hev1Box { pub visual: VisualSampleEntryFields, pub hvcc_box: HvccBox, @@ -2461,6 +2484,7 @@ pub struct Hev1Box { } impl Hev1Box { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"hev1"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -2530,15 +2554,18 @@ impl BaseBox for Hev1Box { } } +/// [`HvccBox`] 内の NAL ユニット配列を保持する構造体 #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct HvccNalUintArray { pub array_completeness: Uint, pub nal_unit_type: Uint, pub nalus: Vec>, } -/// [ISO/IEC 14496-15] HVCConfigurationBox class +/// [ISO/IEC 14496-15] HVCConfigurationBox class (親: [`Hev1Box`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct HvccBox { pub general_profile_space: Uint, pub general_tier_flag: Uint, @@ -2560,6 +2587,7 @@ pub struct HvccBox { } impl HvccBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"hvcC"); const CONFIGURATION_VERSION: u8 = 1; @@ -2716,8 +2744,9 @@ impl BaseBox for HvccBox { } } -/// [] VP8SampleEntry class +/// [] VP8SampleEntry class (親: [`StsdBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct Vp08Box { pub visual: VisualSampleEntryFields, pub vpcc_box: VpccBox, @@ -2725,6 +2754,7 @@ pub struct Vp08Box { } impl Vp08Box { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"vp08"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -2794,8 +2824,9 @@ impl BaseBox for Vp08Box { } } -/// [] VP9SampleEntry class +/// [] VP9SampleEntry class (親: [`StsdBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct Vp09Box { pub visual: VisualSampleEntryFields, pub vpcc_box: VpccBox, @@ -2803,6 +2834,7 @@ pub struct Vp09Box { } impl Vp09Box { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"vp09"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -2872,8 +2904,9 @@ impl BaseBox for Vp09Box { } } -/// [] VPCodecConfigurationBox class +/// [] VPCodecConfigurationBox class (親: [`Vp08Box`], [`Vp09Box`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct VpccBox { pub profile: u8, pub level: u8, @@ -2887,6 +2920,7 @@ pub struct VpccBox { } impl VpccBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"vpcC"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -2981,8 +3015,9 @@ impl FullBox for VpccBox { } } -/// [] AV1SampleEntry class +/// [] AV1SampleEntry class (親: [`StsdBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct Av01Box { pub visual: VisualSampleEntryFields, pub av1c_box: Av1cBox, @@ -2990,6 +3025,7 @@ pub struct Av01Box { } impl Av01Box { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"av01"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -3059,8 +3095,9 @@ impl BaseBox for Av01Box { } } -/// [] AV1CodecConfigurationBox class +/// [] AV1CodecConfigurationBox class (親: [`StsdBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct Av1cBox { pub seq_profile: Uint, pub seq_level_idx_0: Uint, @@ -3076,6 +3113,7 @@ pub struct Av1cBox { } impl Av1cBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"av1C"); const MARKER: u8 = 1; @@ -3182,19 +3220,23 @@ impl BaseBox for Av1cBox { } } +/// [`SttsBox`] が保持するエントリー #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct SttsEntry { pub sample_count: u32, pub sample_delta: u32, } -/// [ISO/IEC 14496-12] TimeToSampleBox class +/// [ISO/IEC 14496-12] TimeToSampleBox class (親: [`StblBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct SttsBox { pub entries: Vec, } impl SttsBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"stts"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -3261,20 +3303,24 @@ impl FullBox for SttsBox { } } +/// [`StscBox`] が保持するエントリー #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct StscEntry { pub first_chunk: u32, pub sample_per_chunk: u32, pub sample_description_index: u32, } -/// [ISO/IEC 14496-12] SampleToChunkBox class +/// [ISO/IEC 14496-12] SampleToChunkBox class (親: [`StblBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct StscBox { pub entries: Vec, } impl StscBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"stsc"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -3343,8 +3389,9 @@ impl FullBox for StscBox { } } -/// [ISO/IEC 14496-12] SampleSizeBox class +/// [ISO/IEC 14496-12] SampleSizeBox class (親: [`StblBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub enum StszBox { Fixed { sample_size: NonZeroU32, @@ -3356,6 +3403,7 @@ pub enum StszBox { } impl StszBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"stsz"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -3438,13 +3486,15 @@ impl FullBox for StszBox { } } -/// [ISO/IEC 14496-12] ChunkOffsetBox class +/// [ISO/IEC 14496-12] ChunkOffsetBox class (親: [`StcoBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct StcoBox { pub chunk_offsets: Vec, } impl StcoBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"stco"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -3507,13 +3557,15 @@ impl FullBox for StcoBox { } } -/// [ISO/IEC 14496-12] ChunkLargeOffsetBox class +/// [ISO/IEC 14496-12] ChunkLargeOffsetBox class (親: [`Co64Box`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct Co64Box { pub chunk_offsets: Vec, } impl Co64Box { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"co64"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -3576,13 +3628,15 @@ impl FullBox for Co64Box { } } -/// [ISO/IEC 14496-12] SyncSampleBox class +/// [ISO/IEC 14496-12] SyncSampleBox class (親: [`StssBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct StssBox { pub sample_numbers: Vec, } impl StssBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"stss"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -3645,8 +3699,9 @@ impl FullBox for StssBox { } } -/// [] OpusSampleEntry class +/// [] OpusSampleEntry class (親: [`StsdBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct OpusBox { pub audio: AudioSampleEntryFields, pub dops_box: DopsBox, @@ -3654,6 +3709,7 @@ pub struct OpusBox { } impl OpusBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"Opus"); fn encode_payload(&self, writer: &mut W) -> Result<()> { @@ -3723,7 +3779,9 @@ impl BaseBox for OpusBox { } } +/// 音声系の [`SampleEntry`] に共通のフィールドをまとめた構造体 #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct AudioSampleEntryFields { pub data_reference_index: u16, pub channelcount: u16, @@ -3775,8 +3833,9 @@ impl Decode for AudioSampleEntryFields { } } -/// [] OpusSpecificBox class +/// [] OpusSpecificBox class (親: [`OpusBox`]) #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(missing_docs)] pub struct DopsBox { pub output_channel_count: u8, pub pre_skip: u16, @@ -3785,7 +3844,9 @@ pub struct DopsBox { } impl DopsBox { + /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"dOps"); + const VERSION: u8 = 0; fn encode_payload(&self, writer: &mut W) -> Result<()> { From afbd07f98b1431edac3b977ddec4166f2617c849 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 09:25:28 +0900 Subject: [PATCH 091/103] Add doc comments for io module --- src/io.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/io.rs b/src/io.rs index 26ad0e7..3bf4b38 100644 --- a/src/io.rs +++ b/src/io.rs @@ -5,11 +5,15 @@ use std::{ use crate::BoxType; +/// このライブラリ用の [`std::result::Result`] 型 pub type Result = std::result::Result; +/// このライブラリ用のエラー型 pub struct Error { - // TODO: add box_type field + /// 具体的なエラー理由 pub io_error: std::io::Error, + + /// エラー発生箇所を示すバックトレース pub backtrace: Backtrace, } @@ -64,7 +68,9 @@ impl std::fmt::Display for Error { } } +/// `self` のバイト列への変換を行うためのトレイト pub trait Encode { + /// `self` をバイト列に変換して `writer` に書き込む fn encode(&self, writer: &mut W) -> Result<()>; } @@ -133,7 +139,9 @@ impl Encode for [T; N] { } } +/// バイト列を `Self` に変換するためのトレイト pub trait Decode: Sized { + /// `reader` から読み込んだバイト列から `Self` を構築する fn decode(reader: &mut R) -> Result; } From 9fed0521ca9ae288331bf98c18b6693f8ec555ac Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 09:36:14 +0900 Subject: [PATCH 092/103] Update comment --- src/io.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/io.rs b/src/io.rs index 3bf4b38..5c34bbb 100644 --- a/src/io.rs +++ b/src/io.rs @@ -14,6 +14,8 @@ pub struct Error { pub io_error: std::io::Error, /// エラー発生箇所を示すバックトレース + /// + /// バックトレースは `RUST_BACKTRACE` 環境変数が設定されていない場合には取得されない pub backtrace: Backtrace, } From 12d2f08cc65d843a95c7b634722e7d28f5408304 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 10:20:02 +0900 Subject: [PATCH 093/103] =?UTF-8?q?=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB?= =?UTF-8?q?=E3=83=88=E5=80=A4=E5=91=A8=E3=82=8A=E3=81=AE=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=E3=82=92=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/basic_types.rs | 17 ++-- src/boxes.rs | 243 +++++++++++++++++++++++---------------------- 2 files changed, 133 insertions(+), 127 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 9eac4d7..64fcba8 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -451,7 +451,7 @@ impl Mp4FileTime { } /// 固定小数点数 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct FixedPointNumber { /// 整数部 pub integer: I, @@ -585,6 +585,16 @@ where + Sub + From, { + /// 指定された数値を受け取ってインスタンスを作成する + pub const fn new(v: T) -> Self { + Self(v) + } + + /// このインスタンスが表現する整数値を返す + pub fn get(self) -> T { + self.0 + } + /// `T` が保持するビット列の `OFFSET` 位置から `BITS` 分のビット列に対応する整数値を返す pub fn from_bits(v: T) -> Self { Self((v >> OFFSET) & (T::from(1) << BITS) - T::from(1)) @@ -596,9 +606,4 @@ where pub fn to_bits(self) -> T { self.0 << OFFSET } - - /// このインスタンスが表現する整数値を返す - pub fn get(self) -> T { - self.0 - } } diff --git a/src/boxes.rs b/src/boxes.rs index 3414490..2536d2e 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -500,6 +500,15 @@ impl MvhdBox { /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"mvhd"); + /// [`MvhdBox::rate`] のデフォルト値(通常の再生速度) + pub const DEFAULT_RATE: FixedPointNumber = FixedPointNumber::new(1, 0); + + /// [`MvhdBox::volume`] のデフォルト値(最大音量) + pub const DEFAULT_VOLUME: FixedPointNumber = FixedPointNumber::new(1, 0); + + /// [`MvhdBox::matrix`] のデフォルト値 + pub const DEFAULT_MATRIX: [i32; 9] = [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000]; + fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; if self.full_box_version() == 1 { @@ -524,7 +533,16 @@ impl MvhdBox { fn decode_payload(reader: &mut std::io::Take) -> Result { let full_header = FullBoxHeader::decode(reader)?; - let mut this = Self::default(); + let mut this = Self { + creation_time: Mp4FileTime::default(), + modification_time: Mp4FileTime::default(), + timescale: 0, + duration: 0, + rate: Self::DEFAULT_RATE, + volume: Self::DEFAULT_VOLUME, + matrix: Self::DEFAULT_MATRIX, + next_track_id: 0, + }; if full_header.version == 1 { this.creation_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; @@ -566,21 +584,6 @@ impl Decode for MvhdBox { } } -impl Default for MvhdBox { - fn default() -> Self { - Self { - creation_time: Mp4FileTime::default(), - modification_time: Mp4FileTime::default(), - timescale: 0, - duration: 0, - rate: FixedPointNumber::new(1, 0), // 通常の再生速度 - volume: FixedPointNumber::new(1, 0), // 最大音量 - matrix: [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000], - next_track_id: 0, - } - } -} - impl BaseBox for MvhdBox { fn box_type(&self) -> BoxType { Self::TYPE @@ -733,6 +736,21 @@ impl TkhdBox { /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"tkhd"); + /// [`TkhdBox::layer`] のデフォルト値 + pub const DEFAULT_LAYER: i16 = 0; + + /// [`TkhdBox::alternate_group`] のデフォルト値 + pub const DEFAULT_ALTERNATE_GROUP: i16 = 0; + + /// 音声用の [`TkhdBox::volume`] のデフォルト値(最大音量) + pub const DEFAULT_AUDIO_VOLUME: FixedPointNumber = FixedPointNumber::new(1, 0); + + /// 映像用の [`TkhdBox::volume`] のデフォルト値(無音) + pub const DEFAULT_VIDEO_VOLUME: FixedPointNumber = FixedPointNumber::new(0, 0); + + /// [`TkhdBox::matrix`] のデフォルト値 + pub const DEFAULT_MATRIX: [i32; 9] = [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000]; + fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; if self.full_box_version() == 1 { @@ -761,7 +779,23 @@ impl TkhdBox { fn decode_payload(reader: &mut std::io::Take) -> Result { let full_header = FullBoxHeader::decode(reader)?; - let mut this = Self::default(); + let mut this = Self { + flag_track_enabled: false, + flag_track_in_movie: false, + flag_track_in_preview: false, + flag_track_size_is_aspect_ratio: false, + + creation_time: Mp4FileTime::default(), + modification_time: Mp4FileTime::default(), + track_id: 0, + duration: 0, + layer: Self::DEFAULT_LAYER, + alternate_group: Self::DEFAULT_ALTERNATE_GROUP, + volume: Self::DEFAULT_AUDIO_VOLUME, + matrix: Self::DEFAULT_MATRIX, + width: FixedPointNumber::new(0, 0), + height: FixedPointNumber::new(0, 0), + }; this.flag_track_enabled = full_header.flags.is_set(0); this.flag_track_in_movie = full_header.flags.is_set(1); @@ -812,28 +846,6 @@ impl Decode for TkhdBox { } } -impl Default for TkhdBox { - fn default() -> Self { - Self { - flag_track_enabled: true, - flag_track_in_movie: true, - flag_track_in_preview: false, - flag_track_size_is_aspect_ratio: false, - - creation_time: Mp4FileTime::default(), - modification_time: Mp4FileTime::default(), - track_id: 0, - duration: 0, - layer: 0, - alternate_group: 0, - volume: FixedPointNumber::new(0, 0), - matrix: [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000], - width: FixedPointNumber::new(0, 0), - height: FixedPointNumber::new(0, 0), - } - } -} - impl BaseBox for TkhdBox { fn box_type(&self) -> BoxType { Self::TYPE @@ -1170,6 +1182,9 @@ impl MdhdBox { /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"mdhd"); + /// 未定義を表す言語コード + pub const LANGUAGE_UNDEFINED: [u8; 3] = *b"und"; + fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; if self.full_box_version() == 1 { @@ -1199,7 +1214,13 @@ impl MdhdBox { fn decode_payload(reader: &mut std::io::Take) -> Result { let full_header = FullBoxHeader::decode(reader)?; - let mut this = Self::default(); + let mut this = Self { + creation_time: Default::default(), + modification_time: Default::default(), + timescale: Default::default(), + duration: Default::default(), + language: Default::default(), + }; if full_header.version == 1 { this.creation_time = u64::decode(reader).map(Mp4FileTime::from_secs)?; @@ -1243,18 +1264,6 @@ impl Decode for MdhdBox { } } -impl Default for MdhdBox { - fn default() -> Self { - Self { - creation_time: Mp4FileTime::default(), - modification_time: Mp4FileTime::default(), - timescale: 0, - duration: 0, - language: *b"und", // undefined - } - } -} - impl BaseBox for MdhdBox { fn box_type(&self) -> BoxType { Self::TYPE @@ -1471,13 +1480,16 @@ impl BaseBox for MinfBox { #[derive(Debug, Default, Clone, PartialEq, Eq)] #[allow(missing_docs)] pub struct SmhdBox { - pub balance: i16, + pub balance: FixedPointNumber, } impl SmhdBox { /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"smhd"); + /// [`SmhdBox::balance`] のデフォルト値(中央) + pub const DEFAULT_BALANCE: FixedPointNumber = FixedPointNumber::new(0, 0); + fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; self.balance.encode(writer)?; @@ -1487,7 +1499,7 @@ impl SmhdBox { fn decode_payload(reader: &mut std::io::Take) -> Result { let _full_header = FullBoxHeader::decode(reader)?; - let balance = i16::decode(reader)?; + let balance = FixedPointNumber::decode(reader)?; let _ = <[u8; 2]>::decode(reader)?; Ok(Self { balance }) } @@ -1545,6 +1557,12 @@ impl VmhdBox { /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"vmhd"); + /// [`Vmhd::graphicsmode`] のデフォルト値(コピー) + pub const DEFAULT_GRAPHICSMODE: u16 = 0; + + /// [`Vmhd::graphicsmode`] のデフォルト値 + pub const DEFAULT_OPCOLOR: [u16; 3] = [0, 0, 0]; + fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; self.graphicsmode.encode(writer)?; @@ -1553,7 +1571,14 @@ impl VmhdBox { } fn decode_payload(reader: &mut std::io::Take) -> Result { - let _full_header = FullBoxHeader::decode(reader)?; + let full_header = FullBoxHeader::decode(reader)?; + if full_header.flags.get() != 1 { + return Err(Error::invalid_data(&format!( + "Unexpected FullBox header flags of 'vmhd' box: {}", + full_header.flags.get() + ))); + } + let graphicsmode = u16::decode(reader)?; let opcolor = <[u16; 3]>::decode(reader)?; Ok(Self { @@ -1615,6 +1640,12 @@ impl DinfBox { /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"dinf"); + /// メディアデータが同じファイル内に格納されていることを示す [`DinfBox`] の値 + pub const LOCAL_FILE: Self = Self { + dref_box: DrefBox::LOCAL_FILE, + unknown_boxes: Vec::new(), + }; + fn encode_payload(&self, writer: &mut W) -> Result<()> { self.dref_box.encode(writer)?; for b in &self.unknown_boxes { @@ -1691,6 +1722,12 @@ impl DrefBox { /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"dref"); + /// メディアデータが同じファイル内に格納されていることを示す [`DrefBox`] の値 + pub const LOCAL_FILE: Self = Self { + url_box: Some(UrlBox::LOCAL_FILE), + unknown_boxes: Vec::new(), + }; + fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; let entry_count = (self.url_box.is_some() as usize + self.unknown_boxes.len()) as u32; @@ -1727,15 +1764,6 @@ impl DrefBox { } } -impl Default for DrefBox { - fn default() -> Self { - Self { - url_box: Some(UrlBox::default()), - unknown_boxes: Vec::new(), - } - } -} - impl Encode for DrefBox { fn encode(&self, writer: &mut W) -> Result<()> { BoxHeader::from_box(self).encode(writer)?; @@ -1781,7 +1809,7 @@ impl FullBox for DrefBox { } /// [ISO/IEC 14496-12] DataEntryUrlBox class (親: [`DrefBox`]) -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[allow(missing_docs)] pub struct UrlBox { pub location: Option, @@ -1791,6 +1819,9 @@ impl UrlBox { /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"url "); + /// メディアデータが同じファイル内に格納されていることを示す [`UrlBox`] の値 + pub const LOCAL_FILE: Self = Self { location: None }; + fn encode_payload(&self, writer: &mut W) -> Result<()> { FullBoxHeader::from_box(self).encode(writer)?; if let Some(l) = &self.location { @@ -2146,19 +2177,21 @@ pub struct VisualSampleEntryFields { pub depth: u16, } -impl Default for VisualSampleEntryFields { - fn default() -> Self { - Self { - data_reference_index: 1, - width: 0, - height: 0, - horizresolution: FixedPointNumber::new(0x48, 0), // 72 dpi - vertresolution: FixedPointNumber::new(0x48, 0), // 72 dpi - frame_count: 1, - compressorname: [0; 32], - depth: 0x0018, // images are in colour with no alpha - } - } +impl VisualSampleEntryFields { + /// [`VisualSampleEntryFields::horizresolution`] のデフォルト値 (72 dpi) + pub const DEFAULT_HORIZRESOLUTION: FixedPointNumber = FixedPointNumber::new(0x48, 0); + + /// [`VisualSampleEntryFields::vertresolution`] のデフォルト値 (72 dpi) + pub const DEFAULT_VERTRESOLUTION: FixedPointNumber = FixedPointNumber::new(0x48, 0); + + /// [`VisualSampleEntryFields::frame_count`] のデフォルト値 (1) + pub const DEFAULT_FRAME_COUNT: u16 = 1; + + /// [`VisualSampleEntryFields::depth`] のデフォルト値 (images are in colour with no alpha) + pub const DEFAULT_DEPTH: u16 = 0x0018; + + /// 名前なしを表す [`VisualSampleEntryFields::compressorname`] の値 + pub const NULL_COMPRESSORNAME: [u8; 32] = [0; 32]; } impl Encode for VisualSampleEntryFields { @@ -2427,23 +2460,6 @@ impl AvccBox { } } -impl Default for AvccBox { - fn default() -> Self { - Self { - avc_profile_indication: 0, - profile_compatibility: 0, - avc_level_indication: 0, - length_size_minus_one: Uint::from_bits(0), - sps_list: Vec::new(), - pps_list: Vec::new(), - chroma_format: None, - bit_depth_luma_minus8: None, - bit_depth_chroma_minus8: None, - sps_ext_list: Vec::new(), - } - } -} - impl Encode for AvccBox { fn encode(&self, writer: &mut W) -> Result<()> { BoxHeader::from_box(self).encode(writer)?; @@ -3116,11 +3132,11 @@ impl Av1cBox { /// ボックス種別 pub const TYPE: BoxType = BoxType::Normal(*b"av1C"); - const MARKER: u8 = 1; - const VERSION: u8 = 1; + const MARKER: Uint = Uint::new(1); + const VERSION: Uint = Uint::new(1); fn encode_payload(&self, writer: &mut W) -> Result<()> { - ((Self::MARKER << 7) | Self::VERSION).encode(writer)?; + (Self::MARKER.to_bits() | Self::VERSION.to_bits()).encode(writer)?; (self.seq_profile.to_bits() | self.seq_level_idx_0.to_bits()).encode(writer)?; (self.seq_tier_0.to_bits() | self.high_bitdepth.to_bits() @@ -3141,13 +3157,15 @@ impl Av1cBox { fn decode_payload(reader: &mut std::io::Take) -> Result { let b = u8::decode(reader)?; - if (b >> 7) != Self::MARKER { + let marker = Uint::from_bits(b); + let version = Uint::from_bits(b); + if marker != Self::MARKER { return Err(Error::invalid_data("Unexpected av1C marker")); } - if (b & 0b0111_1111) != Self::VERSION { + if version != Self::VERSION { return Err(Error::invalid_data(&format!( "Unsupported av1C version: {}", - b & 0b0111_1111 + version.get() ))); } @@ -3789,15 +3807,9 @@ pub struct AudioSampleEntryFields { pub samplerate: FixedPointNumber, } -impl Default for AudioSampleEntryFields { - fn default() -> Self { - Self { - data_reference_index: 1, - channelcount: 1, - samplesize: 16, - samplerate: FixedPointNumber::new(0, 0), - } - } +impl AudioSampleEntryFields { + /// [`AudioSampleEntryFields::sample_size`] のデフォルト値 (16) + pub const DEFAULT_SAMPLESIZE: u16 = 16; } impl Encode for AudioSampleEntryFields { @@ -3886,17 +3898,6 @@ impl DopsBox { } } -impl Default for DopsBox { - fn default() -> Self { - Self { - output_channel_count: 1, - pre_skip: 0, - input_sample_rate: 0, - output_gain: 0, - } - } -} - impl Encode for DopsBox { fn encode(&self, writer: &mut W) -> Result<()> { BoxHeader::from_box(self).encode(writer)?; From f02c2605e7a25956f417057885840647f37f009c Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 10:28:13 +0900 Subject: [PATCH 094/103] =?UTF-8?q?GitHub=20Actions=20=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 94 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5fb8a2e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md + +name: CI + +on: [push] + +jobs: + check: + name: Check + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: [stable, beta, nightly] + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install ${{ matrix.toolchain }} toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + override: true + + - name: Run cargo check + uses: actions-rs/cargo@v1 + with: + command: check + args: --all + + - name: Run cargo check (no default features) + uses: actions-rs/cargo@v1 + with: + command: check + args: --all --no-default-features + + test: + name: Test Suite + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: [stable, beta, nightly] + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install ${{ matrix.toolchain }} toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + override: true + + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all + + lints: + name: Lints + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: [stable, beta, nightly] + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install ${{ matrix.toolchain }} toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + override: true + components: rustfmt, clippy + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all -- -D warnings + + - name: Run cargo clippy (no default features) + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all --no-default-features -- -D warnings From adc7852d260fbbbc2cf6e8c45092a6b4628624e4 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 10:32:04 +0900 Subject: [PATCH 095/103] =?UTF-8?q?CI=20=E3=81=AE=20slack=20=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fb8a2e..35cb308 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,3 +92,17 @@ jobs: with: command: clippy args: --all --no-default-features -- -D warnings + slack_notify_failed: + needs: [check, test, lints] + runs-on: ubuntu-latest + if: ${{ failure() }} + steps: + - name: Slack Notification + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_CHANNEL: hisui + SLACK_COLOR: danger + SLACK_ICON_EMOJI: ":japanese_ogre:" + SLACK_TITLE: "FAILED" + SLACK_MESSAGE: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{github.event.head_commit.message || 'Scheduled run'}}> + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} From 47f5f37e9fd7df85d1026898200de2bb41985171 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 10:36:55 +0900 Subject: [PATCH 096/103] Fix lint warnings --- src/basic_types.rs | 4 ++-- src/boxes.rs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/basic_types.rs b/src/basic_types.rs index 64fcba8..48c9360 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -276,7 +276,7 @@ impl FullBoxFlags { } /// `(ビット位置、フラグがセットされているかどうか)` のイテレーターを受け取って、対応するビットフラグを作成する - pub fn from_iter(iter: I) -> Self + pub fn from_flags(iter: I) -> Self where I: IntoIterator, { @@ -597,7 +597,7 @@ where /// `T` が保持するビット列の `OFFSET` 位置から `BITS` 分のビット列に対応する整数値を返す pub fn from_bits(v: T) -> Self { - Self((v >> OFFSET) & (T::from(1) << BITS) - T::from(1)) + Self((v >> OFFSET) & ((T::from(1) << BITS) - T::from(1))) } /// このインスタンスに対応する `T` 内のビット列を返す diff --git a/src/boxes.rs b/src/boxes.rs index 2536d2e..5459932 100644 --- a/src/boxes.rs +++ b/src/boxes.rs @@ -873,7 +873,7 @@ impl FullBox for TkhdBox { } fn full_box_flags(&self) -> FullBoxFlags { - FullBoxFlags::from_iter([ + FullBoxFlags::from_flags([ (0, self.flag_track_enabled), (1, self.flag_track_in_movie), (2, self.flag_track_in_preview), @@ -2346,16 +2346,16 @@ impl AvccBox { self.avc_profile_indication.encode(writer)?; self.profile_compatibility.encode(writer)?; self.avc_level_indication.encode(writer)?; - (0b111111_00 | self.length_size_minus_one.get()).encode(writer)?; + (0b1111_1100 | self.length_size_minus_one.get()).encode(writer)?; let sps_count = u8::try_from(self.sps_list.len()).map_err(|_| Error::invalid_input("Too many SPSs"))?; - (0b111_00000 | sps_count).encode(writer)?; + (0b1110_0000 | sps_count).encode(writer)?; for sps in &self.sps_list { let size = u16::try_from(sps.len()) .map_err(|e| Error::invalid_input(&format!("Too long SPS: {e}")))?; size.encode(writer)?; - writer.write_all(&sps)?; + writer.write_all(sps)?; } let pps_count = @@ -2365,7 +2365,7 @@ impl AvccBox { let size = u16::try_from(pps.len()) .map_err(|e| Error::invalid_input(&format!("Too long PPS: {e}")))?; size.encode(writer)?; - writer.write_all(&pps)?; + writer.write_all(pps)?; } if !matches!(self.avc_profile_indication, 66 | 77 | 88) { @@ -2378,9 +2378,9 @@ impl AvccBox { let bit_depth_chroma_minus8 = self.bit_depth_chroma_minus8.ok_or_else(|| { Error::invalid_input("Missing 'bit_depth_chroma_minus8' field in 'avcC' boc") })?; - (0b111111_00 | chroma_format.get()).encode(writer)?; - (0b11111_000 | bit_depth_luma_minus8.get()).encode(writer)?; - (0b11111_000 | bit_depth_chroma_minus8.get()).encode(writer)?; + (0b1111_1100 | chroma_format.get()).encode(writer)?; + (0b1111_1000 | bit_depth_luma_minus8.get()).encode(writer)?; + (0b1111_1000 | bit_depth_chroma_minus8.get()).encode(writer)?; let sps_ext_count = u8::try_from(self.sps_ext_list.len()) .map_err(|_| Error::invalid_input("Too many SPS EXTs"))?; @@ -2389,7 +2389,7 @@ impl AvccBox { let size = u16::try_from(sps_ext.len()) .map_err(|e| Error::invalid_input(&format!("Too long SPS EXT: {e}")))?; size.encode(writer)?; - writer.write_all(&sps_ext)?; + writer.write_all(sps_ext)?; } } From 170abc4bde0ff9757088e56846c7ac7860955f53 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 11:20:36 +0900 Subject: [PATCH 097/103] Add example crate --- Cargo.lock | 94 +++++++++++++++++++++++++++++++++++ Cargo.toml | 3 ++ examples/dump_wasm/Cargo.toml | 13 +++++ examples/dump_wasm/src/lib.rs | 63 +++++++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 examples/dump_wasm/Cargo.toml create mode 100644 examples/dump_wasm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d15daf7..ae79a18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,100 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "dump_wasm" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "shiguredo_mp4", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "shiguredo_mp4" version = "0.1.0" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" diff --git a/Cargo.toml b/Cargo.toml index 0ae2ce7..c9b6f36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] + +[workspace] +members = ["examples/dump_wasm"] diff --git a/examples/dump_wasm/Cargo.toml b/examples/dump_wasm/Cargo.toml new file mode 100644 index 0000000..c847cc1 --- /dev/null +++ b/examples/dump_wasm/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dump_wasm" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +serde = { version = "1.0.210", features = ["derive"] } +serde_json = "1.0.128" +shiguredo_mp4 = { "path" = "../../" } diff --git a/examples/dump_wasm/src/lib.rs b/examples/dump_wasm/src/lib.rs new file mode 100644 index 0000000..f5507c3 --- /dev/null +++ b/examples/dump_wasm/src/lib.rs @@ -0,0 +1,63 @@ +use serde::Serialize; +use shiguredo_mp4::{boxes::RootBox, BaseBox, Decode, Mp4File}; + +#[derive(Debug, Serialize)] +struct BoxInfo { + #[serde(rename = "type")] + pub ty: String, + pub size: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub unknown: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub childre: Vec, +} + +impl BoxInfo { + fn new(b: &dyn BaseBox) -> Self { + Self { + ty: b.box_type().to_string(), + size: b.box_size().get(), + unknown: b.is_unknown_box().then_some(true), + childre: b.children().map(Self::new).collect(), + } + } +} + +#[no_mangle] +#[expect(clippy::not_unsafe_ptr_arg_deref)] +pub fn dump(bytes: *const u8, bytes_len: i32) -> *mut Vec { + let bytes = unsafe { std::slice::from_raw_parts(bytes, bytes_len as usize) }; + + let json = Mp4File::::decode(&mut &bytes[..]) + .map_err(|e| e.to_string()) + .and_then(|mp4| { + let infos = mp4.iter().map(BoxInfo::new).collect::>(); + serde_json::to_string(&infos).map_err(|e| e.to_string()) + }) + .unwrap_or_else(|e| e); + + Box::into_raw(Box::new(json.into_bytes())) +} + +#[no_mangle] +#[expect(clippy::not_unsafe_ptr_arg_deref)] +pub fn vec_offset(v: *mut Vec) -> *mut u8 { + unsafe { &mut *v }.as_mut_ptr() +} + +#[no_mangle] +#[expect(clippy::not_unsafe_ptr_arg_deref)] +pub fn vec_len(v: *mut Vec) -> i32 { + unsafe { &*v }.len() as i32 +} + +#[no_mangle] +pub fn allocate_vec(len: i32) -> *mut Vec { + Box::into_raw(Box::new(vec![0; len as usize])) +} + +#[no_mangle] +#[expect(clippy::not_unsafe_ptr_arg_deref)] +pub fn free_vec(v: *mut Vec) { + let _ = unsafe { Box::from_raw(v) }; +} From bf81996d6b6adfa9b14fec7d2060801bb9bb27ba Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 11:44:05 +0900 Subject: [PATCH 098/103] Add sample page --- examples/dump_wasm/dump_wasm.wasm | 1 + examples/dump_wasm/index.html | 60 +++++++++++++++++++++++++++++++ examples/dump_wasm/src/lib.rs | 2 +- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 120000 examples/dump_wasm/dump_wasm.wasm create mode 100644 examples/dump_wasm/index.html diff --git a/examples/dump_wasm/dump_wasm.wasm b/examples/dump_wasm/dump_wasm.wasm new file mode 120000 index 0000000..882a9f5 --- /dev/null +++ b/examples/dump_wasm/dump_wasm.wasm @@ -0,0 +1 @@ +../../target/wasm32-unknown-unknown/release/dump_wasm.wasm \ No newline at end of file diff --git a/examples/dump_wasm/index.html b/examples/dump_wasm/index.html new file mode 100644 index 0000000..47fb1ec --- /dev/null +++ b/examples/dump_wasm/index.html @@ -0,0 +1,60 @@ + + + + MP4 Dump + + + +

MP4 Dump

+ +
shiguredo/mp4-rust の WebAssembly ビルドのサンプルページ。 +
+ アップロードされた MP4 ファイルをパースして、その構造を JSON 形式で出力する。 +

+ + [NOTE] +
    +
  • 出力 JSON 内のボックスの出現順序は入力ファイルでの順序を反映していないことがある
  • +
  • ライブラリが未対応でペイロードのパースが行われなかったボックスは `"unknown": true` が結果に含まれる +
+
+ +

入力 MP4 ファイル

+ + +

出力 JSON

+ + + + + diff --git a/examples/dump_wasm/src/lib.rs b/examples/dump_wasm/src/lib.rs index f5507c3..026d391 100644 --- a/examples/dump_wasm/src/lib.rs +++ b/examples/dump_wasm/src/lib.rs @@ -32,7 +32,7 @@ pub fn dump(bytes: *const u8, bytes_len: i32) -> *mut Vec { .map_err(|e| e.to_string()) .and_then(|mp4| { let infos = mp4.iter().map(BoxInfo::new).collect::>(); - serde_json::to_string(&infos).map_err(|e| e.to_string()) + serde_json::to_string_pretty(&infos).map_err(|e| e.to_string()) }) .unwrap_or_else(|e| e); From 55c229461268888ed5f9c2228159ce52d12551e3 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 11:47:36 +0900 Subject: [PATCH 099/103] Add deploy.yml --- .github/workflows/deploy.yml | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0eac511 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,46 @@ +name: GitHub Pages Deploy + +# Controls when the workflow will run +on: + push: + branches: [ "develop" ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install wasm32-unknown-unknown target + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + - uses: actions-rs/cargo@v1 + with: + command: build + args: --release --target wasm32-unknown-unknown -p dump_wasm + - name: Prepare static files + run: | + mkdir -p _site/examples/dump/ + cp examples/dump_wasm/index.html _site/examples/dump/ + cp target/wasm32-unknown-unknown/release/dump_wasm.wasm _site/examples/dump/ + - name: Upload files + uses: actions/upload-pages-artifact@v1 + + deploy: + needs: build + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 From db572400bd69934d30bab4b9ff1f4fa30d7001a2 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 11:50:18 +0900 Subject: [PATCH 100/103] Update HTML --- examples/dump_wasm/index.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/dump_wasm/index.html b/examples/dump_wasm/index.html index 47fb1ec..5f1418d 100644 --- a/examples/dump_wasm/index.html +++ b/examples/dump_wasm/index.html @@ -7,15 +7,15 @@

MP4 Dump

- shiguredo/mp4-rust の WebAssembly ビルドのサンプルページ。 + shiguredo/mp4-rust の WebAssembly ビルドのサンプルページです。
- アップロードされた MP4 ファイルをパースして、その構造を JSON 形式で出力する。 + アップロードされた MP4 ファイルをパースして、その構造を JSON 形式で出力します。

- [NOTE] + [注意点]
    -
  • 出力 JSON 内のボックスの出現順序は入力ファイルでの順序を反映していないことがある
  • -
  • ライブラリが未対応でペイロードのパースが行われなかったボックスは `"unknown": true` が結果に含まれる +
  • 出力 JSON 内のボックスの出現順序は入力ファイルでの順序を反映していないことがあります
  • +
  • ライブラリが未対応でペイロードのパースが行われなかったボックスは `"unknown": true` が結果に含まれます
From e081acd89e1efbcd871f25b2760326741a751b57 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 11:55:10 +0900 Subject: [PATCH 101/103] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index a0c4043..177d657 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,14 @@ [![Documentation](https://docs.rs/shiguredo_mp4/badge.svg)](https://docs.rs/shiguredo_mp4) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +Rust で実装された MP4 ファイルを読み書きするためのライブラリです。 + +## WebAssembly サンプルページ + +WebAssembly を使ったサンプルを GitHub Pages に用意しています。 + +- [MP4 Dump](https://shiguredo.github.io/mp4-rust/examples/dump/) + ## About Shiguredo's open source software We will not respond to PRs or issues that have not been discussed on Discord. Also, Discord is only available in Japanese. From be325fd12d0fd95912825ed9b276813c0648785c Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 11:56:58 +0900 Subject: [PATCH 102/103] Add CHANGES.md --- CHANGES.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CHANGES.md diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..6290f1a --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,16 @@ +# 変更履歴 + +- UPDATE + - 後方互換がある変更 +- ADD + - 後方互換がある追加 +- CHANGE + - 後方互換のない変更 +- FIX + - バグ修正 + +## develop + +## 2024.1.0 + +**公開** From bf4989f3078f7bfa3d948c35935fa17ee7f42418 Mon Sep 17 00:00:00 2001 From: Takeru Ohta Date: Wed, 25 Sep 2024 11:59:29 +0900 Subject: [PATCH 103/103] Update Cargo.toml --- Cargo.lock | 2 +- Cargo.toml | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae79a18..1ac82f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "shiguredo_mp4" -version = "0.1.0" +version = "2024.1.0" [[package]] name = "syn" diff --git a/Cargo.toml b/Cargo.toml index c9b6f36..934ce23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "shiguredo_mp4" -version = "0.1.0" +version = "2024.1.0" edition = "2021" +authors = ["Shiguredo Inc."] +license = "Apache-2.0" +description = "MP4 library" +homepage = "https://github.com/shiguredo/mp4-rust" +repository = "https://github.com/shiguredo/mp4-rust" +readme = "README.md" [dependencies]