├── .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 | [![Build Status](https://github.com/nfl/scte35/workflows/Java%20CI%20with%20Gradle/badge.svg)] 6 | [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](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 | * 31 | * 32 | * 33 | * 34 | * 35 | * 0 36 | * 0 37 | * 1103619924 38 | * 39 | * } 40 | *

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 | * 77 | * 78 | * 79 | * 80 | * 81 | * 82 | * 83 | * 0 84 | * 0 85 | * -1050374283 86 | * 87 | * } 88 | *

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 | * 128 | * 129 | * 130 | * 131 | * 132 | * 0 133 | * 0 134 | * 2132113993 135 | * 136 | * } 137 | *

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 | } --------------------------------------------------------------------------------