: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// MSOFFICE - data - ZIP compressed data
'pattern' => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
'mime_type' => 'application/octet-stream',
'pattern' => '^(d8\\:announce|d7\\:comment)',
'mime_type' => 'application/x-bittorrent',
// CUE - data - CUEsheet (index to single-file disc images)
'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
'mime_type' => 'application/octet-stream',
* @param string $filedata
* @param string $filename
public function GetFileFormat(&$filedata, $filename='') {
// this function will determine the format of a file based on usually
// the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
// and in the case of ISO CD image, 6 bytes offset 32kb from the start
// Identify file format - loop through $format_info and detect with reg expr
foreach ($this->GetFileFormatArray() as $format_name => $info) {
// The /s switch on preg_match() forces preg_match() NOT to treat
// newline (0x0A) characters as special chars but do a binary match
if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) {
$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
if (preg_match('#\\.mp[123a]$#i', $filename)) {
// Too many mp3 encoders on the market put garbage in front of mpeg files
// use assume format on these if format detection failed
$GetFileFormatArray = $this->GetFileFormatArray();
$info = $GetFileFormatArray['mp3'];
$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
} elseif (preg_match('#\\.mp[cp\\+]$#i', $filename) && preg_match('#[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]#s', $filedata)) {
// old-format (SV4-SV6) Musepack header that has a very loose pattern match and could falsely match other data (e.g. corrupt mp3)
// only enable this pattern check if the filename ends in .mpc/mpp/mp+
$GetFileFormatArray = $this->GetFileFormatArray();
$info = $GetFileFormatArray['mpc'];
$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
} elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
// there's not really a useful consistent "magic" at the beginning of .cue files to identify them
// so until I think of something better, just go by filename if all other format checks fail
// and verify there's at least one instance of "TRACK xx AUDIO" in the file
$GetFileFormatArray = $this->GetFileFormatArray();
$info = $GetFileFormatArray['cue'];
$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
* Converts array to $encoding charset from $this->encoding.
* @param string $encoding
public function CharConvert(&$array, $encoding) {
// identical encoding - end here
if ($encoding == $this->encoding) {
foreach ($array as $key => $value) {
$this->CharConvert($array[$key], $encoding);
elseif (is_string($value)) {
$array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value));
public function HandleAllTags() {
// key name => array (tag name, character encoding)
'asf' => array('asf' , 'UTF-16LE'),
'midi' => array('midi' , 'ISO-8859-1'),
'nsv' => array('nsv' , 'ISO-8859-1'),
'ogg' => array('vorbiscomment' , 'UTF-8'),
'png' => array('png' , 'UTF-8'),
'tiff' => array('tiff' , 'ISO-8859-1'),
'quicktime' => array('quicktime' , 'UTF-8'),
'real' => array('real' , 'ISO-8859-1'),
'vqf' => array('vqf' , 'ISO-8859-1'),
'zip' => array('zip' , 'ISO-8859-1'),
'riff' => array('riff' , 'ISO-8859-1'),
'lyrics3' => array('lyrics3' , 'ISO-8859-1'),
'id3v1' => array('id3v1' , $this->encoding_id3v1),
'id3v2' => array('id3v2' , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8
'ape' => array('ape' , 'UTF-8'),
'cue' => array('cue' , 'ISO-8859-1'),
'matroska' => array('matroska' , 'UTF-8'),
'flac' => array('vorbiscomment' , 'UTF-8'),
'divxtag' => array('divx' , 'ISO-8859-1'),
'iptc' => array('iptc' , 'ISO-8859-1'),
'dsdiff' => array('dsdiff' , 'ISO-8859-1'),
// loop through comments array
foreach ($tags as $comment_name => $tagname_encoding_array) {
list($tag_name, $encoding) = $tagname_encoding_array;
// fill in default encoding type if not already present
if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) {
$this->info[$comment_name]['encoding'] = $encoding;
// copy comments if key name set
if (!empty($this->info[$comment_name]['comments'])) {
foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) {
foreach ($valuearray as $key => $value) {
$value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
if (isset($value) && $value !== "") {
$this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
$this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value;
if ($tag_key == 'picture') {
// pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere
unset($this->info[$comment_name]['comments'][$tag_key]);
if (!isset($this->info['tags'][$tag_name])) {
// comments are set but contain nothing but empty strings, so skip
$this->CharConvert($this->info['tags'][$tag_name], $this->info[$comment_name]['encoding']); // only copy gets converted!
if ($this->option_tags_html) {
foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
if ($tag_key == 'picture') {
// Do not to try to convert binary picture data to HTML
// https://github.com/JamesHeinrich/getID3/issues/178
$this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']);
// pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere
if (!empty($this->info['tags'])) {
$unset_keys = array('tags', 'tags_html');
foreach ($this->info['tags'] as $tagtype => $tagarray) {
foreach ($tagarray as $tagname => $tagdata) {
if ($tagname == 'picture') {
foreach ($tagdata as $key => $tagarray) {
$this->info['comments']['picture'][] = $tagarray;
if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
if (isset($this->info['tags'][$tagtype][$tagname][$key])) {
unset($this->info['tags'][$tagtype][$tagname][$key]);
if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) {
unset($this->info['tags_html'][$tagtype][$tagname][$key]);
foreach ($unset_keys as $unset_key) {
// remove possible empty keys from (e.g. [tags][id3v2][picture])
if (empty($this->info[$unset_key][$tagtype]['picture'])) {
unset($this->info[$unset_key][$tagtype]['picture']);
if (empty($this->info[$unset_key][$tagtype])) {
unset($this->info[$unset_key][$tagtype]);
if (empty($this->info[$unset_key])) {
unset($this->info[$unset_key]);
// remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
if (isset($this->info[$tagtype]['comments']['picture'])) {
unset($this->info[$tagtype]['comments']['picture']);
if (empty($this->info[$tagtype]['comments'])) {
unset($this->info[$tagtype]['comments']);
if (empty($this->info[$tagtype])) {
unset($this->info[$tagtype]);
* Calls getid3_lib::CopyTagsToComments() but passes in the option_tags_html setting from this instance of getID3
* @param array $ThisFileInfo
public function CopyTagsToComments(&$ThisFileInfo) {
return getid3_lib::CopyTagsToComments($ThisFileInfo, $this->option_tags_html);
* @param string $algorithm
public function getHashdata($algorithm) {
return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) {
// We cannot get an identical md5_data value for Ogg files where the comments
// span more than 1 Ogg page (compared to the same audio data with smaller
// comments) using the normal getID3() method of MD5'ing the data between the
// end of the comments and the end of the file (minus any trailing tags),
// because the page sequence numbers of the pages that the audio data is on
// do not match. Under normal circumstances, where comments are smaller than
// the nominal 4-8kB page size, then this is not a problem, but if there are
// very large comments, the only way around it is to strip off the comment
// tags with vorbiscomment and MD5 that file.
// This procedure must be applied to ALL Ogg files, not just the ones with
// comments larger than 1 page, because the below method simply MD5's the
// whole file with the comments stripped, not just the portion after the
// comments block (which is the standard getID3() method.
// The above-mentioned problem of comments spanning multiple pages and changing
// page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
// currently vorbiscomment only works on OggVorbis files.
// phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved
if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
$this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
$this->info[$algorithm.'_data'] = false;
// Prevent user from aborting script
$old_abort = ignore_user_abort(true);
$empty = tempnam(GETID3_TEMP_DIR, 'getID3');
// Use vorbiscomment to make temp file without comments
$temp = tempnam(GETID3_TEMP_DIR, 'getID3');
$file = $this->info['filenamepath'];
if (GETID3_OS_ISWINDOWS) {
if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {
$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
$VorbisCommentError = `$commandline`;
$VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;
$commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
$VorbisCommentError = `$commandline`;
if (!empty($VorbisCommentError)) {
$this->warning('Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError);
$this->info[$algorithm.'_data'] = false;
// Get hash of newly created file
$this->info[$algorithm.'_data'] = md5_file($temp);
$this->info[$algorithm.'_data'] = sha1_file($temp);
ignore_user_abort($old_abort);
if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) {
// get hash from part of file
$this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm);
// get hash from whole file
$this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']);
$this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']);
public function ChannelsBitratePlaytimeCalculations() {
// set channelmode on audio
if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) {
} elseif ($this->info['audio']['channels'] == 1) {
$this->info['audio']['channelmode'] = 'mono';
} elseif ($this->info['audio']['channels'] == 2) {
$this->info['audio']['channelmode'] = 'stereo';
// Calculate combined bitrate - audio + video
$CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0);
$CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0);
if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) {
$this->info['bitrate'] = $CombinedBitrate;
//if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
// // for example, VBR MPEG video files cannot determine video bitrate:
// // should not set overall bitrate and playtime from audio bitrate only
// unset($this->info['bitrate']);
// video bitrate undetermined, but calculable
if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) {
// if video bitrate not set
if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) {
// AND if audio bitrate is set to same as overall bitrate
if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) {
// AND if playtime is set
if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) {
// AND if AV data offset start/end is known
// THEN we can calculate the video bitrate
$this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']);
$this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate'];
if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) {
$this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) {
$this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds'];
if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) {
if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) {
$this->info['audio']['bitrate'] = $this->info['bitrate'];
} elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) {
$this->info['video']['bitrate'] = $this->info['bitrate'];
if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
$this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']);
public function CalculateCompressionRatioVideo() {
if (empty($this->info['video'])) {
if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) {
if (empty($this->info['video']['bits_per_sample'])) {
switch ($this->info['video']['dataformat']) {
$BitrateCompressed = $this->info['filesize'] * 8;
if (!empty($this->info['video']['frame_rate'])) {
$FrameRate = $this->info['video']['frame_rate'];
if (!empty($this->info['playtime_seconds'])) {
$PlaytimeSeconds = $this->info['playtime_seconds'];
if (!empty($this->info['video']['bitrate'])) {
$BitrateCompressed = $this->info['video']['bitrate'];
$BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;
$this->info['video']['compression_ratio'] = getid3_lib::SafeDiv($BitrateCompressed, $BitrateUncompressed, 1);