/scte35-threefive

Quite Possibly the Most Advanced SCTE 35 Parser, Maybe. MPEG-TS, HLS, DASH, Binary, Hex, and Integers. Streams of type 0x06 (Data: bin_data) and 0x86 (SCTE-35)..... 'Yes' to all of the aforementioned.

Primary LanguagePythonMIT LicenseMIT

If you need a SCTE-35 parser, threefive is probably what you want.


Requirements

threefive requires python 3.6+ or pypy3

pypy3 runs threefive Four Times Faster than python3

Install

pip3 install threefive

# for pypy3
pypy3 -m pip install threefive

Versions and Releases

Release versions are odd.

Unstable testing versions are even.

threefive.version() returns the version as a string.

threefive.version_number() returns an int for easy version comparisons.


Easy threefive

threefive.decode is a SCTE-35 decoder function with input type auto-detection. Base64, Binary, Hex Strings,Hex literals, Integers, Mpegts files and Mpegts HTTP/HTTPS Streams

SCTE-35 data can be parsed with just one function call.

the arg stuff is the input. if stuff is not set, decode will attempt to read from sys.stdin.buffer.

if stuff is a file, the file data will be read and the type of the data will be autodetected and decoded.

SCTE-35 data is printed in JSON format.

Examples:

Base64
import threefive 

stuff = '/DAvAAAAAAAA///wBQb+dGKQoAAZAhdDVUVJSAAAjn+fCAgAAAAALKChijUCAKnMZ1g='
threefive.decode(stuff)
Bytes
import threefive 

payload = b'\xfc0\x11\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\x00\x00\x00O%3\x96'
threefive.decode(payload)
Hex String
import threefive 

stuff = '0XFC301100000000000000FFFFFF0000004F253396'
threefive.decode(stuff)
Hex Literal
import threefive 

threefive.decode(0XFC301100000000000000FFFFFF0000004F253396)
Integer
big_int = 1439737590925997869941740173214217318917816529814
threefive.decode(big_int)
Mpegts File
import threefive 

threefive.decode('/path/to/mpegts')
Mpegts HTTP/HTTPS Streams
import threefive 

threefive.decode('https://futzu.com/xaa.ts')
Read from File cue.txt
from threefive import decode

decode('cue.txt')

A threefive SCTE-35 Cue

{
    "info_section": {
        "table_id": "0xfc",
        "section_syntax_indicator": false,
        "private": false,
        "sap_type": "0x3",
        "sap_details": "No Sap Type",
        "section_length": 47,
        "protocol_version": 0,
        "encrypted_packet": false,
        "encryption_algorithm": 0,
        "pts_adjustment": 0.0,
        "cw_index": "0x0",
        "tier": "0xfff",
        "splice_command_length": 4095,
        "splice_command_type": 5,
        "descriptor_loop_length": 10,
        "crc": "0x10fa4d9e"
    },
    "command": {
        "calculated_length": 20,
        "name": "Splice Insert",
        "time_specified_flag": true,
        "pts_time": 89742.161689,
        "break_auto_return": false,
        "break_duration": 242.0,
        "splice_event_id": 662,
        "splice_event_cancel_indicator": false,
        "out_of_network_indicator": true,
        "program_splice_flag": true,
        "duration_flag": true,
        "splice_immediate_flag": false,
        "unique_program_id": 1,
        "avail_num": 0,
        "avail_expected": 0
    },
    "descriptors": [
        {
            "tag": 0,
            "descriptor_length": 8,
            "name": "Avail Descriptor",
            "identifier": "CUEI",
            "provider_avail_id": 0
        }
    ],
    "packet_data": {
        "pid": "0x135",
        "program": 1,
        "pcr": 89730.281789,
        "pts": 89730.289522
    }
}

Advanced threefive


Cue Class

  • src cue.py
  • The threefive.Cue class decodes a SCTE35 binary, base64, or hex encoded string.
  • threefive.Cue provides several methods to access the parsed data.
    >>>> import threefive
    >>>> Base64 = "/DAvAAAAAAAA///wBQb+dGKQoAAZAhdDVUVJSAAAjn+fCAgAAAAALKChijUCAKnMZ1g="
    >>>> cue = threefive.Cue(Base64)

cue.decode() returns True on success,or False if decoding failed

    >>>> cue.decode()
    True

After Calling cue.decode() the instance variables can be accessed via dot notation.

    >>>> cue.command
    {'calculated_length': 5, 'name': 'Time Signal', 'time_specified_flag': True, 'pts_time': 21695.740089}

    >>>> cue.command.pts_time
    21695.740089

    >>>> cue.info_section.table_id

    '0xfc'

When parsing SCTE35 Cues from MPEGTS streams, threefive attempts to include as many of the following as possible.'

  • pid of the packet
  • program of the pid
  • pts of the packet
  • pcr of the packet

  • call one or more of these methods after decode.
Cue Method Description
cue.get() returns cue as a dict
cue.get_json() returns cue as a JSON string
cue.show() prints cue as JSON

Stream Class

 threefive.Stream(tsdata, show_null = False)
  • src stream.py
  • The threefive.Stream class parses SCTE35 messages from a file or stream.
  • Supports
    • Multiple Programs.
    • Multiple SCTE35 Streams.
    • Multi-Packet PAT, PMT, and SCTE35 tables.
    • Constant Data Parsing.
      • threefive.Stream is designed to run continuously
Method Description
Stream.show() Prints Streams that will be checked for SCTE35
Stream.decode(func=show_cue) Prints SCTE-35 cues for SCTE-35 packets. Accepts an optional function, func, as arg.
Stream.decode_next() Returns the next SCTE35 cue as a threefive.Cue instance.
Stream.decode_program(the_program=None, func=show_cue) Same as Stream.decode except only packets where program == the_program
Stream.decode_proxy(func=show_cue) Same as Stream.decode except raw packets are written to stdout for piping to another program.

Stream.show()

  • List programs and streams that will be checked for SCTE35 data.
>>>> from threefive import Stream, version
>>>> version()
'2.2.69'
>>>> with open('video.ts','rb') as tsdata:
....     strm = Stream(tsdata)
....     strm.show()
....     

Program:1030
        PID: 1034(0x40a) Type: 0x6
        PID: 1035(0x40b) Type: 0x86 SCTE35

Program:1100
        PID: 1104(0x450) Type: 0x6
        PID: 1105(0x451) Type: 0x86 SCTE35

Program:1080
        PID: 1084(0x43c) Type: 0x6

Stream.decode(func=show_cue)

import sys
from threefive import Stream

if __name__ =='__main__':
   with open(sys.argv[1],'rb') as tsdata:
       sp = Stream(tsdata)
       sp.decode()
  • Pass in custom function

  • func should match the interface func(cue)

import sys
import threefive

def display(cue):
   print(f'\033[92m{cue.packet_data}\033[00m')
   print(f'{cue.command.name}')

def do():
   with open(sys.argv[1],'rb') as tsdata:
    sp = threefive.Stream(tsdata)
    sp.decode(func = display)       

if __name__ == '__main__':
    do()

Stream.decode_next()

  • Stream.decode_next returns the next SCTE35 cue as a threefive.Cue instance.
import sys
import threefive

def do():
    arg = sys.argv[1]
    with open(arg,'rb') as tsdata:
        st = threefive.Stream(tsdata)
        while True:
            cue = st.decode_next()
            if not cue:
                return False
            if cue:
                cue.show()

if __name__ == "__main__":
    do()

Stream.decode_program(the_program, func = show_cue)

  • Use Stream.decode_program() instead of Stream.decode() to decode SCTE-35 from packets where program == the_program
import threefive

with open('../35.ts','rb') as tsdata:
    threefive.Stream(tsdata).decode_program(1)

Stream.decode_proxy(func = show_cue)

  • Writes all packets to sys.stdout.

  • Writes scte35 data to sys.stderr.

import threefive

with open('vid.ts','rb') as tsdata:
    sp = threefive.Stream(tsdata)
    sp.proxy_decode()
  • Pipe to mplayer
$ python3 proxy.py | mplayer -

Issues and Bugs and Feature Requests


Speak up. I want to hear what you have to say.

If threefive doesn't work as expected,

or if you find a bug ,

or if you have feature request,

please open an issue.

If you want help resolving a video parsing issue, a sample of the video is required .