/cuei

cuei is the Fastest SCTE35 Encoder/ Decoder on the planet. Now with Multicast support. Written in Go. Parses MPEGTS, Bytes, Base64, Hex, Int and more.

BSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

Install | Go Docs | Examples | cuei wins the SCTE-35 Parser Shoot Out

cuei is a SCTE-35 Parser lib written in Go.

Encoder/Decoder for SCTE-35


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

Want to parse an MPEGTS video and print the SCTE-35? 🛰️

Do it in ten lines.

package main                        

import (                              
        "os"                            
        "github.com/futzu/cuei"       
)                                    

func main(){                         
        arg := os.Args[1]             
        stream := cuei.NewStream()    
        stream.Decode(arg)           
}                                    

Latest version is One Two thirty-seven

  • Cyclomatic complexity score for v1.2.39 is 1.96

Documentation

Releases

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.

Install cuei

go get github.com/futzu/cuei@latest

Quick Demo

  • 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)
        }

}

build cueidemo

go build cueidemo.go

parse mpegts video for scte35

./cueidemo a_video_with_scte35.ts

output

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
    }
}

Parse base64 encoded SCTE-35

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()
}

Shadow a Cue struct method

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()
}

Call a shadowed method

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()
	
}

Use Dot notation to access SCTE-35 Cue values

/**
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 )
	}
}

Load JSON and Encode

  • 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

cuei.Stream

Custom Cue Handling for MPEGTS Streams

Four Steps
  1. Create Stream Instance
  2. Read Bytes from the video stream (in multiples of 188)
  3. Call Stream.DecodeBytes(Bytes)
  4. 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=

Custom Cue Handling for MPEGTS Streams Over Multicast

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.
  1. Create Stream Instance
  2. Read Bytes from the video stream (in multiples of 188)
  3. Call Stream.DecodeBytes(Bytes)
  4. 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())
	}
  }
}