├── .arcconfig
├── .github
└── workflows
│ └── gradle.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
└── java
│ └── com
│ └── nfl
│ └── scte35
│ └── decoder
│ ├── Scte35Decoder.java
│ ├── exception
│ └── DecodingException.java
│ └── model
│ ├── AvailDescriptor.java
│ ├── BreakDuration.java
│ ├── DTMFDescriptor.java
│ ├── SegmentationDescriptor.java
│ ├── SpliceInfoSection.java
│ ├── SpliceInsert.java
│ ├── SpliceTime.java
│ └── TimeSignal.java
└── test
└── java
└── com
└── nfl
└── scte35
└── decoder
└── Scte35DecoderTest.java
/.arcconfig:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "project.name" : "scte35",
4 | "phabricator.uri" : "https://phabricator.dm.nfl.com/"
5 | }
--------------------------------------------------------------------------------
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Gradle
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
3 |
4 | name: Java CI with Gradle
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up JDK 1.8
20 | uses: actions/setup-java@v1
21 | with:
22 | java-version: 1.8
23 | - name: Grant execute permission for gradlew
24 | run: chmod +x gradlew
25 | - name: Build with Gradle
26 | run: ./gradlew build test publish
27 | env:
28 | USERNAME: ${{github.actor}}
29 | TOKEN: ${{github.token}}
30 | BUILDNUMBER: ${{github.run_id}}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Mobile Tools for Java (J2ME)
4 | .mtj.tmp/
5 |
6 | # Package Files #
7 | *.jar
8 | *.war
9 | *.ear
10 |
11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
12 | hs_err_pid*
13 | .idea
14 | *.DS_Store
15 |
16 | # Build Artifacts
17 |
18 | .gradle/
19 | build/
20 | target/
21 | bin/
22 | dependency-reduced-pom.xml
23 |
24 | # IntelliJ IDEA Files
25 |
26 | *.iml
27 | *.ipr
28 | *.iws
29 | *.idea
30 |
31 | # Eclipse Project Files
32 |
33 | .classpath
34 | .project
35 | .settings/
36 |
37 | # Operating System Files
38 |
39 | *.DS_Store
40 | Thumbs.db
41 | *.sw?
42 | .#*
43 | *#
44 | *~
45 | *.sublime-*
46 |
47 | Gemfile.lock
48 | .phabricator-comment
49 | archive.tar.gz
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 NFL
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # SCETE 35 Decoder
4 |
5 | []
6 | [](https://github.com/nfl/scte35/blob/master/LICENSE)
7 |
8 | Java library that handles decoding strings that follow the SCTE 35 Standard: https://en.wikipedia.org/wiki/SCTE-35. The result is an Plain Old Java Object of type `SpliceInfoSection`
9 |
10 | This library was forked from https://github.com/riedlse/scte35.
11 |
12 | ## Goals
13 | * Provide a simple interface that can be instatiated or injected
14 | * Remove Netbeans dependency
15 | * Remove UI dependency
16 | * Compatibility with Android
17 | * Removing dependency to `javax.xml.bind.DatatypeConverter` http://stackoverflow.com/a/34424297. Using `commons-codec`
18 | * Removing ` com.turner.decoder.Base64`. Using `commons-codec`
19 | * Keep it independent of Android libraries
20 | * Fully define model
21 | * Move to gradle
22 | * Add unit tests
23 |
24 | ## Integration
25 | This library is made available via maven repository. Alternatively it can be downloaded and included directly into a private maven repository or directly in the libs folder of the target app.
26 | ### Add maven repository
27 | ```groovy
28 | repositories {
29 | maven {
30 | name = "NFL Scte35"
31 | url = uri("https://maven.pkg.github.com/nfl/scte35")
32 | }
33 | }
34 | ```
35 | ### Add dependency
36 | ```groovy
37 | dependencies {
38 | ...
39 | implementation 'com.nfl.dm.scte35:scte35-decoder:1.0.31'
40 | ...
41 | }
42 | ```
43 | ### Create or inject instance:
44 | ```java
45 | Scte35Decoder scte35Decoder = new Scte35Decoder(false);
46 | SpliceInfoSection spliceInfoSection = scte35Decoder.base64Decode("SCTE35_ENCODED_STRING");
47 | //Use values from model: spliceInfoSection
48 | ```
49 |
50 | ## TODOs
51 | * Make models immutable
52 | * Add more examples to increase code coverage
53 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 | }
7 | plugins {
8 | id('maven-publish')
9 | id('java-library')
10 | }
11 |
12 | publishing {
13 | repositories {
14 | maven {
15 | name = "GitHubPackages"
16 | url = uri("https://maven.pkg.github.com/nfl/scte35")
17 | credentials {
18 | username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
19 | password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
20 | }
21 | }
22 | }
23 | publications {
24 | gpr(MavenPublication) {
25 | groupId 'com.nfl.dm.scte35'
26 | version = '1.0.33'
27 | from components.java
28 | }
29 | }
30 | }
31 |
32 | targetCompatibility = '1.7'
33 | sourceCompatibility = '1.7'
34 |
35 |
36 | repositories {
37 | google()
38 | jcenter()
39 | }
40 |
41 | dependencies {
42 | compile 'commons-codec:commons-codec:1.10'
43 | testCompile 'org.testng:testng:6.8.8'
44 | }
45 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nfl/scte35/88f279ad8c2ea1a54b945f163bd0fd6a7291fb5f/gradle.properties
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nfl/scte35/88f279ad8c2ea1a54b945f163bd0fd6a7291fb5f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Apr 28 10:33:02 PDT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'scte35'
2 |
3 |
--------------------------------------------------------------------------------
/src/main/java/com/nfl/scte35/decoder/Scte35Decoder.java:
--------------------------------------------------------------------------------
1 |
2 | package com.nfl.scte35.decoder;
3 |
4 | import com.nfl.scte35.decoder.exception.DecodingException;
5 | import com.nfl.scte35.decoder.model.SegmentationDescriptor;
6 | import com.nfl.scte35.decoder.model.SpliceInfoSection;
7 | import com.nfl.scte35.decoder.model.SpliceInsert;
8 | import com.nfl.scte35.decoder.model.TimeSignal;
9 |
10 | import org.apache.commons.codec.DecoderException;
11 | import org.apache.commons.codec.binary.Base64;
12 | import org.apache.commons.codec.binary.Hex;
13 |
14 | /**
15 | * Implements a SCTE35 Decoding mechanism.
16 | */
17 | public final class Scte35Decoder {
18 |
19 | private static final int SPLICE_NULL = 0x00;
20 | private static final int SPLICE_SCHEDULE = 0x04;
21 | private static final int SPLICE_INSERT = 0x05;
22 | private static final int TIME_SIGNAL = 0x06;
23 | private static final int BANDWIDTH_RESERVATION = 0x07;
24 | private static final int PRIVATE_COMMAND = 0x00ff;
25 | private static final int[] CrcTable = {
26 | 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F,
27 | 0x2F8AD6D6, 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9,
28 | 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3,
29 | 0x709F7B7A, 0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5,
30 | 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, 0xD4326D90, 0xD0F37027,
31 | 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1,
32 | 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C,
33 | 0x2E003DC5, 0x2AC12072, 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA,
34 | 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08,
35 | 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E,
36 | 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044,
37 | 0x902B669D, 0x94EA7B2A, 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2,
38 | 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59,
39 | 0x608EDB80, 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F,
40 | 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5,
41 | 0x3F9B762C, 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623,
42 | 0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, 0xD727BBB6, 0xD3E6A601,
43 | 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7,
44 | 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD,
45 | 0x81B02D74, 0x857130C3, 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C,
46 | 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E,
47 | 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088,
48 | 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12,
49 | 0xDF2F6BCB, 0xDBEE767C, 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4,
50 | 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06,
51 | 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4
52 | };
53 | private boolean printlogs = false;
54 |
55 | public Scte35Decoder(boolean printlogs) {
56 | this.printlogs = printlogs;
57 | }
58 |
59 |
60 | long crc32(byte[] b64, int startIdx, int endIdx) {
61 | int value = 0xFFFFFFFF;
62 | int ptr;
63 |
64 | for (int i = startIdx; i < endIdx; i++) {
65 | ptr = (((value >> 24) & 0x00ff) ^ b64[i]) & 0x00FF;
66 | value = (value << 8) ^ CrcTable[ptr];
67 | }
68 |
69 | return (value & 0xFFFFFFFFL);
70 | }
71 |
72 | private SpliceInfoSection decode35(byte[] b64) throws DecodingException {
73 | SpliceInfoSection spliceInfoSection = new SpliceInfoSection();
74 | SegmentationDescriptor[] seg = new SegmentationDescriptor[10];
75 | int i1;
76 | int i2;
77 | long l1;
78 | long l2;
79 | long l3;
80 | long l4;
81 | long l5;
82 | long l6;
83 | long l7;
84 | long l8;
85 | int bufptr;
86 | int desptr;
87 | int segptr = 0;
88 |
89 | String stemp = "";
90 | log("Hex=0x");
91 |
92 | for (int i = 0; i < b64.length; i++) {
93 | stemp += String.format("%02X", b64[i]);
94 | }
95 | log(stemp + "\nBase64=" + Base64.encodeBase64String(b64) + "\n\n");
96 |
97 | log("Decoded length = " + b64.length + "\n");
98 |
99 |
100 | spliceInfoSection.tableID = b64[0] & 0x00ff;
101 | if (spliceInfoSection.tableID != 0x0FC) {
102 | throw new DecodingException("Invalid Table ID != 0xFC");
103 | }
104 | log("Table ID = 0xFC\n");
105 |
106 | spliceInfoSection.sectionSyntaxIndicator = (b64[1] >> 7) & 0x01;
107 | if (spliceInfoSection.sectionSyntaxIndicator != 0) {
108 | log("ERROR Long section used\n");
109 | } else {
110 | log("MPEG Short Section\n");
111 | }
112 |
113 | spliceInfoSection.privateIndicator = (b64[1] >> 6) & 0x01;
114 | if (spliceInfoSection.privateIndicator != 0) {
115 | log("ERROR Private section signaled\n");
116 | } else {
117 | log("Not Private\n");
118 | }
119 |
120 | spliceInfoSection.reserved1 = (b64[1] >> 4) & 0x03;
121 | log(String.format("Reserved = 0x%x\n", spliceInfoSection.reserved1));
122 |
123 | i1 = b64[1] & 0x0f;
124 | i2 = b64[2] & 0x00ff;
125 | spliceInfoSection.sectionLength = (i1 << 8) + i2;
126 | log(("Section Length = " + spliceInfoSection.sectionLength + "\n"));
127 |
128 | spliceInfoSection.protocolVersion = b64[3];
129 | log(("Protocol Version = " + spliceInfoSection.protocolVersion + "\n"));
130 |
131 | spliceInfoSection.encryptedPacket = (b64[4] >> 7) & 0x01;
132 | spliceInfoSection.encryptionAlgorithm = (b64[4] >> 1) & 0x3F;
133 | if (spliceInfoSection.encryptedPacket != 0) {
134 | log("Encrypted Packet\n");
135 | log(String.format("Encryption Algorithm = 0x%x\n", spliceInfoSection.encryptionAlgorithm));
136 | } else {
137 | log("unencrypted Packet\n");
138 | }
139 |
140 | l1 = b64[4] & 0x01;
141 | l2 = b64[5] & 0x00ff;
142 | l3 = b64[6] & 0x00ff;
143 | l4 = b64[7] & 0x00ff;
144 | l5 = b64[8] & 0x00ff;
145 | spliceInfoSection.ptsAdjustment = (l1 << 32) + (l2 << 24) + (l3 << 16) + (l4 << 8) + l5;
146 | log(String.format("PTS Adjustment = 0x%09x\n", spliceInfoSection.ptsAdjustment));
147 |
148 | spliceInfoSection.cwIndex = b64[9] & 0x00ff;
149 | if (spliceInfoSection.encryptedPacket != 0) {
150 | log(String.format("CW Index = 0x%x\n", spliceInfoSection.cwIndex));
151 | }
152 |
153 | i1 = b64[10] & 0x00ff;
154 | i2 = (b64[11] & 0x00f0) >> 4;
155 | spliceInfoSection.tier = (i1 << 4) + i2;
156 | log(String.format("Tier = 0x%x\n", spliceInfoSection.tier));
157 |
158 | i1 = b64[11] & 0x000f;
159 | i2 = b64[12] & 0x00ff;
160 | spliceInfoSection.spliceCommandLength = (i1 << 8) + i2;
161 | log(String.format("Splice Command Length = 0x%x\n", spliceInfoSection.spliceCommandLength));
162 |
163 | spliceInfoSection.spliceCommandType = b64[13] & 0x00ff;
164 | bufptr = 14;
165 | SpliceInsert spliceInsert = new SpliceInsert();
166 | spliceInfoSection.spliceInsert = spliceInsert;
167 | switch (spliceInfoSection.spliceCommandType) {
168 | case SPLICE_NULL:
169 | log("Splice Null\n");
170 | break;
171 | case SPLICE_SCHEDULE:
172 | log("Splice Schedule\n");
173 | break;
174 | case SPLICE_INSERT:
175 | log("Splice Insert\n");
176 | l1 = b64[bufptr] & 0x00ff;
177 | bufptr++;
178 | l2 = b64[bufptr] & 0x00ff;
179 | bufptr++;
180 | l3 = b64[bufptr] & 0x00ff;
181 | bufptr++;
182 | l4 = b64[bufptr] & 0x00ff;
183 | bufptr++;
184 | spliceInsert.spliceEventID = (int) (((l1 << 24) + (l2 << 16) + (l3 << 8) + l4) & 0x00ffffffff);
185 | log(String.format("Splice Event ID = 0x%x\n", spliceInsert.spliceEventID));
186 |
187 | i1 = b64[bufptr] & 0x080;
188 | bufptr++;
189 | if (i1 != 0) {
190 | spliceInsert.spliceEventCancelIndicator = 1;
191 | log("Splice Event Canceled\n");
192 | } else {
193 | spliceInsert.spliceEventCancelIndicator = 0;
194 | }
195 |
196 | spliceInsert.outOfNetworkIndicator = (b64[bufptr] & 0x080) >> 7;
197 | spliceInsert.programSpliceFlag = (b64[bufptr] & 0x040) >> 6;
198 | spliceInsert.durationFlag = (b64[bufptr] & 0x020) >> 5;
199 | spliceInsert.spliceImmediateFlag = (b64[bufptr] & 0x010) >> 4;
200 | bufptr++;
201 | log("Flags OON=" + spliceInsert.outOfNetworkIndicator + " Prog=" + spliceInsert.programSpliceFlag
202 | + " Duration=" + spliceInsert.durationFlag + " Immediate=" + spliceInsert.spliceImmediateFlag + "\n");
203 |
204 | if ((spliceInsert.programSpliceFlag == 1) && (spliceInsert.spliceImmediateFlag == 0)) {
205 | if ((b64[bufptr] & 0x080) != 0) {
206 | // time specified
207 | l1 = b64[bufptr] & 0x01;
208 | bufptr++;
209 | l2 = b64[bufptr] & 0x00ff;
210 | bufptr++;
211 | l3 = b64[bufptr] & 0x00ff;
212 | bufptr++;
213 | l4 = b64[bufptr] & 0x00ff;
214 | bufptr++;
215 | l5 = b64[bufptr] & 0x00ff;
216 | spliceInsert.sisp.ptsTime = (l1 << 32) + (l2 << 24) + (l3 << 16) + (l4 << 8) + l5;
217 | log(String.format("Splice time = 0x%09x\n", spliceInsert.sisp.ptsTime));
218 | }
219 | bufptr++;
220 | }
221 |
222 | if (spliceInsert.durationFlag != 0) {
223 | spliceInsert.breakDuration.autoReturn = (b64[bufptr] & 0x080) >> 7;
224 | if (spliceInsert.breakDuration.autoReturn != 0) {
225 | log("Auto Return\n");
226 | }
227 | l1 = b64[bufptr] & 0x01;
228 | bufptr++;
229 | l2 = b64[bufptr] & 0x00ff;
230 | bufptr++;
231 | l3 = b64[bufptr] & 0x00ff;
232 | bufptr++;
233 | l4 = b64[bufptr] & 0x00ff;
234 | bufptr++;
235 | l5 = b64[bufptr] & 0x00ff;
236 | bufptr++;
237 | spliceInsert.breakDuration.duration = (l1 << 32) + (l2 << 24) + (l3 << 16) + (l4 << 8) + l5;
238 | double bsecs = spliceInsert.breakDuration.duration;
239 | bsecs /= 90000.0;
240 | log(String.format("break duration = 0x%09x = %f seconds\n", spliceInsert.breakDuration.duration, bsecs));
241 | }
242 | i1 = b64[bufptr] & 0x00ff;
243 | bufptr++;
244 | i2 = b64[bufptr] & 0x00ff;
245 | bufptr++;
246 | spliceInsert.uniqueProgramID = (i1 << 8) + i2;
247 | log("Unique Program ID = " + spliceInsert.uniqueProgramID + "\n");
248 |
249 | spliceInsert.availNum = b64[bufptr] & 0x00ff;
250 | bufptr++;
251 | log("Avail Num = " + spliceInsert.availNum + "\n");
252 |
253 | spliceInsert.availsExpected = b64[bufptr] & 0x00ff;
254 | bufptr++;
255 | log("Avails Expected = " + spliceInsert.availsExpected + "\n");
256 |
257 | break;
258 | case TIME_SIGNAL:
259 | log("Time Signal\n");
260 | TimeSignal timeSignal = new TimeSignal();
261 | timeSignal.tssp.timeSpecifiedFlag = (b64[bufptr] & 0x080) >> 7;
262 | if (timeSignal.tssp.timeSpecifiedFlag != 0) {
263 | // time specified
264 | l1 = b64[bufptr] & 0x01;
265 | bufptr++;
266 | l2 = b64[bufptr] & 0x00ff;
267 | bufptr++;
268 | l3 = b64[bufptr] & 0x00ff;
269 | bufptr++;
270 | l4 = b64[bufptr] & 0x00ff;
271 | bufptr++;
272 | l5 = b64[bufptr] & 0x00ff;
273 | timeSignal.tssp.ptsTime = (l1 << 32) + (l2 << 24) + (l3 << 16) + (l4 << 8) + l5;
274 | log(String.format("Time = 0x%09x\n", timeSignal.tssp.ptsTime));
275 | }
276 | bufptr++;
277 | break;
278 | case BANDWIDTH_RESERVATION:
279 | log("Bandwidth Reservation\n");
280 | break;
281 | case PRIVATE_COMMAND:
282 | log("Private Command\n");
283 | break;
284 | default:
285 | log(String.format("ERROR Unknown command = 0x%x\n", spliceInfoSection.spliceCommandType));
286 | // Unknown command, oops
287 | break;
288 | }
289 |
290 | if (spliceInfoSection.spliceCommandLength != 0x0fff) { // legacy check
291 | if (bufptr != (spliceInfoSection.spliceCommandLength + 14)) {
292 | log("ERROR decoded command length " + bufptr + " not equal to specified command length " + spliceInfoSection.spliceCommandLength + "\n");
293 | //Some kind of error, or unknown command
294 | //bufptr = spliceInfoSection.spliceCommandLength + 14;
295 | }
296 | }
297 |
298 | i1 = b64[bufptr] & 0x00ff;
299 | bufptr++;
300 | i2 = b64[bufptr] & 0x00ff;
301 | bufptr++;
302 | spliceInfoSection.descriptorLoopLength = (i1 << 8) + i2;
303 | log("Descriptor Loop Length = " + spliceInfoSection.descriptorLoopLength + "\n");
304 |
305 | desptr = bufptr;
306 |
307 | if (spliceInfoSection.descriptorLoopLength > 0) {
308 | while ((bufptr - desptr) < spliceInfoSection.descriptorLoopLength) {
309 | int tag = b64[bufptr] & 0x00ff;
310 | bufptr++;
311 | int len = b64[bufptr] & 0x00ff;
312 | bufptr++;
313 | l1 = b64[bufptr] & 0x00ff;
314 | bufptr++;
315 | l2 = b64[bufptr] & 0x00ff;
316 | bufptr++;
317 | l3 = b64[bufptr] & 0x00ff;
318 | bufptr++;
319 | l4 = b64[bufptr] & 0x00ff;
320 | bufptr++;
321 | int identifier = (int) ((l1 << 24) + (l2 << 16) + (l3 << 8) + l4);
322 | if (identifier == 0x43554549) {
323 | switch (tag) {
324 | case 0:
325 | log("Avail Descriptor - Length=" + len + "\n");
326 | l1 = b64[bufptr] & 0x00ff;
327 | bufptr++;
328 | l2 = b64[bufptr] & 0x00ff;
329 | bufptr++;
330 | l3 = b64[bufptr] & 0x00ff;
331 | bufptr++;
332 | l4 = b64[bufptr] & 0x00ff;
333 | bufptr++;
334 | int availDesc = (int) (((l1 << 24) + (l2 << 16) + (l3 << 8) + l4) & 0x00ffffffff);
335 | log(String.format("Avail Descriptor = 0x%08x\n", availDesc));
336 | break;
337 | case 1:
338 | log("DTMF Descriptor - Length=" + len + "\n");
339 | double preroll = b64[bufptr] & 0x00ff;
340 | preroll /= 10;
341 | log("Preroll = " + preroll + "\n");
342 | bufptr++;
343 | int dtmfCount = (b64[bufptr] & 0x00E0) >> 5;
344 | bufptr++;
345 | log(dtmfCount + "DTMF chars = ");
346 | for (int i = 0; i < dtmfCount; i++) {
347 | log(String.format("%c", b64[bufptr] & 0x00ff));
348 | bufptr++;
349 | }
350 | log("\n");
351 | break;
352 | case 2:
353 | log("Segmentation Descriptor - Length=" + len + "\n");
354 | seg[segptr] = new SegmentationDescriptor();
355 | l1 = b64[bufptr] & 0x00ff;
356 | bufptr++;
357 | l2 = b64[bufptr] & 0x00ff;
358 | bufptr++;
359 | l3 = b64[bufptr] & 0x00ff;
360 | bufptr++;
361 | l4 = b64[bufptr] & 0x00ff;
362 | bufptr++;
363 | seg[segptr].segmentationEventID = (int) (((l1 << 24) + (l2 << 16) + (l3 << 8) + l4) & 0x00ffffffff);
364 | log(String.format("Segmentation Event ID = 0x%08x\n", seg[segptr].segmentationEventID));
365 | seg[segptr].segmentationEventCancelIndicator = (b64[bufptr] & 0x080) >> 7;
366 | bufptr++;
367 | if (seg[segptr].segmentationEventCancelIndicator == 0) {
368 | log("Segmentation Event Cancel Indicator NOT set\n");
369 | seg[segptr].programSegmentationFlag = (b64[bufptr] & 0x080) >> 7;
370 | seg[segptr].segmentationDurationFlag = (b64[bufptr] & 0x040) >> 6;
371 | seg[segptr].deliveryNotRestricted = (b64[bufptr] & 0x020) >> 5;
372 | log("Delivery Not Restricted flag = " + seg[segptr].deliveryNotRestricted + "\n");
373 | if (seg[segptr].deliveryNotRestricted == 0) {
374 | seg[segptr].webDeliveryAllowedFlag = (b64[bufptr] & 0x010) >> 4;
375 | log("Web Delivery Allowed flag = " + seg[segptr].webDeliveryAllowedFlag + "\n");
376 | seg[segptr].noRegionalBlackoutFlag = (b64[bufptr] & 0x008) >> 3;
377 | log("No Regional Blackout flag = " + seg[segptr].noRegionalBlackoutFlag + "\n");
378 | seg[segptr].archiveAllowed = (b64[bufptr] & 0x004) >> 2;
379 | log("Archive Allowed flag = " + seg[segptr].archiveAllowed + "\n");
380 | seg[segptr].deviceRestriction = (b64[bufptr] & 0x003);
381 | log("Device Restrictions = " + seg[segptr].deviceRestriction + "\n");
382 | }
383 | bufptr++;
384 | if (seg[segptr].programSegmentationFlag == 0) {
385 | log("Component segmention NOT IMPLEMENTED\n");
386 | } else {
387 | log("Program Segmentation flag SET\n");
388 | }
389 | if (seg[segptr].segmentationDurationFlag == 1) {
390 | l1 = b64[bufptr] & 0x0ff;
391 | bufptr++;
392 | l2 = b64[bufptr] & 0x00ff;
393 | bufptr++;
394 | l3 = b64[bufptr] & 0x00ff;
395 | bufptr++;
396 | l4 = b64[bufptr] & 0x00ff;
397 | bufptr++;
398 | l5 = b64[bufptr] & 0x00ff;
399 | bufptr++;
400 | seg[segptr].segmentationDuration = (l1 << 32) + (l2 << 24) + (l3 << 16) + (l4 << 8) + l5;
401 | double secs = seg[segptr].segmentationDuration;
402 | secs /= 90000.0;
403 | log(String.format("Segmentation Duration = 0x%010x = %f seconds\n", seg[segptr].segmentationDuration, secs));
404 | }
405 | seg[segptr].segmentationUPIDtype = b64[bufptr] & 0x00ff;
406 | bufptr++;
407 | seg[segptr].segmentationUPIDlength = b64[bufptr] & 0x00ff;
408 | bufptr++;
409 | switch (seg[segptr].segmentationUPIDtype) {
410 | case 0x00:
411 | log("UPID Type = Not Used length = " + seg[segptr].segmentationUPIDlength + "\n");
412 | break;
413 | case 0x01:
414 | log("UPID Type = User Defined (Deprecated) length =" + seg[segptr].segmentationUPIDlength + "\nHex=0x");
415 | for (int j = bufptr; j < (bufptr + seg[segptr].segmentationUPIDlength); j++) {
416 | log(String.format("%02X.", b64[j]));
417 | }
418 | log("\n");
419 | bufptr += seg[segptr].segmentationUPIDlength;
420 | break;
421 | case 0x02:
422 | log("UPID Type = ISCII (deprecated)length = " + seg[segptr].segmentationUPIDlength + "\n");
423 | String siTemp = "ISCII=";
424 | for (int j = bufptr; j < (bufptr + seg[segptr].segmentationUPIDlength); j++) {
425 | siTemp += (char) b64[j];
426 | }
427 | siTemp += "\n";
428 | log(siTemp);
429 | bufptr += seg[segptr].segmentationUPIDlength;
430 | break;
431 | case 0x03:
432 | log("UPID Type = Ad-IDlength = " + seg[segptr].segmentationUPIDlength + "\n");
433 | String stTemp = "AdId=";
434 | for (int j = bufptr; j < (bufptr + seg[segptr].segmentationUPIDlength); j++) {
435 | stTemp += (char) b64[j];
436 | }
437 | stTemp += "\n";
438 | log(stTemp);
439 | bufptr += seg[segptr].segmentationUPIDlength;
440 | break;
441 | case 0x04:
442 | log("UPID Type = UMID SMPTE 330M length = " + seg[segptr].segmentationUPIDlength + "\n");
443 | bufptr += seg[segptr].segmentationUPIDlength;
444 | break;
445 | case 0x05:
446 | log("UPID Type = ISAN (Deprecated) length = " + seg[segptr].segmentationUPIDlength + "\n");
447 | bufptr += seg[segptr].segmentationUPIDlength;
448 | break;
449 | case 0x06:
450 | log("UPID Type = ISAN length = " + seg[segptr].segmentationUPIDlength + "\n");
451 | bufptr += seg[segptr].segmentationUPIDlength;
452 | break;
453 | case 0x07:
454 | log("UPID Type = Tribune ID length = " + seg[segptr].segmentationUPIDlength + "\n");
455 | bufptr += seg[segptr].segmentationUPIDlength;
456 | break;
457 | case 0x08:
458 | log("UPID Type = Turner Identifier length = " + seg[segptr].segmentationUPIDlength + "\n");
459 | l1 = b64[bufptr] & 0x0ff;
460 | bufptr++;
461 | l2 = b64[bufptr] & 0x00ff;
462 | bufptr++;
463 | l3 = b64[bufptr] & 0x00ff;
464 | bufptr++;
465 | l4 = b64[bufptr] & 0x00ff;
466 | bufptr++;
467 | l5 = b64[bufptr] & 0x00ff;
468 | bufptr++;
469 | l6 = b64[bufptr] & 0x00ff;
470 | bufptr++;
471 | l7 = b64[bufptr] & 0x00ff;
472 | bufptr++;
473 | l8 = b64[bufptr] & 0x00ff;
474 | bufptr++;
475 | seg[segptr].turnerIdentifier = (l1 << 56) + (l2 << 48) + (l3 << 40) + (l4 << 32) + (l5 << 24) + (l6 << 16) + (l7 << 8) + l8;
476 | log(String.format("Turner Identifier = 0x%016x\n", seg[segptr].turnerIdentifier));
477 | break;
478 | case 0x09:
479 | log("UPID Type = ADI length = " + seg[segptr].segmentationUPIDlength + "\n");
480 | bufptr += seg[segptr].segmentationUPIDlength;
481 | break;
482 | case 0x0A:
483 | log("UPID Type = EIDR length = " + seg[segptr].segmentationUPIDlength + "\n");
484 | bufptr += seg[segptr].segmentationUPIDlength;
485 | break;
486 | case 0x0B:
487 | log("UPID Type = ATSC Content Identifier length = " + seg[segptr].segmentationUPIDlength + "\n");
488 | bufptr += seg[segptr].segmentationUPIDlength;
489 | break;
490 | case 0x0C:
491 | log("UPID Type = Managed Private UPID length = " + seg[segptr].segmentationUPIDlength + "\n");
492 | bufptr += seg[segptr].segmentationUPIDlength;
493 | break;
494 | case 0x0D:
495 | log("UPID Type = Multiple UPID length = " + seg[segptr].segmentationUPIDlength + "\nHex=0x");
496 | for (int j = bufptr; j < (bufptr + seg[segptr].segmentationUPIDlength); j++) {
497 | log(String.format("%02X.", b64[j]));
498 | }
499 | log("\n");
500 | bufptr += seg[segptr].segmentationUPIDlength;
501 | break;
502 | default:
503 | log("UPID Type = UNKNOWN length = " + seg[segptr].segmentationUPIDlength + "\nHex=0x");
504 | for (int j = bufptr; j < (bufptr + seg[segptr].segmentationUPIDlength); j++) {
505 | log(String.format("%02X.", b64[j]));
506 | }
507 | log("\n");
508 | bufptr += seg[segptr].segmentationUPIDlength;
509 | break;
510 | }
511 | seg[segptr].segmentationTypeID = b64[bufptr] & 0x00ff;
512 | bufptr++;
513 | switch (seg[segptr].segmentationTypeID) {
514 | case 0x00:
515 | log("Type = Not Indicated\n");
516 | break;
517 | case 0x01:
518 | log("Type = Content Identification\n");
519 | break;
520 | case 0x10:
521 | log("Type = Program Start\n");
522 | break;
523 | case 0x11:
524 | log("Type = Program End\n");
525 | break;
526 | case 0x12:
527 | log("Type = Program Early Termination\n");
528 | break;
529 | case 0x13:
530 | log("Type = Program Breakaway\n");
531 | break;
532 | case 0x14:
533 | log("Type = Program Resumption\n");
534 | break;
535 | case 0x15:
536 | log("Type = Program Runover Planned\n");
537 | break;
538 | case 0x16:
539 | log("Type = Program Runover Unplanned\n");
540 | break;
541 | case 0x17:
542 | log("Type = Program Overlap Start\n");
543 | break;
544 | case 0x20:
545 | log("Type = Chapter Start\n");
546 | break;
547 | case 0x21:
548 | log("Type = Chapter End\n");
549 | break;
550 | case 0x30:
551 | log("Type = Provider Advertisement Start\n");
552 | break;
553 | case 0x31:
554 | log("Type = Provider Advertisement End\n");
555 | break;
556 | case 0x32:
557 | log("Type = Distributor Advertisement Start\n");
558 | break;
559 | case 0x33:
560 | log("Type = Distributor Advertisement End\n");
561 | break;
562 | case 0x34:
563 | log("Type = Placement Opportunity Start\n");
564 | break;
565 | case 0x35:
566 | log("Type = Placement Opportunity End\n");
567 | break;
568 | case 0x40:
569 | log("Type = Unscheduled Event Start\n");
570 | break;
571 | case 0x41:
572 | log("Type = Unscheduled Event End\n");
573 | break;
574 | case 0x50:
575 | log("Type = Network Start\n");
576 | break;
577 | case 0x51:
578 | log("Type = Network End\n");
579 | break;
580 | default:
581 | log("Type = Unknown = " + seg[segptr].segmentationTypeID + "\n");
582 | break;
583 | }
584 | seg[segptr].segmentNum = b64[bufptr] & 0x00ff;
585 | bufptr++;
586 | seg[segptr].segmentsExpected = b64[bufptr] & 0x00ff;
587 | bufptr++;
588 | log("Segment num = " + seg[segptr].segmentNum + " Segments Expected = " + seg[segptr].segmentsExpected + "\n");
589 | segptr++;
590 | } else {
591 | log("Segmentation Event Cancel Indicator SET\n");
592 | }
593 | break;
594 | }
595 | } else {
596 | log(String.format("Private Descriptor tag=%d Length=%d identifier = 0x%08x Value = 0x", tag, len, identifier));
597 | for (int j = bufptr; j < (bufptr + (len - 4)); j++) {
598 | log(String.format("%02X.", b64[j]));
599 | }
600 | log("\n");
601 | bufptr += len - 4;
602 | }
603 | }
604 | }
605 |
606 | if (bufptr != (spliceInfoSection.descriptorLoopLength + desptr)) {
607 | int dlen = bufptr - desptr;
608 | log("ERROR decoded descriptor length " + dlen + " not equal to specified descriptor length " + spliceInfoSection.descriptorLoopLength + "\n");
609 | bufptr = desptr + spliceInfoSection.descriptorLoopLength;
610 | log("SKIPPING REST OF THE COMMAND!!!!!!\n");
611 | } else {
612 |
613 | if (spliceInfoSection.encryptedPacket != 0) {
614 | spliceInfoSection.alignmentStuffing = 0;
615 | spliceInfoSection.eCRC32 = 0;
616 | }
617 |
618 | l1 = b64[bufptr] & 0x00ff;
619 | bufptr++;
620 | l2 = b64[bufptr] & 0x00ff;
621 | bufptr++;
622 | l3 = b64[bufptr] & 0x00ff;
623 | bufptr++;
624 | l4 = b64[bufptr] & 0x00ff;
625 | bufptr++;
626 | spliceInfoSection.CRC32 = (int) (((l1 << 24) + (l2 << 16) + (l3 << 8) + l4) & 0x00ffffffff);
627 | log(String.format("CRC32 = 0x%08x\n", spliceInfoSection.CRC32));
628 | }
629 | log(String.format("calc CRC32 = 0x%08x --- Should = 0x00000000\n", crc32(b64, 0, bufptr)));
630 | return spliceInfoSection;
631 | }
632 |
633 | /**
634 | * Extend this class and implement this method with an actual logger
635 | *
636 | * @param log statement to print
637 | */
638 | protected void log(String log) {
639 | if (printlogs) {
640 | System.out.println(log);
641 | }
642 | }
643 |
644 |
645 | private SpliceInfoSection hexDecode(String hexin) throws DecodingException {
646 | byte[] b64;
647 | try {
648 | b64 = Hex.decodeHex(hexin.toCharArray());
649 | } catch (DecoderException e) {
650 | throw new DecodingException("Decoding from Hex", e);
651 | }
652 | return decode35(b64);
653 | }
654 |
655 | public SpliceInfoSection base64Decode(String base64in) throws DecodingException {
656 | byte[] b64 = Base64.decodeBase64(base64in);
657 | String stemp = "";
658 | for (int i = 0; i < b64.length; i++) {
659 | stemp += String.format("%02X", b64[i]);
660 | }
661 |
662 | return hexDecode(stemp);
663 | }
664 |
665 | }
--------------------------------------------------------------------------------
/src/main/java/com/nfl/scte35/decoder/exception/DecodingException.java:
--------------------------------------------------------------------------------
1 | package com.nfl.scte35.decoder.exception;
2 |
3 | /**
4 | * Created by andres.aguilar on 6/17/16.
5 | */
6 | public class DecodingException extends Exception {
7 | public DecodingException(String message) {
8 | super(message);
9 | }
10 |
11 | public DecodingException(String message, Exception e) {
12 | super(message, e);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/nfl/scte35/decoder/model/AvailDescriptor.java:
--------------------------------------------------------------------------------
1 | package com.nfl.scte35.decoder.model;
2 |
3 | /**
4 | * Created by andres.aguilar on 6/16/16.
5 | */
6 | public class AvailDescriptor {
7 |
8 | public int spliceDescriptorTag;
9 | public int descriptorLength;
10 | public int identifier;
11 | public int providerAvailID;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/nfl/scte35/decoder/model/BreakDuration.java:
--------------------------------------------------------------------------------
1 | package com.nfl.scte35.decoder.model;
2 |
3 | /**
4 | * Created by andres.aguilar on 6/16/16.
5 | */
6 | public class BreakDuration {
7 |
8 | public int autoReturn;
9 | public int reserved1;
10 | public long duration;
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/nfl/scte35/decoder/model/DTMFDescriptor.java:
--------------------------------------------------------------------------------
1 | package com.nfl.scte35.decoder.model;
2 |
3 | /**
4 | * Created by andres.aguilar on 6/16/16.
5 | */
6 | public class DTMFDescriptor {
7 |
8 | public int spliceDescriptorTag;
9 | public int descriptorLength;
10 | public int identifier;
11 | public int preroll;
12 | public int dtmfCount;
13 | public int reserved;
14 | public byte[] DTMFChar = new byte[8];
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/nfl/scte35/decoder/model/SegmentationDescriptor.java:
--------------------------------------------------------------------------------
1 | package com.nfl.scte35.decoder.model;
2 |
3 | /**
4 | * Created by andres.aguilar on 6/16/16.
5 | */
6 | public class SegmentationDescriptor {
7 |
8 | public int spliceDescriptorTag;
9 | public int descriptorLength;
10 | public int identifier;
11 | public int segmentationEventID;
12 | public int segmentationEventCancelIndicator;
13 | public int reserved1;
14 | public int programSegmentationFlag;
15 | public int segmentationDurationFlag;
16 | public int deliveryNotRestricted;
17 | public int webDeliveryAllowedFlag;
18 | public int noRegionalBlackoutFlag;
19 | public int archiveAllowed;
20 | public int deviceRestriction;
21 | public int reserved2;
22 | public long segmentationDuration;
23 | public long turnerIdentifier;
24 | public int segmentationUPIDtype;
25 | public int segmentationUPIDlength;
26 | public int segmentationTypeID;
27 | public int segmentNum;
28 | public int segmentsExpected;
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/nfl/scte35/decoder/model/SpliceInfoSection.java:
--------------------------------------------------------------------------------
1 | package com.nfl.scte35.decoder.model;
2 |
3 | /**
4 | * Created by andres.aguilar on 6/16/16.
5 | */
6 | public class SpliceInfoSection {
7 |
8 | public int tableID;
9 | public int sectionSyntaxIndicator;
10 | public int privateIndicator;
11 | public int reserved1;
12 | public int sectionLength;
13 | public int protocolVersion;
14 | public int encryptedPacket;
15 | public int encryptionAlgorithm;
16 | public long ptsAdjustment;
17 | public int cwIndex;
18 | public int tier;
19 | public int spliceCommandLength;
20 | public int spliceCommandType;
21 | public int descriptorLoopLength;
22 | public int alignmentStuffing;
23 | public int eCRC32;
24 | public int CRC32;
25 | public SpliceInsert spliceInsert;
26 |
27 | @Override
28 | public String toString() {
29 | return "SpliceInfoSection{" +
30 | "tableID=" + tableID +
31 | ", sectionSyntaxIndicator=" + sectionSyntaxIndicator +
32 | ", privateIndicator=" + privateIndicator +
33 | ", reserved1=" + reserved1 +
34 | ", sectionLength=" + sectionLength +
35 | ", protocolVersion=" + protocolVersion +
36 | ", encryptedPacket=" + encryptedPacket +
37 | ", encryptionAlgorithm=" + encryptionAlgorithm +
38 | ", ptsAdjustment=" + ptsAdjustment +
39 | ", cwIndex=" + cwIndex +
40 | ", tier=" + tier +
41 | ", spliceCommandLength=" + spliceCommandLength +
42 | ", spliceCommandType=" + spliceCommandType +
43 | ", descriptorLoopLength=" + descriptorLoopLength +
44 | ", alignmentStuffing=" + alignmentStuffing +
45 | ", eCRC32=" + eCRC32 +
46 | ", CRC32=" + CRC32 +
47 | ", spliceInsert=" + spliceInsert +
48 | '}';
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/nfl/scte35/decoder/model/SpliceInsert.java:
--------------------------------------------------------------------------------
1 | package com.nfl.scte35.decoder.model;
2 |
3 | /**
4 | * Created by andres.aguilar on 6/16/16.
5 | */
6 | public class SpliceInsert {
7 |
8 | public int spliceEventID;
9 | public int spliceEventCancelIndicator;
10 | public int reserved1;
11 | public int outOfNetworkIndicator;
12 | public int programSpliceFlag;
13 | public SpliceTime sisp = new SpliceTime();
14 | public int durationFlag;
15 | public int spliceImmediateFlag;
16 | public BreakDuration breakDuration = new BreakDuration();
17 | public int reserved2;
18 | public int uniqueProgramID;
19 | public int availNum;
20 | public int availsExpected;
21 |
22 | @Override
23 | public String toString() {
24 | return "SpliceInsert{" +
25 | "spliceEventID=" + spliceEventID +
26 | ", spliceEventCancelIndicator=" + spliceEventCancelIndicator +
27 | ", reserved1=" + reserved1 +
28 | ", outOfNetworkIndicator=" + outOfNetworkIndicator +
29 | ", programSpliceFlag=" + programSpliceFlag +
30 | ", sisp=" + sisp +
31 | ", durationFlag=" + durationFlag +
32 | ", spliceImmediateFlag=" + spliceImmediateFlag +
33 | ", breakDuration=" + breakDuration +
34 | ", reserved2=" + reserved2 +
35 | ", uniqueProgramID=" + uniqueProgramID +
36 | ", availNum=" + availNum +
37 | ", availsExpected=" + availsExpected +
38 | '}';
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/nfl/scte35/decoder/model/SpliceTime.java:
--------------------------------------------------------------------------------
1 | package com.nfl.scte35.decoder.model;
2 |
3 | /**
4 | * Created by andres.aguilar on 6/16/16.
5 | */
6 | public class SpliceTime {
7 |
8 | public int timeSpecifiedFlag;
9 | public int reserved1;
10 | public long ptsTime;
11 | public int reserved2;
12 |
13 | @Override
14 | public String toString() {
15 | return "SpliceTime{" +
16 | "timeSpecifiedFlag=" + timeSpecifiedFlag +
17 | ", reserved1=" + reserved1 +
18 | ", ptsTime=" + ptsTime +
19 | ", reserved2=" + reserved2 +
20 | '}';
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/nfl/scte35/decoder/model/TimeSignal.java:
--------------------------------------------------------------------------------
1 | package com.nfl.scte35.decoder.model;
2 |
3 | /**
4 | * Created by andres.aguilar on 6/16/16.
5 | */
6 | public class TimeSignal {
7 |
8 | public SpliceTime tssp = new SpliceTime();
9 | }
10 |
--------------------------------------------------------------------------------
/src/test/java/com/nfl/scte35/decoder/Scte35DecoderTest.java:
--------------------------------------------------------------------------------
1 | package com.nfl.scte35.decoder;
2 |
3 | import com.nfl.scte35.decoder.model.BreakDuration;
4 | import com.nfl.scte35.decoder.model.SpliceInfoSection;
5 | import com.nfl.scte35.decoder.model.SpliceInsert;
6 | import org.testng.annotations.BeforeClass;
7 | import org.testng.annotations.Test;
8 |
9 | import static org.testng.Assert.assertEquals;
10 |
11 |
12 | /**
13 | * Created by andres.aguilar on 6/17/16.
14 | */
15 | public class Scte35DecoderTest {
16 |
17 | private Scte35Decoder scte35Decoder;
18 |
19 | @BeforeClass
20 | public void setUp() throws Exception {
21 | scte35Decoder = new Scte35Decoder(false);
22 | }
23 |
24 |
25 | /**
26 | * Given input:
27 | * {@code "/DAlAAAAAAAAAP/wFAUAAAPof+//SVqZrP4Ae5igAAEBAQAAQcfnVA=="}
28 | * Expected result:
29 | * {@code
30 | *
41 | * NOTE: reserved fields indices are shifted by 1, i.e. reserved_0 => reserved1
42 | *
43 | * @throws Exception
44 | */
45 | @Test
46 | public void testExample1() throws Exception {
47 | SpliceInfoSection spliceInfoSection = scte35Decoder.base64Decode("/DAlAAAAAAAAAP/wFAUAAAPof+//SVqZrP4Ae5igAAEBAQAAQcfnVA==");
48 | assertEquals(252, spliceInfoSection.tableID);
49 | assertEquals(3, spliceInfoSection.reserved1);
50 | assertEquals(37, spliceInfoSection.sectionLength);
51 | assertEquals(4095, spliceInfoSection.tier);
52 | assertEquals(20, spliceInfoSection.spliceCommandLength);
53 | SpliceInsert spliceInsert = spliceInfoSection.spliceInsert;
54 | assertEquals(1000, spliceInsert.spliceEventID);
55 | //assertEquals(127,spliceInsert.reserved1); //Not set in decoder
56 | assertEquals(1, spliceInsert.outOfNetworkIndicator);
57 | //assertEquals(15,spliceInsert.reserved2); //Not set in decoder
58 | assertEquals(1, spliceInsert.uniqueProgramID);
59 | assertEquals(1, spliceInsert.availNum);
60 | assertEquals(1, spliceInsert.availsExpected);
61 | BreakDuration breakDuration = spliceInsert.breakDuration;
62 | assertEquals(1, breakDuration.autoReturn);
63 | //assertEquals(63, breakDuration.reserved1); //Not set in decoder
64 | assertEquals(8100000L, breakDuration.duration);
65 | assertEquals(1103619924L, spliceInfoSection.CRC32);
66 |
67 | System.out.println(spliceInfoSection);
68 | }
69 |
70 |
71 | /**
72 | * Given input:
73 | * {@code "/DAlAAAAAAAAAP/wFAUAACtnf+/+s9z9LP4Ae5igAAEBAQAAwWSPdQ=="}
74 | * Expected result:
75 | * {@code
76 | *
89 | * NOTE: reserved fields indices are shifted by 1, i.e. reserved_0 => reserved1
90 | *
91 | * @throws Exception
92 | */
93 | @Test
94 | public void testExample2() throws Exception {
95 | SpliceInfoSection spliceInfoSection = scte35Decoder.base64Decode("/DAlAAAAAAAAAP/wFAUAACtnf+/+s9z9LP4Ae5igAAEBAQAAwWSPdQ==");
96 | assertEquals(252, spliceInfoSection.tableID);
97 | assertEquals(3, spliceInfoSection.reserved1);
98 | assertEquals(37, spliceInfoSection.sectionLength);
99 | assertEquals(4095, spliceInfoSection.tier);
100 | assertEquals(20, spliceInfoSection.spliceCommandLength);
101 | SpliceInsert spliceInsert = spliceInfoSection.spliceInsert;
102 | assertEquals(11111, spliceInsert.spliceEventID);
103 | //assertEquals(127,spliceInsert.reserved1); //Not set in decoder
104 | assertEquals(1, spliceInsert.outOfNetworkIndicator);
105 | //assertEquals(15,spliceInsert.reserved2); //Not set in decoder
106 | assertEquals(1, spliceInsert.uniqueProgramID);
107 | assertEquals(1, spliceInsert.availNum);
108 | assertEquals(1, spliceInsert.availsExpected);
109 | BreakDuration breakDuration = spliceInsert.breakDuration;
110 | assertEquals(1, breakDuration.autoReturn);
111 | //assertEquals(63, breakDuration.reserved1); //Not set in decoder
112 | assertEquals(8100000L, breakDuration.duration);
113 | assertEquals(-1050374283L, spliceInfoSection.CRC32);
114 |
115 | System.out.println(spliceInfoSection);
116 | }
117 |
118 |
119 | /**
120 | * From Example on Python decoder: https://gist.github.com/use-sparingly/6517a8b94a52746af028
121 | *
122 | *
123 | * Given input:
124 | * {@code "/DAlAAAAAAAAAP/wFAUAAAABf+/+LRQrAP4BI9MIAAEBAQAAfxV6SQ=="}
125 | * Expected result:
126 | * {@code
127 | *
138 | * NOTE: reserved fields indices are shifted by 1, i.e. reserved_0 => reserved1 139 | * 140 | * @throws Exception 141 | */ 142 | @Test 143 | public void testExample3() throws Exception { 144 | SpliceInfoSection spliceInfoSection = scte35Decoder.base64Decode("/DAlAAAAAAAAAP/wFAUAAAABf+/+LRQrAP4BI9MIAAEBAQAAfxV6SQ=="); 145 | assertEquals(252, spliceInfoSection.tableID); 146 | assertEquals(3, spliceInfoSection.reserved1); 147 | assertEquals(37, spliceInfoSection.sectionLength); 148 | assertEquals(4095, spliceInfoSection.tier); 149 | assertEquals(20, spliceInfoSection.spliceCommandLength); 150 | SpliceInsert spliceInsert = spliceInfoSection.spliceInsert; 151 | assertEquals(1, spliceInsert.spliceEventID); 152 | //assertEquals(127,spliceInsert.reserved1); //Not set in decoder 153 | assertEquals(1, spliceInsert.outOfNetworkIndicator); 154 | //assertEquals(15,spliceInsert.reserved2); //Not set in decoder 155 | assertEquals(1, spliceInsert.uniqueProgramID); 156 | assertEquals(1, spliceInsert.availNum); 157 | assertEquals(1, spliceInsert.availsExpected); 158 | BreakDuration breakDuration = spliceInsert.breakDuration; 159 | assertEquals(1, breakDuration.autoReturn); 160 | //assertEquals(63, breakDuration.reserved1); //Not set in decoder 161 | assertEquals(19125000L, breakDuration.duration); 162 | assertEquals(2132113993L, spliceInfoSection.CRC32); 163 | 164 | System.out.println(spliceInfoSection); 165 | } 166 | 167 | } --------------------------------------------------------------------------------