+**Available at <http://getid3.sourceforge.net> or <https://www.getid3.org>**
+
+getID3() is released under multiple licenses. You may choose from the following licenses, and use getID3 according to the terms of the license most suitable to your project.
+and click the little "monitor package" icon/link. If you're
+previously signed up for the mailing list, be aware that it has
+been discontinued, only the automated Sourceforge notification
+will be used from now on.
+
+
+
+What does getID3() do?
+===
+
+Reads & parses (to varying degrees):
+
++ tags:
+ * APE (v1 and v2)
+ * ID3v1 (& ID3v1.1)
+ * ID3v2 (v2.4, v2.3, v2.2)
+ * Lyrics3 (v1 & v2)
+
++ audio-lossy:
+ * MP3/MP2/MP1
+ * MPC / Musepack
+ * Ogg (Vorbis, OggFLAC, Speex, Opus)
+ * AAC / MP4
+ * AC3
+ * DTS
+ * RealAudio
+ * Speex
+ * DSS
+ * VQF
+
++ audio-lossless:
+ * AIFF
+ * AU
+ * Bonk
+ * CD-audio (*.cda)
+ * FLAC
+ * LA (Lossless Audio)
+ * LiteWave
+ * LPAC
+ * MIDI
+ * Monkey's Audio
+ * OptimFROG
+ * RKAU
+ * Shorten
+ * Tom's lossless Audio Kompressor (TAK)
+ * TTA
+ * VOC
+ * WAV (RIFF)
+ * WavPack
+
++ audio-video:
+ * ASF: ASF, Windows Media Audio (WMA), Windows Media Video (WMV)
+ * AVI (RIFF)
+ * Flash
+ * Matroska (MKV)
+ * MPEG-1 / MPEG-2
+ * NSV (Nullsoft Streaming Video)
+ * Quicktime (including MP4)
+ * RealVideo
+
++ still image:
+ * BMP
+ * GIF
+ * JPEG
+ * PNG
+ * TIFF
+ * SWF (Flash)
+ * PhotoCD
+
++ data:
+ * ISO-9660 CD-ROM image (directory structure)
+ * SZIP (limited support)
+ * ZIP (directory structure)
+ * TAR
+ * CUE
+
+
++ Writes:
+ * ID3v1 (& ID3v1.1)
+ * ID3v2 (v2.3 & v2.4)
+ * VorbisComment on OggVorbis
+ * VorbisComment on FLAC (not OggFLAC)
+ * APE v2
+ * Lyrics3 (delete only)
+
+
+
+Requirements
+===
+
+* PHP 4.2.0 up to 5.2.x for getID3() 1.7.x (and earlier)
+* PHP 5.0.5 (or higher) for getID3() 1.8.x (and up)
+* PHP 5.0.5 (or higher) for getID3() 2.0.x (and up)
+* at least 4MB memory for PHP. 8MB or more is highly recommended.
+ 12MB is required with all modules loaded.
+
+Installation
+===
+The preferred method is via [composer](https://getcomposer.org/). Follow the installation [instructions](https://getcomposer.org/doc/00-intro.md) if you do not already have composer installed.
+
+Once composer is installed, execute the following command in your project root to install this library:
+
+```
+composer require james-heinrich/getid3
+```
+
+Usage
+===
+
+See /demos/demo.basic.php for a very basic use of getID3() with no
+fancy output, just scanning one file.
+
+See structure.txt for the returned data structure.
+
+**For an example of a complete directory-browsing, file-scanning implementation of getID3(), please run /demos/demo.browse.php**
+
+See /demos/demo.mysql.php for a sample recursive scanning code that
+scans every file in a given directory, and all sub-directories, stores
+the results in a database and allows various analysis / maintenance
+operations
+
+To analyze remote files over HTTP or FTP you need to copy the file
+locally first before running getID3(). Your code would look something
+like this:
+
+``` php
+<?php
+
+// Copy remote file locally to scan with getID3()
+die('For security reasons, this demo has been disabled. It can be enabled by removing line '.__LINE__.' in demos/'.basename(__FILE__));
+
+
+define('GETID3_DEMO_BROWSE_ALLOW_EDIT_LINK', false); // if enabled, shows "edit" links (to /demos/demo.write.php) to allow ID3/APE/etc tag editing on applicable file types
+define('GETID3_DEMO_BROWSE_ALLOW_DELETE_LINK', false); // if enabled, shows "delete" links to delete files from the browse interface
+define('GETID3_DEMO_BROWSE_ALLOW_MD5_LINK', false); // if enabled, shows "enable" link for MD5 hashes for file/data/source
+ $listdirectory = getid3_lib::truepath($listdirectory); // get rid of /../../ references
+ $currentfulldir = str_replace(DIRECTORY_SEPARATOR, '/', $listdirectory).'/'; // this mostly just gives a consistant look to Windows and *nix filesystems: (Windows uses \ as directory seperator, *nix uses /)
+
+ ob_start();
+ if ($handle = opendir($listdirectory)) {
+
+ ob_end_clean();
+ echo str_repeat(' ', 300); // IE buffers the first 300 or so chars, making this progressive display useless - fill the buffer with spaces
+ echo 'Processing';
+
+ $starttime = microtime(true);
+
+ $TotalScannedUnknownFiles = 0;
+ $TotalScannedKnownFiles = 0;
+ $TotalScannedPlaytimeFiles = 0;
+ $TotalScannedBitrateFiles = 0;
+ $TotalScannedFilesize = 0;
+ $TotalScannedPlaytime = 0;
+ $TotalScannedBitrate = 0;
+ $FilesWithWarnings = 0;
+ $FilesWithErrors = 0;
+
+ while ($file = readdir($handle)) {
+ $currentfilename = $listdirectory.'/'.$file;
+ set_time_limit(30); // allocate another 30 seconds to process this file - should go much quicker than this unless intense processing (like bitrate histogram analysis) is enabled
+die('For security reasons, this demo has been disabled. It can be enabled by removing line '.__LINE__.' in demos/'.basename(__FILE__));
+
+
+// sample usage:
+// $FilenameOut = 'combined.mp3';
+// $FilenamesIn[] = 'first.mp3'; // filename with no start/length parameters
+// $FilenamesIn[] = array('second.mp3', 0, 0); // filename with zero for start/length is the same as not specified (start = beginning, length = full duration)
+// $FilenamesIn[] = array('third.mp3', 0, 10); // extract first 10 seconds of audio
+// $FilenamesIn[] = array('fourth.mp3', -10, 0); // extract last 10 seconds of audio
+// $FilenamesIn[] = array('fifth.mp3', 10, 0); // extract everything except first 10 seconds of audio
+// $FilenamesIn[] = array('sixth.mp3', 0, -10); // extract everything except last 10 seconds of audio
+// if (CombineMultipleMP3sTo($FilenameOut, $FilenamesIn)) {
+// echo 'Successfully copied '.implode(' + ', $FilenamesIn).' to '.$FilenameOut;
+// } else {
+// echo 'Failed to copy '.implode(' + ', $FilenamesIn).' to '.$FilenameOut;
+// }
+//
+// Could also be called like this to extract portion from single file:
+// CombineMultipleMP3sTo('sample.mp3', array(array('input.mp3', 0, 30))); // extract first 30 seconds of audio
+ echo 'File does not exist: "'.htmlentities($filename).'"<br>';
+ return '';
+ } elseif (!is_readable($filename)) {
+ echo 'File is not readable: "'.htmlentities($filename).'"<br>';
+ return '';
+ }
+
+ // include getID3() library (can be in a different directory if full path is specified)
+ require_once('../getid3/getid3.php');
+ // Initialize getID3 engine
+ $getID3 = new getID3;
+
+ $DeterminedMIMEtype = '';
+ if ($fp = fopen($filename, 'rb')) {
+ $getID3->openfile($filename);
+ if (empty($getID3->info['error'])) {
+
+ // ID3v2 is the only tag format that might be prepended in front of files, and it's non-trivial to skip, easier just to parse it and know where to skip to
+ $ThisFileInfo['warning'] .= "\n".'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$ThisFileInfo['audio']['dataformat'].'"';
+ break;
+ }
+ }
+
+ if (empty($ThisFileInfo['fileformat'])) {
+ $ThisFileInfo['error'] .= "\n".'Synch not found';
+ unset($ThisFileInfo['fileformat']);
+ unset($ThisFileInfo['audio']['bitrate_mode']);
+ unset($ThisFileInfo['avdataoffset']);
+ unset($ThisFileInfo['avdataend']);
+ return false;
+ }
+
+ $ThisFileInfo['mime_type'] = 'audio/mpeg';
+ $ThisFileInfo['audio']['lossless'] = false;
+
+ // Calculate playtime
+ if (!isset($ThisFileInfo['playtime_seconds']) && isset($ThisFileInfo['audio']['bitrate']) && ($ThisFileInfo['audio']['bitrate'] > 0)) {
+ if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') || ($ThisFileInfo['mpeg']['audio']['bitrate'] <= 192)) {
+ // these are ok
+ } else {
+ $ThisFileInfo['error'] .= "\n".$ThisFileInfo['mpeg']['audio']['bitrate'].'kbps not allowed in Layer II, '.$ThisFileInfo['mpeg']['audio']['channelmode'].'.';
+ $ThisFileInfo['error'] .= "\n".$ThisFileInfo['mpeg']['audio']['bitrate'].'kbps not allowed in Layer II, '.$ThisFileInfo['mpeg']['audio']['channelmode'].'.';
+ return false;
+ }
+ break;
+
+ }
+
+ }
+
+
+ if ($ThisFileInfo['audio']['sample_rate'] > 0) {
+ $ThisFileInfo['warning'] .= "\n".'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])).' bytes)';
+ } else {
+ $ThisFileInfo['warning'] .= "\n".'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' ('.(($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)';
+ }
+ }
+
+ if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') && empty($ThisFileInfo['audio']['bitrate'])) {
+ if (($offset == $ThisFileInfo['avdataoffset']) && empty($ThisFileInfo['mpeg']['audio']['VBR_frames'])) {
+ if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
+ $framelength = $framelength2;
+ }
+ if (!$framelength) {
+ $ThisFileInfo['error'] .= "\n".'Cannot find next free-format synch pattern ('.PrintHexBytes($SyncPattern1).' or '.PrintHexBytes($SyncPattern2).') after offset '.$offset;
+ return false;
+ } else {
+ $ThisFileInfo['warning'] .= "\n".'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)';
+ $ThisFileInfo['audio']['codec'] = 'LAME';
+ $ThisFileInfo['audio']['encoder'] = 'LAME3.88';
+ $SyncPattern1 = substr($SyncPattern1, 0, 3);
+ $SyncPattern2 = substr($SyncPattern2, 0, 3);
+ }
+ }
+
+ if ($deepscan) {
+
+ $ActualFrameLengthValues = array();
+ $nextoffset = $offset + $framelength;
+ while ($nextoffset < ($ThisFileInfo['avdataend'] - 6)) {
+ $ThisFileInfo['warning'] .= "\n".'apparently-valid VBR header not used because could not find '.MPEG_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd;
+
+ } else {
+
+ $ThisFileInfo['warning'] .= "\n".'using data from VBR header even though could not find '.MPEG_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')';
+
+ }
+ }
+ }
+
+ if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode']) && ($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($ThisFileInfo['mpeg']['audio']['VBR_method'])) {
+if (!isset($ExistingTableFields['track_volume'])) { // Added in 1.7.0b5
+ echo '<H1><FONT COLOR="red">WARNING! You should erase your database and rescan everything because the comment storing has been changed since the last version</FONT></H1><hr>';
+ echo '<b>adding field `track_volume`</b><br>';
+ mysqli_query_safe($con, 'ALTER TABLE `'.mysqli_real_escape_string($con, GETID3_DB_TABLE).'` ADD `track_volume` FLOAT NOT NULL AFTER `error`');
+if (isset($ExistingTableFields['comments_all']) && ($ExistingTableFields['comments_all']['Type'] != 'longblob')) { // Changed to "longtext" in 1.9.0, changed to "longblob" in 1.9.20-202010140821
+ echo '<b>changing comments fields from text to longtext</b><br>';
+ $SQLquery .= ' SET `track_volume` = "'.$ThisFileInfo['replay_gain']['track']['volume'].'"';
+ $SQLquery .= ' WHERE (`filename` = "'.mysqli_real_escape_string($con, $row['filename']).'")';
+ mysqli_query_safe($con, $SQLquery);
+ }
+
+ } else {
+
+ $MissingTrackVolumeFilesDeleted++;
+ $SQLquery = 'DELETE';
+ $SQLquery .= ' FROM `'.mysqli_real_escape_string($con, GETID3_DB_TABLE).'`';
+ $SQLquery .= ' WHERE (`filename` = "'.mysqli_real_escape_string($con, $row['filename']).'")';
+ mysqli_query_safe($con, $SQLquery);
+
+ }
+ }
+ echo '<hr>Scanned '.number_format($MissingTrackVolumeFilesScanned).' files with no track volume information.<br>';
+ echo 'Found track volume information for '.number_format($MissingTrackVolumeFilesAdjusted).' of them (could not find info for '.number_format($MissingTrackVolumeFilesScanned - $MissingTrackVolumeFilesAdjusted).' files; deleted '.number_format($MissingTrackVolumeFilesDeleted).' records of missing files)<hr>';
+
+} elseif (!empty($_REQUEST['deadfilescheck'])) {
+
+ $SQLquery = 'SELECT COUNT(*) AS `num`, `filename`';
+ $SQLquery .= ' FROM `'.mysqli_real_escape_string($con, GETID3_DB_TABLE).'`';
+ $SQLquery .= ' FROM `'.mysqli_real_escape_string($con, GETID3_DB_TABLE).'`';
+ $SQLquery .= ' WHERE (`filename` = "'.mysqli_real_escape_string($con, $row['filename']).'")';
+ mysqli_query_safe($con, $SQLquery);
+
+ } elseif ($thisdir != $previousdir) {
+
+ echo '. ';
+ flush();
+
+ }
+ $previousdir = $thisdir;
+ }
+
+ echo '<hr><b>'.number_format($totalremoved).' of '.number_format($totalchecked).' files in database no longer exist, or have been altered since last scan. Removed from database.</b><hr>';
+ echo '<th><a href="'.htmlentities($_SERVER['PHP_SELF'].'?unsynchronizedtags='.urlencode($_REQUEST['unsynchronizedtags']).'&autofix=1&autofixforcesource=id3v2&autofixforcedest=A1').'" title="Auto-fix all tags to match ID3v2 contents" onClick="return confirm(\'Are you SURE you want to synchronize all tags to match ID3v2?\');">ID3v2</a></th>';
+ }
+ if ($TagsToCompare['ape']) {
+ echo '<th><a href="'.htmlentities($_SERVER['PHP_SELF'].'?unsynchronizedtags='.urlencode($_REQUEST['unsynchronizedtags']).'&autofix=1&autofixforcesource=ape&autofixforcedest=21').'" title="Auto-fix all tags to match APE contents" onClick="return confirm(\'Are you SURE you want to synchronize all tags to match APE?\');">APE</a></th>';
+ }
+ if ($TagsToCompare['lyrics3']) {
+ echo '<th>Lyrics3</th>';
+ }
+ if ($TagsToCompare['id3v1']) {
+ echo '<th><a href="'.htmlentities($_SERVER['PHP_SELF'].'?unsynchronizedtags='.urlencode($_REQUEST['unsynchronizedtags']).'&autofix=1&autofixforcesource=ape&autofixforcedest=2A').'" title="Auto-fix all tags to match ID3v1 contents" onClick="return confirm(\'Are you SURE you want to synchronize all tags to match ID3v1?\');">ID3v1</a></th>';
+ $SQLquery .= ' FROM `'.mysqli_real_escape_string($con, GETID3_DB_TABLE).'`';
+ $SQLquery .= ' WHERE (`fileformat` NOT LIKE "'.implode('") AND (`fileformat` NOT LIKE "', $IgnoreNoTagFormats).'")';
+ $SQLquery .= ' ORDER BY `filename` ASC';
+ $result = mysqli_query_safe($con, $SQLquery);
+ echo 'Files that do not match naming pattern: (<a href="'.htmlentities($_SERVER['PHP_SELF'].'?filenamepattern='.urlencode($_REQUEST['filenamepattern']).'&autofix=1').'">auto-fix</a>)<br>';
+ echo '<tr><th>view</th><th>Why</th><td><b>Actual filename</b><br>(click to play/edit file)</td><td><b>Correct filename (based on tags)</b>'.(empty($_REQUEST['autofix']) ? '<br>(click to rename file to this)' : '').'</td></tr>';
+ $SQLquery .= ' FROM `'.mysqli_real_escape_string($con, GETID3_DB_TABLE).'`';
+ $SQLquery .= ' WHERE (`fileformat` <> "")';
+ $SQLquery .= ' ORDER BY `filename` ASC';
+ $result = mysqli_query_safe($con, $SQLquery);
+ echo 'Copy and paste the following into a DOS batch file. You may have to run this script more than once to catch all the changes (remember to scan for deleted/changed files and rescan directory between scans)<hr>';
+echo '<input type="submit" value="Go" onClick="return confirm(\'Are you sure you want to erase all entries in the database and start scanning again?\');">';
+//echo '<li><a href="'.htmlentities($_SERVER['PHP_SELF'].'?missingtrackvolume=1').'">Scan for missing track volume information (update database from pre-v1.7.0b5)</a></li>';
+echo '<li><a href="'.htmlentities($_SERVER['PHP_SELF'].'?unsynchronizedtags=2A1').'">Tags that are not synchronized</a> (<a href="'.$_SERVER['PHP_SELF'].'?unsynchronizedtags=2A1&autofix=1">autofix</a>)</li>';
+echo '<li><a href="'.htmlentities($_SERVER['PHP_SELF'].'?filenamepattern='.urlencode('[N] A - T {R}')).'">Filenames that don\'t match pattern</a> (<a href="?filenamepattern='.urlencode('[N] A - T {R}').'&autofix=1">auto-fix</a>)</li>';
+//echo '<li><a href="'.htmlentities($_SERVER['PHP_SELF'].'?filenamepattern='.urlencode('A - T')).'">Filenames that don\'t match pattern</a></li>';
+echo '<li><a href="'.htmlentities($_SERVER['PHP_SELF'].'?correctcase=1').'">Correct filename case (Win/DOS)</a></li>';
+echo '<li><a href="'.htmlentities($_SERVER['PHP_SELF'].'?errorswarnings=1').'">Files with Errors and/or Warnings</a></li>';
+echo '<li><a href="'.htmlentities($_SERVER['PHP_SELF'].'?rescanerrors=1').'">Re-scan only files with Errors and/or Warnings</a></li>';
+echo '</ul>';
+
+$SQLquery = 'SELECT COUNT(*) AS `TotalFiles`, SUM(`playtime_seconds`) AS `TotalPlaytime`, SUM(`filesize`) AS `TotalFilesize`, AVG(`playtime_seconds`) AS `AvgPlaytime`, AVG(`filesize`) AS `AvgFilesize`, AVG(`audio_bitrate` + `video_bitrate`) AS `AvgBitrate`';
+$SQLquery .= ' FROM `'.mysqli_real_escape_string($con, GETID3_DB_TABLE).'`';
+$result = mysqli_query_safe($con, $SQLquery);
+if ($row = mysqli_fetch_array($result)) {
+ echo '<hr size="1">';
+ echo '<div style="float: right;">';
+ echo 'Spent '.number_format(mysqli_query_safe($con, null), 3).' seconds querying the database<br>';
+ echo '</div>';
+ echo '<b>Currently in the database:</b><TABLE>';
+$tagwriter->overwrite_tags = true; // if true will erase existing tag data and write only passed data; if false will merge passed data with existing tag data (experimental)
+$tagwriter->remove_other_tags = false; // if true removes other tag formats (e.g. ID3v1, ID3v2, APE, Lyrics3, etc) that may be present in the file and only write the specified tag format(s). If false leaves any unspecified tag formats as-is.
+ throw new Exception('Cannot use database '.$database);
+ }
+
+ // Set table
+ $this->table = $table;
+
+ // Create cache table if not exists
+ $this->create_table();
+
+ $this->db_structure_check = true; // set to false if you know your table structure has already been migrated to use `hash` as the primary key to avoid
+ $this->migrate_db_structure();
+
+ // Check version number and clear cache if changed
+ $version = '';
+ $SQLquery = 'SELECT `value`';
+ $SQLquery .= ' FROM `'.$this->mysqli->real_escape_string($this->table).'`';
+ $SQLquery .= ' WHERE (`filename` = \''.$this->mysqli->real_escape_string(getID3::VERSION).'\')';
+ $SQLquery .= ' AND (`hash` = \'getID3::VERSION\')';
+ if ($this->cursor = $this->mysqli->query($SQLquery)) {
+ // new value is identical but shorter-than (or equal-length to) one already in comments - skip
+ break 2;
+ }
+
+ if (function_exists('mb_convert_encoding')) {
+ if (trim($value) == trim(substr(mb_convert_encoding($existingvalue, $ThisFileInfo['id3v1']['encoding'], $ThisFileInfo['encoding']), 0, 30))) {
+ // value stored in ID3v1 appears to be probably the multibyte value transliterated (badly) into ISO-8859-1 in ID3v1.
+ // As an example, Foobar2000 will do this if you tag a file with Chinese or Arabic or Cyrillic or something that doesn't fit into ISO-8859-1 the ID3v1 will consist of mostly "?" characters, one per multibyte unrepresentable character
+ break 2;
+ }
+ }
+ }
+
+ } elseif (!is_array($value)) {
+
+ $newvaluelength = strlen(trim($value));
+ $newvaluelengthMB = mb_strlen(trim($value));
+ foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) {
+ $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
+}
+// $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system
+if (!defined('GETID3_TEMP_DIR')) {
+ define('GETID3_TEMP_DIR', $temp_dir);
+}
+unset($open_basedir, $temp_dir);
+
+// End: Defines
+
+
+class getID3
+{
+ /*
+ * Settings
+ */
+
+ /**
+ * CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE
+ *
+ * @var string
+ */
+ public $encoding = 'UTF-8';
+
+ /**
+ * Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252'
+ *
+ * @var string
+ */
+ public $encoding_id3v1 = 'ISO-8859-1';
+
+ /**
+ * ID3v1 should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'Windows-1251' or 'KOI8-R'. If true attempt to detect these encodings, but may return incorrect values for some tags actually in ISO-8859-1 encoding
+ *
+ * @var bool
+ */
+ public $encoding_id3v1_autodetect = false;
+
+ /*
+ * Optional tag checks - disable for speed.
+ */
+
+ /**
+ * Read and process ID3v1 tags
+ *
+ * @var bool
+ */
+ public $option_tag_id3v1 = true;
+
+ /**
+ * Read and process ID3v2 tags
+ *
+ * @var bool
+ */
+ public $option_tag_id3v2 = true;
+
+ /**
+ * Read and process Lyrics3 tags
+ *
+ * @var bool
+ */
+ public $option_tag_lyrics3 = true;
+
+ /**
+ * Read and process APE tags
+ *
+ * @var bool
+ */
+ public $option_tag_apetag = true;
+
+ /**
+ * Copy tags to root key 'tags' and encode to $this->encoding
+ *
+ * @var bool
+ */
+ public $option_tags_process = true;
+
+ /**
+ * Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
+ *
+ * @var bool
+ */
+ public $option_tags_html = true;
+
+ /*
+ * Optional tag/comment calculations
+ */
+
+ /**
+ * Calculate additional info such as bitrate, channelmode etc
+ *
+ * @var bool
+ */
+ public $option_extra_info = true;
+
+ /*
+ * Optional handling of embedded attachments (e.g. images)
+ */
+
+ /**
+ * Defaults to true (ATTACHMENTS_INLINE) for backward compatibility
+ *
+ * @var bool|string
+ */
+ public $option_save_attachments = true;
+
+ /*
+ * Optional calculations
+ */
+
+ /**
+ * Get MD5 sum of data part - slow
+ *
+ * @var bool
+ */
+ public $option_md5_data = false;
+
+ /**
+ * Use MD5 of source file if available - only FLAC and OptimFROG
+ *
+ * @var bool
+ */
+ public $option_md5_data_source = false;
+
+ /**
+ * Get SHA1 sum of data part - slow
+ *
+ * @var bool
+ */
+ public $option_sha1_data = false;
+
+ /**
+ * Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on
+ * PHP_INT_MAX)
+ *
+ * @var bool|null
+ */
+ public $option_max_2gb_check;
+
+ /**
+ * Read buffer size in bytes
+ *
+ * @var int
+ */
+ public $option_fread_buffer_size = 32768;
+
+
+
+ // module-specific options
+
+ /** archive.rar
+ * if true use PHP RarArchive extension, if false (non-extension parsing not yet written in getID3)
+ *
+ * @var bool
+ */
+ public $options_archive_rar_use_php_rar_extension = true;
+
+ /** archive.gzip
+ * Optional file list - disable for speed.
+ * Decode gzipped files, if possible, and parse recursively (.tar.gz for example).
+ *
+ * @var bool
+ */
+ public $options_archive_gzip_parse_contents = false;
+
+ /** audio.midi
+ * if false only parse most basic information, much faster for some files but may be inaccurate
+ *
+ * @var bool
+ */
+ public $options_audio_midi_scanwholefile = true;
+
+ /** audio.mp3
+ * Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow,
+ * unrecommended, but may provide data from otherwise-unusable files.
+ *
+ * @var bool
+ */
+ public $options_audio_mp3_allow_bruteforce = false;
+
+ /** audio.mp3
+ * number of frames to scan to determine if MPEG-audio sequence is valid
+ * Lower this number to 5-20 for faster scanning
+ * Increase this number to 50+ for most accurate detection of valid VBR/CBR mpeg-audio streams
+ *
+ * @var int
+ */
+ public $options_audio_mp3_mp3_valid_check_frames = 50;
+
+ /** audio.wavpack
+ * Avoid scanning all frames (break after finding ID_RIFF_HEADER and ID_CONFIG_BLOCK,
+ * significantly faster for very large files but other data may be missed
+ *
+ * @var bool
+ */
+ public $options_audio_wavpack_quick_parsing = false;
+
+ /** audio-video.flv
+ * Break out of the loop if too many frames have been scanned; only scan this
+ * many if meta frame does not contain useful duration.
+ *
+ * @var int
+ */
+ public $options_audiovideo_flv_max_frames = 100000;
+
+ /** audio-video.matroska
+ * If true, do not return information about CLUSTER chunks, since there's a lot of them
+ * and they're not usually useful [default: TRUE].
+ *
+ * @var bool
+ */
+ public $options_audiovideo_matroska_hide_clusters = true;
+
+ /** audio-video.matroska
+ * True to parse the whole file, not only header [default: FALSE].
+ *
+ * @var bool
+ */
+ public $options_audiovideo_matroska_parse_whole_file = false;
+
+ /** audio-video.quicktime
+ * return all parsed data from all atoms if true, otherwise just returned parsed metadata
+ *
+ * @var bool
+ */
+ public $options_audiovideo_quicktime_ReturnAtomData = false;
+
+ /** audio-video.quicktime
+ * return all parsed data from all atoms if true, otherwise just returned parsed metadata
+ *
+ * @var bool
+ */
+ public $options_audiovideo_quicktime_ParseAllPossibleAtoms = false;
+
+ /** audio-video.swf
+ * return all parsed tags if true, otherwise do not return tags not parsed by getID3
+ *
+ * @var bool
+ */
+ public $options_audiovideo_swf_ReturnAllTagData = false;
+
+ /** graphic.bmp
+ * return BMP palette
+ *
+ * @var bool
+ */
+ public $options_graphic_bmp_ExtractPalette = false;
+
+ /** graphic.bmp
+ * return image data
+ *
+ * @var bool
+ */
+ public $options_graphic_bmp_ExtractData = false;
+
+ /** graphic.png
+ * If data chunk is larger than this do not read it completely (getID3 only needs the first
+ * few dozen bytes for parsing).
+ *
+ * @var int
+ */
+ public $options_graphic_png_max_data_bytes = 10000000;
+
+ /** misc.pdf
+ * return full details of PDF Cross-Reference Table (XREF)
+ *
+ * @var bool
+ */
+ public $options_misc_pdf_returnXREF = false;
+
+ /** misc.torrent
+ * Assume all .torrent files are less than 1MB and just read entire thing into memory for easy processing.
+ * Override this value if you need to process files larger than 1MB
+ *
+ * @var int
+ */
+ public $options_misc_torrent_max_torrent_filesize = 1048576;
+
+
+
+ // Public variables
+
+ /**
+ * Filename of file being analysed.
+ *
+ * @var string
+ */
+ public $filename;
+
+ /**
+ * Filepointer to file being analysed.
+ *
+ * @var resource
+ */
+ public $fp;
+
+ /**
+ * Result array.
+ *
+ * @var array
+ */
+ public $info;
+
+ /**
+ * @var string
+ */
+ public $tempdir = GETID3_TEMP_DIR;
+
+ /**
+ * @var int
+ */
+ public $memory_limit = 0;
+
+ /**
+ * @var string
+ */
+ protected $startup_error = '';
+
+ /**
+ * @var string
+ */
+ protected $startup_warning = '';
+
+ const VERSION = '1.9.21-202207161647';
+ const FREAD_BUFFER_SIZE = 32768;
+
+ const ATTACHMENTS_NONE = false;
+ const ATTACHMENTS_INLINE = true;
+
+ /**
+ * @throws getid3_exception
+ */
+ public function __construct() {
+
+ // Check for PHP version
+ $required_php_version = '5.3.0';
+ if (version_compare(PHP_VERSION, $required_php_version, '<')) {
+ $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION."\n";
+ return;
+ }
+
+ // Check memory
+ $memoryLimit = ini_get('memory_limit');
+ if (preg_match('#([0-9]+) ?M#i', $memoryLimit, $matches)) {
+ // could be stored as "16M" rather than 16777216 for example
+ $memoryLimit = $matches[1] * 1048576;
+ } elseif (preg_match('#([0-9]+) ?G#i', $memoryLimit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
+ // could be stored as "2G" rather than 2147483648 for example
+ $memoryLimit = $matches[1] * 1073741824;
+ }
+ $this->memory_limit = $memoryLimit;
+
+ if ($this->memory_limit <= 0) {
+ // memory limits probably disabled
+ } elseif ($this->memory_limit <= 4194304) {
+ $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'."\n";
+ } elseif ($this->memory_limit <= 12582912) {
+ $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'."\n";
+ }
+
+ // Check safe_mode off
+ if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
+ $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
+ // "mbstring.func_overload in php.ini is a positive value that represents a combination of bitmasks specifying the categories of functions to be overloaded. It should be set to 1 to overload the mail() function. 2 for string functions, 4 for regular expression functions"
+ // getID3 cannot run when string functions are overloaded. It doesn't matter if mail() or ereg* functions are overloaded since getID3 does not use those.
+ $this->startup_error .= 'WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", getID3 cannot run with this setting (bitmask 2 (string functions) cannot be set). Recommended to disable entirely.'."\n";
+ }
+
+ // check for magic quotes in PHP < 7.4.0 (when these functions became deprecated)
+ if (version_compare(PHP_VERSION, '7.4.0', '<')) {
+ // Check for magic_quotes_runtime
+ if (function_exists('get_magic_quotes_runtime')) {
+ if (get_magic_quotes_runtime()) {
+ $this->startup_error .= 'magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'."\n";
+ }
+ }
+ // Check for magic_quotes_gpc
+ if (function_exists('get_magic_quotes_gpc')) {
+ if (get_magic_quotes_gpc()) {
+ $this->startup_error .= 'magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'."\n";
+ }
+ }
+ }
+
+ // Load support library
+ if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
+ $this->startup_error .= 'getid3.lib.php is missing or corrupt'."\n";
+ if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) {
+ $value = $shortname;
+ }
+ }
+ }
+ } else {
+ $this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'."\n";
+ throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.');
+ throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB, please report to info@getid3.org');
+ }
+ $this->info['filesize'] = $real_filesize;
+ $this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB) and is not properly supported by PHP.');
+ }
+ }
+
+ return true;
+
+ } catch (Exception $e) {
+ $this->error($e->getMessage());
+ }
+ return false;
+ }
+
+ /**
+ * analyze file
+ *
+ * @param string $filename
+ * @param int $filesize
+ * @param string $original_filename
+ * @param resource $fp
+ *
+ * @return array
+ */
+ public function analyze($filename, $filesize=null, $original_filename='', $fp=null) {
+ try {
+ if (!$this->openfile($filename, $filesize, $fp)) {
+ $errormessage = 'mb_convert_encoding() or iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. ';
+ if (GETID3_OS_ISWINDOWS) {
+ $errormessage .= 'PHP does not have mb_convert_encoding() or iconv() support. Please enable php_mbstring.dll / php_iconv.dll in php.ini, and copy php_mbstring.dll / iconv.dll from c:/php/dlls to c:/windows/system32';
+ } else {
+ $errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch';
+ //'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)', // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available
+ 'pattern' => '^.{1080}(M\\.K\\.)',
+ 'group' => 'audio',
+ 'module' => 'mod',
+ 'option' => 'mod',
+ 'mime_type' => 'audio/mod',
+ ),
+
+ // MOD - audio - MODule (Impulse Tracker)
+ 'it' => array(
+ 'pattern' => '^IMPM',
+ 'group' => 'audio',
+ 'module' => 'mod',
+ //'option' => 'it',
+ 'mime_type' => 'audio/it',
+ ),
+
+ // MOD - audio - MODule (eXtended Module, various sub-formats)
+ 'xm' => array(
+ 'pattern' => '^Extended Module',
+ 'group' => 'audio',
+ 'module' => 'mod',
+ //'option' => 'xm',
+ 'mime_type' => 'audio/xm',
+ ),
+
+ // MOD - audio - MODule (ScreamTracker)
+ 's3m' => array(
+ 'pattern' => '^.{44}SCRM',
+ 'group' => 'audio',
+ 'module' => 'mod',
+ //'option' => 's3m',
+ 'mime_type' => 'audio/s3m',
+ ),
+
+ // MPC - audio - Musepack / MPEGplus
+ 'mpc' => array(
+ 'pattern' => '^(MPCK|MP\\+)',
+ 'group' => 'audio',
+ 'module' => 'mpc',
+ 'mime_type' => 'audio/x-musepack',
+ ),
+
+ // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS)
+ // CUE - data - CUEsheet (index to single-file disc images)
+ 'cue' => array(
+ 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
+ 'group' => 'misc',
+ 'module' => 'cue',
+ 'mime_type' => 'application/octet-stream',
+ ),
+
+ );
+ }
+
+ return $format_info;
+ }
+
+ /**
+ * @param string $filedata
+ * @param string $filename
+ *
+ * @return mixed|false
+ */
+ 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
+ // of the file).
+
+ // 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)) {
+ '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) {
+ // 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
+ // 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) {
+ // 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.
+
+ 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;
+
+ } else {
+
+ // Prevent user from aborting script
+ $old_abort = ignore_user_abort(true);
+
+ // Create empty file
+ $empty = tempnam(GETID3_TEMP_DIR, 'getID3');
+ touch($empty);
+
+ // 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')) {
+ $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);
+ $saved_filesize = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call
+ * "I found out that the root cause for the problem was how getID3 uses the PHP system function fread().
+ * It seems to assume that fread() would always return as many bytes as were requested.
+ * However, according the PHP manual (http://php.net/manual/en/function.fread.php), this is the case only with regular local files, but not e.g. with Linux pipes.
+ * The call may return only part of the requested data and a new call is needed to get more."
+ if (($this->getid3->memory_limit > 0) && (($bytes / $this->getid3->memory_limit) > 0.99)) { // enable a more-fuzzy match to prevent close misses generating errors like "PHP Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 33554464 bytes)"
+ throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') that is more than available PHP memory ('.$this->getid3->memory_limit.')', 10);
+ }
+ $part = fread($this->getid3->fp, $bytes);
+ $partLength = strlen($part);
+ $bytes -= $partLength;
+ $contents .= $part;
+ } while (($bytes > 0) && ($partLength > 0));
+ return $contents;
+ }
+
+ /**
+ * @param int $bytes
+ * @param int $whence
+ *
+ * @return int
+ *
+ * @throws getid3_exception
+ */
+ protected function fseek($bytes, $whence=SEEK_SET) {
+ if ($info['php_memory_limit'] && ($info['filesize'] > $info['php_memory_limit'])) {
+ $this->error('File is too large ('.number_format($info['filesize']).' bytes) to read into memory (limit: '.number_format($info['php_memory_limit'] / 1048576).'MB)');
+ // "3.3 OCF ZIP Container Media Type Identification
+ // OCF ZIP Containers must include a mimetype file as the first file in the Container, and the contents of this file must be the MIME type string application/epub+zip.
+ // The contents of the mimetype file must not contain any leading padding or whitespace, must not begin with the Unicode signature (or Byte Order Mark),
+ // and the case of the MIME type string must be exactly as presented above. The mimetype file additionally must be neither compressed nor encrypted,
+ // and there must not be an extra field in its ZIP header."
+ $this->error('Cannot find End Of Central Directory (truncated file?)');
+ return false;
+ }
+
+ // central directory couldn't be found and/or parsed
+ // scan through actual file data entries, recover as much as possible from probable trucated file
+ if ($info['zip']['compressed_size'] > ($info['filesize'] - 46 - 22)) {
+ $this->error('Warning: Truncated file! - Total compressed file sizes ('.$info['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($info['filesize'] - 46 - 22).' bytes)');
+ }
+ $this->error('Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete');
+ foreach ($info['zip']['entries'] as $key => $valuearray) {
+ $this->fseek($LocalFileHeader['compressed_size'], SEEK_CUR); // this should (but may not) match value in $LocalFileHeader['raw']['compressed_size'] -- $LocalFileHeader['compressed_size'] could have been overwritten above with value from Central Directory
+
+ if ($LocalFileHeader['flags']['data_descriptor_used']) {
+ if (!$LocalFileHeader['raw']['compressed_size'] && $LocalFileHeader['data_descriptor']['compressed_size']) {
+ foreach ($this->getid3->info['zip']['central_directory'] as $central_directory_entry) {
+ if ($central_directory_entry['entry_offset'] == $LocalFileHeader['offset']) {
+ if ($LocalFileHeader['data_descriptor']['compressed_size'] == $central_directory_entry['compressed_size']) {
+ // $LocalFileHeader['compressed_size'] already set from Central Directory
+ } else {
+ $this->warning('conflicting compressed_size from data_descriptor ('.$LocalFileHeader['data_descriptor']['compressed_size'].') vs Central Directory ('.$central_directory_entry['compressed_size'].') for file at offset '.$LocalFileHeader['offset']);
+ }
+
+ if ($LocalFileHeader['data_descriptor']['uncompressed_size'] == $central_directory_entry['uncompressed_size']) {
+ $this->warning('conflicting uncompressed_size from data_descriptor ('.$LocalFileHeader['data_descriptor']['uncompressed_size'].') vs Central Directory ('.$central_directory_entry['uncompressed_size'].') for file at offset '.$LocalFileHeader['offset']);
+ if ($thisfile_asf_headerobject['objectid'] != GETID3_ASF_Header_Object) {
+ unset($info['fileformat'], $info['asf']);
+ return $this->error('ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}');
+ // File Properties Object: (mandatory, one only)
+ // Field Name Field Type Size (bits)
+ // Object ID GUID 128 // GUID for file properties object - GETID3_ASF_File_Properties_Object
+ // Object Size QWORD 64 // size of file properties object, including 104 bytes of File Properties Object header
+ // File ID GUID 128 // unique ID - identical to File ID in Data Object
+ // File Size QWORD 64 // entire file in bytes. Invalid if Broadcast Flag == 1
+ // Creation Date QWORD 64 // date & time of file creation. Maybe invalid if Broadcast Flag == 1
+ // Data Packets Count QWORD 64 // number of data packets in Data Object. Invalid if Broadcast Flag == 1
+ // Play Duration QWORD 64 // playtime, in 100-nanosecond units. Invalid if Broadcast Flag == 1
+ // Send Duration QWORD 64 // time needed to send file, in 100-nanosecond units. Players can ignore this value. Invalid if Broadcast Flag == 1
+ // Preroll QWORD 64 // time to buffer data before starting to play file, in 1-millisecond units. If <> 0, PlayDuration and PresentationTime have been offset by this amount
+ // Flags DWORD 32 //
+ // * Broadcast Flag bits 1 (0x01) // file is currently being written, some header values are invalid
+ // * Seekable Flag bits 1 (0x02) // is file seekable
+ // * Reserved bits 30 (0xFFFFFFFC) // reserved - set to zero
+ // Minimum Data Packet Size DWORD 32 // in bytes. should be same as Maximum Data Packet Size. Invalid if Broadcast Flag == 1
+ // Maximum Data Packet Size DWORD 32 // in bytes. should be same as Minimum Data Packet Size. Invalid if Broadcast Flag == 1
+ // Maximum Bitrate DWORD 32 // maximum instantaneous bitrate in bits per second for entire file, including all data streams and ASF overhead
+ if ($thisfile_asf_headerextensionobject['reserved_1'] != GETID3_ASF_Reserved_1) {
+ $this->warning('header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')');
+ if ($thisfile_asf_codeclistobject['reserved'] != $this->GUIDtoBytestring('86D15241-311D-11D0-A3A4-00A0C90348F6')) {
+ $this->warning('codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}');
+ if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { // audio codec
+
+ if (strpos($thisfile_asf_codeclistobject_codecentries_current['description'], ',') === false) {
+ $this->warning('[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-separated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"');
+ if ($thisfile_asf_scriptcommandobject['reserved'] != $this->GUIDtoBytestring('4B1ACBE3-100B-11D0-A39B-00A0C90348F6')) {
+ $this->warning('script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}');
+ if ($thisfile_asf_markerobject['reserved'] != $this->GUIDtoBytestring('4CFEDB20-75F6-11CF-9C0F-00A0C90349CB')) {
+ $this->warning('marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}');
+ if (($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Bitrate) && ($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Unknown)) {
+ $this->warning('bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}');
+ $this->warning('error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['error_correction_type']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}');
+ //return false;
+ break;
+ }
+
+ break;
+
+ case GETID3_ASF_Content_Description_Object:
+ // Content Description Object: (optional, one only)
+ // Field Name Field Type Size (bits)
+ // Object ID GUID 128 // GUID for Content Description object - GETID3_ASF_Content_Description_Object
+ // Object Size QWORD 64 // size of Content Description object, including 34 bytes of Content Description Object header
+ // Title Length WORD 16 // number of bytes in Title field
+ // Author Length WORD 16 // number of bytes in Author field
+ // Copyright Length WORD 16 // number of bytes in Copyright field
+ // Description Length WORD 16 // number of bytes in Description field
+ // Rating Length WORD 16 // number of bytes in Rating field
+ // Title WCHAR 16 // array of Unicode characters - Title
+ $this->warning('extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')');
+ if (isset($thisfile_asf['stream_properties_object']) && is_array($thisfile_asf['stream_properties_object'])) {
+
+ $thisfile_audio['bitrate'] = 0;
+ $thisfile_video['bitrate'] = 0;
+
+ foreach ($thisfile_asf['stream_properties_object'] as $streamnumber => $streamdata) {
+
+ switch ($streamdata['stream_type']) {
+ case GETID3_ASF_Audio_Media:
+ // Field Name Field Type Size (bits)
+ // Codec ID / Format Tag WORD 16 // unique ID of audio codec - defined as wFormatTag field of WAVEFORMATEX structure
+ // Number of Channels WORD 16 // number of channels of audio - defined as nChannels field of WAVEFORMATEX structure
+ // Samples Per Second DWORD 32 // in Hertz - defined as nSamplesPerSec field of WAVEFORMATEX structure
+ // Average number of Bytes/sec DWORD 32 // bytes/sec of audio stream - defined as nAvgBytesPerSec field of WAVEFORMATEX structure
+ // Block Alignment WORD 16 // block size in bytes of audio codec - defined as nBlockAlign field of WAVEFORMATEX structure
+ // Bits per sample WORD 16 // bits per sample of mono data. set to zero for variable bitrate codecs. defined as wBitsPerSample field of WAVEFORMATEX structure
+ // Codec Specific Data Size WORD 16 // size in bytes of Codec Specific Data buffer - defined as cbSize field of WAVEFORMATEX structure
+ // Codec Specific Data BYTESTREAM variable // array of codec-specific data bytes
+ // Encoded Image Width DWORD 32 // width of image in pixels
+ // Encoded Image Height DWORD 32 // height of image in pixels
+ // Reserved Flags BYTE 8 // hardcoded: 0x02
+ // Format Data Size WORD 16 // size of Format Data field in bytes
+ // Format Data array of: variable //
+ // * Format Data Size DWORD 32 // number of bytes in Format Data field, in bytes - defined as biSize field of BITMAPINFOHEADER structure
+ // * Image Width LONG 32 // width of encoded image in pixels - defined as biWidth field of BITMAPINFOHEADER structure
+ // * Image Height LONG 32 // height of encoded image in pixels - defined as biHeight field of BITMAPINFOHEADER structure
+ // * Reserved WORD 16 // hardcoded: 0x0001 - defined as biPlanes field of BITMAPINFOHEADER structure
+ // * Bits Per Pixel Count WORD 16 // bits per pixel - defined as biBitCount field of BITMAPINFOHEADER structure
+ // * Compression ID FOURCC 32 // fourcc of video codec - defined as biCompression field of BITMAPINFOHEADER structure
+ // * Image Size DWORD 32 // image size in bytes - defined as biSizeImage field of BITMAPINFOHEADER structure
+ // * Horizontal Pixels / Meter DWORD 32 // horizontal resolution of target device in pixels per meter - defined as biXPelsPerMeter field of BITMAPINFOHEADER structure
+ // * Vertical Pixels / Meter DWORD 32 // vertical resolution of target device in pixels per meter - defined as biYPelsPerMeter field of BITMAPINFOHEADER structure
+ // * Colors Used Count DWORD 32 // number of color indexes in the color table that are actually used - defined as biClrUsed field of BITMAPINFOHEADER structure
+ // * Important Colors Count DWORD 32 // number of color index required for displaying bitmap. if zero, all colors are required. defined as biClrImportant field of BITMAPINFOHEADER structure
+ // * Codec Specific Data BYTESTREAM variable // array of codec-specific data bytes
+ // 6.2 ASF top-level Index Object (optional but recommended when appropriate, 0 or 1)
+ // Field Name Field Type Size (bits)
+ // Object ID GUID 128 // GUID for the Index Object - GETID3_ASF_Index_Object
+ // Object Size QWORD 64 // Specifies the size, in bytes, of the Index Object, including at least 34 bytes of Index Object header
+ // Index Entry Time Interval DWORD 32 // Specifies the time interval between each index entry in ms.
+ // Index Specifiers Count WORD 16 // Specifies the number of Index Specifiers structures in this Index Object.
+ // Index Blocks Count DWORD 32 // Specifies the number of Index Blocks structures in this Index Object.
+
+ // Index Entry Time Interval DWORD 32 // Specifies the time interval between index entries in milliseconds. This value cannot be 0.
+ // Index Specifiers Count WORD 16 // Specifies the number of entries in the Index Specifiers list. Valid values are 1 and greater.
+ // Index Specifiers array of: varies //
+ // * Stream Number WORD 16 // Specifies the stream number that the Index Specifiers refer to. Valid values are between 1 and 127.
+ // * Index Type WORD 16 // Specifies Index Type values as follows:
+ // 1 = Nearest Past Data Packet - indexes point to the data packet whose presentation time is closest to the index entry time.
+ // 2 = Nearest Past Media Object - indexes point to the closest data packet containing an entire object or first fragment of an object.
+ // 3 = Nearest Past Cleanpoint. - indexes point to the closest data packet containing an entire object (or first fragment of an object) that has the Cleanpoint Flag set.
+ // Nearest Past Cleanpoint is the most common type of index.
+ // Index Entry Count DWORD 32 // Specifies the number of Index Entries in the block.
+ // * Block Positions QWORD varies // Specifies a list of byte offsets of the beginnings of the blocks relative to the beginning of the first Data Packet (i.e., the beginning of the Data Object + 50 bytes). The number of entries in this list is specified by the value of the Index Specifiers Count field. The order of those byte offsets is tied to the order in which Index Specifiers are listed.
+ // * Index Entries array of: varies //
+ // * * Offsets DWORD varies // An offset value of 0xffffffff indicates an invalid offset value
+ case GETID3_ASF_Timecode_Index_Parameters_Object:
+ // 4.11 Timecode Index Parameters Object (mandatory only if TIMECODE index is present in file, 0 or 1)
+ // Field name Field type Size (bits)
+ // Object ID GUID 128 // GUID for the Timecode Index Parameters Object - ASF_Timecode_Index_Parameters_Object
+ // Object Size QWORD 64 // Specifies the size, in bytes, of the Timecode Index Parameters Object. Valid values are at least 34 bytes.
+ // Index Entry Count Interval DWORD 32 // This value is ignored for the Timecode Index Parameters Object.
+ // Index Specifiers Count WORD 16 // Specifies the number of entries in the Index Specifiers list. Valid values are 1 and greater.
+ // Index Specifiers array of: varies //
+ // * Stream Number WORD 16 // Specifies the stream number that the Index Specifiers refer to. Valid values are between 1 and 127.
+ // * Index Type WORD 16 // Specifies the type of index. Values are defined as follows (1 is not a valid value):
+ // 2 = Nearest Past Media Object - indexes point to the closest data packet containing an entire video frame or the first fragment of a video frame
+ // 3 = Nearest Past Cleanpoint - indexes point to the closest data packet containing an entire video frame (or first fragment of a video frame) that is a key frame.
+ // Nearest Past Media Object is the most common value
+ if ($info['flv']['header']['signature'] != self::magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"');
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+ exit;
+}
+
+define('EBML_ID_CHAPTERS', 0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation.
+define('EBML_ID_SEEKHEAD', 0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements.
+define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found <http://www.matroska.org/technical/specs/tagging/index.html>.
+define('EBML_ID_INFO', 0x0549A966); // [15][49][A9][66] -- Contains miscellaneous general information and statistics on the file.
+define('EBML_ID_TRACKS', 0x0654AE6B); // [16][54][AE][6B] -- A top-level block of information with many tracks described.
+define('EBML_ID_SEGMENT', 0x08538067); // [18][53][80][67] -- This element contains all other top-level (level 1) elements. Typically a Matroska file is composed of 1 segment.
+define('EBML_ID_EBML', 0x0A45DFA3); // [1A][45][DF][A3] -- Set the EBML characteristics of the data to follow. Each EBML document has to start with this.
+define('EBML_ID_CUES', 0x0C53BB6B); // [1C][53][BB][6B] -- A top-level element to speed seeking access. All entries are local to the segment.
+define('EBML_ID_CLUSTER', 0x0F43B675); // [1F][43][B6][75] -- The lower level element containing the (monolithic) Block structure.
+define('EBML_ID_LANGUAGE', 0x02B59C); // [22][B5][9C] -- Specifies the language of the track in the Matroska languages form.
+define('EBML_ID_TRACKTIMECODESCALE', 0x03314F); // [23][31][4F] -- The scale to apply on this track to work at normal speed in relation with other tracks (mostly used to adjust video speed when the audio length differs).
+define('EBML_ID_DEFAULTDURATION', 0x03E383); // [23][E3][83] -- Number of nanoseconds (i.e. not scaled) per frame.
+define('EBML_ID_CODECNAME', 0x058688); // [25][86][88] -- A human-readable string specifying the codec.
+define('EBML_ID_CODECDOWNLOADURL', 0x06B240); // [26][B2][40] -- A URL to download about the codec used.
+define('EBML_ID_TIMECODESCALE', 0x0AD7B1); // [2A][D7][B1] -- Timecode scale in nanoseconds (1.000.000 means all timecodes in the segment are expressed in milliseconds).
+define('EBML_ID_COLOURSPACE', 0x0EB524); // [2E][B5][24] -- Same value as in AVI (32 bits).
+define('EBML_ID_CODECSETTINGS', 0x1A9697); // [3A][96][97] -- A string describing the encoding setting used.
+define('EBML_ID_CODECINFOURL', 0x1B4040); // [3B][40][40] -- A URL to find information about the codec used.
+define('EBML_ID_PREVFILENAME', 0x1C83AB); // [3C][83][AB] -- An escaped filename corresponding to the previous segment.
+define('EBML_ID_PREVUID', 0x1CB923); // [3C][B9][23] -- A unique ID to identify the previous chained segment (128 bits).
+define('EBML_ID_NEXTFILENAME', 0x1E83BB); // [3E][83][BB] -- An escaped filename corresponding to the next segment.
+define('EBML_ID_NEXTUID', 0x1EB923); // [3E][B9][23] -- A unique ID to identify the next chained segment (128 bits).
+define('EBML_ID_CONTENTCOMPALGO', 0x0254); // [42][54] -- The compression algorithm used. Algorithms that have been specified so far are:
+define('EBML_ID_CONTENTCOMPSETTINGS', 0x0255); // [42][55] -- Settings that might be needed by the decompressor. For Header Stripping (ContentCompAlgo=3), the bytes that were removed from the beggining of each frames of the track.
+define('EBML_ID_DOCTYPE', 0x0282); // [42][82] -- A string that describes the type of document that follows this EBML header ('matroska' in our case).
+define('EBML_ID_DOCTYPEREADVERSION', 0x0285); // [42][85] -- The minimum DocType version an interpreter has to support to read this file.
+define('EBML_ID_EBMLVERSION', 0x0286); // [42][86] -- The version of EBML parser used to create the file.
+define('EBML_ID_DOCTYPEVERSION', 0x0287); // [42][87] -- The version of DocType interpreter used to create the file.
+define('EBML_ID_EBMLMAXIDLENGTH', 0x02F2); // [42][F2] -- The maximum length of the IDs you'll find in this file (4 or less in Matroska).
+define('EBML_ID_EBMLMAXSIZELENGTH', 0x02F3); // [42][F3] -- The maximum length of the sizes you'll find in this file (8 or less in Matroska). This does not override the element size indicated at the beginning of an element. Elements that have an indicated size which is larger than what is allowed by EBMLMaxSizeLength shall be considered invalid.
+define('EBML_ID_EBMLREADVERSION', 0x02F7); // [42][F7] -- The minimum EBML version a parser has to support to read this file.
+define('EBML_ID_CHAPLANGUAGE', 0x037C); // [43][7C] -- The languages corresponding to the string, in the bibliographic ISO-639-2 form.
+define('EBML_ID_CHAPCOUNTRY', 0x037E); // [43][7E] -- The countries corresponding to the string, same 2 octets as in Internet domains.
+define('EBML_ID_SEGMENTFAMILY', 0x0444); // [44][44] -- A randomly generated unique ID that all segments related to each other must use (128 bits).
+define('EBML_ID_DATEUTC', 0x0461); // [44][61] -- Date of the origin of timecode (value 0), i.e. production date.
+define('EBML_ID_TAGLANGUAGE', 0x047A); // [44][7A] -- Specifies the language of the tag specified, in the Matroska languages form.
+define('EBML_ID_TAGDEFAULT', 0x0484); // [44][84] -- Indication to know if this is the default/original language to use for the given tag.
+define('EBML_ID_TAGBINARY', 0x0485); // [44][85] -- The values of the Tag if it is binary. Note that this cannot be used in the same SimpleTag as TagString.
+define('EBML_ID_TAGSTRING', 0x0487); // [44][87] -- The value of the Tag.
+define('EBML_ID_DURATION', 0x0489); // [44][89] -- Duration of the segment (based on TimecodeScale).
+define('EBML_ID_CHAPPROCESSPRIVATE', 0x050D); // [45][0D] -- Some optional data attached to the ChapProcessCodecID information. For ChapProcessCodecID = 1, it is the "DVD level" equivalent.
+define('EBML_ID_CHAPTERFLAGENABLED', 0x0598); // [45][98] -- Specify wether the chapter is enabled. It can be enabled/disabled by a Control Track. When disabled, the movie should skip all the content between the TimeStart and TimeEnd of this chapter.
+define('EBML_ID_TAGNAME', 0x05A3); // [45][A3] -- The name of the Tag that is going to be stored.
+define('EBML_ID_EDITIONENTRY', 0x05B9); // [45][B9] -- Contains all information about a segment edition.
+define('EBML_ID_EDITIONUID', 0x05BC); // [45][BC] -- A unique ID to identify the edition. It's useful for tagging an edition.
+define('EBML_ID_EDITIONFLAGHIDDEN', 0x05BD); // [45][BD] -- If an edition is hidden (1), it should not be available to the user interface (but still to Control Tracks).
+define('EBML_ID_EDITIONFLAGDEFAULT', 0x05DB); // [45][DB] -- If a flag is set (1) the edition should be used as the default one.
+define('EBML_ID_EDITIONFLAGORDERED', 0x05DD); // [45][DD] -- Specify if the chapters can be defined multiple times and the order to play them is enforced.
+define('EBML_ID_FILEDATA', 0x065C); // [46][5C] -- The data of the file.
+define('EBML_ID_FILEMIMETYPE', 0x0660); // [46][60] -- MIME type of the file.
+define('EBML_ID_FILENAME', 0x066E); // [46][6E] -- Filename of the attached file.
+define('EBML_ID_FILEREFERRAL', 0x0675); // [46][75] -- A binary value that a track/codec can refer to when the attachment is needed.
+define('EBML_ID_FILEDESCRIPTION', 0x067E); // [46][7E] -- A human-friendly name for the attached file.
+define('EBML_ID_FILEUID', 0x06AE); // [46][AE] -- Unique ID representing the file, as random as possible.
+define('EBML_ID_CONTENTENCALGO', 0x07E1); // [47][E1] -- The encryption algorithm used. The value '0' means that the contents have not been encrypted but only signed. Predefined values:
+define('EBML_ID_CONTENTENCKEYID', 0x07E2); // [47][E2] -- For public key algorithms this is the ID of the public key the data was encrypted with.
+define('EBML_ID_CONTENTSIGNATURE', 0x07E3); // [47][E3] -- A cryptographic signature of the contents.
+define('EBML_ID_CONTENTSIGKEYID', 0x07E4); // [47][E4] -- This is the ID of the private key the data was signed with.
+define('EBML_ID_CONTENTSIGALGO', 0x07E5); // [47][E5] -- The algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values:
+define('EBML_ID_CONTENTSIGHASHALGO', 0x07E6); // [47][E6] -- The hash algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values:
+define('EBML_ID_MUXINGAPP', 0x0D80); // [4D][80] -- Muxing application or library ("libmatroska-0.4.3").
+define('EBML_ID_SEEK', 0x0DBB); // [4D][BB] -- Contains a single seek entry to an EBML element.
+define('EBML_ID_CONTENTENCODINGORDER', 0x1031); // [50][31] -- Tells when this modification was used during encoding/muxing starting with 0 and counting upwards. The decoder/demuxer has to start with the highest order number it finds and work its way down. This value has to be unique over all ContentEncodingOrder elements in the segment.
+define('EBML_ID_CONTENTENCODINGSCOPE', 0x1032); // [50][32] -- A bit field that describes which elements have been modified in this way. Values (big endian) can be OR'ed. Possible values:
+define('EBML_ID_CONTENTENCODINGTYPE', 0x1033); // [50][33] -- A value describing what kind of transformation has been done. Possible values:
+define('EBML_ID_CONTENTCOMPRESSION', 0x1034); // [50][34] -- Settings describing the compression used. Must be present if the value of ContentEncodingType is 0 and absent otherwise. Each block must be decompressable even if no previous block is available in order not to prevent seeking.
+define('EBML_ID_CONTENTENCRYPTION', 0x1035); // [50][35] -- Settings describing the encryption used. Must be present if the value of ContentEncodingType is 1 and absent otherwise.
+define('EBML_ID_CUEREFNUMBER', 0x135F); // [53][5F] -- Number of the referenced Block of Track X in the specified Cluster.
+define('EBML_ID_NAME', 0x136E); // [53][6E] -- A human-readable track name.
+define('EBML_ID_CUEBLOCKNUMBER', 0x1378); // [53][78] -- Number of the Block in the specified Cluster.
+define('EBML_ID_TRACKOFFSET', 0x137F); // [53][7F] -- A value to add to the Block's Timecode. This can be used to adjust the playback offset of a track.
+define('EBML_ID_SEEKID', 0x13AB); // [53][AB] -- The binary ID corresponding to the element name.
+define('EBML_ID_SEEKPOSITION', 0x13AC); // [53][AC] -- The position of the element in the segment in octets (0 = first level 1 element).
+define('EBML_ID_STEREOMODE', 0x13B8); // [53][B8] -- Stereo-3D video mode.
+define('EBML_ID_OLDSTEREOMODE', 0x13B9); // [53][B9] -- Bogus StereoMode value used in old versions of libmatroska. DO NOT USE. (0: mono, 1: right eye, 2: left eye, 3: both eyes).
+define('EBML_ID_PIXELCROPBOTTOM', 0x14AA); // [54][AA] -- The number of video pixels to remove at the bottom of the image (for HDTV content).
+define('EBML_ID_DISPLAYWIDTH', 0x14B0); // [54][B0] -- Width of the video frames to display.
+define('EBML_ID_DISPLAYUNIT', 0x14B2); // [54][B2] -- Type of the unit for DisplayWidth/Height (0: pixels, 1: centimeters, 2: inches).
+define('EBML_ID_ASPECTRATIOTYPE', 0x14B3); // [54][B3] -- Specify the possible modifications to the aspect ratio (0: free resizing, 1: keep aspect ratio, 2: fixed).
+define('EBML_ID_DISPLAYHEIGHT', 0x14BA); // [54][BA] -- Height of the video frames to display.
+define('EBML_ID_PIXELCROPTOP', 0x14BB); // [54][BB] -- The number of video pixels to remove at the top of the image.
+define('EBML_ID_PIXELCROPLEFT', 0x14CC); // [54][CC] -- The number of video pixels to remove on the left of the image.
+define('EBML_ID_PIXELCROPRIGHT', 0x14DD); // [54][DD] -- The number of video pixels to remove on the right of the image.
+define('EBML_ID_FLAGFORCED', 0x15AA); // [55][AA] -- Set if that track MUST be used during playback. There can be many forced track for a kind (audio, video or subs), the player should select the one which language matches the user preference or the default + forced track. Overlay MAY happen between a forced and non-forced track of the same kind.
+define('EBML_ID_MAXBLOCKADDITIONID', 0x15EE); // [55][EE] -- The maximum value of BlockAddID. A value 0 means there is no BlockAdditions for this track.
+define('EBML_ID_CLUSTERSILENTTRACKS', 0x1854); // [58][54] -- The list of tracks that are not used in that part of the stream. It is useful when using overlay tracks on seeking. Then you should decide what track to use.
+define('EBML_ID_CLUSTERSILENTTRACKNUMBER', 0x18D7); // [58][D7] -- One of the track number that are not used from now on in the stream. It could change later if not specified as silent in a further Cluster.
+define('EBML_ID_ATTACHEDFILE', 0x21A7); // [61][A7] -- An attached file.
+define('EBML_ID_CONTENTENCODING', 0x2240); // [62][40] -- Settings for one content encoding like compression or encryption.
+define('EBML_ID_BITDEPTH', 0x2264); // [62][64] -- Bits per sample, mostly used for PCM.
+define('EBML_ID_CODECPRIVATE', 0x23A2); // [63][A2] -- Private data only known to the codec.
+define('EBML_ID_TARGETS', 0x23C0); // [63][C0] -- Contain all UIDs where the specified meta data apply. It is void to describe everything in the segment.
+define('EBML_ID_CHAPTERPHYSICALEQUIV', 0x23C3); // [63][C3] -- Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see complete list of values.
+define('EBML_ID_TAGCHAPTERUID', 0x23C4); // [63][C4] -- A unique ID to identify the Chapter(s) the tags belong to. If the value is 0 at this level, the tags apply to all chapters in the Segment.
+define('EBML_ID_TAGTRACKUID', 0x23C5); // [63][C5] -- A unique ID to identify the Track(s) the tags belong to. If the value is 0 at this level, the tags apply to all tracks in the Segment.
+define('EBML_ID_TAGATTACHMENTUID', 0x23C6); // [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment.
+define('EBML_ID_TAGEDITIONUID', 0x23C9); // [63][C9] -- A unique ID to identify the EditionEntry(s) the tags belong to. If the value is 0 at this level, the tags apply to all editions in the Segment.
+define('EBML_ID_TARGETTYPE', 0x23CA); // [63][CA] -- An informational string that can be used to display the logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see TargetType).
+define('EBML_ID_TRACKTRANSLATE', 0x2624); // [66][24] -- The track identification for the given Chapter Codec.
+define('EBML_ID_TRACKTRANSLATETRACKID', 0x26A5); // [66][A5] -- The binary value used to represent this track in the chapter codec data. The format depends on the ChapProcessCodecID used.
+define('EBML_ID_TRACKTRANSLATECODEC', 0x26BF); // [66][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu).
+define('EBML_ID_TRACKTRANSLATEEDITIONUID', 0x26FC); // [66][FC] -- Specify an edition UID on which this translation applies. When not specified, it means for all editions found in the segment.
+define('EBML_ID_SIMPLETAG', 0x27C8); // [67][C8] -- Contains general information about the target.
+define('EBML_ID_TARGETTYPEVALUE', 0x28CA); // [68][CA] -- A number to indicate the logical level of the target (see TargetType).
+define('EBML_ID_CHAPPROCESSCOMMAND', 0x2911); // [69][11] -- Contains all the commands associated to the Atom.
+define('EBML_ID_CHAPPROCESSTIME', 0x2922); // [69][22] -- Defines when the process command should be handled (0: during the whole chapter, 1: before starting playback, 2: after playback of the chapter).
+define('EBML_ID_CHAPTERTRANSLATE', 0x2924); // [69][24] -- A tuple of corresponding ID used by chapter codecs to represent this segment.
+define('EBML_ID_CHAPPROCESSDATA', 0x2933); // [69][33] -- Contains the command information. The data should be interpreted depending on the ChapProcessCodecID value. For ChapProcessCodecID = 1, the data correspond to the binary DVD cell pre/post commands.
+define('EBML_ID_CHAPPROCESS', 0x2944); // [69][44] -- Contains all the commands associated to the Atom.
+define('EBML_ID_CHAPPROCESSCODECID', 0x2955); // [69][55] -- Contains the type of the codec used for the processing. A value of 0 means native Matroska processing (to be defined), a value of 1 means the DVD command set is used. More codec IDs can be added later.
+define('EBML_ID_CHAPTERTRANSLATEID', 0x29A5); // [69][A5] -- The binary value used to represent this segment in the chapter codec data. The format depends on the ChapProcessCodecID used.
+define('EBML_ID_CHAPTERTRANSLATECODEC', 0x29BF); // [69][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu).
+define('EBML_ID_CHAPTERTRANSLATEEDITIONUID', 0x29FC); // [69][FC] -- Specify an edition UID on which this correspondance applies. When not specified, it means for all editions found in the segment.
+define('EBML_ID_CONTENTENCODINGS', 0x2D80); // [6D][80] -- Settings for several content encoding mechanisms like compression or encryption.
+define('EBML_ID_MINCACHE', 0x2DE7); // [6D][E7] -- The minimum number of frames a player should be able to cache during playback. If set to 0, the reference pseudo-cache system is not used.
+define('EBML_ID_MAXCACHE', 0x2DF8); // [6D][F8] -- The maximum cache size required to store referenced frames in and the current frame. 0 means no cache is needed.
+define('EBML_ID_CHAPTERSEGMENTUID', 0x2E67); // [6E][67] -- A segment to play in place of this chapter. Edition ChapterSegmentEditionUID should be used for this segment, otherwise no edition is used.
+define('EBML_ID_CHAPTERSEGMENTEDITIONUID', 0x2EBC); // [6E][BC] -- The edition to play from the segment linked in ChapterSegmentUID.
+define('EBML_ID_TRACKOVERLAY', 0x2FAB); // [6F][AB] -- Specify that this track is an overlay track for the Track specified (in the u-integer). That means when this track has a gap (see SilentTracks) the overlay track should be used instead. The order of multiple TrackOverlay matters, the first one is the one that should be used. If not found it should be the second, etc.
+define('EBML_ID_TAG', 0x3373); // [73][73] -- Element containing elements specific to Tracks/Chapters.
+define('EBML_ID_SEGMENTFILENAME', 0x3384); // [73][84] -- A filename corresponding to this segment.
+define('EBML_ID_SEGMENTUID', 0x33A4); // [73][A4] -- A randomly generated unique ID to identify the current segment between many others (128 bits).
+define('EBML_ID_CHAPTERUID', 0x33C4); // [73][C4] -- A unique ID to identify the Chapter.
+define('EBML_ID_TRACKUID', 0x33C5); // [73][C5] -- A unique ID to identify the Track. This should be kept the same when making a direct stream copy of the Track to another file.
+define('EBML_ID_ATTACHMENTLINK', 0x3446); // [74][46] -- The UID of an attachment that is used by this codec.
+define('EBML_ID_CLUSTERBLOCKADDITIONS', 0x35A1); // [75][A1] -- Contain additional blocks to complete the main one. An EBML parser that has no knowledge of the Block structure could still see and use/skip these data.
+define('EBML_ID_CHANNELPOSITIONS', 0x347B); // [7D][7B] -- Table of horizontal angles for each successive channel, see appendix.
+define('EBML_ID_OUTPUTSAMPLINGFREQUENCY', 0x38B5); // [78][B5] -- Real output sampling frequency in Hz (used for SBR techniques).
+define('EBML_ID_TITLE', 0x3BA9); // [7B][A9] -- General name of the segment.
+define('EBML_ID_CHAPTERDISPLAY', 0x00); // [80] -- Contains all possible strings to use for the chapter display.
+define('EBML_ID_TRACKTYPE', 0x03); // [83] -- A set of track types coded on 8 bits (1: video, 2: audio, 3: complex, 0x10: logo, 0x11: subtitle, 0x12: buttons, 0x20: control).
+define('EBML_ID_CHAPSTRING', 0x05); // [85] -- Contains the string to use as the chapter atom.
+define('EBML_ID_CODECID', 0x06); // [86] -- An ID corresponding to the codec, see the codec page for more info.
+define('EBML_ID_FLAGDEFAULT', 0x08); // [88] -- Set if that track (audio, video or subs) SHOULD be used if no language found matches the user preference.
+define('EBML_ID_CHAPTERTRACKNUMBER', 0x09); // [89] -- UID of the Track to apply this chapter too. In the absense of a control track, choosing this chapter will select the listed Tracks and deselect unlisted tracks. Absense of this element indicates that the Chapter should be applied to any currently used Tracks.
+define('EBML_ID_CHAPTERTRACK', 0x0F); // [8F] -- List of tracks on which the chapter applies. If this element is not present, all tracks apply
+define('EBML_ID_CHAPTERTIMESTART', 0x11); // [91] -- Timecode of the start of Chapter (not scaled).
+define('EBML_ID_CHAPTERTIMEEND', 0x12); // [92] -- Timecode of the end of Chapter (timecode excluded, not scaled).
+define('EBML_ID_CUEREFTIME', 0x16); // [96] -- Timecode of the referenced Block.
+define('EBML_ID_CUEREFCLUSTER', 0x17); // [97] -- Position of the Cluster containing the referenced Block.
+define('EBML_ID_CHAPTERFLAGHIDDEN', 0x18); // [98] -- If a chapter is hidden (1), it should not be available to the user interface (but still to Control Tracks).
+define('EBML_ID_FLAGINTERLACED', 0x1A); // [9A] -- Set if the video is interlaced.
+define('EBML_ID_CLUSTERBLOCKDURATION', 0x1B); // [9B] -- The duration of the Block (based on TimecodeScale). This element is mandatory when DefaultDuration is set for the track. When not written and with no DefaultDuration, the value is assumed to be the difference between the timecode of this Block and the timecode of the next Block in "display" order (not coding order). This element can be useful at the end of a Track (as there is not other Block available), or when there is a break in a track like for subtitle tracks.
+define('EBML_ID_FLAGLACING', 0x1C); // [9C] -- Set if the track may contain blocks using lacing.
+define('EBML_ID_CHANNELS', 0x1F); // [9F] -- Numbers of channels in the track.
+define('EBML_ID_CLUSTERBLOCKGROUP', 0x20); // [A0] -- Basic container of information containing a single Block or BlockVirtual, and information specific to that Block/VirtualBlock.
+define('EBML_ID_CLUSTERBLOCK', 0x21); // [A1] -- Block containing the actual data to be rendered and a timecode relative to the Cluster Timecode.
+define('EBML_ID_CLUSTERBLOCKVIRTUAL', 0x22); // [A2] -- A Block with no data. It must be stored in the stream at the place the real Block should be in display order.
+define('EBML_ID_CLUSTERSIMPLEBLOCK', 0x23); // [A3] -- Similar to Block but without all the extra information, mostly used to reduced overhead when no extra feature is needed.
+define('EBML_ID_CLUSTERCODECSTATE', 0x24); // [A4] -- The new codec state to use. Data interpretation is private to the codec. This information should always be referenced by a seek entry.
+define('EBML_ID_CLUSTERBLOCKADDITIONAL', 0x25); // [A5] -- Interpreted by the codec as it wishes (using the BlockAddID).
+define('EBML_ID_CLUSTERBLOCKMORE', 0x26); // [A6] -- Contain the BlockAdditional and some parameters.
+define('EBML_ID_CLUSTERPOSITION', 0x27); // [A7] -- Position of the Cluster in the segment (0 in live broadcast streams). It might help to resynchronise offset on damaged streams.
+define('EBML_ID_CODECDECODEALL', 0x2A); // [AA] -- The codec can decode potentially damaged data.
+define('EBML_ID_CLUSTERPREVSIZE', 0x2B); // [AB] -- Size of the previous Cluster, in octets. Can be useful for backward playing.
+define('EBML_ID_TRACKENTRY', 0x2E); // [AE] -- Describes a track with all elements.
+define('EBML_ID_CLUSTERENCRYPTEDBLOCK', 0x2F); // [AF] -- Similar to SimpleBlock but the data inside the Block are Transformed (encrypt and/or signed).
+define('EBML_ID_PIXELWIDTH', 0x30); // [B0] -- Width of the encoded video frames in pixels.
+define('EBML_ID_CUETIME', 0x33); // [B3] -- Absolute timecode according to the segment time base.
+define('EBML_ID_SAMPLINGFREQUENCY', 0x35); // [B5] -- Sampling frequency in Hz.
+define('EBML_ID_CHAPTERATOM', 0x36); // [B6] -- Contains the atom information to use as the chapter atom (apply to all tracks).
+define('EBML_ID_CUETRACKPOSITIONS', 0x37); // [B7] -- Contain positions for different tracks corresponding to the timecode.
+define('EBML_ID_FLAGENABLED', 0x39); // [B9] -- Set if the track is used.
+define('EBML_ID_PIXELHEIGHT', 0x3A); // [BA] -- Height of the encoded video frames in pixels.
+define('EBML_ID_CUEPOINT', 0x3B); // [BB] -- Contains all information relative to a seek point in the segment.
+define('EBML_ID_CRC32', 0x3F); // [BF] -- The CRC is computed on all the data of the Master element it's in, regardless of its position. It's recommended to put the CRC value at the beggining of the Master element for easier reading. All level 1 elements should include a CRC-32.
+define('EBML_ID_CLUSTERBLOCKADDITIONID', 0x4B); // [CB] -- The ID of the BlockAdditional element (0 is the main Block).
+define('EBML_ID_CLUSTERLACENUMBER', 0x4C); // [CC] -- The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc). While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback.
+define('EBML_ID_CLUSTERFRAMENUMBER', 0x4D); // [CD] -- The number of the frame to generate from this lace with this delay (allow you to generate many frames from the same Block/Frame).
+define('EBML_ID_CLUSTERDELAY', 0x4E); // [CE] -- The (scaled) delay to apply to the element.
+define('EBML_ID_CLUSTERDURATION', 0x4F); // [CF] -- The (scaled) duration to apply to the element.
+define('EBML_ID_TRACKNUMBER', 0x57); // [D7] -- The track number as used in the Block Header (using more than 127 tracks is not encouraged, though the design allows an unlimited number).
+define('EBML_ID_CUEREFERENCE', 0x5B); // [DB] -- The Clusters containing the required referenced Blocks.
+define('EBML_ID_VIDEO', 0x60); // [E0] -- Video settings.
+define('EBML_ID_CLUSTERTIMESLICE', 0x68); // [E8] -- Contains extra time information about the data contained in the Block. While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback.
+define('EBML_ID_CUECODECSTATE', 0x6A); // [EA] -- The position of the Codec State corresponding to this Cue element. 0 means that the data is taken from the initial Track Entry.
+define('EBML_ID_CUEREFCODECSTATE', 0x6B); // [EB] -- The position of the Codec State corresponding to this referenced element. 0 means that the data is taken from the initial Track Entry.
+define('EBML_ID_VOID', 0x6C); // [EC] -- Used to void damaged data, to avoid unexpected behaviors when using damaged data. The content is discarded. Also used to reserve space in a sub-element for later use.
+define('EBML_ID_CLUSTERTIMECODE', 0x67); // [E7] -- Absolute timecode of the cluster (based on TimecodeScale).
+define('EBML_ID_CLUSTERBLOCKADDID', 0x6E); // [EE] -- An ID to identify the BlockAdditional level.
+define('EBML_ID_CUECLUSTERPOSITION', 0x71); // [F1] -- The position of the Cluster containing the required Block.
+define('EBML_ID_CUETRACK', 0x77); // [F7] -- The track for which a position is given.
+define('EBML_ID_CLUSTERREFERENCEPRIORITY', 0x7A); // [FA] -- This frame is referenced and has the specified cache priority. In cache only a frame of the same or higher priority can replace this frame. A value of 0 means the frame is not referenced.
+define('EBML_ID_CLUSTERREFERENCEBLOCK', 0x7B); // [FB] -- Timecode of another frame used as a reference (ie: B or P frame). The timecode is relative to the block it's attached to.
+define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- Relative position of the data that should be in position of the virtual block.
+ if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) {
+ $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set');
+ if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) {
+ foreach ($getid3_temp->info['audio'] as $sub_key => $value) {
+ $track_info[$sub_key] = $value;
+ }
+ }
+ }
+ else {
+ $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because '.$class.'::Analyze() failed at offset '.$getid3_temp->info['avdataoffset']);
+ }
+
+ // copy errors and warnings
+ if (!empty($getid3_temp->info['error'])) {
+ foreach ($getid3_temp->info['error'] as $newerror) {
+ private function unhandledElement($type, $line, $element) {
+ // warn only about unknown and missed elements, not about unuseful
+ if (!in_array($element['id'], $this->unuseful_elements)) {
+ $this->warning('Unhandled '.$type.' element ['.basename(__FILE__).':'.$line.'] ('.$element['id'].'::'.$element['id_name'].' ['.$element['length'].' bytes]) at '.$element['offset']);
+ }
+
+ // increase offset for unparsed elements
+ if (!isset($element['data'])) {
+ $this->current_offset = $element['end'];
+ }
+ }
+
+ /**
+ * @param array $SimpleTagArray
+ *
+ * @return bool
+ */
+ private function ExtractCommentsSimpleTag($SimpleTagArray) {
+ if (!empty($SimpleTagArray['SimpleTag'])) {
+ foreach ($SimpleTagArray['SimpleTag'] as $SimpleTagKey => $SimpleTagData) {
+ if (!empty($SimpleTagData['TagName']) && !empty($SimpleTagData['TagString'])) {
+ $block_data['lace_frames'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)) + 1; // Number of frames in the lace-1 (uint8)
+ if ($block_data['flags']['lacing'] != 0x02) {
+ for ($i = 1; $i < $block_data['lace_frames']; $i ++) { // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace).
+ if ($block_data['flags']['lacing'] == 0x03) { // EBML lacing
+ $block_data['lace_frames_size'][$i] = $this->readEBMLint(); // TODO: read size correctly, calc size for the last frame. For now offsets are deteminded OK with readEBMLint() and that's the most important thing.
+ if ($block_data['flags']['lacing'] == 0x01) { // calc size of the last frame only for Xiph lacing, till EBML sizes are now anyway determined incorrectly
+ $TargetTypeValue[10] = 'A: ~ V:shot'; // the lowest hierarchy found in music or movies
+ $TargetTypeValue[20] = 'A:subtrack/part/movement ~ V:scene'; // corresponds to parts of a track for audio (like a movement)
+ $TargetTypeValue[30] = 'A:track/song ~ V:chapter'; // the common parts of an album or a movie
+ $TargetTypeValue[40] = 'A:part/session ~ V:part/session'; // when an album or episode has different logical parts
+ $TargetTypeValue[50] = 'A:album/opera/concert ~ V:movie/episode/concert'; // the most common grouping level of music and video (equals to an episode for TV series)
+ $TargetTypeValue[60] = 'A:edition/issue/volume/opus ~ V:season/sequel/volume'; // a list of lower levels grouped together
+ $TargetTypeValue[70] = 'A:collection ~ V:collection'; // the high hierarchy consisting of many different lower items
+ $PictureHeader['temporal_reference'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 10); // 10-bit unsigned integer associated with each input picture. It is incremented by one, modulo 1024, for each input frame. When a frame is coded as two fields the temporal reference in the picture header of both fields is the same. Following a group start header the temporal reference of the earliest picture (in display order) shall be reset to zero.
+ $info['mpeg']['video']['raw']['horizontal_size_value'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for horizontal frame size. Note: horizontal_size_extension, if present, will add 2 most-significant bits to this value
+ $info['mpeg']['video']['raw']['vertical_size_value'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for vertical frame size. Note: vertical_size_extension, if present, will add 2 most-significant bits to this value
+ $info['mpeg']['video']['raw']['frame_rate_code'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for Frame Rate id code
+ $info['mpeg']['video']['raw']['bitrate'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 18); // 18 bits for bit_rate_value (18 set bits = VBR, otherwise bitrate = this value * 400)
+ $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation.
+ $info['mpeg']['video']['pixel_aspect_ratio'] = self::videoAspectRatioLookup($info['mpeg']['video']['raw']['aspect_ratio_information']); // may be overridden later if file turns out to be MPEG-2
+ $info['mpeg']['video']['pixel_aspect_ratio_text'] = self::videoAspectRatioTextLookup($info['mpeg']['video']['raw']['aspect_ratio_information']); // may be overridden later if file turns out to be MPEG-2
+ $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation.
+ if (isset($info['mpeg']['video']['raw']['aspect_ratio_information'])) {
+ // MPEG-2 defines the aspect ratio flag differently from MPEG-1, but the MPEG-2 extension start code may occur after we've already looked up the aspect ratio assuming it was MPEG-1, so re-lookup assuming MPEG-2
+ // This must be done after the extended size is known, so the display aspect ratios can be converted to pixel aspect ratios.
+ $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation.
+ $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation.
+ case 9: // 1001 Picture Spatial Scalable Extension ID
+ break;
+ case 10: // 1010 Picture Temporal Scalable Extension ID
+ break;
+
+ default:
+ $this->warning('Unexpected $info[mpeg][video][raw][extension_start_code_identifier] value of '.$info['mpeg']['video']['raw']['extension_start_code_identifier']);
+ break;
+ }
+ break;
+
+
+ case 0xB8: // group_of_pictures_header
+ $GOPcounter++;
+ if (!empty($info['mpeg']['video']['bitrate_mode']) && ($info['mpeg']['video']['bitrate_mode'] == 'vbr')) {
+ $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation.
+ $time_code_separator = ($GOPheader['drop_frame_flag'] ? ';' : ':'); // While non-drop time code is displayed with colons separating the digit pairs "HH:MM:SS:FF" drop frame is usually represented with a semi-colon (;) or period (.) as the divider between all the digit pairs "HH;MM;SS;FF", "HH.MM.SS.FF"
+ $PackedElementaryStream['data_alignment_indicator'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: data_alignment_indicator -- 1 indicates that the PES packet header is immediately followed by the video start code or audio syncword
+ $PackedElementaryStream['dsm_trick_mode_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: dsm_trick_mode_flag -- DSM trick mode - not used by DVD
+// //$this->warning('Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.');
+// if ($info['video']['bitrate'] < 50000) {
+// $this->warning('Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.');
+// }
+// }
+//
+/*
+$time_prev = 0;
+$byte_prev = 0;
+$vbr_bitrates = array();
+foreach ($info['mpeg']['group_of_pictures'] as $gopkey => $gopdata) {
+ $this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
+ $this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
+ if ($max_stts_entries_to_scan < $atom_structure['number_entries']) {
+ $this->warning('QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($this->getid3->memory_limit / 1048576).'MB).');
+ }
+ for ($i = 0; $i < $max_stts_entries_to_scan; $i++) {
+ // 'mdat' contains the actual data for the audio/video, possibly also subtitles
+
+ /* due to lack of known documentation, this is a kludge implementation. If you know of documentation on how mdat is properly structed, please send it to info@getid3.org */
+
+ // first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?)
+ $mdat_offset = 0;
+ while (true) {
+ if (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x08".'wide') {
+ // Observed-but-not-handled atom types are just listed here to prevent warnings being generated
+ case 'FXTC': // Something to do with Adobe After Effects (?)
+ case 'PrmA':
+ case 'code':
+ case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html
+ case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html
+ // tapt seems to be used to compute the video size [https://www.getid3.org/phpBB3/viewtopic.php?t=838]
+ static $metaDATAkey = 1; // real ugly, but so is the QuickTime structure that stores keys and values in different multinested locations that are hard to relate to each other
+ // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data
+ case 'uuid': // user-defined atom often seen containing XML data, also used for potentially many other purposes, only a few specifically handled by getID3 (e.g. 360fly spatial data)
+ case '0537cdab-9d0c-4431-a72a-fa561f2a113e': // Exif - http://fileformats.archiveteam.org/wiki/Exif
+ case '2c4c0100-8504-40b9-a03e-562148d6dfeb': // Photoshop Image Resources - http://fileformats.archiveteam.org/wiki/Photoshop_Image_Resources
+ case '33c7a4d2-b81d-4723-a0ba-f1a3e097ad38': // IPTC-IIM - http://fileformats.archiveteam.org/wiki/IPTC-IIM
+ case '8974dbce-7be7-4c51-84f9-7148f9882554': // PIFF Track Encryption Box - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format
+ case '96a9f1f1-dc98-402d-a7ae-d68e34451809': // GeoJP2 World File Box - http://fileformats.archiveteam.org/wiki/GeoJP2
+ case 'a2394f52-5a9b-4f14-a244-6c427c648df4': // PIFF Sample Encryption Box - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format
+ case 'b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03': // GeoJP2 GeoTIFF Box - http://fileformats.archiveteam.org/wiki/GeoJP2
+ case 'd08a4f18-10f3-4a82-b6c8-32d8aba183d3': // PIFF Protection System Specific Header Box - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format
+ $this->warning('Unhandled (but recognized) "uuid" atom identified by "'.$atom_structure['uuid_field_id'].'" at offset '.$atom_structure['offset'].' ('.strlen($atom_data).' bytes)');
+ break;
+
+ case 'be7acfcb-97a9-42e8-9c71-999491e3afac': // XMP data (in XML format)
+ $atom_structure['sensor_data']['data_type']['debug_list'] = 'No debug items in list!';
+ //}
+ break;
+
+ default:
+ $this->warning('Unhandled "uuid" atom identified by "'.$atom_structure['uuid_field_id'].'" at offset '.$atom_structure['offset'].' ('.strlen($atom_data).' bytes)');
+ // 2017-05-10: I see some of the data, notably the Hour-Minute-Second, but cannot reconcile the rest of the data. However, the NMEA "GPRMC" line is there and relatively easy to parse, so I'm using that instead
+ $this->warning('QuickTime chapter '.$i.' extends beyond end of "chpl" atom');
+ break;
+ }
+ $info['quicktime']['chapters'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 8)) / 10000000; // timestamps are stored as 100-nanosecond units
+ // A QuickTime movie can contain none, one, or several timed metadata tracks. Timed metadata tracks can refer to multiple tracks.
+ // Metadata tracks are linked to the tracks they describe using a track-reference of type 'cdsc'. The metadata track holds the 'cdsc' track reference.
+ if (($thisfile_real_chunks_currentchunk['offset'] + $thisfile_real_chunks_currentchunk['length']) > $info['avdataend']) {
+ $this->warning('Chunk "'.$thisfile_real_chunks_currentchunk['name'].'" at offset '.$thisfile_real_chunks_currentchunk['offset'].' claims to be '.$thisfile_real_chunks_currentchunk['length'].' bytes long, which is beyond end of file');
+ return false;
+ }
+
+ if ($ChunkSize > ($this->getid3->fread_buffer_size() + 8)) {
+ //$this->warning('Expected .RMF-object_version to be "0", actual value is "'.$thisfile_real_chunks_currentchunk['object_version'].'" (should not be a problem)');
+ // RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s
+ $RIFFsubtype = 'WAVE';
+ }
+ if ($RIFFsubtype != 'AMV ') {
+ // AMV files are RIFF-AVI files with parts of the spec deliberately broken, such as chunk size fields hardcoded to zero (because players known in hardware that these fields are always a certain size
+ $this->warning('Expecting "RIFF|JUNK|IDVX" at '.$nextRIFFoffset.', found "'.$nextRIFFheaderID.'" ('.getid3_lib::PrintHexBytes($nextRIFFheaderID).') - skipping rest of file');
+ break 2;
+
+ }
+
+ }
+ if ($RIFFsubtype == 'WAVE') {
+ $thisfile_riff_WAVE = &$thisfile_riff['WAVE'];
+ }
+ break;
+
+ default:
+ $this->error('Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead');
+ //unset($info['fileformat']);
+ return false;
+ }
+
+ $streamindex = 0;
+ switch ($RIFFsubtype) {
+
+ // http://en.wikipedia.org/wiki/Wav
+ case 'WAVE':
+ $info['fileformat'] = 'wav';
+
+ if (empty($thisfile_audio['bitrate_mode'])) {
+ $thisfile_audio['bitrate_mode'] = 'cbr';
+ }
+ if (empty($thisfile_audio_dataformat)) {
+ $thisfile_audio_dataformat = 'wav';
+ }
+
+ if (isset($thisfile_riff_WAVE['data'][0]['offset'])) {
+ $timestamp_sample_rate = (is_array($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) ? max($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) : $parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']); // XML could possibly contain more than one TIMESTAMP_SAMPLE_RATE tag, returning as array instead of integer [why? does it make sense? perhaps doesn't matter but getID3 needs to deal with it] - see https://github.com/JamesHeinrich/getID3/issues/105
+ // output file appears to be incorrectly *not* padded to nearest WORD boundary
+ // Output less severe warning
+ $this->warning('File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)');
+ $info['avdataend'] = $info['filesize'];
+ } else {
+ // Short by more than one byte, throw warning
+ $this->warning('Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)');
+ $info['avdataend'] = $info['filesize'];
+ }
+ break;
+ }
+ }
+ if (!empty($info['mpeg']['audio']['LAME']['audio_bytes'])) {
+ if ((($info['avdataend'] - $info['avdataoffset']) - $info['mpeg']['audio']['LAME']['audio_bytes']) == 1) {
+ $info['avdataend']--;
+ $this->warning('Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored');
+ }
+ }
+ if (isset($thisfile_audio_dataformat) && ($thisfile_audio_dataformat == 'ac3')) {
+ unset($thisfile_audio['bits_per_sample']);
+ if (!empty($info['ac3']['bitrate']) && ($info['ac3']['bitrate'] != $thisfile_audio['bitrate'])) {
+ $this->warning('Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.$thisfile_riff_RIFFsubtype_VHDR_0['sCompression'].'"');
+ break;
+ }
+ }
+
+ if (isset($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'])) {
+ public function ParseRIFFAMV($startoffset, $maxoffset) {
+ // AMV files are RIFF-AVI files with parts of the spec deliberately broken, such as chunk size fields hardcoded to zero (because players known in hardware that these fields are always a certain size
+ // the rest is all hardcoded(?) and does not appear to be useful until you get to audio info at offset 256, even then everything is probably hardcoded
+
+ if (substr($AMVheader, 68, 20) != 'LIST'."\x00\x00\x00\x00".'strlstrh'."\x38\x00\x00\x00") {
+ throw new Exception('expecting "LIST<0x00000000>strlstrh<0x38000000>" at offset '.($startoffset + 68).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 68, 20)).'"');
+ }
+ // followed by 56 bytes of null: substr($AMVheader, 88, 56) -> 144
+ if (substr($AMVheader, 144, 8) != 'strf'."\x24\x00\x00\x00") {
+ throw new Exception('expecting "strf<0x24000000>" at offset '.($startoffset + 144).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 144, 8)).'"');
+ }
+ // followed by 36 bytes of null: substr($AMVheader, 144, 36) -> 180
+
+ if (substr($AMVheader, 188, 20) != 'LIST'."\x00\x00\x00\x00".'strlstrh'."\x30\x00\x00\x00") {
+ throw new Exception('expecting "LIST<0x00000000>strlstrh<0x30000000>" at offset '.($startoffset + 188).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 188, 20)).'"');
+ }
+ // followed by 48 bytes of null: substr($AMVheader, 208, 48) -> 256
+ if (substr($AMVheader, 256, 8) != 'strf'."\x14\x00\x00\x00") {
+ throw new Exception('expecting "strf<0x14000000>" at offset '.($startoffset + 256).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 256, 8)).'"');
+ }
+ // followed by 20 bytes of a modified WAVEFORMATEX:
+ // typedef struct {
+ // WORD wFormatTag; //(Fixme: this is equal to PCM's 0x01 format code)
+ // WORD nChannels; //(Fixme: this is always 1)
+ // DWORD nSamplesPerSec; //(Fixme: for all known sample files this is equal to 22050)
+ // DWORD nAvgBytesPerSec; //(Fixme: for all known sample files this is equal to 44100)
+ // WORD nBlockAlign; //(Fixme: this seems to be 2 in AMV files, is this correct ?)
+ // WORD wBitsPerSample; //(Fixme: this seems to be 16 in AMV files instead of the expected 4)
+ // WORD cbSize; //(Fixme: this seems to be 0 in AMV files)
+ $chunkname = str_replace("\x00", '_', substr($chunknamesize, 0, 4)); // note: chunk names of 4 null bytes do appear to be legal (has been observed inside INFO and PRMI chunks, for example), but makes traversing array keys more difficult
+ $info['playtime_seconds'] = $getid3_temp->info['playtime_seconds']; // may not match RIFF calculations since DTS-WAV often used 14/16 bit-word packing
+ if (!empty($getid3_temp->info['warning'])) {
+ foreach ($getid3_temp->info['warning'] as $newerror) {
+ $this->warning('Chunk "'.$chunkname.'" at offset '.$this->ftell().' is unexpectedly larger than 1MB (claims to be '.number_format($chunksize).' bytes), skipping data');
+ if ($RIFFchunk[$chunkname][$thisindex]['parsed']['filelength'] && !empty($info['filesize']) && ($RIFFchunk[$chunkname][$thisindex]['parsed']['filelength'] != $info['filesize'])) {
+ $this->warning('RIFF.WAVE.scot.filelength ('.$RIFFchunk[$chunkname][$thisindex]['parsed']['filelength'].') different from actual filesize ('.$info['filesize'].')');
+ public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) {
+
+ $parsed = array();
+ $parsed['biSize'] = substr($BITMAPINFOHEADER, 0, 4); // number of bytes required by the BITMAPINFOHEADER structure
+ $parsed['biWidth'] = substr($BITMAPINFOHEADER, 4, 4); // width of the bitmap in pixels
+ $parsed['biHeight'] = substr($BITMAPINFOHEADER, 8, 4); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner
+ $parsed['biPlanes'] = substr($BITMAPINFOHEADER, 12, 2); // number of color planes on the target device. In most cases this value must be set to 1
+ $parsed['biBitCount'] = substr($BITMAPINFOHEADER, 14, 2); // Specifies the number of bits per pixels
+ $parsed['biSizeImage'] = substr($BITMAPINFOHEADER, 20, 4); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures)
+ $parsed['biXPelsPerMeter'] = substr($BITMAPINFOHEADER, 24, 4); // horizontal resolution, in pixels per metre, of the target device
+ $parsed['biYPelsPerMeter'] = substr($BITMAPINFOHEADER, 28, 4); // vertical resolution, in pixels per metre, of the target device
+ $parsed['biClrUsed'] = substr($BITMAPINFOHEADER, 32, 4); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression
+ $parsed['biClrImportant'] = substr($BITMAPINFOHEADER, 36, 4); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important
+ $this->error('Expecting "FWS" or "CWS" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['swf']['header']['signature']).'"');
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+ exit;
+}
+
+class getid3_ts extends getid3_handler
+{
+ /**
+ * @return bool
+ */
+ public function Analyze() {
+ $info = &$this->getid3->info;
+
+ $this->fseek($info['avdataoffset']);
+ $TSheader = $this->fread(19);
+ $magic = "\x47";
+ if (substr($TSheader, 0, 1) != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at '.$info['avdataoffset'].', found '.getid3_lib::PrintHexBytes(substr($TSheader, 0, 1)).' instead.');
+ $info['ts']['packet']['flags']['transport_error_indicator'] = (bool) ($pid_flags_raw & 0x8000); // Set by demodulator if can't correct errors in the stream, to tell the demultiplexer that the packet has an uncorrectable error
+ $info['ts']['packet']['flags']['payload_unit_start_indicator'] = (bool) ($pid_flags_raw & 0x4000); // 1 means start of PES data or PSI otherwise zero only.
+ $info['ts']['packet']['flags']['transport_high_priority'] = (bool) ($pid_flags_raw & 0x2000); // 1 means higher priority than other packets with the same PID.
+ $info['ts']['packet']['adaption']['field_length'] = ($AdaptionField_raw & 0xFF00) >> 8; // Number of bytes in the adaptation field immediately following this byte
+ $info['ts']['packet']['adaption']['flags']['discontinuity'] = (bool) ($AdaptionField_raw & 0x0080); // Set to 1 if current TS packet is in a discontinuity state with respect to either the continuity counter or the program clock reference
+ $info['ts']['packet']['adaption']['flags']['random_access'] = (bool) ($AdaptionField_raw & 0x0040); // Set to 1 if the PES packet in this TS packet starts a video/audio sequence
+ $info['ts']['packet']['adaption']['flags']['pcr'] = (bool) ($AdaptionField_raw & 0x0010); // 1 means adaptation field does contain a PCR field
+ $info['ts']['packet']['adaption']['flags']['opcr'] = (bool) ($AdaptionField_raw & 0x0008); // 1 means adaptation field does contain an OPCR field
+ $info['ts']['packet']['adaption']['flags']['splice_point'] = (bool) ($AdaptionField_raw & 0x0004); // 1 means presence of splice countdown field in adaptation field
+ $info['ts']['packet']['adaption']['flags']['private_data'] = (bool) ($AdaptionField_raw & 0x0002); // 1 means presence of private data bytes in adaptation field
+ $info['ts']['packet']['adaption']['flags']['extension'] = (bool) ($AdaptionField_raw & 0x0001); // 1 means presence of adaptation field extension
+ if ($info['ts']['packet']['adaption']['flags']['pcr']) {
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+ exit;
+}
+
+class getid3_aa extends getid3_handler
+{
+ /**
+ * @return bool
+ */
+ public function Analyze() {
+ $info = &$this->getid3->info;
+
+ $this->fseek($info['avdataoffset']);
+ $AAheader = $this->fread(8);
+
+ $magic = "\x57\x90\x75\x36";
+ if (substr($AAheader, 4, 4) != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($AAheader, 4, 4)).'"');
+ return false;
+ }
+
+ // shortcut
+ $info['aa'] = array();
+ $thisfile_aa = &$info['aa'];
+
+ $info['fileformat'] = 'aa';
+ $info['audio']['dataformat'] = 'aa';
+ $this->error('Audible Audiobook (.aa) parsing not enabled in this version of getID3() ['.$this->getid3->version().']');
+ return false;
+ $info['audio']['bitrate_mode'] = 'cbr'; // is it?
+ // breaks out when end-of-file encountered, or invalid data found,
+ // or MaxFramesToScan frames have been scanned
+
+ if (!getid3_lib::intValueSupported($byteoffset)) {
+ $this->warning('Unable to parse AAC file beyond '.$this->ftell().' (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)');
+ return false;
+ }
+ $this->fseek($byteoffset);
+
+ // First get substring
+ $substring = $this->fread(9); // header is 7 bytes (or 9 if CRC is present)
+ $substringlength = strlen($substring);
+ if ($substringlength != 9) {
+ $this->error('Failed to read 7 bytes at offset '.($this->ftell() - $substringlength).' (only read '.$substringlength.' bytes)');
+ return false;
+ }
+ // this would be easier with 64-bit math, but split it up to allow for 32-bit:
+ if ($info['aac']['header']['raw']['syncword'] != 0x0FFF) {
+ $this->error('Synch pattern (0x0FFF) not found at offset '.($this->ftell() - $substringlength).' (found 0x0'.strtoupper(dechex($info['aac']['header']['raw']['syncword'])).' instead)');
+ //if ($info['fileformat'] == 'aac') {
+ // return true;
+ //}
+ unset($info['aac']);
+ return false;
+ }
+
+ // Gather info for first frame only - this takes time to do 1000 times!
+ $thisfile_ac3_raw_bsi['bsid'] = (getid3_lib::LittleEndian2Int(substr($tempAC3header, 5, 1)) & 0xF8) >> 3; // AC3 and E-AC3 put the "bsid" version identifier in the same place, but unfortnately the 4 bytes between the syncword and the version identifier are interpreted differently, so grab it here so the following code structure can make sense
+ unset($tempAC3header);
+
+ if ($this->AC3header['syncinfo'] !== self::syncword) {
+ if (!$this->isDependencyFor('matroska')) {
+ unset($info['fileformat'], $info['ac3']);
+ return $this->error('Expecting "'.dechex(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.dechex($this->AC3header['syncinfo']).'"');
+ if ($thisfile_ac3_raw_bsi['frmsizecod'] > 37) { // binary: 100101 - see Table 5.18 Frame Size Code Table (1 word = 16 bits)
+ $this->warning('Unexpected ac3.bsi.frmsizecod value: '.$thisfile_ac3_raw_bsi['frmsizecod'].', bitrate not set correctly');
+ }
+
+ $thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5); // we already know this from pre-parsing the version identifier, but re-read it to let the bitstream flow as intended
+ $thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB'; // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31. The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
+
+ $thisfile_ac3_raw_bsi['flags']['compr2'] = (bool) $this->readHeaderBSI(1); // 5.4.2.17 compr2e: Compression Gain Word Exists, ch2, 1 Bit
+ if ($thisfile_ac3_raw_bsi['flags']['compr2']) {
+ $thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8); // 5.4.2.18 compr2: Compression Gain Word, ch2, 8 Bits
+ $thisfile_ac3_raw_bsi['flags']['audprodinfo2'] = (bool) $this->readHeaderBSI(1); // 5.4.2.21 audprodi2e: Audio Production Information Exists, ch2, 1 Bit
+ if ($thisfile_ac3_raw_bsi['flags']['audprodinfo2']) {
+ $thisfile_ac3_raw_bsi['original'] = (bool) $this->readHeaderBSI(1); // 5.4.2.25 origbs: Original Bit Stream, 1 Bit
+
+ $thisfile_ac3_raw_bsi['flags']['timecod1'] = $this->readHeaderBSI(2); // 5.4.2.26 timecod1e, timcode2e: Time Code (first and second) Halves Exist, 2 Bits
+ if ($thisfile_ac3_raw_bsi['flags']['timecod1'] & 0x01) {
+ $thisfile_ac3_raw_bsi['timecod1'] = $this->readHeaderBSI(14); // 5.4.2.27 timecod1: Time code first half, 14 bits
+ $thisfile_ac3['timecode1'] = 0;
+ $thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x3E00) >> 9) * 3600; // The first 5 bits of this 14-bit field represent the time in hours, with valid values of 0�23
+ $thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x01F8) >> 3) * 60; // The next 6 bits represent the time in minutes, with valid values of 0�59
+ $thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x0003) >> 0) * 8; // The final 3 bits represents the time in 8 second increments, with valid values of 0�7 (representing 0, 8, 16, ... 56 seconds)
+ }
+ if ($thisfile_ac3_raw_bsi['flags']['timecod1'] & 0x02) {
+ $thisfile_ac3_raw_bsi['timecod2'] = $this->readHeaderBSI(14); // 5.4.2.28 timecod2: Time code second half, 14 bits
+ $thisfile_ac3['timecode2'] = 0;
+ $thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x3800) >> 11) * 1; // The first 3 bits of this 14-bit field represent the time in seconds, with valid values from 0�7 (representing 0-7 seconds)
+ $thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x07C0) >> 6) * (1 / 30); // The next 5 bits represents the time in frames, with valid values from 0�29 (one frame = 1/30th of a second)
+ $thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x003F) >> 0) * ((1 / 30) / 60); // The final 6 bits represents fractions of 1/64 of a frame, with valid values from 0�63
+ $thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6) + 1; // This 6-bit code, which exists only if addbside is a 1, indicates the length in bytes of additional bit stream information. The valid range of addbsil is 0�63, indicating 1�64 additional bytes, respectively.
+ $this->error('E-AC3 parsing is incomplete and experimental in this version of getID3 ('.$this->getid3->version().'). Notably the bitrate calculations are wrong -- value might (or not) be correct, but it is not calculated correctly. Email info@getid3.org if you know how to calculate EAC3 bitrate correctly.');
+ $thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5); // we already know this from pre-parsing the version identifier, but re-read it to let the bitstream flow as intended
+ $this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 16. Please submit a support ticket with a sample file.');
+ // this isn't right, but it's (usually) close, roughly 5% less than it should be.
+ // but WHERE is the actual bitrate value stored in EAC3?? email info@getid3.org if you know!
+ $thisfile_ac3['bitrate'] = ($thisfile_ac3_raw_bsi['frmsiz'] + 1) * 16 * 30; // The frmsiz field shall contain a value one less than the overall size of the coded syncframe in 16-bit words. That is, this field may assume a value ranging from 0 to 2047, and these values correspond to syncframe sizes ranging from 1 to 2048.
+ // kludge-fix to make it approximately the expected value, still not "right":
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+ exit;
+}
+
+class getid3_amr extends getid3_handler
+{
+ /**
+ * @return bool
+ */
+ public function Analyze() {
+ $info = &$this->getid3->info;
+
+ $this->fseek($info['avdataoffset']);
+ $AMRheader = $this->fread(6);
+
+ $magic = '#!AMR'."\x0A";
+ if (substr($AMRheader, 0, 6) != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($AMRheader, 0, 6)).'"');
+ return false;
+ }
+
+ // shortcut
+ $info['amr'] = array();
+ $thisfile_amr = &$info['amr'];
+
+ $info['fileformat'] = 'amr';
+ $info['audio']['dataformat'] = 'amr';
+ $info['audio']['bitrate_mode'] = 'vbr'; // within a small predefined range: 4.75kbps to 12.2kbps
+ $info['audio']['bits_per_sample'] = 13; // http://en.wikipedia.org/wiki/Adaptive_Multi-Rate_audio_codec: "Sampling frequency 8 kHz/13-bit (160 samples for 20 ms frames), filtered to 200–3400 Hz"
+ $info['audio']['sample_rate'] = 8000; // http://en.wikipedia.org/wiki/Adaptive_Multi-Rate_audio_codec: "Sampling frequency 8 kHz/13-bit (160 samples for 20 ms frames), filtered to 200–3400 Hz"
+ $codec_mode_request = ($AMR_frame_header & 0x78) >> 3; // The 2nd bit through 5th bit (counting the most significant bit as the first bit) comprise the CMR (Codec Mode Request), values 0-7 being valid for AMR. The top bit of the CMR can actually be ignored, though it is used when AMR forms RTP payloads. The lower 3-bits of the header are reserved and are not used. Viewing the header from most significant bit to least significant bit, the encoding is XCCCCXXX, where Xs are reserved (typically 0) and the Cs are the CMR.
+ $info['playtime_seconds'] = array_sum($thisfile_amr['frame_mode_count']) * 0.020; // each frame contain 160 samples and is 20 milliseconds long
+ $info['audio']['bitrate'] = (8 * ($info['avdataend'] - $info['avdataoffset'])) / $info['playtime_seconds']; // bitrate could be calculated from average bitrate by distributation of frame types. That would give effective audio bitrate, this gives overall file bitrate which will be a little bit higher since every frame will waste 8 bits for header, plus a few bits for octet padding
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['avr']['raw']['magic']).'"');
+ if (!getid3_lib::intValueSupported($thisfile_bonk['dataend'])) {
+
+ $this->warning('Unable to parse BONK file from end (v0.6+ preferred method) because PHP filesystem functions only support up to '.round(PHP_INT_MAX / 1073741824).'GB');
+
+ } else {
+
+ // scan-from-end method, for v0.6 and higher
+ $this->fseek($thisfile_bonk['dataend'] - 8);
+ $PossibleBonkTag = $this->fread(8);
+ while ($this->BonkIsValidTagName(substr($PossibleBonkTag, 4, 4), true)) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes("\x00".strtoupper(substr($PossibleBonkTag, 4, 4))).'" at offset '.$BonkTagOffset.', found "'.getid3_lib::PrintHexBytes($TagHeaderTest).'"');
+ $this->error('Expecting "FRM8" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSDIFFheader, 0, 4)).'"');
+ return false;
+ }
+ unset($DSDIFFheader);
+ $this->fseek($info['avdataoffset']);
+
+ $info['encoding'] = 'ISO-8859-1'; // not certain, but assumed
+ $info['fileformat'] = 'dsdiff';
+ $info['mime_type'] = 'audio/dsd';
+ $info['audio']['dataformat'] = 'dsdiff';
+ $info['audio']['bitrate_mode'] = 'cbr';
+ $info['audio']['bits_per_sample'] = 1;
+
+ $info['dsdiff'] = array();
+ $thisChunk = null;
+ while (!$this->feof() && ($ChunkHeader = $this->fread(12))) {
+ if (strlen($ChunkHeader) < 12) {
+ $this->error('Expecting chunk header at offset '.(isset($thisChunk['offset']) ? $thisChunk['offset'] : 'N/A').', found insufficient data in file, aborting parsing');
+ if (!preg_match('#^[\\x21-\\x7E]+ *$#', $thisChunk['name'])) {
+ // "a concatenation of four printable ASCII characters in the range ' ' (space, 0x20) through '~'(0x7E). Space (0x20) cannot precede printing characters; trailing spaces are allowed."
+ $this->error('Invalid chunk name "'.$thisChunk['name'].'" ('.getid3_lib::PrintHexBytes($thisChunk['name']).') at offset '.$thisChunk['offset'].', aborting parsing');
+ $datasize = $thisChunk['size'] + ($thisChunk['size'] % 2); // "If the data is an odd number of bytes in length, a pad byte must be added at the end. The pad byte is not included in ckDataSize."
+
+ switch ($thisChunk['name']) {
+ case 'FRM8':
+ $thisChunk['form_type'] = $this->fread(4);
+ if ($thisChunk['form_type'] != 'DSD ') {
+ $this->error('Expecting "DSD " at offset '.($this->ftell() - 4).', found "'.getid3_lib::PrintHexBytes($thisChunk['form_type']).'", aborting parsing');
+ break 2;
+ }
+ // do nothing further, prevent skipping subchunks
+ break;
+ case 'PROP': // PROPerty chunk
+ $thisChunk['prop_type'] = $this->fread(4);
+ if ($thisChunk['prop_type'] != 'SND ') {
+ $this->error('Expecting "SND " at offset '.($this->ftell() - 4).', found "'.getid3_lib::PrintHexBytes($thisChunk['prop_type']).'", aborting parsing');
+ break 2;
+ }
+ // do nothing further, prevent skipping subchunks
+ // need to seek to multiple of 2 bytes, human-readable string length is only one byte long so if the string is an even number of bytes we need to seek past a padding byte after the string
+ // 3 = 5-channel set-up according to ITU-R BS.775-1 [ITU]
+ // 4 = 6-channel set-up, 5-channel set-up according to ITU-R BS.775-1 [ITU], plus additional Low Frequency Enhancement (LFE) loudspeaker. Also known as "5.1 configuration"
+ // commentText[] is the description of the Comment. This text must be padded with a byte at the end, if needed, to make it an even number of bytes long. This pad byte, if present, is not included in count.
+ // markerText[] is the description of the marker. This text must be padded with a byte at the end, if needed, to make it an even number of bytes long. This pad byte, if present, is not included in count.
+ // This text must be padded with a byte at the end, if needed, to make it an even number of bytes long. This pad byte, if present, is not included in count.
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['dsf']['dsd']['magic']).'"');
+ unset($info['fileformat']);
+ unset($info['audio']);
+ unset($info['dsf']);
+ return false;
+ }
+ $info['dsf']['dsd']['dsd_chunk_size'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8)); // should be 28
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$headeroffset.', found "'.getid3_lib::PrintHexBytes($info['dsf']['fmt']['magic']).'"');
+ return false;
+ }
+ $info['dsf']['fmt']['fmt_chunk_size'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8)); // usually 52 bytes
+ $headeroffset += 8;
+ $dsfheader .= $this->fread($info['dsf']['fmt']['fmt_chunk_size'] - 12 + 12); // we have already read the entire DSD chunk, plus 12 bytes of FMT. We now want to read the size of FMT, plus 12 bytes into the next chunk to get magic and size.
+ if (strlen($dsfheader) != ($info['dsf']['dsd']['dsd_chunk_size'] + $info['dsf']['fmt']['fmt_chunk_size'] + 12)) {
+ $this->error('Expecting '.($info['dsf']['dsd']['dsd_chunk_size'] + $info['dsf']['fmt']['fmt_chunk_size']).' bytes header, found '.strlen($dsfheader).' bytes');
+ return false;
+ }
+ $info['dsf']['fmt']['format_version'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4)); // usually "1"
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$headeroffset.', found "'.getid3_lib::PrintHexBytes($info['dsf']['data']['magic']).'"');
+ $info['dss']['hardware'] = trim(substr($DSSheader, 12, 16)); // identification string for hardware used to create the file, e.g. "DPM 9600", "DS2400"
+ $info['dss']['playtime_ms'] = getid3_lib::LittleEndian2Int(substr($DSSheader, 512, 4)); // exact file playtime in milliseconds. Has also been observed at offset 530 in one sample file, with something else (unknown) at offset 512
+ $info['dss']['sample_rate_index'] = ord(substr($DSSheader, 1538, 1)); // this isn't certain, this may or may not be where the sample rate info is stored, but it seems consistent on my small selection of sample files
+ $this->getid3->warning('DSS above version 3 not fully supported in this version of getID3. Any additional documentation or format specifications would be welcome. This file is version '.$info['dss']['version']);
+ }
+
+ $info['audio']['bits_per_sample'] = 16; // maybe, maybe not -- most compressed audio formats don't have a fixed bits-per-sample value, but this is a reasonable approximation
+ $info['audio']['channels'] = 1;
+
+ if (!empty($info['dss']['playtime_ms']) && (floor($info['dss']['playtime_ms'] / 1000) == $info['dss']['playtime_sec'])) { // *should* just be playtime_ms / 1000 but at least one sample file has playtime_ms at offset 530 instead of offset 512, so safety check
+ $this->getid3->warning('playtime_ms ('.number_format($info['dss']['playtime_ms'] / 1000, 3).') does not match playtime_sec ('.number_format($info['dss']['playtime_sec']).') - using playtime_sec value');
+ $DTSheader = $this->fread(20); // we only need 2 words magic + 6 words frame header, but these words may be normal 16-bit words OR 14-bit words with 2 highest bits set to zero, so 8 words can be either 8*16/8 = 16 bytes OR 8*16*(16/14)/8 = 18.3 bytes
+
+ // check syncword
+ $sync = substr($DTSheader, 0, 4);
+ if (($encoding = array_search($sync, self::$syncwords)) !== false) {
+
+ $info['dts']['raw']['magic'] = $sync;
+ $this->readBinDataOffset = 32;
+
+ } elseif ($this->isDependencyFor('matroska')) {
+
+ // Matroska contains DTS without syncword encoded as raw big-endian format
+ $encoding = 0;
+ $this->readBinDataOffset = 0;
+
+ } else {
+
+ unset($info['fileformat']);
+ return $this->error('Expecting "'.implode('| ', array_map('getid3_lib::PrintHexBytes', self::$syncwords)).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($sync).'"');
+ // 14-bit data packed into 16-bit words, so the playtime is wrong because only (14/16) of the bytes in the data portion of the file are used at the specified bitrate
+ return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"');
+ $this->error('This version of getID3() ['.$this->getid3->version().'] does not support LA version '.substr($rawdata, $offset + 2, 1).'.'.substr($rawdata, $offset + 3, 1).' which this appears to be - check http://getid3.sourceforge.net for updates.');
+ if ($info['lpac']['flags']['fast_compress'] && ($info['lpac']['max_prediction_order'] != 3)) {
+ $this->warning('max_prediction_order expected to be "3" if fast_compress is true, actual value is "'.$info['lpac']['max_prediction_order'].'"');
+ }
+ switch ($info['lpac']['file_version']) {
+ case 6:
+ if ($info['lpac']['flags']['adaptive_quantization']) {
+ $this->warning('adaptive_quantization expected to be false in LPAC file stucture v6, actually true');
+ }
+ if ($info['lpac']['quantization'] != 20) {
+ $this->warning('Quantization expected to be 20 in LPAC file stucture v6, actually '.$info['lpac']['flags']['Q']);
+ }
+ break;
+
+ default:
+ //$this->warning('This version of getID3() ['.$this->getid3->version().'] only supports LPAC file format version 6, this file is version '.$info['lpac']['file_version'].' - please report to info@getid3.org');
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTHD).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($MIDIheaderID).'"');
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTRK).'" at '.($offset - 4).', found "'.getid3_lib::PrintHexBytes($trackID).'" instead');
+ return false;
+ }
+ }
+
+ if (!is_array($trackdataarray) || count($trackdataarray) === 0) {
+ $info['mod']['song_length'] = getid3_lib::BigEndian2Int(substr($filedata, $offset++, 1));// Songlength. Range is 1-128.
+ $info['mod']['bpm'] = getid3_lib::BigEndian2Int(substr($filedata, $offset++, 1));// This byte is set to 127, so that old trackers will search through all patterns when loading. Noisetracker uses this byte for restart, ProTracker doesn't.
+
+ for ($songposition = 0; $songposition <= 127; $songposition++) {
+ // Song positions 0-127. Each hold a number from 0-63 (or 0-127)
+ // that tells the tracker what pattern to play at that position.
+ if ($thisfile_monkeysaudio_raw['magic'] != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_monkeysaudio_raw['magic']).'"');
+ unset($info['fileformat']);
+ return false;
+ }
+ $thisfile_monkeysaudio_raw['nVersion'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 4, 2)); // appears to be uint32 in 3.98+
+
+ if ($thisfile_monkeysaudio_raw['nVersion'] < 3980) {
+ $this->warning('Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"');
+ break;
+ }
+ }
+
+ if (empty($info['fileformat'])) {
+ unset($info['fileformat']);
+ unset($info['audio']['bitrate_mode']);
+ unset($info['avdataoffset']);
+ unset($info['avdataend']);
+ return false;
+ }
+
+ $info['mime_type'] = 'audio/mpeg';
+ $info['audio']['lossless'] = false;
+
+ // Calculate playtime
+ if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) {
+ if (isset($KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) {
+ if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207
+
+ // extra 11 chars are not part of version string when LAMEtag present
+ $thisfile_mpeg_audio['bitrate_mode'] = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr'
+ $this->warning('Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)');
+ }
+ else {
+ $this->warning('Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)');
+ }
+ } else {
+ if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) {
+ // $prenullbytefileoffset = $this->ftell();
+ // $this->fseek($info['avdataend']);
+ // $PossibleNullByte = $this->fread(1);
+ // $this->fseek($prenullbytefileoffset);
+ // if ($PossibleNullByte === "\x00") {
+ $info['avdataend']--;
+ // $this->warning('Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored');
+ // } else {
+ // $this->warning('Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)');
+ // }
+ } else {
+ $this->warning('Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)');
+ }
+ }
+ }
+
+ if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) {
+ if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) {
+ if ($deviation_cbr_from_header_bitrate < 0.01) {
+ // VBR header bitrate may differ slightly from true bitrate of frames, perhaps accounting for overhead of VBR header frame itself?
+ // If measured CBR bitrate is within 1% of specified bitrate in VBR header then assume that file is truly CBR
+ $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
+ //$this->warning('VBR header ignored, assuming CBR '.round($cbr_bitrate_in_short_scan / 1000).'kbps based on scan of '.$this->mp3_valid_check_frames.' frames');
+ }
+ }
+ }
+ if (isset($this->getid3->info['mp3_validity_check_bitrates'])) {
+ if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
+ $framelength = $framelength2;
+ }
+ if (!$framelength) {
+ $this->error('Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset);
+ return false;
+ } else {
+ $this->warning('ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)');
+ $info['audio']['codec'] = 'LAME';
+ $info['audio']['encoder'] = 'LAME3.88';
+ $SyncPattern1 = substr($SyncPattern1, 0, 3);
+ $SyncPattern2 = substr($SyncPattern2, 0, 3);
+ }
+ }
+
+ if ($deepscan) {
+
+ $ActualFrameLengthValues = array();
+ $nextoffset = $offset + $framelength;
+ while ($nextoffset < ($info['avdataend'] - 6)) {
+ $this->warning('too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.');
+ if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) {
+ $info = $dummy;
+ $info['avdataoffset'] = $GarbageOffsetEnd;
+ $this->warning('apparently-valid VBR header not used because could not find '.$this->mp3_valid_check_frames.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd);
+ } else {
+ $this->warning('using data from VBR header even though could not find '.$this->mp3_valid_check_frames.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')');
+ }
+ }
+ }
+ if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) {
+ // file likely contains < $max_frames_scan, just scan as one segment
+ $max_scan_segments = 1;
+ $frames_scan_per_segment = $max_frames_scan;
+ } else {
+ $pct_data_scanned += $this_pct_scanned;
+ break;
+ }
+ }
+ }
+ }
+ if ($pct_data_scanned > 0) {
+ $this->warning('too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.');
+ foreach ($info['mpeg']['audio'] as $key1 => $value1) {
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+ exit;
+}
+
+class getid3_mpc extends getid3_handler
+{
+ /**
+ * @return bool
+ */
+ public function Analyze() {
+ $info = &$this->getid3->info;
+
+ $info['mpc']['header'] = array();
+ $thisfile_mpc_header = &$info['mpc']['header'];
+
+ $info['fileformat'] = 'mpc';
+ $info['audio']['dataformat'] = 'mpc';
+ $info['audio']['bitrate_mode'] = 'vbr';
+ $info['audio']['channels'] = 2; // up to SV7 the format appears to have been hardcoded for stereo only
+ $info['audio']['lossless'] = false;
+
+ $this->fseek($info['avdataoffset']);
+ $MPCheaderData = $this->fread(4);
+ $info['mpc']['header']['preamble'] = substr($MPCheaderData, 0, 4); // should be 'MPCK' (SV8) or 'MP+' (SV7), otherwise possible stream data (SV4-SV6)
+ if (preg_match('#^MPCK#', $info['mpc']['header']['preamble'])) {
+ if ($thisPacket['key'] == $thisPacket['key_name']) {
+ $this->error('Found unexpected key value "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']);
+ return false;
+ }
+ $packetLength = 0;
+ $thisPacket['packet_size'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $keyNameSize), $packetLength); // includes keyname and packet_size field
+ if ($thisPacket['packet_size'] === false) {
+ $this->error('Did not find expected packet length within '.$maxHandledPacketLength.' bytes at offset '.($thisPacket['offset'] + $keyNameSize));
+ //$thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] / 1.5875); // values can range from 0.000 to 15.875, mapped to qualities of 0.0 to 10.0
+ $thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] - 5); // values can range from 0.000 to 15.875, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0
+ $thisfile_mpc_header['quality'] = (float) ($thisfile_mpc_header['raw']['profile'] - 5); // values can range from 0 to 15, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0
+$this->warning('Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable');
+ $info['ogg']['flac']['header']['header_packets'] = getid3_lib::BigEndian2Int(substr($filedata, 7, 2)) + 1; // "A two-byte, big-endian binary number signifying the number of header (non-audio) packets, not including this one. This number may be zero (0x0000) to signify 'unknown' but be aware that some decoders may not be able to handle such streams."
+ $info['audio']['sample_rate'] = 48000; // "All Opus audio is coded at 48 kHz, and should also be decoded at 48 kHz for playback (unless the target hardware does not support this sampling rate). However, this field may be used to resample the audio back to the original sampling rate, for example, when saving the output to a file." -- https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/structOpusHead.html
+ return true;
+ }
+
+ /**
+ * @return array|false
+ */
+ public function ParseOggPageHeader() {
+ // http://xiph.org/ogg/vorbis/doc/framing.html
+ $oggheader = array();
+ $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
+ // The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard.
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+ exit;
+}
+
+class getid3_rkau extends getid3_handler
+{
+ /**
+ * @return bool
+ */
+ public function Analyze() {
+ $info = &$this->getid3->info;
+
+ $this->fseek($info['avdataoffset']);
+ $RKAUHeader = $this->fread(20);
+ $magic = 'RKA';
+ if (substr($RKAUHeader, 0, 3) != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($RKAUHeader, 0, 3)).'"');
+ if (($info['rkau']['version'] > 1.07) || ($info['rkau']['version'] < 1.06)) {
+ $this->error('This version of getID3() ['.$this->getid3->version().'] can only parse RKAU files v1.06 and 1.07 (this file is v'.$info['rkau']['version'].')');
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+ exit;
+}
+
+class getid3_shorten extends getid3_handler
+{
+ /**
+ * @return bool
+ */
+ public function Analyze() {
+ $info = &$this->getid3->info;
+
+ $this->fseek($info['avdataoffset']);
+
+ $ShortenHeader = $this->fread(8);
+ $magic = 'ajkg';
+ if (substr($ShortenHeader, 0, 4) != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($ShortenHeader, 0, 4)).'"');
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['shn']['seektable']['offset'].', found "'.getid3_lib::PrintHexBytes($SeekTableMagic).'"');
+ if ($thisfile_takaudio_raw['magic'] != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_takaudio_raw['magic']).'"');
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['tta']['magic']).'"');
+ unset($info['fileformat']);
+ unset($info['audio']);
+ unset($info['tta']);
+ return false;
+ }
+
+ switch ($ttaheader[3]) {
+ case "\x01": // TTA v1.x
+ case "\x02": // TTA v1.x
+ case "\x03": // TTA v1.x
+ // "It was the demo-version of the TTA encoder. There is no released format with such header. TTA encoder v1 is not supported about a year."
+ // "I have hurried to release the TTA 2.0 encoder. Format documentation is removed from our site. This format still in development. Please wait the TTA2 format, encoder v4."
+ $this->error('This version of getID3() ['.$this->getid3->version().'] only knows how to handle TTA v1 and v2 - it may not work correctly with this file which appears to be TTA v'.$ttaheader[3]);
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+ exit;
+}
+
+class getid3_voc extends getid3_handler
+{
+ /**
+ * @return bool
+ */
+ public function Analyze() {
+ $info = &$this->getid3->info;
+
+ $OriginalAVdataOffset = $info['avdataoffset'];
+ $this->fseek($info['avdataoffset']);
+ $VOCheader = $this->fread(26);
+
+ $magic = 'Creative Voice File';
+ if (substr($VOCheader, 0, 19) != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($VOCheader, 0, 19)).'"');
+ return false;
+ }
+
+ // shortcuts
+ $thisfile_audio = &$info['audio'];
+ $info['voc'] = array();
+ $thisfile_voc = &$info['voc'];
+
+ $info['fileformat'] = 'voc';
+ $thisfile_audio['dataformat'] = 'voc';
+ $thisfile_audio['bitrate_mode'] = 'cbr';
+ $thisfile_audio['lossless'] = true;
+ $thisfile_audio['channels'] = 1; // might be overriden below
+ $thisfile_audio['bits_per_sample'] = 8; // might be overriden below
+ if ($thisfile_vqf_raw['header_tag'] != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_vqf_raw['header_tag']).'"');
+ $this->warning('Invalid DSIZ value "'.$thisfile_vqf['DSIZ'].'". This is known to happen with VQF files encoded by Ahead Nero, and seems to be its way of saying this is TwinVQF v'.($thisfile_vqf['DSIZ'] + 1).'.0');
+ $info['audio']['encoder'] = 'Ahead Nero';
+ break;
+
+ default:
+ $this->warning('Probable corrupted file - should be '.$thisfile_vqf['DSIZ'].' bytes, actually '.($info['avdataend'] - $info['avdataoffset'] - strlen('DATA')));
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param int $frequencyid
+ *
+ * @return int
+ */
+ public function VQFchannelFrequencyLookup($frequencyid) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$blockheader_offset.', found "'.getid3_lib::PrintHexBytes($blockheader_magic).'"');
+ if ($info['wavpack']['blockheader']['size'] >= 0x100000) {
+ $this->error('Expecting WavPack block size less than "0x100000", found "'.$info['wavpack']['blockheader']['size'].'" at offset '.$info['wavpack']['blockheader']['offset']);
+ $this->error('Expecting WavPack version between "4.2" and "4.16", found version "'.$info['wavpack']['blockheader']['major_version'].'.'.$info['wavpack']['blockheader']['minor_version'].'" at offset '.$info['wavpack']['blockheader']['offset']);
+ $this->warning('Expecting 16 bytes of WavPack "MD5 Checksum" in metablock at offset '.$metablock['offset'].', but found '.strlen($metablock['data']).' bytes');
+ if ($thisfile_bmp_header_raw['identifier'] != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_bmp_header_raw['identifier']).'"');
+ if ($info['efax']['header']['magic'] != "\xDC\xFE") {
+ $this->error('Invalid eFax byte order identifier (expecting DC FE, found '.getid3_lib::PrintHexBytes($info['efax']['header']['magic']).') at offset '.$info['avdataoffset']);
+ if ($info['gif']['header']['raw']['identifier'] != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['gif']['header']['raw']['identifier']).'"');
+ unset($info['fileformat']);
+ unset($info['gif']);
+ return false;
+ }
+
+ //if (!$this->getid3->option_extra_info) {
+ // $this->warning('GIF Extensions and Global Color Table not returned due to !getid3->option_extra_info');
+ $this->error('Cannot extract PSD image data for detail levels above BASE (level-3) because encrypted with Kodak-proprietary compression/encryption.');
+ $this->warning('At offset '.$offset.' chunk "'.substr($PNGfiledata, $offset, 4).'" no more data to read, data chunk will be truncated at '.(strlen($PNGfiledata) - 8).' bytes');
+ break;
+ }
+ } else {
+ $this->warning('At offset '.$offset.' chunk "'.substr($PNGfiledata, $offset, 4).'" exceeded max_data_bytes value of '.$this->max_data_bytes.', data chunk will be truncated at '.(strlen($PNGfiledata) - 8).' bytes');
+ $thisfile_png['animation']['num_frames'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4)); // Number of frames
+ $thisfile_png['animation']['num_plays'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4)); // Number of times to loop this APNG. 0 indicates infinite looping.
+ $CurrentIFD['fields'][$i]['raw']['valoff'] = $this->fread(4); // To save time and space the Value Offset contains the Value instead of pointing to the Value if and only if the Value fits into 4 bytes. If the Value is shorter than 4 bytes, it is left-justified within the 4-byte Value Offset, i.e., stored in the lowernumbered bytes. Whether the Value fits within 4 bytes is determined by the Type and Count of the field.
+ // Warning: It is possible that other TIFF field types will be added in the future. Readers should skip over fields containing an unexpected field type.
+ // In TIFF 6.0, some new field types have been defined:
+ // These new field types are also governed by the byte order (II or MM) in the TIFF header.
+ case 6: // SBYTE An 8-bit signed (twos-complement) integer.
+ case 8: // SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
+ case 9: // SLONG A 32-bit (4-byte) signed (twos-complement) integer.
+ case 10: // SRATIONAL Two SLONGs: the first represents the numerator of a fraction, the second the denominator.
+ case 11: // FLOAT Single precision (4-byte) IEEE format
+ case 12: // DOUBLE Double precision (8-byte) IEEE format
+ default:
+ $this->warning('unhandled IFD field type '.$CurrentIFD['fields'][$i]['raw']['type'].' for IFD entry '.$i);
+ 34665 => 'Exif IFD', // A pointer to the Exif IFD.
+ 34675 => 'ICC Profile', // ICC profile data.
+ 34735 => 'GeoKeyDirectoryTag', // Used in interchangeable GeoTIFF files.
+ 34736 => 'GeoDoubleParamsTag', // Used in interchangeable GeoTIFF files.
+ 34737 => 'GeoAsciiParamsTag', // Used in interchangeable GeoTIFF files.
+ 34853 => 'GPS IFD', // A pointer to the Exif-related GPS Info IFD.
+ 34908 => 'HylaFAX FaxRecvParams', // Used by HylaFAX.
+ 34909 => 'HylaFAX FaxSubAddress', // Used by HylaFAX.
+ 34910 => 'HylaFAX FaxRecvTime', // Used by HylaFAX.
+ 37724 => 'ImageSourceData', // Used by Adobe Photoshop.
+ 40965 => 'Interoperability IFD', // A pointer to the Exif-related Interoperability IFD.
+ 42112 => 'GDAL_METADATA', // Used by the GDAL library, holds an XML list of name=value 'metadata' values about the image as a whole, and about specific samples.
+ 42113 => 'GDAL_NODATA', // Used by the GDAL library, contains an ASCII encoded nodata or background pixel value.
+ 50215 => 'Oce Scanjob Description', // Used in the Oce scanning process.
+ 50216 => 'Oce Application Selector', // Used in the Oce scanning process.
+ 50217 => 'Oce Identification Number', // Used in the Oce scanning process.
+ 50218 => 'Oce ImageLogic Characteristics', // Used in the Oce scanning process.
+ 50706 => 'DNGVersion', // Used in IFD 0 of DNG files.
+ 50707 => 'DNGBackwardVersion', // Used in IFD 0 of DNG files.
+ 50708 => 'UniqueCameraModel', // Used in IFD 0 of DNG files.
+ 50709 => 'LocalizedCameraModel', // Used in IFD 0 of DNG files.
+ 50710 => 'CFAPlaneColor', // Used in Raw IFD of DNG files.
+ 50711 => 'CFALayout', // Used in Raw IFD of DNG files.
+ 50712 => 'LinearizationTable', // Used in Raw IFD of DNG files.
+ 50713 => 'BlackLevelRepeatDim', // Used in Raw IFD of DNG files.
+ 50714 => 'BlackLevel', // Used in Raw IFD of DNG files.
+ 50715 => 'BlackLevelDeltaH', // Used in Raw IFD of DNG files.
+ 50716 => 'BlackLevelDeltaV', // Used in Raw IFD of DNG files.
+ 50717 => 'WhiteLevel', // Used in Raw IFD of DNG files.
+ 50718 => 'DefaultScale', // Used in Raw IFD of DNG files.
+ 50719 => 'DefaultCropOrigin', // Used in Raw IFD of DNG files.
+ 50720 => 'DefaultCropSize', // Used in Raw IFD of DNG files.
+ 50721 => 'ColorMatrix1', // Used in IFD 0 of DNG files.
+ 50722 => 'ColorMatrix2', // Used in IFD 0 of DNG files.
+ 50723 => 'CameraCalibration1', // Used in IFD 0 of DNG files.
+ 50724 => 'CameraCalibration2', // Used in IFD 0 of DNG files.
+ 50725 => 'ReductionMatrix1', // Used in IFD 0 of DNG files.
+ 50726 => 'ReductionMatrix2', // Used in IFD 0 of DNG files.
+ 50727 => 'AnalogBalance', // Used in IFD 0 of DNG files.
+ 50728 => 'AsShotNeutral', // Used in IFD 0 of DNG files.
+ 50729 => 'AsShotWhiteXY', // Used in IFD 0 of DNG files.
+ 50730 => 'BaselineExposure', // Used in IFD 0 of DNG files.
+ 50731 => 'BaselineNoise', // Used in IFD 0 of DNG files.
+ 50732 => 'BaselineSharpness', // Used in IFD 0 of DNG files.
+ 50733 => 'BayerGreenSplit', // Used in Raw IFD of DNG files.
+ 50734 => 'LinearResponseLimit', // Used in IFD 0 of DNG files.
+ 50735 => 'CameraSerialNumber', // Used in IFD 0 of DNG files.
+ 50736 => 'LensInfo', // Used in IFD 0 of DNG files.
+ 50737 => 'ChromaBlurRadius', // Used in Raw IFD of DNG files.
+ 50738 => 'AntiAliasStrength', // Used in Raw IFD of DNG files.
+ 50740 => 'DNGPrivateData', // Used in IFD 0 of DNG files.
+ 50741 => 'MakerNoteSafety', // Used in IFD 0 of DNG files.
+ 50778 => 'CalibrationIlluminant1', // Used in IFD 0 of DNG files.
+ 50779 => 'CalibrationIlluminant2', // Used in IFD 0 of DNG files.
+ 50780 => 'BestQualityScale', // Used in Raw IFD of DNG files.
+ 50784 => 'Alias Layer Metadata', // Alias Sketchbook Pro layer usage description.
+ 50908 => 'TIFF_RSID', // This private tag is used in a GEOTIFF standard by DGIWG.
+ 50909 => 'GEO_METADATA', // This private tag is used in a GEOTIFF standard by DGIWG.
+ 33434 => 'ExposureTime', // Exposure time, given in seconds.
+ 33437 => 'FNumber', // The F number.
+ 34850 => 'ExposureProgram', // The class of the program used by the camera to set exposure when the picture is taken.
+ 34852 => 'SpectralSensitivity', // Indicates the spectral sensitivity of each channel of the camera used.
+ 34855 => 'ISOSpeedRatings', // Indicates the ISO Speed and ISO Latitude of the camera or input device as specified in ISO 12232.
+ 34856 => 'OECF', // Indicates the Opto-Electric Conversion Function (OECF) specified in ISO 14524.
+ 36864 => 'ExifVersion', // The version of the supported Exif standard.
+ 36867 => 'DateTimeOriginal', // The date and time when the original image data was generated.
+ 36868 => 'DateTimeDigitized', // The date and time when the image was stored as digital data.
+ 37121 => 'ComponentsConfiguration', // Specific to compressed data; specifies the channels and complements PhotometricInterpretation
+ 37122 => 'CompressedBitsPerPixel', // Specific to compressed data; states the compressed bits per pixel.
+ 37377 => 'ShutterSpeedValue', // Shutter speed.
+ 37378 => 'ApertureValue', // The lens aperture.
+ 37379 => 'BrightnessValue', // The value of brightness.
+ 37380 => 'ExposureBiasValue', // The exposure bias.
+ 37381 => 'MaxApertureValue', // The smallest F number of the lens.
+ 37382 => 'SubjectDistance', // The distance to the subject, given in meters.
+ 37383 => 'MeteringMode', // The metering mode.
+ 37384 => 'LightSource', // The kind of light source.
+ 37385 => 'Flash', // Indicates the status of flash when the image was shot.
+ 37386 => 'FocalLength', // The actual focal length of the lens, in mm.
+ 37396 => 'SubjectArea', // Indicates the location and area of the main subject in the overall scene.
+ 37500 => 'MakerNote', // Manufacturer specific information.
+ 37510 => 'UserComment', // Keywords or comments on the image; complements ImageDescription.
+ 37520 => 'SubsecTime', // A tag used to record fractions of seconds for the DateTime tag.
+ 37521 => 'SubsecTimeOriginal', // A tag used to record fractions of seconds for the DateTimeOriginal tag.
+ 37522 => 'SubsecTimeDigitized', // A tag used to record fractions of seconds for the DateTimeDigitized tag.
+ 40960 => 'FlashpixVersion', // The Flashpix format version supported by a FPXR file.
+ 40961 => 'ColorSpace', // The color space information tag is always recorded as the color space specifier.
+ 40962 => 'PixelXDimension', // Specific to compressed data; the valid width of the meaningful image.
+ 40963 => 'PixelYDimension', // Specific to compressed data; the valid height of the meaningful image.
+ 40964 => 'RelatedSoundFile', // Used to record the name of an audio file related to the image data.
+ 41483 => 'FlashEnergy', // Indicates the strobe energy at the time the image is captured, as measured in Beam Candle Power Seconds
+ 41484 => 'SpatialFrequencyResponse', // Records the camera or input device spatial frequency table and SFR values in the direction of image width, image height, and diagonal direction, as specified in ISO 12233.
+ 41486 => 'FocalPlaneXResolution', // Indicates the number of pixels in the image width (X) direction per FocalPlaneResolutionUnit on the camera focal plane.
+ 41487 => 'FocalPlaneYResolution', // Indicates the number of pixels in the image height (Y) direction per FocalPlaneResolutionUnit on the camera focal plane.
+ 41488 => 'FocalPlaneResolutionUnit', // Indicates the unit for measuring FocalPlaneXResolution and FocalPlaneYResolution.
+ 41492 => 'SubjectLocation', // Indicates the location of the main subject in the scene.
+ 41493 => 'ExposureIndex', // Indicates the exposure index selected on the camera or input device at the time the image is captured.
+ 41495 => 'SensingMethod', // Indicates the image sensor type on the camera or input device.
+ 41728 => 'FileSource', // Indicates the image source.
+ 41729 => 'SceneType', // Indicates the type of scene.
+ 41730 => 'CFAPattern', // Indicates the color filter array (CFA) geometric pattern of the image sensor when a one-chip color area sensor is used.
+ 41985 => 'CustomRendered', // Indicates the use of special processing on image data, such as rendering geared to output.
+ 41986 => 'ExposureMode', // Indicates the exposure mode set when the image was shot.
+ 41987 => 'WhiteBalance', // Indicates the white balance mode set when the image was shot.
+ 41988 => 'DigitalZoomRatio', // Indicates the digital zoom ratio when the image was shot.
+ 41989 => 'FocalLengthIn35mmFilm', // Indicates the equivalent focal length assuming a 35mm film camera, in mm.
+ 41990 => 'SceneCaptureType', // Indicates the type of scene that was shot.
+ 41991 => 'GainControl', // Indicates the degree of overall image gain adjustment.
+ 41992 => 'Contrast', // Indicates the direction of contrast processing applied by the camera when the image was shot.
+ 41993 => 'Saturation', // Indicates the direction of saturation processing applied by the camera when the image was shot.
+ 41994 => 'Sharpness', // Indicates the direction of sharpness processing applied by the camera when the image was shot.
+ 41995 => 'DeviceSettingDescription', // This tag indicates information on the picture-taking conditions of a particular camera model.
+ 41996 => 'SubjectDistanceRange', // Indicates the distance to the subject.
+ 42016 => 'ImageUniqueID', // Indicates an identifier assigned uniquely to each image.
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+ exit;
+}
+
+class getid3_exe extends getid3_handler
+{
+ /**
+ * @return bool
+ */
+ public function Analyze() {
+ $info = &$this->getid3->info;
+
+ $this->fseek($info['avdataoffset']);
+ $EXEheader = $this->fread(28);
+
+ $magic = 'MZ';
+ if (substr($EXEheader, 0, 2) != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($EXEheader, 0, 2)).'"');
+ if (($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048) > $info['filesize']) {
+ $this->error('Volume Space Size ('.($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048).' bytes) is larger than the file size ('.$info['filesize'].' bytes) (truncated file?)');
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $ISOheader
+ *
+ * @return bool
+ */
+ public function ParseSupplementaryVolumeDescriptor(&$ISOheader) {
+ // ISO integer values are stored Both-Endian format!!
+ // ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field
+ if (($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']) > $info['filesize']) {
+ $this->error('Volume Space Size ('.($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']).' bytes) is larger than the file size ('.$info['filesize'].' bytes) (truncated file?)');
+ }
+
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
+ public function ParsePathTable() {
+ $info = &$this->getid3->info;
+ if (!isset($info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']) && !isset($info['iso']['primary_volume_descriptor']['raw']['path_table_l_location'])) {
+ return false;
+ }
+ if (isset($info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location'])) {
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+ exit;
+}
+
+class getid3_msoffice extends getid3_handler
+{
+ /**
+ * @return bool
+ */
+ public function Analyze() {
+ $info = &$this->getid3->info;
+
+ $this->fseek($info['avdataoffset']);
+ $DOCFILEheader = $this->fread(8);
+ $magic = "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1";
+ if (substr($DOCFILEheader, 0, 8) != $magic) {
+ $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at '.$info['avdataoffset'].', found '.getid3_lib::PrintHexBytes(substr($DOCFILEheader, 0, 8)).' instead.');
+ return false;
+ }
+ $info['fileformat'] = 'msoffice';
+
+ $this->error('MS Office (.doc, .xls, etc) parsing not enabled in this version of getID3() ['.$this->getid3->version().']');
+ if (preg_match('#/Count ([0-9]+)#', $objectData, $matches)) {
+ $info['pdf']['pages'] = (int) $matches[1];
+ break; // for now this is the only data we're looking for in the PDF not need to loop through every object in the file (and a large PDF may contain MANY objects). And it MAY be possible that there are other objects elsewhere in the file that define additional (or removed?) pages
+ }
+ }
+ } else {
+ $this->error('Unexpected structure "'.substr($objBlob, 0, 100).'" at offset '.$offset);
+ // Strings are stored as <length of string>:<string>:
+ // 4:wiki
+ $length = $type;
+ while (true) {
+ $char = $TORRENT[$offset++];
+ if ($char == ':') {
+ break;
+ } elseif (!ctype_digit($char)) {
+ $this->error('unexpected('.__LINE__.') input "'.$char.'" at offset '.($offset - 1));
+ return false;
+ }
+ $length .= $char;
+ }
+ if (($offset + $length) > strlen($TORRENT)) {
+ $this->error('string at offset '.$offset.' claims to be '.$length.' bytes long but only '.(strlen($TORRENT) - $offset).' bytes of data left in file');
+ return false;
+ }
+ $string = substr($TORRENT, $offset, $length);
+ $offset += $length;
+//echo '<li>string: '.$string.'</li>';
+ return (string) $string;
+
+ } else {
+
+ $this->error('unexpected('.__LINE__.') input "'.$type.'" at offset '.($offset - 1));
+ return false;
+
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function ReadSequentialDigits(&$TORRENT, &$offset, $allow_negative=false) {
+ if (strstr(substr($APEtagData, $offset), "\x00") === false) {
+ $this->error('Cannot find null-byte (0x00) separator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset));
+ if ($thisfile_replaygain['album']['peak'] <= 0) {
+ $this->warning('ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
+ }
+ } else {
+ $this->warning('MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
+ }
+ break;
+
+ case 'mp3gain_undo':
+ if (preg_match('#^[\\-\\+][0-9]{3},[\\-\\+][0-9]{3},[NW]$#', $thisfile_ape_items_current['data'][0])) {
+ if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
+ // too big, skip
+ $this->warning('attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)');
+ if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
+ unset($ParsedID3v1['genre']);
+ }
+
+ foreach ($ParsedID3v1 as $key => $value) {
+ $ParsedID3v1['comments'][$key][0] = $value;
+ }
+ $ID3v1encoding = $this->getid3->encoding_id3v1;
+ if ($this->getid3->encoding_id3v1_autodetect) {
+ // ID3v1 encoding detection hack START
+ // ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets
+ // Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess
+ foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) {
+ foreach ($valuearray as $key => $value) {
+ if (preg_match('#^[\\x00-\\x40\\x80-\\xFF]+$#', $value) && !ctype_digit((string) $value)) { // check for strings with only characters above chr(128) and punctuation/numbers, but not just numeric strings (e.g. track numbers or years)
+ foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
+ if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) {
+ $ID3v1encoding = $id3v1_bad_encoding;
+ $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
+ // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
+ // of on tag level, making it easier to skip frames, increasing the streamability
+ // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
+ // there exists an unsynchronised frame, while the new unsynchronisation flag in
+ // the frame header [S:4.1.2] indicates unsynchronisation.
+
+
+ //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
+ $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
+
+
+ // Extended Header
+ if (!empty($thisfile_id3v2_flags['exthead'])) {
+ $extended_header_offset = 0;
+
+ if ($id3v2_majorversion == 3) {
+
+ // v2.3 definition:
+ //Extended header size $xx xx xx xx // 32-bit integer
+ $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
+ break;
+ }
+ }
+ break; // skip rest of ID3v2 header
+ }
+ $frame_header = null;
+ $frame_name = null;
+ $frame_size = null;
+ $frame_flags = null;
+ if ($id3v2_majorversion == 2) {
+ // Frame ID $xx xx xx (three characters)
+ // Size $xx xx xx (24-bit integer)
+ // Flags $xx xx
+
+ $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
+ $framedata = substr($framedata, 6); // and leave the rest in $framedata
+ $this->warning('ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3');
+ $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
+ break;
+ }
+ }
+ break; // skip rest of ID3v2 header
+ }
+
+ if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
+ $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
+ $frame_name = $iTunesBrokenFrameNameFixed;
+ }
+ if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
+ if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
+
+ switch ($frame_name) {
+ case "\x00\x00".'MP':
+ case "\x00".'MP3':
+ case ' MP3':
+ case 'MP3e':
+ case "\x00".'MP':
+ case ' MP':
+ case 'MP3':
+ $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]');
+ break;
+
+ default:
+ $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
+ $this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
+ }
+ }
+ }
+ }
+
+ if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
+ if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
+ $this->warning('ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data');
+ }
+ }
+
+ if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
+
+ $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
+ switch ($parsedFrame['frame_name']) {
+ case 'WCOM':
+ $warning .= ' (this is known to happen with files tagged by RioPort)';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); // according to the frame text encoding
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $frame_textencoding_terminator = "\x00";
+ }
+ if (strlen($parsedFrame['data']) >= (4 + strlen($frame_textencoding_terminator))) { // shouldn't be an issue but badly-written files have been spotted in the wild with not only no contents but also missing the required language field, see https://github.com/JamesHeinrich/getID3/issues/315
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $frame_textencoding_terminator = "\x00";
+ }
+
+ $frame_imagetype = null;
+ $frame_mimetype = null;
+ if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
+ $this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
+ unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC
+ if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
+ $this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
+ public static function RemoveStringTerminator($string, $terminator) {
+ // Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present.
+ if (strpos($rawdata, 'LYRICSBEGIN') !== false) {
+
+ $this->warning('"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version);
+ // 0x0003 = DWORD+ / unsigned short (size field *= 2-byte), values are stored CDAB
+ // 0x0004 = QWORD+ / unsigned long (size field *= 4-byte), values are stored EFGHABCD
+ // 0x0005 = float / unsigned rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+ // 0x0006 = signed byte (size field *= 1-byte)
+ // 0x0007 = raw bytes (size field *= 1-byte)
+ // 0x0008 = signed short (size field *= 2-byte), values are stored as CDAB
+ // 0x0009 = signed long (size field *= 4-byte), values are stored as EFGHABCD
+ // 0x000A = float / signed rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+ // * 2 bytes data size field
+ // * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15")
+ case self::EXIF_TYPE_URATIONAL: // 0x0005 = float / unsigned rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+ case self::EXIF_TYPE_RATIONAL: // 0x000A = float / signed rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+ if (is_array($jpeg_header_data) && count($jpeg_header_data) > 0) {
+ foreach ($jpeg_header_data as $segment) {
+ // If we find an APP1 header,
+ if (strcmp($segment['SegName'], 'APP1') === 0) {
+ // And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/\x00) ,
+ if (strncmp($segment['SegData'], 'http://ns.adobe.com/xap/1.0/' . "\x00", 29) === 0) {
+ // Found a XMP/RDF block
+ // Return the XMP text
+ $xmp_data = substr($segment['SegData'], 29);
+
+ // trim() should not be necessary, but some files found in the wild with null-terminated block
+ // (known samples from Apple Aperture) causes problems elsewhere
+ // (see https://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153)
+ return trim($xmp_data);
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses a string containing XMP data (XML), and returns an array
+ * which contains all the XMP (XML) information.
+ *
+ * @param string $xmltext - a string containing the XMP data (XML) to be parsed
+ * @return array|false $xmp_array - an array containing all xmp details retrieved,
+ * FALSE - couldn't parse the XMP data.
+ */
+ public function read_XMP_array_from_text($xmltext)
+ {
+ // Check if there actually is any text to parse
+ if (trim($xmltext) == '')
+ {
+ return false;
+ }
+
+ // Create an instance of a xml parser to parse the XML text
+ $xml_parser = xml_parser_create('UTF-8');
+
+ // Change: Fixed problem that caused the whitespace (especially newlines) to be destroyed when converting xml text to an xml array, as of revision 1.10
+
+ // We would like to remove unneccessary white space, but this will also
+ // remove things like newlines (
) in the XML values, so white space
+ // will have to be removed later
+ if (xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 0) == false)
+ {
+ // Error setting case folding - destroy the parser and return
+ xml_parser_free($xml_parser);
+ return false;
+ }
+
+ // to use XML code correctly we have to turn case folding
+ // (uppercasing) off. XML is case sensitive and upper
+ // casing is in reality XML standards violation
+ if (xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0) == false)
+ {
+ // Error setting case folding - destroy the parser and return
+ xml_parser_free($xml_parser);
+ return false;
+ }
+
+ // Parse the XML text into a array structure
+ if (xml_parse_into_struct($xml_parser, $xmltext, $values, $tags) == 0)
+ {
+ // Error Parsing XML - destroy the parser and return
+ xml_parser_free($xml_parser);
+ return false;
+ }
+
+ // Destroy the xml parser
+ xml_parser_free($xml_parser);
+
+ // Clear the output array
+ $xmp_array = array();
+
+ // The XMP data has now been parsed into an array ...
+
+ // Cycle through each of the array elements
+ $current_property = ''; // current property being processed
+ $container_index = -1; // -1 = no container open, otherwise index of container content
+ foreach ($values as $xml_elem)
+ {
+ // Syntax and Class names
+ switch ($xml_elem['tag'])
+ {
+ case 'x:xmpmeta':
+ // only defined attribute is x:xmptk written by Adobe XMP Toolkit; value is the version of the toolkit
+ break;
+
+ case 'rdf:RDF':
+ // required element immediately within x:xmpmeta; no data here
+ break;
+
+ case 'rdf:Description':
+ switch ($xml_elem['type'])
+ {
+ case 'open':
+ case 'complete':
+ if (array_key_exists('attributes', $xml_elem))
+ {
+ // rdf:Description may contain wanted attributes
+ foreach (array_keys($xml_elem['attributes']) as $key)
+ {
+ // Check whether we want this details from this attribute
+// if (in_array($key, $GLOBALS['XMP_tag_captions']))
+ if (!empty($this->filename) && is_readable($this->filename) && getID3::is_writable($this->filename) && is_file($this->filename)) {
+ $this->setRealFileSize();
+ if (($this->filesize <= 0) || !getid3_lib::intValueSupported($this->filesize)) {
+ $this->errors[] = 'Unable to WriteID3v1('.$this->filename.') because filesize ('.$this->filesize.') is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
+ return false;
+ }
+ if ($fp_source = fopen($this->filename, 'r+b')) {
+ fseek($fp_source, -128, SEEK_END);
+ if (fread($fp_source, 3) == 'TAG') {
+ fseek($fp_source, -128, SEEK_END); // overwrite existing ID3v1 tag
+ } else {
+ fseek($fp_source, 0, SEEK_END); // append new ID3v1 tag
+ foreach ($ThisFileInfo['tags']['id3v1'] as $key => $value) {
+ $id3v1data[$key] = implode(',', $value);
+ }
+ $this->tag_data = $id3v1data;
+ return $this->WriteID3v1();
+ }
+ return false;
+ }
+
+ /**
+ * @return bool
+ */
+ public function RemoveID3v1() {
+ // File MUST be writeable - CHMOD(646) at least
+ if (!empty($this->filename) && is_readable($this->filename) && getID3::is_writable($this->filename) && is_file($this->filename)) {
+ $this->setRealFileSize();
+ if (($this->filesize <= 0) || !getid3_lib::intValueSupported($this->filesize)) {
+ $this->errors[] = 'Unable to RemoveID3v1('.$this->filename.') because filesize ('.$this->filesize.') is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
+ return false;
+ }
+ if ($fp_source = fopen($this->filename, 'r+b')) {
+
+ fseek($fp_source, -128, SEEK_END);
+ if (fread($fp_source, 3) == 'TAG') {
+ ftruncate($fp_source, $this->filesize - 128);
+ } else {
+ // no ID3v1 tag to begin with - do nothing
+ }
+ fclose($fp_source);
+ return true;
+
+ } else {
+ $this->errors[] = 'Could not fopen('.$this->filename.', "r+b")';
+ }
+ } else {
+ $this->errors[] = $this->filename.' is not writeable';
+ }
+ return false;
+ }
+
+ /**
+ * @return bool
+ */
+ public function setRealFileSize() {
+ if (PHP_INT_MAX > 2147483647) {
+ $this->filesize = filesize($this->filename);
+ return true;
+ }
+ // 32-bit PHP will not return correct values for filesize() if file is >=2GB
+ // but getID3->analyze() has workarounds to get actual filesize
+ $flag2 .= $GroupingIdentity ? '1' : '0'; // k - Grouping identity (true == contains group information)
+ $flag2 .= '00000';
+ break;
+
+ default:
+ return false;
+
+ }
+ return chr(bindec($flag1)).chr(bindec($flag2));
+ }
+
+ /**
+ * @param string $frame_name
+ * @param array $source_data_array
+ *
+ * @return string|false
+ */
+ public function GenerateID3v2FrameData($frame_name, $source_data_array) {
+ if (!getid3_id3v2::IsValidID3v2FrameName($frame_name, $this->majorversion)) {
+ return false;
+ }
+ $framedata = '';
+
+ if (($this->majorversion < 3) || ($this->majorversion > 4)) {
+
+ $this->errors[] = 'Only ID3v2.3 and ID3v2.4 are supported in GenerateID3v2FrameData()';
+
+ } else { // $this->majorversion 3 or 4
+
+ switch ($frame_name) {
+ case 'UFID':
+ // 4.1 UFID Unique file identifier
+ // Owner identifier <text string> $00
+ // Identifier <up to 64 bytes binary data>
+ if (strlen($source_data_array['data']) > 64) {
+ $this->errors[] = 'Identifier not allowed to be longer than 64 bytes in '.$frame_name.' (supplied data was '.strlen($source_data_array['data']).' bytes long)';
+ $this->errors[] = 'Content Descriptor must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name;
+ $this->errors[] = 'Language followed by Content Descriptor must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name;
+ $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')';
+ $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID + Data ('.$source_data_array['ownerid'].' + '.$source_data_array['data'].')';
+ $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')';
+ $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID + Data ('.$source_data_array['ownerid'].' + '.$source_data_array['data'].')';
+ $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')';
+ $ID3v2ShortFrameNameLookup[4]['year'] = 'TDRC'; // subset of ISO 8601: valid timestamps are yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and yyyy-MM-ddTHH:mm:ss. All time stamps are UTC
+ $this->errors[] = 'metaflac is not (yet) compatible with OggFLAC files';
+ return false;
+ case 'vorbis':
+ $AllowedTagFormats = array('vorbiscomment');
+ break;
+ default:
+ $this->errors[] = 'metaflac is not (yet) compatible with Ogg files other than OggVorbis';
+ return false;
+ }
+ break;
+
+ default:
+ $AllowedTagFormats = array();
+ break;
+ }
+ foreach ($this->tagformats as $requested_tag_format) {
+ if (!in_array($requested_tag_format, $AllowedTagFormats)) {
+ $errormessage = 'Tag format "'.$requested_tag_format.'" is not allowed on "'.(isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '');
+ if (($success = $vorbiscomment_writer->DeleteVorbisComment()) === false) {
+ $this->errors[] = 'DeleteVorbisComment() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $vorbiscomment_writer->errors)).'</LI></UL></PRE>';
+ }
+ break;
+
+ case 'metaflac':
+ $metaflac_writer = new getid3_write_metaflac;
+ $metaflac_writer->filename = $this->filename;
+ if (($success = $metaflac_writer->DeleteMetaFLAC()) === false) {
+ $this->errors[] = 'DeleteMetaFLAC() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $metaflac_writer->errors)).'</LI></UL></PRE>';
+ }
+ break;
+
+ case 'lyrics3':
+ $lyrics3_writer = new getid3_write_lyrics3;
+ $lyrics3_writer->filename = $this->filename;
+ if (($success = $lyrics3_writer->DeleteLyrics3()) === false) {
+ $this->errors[] = 'DeleteLyrics3() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $lyrics3_writer->errors)).'</LI></UL></PRE>';
+ }
+ break;
+
+ case 'real':
+ $real_writer = new getid3_write_real;
+ $real_writer->filename = $this->filename;
+ if (($success = $real_writer->RemoveReal()) === false) {
+ $this->errors[] = 'RemoveReal() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $real_writer->errors)).'</LI></UL></PRE>';
+ }
+ break;
+
+ default:
+ $this->errors[] = 'Invalid tag format to delete: "'.$DeleteTagFormat.'"';
+ return false;
+ }
+ if (!$success) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @param string $TagFormat
+ * @param array $tag_data
+ *
+ * @return bool
+ * @throws Exception
+ */
+ public function MergeExistingTagData($TagFormat, &$tag_data) {
+ // Merge supplied data with existing data, if requested
+ if ($this->overwrite_tags) {
+ // do nothing - ignore previous data
+ } else {
+ throw new Exception('$this->overwrite_tags=false is known to be buggy in this version of getID3. Check http://github.com/JamesHeinrich/getID3 for a newer version.');
+// if (!isset($this->ThisFileInfo['tags'][$TagFormat])) {
+ //$tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16', $value); // output is UTF-16LE+BOM or UTF-16BE+BOM depending on system architecture
+ $tag_data_id3v2[$ID3v2_framename][$key]['data'] = "\xFF\xFE".getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16LE', $value); // force LittleEndian order version of UTF-16