Skip to content

Instantly share code, notes, and snippets.

@deviationist
Last active January 22, 2018 03:20
Show Gist options
  • Save deviationist/afe6ea35a9f90ef7b3dcfa2ca1790fd9 to your computer and use it in GitHub Desktop.
Save deviationist/afe6ea35a9f90ef7b3dcfa2ca1790fd9 to your computer and use it in GitHub Desktop.
<?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