Skip to main content

Crate dvb_subtitle

Crate dvb_subtitle 

Source
Expand description

§dvb-subtitle

DVB bitmap subtitling segment parser and serializer, per ETSI EN 300 743 V1.6.1.

crates.io docs.rs licence

§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_data stays opaque).
  • It does not convert DVB bitmap subtitles to text or WebVTT/SRT (no OCR); caption-convert documents that as a gap.
  • It does not demultiplex TS or reassemble PES; use mpeg-ts and mpeg-pes.
  • It does not parse the PMT subtitling_descriptor; that lives in dvb-si.

§Standards supported

StandardEditionScopeStatusNotes
ETSI EN 300 743, Digital Video Broadcasting (DVB); Subtitling systemsV1.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)FullTrailing 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 headerPartialProgressive 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 descriptorNot implementedDescriptor 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

FeatureDefaultEffect
stdyesLinks std (broadcast-common/std, thiserror/std). Without it the crate is #![no_std] + alloc.
serdenoserde::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 enum AnySegment (9 typed variants plus Unknown and Malformed; AnySegment::DISPATCHED_SEGMENT_TYPES lists the codes).
  • Composition: PageCompositionSegment (with PageState), RegionCompositionSegment (RegionDepth, RegionLevelOfCompatibility, ObjectType, ObjectProviderFlag), DisplayDefinitionSegment, EndOfDisplaySetSegment, StuffingSegment.
  • Colour: ClutDefinitionSegment/ClutEntry (full and reduced-range entries), AlternativeClutSegment (DynamicRangeColourGamut, OutputBitDepth).
  • Objects: ObjectDataSegment with ObjectDataPayload (interlaced pixel sub-blocks, character codes, progressive block, reserved), PixelDataSubBlock, PixelRun iterator.
  • 3D: DisparitySignallingSegment (regions, subregions, shift update sequences).
  • Errors: Error (#[non_exhaustive]), including FieldOverflow for 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 from fixtures/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 as Reserved(v).
  • Hostile input never panics (tests/it/hostility.rs: truncation at every length, mutation of every byte).
  • Public enums are #[non_exhaustive] and carry name() + Display.
  • no_std + alloc; MSRV 1.95.0.

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§

AlternativeClutEntry
A single alternative CLUT entry.
AlternativeClutSegment
Alternative CLUT Segment.
ClutDefinitionSegment
CLUT Definition Segment.
ClutEntry
A single CLUT entry.
ClutParameters
CLUT parameters as defined in Table 32.
DisparityRegion
A region entry in the disparity signalling segment.
DisparityShiftInterval
An interval within a disparity shift update sequence.
DisparityShiftUpdateSequence
A disparity shift update sequence as defined in Table 30.
DisparitySignallingSegment
Disparity Signalling Segment.
DisplayDefinitionSegment
Display Definition Segment (DDS).
EndOfDisplaySetSegment
End of Display Set Segment.
InterlacedPixelsData
An interlaced-pixels object data payload (coding method 0x00).
ObjectDataSegment
Object Data Segment.
PageCompositionSegment
Page Composition Segment.
PageRegionEntry
A single region entry within a page composition segment.
PesDataField
The top-level PES data field structure for DVB subtitles.
PixelDataSubBlock
A pixel-data sub-block (Table 20).
PixelRun
One decoded run from a 2/4/8-bit pixel-data code string (Tables 22-26): run_length (>= 1) consecutive pixels painted with CLUT entry pixel_code.
PixelRunIter
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’s end_of_string_signal, or at the data boundary if that comes first (never reads past the sub-block’s own data).
ProgressivePixelBlock
A progressive pixel block (Table 27, coding method 0x02).
RegionCompositionSegment
Region Composition Segment.
RegionObjectEntry
An object entry within a region composition segment.
StuffingSegment
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 Unknown fallthrough.
DataType
Data type for pixel-data sub-blocks as defined in Table 21.
DynamicRangeColourGamut
Dynamic range and colour gamut as defined in Table 34.
Error
A DVB subtitle parse or serialize error.
ObjectCodingMethod
Object coding method as defined in Table 18.
ObjectDataPayload
Object data payload variants.
ObjectProviderFlag
Object provider flag as defined in Table 15.
ObjectType
Object type as defined in Table 14.
OutputBitDepth
Output bit depth as defined in Table 33.
PageState
Page state as defined in Table 10.
RegionDepth
Intended region pixel depth as defined in Table 13.
RegionLevelOfCompatibility
Region level of compatibility as defined in Table 12.

Constants§

DATA_IDENTIFIER
The data_identifier for DVB subtitle streams (0x20). The required data_identifier value for DVB subtitles.
END_OF_PES_MARKER
The end_of_PES_data_field_marker (0xFF). The end_of_PES_data_field_marker value.
SUBTITLE_STREAM_ID
The subtitle_stream_id identifying a DVB subtitle stream (0x00). The required subtitle_stream_id value.
SYNC_BYTE
The sync_byte prefixing each subtitling segmentation (0x0F). The sync_byte that prefixes every subtitling_segment.

Traits§

SegmentDef
Implemented by every typed subtitling segment; drives crate::any::AnySegment dispatch. SEGMENT_TYPE is the wire segment_type this type parses.

Type Aliases§

Result
Result alias for DVB subtitle parsing.