Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ body:
attributes:
label: mp4forge Version
description: Which version are you using?
placeholder: "0.1.0"
placeholder: "0.2.0"
validations:
required: true

Expand Down
15 changes: 14 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,23 @@ jobs:
echo "new_release=true" >> "$GITHUB_OUTPUT"
fi

semver:
name: Semver Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
with:
toolchain: stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2.9.1
- name: Install cargo-semver-checks
run: cargo install cargo-semver-checks --locked
- run: cargo semver-checks -p mp4forge

release:
name: GitHub Release
runs-on: ubuntu-latest
needs: [fmt, clippy, test, msrv, docs, audit, check-version]
needs: [fmt, clippy, test, msrv, docs, audit, check-version, semver]
if: github.event_name == 'push' && needs.check-version.outputs.new_release == 'true'
permissions:
contents: write
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/target
/target*
/Cargo.lock
/fuzz/Cargo.lock
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 0.2.0 (April 21, 2026)

- Added typed path-based extraction helpers for common read flows: `extract_box_as`, `extract_boxes_as`, and `extract_boxes_as_with_registry`
- Added typed path-based rewrite helpers for common edit flows: `rewrite_box_as`, `rewrite_boxes_as`, and `rewrite_boxes_as_with_registry`
- Improved matched payload diagnostics so extraction and rewrite failures report the path, box type, and byte offset that triggered the error
- Added higher-level examples for the ergonomic helper layer while preserving the existing low-level examples
- Polished public docs, README coverage, packaging metadata, and release validation around the new helper surface

# 0.1.0 (April 21, 2026)

- Initial crate release
76 changes: 0 additions & 76 deletions Cargo.lock

This file was deleted.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[package]
name = "mp4forge"
version = "0.1.0"
version = "0.2.0"
edition = "2024"
rust-version = "1.88"
authors = ["bakgio"]
license = "MIT OR Apache-2.0"
description = "Rust library and CLI for inspecting, extracting, and rewriting MP4 box structures"
description = "Rust library and CLI for inspecting, probing, extracting, and rewriting MP4 box structures"
repository = "https://github.com/bakgio/mp4forge"
readme = "README.md"
keywords = ["mp4", "isobmff", "parser", "video", "cli"]
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@

- Typed MP4 and ISOBMFF box model with registry-backed custom box support
- Low-level traversal, extraction, stringify, probe, and writer APIs
- Thin typed path-based helpers for common extraction and rewrite flows
- Built-in CLI for `dump`, `extract`, `probe`, `psshdump`, `edit`, and `divide`
- Shared-fixture coverage for regular MP4, fragmented MP4, encrypted init segments, and QuickTime-style metadata cases

## Installation

```toml
[dependencies]
mp4forge = "0.1.0"
mp4forge = "0.2.0"
```

Install the CLI from crates.io:
Expand Down Expand Up @@ -68,7 +69,7 @@ mp4forge psshdump encrypted_init.mp4

`mp4forge` currently ships without public Cargo feature flags.

> See the [`examples/`](./examples) directory for structure walking, extraction, probing, writer-backed rewrite, and custom box registration examples.
> See the [`examples/`](./examples) directory for both the low-level and high-level public API story, including typed extraction in `extract_track_ids_typed.rs`, typed rewrite in `rewrite_emsg.rs`, structure walking, probing, writer-backed rewrite, and custom box registration.

## License

Expand Down
38 changes: 38 additions & 0 deletions examples/extract_track_ids_typed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::env;
use std::error::Error;
use std::fs::File;

use mp4forge::FourCc;
use mp4forge::boxes::iso14496_12::Tkhd;
use mp4forge::extract::extract_box_as;
use mp4forge::walk::BoxPath;

fn main() {
if let Err(error) = run() {
eprintln!("{error}");
std::process::exit(1);
}
}

fn run() -> Result<(), Box<dyn Error>> {
let Some(path) = env::args().nth(1) else {
return Err("usage: cargo run --example extract_track_ids_typed -- <input.mp4>".into());
};

let mut file = File::open(path)?;
let headers = extract_box_as::<_, Tkhd>(
&mut file,
None,
BoxPath::from([
FourCc::from_bytes(*b"moov"),
FourCc::from_bytes(*b"trak"),
FourCc::from_bytes(*b"tkhd"),
]),
)?;

for tkhd in headers {
println!("track ID: {}", tkhd.track_id);
}

Ok(())
}
70 changes: 70 additions & 0 deletions examples/rewrite_emsg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::Cursor;

use mp4forge::FourCc;
use mp4forge::boxes::iso14496_12::Emsg;
use mp4forge::rewrite::rewrite_box_as;
use mp4forge::walk::BoxPath;

fn main() {
if let Err(error) = run() {
eprintln!("{error}");
std::process::exit(1);
}
}

fn run() -> Result<(), Box<dyn Error>> {
let Some(output_path) = env::args().nth(1) else {
return Err("usage: cargo run --example rewrite_emsg -- <output.mp4>".into());
};

let input = sample_emsg_file();
let mut reader = Cursor::new(input);
let output = File::create(output_path)?;
rewrite_box_as::<_, _, Emsg, _>(
&mut reader,
output,
BoxPath::from([FourCc::from_bytes(*b"emsg")]),
|emsg| {
emsg.message_data = b"hello world".to_vec();
},
)?;

Ok(())
}

fn sample_emsg_file() -> Vec<u8> {
let mut emsg_payload = vec![0x00, 0x00, 0x00, 0x00];
append_null_terminated_string(&mut emsg_payload, "urn:test");
append_null_terminated_string(&mut emsg_payload, "demo");
append_u32(&mut emsg_payload, 1000);
append_u32(&mut emsg_payload, 0);
append_u32(&mut emsg_payload, 5);
append_u32(&mut emsg_payload, 1);
emsg_payload.extend_from_slice(b"hello");

let mut file = Vec::new();
file.extend_from_slice(&box_bytes("free", &[0x01, 0x02, 0x03]));
file.extend_from_slice(&box_bytes("emsg", &emsg_payload));
file.extend_from_slice(&box_bytes("free", &[0x04, 0x05]));
file
}

fn append_null_terminated_string(dst: &mut Vec<u8>, value: &str) {
dst.extend_from_slice(value.as_bytes());
dst.push(0x00);
}

fn append_u32(dst: &mut Vec<u8>, value: u32) {
dst.extend_from_slice(&value.to_be_bytes());
}

fn box_bytes(box_type: &str, payload: &[u8]) -> Vec<u8> {
let mut box_bytes = Vec::with_capacity(8 + payload.len());
box_bytes.extend_from_slice(&((payload.len() + 8) as u32).to_be_bytes());
box_bytes.extend_from_slice(box_type.as_bytes());
box_bytes.extend_from_slice(payload);
box_bytes
}
1 change: 1 addition & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
artifacts
corpus
target
Cargo.lock
Loading
Loading