Giter VIP home page Giter VIP logo

cuei's Introduction

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


*️⃣ CUEI is the FourCC / identifier for SCTE-35, that's where I got the name.
  • 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 twenty-seven

  • cuei.Stream Now Supports Multicast!
  • Cyclomatic complexity score for v1.2.27 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())
	}
  }
}

cuei's People

Contributors

futzu avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar

cuei's Issues

Slice bounds out of range

When parsing certain SCTE-35 cues I get a panic:

panic: runtime error: slice bounds out of range [:408] with length 400

goroutine 1 [running]:
github.com/futzu/cuei.(*bitDecoder).chunk(0x14000118e40, 0x8)
        /Users/oleksiitorunov/go/pkg/mod/github.com/futzu/[email protected]/bitter.go:27 +0xac
github.com/futzu/cuei.(*bitDecoder).uInt64(0x10426bc20?, 0x14000120240?)
        /Users/oleksiitorunov/go/pkg/mod/github.com/futzu/[email protected]/bitter.go:55 +0x1c
github.com/futzu/cuei.(*bitDecoder).uInt8(...)
        /Users/oleksiitorunov/go/pkg/mod/github.com/futzu/[email protected]/bitter.go:34
github.com/futzu/cuei.(*Descriptor).decodeSegmentation(0x14000118d30, 0x14000118e40)
        /Users/oleksiitorunov/go/pkg/mod/github.com/futzu/[email protected]/descriptors.go:211 +0x144
github.com/futzu/cuei.(*Descriptor).segmentationDescriptor(0x14000118d30, 0x14000118e40, 0xb8?, 0x8c?)
        /Users/oleksiitorunov/go/pkg/mod/github.com/futzu/[email protected]/descriptors.go:165 +0x118
github.com/futzu/cuei.(*Descriptor).decode(0x0?, 0x2?, 0x0?, 0x0?)
        /Users/oleksiitorunov/go/pkg/mod/github.com/futzu/[email protected]/descriptors.go:91 +0x8c
github.com/futzu/cuei.(*Cue).dscptrLoop(0x1400012e040, 0x19, 0x14000118e40)
        /Users/oleksiitorunov/go/pkg/mod/github.com/futzu/[email protected]/cue.go:77 +0xc8
github.com/futzu/cuei.(*Cue).decodeBytes(0x1400012e040, {0x14000128040?, 0x14000118ea8?, 0x1?})
        /Users/oleksiitorunov/go/pkg/mod/github.com/futzu/[email protected]/cue.go:58 +0xdc
github.com/futzu/cuei.(*Cue).Decode(0x1400012e040, {0x104266f40?, 0x14000118f18?})
        /Users/oleksiitorunov/go/pkg/mod/github.com/futzu/[email protected]/cue.go:40 +0x104

The cue I'm attempting to decode: /DA0AAAAAAAAAAAABQb/UN6ZgAAZAhdDVUVJAIdqN3/TAAApMXgICNP3GiIAAL/Pk3w=

Decoding it like this:

c := cuei.NewCue()
ok := c.Decode("/DA0AAAAAAAAAAAABQb/UN6ZgAAZAhdDVUVJAIdqN3/TAAApMXgICNP3GiIAAL/Pk3w=")
if !ok {
    fmt.Println("failed to decode")
    os.Exit(1)
}

It's worth mentioning that I encoded this cue myself. So I believe it's the encoding that doesn't function properly. For example, if I decode a cue and then encode it again without changing any of its fields I get a somewhat different cue:

c := cuei.NewCue()
ok := c.Decode("/DA0AAAAAAAAAAAABQaB4zZ7tQAeAhxDVUVJAA6GjwDQAAESy7EICAAAAAAA0/cuIgAAXljZSQ==")
if !ok {
    fmt.Println("failed to decode")
    os.Exit(1)
}

fmt.Println(c.Encode2B64()) // Prints "/DA0AAAAAAAAAAAABQb/4zZ7tQAZAhdDVUVJAA6Gj3/TAAESy7EICNP3LiIAALikDmY="

And then it panics when I try to decode the resulting cue.

Can you take a look into it?

Thanks!

Segmentation Descriptors missing

When parsing the following B64:

/DByAAAAAAAAAv/wBQb+AAAAAABcAhRDVUVJUwIu0X/fAAApTwAAACIAAAIWQ1VFSSxMdth/3wAAKU8AAAA0AAAAAAIWQ1VFSRH5kzZ/3wAAKU8AAAA2AAAAAAIUQ1VFSUWu4aR/3wAAKU8AAAAwAADzsyAa

I only see segmentation_type_id 34 and 52. 54 and 48 are missing.

{ "InfoSection": { "Name": "Splice Info Section", "TableID": "0xfc", "SectionSyntaxIndicator": false, "Private": false, "Reserved": "0x3", "SectionLength": 114, "ProtocolVersion": 0, "EncryptedPacket": false, "EncryptionAlgorithm": 0, "PtsAdjustment": 0, "CwIndex": "0x2", "Tier": "0xfff", "CommandLength": 5, "CommandType": 6 }, "Command": { "Name": "Time Signal", "CommandType": 6, "TimeSpecifiedFlag": true }, "DescriptorLoopLength": 92, "Descriptors": [ { "Tag": 2, "Length": 20, "Identifier": "CUEI", "Name": "Segmentation Descriptor", "SegmentationEventID": "0x53022ed1", "ProgramSegmentationFlag": true, "SegmentationDurationFlag": true, "WebDeliveryAllowedFlag": true, "NoRegionalBlackoutFlag": true, "ArchiveAllowedFlag": true, "DeviceRestrictions": "No Restrictions", "SegmentationDuration": 30.08, "SegmentationMessage": "Break Start", "SegmentationTypeID": 34 }, { "Tag": 2, "Length": 22, "Identifier": "CUEI", "Name": "Segmentation Descriptor", "SegmentationEventID": "0x2c4c76d8", "ProgramSegmentationFlag": true, "SegmentationDurationFlag": true, "WebDeliveryAllowedFlag": true, "NoRegionalBlackoutFlag": true, "ArchiveAllowedFlag": true, "DeviceRestrictions": "No Restrictions", "SegmentationDuration": 30.08, "SegmentationMessage": "Provider Placement Opportunity Start", "SegmentationTypeID": 52 }, { "Identifier": "\u0002\u0016CU", "Name": "Avail Descriptor", "ProviderAvailID": 1162416633 }, {} ], "Crc32": 2145320960 }

JSON-and-Encode always use "AvailDescriptor.Identifier=CUEI" value even if setter or json static string is modified.

Example app JSON-and-Encode does not use .Identifier="TEST" or .Identifier="" setter? Value is always CUEI string, if providing an empty string my thinking was field could also be skipped in a binary output?
https://github.com/futzu/cuei#load-json-and-encode

Online scte parser for debug use: https://www.middleman.tv/scte35-parser

disclaimer: I don't have a deep knowledge of .Identifer field, I have not seen it other scte35 examples.

Example output:
/DAvAAAAAAAAAAFgFAUAAABvf+/+AAHd5v4AUmXA//8BAgAKAAhDVUVJAAABTDVM1CE=

Example app:

package main

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

func main() {

	js := `{
    "InfoSection": {
        "Name": "Splice Info Section",
        "TableID": "0xfc",
        "SectionSyntaxIndicator": false,
        "Private": false,
        "ProtocolVersion": 0,
        "EncryptedPacket": false,
        "EncryptionAlgorithm": 0,
        "PtsAdjustment": 0,
        "CwIndex": "0x00",
        "Tier": "0x16",
        "CommandType": 5
    },
    "Command": {
        "Name": "Splice Insert",
        "CommandType": 5,
        "SpliceEventID": 1,
        "OutOfNetworkIndicator": true,
        "ProgramSpliceFlag": true,
        "TimeSpecifiedFlag": true,
        "PTS": 0.0
    },
    "Descriptors": [
        {
            "Name": "Avail Descriptor"
        }
    ]
}
`
	cue :=  cuei.Json2Cue(js)	
	cue.AdjustPts(0.0)                      // 0..N seconds(float) pts adjustment
	cue.Command.PTS            = 1.359355   // 0..N seconds(float)
	cue.Command.AvailNum       = 1
	cue.Command.AvailExpected  = 2
	cue.Command.DurationFlag   = true       // write BreakDuration+AutoReturn fields
	cue.Command.BreakAutoReturn= true
	cue.Command.BreakDuration  = 60   // 0..N seconds
	cue.Command.UniqueProgramID= 65535
	cue.Command.SpliceEventID  = 111   // 111 or 222 or any 0..N value
	cue.Descriptors[0].ProviderAvailID=332
	
	fmt.Println("\nBytes:\n\t", cue.Encode())	// Bytes	
	fmt.Println("\nBase64:\n\t",cue.Encode2B64())  	// Base64	
	fmt.Println("\nHex:\n\t",cue.Encode2Hex()) 	// Hex
}

edit: edited example for more common default values for clarity.

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.