Giter Club home page Giter Club logo

futzu / threefive Goto Github PK

View Code? Open in Web Editor NEW
127.0 15.0 26.0 6.08 MB

SCTE-35 Parser. SCTE-35 Decoder. SCTE-35 Encoder. The Best SCTE-35 Parser. Adrian is Super Cool.

Home Page: https://git.futzu.com

License: MIT License

Python 99.32% Makefile 0.68%
scte35 scte ad-splicing scte-35 cuei scte35-parser python3 ad-insertion scte35-2022 mpegts adrianofdoom adbreak scte35-encoder best-scte35-parser threefive adrian scte35-decoder boobs best best-scte-35

threefive's Introduction

threefive is the highest rated SCTE-35 parser. Ever.

Using threefive for SCTE-35 is like having a letter from The Pope and a rocket launcher. Things are going to tend to go your way.


Parses SCTE-35 from multiple streams in MPEGTS and Multiple Program Transport Streams
Parses SCTE-35 from Cues encoded inBase64, Bytes, Hex, Integers.
Parses SCTE-35 from files, http(s), Multicast, UDP and even stdin ( you can pipe to it).
Parses SCTE-35 from streams converted to bin data ( type 0x06 ) by ffmpeg.

threefive supports the latest SCTE-35 specification SCTE-35 2023r1

  • threefive and cuei are the only parsers I've seen that support the 2023r1 spec.

Latest threefive version is 2.4.37 / release 275

  • cyclomatic complexity for #275
256 blocks (classes, functions, methods) analyzed.
Average complexity: A (1.9921875)

Documentation

Install
  • install threefive with pip
python3 -mpip install --upgrade threefive

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 folks.

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

SCTE-35 on the command line

Trigger on SCTE-35 events using threefive.Stream

SCTE-35 code examples

Parse SCTE-35 programmatically with a few lines of code

Cue Class
  • src cue.py
  • The threefive.Cue class decodes a SCTE35 binary, base64, or hex encoded string.
class Cue(threefive.base.SCTE35Base)
 |  Cue(data=None, packet_data=None)
 |  __init__(self, data=None, packet_data=None)
 |      data may be packet bites or encoded string
 |      packet_data is a instance passed from a Stream instance
  • Cue.decode()
 |  decode(self)
 |      Cue.decode() parses for SCTE35 data
  • 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'
  • Cue.get()
 |  get(self)
 |      Cue.get returns the SCTE-35 Cue
 |      data as a dict of dicts.

Cue.get() Example

>>> from threefive import Cue
>>> 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_descriptors returns a list of
 |      SCTE 35 splice descriptors as dicts.
  • Cue.get_json()
 |  get_json(self)
 |      Cue.get_json returns the Cue instance
 |      data in json.
  • Cue.show()
 |  show(self)
 |      Cue.show prints the Cue as JSON
  • Cue.to_stderr()
 |  to_stderr(self)
 |      Cue.to_stderr prints the Cue
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.

class Stream(builtins.object)
 |  Stream(tsdata, show_null=True)
 |
 |  Stream class for parsing MPEG-TS data.
|  __init__(self, tsdata, show_null=True)
|
|      tsdata is a file or http, https,
|       udp or multicast url.
|
|      set show_null=False to exclude Splice Nulls
  • Stream.decode(func=show_cue)
|  decode(self, func=show_cue)
|      Stream.decode reads self.tsdata to find SCTE35 packets.
|      func can be set to a custom function that accepts
|      a threefive.Cue instance as it's only argument.

Stream.decode Example

import sys
from threefive import Stream
>>>> Stream('plp0.ts').decode()
  • Pass in custom function

  • func should match the interface func(cue)

Stream.decode with custom function Example

import sys
import threefive

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

def do():
   sp = threefive.Stream(tsdata)
   sp.decode(func = display)

if __name__ == '__main__':
    do()

  • Stream.decode_next()
|  decode_next(self)
|      Stream.decode_next returns the next
|      SCTE35 cue as a threefive.Cue instance.

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.
"""

import sys
import threefive
from new_reader import reader

def do():
    arg = sys.argv[1]
    with reader(arg) as tsdata:
        st = threefive.Stream(tsdata)
        while True:
            cue = st.decode_next()
            if not cue:
                return False
            if cue:
                if cue.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_proxy writes all ts packets are written to stdout
|      for piping into another program like mplayer.
|      SCTE-35 cues are printed to stderr.

Stream.proxy Example

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

  • Stream.show()
|  show(self)
|   List programs and streams and info for MPEGTS

Stream.show() Example

>>>> from threefive import Stream
>>>> Stream('https://slo.me/plp0.ts').show()
    Service:    fancy ˹
    Provider:   fu-corp
    Pcr Pid:    1051[0x41b]
    Streams:
                Pid: 1051[0x41b]        Type: 0x1b AVC Video
                Pid: 1052[0x41c]        Type: 0x3 MP2 Audio
                Pid: 1054[0x41e]        Type: 0x6 PES Packets/Private Data
                Pid: 1055[0x41f]        Type: 0x86 SCTE35 Data
Need to verify your splice points?
  • 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 -

image


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.charset issue #55

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

Example Usage:

>>> from threefive import Cue,upids
>>> i="/DBKAAAAAAAAAP/wBQb+YtC8/AA0AiZDVUVJAAAD6X/CAAD3W3ACEmJibG5kcHBobkQCAsGDpQIAAAAAAAEKQ1VFSRSAIyowMljRk9c="

>>> upids.charset
'ascii'
>>> cue=Cue(i)
>>> cue.decode()
ascii
True
>>> cue.descriptors[0].segmentation_upid
'bblndpphnD\x02\x02���\x02\x00\x00'

>>> upids.charset="utf16"
>>> cue.decode()
utf16
True
>>> 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
from threefive import Cue, TimeSignal
from threefive.descriptors import SpliceDescriptor
  • 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}') for k,v in vars(c.descriptors[0]).items() if v is not None}

tag = 47
descriptor_length = 23
identifier = fufu
private_data = b'threefive kicks ass'

Powered by threefive


sideways inject SCTE-35 into HLS via manifest manipulation.
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.
x9k3: SCTE-35 HLS Segmenter and Cue Inserter.
amt-play uses x9k3.
m3ufu: SCTE-35 m3u8 Parser.
six2scte35: ffmpeg changes SCTE-35 stream type to 0x06 bin data, six2scte35 changes it back.
SuperKabuki: SCTE-35 Packet Injection.
showcues m3u8 SCTE-35 parser.

threefive online SCTE-35 Encoder
threefive online SCTE-35 parser

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 are no longer being accepted.

NEW! threefive now has experimental DVB DAS Support ETSI TS 103 752-1

SCTE-35 Parsing Shoot Out:

threefive's People

Contributors

airlangga-m2amedia avatar audiodan avatar deepsource-autofix[bot] avatar deepsourcebot avatar futzu avatar gitfu avatar jamesfining avatar richard-vd avatar vladdoster avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

threefive's Issues

Does anyone speak DASH?

DVB.org put threefive in DVB-DASH Spec as a SCTE-35 parser.
I feel like I need to add direct DASH support to threefive or build something like x9k3 for DASH.
If you do speak DASH, let's collaborate. You can write code, or just advise me.
If you know DASH, say something. That's the point of this issue.

Adrian

TUI for Encoding SCTE35 String Values

I don't care what it looks like, that's up to you.
I'll explain functionality, but keep your code loose,
we have to figure it out as we go.

Vlad, ask questions as they come up.

We can do it in small chunks.
Let's start with the Splice Command.

On the TUI, we need to select a splice command.

  • BandwidthReservation
  • PrivateCommand
  • SpliceInsert
  • SpliceNull
  • TimeSignal

Selecting a command should trigger the fields
for that command to appear.

Fields are determined by which command is selected
get them by creating an instance of the command

These fields need to be editable.

>>>> from threefive.commands import SpliceInsert
>>>> si = SpliceInsert()
>>>> vars(si)
{'command_length': 0, 'bites': None, 'name': 'Splice Insert', 'time_specified_flag': None, 
'pts_time': None, 'break_auto_return': None, 'break_duration': None, 'splice_event_id': None, 
'splice_event_cancel_indicator': None, 'out_of_network_indicator': None, 'program_splice_flag': None, 
'duration_flag': None, 'splice_immediate_flag': None, 'components': None, 'component_count': None,
'unique_program_id': None, 'avail_num': None, 'avail_expected': None}

>>>> from threefive.commands import TimeSignal
>>>> ts = TimeSignal()
>>>> vars(ts)
{'command_length': 0, 'bites': None, 'name': 'Time Signal', 'time_specified_flag': None, 'pts_time': None}

Use whatever TUI you like, but have you seen this?
npyscreen

Can't parse valid SCTE-35

Here it is:
/DBlAAAAAAAAAP/wBQb+GVJTDABPcAZNRFNOQzUCRUNVRUkAAKTff8MAACky4A8xdXJuOnV1aWQ6QnJlYWstQjAwMjA4NTU2ODlfMDAxMi0wNy0xMC1YMDExMjUxNjEyNDAAAPkSB7E=

This is valid SCTE-35 found in the wild (in a DSM-CC stream).
It is perfectly parsable with https://comcast.github.io/scte35-js/

For reference, this is the code the HbbTV app uses to parse it:
http://hbbtv.mediaset.net/app/mplayhbbtvgoldzoo/dev/js/component/scte35Parser.js

Looks like threefive doesn't have support for custom descriptors maybe? It seems to define a custom tag=0x70 descriptor.

I tried to do something like this: https://github.com/futzu/scte35-threefive/blob/master/examples/upid/custom_upid_handling.py
but unfortunately it doesn't work since as soon as threefive sees the unhadled tag=0x70 descriptor (not in known descriptor map) it refuses to parse extra stuff. After that there is a regular tag=2 descriptor but it gets ignored, and I get an empty descriptor array in the end. @futzu I guess this last one is a bug, it shouldn't ignore known descriptors.

I'd like to custom-process the tag=0x70 descriptor AND get the content of the tag=2 one

Decoder exception error - threefive v.2.2.99

Hi,

Encountering an exception error when using the threefive.decode function.

Sample base64 message:
/DA4AAAAAAAAAP/wFAUACM+hf+//BEfkX34AUmNiAAAAAAATAAVTQVBTAQEKQ1VFSQCfMzIwKsHmwpY=

Command used:

threefive.decode('/DA4AAAAAAAAAP/wFAUACM+hf+//BEfkX34AUmNiAAAAAAATAAVTQVBTAQEKQ1VFSQCfMzIwKsHmwpY=')

Error Message:
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/threefive/decode.py", line 38, in _read_stuff
with open(stuff, "rb") as tsdata:
FileNotFoundError: [Errno 2] No such file or directory: '/DA4AAAAAAAAAP/wFAUACM+hf+//BEfkX34AUmNiAAAAAAATAAVTQVBTAQEKQ1VFSQCfMzIwKsHmwpY='

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/threefive/decode.py", line 44, in _read_stuff
with open(stuff, "rb") as tsdata:
FileNotFoundError: [Errno 2] No such file or directory: '/DA4AAAAAAAAAP/wFAUACM+hf+//BEfkX34AUmNiAAAAAAATAAVTQVBTAQEKQ1VFSQCfMzIwKsHmwpY='

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "", line 1, in
File "/usr/local/lib/python3.9/site-packages/threefive/decode.py", line 122, in decode
return _read_stuff(stuff)
File "/usr/local/lib/python3.9/site-packages/threefive/decode.py", line 48, in _read_stuff
return _decode_and_show(stuff)
File "/usr/local/lib/python3.9/site-packages/threefive/decode.py", line 20, in _decode_and_show
if cue.decode():
File "/usr/local/lib/python3.9/site-packages/threefive/cue.py", line 67, in decode
after_dscrptrs = self._mk_descriptors(after_cmd)
File "/usr/local/lib/python3.9/site-packages/threefive/cue.py", line 156, in _mk_descriptors
self._descriptorloop(bites, dll)
File "/usr/local/lib/python3.9/site-packages/threefive/cue.py", line 82, in _descriptorloop
spliced = splice_descriptor(bites)
File "/usr/local/lib/python3.9/site-packages/threefive/descriptors.py", line 247, in splice_descriptor
spliced = descriptor_maptag
File "/usr/local/lib/python3.9/site-packages/threefive/descriptors.py", line 24, in init
self.parse_id()
File "/usr/local/lib/python3.9/site-packages/threefive/descriptors.py", line 44, in parse_id
raise Exception('Identifier Is Not "CUEI"')
Exception: Identifier Is Not "CUEI"

The show function will properly convert the message but decode is throwing this error on the base64 messages.

Commands Used:
cue=threefive.Cue('/DA4AAAAAAAAAP/wFAUACM+hf+//BEfkX34AUmNiAAAAAAATAAVTQVBTAQEKQ1VFSQCfMzIwKsHmwpY=')

cue.decode()
Traceback (most recent call last):
File "", line 1, in
File "/usr/local/lib/python3.9/site-packages/threefive/cue.py", line 67, in decode
after_dscrptrs = self._mk_descriptors(after_cmd)
File "/usr/local/lib/python3.9/site-packages/threefive/cue.py", line 156, in _mk_descriptors
self._descriptorloop(bites, dll)
File "/usr/local/lib/python3.9/site-packages/threefive/cue.py", line 82, in _descriptorloop
spliced = splice_descriptor(bites)
File "/usr/local/lib/python3.9/site-packages/threefive/descriptors.py", line 247, in splice_descriptor
spliced = descriptor_maptag
File "/usr/local/lib/python3.9/site-packages/threefive/descriptors.py", line 24, in init
self.parse_id()
File "/usr/local/lib/python3.9/site-packages/threefive/descriptors.py", line 44, in parse_id
raise Exception('Identifier Is Not "CUEI"')
Exception: Identifier Is Not "CUEI"

cue.show()
{
"info_section": {
"table_id": "0xfc",
"section_syntax_indicator": false,
"private": false,
"sap_type": "0x3",
"sap_details": "No Sap Type",
"section_length": 56,
"protocol_version": 0,
"encrypted_packet": false,
"encryption_algorithm": 0,
"pts_adjustment": 0.0,
"cw_index": "0x0",
"tier": "0xfff",
"splice_command_length": 20,
"splice_command_type": 5,
"descriptor_loop_length": 19
},
"command": {
"calculated_length": 20,
"name": "Splice Insert",
"time_specified_flag": true,
"pts_time": 48519.8631,
"break_auto_return": false,
"break_duration": 59.993267,
"splice_event_id": 577441,
"splice_event_cancel_indicator": false,
"out_of_network_indicator": true,
"program_splice_flag": true,
"duration_flag": true,
"splice_immediate_flag": false,
"unique_program_id": 0,
"avail_num": 0,
"avail_expected": 0
},
"descriptors": []
}

Appears to be working here using this online tool: https://comcast.github.io/scte35-js/

Can you please take a look at this issue?
Do let me know if you need more information.

crash in some SCTE-35 packet in v.2.2.37

I haven't yet found the the exact packet data leading to this crash. Here is the traceback:

  File "./mcastc.py", line 74, in <module>
    read_stream(mcast_sock)
  File "./mcastc.py", line 50, in read_stream
    ts.decode()
  File "./mcastc.py", line 35, in decode
    cue = self._parser(pkt)
  File "/usr/local/lib/python3.6/site-packages/threefive/stream.py", line 173, in _parser
    return self._parse_scte35(pkt, pid)
  File "/usr/local/lib/python3.6/site-packages/threefive/stream.py", line 208, in _parse_scte35
    return Cue(pkt, packet_data)
  File "/usr/local/lib/python3.6/site-packages/threefive/cue.py", line 59, in __init__
    self._parse(payload)
  File "/usr/local/lib/python3.6/site-packages/threefive/cue.py", line 64, in _parse
    payload = self._mk_descriptors(payload)
  File "/usr/local/lib/python3.6/site-packages/threefive/cue.py", line 92, in _mk_descriptors
    self._descriptorloop(payload, dll)
  File "/usr/local/lib/python3.6/site-packages/threefive/cue.py", line 103, in _descriptorloop
    spliced = self._set_splice_descriptor(payload)
  File "/usr/local/lib/python3.6/site-packages/threefive/cue.py", line 181, in _set_splice_descriptor
    spliced.decode(bitbin)
  File "/usr/local/lib/python3.6/site-packages/threefive/segmentation.py", line 102, in decode
    self._set_segmentation(bitbin) 
  File "/usr/local/lib/python3.6/site-packages/threefive/segmentation.py", line 128, in _set_segmentation
    self.segmentation_upid_type = bitbin.asint(8)  # 1 byte
  File "/usr/local/lib/python3.6/site-packages/bitn.py", line 20, in asint
    return (self.bits >> (self.idx)) & ~(~0 << num_bits)
ValueError: negative shift count   

: W0703: Catching too general exception Exception (broad-except)

Vlad,
do you have time to sort this out?

I can post the script I use to test all the cases.

************* Module threefive.decode
threefive/decode.py:25:11: W0703: Catching too general exception Exception (broad-except)
threefive/decode.py:32:15: W0703: Catching too general exception Exception (broad-except)
threefive/decode.py:49:11: W0703: Catching too general exception Exception (broad-except)
threefive/decode.py:58:11: W0703: Catching too general exception Exception (broad-except)
threefive/decode.py:68:11: W0703: Catching too general exception Exception (broad-except)

'hlsparse.py' for SCTE-35 HLS parsing throws error "FileNotFoundError: [Errno 2] No such file or directory"

Hi @futzu

When using hlsparse.py with the following command: pypy3 hlsparse.py <m3u8_manifest> I keep getting the following error:

Traceback (most recent call last):
  File "hlsparse.py", line 12, in <module>
    with open(sys.argv[1], "r") as manifest:
FileNotFoundError: [Errno 2] No such file or directory: 

I have tried with:

  • 5 different Live HLS streams from my side, which have been on-air for the past few years. It does not work and throws the above error
  • The 2 m3u8 examples available on your github. It works.

Would you happen to know what could be the issue? I can provide you with the m3u8 manifest that are causing the issue.

ts_scte_parser.py not decoding SCTE packets

Hello,
I'm trying to decode SCTE packets from a transport stream via ts_scte_parser.py but it seems like it is not able to decode SCTE packet in the incoming stream while other applications was able to detect it.

Version: v.2.1.97

I've edited port and mcast_ip as per my requirement

    def __init__(self, mcast_ip, if_ip="0.0.0.0", hostname="0.0.0.0", port=5511):
        self.HOST = hostname

if __name__ == "__main__":
    test = TSScte35Parser(mcast_ip="225.5.1.1")
    test.run()

Handle unknown chars in segmentation_upid

Input /DBKAAAAAAAAAP/wBQb+YtC8/AA0AiZDVUVJAAAD6X/CAAD3W3ACEmJibG5kcHBobkQCAsGDpQIAAAAAAAEKQ1VFSRSAIyowMljRk9c=

Actual behaviour: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc1 in position 12: invalid start byte at https://github.com/futzu/scte35-threefive/blob/f41c8a614abc1c866c5ddb48caf54368c235145a/threefive/bitn.py#L55

Expected behaviour:

  • Either, having that exception handled, setting segmentation_upid to a default string
  • Or, allowing user to provide encoding charset

Crash when decoding private descriptors from PrivateCommand

I'm experiencing an issue when reading SCTE-35 cues from an MPEG-TS stream using this library. The stream in question has periodic PrivateCommand SCTE-35 command types, as shown below in the pcrextract output.

* pcrextract: PID: 0x0033 (51), SCTE 35 command PrivateCommand
* pcrextract: PID: 0x0033 (51), SCTE 35 command PrivateCommand
* pcrextract: PID: 0x0033 (51), SCTE 35 command PrivateCommand
* pcrextract: PID: 0x0033 (51), SCTE 35 command PrivateCommand
* pcrextract: PID: 0x0033 (51), SCTE 35 command PrivateCommand
* pcrextract: PID: 0x0033 (51), SCTE 35 command SpliceInsert out, exec at PTS 0x1089E9D7D

Observed behavior

Everytime a PrivateCommand appears in the PID, threefive crashes.

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/cmcdaniels/.pyenv/versions/3.12.1/lib/python3.12/site-packages/threefive/stream.py", line 234, in decode
    cue = self._parse(pkt)
          ^^^^^^^^^^^^^^^^
  File "/Users/cmcdaniels/.pyenv/versions/3.12.1/lib/python3.12/site-packages/threefive/stream.py", line 486, in _parse
    cue = self._parse_scte35(pkt, pid)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cmcdaniels/.pyenv/versions/3.12.1/lib/python3.12/site-packages/threefive/stream.py", line 534, in _parse_scte35
    cue = self._parse_cue(pay, pid)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cmcdaniels/.pyenv/versions/3.12.1/lib/python3.12/site-packages/threefive/stream.py", line 511, in _parse_cue
    if cue.decode():
       ^^^^^^^^^^^^
  File "/Users/cmcdaniels/.pyenv/versions/3.12.1/lib/python3.12/site-packages/threefive/cue.py", line 72, in decode
    bites = self._mk_descriptors(bites)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cmcdaniels/.pyenv/versions/3.12.1/lib/python3.12/site-packages/threefive/cue.py", line 165, in _mk_descriptors
    self._descriptor_loop(bites[:dll])
  File "/Users/cmcdaniels/.pyenv/versions/3.12.1/lib/python3.12/site-packages/threefive/cue.py", line 84, in _descriptor_loop
    spliced = splice_descriptor(loop_bites)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cmcdaniels/.pyenv/versions/3.12.1/lib/python3.12/site-packages/threefive/descriptors.py", line 403, in splice_descriptor
    spliced = ( SpliceDescriptor(bites),descriptor_map[tag](bites))[tag in descriptor_map]
                                        ~~~~~~~~~~~~~~^^^^^
KeyError: 68

After digging through the code, it seems that the decode logic is expecting a defined value within the descriptor map for the descriptor type, but since my stream has private descriptors tags an exception occurs on the lookup. The tag is able to be any u32 so it would seem there needs to be support for private descriptors added to this library.

syntax error in Python 3.4

# pip3 install threefive
Downloading/unpacking threefive
  Downloading threefive-2.1.79-py3-none-any.whl
Requirement already satisfied (use --upgrade to upgrade): bitn>=0.0.27 in /usr/local/lib/python3.4/dist-packages (from threefive)
Installing collected packages: threefive
*** Error compiling '/tmp/pip-build-a1bjun2e/threefive/threefive/segmentation.py'...
  File "/tmp/pip-build-a1bjun2e/threefive/threefive/segmentation.py", line 137
    return f'{upid_map[upid_type][0]}:{upid_id}'
                                               ^
SyntaxError: invalid syntax

Successfully installed threefive
Cleaning up...

Decode / encode round trip produces unexpected results

Here's something I ran into:

from threefive import Cue

orig = '/DAlAAAAAyiYAP/wFAVNqW2Jf+//WP/YQ/4AUnrYAAEBAQAAGk9JoA=='
orig_cue = Cue(orig)
orig_cue.decode()

new_cue = Cue(orig_cue.encode())
new_cue.decode()

print(orig_cue.get()['info_section']['pts_adjustment_ticks'], new_cue.get()['info_section']['pts_adjustment_ticks'])

Different results for the two values: 207000 206999


I think the issue is probably in the encode process:

It looks like pts_adjustment_ticks is still saved as an integer, so perhaps it could be encoded again directly instead of going through the computed value?

Encoding and Inserting SCTE35 packets into MPEG-TS video

  • I'm about to start adding SCTE35 encoding methods.
    • I created the bitn.NBin class to make encoding data to easy.
    • check this out:
      * The first method _parse_break, is the decoder using bitn.BitBin.
      • _encode_break, the encoder using bitn.NBin follows the same flow as the decoder method.
    def _parse_break(self, bitbin):
        """
        SpliceInsert._parse_break(bitbin) is called
        if SpliceInsert.duration_flag is set
        """
        self.break_auto_return = bitbin.asflag(1)
        bitbin.forward(6)
        self.break_duration = bitbin.as90k(33)

    def _encode_break(self, nbin):
        """
        SpliceInsert._encode_break(nbin) is called
        if SpliceInsert.duration_flag is set
        """
        nbin.add_flag(self.break_auto_return)
        nbin.forward(6)
        nbin.add_90k(self.break_duration, 33)

    ```

2 quick points

With REF to issue 68

  1. Apologies if I've misunderstood, but just to confirm, are you saying that preroll.py and cue_list.py are known as not to work, or should they be working?
  2. I had to use the following with cue2vtt.py
$ python3 cue2vtt.py cam-scte-out.ts /dev/stdin vlc cam-scte-out.ts -sub -
WEBVTT


 00:00:2.2 -->  00:00:7.2 align:left
command_length  :  20
command_type  :  5
name  :  Splice Insert
time_specified_flag  :  True
pts_time  :  5.0
pts_time_ticks  :  450000
break_auto_return  :  True
break_duration  :  10.0
break_duration_ticks  :  900000
splice_event_id  :  256
splice_event_cancel_indicator  :  False
out_of_network_indicator  :  True
program_splice_flag  :  True
duration_flag  :  True
splice_immediate_flag  :  False
unique_program_id  :  0
avail_num  :  0
avail_expected  :  1

 00:00:3.033 -->  00:00:8.033 align:left
command_length  :  20
command_type  :  5
name  :  Splice Insert
time_specified_flag  :  True
pts_time  :  5.0
pts_time_ticks  :  450000
break_auto_return  :  True
break_duration  :  10.0
break_duration_ticks  :  900000
splice_event_id  :  256
splice_event_cancel_indicator  :  False
out_of_network_indicator  :  True
program_splice_flag  :  True
duration_flag  :  True
splice_immediate_flag  :  False
unique_program_id  :  0
avail_num  :  0
avail_expected  :  1

Issue with decode() and decode_proxy() functions

Using decode() or decode_proxy(), both functions are giving below error:

File "proxy_d35.py", line 6, in
sp.decode_proxy()
File "/usr/local/lib/python3.6/dist-packages/threefive/stream.py", line 127, in decode_proxy
cue = self._parser(pkt)
File "/usr/local/lib/python3.6/dist-packages/threefive/stream.py", line 186, in _parser
return self._parse_scte35(pkt, pid)
File "/usr/local/lib/python3.6/dist-packages/threefive/stream.py", line 219, in _parse_scte35
packet_data = self._mk_packet_data(pid)
File "/usr/local/lib/python3.6/dist-packages/threefive/stream.py", line 147, in _mk_packet_data
packet_data["pts"] = round(self._prog_pts[prgm], 6)

Is than an issue in release or something I might be doing wrong?

Consider making crcmod an optional dependency

Commit 1feffa6 allowed users without crcmod installed to use the bits ofCue functionality that don't require it.

However, setup.py brings in crcmod unconditionally. This means that users who install threefive wind up with crcmod.

My request is to make crcmod an optional dependency, i.e. to put it in an extras_require. This would allow users to control whether they get it or not.

In that world pip install threefive wouldn't bring in crcmod, but something like pip install threefive[decoding] would.

The same request could be made re: pyaes, but it's less pressing in that case because it is a pure Python module that doesn't need to be compiled for exotic architectures.

Thanks for this excellent library!

cue decode fails with UniCodeDecode error, unhandled exception

When using:

Base64 = "/DBhAAAAAyiY///wBQb+AS4ycQBLAh5DVUVJLcYgPX//AAD4flUICAAEzagtxiA9NAECAAACKUNVRUkAAAAAf78MGlZNTlUB+pOe004CEe2tjg5Azy/ChQGxHMmtAQAAphbmGg=="

cue = threefive.Cue(Base64)

cue.decode()

we get:

File "/Users/xxx/xxx/threefive/bitn.py", line 54, in as_ascii
return int.to_bytes(stuff, wide, byteorder="big").decode("utf-8")
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xfa in position 1: invalid start byte

Where we would expect cue.decode() to return False or fail (?)

Having latest.py file breaks rpm build

Hello,

The file latest.py in master branch breaks RPM builds, because it tries to compile to python bytecode all the files and latest.py is not syntaxically correct:

+ /usr/lib/rpm/brp-python-bytecompile /usr/bin/python3 1
*** Error compiling '/builds/myproject/.venv/lib/python3.6/site-packages/threefive/latest.py'...
  File "/latest.py", line 1
    2.3.75
         ^
SyntaxError: invalid syntax

SCTE-35 Preroll

I think your side of the conversation in the tsduck thread was deleted. Let's pick it up here.

It will take me some time to get up to speed on installing and using threefive. In the meantime, where do I grab the latest version that you added preroll to?

edit: I see it, it's the 'master' code thread (terrible name, github... come on)

IndexError thrown in _decode_components() threefive/commands.py:217

The list is empty upon initialization. A loop then attempts to assign indexes of the empty list

Thrown from threefive/commands.py:217

    def _decode_components(self, bitbin):
        """
        SpliceInsert._decode_components loops
        over SpliceInsert.components,
        and is called from SpliceInsert.decode()
        """
        self.component_count = bitbin.as_int(8)
        self.components = []
        for i in range(0, self.component_count):
>           self.components[i] = bitbin.as_int(8)
E           IndexError: list assignment index out of range

The request that triggered the error

% python3               
Python 3.11.2 (main, Feb 16 2023, 02:55:59) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from threefive import SpliceInsert
>>> bites = b'\xfc0%\x00\x00\x00\x03(\x98\x00\xff\xf0\x14\x05d-\xe5Y\x7f\xef\xfe\x00\x1a\xea\xa0\xfe\x00Re\xc0\x00\x01\x01\x01\x00\x00\x88\x8c\xc9\xed'
>>> SpliceInsert(bites).decode()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/homebrew/lib/python3.11/site-packages/threefive/commands.py", line 190, in decode
    self._decode_components(bitbin)
  File "/opt/homebrew/lib/python3.11/site-packages/threefive/commands.py", line 217, in _decode_components
    self.components[i] = bitbin.as_int(8)
    ~~~~~~~~~~~~~~~^^^
IndexError: list assignment index out of range

A possible fix

    def _decode_components(self, bitbin):
        """ 
        SpliceInsert._decode_components loops
        over SpliceInsert.components,
        and is called from SpliceInsert.decode()
        """
        self.component_count = bitbin.as_int(8)
        self.components = [bitbin.as_int(8) for _ in range(0, self.component_count)]

A similar error is expected to be thrown at threefive/commands.py:339 as well

ERROR while decoding TS: 21 since v.2.2.09

When running examples/multicast/ts_scte_parser.py I only see ERROR while decoding TS: 21 for each SCTE-35 packet, using v.2.2.25 on Python 3.6. The latest working release is v.2.2.01, v.2.2.09 is the first release with this error.

'hasp.py' for SCTE-35 HLS parsing throws error "OverflowError: int too big to convert" or "[Errno 104] Connection reset by peer"

Hi @futzu

First of all, great project! :)

When using hasp.py with the following command: pypy3 hasp.py <m3u8_manifest> I keep getting the following errors:

OverflowError: int too big to convert 

and

[Errno 104] Connection reset by peer

I have tried with:

  • Few different Live HLS streams from my side, which have been on-air for the past few years, and also the m3u8 manifest from #37 renamed to "9syd_master1.m3u8.txt" (to comply with github upload). It does not work and throws the above error
  • The 2 m3u8 examples available on your github. It works.

Would you happen to know what could be the issue? I can provide you with the m3u8 manifest that are causing the issue.

Tried editing a splice insert from example but getting different result

I am trying to edit a break_duration from given example in: https://github.com/futzu/scte35-threefive/blob/master/Encoding.md#edit-a-splice-insert-command-in-a--scte35-cue.

I have used the same commands shared in example,

Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import threefive
>>> Base64 = "/DAvAAAAAAAA///wFAVIAACPf+/+c2nALv4AUsz1AAAAAAAKAAhDVUVJAAABNWLbowo="
>>> cue = threefive.Cue(Base64)
>>> cue.decode()
True
>>> cue.command
{'command_length': 20, 'command_type': 5, 'name': 'Splice Insert', 'time_specified_flag': True, 'pts_time': 21514.559089, 'pts_time_ticks': 1936310318, 'break_auto_return': True, 'break_duration': 60.293567, 'break_duration_ticks': 5426421, 'splice_event_id': 1207959695, 'splice_event_cancel_indicator': False, 'out_of_network_indicator': True, 'program_splice_flag': True, 'duration_flag': True, 'splice_immediate_flag': False, 'component_count': None, 'components': None, 'unique_program_id': 0, 'avail_num': 0, 'avail_expected': 0}
>>> cue.command.break_duration= 90.0
>>> cue.encode()
'/DAvAAAAAAAA///wFAVIAACPf+/+c2nALv4AUsz1AAAAAAAKAAhDVUVJAAABNWLbowo='

But instead of getting expected result as in example, I am getting the previous/original encoded string with break_duration:60.293567.

Could you please suggest if I am missing something?

Thank You

@richard

Firstly, please don't hit me 4 pull requests at once,
I have spent hours on Hex padding alone.
Lets do them one at a time please.

I can live with padding, but this is not so good,

 nibbles = (num_bits + 3) >> 2

Since we already have k, instead of num_bits use k.bit_length()

Is there any other way we can do it besides +3 >>2 ?

The code does not have to be super short.

You also need to verify your changes work with NBin for encoding,
that may need updating, I don't know.

Lets resolve this , and then we can get to UUID, we have to have an encode method.
I forgot about that.

Issues with example scripts

Firstly, super many thanks for these tools, amazing work!

Similar to Issue26, I really want the preroll, but most of the example scripts I get various errors for.

cue_list_too.py seems the only one I can get working

$ pypy3 cue_list_too.py cam-scte-out.ts
{
    "info_section": {
        "table_id": "0xfc",
...truncated

But not cue_list.py

$ pypy3 cue_list.py cam-scte-out.ts
Traceback (most recent call last):
  File "cue_list.py", line 34, in <module>
    for cue in do():
  File "cue_list.py", line 25, in do
    cue = strm.decode_next()
  File "/home/me/.local/lib/pypy3.6/site-packages/threefive/stream.py", line 206, in decode_next
    cue = self.decode(func=False)
  File "/home/me/.local/lib/pypy3.6/site-packages/threefive/stream.py", line 170, in decode
    if not self._find_start():
  File "/home/me/.local/lib/pypy3.6/site-packages/threefive/stream.py", line 154, in _find_start
    one = self._tsdata.read(1)
ValueError: read of closed file

Incorrect pts time

We have an 86MB, 9 to 10 minute video snippet where for the first of 2 SCTE messages, in version 2.2.99 the pcr shows 87726.9313 and the pts shows 87726.952711. But when moving the 2.3.01 or 2.3.03, the pcr shows 87726.9313 but the pts shows 87.381333. When monitoring a live video stream over 10 hours we may see approximately 8 out of 61 Scte35 messages have this exact 87.381333 value instead of the correct value. PCR looks correct in all cases and the same video stream sample under 2.2.99 looks correct as well. So we think there may have been a bug introduced. I probably should not make the video snippet available publicly as it has copyright material in it, but if there is a way we could share it with you privately I would be happy to do so. Thoughts?

Additional question, in the xml output does pts_time already have the pts_adjustment factored in or not? We had assumed not and so were adding it in our calculations, but looking at the code it looks like it might be in which case we would be double adding it.

cmd.py in latest PyPI release is not syntaxically correct

Hello,

The file cmd.py in the latest release in PyPI breaks RPM builds, because it tries to compile to python bytecode all the files and cmd.py is not syntaxically correct:

*** Error compiling '/builds/myproject/.venv/lib/python3.6/site-packages/threefive/cmd.py'...
  File "/cmd.py", line 1
    /*
    ^
SyntaxError: invalid syntax

encoding a signal with auto_return = true

Hi Adrian,

I can't tell if this is supposed to be supported or not, but I have a use case where I need to generate a SCTE35 with auto_return = true .

From your code I see various mentions of cmd.break_auto_return, but when I try to encode a SCTE35 (TimeSignal) with this set to True, it seems to be ignored. Please let me know, thanks.

decode local ts file fails...

getting ''str' object has no attribute 'read'' from threefive version 2.4.9

when running the posted example code below.

Can you suggest any updates or changes?

% cat z.py
"""
Example using Stream.decode() with a custom function
to return a list of cues from a video

Usage:

pypy3 cue_list.py video.ts

"""
import sys
import threefive
from threefive import Stream, Cue

# A list to hold cues 
CUES=[]

def cue_func(cue):
    """
    threefive.Stream.decode cqn be passed in a
    custom function that accepts a Cue as the only arg.
    this is an example of a custom function.

    """
    CUES.append(cue)
    cue.show()


def do():
    """
    do collects a list of Cues  from a Stream
    """
    arg = sys.argv[1]
    strm = Stream(arg)
    strm.decode(func=cue_func) # cue_func is the custom function from above


if __name__ == "__main__":
    do()
    [print(cue.command.name,cue.encode()) for cue in CUES]

the output in full reads:
     python3 z.py /path/index_1_00008.ts

Traceback (most recent call last):
  File "/Users/robclem/scripts/z.py", line 40, in <module>
    do()
  File "/Users/robclem/scripts/z.py", line 36, in do
    strm.decode(func=cue_func) # cue_func is the custom function from above
    ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/robclem/scripts/threefive/stream.py", line 79, in decode
    if not self._find_start():
           ^^^^^^^^^^^^^^^^^^
  File "/Users/robclem/scripts/threefive/stream.py", line 65, in _find_start
    one = self._tsdata.read(1)
          ^^^^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'read'

unable to re-encode the following base64 /DA8AAAAAAAAAP/wBQb/ZoaJUwAmAiRDVUVJBPpHwH/9AABSY2IMEERJU0NTTURDMDc3MzAwTEg0AQESS6TU

I'm trying to re-encode a signal after modifying pts value without touching any of the descriptors
I have no issues with several segmentation_type_id 48 that I've tried but for some reason I can't re-encode the following one (segmentation type id 52) - /DA8AAAAAAAAAP/wBQb/ZoaJUwAmAiRDVUVJBPpHwH/9AABSY2IMEERJU0NTTURDMDc3MzAwTEg0AQESS6TU

Traceback (most recent call last):
  File "/Users/lshaked/scte35/./scte35processor.py", line 24, in <module>
    sig_binary_data = new_cue.encode()
  File "/Users/lshaked/scte35/lib/python3.9/site-packages/threefive/cue.py", line 210, in encode
    dscptr_bites = self._unloop_descriptors()
  File "/Users/lshaked/scte35/lib/python3.9/site-packages/threefive/cue.py", line 262, in _unloop_descriptors
    dbite_chunks = [dsptr.encode() for dsptr in self.descriptors]
  File "/Users/lshaked/scte35/lib/python3.9/site-packages/threefive/cue.py", line 262, in <listcomp>
    dbite_chunks = [dsptr.encode() for dsptr in self.descriptors]
  File "/Users/lshaked/scte35/lib/python3.9/site-packages/threefive/descriptors.py", line 335, in encode
    self._encode_segmentation(nbin)
  File "/Users/lshaked/scte35/lib/python3.9/site-packages/threefive/descriptors.py", line 381, in _encode_segmentation
    self._encode_segments(nbin)
  File "/Users/lshaked/scte35/lib/python3.9/site-packages/threefive/descriptors.py", line 394, in _encode_segments
    self._chk_var(int, nbin.add_int, "sub_segment_num", 8)  # 1 byte
  File "/Users/lshaked/scte35/lib/python3.9/site-packages/threefive/base.py", line 104, in _chk_var
    raise ValueError(err_mesg)
ValueError: sub_segment_num is not set, it should be type <class 'int'>

here is the new_cue before encode:

{'bites': None, 'info_section': {'table_id': '0xfc', 'section_syntax_indicator': False, 'private': False, 'sap_type': '0x3', 'sap_details': 'No Sap Type', 'section_length': 60, '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': 5, 'splice_command_type': 6, 'descriptor_loop_length': 38, 'crc': '0x124ba4d4'}, 'command': {'command_length': 5, 'command_type': 6, 'name': 'Time Signal', 'bites': None, 'time_specified_flag': True, 'pts_time': 66834.03677777777, 'pts_time_ticks': 6015063310}, 'descriptors': [{'tag': 2, 'descriptor_length': 36, 'name': 'Segmentation Descriptor', 'identifier': 'CUEI', 'bites': None, 'provider_avail_id': None, 'components': [], 'segmentation_event_id': '0x4fa47c0', 'segmentation_event_cancel_indicator': False, 'component_count': None, 'program_segmentation_flag': True, 'segmentation_duration_flag': True, 'delivery_not_restricted_flag': True, 'web_delivery_allowed_flag': None, 'no_regional_blackout_flag': None, 'archive_allowed_flag': None, 'device_restrictions': None, 'segmentation_duration': 59.993267, 'segmentation_duration_ticks': 5399394, 'segmentation_message': 'Provider Placement Opportunity Start', 'segmentation_upid_type': 12, 'segmentation_upid_type_name': 'MPU', 'segmentation_upid_length': 16, 'segmentation_upid': {'format_identifier': 1145656131, 'private_data': '0x534d44433037373330304c48'}, 'segmentation_type_id': 52, 'segment_num': 1, 'segments_expected': 1, 'sub_segment_num': None, 'sub_segments_expected': None}], 'packet_data': None}

code is:

import threefive
from threefive import Cue

sig_binary_data="/DA8AAAAAAAAAP/wBQb/ZoaJUwAmAiRDVUVJBPpHwH/9AABSY2IMEERJU0NTTURDMDc3MzAwTEg0AQESS6TU" # doesn't work
#sig_binary_data="/DA8AAAAAAAAAP/wBQb+TiMTvgAmAiRDVUVJBRjPQX/9AAELyNwMEERJU0MxMDAzMDI0Nl85OTkwAQEAQHzt" # works PA start
#sig_binary_data="/DA8AAAAAAAAAP/wBQb/Xz380gAmAiRDVUVJBPpG2X/9AADN+HUMEERJU0MxMDAzNTk2N185OTkwAQE1K48T" # works
scte_35_cue = threefive.Cue(sig_binary_data)
scte_35_cue.decode()
print(scte_35_cue)
print(scte_35_cue.descriptors[0].sub_segment_num,"\n")
scte_35_dict = scte_35_cue.get()
for x in scte_35_dict['descriptors'][0].keys():
    print("key:",x,"value:",scte_35_dict['descriptors'][0][x])
print(scte_35_dict['descriptors'][0].keys())
print("signal pts_time",scte_35_dict['command']['pts_time_ticks'])
recorded_pts_time = scte_35_dict['command']['pts_time_ticks'] + 3003
print("new pts",str(recorded_pts_time))
#scte_35_dict['command']['pts_time_ticks'] = recorded_pts_time
#scte_35_dict['command']['pts_time'] = float(recorded_pts_time/90000)
new_cue = threefive.Cue()
new_cue.load(scte_35_dict)
print(new_cue)
sig_binary_data = new_cue.encode()
print(sig_binary_data)

using latest version - 2.3.81

hasp.py error in decoding hls manifest

the issue I am experiencing when testing hasp.py with my project is that when I try to it to decode scte35 messages I am getting the following error:
======error log start
Traceback (most recent call last):
File "/home/dhuang/scte35-threefive/examples/hls/hasp.py", line 177, in
HASP(arg).decode()
File "/home/dhuang/scte35-threefive/examples/hls/hasp.py", line 150, in decode
hold = stanza.decode()
File "/home/dhuang/scte35-threefive/examples/hls/hasp.py", line 96, in decode
self._aes_start(line)
File "/home/dhuang/scte35-threefive/examples/hls/hasp.py", line 38, in _aes_start
self._aes_decrypt()
File "/home/dhuang/scte35-threefive/examples/hls/hasp.py", line 49, in _aes_decrypt
with urllib.request.urlopen(self.segment) as infile:
File "/usr/lib/python3.8/urllib/request.py", line 222, in urlopen
return opener.open(url, data, timeout)
File "/usr/lib/python3.8/urllib/request.py", line 531, in open
response = meth(req, response)
File "/usr/lib/python3.8/urllib/request.py", line 640, in http_response
response = self.parent.error(
File "/usr/lib/python3.8/urllib/request.py", line 569, in error
return self._call_chain(*args)
File "/usr/lib/python3.8/urllib/request.py", line 502, in _call_chain
result = func(*args)
File "/usr/lib/python3.8/urllib/request.py", line 649, in http_error_default
raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 404: Not Found
======error log end
the url & command I am using as a test:
python3 $HOME/scte35-threefive/examples/hls/hasp.py https://9now-livestreams.akamaized.net/hls/live/2007330/ch9-syd/master1.m3u8

interestingly, when I download the manifest(m3u8) manually and using hlsparse.py and it decoded without any issue.
I am hoping someone can assist me in pointing me in the right direction.
Sorry for being new to this and please let me know if you require additional info.

Thanks,
ozrocozroc

Issue with UTF-8 Error

Hi,

Encountering a utf-8 error when splice command type is 6 - time signal.

Sample error message:
UnicodeDecodeError: “utf-8’ codec can’t decode byte 0xd0 in position 1: invalid continuation byte

Sample base64 encoded message:
/DBBAAAAAAAA///wBQb/fe82YAArAilDVUVJAAAAAH+/DBpWTU5VAdD3rHrUzxHnv9QAJrlBTzAB+olIUQEAAAzazmo=

Edit 08/23: Fixed the typo on the base64 message.

FFmpeg SCTE-35 Stream Type

futzu wrote.. "I don't know how to stop ffmpeg from changing the SCTE35 stream type to 0x6."

FYI - I did some experiments with this and it seems to be a limitation of FFmpeg's TS muxer.

If I use the NUT container (FFmpeg's proprietary container format), the -tag:d can be used to set the type. In theory, this tag should also be capable of setting the type via a fourcc value, but that does not seem to be supported for data streams. Hex or decimal work with a NUT container though.

In this case, the stream description was changed from scte_35 to none.

$ ffmpeg -hide_banner -i "https://futzu.com/xaa.ts" -map 0:v:0 -codec:v copy -streamid 0:0x101 -map 0:a:0 -codec:a copy -streamid 1:0x102 -map 0:d:0 -codec:d:0 copy -streamid 2:0x103 -tag:d:0 "0x86" -f nut OUTFILE.nut -y

# or

$ ffmpeg -hide_banner -i "https://futzu.com/xaa.ts" -map 0:v:0 -codec:v copy -streamid 0:0x101 -map 0:a:0 -codec:a copy -streamid 1:0x102 -map 0:d:0 -codec:d:0 copy -streamid 2:0x103 -tag:d:0 "134" -f nut OUTFILE.nut -y

$ ffprobe OUTFILE.nut
  Stream #0:0: Video: h264 (Main) (H264 / 0x34363248), yuv420p(tv, bt470bg), 544x576 [SAR 64:33 DAR 544:297], 50 fps, 50 tbr, 90k tbn, 50 tbc
  Stream #0:1(eng): Audio: mp2 (P[0][0][0] / 0x0050), 48000 Hz, stereo, s16p, 128 kb/s
  Stream #0:2: Data: none ([134][0][0][0] / 0x0086)

If the same command is used with an MPEG-TS container, the FFmpeg muxer overwrites the type to be 0x6. At runtime / console output, it claims it is setting the 0x86, but when probed, the file output is 0x6.

In this case, the stream type was changed from scte_35 to bin_data.

$ ffmpeg -hide_banner -i "https://futzu.com/xaa.ts" -map 0:v:0 -codec:v copy -streamid 0:0x101 -map 0:a:0 -codec:a copy -streamid 1:0x102 -map 0:d:0 -codec:d:0 copy -streamid 2:0x103 -tag:d:0 "0x86" -f mpegts OUTFILE.ts -y

$ ffprobe OUTFILE.ts
  Stream #0:0[0x101]: Video: h264 (Main) ([27][0][0][0] / 0x001B), yuv420p(tv, bt470bg, top first), 544x576 [SAR 64:33 DAR 544:297], 25 fps, 50 tbr, 90k tbn, 50 tbc
  Stream #0:1[0x102](eng): Audio: mp2 ([3][0][0][0] / 0x0003), 48000 Hz, stereo, fltp, 128 kb/s
  Stream #0:2[0x103]: Data: bin_data ([6][0][0][0] / 0x0006)

This is not an issue with threefive, it is purely informational and can be closed. I recognize that threefive successfully works around FFmpeg's limitation. Raising the ticket was just a means of sharing knowledge and narrowing down where FFmpeg was struggling. Neither does the ticket solve the issue because the ultimate goal is setting it in a TS container, not a NUT container. Feel free to close this issue.

'hasp.py' unable to parse #EXT-X-CUE-OUT, #EXT-X-CUE-OUT-CONT, #EXT-X-CUE-IN

Hi @futzu

Following up with another issue, hasp.py is unable to parse the SCTE-35 HLS cues in my two m3u8 playlists:

  • #EXT-X-CUE-OUT
  • #EXT-X-CUE-OUT-CONT
  • #EXT-X-CUE-IN

Playlist 1 has the following syntax:

#EXT-X-CUE-OUT:90.023267
#EXT-X-CUE-OUT-CONT:6.006/90.023267
(..... Removed on purpose ....)
#EXT-X-CUE-OUT-CONT:78.078/90.023267
#EXTINF:6.006,
#EXT-X-CUE-OUT-CONT:84.084/90.023267
#EXTINF:5.939267,
#EXT-X-CUE-IN
#EXTINF:4.004,
#EXTINF:6.006,

While Playlist 2 has a different structure:

#EXTINF:4.0,
sftv_480p/media-ucvpbplas_228029.ts
#EXTINF:1.12,
sftv_480p/media-ucvpbplas_228030.ts
#EXT-OATCLS-SCTE35:c3BsaWNlLTY3MTE0OTQw
#EXT-X-ASSET:CAID=splice-67114940
#EXT-X-CUE-OUT:30.00
#EXT-X-DATERANGE:ID="splice-67114940",START-DATE="2022-02-22T04:33:38.99782700Z",PLANNED-DURATION=30.00,SCTE35-OUT=0x000000000000000000000000000000323032322D30322D32325430343A33333A33382E39393738323730305A
#EXTINF:7.0,
sftv_480p/media-ucvpbplas_228031.ts
#EXT-X-CUE-OUT-CONT:ElapsedTime=7.00,Duration=30.00,SCTE35=c3BsaWNlLTY3MTE0OTQw
#EXTINF:4.0,
sftv_480p/media-ucvpbplas_228032.ts
#EXT-X-CUE-OUT-CONT:ElapsedTime=11.00,Duration=30.00,SCTE35=c3BsaWNlLTY3MTE0OTQw
#EXTINF:4.0,
sftv_480p/media-ucvpbplas_228033.ts
#EXT-X-CUE-OUT-CONT:ElapsedTime=15.00,Duration=30.00,SCTE35=c3BsaWNlLTY3MTE0OTQw
#EXTINF:4.0,
sftv_480p/media-ucvpbplas_228034.ts
#EXT-X-CUE-OUT-CONT:ElapsedTime=19.00,Duration=30.00,SCTE35=c3BsaWNlLTY3MTE0OTQw
#EXTINF:4.0,
sftv_480p/media-ucvpbplas_228035.ts
#EXT-X-CUE-OUT-CONT:ElapsedTime=23.00,Duration=30.00,SCTE35=c3BsaWNlLTY3MTE0OTQw
#EXTINF:4.0,
sftv_480p/media-ucvpbplas_228036.ts
#EXTINF:4.0,
sftv_480p/media-ucvpbplas_228036.ts
#EXT-X-CUE-OUT-CONT:ElapsedTime=27.00,Duration=30.00,SCTE35=c3BsaWNlLTY3MTE0OTQw
#EXTINF:3.0,
sftv_480p/media-ucvpbplas_228037.ts
#EXT-X-CUE-IN
#EXT-X-DATERANGE:ID="splice-67114941",DURATION=0.00,SCTE35-IN=0x000000000000000000000000000000323032322D30322D32325430343A33343A30382E39393738323730305A
#EXTINF:5.0,
sftv_480p/media-ucvpbplas_228038.ts

None of them can be parsed with hasp.py, could you have a look? I can provide you the m3u8 files.

The SCTE35 "section_length" value is always 3 bytes less than the actual size

I'm learning SCTE35 staff and I can't figure out one thing. Why is the length of "section_length" always less than 3 bytes from the actual size?
Example:

>>> test_raw = "/DAlAAAAAAAAAP/wFAUAAAZ5f+/+AAAAAH4Ae5igAAAAAAAA5nk27g=="
>>> test_cue = Cue(test_raw)
>>> test_cue.decode()
>>> test_cue.show()
"section_length": 37,

But if I will check len(test_cue.bites) will see 40:

>>> len(test_cue.bites)
40

I've already checked the documentation but did not find answer:

Probably some "header" that is not included in the "section_length". From the source of the lib i see that "section_length" calculation like this - # 11 bytes for info section + command + 2 descriptor loop length + descriptor loop + 4 for crc, but I`m still confused.

Сould someone please explain why we have this 3 byte difference in "section_length" and the actual size ?

Sorry, the question not related to the project

multi packet PMT and PAT/PMT with adaptation field not working

Hi,

Current TS and section parsing code doesn't handle:

  1. large PMT sections (>1 TS packet)
  2. multiple table sections in a single TS packet (section starting at arbitrary position in TS payload)
  3. TS packets with smaller payload (adaptation field stuffing)

I am working on it.

UPID parsing with strange output

I'm using threefive in a tool that monitors a live HLS stream.
The stream follows the "Addressable TV" spec (https://www.snptv.org/wp-content/uploads/2020/08/SNPTV-AFMM-Addressable-TV-UK-version-2.0.6.1.pdf)

It contains SCTE35 payloads with a 0x02 descriptor "CallAdServer", which contains a 16-byte upid of type "MPU" (Managed Private UPID - 0x0C), which should have the following structure:

• Bytes from 1 to 4: ASCII code for ‘ADFR’ = 0x41444652
• Byte #5: version number: by default 1 = 0x01
• Bytes 6 to 7: TV channel ID - CNI: example for TF1 = 0x33F1
• Bytes 8 to 11 (4 bytes): YYYYMMDD = 0x01341403 (20190211 in decimal)
• Bytes 12 and 13: Ad break code: example = 0x0462 (break 1122)
• Bytes 14 to 16: duration of the break in ms = 0x01C070 (duration: 114800 ms, meaning: 1minute,
54secondes and 20 frames).

Here is an example: 0xFC305E00014ECF5F9800FFF00506FE156DE4F0004802144355454900065E087FFF00001B77400000300710021F4355454900065EFF7FBF0C10414446520133F10134B04F065E060220020000020F4355454900065E077FBF00003106106F4BCB35

I'm a bit puzzled about the output of threefive for that UPID:

{
            "tag": 2,
            "descriptor_length": 31,
            "name": "Segmentation Descriptor",
            "identifier": "CUEI",
            "components": [],
            "segmentation_event_id": "0x65eff",
            "segmentation_event_cancel_indicator": false,
            "program_segmentation_flag": true,
            "segmentation_duration_flag": false,
            "delivery_not_restricted_flag": true,
            "segmentation_upid_type": 12,
            "segmentation_upid_type_name": "MPU",
            "segmentation_upid_length": 16,
            "segmentation_upid": {
                "format_identifier": 1094993490,
                "private_data": "0x133f10134b04f065e060220"
            },
            "segmentation_type_id": 2
        },

The private_data element does not seem to be a valid hex string.
Or at least Python's bytes.fromhex() refuses it.

By the way, would you consider adding the "CallAdServer" segmentation type ID in your table22?
In my code I simply add it in the following way:
threefive.segmentation.table22[2] = "Call Ad Server"

OSError: [Errno 55] No buffer space available

not sure is anyone encounter this error
but when i try to
strm = threefive.Stream('udp://@224.2.2.4:1234')
i keep getting
OSError: [Errno 55] No buffer space available

ffplay playing good

Input #0, mpegts, from 'udp://@224.2.2.4:1234':KB sq= 0B f=0/0 Duration: N/A, start: 630.728000, bitrate: N/A Program 1 Metadata: service_name : Service01 service_provider: FFmpeg Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(tv, bt709, progressive), 1280x720 [SAR 1:1 DAR 16:9], 25 fps, 25 tbr, 90k tbn Stream #0:1[0x101](und): Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 98 kb/s Stream #0:2[0x258]: Data: scte_35

System: macos Big Sur
Python : Python 3.9.13 with Anaconda

thanks

threefive v2.2.91 bug in Stream class

Version 2.2.91 has a bug when parsing MPEGTS with the Stream class.
Stream._parse calls Stream.,_split_by_idx too often.

  ncalls    tottime  percall  cumtime  percall filename:lineno(function)
  377960    0.129    0.000    0.129    0.000    stream.py:185(_split_by_idx)

fixed in version 2.2.93

 ncalls     tottime  percall  cumtime  percall filename:lineno(function)
    35      0.000    0.000    0.000    0.000   stream.py:169(_split_by_idx)

Issue with time signal SCTE-35

Hi,

The segmentation type id for time signal messages are not being converted correctly.

For version 2.2.25:
The segmentation_type_id incorrectly shows "segmentation_type_id": 109.

For version 2.2.95:
When using three5.show(), The descriptors are empty.
When using three5.decode(), TypeError: cannot unpack non-iterable bool object.

Example of the segmentation type id being properly converted:
https://openidconnectweb.azurewebsites.net/Cue?cue=/DA7AAAAAtaWAAAABQb+t0XCpwAlAiNDVUVJ/////3+/ARRtc25iY19FUDAyNTA0MTMwMTIxOQEBAKAxzMk=

Sample cue in base64:
/DA7AAAAAtaWAAAABQb+t0XCpwAlAiNDVUVJ/////3+/ARRtc25iY19FUDAyNTA0MTMwMTIxOQEBAKAxzMk=

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.