Skip to content

Commit

Permalink
feat: add parser for SCTE
Browse files Browse the repository at this point in the history
  • Loading branch information
Wkkkkk committed Jan 24, 2024
1 parent f1ad982 commit 001a17a
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 3 deletions.
8 changes: 5 additions & 3 deletions cmd/mp2ts-info/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ var usg = `Usage of %s:
func parseOptions() internal.Options {
opts := internal.Options{ShowStreamInfo: true, Indent: true}
flag.BoolVar(&opts.ShowService, "service", false, "show service information")
flag.BoolVar(&opts.ShowSCTE35, "scte35", false, "show SCTE35 information")
flag.BoolVar(&opts.Indent, "indent", true, "indent JSON output")
flag.BoolVar(&opts.Version, "version", false, "print version")

flag.Usage = func() {
Expand All @@ -34,13 +36,13 @@ func parseOptions() internal.Options {
return opts
}

func parseInfo(ctx context.Context, w io.Writer, f io.Reader, o internal.Options) error {
return internal.ParseInfo(ctx, w, f, o)
func parse(ctx context.Context, w io.Writer, f io.Reader, o internal.Options) error {
return internal.ParseInfoAndSCTE35(ctx, w, f, o)
}

func main() {
o, inFile := internal.ParseParams(parseOptions)
err := internal.Execute(os.Stdout, o, inFile, parseInfo)
err := internal.Execute(os.Stdout, o, inFile, parse)
if err != nil {
log.Fatal(err)
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/Eyevinn/mp2ts-tools
go 1.19

require (
github.com/Comcast/gots/v2 v2.2.1
github.com/Eyevinn/mp4ff v0.41.1-0.20240123164056-bbd4656aecc0
github.com/asticode/go-astits v1.13.0
github.com/stretchr/testify v1.8.4
Expand All @@ -11,6 +12,7 @@ require (
require (
github.com/asticode/go-astikit v0.42.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/g8rswimmer/error-chain v1.0.0 // indirect
github.com/go-test/deep v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/Comcast/gots/v2 v2.2.1 h1:LU/SRg7p2KQqVkNqInV7I4MOQKAqvWQP/PSSLtygP2s=
github.com/Comcast/gots/v2 v2.2.1/go.mod h1:firJ11on3eUiGHAhbY5cZNqG0OqhQ1+nSZHfsEEzVVU=
github.com/Eyevinn/mp4ff v0.41.1-0.20240123164056-bbd4656aecc0 h1:TVO5vjF4CEAB6OKR0RWMQFvHXtZ+UdKCsp+awECiIVE=
github.com/Eyevinn/mp4ff v0.41.1-0.20240123164056-bbd4656aecc0/go.mod h1:w/6GSa5ghZ1VavzJK6McQ2/flx8mKtcrKDr11SsEweA=
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
Expand All @@ -8,6 +10,8 @@ github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/g8rswimmer/error-chain v1.0.0 h1:WnwnunlvqtGPHVHmBfbmUyAgrtag8Y6nNpwLWmtSYOQ=
github.com/g8rswimmer/error-chain v1.0.0/go.mod h1:XPJ/brUsL7yzc5VRlIxtf9GvoUqnOKVI9fg2MB5DWx8=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
Expand Down
95 changes: 95 additions & 0 deletions internal/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ package internal

import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"strings"

"github.com/Comcast/gots/v2/packet"
"github.com/Comcast/gots/v2/psi"
"github.com/Comcast/gots/v2/scte35"
"github.com/asticode/go-astits"
chain "github.com/g8rswimmer/error-chain"
)

func ParseAll(ctx context.Context, w io.Writer, f io.Reader, o Options) error {
Expand Down Expand Up @@ -155,6 +162,8 @@ dataLoop:
streamInfo = &ElementaryStreamInfo{PID: es.ElementaryPID, Codec: "AAC", Type: "audio"}
case astits.StreamTypeH265Video:
streamInfo = &ElementaryStreamInfo{PID: es.ElementaryPID, Codec: "HEVC", Type: "video"}
case astits.StreamTypeSCTE35:
streamInfo = &ElementaryStreamInfo{PID: es.ElementaryPID, Codec: "SCTE35", Type: "cue"}
}

if streamInfo != nil {
Expand Down Expand Up @@ -183,3 +192,89 @@ dataLoop:

return jp.Error()
}

func ParseSCTE35(ctx context.Context, w io.Writer, f io.Reader, o Options) error {
reader := bufio.NewReader(f)
_, err := packet.Sync(reader)
if err != nil {
return fmt.Errorf("syncing with reader %w", err)
}
pat, err := psi.ReadPAT(reader)
if err != nil {
return fmt.Errorf("reading PAT %w", err)
}

var pmts []psi.PMT
pm := pat.ProgramMap()
for _, pid := range pm {
pmt, err := psi.ReadPMT(reader, pid)
if err != nil {
return fmt.Errorf("reading PMT %w", err)
}
pmts = append(pmts, pmt)
}

jp := &JsonPrinter{W: w, Indent: o.Indent}
scte35PIDs := make(map[int]bool)
for _, pmt := range pmts {
for _, es := range pmt.ElementaryStreams() {
if es.StreamType() == psi.PmtStreamTypeScte35 {
scte35PIDs[es.ElementaryPid()] = true
break
}
}
}

// Print SCTE35
for {
var pkt packet.Packet
if _, err := io.ReadFull(reader, pkt[:]); err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF {
break
}
return fmt.Errorf("reading Packet %w", err)
}

currPID := packet.Pid(&pkt)
if scte35PIDs[currPID] {
pay, err := packet.Payload(&pkt)
if err != nil {
return fmt.Errorf("cannot get payload for packet on PID %d Error=%s\n", currPID, err)
}
msg, err := scte35.NewSCTE35(pay)
if err != nil {
return fmt.Errorf("cannot parse SCTE35 Error=%v\n", err)
}
scte35 := toSCTE35(uint16(currPID), msg)
jp.Print(scte35, o.ShowSCTE35)
}
}

return jp.Error()
}

func ParseInfoAndSCTE35(ctx context.Context, w io.Writer, f io.Reader, o Options) error {
var out1, out2 bytes.Buffer
_, err := CopyToAll(f, &out1, &out2)
ec := chain.New()
if err != nil {
ec.Add(errors.New("failed to copy input"))
}

f1 := strings.NewReader(out1.String())
infoErr := ParseInfo(ctx, w, f1, o)
f2 := strings.NewReader(out2.String())
scteErr := ParseSCTE35(ctx, w, f2, o)
if infoErr == nil && scteErr == nil {
return nil
}

ec.Add(infoErr)
ec.Add(scteErr)
return ec
}

func CopyToAll(rd io.Reader, wrs ...io.Writer) (int64, error) {
mwr := io.MultiWriter(wrs...)
return io.Copy(mwr, rd)
}
2 changes: 2 additions & 0 deletions internal/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestParseFile(t *testing.T) {
fullOptionsWith35PicWithoutNALUSEI.ShowSEIDetails = false

parseInfoFunc := ParseInfo
parseInfoAndSCTE35Func := ParseInfoAndSCTE35
parseAllFunc := ParseAll

cases := []struct {
Expand All @@ -35,6 +36,7 @@ func TestParseFile(t *testing.T) {
}{
{"avc", "testdata/avc_with_time.ts", Options{MaxNrPictures: 10, Indent: true, ShowStreamInfo: true, ShowPS: true, ShowStatistics: true}, "testdata/golden_avc.txt", parseAllFunc},
{"avc_without_ps", "testdata/avc_with_time.ts", Options{MaxNrPictures: 10, ShowStreamInfo: true}, "testdata/golden_avc_without_ps.txt", parseInfoFunc},
{"avc_with_scte35", "testdata/80s_with_ad.ts", Options{MaxNrPictures: 0, ShowStreamInfo: true, ShowService: true, ShowSCTE35: true}, "testdata/golden_avc_with_scte35.txt", parseInfoAndSCTE35Func},
{"bbb_1s", "testdata/bbb_1s.ts", fullOptionsWith35Pic, "testdata/golden_bbb_1s.txt", parseAllFunc},
{"bbb_1s_indented", "testdata/bbb_1s.ts", fullOptionsWith2Pic, "testdata/golden_bbb_1s_indented.txt", parseAllFunc},
{"bbb_1s_no_nalu_no_sei", "testdata/bbb_1s.ts", fullOptionsWith35PicWithoutNALUSEI, "testdata/golden_bbb_1s_no_nalu(no_sei).txt", parseAllFunc},
Expand Down
83 changes: 83 additions & 0 deletions internal/scte35.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package internal

import (
"github.com/Comcast/gots/v2/scte35"
)

type SCTE35Info struct {
PID uint16 `json:"pid"`
SpliceCommand SpliceCommand `json:"spliceCommand"`
SegDesc []SegmentationDescriptor `json:"segmentationDes,omitempty"`
}

type SpliceCommand struct {
Type string `json:"type"`
EventId uint32 `json:"eventId,omitempty"`
PTS uint64 `json:"pts,omitempty"`
Duration uint64 `json:"duration,omitempty"`
}

type SegmentationDescriptor struct {
EventId uint32 `json:"eventId"`
Type string `json:"type"`
Direction string `json:"direction,omitempty"`
Duration uint64 `json:"duration,omitempty"`
}

func toSCTE35(pid uint16, msg scte35.SCTE35) SCTE35Info {
scte35Info := SCTE35Info{PID: pid, SpliceCommand: toSpliceCommand(msg.CommandInfo())}

if insert, ok := msg.CommandInfo().(scte35.SpliceInsertCommand); ok {
scte35Info.SpliceCommand = toSpliceInsertCommand(insert)
}
for _, desc := range msg.Descriptors() {
segDesc := toSegmentationDescriptor(desc)
scte35Info.SegDesc = append(scte35Info.SegDesc, segDesc)
}

return scte35Info
}

func toSpliceCommand(spliceCommand scte35.SpliceCommand) SpliceCommand {
spliceCmd := SpliceCommand{Type: getCommandType(spliceCommand)}
if spliceCommand.HasPTS() {
spliceCmd.PTS = uint64(spliceCommand.PTS())
}

return spliceCmd
}

func toSegmentationDescriptor(segdesc scte35.SegmentationDescriptor) SegmentationDescriptor {
segDesc := SegmentationDescriptor{}
segDesc.Direction = getSegDirection(segdesc)
segDesc.EventId = segdesc.EventID()
segDesc.Type = scte35.SegDescTypeNames[segdesc.TypeID()]
if segdesc.HasDuration() {
segDesc.Duration = uint64(segdesc.Duration())
}
return segDesc
}

func toSpliceInsertCommand(spliceCommand scte35.SpliceInsertCommand) SpliceCommand {
spliceCmd := SpliceCommand{Type: getCommandType(spliceCommand)}
spliceCmd.EventId = spliceCommand.EventID()
if spliceCommand.HasDuration() {
spliceCmd.Duration = uint64(spliceCommand.Duration())
}

return spliceCmd
}

func getCommandType(spliceCommand scte35.SpliceCommand) string {
return scte35.SpliceCommandTypeNames[spliceCommand.CommandType()]
}

func getSegDirection(segdesc scte35.SegmentationDescriptor) string {
if segdesc.IsIn() {
return "In"
}
if segdesc.IsOut() {
return "Out"
}
return ""
}
Binary file added internal/testdata/80s_with_ad.ts
Binary file not shown.
5 changes: 5 additions & 0 deletions internal/testdata/golden_avc_with_scte35.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"pid":256,"codec":"AVC","type":"video"}
{"pid":257,"codec":"AAC","type":"audio"}
{"pid":1001,"codec":"SCTE35","type":"cue"}
{"SDT":[{"serviceId":1,"descriptors":[{"serviceName":"Service01","providerName":"FFmpeg"}]}]}
{"pid":1001,"spliceCommand":{"type":"SpliceInsert","eventId":255,"duration":1800000}}
1 change: 1 addition & 0 deletions internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Options struct {
VerbosePSInfo bool
ShowNALU bool
ShowSEIDetails bool
ShowSCTE35 bool
ShowStatistics bool
}

Expand Down

0 comments on commit 001a17a

Please sign in to comment.