commit f2aae646ea349e196c882dea120da8243efb37f6
parent f06b871300334ace54d1d6c9678156a53cfcff65
Author: Oliver Lowe <o@olowe.co>
Date: Fri, 31 May 2024 12:43:43 +1000
scte35, m3u8: rename SpliceInfo -> Splice
Unlike in std io/fs, there's no extra Info, there's just Splice. The
Info suffix in SpliceInfo doesn't tell us anything more than just
Splice.
Diffstat:
9 files changed, 450 insertions(+), 450 deletions(-)
diff --git a/m3u8/m3u8.go b/m3u8/m3u8.go
@@ -139,13 +139,13 @@ type DateRange struct {
Planned time.Duration
// value must be a string, float or hex sequence (int?)
Custom map[string]any
- CueCommand *scte35.SpliceInfo
+ CueCommand *scte35.Splice
// Contains the first of the in/out cue pair. Command may be
// TimeSignal or Insert, with OutOfNetwork set to true.
- CueOut *scte35.SpliceInfo
+ CueOut *scte35.Splice
// Contains the second of the cue in/out pair. The Command's
// Type must match the "out" cue.
- CueIn *scte35.SpliceInfo
+ CueIn *scte35.Splice
EndOnNext bool
}
diff --git a/m3u8/write.go b/m3u8/write.go
@@ -145,14 +145,14 @@ func writeDateRange(w io.Writer, dr *DateRange) error {
// TODO(otl): dr.Custom.
// TODO(otl): dr.CueCommand, when to write this versuse cuein, cueout.
if dr.CueIn != nil {
- b, err := scte35.EncodeSpliceInfo(dr.CueIn)
+ b, err := scte35.Encode(dr.CueIn)
if err != nil {
return fmt.Errorf("encode cue in: %w", err)
}
attrs = append(attrs, fmt.Sprintf("SCTE35-IN=0x%s", hex.EncodeToString(b)))
}
if dr.CueOut != nil {
- b, err := scte35.EncodeSpliceInfo(dr.CueOut)
+ b, err := scte35.Encode(dr.CueOut)
if err != nil {
return fmt.Errorf("encode cue out: %w", err)
}
diff --git a/scte35/cipher_test.go b/scte35/cipher_test.go
@@ -4,16 +4,16 @@ import "testing"
func TestPackEncryption(t *testing.T) {
type ptest struct {
- sis SpliceInfo
+ sis Splice
want uint8
}
var tests = []ptest{
{
- sis: SpliceInfo{Encrypted: true, Cipher: DES_CBC},
+ sis: Splice{Encrypted: true, Cipher: DES_CBC},
want: 0b10000100,
},
{
- sis: SpliceInfo{Encrypted: true, Cipher: TripleDES},
+ sis: Splice{Encrypted: true, Cipher: TripleDES},
want: 0b10000110,
},
}
diff --git a/scte35/codec_test.go b/scte35/codec_test.go
@@ -13,7 +13,7 @@ func Example() {
if err != nil {
// handle error...
}
- splice, err := scte35.DecodeSpliceInfo(b)
+ splice, err := scte35.Decode(b)
if err != nil {
// handle error...
}
@@ -23,7 +23,7 @@ func Example() {
*dur += 15 * 90000 // add 15 seconds as per 90KHz clock.
fmt.Println(*splice.Descriptors[0].(scte35.SegmentationDescriptor).Duration)
- bb, err := scte35.EncodeSpliceInfo(splice)
+ bb, err := scte35.Encode(splice)
if err != nil {
// handle error...
}
diff --git a/scte35/scte35_test.go b/scte35/scte35_test.go
@@ -3,14 +3,14 @@ package scte35
type sample struct {
name string
encoded string
- want SpliceInfo
+ want Splice
}
var samples = []sample{
{
name: "14.1. time_signal",
encoded: "/DA0AAAAAAAA///wBQb+cr0AUAAeAhxDVUVJSAAAjn/PAAGlmbAICAAAAAAsoKGKNAIAmsnRfg==",
- want: SpliceInfo{
+ want: Splice{
SAPType: SAPNone,
Tier: 0x0fff,
CWIndex: 0xff,
@@ -39,7 +39,7 @@ var samples = []sample{
{
name: "14.2. splice_insert",
encoded: "/DAvAAAAAAAA///wFAVIAACPf+/+c2nALv4AUsz1AAAAAAAKAAhDVUVJAAABNWLbowo=",
- want: SpliceInfo{
+ want: Splice{
SAPType: SAPNone,
CWIndex: 0xff,
Tier: 0x0fff,
@@ -65,7 +65,7 @@ var samples = []sample{
{
name: "14.3. time_signal",
encoded: "/DAvAAAAAAAA///wBQb+dGKQoAAZAhdDVUVJSAAAjn+fCAgAAAAALKChijUCAKnMZ1g=",
- want: SpliceInfo{
+ want: Splice{
SAPType: SAPNone,
CWIndex: 0xff,
Tier: 0x0fff,
@@ -92,7 +92,7 @@ var samples = []sample{
{
name: "14.4. time_signal",
encoded: "/DBIAAAAAAAA///wBQb+ek2ItgAyAhdDVUVJSAAAGH+fCAgAAAAALMvDRBEAAAIXQ1VFSUgAABl/nwgIAAAAACyk26AQAACZcuND",
- want: SpliceInfo{
+ want: Splice{
SAPType: SAPNone,
CWIndex: 0xff,
Tier: 0x0fff,
@@ -129,7 +129,7 @@ var samples = []sample{
// from "The Essential Guide to SCTE-35" by Bitmovin (https://bitmovin.com/scte-35-guide)
name: "dtmf",
encoded: "/DBcAAAAAAAAAP/wBQb//ciI8QBGAh1DVUVJXQk9EX+fAQ5FUDAxODAzODQwMDY2NiEEZAIZQ1VFSV0JPRF/3wABLit7AQVDMTQ2NDABAQEKQ1VFSQCAMTUwKnPhdcU=",
- want: SpliceInfo{
+ want: Splice{
SAPType: SAPNone,
Tier: 0x0fff,
Command: &Command{
diff --git a/scte35/splice.go b/scte35/splice.go
@@ -0,0 +1,267 @@
+package scte35
+
+import (
+ "encoding/binary"
+ "fmt"
+)
+
+// SAPType represents the two-bit field used to indicate that a Stream
+// Access Point (SAP) in the stream
+// as specified in SCTE 35 section 9.6.1.
+type SAPType uint8
+
+const (
+ SAPClosedGOP SAPType = 0
+ SAPClosedGOPLeading = 0x10
+ SAPOpenGOP = 0x20
+ SAPNone = 0x30
+)
+
+func (t SAPType) String() string {
+ switch t {
+ case SAPClosedGOP:
+ return "SAP Type 1 (closed GOP)"
+ case SAPClosedGOPLeading:
+ return "SAP Type 2 (closed GOP with leading pictures)"
+ case SAPOpenGOP:
+ return "SAP Type 3 (open GOP)"
+ case SAPNone:
+ return "none"
+ }
+ return "invalid"
+}
+
+type Splice struct {
+ SAPType SAPType
+ Encrypted bool
+ Cipher Cipher
+ // Holds a 33-bit unsigned integer representing the number of ticks of a 90KHz clock.
+ PTSAdjustment uint64
+ CWIndex uint8
+ // Holds a 12-bit field representing an authorization tier.
+ Tier uint16
+ Command *Command
+ Descriptors []SpliceDescriptor
+ CRC32 uint32
+}
+
+// fields of Splice Info Section which MUST have their values set...
+// as specified in SCTE 35 section 9.6.1.
+const (
+ tableID uint8 = 0xfc
+ protocolVersion = 0x0
+ sectionSyntax = false
+ privateIndicator = false
+)
+
+// maximum 12-bit uint (2^12 - 1)
+const maxTier uint16 = 0xfff
+
+func Encode(splice *Splice) ([]byte, error) {
+ buf := make([]byte, 4)
+ buf[0] = byte(tableID)
+ // next 2 bits (section_syntax_indicator, private_indicator) must be 0.
+ // 0b00000000
+ buf[1] |= byte(splice.SAPType)
+
+ // length, buf[1,2] set at the end
+ buf[3] = protocolVersion
+
+ var b byte
+ if splice.Encrypted {
+ b |= (1 << 7)
+ if splice.Cipher > maxCipher {
+ return nil, fmt.Errorf("cipher %d larger than max %d", splice.Cipher, maxCipher)
+ }
+ // pack cipher, keeping 1 bit for PTSAdjustment.
+ b |= byte(splice.Cipher) << 1
+ }
+ buf = append(buf, b)
+ buf = append(buf, 0, 0, 0, 0)
+ putPTS(buf[4:], splice.PTSAdjustment)
+ if splice.Encrypted {
+ buf = append(buf, splice.CWIndex)
+ } else {
+ // unused; toggle all bits as in the spec.
+ buf = append(buf, 0xff)
+ }
+
+ if splice.Tier > maxTier {
+ return nil, fmt.Errorf("tier %d greater than max %d", splice.Tier, maxTier)
+ }
+ tier := splice.Tier & 0x0fff // just 12 bits
+ // right 4 bits are for command length
+ buf = binary.BigEndian.AppendUint16(buf, tier<<4)
+ if splice.Command == nil {
+ return nil, fmt.Errorf("nil command")
+ }
+ cmd, err := encodeCommand(splice.Command)
+ if err != nil {
+ return nil, fmt.Errorf("encode splice command: %w", err)
+ }
+ cmdlen := uint16(len(cmd)) & 0x0fff
+ // stuff remaining 4 bits into the last byte.
+ buf[len(buf)-1] |= byte(cmdlen >> 8)
+ buf = append(buf, byte(cmdlen))
+ buf = append(buf, byte(splice.Command.Type))
+ buf = append(buf, cmd...)
+
+ var buf1 []byte
+ for _, desc := range splice.Descriptors {
+ buf1 = append(buf1, encodeSpliceDescriptor(desc)...)
+ }
+ buf = binary.BigEndian.AppendUint16(buf, uint16(len(buf1)))
+ buf = append(buf, buf1...)
+
+ // want only 12 bits, left 4 bits are used by flags, saptype.
+ buflen := uint16(len(buf)) & 0x0fff
+ buflen++ // TODO(otl): is this required because of alignment stuffing?
+ buf[1] |= byte(buflen >> 8)
+ buf[2] = byte(buflen)
+
+ crc := checksum(buf, &crctab)
+ return binary.BigEndian.AppendUint32(buf, crc), nil
+}
+
+func Decode(buf []byte) (*Splice, error) {
+ if len(buf) < 3 {
+ return nil, fmt.Errorf("need at least 2 bytes")
+ }
+ // skip buf[0], we don't store table_id.
+
+ var splice Splice
+ // skip 2 bits, straight to sap_type.
+ splice.SAPType = SAPType(buf[1] & 0b00110000)
+ length := binary.BigEndian.Uint16([]byte{buf[1], buf[2]})
+ length &= 0x0fff // 12-bit field
+ buf = buf[3:]
+ if len(buf) != int(length) {
+ return nil, fmt.Errorf("message declares %d bytes but have %d", length, len(buf))
+ }
+
+ // skip version byte, we don't store version as it's constant.
+ if buf[1]&0b10000000 == 1 {
+ splice.Encrypted = true
+ // right-most bit is used by PTSAdjustment.
+ splice.Cipher = Cipher(buf[1] & 0b01111110)
+ }
+
+ pts := make([]byte, 8)
+ pts[0] = buf[1] & (1 << 1)
+ pts[1] = buf[2]
+ pts[2] = buf[3]
+ pts[3] = buf[4]
+ pts[4] = buf[5]
+ splice.PTSAdjustment = binary.BigEndian.Uint64(pts)
+ splice.CWIndex = uint8(buf[6])
+
+ // want left-most 12 bits, remaining is used by command length.
+ // TODO(otl): still not getting expected values here;
+ // check TestDecode
+ tier := binary.BigEndian.Uint16([]byte{buf[7], buf[8] & 0xf0})
+ splice.Tier = tier >> 4
+
+ // 4-bits out of buf[8], then all of buf[9] for a 12-bit integer.
+ cmdlen := binary.BigEndian.Uint16([]byte{buf[8] & 0x0f, buf[9]})
+ cmd, err := decodeCommand(buf[10 : 10+cmdlen+1])
+ if err != nil {
+ return nil, fmt.Errorf("decode command: %w", err)
+ }
+ splice.Command = cmd
+ buf = buf[10+cmdlen+1:]
+
+ desclen := binary.BigEndian.Uint16([]byte{buf[0], buf[1]})
+ descriptors, err := decodeAllDescriptors(buf[2 : 2+desclen])
+ if err != nil {
+ return nil, fmt.Errorf("decode splice descriptors: %w", err)
+ }
+ splice.Descriptors = descriptors
+
+ buf = buf[2+desclen:]
+ if splice.Encrypted {
+ // TODO(otl): handle alignment_stuffing for encrypted packets.
+ // skip past E_CRC_32; we don't store it.
+ buf = buf[1:]
+ }
+ splice.CRC32 = binary.BigEndian.Uint32(buf)
+ return &splice, nil
+}
+
+func decodeCommand(buf []byte) (*Command, error) {
+ var cmd Command
+ cmd.Type = CommandType(buf[0])
+ switch cmd.Type {
+ case SpliceNull, BandwidthReservation:
+ // nothing to decode
+ case TimeSignal:
+ // check if time specified flag is set.
+ // If so, extract the 33-bit integer timestamp.
+ if buf[1]&0x80 == 1<<7 {
+ b := make([]byte, 8)
+ b[3] = buf[1] & 0x01 // ignoring flag and reserved bits
+ b[4] = buf[2]
+ b[5] = buf[3]
+ b[6] = buf[4]
+ b[7] = buf[5]
+ t := binary.BigEndian.Uint64(b)
+ cmd.TimeSignal = &t
+ }
+ case SpliceInsert:
+ var ins Insert
+ ins.ID = binary.BigEndian.Uint32(buf[1:5])
+ if buf[5]&0x80 > 0 {
+ ins.Cancel = true
+ cmd.Insert = &ins
+ // rebelelder told us to do this.
+ return &cmd, nil
+ }
+ if buf[6]&(1<<7) > 0 {
+ ins.OutOfNetwork = true
+ }
+
+ // assume program_splice is set at bit 6;
+
+ var durflag bool
+ if buf[6]&(1<<5) > 0 {
+ durflag = true
+ }
+ // we don't support deprecated component mode.
+ if buf[6]&(1<<4) > 0 {
+ ins.Immediate = true
+ }
+ if buf[6]&(1<<3) > 0 {
+ ins.idCompliance = true
+ }
+ // next 3 bits are reserved.
+
+ if !ins.Immediate {
+ // is time_specified_flag set? if so, read the 33-bit time.
+ if buf[7]&(1<<7) > 0 {
+ b := make([]byte, 3)
+ b = append(b, buf[7]&0x01) // skip reserved bits.
+ b = append(b, buf[8:12]...) // read remaining 32 bits.
+ dur := binary.BigEndian.Uint64(b)
+ ins.SpliceTime = newuint64(dur)
+ buf = buf[12:]
+ } else {
+ buf = buf[8:]
+ }
+ }
+
+ if durflag {
+ a := [5]byte{buf[0], buf[1], buf[2], buf[3], buf[4]}
+ ins.Duration = readBreakDuration(a)
+ buf = buf[5:]
+ }
+
+ ins.ProgramID = binary.BigEndian.Uint16([]byte{buf[0], buf[1]})
+ ins.AvailNum = uint8(buf[2])
+ ins.AvailExpected = uint8(buf[3])
+ cmd.Insert = &ins
+ default:
+ return nil, fmt.Errorf("TODO: cannot decode command type %s", cmd.Type)
+ }
+ return &cmd, nil
+}
+
+func newuint64(i uint64) *uint64 { p := new(uint64); p = &i; return p }
diff --git a/scte35/splice_info.go b/scte35/splice_info.go
@@ -1,267 +0,0 @@
-package scte35
-
-import (
- "encoding/binary"
- "fmt"
-)
-
-// SAPType represents the two-bit field used to indicate that a Stream
-// Access Point (SAP) in the stream
-// as specified in SCTE 35 section 9.6.1.
-type SAPType uint8
-
-const (
- SAPClosedGOP SAPType = 0
- SAPClosedGOPLeading = 0x10
- SAPOpenGOP = 0x20
- SAPNone = 0x30
-)
-
-func (t SAPType) String() string {
- switch t {
- case SAPClosedGOP:
- return "SAP Type 1 (closed GOP)"
- case SAPClosedGOPLeading:
- return "SAP Type 2 (closed GOP with leading pictures)"
- case SAPOpenGOP:
- return "SAP Type 3 (open GOP)"
- case SAPNone:
- return "none"
- }
- return "invalid"
-}
-
-type SpliceInfo struct {
- SAPType SAPType
- Encrypted bool
- Cipher Cipher
- // Holds a 33-bit unsigned integer representing the number of ticks of a 90KHz clock.
- PTSAdjustment uint64
- CWIndex uint8
- // Holds a 12-bit field representing an authorization tier.
- Tier uint16
- Command *Command
- Descriptors []SpliceDescriptor
- CRC32 uint32
-}
-
-// fields of Splice Info Section which MUST have their values set...
-// as specified in SCTE 35 section 9.6.1.
-const (
- tableID uint8 = 0xfc
- protocolVersion = 0x0
- sectionSyntax = false
- privateIndicator = false
-)
-
-// maximum 12-bit uint (2^12 - 1)
-const maxTier uint16 = 0xfff
-
-func EncodeSpliceInfo(info *SpliceInfo) ([]byte, error) {
- buf := make([]byte, 4)
- buf[0] = byte(tableID)
- // next 2 bits (section_syntax_indicator, private_indicator) must be 0.
- // 0b00000000
- buf[1] |= byte(info.SAPType)
-
- // length, buf[1,2] set at the end
- buf[3] = protocolVersion
-
- var b byte
- if info.Encrypted {
- b |= (1 << 7)
- if info.Cipher > maxCipher {
- return nil, fmt.Errorf("cipher %d larger than max %d", info.Cipher, maxCipher)
- }
- // pack cipher, keeping 1 bit for PTSAdjustment.
- b |= byte(info.Cipher) << 1
- }
- buf = append(buf, b)
- buf = append(buf, 0, 0, 0, 0)
- putPTS(buf[4:], info.PTSAdjustment)
- if info.Encrypted {
- buf = append(buf, info.CWIndex)
- } else {
- // unused; toggle all bits as in the spec.
- buf = append(buf, 0xff)
- }
-
- if info.Tier > maxTier {
- return nil, fmt.Errorf("tier %d greater than max %d", info.Tier, maxTier)
- }
- tier := info.Tier & 0x0fff // just 12 bits
- // right 4 bits are for command length
- buf = binary.BigEndian.AppendUint16(buf, tier<<4)
- if info.Command == nil {
- return nil, fmt.Errorf("nil command")
- }
- cmd, err := encodeCommand(info.Command)
- if err != nil {
- return nil, fmt.Errorf("encode splice command: %w", err)
- }
- cmdlen := uint16(len(cmd)) & 0x0fff
- // stuff remaining 4 bits into the last byte.
- buf[len(buf)-1] |= byte(cmdlen >> 8)
- buf = append(buf, byte(cmdlen))
- buf = append(buf, byte(info.Command.Type))
- buf = append(buf, cmd...)
-
- var buf1 []byte
- for _, desc := range info.Descriptors {
- buf1 = append(buf1, encodeSpliceDescriptor(desc)...)
- }
- buf = binary.BigEndian.AppendUint16(buf, uint16(len(buf1)))
- buf = append(buf, buf1...)
-
- // want only 12 bits, left 4 bits are used by flags, saptype.
- buflen := uint16(len(buf)) & 0x0fff
- buflen++ // TODO(otl): is this required because of alignment stuffing?
- buf[1] |= byte(buflen >> 8)
- buf[2] = byte(buflen)
-
- crc := checksum(buf, &crctab)
- return binary.BigEndian.AppendUint32(buf, crc), nil
-}
-
-func DecodeSpliceInfo(buf []byte) (*SpliceInfo, error) {
- if len(buf) < 3 {
- return nil, fmt.Errorf("need at least 2 bytes")
- }
- // skip buf[0], we don't store table_id.
-
- var info SpliceInfo
- // skip 2 bits, straight to sap_type.
- info.SAPType = SAPType(buf[1] & 0b00110000)
- length := binary.BigEndian.Uint16([]byte{buf[1], buf[2]})
- length &= 0x0fff // 12-bit field
- buf = buf[3:]
- if len(buf) != int(length) {
- return nil, fmt.Errorf("message declares %d bytes but have %d", length, len(buf))
- }
-
- // skip version byte, we don't store version as it's constant.
- if buf[1]&0b10000000 == 1 {
- info.Encrypted = true
- // right-most bit is used by PTSAdjustment.
- info.Cipher = Cipher(buf[1] & 0b01111110)
- }
-
- pts := make([]byte, 8)
- pts[0] = buf[1] & (1 << 1)
- pts[1] = buf[2]
- pts[2] = buf[3]
- pts[3] = buf[4]
- pts[4] = buf[5]
- info.PTSAdjustment = binary.BigEndian.Uint64(pts)
- info.CWIndex = uint8(buf[6])
-
- // want left-most 12 bits, remaining is used by command length.
- // TODO(otl): still not getting expected values here;
- // check TestDecodeSpliceInfo
- tier := binary.BigEndian.Uint16([]byte{buf[7], buf[8] & 0xf0})
- info.Tier = tier >> 4
-
- // 4-bits out of buf[8], then all of buf[9] for a 12-bit integer.
- cmdlen := binary.BigEndian.Uint16([]byte{buf[8] & 0x0f, buf[9]})
- cmd, err := decodeCommand(buf[10 : 10+cmdlen+1])
- if err != nil {
- return nil, fmt.Errorf("decode command: %w", err)
- }
- info.Command = cmd
- buf = buf[10+cmdlen+1:]
-
- desclen := binary.BigEndian.Uint16([]byte{buf[0], buf[1]})
- descriptors, err := decodeAllDescriptors(buf[2 : 2+desclen])
- if err != nil {
- return nil, fmt.Errorf("decode splice descriptors: %w", err)
- }
- info.Descriptors = descriptors
-
- buf = buf[2+desclen:]
- if info.Encrypted {
- // TODO(otl): handle alignment_stuffing for encrypted packets.
- // skip past E_CRC_32; we don't store it.
- buf = buf[1:]
- }
- info.CRC32 = binary.BigEndian.Uint32(buf)
- return &info, nil
-}
-
-func decodeCommand(buf []byte) (*Command, error) {
- var cmd Command
- cmd.Type = CommandType(buf[0])
- switch cmd.Type {
- case SpliceNull, BandwidthReservation:
- // nothing to decode
- case TimeSignal:
- // check if time specified flag is set.
- // If so, extract the 33-bit integer timestamp.
- if buf[1]&0x80 == 1<<7 {
- b := make([]byte, 8)
- b[3] = buf[1] & 0x01 // ignoring flag and reserved bits
- b[4] = buf[2]
- b[5] = buf[3]
- b[6] = buf[4]
- b[7] = buf[5]
- t := binary.BigEndian.Uint64(b)
- cmd.TimeSignal = &t
- }
- case SpliceInsert:
- var ins Insert
- ins.ID = binary.BigEndian.Uint32(buf[1:5])
- if buf[5]&0x80 > 0 {
- ins.Cancel = true
- cmd.Insert = &ins
- // rebelelder told us to do this.
- return &cmd, nil
- }
- if buf[6]&(1<<7) > 0 {
- ins.OutOfNetwork = true
- }
-
- // assume program_splice is set at bit 6;
-
- var durflag bool
- if buf[6]&(1<<5) > 0 {
- durflag = true
- }
- // we don't support deprecated component mode.
- if buf[6]&(1<<4) > 0 {
- ins.Immediate = true
- }
- if buf[6]&(1<<3) > 0 {
- ins.idCompliance = true
- }
- // next 3 bits are reserved.
-
- if !ins.Immediate {
- // is time_specified_flag set? if so, read the 33-bit time.
- if buf[7]&(1<<7) > 0 {
- b := make([]byte, 3)
- b = append(b, buf[7]&0x01) // skip reserved bits.
- b = append(b, buf[8:12]...) // read remaining 32 bits.
- dur := binary.BigEndian.Uint64(b)
- ins.SpliceTime = newuint64(dur)
- buf = buf[12:]
- } else {
- buf = buf[8:]
- }
- }
-
- if durflag {
- a := [5]byte{buf[0], buf[1], buf[2], buf[3], buf[4]}
- ins.Duration = readBreakDuration(a)
- buf = buf[5:]
- }
-
- ins.ProgramID = binary.BigEndian.Uint16([]byte{buf[0], buf[1]})
- ins.AvailNum = uint8(buf[2])
- ins.AvailExpected = uint8(buf[3])
- cmd.Insert = &ins
- default:
- return nil, fmt.Errorf("TODO: cannot decode command type %s", cmd.Type)
- }
- return &cmd, nil
-}
-
-func newuint64(i uint64) *uint64 { p := new(uint64); p = &i; return p }
diff --git a/scte35/splice_info_test.go b/scte35/splice_info_test.go
@@ -1,167 +0,0 @@
-package scte35
-
-import (
- "encoding/base64"
- "fmt"
- "reflect"
- "strings"
- "testing"
- "time"
-)
-
-func diffInfo(a, b SpliceInfo) string {
- buf := &strings.Builder{}
- if a.SAPType != b.SAPType {
- fmt.Fprintln(buf, "SAP type differs")
- fmt.Fprintf(buf, "< %s\n> %s\n", a.SAPType, b.SAPType)
- }
- if a.Cipher != b.Cipher {
- fmt.Fprintln(buf, "cipher differs")
- fmt.Fprintf(buf, "< %s\n> %s", a.Cipher, b.Cipher)
- }
- if a.PTSAdjustment != b.PTSAdjustment {
- fmt.Fprintln(buf, "pts adjustment = ", a.PTSAdjustment, b.PTSAdjustment)
- }
- if a.CWIndex != b.CWIndex {
- fmt.Fprintln(buf, "cw index differs")
- fmt.Fprintf(buf, "< %v\n> %v\n", a.CWIndex, b.CWIndex)
- }
- if a.Tier != b.Tier {
- fmt.Fprintln(buf, "tier differs")
- fmt.Fprintf(buf, "< %#x\n> %#x\n", a.Tier, b.Tier)
- }
- if !reflect.DeepEqual(a.Command, b.Command) {
- fmt.Fprintln(buf, "command = ", *a.Command, *b.Command)
- }
- for i := range a.Descriptors {
- if !reflect.DeepEqual(a.Descriptors[i], b.Descriptors[i]) {
- buf.WriteString(diffDescriptors(a.Descriptors[i], b.Descriptors[i]))
- }
- }
- if a.CRC32 != b.CRC32 {
- fmt.Fprintln(buf, "crc32 = ", a.CRC32, b.CRC32)
- }
- return buf.String()
-}
-
-func diffDescriptors(a, b SpliceDescriptor) string {
- buf := &strings.Builder{}
- if a.Tag() != b.Tag() {
- fmt.Fprintln(buf, "tag differs")
- fmt.Fprintln(buf, "<", a.Tag())
- fmt.Fprintln(buf, ">", b.Tag())
- }
- if a.ID() != b.ID() {
- fmt.Fprintln(buf, "id differs")
- fmt.Fprintf(buf, "< %d\n> %d\n", a.ID(), b.ID())
- }
- if !reflect.DeepEqual(a.Data(), b.Data()) {
- fmt.Fprintln(buf, "data differs")
- fmt.Fprintf(buf, "< %v\n> %v\n", a.Data(), b.Data())
- }
- fmt.Fprintf(buf, "< %T %v\n> %T %v\n", a, a, b, b)
- return buf.String()
-}
-
-func TestDecodeSpliceInfo(t *testing.T) {
- for _, tt := range samples {
- t.Run(tt.name, func(t *testing.T) {
- b, err := base64.StdEncoding.DecodeString(tt.encoded)
- if err != nil {
- t.Fatal("decode example splice info:", err)
- }
- info, err := DecodeSpliceInfo(b)
- if err != nil {
- t.Fatalf("decode splice info: %v", err)
- }
-
- // test each possible command
- if tt.want.Command.TimeSignal != nil {
- if *tt.want.Command.TimeSignal != *info.Command.TimeSignal {
- t.Errorf("want timesig %x, got %x", *tt.want.Command.TimeSignal, *info.Command.TimeSignal)
- }
- }
- if tt.want.Command.Insert != nil {
- want := *tt.want.Command.Insert
- got := *info.Command.Insert
- if !reflect.DeepEqual(want, got) {
- t.Errorf("info command: want %+v, got %+v", want, got)
- if *want.SpliceTime != *got.SpliceTime {
- t.Logf("want splice time %d, got %d", want.SpliceTime, got.SpliceTime)
- }
- if want.Duration.Duration != got.Duration.Duration {
- t.Logf("want break duration %d, got %d", want.Duration.Duration, got.Duration.Duration)
- }
- }
- }
-
- if !reflect.DeepEqual(tt.want, *info) {
- t.Errorf("decode splice info: want %+v, got %+v", tt.want, *info)
- t.Log(diffInfo(tt.want, *info))
- }
- })
- }
-
- // these messages are from github.com/futzu/SCTE-35_threefive/examples/hls/
- inserts := map[string]time.Duration{
- "/DAlAAAAAAAAAP/wFAUAAAABf+/+ANgNkv4AFJlwAAEBAQAA5xULLA==": 15 * time.Second,
- "/DAnAAAAAAAAAP/wBQb+AA27oAARAg9DVUVJAAAAAX+HCQA0AAE0xUZn": 10 * time.Second,
- "/DAnAAAAAAAAAP/wBQb+AGb/MAARAg9DVUVJAAAAAn+HCQA0AALMua1L": 75 * time.Second,
- }
- for s, dur := range inserts {
- b, err := base64.StdEncoding.DecodeString(s)
- if err != nil {
- t.Fatal(err)
- }
- splice, err := DecodeSpliceInfo(b)
- if err != nil {
- t.Fatalf("decode splice info: %v", err)
- }
- var got time.Duration
- switch splice.Command.Type {
- case TimeSignal:
- got = time.Duration(*splice.Command.TimeSignal/90000) * time.Second
- case SpliceInsert:
- got = time.Duration(splice.Command.Insert.Duration.Duration/90000) * time.Second
- default:
- t.Fatalf("no duration test supported for %s", splice.Command.Type)
- }
- if got != dur {
- t.Errorf("want %s, got %s", dur, got)
- }
- }
-}
-
-func TestEncodeSpliceInfo(t *testing.T) {
- for _, tt := range samples {
- t.Run(tt.name, func(t *testing.T) {
- b, err := EncodeSpliceInfo(&tt.want)
- if err != nil {
- t.Fatal(err)
- }
- bwant, err := base64.StdEncoding.DecodeString(tt.encoded)
- if err != nil {
- t.Fatal(err)
- }
- // If we're not encrypted, set the CWIndex to be
- // the same as desired; its value is now undefined
- // and should be ignored downstream. This lets our
- // test pass even if our test encoded value has a
- // different CWIndex set than what we encode.
- if !tt.want.Encrypted {
- b[9] = bwant[9]
- }
- got := base64.StdEncoding.EncodeToString(b)
- if tt.encoded != got {
- // as above, since the undefined CWIndex is encoded differently,
- // our checksum could be different.
- // Only error if the value of the message *without* the CRC32 is different.
- if tt.encoded[:len(tt.encoded)-7] != got[:len(got)-7] {
- t.Errorf("expected encoded splice info differs from calculated")
- }
- t.Logf("< %#x", bwant)
- t.Logf("> %#x", b)
- }
- })
- }
-}
diff --git a/scte35/splice_test.go b/scte35/splice_test.go
@@ -0,0 +1,167 @@
+package scte35
+
+import (
+ "encoding/base64"
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+)
+
+func diffInfo(a, b Splice) string {
+ buf := &strings.Builder{}
+ if a.SAPType != b.SAPType {
+ fmt.Fprintln(buf, "SAP type differs")
+ fmt.Fprintf(buf, "< %s\n> %s\n", a.SAPType, b.SAPType)
+ }
+ if a.Cipher != b.Cipher {
+ fmt.Fprintln(buf, "cipher differs")
+ fmt.Fprintf(buf, "< %s\n> %s", a.Cipher, b.Cipher)
+ }
+ if a.PTSAdjustment != b.PTSAdjustment {
+ fmt.Fprintln(buf, "pts adjustment = ", a.PTSAdjustment, b.PTSAdjustment)
+ }
+ if a.CWIndex != b.CWIndex {
+ fmt.Fprintln(buf, "cw index differs")
+ fmt.Fprintf(buf, "< %v\n> %v\n", a.CWIndex, b.CWIndex)
+ }
+ if a.Tier != b.Tier {
+ fmt.Fprintln(buf, "tier differs")
+ fmt.Fprintf(buf, "< %#x\n> %#x\n", a.Tier, b.Tier)
+ }
+ if !reflect.DeepEqual(a.Command, b.Command) {
+ fmt.Fprintln(buf, "command = ", *a.Command, *b.Command)
+ }
+ for i := range a.Descriptors {
+ if !reflect.DeepEqual(a.Descriptors[i], b.Descriptors[i]) {
+ buf.WriteString(diffDescriptors(a.Descriptors[i], b.Descriptors[i]))
+ }
+ }
+ if a.CRC32 != b.CRC32 {
+ fmt.Fprintln(buf, "crc32 = ", a.CRC32, b.CRC32)
+ }
+ return buf.String()
+}
+
+func diffDescriptors(a, b SpliceDescriptor) string {
+ buf := &strings.Builder{}
+ if a.Tag() != b.Tag() {
+ fmt.Fprintln(buf, "tag differs")
+ fmt.Fprintln(buf, "<", a.Tag())
+ fmt.Fprintln(buf, ">", b.Tag())
+ }
+ if a.ID() != b.ID() {
+ fmt.Fprintln(buf, "id differs")
+ fmt.Fprintf(buf, "< %d\n> %d\n", a.ID(), b.ID())
+ }
+ if !reflect.DeepEqual(a.Data(), b.Data()) {
+ fmt.Fprintln(buf, "data differs")
+ fmt.Fprintf(buf, "< %v\n> %v\n", a.Data(), b.Data())
+ }
+ fmt.Fprintf(buf, "< %T %v\n> %T %v\n", a, a, b, b)
+ return buf.String()
+}
+
+func TestDecode(t *testing.T) {
+ for _, tt := range samples {
+ t.Run(tt.name, func(t *testing.T) {
+ b, err := base64.StdEncoding.DecodeString(tt.encoded)
+ if err != nil {
+ t.Fatal("decode example splice:", err)
+ }
+ splice, err := Decode(b)
+ if err != nil {
+ t.Fatalf("decode splice: %v", err)
+ }
+
+ // test each possible command
+ if tt.want.Command.TimeSignal != nil {
+ if *tt.want.Command.TimeSignal != *splice.Command.TimeSignal {
+ t.Errorf("want timesig %x, got %x", *tt.want.Command.TimeSignal, *splice.Command.TimeSignal)
+ }
+ }
+ if tt.want.Command.Insert != nil {
+ want := *tt.want.Command.Insert
+ got := *splice.Command.Insert
+ if !reflect.DeepEqual(want, got) {
+ t.Errorf("splice command: want %+v, got %+v", want, got)
+ if *want.SpliceTime != *got.SpliceTime {
+ t.Logf("want splice time %d, got %d", want.SpliceTime, got.SpliceTime)
+ }
+ if want.Duration.Duration != got.Duration.Duration {
+ t.Logf("want break duration %d, got %d", want.Duration.Duration, got.Duration.Duration)
+ }
+ }
+ }
+
+ if !reflect.DeepEqual(tt.want, *splice) {
+ t.Errorf("decode splice splice: want %+v, got %+v", tt.want, *splice)
+ t.Log(diffInfo(tt.want, *splice))
+ }
+ })
+ }
+
+ // these messages are from github.com/futzu/SCTE-35_threefive/examples/hls/
+ inserts := map[string]time.Duration{
+ "/DAlAAAAAAAAAP/wFAUAAAABf+/+ANgNkv4AFJlwAAEBAQAA5xULLA==": 15 * time.Second,
+ "/DAnAAAAAAAAAP/wBQb+AA27oAARAg9DVUVJAAAAAX+HCQA0AAE0xUZn": 10 * time.Second,
+ "/DAnAAAAAAAAAP/wBQb+AGb/MAARAg9DVUVJAAAAAn+HCQA0AALMua1L": 75 * time.Second,
+ }
+ for s, dur := range inserts {
+ b, err := base64.StdEncoding.DecodeString(s)
+ if err != nil {
+ t.Fatal(err)
+ }
+ splice, err := Decode(b)
+ if err != nil {
+ t.Fatalf("decode splice splice: %v", err)
+ }
+ var got time.Duration
+ switch splice.Command.Type {
+ case TimeSignal:
+ got = time.Duration(*splice.Command.TimeSignal/90000) * time.Second
+ case SpliceInsert:
+ got = time.Duration(splice.Command.Insert.Duration.Duration/90000) * time.Second
+ default:
+ t.Fatalf("no duration test supported for %s", splice.Command.Type)
+ }
+ if got != dur {
+ t.Errorf("want %s, got %s", dur, got)
+ }
+ }
+}
+
+func TestEncode(t *testing.T) {
+ for _, tt := range samples {
+ t.Run(tt.name, func(t *testing.T) {
+ b, err := Encode(&tt.want)
+ if err != nil {
+ t.Fatal(err)
+ }
+ bwant, err := base64.StdEncoding.DecodeString(tt.encoded)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // If we're not encrypted, set the CWIndex to be
+ // the same as desired; its value is now undefined
+ // and should be ignored downstream. This lets our
+ // test pass even if our test encoded value has a
+ // different CWIndex set than what we encode.
+ if !tt.want.Encrypted {
+ b[9] = bwant[9]
+ }
+ got := base64.StdEncoding.EncodeToString(b)
+ if tt.encoded != got {
+ // as above, since the undefined CWIndex is encoded differently,
+ // our checksum could be different.
+ // Only error if the value of the message *without* the CRC32 is different.
+ if tt.encoded[:len(tt.encoded)-7] != got[:len(got)-7] {
+ t.Errorf("expected encoded splice differs from calculated")
+ }
+ t.Logf("< %#x", bwant)
+ t.Logf("> %#x", b)
+ }
+ })
+ }
+}