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.6.0"
placeholder: "0.7.0"
validations:
required: true

Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
# 0.7.0 (April 28, 2026)

- Added the feature-gated decryption release surface across sync library helpers, Tokio async
file-backed companions, and the sync-only `decrypt` CLI, covering the Common Encryption family,
PIFF compatibility, OMA DCF, Marlin IPMP ACBC and ACGK, and the retained IAEC protected-movie
path
- Added the typed OMA DCF, Marlin, ISMA-IAEC, and descriptor-command box or descriptor support
needed to drive the broader protected-format decryption paths without opaque byte-only shortcuts
- Expanded retained decrypt fixtures, parity harnesses, and cross-surface regression coverage so
sync, async, CLI, fragmented, protected-movie, and broader-format decrypt behavior are locked
against stable checked-in assets and comparison-backed expectations
- Extended the fragmented decrypt path to support multi-sample-entry track layouts with
per-fragment sample-description switching and ordered zero-KID track-key binding, and verified
the resulting clear fragmented output against the existing rebuild workflow
- Closed the older non-fragmented `sample_description_index` gap by preserving chunk-level sample
description identity in shared layout helpers, positively covering valid Marlin layouts above
`1`, and making the retained first-description-only OMA and IAEC protected-movie limit explicit
where the reviewed higher-level behavior still keeps that scope

# 0.6.0 (April 26, 2026)

- Added an additive Tokio-based `async` feature for the library, covering seekable async traversal, extraction, typed codec decode and encode, writer flows, rewrite flows, probe surfaces, and top-level `sidx` helpers while keeping the CLI on the established synchronous path
Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mp4forge"
version = "0.6.0"
version = "0.7.0"
edition = "2024"
rust-version = "1.88"
authors = ["bakgio"]
Expand All @@ -19,12 +19,15 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = []
async = ["dep:tokio"]
decrypt = ["dep:aes"]
serde = ["dep:serde"]

[dependencies]
aes = { version = "0.8", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
terminal_size = "0.4"
tokio = { version = "1.52.1", features = ["fs", "io-util", "rt", "rt-multi-thread", "macros"], optional = true }

[dev-dependencies]
aes = "0.8"
serde_json = "1"
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@
- Low-level traversal, extraction, stringify, probe, and writer APIs
- Thin typed path-based helpers and byte-slice convenience wrappers for common extraction, rewrite, and probe flows
- Fragmented top-level `sidx` analysis, planning, and rewrite APIs for supported layouts
- Built-in CLI for `dump`, `extract`, `probe`, `psshdump`, `edit`, and `divide`
- Feature-gated decryption APIs and a sync-only `decrypt` CLI for the supported protected MP4 families
- Built-in CLI for `decrypt`, `dump`, `extract`, `probe`, `psshdump`, `edit`, and `divide`
- 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

## Installation

```toml
[dependencies]
mp4forge = "0.6.0"
mp4forge = "0.7.0"

# With optional features:
# mp4forge = { version = "0.6.0", features = ["async"] }
# mp4forge = { version = "0.6.0", features = ["serde"] }
# mp4forge = { version = "0.7.0", features = ["async"] }
# mp4forge = { version = "0.7.0", features = ["decrypt"] }
# mp4forge = { version = "0.7.0", features = ["decrypt", "async"] }
# mp4forge = { version = "0.7.0", features = ["serde"] }
```

Install the CLI from crates.io:
Expand All @@ -58,6 +61,13 @@ feature flags:
`AsyncRead + AsyncSeek` and `AsyncWrite + AsyncSeek` inputs and outputs, supports normal
multithreaded `tokio::spawn` usage for the supported library paths, and keeps the current CLI on
the existing sync path.
- `decrypt`: enables the additive decryption input, progress, and support-matrix types that fix
the public shape for the decryption surface while keeping the default build unchanged. The
landed sync library path covers the Common Encryption family (`cenc`, `cens`, `cbc1`, `cbcs`),
PIFF-triggered compatibility behavior, OMA DCF atom files and protected movie layouts, Marlin
IPMP ACBC and ACGK OD-track movies, and the retained IAEC protected-movie path. When combined
with `async`, it also enables the additive file-backed Tokio async decrypt companions, while the
CLI remains on the synchronous path.
- `serde`: derives `Serialize` and `Deserialize` for the reusable public report structs under
`mp4forge::cli::probe` and `mp4forge::cli::dump`, along with their nested public codec-detail,
media-characteristics, `FieldValue`, and `FourCc` data. This is intended for library-side report
Expand All @@ -70,6 +80,7 @@ feature flags:
USAGE: mp4forge COMMAND [ARGS]

COMMAND:
decrypt decrypt a protected MP4 file
divide split a fragmented MP4 into track playlists
dump display the MP4 box tree
edit rewrite selected boxes
Expand All @@ -78,6 +89,11 @@ COMMAND:
probe summarize an MP4 file
```

`decrypt` is available when the crate is built with `--features decrypt`. The CLI stays
sync-only, accepts repeated `--key ID:KEY`, optional `--fragments-info FILE`, and optional
`--show-progress`, and reuses the same library decryption surface that backs the feature-gated
sync and async APIs.

`divide` currently targets fragmented inputs with up to one AVC video track and one MP4A audio
track, including encrypted wrappers that preserve those original sample-entry formats. Pass
`-validate` when you want the same probe-driven layout checks without creating any output files.
Expand Down Expand Up @@ -107,7 +123,9 @@ field-order hints. Pass `-detail light` for a lighter-weight probe that skips pe
per-chunk, bitrate, and IDR aggregation, or use `mp4forge::probe::ProbeOptions` from the library
when you need the same control programmatically.

> See the [`examples/`](./examples) directory for the crate's low-level and high-level API usage patterns, including the Tokio-based async library example behind the optional `async` feature.
> See the [`examples/`](./examples) directory for the crate's low-level and high-level API usage
> patterns, including the feature-gated decrypt example and the Tokio-based async library example
> behind the optional `async` feature.

## License

Expand Down
74 changes: 74 additions & 0 deletions examples/decrypt_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#[cfg(feature = "decrypt")]
use std::env;
#[cfg(feature = "decrypt")]
use std::error::Error;
#[cfg(feature = "decrypt")]
use std::fs;
#[cfg(feature = "decrypt")]
use std::io;

#[cfg(feature = "decrypt")]
use mp4forge::decrypt::{DecryptOptions, decrypt_file_with_progress};

#[cfg(feature = "decrypt")]
fn main() {
if let Err(error) = run() {
eprintln!("{error}");
std::process::exit(1);
}
}

#[cfg(feature = "decrypt")]
fn run() -> Result<(), Box<dyn Error>> {
let args = env::args().skip(1).collect::<Vec<_>>();
if args.len() < 3 {
return Err(
"usage: cargo run --example decrypt_file --features decrypt -- <input.mp4> <output.mp4> <key-id:key> [more-keys...] [--fragments-info <init-or-movie.mp4>]"
.into(),
);
}

let input_path = args[0].clone();
let output_path = args[1].clone();
let mut options = DecryptOptions::new();
let mut cursor = 2usize;
while cursor < args.len() {
match args[cursor].as_str() {
"--fragments-info" => {
let fragments_info_path = args.get(cursor + 1).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"missing path after --fragments-info",
)
})?;
options = options.with_fragments_info_bytes(fs::read(fragments_info_path)?);
cursor += 2;
}
key_spec => {
options = options.with_key_spec(key_spec)?;
cursor += 1;
}
}
}

decrypt_file_with_progress(
&input_path,
&output_path,
&options,
|progress| match progress.total {
Some(total) => eprintln!("{:?}: {}/{}", progress.phase, progress.completed, total),
None => eprintln!("{:?}", progress.phase),
},
)?;

println!("wrote clear output to {output_path}");
Ok(())
}

#[cfg(not(feature = "decrypt"))]
fn main() {
eprintln!(
"enable the decrypt feature: cargo run --example decrypt_file --features decrypt -- <input.mp4> <output.mp4> <key-id:key> [more-keys...] [--fragments-info <init-or-movie.mp4>]"
);
std::process::exit(1);
}
Loading
Loading