├── License.txt ├── README.md ├── playback_standardcaptions.png ├── playback_zoomedin.png └── sccyou /License.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019, Dave Rice 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sccyou 2 | A script that interprets the results of FFmpeg's [readeia608](https://ffmpeg.org/ffmpeg-filters.html#readeia608) filter to create scc (and optionally srt) outputs from standard definition video files which contain [EIA-608](https://en.wikipedia.org/wiki/EIA-608) data. 3 | 4 | ## Installation 5 | 6 | ### macOS 7 | 8 | If you use macOS, you can easily install sccyou using a package manager called Homebrew. To install Homebrew, follow [these instructions](https://brew.sh/). 9 | 10 | To install sccyou, run the following two commands in a [Terminal window](https://en.wikipedia.org/wiki/Terminal_%28macOS%29): 11 | ``` 12 | brew tap amiaopensource/amiaos 13 | brew install sccyou 14 | ``` 15 | (The first taps the homebrew recipes of the amiaopensource account; the second installs sccyou and the other programs that it requires.) 16 | 17 | ## Dependencies 18 | 19 | ffmpeg 4.3 or later 20 | 21 | ## Usage 22 | 23 | To use sccyou, install it and run one of the following commands: 24 | 25 | ``` 26 | sccyou -h display the help menu 27 | sccyou INPUT_FILE create an scc output 28 | sccyou -s INPUT_FILE create both scc and srt outputs 29 | sccyou -y INPUT_FILE overwrite any existing outputs 30 | sccyou -o DIRECTORY INPUT_FILE write output files to the provided directory (default is next to the input file) 31 | sccyou -l X INPUT_FILE read captions from line X (replace with a number) rather than let sccyou search for the captions 32 | ``` 33 | The results of sccyou will vary based upon your specific command, but will look like sidecar `INPUT_FILE.scc` and `INPUT_FILE.srt` files. 34 | 35 | ## Playback 36 | After creating your sidecar files, sccyou will provide instructions for two forms of playback with captions (using `ffplay`). 37 | 38 | Standard playback, with the captioned text on screen: 39 | ``` 40 | ffplay "INPUT_FILE" -vf subtitles="INPUT_FILE.scc" 41 | ``` 42 | 43 | ![Alt text](./playback_standardcaptions.png "Standard Playback w/ ffplay") 44 | 45 | 46 | Zoomed-in playback of caption data, scrolling vertically, with the captioned text appearing on screen: 47 | 48 | ``` 49 | ffplay "INPUT_FILE" -vf format=rgb24,crop=iw:1:0:1,scale=iw:4:flags=neighbor,tile=layout=1x120:overlap=119:init_padding=119,setdar=4/3,subtitles="INPUT_FILE.scc" 50 | ``` 51 | 52 | ![Alt text](./playback_zoomedin.png "Zoomed in Playback w/ ffplay") 53 | 54 | 55 | ## Tell me more about closed captions 56 | 57 | Adobe's [Introduction to Closed Captions](https://www.adobe.com/content/dam/acom/en/devnet/video/pdfs/introduction_to_closed_captions.pdf) offers a good technical introduction to the various closed captioning formats that are out there. 58 | Please note: this script will only work with video files that contain EIA-608 (also known as CEA-608 or "line 21") captions, which were used exclusively in NTSC television broadcasts. 59 | 60 | 61 | More information about the scc format (hexadecimal character codes and sets) can be found [here](http://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/). 62 | 63 | 64 | ## Feedback and Issues 65 | We welcome any feedback, in the [Issues](https://github.com/amiaopensource/sccyou/issues) section of this repo, though keep in mind that EIA-608 captions were often improperly created/duplicated, and ffmpeg can only retrieve that which isn't total trash. 66 | 67 | 68 | ## Code Contributors 69 | Dave Rice 70 | 71 | Paul Mahol 72 | 73 | ## Sponsors 74 | Development of sccyou was provided by New York Public Library's 2018/19 Innovation Project, and supported in part by the Charles H. Revson Foundation. 75 | 76 | 77 | ## Code of Conduct 78 | 79 | You can read our contributor code of conduct [here](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). 80 | 81 | ## License 82 | 83 | sccyou is licensed under The MIT License.
84 | -------------------------------------------------------------------------------- /playback_standardcaptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amiaopensource/sccyou/bd6708f8c7fd5a867cfe5ca7a80024a744a19a61/playback_standardcaptions.png -------------------------------------------------------------------------------- /playback_zoomedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amiaopensource/sccyou/bd6708f8c7fd5a867cfe5ca7a80024a744a19a61/playback_zoomedin.png -------------------------------------------------------------------------------- /sccyou: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # sccyou 3 | # script to apply the readeia608 filter and interpret the results in order to create an scc (and optionally srt or vtt) output from standard definition video files which contain EIA 608 data 4 | # thanks to Paul Mahol, Ben Turkus 5 | # Copyright (c) 2019-2024, Dave Rice, see License.txt 6 | 7 | VERSION="0.4" 8 | 9 | SEARCH_FIRST_X_SECONDS_FOR_CAPTION_LINE=20 10 | 11 | SCRIPTDIR="$(dirname "${0}")" 12 | FFMPEG_INPUT_OPTIONS+=(-v 0) 13 | FFPROBE_INPUT_OPTIONS+=(-v 0) 14 | 15 | _usage(){ 16 | cat < write output files to the provided directory (default is 25 | next to the input file) 26 | -s make an srt output as well as scc 27 | -w make a webvtt (vtt) output as well as scc 28 | -y overwrite any existing output files 29 | -v let ffmpeg be verbose, otherwise ffmpeg stderr is hidden 30 | 31 | Caption Discovery 32 | The location of the captions may either be specified as a line or a 33 | specified number of seconds may be searched to find captions. Note 34 | that searching for the caption line takes time, so providing the '-l' 35 | option to specify the line to use is faster. 36 | 37 | -l specify the line where caption data should be read, 38 | otherwise the first ${SEARCH_FIRST_X_SECONDS_FOR_CAPTION_LINE} lines will be searched. 39 | -t specify an amount of time in seconds for which to search 40 | for captions, ${SEARCH_FIRST_X_SECONDS_FOR_CAPTION_LINE} is used by default. By default the first 20 41 | seconds of the video is searched. 42 | 43 | Outcome 44 | will create MOVIE_FILE.scc and optionally MOVIE_FILE.srt or MOVIE_FILE.vtt 45 | 46 | Information about the scc format is available at http://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/ 47 | 48 | Dependencies: ffmpeg 4.3 or later 49 | 50 | -F (provide a custom ffmpeg path. ffprobe is presumed to neighbor ffmpeg) 51 | 52 | If an ffmpeg binary is provided with the '-F ' option that will be used, 53 | else if an ffmpeg binary exists within the same directory of sccyou, that will 54 | be used. Else it will use $(which ffmpeg). 55 | 56 | How tos: 57 | Playback movie with the scc file: 58 | ffplay MOVIE_FILE.mov -vf subtitles=MOVIE_FILE.scc 59 | EOF 60 | } 61 | 62 | _mkdir2(){ 63 | local DIR2MAKE="" 64 | while [ "${*}" != "" ] ; do 65 | DIR2MAKE="${1}" 66 | if [ ! -d "${DIR2MAKE}" ] ; then 67 | mkdir -p "${DIR2MAKE}" 68 | if [ "${?}" -ne 0 ]; then 69 | _report -wt "${0}: Can't create directory at ${DIR2MAKE}" 70 | exit 1 71 | fi 72 | fi 73 | shift 74 | done 75 | } 76 | 77 | _report(){ 78 | local RED="$(tput setaf 1)" # Red - For Warnings 79 | local GREEN="$(tput setaf 2)" # Green - For Declarations 80 | local BLUE="$(tput setaf 4)" # Blue - For Questions 81 | local NC="$(tput sgr0)" # No Color 82 | local COLOR="" 83 | local STARTMESSAGE="" 84 | local ENDMESSAGE="" 85 | local ECHOOPT="" 86 | local LOG_MESSAGE="" 87 | OPTIND=1 88 | while getopts ":qdwstn" OPT; do 89 | case "${OPT}" in 90 | q) COLOR="${BLUE}" ;; 91 | d) COLOR="${GREEN}" ;; 92 | w) COLOR="${RED}" ; LOG_MESSAGE="Y" ;; 93 | s) STARTMESSAGE+=([$(basename "${0}")] ) ;; 94 | t) STARTMESSAGE+=($(_get_iso8601) '- ' ) ;; 95 | n) ECHOOPT="-n" ;; 96 | esac 97 | done 98 | shift $(( OPTIND - 1 )) 99 | MESSAGE="${1}" 100 | echo "${ECHOOPT}" "${COLOR}${STARTMESSAGE[@]}${MESSAGE}${NC}" 101 | [ "${LOG_MESSAGE}" = "Y" ] 102 | } 103 | 104 | _hhmmssmmm_to_hhmmssff(){ 105 | tc="$1" 106 | echo "$tc" | awk -F "[:.]" '{ printf "%02i:%02i:%02i:%02i\n", $1, $2, $3, substr($4,1,3)*(30/1000) }' 107 | } 108 | 109 | _get_line(){ 110 | LINE_SELECT="$("${FFMPEG_PATH}" -i "${MOVIE}" -vf "readeia608=lp=1:spw=${SPW},metadata=mode=print:key=lavfi.readeia608.0.line" -t "${SEARCH_FIRST_X_SECONDS_FOR_CAPTION_LINE}" -f null - 2>&1 | grep -o "lavfi.readeia608.0.line=[0-9]*" | cut -d= -f2 | sort | uniq -c | sort -n -r | head -n 1 | awk '{print $2}')" 111 | } 112 | 113 | _get_cc(){ 114 | # this function tests for the presence of caption data on the specified line 115 | LINE="${1}" 116 | SPW=0.27 117 | AMOUNT_TO_REPORT=10 118 | CC_SELECT="$("${FFMPEG_PATH}" -i "${MOVIE}" -vf "format=rgb24,crop=iw:1:0:${LINE},readeia608=spw=${SPW},metadata=mode=print:key=lavfi.readeia608.0.cc" -t "${SEARCH_FIRST_X_SECONDS_FOR_CAPTION_LINE}" -f null - 2>&1 | grep -o "lavfi.readeia608.0.cc=0x[A-F0-9]*" | cut -d= -f2 | head -n "${AMOUNT_TO_REPORT}" | xargs)" 119 | } 120 | 121 | _get_caption_settings(){ 122 | # test ratio of width reserved for sync code detection at both 0.3 and 0.27 123 | SPW="0.30" 124 | if [[ -n "${LINE_SELECT_USER}" ]] ; then 125 | _get_cc "${LINE_SELECT_USER}" 126 | if [[ -z "${CC_SELECT}" ]] ; then 127 | SPW="0.27" 128 | _get_cc "${LINE_SELECT_USER}" 129 | fi 130 | if [[ -z "${CC_SELECT}" ]] ; then 131 | _report -w "Sadly no caption data is detected in the first ${SEARCH_FIRST_X_SECONDS_FOR_CAPTION_LINE} seconds of ${MOVIE} on line ${LINE_SELECT_USER}." 132 | _report -w "Try increasing the search time with '-t' or try another line with '-l'." 133 | break 134 | else 135 | LINE_SELECT="${LINE_SELECT_USER}" 136 | fi 137 | else 138 | _get_line "${MOVIE}" 139 | if [[ -z "${LINE_SELECT}" ]] ; then 140 | SPW="0.27" 141 | _get_line "${MOVIE}" 142 | fi 143 | if [[ -z "${LINE_SELECT}" ]] ; then 144 | _report -w "No caption data detected in the first ${SEARCH_FIRST_X_SECONDS_FOR_CAPTION_LINE} seconds of ${MOVIE}." 145 | _report -w "Try increasing search time with '-t'." 146 | break 147 | fi 148 | fi 149 | _report -d "Found captions in ${MOVIE}, using spw=${SPW} and line ${LINE_SELECT}." 150 | } 151 | 152 | _write_cc(){ 153 | CC="$1" 154 | if [[ "$START" == "Y" ]] ; then 155 | echo -n " ${CC}" >> "${SCC_OUT}" 156 | else 157 | echo -n " ${CC}" >> "${SCC_OUT}" 158 | fi 159 | START="N" 160 | } 161 | 162 | _write_new_cc_line(){ 163 | SCC_TIME="$(_hhmmssmmm_to_hhmmssff "${SECS}")" 164 | echo -e -n "\n\n${SCC_TIME}" >> "${SCC_OUT}" 165 | START="Y" 166 | } 167 | 168 | _start_scc(){ 169 | echo -n "Scenarist_SCC V1.0" > "${SCC_OUT}" 170 | } 171 | 172 | _end_scc(){ 173 | echo >> "${SCC_OUT}" 174 | } 175 | 176 | while getopts ":hswyo:l:t:vF:" OPT ; do 177 | case "${OPT}" in 178 | h) _usage ; exit 0 ;; 179 | s) SRT_OUTPUT="y" ;; 180 | w) VTT_OUTPUT="y" ;; 181 | y) FFMPEG_INPUT_OPTIONS+=(-y) ; OVERWRITE="sure" ;; 182 | o) OUTPUT_DIR="${OPTARG}" ;; 183 | l) LINE_SELECT_USER="${OPTARG}" ;; 184 | t) SEARCH_FIRST_X_SECONDS_FOR_CAPTION_LINE="${OPTARG}" ;; 185 | v) unset FFMPEG_INPUT_OPTIONS ; unset FFPROBE_INPUT_OPTIONS ;; 186 | F) FFMPEG_PATH="${OPTARG}" ;; 187 | *) echo "bad option -${OPTARG}" ; _usage ; exit 1 ;; 188 | esac 189 | done 190 | shift $(( OPTIND - 1 )) 191 | 192 | # if FFMPEG_PATH wasn't provided as an argument then see if one is installed 193 | if [[ -z "${FFMPEG_PATH}" ]] ; then 194 | FFMPEG_GUESS="${SCRIPTDIR}/ffmpeg" 195 | if [[ -x "${FFMPEG_GUESS}" ]] ; then 196 | FFMPEG_PATH="${FFMPEG_GUESS}" 197 | else 198 | FFMPEG_PATH="$(which ffmpeg)" 199 | fi 200 | fi 201 | FFPROBE_GUESS="$(dirname "${FFMPEG_PATH}")/ffprobe" 202 | if [[ -x "${FFPROBE_GUESS}" ]] ; then 203 | FFPROBE_PATH="${FFPROBE_GUESS}" 204 | else 205 | FFPROBE_PATH="$(which ffprobe)" 206 | fi 207 | 208 | FFMPEG_INPUT_OPTIONS+=(-hide_banner) 209 | FFPROBE_INPUT_OPTIONS+=(-hide_banner) 210 | 211 | if [[ "${*}" == "" ]] ; then 212 | _usage 213 | exit 214 | fi 215 | 216 | while [[ "${*}" != "" ]] ; do 217 | MOVIE="${1}" 218 | if [[ -n "${OUTPUT_DIR}" ]] ; then 219 | MOVIE_BN="$(basename "${MOVIE}")" 220 | SCC_OUT="${OUTPUT_DIR}/${MOVIE_BN%.*}.scc" 221 | SRT_OUT="${OUTPUT_DIR}/${MOVIE_BN%.*}.srt" 222 | VTT_OUT="${OUTPUT_DIR}/${MOVIE_BN%.*}.vtt" 223 | if [[ ! -d "${OUTPUT_DIR}" ]] ; then 224 | _mkdir2 "${OUTPUT_DIR}" 225 | fi 226 | else 227 | SCC_OUT="${MOVIE%.*}.scc" 228 | SRT_OUT="${MOVIE%.*}.srt" 229 | VTT_OUT="${MOVIE%.*}.vtt" 230 | fi 231 | shift 232 | 233 | if [[ "${OVERWRITE}" != "sure" ]] ; then 234 | if [[ -f "${SCC_OUT}" ]] ; then 235 | _report -w "${SCC_OUT} already exists" 236 | break 237 | fi 238 | if [[ -f "${SRT_OUT}" ]] && [[ "${SRT_OUTPUT}" == "y" ]] ; then 239 | _report -w "${SRT_OUT} already exists" 240 | break 241 | fi 242 | if [[ -f "${VTT_OUT}" ]] && [[ "${VTT_OUTPUT}" == "y" ]] ; then 243 | _report -w "${VTT_OUT} already exists" 244 | break 245 | fi 246 | fi 247 | 248 | _get_caption_settings "${MOVIE}" 249 | _start_scc 250 | 251 | _report -d "Working on ${MOVIE}, preview with:" 252 | _report -q " ffplay \"${MOVIE}\" -vf subtitles=\"${SCC_OUT}\"" 253 | 254 | PREV_CC="8080" 255 | CC_PRESENT=0 256 | while IFS="," read FRAME SECS CC_HEX ; do 257 | if [[ -n "${CC_HEX}" ]] ; then 258 | CC="${CC_HEX:2:4}" 259 | if { [[ "${CC}" != "8080" ]] && [[ "${PREV_CC}" == "8080" ]] ; } || { [[ "${CC}" == "9420" ]] && [[ "${PREV_CC}" != "9420" ]] ; } ; then 260 | _write_new_cc_line 261 | _write_cc "$CC" 262 | elif [[ "${CC}" != "8080" ]] ; then 263 | _write_cc "$CC" 264 | fi 265 | PREV_CC="$CC" 266 | if [[ "$CC_PRESENT" == 0 ]] ; then 267 | CC_PRESENT=1 268 | if [[ -n "${CC_LOG}" ]] ; then 269 | TRANSITION_TIME="$(_hhmmssmmm_to_hhmmssff "${SECS}")" 270 | _report -d "${CC_LOG} - ${TRANSITION_TIME}." 271 | fi 272 | TRANSITION_TIME="$(_hhmmssmmm_to_hhmmssff "${SECS}")" 273 | CC_LOG="Caption data ON: ${TRANSITION_TIME}" 274 | fi 275 | else 276 | if [[ "$CC_PRESENT" == 1 ]] ; then 277 | CC_PRESENT=0 278 | if [[ -n "${CC_LOG}" ]] ; then 279 | TRANSITION_TIME="$(_hhmmssmmm_to_hhmmssff "${SECS}")" 280 | _report -d "${CC_LOG} - ${TRANSITION_TIME}." 281 | fi 282 | TRANSITION_TIME="$(_hhmmssmmm_to_hhmmssff "${SECS}")" 283 | CC_LOG="Caption data OFF: ${TRANSITION_TIME}" 284 | fi 285 | PREV_CC="8080" 286 | fi 287 | PREV_SECS="$SECS" 288 | done < <("${FFPROBE_PATH}" "${FFPROBE_INPUT_OPTIONS[@]}" -sexagesimal -f lavfi -i movie="${MOVIE},format=rgb24,crop=iw:1:0:${LINE_SELECT},readeia608=lp=1:spw=${SPW}" -show_entries frame=best_effort_timestamp_time:frame_tags=lavfi.readeia608.0.cc -of csv) 289 | TRANSITION_TIME="$(_hhmmssmmm_to_hhmmssff "${PREV_SECS}")" 290 | _report -d "${CC_LOG} - ${TRANSITION_TIME}." 291 | _end_scc 292 | 293 | # Convert SCC to SRT if requested 294 | if [[ "${SRT_OUTPUT}" == "y" ]] ; then 295 | "${FFMPEG_PATH}" "${FFMPEG_INPUT_OPTIONS[@]}" -i "${SCC_OUT}" "${SRT_OUT}" 296 | fi 297 | 298 | # Convert SCC to WebVTT if requested 299 | if [[ "${VTT_OUTPUT}" == "y" ]] ; then 300 | "${FFMPEG_PATH}" "${FFMPEG_INPUT_OPTIONS[@]}" -i "${SCC_OUT}" "${VTT_OUT}" 301 | fi 302 | 303 | _report -d "Done with ${MOVIE}. We hope you like ${SCC_OUT}." 304 | done 305 | --------------------------------------------------------------------------------