Last active
January 22, 2018 03:20
-
-
Save deviationist/afe6ea35a9f90ef7b3dcfa2ca1790fd9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Class NRK_Subtitle_Downloader | |
* | |
* This is a simple PHP script to help download subtitle files from NRK TV. | |
* The subtitle files are in VTT-format which is supported by Plex. | |
* | |
* Usage: | |
* php nrk-subtitle-downloader.php URL1,URL2,URL3 folder_path NOR | |
* | |
* URLs can be give like this: | |
* https://tv.nrk.no/serie/broen/KMTE41004109/sesong-1/episode-1 | |
* KMTE41004109/sesong-1/episode-1 | |
*/ | |
class NRK_Subtitle_Downloader { | |
/** @var array Array of URLs */ | |
private $urls = []; | |
/** @var string Language string */ | |
private $language; | |
/** @var string */ | |
private $default_language = 'NOR'; | |
/** @var Path to folder for subtitle files */ | |
private $folder_path; | |
/** @var class Colors class instance */ | |
private $colors; | |
/** @var float Start time */ | |
private $startTime; | |
/** | |
* NRK_Subtitle_Downloader constructor. | |
* | |
* @param bool $url_string | |
* @param null $folder_path | |
* @param null $language | |
*/ | |
public function __construct( $url_string = false, $folder_path = null, $language = null ) { | |
// Check if folder path is specified | |
$this->detectFolderPath( $folder_path ); | |
// Check if language is specified | |
$this->detectLangauge( $language ); | |
// Check if we should run on instantiation | |
if ( $url_string ) { | |
// Fetch subtitles | |
$this->fetch( $url_string ); | |
} | |
} | |
/** | |
* Check if langauge is set. | |
* | |
* @param $language | |
*/ | |
private function detectLangauge( $language ) { | |
// Check if language is passed | |
if ( $language ) { | |
// Set language | |
$this->setLanguage( $language ); | |
} else { | |
// Set default language | |
$this->setLanguage( $this->default_language ); | |
} | |
} | |
/** | |
* Check if folder path is passed. | |
* | |
* @param $folder_path | |
*/ | |
private function detectFolderPath( $folder_path ) { | |
// Check if folder path is passed | |
if ( $folder_path ) { | |
// Set folder path | |
$this->setFolder( $folder_path ); | |
} | |
} | |
/** | |
* Set language. | |
* | |
* @param $language | |
*/ | |
public function setLanguage( $language ) { | |
$this->language = mb_strtoupper( $language ); | |
} | |
/** | |
* Set folder to store files in. | |
* | |
* @param $path | |
*/ | |
public function setFolder( $path ) { | |
$this->folder_path = rtrim( $path, '/' ) . '/'; | |
} | |
/** | |
* Check if URL string contains multiple URLs. | |
* | |
* @param $url_string | |
* | |
* @return bool | |
*/ | |
private function isMultipleURL( $url_string ) { | |
// Look for multiple URLs | |
$http_count = substr_count( $url_string, 'http' ); | |
if ( $http_count > 1 ) { | |
return true; | |
} | |
// Look for separator comma | |
$comma_count = substr_count( $url_string, ',' ); | |
if ( $comma_count > 0 ) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Parse URL string. | |
* | |
* @param $url_string | |
*/ | |
private function parseUrlString( $url_string ) { | |
// Parse one or multiple URLs | |
if ( $this->isMultipleURL( $url_string ) ) { | |
$this->urls = explode( ',', $url_string ); | |
} else { | |
$this->urls[] = $url_string; | |
} | |
// Remove empty ones | |
$this->urls = array_filter( $this->urls, function( $value ) { | |
return ! empty( trim( $value ) ); | |
} ); | |
// Remove any leading or trailing whitespace | |
$this->urls = array_map( function( $value ) { | |
$value = trim( $value ); | |
$value = rtrim( $value, '/' ); | |
return $value; | |
}, $this->urls ); | |
} | |
/** | |
* Ensure that destination folder exists. | |
*/ | |
private function ensureFolder() { | |
if ( $this->folder_path && ! file_exists( $this->folder_path ) ) { | |
mkdir( $this->folder_path ); | |
} | |
} | |
/** | |
* Set start time. | |
*/ | |
private function startTime() { | |
$this->startTime = microtime(true); | |
} | |
/** | |
* Calculate execution time from start time. | |
* | |
* @return bool|string | |
*/ | |
private function getExecutionTime() { | |
if ( ! $this->startTime ) return false; | |
$endTime = microtime(true); | |
return number_format( $endTime - $this->startTime, 2 ); | |
} | |
/** | |
* Initialized fetch process. | |
* | |
* @param $url_string | |
*/ | |
public function fetch( $url_string ) { | |
// Set start time | |
$this->startTime(); | |
// Create new Colors class | |
$this->colors = new Colors(); | |
// Ensure that destination folder exists | |
$this->ensureFolder(); | |
// Parse URL string | |
$this->parseUrlString( $url_string ); | |
// Fetch subtitle files by array | |
$this->fetchByArray( $this->urls ); | |
} | |
/** | |
* Fetch subtitle files by array. | |
* | |
* @param $array | |
*/ | |
public function fetchByArray( $array ) { | |
foreach ( $array as $raw_url ) { | |
// Parse URL to object | |
$clip = new NRK_Subtitle_Clip_Info( $raw_url, $this->language ); | |
if ( ! $clip->valid() ) { | |
$this->fail( $raw_url ); | |
continue; | |
} | |
// Do remote fetch | |
$content = $this->fetchSubtitleFile( $clip->url ); | |
if ( ! $content ) { | |
$this->fail( $clip->url, $clip->base_filename ); | |
continue; | |
} | |
// Save subtitle file | |
if ( ! $this->saveFile( $clip->filename, $content ) ) { | |
$this->fail( $clip->url, $clip->base_filename ); | |
continue; | |
} | |
$this->success( $clip->url, $clip->base_filename ); | |
} | |
$this->done(); | |
} | |
/** | |
* Print completed message. | |
*/ | |
private function done() { | |
$executionTime = $this->getExecutionTime(); | |
echo $this->colors->getColoredString( sprintf( 'Process completed in %s seconds.', $executionTime ), 'green', null ) . "\n"; | |
} | |
/** | |
* Print success message. | |
* | |
* @param $url | |
* @param $identifier | |
*/ | |
private function success( $url, $identifier ) { | |
echo $this->colors->getColoredString( sprintf( 'Successfully fetched subtitles from: %s (%s)', $url, $identifier ), 'green', null ) . "\n"; | |
} | |
/** | |
* Print failure message. | |
* | |
* @param $url | |
* @param $identifier | |
*/ | |
private function fail( $url, $identifier = false ) { | |
$string = sprintf( 'Could not fetch subtitles from: %s', $url ); | |
if ( $identifier ) { | |
$string .= ' (' . $identifier . ')'; | |
} | |
echo $this->colors->getColoredString( $string, 'red', null ) . "\n"; | |
} | |
/** | |
* Fetch remote file. | |
* | |
* @param $url | |
* | |
* @return bool|string | |
*/ | |
private function fetchSubtitleFile( $url ) { | |
$curl = curl_init(); | |
curl_setopt( $curl, CURLOPT_URL, $url ); | |
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true ); | |
curl_setopt( $curl, CURLOPT_HEADER, true ); | |
$response = curl_exec( $curl ); | |
$http_status_code = curl_getinfo( $curl, CURLINFO_HTTP_CODE ); | |
return ( ( ! empty( $response ) && $http_status_code === 200 ) ? $response : false ); | |
} | |
/** | |
* Store subtitle file. | |
* | |
* @param $filename | |
* @param $content | |
* | |
* @return bool|int | |
*/ | |
private function saveFile( $filename, $content ) { | |
return file_put_contents( $this->folder_path . $filename, $content ); | |
} | |
} | |
/** | |
* Class NRK_Subtitle_Clip_Info | |
*/ | |
class NRK_Subtitle_Clip_Info { | |
/** @var Assorted properties */ | |
private $full, $prefix, $number_1, $number_2, $number_3, $text_1, $season, $text_2, $episode, $id, $lang, $filename, $url, $base_filename; | |
/** @var bool Valid indicator */ | |
private $valid; | |
/** @var string RegEx pattern for video clip information */ | |
private $url_pattern = '/([A-Z]{4})([0-9]{2})([0-9]{2})([0-9]{4})\/([a-z-]+)([0-9]+)\/([a-z-]+)([0-9]+)$/'; | |
/** @var string Subtitle URL template for NRK */ | |
private $url_template = "https://undertekst.nrk.no/prod/%s%s/%s/%s/%s/%s.vtt"; | |
/** @var string Subtitle file template */ | |
private $subtitle_file_template = 'S%sE%s'; | |
/** | |
* NRK_Subtitle_Clip_Info constructor. | |
* | |
* @param $url | |
*/ | |
public function __construct( $url, $lang ) { | |
$this->lang = $lang; | |
$this->valid = $this->readUrl( trim( $url ) ); | |
return $this->valid; | |
} | |
/** | |
* Check if valid. | |
*/ | |
public function valid() { | |
return $this->valid; | |
} | |
/** | |
* Generate subtitle URL. | |
* | |
* @return string | |
*/ | |
private function generateSubtitleURL() { | |
return sprintf( | |
$this->url_template, | |
$this->prefix, | |
$this->number_1, | |
$this->number_2, | |
$this->id, | |
$this->lang, | |
$this->id | |
); | |
} | |
/** | |
* Populate class properties with data extracted from URL. | |
* | |
* @param $matches | |
*/ | |
private function populateProperties( $matches ) { | |
// Extract variables | |
list( $this->full, $this->prefix, $this->number_1, $this->number_2, $this->number_3, $this->text_1, $this->season, $this->text_2, $this->episode ) = $matches; | |
} | |
/** | |
* Read unparsed URL. | |
* | |
* @param $url | |
* | |
* @return bool | |
*/ | |
private function readUrl( $url ) { | |
// Parse URL into array | |
preg_match( $this->url_pattern, $url, $matches ); | |
// Bail if extraction failed | |
if ( count( $matches ) != 9 ) { | |
return false; | |
} | |
// Populate properties | |
$this->populateProperties( $matches ); | |
// Build full ID | |
$this->id = $this->prefix . $this->number_1 . $this->number_2 . $this->number_3; | |
// Generate subtitle URL | |
$this->url = $this->generateSubtitleURL(); | |
// Generate subtitle filename | |
$this->filename = $this->getSubtitleFilename(); | |
return true; | |
} | |
/** | |
* Get file extension from remote URL. | |
* | |
* @return mixed | |
*/ | |
private function getFileExtension() { | |
return pathinfo( $this->url, PATHINFO_EXTENSION ); | |
} | |
/** | |
* Generate filename for subtitle file. | |
* | |
* @return string | |
*/ | |
private function getSubtitleFilename() { | |
// Add padding to season and episode number | |
$season = str_pad( $this->season, 2, '0', STR_PAD_LEFT ); | |
$episode = str_pad( $this->episode, 2, '0', STR_PAD_LEFT ); | |
$this->base_filename = sprintf( $this->subtitle_file_template, $season, $episode ); | |
return $this->base_filename . '.' . $this->getFileExtension(); | |
} | |
/** | |
* Get private property value. | |
* | |
* @param $name | |
* | |
* @return mixed | |
*/ | |
public function __get( $name ) { | |
return $this->{ $name }; | |
} | |
} | |
/** | |
* Class Colors | |
* Fecthed from: https://www.if-not-true-then-false.com/2010/php-class-for-coloring-php-command-line-cli-scripts-output-php-output-colorizing-using-bash-shell-colors/ | |
*/ | |
class Colors { | |
private $foreground_colors = array(); | |
private $background_colors = array(); | |
public function __construct() { | |
// Set up shell colors | |
$this->foreground_colors['black'] = '0;30'; | |
$this->foreground_colors['dark_gray'] = '1;30'; | |
$this->foreground_colors['blue'] = '0;34'; | |
$this->foreground_colors['light_blue'] = '1;34'; | |
$this->foreground_colors['green'] = '0;32'; | |
$this->foreground_colors['light_green'] = '1;32'; | |
$this->foreground_colors['cyan'] = '0;36'; | |
$this->foreground_colors['light_cyan'] = '1;36'; | |
$this->foreground_colors['red'] = '0;31'; | |
$this->foreground_colors['light_red'] = '1;31'; | |
$this->foreground_colors['purple'] = '0;35'; | |
$this->foreground_colors['light_purple'] = '1;35'; | |
$this->foreground_colors['brown'] = '0;33'; | |
$this->foreground_colors['yellow'] = '1;33'; | |
$this->foreground_colors['light_gray'] = '0;37'; | |
$this->foreground_colors['white'] = '1;37'; | |
$this->background_colors['black'] = '40'; | |
$this->background_colors['red'] = '41'; | |
$this->background_colors['green'] = '42'; | |
$this->background_colors['yellow'] = '43'; | |
$this->background_colors['blue'] = '44'; | |
$this->background_colors['magenta'] = '45'; | |
$this->background_colors['cyan'] = '46'; | |
$this->background_colors['light_gray'] = '47'; | |
} | |
// Returns colored string | |
public function getColoredString($string, $foreground_color = null, $background_color = null) { | |
$colored_string = ""; | |
// Check if given foreground color found | |
if (isset($this->foreground_colors[$foreground_color])) { | |
$colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . "m"; | |
} | |
// Check if given background color found | |
if (isset($this->background_colors[$background_color])) { | |
$colored_string .= "\033[" . $this->background_colors[$background_color] . "m"; | |
} | |
// Add string and end coloring | |
$colored_string .= $string . "\033[0m"; | |
return $colored_string; | |
} | |
// Returns all foreground color names | |
public function getForegroundColors() { | |
return array_keys($this->foreground_colors); | |
} | |
// Returns all background color names | |
public function getBackgroundColors() { | |
return array_keys($this->background_colors); | |
} | |
} | |
// CLI handling | |
$url = ( isset( $argv[1] ) ? $argv[1] : false ); | |
$destination = ( isset( $argv[2] ) ? $argv[2] : null ); | |
$language = ( isset( $argv[3] ) ? $argv[3] : null ); | |
new NRK_Subtitle_Downloader( $url, $destination, $language ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment