Skip to content

Commit

Permalink
Add support for closed captions.
Browse files Browse the repository at this point in the history
If a file matching the video filename but ending in ".vtt" (or with ".vtt" tacked onto the full video filename with extension) is in the same directory,
Channel Two will show closd captions for that video if the captions=true flag has been set in the schedule file or if you press 'c' while Channel Two is
open.

If you press 'c' to force captions on, they'll be shown for every video until you press 'c' again, at which point only videos that have been designated in
the schedule as having captions enabled will show captions.

Fixes #2.
  • Loading branch information
cfinke committed Feb 18, 2023
1 parent 55a3e0c commit 1979eb9
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 24 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,24 @@ then `good-morning.mp4` will play every day, even the 15th. It takes priority be

will cause Channel Two to play `good-morning.mp4` the first day at 7am, then `bad-morning.mp4` the second day, then `good-morning.mp4` the third day, etc.

## Closed Captions

If you have WebVTT caption files for your videos, you can force them to be used as closed captions by adding the `captions=true` flag to a scheduling block:

```
captions=true
0 9 * * * /path/to/play-me-with-captions.mp4
captions=false
0 11 * * * /path/to/no-captions-please.mp4
```

Channel Two will looks for a caption file in the same directory that is either the full filename plus ".vtt", or the filename with ".vtt" replacing the original file extension. For the example above, it would look for either `/path/to/play-me-with-captions.mp4.vtt` or `/path/to/play-me-with-captions.vtt`.

While a program is playing, you can press the "c" to force captions to be turned on if they are not already enabled.

Note that because of browser security policies, in order to use caption files, you must be running Channel Two through a Web server as described in the "Caveats" section below.

## Generating the JavaScript programming file

Any time you modify `schedule.txt`, you'll need to re-generate the JavaScript schedule file. Do this:
Expand Down
123 changes: 102 additions & 21 deletions channel-two.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ jQuery( function ( $ ) {

var chyronTimerId = null;

/**
* Should captions be turned on regardless of the setting in the schedule? Press C to enable.
* Note that there's no way to turn captions off if they've been enabled in the schedule file.
*/
var captionOverride = false;

/**
* Webpages can't autoplay video or go full-screen without some interaction from the user,
* so require a button to be clicked to kick things off.
Expand Down Expand Up @@ -472,9 +478,8 @@ jQuery( function ( $ ) {

saveLastPlay( cron, nextFileToPlay );

play( nextFileToPlay );

tv.data( 'cron', cron );
play( nextFileToPlay );

// If this cron pattern matches every minute, this content should end if something else should be run.
if ( cron.indexOf( '* ' ) === 0 ) {
Expand Down Expand Up @@ -521,9 +526,8 @@ jQuery( function ( $ ) {
if ( nextFileToPlay ) {
saveLastPlay( cron, nextFileToPlay );

play( nextFileToPlay );

tv.data( 'cron', cron );
play( nextFileToPlay );

// If this cron pattern matches every minute, this content should end if something else should be run.
if ( cron.indexOf( '* ' ) === 0 ) {
Expand Down Expand Up @@ -563,7 +567,11 @@ jQuery( function ( $ ) {
$( '#right-back' ).hide();

tv.removeAttr( 'src' );
tv.empty();

tv.attr( 'src', path );
addCaptions( path );
setCaptionStatus();

tvElement.load();

Expand Down Expand Up @@ -612,6 +620,49 @@ jQuery( function ( $ ) {
} );
}

function addCaptions( path ) {
if ( logLevel >= 1 ) console.log( "addCaptions(" + path + ")" );

tv.empty();

let captions = getCaptions( path );

if ( captions ) {
let track = $( '<track/>' )
.attr( 'kind', 'captions' )
.attr( 'src', captions )
.attr( 'default', 'default' );

tv.append( track );

if ( logLevel >= 1 ) console.log( "added captions" );
} else {
if ( logLevel >= 1 ) console.log( "There were no captions to add." );
}
}

function setCaptionStatus() {
let currentCron = tv.data( 'cron' );

if ( 'textTracks' in tvElement ) {
if ( logLevel >= 2 ) console.log( "There are " + tvElement.textTracks.length + " text tracks." );

for ( let i = 0; i < tvElement.textTracks.length; i++ ) {
if ( captionOverride || ( currentCron in programming.schedule && programming.schedule[ currentCron ].flags.captions ) ) {
if ( logLevel >= 2 ) console.log( "Enabling text track " + i );

tvElement.textTracks[i].mode = 'showing';
} else {
if ( logLevel >= 2 ) console.log( "Disabling text track " + i );

tvElement.textTracks[i].mode = 'hidden';
}
}
} else {
if ( logLevel >= 1 ) console.log( "This browser does not support textTracks." );
}
}

/**
* Given a cron pattern, find the next video that should be played.
* For a file or URL, it's the file or URL. For a directory, it's the video that comes alphabetically, or a random one if shuffle is enabled.
Expand Down Expand Up @@ -655,7 +706,7 @@ jQuery( function ( $ ) {
}
}

console.log( "Potential files for " + cron + ": ", potentialFiles );
if ( logLevel >= 2 ) console.log( "Potential files for " + cron + ": ", potentialFiles );

if ( programming.schedule[cron].flags.shuffle ) {
if ( logLevel >= 2 ) console.log( "shuffle is enabled" );
Expand Down Expand Up @@ -751,7 +802,7 @@ jQuery( function ( $ ) {
}
}

console.log( "Potential files for " + cron + ": ", potentialFiles );
if ( logLevel >= 2 ) console.log( "Potential files for " + cron + ": ", potentialFiles );

if ( programming.schedule[cron].flags.shuffle ) {
if ( logLevel >= 2 ) console.log( "shuffle is enabled" );
Expand Down Expand Up @@ -893,6 +944,27 @@ jQuery( function ( $ ) {
return false;
}

/**
* If a caption file is available for a file, return its path.
*
* @return int|bool The duration, or false if not available.
*/
function getCaptions( filePath ) {
if ( logLevel >= 2 ) console.log( "getCaptions(" + filePath + ")" );

if ( filePath in programming.content_index ) {
if ( 'captions' in programming.content_index[ filePath ] ) {
return programming.content_index[ filePath ].captions;
}
} else if ( filePath in programming.ad_index ) {
if ( 'captions' in programming.ad_index[ filePath ] ) {
return programming.ad_index[ filePath ].captions;
}
}

return false;
}

/**
* Save the duration of a file that didn't have a stored duration in programming.js
*
Expand Down Expand Up @@ -1078,33 +1150,42 @@ jQuery( function ( $ ) {
}

let currentCron = tv.data( 'cron' );
let secondsLeft = secondsLeftInCurrentProgram();

switch ( e.which ) {
case 37:
// Left arrow
// Go to the previous episode that should have played, resuming with the same amount of time left in the current episode.
let previousContent = getPreviousContentFromCron( currentCron );
case 39:
// Left or right arrow
let secondsLeft = secondsLeftInCurrentProgram();

programmingQueue.push( {
src: previousContent + '#t=' + ( -1 * secondsLeft ),
cron : currentCron
} );
let switchToThisContent;

queueNextProgramming();
break;
case 39:
// Right arrow
// Go to the next episode that should play, resuming with the same amount of time left in the current episode.
let nextContent = getNextContentFromCron( currentCron );
if ( e.which == 37 ) {
// Go to the previous episode that should have played, resuming with the same amount of time left in the current episode.
switchToThisContent = getPreviousContentFromCron( currentCron );
} else {
// 39: right arrow
// Go to the next episode that should play, resuming with the same amount of time left in the current episode.

switchToThisContent = getNextContentFromCron( currentCron );
}

programmingQueue.push( {
src: nextContent + '#t=' + ( -1 * secondsLeft ),
src: switchToThisContent + '#t=' + ( -1 * secondsLeft ),
cron : currentCron
} );

queueNextProgramming();
break;
case 67:
// c for captions
if ( logLevel >= 2 ) console.log( "c keyup" );

captionOverride = ! captionOverride;
setCaptionStatus();
break;
default:
if ( logLevel >= 2 ) console.log( e.which );
break;
}
} );
} );
20 changes: 17 additions & 3 deletions parse-schedule.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
$flags = array(
'ads' => false,
'shuffle' => false,
'captions' => false,
);

$existing_programming = false;
Expand Down Expand Up @@ -95,7 +96,7 @@ function existing_duration( $path ) {
}


$valid_flags = array( 'ads', 'shuffle' );
$valid_flags = array( 'ads', 'shuffle', 'captions', );

if ( preg_match( '/^(' . join( '|', $valid_flags ) . ')=/', $line ) ) {
// Variable.
Expand All @@ -117,10 +118,11 @@ function existing_duration( $path ) {
}
break;
case 'shuffle':
case 'captions':
if ( strtolower( $parts[1] ) == 'true' ) {
$flags['shuffle'] = true;
$flags[ $parts[0] ] = true;
} else {
$flags['shuffle'] = false;
$flags[ $parts[0] ] = false;
}
break;
}
Expand Down Expand Up @@ -232,6 +234,18 @@ function index_file( $file_path, &$index ) {
$content_data->duration = $duration;
}

$possible_caption_files = array(
$file_path . ".vtt",
preg_replace( '/\.[^\.]+$/', '.vtt', $file_path ),
);

foreach ( $possible_caption_files as $possible_caption_file ) {
if ( file_exists( $possible_caption_file ) ) {
$content_data->captions = substr( $possible_caption_file, max( 0, strlen( $base ) - 1 ) );
break;
}
}

$index[ substr( $file_path, max( 0, strlen( $base ) - 1 ) ) ] = $content_data;
}
}
Expand Down

0 comments on commit 1979eb9

Please sign in to comment.