Install | Go Docs | Examples | cuei wins the SCTE-35 Parser Shoot Out
- Parses SCTE-35 Cues from MPEGTS or Bytes or Base64 or Hex or Int or Octal or even Base 36.
- Parses SCTE-35 Cues spread over multiple MPEGTS packets
- Supports multi-packet PAT and PMT tables
- Supports multiple MPEGTS Programs and multiple SCTE-35 streams
- Encodes Time Signals and Splice Inserts with Descriptors and Upids.
package main
import (
"os"
"github.com/futzu/cuei"
)
func main(){
arg := os.Args[1]
stream := cuei.NewStream()
stream.Decode(arg)
}
- Cyclomatic complexity score for v1.2.39 is 1.96
-
-
cuei.Stream
-
cuei.Cue
-
I do a lot of releases, well sometimes. Whenever I make an improvement or fix a bug, I do a release. Small changes as they happen not all the changes at once.
go get github.com/futzu/cuei@latest
- cueidemo.go
package main
import (
"os"
"fmt"
"github.com/futzu/cuei"
)
func main(){
arg := os.Args[1]
stream := cuei.NewStream()
cues := stream.Decode(arg)
for _,cue := range cues {
fmt.Printf("Command is a %v\n", cue.Command.Name)
}
}
go build cueidemo.go
./cueidemo a_video_with_scte35.ts
Next File: mpegts/out.ts
{
"Name": "Splice Info Section",
"TableID": "0xfc",
"SectionSyntaxIndicator": false,
"Private": false,
"Reserved": "0x3",
"SectionLength": 49,
"ProtocolVersion": 0,
"EncryptedPacket": false,
"EncryptionAlgorithm": 0,
"PtsAdjustment": 0,
"CwIndex": "0x0",
"Tier": "0xfff",
"SpliceCommandLength": 20,
"SpliceCommandType": 5,
"DescriptorLoopLength": 12,
"Command": {
"Name": "Splice Insert",
"CommandType": 5,
"SpliceEventID": "0x5d",
"OutOfNetworkIndicator": true,
"ProgramSpliceFlag": true,
"DurationFlag": true,
"BreakDuration": 90.023266,
"TimeSpecifiedFlag": true,
"PTS": 38113.135577
},
"Descriptors": [
{
"Tag": 1,
"Length": 10,
"Identifier": "CUEI",
"Name": "DTMF Descriptor",
"PreRoll": 177,
"DTMFCount": 4,
"DTMFChars": 4186542473
}
],
"Packet": {
"PacketNumber": 73885,
"Pid": 515,
"Program": 51,
"Pcr": 38104.526277,
"Pts": 38105.268588
}
}
package main
import (
"fmt"
"github.com/futzu/cuei"
)
func main(){
cue := cuei.NewCue()
data := "/DA7AAAAAAAAAP/wFAUAAAABf+/+AItfZn4AKTLgAAEAAAAWAhRDVUVJAAAAAX//AAApMuABACIBAIoXZrM="
cue.Decode(data)
fmt.Println("Cue as Json")
cue.Show()
}
package main
import (
"fmt"
"github.com/futzu/cuei"
)
type Cue2 struct {
cuei.Cue // Embed cuei.Cue
}
func (cue2 *Cue2) Show() { // Override Show
fmt.Printf("%+v",cue2.Command)
}
func main(){
var cue2 Cue2
data := "/DA7AAAAAAAAAP/wFAUAAAABf+/+AItfZn4AKTLgAAEAAAAWAhRDVUVJAAAAAX//AAApMuABACIBAIoXZrM="
cue2.Decode(data)
cue2.Show()
}
package main
import (
"fmt"
"github.com/futzu/cuei"
)
type Cue2 struct {
cuei.Cue // Embed cuei.Cue
}
func (cue2 *Cue2) Show() { // Override Show
fmt.Println("Cue2.Show()")
fmt.Printf("%+v",cue2.Command)
fmt.Println("\n\ncuei.Cue.Show() from cue2.Show()")
cue2.Cue.Show() // Call the Show method from embedded cuei.Cue
}
func main(){
var cue2 Cue2
data := "/DA7AAAAAAAAAP/wFAUAAAABf+/+AItfZn4AKTLgAAEAAAAWAhRDVUVJAAAAAX//AAApMuABACIBAIoXZrM="
cue2.Decode(data)
cue2.Show()
}
/**
Show the packet PTS time and Splice Command Name of SCTE-35 Cues
in a MPEGTS stream.
**/
package main
import (
"os"
"fmt"
"github.com/futzu/cuei"
)
func main() {
arg := os.Args[1]
stream := cuei.NewStream()
cues := stream.Decode(arg)
for _,c := range cues {
fmt.Printf("PTS: %v, Splice Command: %v\n",c.PacketData.Pts, c.Command.Name )
}
}
- cuei can accept SCTE-35 data as JSON and encode it to Base64, Bytes, or Hex string.
- The function cuei.Json2Cue() accepts SCTE-35 JSON as input and returns a *cuei.Cue
package main
import (
"fmt"
"github.com/futzu/cuei"
)
func main() {
js := `{
"InfoSection": {
"Name": "Splice Info Section",
"TableID": "0xfc",
"SectionSyntaxIndicator": false,
"Private": false,
"Reserved": "0x3",
"SectionLength": 42,
"ProtocolVersion": 0,
"EncryptedPacket": false,
"EncryptionAlgorithm": 0,
"PtsAdjustment": 0,
"CwIndex": "0xff",
"Tier": "0xfff",
"CommandLength": 15,
"CommandType": 5
},
"Command": {
"Name": "Splice Insert",
"CommandType": 5,
"SpliceEventID": 5690,
"OutOfNetworkIndicator": true,
"ProgramSpliceFlag": true,
"TimeSpecifiedFlag": true,
"PTS": 23683.480033
},
"DescriptorLoopLength": 10,
"Descriptors": [
{
"Length": 8,
"Identifier": "CUEI",
"Name": "Avail Descriptor"
}
],
"Crc32": "0xd7165c79"
}
`
cue := cuei.Json2Cue(js) //
cue.AdjustPts(28.0) // Apply pts adjustment
fmt.Println("\nBytes:\n\t", cue.Encode()) // Bytes
fmt.Println("\nBase64:\n\t",cue.Encode2B64()) // Base64
fmt.Println("\nHex:\n\t",cue.Encode2Hex()) // Hex
}
- Output
Bytes:
[252 48 42 0 0 0 38 115 192 255 255 240 15 5 0 0 22 58 127 207 254 127 12 79 115
0 0 0 0 0 10 0 8 67 85 69 73 0 0 0 0 236 139 53 78]
Base64:
/DAqAAAAJnPA///wDwUAABY6f8/+fwxPcwAAAAAACgAIQ1VFSQAAAADsizVO
Hex:
0xfc302a0000002673c0fffff00f050000163a7fcffe7f0c4f7300000000000a00084355454900000000ec8b354e
- Create Stream Instance
- Read Bytes from the video stream (in multiples of 188)
- Call Stream.DecodeBytes(Bytes)
- Process [] *Cue returned by Stream.DecodeBytes
package main
import (
"fmt"
"github.com/futzu/cuei"
"os"
)
func main() {
arg := os.Args[1]
stream := cuei.NewStream() // (1)
bufSize := 32768 * 188 // Always read in multiples of 188
file, err := os.Open(arg)
if err != nil {
fmt.Printf("Unable to read %v\n", arg)
}
buffer := make([]byte, bufSize)
for {
_, err := file.Read(buffer) // (2)
if err != nil {
break
}
cues := stream.DecodeBytes(buffer) // (3)
for _, c := range cues { // (4)
fmt.Printf(" %v, %v\n", c.PacketData.Pts, c.Encode2B64())
}
}
}
- Output
60638.745877, /DAWAAAAAAAAAP/wBQb/RUqw1AAAd6OnQA==
60638.745877, /DAgAAAAAAAAAP/wDwUAAAABf//+AFJlwAABAAAAAMOOklg=
60640.714511, /DAWAAAAAAAAAP/wBQb/RU1wqAAAoqaOaA==
60640.714511, /DAgAAAAAAAAAP/wDwUAAAABf//+AFJlwAABAAAAAMOOklg=
60642.015811, /DAWAAAAAAAAAP/wBQb/RU9F4AAA9Te5ag==
60642.015811, /DAgAAAAAAAAAP/wDwUAAAABf//+AFJlwAABAAAAAMOOklg=
60642.749877, /DAWAAAAAAAAAP/wBQb/RVAwfAAAWOLrFQ==
60642.749877, /DAgAAAAAAAAAP/wDwUAAAABf//+AFJlwAABAAAAAMOOklg=
60644.718511, /DAWAAAAAAAAAP/wBQb/RVLwUAAAj7/Pgw==
60644.718511, /DAgAAAAAAAAAP/wDwUAAAABf//+AFJlwAABAAAAAMOOklg=
60646.720511, /DAWAAAAAAAAAP/wBQb/RVWwJAAA8pm/jg==
60646.720511, /DAgAAAAAAAAAP/wDwUAAAABf//+AFJlwAABAAAAAMOOklg=
60648.121911, /DAWAAAAAAAAAP/wBQb/RVec0gAAt0QzqA==
60648.121911, /DAgAAAAAAAAAP/wDwUAAAABf//+AFJlwAABAAAAAMOOklg=
60634.208011, /DAWAAAAAAAAAP/wBQb/RUR1fAAAik8gfQ==
60634.208011, /DAgAAAAAAAAAP/wDwUAAAABf//+AFJlwAABAAAAAMOOklg=
60634.675144, /DAWAAAAAAAAAP/wBQb/RUUxLAAABQVyEA==
60634.675144, /DAgAAAAAAAAAP/wDwUAAAABf//+AFJlwAABAAAAAMOOklg=
60636.710511, /DAWAAAAAAAAAP/wBQb/RUfxAAAA0lhWhg==
60636.710511, /DAgAAAAAAAAAP/wDwUAAAABf//+AFJlwAABAAAAAMOOklg=
Need a multicast sender? Try gums
for multicast we use the same four steps,
the only difference is we read the bytes from the network instead of a local file.
the only difference is we read the bytes from the network instead of a local file.
- Create Stream Instance
- Read Bytes from the video stream (in multiples of 188)
- Call Stream.DecodeBytes(Bytes)
- Process [] *Cue returned by Stream.DecodeBytes
package main
import (
"fmt"
"github.com/futzu/cuei"
"os"
"net"
)
func main() {
arg := os.Args[1]
stream := cuei.NewStream() // (1)
stream.Quiet = true
dgram:=1316 // <-- multicast dgram size is 1316 (188*7) for mpegts
bufSize := 100 * dgram
addr, _ := net.ResolveUDPAddr("udp", arg)
l, _ := net.ListenMulticastUDP("udp", nil, addr) // Multicast Connection
l.SetReadBuffer(bufSize)
for {
buffer := make([]byte, bufSize) // (2)
l.ReadFromUDP(buffer)
cues := stream.DecodeBytes(buffer) // (3)
for _, c := range cues { // (4)
fmt.Printf(" %v, %v\n", c.PacketData.Pts, c.Encode2B64())
}
}
}