123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679 |
- <?php
- class MediaInfo {
- public static function parse($string, $raw=false) {
- $t = new ParseManager($string);
- $t->parse();
- return $raw ? $t->output_raw() : $t->output();
- }
- }
-
- class ParseManager {
- protected $lines;
- protected $parsed_lines;
- protected $index;
- protected $parsers;
- protected $output;
- protected $available_parsers = array(
- 'general'=> 'GeneralSectionParser',
- 'video'=> 'VideoSectionParser',
- 'audio'=> 'AudioSectionParser',
- 'text'=> 'TextSectionParser',
- );
-
- const GENERIC_PARSER = 'generic_parser';
- const MEDIAINFO_START = 'general';
- public function __construct($string='') {
- $this->index = 0;
- $this->output = '';
- $this->parsed_lines = [];
- $this->set_string($string);
- $p = new SectionParser();
- $this->add_parser($p);
- }
-
- protected function set_string($string) {
- $string = trim($string);
- $string = static::strip_escaped_tags($string);
- $lines = preg_split("/\r\n|\n|\r/", $string);
- array_walk($lines,function(&$l) {
- $l=trim($l);
- });
- $this->lines = $lines;
- }
-
- protected function add_parser($p,$name='') {
- $p->set_lines($this->lines,$this->index);
- if (!$name) $name = static::GENERIC_PARSER;
- $this->parsers[$name][] = $p;
- }
-
- public function parse() {
- // get the next section
- while ($this->index < count($this->lines) &&
- !($line = $this->parsers[static::GENERIC_PARSER][0]->parse_line()));
- $section = SectionParser::section_name($line);
- $this->index--; // go back to line we just read
-
- // we can have multiple mediainfo files inside the block, so handle that case here
- if ($section == self::MEDIAINFO_START && isset($this->parsers[$section])) {
- $this->new_mediainfo();
- }
- if (isset($this->available_parsers[$section])){
- $parser = new $this->available_parsers[$section];
- $this->add_parser($parser,$section);
- // parse section using the parser
- while ($line = $parser->parse_line()) $this->parsed_lines[] = $line;
-
- $this->parsed_lines[] = '';
- }
- else {
- // skip until the next blank line or until the next general section
- while ($line = $this->parsers[static::GENERIC_PARSER][0]->parse_line()){
- $section = SectionParser::section_name($line);
- if ($section == self::MEDIAINFO_START) {
- $this->index--; // go back to line we just read
- break;
- }
- }
- }
-
- // keep iterating until the last line
- if ($this->index < count($this->lines)) {
- $this->parse();
- }
- }
-
- public function output($cummulative=true) {
- $string = implode("<br />\n",$this->parsed_lines);
- if (!isset($this->parsers['general'])) return $string;
-
- $midiv_start = '<div class="spoilerContainer hideContainer">
- <input type="button" class="spoilerButton" value="Show '.
- $this->parsers['general'][0]->filename.
- '" /><blockquote class="spoiler hidden">';
- $midiv_end = "</blockquote></div>";
-
- $output = '<table class="mediainfo"><tbody><tr><td>';
- $output .= $this->parsers['general'][0]->output();
- if (isset($this->parsers['video'])){
- $output .= '</td><td>';
- $output .= $this->parsers['video'][0]->output();
- }
- if (isset($this->parsers['audio'])){
- $output .= '</td><td>';
- $output .= '<table><caption>Audio</caption><tbody>';
- foreach($this->parsers['audio'] as $index => $ap) {
- $output .= sprintf('<tr><td>#%d: </td><td>%s</td></tr>',intval($index+1),$ap->output());
- }
- $output .= '</tbody></table>';
- }
- if (isset($this->parsers['text'])){
- // subtitles table will be printed below the previous table
- $output .= '<br />';
- $output .= '<table><caption>Subtitles</caption><tbody>';
- foreach($this->parsers['text'] as $index => $tp) {
- $output .= sprintf('<tr><td>#%d: </td><td>%s</td></tr>',intval($index+1),$tp->output());
- }
- $output .= '</tbody></table>';
- }
- $output .= '</td></tr></tbody></table><br />';
- $output = $midiv_start . $string . $midiv_end . $output;
- if ($cummulative) {
- $output = $this->output . $output;
- }
- return $output;
- }
-
- public function output_raw() {
- $output = [];
- $sections = ['general', 'video', 'audio', 'text'];
-
- foreach($sections as $section) {
- if (isset($this->parsers[$section])) {
- $output[$section] = [];
- foreach($this->parsers[$section] as $index => $parser) {
- $output[$section][$index] = $parser->output_raw();
- }
- }
- }
- return $output;
- }
-
- // strip escaped html tags
- // this is not done for security, just to beautify things (html should already be escaped)
- public static function strip_escaped_tags($string) {
- // use the php function first
- $string = strip_tags($string);
-
- $gt = '>|>|>';
- $lt = '<|<|<';
-
- // there is no opening tag, so don't go through the rest of the regexes
- if (!preg_match("($lt)",$string))
- return $string;
-
- $tag_match = "/(?:$lt)(?P<tag>(?:(?!$gt).)*)(?:$gt)/ims";
-
- // this should match and remove tags
- $string = preg_replace($tag_match,'',$string);
-
- return $string;
- }
-
- protected function new_mediainfo() {
- $this->output .= $this->output(false);
- $this->parsed_lines = [];
- foreach(array_keys($this->parsers) as $key) {
- if ($key != static::GENERIC_PARSER)
- unset($this->parsers[$key]);
- }
- }
-
- }
-
- class SectionParser {
- protected $lines;
- protected $index;
-
-
- public function __construct(&$lines=[],&$i=0) {
- $this->set_lines($lines,$i);
- }
- public function set_lines(&$lines=[],&$i=0){
- $this->lines = &$lines;
- $this->index = &$i;
- }
- // should always return the read line
- public function parse_line(){
- if (!isset($this->lines[$this->index])) {
- return null;
- }
- $line = $this->lines[$this->index++];
- $pair = static::property_value_pair($line);
- $this->handle_cases($pair['property'],$pair['value']);
- return $line;
- }
- public function output() {
-
- }
- protected function handle_cases($property, $value) {
-
- }
- public static function section_name($string) {
- if (!$string) return false;
- $mistart="/^(?:general$|unique id|complete name)/i";
- if (preg_match($mistart, $string)) return ParseManager::MEDIAINFO_START;
- $words = explode(' ',$string);
- return strtolower($words[0]);
- }
- public static function property_value_pair($string) {
- $pair = explode(":", $string, 2);
- return array('property'=>strtolower(trim($pair[0])),'value'=>trim($pair[1]??''));
- }
- public static function strip_path($string) { // remove filepath
- $string = str_replace("\\", "/", $string);
- $path_parts = pathinfo($string);
- return $path_parts['basename'];
- }
- public static function parse_size($string) {
- return str_replace(array('pixels', ' '), null, $string);
- }
- protected static function table_head($caption) {
- return "<table class='nobr'><caption>$caption</caption><tbody>";
- }
- protected static function table_row($property,$value) {
- if ($value)
- return "<tr><td>$property: </td><td>$value</td></tr>";
- return '';
- }
- protected static function table_tail(){
- return '</tbody></table>';
- }
-
- }
-
- class AudioSectionParser extends SectionParser {
- protected $audioformat;
- protected $audiobitrate;
- protected $audiochannels;
- protected $audiochannelpositions;
- protected $audiotitle;
- protected $audiolang;
- protected $audioprofile;
- protected $form_audioformat;
- protected $form_audiochannels;
-
- protected function handle_cases($property,$value) {
- switch ($property) {
- case "format":
- $this->audioformat = $value;
- break;
- case "bit rate":
- $this->audiobitrate = $value;
- break;
- case "channel(s)":
- $this->audiochannels = $value;
- break;
- case "channel positions":
- $this->audiochannelpositions = $value;
- break;
- case "title":
- $this->audiotitle = $value;
- break;
- case "language":
- $this->audiolang = $value;
- break;
- case "format profile":
- $this->audioprofile = $value;
- break;
- }
- }
- public function output() {
- $this->process_vars();
- $output = $this->audiolang . ' ' . $this->channels() . ' ' . $this->format();
- $output .= ($this->audiobitrate) ? " @ $this->audiobitrate" : '';
- $output .= ($this->audiotitle) ? " ($this->audiotitle)" : '';
- return $output;
- }
- public function output_raw() {
- $this->process_vars();
- $output = [];
- $properties = [
- 'audioformat', 'audiobitrate', 'audiochannels',
- 'audiochannelpositions', 'audiotitle', 'audiolang', 'audioprofile',
- 'form_audioformat', 'form_audiochannels'
- ];
- foreach($properties as $property) {
- if ($this->$property) $output[$property] = $this->$property;
- }
- return $output;
- }
- protected function process_vars() {
- $this->form_audioformat = $this->form_format();
- $this->form_audiochannels = $this->form_channels();
- }
- protected function format() {
- if (strtolower($this->audioformat) === 'mpeg audio') {
- switch (strtolower($this->audioprofile)) {
- case 'layer 3':
- return 'MP3';
- case 'layer 2':
- return 'MP2';
- case 'layer 1':
- return 'MP1';
- }
- }
- return $this->audioformat;
- }
- protected function form_format() {
- // Not implemented: Real Audio, DTS-HD
- switch (strtolower($this->format())) {
- case 'mp2':
- return 'MP2';
- case 'mp3':
- return 'MP3';
- case 'vorbis':
- return 'OGG';
- case 'aac':
- return 'AAC';
- case 'ac-3':
- return 'AC3';
- case 'truehd':
- return 'TrueHD';
- case 'dts':
- switch (strtolower($this->audioprofile)) {
- case 'es':
- return 'DTS-ES';
- case 'ma / core':
- return 'DTS-HD MA';
- default:
- return 'DTS';
- }
- case 'flac':
- return 'FLAC';
- case 'pcm':
- return 'PCM';
- case 'wma':
- return 'WMA';
- }
- }
- protected function channels() {
- if (isset($this->audiochannels)) {
- $chans = preg_replace('/^(\d).*$/', '$1', $this->audiochannels);
-
- if (isset($this->audiochannelpositions) && preg_match('/LFE/',
- $this->audiochannelpositions)) {
- $chans -= .9;
- } else {
- $chans = $chans . '.0';
- }
-
- return $chans . 'ch';
- }
- }
- protected function form_channels() {
- return preg_replace('/ch/', '', $this->channels());
- }
- }
-
- class GeneralSectionParser extends SectionParser {
- public $filename;
- protected $generalformat;
- protected $duration;
- protected $filesize;
- protected $form_codec;
- protected $form_releasegroup;
-
- protected function handle_cases($property,$value) {
- switch ($property) {
- case "complete name":
- // Remove autodetected urls
- $value = preg_replace('#\[autourl(?:=.+)?\](.+)\[/autourl\]#', '$1', $value);
- $this->filename = static::strip_path($value);
- $this->lines[$this->index-1] = "Complete name : " . $this->filename;
- $this->filename = substr($this->filename, 0, 150);
- break;
- case "format":
- $this->generalformat = $value;
- break;
- case "duration":
- $this->duration = $value;
- break;
- case "file size":
- $this->filesize = $value;
- break;
- }
- }
- public function output() {
- $this->process_vars();
- $output = static::table_head('General');
- $properties = [
- 'Container' => 'generalformat',
- 'Runtime' => 'duration',
- 'Size' => 'filesize'
- ];
- foreach($properties as $property => $value) {
- $output .= static::table_row($property,$this->$value);
- }
- $output .= static::table_tail();
- return $output;
- }
- public function output_raw() {
- $this->process_vars();
- $output = [];
- $properties = [
- 'filename', 'generalformat', 'duration', 'filesize', 'form_codec',
- 'form_releasegroup'
- ];
- foreach($properties as $property) {
- if ($this->$property) $output[$property] = $this->$property;
- }
- return $output;
- }
- protected function process_vars() {
- switch (strtolower($this->generalformat)) {
- case 'mpeg-ts':
- $this->form_codec = 'MPEG-TS';
- break;
- // We can't determine if it's DVD5 or DVD9, so don't guess.
- case 'mpeg-ps':
- $this->form_codec = '---';
- break;
- }
- $matches = [];
- preg_match('/(?:^|.*\\|\/)\[(.*?)\].*$/',
- $this->filename, $matches);
- $this->form_releasegroup = $matches ? $matches[1] : '';
- }
- }
-
- class TextSectionParser extends SectionParser {
- protected $title;
- protected $language;
- protected $format;
- protected $default;
- protected $processed_language;
- protected $form_format;
-
- protected function handle_cases($property,$value) {
- switch ($property) {
- case 'title':
- $this->title = $value;
- break;
- case 'language':
- $this->language = $value;
- break;
- case 'format':
- $this->format = $value;
- break;
- case 'default':
- $this->default = ($value == 'Yes');
- break;
- }
- }
- public function output() {
- $this->process_vars();
- $language = $this->processed_language;
- $output = "$language ($this->format)";
- if ($this->title) $output .= " ($this->title)";
- if ($this->default) $output .= ' (default)';
- return $output;
- }
- public function output_raw() {
- $this->process_vars();
- $output = [];
- $properties = [
- 'title', 'language', 'format', 'default', 'processed_language',
- 'form_format'
- ];
- foreach($properties as $property) {
- if ($this->$property) $output[$property] = $this->$property;
- }
- return $output;
- }
- protected function process_vars() {
- $this->processed_language = ($this->language) ?
- $this->language : 'Unknown';
- $this->form_format = 'Softsubbed';
- }
- }
-
- class VideoSectionParser extends SectionParser {
- protected $videoformat;
- protected $videoformatversion;
- protected $codec;
- protected $width;
- protected $height;
- protected $writinglibrary;
- protected $frameratemode;
- protected $framerate;
- protected $aspectratio;
- protected $bitrate;
- protected $bitratemode;
- protected $nominalbitrate;
- protected $bpp;
- protected $bitdepth;
- protected $processed_codec;
- protected $processed_resolution;
- protected $processed_framerate;
- protected $form_codec;
- protected $form_resolution;
-
- protected function handle_cases($property,$value) {
- switch ($property) {
- case "format":
- $this->videoformat = $value;
- break;
- case "format version":
- $this->videoformatversion = $value;
- break;
- case "codec id":
- $this->codec = strtolower($value);
- break;
- case "width":
- $this->width = static::parse_size($value);
- break;
- case "height":
- $this->height = static::parse_size($value);
- break;
- case "writing library":
- $this->writinglibrary = $value;
- break;
- case "frame rate mode":
- $this->frameratemode = $value;
- break;
- case "frame rate":
- // if variable this becomes Original frame rate
- $this->framerate = $value;
- break;
- case "display aspect ratio":
- $this->aspectratio = $value;
- break;
- case "bit rate":
- $this->bitrate = $value;
- break;
- case "bit rate mode":
- $this->bitratemode = $value;
- break;
- case "nominal bit rate":
- $this->nominalbitrate = $value;
- break;
- case "bits/(pixel*frame)":
- $this->bpp = $value;
- break;
- case 'bit depth':
- $this->bitdepth = $value;
- break;
- }
- }
- public function output() {
- $this->process_vars();
- $output = static::table_head('Video');
- $properties = [
- 'Codec' => 'processed_codec',
- 'Bit depth' => 'bitdepth',
- 'Resolution' => 'processed_resolution',
- 'Aspect ratio' => 'aspectratio',
- 'Frame rate' => 'processed_framerate',
- 'Bit rate' => 'bitrate',
- 'BPP' => 'bpp'
- ];
- foreach($properties as $property => $value) {
- $output .= static::table_row($property,$this->$value);
- }
- $output .= static::table_tail();
- return $output;
- }
- public function output_raw() {
- $this->process_vars();
- $output = [];
- $properties = [
- 'videoformat', 'videoformatversion', 'codec', 'width', 'height',
- 'writinglibrary', 'frameratemode', 'framerate', 'aspectratio',
- 'bitrate', 'bitratemode', 'nominalbitrate', 'bpp', 'bitdepth',
- 'processed_codec', 'processed_resolution', 'processed_framerate',
- 'form_codec', 'form_resolution'
- ];
- foreach($properties as $property) {
- if ($this->$property) $output[$property] = $this->$property;
- }
- return $output;
- }
- protected function process_vars() {
- $this->processed_codec = $this->compute_codec();
- $this->processed_resolution = ($this->width) ?
- $this->width . 'x' . $this->height : '';
- $this->processed_framerate = (strtolower($this->frameratemode) !=
- "constant" && $this->frameratemode) ?
- $this->frameratemode : $this->framerate;
- $this->form_codec = $this->compute_form_codec();
- $this->form_resolution = $this->compute_form_resolution();
- }
- protected function compute_codec() {
- switch (strtolower($this->videoformat)) {
- case "mpeg video":
- switch (strtolower($this->videoformatversion)) {
- case "version 2":
- return "MPEG-2";
- case "version 1":
- return "MPEG-1";
- }
- return $this->videoformat;
- }
-
- switch (strtolower($this->codec)) {
- case "div3":
- return "DivX 3";
- case "divx":
- case "dx50":
- return "DivX";
- case "xvid":
- return "XviD";
- case "x264":
- return "x264";
- }
-
- $chk = strtolower($this->codec);
- $wl = strtolower($this->writinglibrary);
- if (($chk === "v_mpeg4/iso/avc" || $chk === "avc1") && strpos($wl, "x264 core") === FALSE) {
- return "H264";
- } else if (($chk === "v_mpeg4/iso/avc" || $chk === "avc1") && strpos($wl, "x264 core") > -1) {
- return "x264";
- } else if (strtolower($this->videoformat) === "avc" && strpos($wl, "x264 core") === FALSE) {
- return "H264";
- }
-
- if (($chk === 'v_mpegh/iso/hevc') || ($wl === 'hevc'))
- return 'H265';
- }
- protected function compute_form_codec() {
- // Not implemented: DVD5, DVD9, WMV, Real Video
- // MPEG-TS set as GeneralSectionParser::$form_codec if found
- // MPEG-PS sets GeneralSectionParser::$form_codec to blank form value
- // so DVD5 or DVD9 is selected manually.
- $codec = $this->compute_codec();
- switch(strtolower($codec)) {
- case 'x264':
- case 'h264':
- return strtolower($this->bitdepth) == '10 bits' ?
- 'h264 10-bit' : 'h264';
- case 'h265':
- return 'h265';
- case 'xvid':
- return 'XviD';
- case 'divx':
- case 'divx 3':
- return 'DivX';
- case 'mpeg-1':
- return 'MPEG';
- case 'mpeg-2':
- return 'MPEG-2';
- }
- switch(strtolower($this->codec)) {
- case 'wmv3':
- return 'VC-1';
- case 'mp43':
- return 'MPEG-4 v3';
- }
- switch(strtolower($this->videoformat)) {
- case 'vc-1':
- return 'VC-1';
- case 's-mpeg 4 v3':
- return 'MPEG-4 v3';
- }
- }
- protected function compute_form_resolution() {
- global $Resolutions;
- $closest = null;
- if (isset($this->height)) {
- $resolutions = $Resolutions;
- foreach($resolutions as $resolution) {
- if (!isset($closest) || abs($this->height - $resolution) <
- abs($this->height - $closest)) {
- $closest = $resolution;
- }
- }
- }
- return $closest;
- }
- }
|