I’ve been messing around lately with various HTML5 demos (like popcorn.js) which add interactivity to online audio and video. One great example of these is Happyworm’s Hyperaudio, which uses the transcript of an audio file to link to time in the audio, and various other cool functionality. One frustrating part of using these libraries is getting subtitles or a transcript into a useable format. I’ve written the function below (which I’ve just rewritten in PHP from the Hyperaudio javascript “parseSRT” function) trying to make it easier to get subtitles in a useable format. I was looking to make some improvements to this before posting, but having no time, just posted this (kind of ugly) script. Suggestions for improvements are always welcome… you can test converting your own SRT file on this Demo SRT Converter form.
<?php
// subtitles transform
// get an input .SRT file and output new format
function parseSRT($input) {
//remove blank lines from input
$input = removeEmptyLines($input);
//create final output array
$srt_array = array();
$lines = preg_split ('/$\R?^/m', $input);
$output = "";
$i = 0;
foreach($lines as $line){
$line = strip_tags(trim($line));
//is the current line a sequencially line number
is_numeric($line) ? $is_line_num = true : $is_line_num = false;
if($is_line_num){
$line_number = (int)$line;
//set the array with line nums
$srt_array[] = $line_number;
} else{
//not a numbered line
//is the current line the SRT time range
if(preg_match('/(\d+):(\d+):(\d+),(\d+) --> (\d+):(\d+):(\d+),(\d+)/', trim($line), $match)){
$is_line_range = true;
//get the begin and end in thousands of seconds HH:MM:SS,MMM
$begin = (intval($match[1]) * 3600000) + (intval($match[2]) * 60000) + intval($match[3] * 1000) + intval($match[4]);
$end = (intval($match[5]) * 3600000) + (intval($match[6]) * 60000) + intval($match[7] * 1000) + intval($match[8]);
$total_time = $end - $begin;
} else {
//the text line
//get the num chars in line (include whitespace)
$line_length = strlen($line);
//get the time per character
if($total_time){
$time_per_char = round($total_time / $line_length, 0);
//split the line into words
$words = explode(" ", $line);
$num_words = count($words);
//get the amount of time for each word
$word_count = 0;
foreach($words as $word){
$word_length = strlen($word);
if($word_count == 0){
//set default on first word
$word_time = 0;
}
//$word_time is addative so we also need $current_word_time
//to track the current iteration an zero on each loop
$current_word_time = 0;
$chars = str_split($word);
$char_count = 0;
foreach($chars as $char){
//try to improve the accuracy by giving
//vowels a weight of 1.5x and commas a 2x weight
//and adding an extra "time per character" for spaces
if($char_count == (count($chars) - 1)){
//add a space to the last letter
$space = $time_per_char;
$char_count = 0;
} else {
$space = 0;
}
if($char == "," || $char == "."){
$word_time = $word_time + ($time_per_char * 1.5) + $space;
$current_word_time = $current_word_time + ($time_per_char * 1.5) + $space;
} elseif(preg_match_all('/[aeiou]/i',$char,$matches)) {
$word_time = $word_time + ($time_per_char * 1.25) + $space;
$current_word_time = $current_word_time + ($time_per_char * 1.25) + $space;
} else {
$word_time = $word_time + $time_per_char + $space;
$current_word_time = $current_word_time + $time_per_char + $space;
}
//round
$word_time = round($word_time);
$current_word_time = round($current_word_time);
//get tbe begining time of the word
if($word_count == 0){
//the first word
$word_begin = $begin;
} elseif($word_count == $num_words - 1){
//the last word, set to "end" current begin time is greater
//than the end of the line
$word_begin = ($begin + $word_time > $end - $current_word_time) ? $begin + $word_time: $end - $current_word_time;
} else {
$word_begin = $begin + $word_time;
}
//get the end time of the word
$word_end = $word_begin + $word_time;
$char_count++;
} //end foreach letter
//set the current lines array and add to the final output array
$word_array = array( word => stripslashes($word),
word_length => $word_length,
word_begin => $word_begin,
word_end => $word_end,
word_time => $word_time,
word_class => ereg_replace("[^A-Za-z0-9]", "", $word )
);
//add the word array to the line
$line_array[$word_count] = $word_array;
//increment word count
$word_count++;
} //end foreach word
//add the line to the final output
$srt_array[$line_number] = $line_array;
//reset line array
$line_array = array();
}
}
}
$i++;
//clear temp vars
$output_line = "";
}
return $srt_array;
}
function removeEmptyLines($string){
return preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $string);
}
function outputSRTArray($srt_array,$format,$options) {
switch ($format)
{
case "hyperaudio":
foreach($srt_array as $line){
if(is_array($line)){
//add paragraph tag
if($options['ptags']){
echo '<p>'. PHP_EOL;
}
foreach($line as $key => $val){
echo '<span m="' . $val['word_begin'] . '" oval="' . $val['word_class'] . '">' . $val['word'] . '</span>' . PHP_EOL;
}
//end paragraph tag
if($options['ptags']){
echo '</p>' . PHP_EOL;
}
}
}
break;
case "json":
echo json_encode($srt_array);
break;
}
return true;
}
?>
Here’s an example of how the function could be used to provide subtitles for varies uses. In this example, I’m grabbing an .SRT file from the crowdsourcing subtitling site Universal Subtitles. Using this completely subtitled video, I’m able to use the SRT returned by UniSubs API, run it through the above function and output the data as JSON.
<?php
//include the subtitles transform function
include('srt-converter.php');
// Universal Subtitles API Key
$api_key = '';
//REST API request to retreive subtitles
$video_url = 'http://www.youtube.com/watch?v=_cUdFx-Y8yI';
$url = "http://www.universalsubtitles.org/api/1.0/subtitles/";
$url .= "?video_url=" . urlencode($video_url);
//$url .="&amp;amp;language_id=en";
$url .= "&amp;amp;sformat=srt";
$fetched = file_get_contents($url);
// case error
if(!$fetched){
print("Error, content fetched = ".$fetched);
} else {
//SRT returned from UniSubs
$data = $fetched;
$format = 'json';
//output json
outputSRTArray(parseSRT($data),$format,$options);
}
?>
Ideally, the Universal Subtitles API could return JSON by itself. (I’m not sure if it doesn’t already,) but this could easily be made into a dynamic script to provide subtitles in various different formats to provide the content for your HTML5 audio/video projects. (For example, just changing the header of the example above would give you a valid JSON file.)
As always, comments and suggestions for improvement are always appreciated. Here’s the Demo form to convert a SRT file into HTML.







