Rekording SoundMap DSS2016 v0.1
208
api/getID3/extension.cache.dbm.php
Executable file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// extension.cache.dbm.php - part of getID3() //
|
||||
// Please see readme.txt for more information //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// This extension written by Allan Hansen <ahØartemis*dk> //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* This is a caching extension for getID3(). It works the exact same
|
||||
* way as the getID3 class, but return cached information very fast
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Normal getID3 usage (example):
|
||||
*
|
||||
* require_once 'getid3/getid3.php';
|
||||
* $getID3 = new getID3;
|
||||
* $getID3->encoding = 'UTF-8';
|
||||
* $info1 = $getID3->analyze('file1.flac');
|
||||
* $info2 = $getID3->analyze('file2.wv');
|
||||
*
|
||||
* getID3_cached usage:
|
||||
*
|
||||
* require_once 'getid3/getid3.php';
|
||||
* require_once 'getid3/getid3/extension.cache.dbm.php';
|
||||
* $getID3 = new getID3_cached('db3', '/tmp/getid3_cache.dbm',
|
||||
* '/tmp/getid3_cache.lock');
|
||||
* $getID3->encoding = 'UTF-8';
|
||||
* $info1 = $getID3->analyze('file1.flac');
|
||||
* $info2 = $getID3->analyze('file2.wv');
|
||||
*
|
||||
*
|
||||
* Supported Cache Types
|
||||
*
|
||||
* SQL Databases: (use extension.cache.mysql)
|
||||
*
|
||||
* cache_type cache_options
|
||||
* -------------------------------------------------------------------
|
||||
* mysql host, database, username, password
|
||||
*
|
||||
*
|
||||
* DBM-Style Databases: (this extension)
|
||||
*
|
||||
* cache_type cache_options
|
||||
* -------------------------------------------------------------------
|
||||
* gdbm dbm_filename, lock_filename
|
||||
* ndbm dbm_filename, lock_filename
|
||||
* db2 dbm_filename, lock_filename
|
||||
* db3 dbm_filename, lock_filename
|
||||
* db4 dbm_filename, lock_filename (PHP5 required)
|
||||
*
|
||||
* PHP must have write access to both dbm_filename and lock_filename.
|
||||
*
|
||||
*
|
||||
* Recommended Cache Types
|
||||
*
|
||||
* Infrequent updates, many reads any DBM
|
||||
* Frequent updates mysql
|
||||
*/
|
||||
|
||||
|
||||
class getID3_cached_dbm extends getID3
|
||||
{
|
||||
|
||||
// public: constructor - see top of this file for cache type and cache_options
|
||||
public function getID3_cached_dbm($cache_type, $dbm_filename, $lock_filename) {
|
||||
|
||||
// Check for dba extension
|
||||
if (!extension_loaded('dba')) {
|
||||
throw new Exception('PHP is not compiled with dba support, required to use DBM style cache.');
|
||||
}
|
||||
|
||||
// Check for specific dba driver
|
||||
if (!function_exists('dba_handlers') || !in_array($cache_type, dba_handlers())) {
|
||||
throw new Exception('PHP is not compiled --with '.$cache_type.' support, required to use DBM style cache.');
|
||||
}
|
||||
|
||||
// Create lock file if needed
|
||||
if (!file_exists($lock_filename)) {
|
||||
if (!touch($lock_filename)) {
|
||||
throw new Exception('failed to create lock file: '.$lock_filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Open lock file for writing
|
||||
if (!is_writeable($lock_filename)) {
|
||||
throw new Exception('lock file: '.$lock_filename.' is not writable');
|
||||
}
|
||||
$this->lock = fopen($lock_filename, 'w');
|
||||
|
||||
// Acquire exclusive write lock to lock file
|
||||
flock($this->lock, LOCK_EX);
|
||||
|
||||
// Create dbm-file if needed
|
||||
if (!file_exists($dbm_filename)) {
|
||||
if (!touch($dbm_filename)) {
|
||||
throw new Exception('failed to create dbm file: '.$dbm_filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to open dbm file for writing
|
||||
$this->dba = dba_open($dbm_filename, 'w', $cache_type);
|
||||
if (!$this->dba) {
|
||||
|
||||
// Failed - create new dbm file
|
||||
$this->dba = dba_open($dbm_filename, 'n', $cache_type);
|
||||
|
||||
if (!$this->dba) {
|
||||
throw new Exception('failed to create dbm file: '.$dbm_filename);
|
||||
}
|
||||
|
||||
// Insert getID3 version number
|
||||
dba_insert(getID3::VERSION, getID3::VERSION, $this->dba);
|
||||
}
|
||||
|
||||
// Init misc values
|
||||
$this->cache_type = $cache_type;
|
||||
$this->dbm_filename = $dbm_filename;
|
||||
|
||||
// Register destructor
|
||||
register_shutdown_function(array($this, '__destruct'));
|
||||
|
||||
// Check version number and clear cache if changed
|
||||
if (dba_fetch(getID3::VERSION, $this->dba) != getID3::VERSION) {
|
||||
$this->clear_cache();
|
||||
}
|
||||
|
||||
parent::getID3();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public: destructor
|
||||
public function __destruct() {
|
||||
|
||||
// Close dbm file
|
||||
dba_close($this->dba);
|
||||
|
||||
// Release exclusive lock
|
||||
flock($this->lock, LOCK_UN);
|
||||
|
||||
// Close lock file
|
||||
fclose($this->lock);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public: clear cache
|
||||
public function clear_cache() {
|
||||
|
||||
// Close dbm file
|
||||
dba_close($this->dba);
|
||||
|
||||
// Create new dbm file
|
||||
$this->dba = dba_open($this->dbm_filename, 'n', $this->cache_type);
|
||||
|
||||
if (!$this->dba) {
|
||||
throw new Exception('failed to clear cache/recreate dbm file: '.$this->dbm_filename);
|
||||
}
|
||||
|
||||
// Insert getID3 version number
|
||||
dba_insert(getID3::VERSION, getID3::VERSION, $this->dba);
|
||||
|
||||
// Re-register shutdown function
|
||||
register_shutdown_function(array($this, '__destruct'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public: analyze file
|
||||
public function analyze($filename) {
|
||||
|
||||
if (file_exists($filename)) {
|
||||
|
||||
// Calc key filename::mod_time::size - should be unique
|
||||
$key = $filename.'::'.filemtime($filename).'::'.filesize($filename);
|
||||
|
||||
// Loopup key
|
||||
$result = dba_fetch($key, $this->dba);
|
||||
|
||||
// Hit
|
||||
if ($result !== false) {
|
||||
return unserialize($result);
|
||||
}
|
||||
}
|
||||
|
||||
// Miss
|
||||
$result = parent::analyze($filename);
|
||||
|
||||
// Save result
|
||||
if (file_exists($filename)) {
|
||||
dba_insert($key, serialize($result), $this->dba);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
171
api/getID3/extension.cache.mysql.php
Executable file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// extension.cache.mysql.php - part of getID3() //
|
||||
// Please see readme.txt for more information //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// This extension written by Allan Hansen <ahØartemis*dk> //
|
||||
// Table name mod by Carlo Capocasa <calroØcarlocapocasa*com> //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* This is a caching extension for getID3(). It works the exact same
|
||||
* way as the getID3 class, but return cached information very fast
|
||||
*
|
||||
* Example: (see also demo.cache.mysql.php in /demo/)
|
||||
*
|
||||
* Normal getID3 usage (example):
|
||||
*
|
||||
* require_once 'getid3/getid3.php';
|
||||
* $getID3 = new getID3;
|
||||
* $getID3->encoding = 'UTF-8';
|
||||
* $info1 = $getID3->analyze('file1.flac');
|
||||
* $info2 = $getID3->analyze('file2.wv');
|
||||
*
|
||||
* getID3_cached usage:
|
||||
*
|
||||
* require_once 'getid3/getid3.php';
|
||||
* require_once 'getid3/getid3/extension.cache.mysql.php';
|
||||
* // 5th parameter (tablename) is optional, default is 'getid3_cache'
|
||||
* $getID3 = new getID3_cached_mysql('localhost', 'database', 'username', 'password', 'tablename');
|
||||
* $getID3->encoding = 'UTF-8';
|
||||
* $info1 = $getID3->analyze('file1.flac');
|
||||
* $info2 = $getID3->analyze('file2.wv');
|
||||
*
|
||||
*
|
||||
* Supported Cache Types (this extension)
|
||||
*
|
||||
* SQL Databases:
|
||||
*
|
||||
* cache_type cache_options
|
||||
* -------------------------------------------------------------------
|
||||
* mysql host, database, username, password
|
||||
*
|
||||
*
|
||||
* DBM-Style Databases: (use extension.cache.dbm)
|
||||
*
|
||||
* cache_type cache_options
|
||||
* -------------------------------------------------------------------
|
||||
* gdbm dbm_filename, lock_filename
|
||||
* ndbm dbm_filename, lock_filename
|
||||
* db2 dbm_filename, lock_filename
|
||||
* db3 dbm_filename, lock_filename
|
||||
* db4 dbm_filename, lock_filename (PHP5 required)
|
||||
*
|
||||
* PHP must have write access to both dbm_filename and lock_filename.
|
||||
*
|
||||
*
|
||||
* Recommended Cache Types
|
||||
*
|
||||
* Infrequent updates, many reads any DBM
|
||||
* Frequent updates mysql
|
||||
*/
|
||||
|
||||
|
||||
class getID3_cached_mysql extends getID3
|
||||
{
|
||||
|
||||
// private vars
|
||||
private $cursor;
|
||||
private $connection;
|
||||
|
||||
|
||||
// public: constructor - see top of this file for cache type and cache_options
|
||||
public function getID3_cached_mysql($host, $database, $username, $password, $table='getid3_cache') {
|
||||
|
||||
// Check for mysql support
|
||||
if (!function_exists('mysql_pconnect')) {
|
||||
throw new Exception('PHP not compiled with mysql support.');
|
||||
}
|
||||
|
||||
// Connect to database
|
||||
$this->connection = mysql_pconnect($host, $username, $password);
|
||||
if (!$this->connection) {
|
||||
throw new Exception('mysql_pconnect() failed - check permissions and spelling.');
|
||||
}
|
||||
|
||||
// Select database
|
||||
if (!mysql_select_db($database, $this->connection)) {
|
||||
throw new Exception('Cannot use database '.$database);
|
||||
}
|
||||
|
||||
// Set table
|
||||
$this->table = $table;
|
||||
|
||||
// Create cache table if not exists
|
||||
$this->create_table();
|
||||
|
||||
// Check version number and clear cache if changed
|
||||
$version = '';
|
||||
if ($this->cursor = mysql_query("SELECT `value` FROM `".mysql_real_escape_string($this->table)."` WHERE (`filename` = '".mysql_real_escape_string(getID3::VERSION)."') AND (`filesize` = '-1') AND (`filetime` = '-1') AND (`analyzetime` = '-1')", $this->connection)) {
|
||||
list($version) = mysql_fetch_array($this->cursor);
|
||||
}
|
||||
if ($version != getID3::VERSION) {
|
||||
$this->clear_cache();
|
||||
}
|
||||
|
||||
parent::getID3();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public: clear cache
|
||||
public function clear_cache() {
|
||||
|
||||
$this->cursor = mysql_query("DELETE FROM `".mysql_real_escape_string($this->table)."`", $this->connection);
|
||||
$this->cursor = mysql_query("INSERT INTO `".mysql_real_escape_string($this->table)."` VALUES ('".getID3::VERSION."', -1, -1, -1, '".getID3::VERSION."')", $this->connection);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public: analyze file
|
||||
public function analyze($filename) {
|
||||
|
||||
if (file_exists($filename)) {
|
||||
|
||||
// Short-hands
|
||||
$filetime = filemtime($filename);
|
||||
$filesize = filesize($filename);
|
||||
|
||||
// Lookup file
|
||||
$this->cursor = mysql_query("SELECT `value` FROM `".mysql_real_escape_string($this->table)."` WHERE (`filename` = '".mysql_real_escape_string($filename)."') AND (`filesize` = '".mysql_real_escape_string($filesize)."') AND (`filetime` = '".mysql_real_escape_string($filetime)."')", $this->connection);
|
||||
if (mysql_num_rows($this->cursor) > 0) {
|
||||
// Hit
|
||||
list($result) = mysql_fetch_array($this->cursor);
|
||||
return unserialize(base64_decode($result));
|
||||
}
|
||||
}
|
||||
|
||||
// Miss
|
||||
$analysis = parent::analyze($filename);
|
||||
|
||||
// Save result
|
||||
if (file_exists($filename)) {
|
||||
$this->cursor = mysql_query("INSERT INTO `".mysql_real_escape_string($this->table)."` (`filename`, `filesize`, `filetime`, `analyzetime`, `value`) VALUES ('".mysql_real_escape_string($filename)."', '".mysql_real_escape_string($filesize)."', '".mysql_real_escape_string($filetime)."', '".mysql_real_escape_string(time())."', '".mysql_real_escape_string(base64_encode(serialize($analysis)))."')", $this->connection);
|
||||
}
|
||||
return $analysis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// private: (re)create sql table
|
||||
private function create_table($drop=false) {
|
||||
|
||||
$this->cursor = mysql_query("CREATE TABLE IF NOT EXISTS `".mysql_real_escape_string($this->table)."` (
|
||||
`filename` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`filesize` INT(11) NOT NULL DEFAULT '0',
|
||||
`filetime` INT(11) NOT NULL DEFAULT '0',
|
||||
`analyzetime` INT(11) NOT NULL DEFAULT '0',
|
||||
`value` TEXT NOT NULL,
|
||||
PRIMARY KEY (`filename`,`filesize`,`filetime`)) ENGINE=MyISAM", $this->connection);
|
||||
echo mysql_error($this->connection);
|
||||
}
|
||||
}
|
264
api/getID3/extension.cache.sqlite3.php
Executable file
@ -0,0 +1,264 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org ///
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
/// //
|
||||
// extension.cache.sqlite3.php - part of getID3() //
|
||||
// Please see readme.txt for more information //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
/// //
|
||||
// MySQL extension written by Allan Hansen <ahØartemis*dk> //
|
||||
// Table name mod by Carlo Capocasa <calroØcarlocapocasa*com> //
|
||||
// MySQL extension was reworked for SQLite3 by Karl G. Holz <newaeonØmac*com> //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* This is a caching extension for getID3(). It works the exact same
|
||||
* way as the getID3 class, but return cached information much faster
|
||||
*
|
||||
* Normal getID3 usage (example):
|
||||
*
|
||||
* require_once 'getid3/getid3.php';
|
||||
* $getID3 = new getID3;
|
||||
* $getID3->encoding = 'UTF-8';
|
||||
* $info1 = $getID3->analyze('file1.flac');
|
||||
* $info2 = $getID3->analyze('file2.wv');
|
||||
*
|
||||
* getID3_cached usage:
|
||||
*
|
||||
* require_once 'getid3/getid3.php';
|
||||
* require_once 'getid3/extension.cache.sqlite3.php';
|
||||
* // all parameters are optional, defaults are:
|
||||
* $getID3 = new getID3_cached_sqlite3($table='getid3_cache', $hide=FALSE);
|
||||
* $getID3->encoding = 'UTF-8';
|
||||
* $info1 = $getID3->analyze('file1.flac');
|
||||
* $info2 = $getID3->analyze('file2.wv');
|
||||
*
|
||||
*
|
||||
* Supported Cache Types (this extension)
|
||||
*
|
||||
* SQL Databases:
|
||||
*
|
||||
* cache_type cache_options
|
||||
* -------------------------------------------------------------------
|
||||
* mysql host, database, username, password
|
||||
*
|
||||
* sqlite3 table='getid3_cache', hide=false (PHP5)
|
||||
*
|
||||
|
||||
*** database file will be stored in the same directory as this script,
|
||||
*** webserver must have write access to that directory!
|
||||
*** set $hide to TRUE to prefix db file with .ht to pervent access from web client
|
||||
*** this is a default setting in the Apache configuration:
|
||||
|
||||
# The following lines prevent .htaccess and .htpasswd files from being viewed by Web clients.
|
||||
|
||||
<Files ~ "^\.ht">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
Satisfy all
|
||||
</Files>
|
||||
|
||||
********************************************************************************
|
||||
*
|
||||
* -------------------------------------------------------------------
|
||||
* DBM-Style Databases: (use extension.cache.dbm)
|
||||
*
|
||||
* cache_type cache_options
|
||||
* -------------------------------------------------------------------
|
||||
* gdbm dbm_filename, lock_filename
|
||||
* ndbm dbm_filename, lock_filename
|
||||
* db2 dbm_filename, lock_filename
|
||||
* db3 dbm_filename, lock_filename
|
||||
* db4 dbm_filename, lock_filename (PHP5 required)
|
||||
*
|
||||
* PHP must have write access to both dbm_filename and lock_filename.
|
||||
*
|
||||
* Recommended Cache Types
|
||||
*
|
||||
* Infrequent updates, many reads any DBM
|
||||
* Frequent updates mysql
|
||||
********************************************************************************
|
||||
*
|
||||
* IMHO this is still a bit slow, I'm using this with MP4/MOV/ M4v files
|
||||
* there is a plan to add directory scanning and analyzing to make things work much faster
|
||||
*
|
||||
*
|
||||
*/
|
||||
class getID3_cached_sqlite3 extends getID3 {
|
||||
|
||||
/**
|
||||
* __construct()
|
||||
* @param string $table holds name of sqlite table
|
||||
* @return type
|
||||
*/
|
||||
public function __construct($table='getid3_cache', $hide=false) {
|
||||
$this->table = $table; // Set table
|
||||
$file = dirname(__FILE__).'/'.basename(__FILE__, 'php').'sqlite';
|
||||
if ($hide) {
|
||||
$file = dirname(__FILE__).'/.ht.'.basename(__FILE__, 'php').'sqlite';
|
||||
}
|
||||
$this->db = new SQLite3($file);
|
||||
$db = $this->db;
|
||||
$this->create_table(); // Create cache table if not exists
|
||||
$version = '';
|
||||
$sql = $this->version_check;
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->bindValue(':filename', getID3::VERSION, SQLITE3_TEXT);
|
||||
$result = $stmt->execute();
|
||||
list($version) = $result->fetchArray();
|
||||
if ($version != getID3::VERSION) { // Check version number and clear cache if changed
|
||||
$this->clear_cache();
|
||||
}
|
||||
return parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* close the database connection
|
||||
*/
|
||||
public function __destruct() {
|
||||
$db=$this->db;
|
||||
$db->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* hold the sqlite db
|
||||
* @var SQLite Resource
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* table to use for caching
|
||||
* @var string $table
|
||||
*/
|
||||
private $table;
|
||||
|
||||
/**
|
||||
* clear the cache
|
||||
* @access private
|
||||
* @return type
|
||||
*/
|
||||
private function clear_cache() {
|
||||
$db = $this->db;
|
||||
$sql = $this->delete_cache;
|
||||
$db->exec($sql);
|
||||
$sql = $this->set_version;
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->bindValue(':filename', getID3::VERSION, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':dirname', getID3::VERSION, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':val', getID3::VERSION, SQLITE3_TEXT);
|
||||
return $stmt->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* analyze file and cache them, if cached pull from the db
|
||||
* @param type $filename
|
||||
* @return boolean
|
||||
*/
|
||||
public function analyze($filename) {
|
||||
if (!file_exists($filename)) {
|
||||
return false;
|
||||
}
|
||||
// items to track for caching
|
||||
$filetime = filemtime($filename);
|
||||
$filesize = filesize($filename);
|
||||
// this will be saved for a quick directory lookup of analized files
|
||||
// ... why do 50 seperate sql quries when you can do 1 for the same result
|
||||
$dirname = dirname($filename);
|
||||
// Lookup file
|
||||
$db = $this->db;
|
||||
$sql = $this->get_id3_data;
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->bindValue(':filename', $filename, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':filesize', $filesize, SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':filetime', $filetime, SQLITE3_INTEGER);
|
||||
$res = $stmt->execute();
|
||||
list($result) = $res->fetchArray();
|
||||
if (count($result) > 0 ) {
|
||||
return unserialize(base64_decode($result));
|
||||
}
|
||||
// if it hasn't been analyzed before, then do it now
|
||||
$analysis = parent::analyze($filename);
|
||||
// Save result
|
||||
$sql = $this->cache_file;
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->bindValue(':filename', $filename, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':dirname', $dirname, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':filesize', $filesize, SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':filetime', $filetime, SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':atime', time(), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':val', base64_encode(serialize($analysis)), SQLITE3_TEXT);
|
||||
$res = $stmt->execute();
|
||||
return $analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* create data base table
|
||||
* this is almost the same as MySQL, with the exception of the dirname being added
|
||||
* @return type
|
||||
*/
|
||||
private function create_table() {
|
||||
$db = $this->db;
|
||||
$sql = $this->make_table;
|
||||
return $db->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* get cached directory
|
||||
*
|
||||
* This function is not in the MySQL extention, it's ment to speed up requesting multiple files
|
||||
* which is ideal for podcasting, playlists, etc.
|
||||
*
|
||||
* @access public
|
||||
* @param string $dir directory to search the cache database for
|
||||
* @return array return an array of matching id3 data
|
||||
*/
|
||||
public function get_cached_dir($dir) {
|
||||
$db = $this->db;
|
||||
$rows = array();
|
||||
$sql = $this->get_cached_dir;
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->bindValue(':dirname', $dir, SQLITE3_TEXT);
|
||||
$res = $stmt->execute();
|
||||
while ($row=$res->fetchArray()) {
|
||||
$rows[] = unserialize(base64_decode($row));
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* use the magical __get() for sql queries
|
||||
*
|
||||
* access as easy as $this->{case name}, returns NULL if query is not found
|
||||
*/
|
||||
public function __get($name) {
|
||||
switch($name) {
|
||||
case 'version_check':
|
||||
return "SELECT val FROM $this->table WHERE filename = :filename AND filesize = '-1' AND filetime = '-1' AND analyzetime = '-1'";
|
||||
break;
|
||||
case 'delete_cache':
|
||||
return "DELETE FROM $this->table";
|
||||
break;
|
||||
case 'set_version':
|
||||
return "INSERT INTO $this->table (filename, dirname, filesize, filetime, analyzetime, val) VALUES (:filename, :dirname, -1, -1, -1, :val)";
|
||||
break;
|
||||
case 'get_id3_data':
|
||||
return "SELECT val FROM $this->table WHERE filename = :filename AND filesize = :filesize AND filetime = :filetime";
|
||||
break;
|
||||
case 'cache_file':
|
||||
return "INSERT INTO $this->table (filename, dirname, filesize, filetime, analyzetime, val) VALUES (:filename, :dirname, :filesize, :filetime, :atime, :val)";
|
||||
break;
|
||||
case 'make_table':
|
||||
return "CREATE TABLE IF NOT EXISTS $this->table (filename VARCHAR(255) NOT NULL DEFAULT '', dirname VARCHAR(255) NOT NULL DEFAULT '', filesize INT(11) NOT NULL DEFAULT '0', filetime INT(11) NOT NULL DEFAULT '0', analyzetime INT(11) NOT NULL DEFAULT '0', val text not null, PRIMARY KEY (filename, filesize, filetime))";
|
||||
break;
|
||||
case 'get_cached_dir':
|
||||
return "SELECT val FROM $this->table WHERE dirname = :dirname";
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
1320
api/getID3/getid3.lib.php
Executable file
1800
api/getID3/getid3.php
Executable file
279
api/getID3/module.archive.gzip.php
Executable file
@ -0,0 +1,279 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.archive.gzip.php //
|
||||
// module for analyzing GZIP files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// Module originally written by //
|
||||
// Mike Mozolin <teddybearØmail*ru> //
|
||||
// //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_gzip extends getid3_handler {
|
||||
|
||||
// public: Optional file list - disable for speed.
|
||||
public $option_gzip_parse_contents = false; // decode gzipped files, if possible, and parse recursively (.tar.gz for example)
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'gzip';
|
||||
|
||||
$start_length = 10;
|
||||
$unpack_header = 'a1id1/a1id2/a1cmethod/a1flags/a4mtime/a1xflags/a1os';
|
||||
//+---+---+---+---+---+---+---+---+---+---+
|
||||
//|ID1|ID2|CM |FLG| MTIME |XFL|OS |
|
||||
//+---+---+---+---+---+---+---+---+---+---+
|
||||
|
||||
if ($info['filesize'] > $info['php_memory_limit']) {
|
||||
$info['error'][] = 'File is too large ('.number_format($info['filesize']).' bytes) to read into memory (limit: '.number_format($info['php_memory_limit'] / 1048576).'MB)';
|
||||
return false;
|
||||
}
|
||||
fseek($this->getid3->fp, 0);
|
||||
$buffer = fread($this->getid3->fp, $info['filesize']);
|
||||
|
||||
$arr_members = explode("\x1F\x8B\x08", $buffer);
|
||||
while (true) {
|
||||
$is_wrong_members = false;
|
||||
$num_members = intval(count($arr_members));
|
||||
for ($i = 0; $i < $num_members; $i++) {
|
||||
if (strlen($arr_members[$i]) == 0) {
|
||||
continue;
|
||||
}
|
||||
$buf = "\x1F\x8B\x08".$arr_members[$i];
|
||||
|
||||
$attr = unpack($unpack_header, substr($buf, 0, $start_length));
|
||||
if (!$this->get_os_type(ord($attr['os']))) {
|
||||
// Merge member with previous if wrong OS type
|
||||
$arr_members[$i - 1] .= $buf;
|
||||
$arr_members[$i] = '';
|
||||
$is_wrong_members = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!$is_wrong_members) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$info['gzip']['files'] = array();
|
||||
|
||||
$fpointer = 0;
|
||||
$idx = 0;
|
||||
for ($i = 0; $i < $num_members; $i++) {
|
||||
if (strlen($arr_members[$i]) == 0) {
|
||||
continue;
|
||||
}
|
||||
$thisInfo = &$info['gzip']['member_header'][++$idx];
|
||||
|
||||
$buff = "\x1F\x8B\x08".$arr_members[$i];
|
||||
|
||||
$attr = unpack($unpack_header, substr($buff, 0, $start_length));
|
||||
$thisInfo['filemtime'] = getid3_lib::LittleEndian2Int($attr['mtime']);
|
||||
$thisInfo['raw']['id1'] = ord($attr['cmethod']);
|
||||
$thisInfo['raw']['id2'] = ord($attr['cmethod']);
|
||||
$thisInfo['raw']['cmethod'] = ord($attr['cmethod']);
|
||||
$thisInfo['raw']['os'] = ord($attr['os']);
|
||||
$thisInfo['raw']['xflags'] = ord($attr['xflags']);
|
||||
$thisInfo['raw']['flags'] = ord($attr['flags']);
|
||||
|
||||
$thisInfo['flags']['crc16'] = (bool) ($thisInfo['raw']['flags'] & 0x02);
|
||||
$thisInfo['flags']['extra'] = (bool) ($thisInfo['raw']['flags'] & 0x04);
|
||||
$thisInfo['flags']['filename'] = (bool) ($thisInfo['raw']['flags'] & 0x08);
|
||||
$thisInfo['flags']['comment'] = (bool) ($thisInfo['raw']['flags'] & 0x10);
|
||||
|
||||
$thisInfo['compression'] = $this->get_xflag_type($thisInfo['raw']['xflags']);
|
||||
|
||||
$thisInfo['os'] = $this->get_os_type($thisInfo['raw']['os']);
|
||||
if (!$thisInfo['os']) {
|
||||
$info['error'][] = 'Read error on gzip file';
|
||||
return false;
|
||||
}
|
||||
|
||||
$fpointer = 10;
|
||||
$arr_xsubfield = array();
|
||||
// bit 2 - FLG.FEXTRA
|
||||
//+---+---+=================================+
|
||||
//| XLEN |...XLEN bytes of "extra field"...|
|
||||
//+---+---+=================================+
|
||||
if ($thisInfo['flags']['extra']) {
|
||||
$w_xlen = substr($buff, $fpointer, 2);
|
||||
$xlen = getid3_lib::LittleEndian2Int($w_xlen);
|
||||
$fpointer += 2;
|
||||
|
||||
$thisInfo['raw']['xfield'] = substr($buff, $fpointer, $xlen);
|
||||
// Extra SubFields
|
||||
//+---+---+---+---+==================================+
|
||||
//|SI1|SI2| LEN |... LEN bytes of subfield data ...|
|
||||
//+---+---+---+---+==================================+
|
||||
$idx = 0;
|
||||
while (true) {
|
||||
if ($idx >= $xlen) {
|
||||
break;
|
||||
}
|
||||
$si1 = ord(substr($buff, $fpointer + $idx++, 1));
|
||||
$si2 = ord(substr($buff, $fpointer + $idx++, 1));
|
||||
if (($si1 == 0x41) && ($si2 == 0x70)) {
|
||||
$w_xsublen = substr($buff, $fpointer + $idx, 2);
|
||||
$xsublen = getid3_lib::LittleEndian2Int($w_xsublen);
|
||||
$idx += 2;
|
||||
$arr_xsubfield[] = substr($buff, $fpointer + $idx, $xsublen);
|
||||
$idx += $xsublen;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$fpointer += $xlen;
|
||||
}
|
||||
// bit 3 - FLG.FNAME
|
||||
//+=========================================+
|
||||
//|...original file name, zero-terminated...|
|
||||
//+=========================================+
|
||||
// GZIP files may have only one file, with no filename, so assume original filename is current filename without .gz
|
||||
$thisInfo['filename'] = preg_replace('#\.gz$#i', '', $info['filename']);
|
||||
if ($thisInfo['flags']['filename']) {
|
||||
while (true) {
|
||||
if (ord($buff[$fpointer]) == 0) {
|
||||
$fpointer++;
|
||||
break;
|
||||
}
|
||||
$thisInfo['filename'] .= $buff[$fpointer];
|
||||
$fpointer++;
|
||||
}
|
||||
}
|
||||
// bit 4 - FLG.FCOMMENT
|
||||
//+===================================+
|
||||
//|...file comment, zero-terminated...|
|
||||
//+===================================+
|
||||
if ($thisInfo['flags']['comment']) {
|
||||
while (true) {
|
||||
if (ord($buff[$fpointer]) == 0) {
|
||||
$fpointer++;
|
||||
break;
|
||||
}
|
||||
$thisInfo['comment'] .= $buff[$fpointer];
|
||||
$fpointer++;
|
||||
}
|
||||
}
|
||||
// bit 1 - FLG.FHCRC
|
||||
//+---+---+
|
||||
//| CRC16 |
|
||||
//+---+---+
|
||||
if ($thisInfo['flags']['crc16']) {
|
||||
$w_crc = substr($buff, $fpointer, 2);
|
||||
$thisInfo['crc16'] = getid3_lib::LittleEndian2Int($w_crc);
|
||||
$fpointer += 2;
|
||||
}
|
||||
// bit 0 - FLG.FTEXT
|
||||
//if ($thisInfo['raw']['flags'] & 0x01) {
|
||||
// Ignored...
|
||||
//}
|
||||
// bits 5, 6, 7 - reserved
|
||||
|
||||
$thisInfo['crc32'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 8, 4));
|
||||
$thisInfo['filesize'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 4));
|
||||
|
||||
$info['gzip']['files'] = getid3_lib::array_merge_clobber($info['gzip']['files'], getid3_lib::CreateDeepArray($thisInfo['filename'], '/', $thisInfo['filesize']));
|
||||
|
||||
if ($this->option_gzip_parse_contents) {
|
||||
// Try to inflate GZip
|
||||
$csize = 0;
|
||||
$inflated = '';
|
||||
$chkcrc32 = '';
|
||||
if (function_exists('gzinflate')) {
|
||||
$cdata = substr($buff, $fpointer);
|
||||
$cdata = substr($cdata, 0, strlen($cdata) - 8);
|
||||
$csize = strlen($cdata);
|
||||
$inflated = gzinflate($cdata);
|
||||
|
||||
// Calculate CRC32 for inflated content
|
||||
$thisInfo['crc32_valid'] = (bool) (sprintf('%u', crc32($inflated)) == $thisInfo['crc32']);
|
||||
|
||||
// determine format
|
||||
$formattest = substr($inflated, 0, 32774);
|
||||
$getid3_temp = new getID3();
|
||||
$determined_format = $getid3_temp->GetFileFormat($formattest);
|
||||
unset($getid3_temp);
|
||||
|
||||
// file format is determined
|
||||
$determined_format['module'] = (isset($determined_format['module']) ? $determined_format['module'] : '');
|
||||
switch ($determined_format['module']) {
|
||||
case 'tar':
|
||||
// view TAR-file info
|
||||
if (file_exists(GETID3_INCLUDEPATH.$determined_format['include']) && include_once(GETID3_INCLUDEPATH.$determined_format['include'])) {
|
||||
if (($temp_tar_filename = tempnam(GETID3_TEMP_DIR, 'getID3')) === false) {
|
||||
// can't find anywhere to create a temp file, abort
|
||||
$info['error'][] = 'Unable to create temp file to parse TAR inside GZIP file';
|
||||
break;
|
||||
}
|
||||
if ($fp_temp_tar = fopen($temp_tar_filename, 'w+b')) {
|
||||
fwrite($fp_temp_tar, $inflated);
|
||||
fclose($fp_temp_tar);
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($temp_tar_filename);
|
||||
$getid3_tar = new getid3_tar($getid3_temp);
|
||||
$getid3_tar->Analyze();
|
||||
$info['gzip']['member_header'][$idx]['tar'] = $getid3_temp->info['tar'];
|
||||
unset($getid3_temp, $getid3_tar);
|
||||
unlink($temp_tar_filename);
|
||||
} else {
|
||||
$info['error'][] = 'Unable to fopen() temp file to parse TAR inside GZIP file';
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '':
|
||||
default:
|
||||
// unknown or unhandled format
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Converts the OS type
|
||||
public function get_os_type($key) {
|
||||
static $os_type = array(
|
||||
'0' => 'FAT filesystem (MS-DOS, OS/2, NT/Win32)',
|
||||
'1' => 'Amiga',
|
||||
'2' => 'VMS (or OpenVMS)',
|
||||
'3' => 'Unix',
|
||||
'4' => 'VM/CMS',
|
||||
'5' => 'Atari TOS',
|
||||
'6' => 'HPFS filesystem (OS/2, NT)',
|
||||
'7' => 'Macintosh',
|
||||
'8' => 'Z-System',
|
||||
'9' => 'CP/M',
|
||||
'10' => 'TOPS-20',
|
||||
'11' => 'NTFS filesystem (NT)',
|
||||
'12' => 'QDOS',
|
||||
'13' => 'Acorn RISCOS',
|
||||
'255' => 'unknown'
|
||||
);
|
||||
return (isset($os_type[$key]) ? $os_type[$key] : '');
|
||||
}
|
||||
|
||||
// Converts the eXtra FLags
|
||||
public function get_xflag_type($key) {
|
||||
static $xflag_type = array(
|
||||
'0' => 'unknown',
|
||||
'2' => 'maximum compression',
|
||||
'4' => 'fastest algorithm'
|
||||
);
|
||||
return (isset($xflag_type[$key]) ? $xflag_type[$key] : '');
|
||||
}
|
||||
}
|
||||
|
50
api/getID3/module.archive.rar.php
Executable file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.archive.rar.php //
|
||||
// module for analyzing RAR files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_rar extends getid3_handler
|
||||
{
|
||||
|
||||
public $option_use_rar_extension = false;
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'rar';
|
||||
|
||||
if ($this->option_use_rar_extension === true) {
|
||||
if (function_exists('rar_open')) {
|
||||
if ($rp = rar_open($info['filenamepath'])) {
|
||||
$info['rar']['files'] = array();
|
||||
$entries = rar_list($rp);
|
||||
foreach ($entries as $entry) {
|
||||
$info['rar']['files'] = getid3_lib::array_merge_clobber($info['rar']['files'], getid3_lib::CreateDeepArray($entry->getName(), '/', $entry->getUnpackedSize()));
|
||||
}
|
||||
rar_close($rp);
|
||||
return true;
|
||||
} else {
|
||||
$info['error'][] = 'failed to rar_open('.$info['filename'].')';
|
||||
}
|
||||
} else {
|
||||
$info['error'][] = 'RAR support does not appear to be available in this PHP installation';
|
||||
}
|
||||
} else {
|
||||
$info['error'][] = 'PHP-RAR processing has been disabled (set $getid3_rar->option_use_rar_extension=true to enable)';
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
94
api/getID3/module.archive.szip.php
Executable file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.archive.szip.php //
|
||||
// module for analyzing SZIP compressed files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_szip extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$SZIPHeader = fread($this->getid3->fp, 6);
|
||||
if (substr($SZIPHeader, 0, 4) != "SZ\x0A\x04") {
|
||||
$info['error'][] = 'Expecting "53 5A 0A 04" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($SZIPHeader, 0, 4)).'"';
|
||||
return false;
|
||||
}
|
||||
$info['fileformat'] = 'szip';
|
||||
$info['szip']['major_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 4, 1));
|
||||
$info['szip']['minor_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 5, 1));
|
||||
|
||||
while (!feof($this->getid3->fp)) {
|
||||
$NextBlockID = fread($this->getid3->fp, 2);
|
||||
switch ($NextBlockID) {
|
||||
case 'SZ':
|
||||
// Note that szip files can be concatenated, this has the same effect as
|
||||
// concatenating the files. this also means that global header blocks
|
||||
// might be present between directory/data blocks.
|
||||
fseek($this->getid3->fp, 4, SEEK_CUR);
|
||||
break;
|
||||
|
||||
case 'BH':
|
||||
$BHheaderbytes = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 3));
|
||||
$BHheaderdata = fread($this->getid3->fp, $BHheaderbytes);
|
||||
$BHheaderoffset = 0;
|
||||
while (strpos($BHheaderdata, "\x00", $BHheaderoffset) > 0) {
|
||||
//filename as \0 terminated string (empty string indicates end)
|
||||
//owner as \0 terminated string (empty is same as last file)
|
||||
//group as \0 terminated string (empty is same as last file)
|
||||
//3 byte filelength in this block
|
||||
//2 byte access flags
|
||||
//4 byte creation time (like in unix)
|
||||
//4 byte modification time (like in unix)
|
||||
//4 byte access time (like in unix)
|
||||
|
||||
$BHdataArray['filename'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "\x00"));
|
||||
$BHheaderoffset += (strlen($BHdataArray['filename']) + 1);
|
||||
|
||||
$BHdataArray['owner'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "\x00"));
|
||||
$BHheaderoffset += (strlen($BHdataArray['owner']) + 1);
|
||||
|
||||
$BHdataArray['group'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "\x00"));
|
||||
$BHheaderoffset += (strlen($BHdataArray['group']) + 1);
|
||||
|
||||
$BHdataArray['filelength'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 3));
|
||||
$BHheaderoffset += 3;
|
||||
|
||||
$BHdataArray['access_flags'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 2));
|
||||
$BHheaderoffset += 2;
|
||||
|
||||
$BHdataArray['creation_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4));
|
||||
$BHheaderoffset += 4;
|
||||
|
||||
$BHdataArray['modification_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4));
|
||||
$BHheaderoffset += 4;
|
||||
|
||||
$BHdataArray['access_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4));
|
||||
$BHheaderoffset += 4;
|
||||
|
||||
$info['szip']['BH'][] = $BHdataArray;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
176
api/getID3/module.archive.tar.php
Executable file
@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.archive.tar.php //
|
||||
// module for analyzing TAR files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// Module originally written by //
|
||||
// Mike Mozolin <teddybearØmail*ru> //
|
||||
// //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_tar extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'tar';
|
||||
$info['tar']['files'] = array();
|
||||
|
||||
$unpack_header = 'a100fname/a8mode/a8uid/a8gid/a12size/a12mtime/a8chksum/a1typflag/a100lnkname/a6magic/a2ver/a32uname/a32gname/a8devmaj/a8devmin/a155prefix';
|
||||
$null_512k = str_repeat("\x00", 512); // end-of-file marker
|
||||
|
||||
fseek($this->getid3->fp, 0);
|
||||
while (!feof($this->getid3->fp)) {
|
||||
$buffer = fread($this->getid3->fp, 512);
|
||||
if (strlen($buffer) < 512) {
|
||||
break;
|
||||
}
|
||||
|
||||
// check the block
|
||||
$checksum = 0;
|
||||
for ($i = 0; $i < 148; $i++) {
|
||||
$checksum += ord($buffer{$i});
|
||||
}
|
||||
for ($i = 148; $i < 156; $i++) {
|
||||
$checksum += ord(' ');
|
||||
}
|
||||
for ($i = 156; $i < 512; $i++) {
|
||||
$checksum += ord($buffer{$i});
|
||||
}
|
||||
$attr = unpack($unpack_header, $buffer);
|
||||
$name = (isset($attr['fname'] ) ? trim($attr['fname'] ) : '');
|
||||
$mode = octdec(isset($attr['mode'] ) ? trim($attr['mode'] ) : '');
|
||||
$uid = octdec(isset($attr['uid'] ) ? trim($attr['uid'] ) : '');
|
||||
$gid = octdec(isset($attr['gid'] ) ? trim($attr['gid'] ) : '');
|
||||
$size = octdec(isset($attr['size'] ) ? trim($attr['size'] ) : '');
|
||||
$mtime = octdec(isset($attr['mtime'] ) ? trim($attr['mtime'] ) : '');
|
||||
$chksum = octdec(isset($attr['chksum'] ) ? trim($attr['chksum'] ) : '');
|
||||
$typflag = (isset($attr['typflag']) ? trim($attr['typflag']) : '');
|
||||
$lnkname = (isset($attr['lnkname']) ? trim($attr['lnkname']) : '');
|
||||
$magic = (isset($attr['magic'] ) ? trim($attr['magic'] ) : '');
|
||||
$ver = (isset($attr['ver'] ) ? trim($attr['ver'] ) : '');
|
||||
$uname = (isset($attr['uname'] ) ? trim($attr['uname'] ) : '');
|
||||
$gname = (isset($attr['gname'] ) ? trim($attr['gname'] ) : '');
|
||||
$devmaj = octdec(isset($attr['devmaj'] ) ? trim($attr['devmaj'] ) : '');
|
||||
$devmin = octdec(isset($attr['devmin'] ) ? trim($attr['devmin'] ) : '');
|
||||
$prefix = (isset($attr['prefix'] ) ? trim($attr['prefix'] ) : '');
|
||||
if (($checksum == 256) && ($chksum == 0)) {
|
||||
// EOF Found
|
||||
break;
|
||||
}
|
||||
if ($prefix) {
|
||||
$name = $prefix.'/'.$name;
|
||||
}
|
||||
if ((preg_match('#/$#', $name)) && !$name) {
|
||||
$typeflag = 5;
|
||||
}
|
||||
if ($buffer == $null_512k) {
|
||||
// it's the end of the tar-file...
|
||||
break;
|
||||
}
|
||||
|
||||
// Read to the next chunk
|
||||
fseek($this->getid3->fp, $size, SEEK_CUR);
|
||||
|
||||
$diff = $size % 512;
|
||||
if ($diff != 0) {
|
||||
// Padding, throw away
|
||||
fseek($this->getid3->fp, (512 - $diff), SEEK_CUR);
|
||||
}
|
||||
// Protect against tar-files with garbage at the end
|
||||
if ($name == '') {
|
||||
break;
|
||||
}
|
||||
$info['tar']['file_details'][$name] = array (
|
||||
'name' => $name,
|
||||
'mode_raw' => $mode,
|
||||
'mode' => self::display_perms($mode),
|
||||
'uid' => $uid,
|
||||
'gid' => $gid,
|
||||
'size' => $size,
|
||||
'mtime' => $mtime,
|
||||
'chksum' => $chksum,
|
||||
'typeflag' => self::get_flag_type($typflag),
|
||||
'linkname' => $lnkname,
|
||||
'magic' => $magic,
|
||||
'version' => $ver,
|
||||
'uname' => $uname,
|
||||
'gname' => $gname,
|
||||
'devmajor' => $devmaj,
|
||||
'devminor' => $devmin
|
||||
);
|
||||
$info['tar']['files'] = getid3_lib::array_merge_clobber($info['tar']['files'], getid3_lib::CreateDeepArray($info['tar']['file_details'][$name]['name'], '/', $size));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses the file mode to file permissions
|
||||
public function display_perms($mode) {
|
||||
// Determine Type
|
||||
if ($mode & 0x1000) $type='p'; // FIFO pipe
|
||||
elseif ($mode & 0x2000) $type='c'; // Character special
|
||||
elseif ($mode & 0x4000) $type='d'; // Directory
|
||||
elseif ($mode & 0x6000) $type='b'; // Block special
|
||||
elseif ($mode & 0x8000) $type='-'; // Regular
|
||||
elseif ($mode & 0xA000) $type='l'; // Symbolic Link
|
||||
elseif ($mode & 0xC000) $type='s'; // Socket
|
||||
else $type='u'; // UNKNOWN
|
||||
|
||||
// Determine permissions
|
||||
$owner['read'] = (($mode & 00400) ? 'r' : '-');
|
||||
$owner['write'] = (($mode & 00200) ? 'w' : '-');
|
||||
$owner['execute'] = (($mode & 00100) ? 'x' : '-');
|
||||
$group['read'] = (($mode & 00040) ? 'r' : '-');
|
||||
$group['write'] = (($mode & 00020) ? 'w' : '-');
|
||||
$group['execute'] = (($mode & 00010) ? 'x' : '-');
|
||||
$world['read'] = (($mode & 00004) ? 'r' : '-');
|
||||
$world['write'] = (($mode & 00002) ? 'w' : '-');
|
||||
$world['execute'] = (($mode & 00001) ? 'x' : '-');
|
||||
|
||||
// Adjust for SUID, SGID and sticky bit
|
||||
if ($mode & 0x800) $owner['execute'] = ($owner['execute'] == 'x') ? 's' : 'S';
|
||||
if ($mode & 0x400) $group['execute'] = ($group['execute'] == 'x') ? 's' : 'S';
|
||||
if ($mode & 0x200) $world['execute'] = ($world['execute'] == 'x') ? 't' : 'T';
|
||||
|
||||
$s = sprintf('%1s', $type);
|
||||
$s .= sprintf('%1s%1s%1s', $owner['read'], $owner['write'], $owner['execute']);
|
||||
$s .= sprintf('%1s%1s%1s', $group['read'], $group['write'], $group['execute']);
|
||||
$s .= sprintf('%1s%1s%1s'."\n", $world['read'], $world['write'], $world['execute']);
|
||||
return $s;
|
||||
}
|
||||
|
||||
// Converts the file type
|
||||
public function get_flag_type($typflag) {
|
||||
static $flag_types = array(
|
||||
'0' => 'LF_NORMAL',
|
||||
'1' => 'LF_LINK',
|
||||
'2' => 'LF_SYNLINK',
|
||||
'3' => 'LF_CHR',
|
||||
'4' => 'LF_BLK',
|
||||
'5' => 'LF_DIR',
|
||||
'6' => 'LF_FIFO',
|
||||
'7' => 'LF_CONFIG',
|
||||
'D' => 'LF_DUMPDIR',
|
||||
'K' => 'LF_LONGLINK',
|
||||
'L' => 'LF_LONGNAME',
|
||||
'M' => 'LF_MULTIVOL',
|
||||
'N' => 'LF_NAMES',
|
||||
'S' => 'LF_SPARSE',
|
||||
'V' => 'LF_VOLHDR'
|
||||
);
|
||||
return (isset($flag_types[$typflag]) ? $flag_types[$typflag] : '');
|
||||
}
|
||||
|
||||
}
|
421
api/getID3/module.archive.zip.php
Executable file
@ -0,0 +1,421 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.archive.zip.php //
|
||||
// module for analyzing pkZip files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_zip extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'zip';
|
||||
$info['zip']['encoding'] = 'ISO-8859-1';
|
||||
$info['zip']['files'] = array();
|
||||
|
||||
$info['zip']['compressed_size'] = 0;
|
||||
$info['zip']['uncompressed_size'] = 0;
|
||||
$info['zip']['entries_count'] = 0;
|
||||
|
||||
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||
$info['error'][] = 'File is larger than '.round(PHP_INT_MAX / 1073741824).'GB, not supported by PHP';
|
||||
return false;
|
||||
} else {
|
||||
$EOCDsearchData = '';
|
||||
$EOCDsearchCounter = 0;
|
||||
while ($EOCDsearchCounter++ < 512) {
|
||||
|
||||
fseek($this->getid3->fp, -128 * $EOCDsearchCounter, SEEK_END);
|
||||
$EOCDsearchData = fread($this->getid3->fp, 128).$EOCDsearchData;
|
||||
|
||||
if (strstr($EOCDsearchData, 'PK'."\x05\x06")) {
|
||||
|
||||
$EOCDposition = strpos($EOCDsearchData, 'PK'."\x05\x06");
|
||||
fseek($this->getid3->fp, (-128 * $EOCDsearchCounter) + $EOCDposition, SEEK_END);
|
||||
$info['zip']['end_central_directory'] = $this->ZIPparseEndOfCentralDirectory();
|
||||
|
||||
fseek($this->getid3->fp, $info['zip']['end_central_directory']['directory_offset'], SEEK_SET);
|
||||
$info['zip']['entries_count'] = 0;
|
||||
while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($this->getid3->fp)) {
|
||||
$info['zip']['central_directory'][] = $centraldirectoryentry;
|
||||
$info['zip']['entries_count']++;
|
||||
$info['zip']['compressed_size'] += $centraldirectoryentry['compressed_size'];
|
||||
$info['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size'];
|
||||
|
||||
if ($centraldirectoryentry['uncompressed_size'] > 0) {
|
||||
$info['zip']['files'] = getid3_lib::array_merge_clobber($info['zip']['files'], getid3_lib::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size']));
|
||||
}
|
||||
}
|
||||
|
||||
if ($info['zip']['entries_count'] == 0) {
|
||||
$info['error'][] = 'No Central Directory entries found (truncated file?)';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($info['zip']['end_central_directory']['comment'])) {
|
||||
$info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment'];
|
||||
}
|
||||
|
||||
if (isset($info['zip']['central_directory'][0]['compression_method'])) {
|
||||
$info['zip']['compression_method'] = $info['zip']['central_directory'][0]['compression_method'];
|
||||
}
|
||||
if (isset($info['zip']['central_directory'][0]['flags']['compression_speed'])) {
|
||||
$info['zip']['compression_speed'] = $info['zip']['central_directory'][0]['flags']['compression_speed'];
|
||||
}
|
||||
if (isset($info['zip']['compression_method']) && ($info['zip']['compression_method'] == 'store') && !isset($info['zip']['compression_speed'])) {
|
||||
$info['zip']['compression_speed'] = 'store';
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->getZIPentriesFilepointer()) {
|
||||
|
||||
// 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)) {
|
||||
$info['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)';
|
||||
}
|
||||
$info['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) {
|
||||
$info['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size'];
|
||||
}
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
unset($info['zip']);
|
||||
$info['fileformat'] = '';
|
||||
$info['error'][] = 'Cannot find End Of Central Directory (truncated file?)';
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getZIPHeaderFilepointerTopDown() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'zip';
|
||||
|
||||
$info['zip']['compressed_size'] = 0;
|
||||
$info['zip']['uncompressed_size'] = 0;
|
||||
$info['zip']['entries_count'] = 0;
|
||||
|
||||
rewind($this->getid3->fp);
|
||||
while ($fileentry = $this->ZIPparseLocalFileHeader()) {
|
||||
$info['zip']['entries'][] = $fileentry;
|
||||
$info['zip']['entries_count']++;
|
||||
}
|
||||
if ($info['zip']['entries_count'] == 0) {
|
||||
$info['error'][] = 'No Local File Header entries found';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['zip']['entries_count'] = 0;
|
||||
while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($this->getid3->fp)) {
|
||||
$info['zip']['central_directory'][] = $centraldirectoryentry;
|
||||
$info['zip']['entries_count']++;
|
||||
$info['zip']['compressed_size'] += $centraldirectoryentry['compressed_size'];
|
||||
$info['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size'];
|
||||
}
|
||||
if ($info['zip']['entries_count'] == 0) {
|
||||
$info['error'][] = 'No Central Directory entries found (truncated file?)';
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($EOCD = $this->ZIPparseEndOfCentralDirectory()) {
|
||||
$info['zip']['end_central_directory'] = $EOCD;
|
||||
} else {
|
||||
$info['error'][] = 'No End Of Central Directory entry found (truncated file?)';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($info['zip']['end_central_directory']['comment'])) {
|
||||
$info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function getZIPentriesFilepointer() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['zip']['compressed_size'] = 0;
|
||||
$info['zip']['uncompressed_size'] = 0;
|
||||
$info['zip']['entries_count'] = 0;
|
||||
|
||||
rewind($this->getid3->fp);
|
||||
while ($fileentry = $this->ZIPparseLocalFileHeader()) {
|
||||
$info['zip']['entries'][] = $fileentry;
|
||||
$info['zip']['entries_count']++;
|
||||
$info['zip']['compressed_size'] += $fileentry['compressed_size'];
|
||||
$info['zip']['uncompressed_size'] += $fileentry['uncompressed_size'];
|
||||
}
|
||||
if ($info['zip']['entries_count'] == 0) {
|
||||
$info['error'][] = 'No Local File Header entries found';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function ZIPparseLocalFileHeader() {
|
||||
$LocalFileHeader['offset'] = ftell($this->getid3->fp);
|
||||
|
||||
$ZIPlocalFileHeader = fread($this->getid3->fp, 30);
|
||||
|
||||
$LocalFileHeader['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 0, 4));
|
||||
if ($LocalFileHeader['raw']['signature'] != 0x04034B50) {
|
||||
// invalid Local File Header Signature
|
||||
fseek($this->getid3->fp, $LocalFileHeader['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly
|
||||
return false;
|
||||
}
|
||||
$LocalFileHeader['raw']['extract_version'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 4, 2));
|
||||
$LocalFileHeader['raw']['general_flags'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 6, 2));
|
||||
$LocalFileHeader['raw']['compression_method'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 8, 2));
|
||||
$LocalFileHeader['raw']['last_mod_file_time'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 10, 2));
|
||||
$LocalFileHeader['raw']['last_mod_file_date'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 12, 2));
|
||||
$LocalFileHeader['raw']['crc_32'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 14, 4));
|
||||
$LocalFileHeader['raw']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 18, 4));
|
||||
$LocalFileHeader['raw']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 22, 4));
|
||||
$LocalFileHeader['raw']['filename_length'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 26, 2));
|
||||
$LocalFileHeader['raw']['extra_field_length'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 28, 2));
|
||||
|
||||
$LocalFileHeader['extract_version'] = sprintf('%1.1f', $LocalFileHeader['raw']['extract_version'] / 10);
|
||||
$LocalFileHeader['host_os'] = $this->ZIPversionOSLookup(($LocalFileHeader['raw']['extract_version'] & 0xFF00) >> 8);
|
||||
$LocalFileHeader['compression_method'] = $this->ZIPcompressionMethodLookup($LocalFileHeader['raw']['compression_method']);
|
||||
$LocalFileHeader['compressed_size'] = $LocalFileHeader['raw']['compressed_size'];
|
||||
$LocalFileHeader['uncompressed_size'] = $LocalFileHeader['raw']['uncompressed_size'];
|
||||
$LocalFileHeader['flags'] = $this->ZIPparseGeneralPurposeFlags($LocalFileHeader['raw']['general_flags'], $LocalFileHeader['raw']['compression_method']);
|
||||
$LocalFileHeader['last_modified_timestamp'] = $this->DOStime2UNIXtime($LocalFileHeader['raw']['last_mod_file_date'], $LocalFileHeader['raw']['last_mod_file_time']);
|
||||
|
||||
$FilenameExtrafieldLength = $LocalFileHeader['raw']['filename_length'] + $LocalFileHeader['raw']['extra_field_length'];
|
||||
if ($FilenameExtrafieldLength > 0) {
|
||||
$ZIPlocalFileHeader .= fread($this->getid3->fp, $FilenameExtrafieldLength);
|
||||
|
||||
if ($LocalFileHeader['raw']['filename_length'] > 0) {
|
||||
$LocalFileHeader['filename'] = substr($ZIPlocalFileHeader, 30, $LocalFileHeader['raw']['filename_length']);
|
||||
}
|
||||
if ($LocalFileHeader['raw']['extra_field_length'] > 0) {
|
||||
$LocalFileHeader['raw']['extra_field_data'] = substr($ZIPlocalFileHeader, 30 + $LocalFileHeader['raw']['filename_length'], $LocalFileHeader['raw']['extra_field_length']);
|
||||
}
|
||||
}
|
||||
|
||||
$LocalFileHeader['data_offset'] = ftell($this->getid3->fp);
|
||||
//$LocalFileHeader['compressed_data'] = fread($this->getid3->fp, $LocalFileHeader['raw']['compressed_size']);
|
||||
fseek($this->getid3->fp, $LocalFileHeader['raw']['compressed_size'], SEEK_CUR);
|
||||
|
||||
if ($LocalFileHeader['flags']['data_descriptor_used']) {
|
||||
$DataDescriptor = fread($this->getid3->fp, 12);
|
||||
$LocalFileHeader['data_descriptor']['crc_32'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 0, 4));
|
||||
$LocalFileHeader['data_descriptor']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 4, 4));
|
||||
$LocalFileHeader['data_descriptor']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 8, 4));
|
||||
}
|
||||
|
||||
return $LocalFileHeader;
|
||||
}
|
||||
|
||||
|
||||
public function ZIPparseCentralDirectory() {
|
||||
$CentralDirectory['offset'] = ftell($this->getid3->fp);
|
||||
|
||||
$ZIPcentralDirectory = fread($this->getid3->fp, 46);
|
||||
|
||||
$CentralDirectory['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 0, 4));
|
||||
if ($CentralDirectory['raw']['signature'] != 0x02014B50) {
|
||||
// invalid Central Directory Signature
|
||||
fseek($this->getid3->fp, $CentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly
|
||||
return false;
|
||||
}
|
||||
$CentralDirectory['raw']['create_version'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 4, 2));
|
||||
$CentralDirectory['raw']['extract_version'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 6, 2));
|
||||
$CentralDirectory['raw']['general_flags'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 8, 2));
|
||||
$CentralDirectory['raw']['compression_method'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 10, 2));
|
||||
$CentralDirectory['raw']['last_mod_file_time'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 12, 2));
|
||||
$CentralDirectory['raw']['last_mod_file_date'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 14, 2));
|
||||
$CentralDirectory['raw']['crc_32'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 16, 4));
|
||||
$CentralDirectory['raw']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 20, 4));
|
||||
$CentralDirectory['raw']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 24, 4));
|
||||
$CentralDirectory['raw']['filename_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 28, 2));
|
||||
$CentralDirectory['raw']['extra_field_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 30, 2));
|
||||
$CentralDirectory['raw']['file_comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 32, 2));
|
||||
$CentralDirectory['raw']['disk_number_start'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 34, 2));
|
||||
$CentralDirectory['raw']['internal_file_attrib'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 36, 2));
|
||||
$CentralDirectory['raw']['external_file_attrib'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 38, 4));
|
||||
$CentralDirectory['raw']['local_header_offset'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 42, 4));
|
||||
|
||||
$CentralDirectory['entry_offset'] = $CentralDirectory['raw']['local_header_offset'];
|
||||
$CentralDirectory['create_version'] = sprintf('%1.1f', $CentralDirectory['raw']['create_version'] / 10);
|
||||
$CentralDirectory['extract_version'] = sprintf('%1.1f', $CentralDirectory['raw']['extract_version'] / 10);
|
||||
$CentralDirectory['host_os'] = $this->ZIPversionOSLookup(($CentralDirectory['raw']['extract_version'] & 0xFF00) >> 8);
|
||||
$CentralDirectory['compression_method'] = $this->ZIPcompressionMethodLookup($CentralDirectory['raw']['compression_method']);
|
||||
$CentralDirectory['compressed_size'] = $CentralDirectory['raw']['compressed_size'];
|
||||
$CentralDirectory['uncompressed_size'] = $CentralDirectory['raw']['uncompressed_size'];
|
||||
$CentralDirectory['flags'] = $this->ZIPparseGeneralPurposeFlags($CentralDirectory['raw']['general_flags'], $CentralDirectory['raw']['compression_method']);
|
||||
$CentralDirectory['last_modified_timestamp'] = $this->DOStime2UNIXtime($CentralDirectory['raw']['last_mod_file_date'], $CentralDirectory['raw']['last_mod_file_time']);
|
||||
|
||||
$FilenameExtrafieldCommentLength = $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'] + $CentralDirectory['raw']['file_comment_length'];
|
||||
if ($FilenameExtrafieldCommentLength > 0) {
|
||||
$FilenameExtrafieldComment = fread($this->getid3->fp, $FilenameExtrafieldCommentLength);
|
||||
|
||||
if ($CentralDirectory['raw']['filename_length'] > 0) {
|
||||
$CentralDirectory['filename'] = substr($FilenameExtrafieldComment, 0, $CentralDirectory['raw']['filename_length']);
|
||||
}
|
||||
if ($CentralDirectory['raw']['extra_field_length'] > 0) {
|
||||
$CentralDirectory['raw']['extra_field_data'] = substr($FilenameExtrafieldComment, $CentralDirectory['raw']['filename_length'], $CentralDirectory['raw']['extra_field_length']);
|
||||
}
|
||||
if ($CentralDirectory['raw']['file_comment_length'] > 0) {
|
||||
$CentralDirectory['file_comment'] = substr($FilenameExtrafieldComment, $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'], $CentralDirectory['raw']['file_comment_length']);
|
||||
}
|
||||
}
|
||||
|
||||
return $CentralDirectory;
|
||||
}
|
||||
|
||||
public function ZIPparseEndOfCentralDirectory() {
|
||||
$EndOfCentralDirectory['offset'] = ftell($this->getid3->fp);
|
||||
|
||||
$ZIPendOfCentralDirectory = fread($this->getid3->fp, 22);
|
||||
|
||||
$EndOfCentralDirectory['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 0, 4));
|
||||
if ($EndOfCentralDirectory['signature'] != 0x06054B50) {
|
||||
// invalid End Of Central Directory Signature
|
||||
fseek($this->getid3->fp, $EndOfCentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly
|
||||
return false;
|
||||
}
|
||||
$EndOfCentralDirectory['disk_number_current'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 4, 2));
|
||||
$EndOfCentralDirectory['disk_number_start_directory'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 6, 2));
|
||||
$EndOfCentralDirectory['directory_entries_this_disk'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 8, 2));
|
||||
$EndOfCentralDirectory['directory_entries_total'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 10, 2));
|
||||
$EndOfCentralDirectory['directory_size'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 12, 4));
|
||||
$EndOfCentralDirectory['directory_offset'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 16, 4));
|
||||
$EndOfCentralDirectory['comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 20, 2));
|
||||
|
||||
if ($EndOfCentralDirectory['comment_length'] > 0) {
|
||||
$EndOfCentralDirectory['comment'] = fread($this->getid3->fp, $EndOfCentralDirectory['comment_length']);
|
||||
}
|
||||
|
||||
return $EndOfCentralDirectory;
|
||||
}
|
||||
|
||||
|
||||
public static function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) {
|
||||
$ParsedFlags['encrypted'] = (bool) ($flagbytes & 0x0001);
|
||||
|
||||
switch ($compressionmethod) {
|
||||
case 6:
|
||||
$ParsedFlags['dictionary_size'] = (($flagbytes & 0x0002) ? 8192 : 4096);
|
||||
$ParsedFlags['shannon_fano_trees'] = (($flagbytes & 0x0004) ? 3 : 2);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
case 9:
|
||||
switch (($flagbytes & 0x0006) >> 1) {
|
||||
case 0:
|
||||
$ParsedFlags['compression_speed'] = 'normal';
|
||||
break;
|
||||
case 1:
|
||||
$ParsedFlags['compression_speed'] = 'maximum';
|
||||
break;
|
||||
case 2:
|
||||
$ParsedFlags['compression_speed'] = 'fast';
|
||||
break;
|
||||
case 3:
|
||||
$ParsedFlags['compression_speed'] = 'superfast';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
$ParsedFlags['data_descriptor_used'] = (bool) ($flagbytes & 0x0008);
|
||||
|
||||
return $ParsedFlags;
|
||||
}
|
||||
|
||||
|
||||
public static function ZIPversionOSLookup($index) {
|
||||
static $ZIPversionOSLookup = array(
|
||||
0 => 'MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)',
|
||||
1 => 'Amiga',
|
||||
2 => 'OpenVMS',
|
||||
3 => 'Unix',
|
||||
4 => 'VM/CMS',
|
||||
5 => 'Atari ST',
|
||||
6 => 'OS/2 H.P.F.S.',
|
||||
7 => 'Macintosh',
|
||||
8 => 'Z-System',
|
||||
9 => 'CP/M',
|
||||
10 => 'Windows NTFS',
|
||||
11 => 'MVS',
|
||||
12 => 'VSE',
|
||||
13 => 'Acorn Risc',
|
||||
14 => 'VFAT',
|
||||
15 => 'Alternate MVS',
|
||||
16 => 'BeOS',
|
||||
17 => 'Tandem'
|
||||
);
|
||||
|
||||
return (isset($ZIPversionOSLookup[$index]) ? $ZIPversionOSLookup[$index] : '[unknown]');
|
||||
}
|
||||
|
||||
public static function ZIPcompressionMethodLookup($index) {
|
||||
static $ZIPcompressionMethodLookup = array(
|
||||
0 => 'store',
|
||||
1 => 'shrink',
|
||||
2 => 'reduce-1',
|
||||
3 => 'reduce-2',
|
||||
4 => 'reduce-3',
|
||||
5 => 'reduce-4',
|
||||
6 => 'implode',
|
||||
7 => 'tokenize',
|
||||
8 => 'deflate',
|
||||
9 => 'deflate64',
|
||||
10 => 'PKWARE Date Compression Library Imploding'
|
||||
);
|
||||
|
||||
return (isset($ZIPcompressionMethodLookup[$index]) ? $ZIPcompressionMethodLookup[$index] : '[unknown]');
|
||||
}
|
||||
|
||||
public static function DOStime2UNIXtime($DOSdate, $DOStime) {
|
||||
// wFatDate
|
||||
// Specifies the MS-DOS date. The date is a packed 16-bit value with the following format:
|
||||
// Bits Contents
|
||||
// 0-4 Day of the month (1-31)
|
||||
// 5-8 Month (1 = January, 2 = February, and so on)
|
||||
// 9-15 Year offset from 1980 (add 1980 to get actual year)
|
||||
|
||||
$UNIXday = ($DOSdate & 0x001F);
|
||||
$UNIXmonth = (($DOSdate & 0x01E0) >> 5);
|
||||
$UNIXyear = (($DOSdate & 0xFE00) >> 9) + 1980;
|
||||
|
||||
// wFatTime
|
||||
// Specifies the MS-DOS time. The time is a packed 16-bit value with the following format:
|
||||
// Bits Contents
|
||||
// 0-4 Second divided by 2
|
||||
// 5-10 Minute (0-59)
|
||||
// 11-15 Hour (0-23 on a 24-hour clock)
|
||||
|
||||
$UNIXsecond = ($DOStime & 0x001F) * 2;
|
||||
$UNIXminute = (($DOStime & 0x07E0) >> 5);
|
||||
$UNIXhour = (($DOStime & 0xF800) >> 11);
|
||||
|
||||
return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
|
||||
}
|
||||
|
||||
}
|
2019
api/getID3/module.audio-video.asf.php
Executable file
71
api/getID3/module.audio-video.bink.php
Executable file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.bink.php //
|
||||
// module for analyzing Bink or Smacker audio-video files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_bink extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['error'][] = 'Bink / Smacker files not properly processed by this version of getID3() ['.$this->getid3->version().']';
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$fileTypeID = fread($this->getid3->fp, 3);
|
||||
switch ($fileTypeID) {
|
||||
case 'BIK':
|
||||
return $this->ParseBink();
|
||||
break;
|
||||
|
||||
case 'SMK':
|
||||
return $this->ParseSmacker();
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Expecting "BIK" or "SMK" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($fileTypeID).'"';
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public function ParseBink() {
|
||||
$info = &$this->getid3->info;
|
||||
$info['fileformat'] = 'bink';
|
||||
$info['video']['dataformat'] = 'bink';
|
||||
|
||||
$fileData = 'BIK'.fread($this->getid3->fp, 13);
|
||||
|
||||
$info['bink']['data_size'] = getid3_lib::LittleEndian2Int(substr($fileData, 4, 4));
|
||||
$info['bink']['frame_count'] = getid3_lib::LittleEndian2Int(substr($fileData, 8, 2));
|
||||
|
||||
if (($info['avdataend'] - $info['avdataoffset']) != ($info['bink']['data_size'] + 8)) {
|
||||
$info['error'][] = 'Probably truncated file: expecting '.$info['bink']['data_size'].' bytes, found '.($info['avdataend'] - $info['avdataoffset']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function ParseSmacker() {
|
||||
$info = &$this->getid3->info;
|
||||
$info['fileformat'] = 'smacker';
|
||||
$info['video']['dataformat'] = 'smacker';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
729
api/getID3/module.audio-video.flv.php
Executable file
@ -0,0 +1,729 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// //
|
||||
// FLV module by Seth Kaufman <sethØwhirl-i-gig*com> //
|
||||
// //
|
||||
// * version 0.1 (26 June 2005) //
|
||||
// //
|
||||
// //
|
||||
// * version 0.1.1 (15 July 2005) //
|
||||
// minor modifications by James Heinrich <info@getid3.org> //
|
||||
// //
|
||||
// * version 0.2 (22 February 2006) //
|
||||
// Support for On2 VP6 codec and meta information //
|
||||
// by Steve Webster <steve.websterØfeaturecreep*com> //
|
||||
// //
|
||||
// * version 0.3 (15 June 2006) //
|
||||
// Modified to not read entire file into memory //
|
||||
// by James Heinrich <info@getid3.org> //
|
||||
// //
|
||||
// * version 0.4 (07 December 2007) //
|
||||
// Bugfixes for incorrectly parsed FLV dimensions //
|
||||
// and incorrect parsing of onMetaTag //
|
||||
// by Evgeny Moysevich <moysevichØgmail*com> //
|
||||
// //
|
||||
// * version 0.5 (21 May 2009) //
|
||||
// Fixed parsing of audio tags and added additional codec //
|
||||
// details. The duration is now read from onMetaTag (if //
|
||||
// exists), rather than parsing whole file //
|
||||
// by Nigel Barnes <ngbarnesØhotmail*com> //
|
||||
// //
|
||||
// * version 0.6 (24 May 2009) //
|
||||
// Better parsing of files with h264 video //
|
||||
// by Evgeny Moysevich <moysevichØgmail*com> //
|
||||
// //
|
||||
// * version 0.6.1 (30 May 2011) //
|
||||
// prevent infinite loops in expGolombUe() //
|
||||
// //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio-video.flv.php //
|
||||
// module for analyzing Shockwave Flash Video files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
define('GETID3_FLV_TAG_AUDIO', 8);
|
||||
define('GETID3_FLV_TAG_VIDEO', 9);
|
||||
define('GETID3_FLV_TAG_META', 18);
|
||||
|
||||
define('GETID3_FLV_VIDEO_H263', 2);
|
||||
define('GETID3_FLV_VIDEO_SCREEN', 3);
|
||||
define('GETID3_FLV_VIDEO_VP6FLV', 4);
|
||||
define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5);
|
||||
define('GETID3_FLV_VIDEO_SCREENV2', 6);
|
||||
define('GETID3_FLV_VIDEO_H264', 7);
|
||||
|
||||
define('H264_AVC_SEQUENCE_HEADER', 0);
|
||||
define('H264_PROFILE_BASELINE', 66);
|
||||
define('H264_PROFILE_MAIN', 77);
|
||||
define('H264_PROFILE_EXTENDED', 88);
|
||||
define('H264_PROFILE_HIGH', 100);
|
||||
define('H264_PROFILE_HIGH10', 110);
|
||||
define('H264_PROFILE_HIGH422', 122);
|
||||
define('H264_PROFILE_HIGH444', 144);
|
||||
define('H264_PROFILE_HIGH444_PREDICTIVE', 244);
|
||||
|
||||
class getid3_flv extends getid3_handler
|
||||
{
|
||||
public $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
|
||||
$FLVdataLength = $info['avdataend'] - $info['avdataoffset'];
|
||||
$FLVheader = fread($this->getid3->fp, 5);
|
||||
|
||||
$info['fileformat'] = 'flv';
|
||||
$info['flv']['header']['signature'] = substr($FLVheader, 0, 3);
|
||||
$info['flv']['header']['version'] = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
|
||||
$TypeFlags = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
|
||||
|
||||
$magic = 'FLV';
|
||||
if ($info['flv']['header']['signature'] != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"';
|
||||
unset($info['flv']);
|
||||
unset($info['fileformat']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
|
||||
$info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
|
||||
|
||||
$FrameSizeDataLength = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 4));
|
||||
$FLVheaderFrameLength = 9;
|
||||
if ($FrameSizeDataLength > $FLVheaderFrameLength) {
|
||||
fseek($this->getid3->fp, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
|
||||
}
|
||||
$Duration = 0;
|
||||
$found_video = false;
|
||||
$found_audio = false;
|
||||
$found_meta = false;
|
||||
$found_valid_meta_playtime = false;
|
||||
$tagParseCount = 0;
|
||||
$info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0);
|
||||
$flv_framecount = &$info['flv']['framecount'];
|
||||
while (((ftell($this->getid3->fp) + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) {
|
||||
$ThisTagHeader = fread($this->getid3->fp, 16);
|
||||
|
||||
$PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 0, 4));
|
||||
$TagType = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 4, 1));
|
||||
$DataLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 5, 3));
|
||||
$Timestamp = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 8, 3));
|
||||
$LastHeaderByte = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
|
||||
$NextOffset = ftell($this->getid3->fp) - 1 + $DataLength;
|
||||
if ($Timestamp > $Duration) {
|
||||
$Duration = $Timestamp;
|
||||
}
|
||||
|
||||
$flv_framecount['total']++;
|
||||
switch ($TagType) {
|
||||
case GETID3_FLV_TAG_AUDIO:
|
||||
$flv_framecount['audio']++;
|
||||
if (!$found_audio) {
|
||||
$found_audio = true;
|
||||
$info['flv']['audio']['audioFormat'] = ($LastHeaderByte >> 4) & 0x0F;
|
||||
$info['flv']['audio']['audioRate'] = ($LastHeaderByte >> 2) & 0x03;
|
||||
$info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
|
||||
$info['flv']['audio']['audioType'] = $LastHeaderByte & 0x01;
|
||||
}
|
||||
break;
|
||||
|
||||
case GETID3_FLV_TAG_VIDEO:
|
||||
$flv_framecount['video']++;
|
||||
if (!$found_video) {
|
||||
$found_video = true;
|
||||
$info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
|
||||
|
||||
$FLVvideoHeader = fread($this->getid3->fp, 11);
|
||||
|
||||
if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
|
||||
// this code block contributed by: moysevichØgmail*com
|
||||
|
||||
$AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1));
|
||||
if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) {
|
||||
// read AVCDecoderConfigurationRecord
|
||||
$configurationVersion = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 1));
|
||||
$AVCProfileIndication = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 1));
|
||||
$profile_compatibility = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 1));
|
||||
$lengthSizeMinusOne = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 1));
|
||||
$numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 8, 1));
|
||||
|
||||
if (($numOfSequenceParameterSets & 0x1F) != 0) {
|
||||
// there is at least one SequenceParameterSet
|
||||
// read size of the first SequenceParameterSet
|
||||
//$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2));
|
||||
$spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2));
|
||||
// read the first SequenceParameterSet
|
||||
$sps = fread($this->getid3->fp, $spsSize);
|
||||
if (strlen($sps) == $spsSize) { // make sure that whole SequenceParameterSet was red
|
||||
$spsReader = new AVCSequenceParameterSetReader($sps);
|
||||
$spsReader->readData();
|
||||
$info['video']['resolution_x'] = $spsReader->getWidth();
|
||||
$info['video']['resolution_y'] = $spsReader->getHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
// end: moysevichØgmail*com
|
||||
|
||||
} elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {
|
||||
|
||||
$PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
|
||||
$PictureSizeType = $PictureSizeType & 0x0007;
|
||||
$info['flv']['header']['videoSizeType'] = $PictureSizeType;
|
||||
switch ($PictureSizeType) {
|
||||
case 0:
|
||||
//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
|
||||
//$PictureSizeEnc <<= 1;
|
||||
//$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
|
||||
//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
|
||||
//$PictureSizeEnc <<= 1;
|
||||
//$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
|
||||
|
||||
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2));
|
||||
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
|
||||
$PictureSizeEnc['x'] >>= 7;
|
||||
$PictureSizeEnc['y'] >>= 7;
|
||||
$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
|
||||
$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3));
|
||||
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3));
|
||||
$PictureSizeEnc['x'] >>= 7;
|
||||
$PictureSizeEnc['y'] >>= 7;
|
||||
$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
|
||||
$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$info['video']['resolution_x'] = 352;
|
||||
$info['video']['resolution_y'] = 288;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$info['video']['resolution_x'] = 176;
|
||||
$info['video']['resolution_y'] = 144;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$info['video']['resolution_x'] = 128;
|
||||
$info['video']['resolution_y'] = 96;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
$info['video']['resolution_x'] = 320;
|
||||
$info['video']['resolution_y'] = 240;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
$info['video']['resolution_x'] = 160;
|
||||
$info['video']['resolution_y'] = 120;
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['video']['resolution_x'] = 0;
|
||||
$info['video']['resolution_y'] = 0;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
$info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
|
||||
}
|
||||
break;
|
||||
|
||||
// Meta tag
|
||||
case GETID3_FLV_TAG_META:
|
||||
if (!$found_meta) {
|
||||
$found_meta = true;
|
||||
fseek($this->getid3->fp, -1, SEEK_CUR);
|
||||
$datachunk = fread($this->getid3->fp, $DataLength);
|
||||
$AMFstream = new AMFStream($datachunk);
|
||||
$reader = new AMFReader($AMFstream);
|
||||
$eventName = $reader->readData();
|
||||
$info['flv']['meta'][$eventName] = $reader->readData();
|
||||
unset($reader);
|
||||
|
||||
$copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate');
|
||||
foreach ($copykeys as $sourcekey => $destkey) {
|
||||
if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) {
|
||||
switch ($sourcekey) {
|
||||
case 'width':
|
||||
case 'height':
|
||||
$info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
|
||||
break;
|
||||
case 'audiodatarate':
|
||||
$info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
|
||||
break;
|
||||
case 'videodatarate':
|
||||
case 'frame_rate':
|
||||
default:
|
||||
$info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
|
||||
$found_valid_meta_playtime = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// noop
|
||||
break;
|
||||
}
|
||||
fseek($this->getid3->fp, $NextOffset, SEEK_SET);
|
||||
}
|
||||
|
||||
$info['playtime_seconds'] = $Duration / 1000;
|
||||
if ($info['playtime_seconds'] > 0) {
|
||||
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
}
|
||||
|
||||
if ($info['flv']['header']['hasAudio']) {
|
||||
$info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['audio']['audioFormat']);
|
||||
$info['audio']['sample_rate'] = $this->FLVaudioRate($info['flv']['audio']['audioRate']);
|
||||
$info['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($info['flv']['audio']['audioSampleSize']);
|
||||
|
||||
$info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
|
||||
$info['audio']['lossless'] = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
|
||||
$info['audio']['dataformat'] = 'flv';
|
||||
}
|
||||
if (!empty($info['flv']['header']['hasVideo'])) {
|
||||
$info['video']['codec'] = $this->FLVvideoCodec($info['flv']['video']['videoCodec']);
|
||||
$info['video']['dataformat'] = 'flv';
|
||||
$info['video']['lossless'] = false;
|
||||
}
|
||||
|
||||
// Set information from meta
|
||||
if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
|
||||
$info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
|
||||
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
}
|
||||
if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) {
|
||||
$info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['meta']['onMetaData']['audiocodecid']);
|
||||
}
|
||||
if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) {
|
||||
$info['video']['codec'] = $this->FLVvideoCodec($info['flv']['meta']['onMetaData']['videocodecid']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function FLVaudioFormat($id) {
|
||||
$FLVaudioFormat = array(
|
||||
0 => 'Linear PCM, platform endian',
|
||||
1 => 'ADPCM',
|
||||
2 => 'mp3',
|
||||
3 => 'Linear PCM, little endian',
|
||||
4 => 'Nellymoser 16kHz mono',
|
||||
5 => 'Nellymoser 8kHz mono',
|
||||
6 => 'Nellymoser',
|
||||
7 => 'G.711A-law logarithmic PCM',
|
||||
8 => 'G.711 mu-law logarithmic PCM',
|
||||
9 => 'reserved',
|
||||
10 => 'AAC',
|
||||
11 => false, // unknown?
|
||||
12 => false, // unknown?
|
||||
13 => false, // unknown?
|
||||
14 => 'mp3 8kHz',
|
||||
15 => 'Device-specific sound',
|
||||
);
|
||||
return (isset($FLVaudioFormat[$id]) ? $FLVaudioFormat[$id] : false);
|
||||
}
|
||||
|
||||
public function FLVaudioRate($id) {
|
||||
$FLVaudioRate = array(
|
||||
0 => 5500,
|
||||
1 => 11025,
|
||||
2 => 22050,
|
||||
3 => 44100,
|
||||
);
|
||||
return (isset($FLVaudioRate[$id]) ? $FLVaudioRate[$id] : false);
|
||||
}
|
||||
|
||||
public function FLVaudioBitDepth($id) {
|
||||
$FLVaudioBitDepth = array(
|
||||
0 => 8,
|
||||
1 => 16,
|
||||
);
|
||||
return (isset($FLVaudioBitDepth[$id]) ? $FLVaudioBitDepth[$id] : false);
|
||||
}
|
||||
|
||||
public function FLVvideoCodec($id) {
|
||||
$FLVvideoCodec = array(
|
||||
GETID3_FLV_VIDEO_H263 => 'Sorenson H.263',
|
||||
GETID3_FLV_VIDEO_SCREEN => 'Screen video',
|
||||
GETID3_FLV_VIDEO_VP6FLV => 'On2 VP6',
|
||||
GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
|
||||
GETID3_FLV_VIDEO_SCREENV2 => 'Screen video v2',
|
||||
GETID3_FLV_VIDEO_H264 => 'Sorenson H.264',
|
||||
);
|
||||
return (isset($FLVvideoCodec[$id]) ? $FLVvideoCodec[$id] : false);
|
||||
}
|
||||
}
|
||||
|
||||
class AMFStream {
|
||||
public $bytes;
|
||||
public $pos;
|
||||
|
||||
public function AMFStream(&$bytes) {
|
||||
$this->bytes =& $bytes;
|
||||
$this->pos = 0;
|
||||
}
|
||||
|
||||
public function readByte() {
|
||||
return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
|
||||
}
|
||||
|
||||
public function readInt() {
|
||||
return ($this->readByte() << 8) + $this->readByte();
|
||||
}
|
||||
|
||||
public function readLong() {
|
||||
return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
|
||||
}
|
||||
|
||||
public function readDouble() {
|
||||
return getid3_lib::BigEndian2Float($this->read(8));
|
||||
}
|
||||
|
||||
public function readUTF() {
|
||||
$length = $this->readInt();
|
||||
return $this->read($length);
|
||||
}
|
||||
|
||||
public function readLongUTF() {
|
||||
$length = $this->readLong();
|
||||
return $this->read($length);
|
||||
}
|
||||
|
||||
public function read($length) {
|
||||
$val = substr($this->bytes, $this->pos, $length);
|
||||
$this->pos += $length;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekByte() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readByte();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekInt() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readInt();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekLong() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readLong();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekDouble() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readDouble();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekUTF() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readUTF();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekLongUTF() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readLongUTF();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
|
||||
class AMFReader {
|
||||
public $stream;
|
||||
|
||||
public function AMFReader(&$stream) {
|
||||
$this->stream =& $stream;
|
||||
}
|
||||
|
||||
public function readData() {
|
||||
$value = null;
|
||||
|
||||
$type = $this->stream->readByte();
|
||||
switch ($type) {
|
||||
|
||||
// Double
|
||||
case 0:
|
||||
$value = $this->readDouble();
|
||||
break;
|
||||
|
||||
// Boolean
|
||||
case 1:
|
||||
$value = $this->readBoolean();
|
||||
break;
|
||||
|
||||
// String
|
||||
case 2:
|
||||
$value = $this->readString();
|
||||
break;
|
||||
|
||||
// Object
|
||||
case 3:
|
||||
$value = $this->readObject();
|
||||
break;
|
||||
|
||||
// null
|
||||
case 6:
|
||||
return null;
|
||||
break;
|
||||
|
||||
// Mixed array
|
||||
case 8:
|
||||
$value = $this->readMixedArray();
|
||||
break;
|
||||
|
||||
// Array
|
||||
case 10:
|
||||
$value = $this->readArray();
|
||||
break;
|
||||
|
||||
// Date
|
||||
case 11:
|
||||
$value = $this->readDate();
|
||||
break;
|
||||
|
||||
// Long string
|
||||
case 13:
|
||||
$value = $this->readLongString();
|
||||
break;
|
||||
|
||||
// XML (handled as string)
|
||||
case 15:
|
||||
$value = $this->readXML();
|
||||
break;
|
||||
|
||||
// Typed object (handled as object)
|
||||
case 16:
|
||||
$value = $this->readTypedObject();
|
||||
break;
|
||||
|
||||
// Long string
|
||||
default:
|
||||
$value = '(unknown or unsupported data type)';
|
||||
break;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function readDouble() {
|
||||
return $this->stream->readDouble();
|
||||
}
|
||||
|
||||
public function readBoolean() {
|
||||
return $this->stream->readByte() == 1;
|
||||
}
|
||||
|
||||
public function readString() {
|
||||
return $this->stream->readUTF();
|
||||
}
|
||||
|
||||
public function readObject() {
|
||||
// Get highest numerical index - ignored
|
||||
// $highestIndex = $this->stream->readLong();
|
||||
|
||||
$data = array();
|
||||
|
||||
while ($key = $this->stream->readUTF()) {
|
||||
$data[$key] = $this->readData();
|
||||
}
|
||||
// Mixed array record ends with empty string (0x00 0x00) and 0x09
|
||||
if (($key == '') && ($this->stream->peekByte() == 0x09)) {
|
||||
// Consume byte
|
||||
$this->stream->readByte();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function readMixedArray() {
|
||||
// Get highest numerical index - ignored
|
||||
$highestIndex = $this->stream->readLong();
|
||||
|
||||
$data = array();
|
||||
|
||||
while ($key = $this->stream->readUTF()) {
|
||||
if (is_numeric($key)) {
|
||||
$key = (float) $key;
|
||||
}
|
||||
$data[$key] = $this->readData();
|
||||
}
|
||||
// Mixed array record ends with empty string (0x00 0x00) and 0x09
|
||||
if (($key == '') && ($this->stream->peekByte() == 0x09)) {
|
||||
// Consume byte
|
||||
$this->stream->readByte();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function readArray() {
|
||||
$length = $this->stream->readLong();
|
||||
$data = array();
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$data[] = $this->readData();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function readDate() {
|
||||
$timestamp = $this->stream->readDouble();
|
||||
$timezone = $this->stream->readInt();
|
||||
return $timestamp;
|
||||
}
|
||||
|
||||
public function readLongString() {
|
||||
return $this->stream->readLongUTF();
|
||||
}
|
||||
|
||||
public function readXML() {
|
||||
return $this->stream->readLongUTF();
|
||||
}
|
||||
|
||||
public function readTypedObject() {
|
||||
$className = $this->stream->readUTF();
|
||||
return $this->readObject();
|
||||
}
|
||||
}
|
||||
|
||||
class AVCSequenceParameterSetReader {
|
||||
public $sps;
|
||||
public $start = 0;
|
||||
public $currentBytes = 0;
|
||||
public $currentBits = 0;
|
||||
public $width;
|
||||
public $height;
|
||||
|
||||
public function AVCSequenceParameterSetReader($sps) {
|
||||
$this->sps = $sps;
|
||||
}
|
||||
|
||||
public function readData() {
|
||||
$this->skipBits(8);
|
||||
$this->skipBits(8);
|
||||
$profile = $this->getBits(8); // read profile
|
||||
$this->skipBits(16);
|
||||
$this->expGolombUe(); // read sps id
|
||||
if (in_array($profile, array(H264_PROFILE_HIGH, H264_PROFILE_HIGH10, H264_PROFILE_HIGH422, H264_PROFILE_HIGH444, H264_PROFILE_HIGH444_PREDICTIVE))) {
|
||||
if ($this->expGolombUe() == 3) {
|
||||
$this->skipBits(1);
|
||||
}
|
||||
$this->expGolombUe();
|
||||
$this->expGolombUe();
|
||||
$this->skipBits(1);
|
||||
if ($this->getBit()) {
|
||||
for ($i = 0; $i < 8; $i++) {
|
||||
if ($this->getBit()) {
|
||||
$size = $i < 6 ? 16 : 64;
|
||||
$lastScale = 8;
|
||||
$nextScale = 8;
|
||||
for ($j = 0; $j < $size; $j++) {
|
||||
if ($nextScale != 0) {
|
||||
$deltaScale = $this->expGolombUe();
|
||||
$nextScale = ($lastScale + $deltaScale + 256) % 256;
|
||||
}
|
||||
if ($nextScale != 0) {
|
||||
$lastScale = $nextScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->expGolombUe();
|
||||
$pocType = $this->expGolombUe();
|
||||
if ($pocType == 0) {
|
||||
$this->expGolombUe();
|
||||
} elseif ($pocType == 1) {
|
||||
$this->skipBits(1);
|
||||
$this->expGolombSe();
|
||||
$this->expGolombSe();
|
||||
$pocCycleLength = $this->expGolombUe();
|
||||
for ($i = 0; $i < $pocCycleLength; $i++) {
|
||||
$this->expGolombSe();
|
||||
}
|
||||
}
|
||||
$this->expGolombUe();
|
||||
$this->skipBits(1);
|
||||
$this->width = ($this->expGolombUe() + 1) * 16;
|
||||
$heightMap = $this->expGolombUe() + 1;
|
||||
$this->height = (2 - $this->getBit()) * $heightMap * 16;
|
||||
}
|
||||
|
||||
public function skipBits($bits) {
|
||||
$newBits = $this->currentBits + $bits;
|
||||
$this->currentBytes += (int)floor($newBits / 8);
|
||||
$this->currentBits = $newBits % 8;
|
||||
}
|
||||
|
||||
public function getBit() {
|
||||
$result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01;
|
||||
$this->skipBits(1);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getBits($bits) {
|
||||
$result = 0;
|
||||
for ($i = 0; $i < $bits; $i++) {
|
||||
$result = ($result << 1) + $this->getBit();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function expGolombUe() {
|
||||
$significantBits = 0;
|
||||
$bit = $this->getBit();
|
||||
while ($bit == 0) {
|
||||
$significantBits++;
|
||||
$bit = $this->getBit();
|
||||
|
||||
if ($significantBits > 31) {
|
||||
// something is broken, this is an emergency escape to prevent infinite loops
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return (1 << $significantBits) + $this->getBits($significantBits) - 1;
|
||||
}
|
||||
|
||||
public function expGolombSe() {
|
||||
$result = $this->expGolombUe();
|
||||
if (($result & 0x01) == 0) {
|
||||
return -($result >> 1);
|
||||
} else {
|
||||
return ($result + 1) >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
public function getWidth() {
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
public function getHeight() {
|
||||
return $this->height;
|
||||
}
|
||||
}
|
1761
api/getID3/module.audio-video.matroska.php
Executable file
296
api/getID3/module.audio-video.mpeg.php
Executable file
@ -0,0 +1,296 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio-video.mpeg.php //
|
||||
// module for analyzing MPEG files //
|
||||
// dependencies: module.audio.mp3.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);
|
||||
|
||||
define('GETID3_MPEG_VIDEO_PICTURE_START', "\x00\x00\x01\x00");
|
||||
define('GETID3_MPEG_VIDEO_USER_DATA_START', "\x00\x00\x01\xB2");
|
||||
define('GETID3_MPEG_VIDEO_SEQUENCE_HEADER', "\x00\x00\x01\xB3");
|
||||
define('GETID3_MPEG_VIDEO_SEQUENCE_ERROR', "\x00\x00\x01\xB4");
|
||||
define('GETID3_MPEG_VIDEO_EXTENSION_START', "\x00\x00\x01\xB5");
|
||||
define('GETID3_MPEG_VIDEO_SEQUENCE_END', "\x00\x00\x01\xB7");
|
||||
define('GETID3_MPEG_VIDEO_GROUP_START', "\x00\x00\x01\xB8");
|
||||
define('GETID3_MPEG_AUDIO_START', "\x00\x00\x01\xC0");
|
||||
|
||||
|
||||
class getid3_mpeg extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
if ($info['avdataend'] <= $info['avdataoffset']) {
|
||||
$info['error'][] = '"avdataend" ('.$info['avdataend'].') is unexpectedly less-than-or-equal-to "avdataoffset" ('.$info['avdataoffset'].')';
|
||||
return false;
|
||||
}
|
||||
$info['fileformat'] = 'mpeg';
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$MPEGstreamData = fread($this->getid3->fp, min(100000, $info['avdataend'] - $info['avdataoffset']));
|
||||
$MPEGstreamDataLength = strlen($MPEGstreamData);
|
||||
|
||||
$foundVideo = true;
|
||||
$VideoChunkOffset = 0;
|
||||
while (substr($MPEGstreamData, $VideoChunkOffset++, 4) !== GETID3_MPEG_VIDEO_SEQUENCE_HEADER) {
|
||||
if ($VideoChunkOffset >= $MPEGstreamDataLength) {
|
||||
$foundVideo = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($foundVideo) {
|
||||
|
||||
// Start code 32 bits
|
||||
// horizontal frame size 12 bits
|
||||
// vertical frame size 12 bits
|
||||
// pixel aspect ratio 4 bits
|
||||
// frame rate 4 bits
|
||||
// bitrate 18 bits
|
||||
// marker bit 1 bit
|
||||
// VBV buffer size 10 bits
|
||||
// constrained parameter flag 1 bit
|
||||
// intra quant. matrix flag 1 bit
|
||||
// intra quant. matrix values 512 bits (present if matrix flag == 1)
|
||||
// non-intra quant. matrix flag 1 bit
|
||||
// non-intra quant. matrix values 512 bits (present if matrix flag == 1)
|
||||
|
||||
$info['video']['dataformat'] = 'mpeg';
|
||||
|
||||
$VideoChunkOffset += (strlen(GETID3_MPEG_VIDEO_SEQUENCE_HEADER) - 1);
|
||||
|
||||
$FrameSizeDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 3));
|
||||
$VideoChunkOffset += 3;
|
||||
|
||||
$AspectRatioFrameRateDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 1));
|
||||
$VideoChunkOffset += 1;
|
||||
|
||||
$assortedinformation = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 4));
|
||||
$VideoChunkOffset += 4;
|
||||
|
||||
$info['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xFFF000) >> 12; // 12 bits for horizontal frame size
|
||||
$info['mpeg']['video']['raw']['framesize_vertical'] = ($FrameSizeDWORD & 0x000FFF); // 12 bits for vertical frame size
|
||||
$info['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xF0) >> 4;
|
||||
$info['mpeg']['video']['raw']['frame_rate'] = ($AspectRatioFrameRateDWORD & 0x0F);
|
||||
|
||||
$info['mpeg']['video']['framesize_horizontal'] = $info['mpeg']['video']['raw']['framesize_horizontal'];
|
||||
$info['mpeg']['video']['framesize_vertical'] = $info['mpeg']['video']['raw']['framesize_vertical'];
|
||||
|
||||
$info['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']);
|
||||
$info['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']);
|
||||
$info['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($info['mpeg']['video']['raw']['frame_rate']);
|
||||
|
||||
$info['mpeg']['video']['raw']['bitrate'] = getid3_lib::Bin2Dec(substr($assortedinformation, 0, 18));
|
||||
$info['mpeg']['video']['raw']['marker_bit'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 18, 1));
|
||||
$info['mpeg']['video']['raw']['vbv_buffer_size'] = getid3_lib::Bin2Dec(substr($assortedinformation, 19, 10));
|
||||
$info['mpeg']['video']['raw']['constrained_param_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 29, 1));
|
||||
$info['mpeg']['video']['raw']['intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 30, 1));
|
||||
if ($info['mpeg']['video']['raw']['intra_quant_flag']) {
|
||||
|
||||
// read 512 bits
|
||||
$info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64));
|
||||
$VideoChunkOffset += 64;
|
||||
|
||||
$info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($info['mpeg']['video']['raw']['intra_quant'], 511, 1));
|
||||
$info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)).substr(getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511);
|
||||
|
||||
if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) {
|
||||
$info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64);
|
||||
$VideoChunkOffset += 64;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1));
|
||||
if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) {
|
||||
$info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64);
|
||||
$VideoChunkOffset += 64;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($info['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits
|
||||
|
||||
$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] cannot determine average bitrate of VBR MPEG video files';
|
||||
$info['mpeg']['video']['bitrate_mode'] = 'vbr';
|
||||
|
||||
} else {
|
||||
|
||||
$info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400;
|
||||
$info['mpeg']['video']['bitrate_mode'] = 'cbr';
|
||||
$info['video']['bitrate'] = $info['mpeg']['video']['bitrate'];
|
||||
|
||||
}
|
||||
|
||||
$info['video']['resolution_x'] = $info['mpeg']['video']['framesize_horizontal'];
|
||||
$info['video']['resolution_y'] = $info['mpeg']['video']['framesize_vertical'];
|
||||
$info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate'];
|
||||
$info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode'];
|
||||
$info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio'];
|
||||
$info['video']['lossless'] = false;
|
||||
$info['video']['bits_per_sample'] = 24;
|
||||
|
||||
} else {
|
||||
|
||||
$info['error'][] = 'Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?';
|
||||
|
||||
}
|
||||
|
||||
//0x000001B3 begins the sequence_header of every MPEG video stream.
|
||||
//But in MPEG-2, this header must immediately be followed by an
|
||||
//extension_start_code (0x000001B5) with a sequence_extension ID (1).
|
||||
//(This extension contains all the additional MPEG-2 stuff.)
|
||||
//MPEG-1 doesn't have this extension, so that's a sure way to tell the
|
||||
//difference between MPEG-1 and MPEG-2 video streams.
|
||||
|
||||
if (substr($MPEGstreamData, $VideoChunkOffset, 4) == GETID3_MPEG_VIDEO_EXTENSION_START) {
|
||||
$info['video']['codec'] = 'MPEG-2';
|
||||
} else {
|
||||
$info['video']['codec'] = 'MPEG-1';
|
||||
}
|
||||
|
||||
|
||||
$AudioChunkOffset = 0;
|
||||
while (true) {
|
||||
while (substr($MPEGstreamData, $AudioChunkOffset++, 4) !== GETID3_MPEG_AUDIO_START) {
|
||||
if ($AudioChunkOffset >= $MPEGstreamDataLength) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($this->getid3->filename);
|
||||
$getid3_temp->info = $info;
|
||||
$getid3_mp3 = new getid3_mp3($getid3_temp);
|
||||
for ($i = 0; $i <= 7; $i++) {
|
||||
// some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after
|
||||
// I have no idea why or what the difference is, so this is a stupid hack.
|
||||
// If anybody has any better idea of what's going on, please let me know - info@getid3.org
|
||||
fseek($getid3_temp->fp, ftell($this->getid3->fp), SEEK_SET);
|
||||
$getid3_temp->info = $info; // only overwrite real data if valid header found
|
||||
if ($getid3_mp3->decodeMPEGaudioHeader(($AudioChunkOffset + 3) + 8 + $i, $getid3_temp->info, false)) {
|
||||
$info = $getid3_temp->info;
|
||||
$info['audio']['bitrate_mode'] = 'cbr';
|
||||
$info['audio']['lossless'] = false;
|
||||
unset($getid3_temp, $getid3_mp3);
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
unset($getid3_temp, $getid3_mp3);
|
||||
}
|
||||
|
||||
// Temporary hack to account for interleaving overhead:
|
||||
if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) {
|
||||
$info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['video']['bitrate'] + $info['audio']['bitrate']);
|
||||
|
||||
// Interleaved MPEG audio/video files have a certain amount of overhead that varies
|
||||
// by both video and audio bitrates, and not in any sensible, linear/logarithmic patter
|
||||
// Use interpolated lookup tables to approximately guess how much is overhead, because
|
||||
// playtime is calculated as filesize / total-bitrate
|
||||
$info['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']);
|
||||
|
||||
//switch ($info['video']['bitrate']) {
|
||||
// case('5000000'):
|
||||
// $multiplier = 0.93292642112380355828048824319889;
|
||||
// break;
|
||||
// case('5500000'):
|
||||
// $multiplier = 0.93582895375200989965359777343219;
|
||||
// break;
|
||||
// case('6000000'):
|
||||
// $multiplier = 0.93796247714820932532911373859139;
|
||||
// break;
|
||||
// case('7000000'):
|
||||
// $multiplier = 0.9413264083635103463010117778776;
|
||||
// break;
|
||||
// default:
|
||||
// $multiplier = 1;
|
||||
// break;
|
||||
//}
|
||||
//$info['playtime_seconds'] *= $multiplier;
|
||||
//$info['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) {
|
||||
$info['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.';
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function MPEGsystemNonOverheadPercentage($VideoBitrate, $AudioBitrate) {
|
||||
$OverheadPercentage = 0;
|
||||
|
||||
$AudioBitrate = max(min($AudioBitrate / 1000, 384), 32); // limit to range of 32kbps - 384kbps (should be only legal bitrates, but maybe VBR?)
|
||||
$VideoBitrate = max(min($VideoBitrate / 1000, 10000), 10); // limit to range of 10kbps - 10Mbps (beyond that curves flatten anyways, no big loss)
|
||||
|
||||
|
||||
//OMBB[audiobitrate] = array(video-10kbps, video-100kbps, video-1000kbps, video-10000kbps)
|
||||
$OverheadMultiplierByBitrate[32] = array(0, 0.9676287944368530, 0.9802276264360310, 0.9844916183244460, 0.9852821845179940);
|
||||
$OverheadMultiplierByBitrate[48] = array(0, 0.9779100089209830, 0.9787770035359320, 0.9846738664076130, 0.9852683013799960);
|
||||
$OverheadMultiplierByBitrate[56] = array(0, 0.9731249855367600, 0.9776624308938040, 0.9832606361852130, 0.9843922606633340);
|
||||
$OverheadMultiplierByBitrate[64] = array(0, 0.9755642683275760, 0.9795256705493390, 0.9836573009193170, 0.9851122539404470);
|
||||
$OverheadMultiplierByBitrate[96] = array(0, 0.9788025247497290, 0.9798553314148700, 0.9822956869792560, 0.9834815119124690);
|
||||
$OverheadMultiplierByBitrate[128] = array(0, 0.9816940050925480, 0.9821675936072120, 0.9829756927470870, 0.9839763420152050);
|
||||
$OverheadMultiplierByBitrate[160] = array(0, 0.9825894094561180, 0.9820913399073960, 0.9823907143253970, 0.9832821783651570);
|
||||
$OverheadMultiplierByBitrate[192] = array(0, 0.9832038474336260, 0.9825731694317960, 0.9821028622712400, 0.9828262076447620);
|
||||
$OverheadMultiplierByBitrate[224] = array(0, 0.9836516298538770, 0.9824718601823890, 0.9818302180625380, 0.9823735101626480);
|
||||
$OverheadMultiplierByBitrate[256] = array(0, 0.9845863022094920, 0.9837229411967540, 0.9824521662210830, 0.9828645172100790);
|
||||
$OverheadMultiplierByBitrate[320] = array(0, 0.9849565280263180, 0.9837683142805110, 0.9822885275960400, 0.9824424382727190);
|
||||
$OverheadMultiplierByBitrate[384] = array(0, 0.9856094774357600, 0.9844573394432720, 0.9825970399837330, 0.9824673808303890);
|
||||
|
||||
$BitrateToUseMin = 32;
|
||||
$BitrateToUseMax = 32;
|
||||
$previousBitrate = 32;
|
||||
foreach ($OverheadMultiplierByBitrate as $key => $value) {
|
||||
if ($AudioBitrate >= $previousBitrate) {
|
||||
$BitrateToUseMin = $previousBitrate;
|
||||
}
|
||||
if ($AudioBitrate < $key) {
|
||||
$BitrateToUseMax = $key;
|
||||
break;
|
||||
}
|
||||
$previousBitrate = $key;
|
||||
}
|
||||
$FactorA = ($BitrateToUseMax - $AudioBitrate) / ($BitrateToUseMax - $BitrateToUseMin);
|
||||
|
||||
$VideoBitrateLog10 = log10($VideoBitrate);
|
||||
$VideoFactorMin1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][floor($VideoBitrateLog10)];
|
||||
$VideoFactorMin2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][floor($VideoBitrateLog10)];
|
||||
$VideoFactorMax1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][ceil($VideoBitrateLog10)];
|
||||
$VideoFactorMax2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][ceil($VideoBitrateLog10)];
|
||||
$FactorV = $VideoBitrateLog10 - floor($VideoBitrateLog10);
|
||||
|
||||
$OverheadPercentage = $VideoFactorMin1 * $FactorA * $FactorV;
|
||||
$OverheadPercentage += $VideoFactorMin2 * (1 - $FactorA) * $FactorV;
|
||||
$OverheadPercentage += $VideoFactorMax1 * $FactorA * (1 - $FactorV);
|
||||
$OverheadPercentage += $VideoFactorMax2 * (1 - $FactorA) * (1 - $FactorV);
|
||||
|
||||
return $OverheadPercentage;
|
||||
}
|
||||
|
||||
|
||||
public function MPEGvideoFramerateLookup($rawframerate) {
|
||||
$MPEGvideoFramerateLookup = array(0, 23.976, 24, 25, 29.97, 30, 50, 59.94, 60);
|
||||
return (isset($MPEGvideoFramerateLookup[$rawframerate]) ? (float) $MPEGvideoFramerateLookup[$rawframerate] : (float) 0);
|
||||
}
|
||||
|
||||
public function MPEGvideoAspectRatioLookup($rawaspectratio) {
|
||||
$MPEGvideoAspectRatioLookup = array(0, 1, 0.6735, 0.7031, 0.7615, 0.8055, 0.8437, 0.8935, 0.9157, 0.9815, 1.0255, 1.0695, 1.0950, 1.1575, 1.2015, 0);
|
||||
return (isset($MPEGvideoAspectRatioLookup[$rawaspectratio]) ? (float) $MPEGvideoAspectRatioLookup[$rawaspectratio] : (float) 0);
|
||||
}
|
||||
|
||||
public function MPEGvideoAspectRatioTextLookup($rawaspectratio) {
|
||||
$MPEGvideoAspectRatioTextLookup = array('forbidden', 'square pixels', '0.6735', '16:9, 625 line, PAL', '0.7615', '0.8055', '16:9, 525 line, NTSC', '0.8935', '4:3, 625 line, PAL, CCIR601', '0.9815', '1.0255', '1.0695', '4:3, 525 line, NTSC, CCIR601', '1.1575', '1.2015', 'reserved');
|
||||
return (isset($MPEGvideoAspectRatioTextLookup[$rawaspectratio]) ? $MPEGvideoAspectRatioTextLookup[$rawaspectratio] : '');
|
||||
}
|
||||
|
||||
}
|
223
api/getID3/module.audio-video.nsv.php
Executable file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.nsv.php //
|
||||
// module for analyzing Nullsoft NSV files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_nsv extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$NSVheader = fread($this->getid3->fp, 4);
|
||||
|
||||
switch ($NSVheader) {
|
||||
case 'NSVs':
|
||||
if ($this->getNSVsHeaderFilepointer(0)) {
|
||||
$info['fileformat'] = 'nsv';
|
||||
$info['audio']['dataformat'] = 'nsv';
|
||||
$info['video']['dataformat'] = 'nsv';
|
||||
$info['audio']['lossless'] = false;
|
||||
$info['video']['lossless'] = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NSVf':
|
||||
if ($this->getNSVfHeaderFilepointer(0)) {
|
||||
$info['fileformat'] = 'nsv';
|
||||
$info['audio']['dataformat'] = 'nsv';
|
||||
$info['video']['dataformat'] = 'nsv';
|
||||
$info['audio']['lossless'] = false;
|
||||
$info['video']['lossless'] = false;
|
||||
$this->getNSVsHeaderFilepointer($info['nsv']['NSVf']['header_length']);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Expecting "NSVs" or "NSVf" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($NSVheader).'"';
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($info['nsv']['NSVf'])) {
|
||||
$info['warning'][] = 'NSVf header not present - cannot calculate playtime or bitrate';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getNSVsHeaderFilepointer($fileoffset) {
|
||||
$info = &$this->getid3->info;
|
||||
fseek($this->getid3->fp, $fileoffset, SEEK_SET);
|
||||
$NSVsheader = fread($this->getid3->fp, 28);
|
||||
$offset = 0;
|
||||
|
||||
$info['nsv']['NSVs']['identifier'] = substr($NSVsheader, $offset, 4);
|
||||
$offset += 4;
|
||||
|
||||
if ($info['nsv']['NSVs']['identifier'] != 'NSVs') {
|
||||
$info['error'][] = 'expected "NSVs" at offset ('.$fileoffset.'), found "'.$info['nsv']['NSVs']['identifier'].'" instead';
|
||||
unset($info['nsv']['NSVs']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['nsv']['NSVs']['offset'] = $fileoffset;
|
||||
|
||||
$info['nsv']['NSVs']['video_codec'] = substr($NSVsheader, $offset, 4);
|
||||
$offset += 4;
|
||||
$info['nsv']['NSVs']['audio_codec'] = substr($NSVsheader, $offset, 4);
|
||||
$offset += 4;
|
||||
$info['nsv']['NSVs']['resolution_x'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$info['nsv']['NSVs']['resolution_y'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2));
|
||||
$offset += 2;
|
||||
|
||||
$info['nsv']['NSVs']['framerate_index'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||
$offset += 1;
|
||||
//$info['nsv']['NSVs']['unknown1b'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||
$offset += 1;
|
||||
//$info['nsv']['NSVs']['unknown1c'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||
$offset += 1;
|
||||
//$info['nsv']['NSVs']['unknown1d'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||
$offset += 1;
|
||||
//$info['nsv']['NSVs']['unknown2a'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||
$offset += 1;
|
||||
//$info['nsv']['NSVs']['unknown2b'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||
$offset += 1;
|
||||
//$info['nsv']['NSVs']['unknown2c'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||
$offset += 1;
|
||||
//$info['nsv']['NSVs']['unknown2d'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
switch ($info['nsv']['NSVs']['audio_codec']) {
|
||||
case 'PCM ':
|
||||
$info['nsv']['NSVs']['bits_channel'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||
$offset += 1;
|
||||
$info['nsv']['NSVs']['channels'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||
$offset += 1;
|
||||
$info['nsv']['NSVs']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2));
|
||||
$offset += 2;
|
||||
|
||||
$info['audio']['sample_rate'] = $info['nsv']['NSVs']['sample_rate'];
|
||||
break;
|
||||
|
||||
case 'MP3 ':
|
||||
case 'NONE':
|
||||
default:
|
||||
//$info['nsv']['NSVs']['unknown3'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 4));
|
||||
$offset += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
$info['video']['resolution_x'] = $info['nsv']['NSVs']['resolution_x'];
|
||||
$info['video']['resolution_y'] = $info['nsv']['NSVs']['resolution_y'];
|
||||
$info['nsv']['NSVs']['frame_rate'] = $this->NSVframerateLookup($info['nsv']['NSVs']['framerate_index']);
|
||||
$info['video']['frame_rate'] = $info['nsv']['NSVs']['frame_rate'];
|
||||
$info['video']['bits_per_sample'] = 24;
|
||||
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getNSVfHeaderFilepointer($fileoffset, $getTOCoffsets=false) {
|
||||
$info = &$this->getid3->info;
|
||||
fseek($this->getid3->fp, $fileoffset, SEEK_SET);
|
||||
$NSVfheader = fread($this->getid3->fp, 28);
|
||||
$offset = 0;
|
||||
|
||||
$info['nsv']['NSVf']['identifier'] = substr($NSVfheader, $offset, 4);
|
||||
$offset += 4;
|
||||
|
||||
if ($info['nsv']['NSVf']['identifier'] != 'NSVf') {
|
||||
$info['error'][] = 'expected "NSVf" at offset ('.$fileoffset.'), found "'.$info['nsv']['NSVf']['identifier'].'" instead';
|
||||
unset($info['nsv']['NSVf']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['nsv']['NSVs']['offset'] = $fileoffset;
|
||||
|
||||
$info['nsv']['NSVf']['header_length'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$info['nsv']['NSVf']['file_size'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
if ($info['nsv']['NSVf']['file_size'] > $info['avdataend']) {
|
||||
$info['warning'][] = 'truncated file - NSVf header indicates '.$info['nsv']['NSVf']['file_size'].' bytes, file actually '.$info['avdataend'].' bytes';
|
||||
}
|
||||
|
||||
$info['nsv']['NSVf']['playtime_ms'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$info['nsv']['NSVf']['meta_size'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$info['nsv']['NSVf']['TOC_entries_1'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$info['nsv']['NSVf']['TOC_entries_2'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
if ($info['nsv']['NSVf']['playtime_ms'] == 0) {
|
||||
$info['error'][] = 'Corrupt NSV file: NSVf.playtime_ms == zero';
|
||||
return false;
|
||||
}
|
||||
|
||||
$NSVfheader .= fread($this->getid3->fp, $info['nsv']['NSVf']['meta_size'] + (4 * $info['nsv']['NSVf']['TOC_entries_1']) + (4 * $info['nsv']['NSVf']['TOC_entries_2']));
|
||||
$NSVfheaderlength = strlen($NSVfheader);
|
||||
$info['nsv']['NSVf']['metadata'] = substr($NSVfheader, $offset, $info['nsv']['NSVf']['meta_size']);
|
||||
$offset += $info['nsv']['NSVf']['meta_size'];
|
||||
|
||||
if ($getTOCoffsets) {
|
||||
$TOCcounter = 0;
|
||||
while ($TOCcounter < $info['nsv']['NSVf']['TOC_entries_1']) {
|
||||
if ($TOCcounter < $info['nsv']['NSVf']['TOC_entries_1']) {
|
||||
$info['nsv']['NSVf']['TOC_1'][$TOCcounter] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$TOCcounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trim($info['nsv']['NSVf']['metadata']) != '') {
|
||||
$info['nsv']['NSVf']['metadata'] = str_replace('`', "\x01", $info['nsv']['NSVf']['metadata']);
|
||||
$CommentPairArray = explode("\x01".' ', $info['nsv']['NSVf']['metadata']);
|
||||
foreach ($CommentPairArray as $CommentPair) {
|
||||
if (strstr($CommentPair, '='."\x01")) {
|
||||
list($key, $value) = explode('='."\x01", $CommentPair, 2);
|
||||
$info['nsv']['comments'][strtolower($key)][] = trim(str_replace("\x01", '', $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$info['playtime_seconds'] = $info['nsv']['NSVf']['playtime_ms'] / 1000;
|
||||
$info['bitrate'] = ($info['nsv']['NSVf']['file_size'] * 8) / $info['playtime_seconds'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static function NSVframerateLookup($framerateindex) {
|
||||
if ($framerateindex <= 127) {
|
||||
return (float) $framerateindex;
|
||||
}
|
||||
static $NSVframerateLookup = array();
|
||||
if (empty($NSVframerateLookup)) {
|
||||
$NSVframerateLookup[129] = (float) 29.970;
|
||||
$NSVframerateLookup[131] = (float) 23.976;
|
||||
$NSVframerateLookup[133] = (float) 14.985;
|
||||
$NSVframerateLookup[197] = (float) 59.940;
|
||||
$NSVframerateLookup[199] = (float) 47.952;
|
||||
}
|
||||
return (isset($NSVframerateLookup[$framerateindex]) ? $NSVframerateLookup[$framerateindex] : false);
|
||||
}
|
||||
|
||||
}
|
2145
api/getID3/module.audio-video.quicktime.php
Executable file
527
api/getID3/module.audio-video.real.php
Executable file
@ -0,0 +1,527 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio-video.real.php //
|
||||
// module for analyzing Real Audio/Video files //
|
||||
// dependencies: module.audio-video.riff.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||
|
||||
class getid3_real extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'real';
|
||||
$info['bitrate'] = 0;
|
||||
$info['playtime_seconds'] = 0;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$ChunkCounter = 0;
|
||||
while (ftell($this->getid3->fp) < $info['avdataend']) {
|
||||
$ChunkData = fread($this->getid3->fp, 8);
|
||||
$ChunkName = substr($ChunkData, 0, 4);
|
||||
$ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, 4, 4));
|
||||
|
||||
if ($ChunkName == '.ra'."\xFD") {
|
||||
$ChunkData .= fread($this->getid3->fp, $ChunkSize - 8);
|
||||
if ($this->ParseOldRAheader(substr($ChunkData, 0, 128), $info['real']['old_ra_header'])) {
|
||||
$info['audio']['dataformat'] = 'real';
|
||||
$info['audio']['lossless'] = false;
|
||||
$info['audio']['sample_rate'] = $info['real']['old_ra_header']['sample_rate'];
|
||||
$info['audio']['bits_per_sample'] = $info['real']['old_ra_header']['bits_per_sample'];
|
||||
$info['audio']['channels'] = $info['real']['old_ra_header']['channels'];
|
||||
|
||||
$info['playtime_seconds'] = 60 * ($info['real']['old_ra_header']['audio_bytes'] / $info['real']['old_ra_header']['bytes_per_minute']);
|
||||
$info['audio']['bitrate'] = 8 * ($info['real']['old_ra_header']['audio_bytes'] / $info['playtime_seconds']);
|
||||
$info['audio']['codec'] = $this->RealAudioCodecFourCClookup($info['real']['old_ra_header']['fourcc'], $info['audio']['bitrate']);
|
||||
|
||||
foreach ($info['real']['old_ra_header']['comments'] as $key => $valuearray) {
|
||||
if (strlen(trim($valuearray[0])) > 0) {
|
||||
$info['real']['comments'][$key][] = trim($valuearray[0]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
$info['error'][] = 'There was a problem parsing this RealAudio file. Please submit it for analysis to info@getid3.org';
|
||||
unset($info['bitrate']);
|
||||
unset($info['playtime_seconds']);
|
||||
return false;
|
||||
}
|
||||
|
||||
// shortcut
|
||||
$info['real']['chunks'][$ChunkCounter] = array();
|
||||
$thisfile_real_chunks_currentchunk = &$info['real']['chunks'][$ChunkCounter];
|
||||
|
||||
$thisfile_real_chunks_currentchunk['name'] = $ChunkName;
|
||||
$thisfile_real_chunks_currentchunk['offset'] = ftell($this->getid3->fp) - 8;
|
||||
$thisfile_real_chunks_currentchunk['length'] = $ChunkSize;
|
||||
if (($thisfile_real_chunks_currentchunk['offset'] + $thisfile_real_chunks_currentchunk['length']) > $info['avdataend']) {
|
||||
$info['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)) {
|
||||
|
||||
$ChunkData .= fread($this->getid3->fp, $this->getid3->fread_buffer_size() - 8);
|
||||
fseek($this->getid3->fp, $thisfile_real_chunks_currentchunk['offset'] + $ChunkSize, SEEK_SET);
|
||||
|
||||
} elseif(($ChunkSize - 8) > 0) {
|
||||
|
||||
$ChunkData .= fread($this->getid3->fp, $ChunkSize - 8);
|
||||
|
||||
}
|
||||
$offset = 8;
|
||||
|
||||
switch ($ChunkName) {
|
||||
|
||||
case '.RMF': // RealMedia File Header
|
||||
$thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
switch ($thisfile_real_chunks_currentchunk['object_version']) {
|
||||
|
||||
case 0:
|
||||
$thisfile_real_chunks_currentchunk['file_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['headers_count'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
break;
|
||||
|
||||
default:
|
||||
//$info['warning'][] = 'Expected .RMF-object_version to be "0", actual value is "'.$thisfile_real_chunks_currentchunk['object_version'].'" (should not be a problem)';
|
||||
break;
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'PROP': // Properties Header
|
||||
$thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
if ($thisfile_real_chunks_currentchunk['object_version'] == 0) {
|
||||
$thisfile_real_chunks_currentchunk['max_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['avg_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['max_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['avg_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['num_packets'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['duration'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['preroll'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['index_offset'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['data_offset'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['num_streams'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_real_chunks_currentchunk['flags_raw'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
$info['playtime_seconds'] = $thisfile_real_chunks_currentchunk['duration'] / 1000;
|
||||
if ($thisfile_real_chunks_currentchunk['duration'] > 0) {
|
||||
$info['bitrate'] += $thisfile_real_chunks_currentchunk['avg_bit_rate'];
|
||||
}
|
||||
$thisfile_real_chunks_currentchunk['flags']['save_enabled'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0001);
|
||||
$thisfile_real_chunks_currentchunk['flags']['perfect_play'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0002);
|
||||
$thisfile_real_chunks_currentchunk['flags']['live_broadcast'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0004);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'MDPR': // Media Properties Header
|
||||
$thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
if ($thisfile_real_chunks_currentchunk['object_version'] == 0) {
|
||||
$thisfile_real_chunks_currentchunk['stream_number'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_real_chunks_currentchunk['max_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['avg_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['max_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['avg_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['start_time'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['preroll'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['duration'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['stream_name_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 1));
|
||||
$offset += 1;
|
||||
$thisfile_real_chunks_currentchunk['stream_name'] = substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['stream_name_size']);
|
||||
$offset += $thisfile_real_chunks_currentchunk['stream_name_size'];
|
||||
$thisfile_real_chunks_currentchunk['mime_type_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 1));
|
||||
$offset += 1;
|
||||
$thisfile_real_chunks_currentchunk['mime_type'] = substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['mime_type_size']);
|
||||
$offset += $thisfile_real_chunks_currentchunk['mime_type_size'];
|
||||
$thisfile_real_chunks_currentchunk['type_specific_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['type_specific_data'] = substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['type_specific_len']);
|
||||
$offset += $thisfile_real_chunks_currentchunk['type_specific_len'];
|
||||
|
||||
// shortcut
|
||||
$thisfile_real_chunks_currentchunk_typespecificdata = &$thisfile_real_chunks_currentchunk['type_specific_data'];
|
||||
|
||||
switch ($thisfile_real_chunks_currentchunk['mime_type']) {
|
||||
case 'video/x-pn-realvideo':
|
||||
case 'video/x-pn-multirate-realvideo':
|
||||
// http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html
|
||||
|
||||
// shortcut
|
||||
$thisfile_real_chunks_currentchunk['video_info'] = array();
|
||||
$thisfile_real_chunks_currentchunk_videoinfo = &$thisfile_real_chunks_currentchunk['video_info'];
|
||||
|
||||
$thisfile_real_chunks_currentchunk_videoinfo['dwSize'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 0, 4));
|
||||
$thisfile_real_chunks_currentchunk_videoinfo['fourcc1'] = substr($thisfile_real_chunks_currentchunk_typespecificdata, 4, 4);
|
||||
$thisfile_real_chunks_currentchunk_videoinfo['fourcc2'] = substr($thisfile_real_chunks_currentchunk_typespecificdata, 8, 4);
|
||||
$thisfile_real_chunks_currentchunk_videoinfo['width'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 12, 2));
|
||||
$thisfile_real_chunks_currentchunk_videoinfo['height'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 14, 2));
|
||||
$thisfile_real_chunks_currentchunk_videoinfo['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 16, 2));
|
||||
//$thisfile_real_chunks_currentchunk_videoinfo['unknown1'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 18, 2));
|
||||
//$thisfile_real_chunks_currentchunk_videoinfo['unknown2'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 20, 2));
|
||||
$thisfile_real_chunks_currentchunk_videoinfo['frames_per_second'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 22, 2));
|
||||
//$thisfile_real_chunks_currentchunk_videoinfo['unknown3'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 24, 2));
|
||||
//$thisfile_real_chunks_currentchunk_videoinfo['unknown4'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 26, 2));
|
||||
//$thisfile_real_chunks_currentchunk_videoinfo['unknown5'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 28, 2));
|
||||
//$thisfile_real_chunks_currentchunk_videoinfo['unknown6'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 30, 2));
|
||||
//$thisfile_real_chunks_currentchunk_videoinfo['unknown7'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 32, 2));
|
||||
//$thisfile_real_chunks_currentchunk_videoinfo['unknown8'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 34, 2));
|
||||
//$thisfile_real_chunks_currentchunk_videoinfo['unknown9'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 36, 2));
|
||||
|
||||
$thisfile_real_chunks_currentchunk_videoinfo['codec'] = getid3_riff::fourccLookup($thisfile_real_chunks_currentchunk_videoinfo['fourcc2']);
|
||||
|
||||
$info['video']['resolution_x'] = $thisfile_real_chunks_currentchunk_videoinfo['width'];
|
||||
$info['video']['resolution_y'] = $thisfile_real_chunks_currentchunk_videoinfo['height'];
|
||||
$info['video']['frame_rate'] = (float) $thisfile_real_chunks_currentchunk_videoinfo['frames_per_second'];
|
||||
$info['video']['codec'] = $thisfile_real_chunks_currentchunk_videoinfo['codec'];
|
||||
$info['video']['bits_per_sample'] = $thisfile_real_chunks_currentchunk_videoinfo['bits_per_sample'];
|
||||
break;
|
||||
|
||||
case 'audio/x-pn-realaudio':
|
||||
case 'audio/x-pn-multirate-realaudio':
|
||||
$this->ParseOldRAheader($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk['parsed_audio_data']);
|
||||
|
||||
$info['audio']['sample_rate'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['sample_rate'];
|
||||
$info['audio']['bits_per_sample'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['bits_per_sample'];
|
||||
$info['audio']['channels'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['channels'];
|
||||
if (!empty($info['audio']['dataformat'])) {
|
||||
foreach ($info['audio'] as $key => $value) {
|
||||
if ($key != 'streams') {
|
||||
$info['audio']['streams'][$thisfile_real_chunks_currentchunk['stream_number']][$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'logical-fileinfo':
|
||||
// shortcut
|
||||
$thisfile_real_chunks_currentchunk['logical_fileinfo'] = array();
|
||||
$thisfile_real_chunks_currentchunk_logicalfileinfo = &$thisfile_real_chunks_currentchunk['logical_fileinfo'];
|
||||
|
||||
$thisfile_real_chunks_currentchunk_logicalfileinfo_offset = 0;
|
||||
$thisfile_real_chunks_currentchunk_logicalfileinfo['logical_fileinfo_length'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4));
|
||||
$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4;
|
||||
|
||||
//$thisfile_real_chunks_currentchunk_logicalfileinfo['unknown1'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4));
|
||||
$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4;
|
||||
|
||||
$thisfile_real_chunks_currentchunk_logicalfileinfo['num_tags'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4));
|
||||
$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4;
|
||||
|
||||
//$thisfile_real_chunks_currentchunk_logicalfileinfo['unknown2'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4));
|
||||
$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4;
|
||||
|
||||
//$thisfile_real_chunks_currentchunk_logicalfileinfo['d'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 1));
|
||||
|
||||
//$thisfile_real_chunks_currentchunk_logicalfileinfo['one_type'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4));
|
||||
//$thisfile_real_chunks_currentchunk_logicalfileinfo_thislength = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 4 + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 2));
|
||||
//$thisfile_real_chunks_currentchunk_logicalfileinfo['one'] = substr($thisfile_real_chunks_currentchunk_typespecificdata, 6 + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, $thisfile_real_chunks_currentchunk_logicalfileinfo_thislength);
|
||||
//$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += (6 + $thisfile_real_chunks_currentchunk_logicalfileinfo_thislength);
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (empty($info['playtime_seconds'])) {
|
||||
$info['playtime_seconds'] = max($info['playtime_seconds'], ($thisfile_real_chunks_currentchunk['duration'] + $thisfile_real_chunks_currentchunk['start_time']) / 1000);
|
||||
}
|
||||
if ($thisfile_real_chunks_currentchunk['duration'] > 0) {
|
||||
switch ($thisfile_real_chunks_currentchunk['mime_type']) {
|
||||
case 'audio/x-pn-realaudio':
|
||||
case 'audio/x-pn-multirate-realaudio':
|
||||
$info['audio']['bitrate'] = (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate'];
|
||||
$info['audio']['codec'] = $this->RealAudioCodecFourCClookup($thisfile_real_chunks_currentchunk['parsed_audio_data']['fourcc'], $info['audio']['bitrate']);
|
||||
$info['audio']['dataformat'] = 'real';
|
||||
$info['audio']['lossless'] = false;
|
||||
break;
|
||||
|
||||
case 'video/x-pn-realvideo':
|
||||
case 'video/x-pn-multirate-realvideo':
|
||||
$info['video']['bitrate'] = (isset($info['video']['bitrate']) ? $info['video']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate'];
|
||||
$info['video']['bitrate_mode'] = 'cbr';
|
||||
$info['video']['dataformat'] = 'real';
|
||||
$info['video']['lossless'] = false;
|
||||
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||
break;
|
||||
|
||||
case 'audio/x-ralf-mpeg4-generic':
|
||||
$info['audio']['bitrate'] = (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate'];
|
||||
$info['audio']['codec'] = 'RealAudio Lossless';
|
||||
$info['audio']['dataformat'] = 'real';
|
||||
$info['audio']['lossless'] = true;
|
||||
break;
|
||||
}
|
||||
$info['bitrate'] = (isset($info['video']['bitrate']) ? $info['video']['bitrate'] : 0) + (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'CONT': // Content Description Header (text comments)
|
||||
$thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
if ($thisfile_real_chunks_currentchunk['object_version'] == 0) {
|
||||
$thisfile_real_chunks_currentchunk['title_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_real_chunks_currentchunk['title'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['title_len']);
|
||||
$offset += $thisfile_real_chunks_currentchunk['title_len'];
|
||||
|
||||
$thisfile_real_chunks_currentchunk['artist_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_real_chunks_currentchunk['artist'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['artist_len']);
|
||||
$offset += $thisfile_real_chunks_currentchunk['artist_len'];
|
||||
|
||||
$thisfile_real_chunks_currentchunk['copyright_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_real_chunks_currentchunk['copyright'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['copyright_len']);
|
||||
$offset += $thisfile_real_chunks_currentchunk['copyright_len'];
|
||||
|
||||
$thisfile_real_chunks_currentchunk['comment_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_real_chunks_currentchunk['comment'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['comment_len']);
|
||||
$offset += $thisfile_real_chunks_currentchunk['comment_len'];
|
||||
|
||||
|
||||
$commentkeystocopy = array('title'=>'title', 'artist'=>'artist', 'copyright'=>'copyright', 'comment'=>'comment');
|
||||
foreach ($commentkeystocopy as $key => $val) {
|
||||
if ($thisfile_real_chunks_currentchunk[$key]) {
|
||||
$info['real']['comments'][$val][] = trim($thisfile_real_chunks_currentchunk[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'DATA': // Data Chunk Header
|
||||
// do nothing
|
||||
break;
|
||||
|
||||
case 'INDX': // Index Section Header
|
||||
$thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
if ($thisfile_real_chunks_currentchunk['object_version'] == 0) {
|
||||
$thisfile_real_chunks_currentchunk['num_indices'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_real_chunks_currentchunk['stream_number'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_real_chunks_currentchunk['next_index_header'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
if ($thisfile_real_chunks_currentchunk['next_index_header'] == 0) {
|
||||
// last index chunk found, ignore rest of file
|
||||
break 2;
|
||||
} else {
|
||||
// non-last index chunk, seek to next index chunk (skipping actual index data)
|
||||
fseek($this->getid3->fp, $thisfile_real_chunks_currentchunk['next_index_header'], SEEK_SET);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['warning'][] = 'Unhandled RealMedia chunk "'.$ChunkName.'" at offset '.$thisfile_real_chunks_currentchunk['offset'];
|
||||
break;
|
||||
}
|
||||
$ChunkCounter++;
|
||||
}
|
||||
|
||||
if (!empty($info['audio']['streams'])) {
|
||||
$info['audio']['bitrate'] = 0;
|
||||
foreach ($info['audio']['streams'] as $key => $valuearray) {
|
||||
$info['audio']['bitrate'] += $valuearray['bitrate'];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function ParseOldRAheader($OldRAheaderData, &$ParsedArray) {
|
||||
// http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html
|
||||
|
||||
$ParsedArray = array();
|
||||
$ParsedArray['magic'] = substr($OldRAheaderData, 0, 4);
|
||||
if ($ParsedArray['magic'] != '.ra'."\xFD") {
|
||||
return false;
|
||||
}
|
||||
$ParsedArray['version1'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 4, 2));
|
||||
|
||||
if ($ParsedArray['version1'] < 3) {
|
||||
|
||||
return false;
|
||||
|
||||
} elseif ($ParsedArray['version1'] == 3) {
|
||||
|
||||
$ParsedArray['fourcc1'] = '.ra3';
|
||||
$ParsedArray['bits_per_sample'] = 16; // hard-coded for old versions?
|
||||
$ParsedArray['sample_rate'] = 8000; // hard-coded for old versions?
|
||||
|
||||
$ParsedArray['header_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 6, 2));
|
||||
$ParsedArray['channels'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 8, 2)); // always 1 (?)
|
||||
//$ParsedArray['unknown1'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 10, 2));
|
||||
//$ParsedArray['unknown2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 12, 2));
|
||||
//$ParsedArray['unknown3'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 14, 2));
|
||||
$ParsedArray['bytes_per_minute'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 16, 2));
|
||||
$ParsedArray['audio_bytes'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 18, 4));
|
||||
$ParsedArray['comments_raw'] = substr($OldRAheaderData, 22, $ParsedArray['header_size'] - 22 + 1); // not including null terminator
|
||||
|
||||
$commentoffset = 0;
|
||||
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||
$ParsedArray['comments']['title'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||
$commentoffset += $commentlength;
|
||||
|
||||
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||
$ParsedArray['comments']['artist'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||
$commentoffset += $commentlength;
|
||||
|
||||
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||
$ParsedArray['comments']['copyright'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||
$commentoffset += $commentlength;
|
||||
|
||||
$commentoffset++; // final null terminator (?)
|
||||
$commentoffset++; // fourcc length (?) should be 4
|
||||
$ParsedArray['fourcc'] = substr($OldRAheaderData, 23 + $commentoffset, 4);
|
||||
|
||||
} elseif ($ParsedArray['version1'] <= 5) {
|
||||
|
||||
//$ParsedArray['unknown1'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 6, 2));
|
||||
$ParsedArray['fourcc1'] = substr($OldRAheaderData, 8, 4);
|
||||
$ParsedArray['file_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 12, 4));
|
||||
$ParsedArray['version2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 16, 2));
|
||||
$ParsedArray['header_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 18, 4));
|
||||
$ParsedArray['codec_flavor_id'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 22, 2));
|
||||
$ParsedArray['coded_frame_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 24, 4));
|
||||
$ParsedArray['audio_bytes'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 28, 4));
|
||||
$ParsedArray['bytes_per_minute'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 32, 4));
|
||||
//$ParsedArray['unknown5'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 36, 4));
|
||||
$ParsedArray['sub_packet_h'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 40, 2));
|
||||
$ParsedArray['frame_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 42, 2));
|
||||
$ParsedArray['sub_packet_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 44, 2));
|
||||
//$ParsedArray['unknown6'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 46, 2));
|
||||
|
||||
switch ($ParsedArray['version1']) {
|
||||
|
||||
case 4:
|
||||
$ParsedArray['sample_rate'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 48, 2));
|
||||
//$ParsedArray['unknown8'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 50, 2));
|
||||
$ParsedArray['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 52, 2));
|
||||
$ParsedArray['channels'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 54, 2));
|
||||
$ParsedArray['length_fourcc2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 56, 1));
|
||||
$ParsedArray['fourcc2'] = substr($OldRAheaderData, 57, 4);
|
||||
$ParsedArray['length_fourcc3'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 61, 1));
|
||||
$ParsedArray['fourcc3'] = substr($OldRAheaderData, 62, 4);
|
||||
//$ParsedArray['unknown9'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 66, 1));
|
||||
//$ParsedArray['unknown10'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 67, 2));
|
||||
$ParsedArray['comments_raw'] = substr($OldRAheaderData, 69, $ParsedArray['header_size'] - 69 + 16);
|
||||
|
||||
$commentoffset = 0;
|
||||
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||
$ParsedArray['comments']['title'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||
$commentoffset += $commentlength;
|
||||
|
||||
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||
$ParsedArray['comments']['artist'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||
$commentoffset += $commentlength;
|
||||
|
||||
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||
$ParsedArray['comments']['copyright'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||
$commentoffset += $commentlength;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
$ParsedArray['sample_rate'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 48, 4));
|
||||
$ParsedArray['sample_rate2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 52, 4));
|
||||
$ParsedArray['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 56, 4));
|
||||
$ParsedArray['channels'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 60, 2));
|
||||
$ParsedArray['genr'] = substr($OldRAheaderData, 62, 4);
|
||||
$ParsedArray['fourcc3'] = substr($OldRAheaderData, 66, 4);
|
||||
$ParsedArray['comments'] = array();
|
||||
break;
|
||||
}
|
||||
$ParsedArray['fourcc'] = $ParsedArray['fourcc3'];
|
||||
|
||||
}
|
||||
foreach ($ParsedArray['comments'] as $key => $value) {
|
||||
if ($ParsedArray['comments'][$key][0] === false) {
|
||||
$ParsedArray['comments'][$key][0] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function RealAudioCodecFourCClookup($fourcc, $bitrate) {
|
||||
static $RealAudioCodecFourCClookup = array();
|
||||
if (empty($RealAudioCodecFourCClookup)) {
|
||||
// http://www.its.msstate.edu/net/real/reports/config/tags.stats
|
||||
// http://www.freelists.org/archives/matroska-devel/06-2003/fullthread18.html
|
||||
|
||||
$RealAudioCodecFourCClookup['14_4'][8000] = 'RealAudio v2 (14.4kbps)';
|
||||
$RealAudioCodecFourCClookup['14.4'][8000] = 'RealAudio v2 (14.4kbps)';
|
||||
$RealAudioCodecFourCClookup['lpcJ'][8000] = 'RealAudio v2 (14.4kbps)';
|
||||
$RealAudioCodecFourCClookup['28_8'][15200] = 'RealAudio v2 (28.8kbps)';
|
||||
$RealAudioCodecFourCClookup['28.8'][15200] = 'RealAudio v2 (28.8kbps)';
|
||||
$RealAudioCodecFourCClookup['sipr'][4933] = 'RealAudio v4 (5kbps Voice)';
|
||||
$RealAudioCodecFourCClookup['sipr'][6444] = 'RealAudio v4 (6.5kbps Voice)';
|
||||
$RealAudioCodecFourCClookup['sipr'][8444] = 'RealAudio v4 (8.5kbps Voice)';
|
||||
$RealAudioCodecFourCClookup['sipr'][16000] = 'RealAudio v4 (16kbps Wideband)';
|
||||
$RealAudioCodecFourCClookup['dnet'][8000] = 'RealAudio v3 (8kbps Music)';
|
||||
$RealAudioCodecFourCClookup['dnet'][16000] = 'RealAudio v3 (16kbps Music Low Response)';
|
||||
$RealAudioCodecFourCClookup['dnet'][15963] = 'RealAudio v3 (16kbps Music Mid/High Response)';
|
||||
$RealAudioCodecFourCClookup['dnet'][20000] = 'RealAudio v3 (20kbps Music Stereo)';
|
||||
$RealAudioCodecFourCClookup['dnet'][32000] = 'RealAudio v3 (32kbps Music Mono)';
|
||||
$RealAudioCodecFourCClookup['dnet'][31951] = 'RealAudio v3 (32kbps Music Stereo)';
|
||||
$RealAudioCodecFourCClookup['dnet'][39965] = 'RealAudio v3 (40kbps Music Mono)';
|
||||
$RealAudioCodecFourCClookup['dnet'][40000] = 'RealAudio v3 (40kbps Music Stereo)';
|
||||
$RealAudioCodecFourCClookup['dnet'][79947] = 'RealAudio v3 (80kbps Music Mono)';
|
||||
$RealAudioCodecFourCClookup['dnet'][80000] = 'RealAudio v3 (80kbps Music Stereo)';
|
||||
|
||||
$RealAudioCodecFourCClookup['dnet'][0] = 'RealAudio v3';
|
||||
$RealAudioCodecFourCClookup['sipr'][0] = 'RealAudio v4';
|
||||
$RealAudioCodecFourCClookup['cook'][0] = 'RealAudio G2';
|
||||
$RealAudioCodecFourCClookup['atrc'][0] = 'RealAudio 8';
|
||||
}
|
||||
$roundbitrate = intval(round($bitrate));
|
||||
if (isset($RealAudioCodecFourCClookup[$fourcc][$roundbitrate])) {
|
||||
return $RealAudioCodecFourCClookup[$fourcc][$roundbitrate];
|
||||
} elseif (isset($RealAudioCodecFourCClookup[$fourcc][0])) {
|
||||
return $RealAudioCodecFourCClookup[$fourcc][0];
|
||||
}
|
||||
return $fourcc;
|
||||
}
|
||||
|
||||
}
|
2417
api/getID3/module.audio-video.riff.php
Executable file
139
api/getID3/module.audio-video.swf.php
Executable file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio-video.swf.php //
|
||||
// module for analyzing Shockwave Flash files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_swf extends getid3_handler
|
||||
{
|
||||
public $ReturnAllTagData = false;
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'swf';
|
||||
$info['video']['dataformat'] = 'swf';
|
||||
|
||||
// http://www.openswf.org/spec/SWFfileformat.html
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
|
||||
$SWFfileData = fread($this->getid3->fp, $info['avdataend'] - $info['avdataoffset']); // 8 + 2 + 2 + max(9) bytes NOT including Frame_Size RECT data
|
||||
|
||||
$info['swf']['header']['signature'] = substr($SWFfileData, 0, 3);
|
||||
switch ($info['swf']['header']['signature']) {
|
||||
case 'FWS':
|
||||
$info['swf']['header']['compressed'] = false;
|
||||
break;
|
||||
|
||||
case 'CWS':
|
||||
$info['swf']['header']['compressed'] = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Expecting "FWS" or "CWS" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['swf']['header']['signature']).'"';
|
||||
unset($info['swf']);
|
||||
unset($info['fileformat']);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
$info['swf']['header']['version'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 3, 1));
|
||||
$info['swf']['header']['length'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 4, 4));
|
||||
|
||||
if ($info['swf']['header']['compressed']) {
|
||||
$SWFHead = substr($SWFfileData, 0, 8);
|
||||
$SWFfileData = substr($SWFfileData, 8);
|
||||
if ($decompressed = @gzuncompress($SWFfileData)) {
|
||||
$SWFfileData = $SWFHead.$decompressed;
|
||||
} else {
|
||||
$info['error'][] = 'Error decompressing compressed SWF data ('.strlen($SWFfileData).' bytes compressed, should be '.($info['swf']['header']['length'] - 8).' bytes uncompressed)';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$FrameSizeBitsPerValue = (ord(substr($SWFfileData, 8, 1)) & 0xF8) >> 3;
|
||||
$FrameSizeDataLength = ceil((5 + (4 * $FrameSizeBitsPerValue)) / 8);
|
||||
$FrameSizeDataString = str_pad(decbin(ord(substr($SWFfileData, 8, 1)) & 0x07), 3, '0', STR_PAD_LEFT);
|
||||
for ($i = 1; $i < $FrameSizeDataLength; $i++) {
|
||||
$FrameSizeDataString .= str_pad(decbin(ord(substr($SWFfileData, 8 + $i, 1))), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
list($X1, $X2, $Y1, $Y2) = explode("\n", wordwrap($FrameSizeDataString, $FrameSizeBitsPerValue, "\n", 1));
|
||||
$info['swf']['header']['frame_width'] = getid3_lib::Bin2Dec($X2);
|
||||
$info['swf']['header']['frame_height'] = getid3_lib::Bin2Dec($Y2);
|
||||
|
||||
// http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm
|
||||
// Next in the header is the frame rate, which is kind of weird.
|
||||
// It is supposed to be stored as a 16bit integer, but the first byte
|
||||
// (or last depending on how you look at it) is completely ignored.
|
||||
// Example: 0x000C -> 0x0C -> 12 So the frame rate is 12 fps.
|
||||
|
||||
// Byte at (8 + $FrameSizeDataLength) is always zero and ignored
|
||||
$info['swf']['header']['frame_rate'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 9 + $FrameSizeDataLength, 1));
|
||||
$info['swf']['header']['frame_count'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 10 + $FrameSizeDataLength, 2));
|
||||
|
||||
$info['video']['frame_rate'] = $info['swf']['header']['frame_rate'];
|
||||
$info['video']['resolution_x'] = intval(round($info['swf']['header']['frame_width'] / 20));
|
||||
$info['video']['resolution_y'] = intval(round($info['swf']['header']['frame_height'] / 20));
|
||||
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||
|
||||
if (($info['swf']['header']['frame_count'] > 0) && ($info['swf']['header']['frame_rate'] > 0)) {
|
||||
$info['playtime_seconds'] = $info['swf']['header']['frame_count'] / $info['swf']['header']['frame_rate'];
|
||||
}
|
||||
//echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'<br>';
|
||||
|
||||
|
||||
// SWF tags
|
||||
|
||||
$CurrentOffset = 12 + $FrameSizeDataLength;
|
||||
$SWFdataLength = strlen($SWFfileData);
|
||||
|
||||
while ($CurrentOffset < $SWFdataLength) {
|
||||
//echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'<br>';
|
||||
|
||||
$TagIDTagLength = getid3_lib::LittleEndian2Int(substr($SWFfileData, $CurrentOffset, 2));
|
||||
$TagID = ($TagIDTagLength & 0xFFFC) >> 6;
|
||||
$TagLength = ($TagIDTagLength & 0x003F);
|
||||
$CurrentOffset += 2;
|
||||
if ($TagLength == 0x3F) {
|
||||
$TagLength = getid3_lib::LittleEndian2Int(substr($SWFfileData, $CurrentOffset, 4));
|
||||
$CurrentOffset += 4;
|
||||
}
|
||||
|
||||
unset($TagData);
|
||||
$TagData['offset'] = $CurrentOffset;
|
||||
$TagData['size'] = $TagLength;
|
||||
$TagData['id'] = $TagID;
|
||||
$TagData['data'] = substr($SWFfileData, $CurrentOffset, $TagLength);
|
||||
switch ($TagID) {
|
||||
case 0: // end of movie
|
||||
break 2;
|
||||
|
||||
case 9: // Set background color
|
||||
//$info['swf']['tags'][] = $TagData;
|
||||
$info['swf']['bgcolor'] = strtoupper(str_pad(dechex(getid3_lib::BigEndian2Int($TagData['data'])), 6, '0', STR_PAD_LEFT));
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($this->ReturnAllTagData) {
|
||||
$info['swf']['tags'][] = $TagData;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$CurrentOffset += $TagLength;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
78
api/getID3/module.audio-video.ts.php
Executable file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio-video.ts.php //
|
||||
// module for analyzing MPEG Transport Stream (.ts) files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_ts extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$TSheader = fread($this->getid3->fp, 19);
|
||||
$magic = "\x47";
|
||||
if (substr($TSheader, 0, 1) != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at '.$info['avdataoffset'].', found '.getid3_lib::PrintHexBytes(substr($TSheader, 0, 1)).' instead.';
|
||||
return false;
|
||||
}
|
||||
$info['fileformat'] = 'ts';
|
||||
|
||||
// http://en.wikipedia.org/wiki/.ts
|
||||
|
||||
$offset = 0;
|
||||
$info['ts']['packet']['sync'] = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 1)); $offset += 1;
|
||||
$pid_flags_raw = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 2)); $offset += 2;
|
||||
$SAC_raw = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 1)); $offset += 1;
|
||||
$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']['packet_id'] = ($pid_flags_raw & 0x1FFF) >> 0;
|
||||
|
||||
$info['ts']['packet']['raw']['scrambling_control'] = ($SAC_raw & 0xC0) >> 6;
|
||||
$info['ts']['packet']['flags']['adaption_field_exists'] = (bool) ($SAC_raw & 0x20);
|
||||
$info['ts']['packet']['flags']['payload_exists'] = (bool) ($SAC_raw & 0x10);
|
||||
$info['ts']['packet']['continuity_counter'] = ($SAC_raw & 0x0F) >> 0; // Incremented only when a payload is present
|
||||
$info['ts']['packet']['scrambling_control'] = $this->TSscramblingControlLookup($info['ts']['packet']['raw']['scrambling_control']);
|
||||
|
||||
if ($info['ts']['packet']['flags']['adaption_field_exists']) {
|
||||
$AdaptionField_raw = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 2)); $offset += 2;
|
||||
$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']['high_priority'] = (bool) ($AdaptionField_raw & 0x0020); // 1 = higher priority
|
||||
$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']) {
|
||||
$info['ts']['packet']['adaption']['raw']['pcr'] = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 6)); $offset += 6;
|
||||
}
|
||||
if ($info['ts']['packet']['adaption']['flags']['opcr']) {
|
||||
$info['ts']['packet']['adaption']['raw']['opcr'] = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 6)); $offset += 6;
|
||||
}
|
||||
}
|
||||
|
||||
$info['error'][] = 'MPEG Transport Stream (.ts) parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function TSscramblingControlLookup($raw) {
|
||||
$TSscramblingControlLookup = array(0x00=>'not scrambled', 0x01=>'reserved', 0x02=>'scrambled, even key', 0x03=>'scrambled, odd key');
|
||||
return (isset($TSscramblingControlLookup[$raw]) ? $TSscramblingControlLookup[$raw] : 'invalid');
|
||||
}
|
||||
}
|
58
api/getID3/module.audio.aa.php
Executable file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.aa.php //
|
||||
// module for analyzing Audible Audiobook files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_aa extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$AAheader = fread($this->getid3->fp, 8);
|
||||
|
||||
$magic = "\x57\x90\x75\x36";
|
||||
if (substr($AAheader, 4, 4) != $magic) {
|
||||
$info['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_au = &$info['aa'];
|
||||
|
||||
$info['fileformat'] = 'aa';
|
||||
$info['audio']['dataformat'] = 'aa';
|
||||
$info['error'][] = 'Audible Audiobook (.aa) parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
$info['audio']['bitrate_mode'] = 'cbr'; // is it?
|
||||
$thisfile_au['encoding'] = 'ISO-8859-1';
|
||||
|
||||
$thisfile_au['filesize'] = getid3_lib::BigEndian2Int(substr($AUheader, 0, 4));
|
||||
if ($thisfile_au['filesize'] > ($info['avdataend'] - $info['avdataoffset'])) {
|
||||
$info['warning'][] = 'Possible truncated file - expecting "'.$thisfile_au['filesize'].'" bytes of data, only found '.($info['avdataend'] - $info['avdataoffset']).' bytes"';
|
||||
}
|
||||
|
||||
$info['audio']['bits_per_sample'] = 16; // is it?
|
||||
$info['audio']['sample_rate'] = $thisfile_au['sample_rate'];
|
||||
$info['audio']['channels'] = $thisfile_au['channels'];
|
||||
|
||||
//$info['playtime_seconds'] = 0;
|
||||
//$info['audio']['bitrate'] = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
512
api/getID3/module.audio.aac.php
Executable file
@ -0,0 +1,512 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.aac.php //
|
||||
// module for analyzing AAC Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_aac extends getid3_handler
|
||||
{
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
if (fread($this->getid3->fp, 4) == 'ADIF') {
|
||||
$this->getAACADIFheaderFilepointer();
|
||||
} else {
|
||||
$this->getAACADTSheaderFilepointer();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getAACADIFheaderFilepointer() {
|
||||
$info = &$this->getid3->info;
|
||||
$info['fileformat'] = 'aac';
|
||||
$info['audio']['dataformat'] = 'aac';
|
||||
$info['audio']['lossless'] = false;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$AACheader = fread($this->getid3->fp, 1024);
|
||||
$offset = 0;
|
||||
|
||||
if (substr($AACheader, 0, 4) == 'ADIF') {
|
||||
|
||||
// http://faac.sourceforge.net/wiki/index.php?page=ADIF
|
||||
|
||||
// http://libmpeg.org/mpeg4/doc/w2203tfs.pdf
|
||||
// adif_header() {
|
||||
// adif_id 32
|
||||
// copyright_id_present 1
|
||||
// if( copyright_id_present )
|
||||
// copyright_id 72
|
||||
// original_copy 1
|
||||
// home 1
|
||||
// bitstream_type 1
|
||||
// bitrate 23
|
||||
// num_program_config_elements 4
|
||||
// for (i = 0; i < num_program_config_elements + 1; i++ ) {
|
||||
// if( bitstream_type == '0' )
|
||||
// adif_buffer_fullness 20
|
||||
// program_config_element()
|
||||
// }
|
||||
// }
|
||||
|
||||
$AACheaderBitstream = getid3_lib::BigEndian2Bin($AACheader);
|
||||
$bitoffset = 0;
|
||||
|
||||
$info['aac']['header_type'] = 'ADIF';
|
||||
$bitoffset += 32;
|
||||
$info['aac']['header']['mpeg_version'] = 4;
|
||||
|
||||
$info['aac']['header']['copyright'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
|
||||
$bitoffset += 1;
|
||||
if ($info['aac']['header']['copyright']) {
|
||||
$info['aac']['header']['copyright_id'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 72));
|
||||
$bitoffset += 72;
|
||||
}
|
||||
$info['aac']['header']['original_copy'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
|
||||
$bitoffset += 1;
|
||||
$info['aac']['header']['home'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
|
||||
$bitoffset += 1;
|
||||
$info['aac']['header']['is_vbr'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
|
||||
$bitoffset += 1;
|
||||
if ($info['aac']['header']['is_vbr']) {
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
$info['aac']['header']['bitrate_max'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23));
|
||||
$bitoffset += 23;
|
||||
} else {
|
||||
$info['audio']['bitrate_mode'] = 'cbr';
|
||||
$info['aac']['header']['bitrate'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23));
|
||||
$bitoffset += 23;
|
||||
$info['audio']['bitrate'] = $info['aac']['header']['bitrate'];
|
||||
}
|
||||
if ($info['audio']['bitrate'] == 0) {
|
||||
$info['error'][] = 'Corrupt AAC file: bitrate_audio == zero';
|
||||
return false;
|
||||
}
|
||||
$info['aac']['header']['num_program_configs'] = 1 + getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
|
||||
for ($i = 0; $i < $info['aac']['header']['num_program_configs']; $i++) {
|
||||
// http://www.audiocoding.com/wiki/index.php?page=program_config_element
|
||||
|
||||
// buffer_fullness 20
|
||||
|
||||
// element_instance_tag 4
|
||||
// object_type 2
|
||||
// sampling_frequency_index 4
|
||||
// num_front_channel_elements 4
|
||||
// num_side_channel_elements 4
|
||||
// num_back_channel_elements 4
|
||||
// num_lfe_channel_elements 2
|
||||
// num_assoc_data_elements 3
|
||||
// num_valid_cc_elements 4
|
||||
// mono_mixdown_present 1
|
||||
// mono_mixdown_element_number 4 if mono_mixdown_present == 1
|
||||
// stereo_mixdown_present 1
|
||||
// stereo_mixdown_element_number 4 if stereo_mixdown_present == 1
|
||||
// matrix_mixdown_idx_present 1
|
||||
// matrix_mixdown_idx 2 if matrix_mixdown_idx_present == 1
|
||||
// pseudo_surround_enable 1 if matrix_mixdown_idx_present == 1
|
||||
// for (i = 0; i < num_front_channel_elements; i++) {
|
||||
// front_element_is_cpe[i] 1
|
||||
// front_element_tag_select[i] 4
|
||||
// }
|
||||
// for (i = 0; i < num_side_channel_elements; i++) {
|
||||
// side_element_is_cpe[i] 1
|
||||
// side_element_tag_select[i] 4
|
||||
// }
|
||||
// for (i = 0; i < num_back_channel_elements; i++) {
|
||||
// back_element_is_cpe[i] 1
|
||||
// back_element_tag_select[i] 4
|
||||
// }
|
||||
// for (i = 0; i < num_lfe_channel_elements; i++) {
|
||||
// lfe_element_tag_select[i] 4
|
||||
// }
|
||||
// for (i = 0; i < num_assoc_data_elements; i++) {
|
||||
// assoc_data_element_tag_select[i] 4
|
||||
// }
|
||||
// for (i = 0; i < num_valid_cc_elements; i++) {
|
||||
// cc_element_is_ind_sw[i] 1
|
||||
// valid_cc_element_tag_select[i] 4
|
||||
// }
|
||||
// byte_alignment() VAR
|
||||
// comment_field_bytes 8
|
||||
// for (i = 0; i < comment_field_bytes; i++) {
|
||||
// comment_field_data[i] 8
|
||||
// }
|
||||
|
||||
if (!$info['aac']['header']['is_vbr']) {
|
||||
$info['aac']['program_configs'][$i]['buffer_fullness'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 20));
|
||||
$bitoffset += 20;
|
||||
}
|
||||
$info['aac']['program_configs'][$i]['element_instance_tag'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
$info['aac']['program_configs'][$i]['object_type'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
|
||||
$bitoffset += 2;
|
||||
$info['aac']['program_configs'][$i]['sampling_frequency_index'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
$info['aac']['program_configs'][$i]['num_front_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
$info['aac']['program_configs'][$i]['num_side_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
$info['aac']['program_configs'][$i]['num_back_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
$info['aac']['program_configs'][$i]['num_lfe_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
|
||||
$bitoffset += 2;
|
||||
$info['aac']['program_configs'][$i]['num_assoc_data_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 3));
|
||||
$bitoffset += 3;
|
||||
$info['aac']['program_configs'][$i]['num_valid_cc_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
$info['aac']['program_configs'][$i]['mono_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||
$bitoffset += 1;
|
||||
if ($info['aac']['program_configs'][$i]['mono_mixdown_present']) {
|
||||
$info['aac']['program_configs'][$i]['mono_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
}
|
||||
$info['aac']['program_configs'][$i]['stereo_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||
$bitoffset += 1;
|
||||
if ($info['aac']['program_configs'][$i]['stereo_mixdown_present']) {
|
||||
$info['aac']['program_configs'][$i]['stereo_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
}
|
||||
$info['aac']['program_configs'][$i]['matrix_mixdown_idx_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||
$bitoffset += 1;
|
||||
if ($info['aac']['program_configs'][$i]['matrix_mixdown_idx_present']) {
|
||||
$info['aac']['program_configs'][$i]['matrix_mixdown_idx'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
|
||||
$bitoffset += 2;
|
||||
$info['aac']['program_configs'][$i]['pseudo_surround_enable'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||
$bitoffset += 1;
|
||||
}
|
||||
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_front_channel_elements']; $j++) {
|
||||
$info['aac']['program_configs'][$i]['front_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||
$bitoffset += 1;
|
||||
$info['aac']['program_configs'][$i]['front_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
}
|
||||
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_side_channel_elements']; $j++) {
|
||||
$info['aac']['program_configs'][$i]['side_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||
$bitoffset += 1;
|
||||
$info['aac']['program_configs'][$i]['side_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
}
|
||||
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_back_channel_elements']; $j++) {
|
||||
$info['aac']['program_configs'][$i]['back_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||
$bitoffset += 1;
|
||||
$info['aac']['program_configs'][$i]['back_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
}
|
||||
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_lfe_channel_elements']; $j++) {
|
||||
$info['aac']['program_configs'][$i]['lfe_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
}
|
||||
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_assoc_data_elements']; $j++) {
|
||||
$info['aac']['program_configs'][$i]['assoc_data_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
}
|
||||
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_valid_cc_elements']; $j++) {
|
||||
$info['aac']['program_configs'][$i]['cc_element_is_ind_sw'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||
$bitoffset += 1;
|
||||
$info['aac']['program_configs'][$i]['valid_cc_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||
$bitoffset += 4;
|
||||
}
|
||||
|
||||
$bitoffset = ceil($bitoffset / 8) * 8;
|
||||
|
||||
$info['aac']['program_configs'][$i]['comment_field_bytes'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 8));
|
||||
$bitoffset += 8;
|
||||
$info['aac']['program_configs'][$i]['comment_field'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 8 * $info['aac']['program_configs'][$i]['comment_field_bytes']));
|
||||
$bitoffset += 8 * $info['aac']['program_configs'][$i]['comment_field_bytes'];
|
||||
|
||||
|
||||
$info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['program_configs'][$i]['object_type'], $info['aac']['header']['mpeg_version']);
|
||||
$info['aac']['program_configs'][$i]['sampling_frequency'] = self::AACsampleRateLookup($info['aac']['program_configs'][$i]['sampling_frequency_index']);
|
||||
$info['audio']['sample_rate'] = $info['aac']['program_configs'][$i]['sampling_frequency'];
|
||||
$info['audio']['channels'] = self::AACchannelCountCalculate($info['aac']['program_configs'][$i]);
|
||||
if ($info['aac']['program_configs'][$i]['comment_field']) {
|
||||
$info['aac']['comments'][] = $info['aac']['program_configs'][$i]['comment_field'];
|
||||
}
|
||||
}
|
||||
$info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate'];
|
||||
|
||||
$info['audio']['encoder_options'] = $info['aac']['header_type'].' '.$info['aac']['header']['profile'];
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
unset($info['fileformat']);
|
||||
unset($info['aac']);
|
||||
$info['error'][] = 'AAC-ADIF synch not found at offset '.$info['avdataoffset'].' (expected "ADIF", found "'.substr($AACheader, 0, 4).'" instead)';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function getAACADTSheaderFilepointer($MaxFramesToScan=1000000, $ReturnExtendedInfo=false) {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// based loosely on code from AACfile by Jurgen Faul <jfaulØgmx.de>
|
||||
// http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html
|
||||
|
||||
|
||||
// http://faac.sourceforge.net/wiki/index.php?page=ADTS // dead link
|
||||
// http://wiki.multimedia.cx/index.php?title=ADTS
|
||||
|
||||
// * ADTS Fixed Header: these don't change from frame to frame
|
||||
// syncword 12 always: '111111111111'
|
||||
// ID 1 0: MPEG-4, 1: MPEG-2
|
||||
// MPEG layer 2 If you send AAC in MPEG-TS, set to 0
|
||||
// protection_absent 1 0: CRC present; 1: no CRC
|
||||
// profile 2 0: AAC Main; 1: AAC LC (Low Complexity); 2: AAC SSR (Scalable Sample Rate); 3: AAC LTP (Long Term Prediction)
|
||||
// sampling_frequency_index 4 15 not allowed
|
||||
// private_bit 1 usually 0
|
||||
// channel_configuration 3
|
||||
// original/copy 1 0: original; 1: copy
|
||||
// home 1 usually 0
|
||||
// emphasis 2 only if ID == 0 (ie MPEG-4) // not present in some documentation?
|
||||
|
||||
// * ADTS Variable Header: these can change from frame to frame
|
||||
// copyright_identification_bit 1
|
||||
// copyright_identification_start 1
|
||||
// aac_frame_length 13 length of the frame including header (in bytes)
|
||||
// adts_buffer_fullness 11 0x7FF indicates VBR
|
||||
// no_raw_data_blocks_in_frame 2
|
||||
|
||||
// * ADTS Error check
|
||||
// crc_check 16 only if protection_absent == 0
|
||||
|
||||
$byteoffset = $info['avdataoffset'];
|
||||
$framenumber = 0;
|
||||
|
||||
// Init bit pattern array
|
||||
static $decbin = array();
|
||||
|
||||
// Populate $bindec
|
||||
for ($i = 0; $i < 256; $i++) {
|
||||
$decbin[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
// used to calculate bitrate below
|
||||
$BitrateCache = array();
|
||||
|
||||
|
||||
while (true) {
|
||||
// breaks out when end-of-file encountered, or invalid data found,
|
||||
// or MaxFramesToScan frames have been scanned
|
||||
|
||||
if (!getid3_lib::intValueSupported($byteoffset)) {
|
||||
$info['warning'][] = 'Unable to parse AAC file beyond '.ftell($this->getid3->fp).' (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
|
||||
return false;
|
||||
}
|
||||
fseek($this->getid3->fp, $byteoffset, SEEK_SET);
|
||||
|
||||
// First get substring
|
||||
$substring = fread($this->getid3->fp, 9); // header is 7 bytes (or 9 if CRC is present)
|
||||
$substringlength = strlen($substring);
|
||||
if ($substringlength != 9) {
|
||||
$info['error'][] = 'Failed to read 7 bytes at offset '.(ftell($this->getid3->fp) - $substringlength).' (only read '.$substringlength.' bytes)';
|
||||
return false;
|
||||
}
|
||||
// this would be easier with 64-bit math, but split it up to allow for 32-bit:
|
||||
$header1 = getid3_lib::BigEndian2Int(substr($substring, 0, 2));
|
||||
$header2 = getid3_lib::BigEndian2Int(substr($substring, 2, 4));
|
||||
$header3 = getid3_lib::BigEndian2Int(substr($substring, 6, 1));
|
||||
|
||||
$info['aac']['header']['raw']['syncword'] = ($header1 & 0xFFF0) >> 4;
|
||||
if ($info['aac']['header']['raw']['syncword'] != 0x0FFF) {
|
||||
$info['error'][] = 'Synch pattern (0x0FFF) not found at offset '.(ftell($this->getid3->fp) - $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!
|
||||
if ($framenumber == 0) {
|
||||
$info['aac']['header_type'] = 'ADTS';
|
||||
$info['fileformat'] = 'aac';
|
||||
$info['audio']['dataformat'] = 'aac';
|
||||
|
||||
$info['aac']['header']['raw']['mpeg_version'] = ($header1 & 0x0008) >> 3;
|
||||
$info['aac']['header']['raw']['mpeg_layer'] = ($header1 & 0x0006) >> 1;
|
||||
$info['aac']['header']['raw']['protection_absent'] = ($header1 & 0x0001) >> 0;
|
||||
|
||||
$info['aac']['header']['raw']['profile_code'] = ($header2 & 0xC0000000) >> 30;
|
||||
$info['aac']['header']['raw']['sample_rate_code'] = ($header2 & 0x3C000000) >> 26;
|
||||
$info['aac']['header']['raw']['private_stream'] = ($header2 & 0x02000000) >> 25;
|
||||
$info['aac']['header']['raw']['channels_code'] = ($header2 & 0x01C00000) >> 22;
|
||||
$info['aac']['header']['raw']['original'] = ($header2 & 0x00200000) >> 21;
|
||||
$info['aac']['header']['raw']['home'] = ($header2 & 0x00100000) >> 20;
|
||||
$info['aac']['header']['raw']['copyright_stream'] = ($header2 & 0x00080000) >> 19;
|
||||
$info['aac']['header']['raw']['copyright_start'] = ($header2 & 0x00040000) >> 18;
|
||||
$info['aac']['header']['raw']['frame_length'] = ($header2 & 0x0003FFE0) >> 5;
|
||||
|
||||
$info['aac']['header']['mpeg_version'] = ($info['aac']['header']['raw']['mpeg_version'] ? 2 : 4);
|
||||
$info['aac']['header']['crc_present'] = ($info['aac']['header']['raw']['protection_absent'] ? false: true);
|
||||
$info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['header']['raw']['profile_code'], $info['aac']['header']['mpeg_version']);
|
||||
$info['aac']['header']['sample_frequency'] = self::AACsampleRateLookup($info['aac']['header']['raw']['sample_rate_code']);
|
||||
$info['aac']['header']['private'] = (bool) $info['aac']['header']['raw']['private_stream'];
|
||||
$info['aac']['header']['original'] = (bool) $info['aac']['header']['raw']['original'];
|
||||
$info['aac']['header']['home'] = (bool) $info['aac']['header']['raw']['home'];
|
||||
$info['aac']['header']['channels'] = (($info['aac']['header']['raw']['channels_code'] == 7) ? 8 : $info['aac']['header']['raw']['channels_code']);
|
||||
if ($ReturnExtendedInfo) {
|
||||
$info['aac'][$framenumber]['copyright_id_bit'] = (bool) $info['aac']['header']['raw']['copyright_stream'];
|
||||
$info['aac'][$framenumber]['copyright_id_start'] = (bool) $info['aac']['header']['raw']['copyright_start'];
|
||||
}
|
||||
|
||||
if ($info['aac']['header']['raw']['mpeg_layer'] != 0) {
|
||||
$info['warning'][] = 'Layer error - expected "0", found "'.$info['aac']['header']['raw']['mpeg_layer'].'" instead';
|
||||
}
|
||||
if ($info['aac']['header']['sample_frequency'] == 0) {
|
||||
$info['error'][] = 'Corrupt AAC file: sample_frequency == zero';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['audio']['sample_rate'] = $info['aac']['header']['sample_frequency'];
|
||||
$info['audio']['channels'] = $info['aac']['header']['channels'];
|
||||
}
|
||||
|
||||
$FrameLength = ($header2 & 0x0003FFE0) >> 5;
|
||||
|
||||
if (!isset($BitrateCache[$FrameLength])) {
|
||||
$BitrateCache[$FrameLength] = ($info['aac']['header']['sample_frequency'] / 1024) * $FrameLength * 8;
|
||||
}
|
||||
getid3_lib::safe_inc($info['aac']['bitrate_distribution'][$BitrateCache[$FrameLength]], 1);
|
||||
|
||||
$info['aac'][$framenumber]['aac_frame_length'] = $FrameLength;
|
||||
|
||||
$info['aac'][$framenumber]['adts_buffer_fullness'] = (($header2 & 0x0000001F) << 6) & (($header3 & 0xFC) >> 2);
|
||||
if ($info['aac'][$framenumber]['adts_buffer_fullness'] == 0x07FF) {
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
} else {
|
||||
$info['audio']['bitrate_mode'] = 'cbr';
|
||||
}
|
||||
$info['aac'][$framenumber]['num_raw_data_blocks'] = (($header3 & 0x03) >> 0);
|
||||
|
||||
if ($info['aac']['header']['crc_present']) {
|
||||
//$info['aac'][$framenumber]['crc'] = getid3_lib::BigEndian2Int(substr($substring, 7, 2);
|
||||
}
|
||||
|
||||
if (!$ReturnExtendedInfo) {
|
||||
unset($info['aac'][$framenumber]);
|
||||
}
|
||||
|
||||
/*
|
||||
$rounded_precision = 5000;
|
||||
$info['aac']['bitrate_distribution_rounded'] = array();
|
||||
foreach ($info['aac']['bitrate_distribution'] as $bitrate => $count) {
|
||||
$rounded_bitrate = round($bitrate / $rounded_precision) * $rounded_precision;
|
||||
getid3_lib::safe_inc($info['aac']['bitrate_distribution_rounded'][$rounded_bitrate], $count);
|
||||
}
|
||||
ksort($info['aac']['bitrate_distribution_rounded']);
|
||||
*/
|
||||
|
||||
$byteoffset += $FrameLength;
|
||||
if ((++$framenumber < $MaxFramesToScan) && (($byteoffset + 10) < $info['avdataend'])) {
|
||||
|
||||
// keep scanning
|
||||
|
||||
} else {
|
||||
|
||||
$info['aac']['frames'] = $framenumber;
|
||||
$info['playtime_seconds'] = ($info['avdataend'] / $byteoffset) * (($framenumber * 1024) / $info['aac']['header']['sample_frequency']); // (1 / % of file scanned) * (samples / (samples/sec)) = seconds
|
||||
if ($info['playtime_seconds'] == 0) {
|
||||
$info['error'][] = 'Corrupt AAC file: playtime_seconds == zero';
|
||||
return false;
|
||||
}
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
ksort($info['aac']['bitrate_distribution']);
|
||||
|
||||
$info['audio']['encoder_options'] = $info['aac']['header_type'].' '.$info['aac']['header']['profile'];
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
// should never get here.
|
||||
}
|
||||
|
||||
public static function AACsampleRateLookup($samplerateid) {
|
||||
static $AACsampleRateLookup = array();
|
||||
if (empty($AACsampleRateLookup)) {
|
||||
$AACsampleRateLookup[0] = 96000;
|
||||
$AACsampleRateLookup[1] = 88200;
|
||||
$AACsampleRateLookup[2] = 64000;
|
||||
$AACsampleRateLookup[3] = 48000;
|
||||
$AACsampleRateLookup[4] = 44100;
|
||||
$AACsampleRateLookup[5] = 32000;
|
||||
$AACsampleRateLookup[6] = 24000;
|
||||
$AACsampleRateLookup[7] = 22050;
|
||||
$AACsampleRateLookup[8] = 16000;
|
||||
$AACsampleRateLookup[9] = 12000;
|
||||
$AACsampleRateLookup[10] = 11025;
|
||||
$AACsampleRateLookup[11] = 8000;
|
||||
$AACsampleRateLookup[12] = 0;
|
||||
$AACsampleRateLookup[13] = 0;
|
||||
$AACsampleRateLookup[14] = 0;
|
||||
$AACsampleRateLookup[15] = 0;
|
||||
}
|
||||
return (isset($AACsampleRateLookup[$samplerateid]) ? $AACsampleRateLookup[$samplerateid] : 'invalid');
|
||||
}
|
||||
|
||||
public static function AACprofileLookup($profileid, $mpegversion) {
|
||||
static $AACprofileLookup = array();
|
||||
if (empty($AACprofileLookup)) {
|
||||
$AACprofileLookup[2][0] = 'Main profile';
|
||||
$AACprofileLookup[2][1] = 'Low Complexity profile (LC)';
|
||||
$AACprofileLookup[2][2] = 'Scalable Sample Rate profile (SSR)';
|
||||
$AACprofileLookup[2][3] = '(reserved)';
|
||||
$AACprofileLookup[4][0] = 'AAC_MAIN';
|
||||
$AACprofileLookup[4][1] = 'AAC_LC';
|
||||
$AACprofileLookup[4][2] = 'AAC_SSR';
|
||||
$AACprofileLookup[4][3] = 'AAC_LTP';
|
||||
}
|
||||
return (isset($AACprofileLookup[$mpegversion][$profileid]) ? $AACprofileLookup[$mpegversion][$profileid] : 'invalid');
|
||||
}
|
||||
|
||||
public static function AACchannelCountCalculate($program_configs) {
|
||||
$channels = 0;
|
||||
for ($i = 0; $i < $program_configs['num_front_channel_elements']; $i++) {
|
||||
$channels++;
|
||||
if ($program_configs['front_element_is_cpe'][$i]) {
|
||||
// each front element is channel pair (CPE = Channel Pair Element)
|
||||
$channels++;
|
||||
}
|
||||
}
|
||||
for ($i = 0; $i < $program_configs['num_side_channel_elements']; $i++) {
|
||||
$channels++;
|
||||
if ($program_configs['side_element_is_cpe'][$i]) {
|
||||
// each side element is channel pair (CPE = Channel Pair Element)
|
||||
$channels++;
|
||||
}
|
||||
}
|
||||
for ($i = 0; $i < $program_configs['num_back_channel_elements']; $i++) {
|
||||
$channels++;
|
||||
if ($program_configs['back_element_is_cpe'][$i]) {
|
||||
// each back element is channel pair (CPE = Channel Pair Element)
|
||||
$channels++;
|
||||
}
|
||||
}
|
||||
for ($i = 0; $i < $program_configs['num_lfe_channel_elements']; $i++) {
|
||||
$channels++;
|
||||
}
|
||||
return $channels;
|
||||
}
|
||||
|
||||
}
|
476
api/getID3/module.audio.ac3.php
Executable file
@ -0,0 +1,476 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.ac3.php //
|
||||
// module for analyzing AC-3 (aka Dolby Digital) audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_ac3 extends getid3_handler
|
||||
{
|
||||
private $AC3header = array();
|
||||
private $BSIoffset = 0;
|
||||
|
||||
const syncword = "\x0B\x77";
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
///AH
|
||||
$info['ac3']['raw']['bsi'] = array();
|
||||
$thisfile_ac3 = &$info['ac3'];
|
||||
$thisfile_ac3_raw = &$thisfile_ac3['raw'];
|
||||
$thisfile_ac3_raw_bsi = &$thisfile_ac3_raw['bsi'];
|
||||
|
||||
|
||||
// http://www.atsc.org/standards/a_52a.pdf
|
||||
|
||||
$info['fileformat'] = 'ac3';
|
||||
|
||||
// An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames
|
||||
// Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256
|
||||
// new audio samples per channel. A synchronization information (SI) header at the beginning
|
||||
// of each frame contains information needed to acquire and maintain synchronization. A
|
||||
// bit stream information (BSI) header follows SI, and contains parameters describing the coded
|
||||
// audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the
|
||||
// end of each frame is an error check field that includes a CRC word for error detection. An
|
||||
// additional CRC word is located in the SI header, the use of which, by a decoder, is optional.
|
||||
//
|
||||
// syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC
|
||||
|
||||
// syncinfo() {
|
||||
// syncword 16
|
||||
// crc1 16
|
||||
// fscod 2
|
||||
// frmsizecod 6
|
||||
// } /* end of syncinfo */
|
||||
|
||||
$this->fseek($info['avdataoffset']);
|
||||
$this->AC3header['syncinfo'] = $this->fread(5);
|
||||
|
||||
if (strpos($this->AC3header['syncinfo'], self::syncword) === 0) {
|
||||
$thisfile_ac3_raw['synchinfo']['synchword'] = self::syncword;
|
||||
$offset = 2;
|
||||
} else {
|
||||
if (!$this->isDependencyFor('matroska')) {
|
||||
unset($info['fileformat'], $info['ac3']);
|
||||
return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($this->AC3header['syncinfo'], 0, 2)).'"');
|
||||
}
|
||||
$offset = 0;
|
||||
$this->fseek(-2, SEEK_CUR);
|
||||
}
|
||||
|
||||
$info['audio']['dataformat'] = 'ac3';
|
||||
$info['audio']['bitrate_mode'] = 'cbr';
|
||||
$info['audio']['lossless'] = false;
|
||||
|
||||
$thisfile_ac3_raw['synchinfo']['crc1'] = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], $offset, 2));
|
||||
$ac3_synchinfo_fscod_frmsizecod = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], ($offset + 2), 1));
|
||||
$thisfile_ac3_raw['synchinfo']['fscod'] = ($ac3_synchinfo_fscod_frmsizecod & 0xC0) >> 6;
|
||||
$thisfile_ac3_raw['synchinfo']['frmsizecod'] = ($ac3_synchinfo_fscod_frmsizecod & 0x3F);
|
||||
|
||||
$thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']);
|
||||
if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) {
|
||||
$info['audio']['sample_rate'] = $thisfile_ac3['sample_rate'];
|
||||
}
|
||||
|
||||
$thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']);
|
||||
$thisfile_ac3['bitrate'] = self::bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']);
|
||||
$info['audio']['bitrate'] = $thisfile_ac3['bitrate'];
|
||||
|
||||
$this->AC3header['bsi'] = getid3_lib::BigEndian2Bin($this->fread(15));
|
||||
$ac3_bsi_offset = 0;
|
||||
|
||||
$thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5);
|
||||
if ($thisfile_ac3_raw_bsi['bsid'] > 8) {
|
||||
// Decoders which can decode version 8 will thus be able to decode version numbers less than 8.
|
||||
// If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used.
|
||||
// Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8.
|
||||
$this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8');
|
||||
unset($info['ac3']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3);
|
||||
$thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3);
|
||||
|
||||
$thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']);
|
||||
$ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']);
|
||||
foreach($ac3_coding_mode as $key => $value) {
|
||||
$thisfile_ac3[$key] = $value;
|
||||
}
|
||||
switch ($thisfile_ac3_raw_bsi['acmod']) {
|
||||
case 0:
|
||||
case 1:
|
||||
$info['audio']['channelmode'] = 'mono';
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
$info['audio']['channelmode'] = 'stereo';
|
||||
break;
|
||||
default:
|
||||
$info['audio']['channelmode'] = 'surround';
|
||||
break;
|
||||
}
|
||||
$info['audio']['channels'] = $thisfile_ac3['num_channels'];
|
||||
|
||||
if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) {
|
||||
// If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream.
|
||||
$thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2);
|
||||
$thisfile_ac3['center_mix_level'] = self::centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']);
|
||||
}
|
||||
|
||||
if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) {
|
||||
// If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream.
|
||||
$thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2);
|
||||
$thisfile_ac3['surround_mix_level'] = self::surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']);
|
||||
}
|
||||
|
||||
if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) {
|
||||
// When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround.
|
||||
$thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2);
|
||||
$thisfile_ac3['dolby_surround_mode'] = self::dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']);
|
||||
}
|
||||
|
||||
$thisfile_ac3_raw_bsi['lfeon'] = (bool) $this->readHeaderBSI(1);
|
||||
$thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['lfeon'];
|
||||
if ($thisfile_ac3_raw_bsi['lfeon']) {
|
||||
//$info['audio']['channels']++;
|
||||
$info['audio']['channels'] .= '.1';
|
||||
}
|
||||
|
||||
$thisfile_ac3['channels_enabled'] = self::channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']);
|
||||
|
||||
// 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['dialnorm'] = $this->readHeaderBSI(5);
|
||||
$thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB';
|
||||
|
||||
$thisfile_ac3_raw_bsi['compre_flag'] = (bool) $this->readHeaderBSI(1);
|
||||
if ($thisfile_ac3_raw_bsi['compre_flag']) {
|
||||
$thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8);
|
||||
$thisfile_ac3['heavy_compression'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr']);
|
||||
}
|
||||
|
||||
$thisfile_ac3_raw_bsi['langcode_flag'] = (bool) $this->readHeaderBSI(1);
|
||||
if ($thisfile_ac3_raw_bsi['langcode_flag']) {
|
||||
$thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8);
|
||||
}
|
||||
|
||||
$thisfile_ac3_raw_bsi['audprodie'] = (bool) $this->readHeaderBSI(1);
|
||||
if ($thisfile_ac3_raw_bsi['audprodie']) {
|
||||
$thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5);
|
||||
$thisfile_ac3_raw_bsi['roomtyp'] = $this->readHeaderBSI(2);
|
||||
|
||||
$thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB';
|
||||
$thisfile_ac3['room_type'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']);
|
||||
}
|
||||
|
||||
if ($thisfile_ac3_raw_bsi['acmod'] == 0x00) {
|
||||
// If acmod is 0, then two completely independent program channels (dual mono)
|
||||
// are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case,
|
||||
// a number of additional items are present in BSI or audblk to fully describe Ch2.
|
||||
|
||||
// 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['dialnorm2'] = $this->readHeaderBSI(5);
|
||||
$thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB';
|
||||
|
||||
$thisfile_ac3_raw_bsi['compre_flag2'] = (bool) $this->readHeaderBSI(1);
|
||||
if ($thisfile_ac3_raw_bsi['compre_flag2']) {
|
||||
$thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8);
|
||||
$thisfile_ac3['heavy_compression2'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr2']);
|
||||
}
|
||||
|
||||
$thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) $this->readHeaderBSI(1);
|
||||
if ($thisfile_ac3_raw_bsi['langcode_flag2']) {
|
||||
$thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8);
|
||||
}
|
||||
|
||||
$thisfile_ac3_raw_bsi['audprodie2'] = (bool) $this->readHeaderBSI(1);
|
||||
if ($thisfile_ac3_raw_bsi['audprodie2']) {
|
||||
$thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5);
|
||||
$thisfile_ac3_raw_bsi['roomtyp2'] = $this->readHeaderBSI(2);
|
||||
|
||||
$thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB';
|
||||
$thisfile_ac3['room_type2'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1);
|
||||
|
||||
$thisfile_ac3_raw_bsi['original'] = (bool) $this->readHeaderBSI(1);
|
||||
|
||||
$thisfile_ac3_raw_bsi['timecode1_flag'] = (bool) $this->readHeaderBSI(1);
|
||||
if ($thisfile_ac3_raw_bsi['timecode1_flag']) {
|
||||
$thisfile_ac3_raw_bsi['timecode1'] = $this->readHeaderBSI(14);
|
||||
}
|
||||
|
||||
$thisfile_ac3_raw_bsi['timecode2_flag'] = (bool) $this->readHeaderBSI(1);
|
||||
if ($thisfile_ac3_raw_bsi['timecode2_flag']) {
|
||||
$thisfile_ac3_raw_bsi['timecode2'] = $this->readHeaderBSI(14);
|
||||
}
|
||||
|
||||
$thisfile_ac3_raw_bsi['addbsi_flag'] = (bool) $this->readHeaderBSI(1);
|
||||
if ($thisfile_ac3_raw_bsi['addbsi_flag']) {
|
||||
$thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6);
|
||||
|
||||
$this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin($this->fread($thisfile_ac3_raw_bsi['addbsi_length']));
|
||||
|
||||
$thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8);
|
||||
$this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function readHeaderBSI($length) {
|
||||
$data = substr($this->AC3header['bsi'], $this->BSIoffset, $length);
|
||||
$this->BSIoffset += $length;
|
||||
|
||||
return bindec($data);
|
||||
}
|
||||
|
||||
public static function sampleRateCodeLookup($fscod) {
|
||||
static $sampleRateCodeLookup = array(
|
||||
0 => 48000,
|
||||
1 => 44100,
|
||||
2 => 32000,
|
||||
3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute.
|
||||
);
|
||||
return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false);
|
||||
}
|
||||
|
||||
public static function serviceTypeLookup($bsmod, $acmod) {
|
||||
static $serviceTypeLookup = array();
|
||||
if (empty($serviceTypeLookup)) {
|
||||
for ($i = 0; $i <= 7; $i++) {
|
||||
$serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)';
|
||||
$serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)';
|
||||
$serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)';
|
||||
$serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)';
|
||||
$serviceTypeLookup[4][$i] = 'associated service: dialogue (D)';
|
||||
$serviceTypeLookup[5][$i] = 'associated service: commentary (C)';
|
||||
$serviceTypeLookup[6][$i] = 'associated service: emergency (E)';
|
||||
}
|
||||
|
||||
$serviceTypeLookup[7][1] = 'associated service: voice over (VO)';
|
||||
for ($i = 2; $i <= 7; $i++) {
|
||||
$serviceTypeLookup[7][$i] = 'main audio service: karaoke';
|
||||
}
|
||||
}
|
||||
return (isset($serviceTypeLookup[$bsmod][$acmod]) ? $serviceTypeLookup[$bsmod][$acmod] : false);
|
||||
}
|
||||
|
||||
public static function audioCodingModeLookup($acmod) {
|
||||
static $audioCodingModeLookup = array();
|
||||
if (empty($audioCodingModeLookup)) {
|
||||
// array(channel configuration, # channels (not incl LFE), channel order)
|
||||
$audioCodingModeLookup = array (
|
||||
0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'),
|
||||
1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'),
|
||||
2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'),
|
||||
3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'),
|
||||
4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'),
|
||||
5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'),
|
||||
6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'),
|
||||
7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR')
|
||||
);
|
||||
}
|
||||
return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false);
|
||||
}
|
||||
|
||||
public static function centerMixLevelLookup($cmixlev) {
|
||||
static $centerMixLevelLookup;
|
||||
if (empty($centerMixLevelLookup)) {
|
||||
$centerMixLevelLookup = array(
|
||||
0 => pow(2, -3.0 / 6), // 0.707 (-3.0 dB)
|
||||
1 => pow(2, -4.5 / 6), // 0.595 (-4.5 dB)
|
||||
2 => pow(2, -6.0 / 6), // 0.500 (-6.0 dB)
|
||||
3 => 'reserved'
|
||||
);
|
||||
}
|
||||
return (isset($centerMixLevelLookup[$cmixlev]) ? $centerMixLevelLookup[$cmixlev] : false);
|
||||
}
|
||||
|
||||
public static function surroundMixLevelLookup($surmixlev) {
|
||||
static $surroundMixLevelLookup;
|
||||
if (empty($surroundMixLevelLookup)) {
|
||||
$surroundMixLevelLookup = array(
|
||||
0 => pow(2, -3.0 / 6),
|
||||
1 => pow(2, -6.0 / 6),
|
||||
2 => 0,
|
||||
3 => 'reserved'
|
||||
);
|
||||
}
|
||||
return (isset($surroundMixLevelLookup[$surmixlev]) ? $surroundMixLevelLookup[$surmixlev] : false);
|
||||
}
|
||||
|
||||
public static function dolbySurroundModeLookup($dsurmod) {
|
||||
static $dolbySurroundModeLookup = array(
|
||||
0 => 'not indicated',
|
||||
1 => 'Not Dolby Surround encoded',
|
||||
2 => 'Dolby Surround encoded',
|
||||
3 => 'reserved'
|
||||
);
|
||||
return (isset($dolbySurroundModeLookup[$dsurmod]) ? $dolbySurroundModeLookup[$dsurmod] : false);
|
||||
}
|
||||
|
||||
public static function channelsEnabledLookup($acmod, $lfeon) {
|
||||
$channelsEnabledLookup = array(
|
||||
'ch1'=>(bool) ($acmod == 0),
|
||||
'ch2'=>(bool) ($acmod == 0),
|
||||
'left'=>(bool) ($acmod > 1),
|
||||
'right'=>(bool) ($acmod > 1),
|
||||
'center'=>(bool) ($acmod & 0x01),
|
||||
'surround_mono'=>false,
|
||||
'surround_left'=>false,
|
||||
'surround_right'=>false,
|
||||
'lfe'=>$lfeon);
|
||||
switch ($acmod) {
|
||||
case 4:
|
||||
case 5:
|
||||
$channelsEnabledLookup['surround_mono'] = true;
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
$channelsEnabledLookup['surround_left'] = true;
|
||||
$channelsEnabledLookup['surround_right'] = true;
|
||||
break;
|
||||
}
|
||||
return $channelsEnabledLookup;
|
||||
}
|
||||
|
||||
public static function heavyCompression($compre) {
|
||||
// The first four bits indicate gain changes in 6.02dB increments which can be
|
||||
// implemented with an arithmetic shift operation. The following four bits
|
||||
// indicate linear gain changes, and require a 5-bit multiply.
|
||||
// We will represent the two 4-bit fields of compr as follows:
|
||||
// X0 X1 X2 X3 . Y4 Y5 Y6 Y7
|
||||
// The meaning of the X values is most simply described by considering X to represent a 4-bit
|
||||
// signed integer with values from -8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The
|
||||
// following table shows this in detail.
|
||||
|
||||
// Meaning of 4 msb of compr
|
||||
// 7 +48.16 dB
|
||||
// 6 +42.14 dB
|
||||
// 5 +36.12 dB
|
||||
// 4 +30.10 dB
|
||||
// 3 +24.08 dB
|
||||
// 2 +18.06 dB
|
||||
// 1 +12.04 dB
|
||||
// 0 +6.02 dB
|
||||
// -1 0 dB
|
||||
// -2 -6.02 dB
|
||||
// -3 -12.04 dB
|
||||
// -4 -18.06 dB
|
||||
// -5 -24.08 dB
|
||||
// -6 -30.10 dB
|
||||
// -7 -36.12 dB
|
||||
// -8 -42.14 dB
|
||||
|
||||
$fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT);
|
||||
if ($fourbit{0} == '1') {
|
||||
$log_gain = -8 + bindec(substr($fourbit, 1));
|
||||
} else {
|
||||
$log_gain = bindec(substr($fourbit, 1));
|
||||
}
|
||||
$log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2);
|
||||
|
||||
// The value of Y is a linear representation of a gain change of up to -6 dB. Y is considered to
|
||||
// be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can
|
||||
// represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain
|
||||
// changes from -0.28 dB to -6.02 dB.
|
||||
|
||||
$lin_gain = (16 + ($compre & 0x0F)) / 32;
|
||||
|
||||
// The combination of X and Y values allows compr to indicate gain changes from
|
||||
// 48.16 - 0.28 = +47.89 dB, to
|
||||
// -42.14 - 6.02 = -48.16 dB.
|
||||
|
||||
return $log_gain - $lin_gain;
|
||||
}
|
||||
|
||||
public static function roomTypeLookup($roomtyp) {
|
||||
static $roomTypeLookup = array(
|
||||
0 => 'not indicated',
|
||||
1 => 'large room, X curve monitor',
|
||||
2 => 'small room, flat monitor',
|
||||
3 => 'reserved'
|
||||
);
|
||||
return (isset($roomTypeLookup[$roomtyp]) ? $roomTypeLookup[$roomtyp] : false);
|
||||
}
|
||||
|
||||
public static function frameSizeLookup($frmsizecod, $fscod) {
|
||||
$padding = (bool) ($frmsizecod % 2);
|
||||
$framesizeid = floor($frmsizecod / 2);
|
||||
|
||||
static $frameSizeLookup = array();
|
||||
if (empty($frameSizeLookup)) {
|
||||
$frameSizeLookup = array (
|
||||
0 => array(128, 138, 192),
|
||||
1 => array(40, 160, 174, 240),
|
||||
2 => array(48, 192, 208, 288),
|
||||
3 => array(56, 224, 242, 336),
|
||||
4 => array(64, 256, 278, 384),
|
||||
5 => array(80, 320, 348, 480),
|
||||
6 => array(96, 384, 416, 576),
|
||||
7 => array(112, 448, 486, 672),
|
||||
8 => array(128, 512, 556, 768),
|
||||
9 => array(160, 640, 696, 960),
|
||||
10 => array(192, 768, 834, 1152),
|
||||
11 => array(224, 896, 974, 1344),
|
||||
12 => array(256, 1024, 1114, 1536),
|
||||
13 => array(320, 1280, 1392, 1920),
|
||||
14 => array(384, 1536, 1670, 2304),
|
||||
15 => array(448, 1792, 1950, 2688),
|
||||
16 => array(512, 2048, 2228, 3072),
|
||||
17 => array(576, 2304, 2506, 3456),
|
||||
18 => array(640, 2560, 2786, 3840)
|
||||
);
|
||||
}
|
||||
if (($fscod == 1) && $padding) {
|
||||
// frame lengths are padded by 1 word (16 bits) at 44100
|
||||
$frameSizeLookup[$frmsizecod] += 2;
|
||||
}
|
||||
return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] : false);
|
||||
}
|
||||
|
||||
public static function bitrateLookup($frmsizecod) {
|
||||
$framesizeid = floor($frmsizecod / 2);
|
||||
|
||||
static $bitrateLookup = array(
|
||||
0 => 32000,
|
||||
1 => 40000,
|
||||
2 => 48000,
|
||||
3 => 56000,
|
||||
4 => 64000,
|
||||
5 => 80000,
|
||||
6 => 96000,
|
||||
7 => 112000,
|
||||
8 => 128000,
|
||||
9 => 160000,
|
||||
10 => 192000,
|
||||
11 => 224000,
|
||||
12 => 256000,
|
||||
13 => 320000,
|
||||
14 => 384000,
|
||||
15 => 448000,
|
||||
16 => 512000,
|
||||
17 => 576000,
|
||||
18 => 640000
|
||||
);
|
||||
return (isset($bitrateLookup[$framesizeid]) ? $bitrateLookup[$framesizeid] : false);
|
||||
}
|
||||
|
||||
|
||||
}
|
162
api/getID3/module.audio.au.php
Executable file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.au.php //
|
||||
// module for analyzing AU files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_au extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$AUheader = fread($this->getid3->fp, 8);
|
||||
|
||||
$magic = '.snd';
|
||||
if (substr($AUheader, 0, 4) != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" (".snd") at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($AUheader, 0, 4)).'"';
|
||||
return false;
|
||||
}
|
||||
|
||||
// shortcut
|
||||
$info['au'] = array();
|
||||
$thisfile_au = &$info['au'];
|
||||
|
||||
$info['fileformat'] = 'au';
|
||||
$info['audio']['dataformat'] = 'au';
|
||||
$info['audio']['bitrate_mode'] = 'cbr';
|
||||
$thisfile_au['encoding'] = 'ISO-8859-1';
|
||||
|
||||
$thisfile_au['header_length'] = getid3_lib::BigEndian2Int(substr($AUheader, 4, 4));
|
||||
$AUheader .= fread($this->getid3->fp, $thisfile_au['header_length'] - 8);
|
||||
$info['avdataoffset'] += $thisfile_au['header_length'];
|
||||
|
||||
$thisfile_au['data_size'] = getid3_lib::BigEndian2Int(substr($AUheader, 8, 4));
|
||||
$thisfile_au['data_format_id'] = getid3_lib::BigEndian2Int(substr($AUheader, 12, 4));
|
||||
$thisfile_au['sample_rate'] = getid3_lib::BigEndian2Int(substr($AUheader, 16, 4));
|
||||
$thisfile_au['channels'] = getid3_lib::BigEndian2Int(substr($AUheader, 20, 4));
|
||||
$thisfile_au['comments']['comment'][] = trim(substr($AUheader, 24));
|
||||
|
||||
$thisfile_au['data_format'] = $this->AUdataFormatNameLookup($thisfile_au['data_format_id']);
|
||||
$thisfile_au['used_bits_per_sample'] = $this->AUdataFormatUsedBitsPerSampleLookup($thisfile_au['data_format_id']);
|
||||
if ($thisfile_au['bits_per_sample'] = $this->AUdataFormatBitsPerSampleLookup($thisfile_au['data_format_id'])) {
|
||||
$info['audio']['bits_per_sample'] = $thisfile_au['bits_per_sample'];
|
||||
} else {
|
||||
unset($thisfile_au['bits_per_sample']);
|
||||
}
|
||||
|
||||
$info['audio']['sample_rate'] = $thisfile_au['sample_rate'];
|
||||
$info['audio']['channels'] = $thisfile_au['channels'];
|
||||
|
||||
if (($info['avdataoffset'] + $thisfile_au['data_size']) > $info['avdataend']) {
|
||||
$info['warning'][] = 'Possible truncated file - expecting "'.$thisfile_au['data_size'].'" bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' bytes"';
|
||||
}
|
||||
|
||||
$info['playtime_seconds'] = $thisfile_au['data_size'] / ($thisfile_au['sample_rate'] * $thisfile_au['channels'] * ($thisfile_au['used_bits_per_sample'] / 8));
|
||||
$info['audio']['bitrate'] = ($thisfile_au['data_size'] * 8) / $info['playtime_seconds'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function AUdataFormatNameLookup($id) {
|
||||
static $AUdataFormatNameLookup = array(
|
||||
0 => 'unspecified format',
|
||||
1 => '8-bit mu-law',
|
||||
2 => '8-bit linear',
|
||||
3 => '16-bit linear',
|
||||
4 => '24-bit linear',
|
||||
5 => '32-bit linear',
|
||||
6 => 'floating-point',
|
||||
7 => 'double-precision float',
|
||||
8 => 'fragmented sampled data',
|
||||
9 => 'SUN_FORMAT_NESTED',
|
||||
10 => 'DSP program',
|
||||
11 => '8-bit fixed-point',
|
||||
12 => '16-bit fixed-point',
|
||||
13 => '24-bit fixed-point',
|
||||
14 => '32-bit fixed-point',
|
||||
|
||||
16 => 'non-audio display data',
|
||||
17 => 'SND_FORMAT_MULAW_SQUELCH',
|
||||
18 => '16-bit linear with emphasis',
|
||||
19 => '16-bit linear with compression',
|
||||
20 => '16-bit linear with emphasis + compression',
|
||||
21 => 'Music Kit DSP commands',
|
||||
22 => 'SND_FORMAT_DSP_COMMANDS_SAMPLES',
|
||||
23 => 'CCITT g.721 4-bit ADPCM',
|
||||
24 => 'CCITT g.722 ADPCM',
|
||||
25 => 'CCITT g.723 3-bit ADPCM',
|
||||
26 => 'CCITT g.723 5-bit ADPCM',
|
||||
27 => 'A-Law 8-bit'
|
||||
);
|
||||
return (isset($AUdataFormatNameLookup[$id]) ? $AUdataFormatNameLookup[$id] : false);
|
||||
}
|
||||
|
||||
public function AUdataFormatBitsPerSampleLookup($id) {
|
||||
static $AUdataFormatBitsPerSampleLookup = array(
|
||||
1 => 8,
|
||||
2 => 8,
|
||||
3 => 16,
|
||||
4 => 24,
|
||||
5 => 32,
|
||||
6 => 32,
|
||||
7 => 64,
|
||||
|
||||
11 => 8,
|
||||
12 => 16,
|
||||
13 => 24,
|
||||
14 => 32,
|
||||
|
||||
18 => 16,
|
||||
19 => 16,
|
||||
20 => 16,
|
||||
|
||||
23 => 16,
|
||||
|
||||
25 => 16,
|
||||
26 => 16,
|
||||
27 => 8
|
||||
);
|
||||
return (isset($AUdataFormatBitsPerSampleLookup[$id]) ? $AUdataFormatBitsPerSampleLookup[$id] : false);
|
||||
}
|
||||
|
||||
public function AUdataFormatUsedBitsPerSampleLookup($id) {
|
||||
static $AUdataFormatUsedBitsPerSampleLookup = array(
|
||||
1 => 8,
|
||||
2 => 8,
|
||||
3 => 16,
|
||||
4 => 24,
|
||||
5 => 32,
|
||||
6 => 32,
|
||||
7 => 64,
|
||||
|
||||
11 => 8,
|
||||
12 => 16,
|
||||
13 => 24,
|
||||
14 => 32,
|
||||
|
||||
18 => 16,
|
||||
19 => 16,
|
||||
20 => 16,
|
||||
|
||||
23 => 4,
|
||||
|
||||
25 => 3,
|
||||
26 => 5,
|
||||
27 => 8,
|
||||
);
|
||||
return (isset($AUdataFormatUsedBitsPerSampleLookup[$id]) ? $AUdataFormatUsedBitsPerSampleLookup[$id] : false);
|
||||
}
|
||||
|
||||
}
|
124
api/getID3/module.audio.avr.php
Executable file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.avr.php //
|
||||
// module for analyzing AVR Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_avr extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// http://cui.unige.ch/OSG/info/AudioFormats/ap11.html
|
||||
// http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html
|
||||
// offset type length name comments
|
||||
// ---------------------------------------------------------------------
|
||||
// 0 char 4 ID format ID == "2BIT"
|
||||
// 4 char 8 name sample name (unused space filled with 0)
|
||||
// 12 short 1 mono/stereo 0=mono, -1 (0xFFFF)=stereo
|
||||
// With stereo, samples are alternated,
|
||||
// the first voice is the left :
|
||||
// (LRLRLRLRLRLRLRLRLR...)
|
||||
// 14 short 1 resolution 8, 12 or 16 (bits)
|
||||
// 16 short 1 signed or not 0=unsigned, -1 (0xFFFF)=signed
|
||||
// 18 short 1 loop or not 0=no loop, -1 (0xFFFF)=loop on
|
||||
// 20 short 1 MIDI note 0xFFnn, where 0 <= nn <= 127
|
||||
// 0xFFFF means "no MIDI note defined"
|
||||
// 22 byte 1 Replay speed Frequence in the Replay software
|
||||
// 0=5.485 Khz, 1=8.084 Khz, 2=10.971 Khz,
|
||||
// 3=16.168 Khz, 4=21.942 Khz, 5=32.336 Khz
|
||||
// 6=43.885 Khz, 7=47.261 Khz
|
||||
// -1 (0xFF)=no defined Frequence
|
||||
// 23 byte 3 sample rate in Hertz
|
||||
// 26 long 1 size in bytes (2 * bytes in stereo)
|
||||
// 30 long 1 loop begin 0 for no loop
|
||||
// 34 long 1 loop size equal to 'size' for no loop
|
||||
// 38 short 2 Reserved, MIDI keyboard split */
|
||||
// 40 short 2 Reserved, sample compression */
|
||||
// 42 short 2 Reserved */
|
||||
// 44 char 20; Additional filename space, used if (name[7] != 0)
|
||||
// 64 byte 64 user data
|
||||
// 128 bytes ? sample data (12 bits samples are coded on 16 bits:
|
||||
// 0000 xxxx xxxx xxxx)
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// Note that all values are in motorola (big-endian) format, and that long is
|
||||
// assumed to be 4 bytes, and short 2 bytes.
|
||||
// When reading the samples, you should handle both signed and unsigned data,
|
||||
// and be prepared to convert 16->8 bit, or mono->stereo if needed. To convert
|
||||
// 8-bit data between signed/unsigned just add 127 to the sample values.
|
||||
// Simularly for 16-bit data you should add 32769
|
||||
|
||||
$info['fileformat'] = 'avr';
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$AVRheader = fread($this->getid3->fp, 128);
|
||||
|
||||
$info['avr']['raw']['magic'] = substr($AVRheader, 0, 4);
|
||||
$magic = '2BIT';
|
||||
if ($info['avr']['raw']['magic'] != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['avr']['raw']['magic']).'"';
|
||||
unset($info['fileformat']);
|
||||
unset($info['avr']);
|
||||
return false;
|
||||
}
|
||||
$info['avdataoffset'] += 128;
|
||||
|
||||
$info['avr']['sample_name'] = rtrim(substr($AVRheader, 4, 8));
|
||||
$info['avr']['raw']['mono'] = getid3_lib::BigEndian2Int(substr($AVRheader, 12, 2));
|
||||
$info['avr']['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($AVRheader, 14, 2));
|
||||
$info['avr']['raw']['signed'] = getid3_lib::BigEndian2Int(substr($AVRheader, 16, 2));
|
||||
$info['avr']['raw']['loop'] = getid3_lib::BigEndian2Int(substr($AVRheader, 18, 2));
|
||||
$info['avr']['raw']['midi'] = getid3_lib::BigEndian2Int(substr($AVRheader, 20, 2));
|
||||
$info['avr']['raw']['replay_freq'] = getid3_lib::BigEndian2Int(substr($AVRheader, 22, 1));
|
||||
$info['avr']['sample_rate'] = getid3_lib::BigEndian2Int(substr($AVRheader, 23, 3));
|
||||
$info['avr']['sample_length'] = getid3_lib::BigEndian2Int(substr($AVRheader, 26, 4));
|
||||
$info['avr']['loop_start'] = getid3_lib::BigEndian2Int(substr($AVRheader, 30, 4));
|
||||
$info['avr']['loop_end'] = getid3_lib::BigEndian2Int(substr($AVRheader, 34, 4));
|
||||
$info['avr']['midi_split'] = getid3_lib::BigEndian2Int(substr($AVRheader, 38, 2));
|
||||
$info['avr']['sample_compression'] = getid3_lib::BigEndian2Int(substr($AVRheader, 40, 2));
|
||||
$info['avr']['reserved'] = getid3_lib::BigEndian2Int(substr($AVRheader, 42, 2));
|
||||
$info['avr']['sample_name_extra'] = rtrim(substr($AVRheader, 44, 20));
|
||||
$info['avr']['comment'] = rtrim(substr($AVRheader, 64, 64));
|
||||
|
||||
$info['avr']['flags']['stereo'] = (($info['avr']['raw']['mono'] == 0) ? false : true);
|
||||
$info['avr']['flags']['signed'] = (($info['avr']['raw']['signed'] == 0) ? false : true);
|
||||
$info['avr']['flags']['loop'] = (($info['avr']['raw']['loop'] == 0) ? false : true);
|
||||
|
||||
$info['avr']['midi_notes'] = array();
|
||||
if (($info['avr']['raw']['midi'] & 0xFF00) != 0xFF00) {
|
||||
$info['avr']['midi_notes'][] = ($info['avr']['raw']['midi'] & 0xFF00) >> 8;
|
||||
}
|
||||
if (($info['avr']['raw']['midi'] & 0x00FF) != 0x00FF) {
|
||||
$info['avr']['midi_notes'][] = ($info['avr']['raw']['midi'] & 0x00FF);
|
||||
}
|
||||
|
||||
if (($info['avdataend'] - $info['avdataoffset']) != ($info['avr']['sample_length'] * (($info['avr']['bits_per_sample'] == 8) ? 1 : 2))) {
|
||||
$info['warning'][] = 'Probable truncated file: expecting '.($info['avr']['sample_length'] * (($info['avr']['bits_per_sample'] == 8) ? 1 : 2)).' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']);
|
||||
}
|
||||
|
||||
$info['audio']['dataformat'] = 'avr';
|
||||
$info['audio']['lossless'] = true;
|
||||
$info['audio']['bitrate_mode'] = 'cbr';
|
||||
$info['audio']['bits_per_sample'] = $info['avr']['bits_per_sample'];
|
||||
$info['audio']['sample_rate'] = $info['avr']['sample_rate'];
|
||||
$info['audio']['channels'] = ($info['avr']['flags']['stereo'] ? 2 : 1);
|
||||
$info['playtime_seconds'] = ($info['avr']['sample_length'] / $info['audio']['channels']) / $info['avr']['sample_rate'];
|
||||
$info['audio']['bitrate'] = ($info['avr']['sample_length'] * (($info['avr']['bits_per_sample'] == 8) ? 8 : 16)) / $info['playtime_seconds'];
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
227
api/getID3/module.audio.bonk.php
Executable file
@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.la.php //
|
||||
// module for analyzing BONK audio files //
|
||||
// dependencies: module.tag.id3v2.php (optional) //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_bonk extends getid3_handler
|
||||
{
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// shortcut
|
||||
$info['bonk'] = array();
|
||||
$thisfile_bonk = &$info['bonk'];
|
||||
|
||||
$thisfile_bonk['dataoffset'] = $info['avdataoffset'];
|
||||
$thisfile_bonk['dataend'] = $info['avdataend'];
|
||||
|
||||
if (!getid3_lib::intValueSupported($thisfile_bonk['dataend'])) {
|
||||
|
||||
$info['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
|
||||
fseek($this->getid3->fp, $thisfile_bonk['dataend'] - 8, SEEK_SET);
|
||||
$PossibleBonkTag = fread($this->getid3->fp, 8);
|
||||
while ($this->BonkIsValidTagName(substr($PossibleBonkTag, 4, 4), true)) {
|
||||
$BonkTagSize = getid3_lib::LittleEndian2Int(substr($PossibleBonkTag, 0, 4));
|
||||
fseek($this->getid3->fp, 0 - $BonkTagSize, SEEK_CUR);
|
||||
$BonkTagOffset = ftell($this->getid3->fp);
|
||||
$TagHeaderTest = fread($this->getid3->fp, 5);
|
||||
if (($TagHeaderTest{0} != "\x00") || (substr($PossibleBonkTag, 4, 4) != strtolower(substr($PossibleBonkTag, 4, 4)))) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes("\x00".strtoupper(substr($PossibleBonkTag, 4, 4))).'" at offset '.$BonkTagOffset.', found "'.getid3_lib::PrintHexBytes($TagHeaderTest).'"';
|
||||
return false;
|
||||
}
|
||||
$BonkTagName = substr($TagHeaderTest, 1, 4);
|
||||
|
||||
$thisfile_bonk[$BonkTagName]['size'] = $BonkTagSize;
|
||||
$thisfile_bonk[$BonkTagName]['offset'] = $BonkTagOffset;
|
||||
$this->HandleBonkTags($BonkTagName);
|
||||
$NextTagEndOffset = $BonkTagOffset - 8;
|
||||
if ($NextTagEndOffset < $thisfile_bonk['dataoffset']) {
|
||||
if (empty($info['audio']['encoder'])) {
|
||||
$info['audio']['encoder'] = 'Extended BONK v0.9+';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
fseek($this->getid3->fp, $NextTagEndOffset, SEEK_SET);
|
||||
$PossibleBonkTag = fread($this->getid3->fp, 8);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// seek-from-beginning method for v0.4 and v0.5
|
||||
if (empty($thisfile_bonk['BONK'])) {
|
||||
fseek($this->getid3->fp, $thisfile_bonk['dataoffset'], SEEK_SET);
|
||||
do {
|
||||
$TagHeaderTest = fread($this->getid3->fp, 5);
|
||||
switch ($TagHeaderTest) {
|
||||
case "\x00".'BONK':
|
||||
if (empty($info['audio']['encoder'])) {
|
||||
$info['audio']['encoder'] = 'BONK v0.4';
|
||||
}
|
||||
break;
|
||||
|
||||
case "\x00".'INFO':
|
||||
$info['audio']['encoder'] = 'Extended BONK v0.5';
|
||||
break;
|
||||
|
||||
default:
|
||||
break 2;
|
||||
}
|
||||
$BonkTagName = substr($TagHeaderTest, 1, 4);
|
||||
$thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset'];
|
||||
$thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset'];
|
||||
$this->HandleBonkTags($BonkTagName);
|
||||
|
||||
} while (true);
|
||||
}
|
||||
|
||||
// parse META block for v0.6 - v0.8
|
||||
if (empty($thisfile_bonk['INFO']) && isset($thisfile_bonk['META']['tags']['info'])) {
|
||||
fseek($this->getid3->fp, $thisfile_bonk['META']['tags']['info'], SEEK_SET);
|
||||
$TagHeaderTest = fread($this->getid3->fp, 5);
|
||||
if ($TagHeaderTest == "\x00".'INFO') {
|
||||
$info['audio']['encoder'] = 'Extended BONK v0.6 - v0.8';
|
||||
|
||||
$BonkTagName = substr($TagHeaderTest, 1, 4);
|
||||
$thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset'];
|
||||
$thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset'];
|
||||
$this->HandleBonkTags($BonkTagName);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($info['audio']['encoder'])) {
|
||||
$info['audio']['encoder'] = 'Extended BONK v0.9+';
|
||||
}
|
||||
if (empty($thisfile_bonk['BONK'])) {
|
||||
unset($info['bonk']);
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public function HandleBonkTags($BonkTagName) {
|
||||
$info = &$this->getid3->info;
|
||||
switch ($BonkTagName) {
|
||||
case 'BONK':
|
||||
// shortcut
|
||||
$thisfile_bonk_BONK = &$info['bonk']['BONK'];
|
||||
|
||||
$BonkData = "\x00".'BONK'.fread($this->getid3->fp, 17);
|
||||
$thisfile_bonk_BONK['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1));
|
||||
$thisfile_bonk_BONK['number_samples'] = getid3_lib::LittleEndian2Int(substr($BonkData, 6, 4));
|
||||
$thisfile_bonk_BONK['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BonkData, 10, 4));
|
||||
|
||||
$thisfile_bonk_BONK['channels'] = getid3_lib::LittleEndian2Int(substr($BonkData, 14, 1));
|
||||
$thisfile_bonk_BONK['lossless'] = (bool) getid3_lib::LittleEndian2Int(substr($BonkData, 15, 1));
|
||||
$thisfile_bonk_BONK['joint_stereo'] = (bool) getid3_lib::LittleEndian2Int(substr($BonkData, 16, 1));
|
||||
$thisfile_bonk_BONK['number_taps'] = getid3_lib::LittleEndian2Int(substr($BonkData, 17, 2));
|
||||
$thisfile_bonk_BONK['downsampling_ratio'] = getid3_lib::LittleEndian2Int(substr($BonkData, 19, 1));
|
||||
$thisfile_bonk_BONK['samples_per_packet'] = getid3_lib::LittleEndian2Int(substr($BonkData, 20, 2));
|
||||
|
||||
$info['avdataoffset'] = $thisfile_bonk_BONK['offset'] + 5 + 17;
|
||||
$info['avdataend'] = $thisfile_bonk_BONK['offset'] + $thisfile_bonk_BONK['size'];
|
||||
|
||||
$info['fileformat'] = 'bonk';
|
||||
$info['audio']['dataformat'] = 'bonk';
|
||||
$info['audio']['bitrate_mode'] = 'vbr'; // assumed
|
||||
$info['audio']['channels'] = $thisfile_bonk_BONK['channels'];
|
||||
$info['audio']['sample_rate'] = $thisfile_bonk_BONK['sample_rate'];
|
||||
$info['audio']['channelmode'] = ($thisfile_bonk_BONK['joint_stereo'] ? 'joint stereo' : 'stereo');
|
||||
$info['audio']['lossless'] = $thisfile_bonk_BONK['lossless'];
|
||||
$info['audio']['codec'] = 'bonk';
|
||||
|
||||
$info['playtime_seconds'] = $thisfile_bonk_BONK['number_samples'] / ($thisfile_bonk_BONK['sample_rate'] * $thisfile_bonk_BONK['channels']);
|
||||
if ($info['playtime_seconds'] > 0) {
|
||||
$info['audio']['bitrate'] = (($info['bonk']['dataend'] - $info['bonk']['dataoffset']) * 8) / $info['playtime_seconds'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'INFO':
|
||||
// shortcut
|
||||
$thisfile_bonk_INFO = &$info['bonk']['INFO'];
|
||||
|
||||
$thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1));
|
||||
$thisfile_bonk_INFO['entries_count'] = 0;
|
||||
$NextInfoDataPair = fread($this->getid3->fp, 5);
|
||||
if (!$this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) {
|
||||
while (!feof($this->getid3->fp)) {
|
||||
//$CurrentSeekInfo['offset'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 0, 4));
|
||||
//$CurrentSeekInfo['nextbit'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 4, 1));
|
||||
//$thisfile_bonk_INFO[] = $CurrentSeekInfo;
|
||||
|
||||
$NextInfoDataPair = fread($this->getid3->fp, 5);
|
||||
if ($this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) {
|
||||
fseek($this->getid3->fp, -5, SEEK_CUR);
|
||||
break;
|
||||
}
|
||||
$thisfile_bonk_INFO['entries_count']++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'META':
|
||||
$BonkData = "\x00".'META'.fread($this->getid3->fp, $info['bonk']['META']['size'] - 5);
|
||||
$info['bonk']['META']['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1));
|
||||
|
||||
$MetaTagEntries = floor(((strlen($BonkData) - 8) - 6) / 8); // BonkData - xxxxmeta - ØMETA
|
||||
$offset = 6;
|
||||
for ($i = 0; $i < $MetaTagEntries; $i++) {
|
||||
$MetaEntryTagName = substr($BonkData, $offset, 4);
|
||||
$offset += 4;
|
||||
$MetaEntryTagOffset = getid3_lib::LittleEndian2Int(substr($BonkData, $offset, 4));
|
||||
$offset += 4;
|
||||
$info['bonk']['META']['tags'][$MetaEntryTagName] = $MetaEntryTagOffset;
|
||||
}
|
||||
break;
|
||||
|
||||
case ' ID3':
|
||||
$info['audio']['encoder'] = 'Extended BONK v0.9+';
|
||||
|
||||
// ID3v2 checking is optional
|
||||
if (class_exists('getid3_id3v2')) {
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($this->getid3->filename);
|
||||
$getid3_id3v2 = new getid3_id3v2($getid3_temp);
|
||||
$getid3_id3v2->StartingOffset = $info['bonk'][' ID3']['offset'] + 2;
|
||||
$info['bonk'][' ID3']['valid'] = $getid3_id3v2->Analyze();
|
||||
if ($info['bonk'][' ID3']['valid']) {
|
||||
$info['id3v2'] = $getid3_temp->info['id3v2'];
|
||||
}
|
||||
unset($getid3_temp, $getid3_id3v2);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['warning'][] = 'Unexpected Bonk tag "'.$BonkTagName.'" at offset '.$info['bonk'][$BonkTagName]['offset'];
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static function BonkIsValidTagName($PossibleBonkTag, $ignorecase=false) {
|
||||
static $BonkIsValidTagName = array('BONK', 'INFO', ' ID3', 'META');
|
||||
foreach ($BonkIsValidTagName as $validtagname) {
|
||||
if ($validtagname == $PossibleBonkTag) {
|
||||
return true;
|
||||
} elseif ($ignorecase && (strtolower($validtagname) == strtolower($PossibleBonkTag))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
72
api/getID3/module.audio.dss.php
Executable file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.dss.php //
|
||||
// module for analyzing Digital Speech Standard (DSS) files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_dss extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$DSSheader = fread($this->getid3->fp, 1256);
|
||||
|
||||
if (!preg_match('#^(\x02|\x03)dss#', $DSSheader)) {
|
||||
$info['error'][] = 'Expecting "[02-03] 64 73 73" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSSheader, 0, 4)).'"';
|
||||
return false;
|
||||
}
|
||||
|
||||
// some structure information taken from http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm
|
||||
|
||||
// shortcut
|
||||
$info['dss'] = array();
|
||||
$thisfile_dss = &$info['dss'];
|
||||
|
||||
$info['fileformat'] = 'dss';
|
||||
$info['audio']['dataformat'] = 'dss';
|
||||
$info['audio']['bitrate_mode'] = 'cbr';
|
||||
//$thisfile_dss['encoding'] = 'ISO-8859-1';
|
||||
|
||||
$thisfile_dss['version'] = ord(substr($DSSheader, 0, 1));
|
||||
$thisfile_dss['date_create'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 38, 12));
|
||||
$thisfile_dss['date_complete'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 50, 12));
|
||||
//$thisfile_dss['length'] = intval(substr($DSSheader, 62, 6)); // I thought time was in seconds, it's actually HHMMSS
|
||||
$thisfile_dss['length'] = intval((substr($DSSheader, 62, 2) * 3600) + (substr($DSSheader, 64, 2) * 60) + substr($DSSheader, 66, 2));
|
||||
$thisfile_dss['priority'] = ord(substr($DSSheader, 793, 1));
|
||||
$thisfile_dss['comments'] = trim(substr($DSSheader, 798, 100));
|
||||
|
||||
|
||||
//$info['audio']['bits_per_sample'] = ?;
|
||||
//$info['audio']['sample_rate'] = ?;
|
||||
$info['audio']['channels'] = 1;
|
||||
|
||||
$info['playtime_seconds'] = $thisfile_dss['length'];
|
||||
$info['audio']['bitrate'] = ($info['filesize'] * 8) / $info['playtime_seconds'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function DSSdateStringToUnixDate($datestring) {
|
||||
$y = substr($datestring, 0, 2);
|
||||
$m = substr($datestring, 2, 2);
|
||||
$d = substr($datestring, 4, 2);
|
||||
$h = substr($datestring, 6, 2);
|
||||
$i = substr($datestring, 8, 2);
|
||||
$s = substr($datestring, 10, 2);
|
||||
$y += (($y < 95) ? 2000 : 1900);
|
||||
return mktime($h, $i, $s, $m, $d, $y);
|
||||
}
|
||||
|
||||
}
|
248
api/getID3/module.audio.dts.php
Executable file
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.dts.php //
|
||||
// module for analyzing DTS Audio files //
|
||||
// dependencies: NONE //
|
||||
// //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_dts extends getid3_handler
|
||||
{
|
||||
const syncword = "\x7F\xFE\x80\x01";
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// Specs taken from "DTS Coherent Acoustics;Core and Extensions, ETSI TS 102 114 V1.2.1 (2002-12)"
|
||||
// (http://pda.etsi.org/pda/queryform.asp)
|
||||
// With thanks to Gambit <macteam@users.sourceforge.net> http://mac.sourceforge.net/atl/
|
||||
|
||||
$info['fileformat'] = 'dts';
|
||||
|
||||
$this->fseek($info['avdataoffset']);
|
||||
$DTSheader = $this->fread(16);
|
||||
|
||||
if (strpos($DTSheader, self::syncword) === 0) {
|
||||
$info['dts']['raw']['magic'] = self::syncword;
|
||||
$offset = 4;
|
||||
} else {
|
||||
if (!$this->isDependencyFor('matroska')) {
|
||||
unset($info['fileformat']);
|
||||
return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DTSheader, 0, 4)).'"');
|
||||
}
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
$fhBS = getid3_lib::BigEndian2Bin(substr($DTSheader, $offset, 12));
|
||||
$bsOffset = 0;
|
||||
$info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, $bsOffset, 5);
|
||||
$info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, $bsOffset, 7);
|
||||
$info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, $bsOffset, 14);
|
||||
$info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, $bsOffset, 6);
|
||||
$info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, $bsOffset, 4);
|
||||
$info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, $bsOffset, 5);
|
||||
$info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, $bsOffset, 3);
|
||||
$info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, $bsOffset, 2);
|
||||
$info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
if ($info['dts']['flags']['crc_present']) {
|
||||
$info['dts']['raw']['crc16'] = $this->readBinData($fhBS, $bsOffset, 16);
|
||||
}
|
||||
$info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, $bsOffset, 4);
|
||||
$info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, $bsOffset, 2);
|
||||
$info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, $bsOffset, 2);
|
||||
$info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||
$info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, $bsOffset, 4);
|
||||
|
||||
|
||||
$info['dts']['bitrate'] = self::bitrateLookup($info['dts']['raw']['bitrate']);
|
||||
$info['dts']['bits_per_sample'] = self::bitPerSampleLookup($info['dts']['raw']['bits_per_sample']);
|
||||
$info['dts']['sample_rate'] = self::sampleRateLookup($info['dts']['raw']['sample_frequency']);
|
||||
$info['dts']['dialog_normalization'] = self::dialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']);
|
||||
$info['dts']['flags']['lossless'] = (($info['dts']['raw']['bitrate'] == 31) ? true : false);
|
||||
$info['dts']['bitrate_mode'] = (($info['dts']['raw']['bitrate'] == 30) ? 'vbr' : 'cbr');
|
||||
$info['dts']['channels'] = self::numChannelsLookup($info['dts']['raw']['channel_arrangement']);
|
||||
$info['dts']['channel_arrangement'] = self::channelArrangementLookup($info['dts']['raw']['channel_arrangement']);
|
||||
|
||||
$info['audio']['dataformat'] = 'dts';
|
||||
$info['audio']['lossless'] = $info['dts']['flags']['lossless'];
|
||||
$info['audio']['bitrate_mode'] = $info['dts']['bitrate_mode'];
|
||||
$info['audio']['bits_per_sample'] = $info['dts']['bits_per_sample'];
|
||||
$info['audio']['sample_rate'] = $info['dts']['sample_rate'];
|
||||
$info['audio']['channels'] = $info['dts']['channels'];
|
||||
$info['audio']['bitrate'] = $info['dts']['bitrate'];
|
||||
if (isset($info['avdataend'])) {
|
||||
$info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function readBinData($bin, &$offset, $length) {
|
||||
$data = substr($bin, $offset, $length);
|
||||
$offset += $length;
|
||||
return bindec($data);
|
||||
}
|
||||
|
||||
public static function bitrateLookup($index) {
|
||||
static $lookup = array(
|
||||
0 => 32000,
|
||||
1 => 56000,
|
||||
2 => 64000,
|
||||
3 => 96000,
|
||||
4 => 112000,
|
||||
5 => 128000,
|
||||
6 => 192000,
|
||||
7 => 224000,
|
||||
8 => 256000,
|
||||
9 => 320000,
|
||||
10 => 384000,
|
||||
11 => 448000,
|
||||
12 => 512000,
|
||||
13 => 576000,
|
||||
14 => 640000,
|
||||
15 => 768000,
|
||||
16 => 960000,
|
||||
17 => 1024000,
|
||||
18 => 1152000,
|
||||
19 => 1280000,
|
||||
20 => 1344000,
|
||||
21 => 1408000,
|
||||
22 => 1411200,
|
||||
23 => 1472000,
|
||||
24 => 1536000,
|
||||
25 => 1920000,
|
||||
26 => 2048000,
|
||||
27 => 3072000,
|
||||
28 => 3840000,
|
||||
29 => 'open',
|
||||
30 => 'variable',
|
||||
31 => 'lossless',
|
||||
);
|
||||
return (isset($lookup[$index]) ? $lookup[$index] : false);
|
||||
}
|
||||
|
||||
public static function sampleRateLookup($index) {
|
||||
static $lookup = array(
|
||||
0 => 'invalid',
|
||||
1 => 8000,
|
||||
2 => 16000,
|
||||
3 => 32000,
|
||||
4 => 'invalid',
|
||||
5 => 'invalid',
|
||||
6 => 11025,
|
||||
7 => 22050,
|
||||
8 => 44100,
|
||||
9 => 'invalid',
|
||||
10 => 'invalid',
|
||||
11 => 12000,
|
||||
12 => 24000,
|
||||
13 => 48000,
|
||||
14 => 'invalid',
|
||||
15 => 'invalid',
|
||||
);
|
||||
return (isset($lookup[$index]) ? $lookup[$index] : false);
|
||||
}
|
||||
|
||||
public static function bitPerSampleLookup($index) {
|
||||
static $lookup = array(
|
||||
0 => 16,
|
||||
1 => 20,
|
||||
2 => 24,
|
||||
3 => 24,
|
||||
);
|
||||
return (isset($lookup[$index]) ? $lookup[$index] : false);
|
||||
}
|
||||
|
||||
public static function numChannelsLookup($index) {
|
||||
switch ($index) {
|
||||
case 0:
|
||||
return 1;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
return 2;
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
return 3;
|
||||
break;
|
||||
case 7:
|
||||
case 8:
|
||||
return 4;
|
||||
break;
|
||||
case 9:
|
||||
return 5;
|
||||
break;
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
return 6;
|
||||
break;
|
||||
case 13:
|
||||
return 7;
|
||||
break;
|
||||
case 14:
|
||||
case 15:
|
||||
return 8;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function channelArrangementLookup($index) {
|
||||
static $lookup = array(
|
||||
0 => 'A',
|
||||
1 => 'A + B (dual mono)',
|
||||
2 => 'L + R (stereo)',
|
||||
3 => '(L+R) + (L-R) (sum-difference)',
|
||||
4 => 'LT + RT (left and right total)',
|
||||
5 => 'C + L + R',
|
||||
6 => 'L + R + S',
|
||||
7 => 'C + L + R + S',
|
||||
8 => 'L + R + SL + SR',
|
||||
9 => 'C + L + R + SL + SR',
|
||||
10 => 'CL + CR + L + R + SL + SR',
|
||||
11 => 'C + L + R+ LR + RR + OV',
|
||||
12 => 'CF + CR + LF + RF + LR + RR',
|
||||
13 => 'CL + C + CR + L + R + SL + SR',
|
||||
14 => 'CL + CR + L + R + SL1 + SL2 + SR1 + SR2',
|
||||
15 => 'CL + C+ CR + L + R + SL + S + SR',
|
||||
);
|
||||
return (isset($lookup[$index]) ? $lookup[$index] : 'user-defined');
|
||||
}
|
||||
|
||||
public static function dialogNormalization($index, $version) {
|
||||
switch ($version) {
|
||||
case 7:
|
||||
return 0 - $index;
|
||||
break;
|
||||
case 6:
|
||||
return 0 - 16 - $index;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
441
api/getID3/module.audio.flac.php
Executable file
@ -0,0 +1,441 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.flac.php //
|
||||
// module for analyzing FLAC and OggFLAC audio files //
|
||||
// dependencies: module.audio.ogg.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);
|
||||
|
||||
class getid3_flac extends getid3_handler
|
||||
{
|
||||
const syncword = 'fLaC';
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// http://flac.sourceforge.net/format.html
|
||||
|
||||
$this->fseek($info['avdataoffset']);
|
||||
$StreamMarker = $this->fread(4);
|
||||
if ($StreamMarker != self::syncword) {
|
||||
return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"');
|
||||
}
|
||||
$info['fileformat'] = 'flac';
|
||||
$info['audio']['dataformat'] = 'flac';
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
$info['audio']['lossless'] = true;
|
||||
|
||||
// parse flac container
|
||||
return $this->parseMETAdata();
|
||||
}
|
||||
|
||||
public function parseMETAdata() {
|
||||
$info = &$this->getid3->info;
|
||||
do {
|
||||
$BlockOffset = $this->ftell();
|
||||
$BlockHeader = $this->fread(4);
|
||||
$LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1));
|
||||
$LastBlockFlag = (bool) ($LBFBT & 0x80);
|
||||
$BlockType = ($LBFBT & 0x7F);
|
||||
$BlockLength = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3));
|
||||
$BlockTypeText = self::metaBlockTypeLookup($BlockType);
|
||||
|
||||
if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) {
|
||||
$this->error('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file');
|
||||
break;
|
||||
}
|
||||
if ($BlockLength < 1) {
|
||||
$this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid');
|
||||
break;
|
||||
}
|
||||
|
||||
$info['flac'][$BlockTypeText]['raw'] = array();
|
||||
$BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw'];
|
||||
|
||||
$BlockTypeText_raw['offset'] = $BlockOffset;
|
||||
$BlockTypeText_raw['last_meta_block'] = $LastBlockFlag;
|
||||
$BlockTypeText_raw['block_type'] = $BlockType;
|
||||
$BlockTypeText_raw['block_type_text'] = $BlockTypeText;
|
||||
$BlockTypeText_raw['block_length'] = $BlockLength;
|
||||
if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically
|
||||
$BlockTypeText_raw['block_data'] = $this->fread($BlockLength);
|
||||
}
|
||||
|
||||
switch ($BlockTypeText) {
|
||||
case 'STREAMINFO': // 0x00
|
||||
if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PADDING': // 0x01
|
||||
unset($info['flac']['PADDING']); // ignore
|
||||
break;
|
||||
|
||||
case 'APPLICATION': // 0x02
|
||||
if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'SEEKTABLE': // 0x03
|
||||
if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'VORBIS_COMMENT': // 0x04
|
||||
if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'CUESHEET': // 0x05
|
||||
if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PICTURE': // 0x06
|
||||
if (!$this->parsePICTURE()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset);
|
||||
}
|
||||
|
||||
unset($info['flac'][$BlockTypeText]['raw']);
|
||||
$info['avdataoffset'] = $this->ftell();
|
||||
}
|
||||
while ($LastBlockFlag === false);
|
||||
|
||||
// handle tags
|
||||
if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) {
|
||||
$info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments'];
|
||||
}
|
||||
if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) {
|
||||
$info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']);
|
||||
}
|
||||
|
||||
// copy attachments to 'comments' array if nesesary
|
||||
if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) {
|
||||
foreach ($info['flac']['PICTURE'] as $entry) {
|
||||
if (!empty($entry['data'])) {
|
||||
$info['flac']['comments']['picture'][] = array('image_mime'=>$entry['image_mime'], 'data'=>$entry['data']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($info['flac']['STREAMINFO'])) {
|
||||
if (!$this->isDependencyFor('matroska')) {
|
||||
$info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
|
||||
}
|
||||
$info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
|
||||
if ($info['flac']['uncompressed_audio_bytes'] == 0) {
|
||||
return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero');
|
||||
}
|
||||
if (!$this->isDependencyFor('matroska')) {
|
||||
$info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
|
||||
}
|
||||
}
|
||||
|
||||
// set md5_data_source - built into flac 0.5+
|
||||
if (isset($info['flac']['STREAMINFO']['audio_signature'])) {
|
||||
|
||||
if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
|
||||
$this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
|
||||
}
|
||||
else {
|
||||
$info['md5_data_source'] = '';
|
||||
$md5 = $info['flac']['STREAMINFO']['audio_signature'];
|
||||
for ($i = 0; $i < strlen($md5); $i++) {
|
||||
$info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
|
||||
}
|
||||
if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
|
||||
unset($info['md5_data_source']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) {
|
||||
$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
|
||||
if ($info['audio']['bits_per_sample'] == 8) {
|
||||
// special case
|
||||
// must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
|
||||
// MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
|
||||
$this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function parseSTREAMINFO($BlockData) {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['flac']['STREAMINFO'] = array();
|
||||
$streaminfo = &$info['flac']['STREAMINFO'];
|
||||
|
||||
$streaminfo['min_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2));
|
||||
$streaminfo['max_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2));
|
||||
$streaminfo['min_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3));
|
||||
$streaminfo['max_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3));
|
||||
|
||||
$SRCSBSS = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8));
|
||||
$streaminfo['sample_rate'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 0, 20));
|
||||
$streaminfo['channels'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1;
|
||||
$streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1;
|
||||
$streaminfo['samples_stream'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36));
|
||||
|
||||
$streaminfo['audio_signature'] = substr($BlockData, 18, 16);
|
||||
|
||||
if (!empty($streaminfo['sample_rate'])) {
|
||||
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
$info['audio']['sample_rate'] = $streaminfo['sample_rate'];
|
||||
$info['audio']['channels'] = $streaminfo['channels'];
|
||||
$info['audio']['bits_per_sample'] = $streaminfo['bits_per_sample'];
|
||||
$info['playtime_seconds'] = $streaminfo['samples_stream'] / $streaminfo['sample_rate'];
|
||||
if ($info['playtime_seconds'] > 0) {
|
||||
if (!$this->isDependencyFor('matroska')) {
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
}
|
||||
else {
|
||||
$this->warning('Cannot determine audio bitrate because total stream size is unknown');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return $this->error('Corrupt METAdata block: STREAMINFO');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function parseAPPLICATION($BlockData) {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4));
|
||||
$info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID);
|
||||
$info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function parseSEEKTABLE($BlockData) {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$offset = 0;
|
||||
$BlockLength = strlen($BlockData);
|
||||
$placeholderpattern = str_repeat("\xFF", 8);
|
||||
while ($offset < $BlockLength) {
|
||||
$SampleNumberString = substr($BlockData, $offset, 8);
|
||||
$offset += 8;
|
||||
if ($SampleNumberString == $placeholderpattern) {
|
||||
|
||||
// placeholder point
|
||||
getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
|
||||
$offset += 10;
|
||||
|
||||
} else {
|
||||
|
||||
$SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString);
|
||||
$info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
|
||||
$offset += 8;
|
||||
$info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2));
|
||||
$offset += 2;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function parseVORBIS_COMMENT($BlockData) {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$getid3_ogg = new getid3_ogg($this->getid3);
|
||||
if ($this->isDependencyFor('matroska')) {
|
||||
$getid3_ogg->setStringMode($this->data_string);
|
||||
}
|
||||
$getid3_ogg->ParseVorbisComments();
|
||||
if (isset($info['ogg'])) {
|
||||
unset($info['ogg']['comments_raw']);
|
||||
$info['flac']['VORBIS_COMMENT'] = $info['ogg'];
|
||||
unset($info['ogg']);
|
||||
}
|
||||
|
||||
unset($getid3_ogg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function parseCUESHEET($BlockData) {
|
||||
$info = &$this->getid3->info;
|
||||
$offset = 0;
|
||||
$info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($BlockData, $offset, 128), "\0");
|
||||
$offset += 128;
|
||||
$info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
|
||||
$offset += 8;
|
||||
$info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80);
|
||||
$offset += 1;
|
||||
|
||||
$offset += 258; // reserved
|
||||
|
||||
$info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
|
||||
$TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
|
||||
$offset += 8;
|
||||
$TrackNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset;
|
||||
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($BlockData, $offset, 12);
|
||||
$offset += 12;
|
||||
|
||||
$TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
|
||||
$offset += 1;
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80);
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);
|
||||
|
||||
$offset += 13; // reserved
|
||||
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
|
||||
$IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
|
||||
$offset += 8;
|
||||
$IndexNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
$offset += 3; // reserved
|
||||
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse METADATA_BLOCK_PICTURE flac structure and extract attachment
|
||||
* External usage: audio.ogg
|
||||
*/
|
||||
public function parsePICTURE() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$picture['type'] = self::pictureTypeLookup($picture['typeid']);
|
||||
$picture['image_mime'] = $this->fread(getid3_lib::BigEndian2Int($this->fread(4)));
|
||||
$descr_length = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
if ($descr_length) {
|
||||
$picture['description'] = $this->fread($descr_length);
|
||||
}
|
||||
$picture['width'] = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$picture['height'] = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$picture['color_depth'] = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$data_length = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
|
||||
if ($picture['image_mime'] == '-->') {
|
||||
$picture['data'] = $this->fread($data_length);
|
||||
} else {
|
||||
$picture['data'] = $this->saveAttachment(
|
||||
$picture['type'].'_'.$this->ftell(),
|
||||
$this->ftell(),
|
||||
$data_length,
|
||||
$picture['image_mime']);
|
||||
}
|
||||
|
||||
$info['flac']['PICTURE'][] = $picture;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function metaBlockTypeLookup($blocktype) {
|
||||
static $lookup = array(
|
||||
0 => 'STREAMINFO',
|
||||
1 => 'PADDING',
|
||||
2 => 'APPLICATION',
|
||||
3 => 'SEEKTABLE',
|
||||
4 => 'VORBIS_COMMENT',
|
||||
5 => 'CUESHEET',
|
||||
6 => 'PICTURE',
|
||||
);
|
||||
return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved');
|
||||
}
|
||||
|
||||
public static function applicationIDLookup($applicationid) {
|
||||
// http://flac.sourceforge.net/id.html
|
||||
static $lookup = array(
|
||||
0x41544348 => 'FlacFile', // "ATCH"
|
||||
0x42534F4C => 'beSolo', // "BSOL"
|
||||
0x42554753 => 'Bugs Player', // "BUGS"
|
||||
0x43756573 => 'GoldWave cue points (specification)', // "Cues"
|
||||
0x46696361 => 'CUE Splitter', // "Fica"
|
||||
0x46746F6C => 'flac-tools', // "Ftol"
|
||||
0x4D4F5442 => 'MOTB MetaCzar', // "MOTB"
|
||||
0x4D505345 => 'MP3 Stream Editor', // "MPSE"
|
||||
0x4D754D4C => 'MusicML: Music Metadata Language', // "MuML"
|
||||
0x52494646 => 'Sound Devices RIFF chunk storage', // "RIFF"
|
||||
0x5346464C => 'Sound Font FLAC', // "SFFL"
|
||||
0x534F4E59 => 'Sony Creative Software', // "SONY"
|
||||
0x5351455A => 'flacsqueeze', // "SQEZ"
|
||||
0x54745776 => 'TwistedWave', // "TtWv"
|
||||
0x55495453 => 'UITS Embedding tools', // "UITS"
|
||||
0x61696666 => 'FLAC AIFF chunk storage', // "aiff"
|
||||
0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks', // "imag"
|
||||
0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // "peem"
|
||||
0x71667374 => 'QFLAC Studio', // "qfst"
|
||||
0x72696666 => 'FLAC RIFF chunk storage', // "riff"
|
||||
0x74756E65 => 'TagTuner', // "tune"
|
||||
0x78626174 => 'XBAT', // "xbat"
|
||||
0x786D6364 => 'xmcd', // "xmcd"
|
||||
);
|
||||
return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved');
|
||||
}
|
||||
|
||||
public static function pictureTypeLookup($type_id) {
|
||||
static $lookup = array (
|
||||
0 => 'Other',
|
||||
1 => '32x32 pixels \'file icon\' (PNG only)',
|
||||
2 => 'Other file icon',
|
||||
3 => 'Cover (front)',
|
||||
4 => 'Cover (back)',
|
||||
5 => 'Leaflet page',
|
||||
6 => 'Media (e.g. label side of CD)',
|
||||
7 => 'Lead artist/lead performer/soloist',
|
||||
8 => 'Artist/performer',
|
||||
9 => 'Conductor',
|
||||
10 => 'Band/Orchestra',
|
||||
11 => 'Composer',
|
||||
12 => 'Lyricist/text writer',
|
||||
13 => 'Recording Location',
|
||||
14 => 'During recording',
|
||||
15 => 'During performance',
|
||||
16 => 'Movie/video screen capture',
|
||||
17 => 'A bright coloured fish',
|
||||
18 => 'Illustration',
|
||||
19 => 'Band/artist logotype',
|
||||
20 => 'Publisher/Studio logotype',
|
||||
);
|
||||
return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
|
||||
}
|
||||
|
||||
}
|
225
api/getID3/module.audio.la.php
Executable file
@ -0,0 +1,225 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.la.php //
|
||||
// module for analyzing LA (LosslessAudio) audio files //
|
||||
// dependencies: module.audio.riff.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||
|
||||
class getid3_la extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$offset = 0;
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$rawdata = fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||
|
||||
switch (substr($rawdata, $offset, 4)) {
|
||||
case 'LA02':
|
||||
case 'LA03':
|
||||
case 'LA04':
|
||||
$info['fileformat'] = 'la';
|
||||
$info['audio']['dataformat'] = 'la';
|
||||
$info['audio']['lossless'] = true;
|
||||
|
||||
$info['la']['version_major'] = (int) substr($rawdata, $offset + 2, 1);
|
||||
$info['la']['version_minor'] = (int) substr($rawdata, $offset + 3, 1);
|
||||
$info['la']['version'] = (float) $info['la']['version_major'] + ($info['la']['version_minor'] / 10);
|
||||
$offset += 4;
|
||||
|
||||
$info['la']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||
$offset += 4;
|
||||
if ($info['la']['uncompressed_size'] == 0) {
|
||||
$info['error'][] = 'Corrupt LA file: uncompressed_size == zero';
|
||||
return false;
|
||||
}
|
||||
|
||||
$WAVEchunk = substr($rawdata, $offset, 4);
|
||||
if ($WAVEchunk !== 'WAVE') {
|
||||
$info['error'][] = 'Expected "WAVE" ('.getid3_lib::PrintHexBytes('WAVE').') at offset '.$offset.', found "'.$WAVEchunk.'" ('.getid3_lib::PrintHexBytes($WAVEchunk).') instead.';
|
||||
return false;
|
||||
}
|
||||
$offset += 4;
|
||||
|
||||
$info['la']['fmt_size'] = 24;
|
||||
if ($info['la']['version'] >= 0.3) {
|
||||
|
||||
$info['la']['fmt_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||
$info['la']['header_size'] = 49 + $info['la']['fmt_size'] - 24;
|
||||
$offset += 4;
|
||||
|
||||
} else {
|
||||
|
||||
// version 0.2 didn't support additional data blocks
|
||||
$info['la']['header_size'] = 41;
|
||||
|
||||
}
|
||||
|
||||
$fmt_chunk = substr($rawdata, $offset, 4);
|
||||
if ($fmt_chunk !== 'fmt ') {
|
||||
$info['error'][] = 'Expected "fmt " ('.getid3_lib::PrintHexBytes('fmt ').') at offset '.$offset.', found "'.$fmt_chunk.'" ('.getid3_lib::PrintHexBytes($fmt_chunk).') instead.';
|
||||
return false;
|
||||
}
|
||||
$offset += 4;
|
||||
$fmt_size = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
$info['la']['raw']['format'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
|
||||
$offset += 2;
|
||||
|
||||
$info['la']['channels'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
|
||||
$offset += 2;
|
||||
if ($info['la']['channels'] == 0) {
|
||||
$info['error'][] = 'Corrupt LA file: channels == zero';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['la']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||
$offset += 4;
|
||||
if ($info['la']['sample_rate'] == 0) {
|
||||
$info['error'][] = 'Corrupt LA file: sample_rate == zero';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['la']['bytes_per_second'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||
$offset += 4;
|
||||
$info['la']['bytes_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
|
||||
$offset += 2;
|
||||
$info['la']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
|
||||
$offset += 2;
|
||||
|
||||
$info['la']['samples'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
$info['la']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 1));
|
||||
$offset += 1;
|
||||
$info['la']['flags']['seekable'] = (bool) ($info['la']['raw']['flags'] & 0x01);
|
||||
if ($info['la']['version'] >= 0.4) {
|
||||
$info['la']['flags']['high_compression'] = (bool) ($info['la']['raw']['flags'] & 0x02);
|
||||
}
|
||||
|
||||
$info['la']['original_crc'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
// mikeØbevin*de
|
||||
// Basically, the blocksize/seekevery are 61440/19 in La0.4 and 73728/16
|
||||
// in earlier versions. A seekpoint is added every blocksize * seekevery
|
||||
// samples, so 4 * int(totalSamples / (blockSize * seekEvery)) should
|
||||
// give the number of bytes used for the seekpoints. Of course, if seeking
|
||||
// is disabled, there are no seekpoints stored.
|
||||
if ($info['la']['version'] >= 0.4) {
|
||||
$info['la']['blocksize'] = 61440;
|
||||
$info['la']['seekevery'] = 19;
|
||||
} else {
|
||||
$info['la']['blocksize'] = 73728;
|
||||
$info['la']['seekevery'] = 16;
|
||||
}
|
||||
|
||||
$info['la']['seekpoint_count'] = 0;
|
||||
if ($info['la']['flags']['seekable']) {
|
||||
$info['la']['seekpoint_count'] = floor($info['la']['samples'] / ($info['la']['blocksize'] * $info['la']['seekevery']));
|
||||
|
||||
for ($i = 0; $i < $info['la']['seekpoint_count']; $i++) {
|
||||
$info['la']['seekpoints'][] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||
$offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
if ($info['la']['version'] >= 0.3) {
|
||||
|
||||
// Following the main header information, the program outputs all of the
|
||||
// seekpoints. Following these is what I called the 'footer start',
|
||||
// i.e. the position immediately after the La audio data is finished.
|
||||
$info['la']['footerstart'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
if ($info['la']['footerstart'] > $info['filesize']) {
|
||||
$info['warning'][] = 'FooterStart value points to offset '.$info['la']['footerstart'].' which is beyond end-of-file ('.$info['filesize'].')';
|
||||
$info['la']['footerstart'] = $info['filesize'];
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// La v0.2 didn't have FooterStart value
|
||||
$info['la']['footerstart'] = $info['avdataend'];
|
||||
|
||||
}
|
||||
|
||||
if ($info['la']['footerstart'] < $info['avdataend']) {
|
||||
if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) {
|
||||
if ($RIFF_fp = fopen($RIFFtempfilename, 'w+b')) {
|
||||
$RIFFdata = 'WAVE';
|
||||
if ($info['la']['version'] == 0.2) {
|
||||
$RIFFdata .= substr($rawdata, 12, 24);
|
||||
} else {
|
||||
$RIFFdata .= substr($rawdata, 16, 24);
|
||||
}
|
||||
if ($info['la']['footerstart'] < $info['avdataend']) {
|
||||
fseek($this->getid3->fp, $info['la']['footerstart'], SEEK_SET);
|
||||
$RIFFdata .= fread($this->getid3->fp, $info['avdataend'] - $info['la']['footerstart']);
|
||||
}
|
||||
$RIFFdata = 'RIFF'.getid3_lib::LittleEndian2String(strlen($RIFFdata), 4, false).$RIFFdata;
|
||||
fwrite($RIFF_fp, $RIFFdata, strlen($RIFFdata));
|
||||
fclose($RIFF_fp);
|
||||
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($RIFFtempfilename);
|
||||
$getid3_riff = new getid3_riff($getid3_temp);
|
||||
$getid3_riff->Analyze();
|
||||
|
||||
if (empty($getid3_temp->info['error'])) {
|
||||
$info['riff'] = $getid3_temp->info['riff'];
|
||||
} else {
|
||||
$info['warning'][] = 'Error parsing RIFF portion of La file: '.implode($getid3_temp->info['error']);
|
||||
}
|
||||
unset($getid3_temp, $getid3_riff);
|
||||
}
|
||||
unlink($RIFFtempfilename);
|
||||
}
|
||||
}
|
||||
|
||||
// $info['avdataoffset'] should be zero to begin with, but just in case it's not, include the addition anyway
|
||||
$info['avdataend'] = $info['avdataoffset'] + $info['la']['footerstart'];
|
||||
$info['avdataoffset'] = $info['avdataoffset'] + $offset;
|
||||
|
||||
$info['la']['compression_ratio'] = (float) (($info['avdataend'] - $info['avdataoffset']) / $info['la']['uncompressed_size']);
|
||||
$info['playtime_seconds'] = (float) ($info['la']['samples'] / $info['la']['sample_rate']) / $info['la']['channels'];
|
||||
if ($info['playtime_seconds'] == 0) {
|
||||
$info['error'][] = 'Corrupt LA file: playtime_seconds == zero';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds'];
|
||||
//$info['audio']['codec'] = $info['la']['codec'];
|
||||
$info['audio']['bits_per_sample'] = $info['la']['bits_per_sample'];
|
||||
break;
|
||||
|
||||
default:
|
||||
if (substr($rawdata, $offset, 2) == 'LA') {
|
||||
$info['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.';
|
||||
} else {
|
||||
$info['error'][] = 'Not a LA (Lossless-Audio) file';
|
||||
}
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
$info['audio']['channels'] = $info['la']['channels'];
|
||||
$info['audio']['sample_rate'] = (int) $info['la']['sample_rate'];
|
||||
$info['audio']['encoder'] = 'LA v'.$info['la']['version'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
127
api/getID3/module.audio.lpac.php
Executable file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.lpac.php //
|
||||
// module for analyzing LPAC Audio files //
|
||||
// dependencies: module.audio-video.riff.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||
|
||||
class getid3_lpac extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$LPACheader = fread($this->getid3->fp, 14);
|
||||
if (substr($LPACheader, 0, 4) != 'LPAC') {
|
||||
$info['error'][] = 'Expected "LPAC" at offset '.$info['avdataoffset'].', found "'.$StreamMarker.'"';
|
||||
return false;
|
||||
}
|
||||
$info['avdataoffset'] += 14;
|
||||
|
||||
$info['fileformat'] = 'lpac';
|
||||
$info['audio']['dataformat'] = 'lpac';
|
||||
$info['audio']['lossless'] = true;
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
|
||||
$info['lpac']['file_version'] = getid3_lib::BigEndian2Int(substr($LPACheader, 4, 1));
|
||||
$flags['audio_type'] = getid3_lib::BigEndian2Int(substr($LPACheader, 5, 1));
|
||||
$info['lpac']['total_samples']= getid3_lib::BigEndian2Int(substr($LPACheader, 6, 4));
|
||||
$flags['parameters'] = getid3_lib::BigEndian2Int(substr($LPACheader, 10, 4));
|
||||
|
||||
$info['lpac']['flags']['is_wave'] = (bool) ($flags['audio_type'] & 0x40);
|
||||
$info['lpac']['flags']['stereo'] = (bool) ($flags['audio_type'] & 0x04);
|
||||
$info['lpac']['flags']['24_bit'] = (bool) ($flags['audio_type'] & 0x02);
|
||||
$info['lpac']['flags']['16_bit'] = (bool) ($flags['audio_type'] & 0x01);
|
||||
|
||||
if ($info['lpac']['flags']['24_bit'] && $info['lpac']['flags']['16_bit']) {
|
||||
$info['warning'][] = '24-bit and 16-bit flags cannot both be set';
|
||||
}
|
||||
|
||||
$info['lpac']['flags']['fast_compress'] = (bool) ($flags['parameters'] & 0x40000000);
|
||||
$info['lpac']['flags']['random_access'] = (bool) ($flags['parameters'] & 0x08000000);
|
||||
$info['lpac']['block_length'] = pow(2, (($flags['parameters'] & 0x07000000) >> 24)) * 256;
|
||||
$info['lpac']['flags']['adaptive_prediction_order'] = (bool) ($flags['parameters'] & 0x00800000);
|
||||
$info['lpac']['flags']['adaptive_quantization'] = (bool) ($flags['parameters'] & 0x00400000);
|
||||
$info['lpac']['flags']['joint_stereo'] = (bool) ($flags['parameters'] & 0x00040000);
|
||||
$info['lpac']['quantization'] = ($flags['parameters'] & 0x00001F00) >> 8;
|
||||
$info['lpac']['max_prediction_order'] = ($flags['parameters'] & 0x0000003F);
|
||||
|
||||
if ($info['lpac']['flags']['fast_compress'] && ($info['lpac']['max_prediction_order'] != 3)) {
|
||||
$info['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']) {
|
||||
$info['warning'][] = 'adaptive_quantization expected to be false in LPAC file stucture v6, actually true';
|
||||
}
|
||||
if ($info['lpac']['quantization'] != 20) {
|
||||
$info['warning'][] = 'Quantization expected to be 20 in LPAC file stucture v6, actually '.$info['lpac']['flags']['Q'];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
//$info['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';
|
||||
break;
|
||||
}
|
||||
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($this->getid3->filename);
|
||||
$getid3_temp->info = $info;
|
||||
$getid3_riff = new getid3_riff($getid3_temp);
|
||||
$getid3_riff->Analyze();
|
||||
$info['avdataoffset'] = $getid3_temp->info['avdataoffset'];
|
||||
$info['riff'] = $getid3_temp->info['riff'];
|
||||
$info['error'] = $getid3_temp->info['error'];
|
||||
$info['warning'] = $getid3_temp->info['warning'];
|
||||
$info['lpac']['comments']['comment'] = $getid3_temp->info['comments'];
|
||||
$info['audio']['sample_rate'] = $getid3_temp->info['audio']['sample_rate'];
|
||||
unset($getid3_temp, $getid3_riff);
|
||||
|
||||
$info['audio']['channels'] = ($info['lpac']['flags']['stereo'] ? 2 : 1);
|
||||
|
||||
if ($info['lpac']['flags']['24_bit']) {
|
||||
$info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample'];
|
||||
} elseif ($info['lpac']['flags']['16_bit']) {
|
||||
$info['audio']['bits_per_sample'] = 16;
|
||||
} else {
|
||||
$info['audio']['bits_per_sample'] = 8;
|
||||
}
|
||||
|
||||
if ($info['lpac']['flags']['fast_compress']) {
|
||||
// fast
|
||||
$info['audio']['encoder_options'] = '-1';
|
||||
} else {
|
||||
switch ($info['lpac']['max_prediction_order']) {
|
||||
case 20: // simple
|
||||
$info['audio']['encoder_options'] = '-2';
|
||||
break;
|
||||
case 30: // medium
|
||||
$info['audio']['encoder_options'] = '-3';
|
||||
break;
|
||||
case 40: // high
|
||||
$info['audio']['encoder_options'] = '-4';
|
||||
break;
|
||||
case 60: // extrahigh
|
||||
$info['audio']['encoder_options'] = '-5';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$info['playtime_seconds'] = $info['lpac']['total_samples'] / $info['audio']['sample_rate'];
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
523
api/getID3/module.audio.midi.php
Executable file
@ -0,0 +1,523 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.midi.php //
|
||||
// module for Midi Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
define('GETID3_MIDI_MAGIC_MTHD', 'MThd'); // MIDI file header magic
|
||||
define('GETID3_MIDI_MAGIC_MTRK', 'MTrk'); // MIDI track header magic
|
||||
|
||||
class getid3_midi extends getid3_handler
|
||||
{
|
||||
public $scanwholefile = true;
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// shortcut
|
||||
$info['midi']['raw'] = array();
|
||||
$thisfile_midi = &$info['midi'];
|
||||
$thisfile_midi_raw = &$thisfile_midi['raw'];
|
||||
|
||||
$info['fileformat'] = 'midi';
|
||||
$info['audio']['dataformat'] = 'midi';
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$MIDIdata = fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||
$offset = 0;
|
||||
$MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd'
|
||||
if ($MIDIheaderID != GETID3_MIDI_MAGIC_MTHD) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTHD).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($MIDIheaderID).'"';
|
||||
unset($info['fileformat']);
|
||||
return false;
|
||||
}
|
||||
$offset += 4;
|
||||
$thisfile_midi_raw['headersize'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_midi_raw['fileformat'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_midi_raw['tracks'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_midi_raw['ticksperqnote'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
|
||||
$offset += 2;
|
||||
|
||||
for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) {
|
||||
while ((strlen($MIDIdata) - $offset) < 8) {
|
||||
$MIDIdata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||
}
|
||||
$trackID = substr($MIDIdata, $offset, 4);
|
||||
$offset += 4;
|
||||
if ($trackID == GETID3_MIDI_MAGIC_MTRK) {
|
||||
$tracksize = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
|
||||
$offset += 4;
|
||||
// $thisfile_midi['tracks'][$i]['size'] = $tracksize;
|
||||
$trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize);
|
||||
$offset += $tracksize;
|
||||
} else {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTRK).'" at '.($offset - 4).', found "'.getid3_lib::PrintHexBytes($trackID).'" instead';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($trackdataarray) || !is_array($trackdataarray)) {
|
||||
$info['error'][] = 'Cannot find MIDI track information';
|
||||
unset($thisfile_midi);
|
||||
unset($info['fileformat']);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important
|
||||
$thisfile_midi['totalticks'] = 0;
|
||||
$info['playtime_seconds'] = 0;
|
||||
$CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
|
||||
$CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
|
||||
$MicroSecondsPerQuarterNoteAfter = array ();
|
||||
|
||||
foreach ($trackdataarray as $tracknumber => $trackdata) {
|
||||
|
||||
$eventsoffset = 0;
|
||||
$LastIssuedMIDIcommand = 0;
|
||||
$LastIssuedMIDIchannel = 0;
|
||||
$CumulativeDeltaTime = 0;
|
||||
$TicksAtCurrentBPM = 0;
|
||||
while ($eventsoffset < strlen($trackdata)) {
|
||||
$eventid = 0;
|
||||
if (isset($MIDIevents[$tracknumber]) && is_array($MIDIevents[$tracknumber])) {
|
||||
$eventid = count($MIDIevents[$tracknumber]);
|
||||
}
|
||||
$deltatime = 0;
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$deltatimebyte = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
$deltatime = ($deltatime << 7) + ($deltatimebyte & 0x7F);
|
||||
if ($deltatimebyte & 0x80) {
|
||||
// another byte follows
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$CumulativeDeltaTime += $deltatime;
|
||||
$TicksAtCurrentBPM += $deltatime;
|
||||
$MIDIevents[$tracknumber][$eventid]['deltatime'] = $deltatime;
|
||||
$MIDI_event_channel = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
if ($MIDI_event_channel & 0x80) {
|
||||
// OK, normal event - MIDI command has MSB set
|
||||
$LastIssuedMIDIcommand = $MIDI_event_channel >> 4;
|
||||
$LastIssuedMIDIchannel = $MIDI_event_channel & 0x0F;
|
||||
} else {
|
||||
// running event - assume last command
|
||||
$eventsoffset--;
|
||||
}
|
||||
$MIDIevents[$tracknumber][$eventid]['eventid'] = $LastIssuedMIDIcommand;
|
||||
$MIDIevents[$tracknumber][$eventid]['channel'] = $LastIssuedMIDIchannel;
|
||||
if ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x08) { // Note off (key is released)
|
||||
|
||||
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
|
||||
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x09) { // Note on (key is pressed)
|
||||
|
||||
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
|
||||
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0A) { // Key after-touch
|
||||
|
||||
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
|
||||
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0B) { // Control Change
|
||||
|
||||
$controllernum = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
$newvalue = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
|
||||
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0C) { // Program (patch) change
|
||||
|
||||
$newprogramnum = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
|
||||
$thisfile_midi_raw['track'][$tracknumber]['instrumentid'] = $newprogramnum;
|
||||
if ($tracknumber == 10) {
|
||||
$thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIpercussionLookup($newprogramnum);
|
||||
} else {
|
||||
$thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIinstrumentLookup($newprogramnum);
|
||||
}
|
||||
|
||||
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0D) { // Channel after-touch
|
||||
|
||||
$channelnumber = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
|
||||
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0E) { // Pitch wheel change (2000H is normal or no change)
|
||||
|
||||
$changeLSB = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
$changeMSB = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
$pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F);
|
||||
|
||||
} elseif (($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0F) && ($MIDIevents[$tracknumber][$eventid]['channel'] == 0x0F)) {
|
||||
|
||||
$METAeventCommand = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
$METAeventLength = ord(substr($trackdata, $eventsoffset++, 1));
|
||||
$METAeventData = substr($trackdata, $eventsoffset, $METAeventLength);
|
||||
$eventsoffset += $METAeventLength;
|
||||
switch ($METAeventCommand) {
|
||||
case 0x00: // Set track sequence number
|
||||
$track_sequence_number = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['seqno'] = $track_sequence_number;
|
||||
break;
|
||||
|
||||
case 0x01: // Text: generic
|
||||
$text_generic = substr($METAeventData, 0, $METAeventLength);
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['text'] = $text_generic;
|
||||
$thisfile_midi['comments']['comment'][] = $text_generic;
|
||||
break;
|
||||
|
||||
case 0x02: // Text: copyright
|
||||
$text_copyright = substr($METAeventData, 0, $METAeventLength);
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['copyright'] = $text_copyright;
|
||||
$thisfile_midi['comments']['copyright'][] = $text_copyright;
|
||||
break;
|
||||
|
||||
case 0x03: // Text: track name
|
||||
$text_trackname = substr($METAeventData, 0, $METAeventLength);
|
||||
$thisfile_midi_raw['track'][$tracknumber]['name'] = $text_trackname;
|
||||
break;
|
||||
|
||||
case 0x04: // Text: track instrument name
|
||||
$text_instrument = substr($METAeventData, 0, $METAeventLength);
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['instrument'] = $text_instrument;
|
||||
break;
|
||||
|
||||
case 0x05: // Text: lyrics
|
||||
$text_lyrics = substr($METAeventData, 0, $METAeventLength);
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['lyrics'] = $text_lyrics;
|
||||
if (!isset($thisfile_midi['lyrics'])) {
|
||||
$thisfile_midi['lyrics'] = '';
|
||||
}
|
||||
$thisfile_midi['lyrics'] .= $text_lyrics."\n";
|
||||
break;
|
||||
|
||||
case 0x06: // Text: marker
|
||||
$text_marker = substr($METAeventData, 0, $METAeventLength);
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['marker'] = $text_marker;
|
||||
break;
|
||||
|
||||
case 0x07: // Text: cue point
|
||||
$text_cuepoint = substr($METAeventData, 0, $METAeventLength);
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['cuepoint'] = $text_cuepoint;
|
||||
break;
|
||||
|
||||
case 0x2F: // End Of Track
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['EOT'] = $CumulativeDeltaTime;
|
||||
break;
|
||||
|
||||
case 0x51: // Tempo: microseconds / quarter note
|
||||
$CurrentMicroSecondsPerBeat = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
|
||||
if ($CurrentMicroSecondsPerBeat == 0) {
|
||||
$info['error'][] = 'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero';
|
||||
return false;
|
||||
}
|
||||
$thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat;
|
||||
$CurrentBeatsPerMinute = (1000000 / $CurrentMicroSecondsPerBeat) * 60;
|
||||
$MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat;
|
||||
$TicksAtCurrentBPM = 0;
|
||||
break;
|
||||
|
||||
case 0x58: // Time signature
|
||||
$timesig_numerator = getid3_lib::BigEndian2Int($METAeventData{0});
|
||||
$timesig_denominator = pow(2, getid3_lib::BigEndian2Int($METAeventData{1})); // $02 -> x/4, $03 -> x/8, etc
|
||||
$timesig_32inqnote = getid3_lib::BigEndian2Int($METAeventData{2}); // number of 32nd notes to the quarter note
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_32inqnote'] = $timesig_32inqnote;
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_numerator'] = $timesig_numerator;
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_denominator'] = $timesig_denominator;
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_text'] = $timesig_numerator.'/'.$timesig_denominator;
|
||||
$thisfile_midi['timesignature'][] = $timesig_numerator.'/'.$timesig_denominator;
|
||||
break;
|
||||
|
||||
case 0x59: // Keysignature
|
||||
$keysig_sharpsflats = getid3_lib::BigEndian2Int($METAeventData{0});
|
||||
if ($keysig_sharpsflats & 0x80) {
|
||||
// (-7 -> 7 flats, 0 ->key of C, 7 -> 7 sharps)
|
||||
$keysig_sharpsflats -= 256;
|
||||
}
|
||||
|
||||
$keysig_majorminor = getid3_lib::BigEndian2Int($METAeventData{1}); // 0 -> major, 1 -> minor
|
||||
$keysigs = array(-7=>'Cb', -6=>'Gb', -5=>'Db', -4=>'Ab', -3=>'Eb', -2=>'Bb', -1=>'F', 0=>'C', 1=>'G', 2=>'D', 3=>'A', 4=>'E', 5=>'B', 6=>'F#', 7=>'C#');
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_sharps'] = (($keysig_sharpsflats > 0) ? abs($keysig_sharpsflats) : 0);
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_flats'] = (($keysig_sharpsflats < 0) ? abs($keysig_sharpsflats) : 0);
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] = (bool) $keysig_majorminor;
|
||||
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_text'] = $keysigs[$keysig_sharpsflats].' '.($thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] ? 'minor' : 'major');
|
||||
|
||||
// $keysigs[$keysig_sharpsflats] gets an int key (correct) - $keysigs["$keysig_sharpsflats"] gets a string key (incorrect)
|
||||
$thisfile_midi['keysignature'][] = $keysigs[$keysig_sharpsflats].' '.((bool) $keysig_majorminor ? 'minor' : 'major');
|
||||
break;
|
||||
|
||||
case 0x7F: // Sequencer specific information
|
||||
$custom_data = substr($METAeventData, 0, $METAeventLength);
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['warning'][] = 'Unhandled META Event Command: '.$METAeventCommand;
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$info['warning'][] = 'Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel'];
|
||||
|
||||
}
|
||||
}
|
||||
if (($tracknumber > 0) || (count($trackdataarray) == 1)) {
|
||||
$thisfile_midi['totalticks'] = max($thisfile_midi['totalticks'], $CumulativeDeltaTime);
|
||||
}
|
||||
}
|
||||
$previoustickoffset = null;
|
||||
|
||||
ksort($MicroSecondsPerQuarterNoteAfter);
|
||||
foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) {
|
||||
if (is_null($previoustickoffset)) {
|
||||
$prevmicrosecondsperbeat = $microsecondsperbeat;
|
||||
$previoustickoffset = $tickoffset;
|
||||
continue;
|
||||
}
|
||||
if ($thisfile_midi['totalticks'] > $tickoffset) {
|
||||
|
||||
if ($thisfile_midi_raw['ticksperqnote'] == 0) {
|
||||
$info['error'][] = 'Corrupt MIDI file: ticksperqnote == zero';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000);
|
||||
|
||||
$prevmicrosecondsperbeat = $microsecondsperbeat;
|
||||
$previoustickoffset = $tickoffset;
|
||||
}
|
||||
}
|
||||
if ($thisfile_midi['totalticks'] > $previoustickoffset) {
|
||||
|
||||
if ($thisfile_midi_raw['ticksperqnote'] == 0) {
|
||||
$info['error'][] = 'Corrupt MIDI file: ticksperqnote == zero';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($microsecondsperbeat / 1000000);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!empty($info['playtime_seconds'])) {
|
||||
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
}
|
||||
|
||||
if (!empty($thisfile_midi['lyrics'])) {
|
||||
$thisfile_midi['comments']['lyrics'][] = $thisfile_midi['lyrics'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function GeneralMIDIinstrumentLookup($instrumentid) {
|
||||
|
||||
$begin = __LINE__;
|
||||
|
||||
/** This is not a comment!
|
||||
|
||||
0 Acoustic Grand
|
||||
1 Bright Acoustic
|
||||
2 Electric Grand
|
||||
3 Honky-Tonk
|
||||
4 Electric Piano 1
|
||||
5 Electric Piano 2
|
||||
6 Harpsichord
|
||||
7 Clavier
|
||||
8 Celesta
|
||||
9 Glockenspiel
|
||||
10 Music Box
|
||||
11 Vibraphone
|
||||
12 Marimba
|
||||
13 Xylophone
|
||||
14 Tubular Bells
|
||||
15 Dulcimer
|
||||
16 Drawbar Organ
|
||||
17 Percussive Organ
|
||||
18 Rock Organ
|
||||
19 Church Organ
|
||||
20 Reed Organ
|
||||
21 Accordian
|
||||
22 Harmonica
|
||||
23 Tango Accordian
|
||||
24 Acoustic Guitar (nylon)
|
||||
25 Acoustic Guitar (steel)
|
||||
26 Electric Guitar (jazz)
|
||||
27 Electric Guitar (clean)
|
||||
28 Electric Guitar (muted)
|
||||
29 Overdriven Guitar
|
||||
30 Distortion Guitar
|
||||
31 Guitar Harmonics
|
||||
32 Acoustic Bass
|
||||
33 Electric Bass (finger)
|
||||
34 Electric Bass (pick)
|
||||
35 Fretless Bass
|
||||
36 Slap Bass 1
|
||||
37 Slap Bass 2
|
||||
38 Synth Bass 1
|
||||
39 Synth Bass 2
|
||||
40 Violin
|
||||
41 Viola
|
||||
42 Cello
|
||||
43 Contrabass
|
||||
44 Tremolo Strings
|
||||
45 Pizzicato Strings
|
||||
46 Orchestral Strings
|
||||
47 Timpani
|
||||
48 String Ensemble 1
|
||||
49 String Ensemble 2
|
||||
50 SynthStrings 1
|
||||
51 SynthStrings 2
|
||||
52 Choir Aahs
|
||||
53 Voice Oohs
|
||||
54 Synth Voice
|
||||
55 Orchestra Hit
|
||||
56 Trumpet
|
||||
57 Trombone
|
||||
58 Tuba
|
||||
59 Muted Trumpet
|
||||
60 French Horn
|
||||
61 Brass Section
|
||||
62 SynthBrass 1
|
||||
63 SynthBrass 2
|
||||
64 Soprano Sax
|
||||
65 Alto Sax
|
||||
66 Tenor Sax
|
||||
67 Baritone Sax
|
||||
68 Oboe
|
||||
69 English Horn
|
||||
70 Bassoon
|
||||
71 Clarinet
|
||||
72 Piccolo
|
||||
73 Flute
|
||||
74 Recorder
|
||||
75 Pan Flute
|
||||
76 Blown Bottle
|
||||
77 Shakuhachi
|
||||
78 Whistle
|
||||
79 Ocarina
|
||||
80 Lead 1 (square)
|
||||
81 Lead 2 (sawtooth)
|
||||
82 Lead 3 (calliope)
|
||||
83 Lead 4 (chiff)
|
||||
84 Lead 5 (charang)
|
||||
85 Lead 6 (voice)
|
||||
86 Lead 7 (fifths)
|
||||
87 Lead 8 (bass + lead)
|
||||
88 Pad 1 (new age)
|
||||
89 Pad 2 (warm)
|
||||
90 Pad 3 (polysynth)
|
||||
91 Pad 4 (choir)
|
||||
92 Pad 5 (bowed)
|
||||
93 Pad 6 (metallic)
|
||||
94 Pad 7 (halo)
|
||||
95 Pad 8 (sweep)
|
||||
96 FX 1 (rain)
|
||||
97 FX 2 (soundtrack)
|
||||
98 FX 3 (crystal)
|
||||
99 FX 4 (atmosphere)
|
||||
100 FX 5 (brightness)
|
||||
101 FX 6 (goblins)
|
||||
102 FX 7 (echoes)
|
||||
103 FX 8 (sci-fi)
|
||||
104 Sitar
|
||||
105 Banjo
|
||||
106 Shamisen
|
||||
107 Koto
|
||||
108 Kalimba
|
||||
109 Bagpipe
|
||||
110 Fiddle
|
||||
111 Shanai
|
||||
112 Tinkle Bell
|
||||
113 Agogo
|
||||
114 Steel Drums
|
||||
115 Woodblock
|
||||
116 Taiko Drum
|
||||
117 Melodic Tom
|
||||
118 Synth Drum
|
||||
119 Reverse Cymbal
|
||||
120 Guitar Fret Noise
|
||||
121 Breath Noise
|
||||
122 Seashore
|
||||
123 Bird Tweet
|
||||
124 Telephone Ring
|
||||
125 Helicopter
|
||||
126 Applause
|
||||
127 Gunshot
|
||||
|
||||
*/
|
||||
|
||||
return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIinstrument');
|
||||
}
|
||||
|
||||
public function GeneralMIDIpercussionLookup($instrumentid) {
|
||||
|
||||
$begin = __LINE__;
|
||||
|
||||
/** This is not a comment!
|
||||
|
||||
35 Acoustic Bass Drum
|
||||
36 Bass Drum 1
|
||||
37 Side Stick
|
||||
38 Acoustic Snare
|
||||
39 Hand Clap
|
||||
40 Electric Snare
|
||||
41 Low Floor Tom
|
||||
42 Closed Hi-Hat
|
||||
43 High Floor Tom
|
||||
44 Pedal Hi-Hat
|
||||
45 Low Tom
|
||||
46 Open Hi-Hat
|
||||
47 Low-Mid Tom
|
||||
48 Hi-Mid Tom
|
||||
49 Crash Cymbal 1
|
||||
50 High Tom
|
||||
51 Ride Cymbal 1
|
||||
52 Chinese Cymbal
|
||||
53 Ride Bell
|
||||
54 Tambourine
|
||||
55 Splash Cymbal
|
||||
56 Cowbell
|
||||
57 Crash Cymbal 2
|
||||
59 Ride Cymbal 2
|
||||
60 Hi Bongo
|
||||
61 Low Bongo
|
||||
62 Mute Hi Conga
|
||||
63 Open Hi Conga
|
||||
64 Low Conga
|
||||
65 High Timbale
|
||||
66 Low Timbale
|
||||
67 High Agogo
|
||||
68 Low Agogo
|
||||
69 Cabasa
|
||||
70 Maracas
|
||||
71 Short Whistle
|
||||
72 Long Whistle
|
||||
73 Short Guiro
|
||||
74 Long Guiro
|
||||
75 Claves
|
||||
76 Hi Wood Block
|
||||
77 Low Wood Block
|
||||
78 Mute Cuica
|
||||
79 Open Cuica
|
||||
80 Mute Triangle
|
||||
81 Open Triangle
|
||||
|
||||
*/
|
||||
|
||||
return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIpercussion');
|
||||
}
|
||||
|
||||
}
|
98
api/getID3/module.audio.mod.php
Executable file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.mod.php //
|
||||
// module for analyzing MOD Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_mod extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$fileheader = fread($this->getid3->fp, 1088);
|
||||
if (preg_match('#^IMPM#', $fileheader)) {
|
||||
return $this->getITheaderFilepointer();
|
||||
} elseif (preg_match('#^Extended Module#', $fileheader)) {
|
||||
return $this->getXMheaderFilepointer();
|
||||
} elseif (preg_match('#^.{44}SCRM#', $fileheader)) {
|
||||
return $this->getS3MheaderFilepointer();
|
||||
} elseif (preg_match('#^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)#', $fileheader)) {
|
||||
return $this->getMODheaderFilepointer();
|
||||
}
|
||||
$info['error'][] = 'This is not a known type of MOD file';
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function getMODheaderFilepointer() {
|
||||
$info = &$this->getid3->info;
|
||||
fseek($this->getid3->fp, $info['avdataoffset'] + 1080);
|
||||
$FormatID = fread($this->getid3->fp, 4);
|
||||
if (!preg_match('#^(M.K.|[5-9]CHN|[1-3][0-9]CH)$#', $FormatID)) {
|
||||
$info['error'][] = 'This is not a known type of MOD file';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['fileformat'] = 'mod';
|
||||
|
||||
$info['error'][] = 'MOD parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getXMheaderFilepointer() {
|
||||
$info = &$this->getid3->info;
|
||||
fseek($this->getid3->fp, $info['avdataoffset']);
|
||||
$FormatID = fread($this->getid3->fp, 15);
|
||||
if (!preg_match('#^Extended Module$#', $FormatID)) {
|
||||
$info['error'][] = 'This is not a known type of XM-MOD file';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['fileformat'] = 'xm';
|
||||
|
||||
$info['error'][] = 'XM-MOD parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getS3MheaderFilepointer() {
|
||||
$info = &$this->getid3->info;
|
||||
fseek($this->getid3->fp, $info['avdataoffset'] + 44);
|
||||
$FormatID = fread($this->getid3->fp, 4);
|
||||
if (!preg_match('#^SCRM$#', $FormatID)) {
|
||||
$info['error'][] = 'This is not a ScreamTracker MOD file';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['fileformat'] = 's3m';
|
||||
|
||||
$info['error'][] = 'ScreamTracker parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getITheaderFilepointer() {
|
||||
$info = &$this->getid3->info;
|
||||
fseek($this->getid3->fp, $info['avdataoffset']);
|
||||
$FormatID = fread($this->getid3->fp, 4);
|
||||
if (!preg_match('#^IMPM$#', $FormatID)) {
|
||||
$info['error'][] = 'This is not an ImpulseTracker MOD file';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['fileformat'] = 'it';
|
||||
|
||||
$info['error'][] = 'ImpulseTracker parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
203
api/getID3/module.audio.monkey.php
Executable file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.monkey.php //
|
||||
// module for analyzing Monkey's Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_monkey extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// based loosely on code from TMonkey by Jurgen Faul <jfaulØgmx*de>
|
||||
// http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html
|
||||
|
||||
$info['fileformat'] = 'mac';
|
||||
$info['audio']['dataformat'] = 'mac';
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
$info['audio']['lossless'] = true;
|
||||
|
||||
$info['monkeys_audio']['raw'] = array();
|
||||
$thisfile_monkeysaudio = &$info['monkeys_audio'];
|
||||
$thisfile_monkeysaudio_raw = &$thisfile_monkeysaudio['raw'];
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$MACheaderData = fread($this->getid3->fp, 74);
|
||||
|
||||
$thisfile_monkeysaudio_raw['magic'] = substr($MACheaderData, 0, 4);
|
||||
$magic = 'MAC ';
|
||||
if ($thisfile_monkeysaudio_raw['magic'] != $magic) {
|
||||
$info['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) {
|
||||
$thisfile_monkeysaudio_raw['nCompressionLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 6, 2));
|
||||
$thisfile_monkeysaudio_raw['nFormatFlags'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 8, 2));
|
||||
$thisfile_monkeysaudio_raw['nChannels'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 10, 2));
|
||||
$thisfile_monkeysaudio_raw['nSampleRate'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 12, 4));
|
||||
$thisfile_monkeysaudio_raw['nHeaderDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 16, 4));
|
||||
$thisfile_monkeysaudio_raw['nWAVTerminatingBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 20, 4));
|
||||
$thisfile_monkeysaudio_raw['nTotalFrames'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 24, 4));
|
||||
$thisfile_monkeysaudio_raw['nFinalFrameSamples'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 28, 4));
|
||||
$thisfile_monkeysaudio_raw['nPeakLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 32, 4));
|
||||
$thisfile_monkeysaudio_raw['nSeekElements'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 38, 2));
|
||||
$offset = 8;
|
||||
} else {
|
||||
$offset = 8;
|
||||
// APE_DESCRIPTOR
|
||||
$thisfile_monkeysaudio_raw['nDescriptorBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_monkeysaudio_raw['nHeaderBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_monkeysaudio_raw['nSeekTableBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_monkeysaudio_raw['nHeaderDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_monkeysaudio_raw['nAPEFrameDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_monkeysaudio_raw['nAPEFrameDataBytesHigh'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_monkeysaudio_raw['nTerminatingDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_monkeysaudio_raw['cFileMD5'] = substr($MACheaderData, $offset, 16);
|
||||
$offset += 16;
|
||||
|
||||
// APE_HEADER
|
||||
$thisfile_monkeysaudio_raw['nCompressionLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_monkeysaudio_raw['nFormatFlags'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_monkeysaudio_raw['nBlocksPerFrame'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_monkeysaudio_raw['nFinalFrameBlocks'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_monkeysaudio_raw['nTotalFrames'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_monkeysaudio_raw['nBitsPerSample'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_monkeysaudio_raw['nChannels'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_monkeysaudio_raw['nSampleRate'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
}
|
||||
|
||||
$thisfile_monkeysaudio['flags']['8-bit'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0001);
|
||||
$thisfile_monkeysaudio['flags']['crc-32'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0002);
|
||||
$thisfile_monkeysaudio['flags']['peak_level'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0004);
|
||||
$thisfile_monkeysaudio['flags']['24-bit'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0008);
|
||||
$thisfile_monkeysaudio['flags']['seek_elements'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0010);
|
||||
$thisfile_monkeysaudio['flags']['no_wav_header'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0020);
|
||||
$thisfile_monkeysaudio['version'] = $thisfile_monkeysaudio_raw['nVersion'] / 1000;
|
||||
$thisfile_monkeysaudio['compression'] = $this->MonkeyCompressionLevelNameLookup($thisfile_monkeysaudio_raw['nCompressionLevel']);
|
||||
if ($thisfile_monkeysaudio_raw['nVersion'] < 3980) {
|
||||
$thisfile_monkeysaudio['samples_per_frame'] = $this->MonkeySamplesPerFrame($thisfile_monkeysaudio_raw['nVersion'], $thisfile_monkeysaudio_raw['nCompressionLevel']);
|
||||
}
|
||||
$thisfile_monkeysaudio['bits_per_sample'] = ($thisfile_monkeysaudio['flags']['24-bit'] ? 24 : ($thisfile_monkeysaudio['flags']['8-bit'] ? 8 : 16));
|
||||
$thisfile_monkeysaudio['channels'] = $thisfile_monkeysaudio_raw['nChannels'];
|
||||
$info['audio']['channels'] = $thisfile_monkeysaudio['channels'];
|
||||
$thisfile_monkeysaudio['sample_rate'] = $thisfile_monkeysaudio_raw['nSampleRate'];
|
||||
if ($thisfile_monkeysaudio['sample_rate'] == 0) {
|
||||
$info['error'][] = 'Corrupt MAC file: frequency == zero';
|
||||
return false;
|
||||
}
|
||||
$info['audio']['sample_rate'] = $thisfile_monkeysaudio['sample_rate'];
|
||||
if ($thisfile_monkeysaudio['flags']['peak_level']) {
|
||||
$thisfile_monkeysaudio['peak_level'] = $thisfile_monkeysaudio_raw['nPeakLevel'];
|
||||
$thisfile_monkeysaudio['peak_ratio'] = $thisfile_monkeysaudio['peak_level'] / pow(2, $thisfile_monkeysaudio['bits_per_sample'] - 1);
|
||||
}
|
||||
if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) {
|
||||
$thisfile_monkeysaudio['samples'] = (($thisfile_monkeysaudio_raw['nTotalFrames'] - 1) * $thisfile_monkeysaudio_raw['nBlocksPerFrame']) + $thisfile_monkeysaudio_raw['nFinalFrameBlocks'];
|
||||
} else {
|
||||
$thisfile_monkeysaudio['samples'] = (($thisfile_monkeysaudio_raw['nTotalFrames'] - 1) * $thisfile_monkeysaudio['samples_per_frame']) + $thisfile_monkeysaudio_raw['nFinalFrameSamples'];
|
||||
}
|
||||
$thisfile_monkeysaudio['playtime'] = $thisfile_monkeysaudio['samples'] / $thisfile_monkeysaudio['sample_rate'];
|
||||
if ($thisfile_monkeysaudio['playtime'] == 0) {
|
||||
$info['error'][] = 'Corrupt MAC file: playtime == zero';
|
||||
return false;
|
||||
}
|
||||
$info['playtime_seconds'] = $thisfile_monkeysaudio['playtime'];
|
||||
$thisfile_monkeysaudio['compressed_size'] = $info['avdataend'] - $info['avdataoffset'];
|
||||
$thisfile_monkeysaudio['uncompressed_size'] = $thisfile_monkeysaudio['samples'] * $thisfile_monkeysaudio['channels'] * ($thisfile_monkeysaudio['bits_per_sample'] / 8);
|
||||
if ($thisfile_monkeysaudio['uncompressed_size'] == 0) {
|
||||
$info['error'][] = 'Corrupt MAC file: uncompressed_size == zero';
|
||||
return false;
|
||||
}
|
||||
$thisfile_monkeysaudio['compression_ratio'] = $thisfile_monkeysaudio['compressed_size'] / ($thisfile_monkeysaudio['uncompressed_size'] + $thisfile_monkeysaudio_raw['nHeaderDataBytes']);
|
||||
$thisfile_monkeysaudio['bitrate'] = (($thisfile_monkeysaudio['samples'] * $thisfile_monkeysaudio['channels'] * $thisfile_monkeysaudio['bits_per_sample']) / $thisfile_monkeysaudio['playtime']) * $thisfile_monkeysaudio['compression_ratio'];
|
||||
$info['audio']['bitrate'] = $thisfile_monkeysaudio['bitrate'];
|
||||
|
||||
// add size of MAC header to avdataoffset
|
||||
if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) {
|
||||
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nDescriptorBytes'];
|
||||
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderBytes'];
|
||||
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nSeekTableBytes'];
|
||||
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderDataBytes'];
|
||||
|
||||
$info['avdataend'] -= $thisfile_monkeysaudio_raw['nTerminatingDataBytes'];
|
||||
} else {
|
||||
$info['avdataoffset'] += $offset;
|
||||
}
|
||||
|
||||
if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) {
|
||||
if ($thisfile_monkeysaudio_raw['cFileMD5'] === str_repeat("\x00", 16)) {
|
||||
//$info['warning'][] = 'cFileMD5 is null';
|
||||
} else {
|
||||
$info['md5_data_source'] = '';
|
||||
$md5 = $thisfile_monkeysaudio_raw['cFileMD5'];
|
||||
for ($i = 0; $i < strlen($md5); $i++) {
|
||||
$info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT);
|
||||
}
|
||||
if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
|
||||
unset($info['md5_data_source']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
$info['audio']['bits_per_sample'] = $thisfile_monkeysaudio['bits_per_sample'];
|
||||
$info['audio']['encoder'] = 'MAC v'.number_format($thisfile_monkeysaudio['version'], 2);
|
||||
$info['audio']['encoder_options'] = ucfirst($thisfile_monkeysaudio['compression']).' compression';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function MonkeyCompressionLevelNameLookup($compressionlevel) {
|
||||
static $MonkeyCompressionLevelNameLookup = array(
|
||||
0 => 'unknown',
|
||||
1000 => 'fast',
|
||||
2000 => 'normal',
|
||||
3000 => 'high',
|
||||
4000 => 'extra-high',
|
||||
5000 => 'insane'
|
||||
);
|
||||
return (isset($MonkeyCompressionLevelNameLookup[$compressionlevel]) ? $MonkeyCompressionLevelNameLookup[$compressionlevel] : 'invalid');
|
||||
}
|
||||
|
||||
public function MonkeySamplesPerFrame($versionid, $compressionlevel) {
|
||||
if ($versionid >= 3950) {
|
||||
return 73728 * 4;
|
||||
} elseif ($versionid >= 3900) {
|
||||
return 73728;
|
||||
} elseif (($versionid >= 3800) && ($compressionlevel == 4000)) {
|
||||
return 73728;
|
||||
} else {
|
||||
return 9216;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
2009
api/getID3/module.audio.mp3.php
Executable file
506
api/getID3/module.audio.mpc.php
Executable file
@ -0,0 +1,506 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.mpc.php //
|
||||
// module for analyzing Musepack/MPEG+ Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_mpc extends getid3_handler
|
||||
{
|
||||
|
||||
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;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$MPCheaderData = fread($this->getid3->fp, 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'])) {
|
||||
|
||||
// this is SV8
|
||||
return $this->ParseMPCsv8();
|
||||
|
||||
} elseif (preg_match('#^MP\+#', $info['mpc']['header']['preamble'])) {
|
||||
|
||||
// this is SV7
|
||||
return $this->ParseMPCsv7();
|
||||
|
||||
} elseif (preg_match('/^[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]/s', $MPCheaderData)) {
|
||||
|
||||
// this is SV4 - SV6, handle seperately
|
||||
return $this->ParseMPCsv6();
|
||||
|
||||
} else {
|
||||
|
||||
$info['error'][] = 'Expecting "MP+" or "MPCK" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($MPCheaderData, 0, 4)).'"';
|
||||
unset($info['fileformat']);
|
||||
unset($info['mpc']);
|
||||
return false;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function ParseMPCsv8() {
|
||||
// this is SV8
|
||||
// http://trac.musepack.net/trac/wiki/SV8Specification
|
||||
|
||||
$info = &$this->getid3->info;
|
||||
$thisfile_mpc_header = &$info['mpc']['header'];
|
||||
|
||||
$keyNameSize = 2;
|
||||
$maxHandledPacketLength = 9; // specs say: "n*8; 0 < n < 10"
|
||||
|
||||
$offset = ftell($this->getid3->fp);
|
||||
while ($offset < $info['avdataend']) {
|
||||
$thisPacket = array();
|
||||
$thisPacket['offset'] = $offset;
|
||||
$packet_offset = 0;
|
||||
|
||||
// Size is a variable-size field, could be 1-4 bytes (possibly more?)
|
||||
// read enough data in and figure out the exact size later
|
||||
$MPCheaderData = fread($this->getid3->fp, $keyNameSize + $maxHandledPacketLength);
|
||||
$packet_offset += $keyNameSize;
|
||||
$thisPacket['key'] = substr($MPCheaderData, 0, $keyNameSize);
|
||||
$thisPacket['key_name'] = $this->MPCsv8PacketName($thisPacket['key']);
|
||||
if ($thisPacket['key'] == $thisPacket['key_name']) {
|
||||
$info['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) {
|
||||
$info['error'][] = 'Did not find expected packet length within '.$maxHandledPacketLength.' bytes at offset '.($thisPacket['offset'] + $keyNameSize);
|
||||
return false;
|
||||
}
|
||||
$packet_offset += $packetLength;
|
||||
$offset += $thisPacket['packet_size'];
|
||||
|
||||
switch ($thisPacket['key']) {
|
||||
case 'SH': // Stream Header
|
||||
$moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength;
|
||||
if ($moreBytesToRead > 0) {
|
||||
$MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead);
|
||||
}
|
||||
$thisPacket['crc'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 4));
|
||||
$packet_offset += 4;
|
||||
$thisPacket['stream_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||
$packet_offset += 1;
|
||||
|
||||
$packetLength = 0;
|
||||
$thisPacket['sample_count'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength);
|
||||
$packet_offset += $packetLength;
|
||||
|
||||
$packetLength = 0;
|
||||
$thisPacket['beginning_silence'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength);
|
||||
$packet_offset += $packetLength;
|
||||
|
||||
$otherUsefulData = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
|
||||
$packet_offset += 2;
|
||||
$thisPacket['sample_frequency_raw'] = (($otherUsefulData & 0xE000) >> 13);
|
||||
$thisPacket['max_bands_used'] = (($otherUsefulData & 0x1F00) >> 8);
|
||||
$thisPacket['channels'] = (($otherUsefulData & 0x00F0) >> 4) + 1;
|
||||
$thisPacket['ms_used'] = (bool) (($otherUsefulData & 0x0008) >> 3);
|
||||
$thisPacket['audio_block_frames'] = (($otherUsefulData & 0x0007) >> 0);
|
||||
$thisPacket['sample_frequency'] = $this->MPCfrequencyLookup($thisPacket['sample_frequency_raw']);
|
||||
|
||||
$thisfile_mpc_header['mid_side_stereo'] = $thisPacket['ms_used'];
|
||||
$thisfile_mpc_header['sample_rate'] = $thisPacket['sample_frequency'];
|
||||
$thisfile_mpc_header['samples'] = $thisPacket['sample_count'];
|
||||
$thisfile_mpc_header['stream_version_major'] = $thisPacket['stream_version'];
|
||||
|
||||
$info['audio']['channels'] = $thisPacket['channels'];
|
||||
$info['audio']['sample_rate'] = $thisPacket['sample_frequency'];
|
||||
$info['playtime_seconds'] = $thisPacket['sample_count'] / $thisPacket['sample_frequency'];
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
break;
|
||||
|
||||
case 'RG': // Replay Gain
|
||||
$moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength;
|
||||
if ($moreBytesToRead > 0) {
|
||||
$MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead);
|
||||
}
|
||||
$thisPacket['replaygain_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||
$packet_offset += 1;
|
||||
$thisPacket['replaygain_title_gain'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
|
||||
$packet_offset += 2;
|
||||
$thisPacket['replaygain_title_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
|
||||
$packet_offset += 2;
|
||||
$thisPacket['replaygain_album_gain'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
|
||||
$packet_offset += 2;
|
||||
$thisPacket['replaygain_album_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
|
||||
$packet_offset += 2;
|
||||
|
||||
if ($thisPacket['replaygain_title_gain']) { $info['replay_gain']['title']['gain'] = $thisPacket['replaygain_title_gain']; }
|
||||
if ($thisPacket['replaygain_title_peak']) { $info['replay_gain']['title']['peak'] = $thisPacket['replaygain_title_peak']; }
|
||||
if ($thisPacket['replaygain_album_gain']) { $info['replay_gain']['album']['gain'] = $thisPacket['replaygain_album_gain']; }
|
||||
if ($thisPacket['replaygain_album_peak']) { $info['replay_gain']['album']['peak'] = $thisPacket['replaygain_album_peak']; }
|
||||
break;
|
||||
|
||||
case 'EI': // Encoder Info
|
||||
$moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength;
|
||||
if ($moreBytesToRead > 0) {
|
||||
$MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead);
|
||||
}
|
||||
$profile_pns = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||
$packet_offset += 1;
|
||||
$quality_int = (($profile_pns & 0xF0) >> 4);
|
||||
$quality_dec = (($profile_pns & 0x0E) >> 3);
|
||||
$thisPacket['quality'] = (float) $quality_int + ($quality_dec / 8);
|
||||
$thisPacket['pns_tool'] = (bool) (($profile_pns & 0x01) >> 0);
|
||||
$thisPacket['version_major'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||
$packet_offset += 1;
|
||||
$thisPacket['version_minor'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||
$packet_offset += 1;
|
||||
$thisPacket['version_build'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||
$packet_offset += 1;
|
||||
$thisPacket['version'] = $thisPacket['version_major'].'.'.$thisPacket['version_minor'].'.'.$thisPacket['version_build'];
|
||||
|
||||
$info['audio']['encoder'] = 'MPC v'.$thisPacket['version'].' ('.(($thisPacket['version_minor'] % 2) ? 'unstable' : 'stable').')';
|
||||
$thisfile_mpc_header['encoder_version'] = $info['audio']['encoder'];
|
||||
//$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
|
||||
break;
|
||||
|
||||
case 'SO': // Seek Table Offset
|
||||
$packetLength = 0;
|
||||
$thisPacket['seek_table_offset'] = $thisPacket['offset'] + $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength);
|
||||
$packet_offset += $packetLength;
|
||||
break;
|
||||
|
||||
case 'ST': // Seek Table
|
||||
case 'SE': // Stream End
|
||||
case 'AP': // Audio Data
|
||||
// nothing useful here, just skip this packet
|
||||
$thisPacket = array();
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Found unhandled key type "'.$thisPacket['key'].'" at offset '.$thisPacket['offset'];
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
if (!empty($thisPacket)) {
|
||||
$info['mpc']['packets'][] = $thisPacket;
|
||||
}
|
||||
fseek($this->getid3->fp, $offset);
|
||||
}
|
||||
$thisfile_mpc_header['size'] = $offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function ParseMPCsv7() {
|
||||
// this is SV7
|
||||
// http://www.uni-jena.de/~pfk/mpp/sv8/header.html
|
||||
|
||||
$info = &$this->getid3->info;
|
||||
$thisfile_mpc_header = &$info['mpc']['header'];
|
||||
$offset = 0;
|
||||
|
||||
$thisfile_mpc_header['size'] = 28;
|
||||
$MPCheaderData = $info['mpc']['header']['preamble'];
|
||||
$MPCheaderData .= fread($this->getid3->fp, $thisfile_mpc_header['size'] - strlen($info['mpc']['header']['preamble']));
|
||||
$offset = strlen('MP+');
|
||||
|
||||
$StreamVersionByte = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1));
|
||||
$offset += 1;
|
||||
$thisfile_mpc_header['stream_version_major'] = ($StreamVersionByte & 0x0F) >> 0;
|
||||
$thisfile_mpc_header['stream_version_minor'] = ($StreamVersionByte & 0xF0) >> 4; // should always be 0, subversions no longer exist in SV8
|
||||
$thisfile_mpc_header['frame_count'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
if ($thisfile_mpc_header['stream_version_major'] != 7) {
|
||||
$info['error'][] = 'Only Musepack SV7 supported (this file claims to be v'.$thisfile_mpc_header['stream_version_major'].')';
|
||||
return false;
|
||||
}
|
||||
|
||||
$FlagsDWORD1 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_mpc_header['intensity_stereo'] = (bool) (($FlagsDWORD1 & 0x80000000) >> 31);
|
||||
$thisfile_mpc_header['mid_side_stereo'] = (bool) (($FlagsDWORD1 & 0x40000000) >> 30);
|
||||
$thisfile_mpc_header['max_subband'] = ($FlagsDWORD1 & 0x3F000000) >> 24;
|
||||
$thisfile_mpc_header['raw']['profile'] = ($FlagsDWORD1 & 0x00F00000) >> 20;
|
||||
$thisfile_mpc_header['begin_loud'] = (bool) (($FlagsDWORD1 & 0x00080000) >> 19);
|
||||
$thisfile_mpc_header['end_loud'] = (bool) (($FlagsDWORD1 & 0x00040000) >> 18);
|
||||
$thisfile_mpc_header['raw']['sample_rate'] = ($FlagsDWORD1 & 0x00030000) >> 16;
|
||||
$thisfile_mpc_header['max_level'] = ($FlagsDWORD1 & 0x0000FFFF);
|
||||
|
||||
$thisfile_mpc_header['raw']['title_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_mpc_header['raw']['title_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true);
|
||||
$offset += 2;
|
||||
|
||||
$thisfile_mpc_header['raw']['album_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_mpc_header['raw']['album_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true);
|
||||
$offset += 2;
|
||||
|
||||
$FlagsDWORD2 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_mpc_header['true_gapless'] = (bool) (($FlagsDWORD2 & 0x80000000) >> 31);
|
||||
$thisfile_mpc_header['last_frame_length'] = ($FlagsDWORD2 & 0x7FF00000) >> 20;
|
||||
|
||||
|
||||
$thisfile_mpc_header['raw']['not_sure_what'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 3));
|
||||
$offset += 3;
|
||||
$thisfile_mpc_header['raw']['encoder_version'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
$thisfile_mpc_header['profile'] = $this->MPCprofileNameLookup($thisfile_mpc_header['raw']['profile']);
|
||||
$thisfile_mpc_header['sample_rate'] = $this->MPCfrequencyLookup($thisfile_mpc_header['raw']['sample_rate']);
|
||||
if ($thisfile_mpc_header['sample_rate'] == 0) {
|
||||
$info['error'][] = 'Corrupt MPC file: frequency == zero';
|
||||
return false;
|
||||
}
|
||||
$info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate'];
|
||||
$thisfile_mpc_header['samples'] = ((($thisfile_mpc_header['frame_count'] - 1) * 1152) + $thisfile_mpc_header['last_frame_length']) * $info['audio']['channels'];
|
||||
|
||||
$info['playtime_seconds'] = ($thisfile_mpc_header['samples'] / $info['audio']['channels']) / $info['audio']['sample_rate'];
|
||||
if ($info['playtime_seconds'] == 0) {
|
||||
$info['error'][] = 'Corrupt MPC file: playtime_seconds == zero';
|
||||
return false;
|
||||
}
|
||||
|
||||
// add size of file header to avdataoffset - calc bitrate correctly + MD5 data
|
||||
$info['avdataoffset'] += $thisfile_mpc_header['size'];
|
||||
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
|
||||
$thisfile_mpc_header['title_peak'] = $thisfile_mpc_header['raw']['title_peak'];
|
||||
$thisfile_mpc_header['title_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['title_peak']);
|
||||
if ($thisfile_mpc_header['raw']['title_gain'] < 0) {
|
||||
$thisfile_mpc_header['title_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['title_gain']) / -100;
|
||||
} else {
|
||||
$thisfile_mpc_header['title_gain_db'] = (float) $thisfile_mpc_header['raw']['title_gain'] / 100;
|
||||
}
|
||||
|
||||
$thisfile_mpc_header['album_peak'] = $thisfile_mpc_header['raw']['album_peak'];
|
||||
$thisfile_mpc_header['album_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['album_peak']);
|
||||
if ($thisfile_mpc_header['raw']['album_gain'] < 0) {
|
||||
$thisfile_mpc_header['album_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['album_gain']) / -100;
|
||||
} else {
|
||||
$thisfile_mpc_header['album_gain_db'] = (float) $thisfile_mpc_header['raw']['album_gain'] / 100;;
|
||||
}
|
||||
$thisfile_mpc_header['encoder_version'] = $this->MPCencoderVersionLookup($thisfile_mpc_header['raw']['encoder_version']);
|
||||
|
||||
$info['replay_gain']['track']['adjustment'] = $thisfile_mpc_header['title_gain_db'];
|
||||
$info['replay_gain']['album']['adjustment'] = $thisfile_mpc_header['album_gain_db'];
|
||||
|
||||
if ($thisfile_mpc_header['title_peak'] > 0) {
|
||||
$info['replay_gain']['track']['peak'] = $thisfile_mpc_header['title_peak'];
|
||||
} elseif (round($thisfile_mpc_header['max_level'] * 1.18) > 0) {
|
||||
$info['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round($thisfile_mpc_header['max_level'] * 1.18)); // why? I don't know - see mppdec.c
|
||||
}
|
||||
if ($thisfile_mpc_header['album_peak'] > 0) {
|
||||
$info['replay_gain']['album']['peak'] = $thisfile_mpc_header['album_peak'];
|
||||
}
|
||||
|
||||
//$info['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'].'.'.$thisfile_mpc_header['stream_version_minor'].', '.$thisfile_mpc_header['encoder_version'];
|
||||
$info['audio']['encoder'] = $thisfile_mpc_header['encoder_version'];
|
||||
$info['audio']['encoder_options'] = $thisfile_mpc_header['profile'];
|
||||
$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
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function ParseMPCsv6() {
|
||||
// this is SV4 - SV6
|
||||
|
||||
$info = &$this->getid3->info;
|
||||
$thisfile_mpc_header = &$info['mpc']['header'];
|
||||
$offset = 0;
|
||||
|
||||
$thisfile_mpc_header['size'] = 8;
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$MPCheaderData = fread($this->getid3->fp, $thisfile_mpc_header['size']);
|
||||
|
||||
// add size of file header to avdataoffset - calc bitrate correctly + MD5 data
|
||||
$info['avdataoffset'] += $thisfile_mpc_header['size'];
|
||||
|
||||
// Most of this code adapted from Jurgen Faul's MPEGplus source code - thanks Jurgen! :)
|
||||
$HeaderDWORD[0] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 0, 4));
|
||||
$HeaderDWORD[1] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 4, 4));
|
||||
|
||||
|
||||
// DDDD DDDD CCCC CCCC BBBB BBBB AAAA AAAA
|
||||
// aaaa aaaa abcd dddd dddd deee eeff ffff
|
||||
//
|
||||
// a = bitrate = anything
|
||||
// b = IS = anything
|
||||
// c = MS = anything
|
||||
// d = streamversion = 0000000004 or 0000000005 or 0000000006
|
||||
// e = maxband = anything
|
||||
// f = blocksize = 000001 for SV5+, anything(?) for SV4
|
||||
|
||||
$thisfile_mpc_header['target_bitrate'] = (($HeaderDWORD[0] & 0xFF800000) >> 23);
|
||||
$thisfile_mpc_header['intensity_stereo'] = (bool) (($HeaderDWORD[0] & 0x00400000) >> 22);
|
||||
$thisfile_mpc_header['mid_side_stereo'] = (bool) (($HeaderDWORD[0] & 0x00200000) >> 21);
|
||||
$thisfile_mpc_header['stream_version_major'] = ($HeaderDWORD[0] & 0x001FF800) >> 11;
|
||||
$thisfile_mpc_header['stream_version_minor'] = 0; // no sub-version numbers before SV7
|
||||
$thisfile_mpc_header['max_band'] = ($HeaderDWORD[0] & 0x000007C0) >> 6; // related to lowpass frequency, not sure how it translates exactly
|
||||
$thisfile_mpc_header['block_size'] = ($HeaderDWORD[0] & 0x0000003F);
|
||||
|
||||
switch ($thisfile_mpc_header['stream_version_major']) {
|
||||
case 4:
|
||||
$thisfile_mpc_header['frame_count'] = ($HeaderDWORD[1] >> 16);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
case 6:
|
||||
$thisfile_mpc_header['frame_count'] = $HeaderDWORD[1];
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'] = 'Expecting 4, 5 or 6 in version field, found '.$thisfile_mpc_header['stream_version_major'].' instead';
|
||||
unset($info['mpc']);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (($thisfile_mpc_header['stream_version_major'] > 4) && ($thisfile_mpc_header['block_size'] != 1)) {
|
||||
$info['warning'][] = 'Block size expected to be 1, actual value found: '.$thisfile_mpc_header['block_size'];
|
||||
}
|
||||
|
||||
$thisfile_mpc_header['sample_rate'] = 44100; // AB: used by all files up to SV7
|
||||
$info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate'];
|
||||
$thisfile_mpc_header['samples'] = $thisfile_mpc_header['frame_count'] * 1152 * $info['audio']['channels'];
|
||||
|
||||
if ($thisfile_mpc_header['target_bitrate'] == 0) {
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
} else {
|
||||
$info['audio']['bitrate_mode'] = 'cbr';
|
||||
}
|
||||
|
||||
$info['mpc']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 * 44100 / $thisfile_mpc_header['frame_count'] / 1152;
|
||||
$info['audio']['bitrate'] = $info['mpc']['bitrate'];
|
||||
$info['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function MPCprofileNameLookup($profileid) {
|
||||
static $MPCprofileNameLookup = array(
|
||||
0 => 'no profile',
|
||||
1 => 'Experimental',
|
||||
2 => 'unused',
|
||||
3 => 'unused',
|
||||
4 => 'unused',
|
||||
5 => 'below Telephone (q = 0.0)',
|
||||
6 => 'below Telephone (q = 1.0)',
|
||||
7 => 'Telephone (q = 2.0)',
|
||||
8 => 'Thumb (q = 3.0)',
|
||||
9 => 'Radio (q = 4.0)',
|
||||
10 => 'Standard (q = 5.0)',
|
||||
11 => 'Extreme (q = 6.0)',
|
||||
12 => 'Insane (q = 7.0)',
|
||||
13 => 'BrainDead (q = 8.0)',
|
||||
14 => 'above BrainDead (q = 9.0)',
|
||||
15 => 'above BrainDead (q = 10.0)'
|
||||
);
|
||||
return (isset($MPCprofileNameLookup[$profileid]) ? $MPCprofileNameLookup[$profileid] : 'invalid');
|
||||
}
|
||||
|
||||
public function MPCfrequencyLookup($frequencyid) {
|
||||
static $MPCfrequencyLookup = array(
|
||||
0 => 44100,
|
||||
1 => 48000,
|
||||
2 => 37800,
|
||||
3 => 32000
|
||||
);
|
||||
return (isset($MPCfrequencyLookup[$frequencyid]) ? $MPCfrequencyLookup[$frequencyid] : 'invalid');
|
||||
}
|
||||
|
||||
public function MPCpeakDBLookup($intvalue) {
|
||||
if ($intvalue > 0) {
|
||||
return ((log10($intvalue) / log10(2)) - 15) * 6;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function MPCencoderVersionLookup($encoderversion) {
|
||||
//Encoder version * 100 (106 = 1.06)
|
||||
//EncoderVersion % 10 == 0 Release (1.0)
|
||||
//EncoderVersion % 2 == 0 Beta (1.06)
|
||||
//EncoderVersion % 2 == 1 Alpha (1.05a...z)
|
||||
|
||||
if ($encoderversion == 0) {
|
||||
// very old version, not known exactly which
|
||||
return 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05';
|
||||
}
|
||||
|
||||
if (($encoderversion % 10) == 0) {
|
||||
|
||||
// release version
|
||||
return number_format($encoderversion / 100, 2);
|
||||
|
||||
} elseif (($encoderversion % 2) == 0) {
|
||||
|
||||
// beta version
|
||||
return number_format($encoderversion / 100, 2).' beta';
|
||||
|
||||
}
|
||||
|
||||
// alpha version
|
||||
return number_format($encoderversion / 100, 2).' alpha';
|
||||
}
|
||||
|
||||
public function SV8variableLengthInteger($data, &$packetLength, $maxHandledPacketLength=9) {
|
||||
$packet_size = 0;
|
||||
for ($packetLength = 1; $packetLength <= $maxHandledPacketLength; $packetLength++) {
|
||||
// variable-length size field:
|
||||
// bits, big-endian
|
||||
// 0xxx xxxx - value 0 to 2^7-1
|
||||
// 1xxx xxxx 0xxx xxxx - value 0 to 2^14-1
|
||||
// 1xxx xxxx 1xxx xxxx 0xxx xxxx - value 0 to 2^21-1
|
||||
// 1xxx xxxx 1xxx xxxx 1xxx xxxx 0xxx xxxx - value 0 to 2^28-1
|
||||
// ...
|
||||
$thisbyte = ord(substr($data, ($packetLength - 1), 1));
|
||||
// look through bytes until find a byte with MSB==0
|
||||
$packet_size = ($packet_size << 7);
|
||||
$packet_size = ($packet_size | ($thisbyte & 0x7F));
|
||||
if (($thisbyte & 0x80) === 0) {
|
||||
break;
|
||||
}
|
||||
if ($packetLength >= $maxHandledPacketLength) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $packet_size;
|
||||
}
|
||||
|
||||
public function MPCsv8PacketName($packetKey) {
|
||||
static $MPCsv8PacketName = array();
|
||||
if (empty($MPCsv8PacketName)) {
|
||||
$MPCsv8PacketName = array(
|
||||
'AP' => 'Audio Packet',
|
||||
'CT' => 'Chapter Tag',
|
||||
'EI' => 'Encoder Info',
|
||||
'RG' => 'Replay Gain',
|
||||
'SE' => 'Stream End',
|
||||
'SH' => 'Stream Header',
|
||||
'SO' => 'Seek Table Offset',
|
||||
'ST' => 'Seek Table',
|
||||
);
|
||||
}
|
||||
return (isset($MPCsv8PacketName[$packetKey]) ? $MPCsv8PacketName[$packetKey] : $packetKey);
|
||||
}
|
||||
}
|
671
api/getID3/module.audio.ogg.php
Executable file
@ -0,0 +1,671 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.ogg.php //
|
||||
// module for analyzing Ogg Vorbis, OggFLAC and Speex files //
|
||||
// dependencies: module.audio.flac.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
|
||||
|
||||
class getid3_ogg extends getid3_handler
|
||||
{
|
||||
// http://xiph.org/vorbis/doc/Vorbis_I_spec.html
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'ogg';
|
||||
|
||||
// Warn about illegal tags - only vorbiscomments are allowed
|
||||
if (isset($info['id3v2'])) {
|
||||
$info['warning'][] = 'Illegal ID3v2 tag present.';
|
||||
}
|
||||
if (isset($info['id3v1'])) {
|
||||
$info['warning'][] = 'Illegal ID3v1 tag present.';
|
||||
}
|
||||
if (isset($info['ape'])) {
|
||||
$info['warning'][] = 'Illegal APE tag present.';
|
||||
}
|
||||
|
||||
|
||||
// Page 1 - Stream Header
|
||||
|
||||
$this->fseek($info['avdataoffset']);
|
||||
|
||||
$oggpageinfo = $this->ParseOggPageHeader();
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||
|
||||
if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
|
||||
$info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)';
|
||||
unset($info['fileformat']);
|
||||
unset($info['ogg']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$filedata = $this->fread($oggpageinfo['page_length']);
|
||||
$filedataoffset = 0;
|
||||
|
||||
if (substr($filedata, 0, 4) == 'fLaC') {
|
||||
|
||||
$info['audio']['dataformat'] = 'flac';
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
$info['audio']['lossless'] = true;
|
||||
|
||||
} elseif (substr($filedata, 1, 6) == 'vorbis') {
|
||||
|
||||
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
|
||||
|
||||
} elseif (substr($filedata, 0, 8) == 'Speex ') {
|
||||
|
||||
// http://www.speex.org/manual/node10.html
|
||||
|
||||
$info['audio']['dataformat'] = 'speex';
|
||||
$info['mime_type'] = 'audio/speex';
|
||||
$info['audio']['bitrate_mode'] = 'abr';
|
||||
$info['audio']['lossless'] = false;
|
||||
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex '
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20);
|
||||
$filedataoffset += 20;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
|
||||
$info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
|
||||
$info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
|
||||
$info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
|
||||
$info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
|
||||
$info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
|
||||
|
||||
$info['audio']['sample_rate'] = $info['speex']['sample_rate'];
|
||||
$info['audio']['channels'] = $info['speex']['channels'];
|
||||
if ($info['speex']['vbr']) {
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
}
|
||||
|
||||
|
||||
} elseif (substr($filedata, 0, 8) == "fishead\x00") {
|
||||
|
||||
// Ogg Skeleton version 3.0 Format Specification
|
||||
// http://xiph.org/ogg/doc/skeleton.html
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
|
||||
$filedataoffset += 2;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
|
||||
$filedataoffset += 2;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
|
||||
$filedataoffset += 20;
|
||||
|
||||
$info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
|
||||
$info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
|
||||
$info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
|
||||
$info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc'];
|
||||
|
||||
|
||||
$counter = 0;
|
||||
do {
|
||||
$oggpageinfo = $this->ParseOggPageHeader();
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
|
||||
$filedata = $this->fread($oggpageinfo['page_length']);
|
||||
$this->fseek($oggpageinfo['page_end_offset']);
|
||||
|
||||
if (substr($filedata, 0, 8) == "fisbone\x00") {
|
||||
|
||||
$filedataoffset = 8;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3);
|
||||
$filedataoffset += 3;
|
||||
|
||||
} elseif (substr($filedata, 1, 6) == 'theora') {
|
||||
|
||||
$info['video']['dataformat'] = 'theora';
|
||||
$info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']';
|
||||
//break;
|
||||
|
||||
} elseif (substr($filedata, 1, 6) == 'vorbis') {
|
||||
|
||||
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
|
||||
|
||||
} else {
|
||||
$info['error'][] = 'unexpected';
|
||||
//break;
|
||||
}
|
||||
//} while ($oggpageinfo['page_seqno'] == 0);
|
||||
} while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
|
||||
|
||||
$this->fseek($oggpageinfo['page_start_offset']);
|
||||
|
||||
$info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
|
||||
//return false;
|
||||
|
||||
} else {
|
||||
|
||||
$info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
|
||||
unset($info['ogg']);
|
||||
unset($info['mime_type']);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// Page 2 - Comment Header
|
||||
$oggpageinfo = $this->ParseOggPageHeader();
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||
|
||||
switch ($info['audio']['dataformat']) {
|
||||
case 'vorbis':
|
||||
$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis'
|
||||
|
||||
$this->ParseVorbisComments();
|
||||
break;
|
||||
|
||||
case 'flac':
|
||||
$flac = new getid3_flac($this->getid3);
|
||||
if (!$flac->parseMETAdata()) {
|
||||
$info['error'][] = 'Failed to parse FLAC headers';
|
||||
return false;
|
||||
}
|
||||
unset($flac);
|
||||
break;
|
||||
|
||||
case 'speex':
|
||||
$this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
|
||||
$this->ParseVorbisComments();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Last Page - Number of Samples
|
||||
if (!getid3_lib::intValueSupported($info['avdataend'])) {
|
||||
|
||||
$info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
|
||||
|
||||
} else {
|
||||
|
||||
$this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
|
||||
$LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
|
||||
if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
|
||||
$this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
|
||||
$info['avdataend'] = $this->ftell();
|
||||
$info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
|
||||
$info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
|
||||
if ($info['ogg']['samples'] == 0) {
|
||||
$info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
|
||||
return false;
|
||||
}
|
||||
if (!empty($info['audio']['sample_rate'])) {
|
||||
$info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!empty($info['ogg']['bitrate_average'])) {
|
||||
$info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
|
||||
} elseif (!empty($info['ogg']['bitrate_nominal'])) {
|
||||
$info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
|
||||
} elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
|
||||
$info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
|
||||
}
|
||||
if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
|
||||
if ($info['audio']['bitrate'] == 0) {
|
||||
$info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
|
||||
return false;
|
||||
}
|
||||
$info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
|
||||
}
|
||||
|
||||
if (isset($info['ogg']['vendor'])) {
|
||||
$info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
|
||||
|
||||
// Vorbis only
|
||||
if ($info['audio']['dataformat'] == 'vorbis') {
|
||||
|
||||
// Vorbis 1.0 starts with Xiph.Org
|
||||
if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
|
||||
|
||||
if ($info['audio']['bitrate_mode'] == 'abr') {
|
||||
|
||||
// Set -b 128 on abr files
|
||||
$info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
|
||||
|
||||
} elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
|
||||
// Set -q N on vbr files
|
||||
$info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
|
||||
$info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
|
||||
$info = &$this->getid3->info;
|
||||
$info['audio']['dataformat'] = 'vorbis';
|
||||
$info['audio']['lossless'] = false;
|
||||
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
|
||||
$filedataoffset += 6;
|
||||
$info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['audio']['channels'] = $info['ogg']['numberofchannels'];
|
||||
$info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
if ($info['ogg']['samplerate'] == 0) {
|
||||
$info['error'][] = 'Corrupt Ogg file: sample rate == zero';
|
||||
return false;
|
||||
}
|
||||
$info['audio']['sample_rate'] = $info['ogg']['samplerate'];
|
||||
$info['ogg']['samples'] = 0; // filled in later
|
||||
$info['ogg']['bitrate_average'] = 0; // filled in later
|
||||
$info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
|
||||
$info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
|
||||
$info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
|
||||
|
||||
$info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
|
||||
if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
|
||||
unset($info['ogg']['bitrate_max']);
|
||||
$info['audio']['bitrate_mode'] = 'abr';
|
||||
}
|
||||
if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
|
||||
unset($info['ogg']['bitrate_nominal']);
|
||||
}
|
||||
if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
|
||||
unset($info['ogg']['bitrate_min']);
|
||||
$info['audio']['bitrate_mode'] = 'abr';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function ParseOggPageHeader() {
|
||||
// http://xiph.org/ogg/vorbis/doc/framing.html
|
||||
$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
|
||||
|
||||
$filedata = $this->fread($this->getid3->fread_buffer_size());
|
||||
$filedataoffset = 0;
|
||||
while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
|
||||
if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
|
||||
// should be found before here
|
||||
return false;
|
||||
}
|
||||
if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
|
||||
if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) {
|
||||
// get some more data, unless eof, in which case fail
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
$filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
|
||||
|
||||
$oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
|
||||
$oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
|
||||
$oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
|
||||
|
||||
$oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$oggheader['page_length'] = 0;
|
||||
for ($i = 0; $i < $oggheader['page_segments']; $i++) {
|
||||
$oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$oggheader['page_length'] += $oggheader['segment_table'][$i];
|
||||
}
|
||||
$oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
|
||||
$oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length'];
|
||||
$this->fseek($oggheader['header_end_offset']);
|
||||
|
||||
return $oggheader;
|
||||
}
|
||||
|
||||
// http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
|
||||
public function ParseVorbisComments() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$OriginalOffset = $this->ftell();
|
||||
$commentdataoffset = 0;
|
||||
$VorbisCommentPage = 1;
|
||||
|
||||
switch ($info['audio']['dataformat']) {
|
||||
case 'vorbis':
|
||||
case 'speex':
|
||||
$CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block
|
||||
$this->fseek($CommentStartOffset);
|
||||
$commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
|
||||
$commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
|
||||
|
||||
if ($info['audio']['dataformat'] == 'vorbis') {
|
||||
$commentdataoffset += (strlen('vorbis') + 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'flac':
|
||||
$CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
|
||||
$this->fseek($CommentStartOffset);
|
||||
$commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
$VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
|
||||
$commentdataoffset += 4;
|
||||
|
||||
$info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
|
||||
$commentdataoffset += $VendorSize;
|
||||
|
||||
$CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
|
||||
$commentdataoffset += 4;
|
||||
$info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
|
||||
|
||||
$basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
|
||||
$ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
|
||||
for ($i = 0; $i < $CommentsCount; $i++) {
|
||||
|
||||
$ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
|
||||
|
||||
if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
|
||||
if ($oggpageinfo = $this->ParseOggPageHeader()) {
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||
|
||||
$VorbisCommentPage++;
|
||||
|
||||
// First, save what we haven't read yet
|
||||
$AsYetUnusedData = substr($commentdata, $commentdataoffset);
|
||||
|
||||
// Then take that data off the end
|
||||
$commentdata = substr($commentdata, 0, $commentdataoffset);
|
||||
|
||||
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
|
||||
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||
|
||||
// Finally, stick the unused data back on the end
|
||||
$commentdata .= $AsYetUnusedData;
|
||||
|
||||
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
|
||||
$commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
|
||||
}
|
||||
|
||||
}
|
||||
$ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
|
||||
|
||||
// replace avdataoffset with position just after the last vorbiscomment
|
||||
$info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
|
||||
|
||||
$commentdataoffset += 4;
|
||||
while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
|
||||
if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
|
||||
$info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
|
||||
break 2;
|
||||
}
|
||||
|
||||
$VorbisCommentPage++;
|
||||
|
||||
$oggpageinfo = $this->ParseOggPageHeader();
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||
|
||||
// First, save what we haven't read yet
|
||||
$AsYetUnusedData = substr($commentdata, $commentdataoffset);
|
||||
|
||||
// Then take that data off the end
|
||||
$commentdata = substr($commentdata, 0, $commentdataoffset);
|
||||
|
||||
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
|
||||
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||
|
||||
// Finally, stick the unused data back on the end
|
||||
$commentdata .= $AsYetUnusedData;
|
||||
|
||||
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
|
||||
if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
|
||||
$info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
|
||||
break;
|
||||
}
|
||||
$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
|
||||
if ($readlength <= 0) {
|
||||
$info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
|
||||
break;
|
||||
}
|
||||
$commentdata .= $this->fread($readlength);
|
||||
|
||||
//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
|
||||
}
|
||||
$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
|
||||
$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
|
||||
$commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
|
||||
|
||||
if (!$commentstring) {
|
||||
|
||||
// no comment?
|
||||
$info['warning'][] = 'Blank Ogg comment ['.$i.']';
|
||||
|
||||
} elseif (strstr($commentstring, '=')) {
|
||||
|
||||
$commentexploded = explode('=', $commentstring, 2);
|
||||
$ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]);
|
||||
$ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
|
||||
|
||||
if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
|
||||
|
||||
// http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
|
||||
// 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.
|
||||
// http://flac.sourceforge.net/format.html#metadata_block_picture
|
||||
$flac = new getid3_flac($this->getid3);
|
||||
$flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
|
||||
$flac->parsePICTURE();
|
||||
$info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
|
||||
unset($flac);
|
||||
|
||||
} elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
|
||||
|
||||
$data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
|
||||
$this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
|
||||
/** @todo use 'coverartmime' where available */
|
||||
$imageinfo = getid3_lib::GetDataImageSize($data);
|
||||
if ($imageinfo === false || !isset($imageinfo['mime'])) {
|
||||
$this->warning('COVERART vorbiscomment tag contains invalid image');
|
||||
continue;
|
||||
}
|
||||
|
||||
$ogg = new self($this->getid3);
|
||||
$ogg->setStringMode($data);
|
||||
$info['ogg']['comments']['picture'][] = array(
|
||||
'image_mime' => $imageinfo['mime'],
|
||||
'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
|
||||
);
|
||||
unset($ogg);
|
||||
|
||||
} else {
|
||||
|
||||
$info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
|
||||
|
||||
}
|
||||
unset($ThisFileInfo_ogg_comments_raw[$i]);
|
||||
}
|
||||
unset($ThisFileInfo_ogg_comments_raw);
|
||||
|
||||
|
||||
// Replay Gain Adjustment
|
||||
// http://privatewww.essex.ac.uk/~djmrob/replaygain/
|
||||
if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
|
||||
foreach ($info['ogg']['comments'] as $index => $commentvalue) {
|
||||
switch ($index) {
|
||||
case 'rg_audiophile':
|
||||
case 'replaygain_album_gain':
|
||||
$info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
|
||||
unset($info['ogg']['comments'][$index]);
|
||||
break;
|
||||
|
||||
case 'rg_radio':
|
||||
case 'replaygain_track_gain':
|
||||
$info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
|
||||
unset($info['ogg']['comments'][$index]);
|
||||
break;
|
||||
|
||||
case 'replaygain_album_peak':
|
||||
$info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
|
||||
unset($info['ogg']['comments'][$index]);
|
||||
break;
|
||||
|
||||
case 'rg_peak':
|
||||
case 'replaygain_track_peak':
|
||||
$info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
|
||||
unset($info['ogg']['comments'][$index]);
|
||||
break;
|
||||
|
||||
case 'replaygain_reference_loudness':
|
||||
$info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
|
||||
unset($info['ogg']['comments'][$index]);
|
||||
break;
|
||||
|
||||
default:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->fseek($OriginalOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function SpeexBandModeLookup($mode) {
|
||||
static $SpeexBandModeLookup = array();
|
||||
if (empty($SpeexBandModeLookup)) {
|
||||
$SpeexBandModeLookup[0] = 'narrow';
|
||||
$SpeexBandModeLookup[1] = 'wide';
|
||||
$SpeexBandModeLookup[2] = 'ultra-wide';
|
||||
}
|
||||
return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
|
||||
}
|
||||
|
||||
|
||||
public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
|
||||
for ($i = 0; $i < $SegmentNumber; $i++) {
|
||||
$segmentlength = 0;
|
||||
foreach ($OggInfoArray['segment_table'] as $key => $value) {
|
||||
$segmentlength += $value;
|
||||
if ($value < 255) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $segmentlength;
|
||||
}
|
||||
|
||||
|
||||
public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
|
||||
|
||||
// decrease precision
|
||||
$nominal_bitrate = $nominal_bitrate / 1000;
|
||||
|
||||
if ($nominal_bitrate < 128) {
|
||||
// q-1 to q4
|
||||
$qval = ($nominal_bitrate - 64) / 16;
|
||||
} elseif ($nominal_bitrate < 256) {
|
||||
// q4 to q8
|
||||
$qval = $nominal_bitrate / 32;
|
||||
} elseif ($nominal_bitrate < 320) {
|
||||
// q8 to q9
|
||||
$qval = ($nominal_bitrate + 256) / 64;
|
||||
} else {
|
||||
// q9 to q10
|
||||
$qval = ($nominal_bitrate + 1300) / 180;
|
||||
}
|
||||
//return $qval; // 5.031324
|
||||
//return intval($qval); // 5
|
||||
return round($qval, 1); // 5 or 4.9
|
||||
}
|
||||
|
||||
}
|
426
api/getID3/module.audio.optimfrog.php
Executable file
@ -0,0 +1,426 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.optimfrog.php //
|
||||
// module for analyzing OptimFROG audio files //
|
||||
// dependencies: module.audio.riff.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||
|
||||
class getid3_optimfrog extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'ofr';
|
||||
$info['audio']['dataformat'] = 'ofr';
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
$info['audio']['lossless'] = true;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$OFRheader = fread($this->getid3->fp, 8);
|
||||
if (substr($OFRheader, 0, 5) == '*RIFF') {
|
||||
|
||||
return $this->ParseOptimFROGheader42();
|
||||
|
||||
} elseif (substr($OFRheader, 0, 3) == 'OFR') {
|
||||
|
||||
return $this->ParseOptimFROGheader45();
|
||||
|
||||
}
|
||||
|
||||
$info['error'][] = 'Expecting "*RIFF" or "OFR " at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($OFRheader).'"';
|
||||
unset($info['fileformat']);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function ParseOptimFROGheader42() {
|
||||
// for fileformat of v4.21 and older
|
||||
|
||||
$info = &$this->getid3->info;
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$OptimFROGheaderData = fread($this->getid3->fp, 45);
|
||||
$info['avdataoffset'] = 45;
|
||||
|
||||
$OptimFROGencoderVersion_raw = getid3_lib::LittleEndian2Int(substr($OptimFROGheaderData, 0, 1));
|
||||
$OptimFROGencoderVersion_major = floor($OptimFROGencoderVersion_raw / 10);
|
||||
$OptimFROGencoderVersion_minor = $OptimFROGencoderVersion_raw - ($OptimFROGencoderVersion_major * 10);
|
||||
$RIFFdata = substr($OptimFROGheaderData, 1, 44);
|
||||
$OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8;
|
||||
$OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44;
|
||||
|
||||
if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) {
|
||||
$info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize);
|
||||
fseek($this->getid3->fp, $info['avdataend'], SEEK_SET);
|
||||
$RIFFdata .= fread($this->getid3->fp, $OrignalRIFFheaderSize - $OrignalRIFFdataSize);
|
||||
}
|
||||
|
||||
// move the data chunk after all other chunks (if any)
|
||||
// so that the RIFF parser doesn't see EOF when trying
|
||||
// to skip over the data chunk
|
||||
$RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8);
|
||||
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($this->getid3->filename);
|
||||
$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
|
||||
$getid3_temp->info['avdataend'] = $info['avdataend'];
|
||||
$getid3_riff = new getid3_riff($getid3_temp);
|
||||
$getid3_riff->ParseRIFFdata($RIFFdata);
|
||||
$info['riff'] = $getid3_temp->info['riff'];
|
||||
|
||||
$info['audio']['encoder'] = 'OptimFROG '.$OptimFROGencoderVersion_major.'.'.$OptimFROGencoderVersion_minor;
|
||||
$info['audio']['channels'] = $info['riff']['audio'][0]['channels'];
|
||||
$info['audio']['sample_rate'] = $info['riff']['audio'][0]['sample_rate'];
|
||||
$info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample'];
|
||||
$info['playtime_seconds'] = $OrignalRIFFdataSize / ($info['audio']['channels'] * $info['audio']['sample_rate'] * ($info['audio']['bits_per_sample'] / 8));
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
|
||||
unset($getid3_riff, $getid3_temp, $RIFFdata);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function ParseOptimFROGheader45() {
|
||||
// for fileformat of v4.50a and higher
|
||||
|
||||
$info = &$this->getid3->info;
|
||||
$RIFFdata = '';
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
while (!feof($this->getid3->fp) && (ftell($this->getid3->fp) < $info['avdataend'])) {
|
||||
$BlockOffset = ftell($this->getid3->fp);
|
||||
$BlockData = fread($this->getid3->fp, 8);
|
||||
$offset = 8;
|
||||
$BlockName = substr($BlockData, 0, 4);
|
||||
$BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4));
|
||||
|
||||
if ($BlockName == 'OFRX') {
|
||||
$BlockName = 'OFR ';
|
||||
}
|
||||
if (!isset($info['ofr'][$BlockName])) {
|
||||
$info['ofr'][$BlockName] = array();
|
||||
}
|
||||
$thisfile_ofr_thisblock = &$info['ofr'][$BlockName];
|
||||
|
||||
switch ($BlockName) {
|
||||
case 'OFR ':
|
||||
|
||||
// shortcut
|
||||
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||
|
||||
$info['audio']['encoder'] = 'OptimFROG 4.50 alpha';
|
||||
switch ($BlockSize) {
|
||||
case 12:
|
||||
case 15:
|
||||
// good
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['warning'][] = '"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)';
|
||||
break;
|
||||
}
|
||||
$BlockData .= fread($this->getid3->fp, $BlockSize);
|
||||
|
||||
$thisfile_ofr_thisblock['total_samples'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 6));
|
||||
$offset += 6;
|
||||
$thisfile_ofr_thisblock['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
|
||||
$thisfile_ofr_thisblock['sample_type'] = $this->OptimFROGsampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']);
|
||||
$offset += 1;
|
||||
$thisfile_ofr_thisblock['channel_config'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
|
||||
$thisfile_ofr_thisblock['channels'] = $thisfile_ofr_thisblock['channel_config'];
|
||||
$offset += 1;
|
||||
$thisfile_ofr_thisblock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
if ($BlockSize > 12) {
|
||||
|
||||
// OFR 4.504b or higher
|
||||
$thisfile_ofr_thisblock['channels'] = $this->OptimFROGchannelConfigNumChannelsLookup($thisfile_ofr_thisblock['channel_config']);
|
||||
$thisfile_ofr_thisblock['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2));
|
||||
$thisfile_ofr_thisblock['encoder'] = $this->OptimFROGencoderNameLookup($thisfile_ofr_thisblock['raw']['encoder_id']);
|
||||
$offset += 2;
|
||||
$thisfile_ofr_thisblock['raw']['compression'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
|
||||
$thisfile_ofr_thisblock['compression'] = $this->OptimFROGcompressionLookup($thisfile_ofr_thisblock['raw']['compression']);
|
||||
$thisfile_ofr_thisblock['speedup'] = $this->OptimFROGspeedupLookup($thisfile_ofr_thisblock['raw']['compression']);
|
||||
$offset += 1;
|
||||
|
||||
$info['audio']['encoder'] = 'OptimFROG '.$thisfile_ofr_thisblock['encoder'];
|
||||
$info['audio']['encoder_options'] = '--mode '.$thisfile_ofr_thisblock['compression'];
|
||||
|
||||
if ((($thisfile_ofr_thisblock['raw']['encoder_id'] & 0xF0) >> 4) == 7) { // v4.507
|
||||
if (strtolower(getid3_lib::fileextension($info['filename'])) == 'ofs') {
|
||||
// OptimFROG DualStream format is lossy, but as of v4.507 there is no way to tell the difference
|
||||
// between lossless and lossy other than the file extension.
|
||||
$info['audio']['dataformat'] = 'ofs';
|
||||
$info['audio']['lossless'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$info['audio']['channels'] = $thisfile_ofr_thisblock['channels'];
|
||||
$info['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate'];
|
||||
$info['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']);
|
||||
break;
|
||||
|
||||
|
||||
case 'COMP':
|
||||
// unlike other block types, there CAN be multiple COMP blocks
|
||||
|
||||
$COMPdata['offset'] = $BlockOffset;
|
||||
$COMPdata['size'] = $BlockSize;
|
||||
|
||||
if ($info['avdataoffset'] == 0) {
|
||||
$info['avdataoffset'] = $BlockOffset;
|
||||
}
|
||||
|
||||
// Only interested in first 14 bytes (only first 12 needed for v4.50 alpha), not actual audio data
|
||||
$BlockData .= fread($this->getid3->fp, 14);
|
||||
fseek($this->getid3->fp, $BlockSize - 14, SEEK_CUR);
|
||||
|
||||
$COMPdata['crc_32'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4));
|
||||
$offset += 4;
|
||||
$COMPdata['sample_count'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4));
|
||||
$offset += 4;
|
||||
$COMPdata['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
|
||||
$COMPdata['sample_type'] = $this->OptimFROGsampleTypeLookup($COMPdata['raw']['sample_type']);
|
||||
$offset += 1;
|
||||
$COMPdata['raw']['channel_configuration'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
|
||||
$COMPdata['channel_configuration'] = $this->OptimFROGchannelConfigurationLookup($COMPdata['raw']['channel_configuration']);
|
||||
$offset += 1;
|
||||
$COMPdata['raw']['algorithm_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2));
|
||||
//$COMPdata['algorithm'] = OptimFROGalgorithmNameLookup($COMPdata['raw']['algorithm_id']);
|
||||
$offset += 2;
|
||||
|
||||
if ($info['ofr']['OFR ']['size'] > 12) {
|
||||
|
||||
// OFR 4.504b or higher
|
||||
$COMPdata['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2));
|
||||
$COMPdata['encoder'] = $this->OptimFROGencoderNameLookup($COMPdata['raw']['encoder_id']);
|
||||
$offset += 2;
|
||||
|
||||
}
|
||||
|
||||
if ($COMPdata['crc_32'] == 0x454E4F4E) {
|
||||
// ASCII value of 'NONE' - placeholder value in v4.50a
|
||||
$COMPdata['crc_32'] = false;
|
||||
}
|
||||
|
||||
$thisfile_ofr_thisblock[] = $COMPdata;
|
||||
break;
|
||||
|
||||
case 'HEAD':
|
||||
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||
|
||||
$RIFFdata .= fread($this->getid3->fp, $BlockSize);
|
||||
break;
|
||||
|
||||
case 'TAIL':
|
||||
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||
|
||||
if ($BlockSize > 0) {
|
||||
$RIFFdata .= fread($this->getid3->fp, $BlockSize);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'RECV':
|
||||
// block contains no useful meta data - simply note and skip
|
||||
|
||||
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||
|
||||
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||
break;
|
||||
|
||||
|
||||
case 'APET':
|
||||
// APEtag v2
|
||||
|
||||
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||
$info['warning'][] = 'APEtag processing inside OptimFROG not supported in this version ('.$this->getid3->version().') of getID3()';
|
||||
|
||||
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||
break;
|
||||
|
||||
|
||||
case 'MD5 ':
|
||||
// APEtag v2
|
||||
|
||||
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||
|
||||
if ($BlockSize == 16) {
|
||||
|
||||
$thisfile_ofr_thisblock['md5_binary'] = fread($this->getid3->fp, $BlockSize);
|
||||
$thisfile_ofr_thisblock['md5_string'] = getid3_lib::PrintHexBytes($thisfile_ofr_thisblock['md5_binary'], true, false, false);
|
||||
$info['md5_data_source'] = $thisfile_ofr_thisblock['md5_string'];
|
||||
|
||||
} else {
|
||||
|
||||
$info['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead';
|
||||
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||
|
||||
$info['warning'][] = 'Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset'];
|
||||
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isset($info['ofr']['TAIL']['offset'])) {
|
||||
$info['avdataend'] = $info['ofr']['TAIL']['offset'];
|
||||
}
|
||||
|
||||
$info['playtime_seconds'] = (float) $info['ofr']['OFR ']['total_samples'] / ($info['audio']['channels'] * $info['audio']['sample_rate']);
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
|
||||
// move the data chunk after all other chunks (if any)
|
||||
// so that the RIFF parser doesn't see EOF when trying
|
||||
// to skip over the data chunk
|
||||
$RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8);
|
||||
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($this->getid3->filename);
|
||||
$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
|
||||
$getid3_temp->info['avdataend'] = $info['avdataend'];
|
||||
$getid3_riff = new getid3_riff($getid3_temp);
|
||||
$getid3_riff->ParseRIFFdata($RIFFdata);
|
||||
$info['riff'] = $getid3_temp->info['riff'];
|
||||
|
||||
unset($getid3_riff, $getid3_temp, $RIFFdata);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static function OptimFROGsampleTypeLookup($SampleType) {
|
||||
static $OptimFROGsampleTypeLookup = array(
|
||||
0 => 'unsigned int (8-bit)',
|
||||
1 => 'signed int (8-bit)',
|
||||
2 => 'unsigned int (16-bit)',
|
||||
3 => 'signed int (16-bit)',
|
||||
4 => 'unsigned int (24-bit)',
|
||||
5 => 'signed int (24-bit)',
|
||||
6 => 'unsigned int (32-bit)',
|
||||
7 => 'signed int (32-bit)',
|
||||
8 => 'float 0.24 (32-bit)',
|
||||
9 => 'float 16.8 (32-bit)',
|
||||
10 => 'float 24.0 (32-bit)'
|
||||
);
|
||||
return (isset($OptimFROGsampleTypeLookup[$SampleType]) ? $OptimFROGsampleTypeLookup[$SampleType] : false);
|
||||
}
|
||||
|
||||
public static function OptimFROGbitsPerSampleTypeLookup($SampleType) {
|
||||
static $OptimFROGbitsPerSampleTypeLookup = array(
|
||||
0 => 8,
|
||||
1 => 8,
|
||||
2 => 16,
|
||||
3 => 16,
|
||||
4 => 24,
|
||||
5 => 24,
|
||||
6 => 32,
|
||||
7 => 32,
|
||||
8 => 32,
|
||||
9 => 32,
|
||||
10 => 32
|
||||
);
|
||||
return (isset($OptimFROGbitsPerSampleTypeLookup[$SampleType]) ? $OptimFROGbitsPerSampleTypeLookup[$SampleType] : false);
|
||||
}
|
||||
|
||||
public static function OptimFROGchannelConfigurationLookup($ChannelConfiguration) {
|
||||
static $OptimFROGchannelConfigurationLookup = array(
|
||||
0 => 'mono',
|
||||
1 => 'stereo'
|
||||
);
|
||||
return (isset($OptimFROGchannelConfigurationLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigurationLookup[$ChannelConfiguration] : false);
|
||||
}
|
||||
|
||||
public static function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) {
|
||||
static $OptimFROGchannelConfigNumChannelsLookup = array(
|
||||
0 => 1,
|
||||
1 => 2
|
||||
);
|
||||
return (isset($OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration] : false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// static function OptimFROGalgorithmNameLookup($AlgorithID) {
|
||||
// static $OptimFROGalgorithmNameLookup = array();
|
||||
// return (isset($OptimFROGalgorithmNameLookup[$AlgorithID]) ? $OptimFROGalgorithmNameLookup[$AlgorithID] : false);
|
||||
// }
|
||||
|
||||
|
||||
public static function OptimFROGencoderNameLookup($EncoderID) {
|
||||
// version = (encoderID >> 4) + 4500
|
||||
// system = encoderID & 0xF
|
||||
|
||||
$EncoderVersion = number_format(((($EncoderID & 0xF0) >> 4) + 4500) / 1000, 3);
|
||||
$EncoderSystemID = ($EncoderID & 0x0F);
|
||||
|
||||
static $OptimFROGencoderSystemLookup = array(
|
||||
0x00 => 'Windows console',
|
||||
0x01 => 'Linux console',
|
||||
0x0F => 'unknown'
|
||||
);
|
||||
return $EncoderVersion.' ('.(isset($OptimFROGencoderSystemLookup[$EncoderSystemID]) ? $OptimFROGencoderSystemLookup[$EncoderSystemID] : 'undefined encoder type (0x'.dechex($EncoderSystemID).')').')';
|
||||
}
|
||||
|
||||
public static function OptimFROGcompressionLookup($CompressionID) {
|
||||
// mode = compression >> 3
|
||||
// speedup = compression & 0x07
|
||||
|
||||
$CompressionModeID = ($CompressionID & 0xF8) >> 3;
|
||||
//$CompressionSpeedupID = ($CompressionID & 0x07);
|
||||
|
||||
static $OptimFROGencoderModeLookup = array(
|
||||
0x00 => 'fast',
|
||||
0x01 => 'normal',
|
||||
0x02 => 'high',
|
||||
0x03 => 'extra', // extranew (some versions)
|
||||
0x04 => 'best', // bestnew (some versions)
|
||||
0x05 => 'ultra',
|
||||
0x06 => 'insane',
|
||||
0x07 => 'highnew',
|
||||
0x08 => 'extranew',
|
||||
0x09 => 'bestnew'
|
||||
);
|
||||
return (isset($OptimFROGencoderModeLookup[$CompressionModeID]) ? $OptimFROGencoderModeLookup[$CompressionModeID] : 'undefined mode (0x'.str_pad(dechex($CompressionModeID), 2, '0', STR_PAD_LEFT).')');
|
||||
}
|
||||
|
||||
public static function OptimFROGspeedupLookup($CompressionID) {
|
||||
// mode = compression >> 3
|
||||
// speedup = compression & 0x07
|
||||
|
||||
//$CompressionModeID = ($CompressionID & 0xF8) >> 3;
|
||||
$CompressionSpeedupID = ($CompressionID & 0x07);
|
||||
|
||||
static $OptimFROGencoderSpeedupLookup = array(
|
||||
0x00 => '1x',
|
||||
0x01 => '2x',
|
||||
0x02 => '4x'
|
||||
);
|
||||
return (isset($OptimFROGencoderSpeedupLookup[$CompressionSpeedupID]) ? $OptimFROGencoderSpeedupLookup[$CompressionSpeedupID] : 'undefined mode (0x'.dechex($CompressionSpeedupID));
|
||||
}
|
||||
|
||||
}
|
92
api/getID3/module.audio.rkau.php
Executable file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.shorten.php //
|
||||
// module for analyzing Shorten Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_rkau extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$RKAUHeader = fread($this->getid3->fp, 20);
|
||||
$magic = 'RKA';
|
||||
if (substr($RKAUHeader, 0, 3) != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($RKAUHeader, 0, 3)).'"';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['fileformat'] = 'rkau';
|
||||
$info['audio']['dataformat'] = 'rkau';
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
|
||||
$info['rkau']['raw']['version'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 3, 1));
|
||||
$info['rkau']['version'] = '1.'.str_pad($info['rkau']['raw']['version'] & 0x0F, 2, '0', STR_PAD_LEFT);
|
||||
if (($info['rkau']['version'] > 1.07) || ($info['rkau']['version'] < 1.06)) {
|
||||
$info['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'].')';
|
||||
unset($info['rkau']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['rkau']['source_bytes'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 4, 4));
|
||||
$info['rkau']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 8, 4));
|
||||
$info['rkau']['channels'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 12, 1));
|
||||
$info['rkau']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 13, 1));
|
||||
|
||||
$info['rkau']['raw']['quality'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 14, 1));
|
||||
$this->RKAUqualityLookup($info['rkau']);
|
||||
|
||||
$info['rkau']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 15, 1));
|
||||
$info['rkau']['flags']['joint_stereo'] = (bool) (!($info['rkau']['raw']['flags'] & 0x01));
|
||||
$info['rkau']['flags']['streaming'] = (bool) ($info['rkau']['raw']['flags'] & 0x02);
|
||||
$info['rkau']['flags']['vrq_lossy_mode'] = (bool) ($info['rkau']['raw']['flags'] & 0x04);
|
||||
|
||||
if ($info['rkau']['flags']['streaming']) {
|
||||
$info['avdataoffset'] += 20;
|
||||
$info['rkau']['compressed_bytes'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 16, 4));
|
||||
} else {
|
||||
$info['avdataoffset'] += 16;
|
||||
$info['rkau']['compressed_bytes'] = $info['avdataend'] - $info['avdataoffset'] - 1;
|
||||
}
|
||||
// Note: compressed_bytes does not always equal what appears to be the actual number of compressed bytes,
|
||||
// sometimes it's more, sometimes less. No idea why(?)
|
||||
|
||||
$info['audio']['lossless'] = $info['rkau']['lossless'];
|
||||
$info['audio']['channels'] = $info['rkau']['channels'];
|
||||
$info['audio']['bits_per_sample'] = $info['rkau']['bits_per_sample'];
|
||||
$info['audio']['sample_rate'] = $info['rkau']['sample_rate'];
|
||||
|
||||
$info['playtime_seconds'] = $info['rkau']['source_bytes'] / ($info['rkau']['sample_rate'] * $info['rkau']['channels'] * ($info['rkau']['bits_per_sample'] / 8));
|
||||
$info['audio']['bitrate'] = ($info['rkau']['compressed_bytes'] * 8) / $info['playtime_seconds'];
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function RKAUqualityLookup(&$RKAUdata) {
|
||||
$level = ($RKAUdata['raw']['quality'] & 0xF0) >> 4;
|
||||
$quality = $RKAUdata['raw']['quality'] & 0x0F;
|
||||
|
||||
$RKAUdata['lossless'] = (($quality == 0) ? true : false);
|
||||
$RKAUdata['compression_level'] = $level + 1;
|
||||
if (!$RKAUdata['lossless']) {
|
||||
$RKAUdata['quality_setting'] = $quality;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
181
api/getID3/module.audio.shorten.php
Executable file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.shorten.php //
|
||||
// module for analyzing Shorten Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_shorten extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
|
||||
$ShortenHeader = fread($this->getid3->fp, 8);
|
||||
$magic = 'ajkg';
|
||||
if (substr($ShortenHeader, 0, 4) != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($ShortenHeader, 0, 4)).'"';
|
||||
return false;
|
||||
}
|
||||
$info['fileformat'] = 'shn';
|
||||
$info['audio']['dataformat'] = 'shn';
|
||||
$info['audio']['lossless'] = true;
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
|
||||
$info['shn']['version'] = getid3_lib::LittleEndian2Int(substr($ShortenHeader, 4, 1));
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataend'] - 12, SEEK_SET);
|
||||
$SeekTableSignatureTest = fread($this->getid3->fp, 12);
|
||||
$info['shn']['seektable']['present'] = (bool) (substr($SeekTableSignatureTest, 4, 8) == 'SHNAMPSK');
|
||||
if ($info['shn']['seektable']['present']) {
|
||||
$info['shn']['seektable']['length'] = getid3_lib::LittleEndian2Int(substr($SeekTableSignatureTest, 0, 4));
|
||||
$info['shn']['seektable']['offset'] = $info['avdataend'] - $info['shn']['seektable']['length'];
|
||||
fseek($this->getid3->fp, $info['shn']['seektable']['offset'], SEEK_SET);
|
||||
$SeekTableMagic = fread($this->getid3->fp, 4);
|
||||
$magic = 'SEEK';
|
||||
if ($SeekTableMagic != $magic) {
|
||||
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['shn']['seektable']['offset'].', found "'.getid3_lib::PrintHexBytes($SeekTableMagic).'"';
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
// typedef struct tag_TSeekEntry
|
||||
// {
|
||||
// unsigned long SampleNumber;
|
||||
// unsigned long SHNFileByteOffset;
|
||||
// unsigned long SHNLastBufferReadPosition;
|
||||
// unsigned short SHNByteGet;
|
||||
// unsigned short SHNBufferOffset;
|
||||
// unsigned short SHNFileBitOffset;
|
||||
// unsigned long SHNGBuffer;
|
||||
// unsigned short SHNBitShift;
|
||||
// long CBuf0[3];
|
||||
// long CBuf1[3];
|
||||
// long Offset0[4];
|
||||
// long Offset1[4];
|
||||
// }TSeekEntry;
|
||||
|
||||
$SeekTableData = fread($this->getid3->fp, $info['shn']['seektable']['length'] - 16);
|
||||
$info['shn']['seektable']['entry_count'] = floor(strlen($SeekTableData) / 80);
|
||||
//$info['shn']['seektable']['entries'] = array();
|
||||
//$SeekTableOffset = 0;
|
||||
//for ($i = 0; $i < $info['shn']['seektable']['entry_count']; $i++) {
|
||||
// $SeekTableEntry['sample_number'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||
// $SeekTableOffset += 4;
|
||||
// $SeekTableEntry['shn_file_byte_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||
// $SeekTableOffset += 4;
|
||||
// $SeekTableEntry['shn_last_buffer_read_position'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||
// $SeekTableOffset += 4;
|
||||
// $SeekTableEntry['shn_byte_get'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
|
||||
// $SeekTableOffset += 2;
|
||||
// $SeekTableEntry['shn_buffer_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
|
||||
// $SeekTableOffset += 2;
|
||||
// $SeekTableEntry['shn_file_bit_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
|
||||
// $SeekTableOffset += 2;
|
||||
// $SeekTableEntry['shn_gbuffer'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||
// $SeekTableOffset += 4;
|
||||
// $SeekTableEntry['shn_bit_shift'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
|
||||
// $SeekTableOffset += 2;
|
||||
// for ($j = 0; $j < 3; $j++) {
|
||||
// $SeekTableEntry['cbuf0'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||
// $SeekTableOffset += 4;
|
||||
// }
|
||||
// for ($j = 0; $j < 3; $j++) {
|
||||
// $SeekTableEntry['cbuf1'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||
// $SeekTableOffset += 4;
|
||||
// }
|
||||
// for ($j = 0; $j < 4; $j++) {
|
||||
// $SeekTableEntry['offset0'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||
// $SeekTableOffset += 4;
|
||||
// }
|
||||
// for ($j = 0; $j < 4; $j++) {
|
||||
// $SeekTableEntry['offset1'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||
// $SeekTableOffset += 4;
|
||||
// }
|
||||
//
|
||||
// $info['shn']['seektable']['entries'][] = $SeekTableEntry;
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
|
||||
$info['error'][] = 'PHP running in Safe Mode - backtick operator not available, cannot run shntool to analyze Shorten files';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GETID3_OS_ISWINDOWS) {
|
||||
|
||||
$RequiredFiles = array('shorten.exe', 'cygwin1.dll', 'head.exe');
|
||||
foreach ($RequiredFiles as $required_file) {
|
||||
if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) {
|
||||
$info['error'][] = GETID3_HELPERAPPSDIR.$required_file.' does not exist';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$commandline = GETID3_HELPERAPPSDIR.'shorten.exe -x "'.$info['filenamepath'].'" - | '.GETID3_HELPERAPPSDIR.'head.exe -c 64';
|
||||
$commandline = str_replace('/', '\\', $commandline);
|
||||
|
||||
} else {
|
||||
|
||||
static $shorten_present;
|
||||
if (!isset($shorten_present)) {
|
||||
$shorten_present = file_exists('/usr/local/bin/shorten') || `which shorten`;
|
||||
}
|
||||
if (!$shorten_present) {
|
||||
$info['error'][] = 'shorten binary was not found in path or /usr/local/bin';
|
||||
return false;
|
||||
}
|
||||
$commandline = (file_exists('/usr/local/bin/shorten') ? '/usr/local/bin/' : '' ) . 'shorten -x '.escapeshellarg($info['filenamepath']).' - | head -c 64';
|
||||
|
||||
}
|
||||
|
||||
$output = `$commandline`;
|
||||
|
||||
if (!empty($output) && (substr($output, 12, 4) == 'fmt ')) {
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||
|
||||
$fmt_size = getid3_lib::LittleEndian2Int(substr($output, 16, 4));
|
||||
$DecodedWAVFORMATEX = getid3_riff::parseWAVEFORMATex(substr($output, 20, $fmt_size));
|
||||
$info['audio']['channels'] = $DecodedWAVFORMATEX['channels'];
|
||||
$info['audio']['bits_per_sample'] = $DecodedWAVFORMATEX['bits_per_sample'];
|
||||
$info['audio']['sample_rate'] = $DecodedWAVFORMATEX['sample_rate'];
|
||||
|
||||
if (substr($output, 20 + $fmt_size, 4) == 'data') {
|
||||
|
||||
$info['playtime_seconds'] = getid3_lib::LittleEndian2Int(substr($output, 20 + 4 + $fmt_size, 4)) / $DecodedWAVFORMATEX['raw']['nAvgBytesPerSec'];
|
||||
|
||||
} else {
|
||||
|
||||
$info['error'][] = 'shorten failed to decode DATA chunk to expected location, cannot determine playtime';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8;
|
||||
|
||||
} else {
|
||||
|
||||
$info['error'][] = 'shorten failed to decode file to WAV for parsing';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
106
api/getID3/module.audio.tta.php
Executable file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.tta.php //
|
||||
// module for analyzing TTA Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_tta extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'tta';
|
||||
$info['audio']['dataformat'] = 'tta';
|
||||
$info['audio']['lossless'] = true;
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$ttaheader = fread($this->getid3->fp, 26);
|
||||
|
||||
$info['tta']['magic'] = substr($ttaheader, 0, 3);
|
||||
$magic = 'TTA';
|
||||
if ($info['tta']['magic'] != $magic) {
|
||||
$info['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."
|
||||
$info['tta']['major_version'] = 1;
|
||||
$info['avdataoffset'] += 16;
|
||||
|
||||
$info['tta']['compression_level'] = ord($ttaheader{3});
|
||||
$info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2));
|
||||
$info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2));
|
||||
$info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 4));
|
||||
$info['tta']['samples_per_channel'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 12, 4));
|
||||
|
||||
$info['audio']['encoder_options'] = '-e'.$info['tta']['compression_level'];
|
||||
$info['playtime_seconds'] = $info['tta']['samples_per_channel'] / $info['tta']['sample_rate'];
|
||||
break;
|
||||
|
||||
case '2': // TTA v2.x
|
||||
// "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."
|
||||
$info['tta']['major_version'] = 2;
|
||||
$info['avdataoffset'] += 20;
|
||||
|
||||
$info['tta']['compression_level'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2));
|
||||
$info['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2));
|
||||
$info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2));
|
||||
$info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 2));
|
||||
$info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 12, 4));
|
||||
$info['tta']['data_length'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 16, 4));
|
||||
|
||||
$info['audio']['encoder_options'] = '-e'.$info['tta']['compression_level'];
|
||||
$info['playtime_seconds'] = $info['tta']['data_length'] / $info['tta']['sample_rate'];
|
||||
break;
|
||||
|
||||
case '1': // TTA v3.x
|
||||
// "This is a first stable release of the TTA format. It will be supported by the encoders v3 or higher."
|
||||
$info['tta']['major_version'] = 3;
|
||||
$info['avdataoffset'] += 26;
|
||||
|
||||
$info['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); // getid3_riff::wFormatTagLookup()
|
||||
$info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2));
|
||||
$info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2));
|
||||
$info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 4));
|
||||
$info['tta']['data_length'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 14, 4));
|
||||
$info['tta']['crc32_footer'] = substr($ttaheader, 18, 4);
|
||||
$info['tta']['seek_point'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 22, 4));
|
||||
|
||||
$info['playtime_seconds'] = $info['tta']['data_length'] / $info['tta']['sample_rate'];
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['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};
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
$info['audio']['encoder'] = 'TTA v'.$info['tta']['major_version'];
|
||||
$info['audio']['bits_per_sample'] = $info['tta']['bits_per_sample'];
|
||||
$info['audio']['sample_rate'] = $info['tta']['sample_rate'];
|
||||
$info['audio']['channels'] = $info['tta']['channels'];
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
204
api/getID3/module.audio.voc.php
Executable file
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.voc.php //
|
||||
// module for analyzing Creative VOC Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_voc extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$OriginalAVdataOffset = $info['avdataoffset'];
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$VOCheader = fread($this->getid3->fp, 26);
|
||||
|
||||
$magic = 'Creative Voice File';
|
||||
if (substr($VOCheader, 0, 19) != $magic) {
|
||||
$info['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
|
||||
|
||||
// byte # Description
|
||||
// ------ ------------------------------------------
|
||||
// 00-12 'Creative Voice File'
|
||||
// 13 1A (eof to abort printing of file)
|
||||
// 14-15 Offset of first datablock in .voc file (std 1A 00 in Intel Notation)
|
||||
// 16-17 Version number (minor,major) (VOC-HDR puts 0A 01)
|
||||
// 18-19 2's Comp of Ver. # + 1234h (VOC-HDR puts 29 11)
|
||||
|
||||
$thisfile_voc['header']['datablock_offset'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 20, 2));
|
||||
$thisfile_voc['header']['minor_version'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 22, 1));
|
||||
$thisfile_voc['header']['major_version'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 23, 1));
|
||||
|
||||
do {
|
||||
|
||||
$BlockOffset = ftell($this->getid3->fp);
|
||||
$BlockData = fread($this->getid3->fp, 4);
|
||||
$BlockType = ord($BlockData{0});
|
||||
$BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 1, 3));
|
||||
$ThisBlock = array();
|
||||
|
||||
getid3_lib::safe_inc($thisfile_voc['blocktypes'][$BlockType], 1);
|
||||
switch ($BlockType) {
|
||||
case 0: // Terminator
|
||||
// do nothing, we'll break out of the loop down below
|
||||
break;
|
||||
|
||||
case 1: // Sound data
|
||||
$BlockData .= fread($this->getid3->fp, 2);
|
||||
if ($info['avdataoffset'] <= $OriginalAVdataOffset) {
|
||||
$info['avdataoffset'] = ftell($this->getid3->fp);
|
||||
}
|
||||
fseek($this->getid3->fp, $BlockSize - 2, SEEK_CUR);
|
||||
|
||||
$ThisBlock['sample_rate_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 1));
|
||||
$ThisBlock['compression_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, 5, 1));
|
||||
|
||||
$ThisBlock['compression_name'] = $this->VOCcompressionTypeLookup($ThisBlock['compression_type']);
|
||||
if ($ThisBlock['compression_type'] <= 3) {
|
||||
$thisfile_voc['compressed_bits_per_sample'] = getid3_lib::CastAsInt(str_replace('-bit', '', $ThisBlock['compression_name']));
|
||||
}
|
||||
|
||||
// Less accurate sample_rate calculation than the Extended block (#8) data (but better than nothing if Extended Block is not available)
|
||||
if (empty($thisfile_audio['sample_rate'])) {
|
||||
// SR byte = 256 - (1000000 / sample_rate)
|
||||
$thisfile_audio['sample_rate'] = getid3_lib::trunc((1000000 / (256 - $ThisBlock['sample_rate_id'])) / $thisfile_audio['channels']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // Sound continue
|
||||
case 3: // Silence
|
||||
case 4: // Marker
|
||||
case 6: // Repeat
|
||||
case 7: // End repeat
|
||||
// nothing useful, just skip
|
||||
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||
break;
|
||||
|
||||
case 8: // Extended
|
||||
$BlockData .= fread($this->getid3->fp, 4);
|
||||
|
||||
//00-01 Time Constant:
|
||||
// Mono: 65536 - (256000000 / sample_rate)
|
||||
// Stereo: 65536 - (256000000 / (sample_rate * 2))
|
||||
$ThisBlock['time_constant'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 2));
|
||||
$ThisBlock['pack_method'] = getid3_lib::LittleEndian2Int(substr($BlockData, 6, 1));
|
||||
$ThisBlock['stereo'] = (bool) getid3_lib::LittleEndian2Int(substr($BlockData, 7, 1));
|
||||
|
||||
$thisfile_audio['channels'] = ($ThisBlock['stereo'] ? 2 : 1);
|
||||
$thisfile_audio['sample_rate'] = getid3_lib::trunc((256000000 / (65536 - $ThisBlock['time_constant'])) / $thisfile_audio['channels']);
|
||||
break;
|
||||
|
||||
case 9: // data block that supersedes blocks 1 and 8. Used for stereo, 16 bit
|
||||
$BlockData .= fread($this->getid3->fp, 12);
|
||||
if ($info['avdataoffset'] <= $OriginalAVdataOffset) {
|
||||
$info['avdataoffset'] = ftell($this->getid3->fp);
|
||||
}
|
||||
fseek($this->getid3->fp, $BlockSize - 12, SEEK_CUR);
|
||||
|
||||
$ThisBlock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4));
|
||||
$ThisBlock['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($BlockData, 8, 1));
|
||||
$ThisBlock['channels'] = getid3_lib::LittleEndian2Int(substr($BlockData, 9, 1));
|
||||
$ThisBlock['wFormat'] = getid3_lib::LittleEndian2Int(substr($BlockData, 10, 2));
|
||||
|
||||
$ThisBlock['compression_name'] = $this->VOCwFormatLookup($ThisBlock['wFormat']);
|
||||
if ($this->VOCwFormatActualBitsPerSampleLookup($ThisBlock['wFormat'])) {
|
||||
$thisfile_voc['compressed_bits_per_sample'] = $this->VOCwFormatActualBitsPerSampleLookup($ThisBlock['wFormat']);
|
||||
}
|
||||
|
||||
$thisfile_audio['sample_rate'] = $ThisBlock['sample_rate'];
|
||||
$thisfile_audio['bits_per_sample'] = $ThisBlock['bits_per_sample'];
|
||||
$thisfile_audio['channels'] = $ThisBlock['channels'];
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['warning'][] = 'Unhandled block type "'.$BlockType.'" at offset '.$BlockOffset;
|
||||
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($ThisBlock)) {
|
||||
$ThisBlock['block_offset'] = $BlockOffset;
|
||||
$ThisBlock['block_size'] = $BlockSize;
|
||||
$ThisBlock['block_type_id'] = $BlockType;
|
||||
$thisfile_voc['blocks'][] = $ThisBlock;
|
||||
}
|
||||
|
||||
} while (!feof($this->getid3->fp) && ($BlockType != 0));
|
||||
|
||||
// Terminator block doesn't have size field, so seek back 3 spaces
|
||||
fseek($this->getid3->fp, -3, SEEK_CUR);
|
||||
|
||||
ksort($thisfile_voc['blocktypes']);
|
||||
|
||||
if (!empty($thisfile_voc['compressed_bits_per_sample'])) {
|
||||
$info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($thisfile_voc['compressed_bits_per_sample'] * $thisfile_audio['channels'] * $thisfile_audio['sample_rate']);
|
||||
$thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function VOCcompressionTypeLookup($index) {
|
||||
static $VOCcompressionTypeLookup = array(
|
||||
0 => '8-bit',
|
||||
1 => '4-bit',
|
||||
2 => '2.6-bit',
|
||||
3 => '2-bit'
|
||||
);
|
||||
return (isset($VOCcompressionTypeLookup[$index]) ? $VOCcompressionTypeLookup[$index] : 'Multi DAC ('.($index - 3).') channels');
|
||||
}
|
||||
|
||||
public function VOCwFormatLookup($index) {
|
||||
static $VOCwFormatLookup = array(
|
||||
0x0000 => '8-bit unsigned PCM',
|
||||
0x0001 => 'Creative 8-bit to 4-bit ADPCM',
|
||||
0x0002 => 'Creative 8-bit to 3-bit ADPCM',
|
||||
0x0003 => 'Creative 8-bit to 2-bit ADPCM',
|
||||
0x0004 => '16-bit signed PCM',
|
||||
0x0006 => 'CCITT a-Law',
|
||||
0x0007 => 'CCITT u-Law',
|
||||
0x2000 => 'Creative 16-bit to 4-bit ADPCM'
|
||||
);
|
||||
return (isset($VOCwFormatLookup[$index]) ? $VOCwFormatLookup[$index] : false);
|
||||
}
|
||||
|
||||
public function VOCwFormatActualBitsPerSampleLookup($index) {
|
||||
static $VOCwFormatLookup = array(
|
||||
0x0000 => 8,
|
||||
0x0001 => 4,
|
||||
0x0002 => 3,
|
||||
0x0003 => 2,
|
||||
0x0004 => 16,
|
||||
0x0006 => 8,
|
||||
0x0007 => 8,
|
||||
0x2000 => 4
|
||||
);
|
||||
return (isset($VOCwFormatLookup[$index]) ? $VOCwFormatLookup[$index] : false);
|
||||
}
|
||||
|
||||
}
|
159
api/getID3/module.audio.vqf.php
Executable file
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.vqf.php //
|
||||
// module for analyzing VQF audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_vqf extends getid3_handler
|
||||
{
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// based loosely on code from TTwinVQ by Jurgen Faul <jfaulØgmx*de>
|
||||
// http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html
|
||||
|
||||
$info['fileformat'] = 'vqf';
|
||||
$info['audio']['dataformat'] = 'vqf';
|
||||
$info['audio']['bitrate_mode'] = 'cbr';
|
||||
$info['audio']['lossless'] = false;
|
||||
|
||||
// shortcut
|
||||
$info['vqf']['raw'] = array();
|
||||
$thisfile_vqf = &$info['vqf'];
|
||||
$thisfile_vqf_raw = &$thisfile_vqf['raw'];
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$VQFheaderData = fread($this->getid3->fp, 16);
|
||||
|
||||
$offset = 0;
|
||||
$thisfile_vqf_raw['header_tag'] = substr($VQFheaderData, $offset, 4);
|
||||
$magic = 'TWIN';
|
||||
if ($thisfile_vqf_raw['header_tag'] != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_vqf_raw['header_tag']).'"';
|
||||
unset($info['vqf']);
|
||||
unset($info['fileformat']);
|
||||
return false;
|
||||
}
|
||||
$offset += 4;
|
||||
$thisfile_vqf_raw['version'] = substr($VQFheaderData, $offset, 8);
|
||||
$offset += 8;
|
||||
$thisfile_vqf_raw['size'] = getid3_lib::BigEndian2Int(substr($VQFheaderData, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
while (ftell($this->getid3->fp) < $info['avdataend']) {
|
||||
|
||||
$ChunkBaseOffset = ftell($this->getid3->fp);
|
||||
$chunkoffset = 0;
|
||||
$ChunkData = fread($this->getid3->fp, 8);
|
||||
$ChunkName = substr($ChunkData, $chunkoffset, 4);
|
||||
if ($ChunkName == 'DATA') {
|
||||
$info['avdataoffset'] = $ChunkBaseOffset;
|
||||
break;
|
||||
}
|
||||
$chunkoffset += 4;
|
||||
$ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
|
||||
$chunkoffset += 4;
|
||||
if ($ChunkSize > ($info['avdataend'] - ftell($this->getid3->fp))) {
|
||||
$info['error'][] = 'Invalid chunk size ('.$ChunkSize.') for chunk "'.$ChunkName.'" at offset '.$ChunkBaseOffset;
|
||||
break;
|
||||
}
|
||||
if ($ChunkSize > 0) {
|
||||
$ChunkData .= fread($this->getid3->fp, $ChunkSize);
|
||||
}
|
||||
|
||||
switch ($ChunkName) {
|
||||
case 'COMM':
|
||||
// shortcut
|
||||
$thisfile_vqf['COMM'] = array();
|
||||
$thisfile_vqf_COMM = &$thisfile_vqf['COMM'];
|
||||
|
||||
$thisfile_vqf_COMM['channel_mode'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
|
||||
$chunkoffset += 4;
|
||||
$thisfile_vqf_COMM['bitrate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
|
||||
$chunkoffset += 4;
|
||||
$thisfile_vqf_COMM['sample_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
|
||||
$chunkoffset += 4;
|
||||
$thisfile_vqf_COMM['security_level'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
|
||||
$chunkoffset += 4;
|
||||
|
||||
$info['audio']['channels'] = $thisfile_vqf_COMM['channel_mode'] + 1;
|
||||
$info['audio']['sample_rate'] = $this->VQFchannelFrequencyLookup($thisfile_vqf_COMM['sample_rate']);
|
||||
$info['audio']['bitrate'] = $thisfile_vqf_COMM['bitrate'] * 1000;
|
||||
$info['audio']['encoder_options'] = 'CBR' . ceil($info['audio']['bitrate']/1000);
|
||||
|
||||
if ($info['audio']['bitrate'] == 0) {
|
||||
$info['error'][] = 'Corrupt VQF file: bitrate_audio == zero';
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NAME':
|
||||
case 'AUTH':
|
||||
case '(c) ':
|
||||
case 'FILE':
|
||||
case 'COMT':
|
||||
case 'ALBM':
|
||||
$thisfile_vqf['comments'][$this->VQFcommentNiceNameLookup($ChunkName)][] = trim(substr($ChunkData, 8));
|
||||
break;
|
||||
|
||||
case 'DSIZ':
|
||||
$thisfile_vqf['DSIZ'] = getid3_lib::BigEndian2Int(substr($ChunkData, 8, 4));
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['warning'][] = 'Unhandled chunk type "'.$ChunkName.'" at offset '.$ChunkBaseOffset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate'];
|
||||
|
||||
if (isset($thisfile_vqf['DSIZ']) && (($thisfile_vqf['DSIZ'] != ($info['avdataend'] - $info['avdataoffset'] - strlen('DATA'))))) {
|
||||
switch ($thisfile_vqf['DSIZ']) {
|
||||
case 0:
|
||||
case 1:
|
||||
$info['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:
|
||||
$info['warning'][] = 'Probable corrupted file - should be '.$thisfile_vqf['DSIZ'].' bytes, actually '.($info['avdataend'] - $info['avdataoffset'] - strlen('DATA'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function VQFchannelFrequencyLookup($frequencyid) {
|
||||
static $VQFchannelFrequencyLookup = array(
|
||||
11 => 11025,
|
||||
22 => 22050,
|
||||
44 => 44100
|
||||
);
|
||||
return (isset($VQFchannelFrequencyLookup[$frequencyid]) ? $VQFchannelFrequencyLookup[$frequencyid] : $frequencyid * 1000);
|
||||
}
|
||||
|
||||
public function VQFcommentNiceNameLookup($shortname) {
|
||||
static $VQFcommentNiceNameLookup = array(
|
||||
'NAME' => 'title',
|
||||
'AUTH' => 'artist',
|
||||
'(c) ' => 'copyright',
|
||||
'FILE' => 'filename',
|
||||
'COMT' => 'comment',
|
||||
'ALBM' => 'album'
|
||||
);
|
||||
return (isset($VQFcommentNiceNameLookup[$shortname]) ? $VQFcommentNiceNameLookup[$shortname] : $shortname);
|
||||
}
|
||||
|
||||
}
|
397
api/getID3/module.audio.wavpack.php
Executable file
@ -0,0 +1,397 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.wavpack.php //
|
||||
// module for analyzing WavPack v4.0+ Audio files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_wavpack extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
|
||||
while (true) {
|
||||
|
||||
$wavpackheader = fread($this->getid3->fp, 32);
|
||||
|
||||
if (ftell($this->getid3->fp) >= $info['avdataend']) {
|
||||
break;
|
||||
} elseif (feof($this->getid3->fp)) {
|
||||
break;
|
||||
} elseif (
|
||||
isset($info['wavpack']['blockheader']['total_samples']) &&
|
||||
isset($info['wavpack']['blockheader']['block_samples']) &&
|
||||
($info['wavpack']['blockheader']['total_samples'] > 0) &&
|
||||
($info['wavpack']['blockheader']['block_samples'] > 0) &&
|
||||
(!isset($info['wavpack']['riff_trailer_size']) || ($info['wavpack']['riff_trailer_size'] <= 0)) &&
|
||||
((isset($info['wavpack']['config_flags']['md5_checksum']) && ($info['wavpack']['config_flags']['md5_checksum'] === false)) || !empty($info['md5_data_source']))) {
|
||||
break;
|
||||
}
|
||||
|
||||
$blockheader_offset = ftell($this->getid3->fp) - 32;
|
||||
$blockheader_magic = substr($wavpackheader, 0, 4);
|
||||
$blockheader_size = getid3_lib::LittleEndian2Int(substr($wavpackheader, 4, 4));
|
||||
|
||||
$magic = 'wvpk';
|
||||
if ($blockheader_magic != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$blockheader_offset.', found "'.getid3_lib::PrintHexBytes($blockheader_magic).'"';
|
||||
switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') {
|
||||
case 'wavpack':
|
||||
case 'wvc':
|
||||
break;
|
||||
default:
|
||||
unset($info['fileformat']);
|
||||
unset($info['audio']);
|
||||
unset($info['wavpack']);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($info['wavpack']['blockheader']['block_samples']) ||
|
||||
empty($info['wavpack']['blockheader']['total_samples']) ||
|
||||
($info['wavpack']['blockheader']['block_samples'] <= 0) ||
|
||||
($info['wavpack']['blockheader']['total_samples'] <= 0)) {
|
||||
// Also, it is possible that the first block might not have
|
||||
// any samples (block_samples == 0) and in this case you should skip blocks
|
||||
// until you find one with samples because the other information (like
|
||||
// total_samples) are not guaranteed to be correct until (block_samples > 0)
|
||||
|
||||
// Finally, I have defined a format for files in which the length is not known
|
||||
// (for example when raw files are created using pipes). In these cases
|
||||
// total_samples will be -1 and you must seek to the final block to determine
|
||||
// the total number of samples.
|
||||
|
||||
|
||||
$info['audio']['dataformat'] = 'wavpack';
|
||||
$info['fileformat'] = 'wavpack';
|
||||
$info['audio']['lossless'] = true;
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
|
||||
$info['wavpack']['blockheader']['offset'] = $blockheader_offset;
|
||||
$info['wavpack']['blockheader']['magic'] = $blockheader_magic;
|
||||
$info['wavpack']['blockheader']['size'] = $blockheader_size;
|
||||
|
||||
if ($info['wavpack']['blockheader']['size'] >= 0x100000) {
|
||||
$info['error'][] = 'Expecting WavPack block size less than "0x100000", found "'.$info['wavpack']['blockheader']['size'].'" at offset '.$info['wavpack']['blockheader']['offset'];
|
||||
switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') {
|
||||
case 'wavpack':
|
||||
case 'wvc':
|
||||
break;
|
||||
default:
|
||||
unset($info['fileformat']);
|
||||
unset($info['audio']);
|
||||
unset($info['wavpack']);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['wavpack']['blockheader']['minor_version'] = ord($wavpackheader{8});
|
||||
$info['wavpack']['blockheader']['major_version'] = ord($wavpackheader{9});
|
||||
|
||||
if (($info['wavpack']['blockheader']['major_version'] != 4) ||
|
||||
(($info['wavpack']['blockheader']['minor_version'] < 4) &&
|
||||
($info['wavpack']['blockheader']['minor_version'] > 16))) {
|
||||
$info['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'];
|
||||
switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') {
|
||||
case 'wavpack':
|
||||
case 'wvc':
|
||||
break;
|
||||
default:
|
||||
unset($info['fileformat']);
|
||||
unset($info['audio']);
|
||||
unset($info['wavpack']);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['wavpack']['blockheader']['track_number'] = ord($wavpackheader{10}); // unused
|
||||
$info['wavpack']['blockheader']['index_number'] = ord($wavpackheader{11}); // unused
|
||||
$info['wavpack']['blockheader']['total_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 12, 4));
|
||||
$info['wavpack']['blockheader']['block_index'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 16, 4));
|
||||
$info['wavpack']['blockheader']['block_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 20, 4));
|
||||
$info['wavpack']['blockheader']['flags_raw'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 24, 4));
|
||||
$info['wavpack']['blockheader']['crc'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 28, 4));
|
||||
|
||||
$info['wavpack']['blockheader']['flags']['bytes_per_sample'] = 1 + ($info['wavpack']['blockheader']['flags_raw'] & 0x00000003);
|
||||
$info['wavpack']['blockheader']['flags']['mono'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000004);
|
||||
$info['wavpack']['blockheader']['flags']['hybrid'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000008);
|
||||
$info['wavpack']['blockheader']['flags']['joint_stereo'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000010);
|
||||
$info['wavpack']['blockheader']['flags']['cross_decorrelation'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000020);
|
||||
$info['wavpack']['blockheader']['flags']['hybrid_noiseshape'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000040);
|
||||
$info['wavpack']['blockheader']['flags']['ieee_32bit_float'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000080);
|
||||
$info['wavpack']['blockheader']['flags']['int_32bit'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000100);
|
||||
$info['wavpack']['blockheader']['flags']['hybrid_bitrate_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000200);
|
||||
$info['wavpack']['blockheader']['flags']['hybrid_balance_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000400);
|
||||
$info['wavpack']['blockheader']['flags']['multichannel_initial'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000800);
|
||||
$info['wavpack']['blockheader']['flags']['multichannel_final'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00001000);
|
||||
|
||||
$info['audio']['lossless'] = !$info['wavpack']['blockheader']['flags']['hybrid'];
|
||||
}
|
||||
|
||||
while (!feof($this->getid3->fp) && (ftell($this->getid3->fp) < ($blockheader_offset + $blockheader_size + 8))) {
|
||||
|
||||
$metablock = array('offset'=>ftell($this->getid3->fp));
|
||||
$metablockheader = fread($this->getid3->fp, 2);
|
||||
if (feof($this->getid3->fp)) {
|
||||
break;
|
||||
}
|
||||
$metablock['id'] = ord($metablockheader{0});
|
||||
$metablock['function_id'] = ($metablock['id'] & 0x3F);
|
||||
$metablock['function_name'] = $this->WavPackMetablockNameLookup($metablock['function_id']);
|
||||
|
||||
// The 0x20 bit in the id of the meta subblocks (which is defined as
|
||||
// ID_OPTIONAL_DATA) is a permanent part of the id. The idea is that
|
||||
// if a decoder encounters an id that it does not know about, it uses
|
||||
// that "ID_OPTIONAL_DATA" flag to determine what to do. If it is set
|
||||
// then the decoder simply ignores the metadata, but if it is zero
|
||||
// then the decoder should quit because it means that an understanding
|
||||
// of the metadata is required to correctly decode the audio.
|
||||
$metablock['non_decoder'] = (bool) ($metablock['id'] & 0x20);
|
||||
|
||||
$metablock['padded_data'] = (bool) ($metablock['id'] & 0x40);
|
||||
$metablock['large_block'] = (bool) ($metablock['id'] & 0x80);
|
||||
if ($metablock['large_block']) {
|
||||
$metablockheader .= fread($this->getid3->fp, 2);
|
||||
}
|
||||
$metablock['size'] = getid3_lib::LittleEndian2Int(substr($metablockheader, 1)) * 2; // size is stored in words
|
||||
$metablock['data'] = null;
|
||||
|
||||
if ($metablock['size'] > 0) {
|
||||
|
||||
switch ($metablock['function_id']) {
|
||||
case 0x21: // ID_RIFF_HEADER
|
||||
case 0x22: // ID_RIFF_TRAILER
|
||||
case 0x23: // ID_REPLAY_GAIN
|
||||
case 0x24: // ID_CUESHEET
|
||||
case 0x25: // ID_CONFIG_BLOCK
|
||||
case 0x26: // ID_MD5_CHECKSUM
|
||||
$metablock['data'] = fread($this->getid3->fp, $metablock['size']);
|
||||
|
||||
if ($metablock['padded_data']) {
|
||||
// padded to the nearest even byte
|
||||
$metablock['size']--;
|
||||
$metablock['data'] = substr($metablock['data'], 0, -1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x00: // ID_DUMMY
|
||||
case 0x01: // ID_ENCODER_INFO
|
||||
case 0x02: // ID_DECORR_TERMS
|
||||
case 0x03: // ID_DECORR_WEIGHTS
|
||||
case 0x04: // ID_DECORR_SAMPLES
|
||||
case 0x05: // ID_ENTROPY_VARS
|
||||
case 0x06: // ID_HYBRID_PROFILE
|
||||
case 0x07: // ID_SHAPING_WEIGHTS
|
||||
case 0x08: // ID_FLOAT_INFO
|
||||
case 0x09: // ID_INT32_INFO
|
||||
case 0x0A: // ID_WV_BITSTREAM
|
||||
case 0x0B: // ID_WVC_BITSTREAM
|
||||
case 0x0C: // ID_WVX_BITSTREAM
|
||||
case 0x0D: // ID_CHANNEL_INFO
|
||||
fseek($this->getid3->fp, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET);
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['warning'][] = 'Unexpected metablock type "0x'.str_pad(dechex($metablock['function_id']), 2, '0', STR_PAD_LEFT).'" at offset '.$metablock['offset'];
|
||||
fseek($this->getid3->fp, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET);
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($metablock['function_id']) {
|
||||
case 0x21: // ID_RIFF_HEADER
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||
$original_wav_filesize = getid3_lib::LittleEndian2Int(substr($metablock['data'], 4, 4));
|
||||
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($this->getid3->filename);
|
||||
$getid3_riff = new getid3_riff($getid3_temp);
|
||||
$getid3_riff->ParseRIFFdata($metablock['data']);
|
||||
$metablock['riff'] = $getid3_temp->info['riff'];
|
||||
$info['audio']['sample_rate'] = $getid3_temp->info['riff']['raw']['fmt ']['nSamplesPerSec'];
|
||||
unset($getid3_riff, $getid3_temp);
|
||||
|
||||
$metablock['riff']['original_filesize'] = $original_wav_filesize;
|
||||
$info['wavpack']['riff_trailer_size'] = $original_wav_filesize - $metablock['riff']['WAVE']['data'][0]['size'] - $metablock['riff']['header_size'];
|
||||
$info['playtime_seconds'] = $info['wavpack']['blockheader']['total_samples'] / $info['audio']['sample_rate'];
|
||||
|
||||
// Safe RIFF header in case there's a RIFF footer later
|
||||
$metablockRIFFheader = $metablock['data'];
|
||||
break;
|
||||
|
||||
|
||||
case 0x22: // ID_RIFF_TRAILER
|
||||
$metablockRIFFfooter = $metablockRIFFheader.$metablock['data'];
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||
|
||||
$startoffset = $metablock['offset'] + ($metablock['large_block'] ? 4 : 2);
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($this->getid3->filename);
|
||||
$getid3_temp->info['avdataend'] = $info['avdataend'];
|
||||
$getid3_temp->info['fileformat'] = 'riff';
|
||||
$getid3_riff = new getid3_riff($getid3_temp);
|
||||
$metablock['riff'] = $getid3_riff->ParseRIFF($startoffset, $startoffset + $metablock['size']);
|
||||
|
||||
if (!empty($metablock['riff']['INFO'])) {
|
||||
getid3_riff::parseComments($metablock['riff']['INFO'], $metablock['comments']);
|
||||
$info['tags']['riff'] = $metablock['comments'];
|
||||
}
|
||||
unset($getid3_temp, $getid3_riff);
|
||||
break;
|
||||
|
||||
|
||||
case 0x23: // ID_REPLAY_GAIN
|
||||
$info['warning'][] = 'WavPack "Replay Gain" contents not yet handled by getID3() in metablock at offset '.$metablock['offset'];
|
||||
break;
|
||||
|
||||
|
||||
case 0x24: // ID_CUESHEET
|
||||
$info['warning'][] = 'WavPack "Cuesheet" contents not yet handled by getID3() in metablock at offset '.$metablock['offset'];
|
||||
break;
|
||||
|
||||
|
||||
case 0x25: // ID_CONFIG_BLOCK
|
||||
$metablock['flags_raw'] = getid3_lib::LittleEndian2Int(substr($metablock['data'], 0, 3));
|
||||
|
||||
$metablock['flags']['adobe_mode'] = (bool) ($metablock['flags_raw'] & 0x000001); // "adobe" mode for 32-bit floats
|
||||
$metablock['flags']['fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000002); // fast mode
|
||||
$metablock['flags']['very_fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000004); // double fast
|
||||
$metablock['flags']['high_flag'] = (bool) ($metablock['flags_raw'] & 0x000008); // high quality mode
|
||||
$metablock['flags']['very_high_flag'] = (bool) ($metablock['flags_raw'] & 0x000010); // double high (not used yet)
|
||||
$metablock['flags']['bitrate_kbps'] = (bool) ($metablock['flags_raw'] & 0x000020); // bitrate is kbps, not bits / sample
|
||||
$metablock['flags']['auto_shaping'] = (bool) ($metablock['flags_raw'] & 0x000040); // automatic noise shaping
|
||||
$metablock['flags']['shape_override'] = (bool) ($metablock['flags_raw'] & 0x000080); // shaping mode specified
|
||||
$metablock['flags']['joint_override'] = (bool) ($metablock['flags_raw'] & 0x000100); // joint-stereo mode specified
|
||||
$metablock['flags']['copy_time'] = (bool) ($metablock['flags_raw'] & 0x000200); // copy file-time from source
|
||||
$metablock['flags']['create_exe'] = (bool) ($metablock['flags_raw'] & 0x000400); // create executable
|
||||
$metablock['flags']['create_wvc'] = (bool) ($metablock['flags_raw'] & 0x000800); // create correction file
|
||||
$metablock['flags']['optimize_wvc'] = (bool) ($metablock['flags_raw'] & 0x001000); // maximize bybrid compression
|
||||
$metablock['flags']['quality_mode'] = (bool) ($metablock['flags_raw'] & 0x002000); // psychoacoustic quality mode
|
||||
$metablock['flags']['raw_flag'] = (bool) ($metablock['flags_raw'] & 0x004000); // raw mode (not implemented yet)
|
||||
$metablock['flags']['calc_noise'] = (bool) ($metablock['flags_raw'] & 0x008000); // calc noise in hybrid mode
|
||||
$metablock['flags']['lossy_mode'] = (bool) ($metablock['flags_raw'] & 0x010000); // obsolete (for information)
|
||||
$metablock['flags']['extra_mode'] = (bool) ($metablock['flags_raw'] & 0x020000); // extra processing mode
|
||||
$metablock['flags']['skip_wvx'] = (bool) ($metablock['flags_raw'] & 0x040000); // no wvx stream w/ floats & big ints
|
||||
$metablock['flags']['md5_checksum'] = (bool) ($metablock['flags_raw'] & 0x080000); // compute & store MD5 signature
|
||||
$metablock['flags']['quiet_mode'] = (bool) ($metablock['flags_raw'] & 0x100000); // don't report progress %
|
||||
|
||||
$info['wavpack']['config_flags'] = $metablock['flags'];
|
||||
|
||||
|
||||
$info['audio']['encoder_options'] = '';
|
||||
if ($info['wavpack']['blockheader']['flags']['hybrid']) {
|
||||
$info['audio']['encoder_options'] .= ' -b???';
|
||||
}
|
||||
$info['audio']['encoder_options'] .= ($metablock['flags']['adobe_mode'] ? ' -a' : '');
|
||||
$info['audio']['encoder_options'] .= ($metablock['flags']['optimize_wvc'] ? ' -cc' : '');
|
||||
$info['audio']['encoder_options'] .= ($metablock['flags']['create_exe'] ? ' -e' : '');
|
||||
$info['audio']['encoder_options'] .= ($metablock['flags']['fast_flag'] ? ' -f' : '');
|
||||
$info['audio']['encoder_options'] .= ($metablock['flags']['joint_override'] ? ' -j?' : '');
|
||||
$info['audio']['encoder_options'] .= ($metablock['flags']['high_flag'] ? ' -h' : '');
|
||||
$info['audio']['encoder_options'] .= ($metablock['flags']['md5_checksum'] ? ' -m' : '');
|
||||
$info['audio']['encoder_options'] .= ($metablock['flags']['calc_noise'] ? ' -n' : '');
|
||||
$info['audio']['encoder_options'] .= ($metablock['flags']['shape_override'] ? ' -s?' : '');
|
||||
$info['audio']['encoder_options'] .= ($metablock['flags']['extra_mode'] ? ' -x?' : '');
|
||||
if (!empty($info['audio']['encoder_options'])) {
|
||||
$info['audio']['encoder_options'] = trim($info['audio']['encoder_options']);
|
||||
} elseif (isset($info['audio']['encoder_options'])) {
|
||||
unset($info['audio']['encoder_options']);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 0x26: // ID_MD5_CHECKSUM
|
||||
if (strlen($metablock['data']) == 16) {
|
||||
$info['md5_data_source'] = strtolower(getid3_lib::PrintHexBytes($metablock['data'], true, false, false));
|
||||
} else {
|
||||
$info['warning'][] = 'Expecting 16 bytes of WavPack "MD5 Checksum" in metablock at offset '.$metablock['offset'].', but found '.strlen($metablock['data']).' bytes';
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 0x00: // ID_DUMMY
|
||||
case 0x01: // ID_ENCODER_INFO
|
||||
case 0x02: // ID_DECORR_TERMS
|
||||
case 0x03: // ID_DECORR_WEIGHTS
|
||||
case 0x04: // ID_DECORR_SAMPLES
|
||||
case 0x05: // ID_ENTROPY_VARS
|
||||
case 0x06: // ID_HYBRID_PROFILE
|
||||
case 0x07: // ID_SHAPING_WEIGHTS
|
||||
case 0x08: // ID_FLOAT_INFO
|
||||
case 0x09: // ID_INT32_INFO
|
||||
case 0x0A: // ID_WV_BITSTREAM
|
||||
case 0x0B: // ID_WVC_BITSTREAM
|
||||
case 0x0C: // ID_WVX_BITSTREAM
|
||||
case 0x0D: // ID_CHANNEL_INFO
|
||||
unset($metablock);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (!empty($metablock)) {
|
||||
$info['wavpack']['metablocks'][] = $metablock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$info['audio']['encoder'] = 'WavPack v'.$info['wavpack']['blockheader']['major_version'].'.'.str_pad($info['wavpack']['blockheader']['minor_version'], 2, '0', STR_PAD_LEFT);
|
||||
$info['audio']['bits_per_sample'] = $info['wavpack']['blockheader']['flags']['bytes_per_sample'] * 8;
|
||||
$info['audio']['channels'] = ($info['wavpack']['blockheader']['flags']['mono'] ? 1 : 2);
|
||||
|
||||
if (!empty($info['playtime_seconds'])) {
|
||||
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
|
||||
} else {
|
||||
|
||||
$info['audio']['dataformat'] = 'wvc';
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function WavPackMetablockNameLookup(&$id) {
|
||||
static $WavPackMetablockNameLookup = array(
|
||||
0x00 => 'Dummy',
|
||||
0x01 => 'Encoder Info',
|
||||
0x02 => 'Decorrelation Terms',
|
||||
0x03 => 'Decorrelation Weights',
|
||||
0x04 => 'Decorrelation Samples',
|
||||
0x05 => 'Entropy Variables',
|
||||
0x06 => 'Hybrid Profile',
|
||||
0x07 => 'Shaping Weights',
|
||||
0x08 => 'Float Info',
|
||||
0x09 => 'Int32 Info',
|
||||
0x0A => 'WV Bitstream',
|
||||
0x0B => 'WVC Bitstream',
|
||||
0x0C => 'WVX Bitstream',
|
||||
0x0D => 'Channel Info',
|
||||
0x21 => 'RIFF header',
|
||||
0x22 => 'RIFF trailer',
|
||||
0x23 => 'Replay Gain',
|
||||
0x24 => 'Cuesheet',
|
||||
0x25 => 'Config Block',
|
||||
0x26 => 'MD5 Checksum',
|
||||
);
|
||||
return (isset($WavPackMetablockNameLookup[$id]) ? $WavPackMetablockNameLookup[$id] : '');
|
||||
}
|
||||
|
||||
}
|
687
api/getID3/module.graphic.bmp.php
Executable file
@ -0,0 +1,687 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.graphic.bmp.php //
|
||||
// module for analyzing BMP Image files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_bmp extends getid3_handler
|
||||
{
|
||||
public $ExtractPalette = false;
|
||||
public $ExtractData = false;
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// shortcuts
|
||||
$info['bmp']['header']['raw'] = array();
|
||||
$thisfile_bmp = &$info['bmp'];
|
||||
$thisfile_bmp_header = &$thisfile_bmp['header'];
|
||||
$thisfile_bmp_header_raw = &$thisfile_bmp_header['raw'];
|
||||
|
||||
// BITMAPFILEHEADER [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp
|
||||
// all versions
|
||||
// WORD bfType;
|
||||
// DWORD bfSize;
|
||||
// WORD bfReserved1;
|
||||
// WORD bfReserved2;
|
||||
// DWORD bfOffBits;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$offset = 0;
|
||||
$BMPheader = fread($this->getid3->fp, 14 + 40);
|
||||
|
||||
$thisfile_bmp_header_raw['identifier'] = substr($BMPheader, $offset, 2);
|
||||
$offset += 2;
|
||||
|
||||
$magic = 'BM';
|
||||
if ($thisfile_bmp_header_raw['identifier'] != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_bmp_header_raw['identifier']).'"';
|
||||
unset($info['fileformat']);
|
||||
unset($info['bmp']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$thisfile_bmp_header_raw['filesize'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['reserved1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['reserved2'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['data_offset'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['header_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
|
||||
// check if the hardcoded-to-1 "planes" is at offset 22 or 26
|
||||
$planes22 = getid3_lib::LittleEndian2Int(substr($BMPheader, 22, 2));
|
||||
$planes26 = getid3_lib::LittleEndian2Int(substr($BMPheader, 26, 2));
|
||||
if (($planes22 == 1) && ($planes26 != 1)) {
|
||||
$thisfile_bmp['type_os'] = 'OS/2';
|
||||
$thisfile_bmp['type_version'] = 1;
|
||||
} elseif (($planes26 == 1) && ($planes22 != 1)) {
|
||||
$thisfile_bmp['type_os'] = 'Windows';
|
||||
$thisfile_bmp['type_version'] = 1;
|
||||
} elseif ($thisfile_bmp_header_raw['header_size'] == 12) {
|
||||
$thisfile_bmp['type_os'] = 'OS/2';
|
||||
$thisfile_bmp['type_version'] = 1;
|
||||
} elseif ($thisfile_bmp_header_raw['header_size'] == 40) {
|
||||
$thisfile_bmp['type_os'] = 'Windows';
|
||||
$thisfile_bmp['type_version'] = 1;
|
||||
} elseif ($thisfile_bmp_header_raw['header_size'] == 84) {
|
||||
$thisfile_bmp['type_os'] = 'Windows';
|
||||
$thisfile_bmp['type_version'] = 4;
|
||||
} elseif ($thisfile_bmp_header_raw['header_size'] == 100) {
|
||||
$thisfile_bmp['type_os'] = 'Windows';
|
||||
$thisfile_bmp['type_version'] = 5;
|
||||
} else {
|
||||
$info['error'][] = 'Unknown BMP subtype (or not a BMP file)';
|
||||
unset($info['fileformat']);
|
||||
unset($info['bmp']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['fileformat'] = 'bmp';
|
||||
$info['video']['dataformat'] = 'bmp';
|
||||
$info['video']['lossless'] = true;
|
||||
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||
|
||||
if ($thisfile_bmp['type_os'] == 'OS/2') {
|
||||
|
||||
// OS/2-format BMP
|
||||
// http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm
|
||||
|
||||
// DWORD Size; /* Size of this structure in bytes */
|
||||
// DWORD Width; /* Bitmap width in pixels */
|
||||
// DWORD Height; /* Bitmap height in pixel */
|
||||
// WORD NumPlanes; /* Number of bit planes (color depth) */
|
||||
// WORD BitsPerPixel; /* Number of bits per pixel per plane */
|
||||
|
||||
$thisfile_bmp_header_raw['width'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['height'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['planes'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
|
||||
$info['video']['resolution_x'] = $thisfile_bmp_header_raw['width'];
|
||||
$info['video']['resolution_y'] = $thisfile_bmp_header_raw['height'];
|
||||
$info['video']['codec'] = 'BI_RGB '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
|
||||
$info['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel'];
|
||||
|
||||
if ($thisfile_bmp['type_version'] >= 2) {
|
||||
// DWORD Compression; /* Bitmap compression scheme */
|
||||
// DWORD ImageDataSize; /* Size of bitmap data in bytes */
|
||||
// DWORD XResolution; /* X resolution of display device */
|
||||
// DWORD YResolution; /* Y resolution of display device */
|
||||
// DWORD ColorsUsed; /* Number of color table indices used */
|
||||
// DWORD ColorsImportant; /* Number of important color indices */
|
||||
// WORD Units; /* Type of units used to measure resolution */
|
||||
// WORD Reserved; /* Pad structure to 4-byte boundary */
|
||||
// WORD Recording; /* Recording algorithm */
|
||||
// WORD Rendering; /* Halftoning algorithm used */
|
||||
// DWORD Size1; /* Reserved for halftoning algorithm use */
|
||||
// DWORD Size2; /* Reserved for halftoning algorithm use */
|
||||
// DWORD ColorEncoding; /* Color model used in bitmap */
|
||||
// DWORD Identifier; /* Reserved for application use */
|
||||
|
||||
$thisfile_bmp_header_raw['compression'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['bmp_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['resolution_h'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['resolution_v'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['colors_used'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['colors_important'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['resolution_units'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['reserved1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['recording'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['rendering'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['size1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['size2'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['color_encoding'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['identifier'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
$thisfile_bmp_header['compression'] = $this->BMPcompressionOS2Lookup($thisfile_bmp_header_raw['compression']);
|
||||
|
||||
$info['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
|
||||
}
|
||||
|
||||
} elseif ($thisfile_bmp['type_os'] == 'Windows') {
|
||||
|
||||
// Windows-format BMP
|
||||
|
||||
// BITMAPINFOHEADER - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp
|
||||
// all versions
|
||||
// DWORD biSize;
|
||||
// LONG biWidth;
|
||||
// LONG biHeight;
|
||||
// WORD biPlanes;
|
||||
// WORD biBitCount;
|
||||
// DWORD biCompression;
|
||||
// DWORD biSizeImage;
|
||||
// LONG biXPelsPerMeter;
|
||||
// LONG biYPelsPerMeter;
|
||||
// DWORD biClrUsed;
|
||||
// DWORD biClrImportant;
|
||||
|
||||
// possibly integrate this section and module.audio-video.riff.php::ParseBITMAPINFOHEADER() ?
|
||||
|
||||
$thisfile_bmp_header_raw['width'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['height'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['planes'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_bmp_header_raw['compression'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['bmp_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['resolution_h'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['resolution_v'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['colors_used'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['colors_important'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
$thisfile_bmp_header['compression'] = $this->BMPcompressionWindowsLookup($thisfile_bmp_header_raw['compression']);
|
||||
$info['video']['resolution_x'] = $thisfile_bmp_header_raw['width'];
|
||||
$info['video']['resolution_y'] = $thisfile_bmp_header_raw['height'];
|
||||
$info['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
|
||||
$info['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel'];
|
||||
|
||||
if (($thisfile_bmp['type_version'] >= 4) || ($thisfile_bmp_header_raw['compression'] == 3)) {
|
||||
// should only be v4+, but BMPs with type_version==1 and BI_BITFIELDS compression have been seen
|
||||
$BMPheader .= fread($this->getid3->fp, 44);
|
||||
|
||||
// BITMAPV4HEADER - [44 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_2k1e.asp
|
||||
// Win95+, WinNT4.0+
|
||||
// DWORD bV4RedMask;
|
||||
// DWORD bV4GreenMask;
|
||||
// DWORD bV4BlueMask;
|
||||
// DWORD bV4AlphaMask;
|
||||
// DWORD bV4CSType;
|
||||
// CIEXYZTRIPLE bV4Endpoints;
|
||||
// DWORD bV4GammaRed;
|
||||
// DWORD bV4GammaGreen;
|
||||
// DWORD bV4GammaBlue;
|
||||
$thisfile_bmp_header_raw['red_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['green_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['blue_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['alpha_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['cs_type'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['ciexyz_red'] = substr($BMPheader, $offset, 4);
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['ciexyz_green'] = substr($BMPheader, $offset, 4);
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['ciexyz_blue'] = substr($BMPheader, $offset, 4);
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['gamma_red'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['gamma_green'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['gamma_blue'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
$thisfile_bmp_header['ciexyz_red'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_red']));
|
||||
$thisfile_bmp_header['ciexyz_green'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_green']));
|
||||
$thisfile_bmp_header['ciexyz_blue'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_blue']));
|
||||
}
|
||||
|
||||
if ($thisfile_bmp['type_version'] >= 5) {
|
||||
$BMPheader .= fread($this->getid3->fp, 16);
|
||||
|
||||
// BITMAPV5HEADER - [16 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_7c36.asp
|
||||
// Win98+, Win2000+
|
||||
// DWORD bV5Intent;
|
||||
// DWORD bV5ProfileData;
|
||||
// DWORD bV5ProfileSize;
|
||||
// DWORD bV5Reserved;
|
||||
$thisfile_bmp_header_raw['intent'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['profile_data_offset'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['profile_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_bmp_header_raw['reserved3'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||
$offset += 4;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$info['error'][] = 'Unknown BMP format in header.';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ($this->ExtractPalette || $this->ExtractData) {
|
||||
$PaletteEntries = 0;
|
||||
if ($thisfile_bmp_header_raw['bits_per_pixel'] < 16) {
|
||||
$PaletteEntries = pow(2, $thisfile_bmp_header_raw['bits_per_pixel']);
|
||||
} elseif (isset($thisfile_bmp_header_raw['colors_used']) && ($thisfile_bmp_header_raw['colors_used'] > 0) && ($thisfile_bmp_header_raw['colors_used'] <= 256)) {
|
||||
$PaletteEntries = $thisfile_bmp_header_raw['colors_used'];
|
||||
}
|
||||
if ($PaletteEntries > 0) {
|
||||
$BMPpalette = fread($this->getid3->fp, 4 * $PaletteEntries);
|
||||
$paletteoffset = 0;
|
||||
for ($i = 0; $i < $PaletteEntries; $i++) {
|
||||
// RGBQUAD - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_5f8y.asp
|
||||
// BYTE rgbBlue;
|
||||
// BYTE rgbGreen;
|
||||
// BYTE rgbRed;
|
||||
// BYTE rgbReserved;
|
||||
$blue = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
|
||||
$green = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
|
||||
$red = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
|
||||
if (($thisfile_bmp['type_os'] == 'OS/2') && ($thisfile_bmp['type_version'] == 1)) {
|
||||
// no padding byte
|
||||
} else {
|
||||
$paletteoffset++; // padding byte
|
||||
}
|
||||
$thisfile_bmp['palette'][$i] = (($red << 16) | ($green << 8) | $blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->ExtractData) {
|
||||
fseek($this->getid3->fp, $thisfile_bmp_header_raw['data_offset'], SEEK_SET);
|
||||
$RowByteLength = ceil(($thisfile_bmp_header_raw['width'] * ($thisfile_bmp_header_raw['bits_per_pixel'] / 8)) / 4) * 4; // round up to nearest DWORD boundry
|
||||
$BMPpixelData = fread($this->getid3->fp, $thisfile_bmp_header_raw['height'] * $RowByteLength);
|
||||
$pixeldataoffset = 0;
|
||||
$thisfile_bmp_header_raw['compression'] = (isset($thisfile_bmp_header_raw['compression']) ? $thisfile_bmp_header_raw['compression'] : '');
|
||||
switch ($thisfile_bmp_header_raw['compression']) {
|
||||
|
||||
case 0: // BI_RGB
|
||||
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
|
||||
case 1:
|
||||
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
|
||||
$paletteindexbyte = ord($BMPpixelData{$pixeldataoffset++});
|
||||
for ($i = 7; $i >= 0; $i--) {
|
||||
$paletteindex = ($paletteindexbyte & (0x01 << $i)) >> $i;
|
||||
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
|
||||
$col++;
|
||||
}
|
||||
}
|
||||
while (($pixeldataoffset % 4) != 0) {
|
||||
// lines are padded to nearest DWORD
|
||||
$pixeldataoffset++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
|
||||
$paletteindexbyte = ord($BMPpixelData{$pixeldataoffset++});
|
||||
for ($i = 1; $i >= 0; $i--) {
|
||||
$paletteindex = ($paletteindexbyte & (0x0F << (4 * $i))) >> (4 * $i);
|
||||
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
|
||||
$col++;
|
||||
}
|
||||
}
|
||||
while (($pixeldataoffset % 4) != 0) {
|
||||
// lines are padded to nearest DWORD
|
||||
$pixeldataoffset++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 8:
|
||||
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
|
||||
$paletteindex = ord($BMPpixelData{$pixeldataoffset++});
|
||||
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
|
||||
}
|
||||
while (($pixeldataoffset % 4) != 0) {
|
||||
// lines are padded to nearest DWORD
|
||||
$pixeldataoffset++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 24:
|
||||
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
|
||||
$thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData{$pixeldataoffset+2}) << 16) | (ord($BMPpixelData{$pixeldataoffset+1}) << 8) | ord($BMPpixelData{$pixeldataoffset});
|
||||
$pixeldataoffset += 3;
|
||||
}
|
||||
while (($pixeldataoffset % 4) != 0) {
|
||||
// lines are padded to nearest DWORD
|
||||
$pixeldataoffset++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 32:
|
||||
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
|
||||
$thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData{$pixeldataoffset+3}) << 24) | (ord($BMPpixelData{$pixeldataoffset+2}) << 16) | (ord($BMPpixelData{$pixeldataoffset+1}) << 8) | ord($BMPpixelData{$pixeldataoffset});
|
||||
$pixeldataoffset += 4;
|
||||
}
|
||||
while (($pixeldataoffset % 4) != 0) {
|
||||
// lines are padded to nearest DWORD
|
||||
$pixeldataoffset++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 16:
|
||||
// ?
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 1: // BI_RLE8 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
|
||||
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
|
||||
case 8:
|
||||
$pixelcounter = 0;
|
||||
while ($pixeldataoffset < strlen($BMPpixelData)) {
|
||||
$firstbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||
$secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||
if ($firstbyte == 0) {
|
||||
|
||||
// escaped/absolute mode - the first byte of the pair can be set to zero to
|
||||
// indicate an escape character that denotes the end of a line, the end of
|
||||
// a bitmap, or a delta, depending on the value of the second byte.
|
||||
switch ($secondbyte) {
|
||||
case 0:
|
||||
// end of line
|
||||
// no need for special processing, just ignore
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// end of bitmap
|
||||
$pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// delta - The 2 bytes following the escape contain unsigned values
|
||||
// indicating the horizontal and vertical offsets of the next pixel
|
||||
// from the current position.
|
||||
$colincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||
$rowincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||
$col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
|
||||
$row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
|
||||
$pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
|
||||
break;
|
||||
|
||||
default:
|
||||
// In absolute mode, the first byte is zero and the second byte is a
|
||||
// value in the range 03H through FFH. The second byte represents the
|
||||
// number of bytes that follow, each of which contains the color index
|
||||
// of a single pixel. Each run must be aligned on a word boundary.
|
||||
for ($i = 0; $i < $secondbyte; $i++) {
|
||||
$paletteindex = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
|
||||
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
|
||||
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
|
||||
$pixelcounter++;
|
||||
}
|
||||
while (($pixeldataoffset % 2) != 0) {
|
||||
// Each run must be aligned on a word boundary.
|
||||
$pixeldataoffset++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// encoded mode - the first byte specifies the number of consecutive pixels
|
||||
// to be drawn using the color index contained in the second byte.
|
||||
for ($i = 0; $i < $firstbyte; $i++) {
|
||||
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
|
||||
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
|
||||
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$secondbyte];
|
||||
$pixelcounter++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
|
||||
case 2: // BI_RLE4 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
|
||||
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
|
||||
case 4:
|
||||
$pixelcounter = 0;
|
||||
while ($pixeldataoffset < strlen($BMPpixelData)) {
|
||||
$firstbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||
$secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||
if ($firstbyte == 0) {
|
||||
|
||||
// escaped/absolute mode - the first byte of the pair can be set to zero to
|
||||
// indicate an escape character that denotes the end of a line, the end of
|
||||
// a bitmap, or a delta, depending on the value of the second byte.
|
||||
switch ($secondbyte) {
|
||||
case 0:
|
||||
// end of line
|
||||
// no need for special processing, just ignore
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// end of bitmap
|
||||
$pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// delta - The 2 bytes following the escape contain unsigned values
|
||||
// indicating the horizontal and vertical offsets of the next pixel
|
||||
// from the current position.
|
||||
$colincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||
$rowincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||
$col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
|
||||
$row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
|
||||
$pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
|
||||
break;
|
||||
|
||||
default:
|
||||
// In absolute mode, the first byte is zero. The second byte contains the number
|
||||
// of color indexes that follow. Subsequent bytes contain color indexes in their
|
||||
// high- and low-order 4 bits, one color index for each pixel. In absolute mode,
|
||||
// each run must be aligned on a word boundary.
|
||||
unset($paletteindexes);
|
||||
for ($i = 0; $i < ceil($secondbyte / 2); $i++) {
|
||||
$paletteindexbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||
$paletteindexes[] = ($paletteindexbyte & 0xF0) >> 4;
|
||||
$paletteindexes[] = ($paletteindexbyte & 0x0F);
|
||||
}
|
||||
while (($pixeldataoffset % 2) != 0) {
|
||||
// Each run must be aligned on a word boundary.
|
||||
$pixeldataoffset++;
|
||||
}
|
||||
|
||||
foreach ($paletteindexes as $paletteindex) {
|
||||
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
|
||||
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
|
||||
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
|
||||
$pixelcounter++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// encoded mode - the first byte of the pair contains the number of pixels to be
|
||||
// drawn using the color indexes in the second byte. The second byte contains two
|
||||
// color indexes, one in its high-order 4 bits and one in its low-order 4 bits.
|
||||
// The first of the pixels is drawn using the color specified by the high-order
|
||||
// 4 bits, the second is drawn using the color in the low-order 4 bits, the third
|
||||
// is drawn using the color in the high-order 4 bits, and so on, until all the
|
||||
// pixels specified by the first byte have been drawn.
|
||||
$paletteindexes[0] = ($secondbyte & 0xF0) >> 4;
|
||||
$paletteindexes[1] = ($secondbyte & 0x0F);
|
||||
for ($i = 0; $i < $firstbyte; $i++) {
|
||||
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
|
||||
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
|
||||
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindexes[($i % 2)]];
|
||||
$pixelcounter++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 3: // BI_BITFIELDS
|
||||
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
|
||||
case 16:
|
||||
case 32:
|
||||
$redshift = 0;
|
||||
$greenshift = 0;
|
||||
$blueshift = 0;
|
||||
while ((($thisfile_bmp_header_raw['red_mask'] >> $redshift) & 0x01) == 0) {
|
||||
$redshift++;
|
||||
}
|
||||
while ((($thisfile_bmp_header_raw['green_mask'] >> $greenshift) & 0x01) == 0) {
|
||||
$greenshift++;
|
||||
}
|
||||
while ((($thisfile_bmp_header_raw['blue_mask'] >> $blueshift) & 0x01) == 0) {
|
||||
$blueshift++;
|
||||
}
|
||||
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
|
||||
$pixelvalue = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset, $thisfile_bmp_header_raw['bits_per_pixel'] / 8));
|
||||
$pixeldataoffset += $thisfile_bmp_header_raw['bits_per_pixel'] / 8;
|
||||
|
||||
$red = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['red_mask']) >> $redshift) / ($thisfile_bmp_header_raw['red_mask'] >> $redshift)) * 255));
|
||||
$green = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['green_mask']) >> $greenshift) / ($thisfile_bmp_header_raw['green_mask'] >> $greenshift)) * 255));
|
||||
$blue = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['blue_mask']) >> $blueshift) / ($thisfile_bmp_header_raw['blue_mask'] >> $blueshift)) * 255));
|
||||
$thisfile_bmp['data'][$row][$col] = (($red << 16) | ($green << 8) | ($blue));
|
||||
}
|
||||
while (($pixeldataoffset % 4) != 0) {
|
||||
// lines are padded to nearest DWORD
|
||||
$pixeldataoffset++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default: // unhandled compression type
|
||||
$info['error'][] = 'Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function PlotBMP(&$BMPinfo) {
|
||||
$starttime = time();
|
||||
if (!isset($BMPinfo['bmp']['data']) || !is_array($BMPinfo['bmp']['data'])) {
|
||||
echo 'ERROR: no pixel data<BR>';
|
||||
return false;
|
||||
}
|
||||
set_time_limit(intval(round($BMPinfo['resolution_x'] * $BMPinfo['resolution_y'] / 10000)));
|
||||
if ($im = ImageCreateTrueColor($BMPinfo['resolution_x'], $BMPinfo['resolution_y'])) {
|
||||
for ($row = 0; $row < $BMPinfo['resolution_y']; $row++) {
|
||||
for ($col = 0; $col < $BMPinfo['resolution_x']; $col++) {
|
||||
if (isset($BMPinfo['bmp']['data'][$row][$col])) {
|
||||
$red = ($BMPinfo['bmp']['data'][$row][$col] & 0x00FF0000) >> 16;
|
||||
$green = ($BMPinfo['bmp']['data'][$row][$col] & 0x0000FF00) >> 8;
|
||||
$blue = ($BMPinfo['bmp']['data'][$row][$col] & 0x000000FF);
|
||||
$pixelcolor = ImageColorAllocate($im, $red, $green, $blue);
|
||||
ImageSetPixel($im, $col, $row, $pixelcolor);
|
||||
} else {
|
||||
//echo 'ERROR: no data for pixel '.$row.' x '.$col.'<BR>';
|
||||
//return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (headers_sent()) {
|
||||
echo 'plotted '.($BMPinfo['resolution_x'] * $BMPinfo['resolution_y']).' pixels in '.(time() - $starttime).' seconds<BR>';
|
||||
ImageDestroy($im);
|
||||
exit;
|
||||
} else {
|
||||
header('Content-type: image/png');
|
||||
ImagePNG($im);
|
||||
ImageDestroy($im);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function BMPcompressionWindowsLookup($compressionid) {
|
||||
static $BMPcompressionWindowsLookup = array(
|
||||
0 => 'BI_RGB',
|
||||
1 => 'BI_RLE8',
|
||||
2 => 'BI_RLE4',
|
||||
3 => 'BI_BITFIELDS',
|
||||
4 => 'BI_JPEG',
|
||||
5 => 'BI_PNG'
|
||||
);
|
||||
return (isset($BMPcompressionWindowsLookup[$compressionid]) ? $BMPcompressionWindowsLookup[$compressionid] : 'invalid');
|
||||
}
|
||||
|
||||
public function BMPcompressionOS2Lookup($compressionid) {
|
||||
static $BMPcompressionOS2Lookup = array(
|
||||
0 => 'BI_RGB',
|
||||
1 => 'BI_RLE8',
|
||||
2 => 'BI_RLE4',
|
||||
3 => 'Huffman 1D',
|
||||
4 => 'BI_RLE24',
|
||||
);
|
||||
return (isset($BMPcompressionOS2Lookup[$compressionid]) ? $BMPcompressionOS2Lookup[$compressionid] : 'invalid');
|
||||
}
|
||||
|
||||
}
|
50
api/getID3/module.graphic.efax.php
Executable file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.archive.efax.php //
|
||||
// module for analyzing eFax files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_efax extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$efaxheader = fread($this->getid3->fp, 1024);
|
||||
|
||||
$info['efax']['header']['magic'] = substr($efaxheader, 0, 2);
|
||||
if ($info['efax']['header']['magic'] != "\xDC\xFE") {
|
||||
$info['error'][] = 'Invalid eFax byte order identifier (expecting DC FE, found '.getid3_lib::PrintHexBytes($info['efax']['header']['magic']).') at offset '.$info['avdataoffset'];
|
||||
return false;
|
||||
}
|
||||
$info['fileformat'] = 'efax';
|
||||
|
||||
$info['efax']['header']['filesize'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 2, 4));
|
||||
if ($info['efax']['header']['filesize'] != $info['filesize']) {
|
||||
$info['error'][] = 'Probable '.(($info['efax']['header']['filesize'] > $info['filesize']) ? 'truncated' : 'corrupt').' file, expecting '.$info['efax']['header']['filesize'].' bytes, found '.$info['filesize'].' bytes';
|
||||
}
|
||||
$info['efax']['header']['software1'] = rtrim(substr($efaxheader, 26, 32), "\x00");
|
||||
$info['efax']['header']['software2'] = rtrim(substr($efaxheader, 58, 32), "\x00");
|
||||
$info['efax']['header']['software3'] = rtrim(substr($efaxheader, 90, 32), "\x00");
|
||||
|
||||
$info['efax']['header']['pages'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 198, 2));
|
||||
$info['efax']['header']['data_bytes'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 202, 4));
|
||||
|
||||
$info['error'][] = 'eFax parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
181
api/getID3/module.graphic.gif.php
Executable file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.graphic.gif.php //
|
||||
// module for analyzing GIF Image files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_gif extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'gif';
|
||||
$info['video']['dataformat'] = 'gif';
|
||||
$info['video']['lossless'] = true;
|
||||
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$GIFheader = fread($this->getid3->fp, 13);
|
||||
$offset = 0;
|
||||
|
||||
$info['gif']['header']['raw']['identifier'] = substr($GIFheader, $offset, 3);
|
||||
$offset += 3;
|
||||
|
||||
$magic = 'GIF';
|
||||
if ($info['gif']['header']['raw']['identifier'] != $magic) {
|
||||
$info['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;
|
||||
}
|
||||
|
||||
$info['gif']['header']['raw']['version'] = substr($GIFheader, $offset, 3);
|
||||
$offset += 3;
|
||||
$info['gif']['header']['raw']['width'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$info['gif']['header']['raw']['height'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 2));
|
||||
$offset += 2;
|
||||
$info['gif']['header']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1));
|
||||
$offset += 1;
|
||||
$info['gif']['header']['raw']['bg_color_index'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1));
|
||||
$offset += 1;
|
||||
$info['gif']['header']['raw']['aspect_ratio'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
$info['video']['resolution_x'] = $info['gif']['header']['raw']['width'];
|
||||
$info['video']['resolution_y'] = $info['gif']['header']['raw']['height'];
|
||||
$info['gif']['version'] = $info['gif']['header']['raw']['version'];
|
||||
$info['gif']['header']['flags']['global_color_table'] = (bool) ($info['gif']['header']['raw']['flags'] & 0x80);
|
||||
if ($info['gif']['header']['raw']['flags'] & 0x80) {
|
||||
// Number of bits per primary color available to the original image, minus 1
|
||||
$info['gif']['header']['bits_per_pixel'] = 3 * ((($info['gif']['header']['raw']['flags'] & 0x70) >> 4) + 1);
|
||||
} else {
|
||||
$info['gif']['header']['bits_per_pixel'] = 0;
|
||||
}
|
||||
$info['gif']['header']['flags']['global_color_sorted'] = (bool) ($info['gif']['header']['raw']['flags'] & 0x40);
|
||||
if ($info['gif']['header']['flags']['global_color_table']) {
|
||||
// the number of bytes contained in the Global Color Table. To determine that
|
||||
// actual size of the color table, raise 2 to [the value of the field + 1]
|
||||
$info['gif']['header']['global_color_size'] = pow(2, ($info['gif']['header']['raw']['flags'] & 0x07) + 1);
|
||||
$info['video']['bits_per_sample'] = ($info['gif']['header']['raw']['flags'] & 0x07) + 1;
|
||||
} else {
|
||||
$info['gif']['header']['global_color_size'] = 0;
|
||||
}
|
||||
if ($info['gif']['header']['raw']['aspect_ratio'] != 0) {
|
||||
// Aspect Ratio = (Pixel Aspect Ratio + 15) / 64
|
||||
$info['gif']['header']['aspect_ratio'] = ($info['gif']['header']['raw']['aspect_ratio'] + 15) / 64;
|
||||
}
|
||||
|
||||
// if ($info['gif']['header']['flags']['global_color_table']) {
|
||||
// $GIFcolorTable = fread($this->getid3->fp, 3 * $info['gif']['header']['global_color_size']);
|
||||
// $offset = 0;
|
||||
// for ($i = 0; $i < $info['gif']['header']['global_color_size']; $i++) {
|
||||
// $red = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1));
|
||||
// $green = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1));
|
||||
// $blue = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1));
|
||||
// $info['gif']['global_color_table'][$i] = (($red << 16) | ($green << 8) | ($blue));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Image Descriptor
|
||||
// while (!feof($this->getid3->fp)) {
|
||||
// $NextBlockTest = fread($this->getid3->fp, 1);
|
||||
// switch ($NextBlockTest) {
|
||||
//
|
||||
// case ',': // ',' - Image separator character
|
||||
//
|
||||
// $ImageDescriptorData = $NextBlockTest.fread($this->getid3->fp, 9);
|
||||
// $ImageDescriptor = array();
|
||||
// $ImageDescriptor['image_left'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 1, 2));
|
||||
// $ImageDescriptor['image_top'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 3, 2));
|
||||
// $ImageDescriptor['image_width'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 5, 2));
|
||||
// $ImageDescriptor['image_height'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 7, 2));
|
||||
// $ImageDescriptor['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 9, 1));
|
||||
// $ImageDescriptor['flags']['use_local_color_map'] = (bool) ($ImageDescriptor['flags_raw'] & 0x80);
|
||||
// $ImageDescriptor['flags']['image_interlaced'] = (bool) ($ImageDescriptor['flags_raw'] & 0x40);
|
||||
// $info['gif']['image_descriptor'][] = $ImageDescriptor;
|
||||
//
|
||||
// if ($ImageDescriptor['flags']['use_local_color_map']) {
|
||||
//
|
||||
// $info['warning'][] = 'This version of getID3() cannot parse local color maps for GIFs';
|
||||
// return true;
|
||||
//
|
||||
// }
|
||||
//echo 'Start of raster data: '.ftell($this->getid3->fp).'<BR>';
|
||||
// $RasterData = array();
|
||||
// $RasterData['code_size'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1));
|
||||
// $RasterData['block_byte_count'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1));
|
||||
// $info['gif']['raster_data'][count($info['gif']['image_descriptor']) - 1] = $RasterData;
|
||||
//
|
||||
// $CurrentCodeSize = $RasterData['code_size'] + 1;
|
||||
// for ($i = 0; $i < pow(2, $RasterData['code_size']); $i++) {
|
||||
// $DefaultDataLookupTable[$i] = chr($i);
|
||||
// }
|
||||
// $DefaultDataLookupTable[pow(2, $RasterData['code_size']) + 0] = ''; // Clear Code
|
||||
// $DefaultDataLookupTable[pow(2, $RasterData['code_size']) + 1] = ''; // End Of Image Code
|
||||
//
|
||||
//
|
||||
// $NextValue = $this->GetLSBits($CurrentCodeSize);
|
||||
// echo 'Clear Code: '.$NextValue.'<BR>';
|
||||
//
|
||||
// $NextValue = $this->GetLSBits($CurrentCodeSize);
|
||||
// echo 'First Color: '.$NextValue.'<BR>';
|
||||
//
|
||||
// $Prefix = $NextValue;
|
||||
//$i = 0;
|
||||
// while ($i++ < 20) {
|
||||
// $NextValue = $this->GetLSBits($CurrentCodeSize);
|
||||
// echo $NextValue.'<BR>';
|
||||
// }
|
||||
//return true;
|
||||
// break;
|
||||
//
|
||||
// case '!':
|
||||
// // GIF Extension Block
|
||||
// $ExtensionBlockData = $NextBlockTest.fread($this->getid3->fp, 2);
|
||||
// $ExtensionBlock = array();
|
||||
// $ExtensionBlock['function_code'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 1, 1));
|
||||
// $ExtensionBlock['byte_length'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 2, 1));
|
||||
// $ExtensionBlock['data'] = fread($this->getid3->fp, $ExtensionBlock['byte_length']);
|
||||
// $info['gif']['extension_blocks'][] = $ExtensionBlock;
|
||||
// break;
|
||||
//
|
||||
// case ';':
|
||||
// $info['gif']['terminator_offset'] = ftell($this->getid3->fp) - 1;
|
||||
// // GIF Terminator
|
||||
// break;
|
||||
//
|
||||
// default:
|
||||
// break;
|
||||
//
|
||||
//
|
||||
// }
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function GetLSBits($bits) {
|
||||
static $bitbuffer = '';
|
||||
while (strlen($bitbuffer) < $bits) {
|
||||
$bitbuffer = str_pad(decbin(ord(fread($this->getid3->fp, 1))), 8, '0', STR_PAD_LEFT).$bitbuffer;
|
||||
}
|
||||
$value = bindec(substr($bitbuffer, 0 - $bits));
|
||||
$bitbuffer = substr($bitbuffer, 0, 0 - $bits);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
343
api/getID3/module.graphic.jpg.php
Executable file
@ -0,0 +1,343 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.graphic.jpg.php //
|
||||
// module for analyzing JPEG Image files //
|
||||
// dependencies: PHP compiled with --enable-exif (optional) //
|
||||
// module.tag.xmp.php (optional) //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_jpg extends getid3_handler
|
||||
{
|
||||
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'jpg';
|
||||
$info['video']['dataformat'] = 'jpg';
|
||||
$info['video']['lossless'] = false;
|
||||
$info['video']['bits_per_sample'] = 24;
|
||||
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
|
||||
$imageinfo = array();
|
||||
list($width, $height, $type) = getid3_lib::GetDataImageSize(fread($this->getid3->fp, $info['filesize']), $imageinfo);
|
||||
|
||||
|
||||
if (isset($imageinfo['APP13'])) {
|
||||
// http://php.net/iptcparse
|
||||
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
|
||||
$iptc_parsed = iptcparse($imageinfo['APP13']);
|
||||
if (is_array($iptc_parsed)) {
|
||||
foreach ($iptc_parsed as $iptc_key_raw => $iptc_values) {
|
||||
list($iptc_record, $iptc_tagkey) = explode('#', $iptc_key_raw);
|
||||
$iptc_tagkey = intval(ltrim($iptc_tagkey, '0'));
|
||||
foreach ($iptc_values as $key => $value) {
|
||||
$IPTCrecordName = $this->IPTCrecordName($iptc_record);
|
||||
$IPTCrecordTagName = $this->IPTCrecordTagName($iptc_record, $iptc_tagkey);
|
||||
if (isset($info['iptc'][$IPTCrecordName][$IPTCrecordTagName])) {
|
||||
$info['iptc'][$IPTCrecordName][$IPTCrecordTagName][] = $value;
|
||||
} else {
|
||||
$info['iptc'][$IPTCrecordName][$IPTCrecordTagName] = array($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$returnOK = false;
|
||||
switch ($type) {
|
||||
case IMG_JPG:
|
||||
$info['video']['resolution_x'] = $width;
|
||||
$info['video']['resolution_y'] = $height;
|
||||
|
||||
if (isset($imageinfo['APP1'])) {
|
||||
if (function_exists('exif_read_data')) {
|
||||
if (substr($imageinfo['APP1'], 0, 4) == 'Exif') {
|
||||
//$info['warning'][] = 'known issue: https://bugs.php.net/bug.php?id=62523';
|
||||
//return false;
|
||||
$info['jpg']['exif'] = exif_read_data($info['filenamepath'], null, true, false);
|
||||
} else {
|
||||
$info['warning'][] = 'exif_read_data() cannot parse non-EXIF data in APP1 (expected "Exif", found "'.substr($imageinfo['APP1'], 0, 4).'")';
|
||||
}
|
||||
} else {
|
||||
$info['warning'][] = 'EXIF parsing only available when '.(GETID3_OS_ISWINDOWS ? 'php_exif.dll enabled' : 'compiled with --enable-exif');
|
||||
}
|
||||
}
|
||||
$returnOK = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
$cast_as_appropriate_keys = array('EXIF', 'IFD0', 'THUMBNAIL');
|
||||
foreach ($cast_as_appropriate_keys as $exif_key) {
|
||||
if (isset($info['jpg']['exif'][$exif_key])) {
|
||||
foreach ($info['jpg']['exif'][$exif_key] as $key => $value) {
|
||||
$info['jpg']['exif'][$exif_key][$key] = $this->CastAsAppropriate($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isset($info['jpg']['exif']['GPS'])) {
|
||||
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSVersion'])) {
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$version_subparts[$i] = ord(substr($info['jpg']['exif']['GPS']['GPSVersion'], $i, 1));
|
||||
}
|
||||
$info['jpg']['exif']['GPS']['computed']['version'] = 'v'.implode('.', $version_subparts);
|
||||
}
|
||||
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSDateStamp'])) {
|
||||
$explodedGPSDateStamp = explode(':', $info['jpg']['exif']['GPS']['GPSDateStamp']);
|
||||
$computed_time[5] = (isset($explodedGPSDateStamp[0]) ? $explodedGPSDateStamp[0] : '');
|
||||
$computed_time[3] = (isset($explodedGPSDateStamp[1]) ? $explodedGPSDateStamp[1] : '');
|
||||
$computed_time[4] = (isset($explodedGPSDateStamp[2]) ? $explodedGPSDateStamp[2] : '');
|
||||
|
||||
if (function_exists('date_default_timezone_set')) {
|
||||
date_default_timezone_set('UTC');
|
||||
} else {
|
||||
ini_set('date.timezone', 'UTC');
|
||||
}
|
||||
|
||||
$computed_time = array(0=>0, 1=>0, 2=>0, 3=>0, 4=>0, 5=>0);
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSTimeStamp']) && is_array($info['jpg']['exif']['GPS']['GPSTimeStamp'])) {
|
||||
foreach ($info['jpg']['exif']['GPS']['GPSTimeStamp'] as $key => $value) {
|
||||
$computed_time[$key] = getid3_lib::DecimalizeFraction($value);
|
||||
}
|
||||
}
|
||||
$info['jpg']['exif']['GPS']['computed']['timestamp'] = mktime($computed_time[0], $computed_time[1], $computed_time[2], $computed_time[3], $computed_time[4], $computed_time[5]);
|
||||
}
|
||||
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSLatitude']) && is_array($info['jpg']['exif']['GPS']['GPSLatitude'])) {
|
||||
$direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLatitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLatitudeRef'] == 'S')) ? -1 : 1);
|
||||
foreach ($info['jpg']['exif']['GPS']['GPSLatitude'] as $key => $value) {
|
||||
$computed_latitude[$key] = getid3_lib::DecimalizeFraction($value);
|
||||
}
|
||||
$info['jpg']['exif']['GPS']['computed']['latitude'] = $direction_multiplier * ($computed_latitude[0] + ($computed_latitude[1] / 60) + ($computed_latitude[2] / 3600));
|
||||
}
|
||||
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSLongitude']) && is_array($info['jpg']['exif']['GPS']['GPSLongitude'])) {
|
||||
$direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLongitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLongitudeRef'] == 'W')) ? -1 : 1);
|
||||
foreach ($info['jpg']['exif']['GPS']['GPSLongitude'] as $key => $value) {
|
||||
$computed_longitude[$key] = getid3_lib::DecimalizeFraction($value);
|
||||
}
|
||||
$info['jpg']['exif']['GPS']['computed']['longitude'] = $direction_multiplier * ($computed_longitude[0] + ($computed_longitude[1] / 60) + ($computed_longitude[2] / 3600));
|
||||
}
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSAltitudeRef'])) {
|
||||
$info['jpg']['exif']['GPS']['GPSAltitudeRef'] = ord($info['jpg']['exif']['GPS']['GPSAltitudeRef']); // 0 = above sea level; 1 = below sea level
|
||||
}
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSAltitude'])) {
|
||||
$direction_multiplier = (!empty($info['jpg']['exif']['GPS']['GPSAltitudeRef']) ? -1 : 1); // 0 = above sea level; 1 = below sea level
|
||||
$info['jpg']['exif']['GPS']['computed']['altitude'] = $direction_multiplier * getid3_lib::DecimalizeFraction($info['jpg']['exif']['GPS']['GPSAltitude']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.xmp.php', __FILE__, false)) {
|
||||
if (isset($info['filenamepath'])) {
|
||||
$image_xmp = new Image_XMP($info['filenamepath']);
|
||||
$xmp_raw = $image_xmp->getAllTags();
|
||||
foreach ($xmp_raw as $key => $value) {
|
||||
if (strpos($key, ':')) {
|
||||
list($subsection, $tagname) = explode(':', $key);
|
||||
$info['xmp'][$subsection][$tagname] = $this->CastAsAppropriate($value);
|
||||
} else {
|
||||
$info['warning'][] = 'XMP: expecting "<subsection>:<tagname>", found "'.$key.'"';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$returnOK) {
|
||||
unset($info['fileformat']);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function CastAsAppropriate($value) {
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
} elseif (preg_match('#^[0-9]+/[0-9]+$#', $value)) {
|
||||
return getid3_lib::DecimalizeFraction($value);
|
||||
} elseif (preg_match('#^[0-9]+$#', $value)) {
|
||||
return getid3_lib::CastAsInt($value);
|
||||
} elseif (preg_match('#^[0-9\.]+$#', $value)) {
|
||||
return (float) $value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
public function IPTCrecordName($iptc_record) {
|
||||
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
|
||||
static $IPTCrecordName = array();
|
||||
if (empty($IPTCrecordName)) {
|
||||
$IPTCrecordName = array(
|
||||
1 => 'IPTCEnvelope',
|
||||
2 => 'IPTCApplication',
|
||||
3 => 'IPTCNewsPhoto',
|
||||
7 => 'IPTCPreObjectData',
|
||||
8 => 'IPTCObjectData',
|
||||
9 => 'IPTCPostObjectData',
|
||||
);
|
||||
}
|
||||
return (isset($IPTCrecordName[$iptc_record]) ? $IPTCrecordName[$iptc_record] : '');
|
||||
}
|
||||
|
||||
|
||||
public function IPTCrecordTagName($iptc_record, $iptc_tagkey) {
|
||||
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
|
||||
static $IPTCrecordTagName = array();
|
||||
if (empty($IPTCrecordTagName)) {
|
||||
$IPTCrecordTagName = array(
|
||||
1 => array( // IPTC EnvelopeRecord Tags
|
||||
0 => 'EnvelopeRecordVersion',
|
||||
5 => 'Destination',
|
||||
20 => 'FileFormat',
|
||||
22 => 'FileVersion',
|
||||
30 => 'ServiceIdentifier',
|
||||
40 => 'EnvelopeNumber',
|
||||
50 => 'ProductID',
|
||||
60 => 'EnvelopePriority',
|
||||
70 => 'DateSent',
|
||||
80 => 'TimeSent',
|
||||
90 => 'CodedCharacterSet',
|
||||
100 => 'UniqueObjectName',
|
||||
120 => 'ARMIdentifier',
|
||||
122 => 'ARMVersion',
|
||||
),
|
||||
2 => array( // IPTC ApplicationRecord Tags
|
||||
0 => 'ApplicationRecordVersion',
|
||||
3 => 'ObjectTypeReference',
|
||||
4 => 'ObjectAttributeReference',
|
||||
5 => 'ObjectName',
|
||||
7 => 'EditStatus',
|
||||
8 => 'EditorialUpdate',
|
||||
10 => 'Urgency',
|
||||
12 => 'SubjectReference',
|
||||
15 => 'Category',
|
||||
20 => 'SupplementalCategories',
|
||||
22 => 'FixtureIdentifier',
|
||||
25 => 'Keywords',
|
||||
26 => 'ContentLocationCode',
|
||||
27 => 'ContentLocationName',
|
||||
30 => 'ReleaseDate',
|
||||
35 => 'ReleaseTime',
|
||||
37 => 'ExpirationDate',
|
||||
38 => 'ExpirationTime',
|
||||
40 => 'SpecialInstructions',
|
||||
42 => 'ActionAdvised',
|
||||
45 => 'ReferenceService',
|
||||
47 => 'ReferenceDate',
|
||||
50 => 'ReferenceNumber',
|
||||
55 => 'DateCreated',
|
||||
60 => 'TimeCreated',
|
||||
62 => 'DigitalCreationDate',
|
||||
63 => 'DigitalCreationTime',
|
||||
65 => 'OriginatingProgram',
|
||||
70 => 'ProgramVersion',
|
||||
75 => 'ObjectCycle',
|
||||
80 => 'By-line',
|
||||
85 => 'By-lineTitle',
|
||||
90 => 'City',
|
||||
92 => 'Sub-location',
|
||||
95 => 'Province-State',
|
||||
100 => 'Country-PrimaryLocationCode',
|
||||
101 => 'Country-PrimaryLocationName',
|
||||
103 => 'OriginalTransmissionReference',
|
||||
105 => 'Headline',
|
||||
110 => 'Credit',
|
||||
115 => 'Source',
|
||||
116 => 'CopyrightNotice',
|
||||
118 => 'Contact',
|
||||
120 => 'Caption-Abstract',
|
||||
121 => 'LocalCaption',
|
||||
122 => 'Writer-Editor',
|
||||
125 => 'RasterizedCaption',
|
||||
130 => 'ImageType',
|
||||
131 => 'ImageOrientation',
|
||||
135 => 'LanguageIdentifier',
|
||||
150 => 'AudioType',
|
||||
151 => 'AudioSamplingRate',
|
||||
152 => 'AudioSamplingResolution',
|
||||
153 => 'AudioDuration',
|
||||
154 => 'AudioOutcue',
|
||||
184 => 'JobID',
|
||||
185 => 'MasterDocumentID',
|
||||
186 => 'ShortDocumentID',
|
||||
187 => 'UniqueDocumentID',
|
||||
188 => 'OwnerID',
|
||||
200 => 'ObjectPreviewFileFormat',
|
||||
201 => 'ObjectPreviewFileVersion',
|
||||
202 => 'ObjectPreviewData',
|
||||
221 => 'Prefs',
|
||||
225 => 'ClassifyState',
|
||||
228 => 'SimilarityIndex',
|
||||
230 => 'DocumentNotes',
|
||||
231 => 'DocumentHistory',
|
||||
232 => 'ExifCameraInfo',
|
||||
),
|
||||
3 => array( // IPTC NewsPhoto Tags
|
||||
0 => 'NewsPhotoVersion',
|
||||
10 => 'IPTCPictureNumber',
|
||||
20 => 'IPTCImageWidth',
|
||||
30 => 'IPTCImageHeight',
|
||||
40 => 'IPTCPixelWidth',
|
||||
50 => 'IPTCPixelHeight',
|
||||
55 => 'SupplementalType',
|
||||
60 => 'ColorRepresentation',
|
||||
64 => 'InterchangeColorSpace',
|
||||
65 => 'ColorSequence',
|
||||
66 => 'ICC_Profile',
|
||||
70 => 'ColorCalibrationMatrix',
|
||||
80 => 'LookupTable',
|
||||
84 => 'NumIndexEntries',
|
||||
85 => 'ColorPalette',
|
||||
86 => 'IPTCBitsPerSample',
|
||||
90 => 'SampleStructure',
|
||||
100 => 'ScanningDirection',
|
||||
102 => 'IPTCImageRotation',
|
||||
110 => 'DataCompressionMethod',
|
||||
120 => 'QuantizationMethod',
|
||||
125 => 'EndPoints',
|
||||
130 => 'ExcursionTolerance',
|
||||
135 => 'BitsPerComponent',
|
||||
140 => 'MaximumDensityRange',
|
||||
145 => 'GammaCompensatedValue',
|
||||
),
|
||||
7 => array( // IPTC PreObjectData Tags
|
||||
10 => 'SizeMode',
|
||||
20 => 'MaxSubfileSize',
|
||||
90 => 'ObjectSizeAnnounced',
|
||||
95 => 'MaximumObjectSize',
|
||||
),
|
||||
8 => array( // IPTC ObjectData Tags
|
||||
10 => 'SubFile',
|
||||
),
|
||||
9 => array( // IPTC PostObjectData Tags
|
||||
10 => 'ConfirmedObjectSize',
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
return (isset($IPTCrecordTagName[$iptc_record][$iptc_tagkey]) ? $IPTCrecordTagName[$iptc_record][$iptc_tagkey] : $iptc_tagkey);
|
||||
}
|
||||
|
||||
}
|
132
api/getID3/module.graphic.pcd.php
Executable file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.graphic.pcd.php //
|
||||
// module for analyzing PhotoCD (PCD) Image files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_pcd extends getid3_handler
|
||||
{
|
||||
public $ExtractData = 0;
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'pcd';
|
||||
$info['video']['dataformat'] = 'pcd';
|
||||
$info['video']['lossless'] = false;
|
||||
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'] + 72, SEEK_SET);
|
||||
|
||||
$PCDflags = fread($this->getid3->fp, 1);
|
||||
$PCDisVertical = ((ord($PCDflags) & 0x01) ? true : false);
|
||||
|
||||
|
||||
if ($PCDisVertical) {
|
||||
$info['video']['resolution_x'] = 3072;
|
||||
$info['video']['resolution_y'] = 2048;
|
||||
} else {
|
||||
$info['video']['resolution_x'] = 2048;
|
||||
$info['video']['resolution_y'] = 3072;
|
||||
}
|
||||
|
||||
|
||||
if ($this->ExtractData > 3) {
|
||||
|
||||
$info['error'][] = 'Cannot extract PSD image data for detail levels above BASE (level-3) because encrypted with Kodak-proprietary compression/encryption.';
|
||||
|
||||
} elseif ($this->ExtractData > 0) {
|
||||
|
||||
$PCD_levels[1] = array( 192, 128, 0x02000); // BASE/16
|
||||
$PCD_levels[2] = array( 384, 256, 0x0B800); // BASE/4
|
||||
$PCD_levels[3] = array( 768, 512, 0x30000); // BASE
|
||||
//$PCD_levels[4] = array(1536, 1024, ??); // BASE*4 - encrypted with Kodak-proprietary compression/encryption
|
||||
//$PCD_levels[5] = array(3072, 2048, ??); // BASE*16 - encrypted with Kodak-proprietary compression/encryption
|
||||
//$PCD_levels[6] = array(6144, 4096, ??); // BASE*64 - encrypted with Kodak-proprietary compression/encryption; PhotoCD-Pro only
|
||||
|
||||
list($PCD_width, $PCD_height, $PCD_dataOffset) = $PCD_levels[3];
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'] + $PCD_dataOffset, SEEK_SET);
|
||||
|
||||
for ($y = 0; $y < $PCD_height; $y += 2) {
|
||||
// The image-data of these subtypes start at the respective offsets of 02000h, 0b800h and 30000h.
|
||||
// To decode the YcbYr to the more usual RGB-code, three lines of data have to be read, each
|
||||
// consisting of ‘w’ bytes, where ‘w’ is the width of the image-subtype. The first ‘w’ bytes and
|
||||
// the first half of the third ‘w’ bytes contain data for the first RGB-line, the second ‘w’ bytes
|
||||
// and the second half of the third ‘w’ bytes contain data for a second RGB-line.
|
||||
|
||||
$PCD_data_Y1 = fread($this->getid3->fp, $PCD_width);
|
||||
$PCD_data_Y2 = fread($this->getid3->fp, $PCD_width);
|
||||
$PCD_data_Cb = fread($this->getid3->fp, intval(round($PCD_width / 2)));
|
||||
$PCD_data_Cr = fread($this->getid3->fp, intval(round($PCD_width / 2)));
|
||||
|
||||
for ($x = 0; $x < $PCD_width; $x++) {
|
||||
if ($PCDisVertical) {
|
||||
$info['pcd']['data'][$PCD_width - $x][$y] = $this->YCbCr2RGB(ord($PCD_data_Y1{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)}));
|
||||
$info['pcd']['data'][$PCD_width - $x][$y + 1] = $this->YCbCr2RGB(ord($PCD_data_Y2{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)}));
|
||||
} else {
|
||||
$info['pcd']['data'][$y][$x] = $this->YCbCr2RGB(ord($PCD_data_Y1{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)}));
|
||||
$info['pcd']['data'][$y + 1][$x] = $this->YCbCr2RGB(ord($PCD_data_Y2{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example for plotting extracted data
|
||||
//getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true);
|
||||
//if ($PCDisVertical) {
|
||||
// $BMPinfo['resolution_x'] = $PCD_height;
|
||||
// $BMPinfo['resolution_y'] = $PCD_width;
|
||||
//} else {
|
||||
// $BMPinfo['resolution_x'] = $PCD_width;
|
||||
// $BMPinfo['resolution_y'] = $PCD_height;
|
||||
//}
|
||||
//$BMPinfo['bmp']['data'] = $info['pcd']['data'];
|
||||
//getid3_bmp::PlotBMP($BMPinfo);
|
||||
//exit;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function YCbCr2RGB($Y, $Cb, $Cr) {
|
||||
static $YCbCr_constants = array();
|
||||
if (empty($YCbCr_constants)) {
|
||||
$YCbCr_constants['red']['Y'] = 0.0054980 * 256;
|
||||
$YCbCr_constants['red']['Cb'] = 0.0000000 * 256;
|
||||
$YCbCr_constants['red']['Cr'] = 0.0051681 * 256;
|
||||
$YCbCr_constants['green']['Y'] = 0.0054980 * 256;
|
||||
$YCbCr_constants['green']['Cb'] = -0.0015446 * 256;
|
||||
$YCbCr_constants['green']['Cr'] = -0.0026325 * 256;
|
||||
$YCbCr_constants['blue']['Y'] = 0.0054980 * 256;
|
||||
$YCbCr_constants['blue']['Cb'] = 0.0079533 * 256;
|
||||
$YCbCr_constants['blue']['Cr'] = 0.0000000 * 256;
|
||||
}
|
||||
|
||||
$RGBcolor = array('red'=>0, 'green'=>0, 'blue'=>0);
|
||||
foreach ($RGBcolor as $rgbname => $dummy) {
|
||||
$RGBcolor[$rgbname] = max(0,
|
||||
min(255,
|
||||
intval(
|
||||
round(
|
||||
($YCbCr_constants[$rgbname]['Y'] * $Y) +
|
||||
($YCbCr_constants[$rgbname]['Cb'] * ($Cb - 156)) +
|
||||
($YCbCr_constants[$rgbname]['Cr'] * ($Cr - 137))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return (($RGBcolor['red'] * 65536) + ($RGBcolor['green'] * 256) + $RGBcolor['blue']);
|
||||
}
|
||||
|
||||
}
|
517
api/getID3/module.graphic.png.php
Executable file
@ -0,0 +1,517 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.graphic.png.php //
|
||||
// module for analyzing PNG Image files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_png extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// shortcut
|
||||
$info['png'] = array();
|
||||
$thisfile_png = &$info['png'];
|
||||
|
||||
$info['fileformat'] = 'png';
|
||||
$info['video']['dataformat'] = 'png';
|
||||
$info['video']['lossless'] = false;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$PNGfiledata = fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||
$offset = 0;
|
||||
|
||||
$PNGidentifier = substr($PNGfiledata, $offset, 8); // $89 $50 $4E $47 $0D $0A $1A $0A
|
||||
$offset += 8;
|
||||
|
||||
if ($PNGidentifier != "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
|
||||
$info['error'][] = 'First 8 bytes of file ('.getid3_lib::PrintHexBytes($PNGidentifier).') did not match expected PNG identifier';
|
||||
unset($info['fileformat']);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (((ftell($this->getid3->fp) - (strlen($PNGfiledata) - $offset)) < $info['filesize'])) {
|
||||
$chunk['data_length'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4));
|
||||
$offset += 4;
|
||||
while (((strlen($PNGfiledata) - $offset) < ($chunk['data_length'] + 4)) && (ftell($this->getid3->fp) < $info['filesize'])) {
|
||||
$PNGfiledata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||
}
|
||||
$chunk['type_text'] = substr($PNGfiledata, $offset, 4);
|
||||
$offset += 4;
|
||||
$chunk['type_raw'] = getid3_lib::BigEndian2Int($chunk['type_text']);
|
||||
$chunk['data'] = substr($PNGfiledata, $offset, $chunk['data_length']);
|
||||
$offset += $chunk['data_length'];
|
||||
$chunk['crc'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4));
|
||||
$offset += 4;
|
||||
|
||||
$chunk['flags']['ancilliary'] = (bool) ($chunk['type_raw'] & 0x20000000);
|
||||
$chunk['flags']['private'] = (bool) ($chunk['type_raw'] & 0x00200000);
|
||||
$chunk['flags']['reserved'] = (bool) ($chunk['type_raw'] & 0x00002000);
|
||||
$chunk['flags']['safe_to_copy'] = (bool) ($chunk['type_raw'] & 0x00000020);
|
||||
|
||||
// shortcut
|
||||
$thisfile_png[$chunk['type_text']] = array();
|
||||
$thisfile_png_chunk_type_text = &$thisfile_png[$chunk['type_text']];
|
||||
|
||||
switch ($chunk['type_text']) {
|
||||
|
||||
case 'IHDR': // Image Header
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$thisfile_png_chunk_type_text['width'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4));
|
||||
$thisfile_png_chunk_type_text['height'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4));
|
||||
$thisfile_png_chunk_type_text['raw']['bit_depth'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 1));
|
||||
$thisfile_png_chunk_type_text['raw']['color_type'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 9, 1));
|
||||
$thisfile_png_chunk_type_text['raw']['compression_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 10, 1));
|
||||
$thisfile_png_chunk_type_text['raw']['filter_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 11, 1));
|
||||
$thisfile_png_chunk_type_text['raw']['interlace_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 12, 1));
|
||||
|
||||
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['raw']['compression_method']);
|
||||
$thisfile_png_chunk_type_text['color_type']['palette'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x01);
|
||||
$thisfile_png_chunk_type_text['color_type']['true_color'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x02);
|
||||
$thisfile_png_chunk_type_text['color_type']['alpha'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x04);
|
||||
|
||||
$info['video']['resolution_x'] = $thisfile_png_chunk_type_text['width'];
|
||||
$info['video']['resolution_y'] = $thisfile_png_chunk_type_text['height'];
|
||||
|
||||
$info['video']['bits_per_sample'] = $this->IHDRcalculateBitsPerSample($thisfile_png_chunk_type_text['raw']['color_type'], $thisfile_png_chunk_type_text['raw']['bit_depth']);
|
||||
break;
|
||||
|
||||
|
||||
case 'PLTE': // Palette
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$paletteoffset = 0;
|
||||
for ($i = 0; $i <= 255; $i++) {
|
||||
//$thisfile_png_chunk_type_text['red'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||
//$thisfile_png_chunk_type_text['green'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||
//$thisfile_png_chunk_type_text['blue'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||
$red = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||
$green = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||
$blue = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||
$thisfile_png_chunk_type_text[$i] = (($red << 16) | ($green << 8) | ($blue));
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'tRNS': // Transparency
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
switch ($thisfile_png['IHDR']['raw']['color_type']) {
|
||||
case 0:
|
||||
$thisfile_png_chunk_type_text['transparent_color_gray'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 2));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$thisfile_png_chunk_type_text['transparent_color_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 2));
|
||||
$thisfile_png_chunk_type_text['transparent_color_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 2));
|
||||
$thisfile_png_chunk_type_text['transparent_color_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 2));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
for ($i = 0; $i < strlen($chunk['data']); $i++) {
|
||||
$thisfile_png_chunk_type_text['palette_opacity'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $i, 1));
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
case 6:
|
||||
$info['error'][] = 'Invalid color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type'];
|
||||
|
||||
default:
|
||||
$info['warning'][] = 'Unhandled color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type'];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'gAMA': // Image Gamma
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$thisfile_png_chunk_type_text['gamma'] = getid3_lib::BigEndian2Int($chunk['data']) / 100000;
|
||||
break;
|
||||
|
||||
|
||||
case 'cHRM': // Primary Chromaticities
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$thisfile_png_chunk_type_text['white_x'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4)) / 100000;
|
||||
$thisfile_png_chunk_type_text['white_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4)) / 100000;
|
||||
$thisfile_png_chunk_type_text['red_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 4)) / 100000;
|
||||
$thisfile_png_chunk_type_text['red_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 12, 4)) / 100000;
|
||||
$thisfile_png_chunk_type_text['green_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 16, 4)) / 100000;
|
||||
$thisfile_png_chunk_type_text['green_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 20, 4)) / 100000;
|
||||
$thisfile_png_chunk_type_text['blue_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 24, 4)) / 100000;
|
||||
$thisfile_png_chunk_type_text['blue_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 28, 4)) / 100000;
|
||||
break;
|
||||
|
||||
|
||||
case 'sRGB': // Standard RGB Color Space
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$thisfile_png_chunk_type_text['reindering_intent'] = getid3_lib::BigEndian2Int($chunk['data']);
|
||||
$thisfile_png_chunk_type_text['reindering_intent_text'] = $this->PNGsRGBintentLookup($thisfile_png_chunk_type_text['reindering_intent']);
|
||||
break;
|
||||
|
||||
|
||||
case 'iCCP': // Embedded ICC Profile
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
list($profilename, $compressiondata) = explode("\x00", $chunk['data'], 2);
|
||||
$thisfile_png_chunk_type_text['profile_name'] = $profilename;
|
||||
$thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($compressiondata, 0, 1));
|
||||
$thisfile_png_chunk_type_text['compression_profile'] = substr($compressiondata, 1);
|
||||
|
||||
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']);
|
||||
break;
|
||||
|
||||
|
||||
case 'tEXt': // Textual Data
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
list($keyword, $text) = explode("\x00", $chunk['data'], 2);
|
||||
$thisfile_png_chunk_type_text['keyword'] = $keyword;
|
||||
$thisfile_png_chunk_type_text['text'] = $text;
|
||||
|
||||
$thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text'];
|
||||
break;
|
||||
|
||||
|
||||
case 'zTXt': // Compressed Textual Data
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
list($keyword, $otherdata) = explode("\x00", $chunk['data'], 2);
|
||||
$thisfile_png_chunk_type_text['keyword'] = $keyword;
|
||||
$thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($otherdata, 0, 1));
|
||||
$thisfile_png_chunk_type_text['compressed_text'] = substr($otherdata, 1);
|
||||
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']);
|
||||
switch ($thisfile_png_chunk_type_text['compression_method']) {
|
||||
case 0:
|
||||
$thisfile_png_chunk_type_text['text'] = gzuncompress($thisfile_png_chunk_type_text['compressed_text']);
|
||||
break;
|
||||
|
||||
default:
|
||||
// unknown compression method
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($thisfile_png_chunk_type_text['text'])) {
|
||||
$thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text'];
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'iTXt': // International Textual Data
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
list($keyword, $otherdata) = explode("\x00", $chunk['data'], 2);
|
||||
$thisfile_png_chunk_type_text['keyword'] = $keyword;
|
||||
$thisfile_png_chunk_type_text['compression'] = (bool) getid3_lib::BigEndian2Int(substr($otherdata, 0, 1));
|
||||
$thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($otherdata, 1, 1));
|
||||
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']);
|
||||
list($languagetag, $translatedkeyword, $text) = explode("\x00", substr($otherdata, 2), 3);
|
||||
$thisfile_png_chunk_type_text['language_tag'] = $languagetag;
|
||||
$thisfile_png_chunk_type_text['translated_keyword'] = $translatedkeyword;
|
||||
|
||||
if ($thisfile_png_chunk_type_text['compression']) {
|
||||
|
||||
switch ($thisfile_png_chunk_type_text['compression_method']) {
|
||||
case 0:
|
||||
$thisfile_png_chunk_type_text['text'] = gzuncompress($text);
|
||||
break;
|
||||
|
||||
default:
|
||||
// unknown compression method
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$thisfile_png_chunk_type_text['text'] = $text;
|
||||
|
||||
}
|
||||
|
||||
if (isset($thisfile_png_chunk_type_text['text'])) {
|
||||
$thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text'];
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'bKGD': // Background Color
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
switch ($thisfile_png['IHDR']['raw']['color_type']) {
|
||||
case 0:
|
||||
case 4:
|
||||
$thisfile_png_chunk_type_text['background_gray'] = getid3_lib::BigEndian2Int($chunk['data']);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 6:
|
||||
$thisfile_png_chunk_type_text['background_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth']));
|
||||
$thisfile_png_chunk_type_text['background_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth']));
|
||||
$thisfile_png_chunk_type_text['background_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth']));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$thisfile_png_chunk_type_text['background_index'] = getid3_lib::BigEndian2Int($chunk['data']);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'pHYs': // Physical Pixel Dimensions
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$thisfile_png_chunk_type_text['pixels_per_unit_x'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4));
|
||||
$thisfile_png_chunk_type_text['pixels_per_unit_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4));
|
||||
$thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 1));
|
||||
$thisfile_png_chunk_type_text['unit'] = $this->PNGpHYsUnitLookup($thisfile_png_chunk_type_text['unit_specifier']);
|
||||
break;
|
||||
|
||||
|
||||
case 'sBIT': // Significant Bits
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
switch ($thisfile_png['IHDR']['raw']['color_type']) {
|
||||
case 0:
|
||||
$thisfile_png_chunk_type_text['significant_bits_gray'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
$thisfile_png_chunk_type_text['significant_bits_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||
$thisfile_png_chunk_type_text['significant_bits_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
|
||||
$thisfile_png_chunk_type_text['significant_bits_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 1));
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$thisfile_png_chunk_type_text['significant_bits_gray'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||
$thisfile_png_chunk_type_text['significant_bits_alpha'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
|
||||
break;
|
||||
|
||||
case 6:
|
||||
$thisfile_png_chunk_type_text['significant_bits_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||
$thisfile_png_chunk_type_text['significant_bits_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
|
||||
$thisfile_png_chunk_type_text['significant_bits_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 1));
|
||||
$thisfile_png_chunk_type_text['significant_bits_alpha'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 3, 1));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'sPLT': // Suggested Palette
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
list($palettename, $otherdata) = explode("\x00", $chunk['data'], 2);
|
||||
$thisfile_png_chunk_type_text['palette_name'] = $palettename;
|
||||
$sPLToffset = 0;
|
||||
$thisfile_png_chunk_type_text['sample_depth_bits'] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, 1));
|
||||
$sPLToffset += 1;
|
||||
$thisfile_png_chunk_type_text['sample_depth_bytes'] = $thisfile_png_chunk_type_text['sample_depth_bits'] / 8;
|
||||
$paletteCounter = 0;
|
||||
while ($sPLToffset < strlen($otherdata)) {
|
||||
$thisfile_png_chunk_type_text['red'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
|
||||
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
|
||||
$thisfile_png_chunk_type_text['green'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
|
||||
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
|
||||
$thisfile_png_chunk_type_text['blue'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
|
||||
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
|
||||
$thisfile_png_chunk_type_text['alpha'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
|
||||
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
|
||||
$thisfile_png_chunk_type_text['frequency'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, 2));
|
||||
$sPLToffset += 2;
|
||||
$paletteCounter++;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'hIST': // Palette Histogram
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$hISTcounter = 0;
|
||||
while ($hISTcounter < strlen($chunk['data'])) {
|
||||
$thisfile_png_chunk_type_text[$hISTcounter] = getid3_lib::BigEndian2Int(substr($chunk['data'], $hISTcounter / 2, 2));
|
||||
$hISTcounter += 2;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'tIME': // Image Last-Modification Time
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$thisfile_png_chunk_type_text['year'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 2));
|
||||
$thisfile_png_chunk_type_text['month'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 1));
|
||||
$thisfile_png_chunk_type_text['day'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 3, 1));
|
||||
$thisfile_png_chunk_type_text['hour'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 1));
|
||||
$thisfile_png_chunk_type_text['minute'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 5, 1));
|
||||
$thisfile_png_chunk_type_text['second'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 6, 1));
|
||||
$thisfile_png_chunk_type_text['unix'] = gmmktime($thisfile_png_chunk_type_text['hour'], $thisfile_png_chunk_type_text['minute'], $thisfile_png_chunk_type_text['second'], $thisfile_png_chunk_type_text['month'], $thisfile_png_chunk_type_text['day'], $thisfile_png_chunk_type_text['year']);
|
||||
break;
|
||||
|
||||
|
||||
case 'oFFs': // Image Offset
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$thisfile_png_chunk_type_text['position_x'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4), false, true);
|
||||
$thisfile_png_chunk_type_text['position_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4), false, true);
|
||||
$thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 1));
|
||||
$thisfile_png_chunk_type_text['unit'] = $this->PNGoFFsUnitLookup($thisfile_png_chunk_type_text['unit_specifier']);
|
||||
break;
|
||||
|
||||
|
||||
case 'pCAL': // Calibration Of Pixel Values
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
list($calibrationname, $otherdata) = explode("\x00", $chunk['data'], 2);
|
||||
$thisfile_png_chunk_type_text['calibration_name'] = $calibrationname;
|
||||
$pCALoffset = 0;
|
||||
$thisfile_png_chunk_type_text['original_zero'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 4), false, true);
|
||||
$pCALoffset += 4;
|
||||
$thisfile_png_chunk_type_text['original_max'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 4), false, true);
|
||||
$pCALoffset += 4;
|
||||
$thisfile_png_chunk_type_text['equation_type'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 1));
|
||||
$pCALoffset += 1;
|
||||
$thisfile_png_chunk_type_text['equation_type_text'] = $this->PNGpCALequationTypeLookup($thisfile_png_chunk_type_text['equation_type']);
|
||||
$thisfile_png_chunk_type_text['parameter_count'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 1));
|
||||
$pCALoffset += 1;
|
||||
$thisfile_png_chunk_type_text['parameters'] = explode("\x00", substr($chunk['data'], $pCALoffset));
|
||||
break;
|
||||
|
||||
|
||||
case 'sCAL': // Physical Scale Of Image Subject
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||
$thisfile_png_chunk_type_text['unit'] = $this->PNGsCALUnitLookup($thisfile_png_chunk_type_text['unit_specifier']);
|
||||
list($pixelwidth, $pixelheight) = explode("\x00", substr($chunk['data'], 1));
|
||||
$thisfile_png_chunk_type_text['pixel_width'] = $pixelwidth;
|
||||
$thisfile_png_chunk_type_text['pixel_height'] = $pixelheight;
|
||||
break;
|
||||
|
||||
|
||||
case 'gIFg': // GIF Graphic Control Extension
|
||||
$gIFgCounter = 0;
|
||||
if (isset($thisfile_png_chunk_type_text) && is_array($thisfile_png_chunk_type_text)) {
|
||||
$gIFgCounter = count($thisfile_png_chunk_type_text);
|
||||
}
|
||||
$thisfile_png_chunk_type_text[$gIFgCounter]['header'] = $chunk;
|
||||
$thisfile_png_chunk_type_text[$gIFgCounter]['disposal_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||
$thisfile_png_chunk_type_text[$gIFgCounter]['user_input_flag'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
|
||||
$thisfile_png_chunk_type_text[$gIFgCounter]['delay_time'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 2));
|
||||
break;
|
||||
|
||||
|
||||
case 'gIFx': // GIF Application Extension
|
||||
$gIFxCounter = 0;
|
||||
if (isset($thisfile_png_chunk_type_text) && is_array($thisfile_png_chunk_type_text)) {
|
||||
$gIFxCounter = count($thisfile_png_chunk_type_text);
|
||||
}
|
||||
$thisfile_png_chunk_type_text[$gIFxCounter]['header'] = $chunk;
|
||||
$thisfile_png_chunk_type_text[$gIFxCounter]['application_identifier'] = substr($chunk['data'], 0, 8);
|
||||
$thisfile_png_chunk_type_text[$gIFxCounter]['authentication_code'] = substr($chunk['data'], 8, 3);
|
||||
$thisfile_png_chunk_type_text[$gIFxCounter]['application_data'] = substr($chunk['data'], 11);
|
||||
break;
|
||||
|
||||
|
||||
case 'IDAT': // Image Data
|
||||
$idatinformationfieldindex = 0;
|
||||
if (isset($thisfile_png['IDAT']) && is_array($thisfile_png['IDAT'])) {
|
||||
$idatinformationfieldindex = count($thisfile_png['IDAT']);
|
||||
}
|
||||
unset($chunk['data']);
|
||||
$thisfile_png_chunk_type_text[$idatinformationfieldindex]['header'] = $chunk;
|
||||
break;
|
||||
|
||||
|
||||
case 'IEND': // Image Trailer
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
//unset($chunk['data']);
|
||||
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||
$info['warning'][] = 'Unhandled chunk type: '.$chunk['type_text'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function PNGsRGBintentLookup($sRGB) {
|
||||
static $PNGsRGBintentLookup = array(
|
||||
0 => 'Perceptual',
|
||||
1 => 'Relative colorimetric',
|
||||
2 => 'Saturation',
|
||||
3 => 'Absolute colorimetric'
|
||||
);
|
||||
return (isset($PNGsRGBintentLookup[$sRGB]) ? $PNGsRGBintentLookup[$sRGB] : 'invalid');
|
||||
}
|
||||
|
||||
public function PNGcompressionMethodLookup($compressionmethod) {
|
||||
static $PNGcompressionMethodLookup = array(
|
||||
0 => 'deflate/inflate'
|
||||
);
|
||||
return (isset($PNGcompressionMethodLookup[$compressionmethod]) ? $PNGcompressionMethodLookup[$compressionmethod] : 'invalid');
|
||||
}
|
||||
|
||||
public function PNGpHYsUnitLookup($unitid) {
|
||||
static $PNGpHYsUnitLookup = array(
|
||||
0 => 'unknown',
|
||||
1 => 'meter'
|
||||
);
|
||||
return (isset($PNGpHYsUnitLookup[$unitid]) ? $PNGpHYsUnitLookup[$unitid] : 'invalid');
|
||||
}
|
||||
|
||||
public function PNGoFFsUnitLookup($unitid) {
|
||||
static $PNGoFFsUnitLookup = array(
|
||||
0 => 'pixel',
|
||||
1 => 'micrometer'
|
||||
);
|
||||
return (isset($PNGoFFsUnitLookup[$unitid]) ? $PNGoFFsUnitLookup[$unitid] : 'invalid');
|
||||
}
|
||||
|
||||
public function PNGpCALequationTypeLookup($equationtype) {
|
||||
static $PNGpCALequationTypeLookup = array(
|
||||
0 => 'Linear mapping',
|
||||
1 => 'Base-e exponential mapping',
|
||||
2 => 'Arbitrary-base exponential mapping',
|
||||
3 => 'Hyperbolic mapping'
|
||||
);
|
||||
return (isset($PNGpCALequationTypeLookup[$equationtype]) ? $PNGpCALequationTypeLookup[$equationtype] : 'invalid');
|
||||
}
|
||||
|
||||
public function PNGsCALUnitLookup($unitid) {
|
||||
static $PNGsCALUnitLookup = array(
|
||||
0 => 'meter',
|
||||
1 => 'radian'
|
||||
);
|
||||
return (isset($PNGsCALUnitLookup[$unitid]) ? $PNGsCALUnitLookup[$unitid] : 'invalid');
|
||||
}
|
||||
|
||||
public function IHDRcalculateBitsPerSample($color_type, $bit_depth) {
|
||||
switch ($color_type) {
|
||||
case 0: // Each pixel is a grayscale sample.
|
||||
return $bit_depth;
|
||||
break;
|
||||
|
||||
case 2: // Each pixel is an R,G,B triple
|
||||
return 3 * $bit_depth;
|
||||
break;
|
||||
|
||||
case 3: // Each pixel is a palette index; a PLTE chunk must appear.
|
||||
return $bit_depth;
|
||||
break;
|
||||
|
||||
case 4: // Each pixel is a grayscale sample, followed by an alpha sample.
|
||||
return 2 * $bit_depth;
|
||||
break;
|
||||
|
||||
case 6: // Each pixel is an R,G,B triple, followed by an alpha sample.
|
||||
return 4 * $bit_depth;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
101
api/getID3/module.graphic.svg.php
Executable file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.graphic.svg.php //
|
||||
// module for analyzing SVG Image files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_svg extends getid3_handler
|
||||
{
|
||||
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
|
||||
$SVGheader = fread($this->getid3->fp, 4096);
|
||||
if (preg_match('#\<\?xml([^\>]+)\?\>#i', $SVGheader, $matches)) {
|
||||
$info['svg']['xml']['raw'] = $matches;
|
||||
}
|
||||
if (preg_match('#\<\!DOCTYPE([^\>]+)\>#i', $SVGheader, $matches)) {
|
||||
$info['svg']['doctype']['raw'] = $matches;
|
||||
}
|
||||
if (preg_match('#\<svg([^\>]+)\>#i', $SVGheader, $matches)) {
|
||||
$info['svg']['svg']['raw'] = $matches;
|
||||
}
|
||||
if (isset($info['svg']['svg']['raw'])) {
|
||||
|
||||
$sections_to_fix = array('xml', 'doctype', 'svg');
|
||||
foreach ($sections_to_fix as $section_to_fix) {
|
||||
if (!isset($info['svg'][$section_to_fix])) {
|
||||
continue;
|
||||
}
|
||||
$section_data = array();
|
||||
while (preg_match('/ "([^"]+)"/', $info['svg'][$section_to_fix]['raw'][1], $matches)) {
|
||||
$section_data[] = $matches[1];
|
||||
$info['svg'][$section_to_fix]['raw'][1] = str_replace($matches[0], '', $info['svg'][$section_to_fix]['raw'][1]);
|
||||
}
|
||||
while (preg_match('/([^\s]+)="([^"]+)"/', $info['svg'][$section_to_fix]['raw'][1], $matches)) {
|
||||
$section_data[] = $matches[0];
|
||||
$info['svg'][$section_to_fix]['raw'][1] = str_replace($matches[0], '', $info['svg'][$section_to_fix]['raw'][1]);
|
||||
}
|
||||
$section_data = array_merge($section_data, preg_split('/[\s,]+/', $info['svg'][$section_to_fix]['raw'][1]));
|
||||
foreach ($section_data as $keyvaluepair) {
|
||||
$keyvaluepair = trim($keyvaluepair);
|
||||
if ($keyvaluepair) {
|
||||
$keyvalueexploded = explode('=', $keyvaluepair);
|
||||
$key = (isset($keyvalueexploded[0]) ? $keyvalueexploded[0] : '');
|
||||
$value = (isset($keyvalueexploded[1]) ? $keyvalueexploded[1] : '');
|
||||
$info['svg'][$section_to_fix]['sections'][$key] = trim($value, '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$info['fileformat'] = 'svg';
|
||||
$info['video']['dataformat'] = 'svg';
|
||||
$info['video']['lossless'] = true;
|
||||
//$info['video']['bits_per_sample'] = 24;
|
||||
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||
|
||||
if (!empty($info['svg']['svg']['sections']['width'])) {
|
||||
$info['svg']['width'] = intval($info['svg']['svg']['sections']['width']);
|
||||
}
|
||||
if (!empty($info['svg']['svg']['sections']['height'])) {
|
||||
$info['svg']['height'] = intval($info['svg']['svg']['sections']['height']);
|
||||
}
|
||||
if (!empty($info['svg']['svg']['sections']['version'])) {
|
||||
$info['svg']['version'] = $info['svg']['svg']['sections']['version'];
|
||||
}
|
||||
if (!isset($info['svg']['version']) && isset($info['svg']['doctype']['sections'])) {
|
||||
foreach ($info['svg']['doctype']['sections'] as $key => $value) {
|
||||
if (preg_match('#//W3C//DTD SVG ([0-9\.]+)//#i', $key, $matches)) {
|
||||
$info['svg']['version'] = $matches[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($info['svg']['width'])) {
|
||||
$info['video']['resolution_x'] = $info['svg']['width'];
|
||||
}
|
||||
if (!empty($info['svg']['height'])) {
|
||||
$info['video']['resolution_y'] = $info['svg']['height'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
$info['error'][] = 'Did not find expected <svg> tag';
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
224
api/getID3/module.graphic.tiff.php
Executable file
@ -0,0 +1,224 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.archive.tiff.php //
|
||||
// module for analyzing TIFF files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_tiff extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$TIFFheader = fread($this->getid3->fp, 4);
|
||||
|
||||
switch (substr($TIFFheader, 0, 2)) {
|
||||
case 'II':
|
||||
$info['tiff']['byte_order'] = 'Intel';
|
||||
break;
|
||||
case 'MM':
|
||||
$info['tiff']['byte_order'] = 'Motorola';
|
||||
break;
|
||||
default:
|
||||
$info['error'][] = 'Invalid TIFF byte order identifier ('.substr($TIFFheader, 0, 2).') at offset '.$info['avdataoffset'];
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
$info['fileformat'] = 'tiff';
|
||||
$info['video']['dataformat'] = 'tiff';
|
||||
$info['video']['lossless'] = true;
|
||||
$info['tiff']['ifd'] = array();
|
||||
$CurrentIFD = array();
|
||||
|
||||
$FieldTypeByteLength = array(1=>1, 2=>1, 3=>2, 4=>4, 5=>8);
|
||||
|
||||
$nextIFDoffset = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']);
|
||||
|
||||
while ($nextIFDoffset > 0) {
|
||||
|
||||
$CurrentIFD['offset'] = $nextIFDoffset;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'] + $nextIFDoffset, SEEK_SET);
|
||||
$CurrentIFD['fieldcount'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']);
|
||||
|
||||
for ($i = 0; $i < $CurrentIFD['fieldcount']; $i++) {
|
||||
$CurrentIFD['fields'][$i]['raw']['tag'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']);
|
||||
$CurrentIFD['fields'][$i]['raw']['type'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']);
|
||||
$CurrentIFD['fields'][$i]['raw']['length'] = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']);
|
||||
$CurrentIFD['fields'][$i]['raw']['offset'] = fread($this->getid3->fp, 4);
|
||||
|
||||
switch ($CurrentIFD['fields'][$i]['raw']['type']) {
|
||||
case 1: // BYTE An 8-bit unsigned integer.
|
||||
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) {
|
||||
$CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['offset'], 0, 1), $info['tiff']['byte_order']);
|
||||
} else {
|
||||
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // ASCII 8-bit bytes that store ASCII codes; the last byte must be null.
|
||||
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) {
|
||||
$CurrentIFD['fields'][$i]['value'] = substr($CurrentIFD['fields'][$i]['raw']['offset'], 3);
|
||||
} else {
|
||||
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // SHORT A 16-bit (2-byte) unsigned integer.
|
||||
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 2) {
|
||||
$CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['offset'], 0, 2), $info['tiff']['byte_order']);
|
||||
} else {
|
||||
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: // LONG A 32-bit (4-byte) unsigned integer.
|
||||
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 1) {
|
||||
$CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']);
|
||||
} else {
|
||||
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: // RATIONAL Two LONG_s: the first represents the numerator of a fraction, the second the denominator.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$info['tiff']['ifd'][] = $CurrentIFD;
|
||||
$CurrentIFD = array();
|
||||
$nextIFDoffset = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']);
|
||||
|
||||
}
|
||||
|
||||
foreach ($info['tiff']['ifd'] as $IFDid => $IFDarray) {
|
||||
foreach ($IFDarray['fields'] as $key => $fieldarray) {
|
||||
switch ($fieldarray['raw']['tag']) {
|
||||
case 256: // ImageWidth
|
||||
case 257: // ImageLength
|
||||
case 258: // BitsPerSample
|
||||
case 259: // Compression
|
||||
if (!isset($fieldarray['value'])) {
|
||||
fseek($this->getid3->fp, $fieldarray['offset'], SEEK_SET);
|
||||
$info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($this->getid3->fp, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]);
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case 270: // ImageDescription
|
||||
case 271: // Make
|
||||
case 272: // Model
|
||||
case 305: // Software
|
||||
case 306: // DateTime
|
||||
case 315: // Artist
|
||||
case 316: // HostComputer
|
||||
if (isset($fieldarray['value'])) {
|
||||
$info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $fieldarray['value'];
|
||||
} else {
|
||||
fseek($this->getid3->fp, $fieldarray['offset'], SEEK_SET);
|
||||
$info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($this->getid3->fp, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]);
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch ($fieldarray['raw']['tag']) {
|
||||
case 256: // ImageWidth
|
||||
$info['video']['resolution_x'] = $fieldarray['value'];
|
||||
break;
|
||||
|
||||
case 257: // ImageLength
|
||||
$info['video']['resolution_y'] = $fieldarray['value'];
|
||||
break;
|
||||
|
||||
case 258: // BitsPerSample
|
||||
if (isset($fieldarray['value'])) {
|
||||
$info['video']['bits_per_sample'] = $fieldarray['value'];
|
||||
} else {
|
||||
$info['video']['bits_per_sample'] = 0;
|
||||
for ($i = 0; $i < $fieldarray['raw']['length']; $i++) {
|
||||
$info['video']['bits_per_sample'] += $this->TIFFendian2Int(substr($info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'], $i * $FieldTypeByteLength[$fieldarray['raw']['type']], $FieldTypeByteLength[$fieldarray['raw']['type']]), $info['tiff']['byte_order']);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 259: // Compression
|
||||
$info['video']['codec'] = $this->TIFFcompressionMethod($fieldarray['value']);
|
||||
break;
|
||||
|
||||
case 270: // ImageDescription
|
||||
case 271: // Make
|
||||
case 272: // Model
|
||||
case 305: // Software
|
||||
case 306: // DateTime
|
||||
case 315: // Artist
|
||||
case 316: // HostComputer
|
||||
$TIFFcommentName = $this->TIFFcommentName($fieldarray['raw']['tag']);
|
||||
if (isset($info['tiff']['comments'][$TIFFcommentName])) {
|
||||
$info['tiff']['comments'][$TIFFcommentName][] = $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'];
|
||||
} else {
|
||||
$info['tiff']['comments'][$TIFFcommentName] = array($info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data']);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function TIFFendian2Int($bytestring, $byteorder) {
|
||||
if ($byteorder == 'Intel') {
|
||||
return getid3_lib::LittleEndian2Int($bytestring);
|
||||
} elseif ($byteorder == 'Motorola') {
|
||||
return getid3_lib::BigEndian2Int($bytestring);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function TIFFcompressionMethod($id) {
|
||||
static $TIFFcompressionMethod = array();
|
||||
if (empty($TIFFcompressionMethod)) {
|
||||
$TIFFcompressionMethod = array(
|
||||
1 => 'Uncompressed',
|
||||
2 => 'Huffman',
|
||||
3 => 'Fax - CCITT 3',
|
||||
5 => 'LZW',
|
||||
32773 => 'PackBits',
|
||||
);
|
||||
}
|
||||
return (isset($TIFFcompressionMethod[$id]) ? $TIFFcompressionMethod[$id] : 'unknown/invalid ('.$id.')');
|
||||
}
|
||||
|
||||
public function TIFFcommentName($id) {
|
||||
static $TIFFcommentName = array();
|
||||
if (empty($TIFFcommentName)) {
|
||||
$TIFFcommentName = array(
|
||||
270 => 'imagedescription',
|
||||
271 => 'make',
|
||||
272 => 'model',
|
||||
305 => 'software',
|
||||
306 => 'datetime',
|
||||
315 => 'artist',
|
||||
316 => 'hostcomputer',
|
||||
);
|
||||
}
|
||||
return (isset($TIFFcommentName[$id]) ? $TIFFcommentName[$id] : 'unknown/invalid ('.$id.')');
|
||||
}
|
||||
|
||||
}
|
311
api/getID3/module.misc.cue.php
Executable file
@ -0,0 +1,311 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.misc.cue.php //
|
||||
// module for analyzing CUEsheet files //
|
||||
// dependencies: NONE //
|
||||
// //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// Module originally written [2009-Mar-25] by //
|
||||
// Nigel Barnes <ngbarnesØhotmail*com> //
|
||||
// Minor reformatting and similar small changes to integrate //
|
||||
// into getID3 by James Heinrich <info@getid3.org> //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* CueSheet parser by Nigel Barnes.
|
||||
*
|
||||
* This is a PHP conversion of CueSharp 0.5 by Wyatt O'Day (wyday.com/cuesharp)
|
||||
*/
|
||||
|
||||
/**
|
||||
* A CueSheet class used to open and parse cuesheets.
|
||||
*
|
||||
*/
|
||||
class getid3_cue extends getid3_handler
|
||||
{
|
||||
public $cuesheet = array();
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'cue';
|
||||
$this->readCueSheetFilename($info['filenamepath']);
|
||||
$info['cue'] = $this->cuesheet;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function readCueSheetFilename($filename)
|
||||
{
|
||||
$filedata = file_get_contents($filename);
|
||||
return $this->readCueSheet($filedata);
|
||||
}
|
||||
/**
|
||||
* Parses a cue sheet file.
|
||||
*
|
||||
* @param string $filename - The filename for the cue sheet to open.
|
||||
*/
|
||||
public function readCueSheet(&$filedata)
|
||||
{
|
||||
$cue_lines = array();
|
||||
foreach (explode("\n", str_replace("\r", null, $filedata)) as $line)
|
||||
{
|
||||
if ( (strlen($line) > 0) && ($line[0] != '#'))
|
||||
{
|
||||
$cue_lines[] = trim($line);
|
||||
}
|
||||
}
|
||||
$this->parseCueSheet($cue_lines);
|
||||
|
||||
return $this->cuesheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the cue sheet array.
|
||||
*
|
||||
* @param array $file - The cuesheet as an array of each line.
|
||||
*/
|
||||
public function parseCueSheet($file)
|
||||
{
|
||||
//-1 means still global, all others are track specific
|
||||
$track_on = -1;
|
||||
|
||||
for ($i=0; $i < count($file); $i++)
|
||||
{
|
||||
list($key) = explode(' ', strtolower($file[$i]), 2);
|
||||
switch ($key)
|
||||
{
|
||||
case 'catalog':
|
||||
case 'cdtextfile':
|
||||
case 'isrc':
|
||||
case 'performer':
|
||||
case 'songwriter':
|
||||
case 'title':
|
||||
$this->parseString($file[$i], $track_on);
|
||||
break;
|
||||
case 'file':
|
||||
$currentFile = $this->parseFile($file[$i]);
|
||||
break;
|
||||
case 'flags':
|
||||
$this->parseFlags($file[$i], $track_on);
|
||||
break;
|
||||
case 'index':
|
||||
case 'postgap':
|
||||
case 'pregap':
|
||||
$this->parseIndex($file[$i], $track_on);
|
||||
break;
|
||||
case 'rem':
|
||||
$this->parseComment($file[$i], $track_on);
|
||||
break;
|
||||
case 'track':
|
||||
$track_on++;
|
||||
$this->parseTrack($file[$i], $track_on);
|
||||
if (isset($currentFile)) // if there's a file
|
||||
{
|
||||
$this->cuesheet['tracks'][$track_on]['datafile'] = $currentFile;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//save discarded junk and place string[] with track it was found in
|
||||
$this->parseGarbage($file[$i], $track_on);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the REM command.
|
||||
*
|
||||
* @param string $line - The line in the cue file that contains the TRACK command.
|
||||
* @param integer $track_on - The track currently processing.
|
||||
*/
|
||||
public function parseComment($line, $track_on)
|
||||
{
|
||||
$explodedline = explode(' ', $line, 3);
|
||||
$comment_REM = (isset($explodedline[0]) ? $explodedline[0] : '');
|
||||
$comment_type = (isset($explodedline[1]) ? $explodedline[1] : '');
|
||||
$comment_data = (isset($explodedline[2]) ? $explodedline[2] : '');
|
||||
if (($comment_REM == 'REM') && $comment_type) {
|
||||
$comment_type = strtolower($comment_type);
|
||||
$commment_data = trim($comment_data, ' "');
|
||||
if ($track_on != -1) {
|
||||
$this->cuesheet['tracks'][$track_on]['comments'][$comment_type][] = $comment_data;
|
||||
} else {
|
||||
$this->cuesheet['comments'][$comment_type][] = $comment_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the FILE command.
|
||||
*
|
||||
* @param string $line - The line in the cue file that contains the FILE command.
|
||||
* @return array - Array of FILENAME and TYPE of file..
|
||||
*/
|
||||
public function parseFile($line)
|
||||
{
|
||||
$line = substr($line, strpos($line, ' ') + 1);
|
||||
$type = strtolower(substr($line, strrpos($line, ' ')));
|
||||
|
||||
//remove type
|
||||
$line = substr($line, 0, strrpos($line, ' ') - 1);
|
||||
|
||||
//if quotes around it, remove them.
|
||||
$line = trim($line, '"');
|
||||
|
||||
return array('filename'=>$line, 'type'=>$type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the FLAG command.
|
||||
*
|
||||
* @param string $line - The line in the cue file that contains the TRACK command.
|
||||
* @param integer $track_on - The track currently processing.
|
||||
*/
|
||||
public function parseFlags($line, $track_on)
|
||||
{
|
||||
if ($track_on != -1)
|
||||
{
|
||||
foreach (explode(' ', strtolower($line)) as $type)
|
||||
{
|
||||
switch ($type)
|
||||
{
|
||||
case 'flags':
|
||||
// first entry in this line
|
||||
$this->cuesheet['tracks'][$track_on]['flags'] = array(
|
||||
'4ch' => false,
|
||||
'data' => false,
|
||||
'dcp' => false,
|
||||
'pre' => false,
|
||||
'scms' => false,
|
||||
);
|
||||
break;
|
||||
case 'data':
|
||||
case 'dcp':
|
||||
case '4ch':
|
||||
case 'pre':
|
||||
case 'scms':
|
||||
$this->cuesheet['tracks'][$track_on]['flags'][$type] = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect any unidentified data.
|
||||
*
|
||||
* @param string $line - The line in the cue file that contains the TRACK command.
|
||||
* @param integer $track_on - The track currently processing.
|
||||
*/
|
||||
public function parseGarbage($line, $track_on)
|
||||
{
|
||||
if ( strlen($line) > 0 )
|
||||
{
|
||||
if ($track_on == -1)
|
||||
{
|
||||
$this->cuesheet['garbage'][] = $line;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->cuesheet['tracks'][$track_on]['garbage'][] = $line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the INDEX command of a TRACK.
|
||||
*
|
||||
* @param string $line - The line in the cue file that contains the TRACK command.
|
||||
* @param integer $track_on - The track currently processing.
|
||||
*/
|
||||
public function parseIndex($line, $track_on)
|
||||
{
|
||||
$type = strtolower(substr($line, 0, strpos($line, ' ')));
|
||||
$line = substr($line, strpos($line, ' ') + 1);
|
||||
|
||||
if ($type == 'index')
|
||||
{
|
||||
//read the index number
|
||||
$number = intval(substr($line, 0, strpos($line, ' ')));
|
||||
$line = substr($line, strpos($line, ' ') + 1);
|
||||
}
|
||||
|
||||
//extract the minutes, seconds, and frames
|
||||
$explodedline = explode(':', $line);
|
||||
$minutes = (isset($explodedline[0]) ? $explodedline[0] : '');
|
||||
$seconds = (isset($explodedline[1]) ? $explodedline[1] : '');
|
||||
$frames = (isset($explodedline[2]) ? $explodedline[2] : '');
|
||||
|
||||
switch ($type) {
|
||||
case 'index':
|
||||
$this->cuesheet['tracks'][$track_on][$type][$number] = array('minutes'=>intval($minutes), 'seconds'=>intval($seconds), 'frames'=>intval($frames));
|
||||
break;
|
||||
case 'pregap':
|
||||
case 'postgap':
|
||||
$this->cuesheet['tracks'][$track_on][$type] = array('minutes'=>intval($minutes), 'seconds'=>intval($seconds), 'frames'=>intval($frames));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function parseString($line, $track_on)
|
||||
{
|
||||
$category = strtolower(substr($line, 0, strpos($line, ' ')));
|
||||
$line = substr($line, strpos($line, ' ') + 1);
|
||||
|
||||
//get rid of the quotes
|
||||
$line = trim($line, '"');
|
||||
|
||||
switch ($category)
|
||||
{
|
||||
case 'catalog':
|
||||
case 'cdtextfile':
|
||||
case 'isrc':
|
||||
case 'performer':
|
||||
case 'songwriter':
|
||||
case 'title':
|
||||
if ($track_on == -1)
|
||||
{
|
||||
$this->cuesheet[$category] = $line;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->cuesheet['tracks'][$track_on][$category] = $line;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the TRACK command.
|
||||
*
|
||||
* @param string $line - The line in the cue file that contains the TRACK command.
|
||||
* @param integer $track_on - The track currently processing.
|
||||
*/
|
||||
public function parseTrack($line, $track_on)
|
||||
{
|
||||
$line = substr($line, strpos($line, ' ') + 1);
|
||||
$track = ltrim(substr($line, 0, strpos($line, ' ')), '0');
|
||||
|
||||
//find the data type.
|
||||
$datatype = strtolower(substr($line, strpos($line, ' ') + 1));
|
||||
|
||||
$this->cuesheet['tracks'][$track_on] = array('track_number'=>$track, 'datatype'=>$datatype);
|
||||
}
|
||||
|
||||
}
|
||||
|
58
api/getID3/module.misc.exe.php
Executable file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.misc.exe.php //
|
||||
// module for analyzing EXE files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_exe extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$EXEheader = fread($this->getid3->fp, 28);
|
||||
|
||||
$magic = 'MZ';
|
||||
if (substr($EXEheader, 0, 2) != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($EXEheader, 0, 2)).'"';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['fileformat'] = 'exe';
|
||||
$info['exe']['mz']['magic'] = 'MZ';
|
||||
|
||||
$info['exe']['mz']['raw']['last_page_size'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 2, 2));
|
||||
$info['exe']['mz']['raw']['page_count'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 4, 2));
|
||||
$info['exe']['mz']['raw']['relocation_count'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 6, 2));
|
||||
$info['exe']['mz']['raw']['header_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 8, 2));
|
||||
$info['exe']['mz']['raw']['min_memory_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 10, 2));
|
||||
$info['exe']['mz']['raw']['max_memory_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 12, 2));
|
||||
$info['exe']['mz']['raw']['initial_ss'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 14, 2));
|
||||
$info['exe']['mz']['raw']['initial_sp'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 16, 2));
|
||||
$info['exe']['mz']['raw']['checksum'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 18, 2));
|
||||
$info['exe']['mz']['raw']['cs_ip'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 20, 4));
|
||||
$info['exe']['mz']['raw']['relocation_table_offset'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 24, 2));
|
||||
$info['exe']['mz']['raw']['overlay_number'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 26, 2));
|
||||
|
||||
$info['exe']['mz']['byte_size'] = (($info['exe']['mz']['raw']['page_count'] - 1)) * 512 + $info['exe']['mz']['raw']['last_page_size'];
|
||||
$info['exe']['mz']['header_size'] = $info['exe']['mz']['raw']['header_paragraphs'] * 16;
|
||||
$info['exe']['mz']['memory_minimum'] = $info['exe']['mz']['raw']['min_memory_paragraphs'] * 16;
|
||||
$info['exe']['mz']['memory_recommended'] = $info['exe']['mz']['raw']['max_memory_paragraphs'] * 16;
|
||||
|
||||
$info['error'][] = 'EXE parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
387
api/getID3/module.misc.iso.php
Executable file
@ -0,0 +1,387 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.misc.iso.php //
|
||||
// module for analyzing ISO files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_iso extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'iso';
|
||||
|
||||
for ($i = 16; $i <= 19; $i++) {
|
||||
fseek($this->getid3->fp, 2048 * $i, SEEK_SET);
|
||||
$ISOheader = fread($this->getid3->fp, 2048);
|
||||
if (substr($ISOheader, 1, 5) == 'CD001') {
|
||||
switch (ord($ISOheader{0})) {
|
||||
case 1:
|
||||
$info['iso']['primary_volume_descriptor']['offset'] = 2048 * $i;
|
||||
$this->ParsePrimaryVolumeDescriptor($ISOheader);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$info['iso']['supplementary_volume_descriptor']['offset'] = 2048 * $i;
|
||||
$this->ParseSupplementaryVolumeDescriptor($ISOheader);
|
||||
break;
|
||||
|
||||
default:
|
||||
// skip
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->ParsePathTable();
|
||||
|
||||
$info['iso']['files'] = array();
|
||||
foreach ($info['iso']['path_table']['directories'] as $directorynum => $directorydata) {
|
||||
$info['iso']['directories'][$directorynum] = $this->ParseDirectoryRecord($directorydata);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function ParsePrimaryVolumeDescriptor(&$ISOheader) {
|
||||
// ISO integer values are stored *BOTH* Little-Endian AND Big-Endian format!!
|
||||
// ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field
|
||||
|
||||
// shortcuts
|
||||
$info = &$this->getid3->info;
|
||||
$info['iso']['primary_volume_descriptor']['raw'] = array();
|
||||
$thisfile_iso_primaryVD = &$info['iso']['primary_volume_descriptor'];
|
||||
$thisfile_iso_primaryVD_raw = &$thisfile_iso_primaryVD['raw'];
|
||||
|
||||
$thisfile_iso_primaryVD_raw['volume_descriptor_type'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 0, 1));
|
||||
$thisfile_iso_primaryVD_raw['standard_identifier'] = substr($ISOheader, 1, 5);
|
||||
if ($thisfile_iso_primaryVD_raw['standard_identifier'] != 'CD001') {
|
||||
$info['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_primaryVD['offset'] + 1).'), found "'.$thisfile_iso_primaryVD_raw['standard_identifier'].'" instead';
|
||||
unset($info['fileformat']);
|
||||
unset($info['iso']);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$thisfile_iso_primaryVD_raw['volume_descriptor_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 6, 1));
|
||||
//$thisfile_iso_primaryVD_raw['unused_1'] = substr($ISOheader, 7, 1);
|
||||
$thisfile_iso_primaryVD_raw['system_identifier'] = substr($ISOheader, 8, 32);
|
||||
$thisfile_iso_primaryVD_raw['volume_identifier'] = substr($ISOheader, 40, 32);
|
||||
//$thisfile_iso_primaryVD_raw['unused_2'] = substr($ISOheader, 72, 8);
|
||||
$thisfile_iso_primaryVD_raw['volume_space_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 80, 4));
|
||||
//$thisfile_iso_primaryVD_raw['unused_3'] = substr($ISOheader, 88, 32);
|
||||
$thisfile_iso_primaryVD_raw['volume_set_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 120, 2));
|
||||
$thisfile_iso_primaryVD_raw['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 124, 2));
|
||||
$thisfile_iso_primaryVD_raw['logical_block_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 128, 2));
|
||||
$thisfile_iso_primaryVD_raw['path_table_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 132, 4));
|
||||
$thisfile_iso_primaryVD_raw['path_table_l_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 140, 2));
|
||||
$thisfile_iso_primaryVD_raw['path_table_l_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 144, 2));
|
||||
$thisfile_iso_primaryVD_raw['path_table_m_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 148, 2));
|
||||
$thisfile_iso_primaryVD_raw['path_table_m_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 152, 2));
|
||||
$thisfile_iso_primaryVD_raw['root_directory_record'] = substr($ISOheader, 156, 34);
|
||||
$thisfile_iso_primaryVD_raw['volume_set_identifier'] = substr($ISOheader, 190, 128);
|
||||
$thisfile_iso_primaryVD_raw['publisher_identifier'] = substr($ISOheader, 318, 128);
|
||||
$thisfile_iso_primaryVD_raw['data_preparer_identifier'] = substr($ISOheader, 446, 128);
|
||||
$thisfile_iso_primaryVD_raw['application_identifier'] = substr($ISOheader, 574, 128);
|
||||
$thisfile_iso_primaryVD_raw['copyright_file_identifier'] = substr($ISOheader, 702, 37);
|
||||
$thisfile_iso_primaryVD_raw['abstract_file_identifier'] = substr($ISOheader, 739, 37);
|
||||
$thisfile_iso_primaryVD_raw['bibliographic_file_identifier'] = substr($ISOheader, 776, 37);
|
||||
$thisfile_iso_primaryVD_raw['volume_creation_date_time'] = substr($ISOheader, 813, 17);
|
||||
$thisfile_iso_primaryVD_raw['volume_modification_date_time'] = substr($ISOheader, 830, 17);
|
||||
$thisfile_iso_primaryVD_raw['volume_expiration_date_time'] = substr($ISOheader, 847, 17);
|
||||
$thisfile_iso_primaryVD_raw['volume_effective_date_time'] = substr($ISOheader, 864, 17);
|
||||
$thisfile_iso_primaryVD_raw['file_structure_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 881, 1));
|
||||
//$thisfile_iso_primaryVD_raw['unused_4'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 882, 1));
|
||||
$thisfile_iso_primaryVD_raw['application_data'] = substr($ISOheader, 883, 512);
|
||||
//$thisfile_iso_primaryVD_raw['unused_5'] = substr($ISOheader, 1395, 653);
|
||||
|
||||
$thisfile_iso_primaryVD['system_identifier'] = trim($thisfile_iso_primaryVD_raw['system_identifier']);
|
||||
$thisfile_iso_primaryVD['volume_identifier'] = trim($thisfile_iso_primaryVD_raw['volume_identifier']);
|
||||
$thisfile_iso_primaryVD['volume_set_identifier'] = trim($thisfile_iso_primaryVD_raw['volume_set_identifier']);
|
||||
$thisfile_iso_primaryVD['publisher_identifier'] = trim($thisfile_iso_primaryVD_raw['publisher_identifier']);
|
||||
$thisfile_iso_primaryVD['data_preparer_identifier'] = trim($thisfile_iso_primaryVD_raw['data_preparer_identifier']);
|
||||
$thisfile_iso_primaryVD['application_identifier'] = trim($thisfile_iso_primaryVD_raw['application_identifier']);
|
||||
$thisfile_iso_primaryVD['copyright_file_identifier'] = trim($thisfile_iso_primaryVD_raw['copyright_file_identifier']);
|
||||
$thisfile_iso_primaryVD['abstract_file_identifier'] = trim($thisfile_iso_primaryVD_raw['abstract_file_identifier']);
|
||||
$thisfile_iso_primaryVD['bibliographic_file_identifier'] = trim($thisfile_iso_primaryVD_raw['bibliographic_file_identifier']);
|
||||
$thisfile_iso_primaryVD['volume_creation_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_creation_date_time']);
|
||||
$thisfile_iso_primaryVD['volume_modification_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_modification_date_time']);
|
||||
$thisfile_iso_primaryVD['volume_expiration_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_expiration_date_time']);
|
||||
$thisfile_iso_primaryVD['volume_effective_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_effective_date_time']);
|
||||
|
||||
if (($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048) > $info['filesize']) {
|
||||
$info['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;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
// shortcuts
|
||||
$info = &$this->getid3->info;
|
||||
$info['iso']['supplementary_volume_descriptor']['raw'] = array();
|
||||
$thisfile_iso_supplementaryVD = &$info['iso']['supplementary_volume_descriptor'];
|
||||
$thisfile_iso_supplementaryVD_raw = &$thisfile_iso_supplementaryVD['raw'];
|
||||
|
||||
$thisfile_iso_supplementaryVD_raw['volume_descriptor_type'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 0, 1));
|
||||
$thisfile_iso_supplementaryVD_raw['standard_identifier'] = substr($ISOheader, 1, 5);
|
||||
if ($thisfile_iso_supplementaryVD_raw['standard_identifier'] != 'CD001') {
|
||||
$info['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_supplementaryVD['offset'] + 1).'), found "'.$thisfile_iso_supplementaryVD_raw['standard_identifier'].'" instead';
|
||||
unset($info['fileformat']);
|
||||
unset($info['iso']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$thisfile_iso_supplementaryVD_raw['volume_descriptor_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 6, 1));
|
||||
//$thisfile_iso_supplementaryVD_raw['unused_1'] = substr($ISOheader, 7, 1);
|
||||
$thisfile_iso_supplementaryVD_raw['system_identifier'] = substr($ISOheader, 8, 32);
|
||||
$thisfile_iso_supplementaryVD_raw['volume_identifier'] = substr($ISOheader, 40, 32);
|
||||
//$thisfile_iso_supplementaryVD_raw['unused_2'] = substr($ISOheader, 72, 8);
|
||||
$thisfile_iso_supplementaryVD_raw['volume_space_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 80, 4));
|
||||
if ($thisfile_iso_supplementaryVD_raw['volume_space_size'] == 0) {
|
||||
// Supplementary Volume Descriptor not used
|
||||
//unset($thisfile_iso_supplementaryVD);
|
||||
//return false;
|
||||
}
|
||||
|
||||
//$thisfile_iso_supplementaryVD_raw['unused_3'] = substr($ISOheader, 88, 32);
|
||||
$thisfile_iso_supplementaryVD_raw['volume_set_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 120, 2));
|
||||
$thisfile_iso_supplementaryVD_raw['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 124, 2));
|
||||
$thisfile_iso_supplementaryVD_raw['logical_block_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 128, 2));
|
||||
$thisfile_iso_supplementaryVD_raw['path_table_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 132, 4));
|
||||
$thisfile_iso_supplementaryVD_raw['path_table_l_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 140, 2));
|
||||
$thisfile_iso_supplementaryVD_raw['path_table_l_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 144, 2));
|
||||
$thisfile_iso_supplementaryVD_raw['path_table_m_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 148, 2));
|
||||
$thisfile_iso_supplementaryVD_raw['path_table_m_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 152, 2));
|
||||
$thisfile_iso_supplementaryVD_raw['root_directory_record'] = substr($ISOheader, 156, 34);
|
||||
$thisfile_iso_supplementaryVD_raw['volume_set_identifier'] = substr($ISOheader, 190, 128);
|
||||
$thisfile_iso_supplementaryVD_raw['publisher_identifier'] = substr($ISOheader, 318, 128);
|
||||
$thisfile_iso_supplementaryVD_raw['data_preparer_identifier'] = substr($ISOheader, 446, 128);
|
||||
$thisfile_iso_supplementaryVD_raw['application_identifier'] = substr($ISOheader, 574, 128);
|
||||
$thisfile_iso_supplementaryVD_raw['copyright_file_identifier'] = substr($ISOheader, 702, 37);
|
||||
$thisfile_iso_supplementaryVD_raw['abstract_file_identifier'] = substr($ISOheader, 739, 37);
|
||||
$thisfile_iso_supplementaryVD_raw['bibliographic_file_identifier'] = substr($ISOheader, 776, 37);
|
||||
$thisfile_iso_supplementaryVD_raw['volume_creation_date_time'] = substr($ISOheader, 813, 17);
|
||||
$thisfile_iso_supplementaryVD_raw['volume_modification_date_time'] = substr($ISOheader, 830, 17);
|
||||
$thisfile_iso_supplementaryVD_raw['volume_expiration_date_time'] = substr($ISOheader, 847, 17);
|
||||
$thisfile_iso_supplementaryVD_raw['volume_effective_date_time'] = substr($ISOheader, 864, 17);
|
||||
$thisfile_iso_supplementaryVD_raw['file_structure_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 881, 1));
|
||||
//$thisfile_iso_supplementaryVD_raw['unused_4'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 882, 1));
|
||||
$thisfile_iso_supplementaryVD_raw['application_data'] = substr($ISOheader, 883, 512);
|
||||
//$thisfile_iso_supplementaryVD_raw['unused_5'] = substr($ISOheader, 1395, 653);
|
||||
|
||||
$thisfile_iso_supplementaryVD['system_identifier'] = trim($thisfile_iso_supplementaryVD_raw['system_identifier']);
|
||||
$thisfile_iso_supplementaryVD['volume_identifier'] = trim($thisfile_iso_supplementaryVD_raw['volume_identifier']);
|
||||
$thisfile_iso_supplementaryVD['volume_set_identifier'] = trim($thisfile_iso_supplementaryVD_raw['volume_set_identifier']);
|
||||
$thisfile_iso_supplementaryVD['publisher_identifier'] = trim($thisfile_iso_supplementaryVD_raw['publisher_identifier']);
|
||||
$thisfile_iso_supplementaryVD['data_preparer_identifier'] = trim($thisfile_iso_supplementaryVD_raw['data_preparer_identifier']);
|
||||
$thisfile_iso_supplementaryVD['application_identifier'] = trim($thisfile_iso_supplementaryVD_raw['application_identifier']);
|
||||
$thisfile_iso_supplementaryVD['copyright_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['copyright_file_identifier']);
|
||||
$thisfile_iso_supplementaryVD['abstract_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['abstract_file_identifier']);
|
||||
$thisfile_iso_supplementaryVD['bibliographic_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['bibliographic_file_identifier']);
|
||||
$thisfile_iso_supplementaryVD['volume_creation_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_creation_date_time']);
|
||||
$thisfile_iso_supplementaryVD['volume_modification_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_modification_date_time']);
|
||||
$thisfile_iso_supplementaryVD['volume_expiration_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_expiration_date_time']);
|
||||
$thisfile_iso_supplementaryVD['volume_effective_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_effective_date_time']);
|
||||
|
||||
if (($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']) > $info['filesize']) {
|
||||
$info['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;
|
||||
}
|
||||
|
||||
|
||||
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'])) {
|
||||
$PathTableLocation = $info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location'];
|
||||
$PathTableSize = $info['iso']['supplementary_volume_descriptor']['raw']['path_table_size'];
|
||||
$TextEncoding = 'UTF-16BE'; // Big-Endian Unicode
|
||||
} else {
|
||||
$PathTableLocation = $info['iso']['primary_volume_descriptor']['raw']['path_table_l_location'];
|
||||
$PathTableSize = $info['iso']['primary_volume_descriptor']['raw']['path_table_size'];
|
||||
$TextEncoding = 'ISO-8859-1'; // Latin-1
|
||||
}
|
||||
|
||||
if (($PathTableLocation * 2048) > $info['filesize']) {
|
||||
$info['error'][] = 'Path Table Location specifies an offset ('.($PathTableLocation * 2048).') beyond the end-of-file ('.$info['filesize'].')';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['iso']['path_table']['offset'] = $PathTableLocation * 2048;
|
||||
fseek($this->getid3->fp, $info['iso']['path_table']['offset'], SEEK_SET);
|
||||
$info['iso']['path_table']['raw'] = fread($this->getid3->fp, $PathTableSize);
|
||||
|
||||
$offset = 0;
|
||||
$pathcounter = 1;
|
||||
while ($offset < $PathTableSize) {
|
||||
// shortcut
|
||||
$info['iso']['path_table']['directories'][$pathcounter] = array();
|
||||
$thisfile_iso_pathtable_directories_current = &$info['iso']['path_table']['directories'][$pathcounter];
|
||||
|
||||
$thisfile_iso_pathtable_directories_current['length'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 1));
|
||||
$offset += 1;
|
||||
$thisfile_iso_pathtable_directories_current['extended_length'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 1));
|
||||
$offset += 1;
|
||||
$thisfile_iso_pathtable_directories_current['location_logical'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 4));
|
||||
$offset += 4;
|
||||
$thisfile_iso_pathtable_directories_current['parent_directory'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 2));
|
||||
$offset += 2;
|
||||
$thisfile_iso_pathtable_directories_current['name'] = substr($info['iso']['path_table']['raw'], $offset, $thisfile_iso_pathtable_directories_current['length']);
|
||||
$offset += $thisfile_iso_pathtable_directories_current['length'] + ($thisfile_iso_pathtable_directories_current['length'] % 2);
|
||||
|
||||
$thisfile_iso_pathtable_directories_current['name_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $info['encoding'], $thisfile_iso_pathtable_directories_current['name']);
|
||||
|
||||
$thisfile_iso_pathtable_directories_current['location_bytes'] = $thisfile_iso_pathtable_directories_current['location_logical'] * 2048;
|
||||
if ($pathcounter == 1) {
|
||||
$thisfile_iso_pathtable_directories_current['full_path'] = '/';
|
||||
} else {
|
||||
$thisfile_iso_pathtable_directories_current['full_path'] = $info['iso']['path_table']['directories'][$thisfile_iso_pathtable_directories_current['parent_directory']]['full_path'].$thisfile_iso_pathtable_directories_current['name_ascii'].'/';
|
||||
}
|
||||
$FullPathArray[] = $thisfile_iso_pathtable_directories_current['full_path'];
|
||||
|
||||
$pathcounter++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function ParseDirectoryRecord($directorydata) {
|
||||
$info = &$this->getid3->info;
|
||||
if (isset($info['iso']['supplementary_volume_descriptor'])) {
|
||||
$TextEncoding = 'UTF-16BE'; // Big-Endian Unicode
|
||||
} else {
|
||||
$TextEncoding = 'ISO-8859-1'; // Latin-1
|
||||
}
|
||||
|
||||
fseek($this->getid3->fp, $directorydata['location_bytes'], SEEK_SET);
|
||||
$DirectoryRecordData = fread($this->getid3->fp, 1);
|
||||
|
||||
while (ord($DirectoryRecordData{0}) > 33) {
|
||||
|
||||
$DirectoryRecordData .= fread($this->getid3->fp, ord($DirectoryRecordData{0}) - 1);
|
||||
|
||||
$ThisDirectoryRecord['raw']['length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 0, 1));
|
||||
$ThisDirectoryRecord['raw']['extended_attribute_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 1, 1));
|
||||
$ThisDirectoryRecord['raw']['offset_logical'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 2, 4));
|
||||
$ThisDirectoryRecord['raw']['filesize'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 10, 4));
|
||||
$ThisDirectoryRecord['raw']['recording_date_time'] = substr($DirectoryRecordData, 18, 7);
|
||||
$ThisDirectoryRecord['raw']['file_flags'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 25, 1));
|
||||
$ThisDirectoryRecord['raw']['file_unit_size'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 26, 1));
|
||||
$ThisDirectoryRecord['raw']['interleave_gap_size'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 27, 1));
|
||||
$ThisDirectoryRecord['raw']['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 28, 2));
|
||||
$ThisDirectoryRecord['raw']['file_identifier_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 32, 1));
|
||||
$ThisDirectoryRecord['raw']['file_identifier'] = substr($DirectoryRecordData, 33, $ThisDirectoryRecord['raw']['file_identifier_length']);
|
||||
|
||||
$ThisDirectoryRecord['file_identifier_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $info['encoding'], $ThisDirectoryRecord['raw']['file_identifier']);
|
||||
|
||||
$ThisDirectoryRecord['filesize'] = $ThisDirectoryRecord['raw']['filesize'];
|
||||
$ThisDirectoryRecord['offset_bytes'] = $ThisDirectoryRecord['raw']['offset_logical'] * 2048;
|
||||
$ThisDirectoryRecord['file_flags']['hidden'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x01);
|
||||
$ThisDirectoryRecord['file_flags']['directory'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x02);
|
||||
$ThisDirectoryRecord['file_flags']['associated'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x04);
|
||||
$ThisDirectoryRecord['file_flags']['extended'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x08);
|
||||
$ThisDirectoryRecord['file_flags']['permissions'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x10);
|
||||
$ThisDirectoryRecord['file_flags']['multiple'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x80);
|
||||
$ThisDirectoryRecord['recording_timestamp'] = $this->ISOtime2UNIXtime($ThisDirectoryRecord['raw']['recording_date_time']);
|
||||
|
||||
if ($ThisDirectoryRecord['file_flags']['directory']) {
|
||||
$ThisDirectoryRecord['filename'] = $directorydata['full_path'];
|
||||
} else {
|
||||
$ThisDirectoryRecord['filename'] = $directorydata['full_path'].$this->ISOstripFilenameVersion($ThisDirectoryRecord['file_identifier_ascii']);
|
||||
$info['iso']['files'] = getid3_lib::array_merge_clobber($info['iso']['files'], getid3_lib::CreateDeepArray($ThisDirectoryRecord['filename'], '/', $ThisDirectoryRecord['filesize']));
|
||||
}
|
||||
|
||||
$DirectoryRecord[] = $ThisDirectoryRecord;
|
||||
$DirectoryRecordData = fread($this->getid3->fp, 1);
|
||||
}
|
||||
|
||||
return $DirectoryRecord;
|
||||
}
|
||||
|
||||
public function ISOstripFilenameVersion($ISOfilename) {
|
||||
// convert 'filename.ext;1' to 'filename.ext'
|
||||
if (!strstr($ISOfilename, ';')) {
|
||||
return $ISOfilename;
|
||||
} else {
|
||||
return substr($ISOfilename, 0, strpos($ISOfilename, ';'));
|
||||
}
|
||||
}
|
||||
|
||||
public function ISOtimeText2UNIXtime($ISOtime) {
|
||||
|
||||
$UNIXyear = (int) substr($ISOtime, 0, 4);
|
||||
$UNIXmonth = (int) substr($ISOtime, 4, 2);
|
||||
$UNIXday = (int) substr($ISOtime, 6, 2);
|
||||
$UNIXhour = (int) substr($ISOtime, 8, 2);
|
||||
$UNIXminute = (int) substr($ISOtime, 10, 2);
|
||||
$UNIXsecond = (int) substr($ISOtime, 12, 2);
|
||||
|
||||
if (!$UNIXyear) {
|
||||
return false;
|
||||
}
|
||||
return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
|
||||
}
|
||||
|
||||
public function ISOtime2UNIXtime($ISOtime) {
|
||||
// Represented by seven bytes:
|
||||
// 1: Number of years since 1900
|
||||
// 2: Month of the year from 1 to 12
|
||||
// 3: Day of the Month from 1 to 31
|
||||
// 4: Hour of the day from 0 to 23
|
||||
// 5: Minute of the hour from 0 to 59
|
||||
// 6: second of the minute from 0 to 59
|
||||
// 7: Offset from Greenwich Mean Time in number of 15 minute intervals from -48 (West) to +52 (East)
|
||||
|
||||
$UNIXyear = ord($ISOtime{0}) + 1900;
|
||||
$UNIXmonth = ord($ISOtime{1});
|
||||
$UNIXday = ord($ISOtime{2});
|
||||
$UNIXhour = ord($ISOtime{3});
|
||||
$UNIXminute = ord($ISOtime{4});
|
||||
$UNIXsecond = ord($ISOtime{5});
|
||||
$GMToffset = $this->TwosCompliment2Decimal(ord($ISOtime{5}));
|
||||
|
||||
return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
|
||||
}
|
||||
|
||||
public function TwosCompliment2Decimal($BinaryValue) {
|
||||
// http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html
|
||||
// First check if the number is negative or positive by looking at the sign bit.
|
||||
// If it is positive, simply convert it to decimal.
|
||||
// If it is negative, make it positive by inverting the bits and adding one.
|
||||
// Then, convert the result to decimal.
|
||||
// The negative of this number is the value of the original binary.
|
||||
|
||||
if ($BinaryValue & 0x80) {
|
||||
|
||||
// negative number
|
||||
return (0 - ((~$BinaryValue & 0xFF) + 1));
|
||||
} else {
|
||||
// positive number
|
||||
return $BinaryValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
37
api/getID3/module.misc.msoffice.php
Executable file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.archive.doc.php //
|
||||
// module for analyzing MS Office (.doc, .xls, etc) files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_msoffice extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||
$DOCFILEheader = fread($this->getid3->fp, 8);
|
||||
$magic = "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1";
|
||||
if (substr($DOCFILEheader, 0, 8) != $magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at '.$info['avdataoffset'].', found '.getid3_lib::PrintHexBytes(substr($DOCFILEheader, 0, 8)).' instead.';
|
||||
return false;
|
||||
}
|
||||
$info['fileformat'] = 'msoffice';
|
||||
|
||||
$info['error'][] = 'MS Office (.doc, .xls, etc) parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
30
api/getID3/module.misc.par2.php
Executable file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.misc.par2.php //
|
||||
// module for analyzing PAR2 files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_par2 extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'par2';
|
||||
|
||||
$info['error'][] = 'PAR2 parsing not enabled in this version of getID3()';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
30
api/getID3/module.misc.pdf.php
Executable file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.misc.pdf.php //
|
||||
// module for analyzing PDF files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_pdf extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'pdf';
|
||||
|
||||
$info['error'][] = 'PDF parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
370
api/getID3/module.tag.apetag.php
Executable file
@ -0,0 +1,370 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.tag.apetag.php //
|
||||
// module for analyzing APE tags //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
class getid3_apetag extends getid3_handler
|
||||
{
|
||||
public $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
|
||||
public $overrideendoffset = 0;
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||
$info['warning'][] = 'Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||
return false;
|
||||
}
|
||||
|
||||
$id3v1tagsize = 128;
|
||||
$apetagheadersize = 32;
|
||||
$lyrics3tagsize = 10;
|
||||
|
||||
if ($this->overrideendoffset == 0) {
|
||||
|
||||
fseek($this->getid3->fp, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
|
||||
$APEfooterID3v1 = fread($this->getid3->fp, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
|
||||
|
||||
//if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
|
||||
if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
|
||||
|
||||
// APE tag found before ID3v1
|
||||
$info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
|
||||
|
||||
//} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
|
||||
} elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
|
||||
|
||||
// APE tag found, no ID3v1
|
||||
$info['ape']['tag_offset_end'] = $info['filesize'];
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
fseek($this->getid3->fp, $this->overrideendoffset - $apetagheadersize, SEEK_SET);
|
||||
if (fread($this->getid3->fp, 8) == 'APETAGEX') {
|
||||
$info['ape']['tag_offset_end'] = $this->overrideendoffset;
|
||||
}
|
||||
|
||||
}
|
||||
if (!isset($info['ape']['tag_offset_end'])) {
|
||||
|
||||
// APE tag not found
|
||||
unset($info['ape']);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// shortcut
|
||||
$thisfile_ape = &$info['ape'];
|
||||
|
||||
fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET);
|
||||
$APEfooterData = fread($this->getid3->fp, 32);
|
||||
if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
|
||||
$info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end'];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
|
||||
fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET);
|
||||
$thisfile_ape['tag_offset_start'] = ftell($this->getid3->fp);
|
||||
$APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
|
||||
} else {
|
||||
$thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
|
||||
fseek($this->getid3->fp, $thisfile_ape['tag_offset_start'], SEEK_SET);
|
||||
$APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize']);
|
||||
}
|
||||
$info['avdataend'] = $thisfile_ape['tag_offset_start'];
|
||||
|
||||
if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
|
||||
$info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
|
||||
unset($info['id3v1']);
|
||||
foreach ($info['warning'] as $key => $value) {
|
||||
if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
|
||||
unset($info['warning'][$key]);
|
||||
sort($info['warning']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$offset = 0;
|
||||
if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
|
||||
if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
|
||||
$offset += $apetagheadersize;
|
||||
} else {
|
||||
$info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start'];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// shortcut
|
||||
$info['replay_gain'] = array();
|
||||
$thisfile_replaygain = &$info['replay_gain'];
|
||||
|
||||
for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
|
||||
$value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
|
||||
$offset += 4;
|
||||
$item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
|
||||
$offset += 4;
|
||||
if (strstr(substr($APEtagData, $offset), "\x00") === false) {
|
||||
$info['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset);
|
||||
return false;
|
||||
}
|
||||
$ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
|
||||
$item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
|
||||
|
||||
// shortcut
|
||||
$thisfile_ape['items'][$item_key] = array();
|
||||
$thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
|
||||
|
||||
$thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
|
||||
|
||||
$offset += ($ItemKeyLength + 1); // skip 0x00 terminator
|
||||
$thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
|
||||
$offset += $value_size;
|
||||
|
||||
$thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
|
||||
switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
|
||||
case 0: // UTF-8
|
||||
case 3: // Locator (URL, filename, etc), UTF-8 encoded
|
||||
$thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data']));
|
||||
break;
|
||||
|
||||
default: // binary data
|
||||
break;
|
||||
}
|
||||
|
||||
switch (strtolower($item_key)) {
|
||||
case 'replaygain_track_gain':
|
||||
$thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||
$thisfile_replaygain['track']['originator'] = 'unspecified';
|
||||
break;
|
||||
|
||||
case 'replaygain_track_peak':
|
||||
$thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||
$thisfile_replaygain['track']['originator'] = 'unspecified';
|
||||
if ($thisfile_replaygain['track']['peak'] <= 0) {
|
||||
$info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'replaygain_album_gain':
|
||||
$thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||
$thisfile_replaygain['album']['originator'] = 'unspecified';
|
||||
break;
|
||||
|
||||
case 'replaygain_album_peak':
|
||||
$thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||
$thisfile_replaygain['album']['originator'] = 'unspecified';
|
||||
if ($thisfile_replaygain['album']['peak'] <= 0) {
|
||||
$info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mp3gain_undo':
|
||||
list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
|
||||
$thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left);
|
||||
$thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
|
||||
$thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false);
|
||||
break;
|
||||
|
||||
case 'mp3gain_minmax':
|
||||
list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
|
||||
$thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
|
||||
$thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
|
||||
break;
|
||||
|
||||
case 'mp3gain_album_minmax':
|
||||
list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
|
||||
$thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
|
||||
$thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
|
||||
break;
|
||||
|
||||
case 'tracknumber':
|
||||
if (is_array($thisfile_ape_items_current['data'])) {
|
||||
foreach ($thisfile_ape_items_current['data'] as $comment) {
|
||||
$thisfile_ape['comments']['track'][] = $comment;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'cover art (artist)':
|
||||
case 'cover art (back)':
|
||||
case 'cover art (band logo)':
|
||||
case 'cover art (band)':
|
||||
case 'cover art (colored fish)':
|
||||
case 'cover art (composer)':
|
||||
case 'cover art (conductor)':
|
||||
case 'cover art (front)':
|
||||
case 'cover art (icon)':
|
||||
case 'cover art (illustration)':
|
||||
case 'cover art (lead)':
|
||||
case 'cover art (leaflet)':
|
||||
case 'cover art (lyricist)':
|
||||
case 'cover art (media)':
|
||||
case 'cover art (movie scene)':
|
||||
case 'cover art (other icon)':
|
||||
case 'cover art (other)':
|
||||
case 'cover art (performance)':
|
||||
case 'cover art (publisher logo)':
|
||||
case 'cover art (recording)':
|
||||
case 'cover art (studio)':
|
||||
// list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
|
||||
list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
|
||||
$thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
|
||||
$thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
|
||||
|
||||
$thisfile_ape_items_current['image_mime'] = '';
|
||||
$imageinfo = array();
|
||||
$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
|
||||
$thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
|
||||
|
||||
do {
|
||||
if ($this->inline_attachments === false) {
|
||||
// skip entirely
|
||||
unset($thisfile_ape_items_current['data']);
|
||||
break;
|
||||
}
|
||||
if ($this->inline_attachments === true) {
|
||||
// great
|
||||
} elseif (is_int($this->inline_attachments)) {
|
||||
if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
|
||||
// too big, skip
|
||||
$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)';
|
||||
unset($thisfile_ape_items_current['data']);
|
||||
break;
|
||||
}
|
||||
} elseif (is_string($this->inline_attachments)) {
|
||||
$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
|
||||
if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
|
||||
// cannot write, skip
|
||||
$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
|
||||
unset($thisfile_ape_items_current['data']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if we get this far, must be OK
|
||||
if (is_string($this->inline_attachments)) {
|
||||
$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
|
||||
if (!file_exists($destination_filename) || is_writable($destination_filename)) {
|
||||
file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
|
||||
} else {
|
||||
$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
|
||||
}
|
||||
$thisfile_ape_items_current['data_filename'] = $destination_filename;
|
||||
unset($thisfile_ape_items_current['data']);
|
||||
} else {
|
||||
if (!isset($info['ape']['comments']['picture'])) {
|
||||
$info['ape']['comments']['picture'] = array();
|
||||
}
|
||||
$info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']);
|
||||
}
|
||||
} while (false);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (is_array($thisfile_ape_items_current['data'])) {
|
||||
foreach ($thisfile_ape_items_current['data'] as $comment) {
|
||||
$thisfile_ape['comments'][strtolower($item_key)][] = $comment;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (empty($thisfile_replaygain)) {
|
||||
unset($info['replay_gain']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function parseAPEheaderFooter($APEheaderFooterData) {
|
||||
// http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
|
||||
|
||||
// shortcut
|
||||
$headerfooterinfo['raw'] = array();
|
||||
$headerfooterinfo_raw = &$headerfooterinfo['raw'];
|
||||
|
||||
$headerfooterinfo_raw['footer_tag'] = substr($APEheaderFooterData, 0, 8);
|
||||
if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
|
||||
return false;
|
||||
}
|
||||
$headerfooterinfo_raw['version'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 8, 4));
|
||||
$headerfooterinfo_raw['tagsize'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
|
||||
$headerfooterinfo_raw['tag_items'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
|
||||
$headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
|
||||
$headerfooterinfo_raw['reserved'] = substr($APEheaderFooterData, 24, 8);
|
||||
|
||||
$headerfooterinfo['tag_version'] = $headerfooterinfo_raw['version'] / 1000;
|
||||
if ($headerfooterinfo['tag_version'] >= 2) {
|
||||
$headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
|
||||
}
|
||||
return $headerfooterinfo;
|
||||
}
|
||||
|
||||
public function parseAPEtagFlags($rawflagint) {
|
||||
// "Note: APE Tags 1.0 do not use any of the APE Tag flags.
|
||||
// All are set to zero on creation and ignored on reading."
|
||||
// http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html
|
||||
$flags['header'] = (bool) ($rawflagint & 0x80000000);
|
||||
$flags['footer'] = (bool) ($rawflagint & 0x40000000);
|
||||
$flags['this_is_header'] = (bool) ($rawflagint & 0x20000000);
|
||||
$flags['item_contents_raw'] = ($rawflagint & 0x00000006) >> 1;
|
||||
$flags['read_only'] = (bool) ($rawflagint & 0x00000001);
|
||||
|
||||
$flags['item_contents'] = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
|
||||
|
||||
return $flags;
|
||||
}
|
||||
|
||||
public function APEcontentTypeFlagLookup($contenttypeid) {
|
||||
static $APEcontentTypeFlagLookup = array(
|
||||
0 => 'utf-8',
|
||||
1 => 'binary',
|
||||
2 => 'external',
|
||||
3 => 'reserved'
|
||||
);
|
||||
return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
|
||||
}
|
||||
|
||||
public function APEtagItemIsUTF8Lookup($itemkey) {
|
||||
static $APEtagItemIsUTF8Lookup = array(
|
||||
'title',
|
||||
'subtitle',
|
||||
'artist',
|
||||
'album',
|
||||
'debut album',
|
||||
'publisher',
|
||||
'conductor',
|
||||
'track',
|
||||
'composer',
|
||||
'comment',
|
||||
'copyright',
|
||||
'publicationright',
|
||||
'file',
|
||||
'year',
|
||||
'record date',
|
||||
'record location',
|
||||
'genre',
|
||||
'media',
|
||||
'related',
|
||||
'isrc',
|
||||
'abstract',
|
||||
'language',
|
||||
'bibliography'
|
||||
);
|
||||
return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
|
||||
}
|
||||
|
||||
}
|
359
api/getID3/module.tag.id3v1.php
Executable file
@ -0,0 +1,359 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.tag.id3v1.php //
|
||||
// module for analyzing ID3v1 tags //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_id3v1 extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||
$info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek($this->getid3->fp, -256, SEEK_END);
|
||||
$preid3v1 = fread($this->getid3->fp, 128);
|
||||
$id3v1tag = fread($this->getid3->fp, 128);
|
||||
|
||||
if (substr($id3v1tag, 0, 3) == 'TAG') {
|
||||
|
||||
$info['avdataend'] = $info['filesize'] - 128;
|
||||
|
||||
$ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
|
||||
$ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
|
||||
$ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
|
||||
$ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4));
|
||||
$ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them
|
||||
$ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1));
|
||||
|
||||
// If second-last byte of comment field is null and last byte of comment field is non-null
|
||||
// then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
|
||||
if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
|
||||
$ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1));
|
||||
$ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28);
|
||||
}
|
||||
$ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
|
||||
|
||||
$ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
|
||||
if (!empty($ParsedID3v1['genre'])) {
|
||||
unset($ParsedID3v1['genreid']);
|
||||
}
|
||||
if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
|
||||
unset($ParsedID3v1['genre']);
|
||||
}
|
||||
|
||||
foreach ($ParsedID3v1 as $key => $value) {
|
||||
$ParsedID3v1['comments'][$key][0] = $value;
|
||||
}
|
||||
|
||||
// ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
|
||||
$GoodFormatID3v1tag = $this->GenerateID3v1Tag(
|
||||
$ParsedID3v1['title'],
|
||||
$ParsedID3v1['artist'],
|
||||
$ParsedID3v1['album'],
|
||||
$ParsedID3v1['year'],
|
||||
(isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
|
||||
$ParsedID3v1['comment'],
|
||||
(!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
|
||||
$ParsedID3v1['padding_valid'] = true;
|
||||
if ($id3v1tag !== $GoodFormatID3v1tag) {
|
||||
$ParsedID3v1['padding_valid'] = false;
|
||||
$info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding';
|
||||
}
|
||||
|
||||
$ParsedID3v1['tag_offset_end'] = $info['filesize'];
|
||||
$ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
|
||||
|
||||
$info['id3v1'] = $ParsedID3v1;
|
||||
}
|
||||
|
||||
if (substr($preid3v1, 0, 3) == 'TAG') {
|
||||
// The way iTunes handles tags is, well, brain-damaged.
|
||||
// It completely ignores v1 if ID3v2 is present.
|
||||
// This goes as far as adding a new v1 tag *even if there already is one*
|
||||
|
||||
// A suspected double-ID3v1 tag has been detected, but it could be that
|
||||
// the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
|
||||
if (substr($preid3v1, 96, 8) == 'APETAGEX') {
|
||||
// an APE tag footer was found before the last ID3v1, assume false "TAG" synch
|
||||
} elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
|
||||
// a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
|
||||
} else {
|
||||
// APE and Lyrics3 footers not found - assume double ID3v1
|
||||
$info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes';
|
||||
$info['avdataend'] -= 128;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function cutfield($str) {
|
||||
return trim(substr($str, 0, strcspn($str, "\x00")));
|
||||
}
|
||||
|
||||
public static function ArrayOfGenres($allowSCMPXextended=false) {
|
||||
static $GenreLookup = array(
|
||||
0 => 'Blues',
|
||||
1 => 'Classic Rock',
|
||||
2 => 'Country',
|
||||
3 => 'Dance',
|
||||
4 => 'Disco',
|
||||
5 => 'Funk',
|
||||
6 => 'Grunge',
|
||||
7 => 'Hip-Hop',
|
||||
8 => 'Jazz',
|
||||
9 => 'Metal',
|
||||
10 => 'New Age',
|
||||
11 => 'Oldies',
|
||||
12 => 'Other',
|
||||
13 => 'Pop',
|
||||
14 => 'R&B',
|
||||
15 => 'Rap',
|
||||
16 => 'Reggae',
|
||||
17 => 'Rock',
|
||||
18 => 'Techno',
|
||||
19 => 'Industrial',
|
||||
20 => 'Alternative',
|
||||
21 => 'Ska',
|
||||
22 => 'Death Metal',
|
||||
23 => 'Pranks',
|
||||
24 => 'Soundtrack',
|
||||
25 => 'Euro-Techno',
|
||||
26 => 'Ambient',
|
||||
27 => 'Trip-Hop',
|
||||
28 => 'Vocal',
|
||||
29 => 'Jazz+Funk',
|
||||
30 => 'Fusion',
|
||||
31 => 'Trance',
|
||||
32 => 'Classical',
|
||||
33 => 'Instrumental',
|
||||
34 => 'Acid',
|
||||
35 => 'House',
|
||||
36 => 'Game',
|
||||
37 => 'Sound Clip',
|
||||
38 => 'Gospel',
|
||||
39 => 'Noise',
|
||||
40 => 'Alt. Rock',
|
||||
41 => 'Bass',
|
||||
42 => 'Soul',
|
||||
43 => 'Punk',
|
||||
44 => 'Space',
|
||||
45 => 'Meditative',
|
||||
46 => 'Instrumental Pop',
|
||||
47 => 'Instrumental Rock',
|
||||
48 => 'Ethnic',
|
||||
49 => 'Gothic',
|
||||
50 => 'Darkwave',
|
||||
51 => 'Techno-Industrial',
|
||||
52 => 'Electronic',
|
||||
53 => 'Pop-Folk',
|
||||
54 => 'Eurodance',
|
||||
55 => 'Dream',
|
||||
56 => 'Southern Rock',
|
||||
57 => 'Comedy',
|
||||
58 => 'Cult',
|
||||
59 => 'Gangsta Rap',
|
||||
60 => 'Top 40',
|
||||
61 => 'Christian Rap',
|
||||
62 => 'Pop/Funk',
|
||||
63 => 'Jungle',
|
||||
64 => 'Native American',
|
||||
65 => 'Cabaret',
|
||||
66 => 'New Wave',
|
||||
67 => 'Psychedelic',
|
||||
68 => 'Rave',
|
||||
69 => 'Showtunes',
|
||||
70 => 'Trailer',
|
||||
71 => 'Lo-Fi',
|
||||
72 => 'Tribal',
|
||||
73 => 'Acid Punk',
|
||||
74 => 'Acid Jazz',
|
||||
75 => 'Polka',
|
||||
76 => 'Retro',
|
||||
77 => 'Musical',
|
||||
78 => 'Rock & Roll',
|
||||
79 => 'Hard Rock',
|
||||
80 => 'Folk',
|
||||
81 => 'Folk/Rock',
|
||||
82 => 'National Folk',
|
||||
83 => 'Swing',
|
||||
84 => 'Fast-Fusion',
|
||||
85 => 'Bebob',
|
||||
86 => 'Latin',
|
||||
87 => 'Revival',
|
||||
88 => 'Celtic',
|
||||
89 => 'Bluegrass',
|
||||
90 => 'Avantgarde',
|
||||
91 => 'Gothic Rock',
|
||||
92 => 'Progressive Rock',
|
||||
93 => 'Psychedelic Rock',
|
||||
94 => 'Symphonic Rock',
|
||||
95 => 'Slow Rock',
|
||||
96 => 'Big Band',
|
||||
97 => 'Chorus',
|
||||
98 => 'Easy Listening',
|
||||
99 => 'Acoustic',
|
||||
100 => 'Humour',
|
||||
101 => 'Speech',
|
||||
102 => 'Chanson',
|
||||
103 => 'Opera',
|
||||
104 => 'Chamber Music',
|
||||
105 => 'Sonata',
|
||||
106 => 'Symphony',
|
||||
107 => 'Booty Bass',
|
||||
108 => 'Primus',
|
||||
109 => 'Porn Groove',
|
||||
110 => 'Satire',
|
||||
111 => 'Slow Jam',
|
||||
112 => 'Club',
|
||||
113 => 'Tango',
|
||||
114 => 'Samba',
|
||||
115 => 'Folklore',
|
||||
116 => 'Ballad',
|
||||
117 => 'Power Ballad',
|
||||
118 => 'Rhythmic Soul',
|
||||
119 => 'Freestyle',
|
||||
120 => 'Duet',
|
||||
121 => 'Punk Rock',
|
||||
122 => 'Drum Solo',
|
||||
123 => 'A Cappella',
|
||||
124 => 'Euro-House',
|
||||
125 => 'Dance Hall',
|
||||
126 => 'Goa',
|
||||
127 => 'Drum & Bass',
|
||||
128 => 'Club-House',
|
||||
129 => 'Hardcore',
|
||||
130 => 'Terror',
|
||||
131 => 'Indie',
|
||||
132 => 'BritPop',
|
||||
133 => 'Negerpunk',
|
||||
134 => 'Polsk Punk',
|
||||
135 => 'Beat',
|
||||
136 => 'Christian Gangsta Rap',
|
||||
137 => 'Heavy Metal',
|
||||
138 => 'Black Metal',
|
||||
139 => 'Crossover',
|
||||
140 => 'Contemporary Christian',
|
||||
141 => 'Christian Rock',
|
||||
142 => 'Merengue',
|
||||
143 => 'Salsa',
|
||||
144 => 'Thrash Metal',
|
||||
145 => 'Anime',
|
||||
146 => 'JPop',
|
||||
147 => 'Synthpop',
|
||||
|
||||
255 => 'Unknown',
|
||||
|
||||
'CR' => 'Cover',
|
||||
'RX' => 'Remix'
|
||||
);
|
||||
|
||||
static $GenreLookupSCMPX = array();
|
||||
if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
|
||||
$GenreLookupSCMPX = $GenreLookup;
|
||||
// http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
|
||||
// Extended ID3v1 genres invented by SCMPX
|
||||
// Note that 255 "Japanese Anime" conflicts with standard "Unknown"
|
||||
$GenreLookupSCMPX[240] = 'Sacred';
|
||||
$GenreLookupSCMPX[241] = 'Northern Europe';
|
||||
$GenreLookupSCMPX[242] = 'Irish & Scottish';
|
||||
$GenreLookupSCMPX[243] = 'Scotland';
|
||||
$GenreLookupSCMPX[244] = 'Ethnic Europe';
|
||||
$GenreLookupSCMPX[245] = 'Enka';
|
||||
$GenreLookupSCMPX[246] = 'Children\'s Song';
|
||||
$GenreLookupSCMPX[247] = 'Japanese Sky';
|
||||
$GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
|
||||
$GenreLookupSCMPX[249] = 'Japanese Doom Rock';
|
||||
$GenreLookupSCMPX[250] = 'Japanese J-POP';
|
||||
$GenreLookupSCMPX[251] = 'Japanese Seiyu';
|
||||
$GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
|
||||
$GenreLookupSCMPX[253] = 'Japanese Moemoe';
|
||||
$GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
|
||||
//$GenreLookupSCMPX[255] = 'Japanese Anime';
|
||||
}
|
||||
|
||||
return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
|
||||
}
|
||||
|
||||
public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
|
||||
switch ($genreid) {
|
||||
case 'RX':
|
||||
case 'CR':
|
||||
break;
|
||||
default:
|
||||
if (!is_numeric($genreid)) {
|
||||
return false;
|
||||
}
|
||||
$genreid = intval($genreid); // to handle 3 or '3' or '03'
|
||||
break;
|
||||
}
|
||||
$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
|
||||
return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
|
||||
}
|
||||
|
||||
public static function LookupGenreID($genre, $allowSCMPXextended=false) {
|
||||
$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
|
||||
$LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
|
||||
foreach ($GenreLookup as $key => $value) {
|
||||
if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function StandardiseID3v1GenreName($OriginalGenre) {
|
||||
if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
|
||||
return self::LookupGenreName($GenreID);
|
||||
}
|
||||
return $OriginalGenre;
|
||||
}
|
||||
|
||||
public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
|
||||
$ID3v1Tag = 'TAG';
|
||||
$ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||
$ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||
$ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||
$ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT);
|
||||
if (!empty($track) && ($track > 0) && ($track <= 255)) {
|
||||
$ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
|
||||
$ID3v1Tag .= "\x00";
|
||||
if (gettype($track) == 'string') {
|
||||
$track = (int) $track;
|
||||
}
|
||||
$ID3v1Tag .= chr($track);
|
||||
} else {
|
||||
$ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||
}
|
||||
if (($genreid < 0) || ($genreid > 147)) {
|
||||
$genreid = 255; // 'unknown' genre
|
||||
}
|
||||
switch (gettype($genreid)) {
|
||||
case 'string':
|
||||
case 'integer':
|
||||
$ID3v1Tag .= chr(intval($genreid));
|
||||
break;
|
||||
default:
|
||||
$ID3v1Tag .= chr(255); // 'unknown' genre
|
||||
break;
|
||||
}
|
||||
|
||||
return $ID3v1Tag;
|
||||
}
|
||||
|
||||
}
|
3386
api/getID3/module.tag.id3v2.php
Executable file
294
api/getID3/module.tag.lyrics3.php
Executable file
@ -0,0 +1,294 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// //
|
||||
// module.tag.lyrics3.php //
|
||||
// module for analyzing Lyrics3 tags //
|
||||
// dependencies: module.tag.apetag.php (optional) //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_lyrics3 extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// http://www.volweb.cz/str/tags.htm
|
||||
|
||||
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||
$info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek($this->getid3->fp, (0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
|
||||
$lyrics3_id3v1 = fread($this->getid3->fp, 128 + 9 + 6);
|
||||
$lyrics3lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size
|
||||
$lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200
|
||||
$id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1
|
||||
|
||||
if ($lyrics3end == 'LYRICSEND') {
|
||||
// Lyrics3v1, ID3v1, no APE
|
||||
|
||||
$lyrics3size = 5100;
|
||||
$lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
|
||||
$lyrics3version = 1;
|
||||
|
||||
} elseif ($lyrics3end == 'LYRICS200') {
|
||||
// Lyrics3v2, ID3v1, no APE
|
||||
|
||||
// LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
|
||||
$lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200');
|
||||
$lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
|
||||
$lyrics3version = 2;
|
||||
|
||||
} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) {
|
||||
// Lyrics3v1, no ID3v1, no APE
|
||||
|
||||
$lyrics3size = 5100;
|
||||
$lyrics3offset = $info['filesize'] - $lyrics3size;
|
||||
$lyrics3version = 1;
|
||||
$lyrics3offset = $info['filesize'] - $lyrics3size;
|
||||
|
||||
} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) {
|
||||
|
||||
// Lyrics3v2, no ID3v1, no APE
|
||||
|
||||
$lyrics3size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
|
||||
$lyrics3offset = $info['filesize'] - $lyrics3size;
|
||||
$lyrics3version = 2;
|
||||
|
||||
} else {
|
||||
|
||||
if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) {
|
||||
|
||||
fseek($this->getid3->fp, $info['ape']['tag_offset_start'] - 15, SEEK_SET);
|
||||
$lyrics3lsz = fread($this->getid3->fp, 6);
|
||||
$lyrics3end = fread($this->getid3->fp, 9);
|
||||
|
||||
if ($lyrics3end == 'LYRICSEND') {
|
||||
// Lyrics3v1, APE, maybe ID3v1
|
||||
|
||||
$lyrics3size = 5100;
|
||||
$lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
|
||||
$info['avdataend'] = $lyrics3offset;
|
||||
$lyrics3version = 1;
|
||||
$info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability';
|
||||
|
||||
} elseif ($lyrics3end == 'LYRICS200') {
|
||||
// Lyrics3v2, APE, maybe ID3v1
|
||||
|
||||
$lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
|
||||
$lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
|
||||
$lyrics3version = 2;
|
||||
$info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (isset($lyrics3offset)) {
|
||||
$info['avdataend'] = $lyrics3offset;
|
||||
$this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size);
|
||||
|
||||
if (!isset($info['ape'])) {
|
||||
$GETID3_ERRORARRAY = &$info['warning'];
|
||||
if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, false)) {
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($this->getid3->filename);
|
||||
$getid3_apetag = new getid3_apetag($getid3_temp);
|
||||
$getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start'];
|
||||
$getid3_apetag->Analyze();
|
||||
if (!empty($getid3_temp->info['ape'])) {
|
||||
$info['ape'] = $getid3_temp->info['ape'];
|
||||
}
|
||||
if (!empty($getid3_temp->info['replay_gain'])) {
|
||||
$info['replay_gain'] = $getid3_temp->info['replay_gain'];
|
||||
}
|
||||
unset($getid3_temp, $getid3_apetag);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getLyrics3Data($endoffset, $version, $length) {
|
||||
// http://www.volweb.cz/str/tags.htm
|
||||
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
if (!getid3_lib::intValueSupported($endoffset)) {
|
||||
$info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek($this->getid3->fp, $endoffset, SEEK_SET);
|
||||
if ($length <= 0) {
|
||||
return false;
|
||||
}
|
||||
$rawdata = fread($this->getid3->fp, $length);
|
||||
|
||||
$ParsedLyrics3['raw']['lyrics3version'] = $version;
|
||||
$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
|
||||
$ParsedLyrics3['tag_offset_start'] = $endoffset;
|
||||
$ParsedLyrics3['tag_offset_end'] = $endoffset + $length - 1;
|
||||
|
||||
if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') {
|
||||
if (strpos($rawdata, 'LYRICSBEGIN') !== false) {
|
||||
|
||||
$info['warning'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version;
|
||||
$info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN');
|
||||
$rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN'));
|
||||
$length = strlen($rawdata);
|
||||
$ParsedLyrics3['tag_offset_start'] = $info['avdataend'];
|
||||
$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
|
||||
|
||||
} else {
|
||||
|
||||
$info['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch ($version) {
|
||||
|
||||
case 1:
|
||||
if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') {
|
||||
$ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9));
|
||||
$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
|
||||
} else {
|
||||
$info['error'][] = '"LYRICSEND" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead';
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') {
|
||||
$ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ
|
||||
$rawdata = $ParsedLyrics3['raw']['unparsed'];
|
||||
while (strlen($rawdata) > 0) {
|
||||
$fieldname = substr($rawdata, 0, 3);
|
||||
$fieldsize = (int) substr($rawdata, 3, 5);
|
||||
$ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize);
|
||||
$rawdata = substr($rawdata, 3 + 5 + $fieldsize);
|
||||
}
|
||||
|
||||
if (isset($ParsedLyrics3['raw']['IND'])) {
|
||||
$i = 0;
|
||||
$flagnames = array('lyrics', 'timestamps', 'inhibitrandom');
|
||||
foreach ($flagnames as $flagname) {
|
||||
if (strlen($ParsedLyrics3['raw']['IND']) > $i++) {
|
||||
$ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1 - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fieldnametranslation = array('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author');
|
||||
foreach ($fieldnametranslation as $key => $value) {
|
||||
if (isset($ParsedLyrics3['raw'][$key])) {
|
||||
$ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($ParsedLyrics3['raw']['IMG'])) {
|
||||
$imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']);
|
||||
foreach ($imagestrings as $key => $imagestring) {
|
||||
if (strpos($imagestring, '||') !== false) {
|
||||
$imagearray = explode('||', $imagestring);
|
||||
$ParsedLyrics3['images'][$key]['filename'] = (isset($imagearray[0]) ? $imagearray[0] : '');
|
||||
$ParsedLyrics3['images'][$key]['description'] = (isset($imagearray[1]) ? $imagearray[1] : '');
|
||||
$ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : '');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($ParsedLyrics3['raw']['LYR'])) {
|
||||
$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
|
||||
}
|
||||
} else {
|
||||
$info['error'][] = '"LYRICS200" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead';
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)';
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) {
|
||||
$info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data';
|
||||
unset($info['id3v1']);
|
||||
foreach ($info['warning'] as $key => $value) {
|
||||
if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
|
||||
unset($info['warning'][$key]);
|
||||
sort($info['warning']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$info['lyrics3'] = $ParsedLyrics3;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function Lyrics3Timestamp2Seconds($rawtimestamp) {
|
||||
if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) {
|
||||
return (int) (($regs[1] * 60) + $regs[2]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function Lyrics3LyricsTimestampParse(&$Lyrics3data) {
|
||||
$lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']);
|
||||
foreach ($lyricsarray as $key => $lyricline) {
|
||||
$regs = array();
|
||||
unset($thislinetimestamps);
|
||||
while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) {
|
||||
$thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]);
|
||||
$lyricline = str_replace($regs[0], '', $lyricline);
|
||||
}
|
||||
$notimestamplyricsarray[$key] = $lyricline;
|
||||
if (isset($thislinetimestamps) && is_array($thislinetimestamps)) {
|
||||
sort($thislinetimestamps);
|
||||
foreach ($thislinetimestamps as $timestampkey => $timestamp) {
|
||||
if (isset($Lyrics3data['synchedlyrics'][$timestamp])) {
|
||||
// timestamps only have a 1-second resolution, it's possible that multiple lines
|
||||
// could have the same timestamp, if so, append
|
||||
$Lyrics3data['synchedlyrics'][$timestamp] .= "\r\n".$lyricline;
|
||||
} else {
|
||||
$Lyrics3data['synchedlyrics'][$timestamp] = $lyricline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$Lyrics3data['unsynchedlyrics'] = implode("\r\n", $notimestamplyricsarray);
|
||||
if (isset($Lyrics3data['synchedlyrics']) && is_array($Lyrics3data['synchedlyrics'])) {
|
||||
ksort($Lyrics3data['synchedlyrics']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function IntString2Bool($char) {
|
||||
if ($char == '1') {
|
||||
return true;
|
||||
} elseif ($char == '0') {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
767
api/getID3/module.tag.xmp.php
Executable file
@ -0,0 +1,767 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.tag.xmp.php //
|
||||
// module for analyzing XMP metadata (e.g. in JPEG files) //
|
||||
// dependencies: NONE //
|
||||
// //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// Module originally written [2009-Mar-26] by //
|
||||
// Nigel Barnes <ngbarnesØhotmail*com> //
|
||||
// Bundled into getID3 with permission //
|
||||
// called by getID3 in module.graphic.jpg.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
/**************************************************************************************************
|
||||
* SWISScenter Source Nigel Barnes
|
||||
*
|
||||
* Provides functions for reading information from the 'APP1' Extensible Metadata
|
||||
* Platform (XMP) segment of JPEG format files.
|
||||
* This XMP segment is XML based and contains the Resource Description Framework (RDF)
|
||||
* data, which itself can contain the Dublin Core Metadata Initiative (DCMI) information.
|
||||
*
|
||||
* This code uses segments from the JPEG Metadata Toolkit project by Evan Hunter.
|
||||
*************************************************************************************************/
|
||||
class Image_XMP
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* The name of the image file that contains the XMP fields to extract and modify.
|
||||
* @see Image_XMP()
|
||||
*/
|
||||
public $_sFilename = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* The XMP fields that were extracted from the image or updated by this class.
|
||||
* @see getAllTags()
|
||||
*/
|
||||
public $_aXMP = array();
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
* True if an APP1 segment was found to contain XMP metadata.
|
||||
* @see isValid()
|
||||
*/
|
||||
public $_bXMPParse = false;
|
||||
|
||||
/**
|
||||
* Returns the status of XMP parsing during instantiation
|
||||
*
|
||||
* You'll normally want to call this method before trying to get XMP fields.
|
||||
*
|
||||
* @return boolean
|
||||
* Returns true if an APP1 segment was found to contain XMP metadata.
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
return $this->_bXMPParse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a copy of all XMP tags extracted from the image
|
||||
*
|
||||
* @return array - An array of XMP fields as it extracted by the XMPparse() function
|
||||
*/
|
||||
public function getAllTags()
|
||||
{
|
||||
return $this->_aXMP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all the JPEG header segments from an JPEG image file into an array
|
||||
*
|
||||
* @param string $filename - the filename of the JPEG file to read
|
||||
* @return array $headerdata - Array of JPEG header segments
|
||||
* @return boolean FALSE - if headers could not be read
|
||||
*/
|
||||
public function _get_jpeg_header_data($filename)
|
||||
{
|
||||
// prevent refresh from aborting file operations and hosing file
|
||||
ignore_user_abort(true);
|
||||
|
||||
// Attempt to open the jpeg file - the at symbol supresses the error message about
|
||||
// not being able to open files. The file_exists would have been used, but it
|
||||
// does not work with files fetched over http or ftp.
|
||||
if (is_readable($filename) && is_file($filename) && ($filehnd = fopen($filename, 'rb'))) {
|
||||
// great
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the first two characters
|
||||
$data = fread($filehnd, 2);
|
||||
|
||||
// Check that the first two characters are 0xFF 0xD8 (SOI - Start of image)
|
||||
if ($data != "\xFF\xD8")
|
||||
{
|
||||
// No SOI (FF D8) at start of file - This probably isn't a JPEG file - close file and return;
|
||||
echo '<p>This probably is not a JPEG file</p>'."\n";
|
||||
fclose($filehnd);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the third character
|
||||
$data = fread($filehnd, 2);
|
||||
|
||||
// Check that the third character is 0xFF (Start of first segment header)
|
||||
if ($data{0} != "\xFF")
|
||||
{
|
||||
// NO FF found - close file and return - JPEG is probably corrupted
|
||||
fclose($filehnd);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Flag that we havent yet hit the compressed image data
|
||||
$hit_compressed_image_data = false;
|
||||
|
||||
// Cycle through the file until, one of: 1) an EOI (End of image) marker is hit,
|
||||
// 2) we have hit the compressed image data (no more headers are allowed after data)
|
||||
// 3) or end of file is hit
|
||||
|
||||
while (($data{1} != "\xD9") && (!$hit_compressed_image_data) && (!feof($filehnd)))
|
||||
{
|
||||
// Found a segment to look at.
|
||||
// Check that the segment marker is not a Restart marker - restart markers don't have size or data after them
|
||||
if ((ord($data{1}) < 0xD0) || (ord($data{1}) > 0xD7))
|
||||
{
|
||||
// Segment isn't a Restart marker
|
||||
// Read the next two bytes (size)
|
||||
$sizestr = fread($filehnd, 2);
|
||||
|
||||
// convert the size bytes to an integer
|
||||
$decodedsize = unpack('nsize', $sizestr);
|
||||
|
||||
// Save the start position of the data
|
||||
$segdatastart = ftell($filehnd);
|
||||
|
||||
// Read the segment data with length indicated by the previously read size
|
||||
$segdata = fread($filehnd, $decodedsize['size'] - 2);
|
||||
|
||||
// Store the segment information in the output array
|
||||
$headerdata[] = array(
|
||||
'SegType' => ord($data{1}),
|
||||
'SegName' => $GLOBALS['JPEG_Segment_Names'][ord($data{1})],
|
||||
'SegDataStart' => $segdatastart,
|
||||
'SegData' => $segdata,
|
||||
);
|
||||
}
|
||||
|
||||
// If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows
|
||||
if ($data{1} == "\xDA")
|
||||
{
|
||||
// Flag that we have hit the compressed image data - exit loop as no more headers available.
|
||||
$hit_compressed_image_data = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not an SOS - Read the next two bytes - should be the segment marker for the next segment
|
||||
$data = fread($filehnd, 2);
|
||||
|
||||
// Check that the first byte of the two is 0xFF as it should be for a marker
|
||||
if ($data{0} != "\xFF")
|
||||
{
|
||||
// NO FF found - close file and return - JPEG is probably corrupted
|
||||
fclose($filehnd);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close File
|
||||
fclose($filehnd);
|
||||
// Alow the user to abort from now on
|
||||
ignore_user_abort(false);
|
||||
|
||||
// Return the header data retrieved
|
||||
return $headerdata;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves XMP information from an APP1 JPEG segment and returns the raw XML text as a string.
|
||||
*
|
||||
* @param string $filename - the filename of the JPEG file to read
|
||||
* @return string $xmp_data - the string of raw XML text
|
||||
* @return boolean FALSE - if an APP 1 XMP segment could not be found, or if an error occured
|
||||
*/
|
||||
public function _get_XMP_text($filename)
|
||||
{
|
||||
//Get JPEG header data
|
||||
$jpeg_header_data = $this->_get_jpeg_header_data($filename);
|
||||
|
||||
//Cycle through the header segments
|
||||
for ($i = 0; $i < count($jpeg_header_data); $i++)
|
||||
{
|
||||
// If we find an APP1 header,
|
||||
if (strcmp($jpeg_header_data[$i]['SegName'], 'APP1') == 0)
|
||||
{
|
||||
// And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/\x00) ,
|
||||
if (strncmp($jpeg_header_data[$i]['SegData'], 'http://ns.adobe.com/xap/1.0/'."\x00", 29) == 0)
|
||||
{
|
||||
// Found a XMP/RDF block
|
||||
// Return the XMP text
|
||||
$xmp_data = substr($jpeg_header_data[$i]['SegData'], 29);
|
||||
|
||||
return trim($xmp_data); // trim() should not be neccesary, but some files found in the wild with null-terminated block (known samples from Apple Aperture) causes problems elsewhere (see http://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string containing XMP data (XML), and returns an array
|
||||
* which contains all the XMP (XML) information.
|
||||
*
|
||||
* @param string $xml_text - a string containing the XMP data (XML) to be parsed
|
||||
* @return array $xmp_array - an array containing all xmp details retrieved.
|
||||
* @return boolean 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 (true)
|
||||
{
|
||||
// Attribute wanted
|
||||
$xmp_array[$key] = $xml_elem['attributes'][$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'cdata':
|
||||
case 'close':
|
||||
break;
|
||||
}
|
||||
|
||||
case 'rdf:ID':
|
||||
case 'rdf:nodeID':
|
||||
// Attributes are ignored
|
||||
break;
|
||||
|
||||
case 'rdf:li':
|
||||
// Property member
|
||||
if ($xml_elem['type'] == 'complete')
|
||||
{
|
||||
if (array_key_exists('attributes', $xml_elem))
|
||||
{
|
||||
// If Lang Alt (language alternatives) then ensure we take the default language
|
||||
if (isset($xml_elem['attributes']['xml:lang']) && ($xml_elem['attributes']['xml:lang'] != 'x-default'))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($current_property != '')
|
||||
{
|
||||
$xmp_array[$current_property][$container_index] = (isset($xml_elem['value']) ? $xml_elem['value'] : '');
|
||||
$container_index += 1;
|
||||
}
|
||||
//else unidentified attribute!!
|
||||
}
|
||||
break;
|
||||
|
||||
case 'rdf:Seq':
|
||||
case 'rdf:Bag':
|
||||
case 'rdf:Alt':
|
||||
// Container found
|
||||
switch ($xml_elem['type'])
|
||||
{
|
||||
case 'open':
|
||||
$container_index = 0;
|
||||
break;
|
||||
case 'close':
|
||||
$container_index = -1;
|
||||
break;
|
||||
case 'cdata':
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Check whether we want the details from this attribute
|
||||
// if (in_array($xml_elem['tag'], $GLOBALS['XMP_tag_captions']))
|
||||
if (true)
|
||||
{
|
||||
switch ($xml_elem['type'])
|
||||
{
|
||||
case 'open':
|
||||
// open current element
|
||||
$current_property = $xml_elem['tag'];
|
||||
break;
|
||||
|
||||
case 'close':
|
||||
// close current element
|
||||
$current_property = '';
|
||||
break;
|
||||
|
||||
case 'complete':
|
||||
// store attribute value
|
||||
$xmp_array[$xml_elem['tag']] = (isset($xml_elem['attributes']) ? $xml_elem['attributes'] : (isset($xml_elem['value']) ? $xml_elem['value'] : ''));
|
||||
break;
|
||||
|
||||
case 'cdata':
|
||||
// ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return $xmp_array;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string - Name of the image file to access and extract XMP information from.
|
||||
*/
|
||||
public function Image_XMP($sFilename)
|
||||
{
|
||||
$this->_sFilename = $sFilename;
|
||||
|
||||
if (is_file($this->_sFilename))
|
||||
{
|
||||
// Get XMP data
|
||||
$xmp_data = $this->_get_XMP_text($sFilename);
|
||||
if ($xmp_data)
|
||||
{
|
||||
$this->_aXMP = $this->read_XMP_array_from_text($xmp_data);
|
||||
$this->_bXMPParse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Global Variable: XMP_tag_captions
|
||||
*
|
||||
* The Property names of all known XMP fields.
|
||||
* Note: this is a full list with unrequired properties commented out.
|
||||
*/
|
||||
/*
|
||||
$GLOBALS['XMP_tag_captions'] = array(
|
||||
// IPTC Core
|
||||
'Iptc4xmpCore:CiAdrCity',
|
||||
'Iptc4xmpCore:CiAdrCtry',
|
||||
'Iptc4xmpCore:CiAdrExtadr',
|
||||
'Iptc4xmpCore:CiAdrPcode',
|
||||
'Iptc4xmpCore:CiAdrRegion',
|
||||
'Iptc4xmpCore:CiEmailWork',
|
||||
'Iptc4xmpCore:CiTelWork',
|
||||
'Iptc4xmpCore:CiUrlWork',
|
||||
'Iptc4xmpCore:CountryCode',
|
||||
'Iptc4xmpCore:CreatorContactInfo',
|
||||
'Iptc4xmpCore:IntellectualGenre',
|
||||
'Iptc4xmpCore:Location',
|
||||
'Iptc4xmpCore:Scene',
|
||||
'Iptc4xmpCore:SubjectCode',
|
||||
// Dublin Core Schema
|
||||
'dc:contributor',
|
||||
'dc:coverage',
|
||||
'dc:creator',
|
||||
'dc:date',
|
||||
'dc:description',
|
||||
'dc:format',
|
||||
'dc:identifier',
|
||||
'dc:language',
|
||||
'dc:publisher',
|
||||
'dc:relation',
|
||||
'dc:rights',
|
||||
'dc:source',
|
||||
'dc:subject',
|
||||
'dc:title',
|
||||
'dc:type',
|
||||
// XMP Basic Schema
|
||||
'xmp:Advisory',
|
||||
'xmp:BaseURL',
|
||||
'xmp:CreateDate',
|
||||
'xmp:CreatorTool',
|
||||
'xmp:Identifier',
|
||||
'xmp:Label',
|
||||
'xmp:MetadataDate',
|
||||
'xmp:ModifyDate',
|
||||
'xmp:Nickname',
|
||||
'xmp:Rating',
|
||||
'xmp:Thumbnails',
|
||||
'xmpidq:Scheme',
|
||||
// XMP Rights Management Schema
|
||||
'xmpRights:Certificate',
|
||||
'xmpRights:Marked',
|
||||
'xmpRights:Owner',
|
||||
'xmpRights:UsageTerms',
|
||||
'xmpRights:WebStatement',
|
||||
// These are not in spec but Photoshop CS seems to use them
|
||||
'xap:Advisory',
|
||||
'xap:BaseURL',
|
||||
'xap:CreateDate',
|
||||
'xap:CreatorTool',
|
||||
'xap:Identifier',
|
||||
'xap:MetadataDate',
|
||||
'xap:ModifyDate',
|
||||
'xap:Nickname',
|
||||
'xap:Rating',
|
||||
'xap:Thumbnails',
|
||||
'xapidq:Scheme',
|
||||
'xapRights:Certificate',
|
||||
'xapRights:Copyright',
|
||||
'xapRights:Marked',
|
||||
'xapRights:Owner',
|
||||
'xapRights:UsageTerms',
|
||||
'xapRights:WebStatement',
|
||||
// XMP Media Management Schema
|
||||
'xapMM:DerivedFrom',
|
||||
'xapMM:DocumentID',
|
||||
'xapMM:History',
|
||||
'xapMM:InstanceID',
|
||||
'xapMM:ManagedFrom',
|
||||
'xapMM:Manager',
|
||||
'xapMM:ManageTo',
|
||||
'xapMM:ManageUI',
|
||||
'xapMM:ManagerVariant',
|
||||
'xapMM:RenditionClass',
|
||||
'xapMM:RenditionParams',
|
||||
'xapMM:VersionID',
|
||||
'xapMM:Versions',
|
||||
'xapMM:LastURL',
|
||||
'xapMM:RenditionOf',
|
||||
'xapMM:SaveID',
|
||||
// XMP Basic Job Ticket Schema
|
||||
'xapBJ:JobRef',
|
||||
// XMP Paged-Text Schema
|
||||
'xmpTPg:MaxPageSize',
|
||||
'xmpTPg:NPages',
|
||||
'xmpTPg:Fonts',
|
||||
'xmpTPg:Colorants',
|
||||
'xmpTPg:PlateNames',
|
||||
// Adobe PDF Schema
|
||||
'pdf:Keywords',
|
||||
'pdf:PDFVersion',
|
||||
'pdf:Producer',
|
||||
// Photoshop Schema
|
||||
'photoshop:AuthorsPosition',
|
||||
'photoshop:CaptionWriter',
|
||||
'photoshop:Category',
|
||||
'photoshop:City',
|
||||
'photoshop:Country',
|
||||
'photoshop:Credit',
|
||||
'photoshop:DateCreated',
|
||||
'photoshop:Headline',
|
||||
'photoshop:History',
|
||||
// Not in XMP spec
|
||||
'photoshop:Instructions',
|
||||
'photoshop:Source',
|
||||
'photoshop:State',
|
||||
'photoshop:SupplementalCategories',
|
||||
'photoshop:TransmissionReference',
|
||||
'photoshop:Urgency',
|
||||
// EXIF Schemas
|
||||
'tiff:ImageWidth',
|
||||
'tiff:ImageLength',
|
||||
'tiff:BitsPerSample',
|
||||
'tiff:Compression',
|
||||
'tiff:PhotometricInterpretation',
|
||||
'tiff:Orientation',
|
||||
'tiff:SamplesPerPixel',
|
||||
'tiff:PlanarConfiguration',
|
||||
'tiff:YCbCrSubSampling',
|
||||
'tiff:YCbCrPositioning',
|
||||
'tiff:XResolution',
|
||||
'tiff:YResolution',
|
||||
'tiff:ResolutionUnit',
|
||||
'tiff:TransferFunction',
|
||||
'tiff:WhitePoint',
|
||||
'tiff:PrimaryChromaticities',
|
||||
'tiff:YCbCrCoefficients',
|
||||
'tiff:ReferenceBlackWhite',
|
||||
'tiff:DateTime',
|
||||
'tiff:ImageDescription',
|
||||
'tiff:Make',
|
||||
'tiff:Model',
|
||||
'tiff:Software',
|
||||
'tiff:Artist',
|
||||
'tiff:Copyright',
|
||||
'exif:ExifVersion',
|
||||
'exif:FlashpixVersion',
|
||||
'exif:ColorSpace',
|
||||
'exif:ComponentsConfiguration',
|
||||
'exif:CompressedBitsPerPixel',
|
||||
'exif:PixelXDimension',
|
||||
'exif:PixelYDimension',
|
||||
'exif:MakerNote',
|
||||
'exif:UserComment',
|
||||
'exif:RelatedSoundFile',
|
||||
'exif:DateTimeOriginal',
|
||||
'exif:DateTimeDigitized',
|
||||
'exif:ExposureTime',
|
||||
'exif:FNumber',
|
||||
'exif:ExposureProgram',
|
||||
'exif:SpectralSensitivity',
|
||||
'exif:ISOSpeedRatings',
|
||||
'exif:OECF',
|
||||
'exif:ShutterSpeedValue',
|
||||
'exif:ApertureValue',
|
||||
'exif:BrightnessValue',
|
||||
'exif:ExposureBiasValue',
|
||||
'exif:MaxApertureValue',
|
||||
'exif:SubjectDistance',
|
||||
'exif:MeteringMode',
|
||||
'exif:LightSource',
|
||||
'exif:Flash',
|
||||
'exif:FocalLength',
|
||||
'exif:SubjectArea',
|
||||
'exif:FlashEnergy',
|
||||
'exif:SpatialFrequencyResponse',
|
||||
'exif:FocalPlaneXResolution',
|
||||
'exif:FocalPlaneYResolution',
|
||||
'exif:FocalPlaneResolutionUnit',
|
||||
'exif:SubjectLocation',
|
||||
'exif:SensingMethod',
|
||||
'exif:FileSource',
|
||||
'exif:SceneType',
|
||||
'exif:CFAPattern',
|
||||
'exif:CustomRendered',
|
||||
'exif:ExposureMode',
|
||||
'exif:WhiteBalance',
|
||||
'exif:DigitalZoomRatio',
|
||||
'exif:FocalLengthIn35mmFilm',
|
||||
'exif:SceneCaptureType',
|
||||
'exif:GainControl',
|
||||
'exif:Contrast',
|
||||
'exif:Saturation',
|
||||
'exif:Sharpness',
|
||||
'exif:DeviceSettingDescription',
|
||||
'exif:SubjectDistanceRange',
|
||||
'exif:ImageUniqueID',
|
||||
'exif:GPSVersionID',
|
||||
'exif:GPSLatitude',
|
||||
'exif:GPSLongitude',
|
||||
'exif:GPSAltitudeRef',
|
||||
'exif:GPSAltitude',
|
||||
'exif:GPSTimeStamp',
|
||||
'exif:GPSSatellites',
|
||||
'exif:GPSStatus',
|
||||
'exif:GPSMeasureMode',
|
||||
'exif:GPSDOP',
|
||||
'exif:GPSSpeedRef',
|
||||
'exif:GPSSpeed',
|
||||
'exif:GPSTrackRef',
|
||||
'exif:GPSTrack',
|
||||
'exif:GPSImgDirectionRef',
|
||||
'exif:GPSImgDirection',
|
||||
'exif:GPSMapDatum',
|
||||
'exif:GPSDestLatitude',
|
||||
'exif:GPSDestLongitude',
|
||||
'exif:GPSDestBearingRef',
|
||||
'exif:GPSDestBearing',
|
||||
'exif:GPSDestDistanceRef',
|
||||
'exif:GPSDestDistance',
|
||||
'exif:GPSProcessingMethod',
|
||||
'exif:GPSAreaInformation',
|
||||
'exif:GPSDifferential',
|
||||
'stDim:w',
|
||||
'stDim:h',
|
||||
'stDim:unit',
|
||||
'xapGImg:height',
|
||||
'xapGImg:width',
|
||||
'xapGImg:format',
|
||||
'xapGImg:image',
|
||||
'stEvt:action',
|
||||
'stEvt:instanceID',
|
||||
'stEvt:parameters',
|
||||
'stEvt:softwareAgent',
|
||||
'stEvt:when',
|
||||
'stRef:instanceID',
|
||||
'stRef:documentID',
|
||||
'stRef:versionID',
|
||||
'stRef:renditionClass',
|
||||
'stRef:renditionParams',
|
||||
'stRef:manager',
|
||||
'stRef:managerVariant',
|
||||
'stRef:manageTo',
|
||||
'stRef:manageUI',
|
||||
'stVer:comments',
|
||||
'stVer:event',
|
||||
'stVer:modifyDate',
|
||||
'stVer:modifier',
|
||||
'stVer:version',
|
||||
'stJob:name',
|
||||
'stJob:id',
|
||||
'stJob:url',
|
||||
// Exif Flash
|
||||
'exif:Fired',
|
||||
'exif:Return',
|
||||
'exif:Mode',
|
||||
'exif:Function',
|
||||
'exif:RedEyeMode',
|
||||
// Exif OECF/SFR
|
||||
'exif:Columns',
|
||||
'exif:Rows',
|
||||
'exif:Names',
|
||||
'exif:Values',
|
||||
// Exif CFAPattern
|
||||
'exif:Columns',
|
||||
'exif:Rows',
|
||||
'exif:Values',
|
||||
// Exif DeviceSettings
|
||||
'exif:Columns',
|
||||
'exif:Rows',
|
||||
'exif:Settings',
|
||||
);
|
||||
*/
|
||||
|
||||
/**
|
||||
* Global Variable: JPEG_Segment_Names
|
||||
*
|
||||
* The names of the JPEG segment markers, indexed by their marker number
|
||||
*/
|
||||
$GLOBALS['JPEG_Segment_Names'] = array(
|
||||
0x01 => 'TEM',
|
||||
0x02 => 'RES',
|
||||
0xC0 => 'SOF0',
|
||||
0xC1 => 'SOF1',
|
||||
0xC2 => 'SOF2',
|
||||
0xC3 => 'SOF4',
|
||||
0xC4 => 'DHT',
|
||||
0xC5 => 'SOF5',
|
||||
0xC6 => 'SOF6',
|
||||
0xC7 => 'SOF7',
|
||||
0xC8 => 'JPG',
|
||||
0xC9 => 'SOF9',
|
||||
0xCA => 'SOF10',
|
||||
0xCB => 'SOF11',
|
||||
0xCC => 'DAC',
|
||||
0xCD => 'SOF13',
|
||||
0xCE => 'SOF14',
|
||||
0xCF => 'SOF15',
|
||||
0xD0 => 'RST0',
|
||||
0xD1 => 'RST1',
|
||||
0xD2 => 'RST2',
|
||||
0xD3 => 'RST3',
|
||||
0xD4 => 'RST4',
|
||||
0xD5 => 'RST5',
|
||||
0xD6 => 'RST6',
|
||||
0xD7 => 'RST7',
|
||||
0xD8 => 'SOI',
|
||||
0xD9 => 'EOI',
|
||||
0xDA => 'SOS',
|
||||
0xDB => 'DQT',
|
||||
0xDC => 'DNL',
|
||||
0xDD => 'DRI',
|
||||
0xDE => 'DHP',
|
||||
0xDF => 'EXP',
|
||||
0xE0 => 'APP0',
|
||||
0xE1 => 'APP1',
|
||||
0xE2 => 'APP2',
|
||||
0xE3 => 'APP3',
|
||||
0xE4 => 'APP4',
|
||||
0xE5 => 'APP5',
|
||||
0xE6 => 'APP6',
|
||||
0xE7 => 'APP7',
|
||||
0xE8 => 'APP8',
|
||||
0xE9 => 'APP9',
|
||||
0xEA => 'APP10',
|
||||
0xEB => 'APP11',
|
||||
0xEC => 'APP12',
|
||||
0xED => 'APP13',
|
||||
0xEE => 'APP14',
|
||||
0xEF => 'APP15',
|
||||
0xF0 => 'JPG0',
|
||||
0xF1 => 'JPG1',
|
||||
0xF2 => 'JPG2',
|
||||
0xF3 => 'JPG3',
|
||||
0xF4 => 'JPG4',
|
||||
0xF5 => 'JPG5',
|
||||
0xF6 => 'JPG6',
|
||||
0xF7 => 'JPG7',
|
||||
0xF8 => 'JPG8',
|
||||
0xF9 => 'JPG9',
|
||||
0xFA => 'JPG10',
|
||||
0xFB => 'JPG11',
|
||||
0xFC => 'JPG12',
|
||||
0xFD => 'JPG13',
|
||||
0xFE => 'COM',
|
||||
);
|
223
api/getID3/write.apetag.php
Executable file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// write.apetag.php //
|
||||
// module for writing APE tags //
|
||||
// dependencies: module.tag.apetag.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true);
|
||||
|
||||
class getid3_write_apetag
|
||||
{
|
||||
|
||||
public $filename;
|
||||
public $tag_data;
|
||||
public $always_preserve_replaygain = true; // ReplayGain / MP3gain tags will be copied from old tag even if not passed in data
|
||||
public $warnings = array(); // any non-critical errors will be stored here
|
||||
public $errors = array(); // any critical errors will be stored here
|
||||
|
||||
public function getid3_write_apetag() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function WriteAPEtag() {
|
||||
// NOTE: All data passed to this function must be UTF-8 format
|
||||
|
||||
$getID3 = new getID3;
|
||||
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||
|
||||
if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['lyrics3']['tag_offset_end'])) {
|
||||
if ($ThisFileInfo['ape']['tag_offset_start'] >= $ThisFileInfo['lyrics3']['tag_offset_end']) {
|
||||
// Current APE tag between Lyrics3 and ID3v1/EOF
|
||||
// This break Lyrics3 functionality
|
||||
if (!$this->DeleteAPEtag()) {
|
||||
return false;
|
||||
}
|
||||
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->always_preserve_replaygain) {
|
||||
$ReplayGainTagsToPreserve = array('mp3gain_minmax', 'mp3gain_album_minmax', 'mp3gain_undo', 'replaygain_track_peak', 'replaygain_track_gain', 'replaygain_album_peak', 'replaygain_album_gain');
|
||||
foreach ($ReplayGainTagsToPreserve as $rg_key) {
|
||||
if (isset($ThisFileInfo['ape']['items'][strtolower($rg_key)]['data'][0]) && !isset($this->tag_data[strtoupper($rg_key)][0])) {
|
||||
$this->tag_data[strtoupper($rg_key)][0] = $ThisFileInfo['ape']['items'][strtolower($rg_key)]['data'][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($APEtag = $this->GenerateAPEtag()) {
|
||||
if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) {
|
||||
$oldignoreuserabort = ignore_user_abort(true);
|
||||
flock($fp, LOCK_EX);
|
||||
|
||||
$PostAPEdataOffset = $ThisFileInfo['avdataend'];
|
||||
if (isset($ThisFileInfo['ape']['tag_offset_end'])) {
|
||||
$PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['ape']['tag_offset_end']);
|
||||
}
|
||||
if (isset($ThisFileInfo['lyrics3']['tag_offset_start'])) {
|
||||
$PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['lyrics3']['tag_offset_start']);
|
||||
}
|
||||
fseek($fp, $PostAPEdataOffset, SEEK_SET);
|
||||
$PostAPEdata = '';
|
||||
if ($ThisFileInfo['filesize'] > $PostAPEdataOffset) {
|
||||
$PostAPEdata = fread($fp, $ThisFileInfo['filesize'] - $PostAPEdataOffset);
|
||||
}
|
||||
|
||||
fseek($fp, $PostAPEdataOffset, SEEK_SET);
|
||||
if (isset($ThisFileInfo['ape']['tag_offset_start'])) {
|
||||
fseek($fp, $ThisFileInfo['ape']['tag_offset_start'], SEEK_SET);
|
||||
}
|
||||
ftruncate($fp, ftell($fp));
|
||||
fwrite($fp, $APEtag, strlen($APEtag));
|
||||
if (!empty($PostAPEdata)) {
|
||||
fwrite($fp, $PostAPEdata, strlen($PostAPEdata));
|
||||
}
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
ignore_user_abort($oldignoreuserabort);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function DeleteAPEtag() {
|
||||
$getID3 = new getID3;
|
||||
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||
if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['ape']['tag_offset_end'])) {
|
||||
if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) {
|
||||
|
||||
flock($fp, LOCK_EX);
|
||||
$oldignoreuserabort = ignore_user_abort(true);
|
||||
|
||||
fseek($fp, $ThisFileInfo['ape']['tag_offset_end'], SEEK_SET);
|
||||
$DataAfterAPE = '';
|
||||
if ($ThisFileInfo['filesize'] > $ThisFileInfo['ape']['tag_offset_end']) {
|
||||
$DataAfterAPE = fread($fp, $ThisFileInfo['filesize'] - $ThisFileInfo['ape']['tag_offset_end']);
|
||||
}
|
||||
|
||||
ftruncate($fp, $ThisFileInfo['ape']['tag_offset_start']);
|
||||
fseek($fp, $ThisFileInfo['ape']['tag_offset_start'], SEEK_SET);
|
||||
|
||||
if (!empty($DataAfterAPE)) {
|
||||
fwrite($fp, $DataAfterAPE, strlen($DataAfterAPE));
|
||||
}
|
||||
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
ignore_user_abort($oldignoreuserabort);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function GenerateAPEtag() {
|
||||
// NOTE: All data passed to this function must be UTF-8 format
|
||||
|
||||
$items = array();
|
||||
if (!is_array($this->tag_data)) {
|
||||
return false;
|
||||
}
|
||||
foreach ($this->tag_data as $key => $arrayofvalues) {
|
||||
if (!is_array($arrayofvalues)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$valuestring = '';
|
||||
foreach ($arrayofvalues as $value) {
|
||||
$valuestring .= str_replace("\x00", '', $value)."\x00";
|
||||
}
|
||||
$valuestring = rtrim($valuestring, "\x00");
|
||||
|
||||
// Length of the assigned value in bytes
|
||||
$tagitem = getid3_lib::LittleEndian2String(strlen($valuestring), 4);
|
||||
|
||||
//$tagitem .= $this->GenerateAPEtagFlags(true, true, false, 0, false);
|
||||
$tagitem .= "\x00\x00\x00\x00";
|
||||
|
||||
$tagitem .= $this->CleanAPEtagItemKey($key)."\x00";
|
||||
$tagitem .= $valuestring;
|
||||
|
||||
$items[] = $tagitem;
|
||||
|
||||
}
|
||||
|
||||
return $this->GenerateAPEtagHeaderFooter($items, true).implode('', $items).$this->GenerateAPEtagHeaderFooter($items, false);
|
||||
}
|
||||
|
||||
public function GenerateAPEtagHeaderFooter(&$items, $isheader=false) {
|
||||
$tagdatalength = 0;
|
||||
foreach ($items as $itemdata) {
|
||||
$tagdatalength += strlen($itemdata);
|
||||
}
|
||||
|
||||
$APEheader = 'APETAGEX';
|
||||
$APEheader .= getid3_lib::LittleEndian2String(2000, 4);
|
||||
$APEheader .= getid3_lib::LittleEndian2String(32 + $tagdatalength, 4);
|
||||
$APEheader .= getid3_lib::LittleEndian2String(count($items), 4);
|
||||
$APEheader .= $this->GenerateAPEtagFlags(true, true, $isheader, 0, false);
|
||||
$APEheader .= str_repeat("\x00", 8);
|
||||
|
||||
return $APEheader;
|
||||
}
|
||||
|
||||
public function GenerateAPEtagFlags($header=true, $footer=true, $isheader=false, $encodingid=0, $readonly=false) {
|
||||
$APEtagFlags = array_fill(0, 4, 0);
|
||||
if ($header) {
|
||||
$APEtagFlags[0] |= 0x80; // Tag contains a header
|
||||
}
|
||||
if (!$footer) {
|
||||
$APEtagFlags[0] |= 0x40; // Tag contains no footer
|
||||
}
|
||||
if ($isheader) {
|
||||
$APEtagFlags[0] |= 0x20; // This is the header, not the footer
|
||||
}
|
||||
|
||||
// 0: Item contains text information coded in UTF-8
|
||||
// 1: Item contains binary information °)
|
||||
// 2: Item is a locator of external stored information °°)
|
||||
// 3: reserved
|
||||
$APEtagFlags[3] |= ($encodingid << 1);
|
||||
|
||||
if ($readonly) {
|
||||
$APEtagFlags[3] |= 0x01; // Tag or Item is Read Only
|
||||
}
|
||||
|
||||
return chr($APEtagFlags[3]).chr($APEtagFlags[2]).chr($APEtagFlags[1]).chr($APEtagFlags[0]);
|
||||
}
|
||||
|
||||
public function CleanAPEtagItemKey($itemkey) {
|
||||
$itemkey = preg_replace("#[^\x20-\x7E]#i", '', $itemkey);
|
||||
|
||||
// http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html
|
||||
switch (strtoupper($itemkey)) {
|
||||
case 'EAN/UPC':
|
||||
case 'ISBN':
|
||||
case 'LC':
|
||||
case 'ISRC':
|
||||
$itemkey = strtoupper($itemkey);
|
||||
break;
|
||||
|
||||
default:
|
||||
$itemkey = ucwords($itemkey);
|
||||
break;
|
||||
}
|
||||
return $itemkey;
|
||||
|
||||
}
|
||||
|
||||
}
|
136
api/getID3/write.id3v1.php
Executable file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// write.id3v1.php //
|
||||
// module for writing ID3v1 tags //
|
||||
// dependencies: module.tag.id3v1.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
|
||||
|
||||
class getid3_write_id3v1
|
||||
{
|
||||
public $filename;
|
||||
public $filesize;
|
||||
public $tag_data;
|
||||
public $warnings = array(); // any non-critical errors will be stored here
|
||||
public $errors = array(); // any critical errors will be stored here
|
||||
|
||||
public function getid3_write_id3v1() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function WriteID3v1() {
|
||||
// File MUST be writeable - CHMOD(646) at least
|
||||
if (!empty($this->filename) && is_readable($this->filename) && 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
|
||||
}
|
||||
$this->tag_data['track'] = (isset($this->tag_data['track']) ? $this->tag_data['track'] : (isset($this->tag_data['track_number']) ? $this->tag_data['track_number'] : (isset($this->tag_data['tracknumber']) ? $this->tag_data['tracknumber'] : '')));
|
||||
|
||||
$new_id3v1_tag_data = getid3_id3v1::GenerateID3v1Tag(
|
||||
(isset($this->tag_data['title'] ) ? $this->tag_data['title'] : ''),
|
||||
(isset($this->tag_data['artist'] ) ? $this->tag_data['artist'] : ''),
|
||||
(isset($this->tag_data['album'] ) ? $this->tag_data['album'] : ''),
|
||||
(isset($this->tag_data['year'] ) ? $this->tag_data['year'] : ''),
|
||||
(isset($this->tag_data['genreid']) ? $this->tag_data['genreid'] : ''),
|
||||
(isset($this->tag_data['comment']) ? $this->tag_data['comment'] : ''),
|
||||
(isset($this->tag_data['track'] ) ? $this->tag_data['track'] : ''));
|
||||
fwrite($fp_source, $new_id3v1_tag_data, 128);
|
||||
fclose($fp_source);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
$this->errors[] = 'Could not fopen('.$this->filename.', "r+b")';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$this->errors[] = 'File is not writeable: '.$this->filename;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function FixID3v1Padding() {
|
||||
// ID3v1 data is supposed to be padded with NULL characters, but some taggers incorrectly use spaces
|
||||
// This function rewrites the ID3v1 tag with correct padding
|
||||
|
||||
// Initialize getID3 engine
|
||||
$getID3 = new getID3;
|
||||
$getID3->option_tag_id3v2 = false;
|
||||
$getID3->option_tag_apetag = false;
|
||||
$getID3->option_tags_html = false;
|
||||
$getID3->option_extra_info = false;
|
||||
$getID3->option_tag_id3v1 = true;
|
||||
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||
if (isset($ThisFileInfo['tags']['id3v1'])) {
|
||||
foreach ($ThisFileInfo['tags']['id3v1'] as $key => $value) {
|
||||
$id3v1data[$key] = implode(',', $value);
|
||||
}
|
||||
$this->tag_data = $id3v1data;
|
||||
return $this->WriteID3v1();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function RemoveID3v1() {
|
||||
// File MUST be writeable - CHMOD(646) at least
|
||||
if (!empty($this->filename) && is_readable($this->filename) && 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;
|
||||
}
|
||||
|
||||
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
|
||||
$getID3 = new getID3;
|
||||
$getID3->option_tag_id3v1 = false;
|
||||
$getID3->option_tag_id3v2 = false;
|
||||
$getID3->option_tag_apetag = false;
|
||||
$getID3->option_tags_html = false;
|
||||
$getID3->option_extra_info = false;
|
||||
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||
$this->filesize = $ThisFileInfo['filesize'];
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
2049
api/getID3/write.id3v2.php
Executable file
71
api/getID3/write.lyrics3.php
Executable file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// write.lyrics3.php //
|
||||
// module for writing Lyrics3 tags //
|
||||
// dependencies: module.tag.lyrics3.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_write_lyrics3
|
||||
{
|
||||
public $filename;
|
||||
public $tag_data;
|
||||
//public $lyrics3_version = 2; // 1 or 2
|
||||
public $warnings = array(); // any non-critical errors will be stored here
|
||||
public $errors = array(); // any critical errors will be stored here
|
||||
|
||||
public function getid3_write_lyrics3() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function WriteLyrics3() {
|
||||
$this->errors[] = 'WriteLyrics3() not yet functional - cannot write Lyrics3';
|
||||
return false;
|
||||
}
|
||||
public function DeleteLyrics3() {
|
||||
// Initialize getID3 engine
|
||||
$getID3 = new getID3;
|
||||
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||
if (isset($ThisFileInfo['lyrics3']['tag_offset_start']) && isset($ThisFileInfo['lyrics3']['tag_offset_end'])) {
|
||||
if (is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) {
|
||||
|
||||
flock($fp, LOCK_EX);
|
||||
$oldignoreuserabort = ignore_user_abort(true);
|
||||
|
||||
fseek($fp, $ThisFileInfo['lyrics3']['tag_offset_end'], SEEK_SET);
|
||||
$DataAfterLyrics3 = '';
|
||||
if ($ThisFileInfo['filesize'] > $ThisFileInfo['lyrics3']['tag_offset_end']) {
|
||||
$DataAfterLyrics3 = fread($fp, $ThisFileInfo['filesize'] - $ThisFileInfo['lyrics3']['tag_offset_end']);
|
||||
}
|
||||
|
||||
ftruncate($fp, $ThisFileInfo['lyrics3']['tag_offset_start']);
|
||||
|
||||
if (!empty($DataAfterLyrics3)) {
|
||||
fseek($fp, $ThisFileInfo['lyrics3']['tag_offset_start'], SEEK_SET);
|
||||
fwrite($fp, $DataAfterLyrics3, strlen($DataAfterLyrics3));
|
||||
}
|
||||
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
ignore_user_abort($oldignoreuserabort);
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
$this->errors[] = 'Cannot fopen('.$this->filename.', "a+b")';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// no Lyrics3 present
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
161
api/getID3/write.metaflac.php
Executable file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// write.metaflac.php //
|
||||
// module for writing metaflac tags //
|
||||
// dependencies: /helperapps/metaflac.exe //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_write_metaflac
|
||||
{
|
||||
|
||||
public $filename;
|
||||
public $tag_data;
|
||||
public $warnings = array(); // any non-critical errors will be stored here
|
||||
public $errors = array(); // any critical errors will be stored here
|
||||
|
||||
public function getid3_write_metaflac() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function WriteMetaFLAC() {
|
||||
|
||||
if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
|
||||
$this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call metaflac, tags not written';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create file with new comments
|
||||
$tempcommentsfilename = tempnam(GETID3_TEMP_DIR, 'getID3');
|
||||
if (is_writable($tempcommentsfilename) && is_file($tempcommentsfilename) && ($fpcomments = fopen($tempcommentsfilename, 'wb'))) {
|
||||
foreach ($this->tag_data as $key => $value) {
|
||||
foreach ($value as $commentdata) {
|
||||
fwrite($fpcomments, $this->CleanmetaflacName($key).'='.$commentdata."\n");
|
||||
}
|
||||
}
|
||||
fclose($fpcomments);
|
||||
|
||||
} else {
|
||||
$this->errors[] = 'failed to open temporary tags file, tags not written - fopen("'.$tempcommentsfilename.'", "wb")';
|
||||
return false;
|
||||
}
|
||||
|
||||
$oldignoreuserabort = ignore_user_abort(true);
|
||||
if (GETID3_OS_ISWINDOWS) {
|
||||
|
||||
if (file_exists(GETID3_HELPERAPPSDIR.'metaflac.exe')) {
|
||||
//$commandline = '"'.GETID3_HELPERAPPSDIR.'metaflac.exe" --no-utf8-convert --remove-all-tags --import-tags-from="'.$tempcommentsfilename.'" "'.str_replace('/', '\\', $this->filename).'"';
|
||||
// metaflac works fine if you copy-paste the above commandline into a command prompt,
|
||||
// but refuses to work with `backtick` if there are "doublequotes" present around BOTH
|
||||
// the metaflac pathname and the target filename. For whatever reason...??
|
||||
// The solution is simply ensure that the metaflac pathname has no spaces,
|
||||
// and therefore does not need to be quoted
|
||||
|
||||
// On top of that, if error messages are not always captured properly under Windows
|
||||
// To at least see if there was a problem, compare file modification timestamps before and after writing
|
||||
clearstatcache();
|
||||
$timestampbeforewriting = filemtime($this->filename);
|
||||
|
||||
$commandline = GETID3_HELPERAPPSDIR.'metaflac.exe --no-utf8-convert --remove-all-tags --import-tags-from='.escapeshellarg($tempcommentsfilename).' '.escapeshellarg($this->filename).' 2>&1';
|
||||
$metaflacError = `$commandline`;
|
||||
|
||||
if (empty($metaflacError)) {
|
||||
clearstatcache();
|
||||
if ($timestampbeforewriting == filemtime($this->filename)) {
|
||||
$metaflacError = 'File modification timestamp has not changed - it looks like the tags were not written';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$metaflacError = 'metaflac.exe not found in '.GETID3_HELPERAPPSDIR;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// It's simpler on *nix
|
||||
$commandline = 'metaflac --no-utf8-convert --remove-all-tags --import-tags-from='.escapeshellarg($tempcommentsfilename).' '.escapeshellarg($this->filename).' 2>&1';
|
||||
$metaflacError = `$commandline`;
|
||||
|
||||
}
|
||||
|
||||
// Remove temporary comments file
|
||||
unlink($tempcommentsfilename);
|
||||
ignore_user_abort($oldignoreuserabort);
|
||||
|
||||
if (!empty($metaflacError)) {
|
||||
|
||||
$this->errors[] = 'System call to metaflac failed with this message returned: '."\n\n".$metaflacError;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function DeleteMetaFLAC() {
|
||||
|
||||
if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
|
||||
$this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call metaflac, tags not deleted';
|
||||
return false;
|
||||
}
|
||||
|
||||
$oldignoreuserabort = ignore_user_abort(true);
|
||||
if (GETID3_OS_ISWINDOWS) {
|
||||
|
||||
if (file_exists(GETID3_HELPERAPPSDIR.'metaflac.exe')) {
|
||||
// To at least see if there was a problem, compare file modification timestamps before and after writing
|
||||
clearstatcache();
|
||||
$timestampbeforewriting = filemtime($this->filename);
|
||||
|
||||
$commandline = GETID3_HELPERAPPSDIR.'metaflac.exe --remove-all-tags "'.$this->filename.'" 2>&1';
|
||||
$metaflacError = `$commandline`;
|
||||
|
||||
if (empty($metaflacError)) {
|
||||
clearstatcache();
|
||||
if ($timestampbeforewriting == filemtime($this->filename)) {
|
||||
$metaflacError = 'File modification timestamp has not changed - it looks like the tags were not deleted';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$metaflacError = 'metaflac.exe not found in '.GETID3_HELPERAPPSDIR;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// It's simpler on *nix
|
||||
$commandline = 'metaflac --remove-all-tags "'.$this->filename.'" 2>&1';
|
||||
$metaflacError = `$commandline`;
|
||||
|
||||
}
|
||||
|
||||
ignore_user_abort($oldignoreuserabort);
|
||||
|
||||
if (!empty($metaflacError)) {
|
||||
$this->errors[] = 'System call to metaflac failed with this message returned: '."\n\n".$metaflacError;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function CleanmetaflacName($originalcommentname) {
|
||||
// A case-insensitive field name that may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded.
|
||||
// ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through
|
||||
// 0x7A inclusive (a-z).
|
||||
|
||||
// replace invalid chars with a space, return uppercase text
|
||||
// Thanks Chris Bolt <chris-getid3Øbolt*cx> for improving this function
|
||||
// note: *reg_replace() replaces nulls with empty string (not space)
|
||||
return strtoupper(preg_replace('#[^ -<>-}]#', ' ', str_replace("\x00", ' ', $originalcommentname)));
|
||||
|
||||
}
|
||||
|
||||
}
|
613
api/getID3/write.php
Executable file
@ -0,0 +1,613 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// //
|
||||
// write.php //
|
||||
// module for writing tags (APEv2, ID3v1, ID3v2) //
|
||||
// dependencies: getid3.lib.php //
|
||||
// write.apetag.php (optional) //
|
||||
// write.id3v1.php (optional) //
|
||||
// write.id3v2.php (optional) //
|
||||
// write.vorbiscomment.php (optional) //
|
||||
// write.metaflac.php (optional) //
|
||||
// write.lyrics3.php (optional) //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
if (!defined('GETID3_INCLUDEPATH')) {
|
||||
throw new Exception('getid3.php MUST be included before calling getid3_writetags');
|
||||
}
|
||||
if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
|
||||
throw new Exception('write.php depends on getid3.lib.php, which is missing.');
|
||||
}
|
||||
|
||||
|
||||
// NOTES:
|
||||
//
|
||||
// You should pass data here with standard field names as follows:
|
||||
// * TITLE
|
||||
// * ARTIST
|
||||
// * ALBUM
|
||||
// * TRACKNUMBER
|
||||
// * COMMENT
|
||||
// * GENRE
|
||||
// * YEAR
|
||||
// * ATTACHED_PICTURE (ID3v2 only)
|
||||
//
|
||||
// http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html
|
||||
// The APEv2 Tag Items Keys definition says "TRACK" is correct but foobar2000 uses "TRACKNUMBER" instead
|
||||
// Pass data here as "TRACKNUMBER" for compatability with all formats
|
||||
|
||||
|
||||
class getid3_writetags
|
||||
{
|
||||
// public
|
||||
public $filename; // absolute filename of file to write tags to
|
||||
public $tagformats = array(); // array of tag formats to write ('id3v1', 'id3v2.2', 'id2v2.3', 'id3v2.4', 'ape', 'vorbiscomment', 'metaflac', 'real')
|
||||
public $tag_data = array(array()); // 2-dimensional array of tag data (ex: $data['ARTIST'][0] = 'Elvis')
|
||||
public $tag_encoding = 'ISO-8859-1'; // text encoding used for tag data ('ISO-8859-1', 'UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE', )
|
||||
public $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
|
||||
public $remove_other_tags = false; // if true will erase remove all existing tags and only write those passed in $tagformats; if false will ignore any tags not mentioned in $tagformats
|
||||
|
||||
public $id3v2_tag_language = 'eng'; // ISO-639-2 3-character language code needed for some ID3v2 frames (http://www.id3.org/iso639-2.html)
|
||||
public $id3v2_paddedlength = 4096; // minimum length of ID3v2 tags (will be padded to this length if tag data is shorter)
|
||||
|
||||
public $warnings = array(); // any non-critical errors will be stored here
|
||||
public $errors = array(); // any critical errors will be stored here
|
||||
|
||||
// private
|
||||
private $ThisFileInfo; // analysis of file before writing
|
||||
|
||||
public function getid3_writetags() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function WriteTags() {
|
||||
|
||||
if (empty($this->filename)) {
|
||||
$this->errors[] = 'filename is undefined in getid3_writetags';
|
||||
return false;
|
||||
} elseif (!file_exists($this->filename)) {
|
||||
$this->errors[] = 'filename set to non-existant file "'.$this->filename.'" in getid3_writetags';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_array($this->tagformats)) {
|
||||
$this->errors[] = 'tagformats must be an array in getid3_writetags';
|
||||
return false;
|
||||
}
|
||||
|
||||
$TagFormatsToRemove = array();
|
||||
if (filesize($this->filename) == 0) {
|
||||
|
||||
// empty file special case - allow any tag format, don't check existing format
|
||||
// could be useful if you want to generate tag data for a non-existant file
|
||||
$this->ThisFileInfo = array('fileformat'=>'');
|
||||
$AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3');
|
||||
|
||||
} else {
|
||||
|
||||
$getID3 = new getID3;
|
||||
$getID3->encoding = $this->tag_encoding;
|
||||
$this->ThisFileInfo = $getID3->analyze($this->filename);
|
||||
|
||||
// check for what file types are allowed on this fileformat
|
||||
switch (isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '') {
|
||||
case 'mp3':
|
||||
case 'mp2':
|
||||
case 'mp1':
|
||||
case 'riff': // maybe not officially, but people do it anyway
|
||||
$AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3');
|
||||
break;
|
||||
|
||||
case 'mpc':
|
||||
$AllowedTagFormats = array('ape');
|
||||
break;
|
||||
|
||||
case 'flac':
|
||||
$AllowedTagFormats = array('metaflac');
|
||||
break;
|
||||
|
||||
case 'real':
|
||||
$AllowedTagFormats = array('real');
|
||||
break;
|
||||
|
||||
case 'ogg':
|
||||
switch (isset($this->ThisFileInfo['audio']['dataformat']) ? $this->ThisFileInfo['audio']['dataformat'] : '') {
|
||||
case 'flac':
|
||||
//$AllowedTagFormats = array('metaflac');
|
||||
$this->errors[] = 'metaflac is not (yet) compatible with OggFLAC files';
|
||||
return false;
|
||||
break;
|
||||
case 'vorbis':
|
||||
$AllowedTagFormats = array('vorbiscomment');
|
||||
break;
|
||||
default:
|
||||
$this->errors[] = 'metaflac is not (yet) compatible with Ogg files other than OggVorbis';
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
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'] : '');
|
||||
$errormessage .= (isset($this->ThisFileInfo['audio']['dataformat']) ? '.'.$this->ThisFileInfo['audio']['dataformat'] : '');
|
||||
$errormessage .= '" files';
|
||||
$this->errors[] = $errormessage;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// List of other tag formats, removed if requested
|
||||
if ($this->remove_other_tags) {
|
||||
foreach ($AllowedTagFormats as $AllowedTagFormat) {
|
||||
switch ($AllowedTagFormat) {
|
||||
case 'id3v2.2':
|
||||
case 'id3v2.3':
|
||||
case 'id3v2.4':
|
||||
if (!in_array('id3v2', $TagFormatsToRemove) && !in_array('id3v2.2', $this->tagformats) && !in_array('id3v2.3', $this->tagformats) && !in_array('id3v2.4', $this->tagformats)) {
|
||||
$TagFormatsToRemove[] = 'id3v2';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!in_array($AllowedTagFormat, $this->tagformats)) {
|
||||
$TagFormatsToRemove[] = $AllowedTagFormat;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$WritingFilesToInclude = array_merge($this->tagformats, $TagFormatsToRemove);
|
||||
|
||||
// Check for required include files and include them
|
||||
foreach ($WritingFilesToInclude as $tagformat) {
|
||||
switch ($tagformat) {
|
||||
case 'ape':
|
||||
$GETID3_ERRORARRAY = &$this->errors;
|
||||
if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.apetag.php', __FILE__, false)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'id3v1':
|
||||
case 'lyrics3':
|
||||
case 'vorbiscomment':
|
||||
case 'metaflac':
|
||||
case 'real':
|
||||
$GETID3_ERRORARRAY = &$this->errors;
|
||||
if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.'.$tagformat.'.php', __FILE__, false)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'id3v2.2':
|
||||
case 'id3v2.3':
|
||||
case 'id3v2.4':
|
||||
case 'id3v2':
|
||||
$GETID3_ERRORARRAY = &$this->errors;
|
||||
if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.id3v2.php', __FILE__, false)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->errors[] = 'unknown tag format "'.$tagformat.'" in $tagformats in WriteTags()';
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Validation of supplied data
|
||||
if (!is_array($this->tag_data)) {
|
||||
$this->errors[] = '$this->tag_data is not an array in WriteTags()';
|
||||
return false;
|
||||
}
|
||||
// convert supplied data array keys to upper case, if they're not already
|
||||
foreach ($this->tag_data as $tag_key => $tag_array) {
|
||||
if (strtoupper($tag_key) !== $tag_key) {
|
||||
$this->tag_data[strtoupper($tag_key)] = $this->tag_data[$tag_key];
|
||||
unset($this->tag_data[$tag_key]);
|
||||
}
|
||||
}
|
||||
// convert source data array keys to upper case, if they're not already
|
||||
if (!empty($this->ThisFileInfo['tags'])) {
|
||||
foreach ($this->ThisFileInfo['tags'] as $tag_format => $tag_data_array) {
|
||||
foreach ($tag_data_array as $tag_key => $tag_array) {
|
||||
if (strtoupper($tag_key) !== $tag_key) {
|
||||
$this->ThisFileInfo['tags'][$tag_format][strtoupper($tag_key)] = $this->ThisFileInfo['tags'][$tag_format][$tag_key];
|
||||
unset($this->ThisFileInfo['tags'][$tag_format][$tag_key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert "TRACK" to "TRACKNUMBER" (if needed) for compatability with all formats
|
||||
if (isset($this->tag_data['TRACK']) && !isset($this->tag_data['TRACKNUMBER'])) {
|
||||
$this->tag_data['TRACKNUMBER'] = $this->tag_data['TRACK'];
|
||||
unset($this->tag_data['TRACK']);
|
||||
}
|
||||
|
||||
// Remove all other tag formats, if requested
|
||||
if ($this->remove_other_tags) {
|
||||
$this->DeleteTags($TagFormatsToRemove);
|
||||
}
|
||||
|
||||
// Write data for each tag format
|
||||
foreach ($this->tagformats as $tagformat) {
|
||||
$success = false; // overridden if tag writing is successful
|
||||
switch ($tagformat) {
|
||||
case 'ape':
|
||||
$ape_writer = new getid3_write_apetag;
|
||||
if (($ape_writer->tag_data = $this->FormatDataForAPE()) !== false) {
|
||||
$ape_writer->filename = $this->filename;
|
||||
if (($success = $ape_writer->WriteAPEtag()) === false) {
|
||||
$this->errors[] = 'WriteAPEtag() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $ape_writer->errors)))).'</li></ul></pre>';
|
||||
}
|
||||
} else {
|
||||
$this->errors[] = 'FormatDataForAPE() failed';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'id3v1':
|
||||
$id3v1_writer = new getid3_write_id3v1;
|
||||
if (($id3v1_writer->tag_data = $this->FormatDataForID3v1()) !== false) {
|
||||
$id3v1_writer->filename = $this->filename;
|
||||
if (($success = $id3v1_writer->WriteID3v1()) === false) {
|
||||
$this->errors[] = 'WriteID3v1() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $id3v1_writer->errors)))).'</li></ul></pre>';
|
||||
}
|
||||
} else {
|
||||
$this->errors[] = 'FormatDataForID3v1() failed';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'id3v2.2':
|
||||
case 'id3v2.3':
|
||||
case 'id3v2.4':
|
||||
$id3v2_writer = new getid3_write_id3v2;
|
||||
$id3v2_writer->majorversion = intval(substr($tagformat, -1));
|
||||
$id3v2_writer->paddedlength = $this->id3v2_paddedlength;
|
||||
if (($id3v2_writer->tag_data = $this->FormatDataForID3v2($id3v2_writer->majorversion)) !== false) {
|
||||
$id3v2_writer->filename = $this->filename;
|
||||
if (($success = $id3v2_writer->WriteID3v2()) === false) {
|
||||
$this->errors[] = 'WriteID3v2() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $id3v2_writer->errors)))).'</li></ul></pre>';
|
||||
}
|
||||
} else {
|
||||
$this->errors[] = 'FormatDataForID3v2() failed';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'vorbiscomment':
|
||||
$vorbiscomment_writer = new getid3_write_vorbiscomment;
|
||||
if (($vorbiscomment_writer->tag_data = $this->FormatDataForVorbisComment()) !== false) {
|
||||
$vorbiscomment_writer->filename = $this->filename;
|
||||
if (($success = $vorbiscomment_writer->WriteVorbisComment()) === false) {
|
||||
$this->errors[] = 'WriteVorbisComment() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $vorbiscomment_writer->errors)))).'</li></ul></pre>';
|
||||
}
|
||||
} else {
|
||||
$this->errors[] = 'FormatDataForVorbisComment() failed';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'metaflac':
|
||||
$metaflac_writer = new getid3_write_metaflac;
|
||||
if (($metaflac_writer->tag_data = $this->FormatDataForMetaFLAC()) !== false) {
|
||||
$metaflac_writer->filename = $this->filename;
|
||||
if (($success = $metaflac_writer->WriteMetaFLAC()) === false) {
|
||||
$this->errors[] = 'WriteMetaFLAC() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $metaflac_writer->errors)))).'</li></ul></pre>';
|
||||
}
|
||||
} else {
|
||||
$this->errors[] = 'FormatDataForMetaFLAC() failed';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'real':
|
||||
$real_writer = new getid3_write_real;
|
||||
if (($real_writer->tag_data = $this->FormatDataForReal()) !== false) {
|
||||
$real_writer->filename = $this->filename;
|
||||
if (($success = $real_writer->WriteReal()) === false) {
|
||||
$this->errors[] = 'WriteReal() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $real_writer->errors)))).'</li></ul></pre>';
|
||||
}
|
||||
} else {
|
||||
$this->errors[] = 'FormatDataForReal() failed';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->errors[] = 'Invalid tag format to write: "'.$tagformat.'"';
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
if (!$success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function DeleteTags($TagFormatsToDelete) {
|
||||
foreach ($TagFormatsToDelete as $DeleteTagFormat) {
|
||||
$success = false; // overridden if tag deletion is successful
|
||||
switch ($DeleteTagFormat) {
|
||||
case 'id3v1':
|
||||
$id3v1_writer = new getid3_write_id3v1;
|
||||
$id3v1_writer->filename = $this->filename;
|
||||
if (($success = $id3v1_writer->RemoveID3v1()) === false) {
|
||||
$this->errors[] = 'RemoveID3v1() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v1_writer->errors)).'</LI></UL></PRE>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'id3v2':
|
||||
$id3v2_writer = new getid3_write_id3v2;
|
||||
$id3v2_writer->filename = $this->filename;
|
||||
if (($success = $id3v2_writer->RemoveID3v2()) === false) {
|
||||
$this->errors[] = 'RemoveID3v2() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v2_writer->errors)).'</LI></UL></PRE>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ape':
|
||||
$ape_writer = new getid3_write_apetag;
|
||||
$ape_writer->filename = $this->filename;
|
||||
if (($success = $ape_writer->DeleteAPEtag()) === false) {
|
||||
$this->errors[] = 'DeleteAPEtag() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $ape_writer->errors)).'</LI></UL></PRE>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'vorbiscomment':
|
||||
$vorbiscomment_writer = new getid3_write_vorbiscomment;
|
||||
$vorbiscomment_writer->filename = $this->filename;
|
||||
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: "'.$tagformat.'"';
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
if (!$success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
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. Will be fixed in the near future, check www.getid3.org for a newer version.');
|
||||
if (!isset($this->ThisFileInfo['tags'][$TagFormat])) {
|
||||
return false;
|
||||
}
|
||||
$tag_data = array_merge_recursive($tag_data, $this->ThisFileInfo['tags'][$TagFormat]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function FormatDataForAPE() {
|
||||
$ape_tag_data = array();
|
||||
foreach ($this->tag_data as $tag_key => $valuearray) {
|
||||
switch ($tag_key) {
|
||||
case 'ATTACHED_PICTURE':
|
||||
// ATTACHED_PICTURE is ID3v2 only - ignore
|
||||
$this->warnings[] = '$data['.$tag_key.'] is assumed to be ID3v2 APIC data - NOT written to APE tag';
|
||||
break;
|
||||
|
||||
default:
|
||||
foreach ($valuearray as $key => $value) {
|
||||
if (is_string($value) || is_numeric($value)) {
|
||||
$ape_tag_data[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
|
||||
} else {
|
||||
$this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to APE tag';
|
||||
unset($ape_tag_data[$tag_key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->MergeExistingTagData('ape', $ape_tag_data);
|
||||
return $ape_tag_data;
|
||||
}
|
||||
|
||||
|
||||
public function FormatDataForID3v1() {
|
||||
$tag_data_id3v1['genreid'] = 255;
|
||||
if (!empty($this->tag_data['GENRE'])) {
|
||||
foreach ($this->tag_data['GENRE'] as $key => $value) {
|
||||
if (getid3_id3v1::LookupGenreID($value) !== false) {
|
||||
$tag_data_id3v1['genreid'] = getid3_id3v1::LookupGenreID($value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$tag_data_id3v1['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array())));
|
||||
$tag_data_id3v1['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array())));
|
||||
$tag_data_id3v1['album'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ALBUM'] ) ? $this->tag_data['ALBUM'] : array())));
|
||||
$tag_data_id3v1['year'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['YEAR'] ) ? $this->tag_data['YEAR'] : array())));
|
||||
$tag_data_id3v1['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COMMENT'] ) ? $this->tag_data['COMMENT'] : array())));
|
||||
$tag_data_id3v1['track'] = intval(getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TRACKNUMBER']) ? $this->tag_data['TRACKNUMBER'] : array()))));
|
||||
if ($tag_data_id3v1['track'] <= 0) {
|
||||
$tag_data_id3v1['track'] = '';
|
||||
}
|
||||
|
||||
$this->MergeExistingTagData('id3v1', $tag_data_id3v1);
|
||||
return $tag_data_id3v1;
|
||||
}
|
||||
|
||||
public function FormatDataForID3v2($id3v2_majorversion) {
|
||||
$tag_data_id3v2 = array();
|
||||
|
||||
$ID3v2_text_encoding_lookup[2] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
|
||||
$ID3v2_text_encoding_lookup[3] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
|
||||
$ID3v2_text_encoding_lookup[4] = array('ISO-8859-1'=>0, 'UTF-16'=>1, 'UTF-16BE'=>2, 'UTF-8'=>3);
|
||||
foreach ($this->tag_data as $tag_key => $valuearray) {
|
||||
$ID3v2_framename = getid3_write_id3v2::ID3v2ShortFrameNameLookup($id3v2_majorversion, $tag_key);
|
||||
switch ($ID3v2_framename) {
|
||||
case 'APIC':
|
||||
foreach ($valuearray as $key => $apic_data_array) {
|
||||
if (isset($apic_data_array['data']) &&
|
||||
isset($apic_data_array['picturetypeid']) &&
|
||||
isset($apic_data_array['description']) &&
|
||||
isset($apic_data_array['mime'])) {
|
||||
$tag_data_id3v2['APIC'][] = $apic_data_array;
|
||||
} else {
|
||||
$this->errors[] = 'ID3v2 APIC data is not properly structured';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '':
|
||||
$this->errors[] = 'ID3v2: Skipping "'.$tag_key.'" because cannot match it to a known ID3v2 frame type';
|
||||
// some other data type, don't know how to handle it, ignore it
|
||||
break;
|
||||
|
||||
default:
|
||||
// most other (text) frames can be copied over as-is
|
||||
foreach ($valuearray as $key => $value) {
|
||||
if (isset($ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding])) {
|
||||
// source encoding is valid in ID3v2 - use it with no conversion
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = $ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding];
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
|
||||
} else {
|
||||
// source encoding is NOT valid in ID3v2 - convert it to an ID3v2-valid encoding first
|
||||
if ($id3v2_majorversion < 4) {
|
||||
// convert data from other encoding to UTF-16 (with BOM)
|
||||
// note: some software, notably Windows Media Player and iTunes are broken and treat files tagged with UTF-16BE (with BOM) as corrupt
|
||||
// therefore we force data to UTF-16LE and manually prepend the BOM
|
||||
$ID3v2_tag_data_converted = false;
|
||||
if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'ISO-8859-1')) {
|
||||
// great, leave data as-is for minimum compatability problems
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0;
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
|
||||
$ID3v2_tag_data_converted = true;
|
||||
}
|
||||
if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'UTF-8')) {
|
||||
do {
|
||||
// if UTF-8 string does not include any characters above chr(127) then it is identical to ISO-8859-1
|
||||
for ($i = 0; $i < strlen($value); $i++) {
|
||||
if (ord($value{$i}) > 127) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0;
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
|
||||
$ID3v2_tag_data_converted = true;
|
||||
} while (false);
|
||||
}
|
||||
if (!$ID3v2_tag_data_converted) {
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 1;
|
||||
//$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
|
||||
$ID3v2_tag_data_converted = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
// convert data from other encoding to UTF-8
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 3;
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
|
||||
}
|
||||
}
|
||||
|
||||
// These values are not needed for all frame types, but if they're not used no matter
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['description'] = '';
|
||||
$tag_data_id3v2[$ID3v2_framename][$key]['language'] = $this->id3v2_tag_language;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->MergeExistingTagData('id3v2', $tag_data_id3v2);
|
||||
return $tag_data_id3v2;
|
||||
}
|
||||
|
||||
public function FormatDataForVorbisComment() {
|
||||
$tag_data_vorbiscomment = $this->tag_data;
|
||||
|
||||
// check for multi-line comment values - split out to multiple comments if neccesary
|
||||
// and convert data to UTF-8 strings
|
||||
foreach ($tag_data_vorbiscomment as $tag_key => $valuearray) {
|
||||
foreach ($valuearray as $key => $value) {
|
||||
str_replace("\r", "\n", $value);
|
||||
if (strstr($value, "\n")) {
|
||||
unset($tag_data_vorbiscomment[$tag_key][$key]);
|
||||
$multilineexploded = explode("\n", $value);
|
||||
foreach ($multilineexploded as $newcomment) {
|
||||
if (strlen(trim($newcomment)) > 0) {
|
||||
$tag_data_vorbiscomment[$tag_key][] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $newcomment);
|
||||
}
|
||||
}
|
||||
} elseif (is_string($value) || is_numeric($value)) {
|
||||
$tag_data_vorbiscomment[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
|
||||
} else {
|
||||
$this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to VorbisComment tag';
|
||||
unset($tag_data_vorbiscomment[$tag_key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->MergeExistingTagData('vorbiscomment', $tag_data_vorbiscomment);
|
||||
return $tag_data_vorbiscomment;
|
||||
}
|
||||
|
||||
public function FormatDataForMetaFLAC() {
|
||||
// FLAC & OggFLAC use VorbisComments same as OggVorbis
|
||||
// but require metaflac to do the writing rather than vorbiscomment
|
||||
return $this->FormatDataForVorbisComment();
|
||||
}
|
||||
|
||||
public function FormatDataForReal() {
|
||||
$tag_data_real['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array())));
|
||||
$tag_data_real['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array())));
|
||||
$tag_data_real['copyright'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COPYRIGHT']) ? $this->tag_data['COPYRIGHT'] : array())));
|
||||
$tag_data_real['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COMMENT'] ) ? $this->tag_data['COMMENT'] : array())));
|
||||
|
||||
$this->MergeExistingTagData('real', $tag_data_real);
|
||||
return $tag_data_real;
|
||||
}
|
||||
|
||||
}
|
273
api/getID3/write.real.php
Executable file
@ -0,0 +1,273 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// write.real.php //
|
||||
// module for writing RealAudio/RealVideo tags //
|
||||
// dependencies: module.tag.real.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
class getid3_write_real
|
||||
{
|
||||
public $filename;
|
||||
public $tag_data = array();
|
||||
public $fread_buffer_size = 32768; // read buffer size in bytes
|
||||
public $warnings = array(); // any non-critical errors will be stored here
|
||||
public $errors = array(); // any critical errors will be stored here
|
||||
public $paddedlength = 512; // minimum length of CONT tag in bytes
|
||||
|
||||
public function getid3_write_real() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function WriteReal() {
|
||||
// File MUST be writeable - CHMOD(646) at least
|
||||
if (is_writeable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) {
|
||||
|
||||
// Initialize getID3 engine
|
||||
$getID3 = new getID3;
|
||||
$OldThisFileInfo = $getID3->analyze($this->filename);
|
||||
if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) {
|
||||
$this->errors[] = 'Cannot write Real tags on old-style file format';
|
||||
fclose($fp_source);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($OldThisFileInfo['real']['chunks'])) {
|
||||
$this->errors[] = 'Cannot write Real tags because cannot find DATA chunk in file';
|
||||
fclose($fp_source);
|
||||
return false;
|
||||
}
|
||||
foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) {
|
||||
$oldChunkInfo[$chunkarray['name']] = $chunkarray;
|
||||
}
|
||||
if (!empty($oldChunkInfo['CONT']['length'])) {
|
||||
$this->paddedlength = max($oldChunkInfo['CONT']['length'], $this->paddedlength);
|
||||
}
|
||||
|
||||
$new_CONT_tag_data = $this->GenerateCONTchunk();
|
||||
$new_PROP_tag_data = $this->GeneratePROPchunk($OldThisFileInfo['real']['chunks'], $new_CONT_tag_data);
|
||||
$new__RMF_tag_data = $this->GenerateRMFchunk($OldThisFileInfo['real']['chunks']);
|
||||
|
||||
if (isset($oldChunkInfo['.RMF']['length']) && ($oldChunkInfo['.RMF']['length'] == strlen($new__RMF_tag_data))) {
|
||||
fseek($fp_source, $oldChunkInfo['.RMF']['offset'], SEEK_SET);
|
||||
fwrite($fp_source, $new__RMF_tag_data);
|
||||
} else {
|
||||
$this->errors[] = 'new .RMF tag ('.strlen($new__RMF_tag_data).' bytes) different length than old .RMF tag ('.$oldChunkInfo['.RMF']['length'].' bytes)';
|
||||
fclose($fp_source);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($oldChunkInfo['PROP']['length']) && ($oldChunkInfo['PROP']['length'] == strlen($new_PROP_tag_data))) {
|
||||
fseek($fp_source, $oldChunkInfo['PROP']['offset'], SEEK_SET);
|
||||
fwrite($fp_source, $new_PROP_tag_data);
|
||||
} else {
|
||||
$this->errors[] = 'new PROP tag ('.strlen($new_PROP_tag_data).' bytes) different length than old PROP tag ('.$oldChunkInfo['PROP']['length'].' bytes)';
|
||||
fclose($fp_source);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($oldChunkInfo['CONT']['length']) && ($oldChunkInfo['CONT']['length'] == strlen($new_CONT_tag_data))) {
|
||||
|
||||
// new data length is same as old data length - just overwrite
|
||||
fseek($fp_source, $oldChunkInfo['CONT']['offset'], SEEK_SET);
|
||||
fwrite($fp_source, $new_CONT_tag_data);
|
||||
fclose($fp_source);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
if (empty($oldChunkInfo['CONT'])) {
|
||||
// no existing CONT chunk
|
||||
$BeforeOffset = $oldChunkInfo['DATA']['offset'];
|
||||
$AfterOffset = $oldChunkInfo['DATA']['offset'];
|
||||
} else {
|
||||
// new data is longer than old data
|
||||
$BeforeOffset = $oldChunkInfo['CONT']['offset'];
|
||||
$AfterOffset = $oldChunkInfo['CONT']['offset'] + $oldChunkInfo['CONT']['length'];
|
||||
}
|
||||
if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) {
|
||||
if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) {
|
||||
|
||||
rewind($fp_source);
|
||||
fwrite($fp_temp, fread($fp_source, $BeforeOffset));
|
||||
fwrite($fp_temp, $new_CONT_tag_data);
|
||||
fseek($fp_source, $AfterOffset, SEEK_SET);
|
||||
while ($buffer = fread($fp_source, $this->fread_buffer_size)) {
|
||||
fwrite($fp_temp, $buffer, strlen($buffer));
|
||||
}
|
||||
fclose($fp_temp);
|
||||
|
||||
if (copy($tempfilename, $this->filename)) {
|
||||
unlink($tempfilename);
|
||||
fclose($fp_source);
|
||||
return true;
|
||||
}
|
||||
unlink($tempfilename);
|
||||
$this->errors[] = 'FAILED: copy('.$tempfilename.', '.$this->filename.')';
|
||||
|
||||
} else {
|
||||
$this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")';
|
||||
}
|
||||
}
|
||||
fclose($fp_source);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
$this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")';
|
||||
return false;
|
||||
}
|
||||
|
||||
public function GenerateRMFchunk(&$chunks) {
|
||||
$oldCONTexists = false;
|
||||
foreach ($chunks as $key => $chunk) {
|
||||
$chunkNameKeys[$chunk['name']] = $key;
|
||||
if ($chunk['name'] == 'CONT') {
|
||||
$oldCONTexists = true;
|
||||
}
|
||||
}
|
||||
$newHeadersCount = $chunks[$chunkNameKeys['.RMF']]['headers_count'] + ($oldCONTexists ? 0 : 1);
|
||||
|
||||
$RMFchunk = "\x00\x00"; // object version
|
||||
$RMFchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['.RMF']]['file_version'], 4);
|
||||
$RMFchunk .= getid3_lib::BigEndian2String($newHeadersCount, 4);
|
||||
|
||||
$RMFchunk = '.RMF'.getid3_lib::BigEndian2String(strlen($RMFchunk) + 8, 4).$RMFchunk; // .RMF chunk identifier + chunk length
|
||||
return $RMFchunk;
|
||||
}
|
||||
|
||||
public function GeneratePROPchunk(&$chunks, &$new_CONT_tag_data) {
|
||||
$old_CONT_length = 0;
|
||||
$old_DATA_offset = 0;
|
||||
$old_INDX_offset = 0;
|
||||
foreach ($chunks as $key => $chunk) {
|
||||
$chunkNameKeys[$chunk['name']] = $key;
|
||||
if ($chunk['name'] == 'CONT') {
|
||||
$old_CONT_length = $chunk['length'];
|
||||
} elseif ($chunk['name'] == 'DATA') {
|
||||
if (!$old_DATA_offset) {
|
||||
$old_DATA_offset = $chunk['offset'];
|
||||
}
|
||||
} elseif ($chunk['name'] == 'INDX') {
|
||||
if (!$old_INDX_offset) {
|
||||
$old_INDX_offset = $chunk['offset'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$CONTdelta = strlen($new_CONT_tag_data) - $old_CONT_length;
|
||||
|
||||
$PROPchunk = "\x00\x00"; // object version
|
||||
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_bit_rate'], 4);
|
||||
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_bit_rate'], 4);
|
||||
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_packet_size'], 4);
|
||||
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_packet_size'], 4);
|
||||
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_packets'], 4);
|
||||
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['duration'], 4);
|
||||
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['preroll'], 4);
|
||||
$PROPchunk .= getid3_lib::BigEndian2String(max(0, $old_INDX_offset + $CONTdelta), 4);
|
||||
$PROPchunk .= getid3_lib::BigEndian2String(max(0, $old_DATA_offset + $CONTdelta), 4);
|
||||
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_streams'], 2);
|
||||
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['flags_raw'], 2);
|
||||
|
||||
$PROPchunk = 'PROP'.getid3_lib::BigEndian2String(strlen($PROPchunk) + 8, 4).$PROPchunk; // PROP chunk identifier + chunk length
|
||||
return $PROPchunk;
|
||||
}
|
||||
|
||||
public function GenerateCONTchunk() {
|
||||
foreach ($this->tag_data as $key => $value) {
|
||||
// limit each value to 0xFFFF bytes
|
||||
$this->tag_data[$key] = substr($value, 0, 65535);
|
||||
}
|
||||
|
||||
$CONTchunk = "\x00\x00"; // object version
|
||||
|
||||
$CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['title']) ? strlen($this->tag_data['title']) : 0), 2);
|
||||
$CONTchunk .= (!empty($this->tag_data['title']) ? strlen($this->tag_data['title']) : '');
|
||||
|
||||
$CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['artist']) ? strlen($this->tag_data['artist']) : 0), 2);
|
||||
$CONTchunk .= (!empty($this->tag_data['artist']) ? strlen($this->tag_data['artist']) : '');
|
||||
|
||||
$CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['copyright']) ? strlen($this->tag_data['copyright']) : 0), 2);
|
||||
$CONTchunk .= (!empty($this->tag_data['copyright']) ? strlen($this->tag_data['copyright']) : '');
|
||||
|
||||
$CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['comment']) ? strlen($this->tag_data['comment']) : 0), 2);
|
||||
$CONTchunk .= (!empty($this->tag_data['comment']) ? strlen($this->tag_data['comment']) : '');
|
||||
|
||||
if ($this->paddedlength > (strlen($CONTchunk) + 8)) {
|
||||
$CONTchunk .= str_repeat("\x00", $this->paddedlength - strlen($CONTchunk) - 8);
|
||||
}
|
||||
|
||||
$CONTchunk = 'CONT'.getid3_lib::BigEndian2String(strlen($CONTchunk) + 8, 4).$CONTchunk; // CONT chunk identifier + chunk length
|
||||
|
||||
return $CONTchunk;
|
||||
}
|
||||
|
||||
public function RemoveReal() {
|
||||
// File MUST be writeable - CHMOD(646) at least
|
||||
if (is_writeable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) {
|
||||
|
||||
// Initialize getID3 engine
|
||||
$getID3 = new getID3;
|
||||
$OldThisFileInfo = $getID3->analyze($this->filename);
|
||||
if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) {
|
||||
$this->errors[] = 'Cannot remove Real tags from old-style file format';
|
||||
fclose($fp_source);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($OldThisFileInfo['real']['chunks'])) {
|
||||
$this->errors[] = 'Cannot remove Real tags because cannot find DATA chunk in file';
|
||||
fclose($fp_source);
|
||||
return false;
|
||||
}
|
||||
foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) {
|
||||
$oldChunkInfo[$chunkarray['name']] = $chunkarray;
|
||||
}
|
||||
|
||||
if (empty($oldChunkInfo['CONT'])) {
|
||||
// no existing CONT chunk
|
||||
fclose($fp_source);
|
||||
return true;
|
||||
}
|
||||
|
||||
$BeforeOffset = $oldChunkInfo['CONT']['offset'];
|
||||
$AfterOffset = $oldChunkInfo['CONT']['offset'] + $oldChunkInfo['CONT']['length'];
|
||||
if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) {
|
||||
if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) {
|
||||
|
||||
rewind($fp_source);
|
||||
fwrite($fp_temp, fread($fp_source, $BeforeOffset));
|
||||
fseek($fp_source, $AfterOffset, SEEK_SET);
|
||||
while ($buffer = fread($fp_source, $this->fread_buffer_size)) {
|
||||
fwrite($fp_temp, $buffer, strlen($buffer));
|
||||
}
|
||||
fclose($fp_temp);
|
||||
|
||||
if (copy($tempfilename, $this->filename)) {
|
||||
unlink($tempfilename);
|
||||
fclose($fp_source);
|
||||
return true;
|
||||
}
|
||||
unlink($tempfilename);
|
||||
$this->errors[] = 'FAILED: copy('.$tempfilename.', '.$this->filename.')';
|
||||
|
||||
} else {
|
||||
$this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")';
|
||||
}
|
||||
}
|
||||
fclose($fp_source);
|
||||
return false;
|
||||
}
|
||||
$this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")';
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
119
api/getID3/write.vorbiscomment.php
Executable file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// write.vorbiscomment.php //
|
||||
// module for writing VorbisComment tags //
|
||||
// dependencies: /helperapps/vorbiscomment.exe //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_write_vorbiscomment
|
||||
{
|
||||
|
||||
public $filename;
|
||||
public $tag_data;
|
||||
public $warnings = array(); // any non-critical errors will be stored here
|
||||
public $errors = array(); // any critical errors will be stored here
|
||||
|
||||
public function getid3_write_vorbiscomment() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function WriteVorbisComment() {
|
||||
|
||||
if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
|
||||
$this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call vorbiscomment, tags not written';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create file with new comments
|
||||
$tempcommentsfilename = tempnam(GETID3_TEMP_DIR, 'getID3');
|
||||
if (is_writable($tempcommentsfilename) && is_file($tempcommentsfilename) && ($fpcomments = fopen($tempcommentsfilename, 'wb'))) {
|
||||
|
||||
foreach ($this->tag_data as $key => $value) {
|
||||
foreach ($value as $commentdata) {
|
||||
fwrite($fpcomments, $this->CleanVorbisCommentName($key).'='.$commentdata."\n");
|
||||
}
|
||||
}
|
||||
fclose($fpcomments);
|
||||
|
||||
} else {
|
||||
$this->errors[] = 'failed to open temporary tags file "'.$tempcommentsfilename.'", tags not written';
|
||||
return false;
|
||||
}
|
||||
|
||||
$oldignoreuserabort = ignore_user_abort(true);
|
||||
if (GETID3_OS_ISWINDOWS) {
|
||||
|
||||
if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {
|
||||
//$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w --raw -c "'.$tempcommentsfilename.'" "'.str_replace('/', '\\', $this->filename).'"';
|
||||
// vorbiscomment works fine if you copy-paste the above commandline into a command prompt,
|
||||
// but refuses to work with `backtick` if there are "doublequotes" present around BOTH
|
||||
// the metaflac pathname and the target filename. For whatever reason...??
|
||||
// The solution is simply ensure that the metaflac pathname has no spaces,
|
||||
// and therefore does not need to be quoted
|
||||
|
||||
// On top of that, if error messages are not always captured properly under Windows
|
||||
// To at least see if there was a problem, compare file modification timestamps before and after writing
|
||||
clearstatcache();
|
||||
$timestampbeforewriting = filemtime($this->filename);
|
||||
|
||||
$commandline = GETID3_HELPERAPPSDIR.'vorbiscomment.exe -w --raw -c "'.$tempcommentsfilename.'" "'.$this->filename.'" 2>&1';
|
||||
$VorbiscommentError = `$commandline`;
|
||||
|
||||
if (empty($VorbiscommentError)) {
|
||||
clearstatcache();
|
||||
if ($timestampbeforewriting == filemtime($this->filename)) {
|
||||
$VorbiscommentError = 'File modification timestamp has not changed - it looks like the tags were not written';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$VorbiscommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$commandline = 'vorbiscomment -w --raw -c "'.$tempcommentsfilename.'" "'.$this->filename.'" 2>&1';
|
||||
$VorbiscommentError = `$commandline`;
|
||||
|
||||
}
|
||||
|
||||
// Remove temporary comments file
|
||||
unlink($tempcommentsfilename);
|
||||
ignore_user_abort($oldignoreuserabort);
|
||||
|
||||
if (!empty($VorbiscommentError)) {
|
||||
|
||||
$this->errors[] = 'system call to vorbiscomment failed with message: '."\n\n".$VorbiscommentError;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function DeleteVorbisComment() {
|
||||
$this->tag_data = array(array());
|
||||
return $this->WriteVorbisComment();
|
||||
}
|
||||
|
||||
public function CleanVorbisCommentName($originalcommentname) {
|
||||
// A case-insensitive field name that may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded.
|
||||
// ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through
|
||||
// 0x7A inclusive (a-z).
|
||||
|
||||
// replace invalid chars with a space, return uppercase text
|
||||
// Thanks Chris Bolt <chris-getid3Øbolt*cx> for improving this function
|
||||
// note: *reg_replace() replaces nulls with empty string (not space)
|
||||
return strtoupper(preg_replace('#[^ -<>-}]#', ' ', str_replace("\x00", ' ', $originalcommentname)));
|
||||
|
||||
}
|
||||
|
||||
}
|
134
api/soundmap.api.php
Executable file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Soundmap_Helper')){
|
||||
|
||||
class Soundmap_Helper{
|
||||
|
||||
function get_all_markers(){
|
||||
//coge todos los marcadores.
|
||||
$query = new WP_Query(array(
|
||||
'post_type' => 'marker',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => -1
|
||||
)
|
||||
);
|
||||
|
||||
if ( !$query->have_posts() )
|
||||
return false;
|
||||
|
||||
foreach($query->posts as &$post){
|
||||
$post_id = $post->ID;
|
||||
$custom = get_post_custom($post_id);
|
||||
$post->marker = array();
|
||||
if(isset($custom['soundmap_marker_lat']))
|
||||
$post->marker['lat'] = $custom['soundmap_marker_lat'];
|
||||
|
||||
if(isset($custom['soundmap_marker_lng']))
|
||||
$post->marker['lng'] = $custom['soundmap_marker_lng'];
|
||||
|
||||
if(isset($custom['soundmap_marker_author']))
|
||||
$post->marker['author'] = $custom['soundmap_marker_author'];
|
||||
|
||||
if(isset($custom['soundmap_marker_date']))
|
||||
$post->marker['date'] = $custom['soundmap_marker_date'];
|
||||
|
||||
if(isset($custom['soundmap_attachments_id']))
|
||||
$post->marker['attachments'] = $custom['soundmap_attachments_id'];
|
||||
}
|
||||
|
||||
return $query->posts;
|
||||
|
||||
}
|
||||
|
||||
function get_marker($id){
|
||||
|
||||
//coge todos los marcadores.
|
||||
$q = get_post($id);
|
||||
global $soundmap;
|
||||
|
||||
if ( !$q )
|
||||
return false;
|
||||
|
||||
$post_id = $q->ID;
|
||||
$custom = get_post_custom($post_id);
|
||||
$q->marker = array();
|
||||
$q->marker['lat'] = $custom['soundmap_marker_lat'];
|
||||
$q->marker['lng'] = $custom['soundmap_marker_lng'];
|
||||
$q->marker['author'] = $custom['soundmap_marker_author'];
|
||||
// $q->marker['date'] = $custom['soundmap_marker_date'];
|
||||
$files = $custom['soundmap_attachments_id'];
|
||||
|
||||
$info = array();
|
||||
foreach ($files as $key => $value){
|
||||
$file = array();
|
||||
$att = get_post($value);
|
||||
$file['id'] = $value;
|
||||
$file['fileURI'] = wp_get_attachment_url($value);
|
||||
$file['filePath'] = get_attached_file($value);
|
||||
// $file['info'] = $soundmap->getID3($file['filePath']);
|
||||
$file['name'] = $att->post_name;
|
||||
$file['file_name'] = basename($file['filePath']);
|
||||
$info[] = $file;
|
||||
}
|
||||
$q->marker['attachments'] = $info;
|
||||
return $q;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//Soundmap_Helper
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (!class_exists('Soundmap_AJAX')){
|
||||
|
||||
class Soundmap_AJAX{
|
||||
|
||||
|
||||
function __construct(){
|
||||
//Register hooks and filters
|
||||
$this->register_hooks();
|
||||
}
|
||||
|
||||
function register_hooks(){
|
||||
add_action('wp_ajax_get-markers-collection', array($this, 'get_markers_collection'));
|
||||
}
|
||||
|
||||
function get_markers_collection(){
|
||||
|
||||
// $query = new WP_Query(array('posts_per_page' => -1,'cat' => 4, 'post_type' => 'marker', 'post_status' => 'publish'));
|
||||
$query = new WP_Query(array('posts_per_page' => -1,'cat' => 5, 'post_type' => 'marker', 'post_status' => 'publish'));
|
||||
if ( !$query->have_posts() )
|
||||
die();
|
||||
$posts = $query->posts;
|
||||
$markers_collection = array();
|
||||
|
||||
foreach($posts as $post){
|
||||
$post_id = $post->ID;
|
||||
$m_lat = get_post_meta($post_id,'soundmap_marker_lat', TRUE);
|
||||
$m_lng = get_post_meta($post_id,'soundmap_marker_lng', TRUE);
|
||||
$title = get_the_title ($post_id);
|
||||
$autor = get_post_meta($post_id, 'soundmap_marker_author', TRUE);
|
||||
$files = get_post_meta($post_id, 'soundmap_attachments_id', FALSE);
|
||||
$feature = new stdClass();
|
||||
$feature->id = $post_id;
|
||||
$feature->lat = $m_lat;
|
||||
$feature->lng = $m_lng;
|
||||
$feature->title = $title;
|
||||
$feature->autor = $autor;
|
||||
$feature->files = $files[0];
|
||||
$markers_collection[] = $feature;
|
||||
}
|
||||
wp_send_json_success($markers_collection);
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}//Soundmap_AJAX
|
||||
|
||||
}
|
||||
|
||||
global $soundmap_AJAX;
|
||||
$soundmap_AJAX = new Soundmap_AJAX();
|
20
api/soundmap.player.php
Executable file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Soundmap_BasePlayer'))
|
||||
{
|
||||
class Soundmap_BasePlayer{
|
||||
|
||||
function __construct(){
|
||||
$this->register_hooks();
|
||||
}//__construct
|
||||
|
||||
function register_hooks(){
|
||||
add_action('wp_enqueue_scripts', array($this, 'wp_enqueue_scripts'));
|
||||
}//register_hooks
|
||||
|
||||
function wp_enqueue_scripts(){
|
||||
wp_enqueue_script('mediaelement');
|
||||
}//wp_enqueue_scripts
|
||||
|
||||
}// class
|
||||
}// if
|
173
api/soundmap.tags.php
Executable file
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
|
||||
function insert_map($query = undefined){
|
||||
if(is_array($query)){
|
||||
// Tenemos una consulta propia, por lo que vamos a cargar los marcadores.
|
||||
|
||||
$options = array(
|
||||
'post_type' => 'marker',
|
||||
'post_status' => 'publish',
|
||||
);
|
||||
|
||||
$options = array_merge($options, $query);
|
||||
$_q = new WP_Query($options);
|
||||
var_dump($_q->found_posts);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function get_the_latitude($id){
|
||||
return get_post_meta($id,'soundmap_marker_lat', TRUE);
|
||||
}
|
||||
|
||||
function get_the_longitude($id){
|
||||
return get_post_meta($id,'soundmap_marker_lng', TRUE);
|
||||
}
|
||||
|
||||
function the_map($css_id = 'map_canvas', $all_markers = FALSE, $options = array()){
|
||||
|
||||
global $soundmap;
|
||||
|
||||
?>
|
||||
|
||||
<div class="<?php echo $css_id ?> soundmap-canvas">
|
||||
<div style="height:100%;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="hidden-markers-content" style ="display:hidden;"></div>
|
||||
<?php
|
||||
if ($all_markers){
|
||||
//cargo todos los marcadores
|
||||
$soundmap->set_query('all');
|
||||
|
||||
}else{
|
||||
|
||||
global $posts; //Array with the posts to show
|
||||
if (is_array($posts)){
|
||||
$list = array();
|
||||
foreach ($posts as $post){
|
||||
array_push($list,$post->ID);
|
||||
}
|
||||
}
|
||||
if (!is_array($list))
|
||||
$soundmap->set_query($list);
|
||||
}
|
||||
}
|
||||
|
||||
function the_marker_info($pre = '<ul><li class="post-info">',$sep = '</li><li>',$after = '</li></ul>'){
|
||||
global $post;
|
||||
$marker_id = $post->ID;
|
||||
|
||||
if ($post->post_type != "marker")
|
||||
return;
|
||||
|
||||
$author = get_post_meta($marker_id, 'soundmap_marker_author', TRUE);
|
||||
$date = get_post_meta($marker_id, 'soundmap_marker_date', TRUE);
|
||||
echo $pre;
|
||||
echo $author;
|
||||
echo $sep;
|
||||
echo $date;
|
||||
echo $after;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function get_marker_author($id){
|
||||
|
||||
return get_post_meta($id, 'soundmap_marker_author', TRUE);
|
||||
|
||||
}
|
||||
|
||||
|
||||
function get_marker_date($id){
|
||||
|
||||
return get_post_meta($id, 'soundmap_marker_date', TRUE);
|
||||
|
||||
}
|
||||
|
||||
function the_player($marker_id){
|
||||
|
||||
$data = array();
|
||||
$files = get_post_meta($marker_id, 'soundmap_attachments_id', FALSE);
|
||||
foreach ($files as $key => $value){
|
||||
$file = array();
|
||||
$att = get_post($value);
|
||||
$file['id'] = $value;
|
||||
$file['fileURI'] = wp_get_attachment_url($value);
|
||||
$file['filePath'] = get_attached_file($value);
|
||||
$file['info'] = soundmap_get_id3info($file['filePath']);
|
||||
$file['name'] = $att->post_name;
|
||||
$data['m_files'][] = $file;
|
||||
}
|
||||
add_player_interface($data['m_files'], $marker_id);
|
||||
}
|
||||
|
||||
|
||||
function insert_upload_form($text){
|
||||
|
||||
if (function_exists("qtrans_init")){
|
||||
//We are using multilanguage
|
||||
global $q_config;
|
||||
$lang=$q_config['language'];
|
||||
$dir = WP_PLUGIN_URL . "/soundmap/modules/module.soundmap.upload.php?lang=$lang&TB_iframe=true&width=960&height=550";
|
||||
}else{
|
||||
$dir = WP_PLUGIN_URL . "/soundmap/modules/module.soundmap.upload.php?TB_iframe=true&width=960&height=550";
|
||||
}
|
||||
$t="";
|
||||
$title=__("Add new recording","soundmap");
|
||||
$t .="<a class=\"thickbox\" title=\"$title\" href=\"$dir\">$text</a>";
|
||||
echo $t;
|
||||
}
|
||||
|
||||
function soundmap_rss_enclosure(){
|
||||
|
||||
$id = get_the_ID();
|
||||
|
||||
$files = get_post_meta($id, 'soundmap_attachments_id', FALSE);
|
||||
|
||||
foreach ( (array) $files as $key => $value) {
|
||||
|
||||
$fileURI= wp_get_attachment_url($value);
|
||||
$info= soundmap_get_id3info($file['filePath']);
|
||||
echo apply_filters('rss_enclosure', '<enclosure url="' . trim(htmlspecialchars($fileURI)) . '" length="' . trim($info['playtime_string']) . '" type="audio/mpeg" />' . "\n");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function add_player_interface($files, $id){
|
||||
|
||||
global $soundmap;
|
||||
$out = '';
|
||||
if(!is_array($files) || (count($files)==0))
|
||||
return;
|
||||
|
||||
if(count($files) == 1){
|
||||
$fileInfo = $soundmap->getID3($files[0]['filePath']);
|
||||
$script = '';
|
||||
if (is_array($fileInfo)){
|
||||
switch ($fileInfo['format']){
|
||||
case 'ogg':
|
||||
case 'mp3':
|
||||
$script = "<script type='text/javascript'>soundMapPlayer.create_player('audio', '{$fileInfo['format']}', '{$files[0]['fileURI']}', 'soundmap-player-{$id}', '{$id}');</script>";
|
||||
break;
|
||||
}
|
||||
$out = "<div id='soundmap-player-{$id}'></div>{$script}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
|
||||
}
|
||||
|
||||
return $out;
|
||||
// global $soundmap_Player;
|
||||
|
||||
// $insert_content = $soundmap_Player->print_audio_content($files, $id);
|
||||
//echo $insert_content;
|
||||
|
||||
}
|
94
api/soundmap.uploader.php
Executable file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
function soundmap_verify_captcha(){
|
||||
|
||||
session_start();
|
||||
|
||||
$aResponse['error'] = false;
|
||||
$_SESSION['iQaptcha'] = false;
|
||||
|
||||
if(isset($_POST['data']))
|
||||
{
|
||||
if(htmlentities($_POST['data'], ENT_QUOTES, 'UTF-8') == 'qaptcha')
|
||||
{
|
||||
$_SESSION['iQaptcha'] = true;
|
||||
if($_SESSION['iQaptcha']==true){
|
||||
echo json_encode($aResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResponse['error'] = true;
|
||||
echo json_encode($aResponse);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResponse['error'] = true;
|
||||
echo json_encode($aResponse);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResponse['error'] = true;
|
||||
echo json_encode($aResponse);
|
||||
}
|
||||
die();
|
||||
}
|
||||
|
||||
function soundmap_save_public_upload(){
|
||||
$info = $_REQUEST['info'];
|
||||
$uploader = $_REQUEST['uploader'];
|
||||
|
||||
$content = _soundmap_mountPostContent($info);
|
||||
$title = _soundmap_mountPostTitle($info);
|
||||
|
||||
$soundmark_lat = $info['posLat'];
|
||||
$soundmark_lng = $info['posLong'];
|
||||
$soundmark_author = $info['author'];
|
||||
$soundmark_date = $info['date'];
|
||||
$soundmark_mail = $uploader['email'];
|
||||
|
||||
if ($title == "" && $soundmark_author != ""){
|
||||
$title = $soundmark_author;
|
||||
}elseif ($title =="" && $soundmark_author ==""){
|
||||
$title = $info['fileName'];
|
||||
}
|
||||
|
||||
if (!is_array($info['categoria'])){
|
||||
$val = $info['categoria'];
|
||||
$info['categoria'] = array($val);
|
||||
}
|
||||
|
||||
$post_d = array(
|
||||
'post_category' => $info['categoria'],
|
||||
'post_content' => $content,
|
||||
'post_status' => 'publish',
|
||||
'post_title' => $title,
|
||||
'post_type' => 'marker',
|
||||
'comment_status' => 'closed'
|
||||
//'tags_input' => [ '<tag>, <tag>, <...>' ] //For tags.
|
||||
);
|
||||
$post_id=wp_insert_post($post_d);
|
||||
|
||||
|
||||
|
||||
|
||||
update_post_meta($post_id, 'soundmap_marker_lat', $soundmark_lat);
|
||||
update_post_meta($post_id, 'soundmap_marker_lng', $soundmark_lng);
|
||||
update_post_meta($post_id, 'soundmap_marker_author', $soundmark_author);
|
||||
update_post_meta($post_id, 'soundmap_marker_date', $soundmark_date);
|
||||
update_post_meta($post_id, 'EMAIL', $soundmark_mail);
|
||||
|
||||
|
||||
$files = $info['attachID'];
|
||||
delete_post_meta($post_id, 'soundmap_attachments_id');
|
||||
update_post_meta($post_id, 'soundmap_attachments_id', $files);
|
||||
soundmap_attach_file($files, $post_id);
|
||||
|
||||
echo "ok";
|
||||
die();
|
||||
|
||||
}
|
||||
?>
|
189
css/OLD/jquery.datepicker.css
Executable file
@ -0,0 +1,189 @@
|
||||
div.datepicker {
|
||||
position: relative;
|
||||
width: 196px;
|
||||
height: 147px;
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: none;
|
||||
}
|
||||
.datepickerContainer {
|
||||
background: white;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
.datepickerBorderT {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
height: 10px;
|
||||
background: url(../img/datepicker_t.png);
|
||||
}
|
||||
.datepickerBorderB {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
bottom: 0;
|
||||
right: 10px;
|
||||
height: 10px;
|
||||
background: url(../img/datepicker_b.png);
|
||||
}
|
||||
.datepickerBorderL {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 10px;
|
||||
top: 10px;
|
||||
width: 10px;
|
||||
background: url(../img/datepicker_l.png);
|
||||
}
|
||||
.datepickerBorderR {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 10px;
|
||||
top: 10px;
|
||||
width: 10px;
|
||||
background: url(../img/datepicker_r.png);
|
||||
}
|
||||
.datepickerBorderTL {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: url(../img/datepicker_tl.png);
|
||||
}
|
||||
.datepickerBorderTR {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: url(../img/datepicker_tr.png);
|
||||
}
|
||||
.datepickerBorderBL {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: url(../img/datepicker_bl.png);
|
||||
}
|
||||
.datepickerBorderBR {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: url(../img/datepicker_br.png);
|
||||
}
|
||||
.datepickerHidden {
|
||||
display: none;
|
||||
}
|
||||
div.datepicker table {
|
||||
border-collapse:collapse;
|
||||
}
|
||||
div.datepicker a {
|
||||
color: #222;
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
outline: none;
|
||||
}
|
||||
div.datepicker table td {
|
||||
text-align: right;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
div.datepicker th {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
}
|
||||
div.datepicker tbody th {
|
||||
text-align: left;
|
||||
}
|
||||
div.datepicker tbody a {
|
||||
display: block;
|
||||
}
|
||||
.datepickerDays a {
|
||||
width: 20px;
|
||||
line-height: 16px;
|
||||
height: 16px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
.datepickerYears a,
|
||||
.datepickerMonths a{
|
||||
width: 44px;
|
||||
line-height: 36px;
|
||||
height: 36px;
|
||||
text-align: center;
|
||||
}
|
||||
td.datepickerNotInMonth a {
|
||||
color: #eee;
|
||||
}
|
||||
tbody.datepickerDays td.datepickerSelected{
|
||||
background: #ccc;
|
||||
}
|
||||
tbody.datepickerDays td.datepickerNotInMonth.datepickerSelected {
|
||||
background: #17384d;
|
||||
}
|
||||
tbody.datepickerYears td.datepickerSelected,
|
||||
tbody.datepickerMonths td.datepickerSelected{
|
||||
background: #17384d;
|
||||
}
|
||||
div.datepicker a:hover,
|
||||
div.datepicker a:hover {
|
||||
color: red;
|
||||
}
|
||||
div.datepicker td.datepickerNotInMonth a:hover {
|
||||
color: #999;
|
||||
}
|
||||
div.datepicker tbody th {
|
||||
text-align: left;
|
||||
}
|
||||
.datepickerSpace div {
|
||||
width: 20px;
|
||||
}
|
||||
.datepickerGoNext a,
|
||||
.datepickerGoPrev a,
|
||||
.datepickerMonth a {
|
||||
text-align: center;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.datepickerGoNext a {
|
||||
float: right;
|
||||
width: 20px;
|
||||
}
|
||||
.datepickerGoPrev a {
|
||||
float: left;
|
||||
width: 20px;
|
||||
}
|
||||
table.datepickerViewDays tbody.datepickerMonths,
|
||||
table.datepickerViewDays tbody.datepickerYears {
|
||||
display: none;
|
||||
}
|
||||
table.datepickerViewMonths tbody.datepickerDays,
|
||||
table.datepickerViewMonths tbody.datepickerYears,
|
||||
table.datepickerViewMonths tr.datepickerDoW {
|
||||
display: none;
|
||||
}
|
||||
table.datepickerViewYears tbody.datepickerDays,
|
||||
table.datepickerViewYears tbody.datepickerMonths,
|
||||
table.datepickerViewYears tr.datepickerDoW {
|
||||
display: none;
|
||||
}
|
||||
td.datepickerDisabled a,
|
||||
td.datepickerDisabled.datepickerNotInMonth a{
|
||||
color: #333;
|
||||
}
|
||||
td.datepickerDisabled a:hover {
|
||||
color: #333;
|
||||
}
|
||||
td.datepickerSpecial a {
|
||||
background: #700;
|
||||
}
|
||||
td.datepickerSpecial.datepickerSelected a {
|
||||
background: #a00;
|
||||
}
|
18
css/OLD/soundmap-admin OLD.css
Executable file
@ -0,0 +1,18 @@
|
||||
|
||||
#map_canvas, #map_canvas_options{width:100%; height:400px;}
|
||||
#sound-att-table {width:100%; table-layout: fixed; word-break: break-word; border-collapse: collapse;}
|
||||
#sound-att-table th{text-align:left;}
|
||||
#sound-att-table .soundmap-att-left{width:60%; padding-right:6px}
|
||||
#sound-att-table tr {border-bottom:1px solid #666;}
|
||||
#sound-att-table td {padding: 3px 0;}
|
||||
|
||||
.uploadFileQueued{
|
||||
height:36px;
|
||||
margin:10px 0;
|
||||
}
|
||||
|
||||
.filename{
|
||||
position:relative;
|
||||
margin-top:10px;
|
||||
font-weight: bold;
|
||||
}
|
135
css/OLD/soundmap-admin.css
Executable file
@ -0,0 +1,135 @@
|
||||
|
||||
#map_canvas, #map_canvas_options{width:100%; height:400px;}
|
||||
#sound-att-table {width:100%; table-layout: fixed; word-break: break-word; border-collapse: collapse;}
|
||||
#sound-att-table th{text-align:left;}
|
||||
#sound-att-table .soundmap-att-left{width:60%; padding-right:6px}
|
||||
#sound-att-table tr {border-bottom:1px solid #666;}
|
||||
#sound-att-table td {padding: 3px 0;}
|
||||
|
||||
#sound-att-table .soundmap-att-left span{
|
||||
float: left;
|
||||
}
|
||||
|
||||
.uploadFileQueued{
|
||||
height:36px;
|
||||
margin:10px 0;
|
||||
}
|
||||
|
||||
.filename{
|
||||
position:relative;
|
||||
margin-top:10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#uploader_drop{
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#uploader{
|
||||
}
|
||||
|
||||
#uploader_drop.hover{
|
||||
background-color: #94cde7;
|
||||
}
|
||||
|
||||
#uploader_over.hover{
|
||||
display: block;
|
||||
}
|
||||
|
||||
#uploader_buttons{
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
|
||||
}
|
||||
#uploader_buttons h4{
|
||||
border: 0;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
font-size: 18px;
|
||||
margin-bottom: 5px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.fileItem{
|
||||
height: 40px;
|
||||
}
|
||||
.fileItem .fileName{
|
||||
margin-left: 10px;
|
||||
}
|
||||
.meter {
|
||||
height: 8px; /* Can be anything */
|
||||
position: relative;
|
||||
background: #555;
|
||||
-moz-border-radius: 25px;
|
||||
-webkit-border-radius: 25px;
|
||||
border-radius: 25px;
|
||||
padding: 0px;
|
||||
-webkit-box-shadow: inset 0 -1px 1px rgba(255,255,255,0.3);
|
||||
-moz-box-shadow : inset 0 -1px 1px rgba(255,255,255,0.3);
|
||||
box-shadow : inset 0 -1px 1px rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
.meter > span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
-webkit-border-top-right-radius: 8px;
|
||||
-webkit-border-bottom-right-radius: 8px;
|
||||
-moz-border-radius-topright: 8px;
|
||||
-moz-border-radius-bottomright: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
-webkit-border-top-left-radius: 20px;
|
||||
-webkit-border-bottom-left-radius: 20px;
|
||||
-moz-border-radius-topleft: 20px;
|
||||
-moz-border-radius-bottomleft: 20px;
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
background-color: rgb(43,194,83);
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0, rgb(43,194,83)),
|
||||
color-stop(1, rgb(84,240,84))
|
||||
);
|
||||
background-image: -webkit-linear-gradient(
|
||||
center bottom,
|
||||
rgb(43,194,83) 37%,
|
||||
rgb(84,240,84) 69%
|
||||
);
|
||||
background-image: -moz-linear-gradient(
|
||||
center bottom,
|
||||
rgb(43,194,83) 37%,
|
||||
rgb(84,240,84) 69%
|
||||
);
|
||||
background-image: -ms-linear-gradient(
|
||||
center bottom,
|
||||
rgb(43,194,83) 37%,
|
||||
rgb(84,240,84) 69%
|
||||
);
|
||||
background-image: -o-linear-gradient(
|
||||
center bottom,
|
||||
rgb(43,194,83) 37%,
|
||||
rgb(84,240,84) 69%
|
||||
);
|
||||
-webkit-box-shadow:
|
||||
inset 0 2px 9px rgba(255,255,255,0.3),
|
||||
inset 0 -2px 6px rgba(0,0,0,0.4);
|
||||
-moz-box-shadow:
|
||||
inset 0 2px 9px rgba(255,255,255,0.3),
|
||||
inset 0 -2px 6px rgba(0,0,0,0.4);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.file-delete{
|
||||
display: block;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
background-image: url(../img/delete.png);
|
||||
float: right;
|
||||
margin-top: 3px;
|
||||
}
|
31
css/soundmap.add.css
Executable file
@ -0,0 +1,31 @@
|
||||
#map_canvas {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
#soundmap-attachments-list {
|
||||
margin-top: 10px;
|
||||
}
|
||||
#soundmap-attachments-list .att-info {
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
width: 80%;
|
||||
overflow-x: none;
|
||||
}
|
||||
#soundmap-attachments-list .att-info .att-length {
|
||||
font-style: italic;
|
||||
}
|
||||
#soundmap-attachments-list .att-info .delete-att-item {
|
||||
color: red;
|
||||
font-size: 10px;
|
||||
}
|
||||
#soundmap-attachments-list .att-icon {
|
||||
float: left;
|
||||
margin-right: 1%;
|
||||
width: 19%px;
|
||||
}
|
||||
#soundmap-attachments-list .att-icon img {
|
||||
width: 100%;
|
||||
}
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
33
css/soundmap.add.less
Executable file
@ -0,0 +1,33 @@
|
||||
#map_canvas{
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#soundmap-attachments-list{
|
||||
margin-top: 10px;
|
||||
.att-info{
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
width: 80%;
|
||||
overflow-x: none;
|
||||
.att-length{
|
||||
font-style: italic;
|
||||
}
|
||||
.delete-att-item{
|
||||
color: red;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
.att-icon{
|
||||
float: left;
|
||||
margin-right: 1%;
|
||||
width: 19%px;
|
||||
img{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clear{
|
||||
clear: both;
|
||||
}
|
4
css/soundmap.config.css
Executable file
@ -0,0 +1,4 @@
|
||||
#map_canvas_options {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
}
|
4
css/soundmap.config.less
Executable file
@ -0,0 +1,4 @@
|
||||
#map_canvas_options{
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
}
|
0
css/soundmap.front.css
Executable file
0
css/soundmap.front.less
Executable file
BIN
img/datepicker_b.png
Executable file
After Width: | Height: | Size: 110 B |
BIN
img/datepicker_bl.png
Executable file
After Width: | Height: | Size: 177 B |
BIN
img/datepicker_br.png
Executable file
After Width: | Height: | Size: 157 B |
BIN
img/datepicker_l.png
Executable file
After Width: | Height: | Size: 116 B |
BIN
img/datepicker_r.png
Executable file
After Width: | Height: | Size: 110 B |
BIN
img/datepicker_t.png
Executable file
After Width: | Height: | Size: 113 B |
BIN
img/datepicker_tl.png
Executable file
After Width: | Height: | Size: 169 B |
BIN
img/datepicker_tr.png
Executable file
After Width: | Height: | Size: 179 B |
BIN
img/delete.gif
Executable file
After Width: | Height: | Size: 180 B |
BIN
img/delete.png
Executable file
After Width: | Height: | Size: 194 B |
BIN
img/done.gif
Executable file
After Width: | Height: | Size: 1.0 KiB |
BIN
img/error.gif
Executable file
After Width: | Height: | Size: 994 B |
BIN
img/field.png
Executable file
After Width: | Height: | Size: 767 B |
BIN
img/map_icons/marker.png
Executable file
After Width: | Height: | Size: 1.5 KiB |
BIN
img/map_icons/marker_shadow.png
Executable file
After Width: | Height: | Size: 2.1 KiB |