aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFelix Kaaman <tmtu@tmtu.ee>2021-01-07 20:32:49 +0100
committerFelix Kaaman <tmtu@tmtu.ee>2021-01-07 20:32:49 +0100
commit9e6e4962f2346a3fbd96ab3e6c331858ef6ec0d1 (patch)
tree14b580295f71243def9c8df9c242c33f9f2c66a8
downloadfmp4-trunk.tar.gz
fmp4-trunk.zip
Initial commitHEADtrunk
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock91
-rw-r--r--Cargo.toml13
-rw-r--r--LICENSE-MPL320
-rw-r--r--README.md102
-rw-r--r--src/boxes/avc1.rs62
-rw-r--r--src/boxes/avcc.rs76
-rw-r--r--src/boxes/co64.rs45
-rw-r--r--src/boxes/dinf.rs26
-rw-r--r--src/boxes/dref.rs47
-rw-r--r--src/boxes/ftyp.rs52
-rw-r--r--src/boxes/hdlr.rs48
-rw-r--r--src/boxes/mdhd.rs48
-rw-r--r--src/boxes/mdia.rs30
-rw-r--r--src/boxes/mehd.rs35
-rw-r--r--src/boxes/mfhd.rs32
-rw-r--r--src/boxes/minf.rs46
-rw-r--r--src/boxes/mod.rs38
-rw-r--r--src/boxes/moof.rs27
-rw-r--r--src/boxes/moov.rs46
-rw-r--r--src/boxes/mvex.rs27
-rw-r--r--src/boxes/mvhd.rs63
-rw-r--r--src/boxes/smhd.rs31
-rw-r--r--src/boxes/stbl.rs36
-rw-r--r--src/boxes/stsc.rs47
-rw-r--r--src/boxes/stsd.rs61
-rw-r--r--src/boxes/stsz.rs42
-rw-r--r--src/boxes/stts.rs46
-rw-r--r--src/boxes/tfdt.rs33
-rw-r--r--src/boxes/tfhd.rs131
-rw-r--r--src/boxes/tkhd.rs62
-rw-r--r--src/boxes/traf.rs45
-rw-r--r--src/boxes/trak.rs28
-rw-r--r--src/boxes/trex.rs47
-rw-r--r--src/boxes/trun.rs179
-rw-r--r--src/boxes/url.rs35
-rw-r--r--src/boxes/vmhd.rs31
-rw-r--r--src/lib.rs129
38 files changed, 2258 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..4f88406
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,91 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "byteorder"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
+
+[[package]]
+name = "bytes"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
+
+[[package]]
+name = "fmp4"
+version = "0.1.0"
+dependencies = [
+ "bitflags",
+ "byteorder",
+ "bytes",
+ "four-cc",
+ "thiserror",
+]
+
+[[package]]
+name = "four-cc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3958af68a31b1d1384d3f39b6aa33eb14b6009065b5ca305ddd9712a4237124f"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..1746d9f
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "fmp4"
+version = "0.1.0"
+authors = ["Felix Kaaman <tmtu@tmtu.ee>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[dependencies]
+byteorder = "1.3"
+four-cc = "0.1"
+bitflags = "1.0"
+thiserror = "1.0"
+bytes = "0.5"
diff --git a/LICENSE-MPL b/LICENSE-MPL
new file mode 100644
index 0000000..053e137
--- /dev/null
+++ b/LICENSE-MPL
@@ -0,0 +1,320 @@
+Mozilla Public License, version 2.0
+
+Copyright (c) 2021, Felix Kaaman
+
+1. Definitions
+
+ 1.1. “Contributor” means each individual or legal entity that
+ creates, contributes to the creation of, or owns Covered Software.
+
+ 1.2. “Contributor Version” means the combination of the
+ Contributions of others (if any) used by a Contributor and that
+ particular Contributor’s Contribution.
+
+ 1.3. “Contribution” means Covered Software of a particular
+ Contributor.
+
+ 1.4. “Covered Software” means Source Code Form to which the initial
+ Contributor has attached the notice in Exhibit A, the Executable
+ Form of such Source Code Form, and Modifications of such Source Code
+ Form, in each case including portions thereof.
+
+ 1.5. “Incompatible With Secondary Licenses” means
+
+ that the initial Contributor has attached the notice described in
+ Exhibit B to the Covered Software; or
+
+ that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the terms
+ of a Secondary License.
+
+ 1.6. “Executable Form” means any form of the work other than Source
+ Code Form.
+
+ 1.7. “Larger Work” means a work that combines Covered Software with
+ other material, in a separate file or files, that is not Covered
+ Software.
+
+ 1.8. “License” means this document.
+
+ 1.9. “Licensable” means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or
+ subsequently, any and all of the rights conveyed by this License.
+
+ 1.10. “Modifications” means any of the following:
+
+ any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered Software;
+ or
+
+ any new file in Source Code Form that contains any Covered Software.
+
+ 1.11. “Patent Claims” of a Contributor means any patent claim(s),
+ including without limitation, method, process, and apparatus claims,
+ in any patent Licensable by such Contributor that would be
+ infringed, but for the grant of the License, by the making, using,
+ selling, offering for sale, having made, import, or transfer of
+ either its Contributions or its Contributor Version.
+
+ 1.12. “Secondary License” means either the GNU General Public
+ License, Version 2.0, the GNU Lesser General Public License, Version
+ 2.1, the GNU Affero General Public License, Version 3.0, or any
+ later versions of those licenses.
+
+ 1.13. “Source Code Form” means the form of the work preferred for
+ making modifications.
+
+ 1.14. “You” (or “Your”) means an individual or a legal entity
+ exercising rights under this License. For legal entities, “You”
+ includes any entity that controls, is controlled by, or is under
+ common control with You. For purposes of this definition, “control”
+ means (a) the power, direct or indirect, to cause the direction or
+ management of such entity, whether by contract or otherwise, or (b)
+ ownership of more than fifty percent (50%) of the outstanding shares
+ or beneficial ownership of such entity.
+
+
+2. License Grants and Conditions
+
+ 2.1. Grants Each Contributor hereby grants You a world-wide,
+ royalty-free, non-exclusive license:
+
+ under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+ under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+ 2.2. Effective Date The licenses granted in Section 2.1 with respect
+ to any Contribution become effective for each Contribution on the
+ date the Contributor first distributes such Contribution.
+
+ 2.3. Limitations on Grant Scope The licenses granted in this Section
+ 2 are the only rights granted under this License. No additional
+ rights or licenses will be implied from the distribution or
+ licensing of Covered Software under this License. Notwithstanding
+ Section 2.1(b) above, no patent license is granted by a Contributor:
+
+ for any code that a Contributor has removed from Covered Software;
+ or
+
+ for infringements caused by: (i) Your and any other third party’s
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+ under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+ This License does not grant any rights in the trademarks, service
+ marks, or logos of any Contributor (except as may be necessary to
+ comply with the notice requirements in Section 3.4).
+
+ 2.4. Subsequent Licenses No Contributor makes additional grants as a
+ result of Your choice to distribute the Covered Software under a
+ subsequent version of this License (see Section 10.2) or under the
+ terms of a Secondary License (if permitted under the terms of
+ Section 3.3).
+
+ 2.5. Representation Each Contributor represents that the Contributor
+ believes its Contributions are its original creation(s) or it has
+ sufficient rights to grant the rights to its Contributions conveyed
+ by this License.
+
+ 2.6. Fair Use This License is not intended to limit any rights You
+ have under applicable copyright doctrines of fair use, fair dealing,
+ or other equivalents.
+
+ 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of
+ the licenses granted in Section 2.1.
+
+
+3. Responsibilities
+
+ 3.1. Distribution of Source Form All distribution of Covered
+ Software in Source Code Form, including any Modifications that You
+ create or to which You contribute, must be under the terms of this
+ License. You must inform recipients that the Source Code Form of the
+ Covered Software is governed by the terms of this License, and how
+ they can obtain a copy of this License. You may not attempt to alter
+ or restrict the recipients’ rights in the Source Code Form.
+
+ 3.2. Distribution of Executable Form If You distribute Covered
+ Software in Executable Form then:
+
+ such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+ You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients’ rights in the Source Code Form under this License.
+
+ 3.3. Distribution of a Larger Work You may create and distribute a
+ Larger Work under terms of Your choice, provided that You also
+ comply with the requirements of this License for the Covered
+ Software. If the Larger Work is a combination of Covered Software
+ with a work governed by one or more Secondary Licenses, and the
+ Covered Software is not Incompatible With Secondary Licenses, this
+ License permits You to additionally distribute such Covered Software
+ under the terms of such Secondary License(s), so that the recipient
+ of the Larger Work may, at their option, further distribute the
+ Covered Software under the terms of either this License or such
+ Secondary License(s).
+
+ 3.4. Notices You may not remove or alter the substance of any
+ license notices (including copyright notices, patent notices,
+ disclaimers of warranty, or limitations of liability) contained
+ within the Source Code Form of the Covered Software, except that You
+ may alter any license notices to the extent required to remedy known
+ factual inaccuracies.
+
+ 3.5. Application of Additional Terms You may choose to offer, and to
+ charge a fee for, warranty, support, indemnity or liability
+ obligations to one or more recipients of Covered Software. However,
+ You may do so only on Your own behalf, and not on behalf of any
+ Contributor. You must make it absolutely clear that any such
+ warranty, support, indemnity, or liability obligation is offered by
+ You alone, and You hereby agree to indemnify every Contributor for
+ any liability incurred by such Contributor as a result of warranty,
+ support, indemnity or liability terms You offer. You may include
+ additional disclaimers of warranty and limitations of liability
+ specific to any jurisdiction.
+
+
+4. Inability to Comply Due to Statute or Regulation
+
+ If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Software due to
+ statute, judicial order, or regulation then You must: (a) comply
+ with the terms of this License to the maximum extent possible; and
+ (b) describe the limitations and the code they affect. Such
+ description must be placed in a text file included with all
+ distributions of the Covered Software under this License. Except to
+ the extent prohibited by statute or regulation, such description
+ must be sufficiently detailed for a recipient of ordinary skill to
+ be able to understand it.
+
+
+5. Termination
+
+ 5.1. The rights granted under this License will terminate
+ automatically if You fail to comply with any of its terms. However,
+ if You become compliant, then the rights granted under this License
+ from a particular Contributor are reinstated (a) provisionally,
+ unless and until such Contributor explicitly and finally terminates
+ Your grants, and (b) on an ongoing basis, if such Contributor fails
+ to notify You of the non-compliance by some reasonable means prior
+ to 60 days after You have come back into compliance. Moreover, Your
+ grants from a particular Contributor are reinstated on an ongoing
+ basis if such Contributor notifies You of the non-compliance by some
+ reasonable means, this is the first time You have received notice of
+ non-compliance with this License from such Contributor, and You
+ become compliant prior to 30 days after Your receipt of the notice.
+
+ 5.2. If You initiate litigation against any entity by asserting a
+ patent infringement claim (excluding declaratory judgment actions,
+ counter-claims, and cross-claims) alleging that a Contributor
+ Version directly or indirectly infringes any patent, then the rights
+ granted to You by any and all Contributors for the Covered Software
+ under Section 2.1 of this License shall terminate.
+
+ 5.3. In the event of termination under Sections 5.1 or 5.2 above,
+ all end user license agreements (excluding distributors and
+ resellers) which have been validly granted by You or Your
+ distributors under this License prior to termination shall survive
+ termination.
+
+6. Disclaimer of Warranty Covered Software is provided under this
+License on an “as is” basis, without warranty of any kind, either
+expressed, implied, or statutory, including, without limitation,
+warranties that the Covered Software is free of defects, merchantable,
+fit for a particular purpose or non-infringing. The entire risk as to
+the quality and performance of the Covered Software is with You.
+Should any Covered Software prove defective in any respect, You (not
+any Contributor) assume the cost of any necessary servicing, repair,
+or correction. This disclaimer of warranty constitutes an essential
+part of this License. No use of any Covered Software is authorized
+under this License except under this disclaimer.
+
+7. Limitation of Liability Under no circumstances and under no legal
+theory, whether tort (including negligence), contract, or otherwise,
+shall any Contributor, or anyone who distributes Covered Software as
+permitted above, be liable to You for any direct, indirect, special,
+incidental, or consequential damages of any character including,
+without limitation, damages for lost profits, loss of goodwill, work
+stoppage, computer failure or malfunction, or any and all other
+commercial damages or losses, even if such party shall have been
+informed of the possibility of such damages. This limitation of
+liability shall not apply to liability for death or personal injury
+resulting from such party’s negligence to the extent applicable law
+prohibits such limitation. Some jurisdictions do not allow the
+exclusion or limitation of incidental or consequential damages, so
+this exclusion and limitation may not apply to You.
+
+8. Litigation Any litigation relating to this License may be brought
+only in the courts of a jurisdiction where the defendant maintains its
+principal place of business and such litigation shall be governed by
+laws of that jurisdiction, without reference to its conflict-of-law
+provisions. Nothing in this Section shall prevent a party’s ability to
+bring cross-claims or counter-claims.
+
+9. Miscellaneous This License represents the complete agreement
+concerning the subject matter hereof. If any provision of this License
+is held to be unenforceable, such provision shall be reformed only to
+the extent necessary to make it enforceable. Any law or regulation
+which provides that the language of a contract shall be construed
+against the drafter shall not be used to construe this License against
+a Contributor.
+
+10. Versions of the License
+
+ 10.1. New Versions Mozilla Foundation is the license steward. Except
+ as provided in Section 10.3, no one other than the license steward
+ has the right to modify or publish new versions of this License.
+ Each version will be given a distinguishing version number.
+
+ 10.2. Effect of New Versions You may distribute the Covered Software
+ under the terms of the version of the License under which You
+ originally received the Covered Software, or under the terms of any
+ subsequent version published by the license steward.
+
+ 10.3. Modified Versions If you create software not governed by this
+ License, and you want to create a new license for such software, you
+ may create and use a modified version of this License if you rename
+ the license and remove any references to the name of the license
+ steward (except to note that such modified license differs from this
+ License).
+
+ 10.4. Distributing Source Code Form that is Incompatible With
+ Secondary Licenses If You choose to distribute Source Code Form that
+ is Incompatible With Secondary Licenses under the terms of this
+ version of the License, the notice described in Exhibit B of this
+ License must be attached.
+
+Exhibit A - Source Code Form License Notice
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ If it is not possible or desirable to put the notice in a particular
+ file, then You may include the notice in a location (such as a
+ LICENSE file in a relevant directory) where a recipient would be
+ likely to look for such a notice.
+
+ You may add additional accurate notices of copyright ownership.
+
+Exhibit B - “Incompatible With Secondary Licenses” Notice
+
+ This Source Code Form is “Incompatible With Secondary Licenses”, as
+ defined by the Mozilla Public License, v. 2.0.
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..35c2fcb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,102 @@
+# fmp4
+
+## fragmented MP4 / fantastic MP4 / fabulous MP4
+
+Crate for writing primarily fragmented MP4 files according to [ISO BMFF](https://w3c.github.io/mse-byte-stream-format-isobmff/).
+
+# Features
+
+[x] H.264/AVC Video
+[ ] H.265/HEVC Video
+[ ] AAC Audio
+
+# Usage
+
+```rust
+use fmp4::*;
+
+let ftyp = FileTypeBox::new(
+ FourCC(*b"isom"),
+ 0,
+ vec![FourCC(*b"isom"), FourCC(*b"iso5"), FourCC(*b"dash")],
+);
+
+let moov = MovieBox {
+ mvhd: MovieHeaderBox {
+ creation_time: 0,
+ modification_time: 0,
+ timescale: 90000,
+ duration: 0,
+ },
+ mvex: Some(MovieExtendsBox {
+ mehd: MovieExtendsHeaderBox {
+ fragment_duration: 0,
+ },
+ trex: TrackExtendsBox {
+ track_id: 1,
+ default_sample_description_index: 0,
+ default_sample_duration: 0,
+ default_sample_size: 0,
+ default_sample_flags: 0,
+ },
+ }),
+ tracks: vec![
+ TrackBox {
+ tkhd: TrackHeaderBox {
+ creation_time: 0,
+ modification_time: 0,
+ track_id: 1,
+ duration: 0,
+ },
+ mdia: MediaBox {
+ mdhd: MediaHeaderBox {
+ creation_time: 0,
+ modification_time: 0,
+ timescale: 90000,
+ duration: 0,
+ },
+ hdlr: HandlerBox {
+ handler_type: 0x76696465,
+ name: String::from("Video Handler"),
+ },
+ minf: MediaInformationBox {
+ media_header: MediaHeader::Video(VideoMediaHeaderBox {}),
+ dinf: DataInformationBox {
+ dref: DataReferenceBox {
+ entries: vec![DataEntryUrlBox {
+ location: String::from(""),
+ }],
+ },
+ },
+ stbl: SampleTableBox {
+ stsd: SampleDescriptionBox {
+ entries: Vec::new(),
+ },
+ stts: TimeToSampleBox {
+ entries: Vec::new(),
+ },
+ stsc: SampleToChunkBox {
+ entries: Vec::new(),
+ },
+ stsz: SampleSizeBox {
+ sample_sizes: Vec::new(),
+ },
+ co64: ChunkLargeOffsetBox {
+ chunk_offsets: Vec::new(),
+ },
+ },
+ },
+ },
+ }
+ ],
+};
+
+let mut bytes = bytes::BytesMut::with_capacity(1024);
+
+ftyp.write(&mut bytes)?;
+moov.write(&mut bytes)?;
+```
+
+# License
+
+fmp4 is licensed under the [Mozilla Public License](LICENSE-MPL).
diff --git a/src/boxes/avc1.rs b/src/boxes/avc1.rs
new file mode 100644
index 0000000..dc306e5
--- /dev/null
+++ b/src/boxes/avc1.rs
@@ -0,0 +1,62 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::AvcConfigurationBox;
+
+use std::mem::size_of;
+
+pub struct AvcSampleEntryBox {
+ pub width: u16,
+ pub height: u16,
+ pub avcc: AvcConfigurationBox,
+}
+
+impl Mp4Box for AvcSampleEntryBox {
+ const NAME: FourCC = FourCC(*b"avc1");
+
+ fn content_size(&self) -> u64 {
+ size_of::<u8>() as u64 * 6
+ + size_of::<u16>() as u64
+ + size_of::<u8>() as u64 * 16
+ + size_of::<u16>() as u64
+ + size_of::<u16>() as u64
+ + size_of::<u32>() as u64
+ + size_of::<u32>() as u64
+ + size_of::<u8>() as u64 * 4
+ + size_of::<u16>() as u64
+ + size_of::<u8>() as u64 * 32
+ + size_of::<u16>() as u64
+ + size_of::<i16>() as u64
+ + self.avcc.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.extend(&[0u8; 6]);
+ v.write_u16::<BigEndian>(1)?;
+
+ v.extend(&[0u8; 16]);
+
+ v.write_u16::<BigEndian>(self.width)?;
+ v.write_u16::<BigEndian>(self.height)?;
+ v.write_u32::<BigEndian>(0x0048_0000)?;
+ v.write_u32::<BigEndian>(0x0048_0000)?;
+ v.extend(&[0u8; 4]);
+ v.write_u16::<BigEndian>(1)?;
+ v.extend(&[0u8; 32]);
+ v.write_u16::<BigEndian>(0x0018)?;
+ v.write_i16::<BigEndian>(-1)?;
+
+ writer.put_slice(&v);
+
+ self.avcc.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/avcc.rs b/src/boxes/avcc.rs
new file mode 100644
index 0000000..e932543
--- /dev/null
+++ b/src/boxes/avcc.rs
@@ -0,0 +1,76 @@
+use byteorder::{BigEndian, WriteBytesExt};
+
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use std::mem::size_of;
+
+pub struct AvcConfigurationBox {
+ pub config: AvcDecoderConfigurationRecord,
+}
+
+impl Mp4Box for AvcConfigurationBox {
+ const NAME: FourCC = FourCC(*b"avcC");
+
+ fn content_size(&self) -> u64 {
+ self.config.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.config.write(writer)?;
+
+ Ok(())
+ }
+}
+
+pub struct AvcDecoderConfigurationRecord {
+ pub profile_indication: u8,
+ pub profile_compatibility: u8,
+ pub level_indication: u8,
+ pub sequence_parameter_set: Vec<u8>,
+ pub picture_parameter_set: Vec<u8>,
+}
+
+impl AvcDecoderConfigurationRecord {
+ fn size(&self) -> u64 {
+ size_of::<u8>() as u64 // configurationVersion
+ + size_of::<u8>() as u64 // AVCProfileIndication
+ + size_of::<u8>() as u64 // profile_compatibility
+ + size_of::<u8>() as u64 // AVCLevelIndication
+ + size_of::<u8>() as u64 // lengthSizeMinusOne
+ + size_of::<u8>() as u64 // numOfSequenceParameterSets
+ + size_of::<u16>() as u64 // sequenceParameterSetLength
+ + self.sequence_parameter_set.len() as u64
+ + size_of::<u8>() as u64 // numOfPictureParameterSets
+ + size_of::<u16>() as u64 // pictureParameterSetLength
+ + self.picture_parameter_set.len() as u64
+ }
+
+ fn write(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.push(1);
+ v.push(self.profile_indication);
+ v.push(self.profile_compatibility);
+ v.push(self.level_indication);
+ v.push(0b1111_1100 | 3);
+
+ v.push(0b1110_0000 | 1);
+ v.write_u16::<BigEndian>(self.sequence_parameter_set.len() as u16)?;
+ v.extend(&self.sequence_parameter_set);
+
+ v.push(1);
+ v.write_u16::<BigEndian>(self.picture_parameter_set.len() as u16)?;
+ v.extend(&self.picture_parameter_set);
+
+ assert_eq!(self.size(), v.len() as u64);
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/co64.rs b/src/boxes/co64.rs
new file mode 100644
index 0000000..d52c4b0
--- /dev/null
+++ b/src/boxes/co64.rs
@@ -0,0 +1,45 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct SampleToChunkEntry {
+ first_chunk: u32,
+ samples_per_chunk: u32,
+ sample_description_index: u32,
+}
+
+pub struct ChunkLargeOffsetBox {
+ pub chunk_offsets: Vec<u64>,
+}
+
+impl Mp4Box for ChunkLargeOffsetBox {
+ const NAME: FourCC = FourCC(*b"co64");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64 + (size_of::<u64>() as u64) * self.chunk_offsets.len() as u64
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(self.chunk_offsets.len() as u32)?;
+
+ for &chunk_offset in &self.chunk_offsets {
+ v.write_u64::<BigEndian>(chunk_offset)?;
+ }
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/dinf.rs b/src/boxes/dinf.rs
new file mode 100644
index 0000000..cff0f64
--- /dev/null
+++ b/src/boxes/dinf.rs
@@ -0,0 +1,26 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::DataReferenceBox;
+
+pub struct DataInformationBox {
+ pub dref: DataReferenceBox,
+}
+
+impl Mp4Box for DataInformationBox {
+ const NAME: FourCC = FourCC(*b"dinf");
+
+ fn content_size(&self) -> u64 {
+ self.dref.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.dref.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/dref.rs b/src/boxes/dref.rs
new file mode 100644
index 0000000..a962b72
--- /dev/null
+++ b/src/boxes/dref.rs
@@ -0,0 +1,47 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+use super::DataEntryUrlBox;
+
+pub struct DataReferenceBox {
+ pub entries: Vec<DataEntryUrlBox>,
+}
+
+impl Mp4Box for DataReferenceBox {
+ const NAME: FourCC = FourCC(*b"dref");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ let mut size = size_of::<u32>() as u64; // entry_count
+
+ for entry in &self.entries {
+ size += entry.size();
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 4];
+
+ BigEndian::write_u32(&mut contents[..], self.entries.len() as _);
+
+ writer.put_slice(&contents);
+
+ for entry in &self.entries {
+ entry.write(writer)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/ftyp.rs b/src/boxes/ftyp.rs
new file mode 100644
index 0000000..f959e51
--- /dev/null
+++ b/src/boxes/ftyp.rs
@@ -0,0 +1,52 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use std::io::Write;
+use std::mem::size_of;
+
+use crate::{Mp4Box, Mp4BoxError};
+
+pub struct FileTypeBox {
+ major_brand: FourCC,
+ minor_version: u32,
+ compatible_brands: Vec<FourCC>,
+}
+
+impl FileTypeBox {
+ pub fn new(major_brand: FourCC, minor_version: u32, compatible_brands: Vec<FourCC>) -> Self {
+ FileTypeBox {
+ major_brand,
+ minor_version,
+ compatible_brands,
+ }
+ }
+}
+
+impl Mp4Box for FileTypeBox {
+ const NAME: FourCC = FourCC(*b"ftyp");
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64 + // major_brand
+ size_of::<u32>() as u64 + // minor_version
+ size_of::<u32>() as u64 * self.compatible_brands.len() as u64 // compatible_brands
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ Write::write_all(&mut v, &self.major_brand.0)?;
+ v.write_u32::<BigEndian>(self.minor_version)?;
+
+ for brand in &self.compatible_brands {
+ Write::write_all(&mut v, &brand.0)?;
+ }
+
+ assert_eq!(self.content_size() as usize, v.len());
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/hdlr.rs b/src/boxes/hdlr.rs
new file mode 100644
index 0000000..0e8dfc8
--- /dev/null
+++ b/src/boxes/hdlr.rs
@@ -0,0 +1,48 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct HandlerBox {
+ pub handler_type: u32,
+ pub name: String,
+}
+
+impl Mp4Box for HandlerBox {
+ const NAME: FourCC = FourCC(*b"hdlr");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64 + // pre_defined
+ size_of::<u32>() as u64 + // handler_type
+ size_of::<u32>() as u64 * 3 + // reserved
+ self.name.as_bytes().len() as u64 + // name
+ 1
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(0)?;
+ v.write_u32::<BigEndian>(self.handler_type)?;
+ v.write_u32::<BigEndian>(0)?;
+ v.write_u32::<BigEndian>(0)?;
+ v.write_u32::<BigEndian>(0)?;
+ v.extend(self.name.as_bytes());
+ v.push(0);
+
+ assert_eq!(v.len() as u64, self.content_size());
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mdhd.rs b/src/boxes/mdhd.rs
new file mode 100644
index 0000000..5befd67
--- /dev/null
+++ b/src/boxes/mdhd.rs
@@ -0,0 +1,48 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct MediaHeaderBox {
+ pub creation_time: u64,
+ pub modification_time: u64,
+ pub timescale: u32,
+ pub duration: u64,
+}
+
+impl Mp4Box for MediaHeaderBox {
+ const NAME: FourCC = FourCC(*b"mdhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(1, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u64>() as u64 + // creation_time
+ size_of::<u64>() as u64 + // modification_time
+ size_of::<u32>() as u64 + // timescale
+ size_of::<u64>() as u64 + // duration
+ size_of::<u16>() as u64 + // language
+ size_of::<u16>() as u64 // pre_defined
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 32];
+
+ BigEndian::write_u64(&mut contents[..], self.creation_time);
+ BigEndian::write_u64(&mut contents[8..], self.modification_time);
+ BigEndian::write_u32(&mut contents[16..], self.timescale);
+ BigEndian::write_u64(&mut contents[20..], self.duration);
+
+ BigEndian::write_u16(&mut contents[28..], 0); // language
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mdia.rs b/src/boxes/mdia.rs
new file mode 100644
index 0000000..f18a8e9
--- /dev/null
+++ b/src/boxes/mdia.rs
@@ -0,0 +1,30 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::{HandlerBox, MediaHeaderBox, MediaInformationBox};
+
+pub struct MediaBox {
+ pub mdhd: MediaHeaderBox,
+ pub hdlr: HandlerBox,
+ pub minf: MediaInformationBox,
+}
+
+impl Mp4Box for MediaBox {
+ const NAME: FourCC = FourCC(*b"mdia");
+
+ fn content_size(&self) -> u64 {
+ self.mdhd.size() + self.hdlr.size() + self.minf.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.mdhd.write(writer)?;
+ self.hdlr.write(writer)?;
+ self.minf.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mehd.rs b/src/boxes/mehd.rs
new file mode 100644
index 0000000..74082c1
--- /dev/null
+++ b/src/boxes/mehd.rs
@@ -0,0 +1,35 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct MovieExtendsHeaderBox {
+ pub fragment_duration: u64,
+}
+
+impl Mp4Box for MovieExtendsHeaderBox {
+ const NAME: FourCC = FourCC(*b"mehd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(1, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u64>() as u64 // fragment_duration
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 8];
+
+ BigEndian::write_u64(&mut contents[..], self.fragment_duration);
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mfhd.rs b/src/boxes/mfhd.rs
new file mode 100644
index 0000000..f5235c2
--- /dev/null
+++ b/src/boxes/mfhd.rs
@@ -0,0 +1,32 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+pub struct MovieFragmentHeaderBox {
+ pub sequence_number: u32,
+}
+
+impl Mp4Box for MovieFragmentHeaderBox {
+ const NAME: FourCC = FourCC(*b"mfhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ 4
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 4];
+ BigEndian::write_u32(&mut contents, self.sequence_number);
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/minf.rs b/src/boxes/minf.rs
new file mode 100644
index 0000000..70a6cc4
--- /dev/null
+++ b/src/boxes/minf.rs
@@ -0,0 +1,46 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::{DataInformationBox, SampleTableBox, SoundMediaHeaderBox, VideoMediaHeaderBox};
+
+pub enum MediaHeader {
+ Video(VideoMediaHeaderBox),
+ Sound(SoundMediaHeaderBox),
+}
+
+pub struct MediaInformationBox {
+ pub media_header: MediaHeader,
+ pub dinf: DataInformationBox,
+ pub stbl: SampleTableBox,
+}
+
+impl Mp4Box for MediaInformationBox {
+ const NAME: FourCC = FourCC(*b"minf");
+
+ fn content_size(&self) -> u64 {
+ let mut size = self.dinf.size() + self.stbl.size();
+
+ match &self.media_header {
+ MediaHeader::Video(vmhd) => size += vmhd.size(),
+ MediaHeader::Sound(smhd) => size += smhd.size(),
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ match &self.media_header {
+ MediaHeader::Video(vmhd) => vmhd.write(writer)?,
+ MediaHeader::Sound(smhd) => smhd.write(writer)?,
+ }
+
+ self.dinf.write(writer)?;
+ self.stbl.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mod.rs b/src/boxes/mod.rs
new file mode 100644
index 0000000..ce73e5f
--- /dev/null
+++ b/src/boxes/mod.rs
@@ -0,0 +1,38 @@
+mod avc1;
+mod avcc;
+mod co64;
+mod dinf;
+mod dref;
+mod ftyp;
+mod hdlr;
+mod mdhd;
+mod mdia;
+mod mehd;
+mod mfhd;
+mod minf;
+mod moof;
+mod moov;
+mod mvex;
+mod mvhd;
+mod smhd;
+mod stbl;
+mod stsc;
+mod stsd;
+mod stsz;
+mod stts;
+mod tfdt;
+mod tfhd;
+mod tkhd;
+mod traf;
+mod trak;
+mod trex;
+mod trun;
+mod url;
+mod vmhd;
+
+pub use self::{
+ avc1::*, avcc::*, co64::*, dinf::*, dref::*, ftyp::*, hdlr::*, mdhd::*, mdia::*, mehd::*,
+ mfhd::*, minf::*, moof::*, moov::*, mvex::*, mvhd::*, smhd::*, stbl::*, stsc::*, stsd::*,
+ stsz::*, stts::*, tfdt::*, tfhd::*, tkhd::*, traf::*, trak::*, trex::*, trun::*, url::*,
+ vmhd::*,
+};
diff --git a/src/boxes/moof.rs b/src/boxes/moof.rs
new file mode 100644
index 0000000..7e028f5
--- /dev/null
+++ b/src/boxes/moof.rs
@@ -0,0 +1,27 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::{Mp4Box, Mp4BoxError};
+
+use super::{MovieFragmentHeaderBox, TrackFragmentBox};
+
+pub struct MovieFragmentBox {
+ pub mfhd: MovieFragmentHeaderBox,
+ pub traf: TrackFragmentBox,
+}
+
+impl Mp4Box for MovieFragmentBox {
+ const NAME: FourCC = FourCC(*b"moof");
+
+ fn content_size(&self) -> u64 {
+ self.mfhd.size() + self.traf.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.mfhd.write(writer)?;
+ self.traf.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/moov.rs b/src/boxes/moov.rs
new file mode 100644
index 0000000..6140d59
--- /dev/null
+++ b/src/boxes/moov.rs
@@ -0,0 +1,46 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::{MovieExtendsBox, MovieHeaderBox, TrackBox};
+
+pub struct MovieBox {
+ pub mvhd: MovieHeaderBox,
+ pub mvex: Option<MovieExtendsBox>,
+ pub tracks: Vec<TrackBox>,
+}
+
+impl Mp4Box for MovieBox {
+ const NAME: FourCC = FourCC(*b"moov");
+
+ fn content_size(&self) -> u64 {
+ let mut size = self.mvhd.size();
+
+ if let Some(mvex) = &self.mvex {
+ size += mvex.size();
+ }
+
+ for track in &self.tracks {
+ size += track.size();
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.mvhd.write(writer)?;
+
+ if let Some(mvex) = &self.mvex {
+ mvex.write(writer)?;
+ }
+
+ for track in &self.tracks {
+ track.write(writer)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mvex.rs b/src/boxes/mvex.rs
new file mode 100644
index 0000000..ede5da1
--- /dev/null
+++ b/src/boxes/mvex.rs
@@ -0,0 +1,27 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::{Mp4Box, Mp4BoxError};
+
+use super::{MovieExtendsHeaderBox, TrackExtendsBox};
+
+pub struct MovieExtendsBox {
+ pub mehd: MovieExtendsHeaderBox,
+ pub trex: TrackExtendsBox,
+}
+
+impl Mp4Box for MovieExtendsBox {
+ const NAME: FourCC = FourCC(*b"mvex");
+
+ fn content_size(&self) -> u64 {
+ self.mehd.size() + self.trex.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.mehd.write(writer)?;
+ self.trex.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mvhd.rs b/src/boxes/mvhd.rs
new file mode 100644
index 0000000..2f8984c
--- /dev/null
+++ b/src/boxes/mvhd.rs
@@ -0,0 +1,63 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct MovieHeaderBox {
+ pub creation_time: u64,
+ pub modification_time: u64,
+ pub timescale: u32,
+ pub duration: u64,
+}
+
+impl Mp4Box for MovieHeaderBox {
+ const NAME: FourCC = FourCC(*b"mvhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(1, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u64>() as u64 + // creation_time
+ size_of::<u64>() as u64 + // modification_time
+ size_of::<u32>() as u64 + // timescale
+ size_of::<u64>() as u64 + // duration
+ size_of::<u32>() as u64 + // rate
+ size_of::<u16>() as u64 + // volume
+ size_of::<u16>() as u64 + // reserved
+ size_of::<u32>() as u64 * 2 + // reserved
+ size_of::<i32>() as u64 * 9 + // matrix
+ size_of::<u32>() as u64 * 6 + // pre_defined
+ size_of::<u32>() as u64 // next_track_ID
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 108];
+
+ BigEndian::write_u64(&mut contents[..], self.creation_time);
+ BigEndian::write_u64(&mut contents[8..], self.modification_time);
+ BigEndian::write_u32(&mut contents[16..], self.timescale);
+ BigEndian::write_u64(&mut contents[20..], self.duration);
+
+ BigEndian::write_i32(&mut contents[28..], 0x00010000);
+ BigEndian::write_i16(&mut contents[32..], 0x0100);
+
+ BigEndian::write_u16(&mut contents[34..], 0);
+ BigEndian::write_u64(&mut contents[36..], 0);
+
+ BigEndian::write_i32(&mut contents[44..], 0x00010000);
+ BigEndian::write_i32(&mut contents[60..], 0x00010000);
+ BigEndian::write_i32(&mut contents[76..], 0x40000000);
+
+ BigEndian::write_u32(&mut contents[104..], 1);
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/smhd.rs b/src/boxes/smhd.rs
new file mode 100644
index 0000000..dbb3869
--- /dev/null
+++ b/src/boxes/smhd.rs
@@ -0,0 +1,31 @@
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct SoundMediaHeaderBox {}
+
+impl Mp4Box for SoundMediaHeaderBox {
+ const NAME: FourCC = FourCC(*b"smhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u16>() as u64 + // balance
+ size_of::<u16>() as u64 // reserved
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let contents = [0u8; 4];
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/stbl.rs b/src/boxes/stbl.rs
new file mode 100644
index 0000000..1ea41ae
--- /dev/null
+++ b/src/boxes/stbl.rs
@@ -0,0 +1,36 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::{
+ ChunkLargeOffsetBox, SampleDescriptionBox, SampleSizeBox, SampleToChunkBox, TimeToSampleBox,
+};
+
+pub struct SampleTableBox {
+ pub stsd: SampleDescriptionBox,
+ pub stts: TimeToSampleBox,
+ pub stsc: SampleToChunkBox,
+ pub stsz: SampleSizeBox,
+ pub co64: ChunkLargeOffsetBox,
+}
+
+impl Mp4Box for SampleTableBox {
+ const NAME: FourCC = FourCC(*b"stbl");
+
+ fn content_size(&self) -> u64 {
+ self.stsd.size() + self.stts.size() + self.stsc.size() + self.stsz.size() + self.co64.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.stsd.write(writer)?;
+ self.stts.write(writer)?;
+ self.stsc.write(writer)?;
+ self.stsz.write(writer)?;
+ self.co64.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/stsc.rs b/src/boxes/stsc.rs
new file mode 100644
index 0000000..a8bb059
--- /dev/null
+++ b/src/boxes/stsc.rs
@@ -0,0 +1,47 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct SampleToChunkEntry {
+ first_chunk: u32,
+ samples_per_chunk: u32,
+ sample_description_index: u32,
+}
+
+pub struct SampleToChunkBox {
+ pub entries: Vec<SampleToChunkEntry>,
+}
+
+impl Mp4Box for SampleToChunkBox {
+ const NAME: FourCC = FourCC(*b"stsc");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64 + (size_of::<u32>() as u64 * 3) * self.entries.len() as u64
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(self.entries.len() as u32)?;
+
+ for entry in &self.entries {
+ v.write_u32::<BigEndian>(entry.first_chunk)?;
+ v.write_u32::<BigEndian>(entry.samples_per_chunk)?;
+ v.write_u32::<BigEndian>(entry.sample_description_index)?;
+ }
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/stsd.rs b/src/boxes/stsd.rs
new file mode 100644
index 0000000..35ee5b2
--- /dev/null
+++ b/src/boxes/stsd.rs
@@ -0,0 +1,61 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::FullBoxHeader;
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::AvcSampleEntryBox;
+
+use std::mem::size_of;
+
+pub enum SampleEntry {
+ Avc(AvcSampleEntryBox),
+}
+
+impl SampleEntry {
+ fn size(&self) -> u64 {
+ match self {
+ SampleEntry::Avc(avc) => avc.size(),
+ }
+ }
+}
+
+pub struct SampleDescriptionBox {
+ pub entries: Vec<SampleEntry>,
+}
+
+impl Mp4Box for SampleDescriptionBox {
+ const NAME: FourCC = FourCC(*b"stsd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ let mut size = size_of::<u32>() as u64;
+
+ for entry in &self.entries {
+ size += entry.size();
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 4];
+ BigEndian::write_u32(&mut contents, self.entries.len() as _);
+
+ writer.put_slice(&contents);
+
+ for entry in &self.entries {
+ match entry {
+ SampleEntry::Avc(avc) => avc.write(writer)?,
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/stsz.rs b/src/boxes/stsz.rs
new file mode 100644
index 0000000..7dc3eba
--- /dev/null
+++ b/src/boxes/stsz.rs
@@ -0,0 +1,42 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct SampleSizeBox {
+ pub sample_sizes: Vec<u32>,
+}
+
+impl Mp4Box for SampleSizeBox {
+ const NAME: FourCC = FourCC(*b"stsz");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64
+ + size_of::<u32>() as u64
+ + size_of::<u32>() as u64 * self.sample_sizes.len() as u64
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(0)?;
+ v.write_u32::<BigEndian>(self.sample_sizes.len() as u32)?;
+
+ for &size in &self.sample_sizes {
+ v.write_u32::<BigEndian>(size)?;
+ }
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/stts.rs b/src/boxes/stts.rs
new file mode 100644
index 0000000..b956a52
--- /dev/null
+++ b/src/boxes/stts.rs
@@ -0,0 +1,46 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct TimeToSampleEntry {
+ count: u32,
+ delta: u32,
+}
+
+pub struct TimeToSampleBox {
+ pub entries: Vec<TimeToSampleEntry>,
+}
+
+impl Mp4Box for TimeToSampleBox {
+ const NAME: FourCC = FourCC(*b"stts");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64
+ + (size_of::<u32>() as u64 + size_of::<u32>() as u64) * self.entries.len() as u64
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(self.entries.len() as _)?;
+
+ for entry in &self.entries {
+ v.write_u32::<BigEndian>(entry.count)?;
+ v.write_u32::<BigEndian>(entry.delta)?;
+ }
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/tfdt.rs b/src/boxes/tfdt.rs
new file mode 100644
index 0000000..9ea0747
--- /dev/null
+++ b/src/boxes/tfdt.rs
@@ -0,0 +1,33 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::{FullBoxHeader, Mp4Box, Mp4BoxError};
+
+use std::mem::size_of;
+
+pub struct TrackFragmentBaseMediaDecodeTimeBox {
+ pub base_media_decode_time: u64,
+}
+
+impl Mp4Box for TrackFragmentBaseMediaDecodeTimeBox {
+ const NAME: FourCC = FourCC(*b"tfdt");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(1, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u64>() as u64 // base_media_decode_time
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut content = [0u8; 8];
+ BigEndian::write_u64(&mut content[..], self.base_media_decode_time);
+
+ writer.put_slice(&content);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/tfhd.rs b/src/boxes/tfhd.rs
new file mode 100644
index 0000000..45c5301
--- /dev/null
+++ b/src/boxes/tfhd.rs
@@ -0,0 +1,131 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::{FullBoxHeader, Mp4Box, Mp4BoxError};
+
+use std::mem::size_of;
+
+bitflags::bitflags! {
+ pub struct TrackFragmentHeaderFlags: u32 {
+ const BASE_DATA_OFFSET_PRESENT = 0x000001;
+ const SAMPLE_DESCRIPTION_INDEX_PRESENT = 0x000002;
+ const DEFAULT_SAMPLE_DURATION_PRESENT = 0x000008;
+ const DEFAULT_SAMPLE_SIZE_PRESENT = 0x000010;
+ const DEFAULT_SAMPLE_FLAGS_PRESENT = 0x000020;
+ const DURATION_IS_EMPTY = 0x010000;
+ const DEFAULT_BASE_IS_MOOF = 0x020000;
+ }
+}
+
+pub struct TrackFragmentHeaderBox {
+ pub track_id: u32,
+ pub base_data_offset: Option<u64>,
+ pub sample_description_index: Option<u32>,
+ pub default_sample_duration: Option<u32>,
+ pub default_sample_size: Option<u32>,
+ pub default_sample_flags: Option<u32>,
+ pub duration_is_empty: bool,
+ pub default_base_is_moof: bool,
+}
+
+impl TrackFragmentHeaderBox {
+ fn flags_from_fields(&self) -> TrackFragmentHeaderFlags {
+ let mut flags = TrackFragmentHeaderFlags::empty();
+
+ if self.base_data_offset.is_some() {
+ flags.insert(TrackFragmentHeaderFlags::BASE_DATA_OFFSET_PRESENT);
+ }
+
+ if self.sample_description_index.is_some() {
+ flags.insert(TrackFragmentHeaderFlags::SAMPLE_DESCRIPTION_INDEX_PRESENT);
+ }
+
+ if self.default_sample_duration.is_some() {
+ flags.insert(TrackFragmentHeaderFlags::DEFAULT_SAMPLE_DURATION_PRESENT);
+ }
+
+ if self.default_sample_size.is_some() {
+ flags.insert(TrackFragmentHeaderFlags::DEFAULT_SAMPLE_SIZE_PRESENT);
+ }
+
+ if self.default_sample_flags.is_some() {
+ flags.insert(TrackFragmentHeaderFlags::DEFAULT_SAMPLE_FLAGS_PRESENT);
+ }
+
+ if self.duration_is_empty {
+ flags.insert(TrackFragmentHeaderFlags::DURATION_IS_EMPTY);
+ }
+
+ if self.default_base_is_moof {
+ flags.insert(TrackFragmentHeaderFlags::DEFAULT_BASE_IS_MOOF);
+ }
+
+ flags
+ }
+}
+
+impl Mp4Box for TrackFragmentHeaderBox {
+ const NAME: FourCC = FourCC(*b"tfhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, self.flags_from_fields().bits()))
+ }
+
+ fn content_size(&self) -> u64 {
+ let mut size = size_of::<u32>() as u64; // track_ID
+
+ if self.base_data_offset.is_some() {
+ size += size_of::<u64>() as u64;
+ }
+
+ if self.sample_description_index.is_some() {
+ size += size_of::<u32>() as u64;
+ }
+
+ if self.default_sample_duration.is_some() {
+ size += size_of::<u32>() as u64;
+ }
+
+ if self.default_sample_size.is_some() {
+ size += size_of::<u32>() as u64;
+ }
+
+ if self.default_sample_flags.is_some() {
+ size += size_of::<u32>() as u64;
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(self.track_id)?;
+
+ if let Some(base_data_offset) = self.base_data_offset {
+ v.write_u64::<BigEndian>(base_data_offset)?;
+ }
+
+ if let Some(sample_description_index) = self.sample_description_index {
+ v.write_u32::<BigEndian>(sample_description_index)?;
+ }
+
+ if let Some(default_sample_duration) = self.default_sample_duration {
+ v.write_u32::<BigEndian>(default_sample_duration)?;
+ }
+
+ if let Some(default_sample_size) = self.default_sample_size {
+ v.write_u32::<BigEndian>(default_sample_size)?;
+ }
+
+ if let Some(default_sample_flags) = self.default_sample_flags {
+ v.write_u32::<BigEndian>(default_sample_flags)?;
+ }
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/tkhd.rs b/src/boxes/tkhd.rs
new file mode 100644
index 0000000..9519940
--- /dev/null
+++ b/src/boxes/tkhd.rs
@@ -0,0 +1,62 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct TrackHeaderBox {
+ pub creation_time: u64,
+ pub modification_time: u64,
+ pub track_id: u32,
+ pub duration: u64,
+}
+
+impl Mp4Box for TrackHeaderBox {
+ const NAME: FourCC = FourCC(*b"tkhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(1, 0)) // TODO: flags matter
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u64>() as u64 + // creation_time
+ size_of::<u64>() as u64 + // modification_time
+ size_of::<u32>() as u64 + // track_ID
+ size_of::<u32>() as u64 + // reserved
+ size_of::<u64>() as u64 + // duration
+ size_of::<u32>() as u64 * 2 + // reserved
+ size_of::<u16>() as u64 + // layer
+ size_of::<u16>() as u64 + // alternate_group
+ size_of::<u16>() as u64 + // volume
+ size_of::<u16>() as u64 + // reserved
+ size_of::<i32>() as u64 * 9 + // matrix
+ size_of::<u32>() as u64 + // width
+ size_of::<u32>() as u64 // height
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 92];
+
+ BigEndian::write_u64(&mut contents[..], self.creation_time);
+ BigEndian::write_u64(&mut contents[8..], self.modification_time);
+ BigEndian::write_u32(&mut contents[16..], self.track_id);
+ BigEndian::write_u64(&mut contents[24..], self.duration);
+
+ BigEndian::write_i32(&mut contents[44..], 0); // volume
+
+ BigEndian::write_i32(&mut contents[46..], 0x00010000);
+ BigEndian::write_i32(&mut contents[62..], 0x00010000);
+ BigEndian::write_i32(&mut contents[78..], 0x40000000);
+
+ BigEndian::write_u32(&mut contents[84..], 1); // width
+ BigEndian::write_u32(&mut contents[88..], 2); // height
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/traf.rs b/src/boxes/traf.rs
new file mode 100644
index 0000000..42dbea2
--- /dev/null
+++ b/src/boxes/traf.rs
@@ -0,0 +1,45 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::{Mp4Box, Mp4BoxError};
+
+use super::{TrackFragmentBaseMediaDecodeTimeBox, TrackFragmentHeaderBox, TrackFragmentRunBox};
+
+pub struct TrackFragmentBox {
+ pub tfhd: TrackFragmentHeaderBox,
+ pub track_runs: Vec<TrackFragmentRunBox>,
+ pub base_media_decode_time: Option<TrackFragmentBaseMediaDecodeTimeBox>,
+}
+
+impl Mp4Box for TrackFragmentBox {
+ const NAME: FourCC = FourCC(*b"traf");
+
+ fn content_size(&self) -> u64 {
+ let mut size = self.tfhd.size();
+
+ for trun in &self.track_runs {
+ size += trun.size();
+ }
+
+ if let Some(base_media_decode_time) = &self.base_media_decode_time {
+ size += base_media_decode_time.size();
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.tfhd.write(writer)?;
+
+ if let Some(base_media_decode_time) = &self.base_media_decode_time {
+ base_media_decode_time.write(writer)?;
+ }
+
+ for run in &self.track_runs {
+ run.write(writer)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/trak.rs b/src/boxes/trak.rs
new file mode 100644
index 0000000..bb3630f
--- /dev/null
+++ b/src/boxes/trak.rs
@@ -0,0 +1,28 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::{MediaBox, TrackHeaderBox};
+
+pub struct TrackBox {
+ pub tkhd: TrackHeaderBox,
+ pub mdia: MediaBox,
+}
+
+impl Mp4Box for TrackBox {
+ const NAME: FourCC = FourCC(*b"trak");
+
+ fn content_size(&self) -> u64 {
+ self.tkhd.size() + self.mdia.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.tkhd.write(writer)?;
+ self.mdia.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/trex.rs b/src/boxes/trex.rs
new file mode 100644
index 0000000..fa9f496
--- /dev/null
+++ b/src/boxes/trex.rs
@@ -0,0 +1,47 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct TrackExtendsBox {
+ pub track_id: u32,
+ pub default_sample_description_index: u32,
+ pub default_sample_duration: u32,
+ pub default_sample_size: u32,
+ pub default_sample_flags: u32,
+}
+
+impl Mp4Box for TrackExtendsBox {
+ const NAME: FourCC = FourCC(*b"trex");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64 + // track_ID
+ size_of::<u32>() as u64 + // default_sample_description_index
+ size_of::<u32>() as u64 + // default_sample_duration
+ size_of::<u32>() as u64 + // default_sample_size
+ size_of::<u32>() as u64 // default_sample_flags
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 20];
+
+ BigEndian::write_u32(&mut contents[..], self.track_id);
+ BigEndian::write_u32(&mut contents[4..], self.default_sample_description_index);
+ BigEndian::write_u32(&mut contents[8..], self.default_sample_duration);
+ BigEndian::write_u32(&mut contents[12..], self.default_sample_size);
+ BigEndian::write_u32(&mut contents[16..], self.default_sample_flags);
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/trun.rs b/src/boxes/trun.rs
new file mode 100644
index 0000000..f093a41
--- /dev/null
+++ b/src/boxes/trun.rs
@@ -0,0 +1,179 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use std::mem::size_of;
+
+use crate::{FullBoxHeader, Mp4Box, Mp4BoxError};
+
+bitflags::bitflags! {
+ pub struct TrackFragmentRunFlags: u32 {
+ const DATA_OFFSET_PRESENT = 0x00000001;
+ const FIRST_SAMPLE_FLAGS_PRESENT = 0x00000004;
+ const SAMPLE_DURATION_PRESENT = 0x00000100;
+ const SAMPLE_SIZE_PRESENT = 0x00000200;
+ const SAMPLE_FLAGS_PRESENT = 0x00000400;
+ const SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT = 0x00000800;
+ }
+}
+
+pub struct TrackFragmentSample {
+ pub duration: Option<u32>,
+ pub size: Option<u32>,
+ pub flags: Option<u32>,
+ pub composition_time_offset: Option<i32>,
+}
+
+pub struct TrackFragmentRunBox {
+ pub data_offset: Option<i32>,
+ pub first_sample_flags: Option<u32>,
+ pub samples: Vec<TrackFragmentSample>,
+}
+
+impl TrackFragmentRunBox {
+ fn sample_size(&self, flags: TrackFragmentRunFlags) -> u64 {
+ let mut sample_size = 0;
+
+ if flags.contains(TrackFragmentRunFlags::SAMPLE_DURATION_PRESENT) {
+ sample_size += 4; // sample_duration
+ }
+
+ if flags.contains(TrackFragmentRunFlags::SAMPLE_SIZE_PRESENT) {
+ sample_size += 4; // sample_size
+ }
+
+ if flags.contains(TrackFragmentRunFlags::SAMPLE_FLAGS_PRESENT) {
+ sample_size += 4; // sample_flags
+ }
+
+ if flags.contains(TrackFragmentRunFlags::SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT) {
+ sample_size += 4; // sample_composition_time_offset
+ }
+
+ sample_size
+ }
+
+ fn flags_from_fields(&self) -> TrackFragmentRunFlags {
+ let mut flags = TrackFragmentRunFlags::empty();
+
+ if self.data_offset.is_some() {
+ flags.insert(TrackFragmentRunFlags::DATA_OFFSET_PRESENT);
+ }
+
+ if self.first_sample_flags.is_some() {
+ flags.insert(TrackFragmentRunFlags::FIRST_SAMPLE_FLAGS_PRESENT);
+ }
+
+ if let Some(sample) = self.samples.get(0) {
+ if sample.duration.is_some() {
+ flags.insert(TrackFragmentRunFlags::SAMPLE_DURATION_PRESENT);
+ }
+
+ if sample.size.is_some() {
+ flags.insert(TrackFragmentRunFlags::SAMPLE_SIZE_PRESENT);
+ }
+
+ if sample.flags.is_some() {
+ flags.insert(TrackFragmentRunFlags::SAMPLE_FLAGS_PRESENT);
+ }
+
+ if sample.composition_time_offset.is_some() {
+ flags.insert(TrackFragmentRunFlags::SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT);
+ }
+ }
+
+ flags
+ }
+}
+
+impl Mp4Box for TrackFragmentRunBox {
+ const NAME: FourCC = FourCC(*b"trun");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, self.flags_from_fields().bits()))
+ }
+
+ fn content_size(&self) -> u64 {
+ let flags = self.flags_from_fields();
+
+ let mut size = 0;
+
+ size += size_of::<u32>() as u64; // sample_count
+
+ if flags.contains(TrackFragmentRunFlags::DATA_OFFSET_PRESENT) {
+ size += size_of::<i32>() as u64; // data_offset
+ }
+
+ if flags.contains(TrackFragmentRunFlags::FIRST_SAMPLE_FLAGS_PRESENT) {
+ size += size_of::<u32>() as u64; // first_sample_flags
+ }
+
+ size += self.sample_size(flags) * self.samples.len() as u64;
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(self.samples.len() as u32)?; // TODO: check for truncation
+
+ if let Some(data_offset) = self.data_offset {
+ v.write_i32::<BigEndian>(data_offset)?;
+ }
+
+ if let Some(first_sample_flags) = self.first_sample_flags {
+ v.write_u32::<BigEndian>(first_sample_flags)?;
+ }
+
+ let flags = self.flags_from_fields();
+ for sample in &self.samples {
+ ensure_sample_fields_present(&sample, flags);
+
+ if let Some(duration) = sample.duration {
+ v.write_u32::<BigEndian>(duration)?;
+ }
+
+ if let Some(size) = sample.size {
+ v.write_u32::<BigEndian>(size)?;
+ }
+
+ if let Some(flags) = sample.flags {
+ v.write_u32::<BigEndian>(flags)?;
+ }
+
+ if let Some(composition_time_offset) = sample.composition_time_offset {
+ v.write_i32::<BigEndian>(composition_time_offset)?;
+ }
+ }
+
+ assert_eq!(v.len() as u64, self.content_size());
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
+
+fn ensure_sample_fields_present(sample: &TrackFragmentSample, flags: TrackFragmentRunFlags) {
+ let duration_should_be_present = flags.contains(TrackFragmentRunFlags::SAMPLE_DURATION_PRESENT);
+ let size_should_be_present = flags.contains(TrackFragmentRunFlags::SAMPLE_SIZE_PRESENT);
+ let flags_should_be_present = flags.contains(TrackFragmentRunFlags::SAMPLE_FLAGS_PRESENT);
+ let composition_time_offset_should_be_present =
+ flags.contains(TrackFragmentRunFlags::SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT);
+
+ let duration_is_present = sample.duration.is_some();
+ let size_is_present = sample.size.is_some();
+ let flags_is_present = sample.flags.is_some();
+ let composition_time_offset_is_present = sample.composition_time_offset.is_some();
+
+ // TODO: return error
+ assert_eq!(duration_should_be_present, duration_is_present);
+ assert_eq!(size_should_be_present, size_is_present);
+ assert_eq!(flags_should_be_present, flags_is_present);
+ assert_eq!(
+ composition_time_offset_should_be_present,
+ composition_time_offset_is_present
+ );
+}
diff --git a/src/boxes/url.rs b/src/boxes/url.rs
new file mode 100644
index 0000000..91b471d
--- /dev/null
+++ b/src/boxes/url.rs
@@ -0,0 +1,35 @@
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+pub struct DataEntryUrlBox {
+ pub location: String,
+}
+
+impl Mp4Box for DataEntryUrlBox {
+ const NAME: FourCC = FourCC(*b"url ");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0x000001))
+ }
+
+ fn content_size(&self) -> u64 {
+ self.location.as_bytes().len() as u64 + 1
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.extend(self.location.as_bytes());
+ v.push(0);
+
+ assert_eq!(v.len() as u64, self.content_size());
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/vmhd.rs b/src/boxes/vmhd.rs
new file mode 100644
index 0000000..93f8ddf
--- /dev/null
+++ b/src/boxes/vmhd.rs
@@ -0,0 +1,31 @@
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct VideoMediaHeaderBox {}
+
+impl Mp4Box for VideoMediaHeaderBox {
+ const NAME: FourCC = FourCC(*b"vmhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 1))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u16>() as u64 + // graphicsmode
+ (size_of::<u16>() as u64 * 3) // opcolor
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let contents = [0u8; 8];
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..0630a15
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,129 @@
+#![allow(dead_code)]
+
+use std::io;
+
+use byteorder::{BigEndian, ByteOrder};
+use bytes::{BufMut, BytesMut};
+pub use four_cc::FourCC;
+
+mod boxes;
+
+pub use boxes::*;
+
+fn get_total_box_size<B: Mp4Box + ?Sized>(boks: &B) -> u64 {
+ let size = boks.content_size();
+
+ if boks.get_full_box_header().is_some() {
+ size + FullBoxHeader::SIZE + 8
+ } else {
+ size + 8
+ }
+}
+
+fn write_box_header<B: Mp4Box + ?Sized>(header: &mut [u8], size: u64) -> usize {
+ if size > u32::MAX as _ {
+ BigEndian::write_u32(&mut header[..], 1);
+ header[4..8].copy_from_slice(&B::NAME.0);
+ BigEndian::write_u64(&mut header[8..], size);
+
+ 16
+ } else {
+ BigEndian::write_u32(&mut header[..], size as u32);
+ header[4..8].copy_from_slice(&B::NAME.0);
+
+ 8
+ }
+}
+
+fn write_full_box_header(header: &mut [u8], box_header: FullBoxHeader) -> usize {
+ header[0] = box_header.version;
+ BigEndian::write_u24(&mut header[1..], box_header.flags);
+
+ 4
+}
+
+#[derive(Copy, Clone)]
+pub struct FullBoxHeader {
+ version: u8,
+ flags: u32,
+}
+
+impl FullBoxHeader {
+ pub const SIZE: u64 = 4;
+
+ pub fn new(version: u8, flags: u32) -> Self {
+ FullBoxHeader { version, flags }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Mp4BoxError {
+ #[error("Failed to write box: {0}")]
+ IoError(#[from] io::Error),
+}
+
+/// A trait interface for a MP4 box.
+pub trait Mp4Box {
+ const NAME: FourCC;
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ None
+ }
+
+ fn flags(&self) -> Option<u32> {
+ self.get_full_box_header().map(|h| h.flags)
+ }
+
+ /// The size of the contents of the box.
+ fn content_size(&self) -> u64;
+
+ fn size(&self) -> u64 {
+ get_total_box_size::<Self>(&self)
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError>;
+
+ fn write(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut header = [0u8; 20];
+
+ /* println!(
+ "{}: {}/{}",
+ std::any::type_name::<Self>(),
+ self.size(),
+ self.content_size()
+ ); */
+
+ let mut size = write_box_header::<Self>(&mut header, self.size());
+ if let Some(box_header) = self.get_full_box_header() {
+ size += write_full_box_header(&mut header[size..], box_header);
+ }
+
+ writer.put_slice(&header[..size]);
+
+ self.write_box_contents(writer)?;
+
+ Ok(())
+ }
+}
+
+pub struct MediaDataBox<'a> {
+ pub data: &'a [u8],
+}
+
+impl<'a> Mp4Box for MediaDataBox<'a> {
+ const NAME: FourCC = FourCC(*b"mdat");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ self.data.len() as _
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ writer.put_slice(&self.data);
+
+ Ok(())
+ }
+}