Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 8 additions & 16 deletions src/wp-includes/ID3/getid3.lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
}
}

// Available since PHP 7.0 (2015-Dec-03 https://www.php.net/ChangeLog-7.php)
if (!defined('PHP_INT_MIN')) {
define('PHP_INT_MIN', ~PHP_INT_MAX);
}

class getid3_lib
{
/**
Expand Down Expand Up @@ -74,7 +79,7 @@ public static function trunc($floatnumber) {
/**
* @param int|null $variable
* @param-out int $variable
* @param int $increment
* @param int $increment
*
* @return bool
*/
Expand Down Expand Up @@ -113,21 +118,8 @@ public static function CastAsInt($floatnum) {
* @return bool
*/
public static function intValueSupported($num) {
// check if integers are 64-bit
static $hasINT64 = null;
if ($hasINT64 === null) { // 10x faster than is_null()
/** @var int|float|object $bigInt */
$bigInt = pow(2, 31);
$hasINT64 = is_int($bigInt); // 32-bit int are limited to (2^31)-1
if (!$hasINT64 && !defined('PHP_INT_MIN')) {
define('PHP_INT_MIN', ~PHP_INT_MAX);
}
}
// if integers are 64-bit - no other check required
if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) {
return true;
}
return false;
// really should be <= and >= but trying "(int)9.2233720368548E+18" results in PHP warning "The float 9.2233720368548E+18 is not representable as an int, cast occurred"
return (($num < PHP_INT_MAX) && ($num > PHP_INT_MIN));
}

/**
Expand Down
8 changes: 7 additions & 1 deletion src/wp-includes/ID3/getid3.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ class getID3
*/
protected $startup_warning = '';

const VERSION = '1.9.24-202509040923';
const VERSION = '1.9.25-202603080933';
const FREAD_BUFFER_SIZE = 32768;

const ATTACHMENTS_NONE = false;
Expand Down Expand Up @@ -1951,6 +1951,12 @@ public function ChannelsBitratePlaytimeCalculations() {
if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
$this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']);
}

// Look up codec name if fourcc is set but codec is not
if (!empty($this->info['video']['fourcc']) && !isset($this->info['video']['codec'])) {
$this->include_module('audio-video.riff');
$this->info['video']['codec'] = getid3_riff::fourccLookup($this->info['video']['fourcc']);
}
}

/**
Expand Down
176 changes: 163 additions & 13 deletions src/wp-includes/ID3/module.audio-video.matroska.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,19 @@
define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- Relative position of the data that should be in position of the virtual block.


/**
* Matroska constants
*/
define('MATROSKA_DEFAULT_TIMECODESCALE', 1000000);

/**
* Matroska scan modes are internal state flags for how much of the file we are scanning
*/
define('MATROSKA_SCAN_HEADER', 0);
define('MATROSKA_SCAN_WHOLE_FILE', 1);
define('MATROSKA_SCAN_FIRST_CLUSTER', 2);
define('MATROSKA_SCAN_LAST_CLUSTER', 3);

/**
* @tutorial http://www.matroska.org/technical/specs/index.html
*
Expand Down Expand Up @@ -241,13 +254,15 @@ class getid3_matroska extends getid3_handler
private $EBMLbuffer_length = 0;
private $current_offset = 0;
private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID);
private $scan_mode = MATROSKA_SCAN_HEADER;

/**
* @return bool
*/
public function Analyze()
{
$info = &$this->getid3->info;
$this->scan_mode = $this->parse_whole_file ? MATROSKA_SCAN_WHOLE_FILE : MATROSKA_SCAN_HEADER;

// parse container
try {
Expand All @@ -256,14 +271,25 @@ public function Analyze()
$this->error('EBML parser: '.$e->getMessage());
}

// calculate playtime
if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) {
foreach ($info['matroska']['info'] as $key => $infoarray) {
if (isset($infoarray['Duration'])) {
// TimecodeScale is how many nanoseconds each Duration unit is
$info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000);
break;
}
$this->playtimeFromMetadata($info);

// If there was no duration metadata, this might be an incomplete file or a streaming file
// We need Cluster information so we can use their timecodes to estimate playtime.
if (!isset($info['playtime_seconds']) && !$this->parse_whole_file) {
// Scan the start and end of file for Clusters to estimate duration
$this->scanStartEndForClusters($info);
}

if (isset($info['matroska']['cluster']) && is_array($info['matroska']['cluster'])) {
if (!isset($info['playtime_seconds']) && !empty($info['matroska']['cluster'])) {
// estimate playtime using clusters if not yet known
$this->calculatePlaytimeFromClusters($info);
}

// Remove cluster information from output if hide_clusters is true
// These could have been set during scanStartEndForClusters()
if ($this->hide_clusters) {
unset($info['matroska']['cluster']);
}
}

Expand Down Expand Up @@ -332,7 +358,11 @@ public function Analyze()
break;*/
}

$info['video']['streams'][$trackarray['TrackUID']] = $track_info;
if (isset($trackarray['TrackUID'])) {
$info['video']['streams'][$trackarray['TrackUID']] = $track_info;
} else {
$this->warning('Missing mandatory TrackUID for video track');
}
break;

case 2: // Audio
Expand Down Expand Up @@ -480,8 +510,11 @@ public function Analyze()
$this->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"');
break;
}

$info['audio']['streams'][$trackarray['TrackUID']] = $track_info;
if (isset($trackarray['TrackUID'])) {
$info['audio']['streams'][$trackarray['TrackUID']] = $track_info;
} else {
$this->warning('Missing mandatory TrackUID for audio track');
}
break;
}
}
Expand Down Expand Up @@ -1246,12 +1279,17 @@ private function parseEBML(&$info) {
}
$this->current_offset = $subelement['end'];
}
if (!$this->hide_clusters) {

if (!$this->hide_clusters || $this->playtimeFromMetadata($info) === false) {
$info['matroska']['cluster'][] = $cluster_entry;
}
if ($this->scan_mode === MATROSKA_SCAN_FIRST_CLUSTER) {
// Stop parsing after finding first cluster
return;
}

// check to see if all the data we need exists already, if so, break out of the loop
if (!$this->parse_whole_file) {
if ($this->scan_mode === MATROSKA_SCAN_HEADER) {
if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) {
if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) {
if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) {
Expand Down Expand Up @@ -1919,4 +1957,116 @@ private static function getDefaultStreamInfo($streams)
return $info;
}

/**
* @param array $info
*
* @return float|bool Duration when present in metadata or false
*/
private function playtimeFromMetadata(&$info) {
if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) {
foreach ($info['matroska']['info'] as $infoarray) {
if (isset($infoarray['Duration'])) {
// TimecodeScale is how many nanoseconds each Duration unit is
$info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : MATROSKA_DEFAULT_TIMECODESCALE) / 1000000000);
return $info['playtime_seconds'];
}
}
}
return false;
}

/**
* @param int $offset New starting offset for the buffer
*
* @return void
*/
private function resetParserBuffer($offset) {
$this->current_offset = $offset;
$this->EBMLbuffer = '';
$this->EBMLbuffer_offset = 0;
$this->EBMLbuffer_length = 0;
}

/**
* Scan start and end of file for cluster information when Duration is missing
* Only use this if no Duration was found in the Info element and we are not in parse_whole_file mode
*
* @param array $info
*
* @return void
*/
private function scanStartEndForClusters(&$info) {
// Scan beginning of file for first cluster
$this->resetParserBuffer($info['avdataoffset']);
$this->scan_mode = MATROSKA_SCAN_FIRST_CLUSTER;

try {
$this->parseEBML($info);
} catch (Exception $e) {
$this->error('EBML parser (start of file): '.$e->getMessage());
}

// Scan end of file for last cluster
if (is_array($info['matroska']['cluster']) && !empty($info['matroska']['cluster'])) {
// Scan maximum 1MB window before EOF
$this->resetParserBuffer(max(0, $info['avdataend'] - (1024 * 1024)));
$this->scan_mode = MATROSKA_SCAN_LAST_CLUSTER;

try {
$this->parseEBML($info);
} catch (Exception $e) {
$this->error('EBML parser (end of file): '.$e->getMessage());
}
}

// Reset to header parsing mode (this method is only called during header-only parsing)
$this->scan_mode = MATROSKA_SCAN_HEADER;
}

/**
* Fetch TimecodeScale from Info element
*
* @param array $info
*
* @return int TimecodeScale value
*/
private function getTimecodeScale(&$info) {
$timecodeScale = MATROSKA_DEFAULT_TIMECODESCALE;
if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) {
foreach ($info['matroska']['info'] as $infoarray) {
if (isset($infoarray['TimecodeScale'])) {
$timecodeScale = $infoarray['TimecodeScale'];
break;
}
}
}
return $timecodeScale;
}

/**
* Calculate duration from scanned cluster timecodes
*
* @param array $info
*
* @return void
*/
private function calculatePlaytimeFromClusters(&$info) {
$minTimecode = null;
$maxTimecode = null;
if (isset($info['matroska']['cluster']) && is_array($info['matroska']['cluster'])) {
foreach ($info['matroska']['cluster'] as $cluster) {
if (isset($cluster['ClusterTimecode'])) {
if ($minTimecode === null || $cluster['ClusterTimecode'] < $minTimecode) {
$minTimecode = $cluster['ClusterTimecode'];
}
if ($maxTimecode === null || $cluster['ClusterTimecode'] > $maxTimecode) {
$maxTimecode = $cluster['ClusterTimecode'];
}
}
}
}
if ($maxTimecode !== null && $minTimecode !== null && $maxTimecode > $minTimecode) {
$info['playtime_seconds'] = ($maxTimecode - $minTimecode) * ($this->getTimecodeScale($info) / 1000000000);
}
}
}
Loading
Loading