Skip to content

Commit 5cc753f

Browse files
authored
Release mp4forge 0.8.0 with mux and streaming media support
Release `mp4forge` 0.8.0. Added the feature-gated mux surface with sync/async library APIs, a sync CLI route, flat and fragmented MP4 writing, split init/media output, destination update-or-create behavior, broad path-first import coverage, direct-ingest inspection, sample readers, and elementary sample rewrite/export helpers. Expanded typed box/sample-entry support, divide/probe media coverage, examples, retained fixtures, tests, and fuzz targets. Improved supported streaming decrypt rewrite paths and refreshed affected top-level `sidx` data after clear-output rewrites. Updated release-facing docs and metadata for `0.8.0`, and removed deprecated `dump` shorthands in favor of explicit `-full` options.
1 parent 0a910d9 commit 5cc753f

163 files changed

Lines changed: 147323 additions & 1908 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ body:
1313
attributes:
1414
label: mp4forge Version
1515
description: Which version are you using?
16-
placeholder: "0.7.0"
16+
placeholder: "0.8.0"
1717
validations:
1818
required: true
1919

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,39 @@
1+
# 0.8.0 (May 26, 2026)
2+
3+
- Added the feature-gated mux release surface, including `MuxRequest`, path-first
4+
`MuxTrackSpec` parsing with selector suffixes, flat and fragmented output layout selection,
5+
segment or fragment duration modes, sync `mux_to_path`, `mux_into_path`, and
6+
`mux_fragmented_to_paths` helpers, Tokio async companions behind `mux` plus `async`, and the
7+
sync-only `mux` CLI route
8+
- Added real MP4 mux writing for both flat and fragmented layouts on one shared mux event graph,
9+
with deterministic payload planning, chunk interleaving, duration-based chunking, sync-aligned
10+
fragment or segment boundaries, split init/media fragmented outputs, destination update or
11+
create-new behavior, preserved flat destination metadata where supported, and explicit
12+
validation for unsupported duration-mode or fragmented multi-video requests
13+
- Added broad mux import coverage for MP4 track import with retained sample-entry bytes, selected
14+
AVI, MPEG-PS, MPEG-TS, DASH, NHML, NHNT, SAF, and VobSub-style inputs, plus demux-backed
15+
elementary or containerized audio, video, text, subtitle, image, raw-video, and PCM families
16+
across the public path-first request shape
17+
- Added `mp4forge::mux::inspect`, `mp4forge::mux::sample_reader`, and
18+
`mp4forge::mux::rewrite` helpers for direct-ingest reporting, packet-focused exports,
19+
one-sample-at-a-time seekable or progressive reading, and elementary sample rewrite/export
20+
flows for AVC, HEVC, VVC, AV1, AAC ADTS, and MHAS data
21+
- Expanded typed box and sample-entry coverage needed by the mux, probe, divide, and rewrite
22+
paths, including additional DTS, Dolby, IAMF, FLAC, MPEG-H, PCM, uncompressed-visual,
23+
codec-configuration, timing, and metadata structures
24+
- Broadened `divide` and probe behavior for more video and audio sample-entry families while
25+
keeping the existing validation-first workflow for unsupported fragmented output shapes
26+
- Reworked file-backed decrypt rewrite execution to use streaming root rewrite plans for the
27+
supported sync and async surfaces, refreshed affected top-level `sidx` data after clear-output
28+
rewrites, and kept progress-enabled decrypt surfaces byte-aligned with their non-progress
29+
companions
30+
- Added mux-focused examples, expanded retained fixture coverage, moved test output helpers onto
31+
auto-cleaned temporary paths, and added fuzz targets for mux demuxing, sample rewrites, async
32+
API parity, decrypt byte-surface parity, extra box roundtrips, bit I/O boundaries, and `sidx`
33+
plan application
34+
- Removed the deprecated `dump` command `-mdat` and `-free` shorthand options; use
35+
`-full mdat` or `-full free,skip` explicitly instead
36+
137
# 0.7.0 (April 28, 2026)
238

339
- Added the feature-gated decryption release surface across sync library helpers, Tokio async

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
[package]
22
name = "mp4forge"
3-
version = "0.7.0"
3+
version = "0.8.0"
44
edition = "2024"
55
rust-version = "1.88"
66
authors = ["bakgio"]
77
license = "MIT OR Apache-2.0"
8-
description = "Rust library and CLI for inspecting, probing, extracting, and rewriting MP4 box structures"
8+
description = "Rust library and CLI for inspecting, probing, extracting, muxing, and rewriting MP4 structures"
99
repository = "https://github.com/bakgio/mp4forge"
1010
readme = "README.md"
1111
keywords = ["mp4", "isobmff", "parser", "video", "cli"]
@@ -20,14 +20,17 @@ rustdoc-args = ["--cfg", "docsrs"]
2020
default = []
2121
async = ["dep:tokio"]
2222
decrypt = ["dep:aes"]
23+
mux = []
2324
serde = ["dep:serde"]
2425

2526
[dependencies]
2627
aes = { version = "0.8", optional = true }
28+
miniz_oxide = "0.8"
2729
serde = { version = "1", features = ["derive"], optional = true }
2830
terminal_size = "0.4"
2931
tokio = { version = "1.52.1", features = ["fs", "io-util", "rt", "rt-multi-thread", "macros"], optional = true }
3032

3133
[dev-dependencies]
3234
aes = "0.8"
3335
serde_json = "1"
36+
tempfile = "3"

README.md

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<p align="center">
22
<h1 align="center">mp4forge</h1>
33
<p align="center">
4-
Rust library and CLI for inspecting, extracting, probing, and rewriting MP4 box structures.
4+
Rust library and CLI for inspecting, extracting, probing, muxing, and rewriting MP4 structures.
55
</p>
66
<p align="center">
77
<a href="https://crates.io/crates/mp4forge"><img src="https://img.shields.io/crates/v/mp4forge.svg" alt="Crates.io"></a>
@@ -21,20 +21,22 @@
2121
- Thin typed path-based helpers and byte-slice convenience wrappers for common extraction, rewrite, and probe flows
2222
- Fragmented top-level `sidx` analysis, planning, and rewrite APIs for supported layouts
2323
- Feature-gated decryption APIs and a sync-only `decrypt` CLI for the supported protected MP4 families
24-
- Built-in CLI for `decrypt`, `dump`, `extract`, `probe`, `psshdump`, `edit`, and `divide`
24+
- Built-in CLI for `decrypt`, `divide`, `dump`, `edit`, `extract`, `mux`, `probe`, and `psshdump`
2525
- Shared-fixture coverage for regular MP4, fragmented MP4, encrypted init segments, QuickTime-style metadata cases, and derived real codec fixtures for additional codec-family coverage
2626

2727
## Installation
2828

2929
```toml
3030
[dependencies]
31-
mp4forge = "0.7.0"
31+
mp4forge = "0.8.0"
3232

3333
# With optional features:
34-
# mp4forge = { version = "0.7.0", features = ["async"] }
35-
# mp4forge = { version = "0.7.0", features = ["decrypt"] }
36-
# mp4forge = { version = "0.7.0", features = ["decrypt", "async"] }
37-
# mp4forge = { version = "0.7.0", features = ["serde"] }
34+
# mp4forge = { version = "0.8.0", features = ["async"] }
35+
# mp4forge = { version = "0.8.0", features = ["decrypt"] }
36+
# mp4forge = { version = "0.8.0", features = ["decrypt", "async"] }
37+
# mp4forge = { version = "0.8.0", features = ["mux"] }
38+
# mp4forge = { version = "0.8.0", features = ["mux", "async"] }
39+
# mp4forge = { version = "0.8.0", features = ["serde"] }
3840
```
3941

4042
Install the CLI from crates.io:
@@ -68,6 +70,14 @@ feature flags:
6870
IPMP ACBC and ACGK OD-track movies, and the retained IAEC protected-movie path. When combined
6971
with `async`, it also enables the additive file-backed Tokio async decrypt companions, while the
7072
CLI remains on the synchronous path.
73+
- `mux`: enables the additive mux task surface and the retained low-level helpers underneath it.
74+
The library path covers the narrow public `MuxRequest` model with repeated track specs plus
75+
optional `segment_duration` or `fragment_duration`, real `ftyp`/`moov`/`mdat` writing for sync
76+
callers, additive async real-container writing when combined with `async`, internal chunk and
77+
duration coordination on one mux event graph, the retained low-level seekable and progressive
78+
payload assembly helpers, and one-sample-at-a-time seekable or progressive readers. It also
79+
enables the sync-only `mux` CLI route for one output MP4 built from repeated
80+
path-first `--track` inputs.
7181
- `serde`: derives `Serialize` and `Deserialize` for the reusable public report structs under
7282
`mp4forge::cli::probe` and `mp4forge::cli::dump`, along with their nested public codec-detail,
7383
media-characteristics, `FieldValue`, and `FourCc` data. This is intended for library-side report
@@ -85,6 +95,7 @@ COMMAND:
8595
dump display the MP4 box tree
8696
edit rewrite selected boxes
8797
extract extract raw boxes by type or path
98+
mux merge one video track plus audio, text, and subtitle tracks into one MP4
8899
psshdump summarize pssh boxes
89100
probe summarize an MP4 file
90101
```
@@ -94,9 +105,37 @@ sync-only, accepts repeated `--key ID:KEY`, optional `--fragments-info FILE`, an
94105
`--show-progress`, and reuses the same library decryption surface that backs the feature-gated
95106
sync and async APIs.
96107

97-
`divide` currently targets fragmented inputs with up to one AVC video track and one MP4A audio
98-
track, including encrypted wrappers that preserve those original sample-entry formats. Pass
99-
`-validate` when you want the same probe-driven layout checks without creating any output files.
108+
`mux` is available when the crate is built with `--features mux`. The CLI route stays sync-only
109+
and accepts repeated `--track` inputs, one required positional output path, and at most one of
110+
`--segment_duration` or `--fragment_duration`. The current public `--track` grammar is path-first:
111+
`PATH` imports one raw source or every supported track from one MP4 source, while
112+
`PATH#video`, `PATH#audio`, `PATH#audio:N`, `PATH#text`, `PATH#text:N`, and `PATH#track:ID`
113+
select one specific track from a containerized source. The landed path-only auto-detection
114+
currently covers MP4, supported AVI audio streams plus H.263/JPEG/PNG/MPEG-4 Part 2/H.264/AVC1 video streams, supported
115+
MPEG-PS MPEG audio streams plus LPCM audio plus MPEG-4 Part 2/H.264/H.265/VVC video streams, supported MPEG-TS
116+
MPEG audio streams plus AAC LATM/MHAS plus AC-3/E-AC-3/AC-4/DTS/TrueHD audio plus MPEG-2/AV1/AVS3/MPEG-4 Part 2/H.264/H.265/VVC video streams, AAC
117+
ADTS, MP3, AC-3, E-AC-3, AC-4, AMR, AMR-WB, QCP voice audio, DTS core audio, AAC LATM, Dolby
118+
TrueHD, leading-sync MHAS MPEG-H, IAMF, H.263 elementary video, MPEG-2 elementary video, MPEG-4 Part 2 elementary video,
119+
H.264 Annex B, H.265 Annex B, VVC Annex B, raw AV1 OBU, raw AV1 Annex B, IVF-backed AV1, IVF-backed VP8, IVF-backed VP9,
120+
JPEG still images, PNG still images, WAVE/AIFF/AIFC PCM, native FLAC, Ogg-backed FLAC,
121+
Ogg-backed Opus, Ogg-backed Vorbis, Ogg-backed Speex, Ogg-backed Theora, and CAF-backed ALAC.
122+
Broader DTS-family
123+
sample-entry variants remain supported through MP4 track import, and the broader demux-backed
124+
path-only families continue to move over behind the same public shape.
125+
MP4-track merges continue to cover the broader registered sample-entry families because they
126+
preserve encoded sample-entry bytes from the source file, and mixed video/audio/text/subtitle jobs
127+
retain imported handler names and languages on the real MP4 path. The matching sync and async
128+
library entry points use the same `MuxRequest` surface, while the retained lower-level mux helpers
129+
remain available separately when you need staged planning or payload-copy behavior without the
130+
task-level request layer. The public `mp4forge::mux::sample_reader` helpers can also expose stable
131+
text or subtitle track identity when you construct them with companion `MuxTrackConfig` values.
132+
133+
`divide` currently targets fragmented inputs with up to one video track from AVC, HEVC, Dolby
134+
Vision on HEVC, AV1, VP8, or VP9 and one audio track from MP4A-based audio, Opus, AC-3,
135+
E-AC-3, AC-4, ALAC, DTS-family entries, FLAC, IAMF, MPEG-H, or PCM, including encrypted wrappers
136+
that preserve those original sample-entry formats. Subtitle and text tracks remain unsupported in
137+
the current divide output model. Pass `-validate` when you want the same probe-driven layout
138+
checks without creating any output files.
100139

101140
`dump` defaults to the existing human-readable tree view. Pass `-format json` or `-format yaml` for
102141
deterministic structured tree export with stable `payload_fields` for supported boxes; `-full` and
@@ -124,8 +163,9 @@ per-chunk, bitrate, and IDR aggregation, or use `mp4forge::probe::ProbeOptions`
124163
when you need the same control programmatically.
125164

126165
> See the [`examples/`](./examples) directory for the crate's low-level and high-level API usage
127-
> patterns, including the feature-gated decrypt example and the Tokio-based async library example
128-
> behind the optional `async` feature.
166+
> patterns, including the feature-gated decrypt example, the feature-gated real-mux and
167+
> mux/sample-reader examples, and the Tokio-based async library example behind the optional
168+
> `async` feature.
129169
130170
## License
131171

examples/inspect_mux_boundaries.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#[cfg(feature = "mux")]
2+
fn main() {
3+
use mp4forge::mux::{MuxInterleavePolicy, MuxStagedMediaItem, plan_staged_media_items};
4+
5+
let plan = plan_staged_media_items(
6+
vec![
7+
MuxStagedMediaItem::new(0, 1, 0, 1024, 4096, 2048).with_sync_sample(true),
8+
MuxStagedMediaItem::new(1, 2, 512, 512, 2048, 1024),
9+
],
10+
MuxInterleavePolicy::DecodeTime,
11+
)
12+
.unwrap();
13+
14+
for item in plan.planned_items() {
15+
println!(
16+
"track {} decode [{}..{}) output [{}..{})",
17+
item.staged().track_id(),
18+
item.staged().decode_time(),
19+
item.decode_end_time(),
20+
item.output_offset(),
21+
item.output_end_offset()
22+
);
23+
}
24+
}
25+
26+
#[cfg(not(feature = "mux"))]
27+
fn main() {
28+
eprintln!("enable the `mux` feature to run this example");
29+
}

examples/mux_fragment_duration.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#[cfg(feature = "mux")]
2+
#[path = "support/mux_example_support.rs"]
3+
mod mux_example_support;
4+
5+
#[cfg(feature = "mux")]
6+
use std::error::Error;
7+
8+
#[cfg(feature = "mux")]
9+
use mp4forge::mux::{
10+
MuxDurationMode, MuxMp4TrackSelector, MuxOutputLayout, MuxRequest, MuxTrackSpec, mux_to_path,
11+
};
12+
13+
#[cfg(feature = "mux")]
14+
fn main() -> Result<(), Box<dyn Error>> {
15+
let audio_input = mux_example_support::build_audio_input_file(
16+
"example-fragment-audio",
17+
mux_example_support::fourcc("dash"),
18+
"mp4a",
19+
&[b"one", b"two", b"three"],
20+
);
21+
let output_path = mux_example_support::write_temp_file("example-fragment-output", "mp4", &[]);
22+
23+
let request = MuxRequest::new(vec![MuxTrackSpec::mp4(
24+
&audio_input,
25+
MuxMp4TrackSelector::Audio { occurrence: 1 },
26+
)])
27+
.with_output_layout(MuxOutputLayout::Fragmented)
28+
.with_duration_mode(MuxDurationMode::Fragment { seconds: 0.05 });
29+
30+
mux_to_path(&request, &output_path)?;
31+
println!("wrote {}", output_path.display());
32+
Ok(())
33+
}
34+
35+
#[cfg(not(feature = "mux"))]
36+
fn main() {
37+
eprintln!("Enable the `mux` feature to run this example.");
38+
}

examples/mux_raw_tracks.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#[cfg(feature = "mux")]
2+
#[path = "support/mux_example_support.rs"]
3+
mod mux_example_support;
4+
5+
#[cfg(feature = "mux")]
6+
use std::error::Error;
7+
8+
#[cfg(feature = "mux")]
9+
use mp4forge::mux::{MuxRequest, MuxTrackSpec, mux_to_path};
10+
11+
#[cfg(feature = "mux")]
12+
fn main() -> Result<(), Box<dyn Error>> {
13+
let audio_input = mux_example_support::write_test_flac_file("example-raw-audio", b"flac-frame");
14+
let first_video_frame = mux_example_support::build_test_av1_sequence_header_obu(640, 360);
15+
let second_video_frame = mux_example_support::build_test_av1_sequence_header_obu(640, 360);
16+
let video_input = mux_example_support::write_test_av1_ivf_file(
17+
"example-raw-video",
18+
640,
19+
360,
20+
&[0, 1],
21+
&[&first_video_frame, &second_video_frame],
22+
);
23+
let output_path = mux_example_support::write_temp_file("example-raw-output", "mp4", &[]);
24+
25+
let request = MuxRequest::new(vec![
26+
MuxTrackSpec::path(&audio_input),
27+
MuxTrackSpec::path(&video_input),
28+
]);
29+
30+
mux_to_path(&request, &output_path)?;
31+
println!("wrote {}", output_path.display());
32+
Ok(())
33+
}
34+
35+
#[cfg(not(feature = "mux"))]
36+
fn main() {
37+
eprintln!("Enable the `mux` feature to run this example.");
38+
}

examples/mux_segment_duration.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#[cfg(feature = "mux")]
2+
#[path = "support/mux_example_support.rs"]
3+
mod mux_example_support;
4+
5+
#[cfg(feature = "mux")]
6+
use std::error::Error;
7+
8+
#[cfg(feature = "mux")]
9+
use mp4forge::mux::{
10+
MuxDurationMode, MuxMp4TrackSelector, MuxOutputLayout, MuxRequest, MuxTrackSpec, mux_to_path,
11+
};
12+
13+
#[cfg(feature = "mux")]
14+
fn main() -> Result<(), Box<dyn Error>> {
15+
let audio_input = mux_example_support::build_audio_input_file(
16+
"example-segment-audio",
17+
mux_example_support::fourcc("dash"),
18+
"mp4a",
19+
&[b"one", b"two", b"three"],
20+
);
21+
let output_path = mux_example_support::write_temp_file("example-segment-output", "mp4", &[]);
22+
23+
let request = MuxRequest::new(vec![MuxTrackSpec::mp4(
24+
&audio_input,
25+
MuxMp4TrackSelector::Audio { occurrence: 1 },
26+
)])
27+
.with_output_layout(MuxOutputLayout::Fragmented)
28+
.with_duration_mode(MuxDurationMode::Segment { seconds: 0.05 });
29+
30+
mux_to_path(&request, &output_path)?;
31+
println!("wrote {}", output_path.display());
32+
Ok(())
33+
}
34+
35+
#[cfg(not(feature = "mux"))]
36+
fn main() {
37+
eprintln!("Enable the `mux` feature to run this example.");
38+
}

examples/mux_subtitle_tracks.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#[cfg(feature = "mux")]
2+
#[path = "support/mux_example_support.rs"]
3+
mod mux_example_support;
4+
5+
#[cfg(feature = "mux")]
6+
use std::error::Error;
7+
8+
#[cfg(feature = "mux")]
9+
use mp4forge::mux::{MuxMp4TrackSelector, MuxRequest, MuxTrackSpec, mux_to_path};
10+
11+
#[cfg(feature = "mux")]
12+
fn main() -> Result<(), Box<dyn Error>> {
13+
let video_input = mux_example_support::build_video_input_file(
14+
"example-subtitle-video",
15+
mux_example_support::fourcc("isom"),
16+
"avc1",
17+
&[b"video"],
18+
);
19+
let audio_input = mux_example_support::build_audio_input_file_with_timing(
20+
"example-subtitle-audio",
21+
mux_example_support::fourcc("dash"),
22+
"mp4a",
23+
1_000,
24+
1_000,
25+
&[b"aud"],
26+
);
27+
let text_input = mux_example_support::build_text_input_file(
28+
"example-subtitle-text",
29+
mux_example_support::fourcc("mp42"),
30+
);
31+
let output_path = mux_example_support::write_temp_file("example-subtitle-output", "mp4", &[]);
32+
33+
let request = MuxRequest::new(vec![
34+
MuxTrackSpec::mp4(&video_input, MuxMp4TrackSelector::Video),
35+
MuxTrackSpec::mp4(&audio_input, MuxMp4TrackSelector::Audio { occurrence: 1 }),
36+
MuxTrackSpec::mp4(&text_input, MuxMp4TrackSelector::Text { occurrence: 1 }),
37+
MuxTrackSpec::mp4(&text_input, MuxMp4TrackSelector::Text { occurrence: 2 }),
38+
]);
39+
40+
mux_to_path(&request, &output_path)?;
41+
println!("wrote {}", output_path.display());
42+
Ok(())
43+
}
44+
45+
#[cfg(not(feature = "mux"))]
46+
fn main() {
47+
eprintln!("Enable the `mux` feature to run this example.");
48+
}

0 commit comments

Comments
 (0)