# mpeg-pes

[![crates.io](https://img.shields.io/crates/v/mpeg-pes.svg)](https://crates.io/crates/mpeg-pes)
[![docs.rs](https://img.shields.io/docsrs/mpeg-pes)](https://docs.rs/mpeg-pes)
[![MSRV](https://img.shields.io/badge/MSRV-1.81-blue.svg)](https://blog.rust-lang.org/)
[![license](https://img.shields.io/crates/l/mpeg-pes.svg)](#license)

**PES (Packetized Elementary Stream) depacketization + PTS/DTS** — the sublayer
between an MPEG-TS packet layer and an elementary-stream consumer. Per ISO/IEC
13818-1 (Rec. ITU-T H.222.0) §2.4.3.6 / §2.4.3.7.

`#![no_std]` (+ `alloc`), depends only on `dvb-common`, WASM-clean. Pairs with
[`dvb-si`](https://crates.io/crates/dvb-si) for the TS/PSI layer:

```text
TsPacket payload + payload_unit_start ──► mpeg-pes ──► PesPacket { stream_id, pts, dts, payload }
```

## Quickstart

```rust
use mpeg_pes::{PesPacket, StreamId};

let bytes = [
    0x00, 0x00, 0x01, 0xE0, 0x00, 0x0A, 0x80, 0x80, 0x05,
    0x21, 0x00, 0x01, 0x00, 0x01, // PTS = 0
    0xAA, 0xBB,                   // ES payload
];
let pkt = PesPacket::parse(&bytes)?;
assert_eq!(pkt.stream_id, StreamId(0xE0));
assert!(pkt.stream_id.is_video());
assert_eq!(pkt.header.unwrap().pts.unwrap().ticks(), 0);
assert_eq!(pkt.payload, &[0xAA, 0xBB]);
# Ok::<(), mpeg_pes::Error>(())
```

## Features

| Feature | Default | Effect |
|---|---|---|
| `std` | ✅ | Link `std`. Off → `#![no_std]` + `alloc`. |
| `serde` | – | `serde::Serialize` on the public types. |

## Scope

In: PES packet header, `stream_id`, `PES_packet_length` (incl. unbounded video
`0`), the optional header flags, and **PTS/DTS** (33-bit @ 90 kHz). Out:
elementary-stream codec bitstream parsing (the consumer's job); network transport
(see `dvb-stream`).

## Examples

Run with `cargo run -p mpeg-pes --example <name>`:

- **`parse_pes_packet`** — parse one PES packet from raw bytes (stream_id + PTS + payload).
- **`extract_pts`** — depacketize a real capture, reassemble PES on a PID, and report the PTS timeline.
- **`build_pes_header`** — construct a PES packet header from typed fields (PTS+DTS, ESCR, DSM trick mode), serialize, re-parse, and verify round-trip.

### Building & editing PES headers

The [`PesPacket::serialize_into`] method lets you re-serialize a parsed or
hand-built packet byte-for-byte. Because [`PesHeader`] is `#[non_exhaustive]`,
constructors from outside the crate currently need to assemble the wire bytes
using the public typed field encoders and then parse:

```rust,ignore

// Build raw bytes with PTS+DTS using the typed field encoders, then parse.
let pts = Pts(90_000);
let dts = mpeg_pes::Dts(45_000);
// (full example: `cargo run -p mpeg-pes --example build_pes_header`)

// Once parsed, round-trip through serialize_into:
let pkt = PesPacket::parse(&bytes).unwrap();
let mut buf = vec![0u8; pkt.serialized_len()];
pkt.serialize_into(&mut buf).unwrap();
let re_pkt = PesPacket::parse(&buf).unwrap();
assert_eq!(re_pkt.header.unwrap().pts.unwrap().ticks(), 90_000);
```

Run the worked example:
```sh
cargo run -p mpeg-pes --example build_pes_header
```

## License

MIT OR Apache-2.0.
