# CLPI Clip Information Format

## What is CLPI?

CLPI (Clip Information) files describe the structure of individual M2TS transport streams on a Blu-ray disc. Each `.clpi` file in `BDMV/CLIPINF/` corresponds to one `.m2ts` file in `BDMV/STREAM/` with the same numeric name (e.g. `00001.clpi` describes `00001.m2ts`).

The most important data in a CLPI file is the **EP (Entry Point) map**, which provides timestamp-to-sector mapping. This is essential for seeking and for extracting the specific sector ranges that correspond to a playlist's in/out time window.

## File Structure

CLPI files use big-endian byte order. The header:

```
Offset  Size  Field
------  ----  -----
0       4     Magic: "HDMV"
4       4     Version: "0200" (BD) or "0300" (UHD BD)
8       4     SequenceInfo start offset
12      4     ProgramInfo start offset
16      4     CPI (Characteristic Point Information) start offset
20      4     ClipMark start offset
24      4     ExtensionData start offset
```

### ClipInfo Section (offset 40)

Contains basic clip metadata. The source packet count (total number of 192-byte source packets in the M2TS file) is at offset 56:

```
Offset  Size  Field
------  ----  -----
40      4     ClipInfo length
44      2     Reserved
46      1     Stream type
47      1     Application type
48      4     Reserved
52      4     TS recording rate
56      4     Source packet count
```

The source packet count multiplied by 192 gives the total byte size of the M2TS file.

## EP Map

The EP map lives inside the CPI section and provides random access into the transport stream. It maps PTS timestamps to Source Packet Numbers (SPN), enabling precise seeking without scanning the stream.

### CPI Section Layout

```
Offset  Size  Field
------  ----  -----
0       4     CPI length
4       2     Reserved / CPI type
6       ...   EP map
```

### EP Map Header

```
Offset  Size  Field
------  ----  -----
0       1     Reserved
1       1     Number of stream PID entries
2       ...   Stream PID entry headers (one per stream)
```

Each stream PID entry header (14 bytes):

```
Offset  Size  Field
------  ----  -----
0       2     Stream PID
2       2     Reserved + EP stream type
4       2     Number of coarse entries
6       4     Number of fine entries (note: 32-bit, can be large)
10      4     EP map start offset (relative to EP map start)
```

libfreemkv parses only the first stream (primary video), which is sufficient for sector-level seeking.

### Two-Level Index

The EP map uses a two-level structure to compress what would otherwise be a very large lookup table:

- **Coarse entries**: low-resolution index covering large time/sector ranges
- **Fine entries**: high-resolution entries within each coarse range

Each coarse entry points to a range of fine entries via `ref_to_fine_id`.

### Coarse Entries (8 bytes each)

Located immediately after the fine table start offset (4 bytes) in the per-stream EP map:

```
Bits    Field
------  -----
[31:14] ref_to_fine_id (18 bits) -- index of first fine entry in this range
[13:0]  pts_coarse (14 bits) -- upper bits of PTS
```

Second dword:

```
Bits    Field
------  -----
[31:0]  spn_coarse (32 bits) -- upper bits of SPN
```

### Fine Entries (4 bytes each)

Located at the fine table start offset within the per-stream EP map:

```
Bits    Field
------  -----
[31]    is_angle_change_point (1 bit)
[30:28] I_end_position_offset (3 bits)
[27:17] pts_fine (11 bits) -- lower bits of PTS
[16:0]  spn_fine (17 bits) -- lower bits of SPN
```

## Reconstructing Full PTS and SPN

The full timestamp and packet number are assembled by combining the coarse and fine components:

### Full PTS

```
full_pts = (pts_coarse << 19) + (pts_fine << 8)
```

The coarse component provides the upper bits, shifted left by 19. The fine component provides mid-range bits, shifted left by 8. The resulting PTS is in 45kHz ticks, matching the MPLS timestamp format.

### Full SPN

```
full_spn = (spn_coarse & 0xFFFE0000) + spn_fine
```

The coarse SPN provides the upper 15 bits (masked to clear the lower 17). The fine SPN provides the lower 17 bits. The result is a Source Packet Number -- each source packet is 192 bytes in the M2TS file.

### Resolved EP Map

The `resolved_ep_map()` method iterates through all coarse entries and their associated fine entries to produce a flat list of `(PTS, SPN)` pairs. For each coarse entry at index `ci`:

- Fine entries range from `coarse[ci].ref_to_fine_id` to `coarse[ci+1].ref_to_fine_id` (or end of fine table for the last coarse entry).
- Each fine entry is combined with its parent coarse entry to produce one full `(PTS, SPN)` pair.

## Deriving Sector Extents for Ripping

Given a playlist's in_time and out_time (from MPLS), the CLPI EP map provides the sector ranges to read from disc. The `get_extents()` method does this in three steps:

### Step 1: PTS to SPN

Binary search the resolved EP map for the in_time and out_time:

- **start_spn**: the SPN at or before in_time (seek backward to the nearest I-frame)
- **end_spn**: the SPN at or after out_time (include the full GOP)

### Step 2: SPN to Byte Offset

Each Source Packet is 192 bytes (188 bytes MPEG-TS payload + 4 bytes M2TS header):

```
byte_offset = spn * 192
```

### Step 3: Byte Offset to Sector

M2TS files are stored contiguously on disc. Sectors are 2048 bytes:

```
start_sector = start_byte / 2048
end_sector   = (end_byte + 2047) / 2048
sector_count = end_sector - start_sector
```

The resulting `Extent` contains `start_lba` (relative to the M2TS file's starting sector on disc) and `sector_count`. The caller adds the file's absolute starting LBA from UDF to get disc-absolute sector numbers.

### Alignment Note

The 192-byte source packet size and 2048-byte sector size share no common factor beyond 1. A single sector contains roughly 10.67 source packets. The conversion rounds start down and end up to ensure complete coverage.

## Putting It Together

The full ripping pipeline chains three parsers:

1. **MPLS** provides clip IDs and in/out timestamps.
2. **CLPI** converts those timestamps to SPN ranges, then to sector extents.
3. **UDF** provides the file's starting LBA on disc for absolute sector addressing.

The `Disc::scan()` method in `src/disc/mod.rs` orchestrates this: for each play item in each playlist, it loads the corresponding CLPI, calls `get_extents()` with the play item's in/out times, and collects the resulting sector ranges into the title's extent list.

## References

- BD-ROM Part 3, Section 5.5: Clip Information file format
- https://github.com/lw/BluRay/wiki/CLPI
