Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

4,017 Commits

Repository files navigation

| cli | lib | examples | hls | encoding | sixfix | sidecar files | xml | iodisco.com/scte35 |

threefive is SCTE-35.

SCTE-35 Parser. SCTE-35 Decoder. SCTE-35 Encoder. MPEGTS SCTE-35 Packet Injection. HLS Manifest and Segment SCTE-35 Parser. HLS SCTE-35 Injection. SCTE-35 Xml and Xml+bin Decoder/ Encoder for DASH.

Latest version is v2.4.97

  • Updated to latest version of new_reader for Zero packet loss Multicast (tested on OpenBSD and Debian).
  • The cli has been Upgraded and tuned.
  • Xml Node Namespaces and attribute namespaces are now configurable.
  • Turned SegmentationDescriptor.segmentation_message back on.
  • Fix UMID upid encoding
  • Added a Super Amazing HLS SCTE-35 Parser to the threefive cli..
  • Added a HLS SCTE-35 Encoder to the threefive cli..
  • MPEGTS Packet Injection added to the threefive cli..
  • sixfix : ffmpeg turns SCTE-35 streams to bin data (0x06), sixfix switches them back to SCTE-35 (0x86).
  • Xml: Experimental support for SCTE-35 xml as an input and output ( DASH SCTE-214)

Documentation

  • threefive is developed and tested on OpenBSD and Debian Sid.

Latest docs are always available in the python shell

>>>>fromthreefiveimportTimeSignal>>>>help(TimeSignal)

install

  • python3
python3-mpipinstallthreefive
  • pypy3
pypy3-mpipinstallthreefive
Versions and Releases

Every time I fix a bug or add a feature, I do a new release.
This makes tracking down bugs and stuff much easier.
Keep up, I do releases for reasons.

a@slow:~/threefive$ threefiveversion2.4.81
a@slow:~/threefive$ 
  • Release versions are odd.
  • Unstable testing versions are even.

image

cli

lib

online parser

examples

decryption

webvtt

dash

encoding

Cue Class
  • src cue.py
  • The threefive.Cue class decodes a SCTE35 binary, base64, or hex encoded string.

image

classCue(threefive.base.SCTE35Base)
|Cue(data=None, packet_data=None)
|__init__(self,data=None,packet_data=None)|datamaybepacketbitesorencodedstring|packet_dataisainstancepassedfromaStreaminstance
  • Cue.decode()
|decode(self)|Cue.decode()parsesforSCTE35data
  • 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_time21695.740089>>>>cue.info_section.table_id'0xfc'
  • Cue.get()
|get(self)|Cue.getreturnstheSCTE-35Cue|dataasadictofdicts.

Cue.get() Example

>>>fromthreefiveimportCue>>>cue=Cue('0XFC301100000000000000FFFFFF0000004F253396')
>>>cue.decode()
True>>>cue
{'bites': b'\xfc0\x11\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\x00\x00\x00O%3\x96',
'info_section': {'table_id': '0xfc', 'section_syntax_indicator': False, 'private': False, 'sap_type': '0x3',
'sap_details': 'No Sap Type', 'section_length': 17, 'protocol_version': 0, 'encrypted_packet': False,
'encryption_algorithm': 0, 'pts_adjustment_ticks': 0, 'pts_adjustment': 0.0, 'cw_index': '0x0', 'tier': '0xfff',
'splice_command_length': 4095, 'splice_command_type': 0, 'descriptor_loop_length': 0, 'crc': '0x4f253396'},
'command': {'command_length': None, 'command_type': 0, 'name': 'Splice Null'},
'descriptors': [], 'packet_data': None}
  • Cue.get() omits cue.bites and empty values
>>> cue.get()
{'info_section': {'table_id': '0xfc', 'section_syntax_indicator': False,'private': False, 'sap_type': '0x3',
'sap_details': 'No Sap Type', 'section_length': 17, 'protocol_version': 0, 'encrypted_packet': False,
'encryption_algorithm': 0, 'pts_adjustment_ticks': 0, 'pts_adjustment': 0.0, 'cw_index': '0x0', 'tier': '0xfff',
'splice_command_length': 4095, 'splice_command_type': 0, 'descriptor_loop_length': 0, 'crc': '0x4f253396'},
'command': {'command_type': 0, 'name': 'Splice Null'},
'descriptors': []}
  • Cue.get_descriptors()
|get_descriptors(self)|Cue.get_descriptorsreturnsalistof|SCTE35splicedescriptorsasdicts.
  • Cue.get_json()
|get_json(self)|Cue.get_jsonreturnstheCueinstance|datainjson.
  • Cue.show()
|show(self)|Cue.showprintstheCueasJSON
  • Cue.to_stderr()
|to_stderr(self)|Cue.to_stderrprintstheCue
Stream Class
  • src stream.py

  • The threefive.Stream class parses SCTE35 from Mpegts.

  • Supports:

    • File and Http(s) and Udp and Multicast protocols.
    • Multiple Programs.
    • Multi-Packet PAT, PMT, and SCTE35 tables.
  • threefive tries to include pid, program, anf pts of the SCTE-35 packet.

classStream(builtins.object)|Stream(tsdata,show_null=True)||StreamclassforparsingMPEG-TSdata.
|__init__(self, tsdata, show_null=True)
||tsdataisafileorhttp, https,
|udpormulticasturl.
||setshow_null=FalsetoexcludeSpliceNulls
  • Stream.decode(func=show_cue)
|decode(self, func=show_cue)
|Stream.decodereadsself.tsdatatofindSCTE35packets.
|funccanbesettoacustomfunctionthataccepts|athreefive.Cueinstanceasit'sonlyargument.

Stream.decode Example

importsysfromthreefiveimportStream>>>>Stream('plp0.ts').decode()
  • Pass in custom function

  • func should match the interface func(cue)

Stream.decode with custom function Example

importsysimportthreefivedefdisplay(cue):
print(f'\033[92m{cue.packet_data}\033[00m')
print(f'{cue.command.name}')
defdo():
sp=threefive.Stream(tsdata)
sp.decode(func=display)
if__name__=='__main__':
do()

  • Stream.decode_next()
|decode_next(self)|Stream.decode_nextreturnsthenext|SCTE35cueasathreefive.Cueinstance.

Stream.decode_next Example

"""Stream.decode_next example.decode_next returns the Cue every time a Cue is found.This uses a while loop to pull the Cues from a mpegts stream.When a Cue is found, if it's a Time Signal,cue.command.command_type=6, print Cue.command.You can filter on any var in the SCTE-35 Cue."""importsysimportthreefivefromnew_readerimportreaderdefdo():
arg=sys.argv[1]
withreader(arg) astsdata:
st=threefive.Stream(tsdata)
whileTrue:
cue=st.decode_next()
ifnotcue:
returnFalseifcue:
ifcue.command.command_type==6:
print(cue.command)
if__name__=="__main__":
do()
  • Stream.proxy(func = show_cue)

    • Writes all packets to sys.stdout.

    • Writes scte35 data to sys.stderr.

|decode(self,func=show_cue_stderr)|Stream.decode_proxywritesalltspacketsarewrittentostdout|forpipingintoanotherprogramlikemplayer.|SCTE-35cuesareprintedtostderr.

Stream.proxy Example

importthreefivesp=threefive.Stream('https://futzu.com/xaa.ts')
sp.decode_proxy()
  • Pipe to mplayer
$ python3 proxy.py | mplayer -

  • Stream.show()
|show(self)|ListprogramsandstreamsandinfoforMPEGTS

Stream.show() Example

>>>>fromthreefiveimportStream>>>>Stream('https://slo.me/plp0.ts').show()
 Service: fancy˹
Provider: fu-corp
Pcr Pid: 1051[0x41b]
Streams:
Pid: 1051[0x41b] Type: 0x1bAVCVideo
Pid: 1052[0x41c] Type: 0x3MP2Audio
Pid: 1054[0x41e] Type: 0x6PESPackets/PrivateData
Pid: 1055[0x41f] Type: 0x86SCTE35Data
Need to verify your splice points?

image

  • Try cue2vtt.py in the examples.

    • cue2vtt.py creates webvtt subtitles out of SCTE-35 Cue data
  • use it like this

pypy3 cue2vtt.py video.ts | mplayer video.ts -sub -

threefive is now addressable TV compatible
"tag": 2,
"descriptor_length": 31,
"name": "Segmentation Descriptor",
"identifier": "CUEI",
"components": [],
"segmentation_event_id": "0x065eff",
"segmentation_event_cancel_indicator": false,
"segmentation_event_id_compliance_indicator": true,
"program_segmentation_flag": true,
"segmentation_duration_flag": false,
"delivery_not_restricted_flag": true,
"segmentation_message": "Call Ad Server", <---Boom"segmentation_upid_type": 12,
"segmentation_upid_type_name": "MPU",
"segmentation_upid_length": 16,
"segmentation_upid": {
"format_identifier": "ADFR",	<---Boom"private_data": "0x0133f10134b04f065e060220",
"version": 1, <----Boom"channel_identifier": "0x33f1", <----Boom"date": 20230223, <----Boom"break_code": 1630, <----Boom"duration": "0x602"<----Boom
},
"segmentation_type_id": 2, <----Boom"segment_num": 0,
"segments_expected": 0
},

Custom charsets for UPIDS aka upids.charset

Specify a charset for Upid data by setting threefive.upids.charsetissue #55

  • default charset is ascii
  • python charsets info Here
  • setting charset to None will return raw bytes.

Example Usage:

>>>fromthreefiveimportCue,upids>>>i="/DBKAAAAAAAAAP/wBQb+YtC8/AA0AiZDVUVJAAAD6X/CAAD3W3ACEmJibG5kcHBobkQCAsGDpQIAAAAAAAEKQ1VFSRSAIyowMljRk9c=">>>upids.charset'ascii'>>>cue=Cue(i)
>>>cue.decode()
asciiTrue>>>cue.descriptors[0].segmentation_upid'bblndpphnD\x02\x02���\x02\x00\x00'>>>upids.charset="utf16">>>cue.decode()
utf16True>>>cue.descriptors[0].segmentation_upid'扢湬灤桰䑮Ȃ菁ʥ\x00'
Custom Private Splice Descriptors ( new! )

threefive now supports custom private splice descriptors, right out the box.

  • The first byte of the descriptor is read as an int for the Descriptor tag
  • The second byte is read as an int for the desciptor length
  • The next four bytes are read as ASCII for the Identifier
  • remanining bytes are returned as private data
fromthreefiveimportCue,TimeSignalfromthreefive.descriptorsimportSpliceDescriptor
  • make a Cue
c =Cue()
  • add a Time Signal
c.command =TimeSignal()
c.command.time_specified_flag=True
c.command.pts_time=1234.567890
  • add Splice Descriptor
sd =SpliceDescriptor()
sd.tag =47
sd.identifier ='fufu'
sd.private_data = b'threefive kicks ass'
c.descriptors.append(sd)
  • encode
c.encode()
'/DAvAAAAAAAAAP/wBQb+Bp9rxgAZLxdmdWZ1dGhyZWVmaXZlIGtpY2tzIGFzc1m+EsU='
  • show
c.show()
{
"info_section": {
"table_id": "0xfc",
"section_syntax_indicator": false,
"private": false,
"sap_type": "0x03",
"sap_details": "No Sap Type",
"section_length": 47,
"protocol_version": 0,
"encrypted_packet": false,
"encryption_algorithm": 0,
"pts_adjustment_ticks": 0,
"cw_index": "0x0",
"tier": "0xfff",
"splice_command_length": 5,
"splice_command_type": 6,
"descriptor_loop_length": 25,
"crc": "0x59be12c5"
},
"command": {
"command_length": 5,
"command_type": 6,
"name": "Time Signal",
"time_specified_flag": true,
"pts_time": 1234.56789,
"pts_time_ticks": 111111110
},
"descriptors": [
{
"tag": 47,
"descriptor_length": 23,
"identifier": "fufu",
"private_data": [
116,
104,
114,
101,
101,
102,
105,
118,
101,
32,
107,
105,
99,
107,
115,
32,
97,
115,
115
]
}
]
}
  • the custom Splice Descriptor
c.descriptors[0]{'tag': 47,'descriptor_length': 23,'name': None,'identifier': 'fufu','bites': None,'provider_avail_id': None,'components': None,'private_data': b'threefive kicks ass'}
  • Cool dictionaary comprehension to print the Splice Descriptor with only relevant values
{print(f'{k} = {v}')fork,vinvars(c.descriptors[0]).items()ifvisnotNone}tag=47descriptor_length=23identifier=fufuprivate_data=b'threefive kicks ass'

Powered by threefive


x9k3: SCTE-35 HLS Segmenter and Cue Inserter.
m3ufu: SCTE-35 m3u8 Parser.
adbreak2 a cli tool that quickly and easily generates SCTE-35 Cues for HLS and stuff.
Ultra Mega Zoom Zoom ABR HLS segmenter and SCTE-35 inserter.
POIS Server is Super Cool.
bpkio-cli: A command line interface to the broadpeak.io APIs.
amt-play uses x9k3.

threefive | more


Diagram of a threefive SCTE-35 Cue.
ffmpeg and threefive and SCTE35 and Stream Type 0x6 bin data.
Issues and Bugs and Feature Requests will be considered. Please don't make me regret it.
NEW!threefive now has experimental DVB DAS Support ETSI TS 103 752-1

image

Used by

Contributors

Languages