Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing SCTE-35 implicit signal closing rules. #63

Merged
merged 5 commits into from Jan 26, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -2,3 +2,4 @@
.DS_Store
.*.swp
*.ts
coverage.*
2 changes: 2 additions & 0 deletions scte35/doc.go
Expand Up @@ -233,6 +233,8 @@ type SegmentationDescriptor interface {
SubSegmentNumber() uint8
// SubSegmentsExpected returns the number of expected sub-segments for this descriptor.
SubSegmentsExpected() uint8
// HasSubSegments returns true if this segmentation descriptor has subsegment fields.
HasSubSegments() bool
}

// State maintains current state for all signals and descriptors. The intended
Expand Down
36 changes: 29 additions & 7 deletions scte35/segmentationdescriptor.go
Expand Up @@ -45,6 +45,7 @@ type segmentationDescriptor struct {
subSegsExpected uint8
spliceInfo SCTE35
eventCancelIndicator bool
hasSubSegments bool
}

type segCloseType uint8
Expand Down Expand Up @@ -148,6 +149,7 @@ func (d *segmentationDescriptor) parseDescriptor(data []byte) error {
if buf.Len() > 0 && (d.typeID == 0x34 || d.typeID == 0x36) {
d.subSegNum = readByte()
d.subSegsExpected = readByte()
d.hasSubSegments = true
}
}
return nil
Expand Down Expand Up @@ -255,19 +257,20 @@ func (d *segmentationDescriptor) CanClose(out SegmentationDescriptor) bool {
return true
}
case segCloseEventIDNotNested:
// this should also consider segnum == segexpected for IN signals closing an out signal.
if d.IsIn() && d.EventID() == out.EventID() && d.SegmentNumber() == d.SegmentsExpected() {
return true
}
if d.EventID() != out.EventID() {
return false
}
fallthrough
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this fallthrough still needed? From cog esam 2.0 doc, it seems like the logic has diverged enough that only the first if block is needed and all the old code can be deleted.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if d.EventId()... statement is extraneous. As is the fallthrough.

case segCloseNotNested:
if d.IsIn() {
// if in, last desc is x/x
if d.segNum == d.segsExpected {
case segCloseNotNested: // only applies to 0x34 and 0x36 with subsegments.
if d.IsOut() && (d.TypeID() == SegDescProviderPOStart || d.TypeID() == SegDescDistributorPOStart) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this should be dictated by the closeRules map only 0x34 and 0x36 should have segCloseNotNested, the extra logic here is unneeded, and two places will have to be updated if another signal gets this close rule footnote.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call.

if d.HasSubSegments() && d.SubSegmentNumber() == d.SubSegmentsExpected() {
return true
}
} else if d.IsOut() {
// if out, first descriptor in set closes existing open
if d.segNum == 1 {
if d.TypeID() == out.TypeID() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same story here, I think this is already done in the close rules map and this is just extra, unneeded logic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

return true
}
}
Expand Down Expand Up @@ -296,6 +299,21 @@ func (d *segmentationDescriptor) Equal(c SegmentationDescriptor) bool {
if d.EventID() != c.EventID() {
return false
}
if d.SegmentNumber() != c.SegmentNumber() {
return false
}
if d.SegmentsExpected() != c.SegmentsExpected() {
return false
}
if d.HasSubSegments() != c.HasSubSegments() {
return false
}
if d.HasSubSegments() && c.HasSubSegments() && (d.SubSegmentNumber() != c.SubSegmentNumber()) {
return false
}
if d.HasSubSegments() && c.HasSubSegments() && (d.SubSegmentsExpected() != c.SubSegmentsExpected()) {
return false
}
return true
}

Expand All @@ -307,6 +325,10 @@ func (d *segmentationDescriptor) SegmentsExpected() uint8 {
return d.segsExpected
}

func (d *segmentationDescriptor) HasSubSegments() bool {
return d.hasSubSegments
}

func (d *segmentationDescriptor) SubSegmentNumber() uint8 {
return d.subSegNum
}
Expand Down
223 changes: 223 additions & 0 deletions scte35/segmentationdescriptor_test.go
@@ -0,0 +1,223 @@
/*
MIT License

Copyright 2016 Comcast Cable Communications Management, LLC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package scte35

import "testing"

var csp = []byte{
0x00, 0xfc, 0x30, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x00,
0x3d, 0x02, 0x3b, 0x43, 0x55, 0x45, 0x49, 0xc0, 0x00, 0x00, 0x00, 0x7f, 0xbf, 0x0f, 0x2c, 0x75,
0x72, 0x6e, 0x3a, 0x6d, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x3a, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72,
0x3a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x3a, 0x38, 0x39, 0x38, 0x37, 0x32, 0x30, 0x35, 0x34,
0x37, 0x34, 0x34, 0x32, 0x34, 0x39, 0x38, 0x34, 0x31, 0x36, 0x33, 0x01, 0x00, 0x00, 0x1a, 0x3f,
0x5c, 0x92,
}

var unscheduled_event_start = []byte{
0x00, 0xfc, 0x30, 0x30, 0x00, 0x00, 0x00, 0x02, 0xdd, 0x20, 0x00, 0x00, 0x00, 0x05, 0x06, 0xfe,
0x00, 0x02, 0xbf, 0xd4, 0x00, 0x1a, 0x02, 0x18, 0x43, 0x55, 0x45, 0x49, 0x00, 0x00, 0x00, 0x02,
0x7f, 0xff, 0x00, 0x00, 0x0a, 0xff, 0x50, 0x09, 0x04, 0x54, 0x45, 0x53, 0x54, 0x40, 0x00, 0x00,
0xff, 0xcb, 0x8c, 0xcc,
}

var network_end = []byte{
0x00, 0xfc, 0x30, 0x30, 0x00, 0x00, 0x00, 0x02, 0xdd, 0x20, 0x00, 0x00, 0x00, 0x05, 0x06, 0xfe,
0x00, 0x02, 0xbf, 0xd4, 0x00, 0x1a, 0x02, 0x18, 0x43, 0x55, 0x45, 0x49, 0x00, 0x00, 0x00, 0x02,
0x7f, 0xff, 0x00, 0x00, 0x0a, 0xff, 0x50, 0x09, 0x04, 0x54, 0x45, 0x53, 0x54, 0x51, 0x00, 0x00,
0xfd, 0xfd, 0x2c, 0x21,
}

var program_start = []byte{
0x00, 0xfc, 0x30, 0x35, 0x00, 0x00, 0x00, 0x02, 0xdd, 0x20, 0x00, 0x00, 0x00, 0x05, 0x06, 0xfe,
0x00, 0x02, 0xbf, 0xd4, 0x00, 0x1f, 0x02, 0x1d, 0x43, 0x55, 0x45, 0x49, 0x00, 0x00, 0x00, 0x02,
0x7f, 0xff, 0x00, 0x09, 0xa7, 0xec, 0x80, 0x09, 0x09, 0x50, 0x72, 0x6f, 0x67, 0x53, 0x74, 0x61,
0x72, 0x74, 0x10, 0x01, 0x01, 0xfd, 0xbe, 0x65, 0x8c,
}

var program_end = []byte{
0x00, 0xfc, 0x30, 0x2e, 0x00, 0x00, 0x00, 0x02, 0xdd, 0x20, 0x00, 0x00, 0x00, 0x05, 0x06, 0xfe,
0x00, 0x02, 0xbf, 0xd4, 0x00, 0x18, 0x02, 0x16, 0x43, 0x55, 0x45, 0x49, 0x00, 0x00, 0x00, 0x02,
0x7f, 0xbf, 0x09, 0x07, 0x50, 0x72, 0x6f, 0x67, 0x45, 0x6e, 0x64, 0x11, 0x01, 0x01, 0xfc, 0xbe,
0x04, 0x2a,
}

var provider_ad_start = []byte{
0x00, 0xfc, 0x30, 0x3b, 0x00, 0x00, 0x00, 0x02, 0xdd, 0x20, 0x00, 0x00, 0x00, 0x05, 0x06, 0xfe,
0x00, 0x02, 0xbf, 0xd4, 0x00, 0x25, 0x02, 0x23, 0x43, 0x55, 0x45, 0x49, 0x00, 0x00, 0x00, 0x02,
0x7f, 0xff, 0x00, 0x00, 0x52, 0x65, 0xc0, 0x09, 0x0f, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
0x72, 0x41, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x30, 0x00, 0x00, 0xfa, 0xa9, 0xe1, 0x3f,
}

var distributor_po_start = []byte{
0x00, 0xfc, 0x30, 0x40, 0x00, 0x00, 0x00, 0x02, 0xdd, 0x21, 0x00, 0x00, 0x00, 0x05, 0x06, 0xfe,
0x00, 0x02, 0xbf, 0xd4, 0x00, 0x2a, 0x02, 0x28, 0x43, 0x55, 0x45, 0x49, 0x00, 0x00, 0x00, 0x02,
0x7f, 0xff, 0x00, 0x00, 0x52, 0x65, 0xc0, 0x09, 0x12, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62,
0x75, 0x74, 0x6f, 0x72, 0x50, 0x4f, 0x53, 0x74, 0x61, 0x72, 0x74, 0x36, 0x00, 0x00, 0x00, 0x00,
0xfd, 0xea, 0xaf, 0xb8,
}

var program_resumption = []byte{
0x00, 0xfc, 0x30, 0x3a, 0x00, 0x00, 0x00, 0x02, 0xdd, 0x20, 0x00, 0x00, 0x00, 0x05, 0x06, 0xfe,
0x00, 0x02, 0xbf, 0xd4, 0x00, 0x24, 0x02, 0x22, 0x43, 0x55, 0x45, 0x49, 0x00, 0x00, 0x00, 0x02,
0x7f, 0xff, 0x00, 0x09, 0xa7, 0xec, 0x80, 0x09, 0x0e, 0x50, 0x72, 0x6f, 0x67, 0x52, 0x65, 0x73,
0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x14, 0x01, 0x01, 0xf9, 0x12, 0xba, 0x59,
}

func TestNoInRules(t *testing.T) {
out, err := NewSCTE35(poOpen1)
if err != nil {
t.Error("NewSCTE35(poOpen1) returned err:", err.Error())
t.FailNow()
}

csp, err := NewSCTE35(csp)
if err != nil {
t.Error("NewSCTE35(csp) returned err:", err.Error())
t.FailNow()
}

canClose := csp.Descriptors()[0].CanClose(out.Descriptors()[0])
if canClose {
t.Error("CanClose returned true")
}
}

func TestOutNoRules(t *testing.T) {
out, err := NewSCTE35(poOpen1)
if err != nil {
t.Error("NewSCTE35(poOpen1) returned err:", err.Error())
t.FailNow()
}

csp, err := NewSCTE35(csp)
if err != nil {
t.Error("NewSCTE35(csp) returned err:", err.Error())
t.FailNow()
}

canClose := out.Descriptors()[0].CanClose(csp.Descriptors()[0])
if canClose {
t.Error("CanClose returned true")
}
}

func TestCloseUnconditional(t *testing.T) {
// Create a 0x40 (unscheduled event start)
unschedEvent, err := NewSCTE35(unscheduled_event_start)
if err != nil {
t.Error("NewSCTE35(unscheduled_event_start) returned err:", err.Error())
t.FailNow()
}

// Create a 0x51 (network end)
networkEnd, err := NewSCTE35(network_end)
if err != nil {
t.Error("NewSCTE35(network_end) returned err:", err.Error())
t.FailNow()
}

canClose := networkEnd.Descriptors()[0].CanClose(unschedEvent.Descriptors()[0])
if !canClose {
t.Errorf("CanClose returned false.")
}
}

func TestCloseEventId(t *testing.T) {
// Create a 0x10 (program start)
programStart, err := NewSCTE35(program_start)
if err != nil {
t.Error("NewSCTE35(program_start) returned err:", err.Error())
t.FailNow()
}

// Create a 0x11 (program end)
programEnd, err := NewSCTE35(program_end)
if err != nil {
t.Error("NewSCTE35(program_end) returned err:", err.Error())
t.FailNow()
}

canClose := programEnd.Descriptors()[0].CanClose(programStart.Descriptors()[0])
if !canClose {
t.Errorf("CanClose returned false.")
}

progEndSegDesc := programEnd.Descriptors()[0].(*segmentationDescriptor)
progEndSegDesc.eventID = 4

canClose = programEnd.Descriptors()[0].CanClose(programStart.Descriptors()[0])
if canClose {
t.Errorf("CanClose returned true.")
}
}

func TestCloseDifferentPTS(t *testing.T) {
// Create a 0x30 (provider ad start)
adStart, err := NewSCTE35(provider_ad_start)
if err != nil {
t.Error("NewSCTE35(provider_ad_start) returned err:", err.Error())
t.FailNow()
}

// Create a 0x36 (distributor PO start)
poStart, err := NewSCTE35(distributor_po_start)
if err != nil {
t.Error("NewSCTE35(distributor_po_start) returned err:", err.Error())
t.FailNow()
}

canClose := poStart.Descriptors()[0].CanClose(adStart.Descriptors()[0])
if !canClose {
t.Errorf("CanClose returned false.")
}

poStartSignal := poStart.(*scte35)
poStartSignal.pts = 367860

canClose = poStart.Descriptors()[0].CanClose(adStart.Descriptors()[0])
if canClose {
t.Errorf("CanClose returned trues.")
}
}

func TestCloseBreakaway(t *testing.T) {
// Create a 0x10 (program start)
programStart, err := NewSCTE35(program_start)
if err != nil {
t.Error("NewSCTE35(program_start) returned err:", err.Error())
t.FailNow()
}

// Create a 0x14 (program resumption)
programResumption, err := NewSCTE35(program_resumption)
if err != nil {
t.Error("NewSCTE35(program_resumption) returned err:", err.Error())
t.FailNow()
}

canClose := programResumption.Descriptors()[0].CanClose(programStart.Descriptors()[0])
if !canClose {
t.Errorf("CanClose returned false")
}
}
4 changes: 1 addition & 3 deletions scte35/state.go
Expand Up @@ -124,10 +124,8 @@ func (s *state) ProcessDescriptor(desc SegmentationDescriptor) ([]SegmentationDe
SegDescNetworkStart,
SegDescProgramOverlapStart,
SegDescProgramStartInProgress:
if len(closed) != 0 {
err = gots.ErrSCTE35MissingOut
}
s.open = append(s.open, desc)

// in signals
// SegDescProgramEnd treated individually since it is expected to normally
// close program resumption AND program start
Expand Down