futzu / threefive Goto Github PK
View Code? Open in Web Editor NEWSCTE-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
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
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
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.
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
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:
Would you happen to know what could be the issue? I can provide you with the m3u8 manifest that are causing the issue.
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()
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'
Hi,
Current TS and section parsing code doesn't handle:
I am working on it.
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.
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
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
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)
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.
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 (?)
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
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.
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:
_encode_pts_adjustment
uses nbin.add_90k(self.pts_adjustment, 33)
add_90k
uses floating point math instead of integer math.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?
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)
Are you for hire in scte35 related jobs? If not, just delete this issue and sorry for the post.
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.
Hi,
Is the 2nd argument missing?
Thanks!
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=
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
# 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...
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
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
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.
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.
NA
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
Hi @futzu
Following up with another issue, hasp.py
is unable to parse the SCTE-35 HLS cues in my two m3u8 playlists:
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.
With REF to issue 68
$ 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
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)
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
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
I have a tv HLS URL and I want to check scte-35 marker is present or not?
I tried through tsduck lib but I can't find any solution there.
Please share any command or solution
Thanks!
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.
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:
Would you happen to know what could be the issue? I can provide you with the m3u8 manifest that are causing the issue.
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:
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)
```
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?
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"
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.
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!
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
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
I can't find your email, but would love to reach out to you directly. REDACTED.
Thank you!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.