Expand description
§dvb-subtitle
DVB bitmap subtitling segment parser and serializer, per ETSI EN 300 743 V1.6.1.
§What it does / what it does not
Does. Takes the reassembled PES data field of a DVB subtitle stream (the
payload of a PES packet carrying data_identifier 0x20) and returns typed
segments: page, region, CLUT, object, display definition, disparity
signalling, alternative CLUT, end of display set and stuffing. Every segment
serializes back to the same bytes. Typical users are subtitle extractors,
stream analysers and head-end tooling.
Does not.
- It does not render. It decodes 2/4/8-bit pixel-data run codes into runs
(
PixelDataSubBlock::runs), but never composes a page, applies a CLUT to produce RGBA, or draws text from character codes. - It does not inflate the zlib data of a progressive pixel block
(
ProgressivePixelBlock::compressed_datastays opaque). - It does not convert DVB bitmap subtitles to text or WebVTT/SRT (no OCR);
caption-convertdocuments that as a gap. - It does not demultiplex TS or reassemble PES; use
mpeg-tsandmpeg-pes. - It does not parse the PMT
subtitling_descriptor; that lives indvb-si.
§Standards supported
| Standard | Edition | Scope | Status | Notes |
|---|---|---|---|---|
| ETSI EN 300 743, Digital Video Broadcasting (DVB); Subtitling systems | V1.6.1 (2018-10), checked against private/specs-md/INDEX.md | §7.1 PES data field (data_identifier 0x20, subtitle_stream_id 0x00, end marker 0xFF) | Full | Trailing bytes after the last segment are kept for byte-exact round-trip |
| §7.2.1 display definition (0x14), §7.2.2 page composition (0x10), §7.2.3 region composition (0x11), §7.2.4 CLUT definition (0x12), §7.2.6 end of display set (0x80), Table 7 stuffing (0xFF) | Full | |||
| §7.2.5 object data (0x13): 2/4/8-bit pixel-data sub-blocks, map tables, character strings, progressive pixel block header | Partial | Progressive pixel data is typed-opaque (compressed bytes); pixel runs are decoded on request, no rendering | ||
| §7.2.7 disparity signalling (0x15), §7.2.8 alternative CLUT (0x16) | Full | |||
| Rendering model (§8, §9 decoder behaviour), PMT subtitling descriptor | Not implemented | Descriptor is in dvb-si |
Segment types with no typed implementation parse as AnySegment::Unknown;
a recognised type whose typed parse fails becomes AnySegment::Malformed.
Both re-serialize their body verbatim, so a capture never fails to round-trip
because of one bad segment.
§Feature flags
| Feature | Default | Effect |
|---|---|---|
std | yes | Links std (broadcast-common/std, thiserror/std). Without it the crate is #![no_std] + alloc. |
serde | no | serde::Serialize on segments (serialize-only, camelCase external tagging). |
§Quick start
use broadcast_common::Parse;
use dvb_subtitle::{PesDataField, DATA_IDENTIFIER, SYNC_BYTE, END_OF_PES_MARKER};
let bytes = [
DATA_IDENTIFIER, // 0x20
0x00, // subtitle_stream_id
SYNC_BYTE, // 0x0F
0x80, 0x00, 0x01, 0x00, 0x00, // end_of_display_set
END_OF_PES_MARKER, // 0xFF
];
let field = PesDataField::parse(&bytes).unwrap();
assert_eq!(field.segments.len(), 1);Two runnable examples ship in examples/ (parse_segment, parse_full_pes);
run with cargo run -p dvb-subtitle --example <name>.
§What is implemented
- Container:
PesDataField(data identifier, stream id, segment list, end marker) and the dispatch enumAnySegment(9 typed variants plusUnknownandMalformed;AnySegment::DISPATCHED_SEGMENT_TYPESlists the codes). - Composition:
PageCompositionSegment(withPageState),RegionCompositionSegment(RegionDepth,RegionLevelOfCompatibility,ObjectType,ObjectProviderFlag),DisplayDefinitionSegment,EndOfDisplaySetSegment,StuffingSegment. - Colour:
ClutDefinitionSegment/ClutEntry(full and reduced-range entries),AlternativeClutSegment(DynamicRangeColourGamut,OutputBitDepth). - Objects:
ObjectDataSegmentwithObjectDataPayload(interlaced pixel sub-blocks, character codes, progressive block, reserved),PixelDataSubBlock,PixelRuniterator. - 3D:
DisparitySignallingSegment(regions, subregions, shift update sequences). - Errors:
Error(#[non_exhaustive]), includingFieldOverflowfor a value wider than its wire field.
§Design notes
- Zero-copy: parsed structs borrow
&'a [u8]from the input. - Symmetric
Parse/Serialize; parse then serialize is byte-identical, and serialize then parse compares equal. Tested against real M6 subtitle PES reassembled fromfixtures/ts/m6-single.ts(tests/it/fixture_subtitle.rs). - Serializers reject a value wider than its bit-field (
Error::FieldOverflow) instead of masking; reserved code values are carried verbatim asReserved(v). - Hostile input never panics (
tests/it/hostility.rs: truncation at every length, mutation of every byte). - Public enums are
#[non_exhaustive]and carryname()+Display. no_std+alloc; MSRV 1.95.0.
§Related crates
Depends on broadcast-common only at runtime (mpeg-pes is a dev-dependency
for the fixture test). Feed it from mpeg-ts -> mpeg-pes. dvb-si parses the
PMT/SDT descriptors that locate the subtitle PID. caption-convert does
text-caption conversion and documents bitmap subtitles as unsupported.
§MSRV and licence
Rust 1.95.0. MIT OR Apache-2.0.
§Examples
Two runnable examples ship with this crate (cargo run -p dvb-subtitle --example <name>).
§parse_segment
//! Basic: parse a single subtitling segment from inline bytes.
//!
//! Run with: `cargo run -p dvb-subtitle --example parse_segment`
use broadcast_common::Parse;
use dvb_subtitle::{AnySegment, DATA_IDENTIFIER, END_OF_PES_MARKER, PesDataField, SYNC_BYTE};
fn main() {
// A complete PES data field with one display definition segment (no window).
let bytes = [
DATA_IDENTIFIER, // 0x20
0x00, // subtitle_stream_id = 0x00
// Display definition segment (segment_type 0x14, no window):
SYNC_BYTE, // 0x0F
0x14, // segment_type = display_definition
0x00,
0x01, // page_id = 1
0x00,
0x05, // segment_length = 5
0x30, // dds_version_number = 3, display_window_flag = 0
0x02,
0xCF, // display_width = 720-1 = 719
0x01,
0x1F, // display_height = 288-1 = 287
END_OF_PES_MARKER, // 0xFF
];
let field = PesDataField::parse(&bytes).expect("valid PES data field");
println!("Segments: {}", field.segments.len());
match &field.segments[0] {
AnySegment::DisplayDefinition(dds) => {
println!("Display width : {}", dds.display_width + 1);
println!("Display height : {}", dds.display_height + 1);
println!("Version : {}", dds.dds_version_number);
println!("Window flag : {}", dds.display_window_flag);
}
seg => println!("Unexpected segment: {}", seg.name()),
}
}§parse_full_pes
//! Advanced: parse a full PES data field with several segments from inline bytes.
//!
//! Run with: `cargo run -p dvb-subtitle --example parse_full_pes`
use broadcast_common::Parse;
use dvb_subtitle::{
AnySegment, DATA_IDENTIFIER, END_OF_PES_MARKER, ObjectDataPayload, PesDataField, SYNC_BYTE,
};
fn main() {
// A multi-segment PES data field: DDS, PCS, ODS, EDS.
let bytes = [
DATA_IDENTIFIER, // 0x20
0x00, // subtitle_stream_id
// Display definition segment (no window)
SYNC_BYTE,
0x14,
0x00,
0x01,
0x00,
0x05,
0x30,
0x02,
0xCF,
0x01,
0x1F,
// Page composition segment (one region, page state = acquisition)
SYNC_BYTE,
0x10,
0x00,
0x01,
0x00,
0x08,
0x05, // page_time_out = 5 seconds
0x04, // version=0, state=acquisition
0x01,
0x00, // region_id=1, reserved=0
0x00,
0x64,
0x00,
0x32, // region at (100, 50)
// Object data segment (character string)
SYNC_BYTE,
0x13,
0x00,
0x01,
0x00,
0x08,
0x00,
0x0A, // object_id = 10
0x04, // version=0, coding=characters
0x02, // number_of_codes = 2
0x00,
0x41,
0x00,
0x42, // 'A', 'B'
// End of display set
SYNC_BYTE,
0x80,
0x00,
0x01,
0x00,
0x00,
END_OF_PES_MARKER, // 0xFF
];
let field = PesDataField::parse(&bytes).expect("valid PES data field");
println!("{} segments found:\n", field.segments.len());
for seg in &field.segments {
match seg {
AnySegment::DisplayDefinition(dds) => {
println!(
" Display Definition: {}x{}",
dds.display_width + 1,
dds.display_height + 1
);
}
AnySegment::PageComposition(pcs) => {
println!(
" Page Composition: timeout={}s, state={}",
pcs.page_time_out, pcs.page_state
);
for r in &pcs.regions {
println!(
" Region {} @ ({}, {})",
r.region_id, r.region_horizontal_address, r.region_vertical_address
);
}
}
AnySegment::ObjectData(ods) => {
println!(
" Object Data: id={}, coding={}",
ods.object_id, ods.object_coding_method
);
if let ObjectDataPayload::Characters {
character_codes, ..
} = &ods.payload
{
for code in character_codes {
if let Ok(ch) = char::try_from(u32::from(*code)) {
print!(" '{ch}'");
}
}
println!();
}
}
AnySegment::EndOfDisplaySet(_) => {
println!(" End of Display Set");
}
other => println!(" {}: (type=?)", other.name()),
}
}
}Structs§
- Alternative
Clut Entry - A single alternative CLUT entry.
- Alternative
Clut Segment - Alternative CLUT Segment.
- Clut
Definition Segment - CLUT Definition Segment.
- Clut
Entry - A single CLUT entry.
- Clut
Parameters - CLUT parameters as defined in Table 32.
- Disparity
Region - A region entry in the disparity signalling segment.
- Disparity
Shift Interval - An interval within a disparity shift update sequence.
- Disparity
Shift Update Sequence - A disparity shift update sequence as defined in Table 30.
- Disparity
Signalling Segment - Disparity Signalling Segment.
- Display
Definition Segment - Display Definition Segment (DDS).
- EndOf
Display SetSegment - End of Display Set Segment.
- Interlaced
Pixels Data - An interlaced-pixels object data payload (coding method 0x00).
- Object
Data Segment - Object Data Segment.
- Page
Composition Segment - Page Composition Segment.
- Page
Region Entry - A single region entry within a page composition segment.
- PesData
Field - The top-level PES data field structure for DVB subtitles.
- Pixel
Data SubBlock - A pixel-data sub-block (Table 20).
- Pixel
Run - One decoded run from a 2/4/8-bit pixel-data code string (Tables 22-26):
run_length(>= 1) consecutive pixels painted with CLUT entrypixel_code. - Pixel
RunIter - A lazy decoder over one 2/4/8-bit pixel-data code string’s RLE pixel runs
(Tables 22-26), returned by
PixelDataSubBlock::runs. Stops at the string’send_of_string_signal, or at the data boundary if that comes first (never reads past the sub-block’s owndata). - Progressive
Pixel Block - A progressive pixel block (Table 27, coding method 0x02).
- Region
Composition Segment - Region Composition Segment.
- Region
Object Entry - An object entry within a region composition segment.
- Stuffing
Segment - A stuffing segment — opaque data bytes following the header.
- Subregion
- A subregion within a region for disparity signalling.
Enums§
- AnySegment
- Every crate-implemented segment type, plus an
Unknownfallthrough. - Data
Type - Data type for pixel-data sub-blocks as defined in Table 21.
- Dynamic
Range Colour Gamut - Dynamic range and colour gamut as defined in Table 34.
- Error
- A DVB subtitle parse or serialize error.
- Object
Coding Method - Object coding method as defined in Table 18.
- Object
Data Payload - Object data payload variants.
- Object
Provider Flag - Object provider flag as defined in Table 15.
- Object
Type - Object type as defined in Table 14.
- Output
BitDepth - Output bit depth as defined in Table 33.
- Page
State - Page state as defined in Table 10.
- Region
Depth - Intended region pixel depth as defined in Table 13.
- Region
Level OfCompatibility - Region level of compatibility as defined in Table 12.
Constants§
- DATA_
IDENTIFIER - The
data_identifierfor DVB subtitle streams (0x20). The requireddata_identifiervalue for DVB subtitles. - END_
OF_ PES_ MARKER - The
end_of_PES_data_field_marker(0xFF). Theend_of_PES_data_field_markervalue. - SUBTITLE_
STREAM_ ID - The
subtitle_stream_ididentifying a DVB subtitle stream (0x00). The requiredsubtitle_stream_idvalue. - SYNC_
BYTE - The sync_byte prefixing each subtitling segmentation (0x0F). The sync_byte that prefixes every subtitling_segment.
Traits§
- Segment
Def - Implemented by every typed subtitling segment; drives
crate::any::AnySegmentdispatch.SEGMENT_TYPEis the wire segment_type this type parses.
Type Aliases§
- Result
- Result alias for DVB subtitle parsing.